182 lines
6.3 KiB
C#
182 lines
6.3 KiB
C#
using UnityEngine;
|
|
|
|
namespace TKS
|
|
{
|
|
/// <summary>
|
|
/// 넓은 전장용 카메라: 화면보다 큰 전장을 빈 곳 드래그로 스크롤 · 휠로 줌 · 행동 중인 유닛 자동 추적 · 전장 밖으로 못 나가게 고정.
|
|
/// </summary>
|
|
public class CameraController : MonoBehaviour
|
|
{
|
|
public Board Board;
|
|
public float ViewHeight = 18f;
|
|
public float MinViewHeight = 10f;
|
|
public float MaxViewHeight = 36f;
|
|
public float Margin = 1f;
|
|
[Tooltip("위쪽 HUD(스테이지·턴 글자) 밑에 유닛이 깔리지 않도록 전장 위 끝을 더 내려 볼 수 있는 여유")]
|
|
public float TopMargin = 2.6f;
|
|
[Tooltip("아래쪽 HUD(안내·유닛 정보) 여유")]
|
|
public float BottomMargin = 2.2f;
|
|
public float FollowSpeed = 7f;
|
|
|
|
Camera cam;
|
|
Vector2? focusTarget;
|
|
Transform followTarget;
|
|
|
|
public Camera Cam
|
|
{
|
|
get
|
|
{
|
|
if (cam == null) cam = GetComponent<Camera>();
|
|
return cam;
|
|
}
|
|
}
|
|
|
|
public void Init(Board board, float viewHeight, float minViewHeight, float maxViewHeight)
|
|
{
|
|
Board = board;
|
|
MinViewHeight = Mathf.Max(1f, minViewHeight);
|
|
MaxViewHeight = Mathf.Max(MinViewHeight, maxViewHeight);
|
|
ViewHeight = Mathf.Clamp(viewHeight, MinViewHeight, MaxViewHeight);
|
|
Cam.orthographicSize = ViewHeight * 0.5f;
|
|
SetPosition(transform.position);
|
|
}
|
|
|
|
/// <summary>화면 1 픽셀이 월드 몇 단위인가(드래그 스크롤 환산).</summary>
|
|
public float WorldUnitsPerPixel
|
|
{
|
|
get { return Cam.orthographicSize * 2f / Mathf.Max(1, Cam.pixelHeight); }
|
|
}
|
|
|
|
public Rect ViewRect
|
|
{
|
|
get
|
|
{
|
|
float halfH = Cam.orthographicSize;
|
|
float halfW = halfH * Cam.aspect;
|
|
Vector2 c = transform.position;
|
|
return new Rect(c.x - halfW, c.y - halfH, halfW * 2f, halfH * 2f);
|
|
}
|
|
}
|
|
|
|
public void PanBy(Vector2 worldDelta)
|
|
{
|
|
focusTarget = null;
|
|
followTarget = null;
|
|
SetPosition((Vector2)transform.position + worldDelta);
|
|
}
|
|
|
|
public void FocusOn(Vector2 worldPos)
|
|
{
|
|
followTarget = null;
|
|
focusTarget = worldPos;
|
|
}
|
|
|
|
public void SnapTo(Vector2 worldPos)
|
|
{
|
|
followTarget = null;
|
|
focusTarget = null;
|
|
SetPosition(worldPos);
|
|
}
|
|
|
|
public void Follow(Transform t)
|
|
{
|
|
focusTarget = null;
|
|
followTarget = t;
|
|
}
|
|
|
|
public void StopFollow()
|
|
{
|
|
followTarget = null;
|
|
focusTarget = null;
|
|
}
|
|
|
|
public void Zoom(float factor)
|
|
{
|
|
SetViewHeight(ViewHeight * factor);
|
|
}
|
|
|
|
public void SetViewHeight(float height)
|
|
{
|
|
ViewHeight = Mathf.Clamp(height, MinViewHeight, MaxViewHeight);
|
|
Cam.orthographicSize = ViewHeight * 0.5f;
|
|
SetPosition(transform.position);
|
|
}
|
|
|
|
// 화면 흔들림(타격감 · PD 2026-11-10 「타격감을 위한 화면 효과(화면 흔들림 등)도 IB 를 참고해서」) — 추적·스크롤 위치(기준)에 매 프레임 흔들림만 더하고
|
|
// 다음 프레임 처음에 빼서 기준 위치를 흔들지 않는다. 값 = 표(Resources/Skills/hit_feel.csv · HitFeel).
|
|
Vector2 shakeApplied;
|
|
float shakeTime;
|
|
float shakeDuration;
|
|
float shakeMagnitude;
|
|
|
|
/// <summary>흔들기 시작(진행 중이면 더 센 쪽을 남김) · duration 초 · magnitude = 최대 흔들림(월드 단위) · 끝으로 갈수록 약해짐.</summary>
|
|
public void Shake(float duration, float magnitude)
|
|
{
|
|
if (duration <= 0f || magnitude <= 0f) return;
|
|
float remain = shakeDuration > 0f ? shakeMagnitude * Mathf.Clamp01(1f - shakeTime / shakeDuration) : 0f;
|
|
if (magnitude < remain) return;
|
|
shakeDuration = duration;
|
|
shakeMagnitude = magnitude;
|
|
shakeTime = 0f;
|
|
}
|
|
|
|
public bool Shaking
|
|
{
|
|
get { return shakeDuration > 0f && shakeTime < shakeDuration; }
|
|
}
|
|
|
|
void LateUpdate()
|
|
{
|
|
if (shakeApplied != Vector2.zero) // 지난 프레임 흔들림 빼기(기준 위치 복원)
|
|
{
|
|
transform.position -= (Vector3)shakeApplied;
|
|
shakeApplied = Vector2.zero;
|
|
}
|
|
Follow();
|
|
if (Shaking)
|
|
{
|
|
shakeTime += Time.unscaledDeltaTime;
|
|
float k = Mathf.Clamp01(1f - shakeTime / shakeDuration);
|
|
shakeApplied = Random.insideUnitCircle * (shakeMagnitude * k);
|
|
transform.position += (Vector3)shakeApplied;
|
|
}
|
|
}
|
|
|
|
void Follow()
|
|
{
|
|
Vector2? goal = null;
|
|
if (followTarget != null && followTarget.gameObject.activeInHierarchy) goal = (Vector2)followTarget.position;
|
|
else if (focusTarget.HasValue) goal = focusTarget;
|
|
if (!goal.HasValue) return;
|
|
|
|
Vector2 target = Clamp(goal.Value);
|
|
Vector2 cur = transform.position;
|
|
float k = 1f - Mathf.Exp(-FollowSpeed * Time.deltaTime);
|
|
Vector2 next = Vector2.Lerp(cur, target, k);
|
|
SetPosition(next);
|
|
if (focusTarget.HasValue && Vector2.Distance(next, target) < 0.02f) focusTarget = null;
|
|
}
|
|
|
|
void SetPosition(Vector2 p)
|
|
{
|
|
p = Clamp(p);
|
|
transform.position = new Vector3(p.x, p.y, -10f);
|
|
}
|
|
|
|
public Vector2 Clamp(Vector2 p)
|
|
{
|
|
if (Board == null) return p;
|
|
float halfH = Cam.orthographicSize;
|
|
float halfW = halfH * Cam.aspect;
|
|
var r = Board.Rect;
|
|
float minX = r.xMin - Margin + halfW;
|
|
float maxX = r.xMax + Margin - halfW;
|
|
float minY = r.yMin - BottomMargin + halfH;
|
|
float maxY = r.yMax + TopMargin - halfH;
|
|
p.x = minX > maxX ? 0f : Mathf.Clamp(p.x, minX, maxX);
|
|
p.y = minY > maxY ? 0f : Mathf.Clamp(p.y, minY, maxY);
|
|
return p;
|
|
}
|
|
}
|
|
}
|