Files
RaidersRPG/Assets/Scripts/CameraController.cs
SHOUTING_PIRATE 3d2d2dc3e3 Initial commit
2025-07-01 21:03:21 +01:00

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
}
}
}