Files
Backyard-CTF/Backyard CTF/Assets/Scripts/CameraController.cs
John Lamb e4ac24f989 feat(grid): Replace real-time CTF with turn-based grid system
Replace continuous free-form movement with discrete 20x50 grid-based
gameplay featuring asymmetric movement mechanics:

- Blue/Red teams with 3 units each
- Zone-based movement: orthogonal (4 dir) in offense, diagonal (8 dir) in defense
- Alternating turns with click-to-select, click-to-move input
- Fog of war: 3-cell Chebyshev vision radius per unit
- Defense speed nerf: skip every 4th move in own zone
- AI opponent that chases flag carriers and advances toward enemy flag
- Collision resolution: defender wins in their zone, lower ID wins in neutral

Implements all 3 phases from the plan:
- Phase 1: Playable grid with hot-seat two-player
- Phase 2: Fog of war + defense speed nerf
- Phase 3: AI opponent

Deleted obsolete files: Flag.cs, Unit.cs, RouteDrawer.cs, SimpleAI.cs, Visibility.cs

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-07 17:05:44 -06:00

169 lines
4.7 KiB
C#

using UnityEngine;
using UnityEngine.InputSystem;
public class CameraController : MonoBehaviour
{
// Zoom settings
public float minZoom = 5f;
public float maxZoom = 35f;
public float zoomSpeed = 2f;
public float pinchZoomSpeed = 0.1f;
// Pan settings
public float panSpeed = 1f;
// Map bounds (grid-based)
float mapWidth = ZoneBoundaries.BoardWidth;
float mapHeight = ZoneBoundaries.BoardHeight;
Camera cam;
Vector2 lastPanPosition;
bool isPanning;
float lastPinchDistance;
bool isPinching;
void Start()
{
cam = Camera.main;
}
void Update()
{
HandleMouseInput();
HandleTouchInput();
ClampCameraPosition();
}
void HandleMouseInput()
{
var mouse = Mouse.current;
if (mouse == null) return;
// Scroll wheel zoom
float scroll = mouse.scroll.ReadValue().y;
if (Mathf.Abs(scroll) > 0.01f)
{
Zoom(-scroll * zoomSpeed * Time.deltaTime * 10f);
}
// Right-click pan (left-click is for unit selection)
if (mouse.rightButton.wasPressedThisFrame)
{
lastPanPosition = mouse.position.ReadValue();
isPanning = true;
}
else if (mouse.rightButton.wasReleasedThisFrame)
{
isPanning = false;
}
if (isPanning && mouse.rightButton.isPressed)
{
Vector2 currentPos = mouse.position.ReadValue();
Vector2 delta = currentPos - lastPanPosition;
Pan(-delta);
lastPanPosition = currentPos;
}
}
void HandleTouchInput()
{
var touch = Touchscreen.current;
if (touch == null) return;
int touchCount = 0;
foreach (var t in touch.touches)
{
if (t.press.isPressed) touchCount++;
}
if (touchCount == 2)
{
// Two finger pinch zoom and pan
var touch0 = touch.touches[0];
var touch1 = touch.touches[1];
Vector2 pos0 = touch0.position.ReadValue();
Vector2 pos1 = touch1.position.ReadValue();
float currentDistance = Vector2.Distance(pos0, pos1);
if (!isPinching)
{
isPinching = true;
lastPinchDistance = currentDistance;
lastPanPosition = (pos0 + pos1) / 2f;
}
else
{
// Pinch zoom
float deltaDistance = lastPinchDistance - currentDistance;
Zoom(deltaDistance * pinchZoomSpeed);
lastPinchDistance = currentDistance;
// Two-finger pan
Vector2 currentCenter = (pos0 + pos1) / 2f;
Vector2 delta = currentCenter - lastPanPosition;
Pan(-delta);
lastPanPosition = currentCenter;
}
isPanning = false; // Don't single-finger pan while pinching
}
else if (touchCount == 1)
{
isPinching = false;
var primaryTouch = touch.primaryTouch;
Vector2 touchPos = primaryTouch.position.ReadValue();
// Two-finger gestures only for pan on touch - single finger is for unit selection
// Don't initiate pan on single touch
}
else
{
isPinching = false;
isPanning = false;
}
}
void Zoom(float delta)
{
float newSize = cam.orthographicSize + delta;
cam.orthographicSize = Mathf.Clamp(newSize, minZoom, maxZoom);
}
void Pan(Vector2 screenDelta)
{
// Convert screen delta to world delta
float worldUnitsPerPixel = cam.orthographicSize * 2f / Screen.height;
Vector3 worldDelta = new Vector3(
screenDelta.x * worldUnitsPerPixel * panSpeed,
screenDelta.y * worldUnitsPerPixel * panSpeed,
0
);
cam.transform.position += worldDelta;
}
void ClampCameraPosition()
{
// Keep camera within map bounds (with some padding for zoom)
float halfHeight = cam.orthographicSize;
float halfWidth = halfHeight * cam.aspect;
float minX = -mapWidth / 2f + halfWidth;
float maxX = mapWidth / 2f - halfWidth;
float minY = -mapHeight / 2f + halfHeight;
float maxY = mapHeight / 2f - halfHeight;
// Handle case where zoom is wider than map
if (minX > maxX) minX = maxX = 0;
if (minY > maxY) minY = maxY = 0;
Vector3 pos = cam.transform.position;
pos.x = Mathf.Clamp(pos.x, minX, maxX);
pos.y = Mathf.Clamp(pos.y, minY, maxY);
cam.transform.position = pos;
}
}