Upload project

This commit is contained in:
Lauchstange committed 2025-01-30 17:20:27 +01:00
1 parent 6bbeab16d1
commit bf545f3baf
371 files changed
+91095

No files matched your search

@@ -0,0 +1,10 @@
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
[Serializable]
public struct CameraTarget : IComponentData
{
public Entity TargetEntity;
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 74ff9a59933a5494bbc1bb465a5f2180
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,22 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
[DisallowMultipleComponent]
public class CameraTargetAuthoring : MonoBehaviour
{
public GameObject Target;
public class Baker : Baker<CameraTargetAuthoring>
{
public override void Bake(CameraTargetAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new CameraTarget
{
TargetEntity = GetEntity(authoring.Target, TransformUsageFlags.Dynamic),
});
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d3a88336df575c34e80048c54a815367
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,44 @@
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[Serializable]
public struct OrbitCamera : IComponentData
{
public float RotationSpeed;
public float MaxVAngle;
public float MinVAngle;
public bool RotateWithCharacterParent;
public float MinDistance;
public float MaxDistance;
public float DistanceMovementSpeed;
public float DistanceMovementSharpness;
public float ObstructionRadius;
public float ObstructionInnerSmoothingSharpness;
public float ObstructionOuterSmoothingSharpness;
public bool PreventFixedUpdateJitter;
public float TargetDistance;
public float SmoothedTargetDistance;
public float ObstructedDistance;
public float PitchAngle;
public float3 PlanarForward;
}
[Serializable]
public struct OrbitCameraControl : IComponentData
{
public Entity FollowedCharacterEntity;
public float2 LookDegreesDelta;
public float ZoomDelta;
}
[Serializable]
public struct OrbitCameraIgnoredEntityBufferElement : IBufferElementData
{
public Entity Entity;
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 441900b2dca2cda429cba5f372fcf5fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,75 @@
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
[DisallowMultipleComponent]
public class OrbitCameraAuthoring : MonoBehaviour
{
[Header("Rotation")]
public float RotationSpeed = 2f;
public float MaxVAngle = 89f;
public float MinVAngle = -89f;
public bool RotateWithCharacterParent = true;
[Header("Distance")]
public float StartDistance = 5f;
public float MinDistance = 0f;
public float MaxDistance = 10f;
public float DistanceMovementSpeed = 1f;
public float DistanceMovementSharpness = 20f;
[Header("Obstructions")]
public float ObstructionRadius = 0.1f;
public float ObstructionInnerSmoothingSharpness = float.MaxValue;
public float ObstructionOuterSmoothingSharpness = 5f;
public bool PreventFixedUpdateJitter = true;
[Header("Misc")]
public List<GameObject> IgnoredEntities = new List<GameObject>();
public class Baker : Baker<OrbitCameraAuthoring>
{
public override void Bake(OrbitCameraAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic | TransformUsageFlags.WorldSpace);
AddComponent(entity, new OrbitCamera
{
RotationSpeed = authoring.RotationSpeed,
MaxVAngle = authoring.MaxVAngle,
MinVAngle = authoring.MinVAngle,
RotateWithCharacterParent = authoring.RotateWithCharacterParent,
MinDistance = authoring.MinDistance,
MaxDistance = authoring.MaxDistance,
DistanceMovementSpeed = authoring.DistanceMovementSpeed,
DistanceMovementSharpness = authoring.DistanceMovementSharpness,
ObstructionRadius = authoring.ObstructionRadius,
ObstructionInnerSmoothingSharpness = authoring.ObstructionInnerSmoothingSharpness,
ObstructionOuterSmoothingSharpness = authoring.ObstructionOuterSmoothingSharpness,
PreventFixedUpdateJitter = authoring.PreventFixedUpdateJitter,
TargetDistance = authoring.StartDistance,
SmoothedTargetDistance = authoring.StartDistance,
ObstructedDistance = authoring.StartDistance,
PitchAngle = 0f,
PlanarForward = -math.forward(),
});
AddComponent(entity, new OrbitCameraControl());
DynamicBuffer<OrbitCameraIgnoredEntityBufferElement> ignoredEntitiesBuffer = AddBuffer<OrbitCameraIgnoredEntityBufferElement>(entity);
for (int i = 0; i < authoring.IgnoredEntities.Count; i++)
{
ignoredEntitiesBuffer.Add(new OrbitCameraIgnoredEntityBufferElement
{
Entity = GetEntity(authoring.IgnoredEntities[i], TransformUsageFlags.None),
});
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 70c323b4be4b1fa4089d1a1fd2872754
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,367 @@
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Transforms;
using Unity.CharacterController;
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(FixedStepSimulationSystemGroup))]
[UpdateAfter(typeof(ThirdPersonPlayerVariableStepControlSystem))]
[UpdateAfter(typeof(ThirdPersonCharacterVariableUpdateSystem))]
[UpdateBefore(typeof(TransformSystemGroup))]
[BurstCompile]
public partial struct OrbitCameraSimulationSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<OrbitCamera, OrbitCameraControl>().Build());
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
OrbitCameraSimulationJob job = new OrbitCameraSimulationJob
{
DeltaTime = SystemAPI.Time.DeltaTime,
LocalTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(false),
ParentLookup = SystemAPI.GetComponentLookup<Parent>(true),
PostTransformMatrixLookup = SystemAPI.GetComponentLookup<PostTransformMatrix>(true),
CameraTargetLookup = SystemAPI.GetComponentLookup<CameraTarget>(true),
KinematicCharacterBodyLookup = SystemAPI.GetComponentLookup<KinematicCharacterBody>(true),
};
job.Schedule();
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct OrbitCameraSimulationJob : IJobEntity
{
public float DeltaTime;
public ComponentLookup<LocalTransform> LocalTransformLookup;
[ReadOnly] public ComponentLookup<Parent> ParentLookup;
[ReadOnly] public ComponentLookup<PostTransformMatrix> PostTransformMatrixLookup;
[ReadOnly] public ComponentLookup<CameraTarget> CameraTargetLookup;
[ReadOnly] public ComponentLookup<KinematicCharacterBody> KinematicCharacterBodyLookup;
void Execute(Entity entity, ref OrbitCamera orbitCamera, in OrbitCameraControl cameraControl)
{
if (OrbitCameraUtilities.TryGetCameraTargetSimulationWorldTransform(
cameraControl.FollowedCharacterEntity,
ref LocalTransformLookup,
ref ParentLookup,
ref PostTransformMatrixLookup,
ref CameraTargetLookup,
out float4x4 targetWorldTransform))
{
float3 targetUp = targetWorldTransform.Up();
float3 targetPosition = targetWorldTransform.Translation();
// Update planar forward based on target up direction and rotation from parent
{
quaternion tmpPlanarRotation = MathUtilities.CreateRotationWithUpPriority(targetUp, orbitCamera.PlanarForward);
// Rotation from character parent
if (orbitCamera.RotateWithCharacterParent &&
KinematicCharacterBodyLookup.TryGetComponent(cameraControl.FollowedCharacterEntity, out KinematicCharacterBody characterBody))
{
// Only consider rotation around the character up, since the camera is already adjusting itself to character up
quaternion planarRotationFromParent = characterBody.RotationFromParent;
KinematicCharacterUtilities.AddVariableRateRotationFromFixedRateRotation(ref tmpPlanarRotation, planarRotationFromParent, DeltaTime, characterBody.LastPhysicsUpdateDeltaTime);
}
orbitCamera.PlanarForward = MathUtilities.GetForwardFromRotation(tmpPlanarRotation);
}
// Yaw
float yawAngleChange = cameraControl.LookDegreesDelta.x * orbitCamera.RotationSpeed;
quaternion yawRotation = quaternion.Euler(targetUp * math.radians(yawAngleChange));
orbitCamera.PlanarForward = math.rotate(yawRotation, orbitCamera.PlanarForward);
// Pitch
orbitCamera.PitchAngle += -cameraControl.LookDegreesDelta.y * orbitCamera.RotationSpeed;
orbitCamera.PitchAngle = math.clamp(orbitCamera.PitchAngle, orbitCamera.MinVAngle, orbitCamera.MaxVAngle);
// Calculate final rotation
quaternion cameraRotation = OrbitCameraUtilities.CalculateCameraRotation(targetUp, orbitCamera.PlanarForward, orbitCamera.PitchAngle);
// Distance input
float desiredDistanceMovementFromInput = cameraControl.ZoomDelta * orbitCamera.DistanceMovementSpeed;
orbitCamera.TargetDistance = math.clamp(orbitCamera.TargetDistance + desiredDistanceMovementFromInput, orbitCamera.MinDistance, orbitCamera.MaxDistance);
// Calculate camera position (no smoothing or obstructions yet; these are done in the camera late update)
float3 cameraPosition = OrbitCameraUtilities.CalculateCameraPosition(targetPosition, cameraRotation, orbitCamera.TargetDistance);
// Write back to component
LocalTransformLookup[entity] = LocalTransform.FromPositionRotation(cameraPosition, cameraRotation);
}
}
}
}
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(TransformSystemGroup))]
[BurstCompile]
public partial struct OrbitCameraLateUpdateSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<PhysicsWorldSingleton>();
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<OrbitCamera, OrbitCameraControl>().Build());
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
OrbitCameraLateUpdateJob job = new OrbitCameraLateUpdateJob
{
DeltaTime = SystemAPI.Time.DeltaTime,
PhysicsWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().PhysicsWorld,
LocalToWorldLookup = SystemAPI.GetComponentLookup<LocalToWorld>(false),
CameraTargetLookup = SystemAPI.GetComponentLookup<CameraTarget>(true),
KinematicCharacterBodyLookup = SystemAPI.GetComponentLookup<KinematicCharacterBody>(true),
};
job.Schedule();
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct OrbitCameraLateUpdateJob : IJobEntity
{
public float DeltaTime;
[ReadOnly]
public PhysicsWorld PhysicsWorld;
public ComponentLookup<LocalToWorld> LocalToWorldLookup;
[ReadOnly]
public ComponentLookup<CameraTarget> CameraTargetLookup;
[ReadOnly]
public ComponentLookup<KinematicCharacterBody> KinematicCharacterBodyLookup;
void Execute(
Entity entity,
ref OrbitCamera orbitCamera,
in OrbitCameraControl cameraControl,
in DynamicBuffer<OrbitCameraIgnoredEntityBufferElement> ignoredEntitiesBuffer)
{
if (OrbitCameraUtilities.TryGetCameraTargetInterpolatedWorldTransform(
cameraControl.FollowedCharacterEntity,
ref LocalToWorldLookup,
ref CameraTargetLookup,
out LocalToWorld targetWorldTransform))
{
quaternion cameraRotation = OrbitCameraUtilities.CalculateCameraRotation(targetWorldTransform.Up, orbitCamera.PlanarForward, orbitCamera.PitchAngle);
float3 cameraForward = math.mul(cameraRotation, math.forward());
float3 targetPosition = targetWorldTransform.Position;
// Distance smoothing
orbitCamera.SmoothedTargetDistance = math.lerp(orbitCamera.SmoothedTargetDistance, orbitCamera.TargetDistance, MathUtilities.GetSharpnessInterpolant(orbitCamera.DistanceMovementSharpness, DeltaTime));
// Obstruction handling
// Obstruction detection is handled here, because we have to adjust the obstruction distance
// to match the interpolated physics body transform (as opposed to the "simulation" transform). Otherwise, a
// camera getting obstructed by a moving physics body would have visible jitter.
if (orbitCamera.ObstructionRadius > 0f)
{
float obstructionCheckDistance = orbitCamera.SmoothedTargetDistance;
CameraObstructionHitsCollector collector = new CameraObstructionHitsCollector(cameraControl.FollowedCharacterEntity, ignoredEntitiesBuffer, cameraForward);
PhysicsWorld.SphereCastCustom(
targetPosition,
orbitCamera.ObstructionRadius,
-cameraForward,
obstructionCheckDistance,
ref collector,
CollisionFilter.Default,
QueryInteraction.IgnoreTriggers);
float newObstructedDistance = obstructionCheckDistance;
if (collector.NumHits > 0)
{
newObstructedDistance = obstructionCheckDistance * collector.ClosestHit.Fraction;
// Redo cast with the interpolated body transform to prevent FixedUpdate jitter in obstruction detection
if (orbitCamera.PreventFixedUpdateJitter)
{
RigidBody hitBody = PhysicsWorld.Bodies[collector.ClosestHit.RigidBodyIndex];
if (LocalToWorldLookup.TryGetComponent(hitBody.Entity, out LocalToWorld hitBodyLocalToWorld))
{
// Adjust the rigidbody transform for interpolation, so we can raycast it in that state
hitBody.WorldFromBody = new RigidTransform(quaternion.LookRotationSafe(hitBodyLocalToWorld.Forward, hitBodyLocalToWorld.Up), hitBodyLocalToWorld.Position);
collector = new CameraObstructionHitsCollector(cameraControl.FollowedCharacterEntity, ignoredEntitiesBuffer, cameraForward);
hitBody.SphereCastCustom(
targetPosition,
orbitCamera.ObstructionRadius,
-cameraForward,
obstructionCheckDistance,
ref collector,
CollisionFilter.Default,
QueryInteraction.IgnoreTriggers);
if (collector.NumHits > 0)
{
newObstructedDistance = obstructionCheckDistance * collector.ClosestHit.Fraction;
}
}
}
}
// Update current distance based on obstructed distance
if (orbitCamera.ObstructedDistance < newObstructedDistance)
{
// Move outer
orbitCamera.ObstructedDistance = math.lerp(orbitCamera.ObstructedDistance, newObstructedDistance, MathUtilities.GetSharpnessInterpolant(orbitCamera.ObstructionOuterSmoothingSharpness, DeltaTime));
}
else if (orbitCamera.ObstructedDistance > newObstructedDistance)
{
// Move inner
orbitCamera.ObstructedDistance = math.lerp(orbitCamera.ObstructedDistance, newObstructedDistance, MathUtilities.GetSharpnessInterpolant(orbitCamera.ObstructionInnerSmoothingSharpness, DeltaTime));
}
}
else
{
orbitCamera.ObstructedDistance = orbitCamera.SmoothedTargetDistance;
}
// Place camera at the final distance (includes smoothing and obstructions)
float3 cameraPosition = OrbitCameraUtilities.CalculateCameraPosition(targetPosition, cameraRotation, orbitCamera.ObstructedDistance);
// Write to LtW
LocalToWorldLookup[entity] = new LocalToWorld { Value = new float4x4(cameraRotation, cameraPosition) };
}
}
}
}
public static class OrbitCameraUtilities
{
public static bool TryGetCameraTargetSimulationWorldTransform(
Entity targetCharacterEntity,
ref ComponentLookup<LocalTransform> localTransformLookup,
ref ComponentLookup<Parent> parentLookup,
ref ComponentLookup<PostTransformMatrix> postTransformMatrixLookup,
ref ComponentLookup<CameraTarget> cameraTargetLookup,
out float4x4 worldTransform)
{
bool foundValidCameraTarget = false;
worldTransform = float4x4.identity;
// Camera target is either defined by the CameraTarget component, or if not, the transform of the followed character
if (cameraTargetLookup.TryGetComponent(targetCharacterEntity, out CameraTarget cameraTarget) &&
localTransformLookup.HasComponent(cameraTarget.TargetEntity))
{
TransformHelpers.ComputeWorldTransformMatrix(
cameraTarget.TargetEntity,
out worldTransform,
ref localTransformLookup,
ref parentLookup,
ref postTransformMatrixLookup);
foundValidCameraTarget = true;
}
else if (localTransformLookup.TryGetComponent(targetCharacterEntity, out LocalTransform characterLocalTransform))
{
worldTransform = float4x4.TRS(characterLocalTransform.Position, characterLocalTransform.Rotation, 1f);
foundValidCameraTarget = true;
}
return foundValidCameraTarget;
}
public static bool TryGetCameraTargetInterpolatedWorldTransform(
Entity targetCharacterEntity,
ref ComponentLookup<LocalToWorld> localToWorldLookup,
ref ComponentLookup<CameraTarget> cameraTargetLookup,
out LocalToWorld worldTransform)
{
bool foundValidCameraTarget = false;
worldTransform = default;
// Get the interpolated transform of the target
if (cameraTargetLookup.TryGetComponent(targetCharacterEntity, out CameraTarget cameraTarget) &&
localToWorldLookup.TryGetComponent(cameraTarget.TargetEntity, out worldTransform))
{
foundValidCameraTarget = true;
}
else if (localToWorldLookup.TryGetComponent(targetCharacterEntity, out worldTransform))
{
foundValidCameraTarget = true;
}
return foundValidCameraTarget;
}
public static quaternion CalculateCameraRotation(float3 targetUp, float3 planarForward, float pitchAngle)
{
quaternion pitchRotation = quaternion.Euler(math.right() * math.radians(pitchAngle));
quaternion cameraRotation = MathUtilities.CreateRotationWithUpPriority(targetUp, planarForward);
cameraRotation = math.mul(cameraRotation, pitchRotation);
return cameraRotation;
}
public static float3 CalculateCameraPosition(float3 targetPosition, quaternion cameraRotation, float distance)
{
return targetPosition + (-MathUtilities.GetForwardFromRotation(cameraRotation) * distance);
}
}
public struct CameraObstructionHitsCollector : ICollector<ColliderCastHit>
{
public bool EarlyOutOnFirstHit => false;
public float MaxFraction => 1f;
public int NumHits { get; private set; }
public ColliderCastHit ClosestHit;
private float _closestHitFraction;
private float3 _cameraDirection;
private Entity _followedCharacter;
private DynamicBuffer<OrbitCameraIgnoredEntityBufferElement> _ignoredEntitiesBuffer;
public CameraObstructionHitsCollector(Entity followedCharacter, DynamicBuffer<OrbitCameraIgnoredEntityBufferElement> ignoredEntitiesBuffer, float3 cameraDirection)
{
NumHits = 0;
ClosestHit = default;
_closestHitFraction = float.MaxValue;
_cameraDirection = cameraDirection;
_followedCharacter = followedCharacter;
_ignoredEntitiesBuffer = ignoredEntitiesBuffer;
}
public bool AddHit(ColliderCastHit hit)
{
if (_followedCharacter == hit.Entity)
{
return false;
}
if (math.dot(hit.SurfaceNormal, _cameraDirection) < 0f || !PhysicsUtilities.IsCollidable(hit.Material))
{
return false;
}
for (int i = 0; i < _ignoredEntitiesBuffer.Length; i++)
{
if (_ignoredEntitiesBuffer[i].Entity == hit.Entity)
{
return false;
}
}
// Process valid hit
if (hit.Fraction < _closestHitFraction)
{
_closestHitFraction = hit.Fraction;
ClosestHit = hit;
}
NumHits++;
return true;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6d6d36bc694f390408f5683be8de8c7d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: