HUD and Height Changes
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Drop one instance into the Gameplay subscene. Bakes <see cref="WorldCollisionConfig"/> capturing the
|
||||
/// "Environment" physics layer's BelongsTo mask (the layer the boundary-ring + landmark colliders live on).
|
||||
/// The baker runs on the main thread, so the managed <see cref="LayerMask.NameToLayer"/> lookup is fine here —
|
||||
/// this exists precisely so the Bursted server EnemyAISystem can read the mask as a plain uint at runtime.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class WorldCollisionAuthoring : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Name of the Unity layer carrying the static world colliders (boundary ring + landmarks).")]
|
||||
public string EnvironmentLayerName = "Environment";
|
||||
|
||||
private class WorldCollisionBaker : Baker<WorldCollisionAuthoring>
|
||||
{
|
||||
public override void Bake(WorldCollisionAuthoring authoring)
|
||||
{
|
||||
int layer = LayerMask.NameToLayer(authoring.EnvironmentLayerName);
|
||||
uint mask = layer >= 0 ? 1u << layer : 0u;
|
||||
var entity = GetEntity(TransformUsageFlags.None);
|
||||
AddComponent(entity, new WorldCollisionConfig { EnvironmentMask = mask });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab1c895817ca4b64db74215f13abb5e3
|
||||
@@ -4,6 +4,7 @@ using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.NetCode;
|
||||
using Unity.Physics;
|
||||
using Unity.Transforms;
|
||||
|
||||
namespace ProjectM.Server
|
||||
@@ -61,6 +62,11 @@ namespace ProjectM.Server
|
||||
var serverTick = SystemAPI.GetSingleton<NetworkTime>().ServerTick;
|
||||
uint now = serverTick.TickIndexForValidTick;
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
bool havePhysics = SystemAPI.TryGetSingleton<PhysicsWorldSingleton>(out var physics);
|
||||
uint envMask = SystemAPI.TryGetSingleton<WorldCollisionConfig>(out var worldCol) ? worldCol.EnvironmentMask : 0u;
|
||||
var envFilter = new CollisionFilter { BelongsTo = ~0u, CollidesWith = envMask, GroupIndex = 0 };
|
||||
bool sweep = havePhysics && envMask != 0u;
|
||||
const float SweepRadius = 0.5f; // collide-and-slide sphere radius for Husk movement
|
||||
|
||||
foreach (var (xform, stats, cooldown, knockback, windup) in
|
||||
SystemAPI.Query<RefRW<LocalTransform>, RefRO<EnemyStats>, RefRW<EnemyAttackCooldown>,
|
||||
@@ -78,6 +84,8 @@ namespace ProjectM.Server
|
||||
{
|
||||
float3 kpos = pos + new float3(kb.Dir.x, 0f, kb.Dir.y) * (kb.Speed * dt);
|
||||
kpos.y = pos.y;
|
||||
if (sweep)
|
||||
kpos = SweptMove(in physics, pos, kpos, SweepRadius, envFilter);
|
||||
xform.ValueRW.Position = kpos;
|
||||
windup.ValueRW.WindUpUntilTick = 0; // a recoiling Husk does not wind up
|
||||
continue; // recoiling: skip seek + strike this tick
|
||||
@@ -106,6 +114,8 @@ namespace ProjectM.Server
|
||||
float3 vel = EnemyAIMath.SeekVelocity(pos, targetPos, stats.ValueRO.MoveSpeed, stopDistance);
|
||||
float3 newPos = pos + vel * dt;
|
||||
newPos.y = pos.y; // hold the movement plane
|
||||
if (sweep)
|
||||
newPos = SweptMove(in physics, pos, newPos, SweepRadius, envFilter);
|
||||
xform.ValueRW.Position = newPos;
|
||||
|
||||
// Face the target (planar) for presentation.
|
||||
@@ -166,5 +176,38 @@ namespace ProjectM.Server
|
||||
playerEntities.Dispose();
|
||||
playerPositions.Dispose();
|
||||
}
|
||||
|
||||
// Swept collide-and-slide for server-authoritative Husk movement: sphere-cast the intended step against
|
||||
// the static environment (boundary ring + landmarks) and stop at / glance along the first wall hit. Closest-
|
||||
// hit SphereCast is non-generic -> Burst-safe (CLAUDE.md generic-collector hazard avoided). Y is held flat.
|
||||
static float3 SweptMove(in PhysicsWorldSingleton physics, float3 from, float3 to, float radius, CollisionFilter filter)
|
||||
{
|
||||
float3 delta = to - from;
|
||||
delta.y = 0f;
|
||||
float dist = math.length(delta);
|
||||
if (dist < 1e-5f)
|
||||
return to;
|
||||
float3 dir = delta / dist;
|
||||
const float skin = 0.05f;
|
||||
var cw = physics.CollisionWorld;
|
||||
if (!cw.SphereCast(from, radius, dir, dist, out var hit, filter))
|
||||
return to;
|
||||
|
||||
float allowed = math.max(0f, hit.Fraction * dist - skin);
|
||||
float3 stop = from + dir * allowed;
|
||||
stop.y = from.y;
|
||||
|
||||
// Slide the unused motion along the wall, then sweep the slide so we don't tunnel a second wall.
|
||||
float3 slide = EnemyAIMath.SlideVelocity(to - stop, hit.SurfaceNormal);
|
||||
float slideDist = math.length(slide);
|
||||
if (slideDist < 1e-5f)
|
||||
return stop;
|
||||
float3 sdir = slide / slideDist;
|
||||
float3 result = cw.SphereCast(stop, radius, sdir, slideDist, out var hit2, filter)
|
||||
? stop + sdir * math.max(0f, hit2.Fraction * slideDist - skin)
|
||||
: stop + slide;
|
||||
result.y = from.y;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"Unity.Mathematics",
|
||||
"Unity.Burst",
|
||||
"Unity.NetCode",
|
||||
"Unity.Networking.Transport"
|
||||
"Unity.Networking.Transport",
|
||||
"Unity.Physics"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
||||
@@ -35,6 +35,24 @@ namespace ProjectM.Simulation
|
||||
d.y = 0f;
|
||||
return math.lengthsq(d) <= range * range;
|
||||
}
|
||||
/// <summary>
|
||||
/// Projects a planar movement <paramref name="vel"/> onto a wall plane defined by <paramref name="surfaceNormal"/>
|
||||
/// (collide-and-slide): removes the component of <paramref name="vel"/> that pushes into the surface so the
|
||||
/// mover glances along the wall instead of stopping dead. Both inputs are flattened to the XZ plane (top-down).
|
||||
/// Returns <paramref name="vel"/> unchanged when the normal is degenerate.
|
||||
/// </summary>
|
||||
public static float3 SlideVelocity(float3 vel, float3 surfaceNormal)
|
||||
{
|
||||
surfaceNormal.y = 0f;
|
||||
float len = math.length(surfaceNormal);
|
||||
if (len < 1e-6f)
|
||||
return vel;
|
||||
float3 n = surfaceNormal / len;
|
||||
float3 slid = vel - math.dot(vel, n) * n;
|
||||
slid.y = 0f;
|
||||
return slid;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Deterministic planar ring position around <paramref name="center"/> for spawn
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Singleton holding the physics-filter mask of the static world-collision layer ("Environment"): the baked
|
||||
/// boundary ring + landmark colliders the player CC sweeps. Baked from the GameObject layer at edit-time (see
|
||||
/// WorldCollisionAuthoring) so server systems can build a <c>CollisionFilter</c> in Burst WITHOUT a managed
|
||||
/// <c>LayerMask.NameToLayer</c> call or a hardcoded layer index. Read by <see cref="ProjectM.Server"/>'s
|
||||
/// EnemyAISystem to sweep-test Husk movement against the environment only (never the player / other Husks).
|
||||
/// </summary>
|
||||
public struct WorldCollisionConfig : IComponentData
|
||||
{
|
||||
/// <summary>BelongsTo bitmask of the Environment physics layer (<c>1u << layerIndex</c>).</summary>
|
||||
public uint EnvironmentMask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70c75b96478d39f43aca221c926c9561
|
||||
Reference in New Issue
Block a user