642 lines
21 KiB
C#
642 lines
21 KiB
C#
using UnityEngine;
|
|
using RaycastPro.RaySensors;
|
|
using System.Collections.Generic;
|
|
|
|
public class CameraController : MonoBehaviour
|
|
{
|
|
[Header("Movement Settings")]
|
|
public float moveSpeed = 10f;
|
|
public float edgeScrollSpeed = 15f;
|
|
public float edgeScrollBoundary = 50f;
|
|
|
|
[Header("Zoom Settings")]
|
|
public float zoomSpeed = 5f;
|
|
public float minZoom = 5f;
|
|
public float maxZoom = 20f;
|
|
|
|
[Header("Rotation Settings")]
|
|
public float rotationSpeed = 100f;
|
|
public bool enableRotation = true;
|
|
public float orbitDistance = 15f; // Distance from the orbit center
|
|
public Transform orbitCenter; // Optional: specific point to orbit around
|
|
|
|
[Header("Boundaries")]
|
|
public Vector2 minBounds = new Vector2(-50f, -50f);
|
|
public Vector2 maxBounds = new Vector2(50f, 50f);
|
|
|
|
[Header("Smoothing")]
|
|
public float movementSmoothing = 5f;
|
|
public float zoomSmoothing = 5f;
|
|
|
|
[Header("RTS Controls")]
|
|
[SerializeField] private RaySensor mouseRaySensor;
|
|
[SerializeField] private LayerMask unitLayer = 1 << 8; // Units layer
|
|
[SerializeField] private LayerMask groundLayer = 1 << 9; // Ground layer
|
|
[SerializeField] private LayerMask interactableLayer = 1 << 10; // Interactable layer (resources, buildings, etc.)
|
|
|
|
[Header("Selection")]
|
|
public Material selectionMaterial;
|
|
public GameObject selectionIndicatorPrefab;
|
|
|
|
private Camera cam;
|
|
private Vector3 targetPosition;
|
|
private float targetZoom;
|
|
private Vector3 lastMousePosition;
|
|
private bool isDragging = false;
|
|
|
|
// RTS Selection variables
|
|
private List<GameObject> selectedUnits = new List<GameObject>();
|
|
private Dictionary<GameObject, GameObject> selectionIndicators = new Dictionary<GameObject, GameObject>();
|
|
private Vector3 selectionStartPosition;
|
|
|
|
// RaycastPro variables
|
|
private RaycastHit lastRaycastHit;
|
|
private bool hasValidRaycastHit = false;
|
|
|
|
// Orbit rotation variables
|
|
private Vector3 currentOrbitCenter;
|
|
|
|
// Events for RTS functionality
|
|
public System.Action<List<GameObject>> OnUnitsSelected;
|
|
public System.Action<Vector3> OnMoveCommand;
|
|
public System.Action<GameObject> OnUnitClicked;
|
|
public System.Action<GameObject> OnBuildingClicked;
|
|
|
|
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
|
void Start()
|
|
{
|
|
cam = GetComponent<Camera>();
|
|
if (cam == null)
|
|
cam = Camera.main;
|
|
|
|
targetPosition = transform.position;
|
|
targetZoom = cam.orthographicSize;
|
|
|
|
// Initialize orbit center
|
|
if (orbitCenter != null)
|
|
{
|
|
currentOrbitCenter = orbitCenter.position;
|
|
}
|
|
else
|
|
{
|
|
// Default orbit center is in front of the camera
|
|
currentOrbitCenter = transform.position + transform.forward * orbitDistance;
|
|
}
|
|
|
|
// Setup RaycastPro if available
|
|
SetupRaycastPro();
|
|
}
|
|
|
|
void SetupRaycastPro()
|
|
{
|
|
// Find or create RaySensor for mouse raycasting
|
|
if (mouseRaySensor == null)
|
|
{
|
|
mouseRaySensor = GetComponent<RaySensor>();
|
|
}
|
|
|
|
if (mouseRaySensor != null)
|
|
{
|
|
// Setup RaycastPro events
|
|
mouseRaySensor.onBeginDetect.AddListener(OnRaycastProHit);
|
|
mouseRaySensor.onEndDetect.AddListener(OnRaycastProEnd);
|
|
|
|
Debug.Log("RaycastPro initialized successfully!");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("RaycastPro RaySensor not found. Using Unity's built-in Physics.Raycast instead.");
|
|
}
|
|
}
|
|
|
|
// Update is called once per frame
|
|
void Update()
|
|
{
|
|
HandleKeyboardMovement();
|
|
HandleMouseEdgeScrolling();
|
|
HandleMouseDrag();
|
|
HandleZoom();
|
|
HandleRotation();
|
|
HandleRTSControls();
|
|
ApplyMovementAndZoom();
|
|
}
|
|
|
|
void HandleKeyboardMovement()
|
|
{
|
|
Vector3 moveDirection = Vector3.zero;
|
|
|
|
// WASD movement
|
|
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow))
|
|
moveDirection += transform.forward;
|
|
if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow))
|
|
moveDirection -= transform.forward;
|
|
if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
|
|
moveDirection -= transform.right;
|
|
if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
|
|
moveDirection += transform.right;
|
|
|
|
// Apply movement
|
|
if (moveDirection != Vector3.zero)
|
|
{
|
|
moveDirection.Normalize();
|
|
targetPosition += moveDirection * moveSpeed * Time.deltaTime;
|
|
}
|
|
}
|
|
|
|
void HandleMouseEdgeScrolling()
|
|
{
|
|
Vector2 mousePosition = Input.mousePosition;
|
|
Vector3 moveDirection = Vector3.zero;
|
|
|
|
// Check screen edges
|
|
if (mousePosition.x < edgeScrollBoundary)
|
|
moveDirection -= transform.right;
|
|
else if (mousePosition.x > Screen.width - edgeScrollBoundary)
|
|
moveDirection += transform.right;
|
|
|
|
if (mousePosition.y < edgeScrollBoundary)
|
|
moveDirection -= transform.forward;
|
|
else if (mousePosition.y > Screen.height - edgeScrollBoundary)
|
|
moveDirection += transform.forward;
|
|
|
|
// Apply edge scrolling
|
|
if (moveDirection != Vector3.zero)
|
|
{
|
|
moveDirection.Normalize();
|
|
targetPosition += moveDirection * edgeScrollSpeed * Time.deltaTime;
|
|
}
|
|
}
|
|
|
|
void HandleMouseDrag()
|
|
{
|
|
// Middle mouse button drag
|
|
if (Input.GetMouseButtonDown(2))
|
|
{
|
|
isDragging = true;
|
|
lastMousePosition = Input.mousePosition;
|
|
}
|
|
else if (Input.GetMouseButtonUp(2))
|
|
{
|
|
isDragging = false;
|
|
}
|
|
|
|
if (isDragging)
|
|
{
|
|
Vector3 mouseDelta = Input.mousePosition - lastMousePosition;
|
|
Vector3 moveDirection = (-mouseDelta.x * transform.right - mouseDelta.y * transform.forward) * 0.01f;
|
|
targetPosition += moveDirection * moveSpeed * Time.deltaTime;
|
|
lastMousePosition = Input.mousePosition;
|
|
}
|
|
}
|
|
|
|
void HandleZoom()
|
|
{
|
|
float scroll = Input.GetAxis("Mouse ScrollWheel");
|
|
|
|
if (scroll != 0)
|
|
{
|
|
targetZoom -= scroll * zoomSpeed;
|
|
targetZoom = Mathf.Clamp(targetZoom, minZoom, maxZoom);
|
|
}
|
|
}
|
|
|
|
void HandleRotation()
|
|
{
|
|
if (!enableRotation) return;
|
|
|
|
float rotationInput = 0f;
|
|
|
|
// Q and E keys for rotation
|
|
if (Input.GetKey(KeyCode.Q))
|
|
rotationInput = -1f;
|
|
if (Input.GetKey(KeyCode.E))
|
|
rotationInput = 1f;
|
|
|
|
if (rotationInput != 0f)
|
|
{
|
|
// Update orbit center to be at the current focus point
|
|
UpdateOrbitCenter();
|
|
|
|
// Calculate orbit rotation
|
|
RotateAroundCenter(rotationInput * rotationSpeed * Time.deltaTime);
|
|
}
|
|
}
|
|
|
|
void RotateAroundCenter(float angle)
|
|
{
|
|
// Store the current X rotation before orbiting
|
|
float currentXRotation = transform.eulerAngles.x;
|
|
if (currentXRotation > 180f) currentXRotation -= 360f; // Normalize to -180 to 180
|
|
|
|
// Calculate the vector from orbit center to camera
|
|
Vector3 offset = transform.position - currentOrbitCenter;
|
|
|
|
// Rotate the offset around the Y axis (horizontal rotation only)
|
|
Quaternion rotation = Quaternion.AngleAxis(angle, Vector3.up);
|
|
Vector3 newOffset = rotation * offset;
|
|
|
|
// Update camera position
|
|
Vector3 newPosition = currentOrbitCenter + newOffset;
|
|
targetPosition = newPosition;
|
|
|
|
// Make camera look at the orbit center but preserve X rotation
|
|
Vector3 lookDirection = (currentOrbitCenter - newPosition).normalized;
|
|
if (lookDirection != Vector3.zero)
|
|
{
|
|
// Calculate the Y rotation needed to look at the center
|
|
float yRotation = Mathf.Atan2(lookDirection.x, lookDirection.z) * Mathf.Rad2Deg;
|
|
|
|
// Apply rotation with preserved X rotation
|
|
transform.rotation = Quaternion.Euler(currentXRotation, yRotation, 0f);
|
|
}
|
|
}
|
|
|
|
void ApplyMovementAndZoom()
|
|
{
|
|
// Clamp position to boundaries
|
|
targetPosition.x = Mathf.Clamp(targetPosition.x, minBounds.x, maxBounds.x);
|
|
targetPosition.z = Mathf.Clamp(targetPosition.z, minBounds.y, maxBounds.y);
|
|
|
|
// Smooth movement
|
|
transform.position = Vector3.Lerp(transform.position, targetPosition, movementSmoothing * Time.deltaTime);
|
|
|
|
// Smooth zoom
|
|
if (cam.orthographic)
|
|
{
|
|
cam.orthographicSize = Mathf.Lerp(cam.orthographicSize, targetZoom, zoomSmoothing * Time.deltaTime);
|
|
}
|
|
else
|
|
{
|
|
// For perspective camera, adjust the distance
|
|
Vector3 currentPos = transform.position;
|
|
Vector3 targetPos = new Vector3(currentPos.x, targetZoom, currentPos.z);
|
|
transform.position = Vector3.Lerp(currentPos, targetPos, zoomSmoothing * Time.deltaTime);
|
|
}
|
|
}
|
|
|
|
void HandleRTSControls()
|
|
{
|
|
// Left click for selection and commands
|
|
if (Input.GetMouseButtonDown(0))
|
|
{
|
|
HandleLeftClick();
|
|
}
|
|
|
|
// Right click for move commands
|
|
if (Input.GetMouseButtonDown(1))
|
|
{
|
|
HandleRightClick();
|
|
}
|
|
|
|
// Escape to deselect all
|
|
if (Input.GetKeyDown(KeyCode.Escape))
|
|
{
|
|
DeselectAllUnits();
|
|
}
|
|
}
|
|
|
|
void HandleLeftClick()
|
|
{
|
|
RaycastHit hit;
|
|
bool hitFound = false;
|
|
|
|
// Try RaycastPro first if available
|
|
if (mouseRaySensor != null && TryRaycastProClick(out hit))
|
|
{
|
|
hitFound = true;
|
|
Debug.Log("Using RaycastPro for left click");
|
|
}
|
|
// Fallback to Unity's built-in raycast
|
|
else if (TryUnityRaycast(out hit))
|
|
{
|
|
hitFound = true;
|
|
Debug.Log("Using Unity Physics.Raycast for left click");
|
|
}
|
|
|
|
if (hitFound)
|
|
{
|
|
GameObject hitObject = hit.transform.gameObject;
|
|
Debug.Log($"Hit object: {hitObject.name} on layer {hitObject.layer}");
|
|
Debug.Log($"Unit layer mask: {unitLayer.value}, Ground layer mask: {groundLayer.value}, Interactable layer mask: {interactableLayer.value}");
|
|
|
|
// Check if we hit a unit
|
|
if (IsInLayerMask(hitObject, unitLayer))
|
|
{
|
|
Debug.Log($"Selecting unit: {hitObject.name}");
|
|
HandleUnitSelection(hitObject);
|
|
OnUnitClicked?.Invoke(hitObject);
|
|
}
|
|
// Check if we hit an interactable object
|
|
else if (IsInLayerMask(hitObject, interactableLayer))
|
|
{
|
|
Debug.Log($"Clicked interactable: {hitObject.name}");
|
|
OnBuildingClicked?.Invoke(hitObject);
|
|
}
|
|
// Hit ground - deselect or start box selection
|
|
else if (IsInLayerMask(hitObject, groundLayer))
|
|
{
|
|
Debug.Log($"Clicked ground: {hitObject.name}");
|
|
if (!Input.GetKey(KeyCode.LeftShift))
|
|
{
|
|
DeselectAllUnits();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"Clicked object on unknown layer: {hitObject.name} (layer {hitObject.layer})");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.Log("No raycast hit detected");
|
|
}
|
|
}
|
|
|
|
void HandleRightClick()
|
|
{
|
|
Debug.Log($"Right click - Selected units count: {selectedUnits.Count}");
|
|
|
|
if (selectedUnits.Count > 0)
|
|
{
|
|
RaycastHit hit;
|
|
bool hitFound = false;
|
|
|
|
// Try RaycastPro first if available
|
|
if (mouseRaySensor != null && TryRaycastProClick(out hit))
|
|
{
|
|
hitFound = true;
|
|
Debug.Log("Using RaycastPro for right click");
|
|
}
|
|
// Fallback to Unity's built-in raycast
|
|
else if (TryUnityRaycast(out hit))
|
|
{
|
|
hitFound = true;
|
|
Debug.Log("Using Unity Physics.Raycast for right click");
|
|
}
|
|
|
|
if (hitFound)
|
|
{
|
|
GameObject hitObject = hit.transform.gameObject;
|
|
|
|
if (IsInLayerMask(hitObject, interactableLayer))
|
|
{
|
|
Debug.Log($"Right click on interactable object: {hitObject.name} at position: {hit.point}");
|
|
|
|
// Check if it's a resource node - send work command to selected units
|
|
ResourceNode resourceNode = hitObject.GetComponent<ResourceNode>();
|
|
if (resourceNode != null)
|
|
{
|
|
Debug.Log($"Commanding {selectedUnits.Count} units to work on {hitObject.name}");
|
|
foreach (GameObject selectedUnit in selectedUnits)
|
|
{
|
|
Unit unit = selectedUnit.GetComponent<Unit>();
|
|
if (unit != null)
|
|
{
|
|
Debug.Log($"Sending {unit.unitName} to work on {hitObject.name}");
|
|
unit.WorkOn(hitObject);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// It's a building or other interactable
|
|
OnBuildingClicked?.Invoke(hitObject);
|
|
}
|
|
}
|
|
else if (IsInLayerMask(hitObject, groundLayer))
|
|
{
|
|
Debug.Log($"Right click on ground at position: {hit.point}");
|
|
Vector3 targetPosition = hit.point;
|
|
Debug.Log($"Issuing move command to position: {targetPosition}");
|
|
OnMoveCommand?.Invoke(targetPosition);
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"Right click on object: {hitObject.name} at position: {hit.point}");
|
|
Vector3 targetPosition = hit.point;
|
|
Debug.Log($"Issuing move command to position: {targetPosition}");
|
|
OnMoveCommand?.Invoke(targetPosition);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.Log("Right click: No raycast hit detected");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.Log("Right click: No units selected");
|
|
}
|
|
}
|
|
|
|
void HandleUnitSelection(GameObject unit)
|
|
{
|
|
Debug.Log($"HandleUnitSelection called for: {unit.name}");
|
|
|
|
// If holding shift, add to selection
|
|
if (Input.GetKey(KeyCode.LeftShift))
|
|
{
|
|
if (selectedUnits.Contains(unit))
|
|
{
|
|
Debug.Log($"Removing {unit.name} from selection");
|
|
RemoveFromSelection(unit);
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"Adding {unit.name} to selection");
|
|
AddToSelection(unit);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Single selection - clear others first
|
|
Debug.Log($"Single selecting {unit.name}");
|
|
DeselectAllUnits();
|
|
AddToSelection(unit);
|
|
}
|
|
|
|
Debug.Log($"Selected units count: {selectedUnits.Count}");
|
|
OnUnitsSelected?.Invoke(selectedUnits);
|
|
}
|
|
|
|
void AddToSelection(GameObject unit)
|
|
{
|
|
if (!selectedUnits.Contains(unit))
|
|
{
|
|
selectedUnits.Add(unit);
|
|
CreateSelectionIndicator(unit);
|
|
}
|
|
}
|
|
|
|
void RemoveFromSelection(GameObject unit)
|
|
{
|
|
if (selectedUnits.Contains(unit))
|
|
{
|
|
selectedUnits.Remove(unit);
|
|
DestroySelectionIndicator(unit);
|
|
}
|
|
}
|
|
|
|
void DeselectAllUnits()
|
|
{
|
|
foreach (GameObject unit in selectedUnits)
|
|
{
|
|
DestroySelectionIndicator(unit);
|
|
}
|
|
selectedUnits.Clear();
|
|
OnUnitsSelected?.Invoke(selectedUnits);
|
|
}
|
|
|
|
void CreateSelectionIndicator(GameObject unit)
|
|
{
|
|
if (selectionIndicatorPrefab != null && !selectionIndicators.ContainsKey(unit))
|
|
{
|
|
GameObject indicator = Instantiate(selectionIndicatorPrefab, unit.transform.position, Quaternion.identity);
|
|
indicator.transform.SetParent(unit.transform);
|
|
indicator.transform.localPosition = Vector3.zero;
|
|
selectionIndicators[unit] = indicator;
|
|
}
|
|
else if (selectionMaterial != null)
|
|
{
|
|
// Alternative: Change material of the unit
|
|
Renderer renderer = unit.GetComponent<Renderer>();
|
|
if (renderer != null)
|
|
{
|
|
// Store original material if needed
|
|
renderer.material = selectionMaterial;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DestroySelectionIndicator(GameObject unit)
|
|
{
|
|
if (selectionIndicators.ContainsKey(unit))
|
|
{
|
|
if (selectionIndicators[unit] != null)
|
|
{
|
|
Destroy(selectionIndicators[unit]);
|
|
}
|
|
selectionIndicators.Remove(unit);
|
|
}
|
|
}
|
|
|
|
bool IsInLayerMask(GameObject obj, LayerMask layerMask)
|
|
{
|
|
return (layerMask.value & (1 << obj.layer)) > 0;
|
|
}
|
|
|
|
// Helper methods for raycasting
|
|
bool TryRaycastProClick(out RaycastHit hit)
|
|
{
|
|
hit = default(RaycastHit);
|
|
|
|
if (mouseRaySensor == null) return false;
|
|
|
|
// Update the ray sensor direction to point from camera to mouse
|
|
Vector3 mousePos = Input.mousePosition;
|
|
Ray ray = cam.ScreenPointToRay(mousePos);
|
|
|
|
// Temporarily set the sensor position and direction without moving the camera
|
|
Vector3 originalPos = mouseRaySensor.transform.position;
|
|
Quaternion originalRot = mouseRaySensor.transform.rotation;
|
|
|
|
// Set ray sensor to start from camera and point towards mouse
|
|
mouseRaySensor.transform.position = ray.origin;
|
|
mouseRaySensor.transform.rotation = Quaternion.LookRotation(ray.direction);
|
|
|
|
// Perform the raycast
|
|
mouseRaySensor.Cast();
|
|
|
|
// Check if we have a valid hit
|
|
bool hasHit = mouseRaySensor.Performed && hasValidRaycastHit;
|
|
if (hasHit)
|
|
{
|
|
hit = lastRaycastHit;
|
|
}
|
|
|
|
// Restore original transform (important!)
|
|
mouseRaySensor.transform.position = originalPos;
|
|
mouseRaySensor.transform.rotation = originalRot;
|
|
|
|
return hasHit;
|
|
}
|
|
|
|
bool TryUnityRaycast(out RaycastHit hit)
|
|
{
|
|
Vector3 mousePos = Input.mousePosition;
|
|
Ray ray = cam.ScreenPointToRay(mousePos);
|
|
|
|
return Physics.Raycast(ray, out hit);
|
|
}
|
|
|
|
// RaycastPro event handlers
|
|
void OnRaycastProHit(RaycastHit hit)
|
|
{
|
|
lastRaycastHit = hit;
|
|
hasValidRaycastHit = true;
|
|
}
|
|
|
|
void OnRaycastProEnd(RaycastHit hit)
|
|
{
|
|
hasValidRaycastHit = false;
|
|
}
|
|
|
|
// Public methods for external control
|
|
public void SetCameraPosition(Vector3 position)
|
|
{
|
|
targetPosition = position;
|
|
}
|
|
|
|
public void FocusOnPosition(Vector3 position)
|
|
{
|
|
targetPosition = new Vector3(position.x, transform.position.y, position.z);
|
|
|
|
// Update orbit center to the focused position
|
|
currentOrbitCenter = position;
|
|
currentOrbitCenter.y = transform.position.y;
|
|
|
|
// Make camera look at the new focus point
|
|
Vector3 lookDirection = (currentOrbitCenter - transform.position).normalized;
|
|
if (lookDirection != Vector3.zero)
|
|
{
|
|
transform.rotation = Quaternion.LookRotation(lookDirection);
|
|
}
|
|
}
|
|
|
|
public void SetZoom(float zoom)
|
|
{
|
|
targetZoom = Mathf.Clamp(zoom, minZoom, maxZoom);
|
|
}
|
|
|
|
public void SetOrbitCenter(Vector3 center)
|
|
{
|
|
currentOrbitCenter = center;
|
|
}
|
|
|
|
public void SetOrbitDistance(float distance)
|
|
{
|
|
orbitDistance = distance;
|
|
UpdateOrbitCenter();
|
|
}
|
|
|
|
void UpdateOrbitCenter()
|
|
{
|
|
// If we have a specific orbit center set, use it
|
|
if (orbitCenter != null)
|
|
{
|
|
currentOrbitCenter = orbitCenter.position;
|
|
}
|
|
else
|
|
{
|
|
// Use a point in front of the camera at the specified distance
|
|
// This creates a natural orbit point based on where the camera is looking
|
|
Vector3 forward = transform.forward;
|
|
forward.y = 0; // Keep it horizontal
|
|
forward.Normalize();
|
|
|
|
currentOrbitCenter = transform.position + forward * orbitDistance;
|
|
currentOrbitCenter.y = transform.position.y; // Keep same height as camera
|
|
}
|
|
}
|
|
}
|