Initial Combat Implementation

This commit is contained in:
Luis Gonzalez
2026-05-31 21:35:12 -07:00
parent 7fa77ce821
commit 1f647dd5e1
166 changed files with 93337 additions and 91 deletions
@@ -0,0 +1,98 @@
using ProjectM.Simulation;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Transforms;
using UnityEngine;
namespace ProjectM.Client
{
/// <summary>
/// Prototype ARPG follow camera. Attach to the Main Camera (a plain MonoBehaviour): each LateUpdate
/// it frames the local player at a fixed pitch/yaw and distance — the classic top-down/isometric ARPG
/// framing (V Rising / Diablo / PoE2). Every lever is exposed so the feel can be dialled in live in
/// Play Mode. The player's world position is published by <see cref="PrototypeCameraTargetSystem"/>
/// (an ECS system that observes job dependencies safely); the MonoBehaviour never touches the
/// EntityManager directly — doing so from LateUpdate raced the subscene async-load job and threw a
/// job-safety exception at startup. Before a player spawns it frames <see cref="FallbackTarget"/>.
/// <para>
/// Presentation only — uses wall-clock <c>Time.deltaTime</c> for framerate-independent smoothing,
/// which is correct here (not deterministic simulation). Default: mid 3/4 ~45°, perspective.
/// </para>
/// </summary>
[RequireComponent(typeof(Camera))]
[DisallowMultipleComponent]
public class PrototypeCameraRig : MonoBehaviour
{
/// <summary>Local player world position, published each client tick by <see cref="PrototypeCameraTargetSystem"/>.</summary>
public static float3 TargetWorldPos;
/// <summary>True while a locally-owned player exists to follow.</summary>
public static bool HasTarget;
[Header("Angle (degrees)")]
[Range(10f, 89f)] public float Pitch = 45f;
[Range(-180f, 180f)] public float Yaw = 0f;
[Header("Framing")]
[Min(1f)] public float Distance = 16f;
[Tooltip("Raise the look-at point off the ground toward the character's centre of mass.")]
public float TargetHeight = 1f;
[Header("Lens")]
public bool Orthographic = false;
[Range(20f, 90f)] public float FieldOfView = 55f;
[Min(1f)] public float OrthoSize = 10f;
[Header("Follow")]
[Tooltip("Higher = snappier follow. 0 = instant. Framerate-independent.")]
[Min(0f)] public float FollowSharpness = 8f;
[Tooltip("What to frame before a local player exists (edit mode / pre-spawn).")]
public Vector3 FallbackTarget = new Vector3(3f, 0f, 4f);
Camera _cam;
void Awake() => _cam = GetComponent<Camera>();
void LateUpdate()
{
if (_cam == null) _cam = GetComponent<Camera>();
_cam.orthographic = Orthographic;
_cam.fieldOfView = FieldOfView;
_cam.orthographicSize = OrthoSize;
Vector3 target = HasTarget ? (Vector3)TargetWorldPos : FallbackTarget;
target.y += TargetHeight;
var rot = Quaternion.Euler(Pitch, Yaw, 0f);
Vector3 desired = target - (rot * Vector3.forward) * Distance;
float k = FollowSharpness <= 0f ? 1f : 1f - Mathf.Exp(-FollowSharpness * Time.deltaTime);
transform.SetPositionAndRotation(Vector3.Lerp(transform.position, desired, k), rot);
}
}
/// <summary>
/// Publishes the locally-owned player's world position to <see cref="PrototypeCameraRig"/> statics
/// each client tick. Lives in ECS (not the camera MonoBehaviour) so reading <see cref="LocalTransform"/>
/// respects job dependencies — avoiding the subscene-load job-safety exception that direct
/// EntityManager access from LateUpdate caused.
/// </summary>
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
public partial class PrototypeCameraTargetSystem : SystemBase
{
protected override void OnUpdate()
{
bool found = false;
foreach (var transform in SystemAPI.Query<RefRO<LocalTransform>>()
.WithAll<GhostOwnerIsLocal, PlayerTag>())
{
PrototypeCameraRig.TargetWorldPos = transform.ValueRO.Position;
found = true;
break;
}
PrototypeCameraRig.HasTarget = found;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3e5890693b64a429789bf3edfae0a6ff