CharacterControllerSamples PDF
CharacterControllerSamples PDF
md
Unity.CharacterController Samples Copyright (c) 2022 Unity Technologies ApS
Licensed under the Unity Companion License for Unity-dependent projects--see Unity Companion License.
Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS†BASIS WITHOUT
WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions.
README.md
Unity.CharacterController Samples
You can either checkout this repository using git, or download it using the Download ZIP option under the Code button at the top of this page.
Once you have these projects on your computer, you can open each one of them using the appropriate Unity version.
Character Controller package documentation
Consult the Tutorial section for a guided tutorial on how to customize a character controller in various ways.
Consult the Samples section for an overview of the various sample projects.
Basic/Assets/Data/Input/BasicInputActions.cs
//------------------------------------------------------------------------------
// <auto-generated>
// This code was auto-generated by com.unity.inputsystem:InputActionCodeGenerator
// version 1.5.1
// from Assets/Data/Input/BasicInputActions.inputactions
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Utilities;
[Serializable]
public struct CameraTarget : IComponentData
{
public Entity TargetEntity;
}
Basic/Assets/Scripts/Camera/CameraTargetAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
[DisallowMultipleComponent]
public class CameraTargetAuthoring : MonoBehaviour
{
public GameObject Target;
[Serializable]
public struct MainEntityCamera : IComponentData
{
}
Basic/Assets/Scripts/Camera/MainEntityCameraAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
[DisallowMultipleComponent]
public class MainEntityCameraAuthoring : MonoBehaviour
{
public class Baker : Baker<MainEntityCameraAuthoring>
{
public override void Bake(MainEntityCameraAuthoring authoring)
{
AddComponent<MainEntityCamera>(GetEntity(TransformUsageFlags.Dynamic));
}
}
}
Basic/Assets/Scripts/Camera/MainGameObjectCamera.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
void Awake()
{
Instance = GetComponent<UnityEngine.Camera>();
}
}
Basic/Assets/Scripts/Camera/OrbitCamera.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[Serializable]
public struct OrbitCamera : IComponentData
{
[Header("Rotation")]
public float RotationSpeed;
public float MaxVAngle;
public float MinVAngle;
public bool RotateWithCharacterParent;
[Header("Distance")]
public float TargetDistance;
public float MinDistance;
public float MaxDistance;
public float DistanceMovementSpeed;
public float DistanceMovementSharpness;
[Header("Obstructions")]
public float ObstructionRadius;
public float ObstructionInnerSmoothingSharpness;
public float ObstructionOuterSmoothingSharpness;
public bool PreventFixedUpdateJitter;
// Data in calculations
[HideInInspector]
public float CurrentDistanceFromMovement;
[HideInInspector]
public float CurrentDistanceFromObstruction;
[HideInInspector]
public float PitchAngle;
[HideInInspector]
public float3 PlanarForward;
TargetDistance = 5f,
MinDistance = 0f,
MaxDistance = 10f,
DistanceMovementSpeed = 50f,
DistanceMovementSharpness = 20f,
ObstructionRadius = 0.1f,
ObstructionInnerSmoothingSharpness = float.MaxValue,
ObstructionOuterSmoothingSharpness = 5f,
PreventFixedUpdateJitter = true,
CurrentDistanceFromObstruction = 0f,
};
return c;
}
}
[Serializable]
public struct OrbitCameraControl : IComponentData
{
public Entity FollowedCharacterEntity;
public float2 Look;
public float Zoom;
}
[Serializable]
public struct OrbitCameraIgnoredEntityBufferElement : IBufferElementData
{
public Entity Entity;
}
Basic/Assets/Scripts/Camera/OrbitCameraAuthoring.cs
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
[DisallowMultipleComponent]
public class OrbitCameraAuthoring : MonoBehaviour
{
public List<GameObject> IgnoredEntities = new List<GameObject>();
public OrbitCamera OrbitCamera = OrbitCamera.GetDefault();
AddComponent(selfEntity, authoring.OrbitCamera);
AddComponent(selfEntity, new OrbitCameraControl());
DynamicBuffer<OrbitCameraIgnoredEntityBufferElement> ignoredEntitiesBuffer = AddBuffer<OrbitCameraIgnoredEntityBufferElement>(selfEntity);
for (int i = 0; i < authoring.IgnoredEntities.Count; i++)
{
ignoredEntitiesBuffer.Add(new OrbitCameraIgnoredEntityBufferElement
{
Entity = GetEntity(authoring.IgnoredEntities[i], TransformUsageFlags.None),
});
}
}
}
}
Basic/Assets/Scripts/Camera/OrbitCameraSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Systems;
using Unity.Transforms;
using Unity.CharacterController;
[UpdateAfter(typeof(TransformSystemGroup))]
[UpdateBefore(typeof(EndSimulationEntityCommandBufferSystem))]
public partial struct OrbitCameraSystem : ISystem
{
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;
}
}
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<PhysicsWorldSingleton>();
}
public void OnDestroy(ref SystemState state)
{
}
public void OnUpdate(ref SystemState state)
{
OrbitCameraJob job = new OrbitCameraJob
{
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]
public partial struct OrbitCameraJob : IJobEntity
{
public float DeltaTime;
public PhysicsWorld PhysicsWorld;
public ComponentLookup<LocalToWorld> LocalToWorldLookup;
[ReadOnly] public ComponentLookup<CameraTarget> CameraTargetLookup;
[ReadOnly] public ComponentLookup<KinematicCharacterBody> KinematicCharacterBodyLookup;
void Execute(
Entity entity,
ref LocalTransform localTransform,
ref OrbitCamera orbitCamera,
in OrbitCameraControl cameraControl,
in DynamicBuffer<OrbitCameraIgnoredEntityBufferElement> ignoredEntitiesBuffer)
{
// if there is a followed entity, place the camera relatively to it
if (LocalToWorldLookup.TryGetComponent(cameraControl.FollowedCharacterEntity, out LocalToWorld characterLTW))
{
// Select the real camera target
LocalToWorld targetEntityLocalToWorld = default;
if (CameraTargetLookup.TryGetComponent(cameraControl.FollowedCharacterEntity, out CameraTarget cameraTarget) &&
LocalToWorldLookup.TryGetComponent(cameraTarget.TargetEntity, out LocalToWorld camTargetLTW))
{
targetEntityLocalToWorld = camTargetLTW;
}
else
{
targetEntityLocalToWorld = characterLTW;
}
// Rotation
{
localTransform.Rotation = quaternion.LookRotationSafe(orbitCamera.PlanarForward, targetEntityLocalToWorld.Up);
// Handle rotating the camera along with character's parent entity (moving platform)
if (orbitCamera.RotateWithCharacterParent && KinematicCharacterBodyLookup.TryGetComponent(cameraControl.FollowedCharacterEntity, out KinematicCharacterBody characterBod
{
KinematicCharacterUtilities.AddVariableRateRotationFromFixedRateRotation(ref localTransform.Rotation, characterBody.RotationFromParent, DeltaTime, characterBody
orbitCamera.PlanarForward = math.normalizesafe(MathUtilities.ProjectOnPlane(MathUtilities.GetForwardFromRotation(localTransform.Rotation), targetEntityLocalToWorld
}
// Yaw
float yawAngleChange = cameraControl.Look.x * orbitCamera.RotationSpeed;
quaternion yawRotation = quaternion.Euler(targetEntityLocalToWorld.Up * math.radians(yawAngleChange));
orbitCamera.PlanarForward = math.rotate(yawRotation, orbitCamera.PlanarForward);
// Pitch
orbitCamera.PitchAngle += -cameraControl.Look.y * orbitCamera.RotationSpeed;
orbitCamera.PitchAngle = math.clamp(orbitCamera.PitchAngle, orbitCamera.MinVAngle, orbitCamera.MaxVAngle);
quaternion pitchRotation = quaternion.Euler(math.right() * math.radians(orbitCamera.PitchAngle));
// Final rotation
localTransform.Rotation = quaternion.LookRotationSafe(orbitCamera.PlanarForward, targetEntityLocalToWorld.Up);
localTransform.Rotation = math.mul(localTransform.Rotation, pitchRotation);
}
float3 cameraForward = MathUtilities.GetForwardFromRotation(localTransform.Rotation);
// Distance input
float desiredDistanceMovementFromInput = cameraControl.Zoom * orbitCamera.DistanceMovementSpeed;
orbitCamera.TargetDistance = math.clamp(orbitCamera.TargetDistance + desiredDistanceMovementFromInput, orbitCamera.MinDistance, orbitCamera.MaxDistance);
orbitCamera.CurrentDistanceFromMovement = math.lerp(orbitCamera.CurrentDistanceFromMovement, orbitCamera.TargetDistance, MathUtilities.GetSharpnessInterpolant(orbitCamera
// Obstructions
if (orbitCamera.ObstructionRadius > 0f)
{
float obstructionCheckDistance = orbitCamera.CurrentDistanceFromMovement;
CameraObstructionHitsCollector collector = new CameraObstructionHitsCollector(cameraControl.FollowedCharacterEntity, ignoredEntitiesBuffer, cameraForward);
PhysicsWorld.SphereCastCustom<CameraObstructionHitsCollector>(
targetEntityLocalToWorld.Position,
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))
{
hitBody.WorldFromBody = new RigidTransform(quaternion.LookRotationSafe(hitBodyLocalToWorld.Forward, hitBodyLocalToWorld.Up), hitBodyLocalToWorld.Position
collector = new CameraObstructionHitsCollector(cameraControl.FollowedCharacterEntity, ignoredEntitiesBuffer, cameraForward);
hitBody.SphereCastCustom<CameraObstructionHitsCollector>(
targetEntityLocalToWorld.Position,
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.CurrentDistanceFromObstruction < newObstructedDistance)
{
// Move outer
orbitCamera.CurrentDistanceFromObstruction = math.lerp(orbitCamera.CurrentDistanceFromObstruction, newObstructedDistance, MathUtilities.GetSharpnessInterpolant
}
else if (orbitCamera.CurrentDistanceFromObstruction > newObstructedDistance)
{
// Move inner
orbitCamera.CurrentDistanceFromObstruction = math.lerp(orbitCamera.CurrentDistanceFromObstruction, newObstructedDistance, MathUtilities.GetSharpnessInterpolant
}
}
else
{
orbitCamera.CurrentDistanceFromObstruction = orbitCamera.CurrentDistanceFromMovement;
}
// Calculate final camera position from targetposition + rotation + distance
localTransform.Position = targetEntityLocalToWorld.Position + (-cameraForward * orbitCamera.CurrentDistanceFromObstruction);
// Manually calculate the LocalToWorld since this is updating after the Transform systems, and the LtW is what rendering uses
LocalToWorld cameraLocalToWorld = new LocalToWorld();
cameraLocalToWorld.Value = new float4x4(localTransform.Rotation, localTransform.Position);
LocalToWorldLookup[entity] = cameraLocalToWorld;
}
}
}
}
Basic/Assets/Scripts/Character/BasicAICharacter.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
[Serializable]
public struct BasicAICharacter : IComponentData
{
public float MovementPeriod;
public float3 MovementDirection;
}
Basic/Assets/Scripts/Character/BasicAICharacterAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
return true;
}
public bool IsGroundedOnHit(
ref BasicCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
in BasicHit hit,
int groundingEvaluationType)
{
BasicCharacterComponent characterComponent = CharacterComponent.ValueRO;
// Ignore grounding
if (PhysicsUtilities.HasPhysicsTag(in baseContext.PhysicsWorld, hit.RigidBodyIndex, characterComponent.IgnoreGroundingTag))
{
return false;
}
// Ignore step handling
if (characterComponent.StepAndSlopeHandling.StepHandling && PhysicsUtilities.HasPhysicsTag(in baseContext.PhysicsWorld, hit.RigidBodyIndex, characterComponent.IgnoreStepHandlingTag
{
characterComponent.StepAndSlopeHandling.StepHandling = false;
}
return CharacterAspect.Default_IsGroundedOnHit(
in this,
ref context,
ref baseContext,
in hit,
in characterComponent.StepAndSlopeHandling,
groundingEvaluationType);
}
public void OnMovementHit(
ref BasicCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
ref KinematicCharacterHit hit,
ref float3 remainingMovementDirection,
ref float remainingMovementLength,
float3 originalVelocityDirection,
float hitDistance)
{
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref float3 characterPosition = ref CharacterAspect.LocalTransform.ValueRW.Position;
BasicCharacterComponent characterComponent = CharacterComponent.ValueRO;
// Ignore step handling
if (characterComponent.StepAndSlopeHandling.StepHandling && PhysicsUtilities.HasPhysicsTag(in baseContext.PhysicsWorld, hit.RigidBodyIndex, characterComponent.IgnoreStepHandlingTag
{
characterComponent.StepAndSlopeHandling.StepHandling = false;
}
CharacterAspect.Default_OnMovementHit(
in this,
ref context,
ref baseContext,
ref characterBody,
ref characterPosition,
ref hit,
ref remainingMovementDirection,
ref remainingMovementLength,
originalVelocityDirection,
hitDistance,
characterComponent.StepAndSlopeHandling.StepHandling,
characterComponent.StepAndSlopeHandling.MaxStepHeight,
characterComponent.StepAndSlopeHandling.CharacterWidthForStepGroundingCheck);
}
public void OverrideDynamicHitMasses(
ref BasicCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
ref PhysicsMass characterMass,
ref PhysicsMass otherMass,
BasicHit hit)
{
BasicCharacterComponent characterComponent = CharacterComponent.ValueRO;
if (PhysicsUtilities.HasPhysicsTag(in baseContext.PhysicsWorld, hit.RigidBodyIndex, characterComponent.ZeroMassAgainstCharacterTag))
{
characterMass.InverseMass = 0f;
characterMass.InverseInertia = new float3(0f);
otherMass.InverseMass = 1f;
otherMass.InverseInertia = new float3(1f);
}
if (PhysicsUtilities.HasPhysicsTag(in baseContext.PhysicsWorld, hit.RigidBodyIndex, characterComponent.InfiniteMassAgainstCharacterTag))
{
characterMass.InverseMass = 1f;
characterMass.InverseInertia = new float3(1f);
otherMass.InverseMass = 0f;
otherMass.InverseInertia = new float3(0f);
}
}
public void ProjectVelocityOnHits(
ref BasicCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
ref float3 velocity,
ref bool characterIsGrounded,
ref BasicHit characterGroundHit,
in DynamicBuffer<KinematicVelocityProjectionHit> velocityProjectionHits,
float3 originalVelocityDirection)
{
BasicCharacterComponent characterComponent = CharacterComponent.ValueRO;
var latestHit = velocityProjectionHits[velocityProjectionHits.Length - 1];
if (context.BouncySurfaceLookup.HasComponent(latestHit.Entity))
{
BouncySurface bouncySurface = context.BouncySurfaceLookup[latestHit.Entity];
velocity = math.reflect(velocity, latestHit.Normal);
velocity *= bouncySurface.BounceEnergyMultiplier;
}
else
{
CharacterAspect.Default_ProjectVelocityOnHits(
ref velocity,
ref characterIsGrounded,
ref characterGroundHit,
in velocityProjectionHits,
originalVelocityDirection,
characterComponent.StepAndSlopeHandling.ConstrainVelocityToGroundPlane);
}
}
#endregion
}
Basic/Assets/Scripts/Character/BasicCharacterAuthoring.cs
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEngine;
using Unity.CharacterController;
using Unity.Physics;
using UnityEngine.Serialization;
[DisallowMultipleComponent]
public class BasicCharacterAuthoring : MonoBehaviour
{
public AuthoringKinematicCharacterProperties CharacterProperties = AuthoringKinematicCharacterProperties.GetDefault();
public BasicCharacterComponent Character = BasicCharacterComponent.GetDefault();
AddComponent(GetEntity(TransformUsageFlags.Dynamic), authoring.Character);
AddComponent(GetEntity(TransformUsageFlags.Dynamic), new BasicCharacterControl());
}
}
}
Basic/Assets/Scripts/Character/BasicCharacterComponent.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using Unity.CharacterController;
using Unity.Physics.Authoring;
[Serializable]
public struct BasicCharacterComponent : IComponentData
{
[Header("Movement")]
public float RotationSharpness;
public float GroundMaxSpeed;
public float GroundedMovementSharpness;
public float AirAcceleration;
public float AirMaxSpeed;
public float AirDrag;
public float JumpSpeed;
public float3 Gravity;
public bool PreventAirAccelerationAgainstUngroundedHits;
public int MaxJumpsInAir;
public BasicStepAndSlopeHandlingParameters StepAndSlopeHandling;
[Header("Tags")]
public CustomPhysicsBodyTags IgnoreCollisionsTag;
public CustomPhysicsBodyTags IgnoreGroundingTag;
public CustomPhysicsBodyTags ZeroMassAgainstCharacterTag;
public CustomPhysicsBodyTags InfiniteMassAgainstCharacterTag;
public CustomPhysicsBodyTags IgnoreStepHandlingTag;
[NonSerialized]
public int CurrentJumpsInAir;
[UpdateInGroup(typeof(KinematicCharacterPhysicsUpdateGroup))]
[BurstCompile]
public partial struct BasicCharacterPhysicsUpdateSystem : ISystem
{
private EntityQuery _characterQuery;
private BasicCharacterUpdateContext _context;
private KinematicCharacterUpdateContext _baseContext;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
_characterQuery = KinematicCharacterUtilities.GetBaseCharacterQueryBuilder()
.WithAll<
BasicCharacterComponent,
BasicCharacterControl>()
.Build(ref state);
state.RequireForUpdate(_characterQuery);
state.RequireForUpdate<PhysicsWorldSingleton>();
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
_context.OnSystemUpdate(ref state);
_baseContext.OnSystemUpdate(ref state, SystemAPI.Time, SystemAPI.GetSingleton<PhysicsWorldSingleton>());
[BurstCompile]
public partial struct BasicCharacterPhysicsUpdateJob : IJobEntity, IJobEntityChunkBeginEnd
{
public BasicCharacterUpdateContext Context;
public KinematicCharacterUpdateContext BaseContext;
public bool OnChunkBegin(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
BaseContext.EnsureCreationOfTmpCollections();
return true;
}
public void OnChunkEnd(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask, bool chunkWasExecuted)
{ }
}
}
[UpdateInGroup(typeof(KinematicCharacterVariableUpdateGroup))]
[BurstCompile]
public partial struct BasicCharacterVariableUpdateSystem : ISystem
{
private EntityQuery _characterQuery;
private BasicCharacterUpdateContext _context;
private KinematicCharacterUpdateContext _baseContext;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
_characterQuery = KinematicCharacterUtilities.GetBaseCharacterQueryBuilder()
.WithAll<
BasicCharacterComponent,
BasicCharacterControl>()
.Build(ref state);
_context = new BasicCharacterUpdateContext();
_context.OnSystemCreate(ref state);
_baseContext = new KinematicCharacterUpdateContext();
_baseContext.OnSystemCreate(ref state);
state.RequireForUpdate(_characterQuery);
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
_context.OnSystemUpdate(ref state);
_baseContext.OnSystemUpdate(ref state, SystemAPI.Time, SystemAPI.GetSingleton<PhysicsWorldSingleton>());
BasicCharacterVariableUpdateJob job = new BasicCharacterVariableUpdateJob
{
Context = _context,
BaseContext = _baseContext,
};
job.ScheduleParallel();
}
[BurstCompile]
public partial struct BasicCharacterVariableUpdateJob : IJobEntity, IJobEntityChunkBeginEnd
{
public BasicCharacterUpdateContext Context;
public KinematicCharacterUpdateContext BaseContext;
public bool OnChunkBegin(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
BaseContext.EnsureCreationOfTmpCollections();
return true;
}
public void OnChunkEnd(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask, bool chunkWasExecuted)
{ }
}
}
Basic/Assets/Scripts/Misc/BouncySurface.cs
using System;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[Serializable]
public struct BouncySurface : IComponentData
{
public float BounceEnergyMultiplier;
}
Basic/Assets/Scripts/Misc/BouncySurfaceAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
return false;
}
}
Basic/Assets/Scripts/Misc/FixedTickSystem.cs
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using Unity.Burst;
using Unity.Entities;
using UnityEngine;
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
if (!SystemAPI.HasSingleton<Singleton>())
{
Entity singletonEntity = state.EntityManager.CreateEntity();
state.EntityManager.AddComponentData(singletonEntity, new Singleton());
}
_framesDeltaSum = 0f;
_framesCount = 0;
_minDeltaTimeForAvg = Mathf.Infinity;
_maxDeltaTimeForAvg = Mathf.NegativeInfinity;
}
}
void Update()
{
// show hide
if (Input.GetKeyDown(KeyCode.F1))
{
MainCanvas.gameObject.SetActive(!MainCanvas.gameObject.activeSelf);
}
if (Input.GetKeyDown(KeyCode.F3))
{
_hasVSync = !_hasVSync;
UpdateRenderSettings();
}
// FPS
_framerateCalculator.Update();
if (Time.time >= _lastTimePolledFPS + FPSPollRate)
{
_framerateCalculator.PollFramerate(out string avg, out string worst, out string best);
AvgFPS.text = avg;
WorstFPS.text = worst;
BestFPS.text = best;
_lastTimePolledFPS = Time.time;
}
}
private void UpdateRenderSettings()
{
QualitySettings.vSyncCount = _hasVSync ? 1 : 0;
}
}
Basic/Assets/Scripts/Misc/FramerateSetter.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
void Start()
{
Application.targetFrameRate = Framerate;
[BurstCompile]
public partial struct PrefabThrowerSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{ }
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
if (Input.GetKeyDown(KeyCode.Return))
{
PrefabThrowerJob job = new PrefabThrowerJob
{
ECB = SystemAPI.GetSingletonRW<EndSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged),
};
job.Schedule();
}
}
[BurstCompile]
public partial struct PrefabThrowerJob : IJobEntity
{
public EntityCommandBuffer ECB;
void Execute(ref PrefabThrower prefabThrower, in LocalToWorld localToWorld)
{
Entity spawnedEntity = ECB.Instantiate(prefabThrower.PrefabEntity);
ECB.SetComponent(spawnedEntity, new LocalTransform { Position = localToWorld.Position, Rotation = quaternion.Euler(prefabThrower.InitialEulerAngles), Scale = 1f});
ECB.SetComponent(spawnedEntity, new PhysicsVelocity { Linear = localToWorld.Forward * prefabThrower.ThrowForce });
}
}
}
Basic/Assets/Scripts/Misc/SceneInitialization.cs
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[System.Serializable]
public struct SceneInitialization : IComponentData
{
public Entity CharacterSpawnPointEntity;
public Entity CharacterPrefabEntity;
public Entity CameraPrefabEntity;
public Entity PlayerPrefabEntity;
}
Basic/Assets/Scripts/Misc/SceneInitializationAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
[UpdateInGroup(typeof(InitializationSystemGroup))]
[BurstCompile]
public partial struct SceneInitializationSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{ }
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
// Game init
if (SystemAPI.HasSingleton<SceneInitialization>())
{
ref SceneInitialization sceneInitializer = ref SystemAPI.GetSingletonRW<SceneInitialization>().ValueRW;
// Cursor
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
// Spawn player
Entity playerEntity = state.EntityManager.Instantiate(sceneInitializer.PlayerPrefabEntity);
// Spawn camera
Entity cameraEntity = state.EntityManager.Instantiate(sceneInitializer.CameraPrefabEntity);
// Assign camera & character to player
BasicPlayer player = SystemAPI.GetComponent<BasicPlayer>(playerEntity);
player.ControlledCharacter = characterEntity;
player.ControlledCamera = cameraEntity;
SystemAPI.SetComponent(playerEntity, player);
state.EntityManager.DestroyEntity(SystemAPI.GetSingletonEntity<SceneInitialization>());
}
}
}
Basic/Assets/Scripts/Misc/SelfDestructAfterTime.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[Serializable]
public struct SelfDestructAfterTime : IComponentData
{
public float LifeTime;
public float TimeSinceAlive;
}
Basic/Assets/Scripts/Misc/SelfDestructAfterTimeAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
[BurstCompile]
public partial struct SelfDestructAfterTimeSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
SelfDestructAfterTimeJob job = new SelfDestructAfterTimeJob
{
DeltaTime = SystemAPI.Time.DeltaTime,
ECB = SystemAPI.GetSingletonRW<EndSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged),
};
job.Schedule();
}
[BurstCompile]
public partial struct SelfDestructAfterTimeJob : IJobEntity
{
public float DeltaTime;
public EntityCommandBuffer ECB;
[Serializable]
public struct Teleporter : IComponentData
{
public Entity DestinationEntity;
}
Basic/Assets/Scripts/Misc/TeleporterAuthoring.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[DisallowMultipleComponent]
public class TeleporterAuthoring : MonoBehaviour
{
public GameObject Destination;
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
TeleporterJob job = new TeleporterJob
{
LocalTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(false),
CharacterBodyLookup = SystemAPI.GetComponentLookup<KinematicCharacterBody>(true),
CharacterInterpolationLookup = SystemAPI.GetComponentLookup<CharacterInterpolation>(false),
};
job.Schedule();
}
[BurstCompile]
public partial struct TeleporterJob : IJobEntity
{
public ComponentLookup<LocalTransform> LocalTransformLookup;
[ReadOnly] public ComponentLookup<KinematicCharacterBody> CharacterBodyLookup;
public ComponentLookup<CharacterInterpolation> CharacterInterpolationLookup;
[Serializable]
public struct TestMovingPlatform : IComponentData
{
[Serializable]
public struct AuthoringData
{
public float3 TranslationAxis;
public float TranslationAmplitude;
public float TranslationSpeed;
public float3 RotationAxis;
public float RotationSpeed;
public float3 OscillationAxis;
public float OscillationAmplitude;
public float OscillationSpeed;
}
[DisallowMultipleComponent]
public class TestMovingPlatformAuthoring : MonoBehaviour
{
public TestMovingPlatform.AuthoringData MovingPlatform;
public class Baker : Baker<TestMovingPlatformAuthoring>
{
public override void Bake(TestMovingPlatformAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new TestMovingPlatform
{
Data = authoring.MovingPlatform,
OriginalPosition = authoring.transform.position,
OriginalRotation = authoring.transform.rotation,
});
}
}
}
Basic/Assets/Scripts/Misc/TestMovingPlatformSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Systems;
using Unity.Physics.Extensions;
using Unity.Transforms;
using Unity.CharacterController;
[UpdateInGroup(typeof(BeforePhysicsSystemGroup))]
[BurstCompile]
public partial struct TestMovingPlatformSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
float deltaTime = SystemAPI.Time.DeltaTime;
if (deltaTime <= 0f)
return;
TestMovingPlatformJob job = new TestMovingPlatformJob
{
Time = (float)SystemAPI.Time.ElapsedTime,
InvDeltaTime = 1f / deltaTime,
};
job.Schedule();
}
[BurstCompile]
public partial struct TestMovingPlatformJob : IJobEntity
{
public float Time;
public float InvDeltaTime;
void Execute(Entity entity, ref PhysicsVelocity physicsVelocity, in PhysicsMass physicsMass, in LocalTransform localTransform, in TestMovingPlatform movingPlatform)
{
float3 targetPos = movingPlatform.OriginalPosition + (math.normalizesafe(movingPlatform.Data.TranslationAxis) * math.sin(Time * movingPlatform.Data.TranslationSpeed) *
quaternion rotationFromRotation = quaternion.Euler(math.normalizesafe(movingPlatform.Data.RotationAxis) * movingPlatform.Data.RotationSpeed * Time);
quaternion rotationFromOscillation = quaternion.Euler(math.normalizesafe(movingPlatform.Data.OscillationAxis) * (math.sin(Time * movingPlatform.Data.OscillationSpeed)
quaternion totalRotation = math.mul(rotationFromRotation, rotationFromOscillation);
quaternion targetRot = math.mul(totalRotation, movingPlatform.OriginalRotation);
RigidTransform targetTransform = new RigidTransform(targetRot, targetPos);
physicsVelocity = PhysicsVelocity.CalculateVelocityToTarget(in physicsMass, localTransform.Position, localTransform.Rotation, in targetTransform, InvDeltaTime);
}
}
}
Basic/Assets/Scripts/Misc/Vehicle.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
[Serializable]
public struct Vehicle : IComponentData
{
public float MaxSpeed;
public float Acceleration;
public float MaxRotationSpeed;
public float RotationAcceleration;
[DisallowMultipleComponent]
public class VehicleAuthoring : MonoBehaviour
{
public Vehicle Vehicle;
public List<GameObject> Wheels;
AddComponent(entity, authoring.Vehicle);
DynamicBuffer<VehicleWheels> wheelsBuffer = AddBuffer<VehicleWheels>(entity);
foreach (GameObject wheelGO in authoring.Wheels)
{
wheelsBuffer.Add(new VehicleWheels {
MeshEntity = GetEntity(wheelGO.GetComponentInChildren<MeshRenderer>().gameObject, TransformUsageFlags.Dynamic),
CollisionEntity = GetEntity(wheelGO.GetComponentInChildren<PhysicsShapeAuthoring>().gameObject, TransformUsageFlags.Dynamic),
});
}
}
}
}
Basic/Assets/Scripts/Misc/VehicleSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Extensions;
using Unity.Physics.Systems;
using Unity.Transforms;
using UnityEngine;
using Unity.CharacterController;
[UpdateInGroup(typeof(AfterPhysicsSystemGroup))]
[UpdateAfter(typeof(KinematicCharacterPhysicsUpdateGroup))]
[BurstCompile]
public partial struct VehicleSystem : ISystem
{
public struct WheelHitCollector<T> : ICollector<T> where T : struct, IQueryResult
{
public bool EarlyOutOnFirstHit => false;
public float MaxFraction => 1f;
public int NumHits { get; set; }
public T ClosestHit;
private Entity _wheelEntity;
private Entity _vehicleEntity;
private float _closestHitFraction;
public void Init(Entity wheelEntity, Entity vehicleEntity)
{
_wheelEntity = wheelEntity;
_vehicleEntity = vehicleEntity;
_closestHitFraction = float.MaxValue;
}
public bool AddHit(T hit)
{
if (hit.Entity != _wheelEntity && hit.Entity != _vehicleEntity && hit.Fraction < _closestHitFraction)
{
ClosestHit = hit;
_closestHitFraction = hit.Fraction;
NumHits = 1;
return true;
}
return false;
}
}
[BurstCompile]
public void OnCreate(ref SystemState state)
{
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
float fwdInput = (Input.GetKey(KeyCode.UpArrow) ? 1f : 0f) + (Input.GetKey(KeyCode.DownArrow) ? -1f : 0f);
float sideInput = (Input.GetKey(KeyCode.RightArrow) ? 1f : 0f) + (Input.GetKey(KeyCode.LeftArrow) ? -1f : 0f);
VehicleJob job = new VehicleJob
{
DeltaTime = SystemAPI.Time.DeltaTime,
FwdInput = fwdInput,
SideInput = sideInput,
CollisionWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().CollisionWorld,
LocalTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(false),
PhysicsColliderLookup = SystemAPI.GetComponentLookup<PhysicsCollider>(true),
};
job.Schedule();
}
[BurstCompile]
public partial struct VehicleJob : IJobEntity
{
public float DeltaTime;
public float FwdInput;
public float SideInput;
public CollisionWorld CollisionWorld;
public ComponentLookup<LocalTransform> LocalTransformLookup;
[ReadOnly] public ComponentLookup<PhysicsCollider> PhysicsColliderLookup;
void Execute(Entity entity, ref PhysicsVelocity physicsVelocity, in Vehicle vehicle, in PhysicsMass physicsMass, in DynamicBuffer<VehicleWheels> vehicleWheelsBuffer)
{
LocalTransform localTransform = LocalTransformLookup[entity];
float3 vehicleUp = MathUtilities.GetUpFromRotation(localTransform.Rotation);
float3 vehicleForward = MathUtilities.GetForwardFromRotation(localTransform.Rotation);
float3 vehicleRight = MathUtilities.GetRightFromRotation(localTransform.Rotation);
float wheelGroundingAmount = 0f;
float wheelRatio = 1f / (float)vehicleWheelsBuffer.Length;
// Wheel collision casts
for (int i = 0; i < vehicleWheelsBuffer.Length; i++)
{
VehicleWheels wheel = vehicleWheelsBuffer[i];
LocalTransform wheelLocalTransform = LocalTransformLookup[wheel.CollisionEntity];
ColliderCastInput castInput = new ColliderCastInput(PhysicsColliderLookup[wheel.CollisionEntity].Value, wheelLocalTransform.Position, wheelLocalTransform.Position
WheelHitCollector<ColliderCastHit> collector = default;
collector.Init(wheel.CollisionEntity, entity);
float hitDistance = vehicle.WheelSuspensionDistance;
if (CollisionWorld.CastCollider(castInput, ref collector))
{
hitDistance = collector.ClosestHit.Fraction * vehicle.WheelSuspensionDistance;
wheelGroundingAmount += wheelRatio;
// Suspension
float suspensionCompressedRatio = 1f - (hitDistance / vehicle.WheelSuspensionDistance);
// Add suspension force
float3 vehicleVelocityAtWheelPoint = physicsVelocity.GetLinearVelocity(physicsMass, localTransform.Position, localTransform.Rotation, wheelLocalTransform.Position
float vehicleVelocityInUpDirection = math.dot(vehicleVelocityAtWheelPoint, vehicleUp);
float suspensionImpulseVelocityChangeOnUpDirection = suspensionCompressedRatio * vehicle.WheelSuspensionStrength;
suspensionImpulseVelocityChangeOnUpDirection -= vehicleVelocityInUpDirection;
if (suspensionImpulseVelocityChangeOnUpDirection > 0f)
{
physicsVelocity.ApplyImpulse(in physicsMass, in localTransform.Position, in localTransform.Rotation, vehicleUp * suspensionImpulseVelocityChangeOnUpDirection
}
}
// Place wheel mesh at goal position
LocalTransform wheelMeshTransform = LocalTransformLookup[wheel.MeshEntity];
wheelMeshTransform.Position = -math.up() * hitDistance;
LocalTransformLookup[wheel.MeshEntity] = wheelMeshTransform;
}
if (wheelGroundingAmount > 0f)
{
float chosenAcceleration = wheelGroundingAmount * vehicle.Acceleration;
// Acceleration
float3 addedVelocityFromAcceleration = vehicleForward * FwdInput * chosenAcceleration * DeltaTime;
float3 tmpNewVelocity = physicsVelocity.Linear + addedVelocityFromAcceleration;
tmpNewVelocity = MathUtilities.ClampToMaxLength(tmpNewVelocity, vehicle.MaxSpeed);
addedVelocityFromAcceleration = tmpNewVelocity - physicsVelocity.Linear;
physicsVelocity.Linear += addedVelocityFromAcceleration;
// Friction & Roll resistance
float3 upVelocity = math.projectsafe(physicsVelocity.Linear, vehicleUp);
float3 fwdVelocity = math.projectsafe(physicsVelocity.Linear, vehicleForward);
float3 lateralVelocity = math.projectsafe(physicsVelocity.Linear, vehicleRight);
lateralVelocity *= (1f / (1f + (vehicle.WheelFriction * DeltaTime)));
bool movingInIntendedDirection = math.dot(fwdVelocity, vehicleForward * FwdInput) > 0f;
if (!movingInIntendedDirection)
{
fwdVelocity *= (1f / (1f + (vehicle.WheelRollResistance * DeltaTime)));
}
physicsVelocity.Linear = upVelocity + fwdVelocity + lateralVelocity;
// Rotation
physicsVelocity.Angular.y += vehicle.RotationAcceleration * SideInput * DeltaTime;
physicsVelocity.Angular.y = math.clamp(physicsVelocity.Angular.y, -vehicle.MaxRotationSpeed, vehicle.MaxRotationSpeed);
physicsVelocity.Angular.y *= (1f / (1f + (vehicle.RotationDamping * DeltaTime)));
}
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/AssemblyInfo.cs
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.Physics.Custom.Editor")]
[assembly: InternalsVisibleTo("Unity.Physics.Custom.EditModeTests")]
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/BaseBodyPairConnector.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
[RequireComponent(typeof(PhysicsBodyAuthoring))]
public abstract class BaseBodyPairConnector : MonoBehaviour
{
public PhysicsBodyAuthoring LocalBody => GetComponent<PhysicsBodyAuthoring>();
public PhysicsBodyAuthoring ConnectedBody;
void OnEnable()
{
// included so tick box appears in Editor
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/CustomPhysicsMaterialTagNames.cs
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Serialization;
namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Custom Physics Material Tag Names", fileName = "Custom Material Tag Names", order = 506)]
public sealed partial class CustomPhysicsMaterialTagNames : ScriptableObject, ITagNames
{
CustomPhysicsMaterialTagNames() {}
public IReadOnlyList<string> TagNames => m_TagNames;
[SerializeField]
[FormerlySerializedAs("m_FlagNames")]
string[] m_TagNames = Enumerable.Range(0, 8).Select(i => string.Empty).ToArray();
void OnValidate()
{
if (m_TagNames.Length != 8)
Array.Resize(ref m_TagNames, 8);
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/CustomPhysicsMaterialTags.cs
using System;
using Unity.Mathematics;
namespace Unity.Physics.Authoring
{
[Serializable]
public struct CustomPhysicsMaterialTags : IEquatable<CustomPhysicsMaterialTags>
{
public static CustomPhysicsMaterialTags Everything => new CustomPhysicsMaterialTags { Value = unchecked((byte)~0) };
public static CustomPhysicsMaterialTags Nothing => new CustomPhysicsMaterialTags { Value = 0 };
public bool Tag00;
public bool Tag01;
public bool Tag02;
public bool Tag03;
public bool Tag04;
public bool Tag05;
public bool Tag06;
public bool Tag07;
internal bool this[int i]
{
get
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 7), nameof(i));
switch (i)
{
case 0: return Tag00;
case 1: return Tag01;
case 2: return Tag02;
case 3: return Tag03;
case 4: return Tag04;
case 5: return Tag05;
case 6: return Tag06;
case 7: return Tag07;
default: return default;
}
}
set
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 7), nameof(i));
switch (i)
{
case 0: Tag00 = value; break;
case 1: Tag01 = value; break;
case 2: Tag02 = value; break;
case 3: Tag03 = value; break;
case 4: Tag04 = value; break;
case 5: Tag05 = value; break;
case 6: Tag06 = value; break;
case 7: Tag07 = value; break;
}
}
}
public byte Value
{
get
{
var result = 0;
result |= (Tag00 ? 1 : 0) << 0;
result |= (Tag01 ? 1 : 0) << 1;
result |= (Tag02 ? 1 : 0) << 2;
result |= (Tag03 ? 1 : 0) << 3;
result |= (Tag04 ? 1 : 0) << 4;
result |= (Tag05 ? 1 : 0) << 5;
result |= (Tag06 ? 1 : 0) << 6;
result |= (Tag07 ? 1 : 0) << 7;
return (byte)result;
}
set
{
Tag00 = (value & (1 << 0)) != 0;
Tag01 = (value & (1 << 1)) != 0;
Tag02 = (value & (1 << 2)) != 0;
Tag03 = (value & (1 << 3)) != 0;
Tag04 = (value & (1 << 4)) != 0;
Tag05 = (value & (1 << 5)) != 0;
Tag06 = (value & (1 << 6)) != 0;
Tag07 = (value & (1 << 7)) != 0;
}
}
public bool Equals(CustomPhysicsMaterialTags other) => Value == other.Value;
public override bool Equals(object obj) => obj is CustomPhysicsMaterialTags other && Equals(other);
namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Physics Category Names", fileName = "Physics Category Names", order = 507)]
public sealed class PhysicsCategoryNames : ScriptableObject, ITagNames
{
PhysicsCategoryNames() {}
void OnValidate()
{
if (m_CategoryNames.Length != 32)
Array.Resize(ref m_CategoryNames, 32);
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsCategoryTags.cs
using System;
using Unity.Mathematics;
namespace Unity.Physics.Authoring
{
[Serializable]
public struct PhysicsCategoryTags : IEquatable<PhysicsCategoryTags>
{
public static PhysicsCategoryTags Everything => new PhysicsCategoryTags { Value = unchecked((uint)~0) };
public static PhysicsCategoryTags Nothing => new PhysicsCategoryTags { Value = 0 };
public bool Category00;
public bool Category01;
public bool Category02;
public bool Category03;
public bool Category04;
public bool Category05;
public bool Category06;
public bool Category07;
public bool Category08;
public bool Category09;
public bool Category10;
public bool Category11;
public bool Category12;
public bool Category13;
public bool Category14;
public bool Category15;
public bool Category16;
public bool Category17;
public bool Category18;
public bool Category19;
public bool Category20;
public bool Category21;
public bool Category22;
public bool Category23;
public bool Category24;
public bool Category25;
public bool Category26;
public bool Category27;
public bool Category28;
public bool Category29;
public bool Category30;
public bool Category31;
internal bool this[int i]
{
get
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 31), nameof(i));
switch (i)
{
case 0: return Category00;
case 1: return Category01;
case 2: return Category02;
case 3: return Category03;
case 4: return Category04;
case 5: return Category05;
case 6: return Category06;
case 7: return Category07;
case 8: return Category08;
case 9: return Category09;
case 10: return Category10;
case 11: return Category11;
case 12: return Category12;
case 13: return Category13;
case 14: return Category14;
case 15: return Category15;
case 16: return Category16;
case 17: return Category17;
case 18: return Category18;
case 19: return Category19;
case 20: return Category20;
case 21: return Category21;
case 22: return Category22;
case 23: return Category23;
case 24: return Category24;
case 25: return Category25;
case 26: return Category26;
case 27: return Category27;
case 28: return Category28;
case 29: return Category29;
case 30: return Category30;
case 31: return Category31;
default: return default;
}
}
set
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 31), nameof(i));
switch (i)
{
case 0: Category00 = value; break;
case 1: Category01 = value; break;
case 2: Category02 = value; break;
case 3: Category03 = value; break;
case 4: Category04 = value; break;
case 5: Category05 = value; break;
case 6: Category06 = value; break;
case 7: Category07 = value; break;
case 8: Category08 = value; break;
case 9: Category09 = value; break;
case 10: Category10 = value; break;
case 11: Category11 = value; break;
case 12: Category12 = value; break;
case 13: Category13 = value; break;
case 14: Category14 = value; break;
case 15: Category15 = value; break;
case 16: Category16 = value; break;
case 17: Category17 = value; break;
case 18: Category18 = value; break;
case 19: Category19 = value; break;
case 20: Category20 = value; break;
case 21: Category21 = value; break;
case 22: Category22 = value; break;
case 23: Category23 = value; break;
case 24: Category24 = value; break;
case 25: Category25 = value; break;
case 26: Category26 = value; break;
case 27: Category27 = value; break;
case 28: Category28 = value; break;
case 29: Category29 = value; break;
case 30: Category30 = value; break;
case 31: Category31 = value; break;
}
}
}
public uint Value
{
get
{
var result = 0;
result |= (Category00 ? 1 : 0) << 0;
result |= (Category01 ? 1 : 0) << 1;
result |= (Category02 ? 1 : 0) << 2;
result |= (Category03 ? 1 : 0) << 3;
result |= (Category04 ? 1 : 0) << 4;
result |= (Category05 ? 1 : 0) << 5;
result |= (Category06 ? 1 : 0) << 6;
result |= (Category07 ? 1 : 0) << 7;
result |= (Category08 ? 1 : 0) << 8;
result |= (Category09 ? 1 : 0) << 9;
result |= (Category10 ? 1 : 0) << 10;
result |= (Category11 ? 1 : 0) << 11;
result |= (Category12 ? 1 : 0) << 12;
result |= (Category13 ? 1 : 0) << 13;
result |= (Category14 ? 1 : 0) << 14;
result |= (Category15 ? 1 : 0) << 15;
result |= (Category16 ? 1 : 0) << 16;
result |= (Category17 ? 1 : 0) << 17;
result |= (Category18 ? 1 : 0) << 18;
result |= (Category19 ? 1 : 0) << 19;
result |= (Category20 ? 1 : 0) << 20;
result |= (Category21 ? 1 : 0) << 21;
result |= (Category22 ? 1 : 0) << 22;
result |= (Category23 ? 1 : 0) << 23;
result |= (Category24 ? 1 : 0) << 24;
result |= (Category25 ? 1 : 0) << 25;
result |= (Category26 ? 1 : 0) << 26;
result |= (Category27 ? 1 : 0) << 27;
result |= (Category28 ? 1 : 0) << 28;
result |= (Category29 ? 1 : 0) << 29;
result |= (Category30 ? 1 : 0) << 30;
result |= (Category31 ? 1 : 0) << 31;
return unchecked((uint)result);
}
set
{
Category00 = (value & (1 << 0)) != 0;
Category01 = (value & (1 << 1)) != 0;
Category02 = (value & (1 << 2)) != 0;
Category03 = (value & (1 << 3)) != 0;
Category04 = (value & (1 << 4)) != 0;
Category05 = (value & (1 << 5)) != 0;
Category06 = (value & (1 << 6)) != 0;
Category07 = (value & (1 << 7)) != 0;
Category08 = (value & (1 << 8)) != 0;
Category09 = (value & (1 << 9)) != 0;
Category10 = (value & (1 << 10)) != 0;
Category11 = (value & (1 << 11)) != 0;
Category12 = (value & (1 << 12)) != 0;
Category13 = (value & (1 << 13)) != 0;
Category14 = (value & (1 << 14)) != 0;
Category15 = (value & (1 << 15)) != 0;
Category16 = (value & (1 << 16)) != 0;
Category17 = (value & (1 << 17)) != 0;
Category18 = (value & (1 << 18)) != 0;
Category19 = (value & (1 << 19)) != 0;
Category20 = (value & (1 << 20)) != 0;
Category21 = (value & (1 << 21)) != 0;
Category22 = (value & (1 << 22)) != 0;
Category23 = (value & (1 << 23)) != 0;
Category24 = (value & (1 << 24)) != 0;
Category25 = (value & (1 << 25)) != 0;
Category26 = (value & (1 << 26)) != 0;
Category27 = (value & (1 << 27)) != 0;
Category28 = (value & (1 << 28)) != 0;
Category29 = (value & (1 << 29)) != 0;
Category30 = (value & (1 << 30)) != 0;
Category31 = (value & (1 << 31)) != 0;
}
}
public bool Equals(PhysicsCategoryTags other) => Value == other.Value;
public override bool Equals(object obj) => obj is PhysicsCategoryTags other && Equals(other);
[Serializable]
public struct PhysicsMaterialCoefficient
{
[SoftRange(0f, 1f, TextFieldMax = float.MaxValue)]
public float Value;
public Material.CombinePolicy CombineMode;
}
public T Value
{
get => m_Value;
set
{
m_Value = value;
Override = true;
}
}
[SerializeField]
T m_Value;
[Serializable]
class OverridableCollisionResponse : OverridableValue<CollisionResponsePolicy> {}
[Serializable]
class OverridableMaterialCoefficient : OverridableValue<PhysicsMaterialCoefficient>
{
protected override void OnValidate(ref PhysicsMaterialCoefficient value) =>
value.Value = math.max(0f, value.Value);
}
[Serializable]
class OverridableCategoryTags : OverridableValue<PhysicsCategoryTags> {}
[Serializable]
class OverridableCustomMaterialTags : OverridableValue<CustomPhysicsMaterialTags> {}
[Serializable]
class PhysicsMaterialProperties : IInheritPhysicsMaterialProperties, ISerializationCallbackReceiver
{
public PhysicsMaterialProperties(bool supportsTemplate) => m_SupportsTemplate = supportsTemplate;
[SerializeField, HideInInspector]
bool m_SupportsTemplate;
public bool OverrideCollisionResponse { get => m_CollisionResponse.Override; set => m_CollisionResponse.Override = value; }
public CollisionResponsePolicy CollisionResponse
{
get => Get(m_CollisionResponse, m_Template == null ? null : m_Template?.CollisionResponse);
set => m_CollisionResponse.Value = value;
}
[SerializeField]
OverridableCollisionResponse m_CollisionResponse = new OverridableCollisionResponse
{
Value = CollisionResponsePolicy.Collide,
Override = false
};
public bool OverrideFriction { get => m_Friction.Override; set => m_Friction.Override = value; }
public PhysicsMaterialCoefficient Friction
{
get => Get(m_Friction, m_Template == null ? null : m_Template?.Friction);
set => m_Friction.Value = value;
}
[SerializeField]
OverridableMaterialCoefficient m_Friction = new OverridableMaterialCoefficient
{
Value = new PhysicsMaterialCoefficient { Value = 0.5f, CombineMode = Material.CombinePolicy.GeometricMean },
Override = false
};
public bool OverrideRestitution { get => m_Restitution.Override; set => m_Restitution.Override = value; }
public PhysicsMaterialCoefficient Restitution
{
get => Get(m_Restitution, m_Template == null ? null : m_Template?.Restitution);
set => m_Restitution.Value = value;
}
[SerializeField]
OverridableMaterialCoefficient m_Restitution = new OverridableMaterialCoefficient
{
Value = new PhysicsMaterialCoefficient { Value = 0f, CombineMode = Material.CombinePolicy.Maximum },
Override = false
};
public bool OverrideBelongsTo { get => m_BelongsToCategories.Override; set => m_BelongsToCategories.Override = value; }
public PhysicsCategoryTags BelongsTo
{
get => Get(m_BelongsToCategories, m_Template == null ? null : m_Template?.BelongsTo);
set => m_BelongsToCategories.Value = value;
}
[SerializeField]
OverridableCategoryTags m_BelongsToCategories =
new OverridableCategoryTags { Value = PhysicsCategoryTags.Everything, Override = false };
public bool OverrideCollidesWith { get => m_CollidesWithCategories.Override; set => m_CollidesWithCategories.Override = value; }
public PhysicsCategoryTags CollidesWith
{
get => Get(m_CollidesWithCategories, m_Template == null ? null : m_Template?.CollidesWith);
set => m_CollidesWithCategories.Value = value;
}
[SerializeField]
OverridableCategoryTags m_CollidesWithCategories =
new OverridableCategoryTags { Value = PhysicsCategoryTags.Everything, Override = false };
public bool OverrideCustomTags { get => m_CustomMaterialTags.Override; set => m_CustomMaterialTags.Override = value; }
public CustomPhysicsMaterialTags CustomTags
{
get => Get(m_CustomMaterialTags, m_Template == null ? null : m_Template?.CustomTags);
set => m_CustomMaterialTags.Value = value;
}
[SerializeField]
OverridableCustomMaterialTags m_CustomMaterialTags =
new OverridableCustomMaterialTags { Value = default, Override = false };
material.m_SupportsTemplate = supportsTemplate;
if (!supportsTemplate)
{
material.m_Template = null;
material.m_CollisionResponse.Override = true;
material.m_Friction.Override = true;
material.m_Restitution.Override = true;
}
material.m_Friction.OnValidate();
material.m_Restitution.OnValidate();
}
[SerializeField]
int m_SerializedVersion = 0;
void ISerializationCallbackReceiver.OnBeforeSerialize() {}
void ISerializationCallbackReceiver.OnAfterDeserialize() => UpgradeVersionIfNecessary();
namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Physics Material Template", fileName = "Physics Material Template", order = 508)]
public sealed class PhysicsMaterialTemplate : ScriptableObject, IPhysicsMaterialProperties
{
PhysicsMaterialTemplate() {}
public CollisionResponsePolicy CollisionResponse { get => m_Value.CollisionResponse; set => m_Value.CollisionResponse = value; }
public PhysicsMaterialCoefficient Friction { get => m_Value.Friction; set => m_Value.Friction = value; }
public PhysicsMaterialCoefficient Restitution { get => m_Value.Restitution; set => m_Value.Restitution = value; }
public PhysicsCategoryTags BelongsTo { get => m_Value.BelongsTo; set => m_Value.BelongsTo = value; }
public PhysicsCategoryTags CollidesWith { get => m_Value.CollidesWith; set => m_Value.CollidesWith = value; }
public CustomPhysicsMaterialTags CustomTags { get => m_Value.CustomTags; set => m_Value.CustomTags = value; }
[SerializeField]
PhysicsMaterialProperties m_Value = new PhysicsMaterialProperties(false);
PhysicsBodyAuthoring() {}
public BodyMotionType MotionType { get => m_MotionType; set => m_MotionType = value; }
[SerializeField]
[Tooltip("Specifies whether the body should be fully physically simulated, moved directly, or fixed in place.")]
BodyMotionType m_MotionType;
public BodySmoothing Smoothing { get => m_Smoothing; set => m_Smoothing = value; }
[SerializeField]
[Tooltip("Specifies how this body's motion in its graphics representation should be smoothed when the rendering framerate is greater than the fixed step rate used by physics.")]
BodySmoothing m_Smoothing = BodySmoothing.None;
const float k_MinimumMass = 0.001f;
public float Mass
{
get => m_MotionType == BodyMotionType.Dynamic ? m_Mass : float.PositiveInfinity;
set => m_Mass = math.max(k_MinimumMass, value);
}
[SerializeField]
float m_Mass = 1.0f;
public float LinearDamping { get => m_LinearDamping; set => m_LinearDamping = math.max(0f, value); }
[SerializeField]
[Tooltip("This is applied to a body's linear velocity reducing it over time.")]
float m_LinearDamping = 0.01f;
public float AngularDamping { get => m_AngularDamping; set => m_AngularDamping = math.max(0f, value); }
[SerializeField]
[Tooltip("This is applied to a body's angular velocity reducing it over time.")]
float m_AngularDamping = 0.05f;
public float3 InitialLinearVelocity { get => m_InitialLinearVelocity; set => m_InitialLinearVelocity = value; }
[SerializeField]
[Tooltip("The initial linear velocity of the body in world space")]
float3 m_InitialLinearVelocity = float3.zero;
public float3 InitialAngularVelocity { get => m_InitialAngularVelocity; set => m_InitialAngularVelocity = value; }
[SerializeField]
[Tooltip("This represents the initial rotation speed around each axis in the local motion space of the body i.e. around the center of mass")]
float3 m_InitialAngularVelocity = float3.zero;
public float GravityFactor
{
get => m_MotionType == BodyMotionType.Dynamic ? m_GravityFactor : 0f;
set => m_GravityFactor = value;
}
[SerializeField]
[Tooltip("Scales the amount of gravity to apply to this body.")]
float m_GravityFactor = 1f;
public bool OverrideDefaultMassDistribution
{
#pragma warning disable 618
get => m_OverrideDefaultMassDistribution;
set => m_OverrideDefaultMassDistribution = value;
#pragma warning restore 618
}
[SerializeField]
[Tooltip("Default mass distribution is based on the shapes associated with this body.")]
bool m_OverrideDefaultMassDistribution;
public MassDistribution CustomMassDistribution
{
get => new MassDistribution
{
Transform = new RigidTransform(m_Orientation, m_CenterOfMass),
InertiaTensor =
m_MotionType == BodyMotionType.Dynamic ? m_InertiaTensor : new float3(float.PositiveInfinity)
};
set
{
m_CenterOfMass = value.Transform.pos;
m_Orientation.SetValue(value.Transform.rot);
m_InertiaTensor = value.InertiaTensor;
#pragma warning disable 618
m_OverrideDefaultMassDistribution = true;
#pragma warning restore 618
}
}
[SerializeField]
float3 m_CenterOfMass;
[SerializeField]
EulerAngles m_Orientation = EulerAngles.Default;
[SerializeField]
// Default value to solid unit sphere : https://en.wikipedia.org/wiki/List_of_moments_of_inertia
float3 m_InertiaTensor = new float3(2f / 5f);
public uint WorldIndex { get => m_WorldIndex; set => m_WorldIndex = value; }
[SerializeField]
[Tooltip("The index of the physics world this body belongs to. Default physics world has index 0.")]
uint m_WorldIndex = 0;
public CustomPhysicsBodyTags CustomTags { get => m_CustomTags; set => m_CustomTags = value; }
[SerializeField]
CustomPhysicsBodyTags m_CustomTags = CustomPhysicsBodyTags.Nothing;
void OnEnable()
{
// included so tick box appears in Editor
}
void OnValidate()
{
m_Mass = math.max(k_MinimumMass, m_Mass);
m_LinearDamping = math.max(m_LinearDamping, 0f);
m_AngularDamping = math.max(m_AngularDamping, 0f);
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/PhysicsShapeAuthoring.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using UnityMesh = UnityEngine.Mesh;
namespace Unity.Physics.Authoring
{
public sealed class UnimplementedShapeException : NotImplementedException
{
public UnimplementedShapeException(ShapeType shapeType)
: base($"Unknown shape type {shapeType} requires explicit implementation") {}
}
#if UNITY_2021_2_OR_NEWER
[Icon(k_IconPath)]
#endif
[AddComponentMenu("Entities/Physics/Physics Shape")]
public sealed class PhysicsShapeAuthoring : MonoBehaviour, IInheritPhysicsMaterialProperties, ISerializationCallbackReceiver
{
const string k_IconPath = "Packages/com.unity.physics/Unity.Physics.Editor/Editor Default Resources/Icons/d_BoxCollider@64.png";
PhysicsShapeAuthoring() {}
[Serializable]
struct CylindricalProperties
{
public float Height;
public float Radius;
[HideInInspector]
public int Axis;
}
[SerializeField]
[ExpandChildren]
CylindricalProperties m_Capsule = new CylindricalProperties { Height = 1f, Radius = 0.5f, Axis = 2 };
[SerializeField]
[ExpandChildren]
CylindricalProperties m_Cylinder = new CylindricalProperties { Height = 1f, Radius = 0.5f, Axis = 2 };
[SerializeField]
[Tooltip("How many sides the convex cylinder shape should have.")]
[Range(CylinderGeometry.MinSideCount, CylinderGeometry.MaxSideCount)]
int m_CylinderSideCount = 20;
[SerializeField]
float m_SphereRadius = 0.5f;
public ConvexHullGenerationParameters ConvexHullGenerationParameters => m_ConvexHullGenerationParameters;
[SerializeField]
[Tooltip(
"Specifies the minimum weight of a skinned vertex assigned to this shape and/or its transform children required for it to be included for automatic detection. " +
"A value of 0 will include all points with any weight assigned to this shape's hierarchy."
)]
[Range(0f, 1f)]
float m_MinimumSkinnedVertexWeight = 0.1f;
[SerializeField]
[ExpandChildren]
ConvexHullGenerationParameters m_ConvexHullGenerationParameters = ConvexHullGenerationParameters.Default.ToAuthoring();
// TODO: remove this accessor in favor of GetRawVertices() when blob data is serializable
internal UnityMesh CustomMesh => m_CustomMesh;
[SerializeField]
[Tooltip("If no custom mesh is specified, then one will be generated using this body's rendered meshes.")]
UnityMesh m_CustomMesh;
public bool ForceUnique { get => m_ForceUnique; set => m_ForceUnique = value; }
[SerializeField]
bool m_ForceUnique;
public PhysicsMaterialTemplate MaterialTemplate { get => m_Material.Template; set => m_Material.Template = value; }
PhysicsMaterialTemplate IInheritPhysicsMaterialProperties.Template
{
get => m_Material.Template;
set => m_Material.Template = value;
}
public bool OverrideCollisionResponse { get => m_Material.OverrideCollisionResponse; set => m_Material.OverrideCollisionResponse = value; }
public CollisionResponsePolicy CollisionResponse { get => m_Material.CollisionResponse; set => m_Material.CollisionResponse = value; }
public bool OverrideFriction { get => m_Material.OverrideFriction; set => m_Material.OverrideFriction = value; }
public PhysicsMaterialCoefficient Friction { get => m_Material.Friction; set => m_Material.Friction = value; }
public bool OverrideRestitution
{
get => m_Material.OverrideRestitution;
set => m_Material.OverrideRestitution = value;
}
public PhysicsMaterialCoefficient Restitution
{
get => m_Material.Restitution;
set => m_Material.Restitution = value;
}
public bool OverrideBelongsTo
{
get => m_Material.OverrideBelongsTo;
set => m_Material.OverrideBelongsTo = value;
}
void AppendMeshPropertiesToNativeBuffers(
float4x4 localToWorld, UnityMesh mesh, NativeList<float3> vertices, NativeList<int3> triangles, bool validate,
NativeList<HashableShapeInputs> inputs, HashSet<UnityMesh> meshAssets
)
{
if (mesh == null || validate && !mesh.IsValidForConversion(gameObject))
return;
var childToShape = math.mul(transform.worldToLocalMatrix, localToWorld);
AppendMeshPropertiesToNativeBuffers(childToShape, mesh, vertices, triangles, inputs, meshAssets);
}
internal static void AppendMeshPropertiesToNativeBuffers(
float4x4 childToShape, UnityMesh mesh, NativeList<float3> vertices, NativeList<int3> triangles,
NativeList<HashableShapeInputs> inputs, HashSet<UnityMesh> meshAssets
)
{
var offset = 0u;
#if UNITY_EDITOR
// TODO: when min spec is 2020.1, collect all meshes and their data via single Burst job rather than one at a time
using (var meshData = UnityEditor.MeshUtility.AcquireReadOnlyMeshData(mesh))
#else
using (var meshData = UnityMesh.AcquireReadOnlyMeshData(mesh))
#endif
{
if (vertices.IsCreated)
{
offset = (uint)vertices.Length;
var tmpVertices = new NativeArray<Vector3>(meshData[0].vertexCount, Allocator.Temp);
meshData[0].GetVertices(tmpVertices);
if (vertices.Capacity < vertices.Length + tmpVertices.Length)
vertices.Capacity = vertices.Length + tmpVertices.Length;
foreach (var v in tmpVertices)
vertices.Add(math.mul(childToShape, new float4(v, 1f)).xyz);
}
if (triangles.IsCreated)
{
switch (meshData[0].indexFormat)
{
case IndexFormat.UInt16:
var indices16 = meshData[0].GetIndexData<ushort>();
var numTriangles = indices16.Length / 3;
if (triangles.Capacity < triangles.Length + numTriangles)
triangles.Capacity = triangles.Length + numTriangles;
for (var sm = 0; sm < meshData[0].subMeshCount; ++sm)
{
var subMesh = meshData[0].GetSubMesh(sm);
for (int i = subMesh.indexStart, count = 0; count < subMesh.indexCount; i += 3, count += 3)
triangles.Add((int3) new uint3(offset + indices16[i], offset + indices16[i + 1], offset + indices16[i + 2]));
}
break;
case IndexFormat.UInt32:
var indices32 = meshData[0].GetIndexData<uint>();
numTriangles = indices32.Length / 3;
if (triangles.Capacity < triangles.Length + numTriangles)
triangles.Capacity = triangles.Length + numTriangles;
for (var sm = 0; sm < meshData[0].subMeshCount; ++sm)
{
var subMesh = meshData[0].GetSubMesh(sm);
for (int i = subMesh.indexStart, count = 0; count < subMesh.indexCount; i += 3, count += 3)
triangles.Add((int3) new uint3(offset + indices32[i], offset + indices32[i + 1], offset + indices32[i + 2]));
}
break;
}
}
}
if (inputs.IsCreated)
inputs.Add(HashableShapeInputs.FromMesh(mesh, childToShape));
meshAssets?.Add(mesh);
}
void UpdateCapsuleAxis()
{
var cmax = math.cmax(m_PrimitiveSize);
var cmin = math.cmin(m_PrimitiveSize);
if (cmin == cmax)
return;
m_Capsule.Axis = m_PrimitiveSize.GetMaxAxis();
}
void UpdateCylinderAxis() => m_Cylinder.Axis = m_PrimitiveSize.GetDeviantAxis();
m_ConvexHullGenerationParameters.BevelRadius = geometry.BevelRadius;
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetSphere(SphereGeometry geometry, quaternion orientation)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(orientation);
SetSphere(geometry, euler);
}
internal void SetSphere(SphereGeometry geometry)
{
SetSphere(geometry, m_PrimitiveOrientation);
}
internal void SetSphere(SphereGeometry geometry, EulerAngles orientation)
{
m_ShapeType = ShapeType.Sphere;
m_PrimitiveCenter = geometry.Center;
var radius = math.max(0f, geometry.Radius);
m_PrimitiveSize = new float3(2f * radius, 2f * radius, 2f * radius);
m_PrimitiveOrientation = orientation;
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetPlane(float3 center, float2 size, quaternion orientation)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(orientation);
SetPlane(center, size, euler);
}
internal void SetPlane(float3 center, float2 size, EulerAngles orientation)
{
m_ShapeType = ShapeType.Plane;
m_PrimitiveCenter = center;
m_PrimitiveOrientation = orientation;
m_PrimitiveSize = new float3(size.x, 0f, size.y);
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetConvexHull(
ConvexHullGenerationParameters hullGenerationParameters, float minimumSkinnedVertexWeight
)
{
m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
SetConvexHull(hullGenerationParameters);
}
public void SetConvexHull(ConvexHullGenerationParameters hullGenerationParameters, UnityMesh customMesh = null)
{
m_ShapeType = ShapeType.ConvexHull;
m_CustomMesh = customMesh;
hullGenerationParameters.OnValidate();
m_ConvexHullGenerationParameters = hullGenerationParameters;
}
public void SetMesh(UnityMesh mesh = null)
{
m_ShapeType = ShapeType.Mesh;
m_CustomMesh = mesh;
}
void OnEnable()
{
// included so tick box appears in Editor
}
const int k_LatestVersion = 1;
[SerializeField]
int m_SerializedVersion = 0;
void ISerializationCallbackReceiver.OnBeforeSerialize() {}
void ISerializationCallbackReceiver.OnAfterDeserialize() => UpgradeVersionIfNecessary();
#pragma warning disable 618
void UpgradeVersionIfNecessary()
{
if (m_SerializedVersion < k_LatestVersion)
{
// old data from version < 1 have been removed
if (m_SerializedVersion < 1)
m_SerializedVersion = 1;
}
}
#pragma warning restore 618
static void Validate(ref CylindricalProperties props)
{
props.Height = math.max(0f, props.Height);
props.Radius = math.max(0f, props.Radius);
}
void OnValidate()
{
UpgradeVersionIfNecessary();
m_PrimitiveSize = math.max(m_PrimitiveSize, new float3());
Validate(ref m_Capsule);
Validate(ref m_Cylinder);
switch (m_ShapeType)
{
case ShapeType.Box:
SetBox(GetBoxProperties(out var orientation), orientation);
break;
case ShapeType.Capsule:
SetCapsule(GetCapsuleProperties());
break;
case ShapeType.Cylinder:
SetCylinder(GetCylinderProperties(out orientation), orientation);
break;
case ShapeType.Sphere:
SetSphere(GetSphereProperties(out orientation), orientation);
break;
case ShapeType.Plane:
GetPlaneProperties(out var center, out var size2D, out orientation);
SetPlane(center, size2D, orientation);
break;
case ShapeType.ConvexHull:
case ShapeType.Mesh:
break;
default:
throw new UnimplementedShapeException(m_ShapeType);
}
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
m_CylinderSideCount =
math.clamp(m_CylinderSideCount, CylinderGeometry.MinSideCount, CylinderGeometry.MaxSideCount);
m_ConvexHullGenerationParameters.OnValidate();
PhysicsMaterialProperties.OnValidate(ref m_Material, true);
}
// matrix to transform point from shape space into world space
public float4x4 GetShapeToWorldMatrix() =>
new float4x4(Math.DecomposeRigidBodyTransform(transform.localToWorldMatrix));
// matrix to transform point from object's local transform matrix into shape space
internal float4x4 GetLocalToShapeMatrix() =>
math.mul(math.inverse(GetShapeToWorldMatrix()), transform.localToWorldMatrix);
internal unsafe void BakePoints(NativeArray<float3> points)
{
var localToShapeQuantized = GetLocalToShapeMatrix();
using (var aabb = new NativeArray<Aabb>(1, Allocator.TempJob))
{
new PhysicsShapeExtensions.GetAabbJob { Points = points, Aabb = aabb }.Run();
HashableShapeInputs.GetQuantizedTransformations(localToShapeQuantized, aabb[0], out localToShapeQuantized);
}
using (var bakedPoints = new NativeArray<float3>(points.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory))
{
new BakePointsJob
{
Points = points,
LocalToShape = localToShapeQuantized,
Output = bakedPoints
}.Schedule(points.Length, 16).Complete();
UnsafeUtility.MemCpy(points.GetUnsafePtr(), bakedPoints.GetUnsafePtr(), points.Length * UnsafeUtility.SizeOf<float3>());
}
}
[BurstCompile]
struct BakePointsJob : IJobParallelFor
{
[Collections.ReadOnly]
public NativeArray<float3> Points;
public float4x4 LocalToShape;
public NativeArray<float3> Output;
public void Execute(int index) => Output[index] = math.mul(LocalToShape, new float4(Points[index], 1f)).xyz;
}
/// <summary>
/// Fit this shape to render geometry in its GameObject hierarchy. Children in the hierarchy will
/// influence the result if they have enabled MeshRenderer components or have vertices bound to
/// them on a SkinnedMeshRenderer. Children will only count as influences if this shape is the
/// first ancestor shape in their hierarchy. As such, you should add shape components to all
/// GameObjects that should have them before you call this method on any of them.
/// </summary>
///
/// <exception cref="UnimplementedShapeException"> Thrown when an Unimplemented Shape error
/// condition occurs. </exception>
///
/// <param name="minimumSkinnedVertexWeight"> (Optional)
/// The minimum total weight that a vertex in a skinned mesh must have assigned to this object
/// and/or any of its influencing children. </param>
public void FitToEnabledRenderMeshes(float minimumSkinnedVertexWeight = 0f)
{
var shapeType = m_ShapeType;
m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
using (var points = new NativeList<float3>(65535, Allocator.Persistent))
{
// temporarily un-assign custom mesh and assume this shape is a convex hull
var customMesh = m_CustomMesh;
m_CustomMesh = null;
GetConvexHullProperties(points, Application.isPlaying, default, default, default, default);
m_CustomMesh = customMesh;
if (points.Length == 0)
return;
// TODO: find best rotation, particularly if any points came from skinned mesh
var orientation = quaternion.identity;
var bounds = new Bounds(points[0], float3.zero);
for (int i = 1, count = points.Length; i < count; ++i)
bounds.Encapsulate(points[i]);
SetBox(
new BoxGeometry
{
Center = bounds.center,
Size = bounds.size,
Orientation = orientation,
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius
}
);
}
switch (shapeType)
{
case ShapeType.Capsule:
SetCapsule(GetCapsuleProperties());
break;
case ShapeType.Cylinder:
SetCylinder(GetCylinderProperties(out var orientation), orientation);
break;
case ShapeType.Sphere:
SetSphere(GetSphereProperties(out orientation), orientation);
break;
case ShapeType.Plane:
// force recalculation of plane orientation by making it think shape type is out of date
m_ShapeType = ShapeType.Box;
GetPlaneProperties(out var center, out var size2D, out orientation);
SetPlane(center, size2D, orientation);
break;
case ShapeType.Box:
case ShapeType.ConvexHull:
case ShapeType.Mesh:
m_ShapeType = shapeType;
break;
default:
throw new UnimplementedShapeException(shapeType);
}
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
[Conditional("UNITY_EDITOR")]
void Reset()
{
#if UNITY_EDITOR
InitializeConvexHullGenerationParameters();
FitToEnabledRenderMeshes(m_MinimumSkinnedVertexWeight);
// TODO: also pick best primitive shape
UnityEditor.SceneView.RepaintAll();
#endif
}
public void InitializeConvexHullGenerationParameters()
{
var pointCloud = new NativeList<float3>(65535, Allocator.Temp);
GetConvexHullProperties(pointCloud, false, default, default, default, default);
m_ConvexHullGenerationParameters.InitializeToRecommendedAuthoringValues(pointCloud.AsArray());
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/BakingSystems/PhysicsBodyBakingSystem.cs
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics.GraphicsIntegration;
using UnityEngine;
using LegacyRigidbody = UnityEngine.Rigidbody;
using LegacyCollider = UnityEngine.Collider;
namespace Unity.Physics.Authoring
{
[TemporaryBakingType]
public struct PhysicsBodyAuthoringData : IComponentData
{
public bool IsDynamic;
public float Mass;
public bool OverrideDefaultMassDistribution;
public MassDistribution CustomMassDistribution;
}
class PhysicsBodyAuthoringBaker : BasePhysicsBaker<PhysicsBodyAuthoring>
{
internal List<UnityEngine.Collider> colliderComponents = new List<UnityEngine.Collider>();
internal List<PhysicsShapeAuthoring> physicsShapeComponents = new List<PhysicsShapeAuthoring>();
public override void Bake(PhysicsBodyAuthoring authoring)
{
// Priority is to Legacy Components. Ignore if baked by Legacy.
if (GetComponent<LegacyRigidbody>() || GetComponent<LegacyCollider>())
{
return;
}
var entity = GetEntity(TransformUsageFlags.Dynamic);
// To process later in the Baking System
AddComponent(entity, new PhysicsBodyAuthoringData
{
IsDynamic = (authoring.MotionType == BodyMotionType.Dynamic),
Mass = authoring.Mass,
OverrideDefaultMassDistribution = authoring.OverrideDefaultMassDistribution,
CustomMassDistribution = authoring.CustomMassDistribution
});
AddSharedComponent(entity, new PhysicsWorldIndex(authoring.WorldIndex));
var bodyTransform = GetComponent<Transform>();
var motionType = authoring.MotionType;
var hasSmoothing = authoring.Smoothing != BodySmoothing.None;
PostProcessTransform(bodyTransform, motionType);
var customTags = authoring.CustomTags;
if (!customTags.Equals(CustomPhysicsBodyTags.Nothing))
AddComponent(entity, new PhysicsCustomTags { Value = customTags.Value });
// Check that there is at least one collider in the hierarchy to add these three
GetComponentsInChildren(colliderComponents);
GetComponentsInChildren(physicsShapeComponents);
if (colliderComponents.Count > 0 || physicsShapeComponents.Count > 0)
{
AddComponent(entity, new PhysicsCompoundData()
{
AssociateBlobToBody = false,
ConvertedBodyInstanceID = authoring.GetInstanceID(),
Hash = default,
});
AddComponent<PhysicsRootBaked>(entity);
AddComponent<PhysicsCollider>(entity);
AddBuffer<PhysicsColliderKeyEntityPair>(entity);
}
if (authoring.MotionType == BodyMotionType.Static || IsStatic())
return;
var massProperties = MassProperties.UnitSphere;
AddComponent(entity, authoring.MotionType == BodyMotionType.Dynamic ?
PhysicsMass.CreateDynamic(massProperties, authoring.Mass) :
PhysicsMass.CreateKinematic(massProperties));
var physicsVelocity = new PhysicsVelocity
{
Linear = authoring.InitialLinearVelocity,
Angular = authoring.InitialAngularVelocity
};
AddComponent(entity, physicsVelocity);
if (authoring.MotionType == BodyMotionType.Dynamic)
{
// TODO make these optional in editor?
AddComponent(entity, new PhysicsDamping
{
Linear = authoring.LinearDamping,
Angular = authoring.AngularDamping
});
if (authoring.GravityFactor != 1)
{
AddComponent(entity, new PhysicsGravityFactor
{
Value = authoring.GravityFactor
});
}
}
else if (authoring.MotionType == BodyMotionType.Kinematic)
{
AddComponent(entity, new PhysicsGravityFactor
{
Value = 0
});
}
if (hasSmoothing)
{
AddComponent(entity, new PhysicsGraphicalSmoothing());
if (authoring.Smoothing == BodySmoothing.Interpolation)
{
AddComponent(entity, new PhysicsGraphicalInterpolationBuffer
{
PreviousTransform = Math.DecomposeRigidBodyTransform(bodyTransform.localToWorldMatrix),
PreviousVelocity = physicsVelocity,
});
}
}
}
}
[RequireMatchingQueriesForUpdate]
[UpdateAfter(typeof(EndColliderBakingSystem))]
[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
public partial class PhysicsBodyBakingSystem : SystemBase
{
protected override void OnUpdate()
{
// Fill in the MassProperties based on the potential calculated value by BuildCompoundColliderBakingSystem
foreach (var(physicsMass, bodyData, collider) in
SystemAPI.Query<RefRW<PhysicsMass>, RefRO<PhysicsBodyAuthoringData>, RefRO<PhysicsCollider>>().WithOptions(EntityQueryOptions.IncludePrefab | EntityQueryOptions
{
// Build mass component
var massProperties = collider.ValueRO.MassProperties;
if (bodyData.ValueRO.OverrideDefaultMassDistribution)
{
massProperties.MassDistribution = bodyData.ValueRO.CustomMassDistribution;
// Increase the angular expansion factor to account for the shift in center of mass
massProperties.AngularExpansionFactor += math.length(massProperties.MassDistribution.Transform.pos - bodyData.ValueRO.CustomMassDistribution.Transform.pos);
}
physicsMass.ValueRW = bodyData.ValueRO.IsDynamic ?
PhysicsMass.CreateDynamic(massProperties, bodyData.ValueRO.Mass) :
PhysicsMass.CreateKinematic(massProperties);
}
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/BakingSystems/PhysicsShapeBakingSystem.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Profiling;
using LegacyCollider = UnityEngine.Collider;
using UnityMesh = UnityEngine.Mesh;
namespace Unity.Physics.Authoring
{
class PhysicsShapeBaker : BaseColliderBaker<PhysicsShapeAuthoring>
{
public static List<PhysicsShapeAuthoring> physicsShapeComponents = new List<PhysicsShapeAuthoring>();
public static List<LegacyCollider> legacyColliderComponents = new List<LegacyCollider>();
bool ShouldConvertShape(PhysicsShapeAuthoring authoring)
{
return authoring.enabled;
}
private GameObject GetPrimaryBody(GameObject shape, out bool hasBodyComponent, out bool isStaticBody)
{
var pb = FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
var rb = FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
hasBodyComponent = (pb != null || rb != null);
isStaticBody = false;
if (pb != null)
{
return rb == null ? pb.gameObject :
pb.transform.IsChildOf(rb.transform) ? pb.gameObject : rb.gameObject;
}
if (rb != null)
return rb.gameObject;
// for implicit static shape, first see if it is part of static optimized hierarchy
isStaticBody = FindTopmostStaticEnabledAncestor(shape, out var topStatic);
if (topStatic != null)
return topStatic;
// otherwise, find topmost enabled Collider or PhysicsShapeAuthoring
var topCollider = FindTopmostEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_CollidersBuffer);
var topShape = FindTopmostEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_ShapesBuffer);
return topCollider == null
? topShape == null ? shape.gameObject : topShape
: topShape == null
? topCollider
: topShape.transform.IsChildOf(topCollider.transform)
? topCollider
: topShape;
}
ShapeComputationDataBaking GetInputDataFromAuthoringComponent(PhysicsShapeAuthoring shape, Entity colliderEntity)
{
GameObject shapeGameObject = shape.gameObject;
var body = GetPrimaryBody(shapeGameObject, out bool hasBodyComponent, out bool isStaticBody);
var child = shapeGameObject;
var shapeInstanceID = shape.GetInstanceID();
var bodyEntity = GetEntity(body, TransformUsageFlags.Dynamic);
// prepare the static root
if (isStaticBody)
{
var staticRootMarker = CreateAdditionalEntity(TransformUsageFlags.Dynamic, true, "StaticRootBakeMarker");
AddComponent(staticRootMarker, new BakeStaticRoot() { Body = bodyEntity, ConvertedBodyInstanceID = body.transform.GetInstanceID() });
}
// Track dependencies to the transforms
Transform shapeTransform = GetComponent<Transform>(shape);
Transform bodyTransform = GetComponent<Transform>(body);
var instance = new ColliderInstanceBaking
{
AuthoringComponentId = shapeInstanceID,
BodyEntity = bodyEntity,
ShapeEntity = GetEntity(shapeGameObject, TransformUsageFlags.Dynamic),
ChildEntity = GetEntity(child, TransformUsageFlags.Dynamic),
BodyFromShape = ColliderInstanceBaking.GetCompoundFromChild(shapeTransform, bodyTransform),
};
var data = GenerateComputationData(shape, instance, colliderEntity);
data.Instance.ConvertedAuthoringInstanceID = shapeInstanceID;
data.Instance.ConvertedBodyInstanceID = bodyTransform.GetInstanceID();
var rb = FindFirstEnabledAncestor(shapeGameObject, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
var pb = FindFirstEnabledAncestor(shapeGameObject, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
// The LegacyRigidBody cannot know about the ShapeAuthoringComponent. We need to take responsibility of baking the collider.
if (rb || (!rb && !pb) && body == shapeGameObject)
{
GetComponents(physicsShapeComponents);
GetComponents(legacyColliderComponents);
// We need to check that there are no other colliders in the same object, if so, only the first one should do this, otherwise there will be 2 bakers adding this to the enti
// This will be needed to trigger BuildCompoundColliderBakingSystem
// If they are legacy Colliders and PhysicsShapeAuthoring in the same object, the PhysicsShapeAuthoring will add this
if (legacyColliderComponents.Count == 0 && physicsShapeComponents.Count > 0 && physicsShapeComponents[0].GetInstanceID() == shapeInstanceID)
{
var entity = GetEntity(TransformUsageFlags.Dynamic);
// // Rigid Body bakes always add the PhysicsWorldIndex component and process transform
if (!hasBodyComponent)
{
AddSharedComponent(entity, new PhysicsWorldIndex());
PostProcessTransform(bodyTransform);
}
AddComponent(entity, new PhysicsCompoundData()
{
AssociateBlobToBody = false,
ConvertedBodyInstanceID = shapeInstanceID,
Hash = default,
});
AddComponent<PhysicsRootBaked>(entity);
AddComponent<PhysicsCollider>(entity);
AddBuffer<PhysicsColliderKeyEntityPair>(entity);
}
}
return data;
}
Material ProduceMaterial(PhysicsShapeAuthoring shape)
{
var materialTemplate = shape.MaterialTemplate;
if (materialTemplate != null)
DependsOn(materialTemplate);
return shape.GetMaterial();
}
CollisionFilter ProduceCollisionFilter(PhysicsShapeAuthoring shape)
{
return shape.GetFilter();
}
UnityEngine.Mesh GetMesh(PhysicsShapeAuthoring shape, out float4x4 childToShape)
{
var mesh = shape.CustomMesh;
childToShape = float4x4.identity;
if (mesh == null)
{
// Try to get a mesh in the children
var filter = GetComponentInChildren<MeshFilter>();
if (filter != null && filter.sharedMesh != null)
{
mesh = filter.sharedMesh;
var childTransform = GetComponent<Transform>(filter);
childToShape = math.mul(shape.transform.worldToLocalMatrix, childTransform.localToWorldMatrix);;
}
}
if (mesh == null)
{
throw new InvalidOperationException(
$"No {nameof(PhysicsShapeAuthoring.CustomMesh)} assigned on {shape.name}."
);
}
DependsOn(mesh);
return mesh;
}
namespace Unity.Physics.Authoring
{
[BakingType]
public struct JointEntityBaking : IComponentData
{
public Entity Entity;
}
public class BallAndSocketJoint : BaseJoint
{
// Editor only settings
[HideInInspector]
public bool EditPivots;
[Tooltip("If checked, PositionLocal will snap to match PositionInConnectedEntity")]
public bool AutoSetConnected = true;
namespace Unity.Physics.Authoring
{
public abstract class BaseJoint : BaseBodyPairConnector
{
public bool EnableCollision;
public float3 MaxImpulse = float.PositiveInfinity;
void OnEnable()
{
// included so tick box appears in Editor
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/FreeHingeJoint.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
public class FreeHingeJoint : BallAndSocketJoint
{
// Editor only settings
[HideInInspector]
public bool EditAxes;
public float3 HingeAxisLocal;
public float3 HingeAxisInConnectedEntity;
public override void UpdateAuto()
{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
HingeAxisInConnectedEntity = math.mul(bFromA.rot, HingeAxisLocal);
}
}
}
class FreeHingeJointBaker : JointBaker<FreeHingeJoint>
{
public override void Bake(FreeHingeJoint authoring)
{
authoring.UpdateAuto();
Math.CalculatePerpendicularNormalized(authoring.HingeAxisLocal, out var perpendicularLocal, out _);
Math.CalculatePerpendicularNormalized(authoring.HingeAxisInConnectedEntity, out var perpendicularConnected, out _);
var physicsJoint = PhysicsJoint.CreateHinge(
new BodyFrame {Axis = authoring.HingeAxisLocal, Position = authoring.PositionLocal, PerpendicularAxis = perpendicularLocal},
new BodyFrame {Axis = authoring.HingeAxisInConnectedEntity, Position = authoring.PositionInConnectedEntity, PerpendicularAxis = perpendicularConnected }
);
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntity(worldIndex, constraintBodyPair, physicsJoint);
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics Authoring/Unity.Physics.Custom/Joints/LimitDOFJoint.cs
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
// This Joint allows you to lock one or more of the 6 degrees of freedom of a constrained body.
// This is achieved by combining the appropriate lower level 'constraint atoms' to form the higher level Joint.
// In this case Linear and Angular constraint atoms are combined.
// One use-case for this Joint could be to restrict a 3d simulation to a 2d plane.
public class LimitDOFJoint : BaseJoint
{
public bool3 LockLinearAxes;
public bool3 LockAngularAxes;
public PhysicsJoint CreateLimitDOFJoint(RigidTransform offset)
{
var constraints = new FixedList512Bytes<Constraint>();
if (math.any(LockLinearAxes))
{
constraints.Add(new Constraint
{
ConstrainedAxes = LockLinearAxes,
Type = ConstraintType.Linear,
Min = 0,
Max = 0,
SpringFrequency = Constraint.DefaultSpringFrequency,
SpringDamping = Constraint.DefaultSpringDamping,
MaxImpulse = MaxImpulse,
});
}
if (math.any(LockAngularAxes))
{
constraints.Add(new Constraint
{
ConstrainedAxes = LockAngularAxes,
Type = ConstraintType.Angular,
Min = 0,
Max = 0,
SpringFrequency = Constraint.DefaultSpringFrequency,
SpringDamping = Constraint.DefaultSpringDamping,
MaxImpulse = MaxImpulse,
});
}
var joint = new PhysicsJoint
{
BodyAFromJoint = BodyFrame.Identity,
BodyBFromJoint = offset
};
joint.SetConstraints(constraints);
return joint;
}
}
class LimitDOFJointBaker : Baker<LimitDOFJoint>
{
public Entity CreateJointEntity(uint worldIndex, PhysicsConstrainedBodyPair constrainedBodyPair, PhysicsJoint joint)
{
using (var joints = new NativeArray<PhysicsJoint>(1, Allocator.Temp) { [0] = joint })
using (var jointEntities = new NativeList<Entity>(1, Allocator.Temp))
{
CreateJointEntities(worldIndex, constrainedBodyPair, joints, jointEntities);
return jointEntities[0];
}
}
public void CreateJointEntities(uint worldIndex, PhysicsConstrainedBodyPair constrainedBodyPair, NativeArray<PhysicsJoint> joints, NativeList<Entity> newJointEntities = default
{
if (!joints.IsCreated || joints.Length == 0)
return;
if (newJointEntities.IsCreated)
newJointEntities.Clear();
else
newJointEntities = new NativeList<Entity>(joints.Length, Allocator.Temp);
// create all new joints
var multipleJoints = joints.Length > 1;
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
namespace Unity.Physics.Authoring
{
// stores an initial value and a pair of scalar curves to apply to relevant constraints on the joint
struct ModifyJointLimits : ISharedComponentData, IEquatable<ModifyJointLimits>
{
public PhysicsJoint InitialValue;
public ParticleSystem.MinMaxCurve AngularRangeScalar;
public ParticleSystem.MinMaxCurve LinearRangeScalar;
public bool Equals(ModifyJointLimits other) =>
AngularRangeScalar.Equals(other.AngularRangeScalar) && LinearRangeScalar.Equals(other.LinearRangeScalar);
public override bool Equals(object obj) => obj is ModifyJointLimits other && Equals(other);
_ModifyJointLimitsBakingDataQuery.AddChangedVersionFilter(typeof(ModifyJointLimitsBakingData));
_JointEntityBakingQuery.AddChangedVersionFilter(typeof(JointEntityBaking));
}
public void OnUpdate(ref SystemState state)
{
if (_ModifyJointLimitsBakingDataQuery.IsEmpty && _JointEntityBakingQuery.IsEmpty)
{
return;
}
// Collect all the joints
NativeParallelMultiHashMap<Entity, (Entity, PhysicsJoint)> jointsLookUp =
new NativeParallelMultiHashMap<Entity, (Entity, PhysicsJoint)>(10, Allocator.TempJob);
foreach (var(jointEntity, physicsJoint, entity) in SystemAPI
.Query<RefRO<JointEntityBaking>, RefRO<PhysicsJoint>>().WithEntityAccess()
.WithOptions(EntityQueryOptions.IncludeDisabledEntities | EntityQueryOptions.IncludePrefab))
{
jointsLookUp.Add(jointEntity.ValueRO.Entity, (entity, physicsJoint.ValueRO));
}
foreach (var(modifyJointLimits, entity) in SystemAPI.Query<ModifyJointLimitsBakingData>()
.WithEntityAccess().WithOptions(EntityQueryOptions.IncludeDisabledEntities |
EntityQueryOptions.IncludePrefab))
{
var angularModification = new ParticleSystem.MinMaxCurve(
multiplier: math.radians(modifyJointLimits.AngularRangeScalar.curveMultiplier),
min: modifyJointLimits.AngularRangeScalar.curveMin,
max: modifyJointLimits.AngularRangeScalar.curveMax
);
foreach (var joint in jointsLookUp.GetValuesForKey(entity))
{
state.EntityManager.SetSharedComponentManaged(joint.Item1, new ModifyJointLimits
{
InitialValue = joint.Item2,
AngularRangeScalar = angularModification,
LinearRangeScalar = modifyJointLimits.LinearRangeScalar
});
}
}
jointsLookUp.Dispose();
}
}
// apply an animated effect to the limits on supported types of joints
[RequireMatchingQueriesForUpdate]
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
[UpdateAfter(typeof(PhysicsSystemGroup))]
partial struct ModifyJointLimitsSystem : ISystem
{
public void OnUpdate(ref SystemState state)
{
var time = (float)SystemAPI.Time.ElapsedTime;
foreach (var(joint, modification) in SystemAPI.Query<RefRW<PhysicsJoint>, ModifyJointLimits>())
{
var animatedAngularScalar = new FloatRange(
modification.AngularRangeScalar.curveMin.Evaluate(time),
modification.AngularRangeScalar.curveMax.Evaluate(time)
);
var animatedLinearScalar = new FloatRange(
modification.LinearRangeScalar.curveMin.Evaluate(time),
modification.LinearRangeScalar.curveMax.Evaluate(time)
);
// in each case, get relevant properties from the initial value based on joint type, and apply scalar
switch (joint.ValueRW.JointType)
{
// Custom type could be anything, so this demo just applies changes to all constraints
case JointType.Custom:
var constraints = modification.InitialValue.GetConstraints();
for (var i = 0; i < constraints.Length; i++)
{
var constraint = constraints[i];
var isAngular = constraint.Type == ConstraintType.Angular;
var scalar = math.select(animatedLinearScalar, animatedAngularScalar, isAngular);
var constraintRange = (FloatRange)(new float2(constraint.Min, constraint.Max) * scalar);
constraint.Min = constraintRange.Min;
constraint.Max = constraintRange.Max;
constraints[i] = constraint;
}
joint.ValueRW.SetConstraints(constraints);
break;
// other types have corresponding getters/setters to retrieve more meaningful data
case JointType.LimitedDistance:
var distanceRange = modification.InitialValue.GetLimitedDistanceRange();
joint.ValueRW.SetLimitedDistanceRange(distanceRange * (float2)animatedLinearScalar);
break;
case JointType.LimitedHinge:
var angularRange = modification.InitialValue.GetLimitedHingeRange();
joint.ValueRW.SetLimitedHingeRange(angularRange * (float2)animatedAngularScalar);
break;
case JointType.Prismatic:
var distanceOnAxis = modification.InitialValue.GetPrismaticRange();
joint.ValueRW.SetPrismaticRange(distanceOnAxis * (float2)animatedLinearScalar);
break;
// ragdoll joints are composed of two separate joints with different meanings
case JointType.RagdollPrimaryCone:
modification.InitialValue.GetRagdollPrimaryConeAndTwistRange(
out var maxConeAngle,
out var angularTwistRange
);
joint.ValueRW.SetRagdollPrimaryConeAndTwistRange(
maxConeAngle * animatedAngularScalar.Max,
angularTwistRange * (float2)animatedAngularScalar
);
break;
case JointType.RagdollPerpendicularCone:
var angularPlaneRange = modification.InitialValue.GetRagdollPerpendicularConeRange();
joint.ValueRW.SetRagdollPerpendicularConeRange(angularPlaneRange *
(float2)animatedAngularScalar);
break;
// remaining types have no limits on their Constraint atoms to meaningfully modify
case JointType.BallAndSocket:
case JointType.Fixed:
case JointType.Hinge:
break;
}
}
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/PrismaticJoint.cs
using Unity.Entities;
using Unity.Mathematics;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class PrismaticJoint : BallAndSocketJoint
{
public float3 AxisLocal;
public float3 AxisInConnectedEntity;
public float3 PerpendicularAxisLocal;
public float3 PerpendicularAxisInConnectedEntity;
public float MinDistanceOnAxis;
public float MaxDistanceOnAxis;
[SerializeField]
int m_Version;
public float3 TwistAxisLocal;
public float3 TwistAxisInConnectedEntity;
public float3 PerpendicularAxisLocal;
public float3 PerpendicularAxisInConnectedEntity;
public float MaxConeAngle;
public float MinPerpendicularAngle;
public float MaxPerpendicularAngle;
public float MinTwistAngle;
public float MaxTwistAngle;
internal void UpgradeVersionIfNecessary()
{
if (m_Version >= k_LatestVersion)
return;
MinPerpendicularAngle -= 90f;
MaxPerpendicularAngle -= 90f;
m_Version = k_LatestVersion;
}
void OnValidate()
{
UpgradeVersionIfNecessary();
MaxConeAngle = math.clamp(MaxConeAngle, 0f, 180f);
MaxPerpendicularAngle = math.clamp(MaxPerpendicularAngle, -90f, 90f);
MinPerpendicularAngle = math.clamp(MinPerpendicularAngle, -90f, 90f);
if (MaxPerpendicularAngle < MinPerpendicularAngle)
{
var swap = new FloatRange(MinPerpendicularAngle, MaxPerpendicularAngle).Sorted();
MinPerpendicularAngle = swap.Min;
MaxPerpendicularAngle = swap.Max;
}
MinTwistAngle = math.clamp(MinTwistAngle, -180f, 180f);
MaxTwistAngle = math.clamp(MaxTwistAngle, -180f, 180f);
if (MaxTwistAngle < MinTwistAngle)
{
var swap = new FloatRange(MinTwistAngle, MaxTwistAngle).Sorted();
MinTwistAngle = swap.Min;
MaxTwistAngle = swap.Max;
}
}
public override void UpdateAuto()
{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
TwistAxisInConnectedEntity = math.mul(bFromA.rot, TwistAxisLocal);
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
}
}
}
class RagdollJointBaker : JointBaker<RagdollJoint>
{
public override void Bake(RagdollJoint authoring)
{
authoring.UpdateAuto();
authoring.UpgradeVersionIfNecessary();
PhysicsJoint.CreateRagdoll(
new BodyFrame { Axis = authoring.TwistAxisLocal, PerpendicularAxis = authoring.PerpendicularAxisLocal, Position = authoring.PositionLocal },
new BodyFrame { Axis = authoring.TwistAxisInConnectedEntity, PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity, Position = authoring.PositionInConnectedEntit
math.radians(authoring.MaxConeAngle),
math.radians(new FloatRange(authoring.MinPerpendicularAngle, authoring.MaxPerpendicularAngle)),
math.radians(new FloatRange(authoring.MinTwistAngle, authoring.MaxTwistAngle)),
out var primaryCone,
out var perpendicularCone
);
primaryCone.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
perpendicularCone.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
using NativeList<Entity> entities = new NativeList<Entity>(1, Allocator.TempJob);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntities(worldIndex,
constraintBodyPair,
new NativeArray<PhysicsJoint>(2, Allocator.Temp) { [0] = primaryCone, [1] = perpendicularCone }, entities);
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/RigidJoint.cs
using Unity.Entities;
using Unity.Mathematics;
namespace Unity.Physics.Authoring
{
public class RigidJoint : BallAndSocketJoint
{
public quaternion OrientationLocal = quaternion.identity;
public quaternion OrientationInConnectedEntity = quaternion.identity;
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
namespace Unity.Physics.Authoring
{
public class AngularVelocityMotor : BaseJoint
{
[Tooltip("An offset from center of entity with motor. Representing the anchor/pivot point of rotation")]
public float3 PivotPosition;
[Tooltip("The axis of rotation of the motor. Value will be normalized")]
public float3 AxisOfRotation;
[Tooltip("Target speed for the motor to maintain, in degrees/s")]
public float TargetSpeed;
[Tooltip("The magnitude of the maximum impulse the motor can exert in a single step. Applies only to the motor constraint.")]
public float MaxImpulseAppliedByMotor = math.INFINITY;
private float3 PerpendicularAxisLocal;
private float3 PositionInConnectedEntity;
private float3 HingeAxisInConnectedEntity;
private float3 PerpendicularAxisInConnectedEntity;
class AngularVelocityMotorBaker : JointBaker<AngularVelocityMotor>
{
public override void Bake(AngularVelocityMotor authoring)
{
float3 axisInA = math.normalize(authoring.AxisOfRotation);
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.PivotPosition); //position of motored body pivot relative to Connected Entity in world space
authoring.HingeAxisInConnectedEntity = math.mul(bFromA.rot, axisInA); //motor axis in Connected Entity space
// Always calculate the perpendicular axes
Math.CalculatePerpendicularNormalized(axisInA, out var perpendicularLocal, out _);
authoring.PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, perpendicularLocal); //perp motor axis in Connected Entity space
namespace Unity.Physics.Authoring
{
public class PositionMotor : BaseJoint
{
[Tooltip("An offset from the center of the body with the motor, representing the anchor point of translation.")]
public float3 AnchorPosition;
[Tooltip("The direction of the motor, relative to the orientation of the Connected Body (bodyB). Value will be normalized")]
public float3 DirectionOfMovement;
[Tooltip("Motor will drive this length away from the anchor position of bodyA.")]
public float TargetDistance;
[Tooltip("The magnitude of the maximum impulse the motor can exert in a single step. Applies only to the motor constraint.")]
public float MaxImpulseAppliedByMotor = math.INFINITY;
private float3 PerpendicularAxisLocal;
private float3 PositionInConnectedEntity;
private float3 AxisInConnectedEntity;
private float3 PerpendicularAxisInConnectedEntity;
joint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntity(worldIndex, constraintBodyPair, joint);
}
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Motors/RotationalMotor.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
public class RotationalMotor : BaseJoint
{
[Tooltip("An offset from center of entity with motor. Representing the anchor/pivot point of rotation")]
public float3 PivotPosition;
[Tooltip("The axis of rotation of the motor. Value will be normalized")]
public float3 AxisOfRotation;
[Tooltip("Motor will maintain this target angle around the AxisOfRotation, in degrees")]
public float TargetAngle;
[Tooltip("The magnitude of the maximum impulse the motor can exert in one step. Applies only to the motor constraint.")]
public float MaxImpulseAppliedByMotor = math.INFINITY;
private float3 PerpendicularAxisLocal;
private float3 PositionInConnectedEntity;
private float3 HingeAxisInConnectedEntity;
private float3 PerpendicularAxisInConnectedEntity;
class RotationalMotorBaker : JointBaker<RotationalMotor>
{
public override void Bake(RotationalMotor authoring)
{
float3 axisInA = math.normalize(authoring.AxisOfRotation);
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.PivotPosition); //position of motored body pivot relative to Connected Entity in world space
authoring.HingeAxisInConnectedEntity = math.mul(bFromA.rot, axisInA); //motor axis in Connected Entity space
// Always calculate the perpendicular axes
Math.CalculatePerpendicularNormalized(axisInA, out var perpendicularLocal, out _);
authoring.PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, perpendicularLocal); //perp motor axis in Connected Entity space
#region Sphere
[BurstCompile]
struct BakeSphereJob : IJob
{
public NativeArray<SphereGeometry> Sphere;
public NativeArray<EulerAngles> Orientation;
// TODO: make members PascalCase after merging static query fixes
public float4x4 localToWorld;
public float4x4 shapeToWorld;
public void Execute()
{
var center = Sphere[0].Center;
var radius = Sphere[0].Radius;
var orientation = Orientation[0];
var basisToWorld = GetBasisToWorldMatrix(localToWorld, center, orientation, 1f);
var basisPriority = basisToWorld.HasShear() ? GetBasisAxisPriority(basisToWorld) : k_DefaultAxisPriority;
var bakeToShape = GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center, ref orientation, 1f, basisPriority);
radius *= math.cmax(bakeToShape.DecomposeScale());
Sphere[0] = new SphereGeometry
{
Center = center,
Radius = radius
};
Orientation[0] = orientation;
}
}
internal static SphereGeometry BakeToBodySpace(
this SphereGeometry sphere, float4x4 localToWorld, float4x4 shapeToWorld, ref EulerAngles orientation
)
{
using (var geometry = new NativeArray<SphereGeometry>(1, Allocator.TempJob) { [0] = sphere })
using (var outOrientation = new NativeArray<EulerAngles>(1, Allocator.TempJob) { [0] = orientation })
{
var job = new BakeSphereJob
{
Sphere = geometry,
Orientation = outOrientation,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld
};
job.Run();
orientation = outOrientation[0];
return geometry[0];
}
}
#endregion
#region Plane
[BurstCompile]
struct BakePlaneJob : IJob
{
public NativeArray<float3x4> Vertices;
// TODO: make members PascalCase after merging static query fixes
public float3 center;
public float2 size;
public EulerAngles orientation;
public float4x4 localToWorld;
public float4x4 shapeToWorld;
#endregion
#region ShapeInputHash
#if !(UNITY_ANDROID && !UNITY_64) // !Android32
// Getting memory alignment errors from HashUtility.Hash128 on Android32
[BurstCompile]
#endif
internal struct GetShapeInputsHashJob : IJob
{
public NativeArray<Hash128> Result;
#region AABB
[BurstCompile]
internal struct GetAabbJob : IJob
{
[ReadOnly] public NativeArray<float3> Points;
public NativeArray<Aabb> Aabb;
public void Execute()
{
var aabb = new Aabb { Min = float.MaxValue, Max = float.MinValue };
for (var i = 0; i < Points.Length; ++i)
aabb.Include(Points[i]);
Aabb[0] = aabb;
}
}
#endregion
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/BakeGeometryJobsExtensions.cs
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
static partial class PhysicsShapeExtensions
{
public static class BakeBoxJobExtension
{
internal static float4x4 GetBakeToShape(PhysicsShapeAuthoring shape, float3 center, EulerAngles orientation)
{
var transform = shape.transform;
var localToWorld = (float4x4)transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
return BakeBoxJob.GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientation);
}
}
public static class BakeCapsuleJobExtension
{
internal static float4x4 GetBakeToShape(PhysicsShapeAuthoring shape, float3 center, EulerAngles orientation)
{
var transform = shape.transform;
var localToWorld = (float4x4)transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
return BakeCapsuleJob.GetBakeToShape(localToWorld, shapeToWorld, ref center,
ref orientation);
}
}
public static void SetBakedCapsuleSize(this PhysicsShapeAuthoring shape, float height, float radius)
{
var capsule = shape.GetCapsuleProperties();
var center = capsule.Center;
namespace Unity.Physics.Authoring
{
/// <summary>
/// A structure for storing authoring data for a capsule shape. In contrast to the
/// CapsuleGeometry struct in the run-time, this structure permits storing stable orientation
/// values, as well as height values that can be retained when the source data are defined with
/// respect to a non-uniformly scaled object.
/// </summary>
[Serializable]
public struct CapsuleGeometryAuthoring : IEquatable<CapsuleGeometryAuthoring>
{
/// <summary>
/// The local orientation of the capsule. It is aligned with the forward axis (z) when it is
/// identity.
/// </summary>
public quaternion Orientation { get => m_OrientationEuler; set => m_OrientationEuler.SetValue(value); }
internal EulerAngles OrientationEuler { get => m_OrientationEuler; set => m_OrientationEuler = value; }
[SerializeField]
EulerAngles m_OrientationEuler;
/// <summary> The local position offset of the capsule. </summary>
public float3 Center { get => m_Center; set => m_Center = value; }
[SerializeField]
float3 m_Center;
/// <summary>
/// The height of the capsule. It may store any value, but will ultimately always be converted
/// into a value that is at least twice the radius.
/// </summary>
public float Height { get => m_Height; set => m_Height = value; }
[SerializeField]
float m_Height;
namespace Unity.Physics.Authoring
{
public static class ConvexHullGenerationParametersExtensions
{
// recommended simplification tolerance is at least 1 centimeter
internal const float k_MinRecommendedSimplificationTolerance = 0.01f;
if (points.Length <= 1)
return;
internal static void OnValidate(ref this ConvexHullGenerationParameters generationParameters, float maxAngle = 180f)
{
generationParameters.SimplificationTolerance = math.max(0f, generationParameters.SimplificationTolerance);
generationParameters.BevelRadius = math.max(0f, generationParameters.BevelRadius);
generationParameters.MinimumAngle = math.clamp(generationParameters.MinimumAngle, 0f, maxAngle);
}
namespace Unity.Physics.Authoring
{
[Serializable]
internal struct EulerAngles : IEquatable<EulerAngles>
{
public static EulerAngles Default => new EulerAngles { RotationOrder = math.RotationOrder.ZXY };
public float3 Value;
[HideInInspector]
public math.RotationOrder RotationOrder;
internal void SetValue(quaternion value) => Value = math.degrees(Math.ToEulerAngles(value, RotationOrder));
if (m_CheckIfComponentBelongsToShape)
{
if (PhysicsShapeExtensions.GetPrimaryBody(child.gameObject) != m_PrimaryBody)
return false;
child.gameObject.GetComponentsInParent(true, s_PhysicsShapes);
if (s_PhysicsShapes[0] != m_Shape)
{
s_PhysicsShapes.Clear();
return false;
}
}
// do not simply use GameObject.activeInHierarchy because it will be false when instantiating a prefab
var t = child.transform;
var activeInHierarchy = t.gameObject.activeSelf;
while (activeInHierarchy && t != m_Root)
{
t = t.parent;
activeInHierarchy &= t.gameObject.activeSelf;
}
return activeInHierarchy;
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/PhysicsShapeExtensions.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
// put static UnityObject buffers in separate utility class so other methods can Burst compile
static class PhysicsShapeExtensions_NonBursted
{
internal static readonly List<PhysicsBodyAuthoring> s_PhysicsBodiesBuffer = new List<PhysicsBodyAuthoring>(16);
internal static readonly List<PhysicsShapeAuthoring> s_ShapesBuffer = new List<PhysicsShapeAuthoring>(16);
internal static readonly List<Rigidbody> s_RigidbodiesBuffer = new List<Rigidbody>(16);
internal static readonly List<UnityEngine.Collider> s_CollidersBuffer = new List<UnityEngine.Collider>(16);
}
public static partial class PhysicsShapeExtensions
{
// used for de-skewing basis vectors; default priority assumes primary axis is z, secondary axis is y
public static readonly int3 k_DefaultAxisPriority = new int3(2, 1, 0);
// matrix to transform point from shape's local basis into world space
public static float4x4 GetBasisToWorldMatrix(
float4x4 localToWorld, float3 center, quaternion orientation, float3 size
) =>
math.mul(localToWorld, float4x4.TRS(center, orientation, size));
static float4 DeskewSecondaryAxis(float4 primaryAxis, float4 secondaryAxis)
{
var n0 = math.normalizesafe(primaryAxis);
var dot = math.dot(secondaryAxis, n0);
return secondaryAxis - n0 * dot;
}
// priority is determined by length of each size dimension in the shape's basis after applying localToWorld transformation
public static int3 GetBasisAxisPriority(float4x4 basisToWorld)
{
var basisAxisLengths = basisToWorld.DecomposeScale();
var max = math.cmax(basisAxisLengths);
var min = math.cmin(basisAxisLengths);
if (max == min)
return k_DefaultAxisPriority;
basisAxisLengths = basisToWorld.DecomposeScale();
min = math.cmin(basisAxisLengths);
var imin = min == basisAxisLengths.x ? 0 : min == basisAxisLengths.y ? 1 : 2;
if (imin == imax)
imin = k_NextAxis[imax];
var imid = k_NextAxis[imax] == imin ? k_PrevAxis[imax] : k_NextAxis[imax];
[Conditional(CompilationSymbols.CollectionsChecksSymbol), Conditional(CompilationSymbols.DebugChecksSymbol)]
static void CheckBasisPriorityAndThrow(int3 basisPriority)
{
if (
basisPriority.x == basisPriority.y
|| basisPriority.x == basisPriority.z
|| basisPriority.y == basisPriority.z
)
throw new ArgumentException(nameof(basisPriority));
}
public static bool HasNonUniformScale(this float4x4 m)
{
var s = new float3(math.lengthsq(m.c0.xyz), math.lengthsq(m.c1.xyz), math.lengthsq(m.c2.xyz));
return math.cmin(s) != math.cmax(s);
}
// matrix to transform point on a primitive from bake space into space of the shape
internal static float4x4 GetPrimitiveBakeToShapeMatrix(
float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center, ref EulerAngles orientation, float3 scale, int3 basisPriority
)
{
CheckBasisPriorityAndThrow(basisPriority);
var localToBasis = float4x4.TRS(center, orientation, scale);
// correct for imprecision in cases of no scale to prevent e.g., convex radius from being altered
if (scale.Equals(new float3(1f)))
{
localToBasis.c0 = math.normalizesafe(localToBasis.c0);
localToBasis.c1 = math.normalizesafe(localToBasis.c1);
localToBasis.c2 = math.normalizesafe(localToBasis.c2);
}
var localToBake = math.mul(localToWorld, localToBasis);
if (localToBake.HasNonUniformScale() || localToBake.HasShear())
{
// deskew second longest axis with respect to longest axis
localToBake[basisPriority[1]] =
DeskewSecondaryAxis(localToBake[basisPriority[0]], localToBake[basisPriority[1]]);
return bakeToShape;
}
public static void SetBakedCylinderSize(this PhysicsShapeAuthoring shape, float height, float radius, float bevelRadius)
{
var cylinder = shape.GetCylinderProperties(out EulerAngles orientation);
var center = cylinder.Center;
var bakeToShape = BakeCylinderJobExtension.GetBakeToShape(shape, center, orientation);
var scale = bakeToShape.DecomposeScale();
namespace Unity.Physics.Authoring
{
sealed class EnumFlagsAttribute : PropertyAttribute {}
sealed class ExpandChildrenAttribute : PropertyAttribute {}
sealed class SoftRangeAttribute : PropertyAttribute
{
public readonly float SliderMin;
public readonly float SliderMax;
public float TextFieldMin { get; set; }
public float TextFieldMax { get; set; }
[assembly: InternalsVisibleTo("Unity.Physics.Custom.EditModeTests")]
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/BallAndSocketJointEditor.cs
#if UNITY_EDITOR
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(BallAndSocketJoint))]
public class BallAndSocketEditor : UnityEditor.Editor
{
protected virtual void OnSceneGUI()
{
BallAndSocketJoint ballAndSocket = (BallAndSocketJoint)target;
EditorGUI.BeginChangeCheck();
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(CustomPhysicsMaterialTagNames))]
[CanEditMultipleObjects]
class CustomPhysicsMaterialTagNamesEditor : BaseEditor
{
#pragma warning disable 649
[AutoPopulate(ElementFormatString = "Custom Physics Material Tag {0}", Resizable = false, Reorderable = false)]
ReorderableList m_TagNames;
#pragma warning restore 649
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/EditorUtilities.cs
using UnityEngine;
using Unity.Mathematics;
using static Unity.Physics.Math;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.IMGUI.Controls;
namespace Unity.Physics.Editor
{
/// <summary>
/// Provides utilities that use Handles to set positions and axes,
/// </summary>
public class EditorUtilities
{
// Editor for a joint pivot or pivot pair
public static void EditPivot(RigidTransform worldFromA, RigidTransform worldFromB, bool lockBtoA,
ref float3 pivotA, ref float3 pivotB, Object target)
{
EditorGUI.BeginChangeCheck();
float3 pivotAinW = Handles.PositionHandle(math.transform(worldFromA, pivotA), quaternion.identity);
float3 pivotBinW;
if (lockBtoA)
{
pivotBinW = pivotAinW;
pivotB = math.transform(math.inverse(worldFromB), pivotBinW);
}
else
{
pivotBinW = Handles.PositionHandle(math.transform(worldFromB, pivotB), quaternion.identity);
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Edit joint pivot");
pivotA = math.transform(math.inverse(worldFromA), pivotAinW);
pivotB = math.transform(math.inverse(worldFromB), pivotBinW);
}
Handles.DrawLine(worldFromA.pos, pivotAinW);
Handles.DrawLine(worldFromB.pos, pivotBinW);
}
// Editor for a joint axis or axis pair
public class AxisEditor
{
// Even though we're only editing axes and not rotations, we need to track a full rotation in order to keep the rotation handle stable
private quaternion m_RefA = quaternion.identity;
private quaternion m_RefB = quaternion.identity;
// Detect changes in the object being edited to reset the reference orientations
private Object m_LastTarget;
private static bool NormalizeSafe(ref float3 x)
{
float lengthSq = math.lengthsq(x);
const float epsSq = 1e-8f;
if (math.abs(lengthSq - 1) > epsSq)
{
if (lengthSq > epsSq)
{
x *= math.rsqrt(lengthSq);
}
else
{
x = new float3(1, 0, 0);
}
return true;
}
return false;
}
private static bool NormalizePerpendicular(float3 axis, ref float3 perpendicular)
{
// make sure perpendicular is actually perpendicular to direction
float dot = math.dot(axis, perpendicular);
float absDot = math.abs(dot);
if (absDot > 1.0f - 1e-5f)
{
// parallel, choose an arbitrary perpendicular
float3 dummy;
CalculatePerpendicularNormalized(axis, out perpendicular, out dummy);
return true;
}
if (absDot > 1e-5f)
{
// reject direction
perpendicular -= dot * axis;
NormalizeSafe(ref perpendicular);
return true;
}
return NormalizeSafe(ref perpendicular);
}
public void Update(RigidTransform worldFromA, RigidTransform worldFromB, bool lockBtoA, float3 pivotA, float3 pivotB,
ref float3 directionA, ref float3 directionB, ref float3 perpendicularA, ref float3 perpendicularB, Object target)
{
// Work in world space
float3 directionAinW = math.rotate(worldFromA, directionA);
float3 directionBinW = math.rotate(worldFromB, directionB);
float3 perpendicularAinW = math.rotate(worldFromB, perpendicularA);
float3 perpendicularBinW = math.rotate(worldFromB, perpendicularB);
bool changed = false;
// If the target changed, fix up the inputs and reset the reference orientations to align with the new target's axes
if (target != m_LastTarget)
{
m_LastTarget = target;
// Enforce normalized directions
changed |= NormalizeSafe(ref directionAinW);
changed |= NormalizeSafe(ref directionBinW);
// Enforce normalized perpendiculars, orthogonal to their respective directions
changed |= NormalizePerpendicular(directionAinW, ref perpendicularAinW);
changed |= NormalizePerpendicular(directionBinW, ref perpendicularBinW);
// Calculate the rotation of the joint in A from direction and perpendicular
float3x3 rotationA = new float3x3(directionAinW, perpendicularAinW, math.cross(directionAinW, perpendicularAinW));
m_RefA = new quaternion(rotationA);
if (lockBtoA)
{
m_RefB = m_RefA;
}
else
{
// Calculate the rotation of the joint in B from direction and perpendicular
float3x3 rotationB = new float3x3(directionBinW, perpendicularBinW, math.cross(directionBinW, perpendicularBinW));
m_RefB = new quaternion(rotationB);
}
}
EditorGUI.BeginChangeCheck();
// Make rotators
quaternion oldRefA = m_RefA;
quaternion oldRefB = m_RefB;
float3 pivotAinW = math.transform(worldFromA, pivotA);
m_RefA = Handles.RotationHandle(m_RefA, pivotAinW);
float3 pivotBinW;
if (lockBtoA)
{
directionB = math.rotate(math.inverse(worldFromB), directionAinW);
perpendicularB = math.rotate(math.inverse(worldFromB), perpendicularAinW);
pivotBinW = pivotAinW;
m_RefB = m_RefA;
}
else
{
pivotBinW = math.transform(worldFromB, pivotB);
m_RefB = Handles.RotationHandle(m_RefB, pivotBinW);
}
// Apply changes from the rotators
if (EditorGUI.EndChangeCheck())
{
quaternion dqA = math.mul(m_RefA, math.inverse(oldRefA));
quaternion dqB = math.mul(m_RefB, math.inverse(oldRefB));
directionAinW = math.mul(dqA, directionAinW);
directionBinW = math.mul(dqB, directionBinW);
perpendicularAinW = math.mul(dqB, perpendicularAinW);
perpendicularBinW = math.mul(dqB, perpendicularBinW);
changed = true;
}
// Write back if the axes changed
if (changed)
{
Undo.RecordObject(target, "Edit joint axis");
directionA = math.rotate(math.inverse(worldFromA), directionAinW);
directionB = math.rotate(math.inverse(worldFromB), directionBinW);
perpendicularA = math.rotate(math.inverse(worldFromB), perpendicularAinW);
perpendicularB = math.rotate(math.inverse(worldFromB), perpendicularBinW);
}
// Draw the updated axes
float3 z = new float3(0, 0, 1); // ArrowHandleCap() draws an arrow pointing in (0, 0, 1)
Handles.ArrowHandleCap(0, pivotAinW, Quaternion.FromToRotation(z, directionAinW), HandleUtility.GetHandleSize(pivotAinW) * 0.75f, Event.current.type);
Handles.ArrowHandleCap(0, pivotAinW, Quaternion.FromToRotation(z, perpendicularAinW), HandleUtility.GetHandleSize(pivotAinW) * 0.75f, Event.current.type);
if (!lockBtoA)
{
Handles.ArrowHandleCap(0, pivotBinW, Quaternion.FromToRotation(z, directionBinW), HandleUtility.GetHandleSize(pivotBinW) * 0.75f, Event.current.type);
Handles.ArrowHandleCap(0, pivotBinW, Quaternion.FromToRotation(z, perpendicularBinW), HandleUtility.GetHandleSize(pivotBinW) * 0.75f, Event.current.type);
}
}
}
public static void EditLimits(RigidTransform worldFromA, RigidTransform worldFromB, float3 pivotA, float3 axisA, float3 axisB, float3 perpendicularA, float3 perpendicularB
ref float minLimit, ref float maxLimit, JointAngularLimitHandle limitHandle, Object target)
{
// Transform to world space
float3 pivotAinW = math.transform(worldFromA, pivotA);
float3 axisAinW = math.rotate(worldFromA, axisA);
float3 perpendicularAinW = math.rotate(worldFromA, perpendicularA);
float3 axisBinW = math.rotate(worldFromA, axisB);
float3 perpendicularBinW = math.rotate(worldFromB, perpendicularB);
// Get rotations from joint space
// JointAngularLimitHandle uses axis = (1, 0, 0) with angle = 0 at (0, 0, 1), so choose the rotations to point those in the directions of our axis and perpendicular
float3x3 worldFromJointA = new float3x3(axisAinW, -math.cross(axisAinW, perpendicularAinW), perpendicularAinW);
float3x3 worldFromJointB = new float3x3(axisBinW, -math.cross(axisBinW, perpendicularBinW), perpendicularBinW);
float3x3 jointBFromA = math.mul(math.transpose(worldFromJointB), worldFromJointA);
// Set orientation for the angular limit control
float angle = CalculateTwistAngle(new quaternion(jointBFromA), 0); // index = 0 because axis is the first column in worldFromJoint
quaternion limitOrientation = math.mul(quaternion.AxisAngle(axisAinW, angle), new quaternion(worldFromJointA));
Matrix4x4 handleMatrix = Matrix4x4.TRS(pivotAinW, limitOrientation, Vector3.one);
float size = HandleUtility.GetHandleSize(pivotAinW) * 0.75f;
limitHandle.xMin = -maxLimit;
limitHandle.xMax = -minLimit;
limitHandle.xMotion = ConfigurableJointMotion.Limited;
limitHandle.yMotion = ConfigurableJointMotion.Locked;
limitHandle.zMotion = ConfigurableJointMotion.Locked;
limitHandle.yHandleColor = new Color(0, 0, 0, 0);
limitHandle.zHandleColor = new Color(0, 0, 0, 0);
limitHandle.radius = size;
using (new Handles.DrawingScope(handleMatrix))
{
// Draw the reference axis
float3 z = new float3(0, 0, 1); // ArrowHandleCap() draws an arrow pointing in (0, 0, 1)
Handles.ArrowHandleCap(0, float3.zero, Quaternion.FromToRotation(z, new float3(1, 0, 0)), size, Event.current.type);
// Draw the limit editor handle
EditorGUI.BeginChangeCheck();
limitHandle.DrawHandle();
if (EditorGUI.EndChangeCheck())
{
// Record the target object before setting new limits so changes can be undone/redone
Undo.RecordObject(target, "Edit joint angular limits");
minLimit = -limitHandle.xMax;
maxLimit = -limitHandle.xMin;
}
}
}
}
}
#endif
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/LimitedHingeJointEditor.cs
#if UNITY_EDITOR
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(LimitedHingeJoint))]
public class LimitedHingeEditor : UnityEditor.Editor
{
private EditorUtilities.AxisEditor m_AxisEditor = new EditorUtilities.AxisEditor();
private JointAngularLimitHandle m_LimitHandle = new JointAngularLimitHandle();
EditorGUI.BeginChangeCheck();
GUILayout.BeginHorizontal();
GUILayout.Label("Editors:");
limitedHinge.EditPivots = GUILayout.Toggle(limitedHinge.EditPivots, new GUIContent("Pivot"), "Button");
limitedHinge.EditAxes = GUILayout.Toggle(limitedHinge.EditAxes, new GUIContent("Axis"), "Button");
limitedHinge.EditLimits = GUILayout.Toggle(limitedHinge.EditLimits, new GUIContent("Limits"), "Button");
GUILayout.EndHorizontal();
DrawDefaultInspector();
if (EditorGUI.EndChangeCheck())
{
SceneView.RepaintAll();
}
}
if (limitedHinge.EditPivots)
{
EditorUtilities.EditPivot(limitedHinge.worldFromA, limitedHinge.worldFromB, limitedHinge.AutoSetConnected,
ref limitedHinge.PositionLocal, ref limitedHinge.PositionInConnectedEntity, limitedHinge);
}
if (limitedHinge.EditAxes)
{
m_AxisEditor.Update(limitedHinge.worldFromA, limitedHinge.worldFromB,
limitedHinge.AutoSetConnected,
limitedHinge.PositionLocal, limitedHinge.PositionInConnectedEntity,
ref limitedHinge.HingeAxisLocal, ref limitedHinge.HingeAxisInConnectedEntity,
ref limitedHinge.PerpendicularAxisLocal, ref limitedHinge.PerpendicularAxisInConnectedEntity,
limitedHinge);
}
if (limitedHinge.EditLimits)
{
EditorUtilities.EditLimits(limitedHinge.worldFromA, limitedHinge.worldFromB,
limitedHinge.PositionLocal,
limitedHinge.HingeAxisLocal, limitedHinge.HingeAxisInConnectedEntity,
limitedHinge.PerpendicularAxisLocal, limitedHinge.PerpendicularAxisInConnectedEntity,
ref limitedHinge.MinAngle, ref limitedHinge.MaxAngle, m_LimitHandle, limitedHinge);
}
}
}
}
#endif
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/PhysicsBodyAuthoringEditor.cs
using System.Collections.Generic;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(PhysicsBodyAuthoring))]
[CanEditMultipleObjects]
class PhysicsBodyAuthoringEditor : BaseEditor
{
static class Content
{
public static readonly GUIContent MassLabel = EditorGUIUtility.TrTextContent("Mass");
public static readonly GUIContent CenterOfMassLabel = EditorGUIUtility.TrTextContent(
"Center of Mass", "Center of mass in the space of this body's transform."
);
public static readonly GUIContent InertiaTensorLabel = EditorGUIUtility.TrTextContent(
"Inertia Tensor", "Resistance to angular motion about each axis of rotation."
);
public static readonly GUIContent OrientationLabel = EditorGUIUtility.TrTextContent(
"Orientation", "Orientation of the body's inertia tensor in the space of its transform."
);
public static readonly GUIContent AdvancedLabel = EditorGUIUtility.TrTextContent(
"Advanced", "Advanced options"
);
}
#pragma warning disable 649
[AutoPopulate] SerializedProperty m_MotionType;
[AutoPopulate] SerializedProperty m_Smoothing;
[AutoPopulate] SerializedProperty m_Mass;
[AutoPopulate] SerializedProperty m_GravityFactor;
[AutoPopulate] SerializedProperty m_LinearDamping;
[AutoPopulate] SerializedProperty m_AngularDamping;
[AutoPopulate] SerializedProperty m_InitialLinearVelocity;
[AutoPopulate] SerializedProperty m_InitialAngularVelocity;
[AutoPopulate] SerializedProperty m_OverrideDefaultMassDistribution;
[AutoPopulate] SerializedProperty m_CenterOfMass;
[AutoPopulate] SerializedProperty m_Orientation;
[AutoPopulate] SerializedProperty m_InertiaTensor;
[AutoPopulate] SerializedProperty m_WorldIndex;
[AutoPopulate] SerializedProperty m_CustomTags;
#pragma warning restore 649
bool showAdvanced;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_MotionType);
if (m_MotionType.intValue != (int)BodyMotionType.Static)
EditorGUILayout.PropertyField(m_Smoothing);
if (m_MotionType.intValue == (int)BodyMotionType.Dynamic)
{
EditorGUILayout.PropertyField(m_LinearDamping, true);
EditorGUILayout.PropertyField(m_AngularDamping, true);
}
if (m_MotionType.intValue != (int)BodyMotionType.Static)
{
EditorGUILayout.PropertyField(m_InitialLinearVelocity, true);
EditorGUILayout.PropertyField(m_InitialAngularVelocity, true);
}
if (m_MotionType.intValue == (int)BodyMotionType.Dynamic)
{
EditorGUILayout.PropertyField(m_GravityFactor, true);
}
EditorGUI.BeginDisabledGroup(!dynamic);
if (dynamic)
{
EditorGUILayout.PropertyField(m_Orientation, Content.OrientationLabel);
EditorGUILayout.PropertyField(m_InertiaTensor, Content.InertiaTensorLabel);
}
else
{
EditorGUI.BeginDisabledGroup(true);
var position =
EditorGUILayout.GetControlRect(true, EditorGUI.GetPropertyHeight(m_InertiaTensor));
EditorGUI.BeginProperty(position, Content.InertiaTensorLabel, m_InertiaTensor);
EditorGUI.Vector3Field(position, Content.InertiaTensorLabel,
Vector3.one * float.PositiveInfinity);
EditorGUI.EndProperty();
EditorGUI.EndDisabledGroup();
}
EditorGUI.EndDisabledGroup();
--EditorGUI.indentLevel;
}
}
EditorGUILayout.PropertyField(m_CustomTags);
--EditorGUI.indentLevel;
}
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
DisplayStatusMessages();
}
MessageType m_Status;
List<string> m_StatusMessages = new List<string>(8);
void DisplayStatusMessages()
{
m_Status = MessageType.None;
m_StatusMessages.Clear();
var hierarchyStatus = StatusMessageUtility.GetHierarchyStatusMessage(targets, out var hierarchyStatusMessage);
if (!string.IsNullOrEmpty(hierarchyStatusMessage))
{
m_StatusMessages.Add(hierarchyStatusMessage);
m_Status = (MessageType)math.max((int)m_Status, (int)hierarchyStatus);
}
if (m_StatusMessages.Count > 0)
EditorGUILayout.HelpBox(string.Join("\n\n", m_StatusMessages), m_Status);
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/PhysicsCategoryNamesEditor.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(PhysicsCategoryNames))]
[CanEditMultipleObjects]
class PhysicsCategoryNamesEditor : BaseEditor
{
#pragma warning disable 649
[AutoPopulate(ElementFormatString = "Category {0}", Resizable = false, Reorderable = false)]
ReorderableList m_CategoryNames;
#pragma warning restore 649
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/PhysicsShapeAuthoringEditor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityMesh = UnityEngine.Mesh;
using Unity.Physics.Extensions;
using LegacyRigidBody = UnityEngine.Rigidbody;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(PhysicsShapeAuthoring))]
[CanEditMultipleObjects]
class PhysicsShapeAuthoringEditor : BaseEditor
{
static class Styles
{
const string k_Plural = "One or more selected objects";
const string k_Singular = "This object";
public static readonly string GenericUndoMessage = L10n.Tr("Change Shape");
public static readonly string MultipleShapeTypesLabel =
L10n.Tr("Multiple shape types in current selection.");
public static readonly string PreviewGenerationNotification =
L10n.Tr("Generating collision geometry preview...");
static readonly GUIContent k_FitToRenderMeshesLabel =
EditorGUIUtility.TrTextContent("Fit to Enabled Render Meshes");
static readonly GUIContent k_FitToRenderMeshesWarningLabelSg = new GUIContent(
k_FitToRenderMeshesLabel.text,
EditorGUIUtility.Load("console.warnicon") as Texture,
L10n.Tr($"{k_Singular} has non-uniform scale. Trying to fit the shape to render meshes might produce unexpected results.")
);
static readonly GUIContent k_FitToRenderMeshesWarningLabelPl = new GUIContent(
k_FitToRenderMeshesLabel.text,
EditorGUIUtility.Load("console.warnicon") as Texture,
L10n.Tr($"{k_Plural} has non-uniform scale. Trying to fit the shape to render meshes might produce unexpected results.")
);
public static readonly GUIContent CenterLabel = EditorGUIUtility.TrTextContent("Center");
public static readonly GUIContent SizeLabel = EditorGUIUtility.TrTextContent("Size");
public static readonly GUIContent OrientationLabel = EditorGUIUtility.TrTextContent(
"Orientation", "Euler orientation in the shape's local space (ZXY order)."
);
public static readonly GUIContent CylinderSideCountLabel = EditorGUIUtility.TrTextContent("Side Count");
public static readonly GUIContent RadiusLabel = EditorGUIUtility.TrTextContent("Radius");
public static readonly GUIContent ForceUniqueLabel = EditorGUIUtility.TrTextContent(
"Force Unique",
"If set to true, then this object will always produce a unique collider for run-time during conversion. " +
"If set to false, then this object may share its collider data with other objects if they have the same inputs. " +
"You should enable this option if you plan to modify this instance's collider at run-time."
);
public static readonly GUIContent MaterialLabel = EditorGUIUtility.TrTextContent("Material");
public static readonly GUIContent SetRecommendedConvexValues = EditorGUIUtility.TrTextContent(
"Set Recommended Default Values",
"Set recommended values for convex hull generation parameters based on either render meshes or custom mesh."
);
public static GUIContent GetFitToRenderMeshesLabel(int numTargets, MessageType status) =>
status >= MessageType.Warning
? numTargets == 1 ? k_FitToRenderMeshesWarningLabelSg : k_FitToRenderMeshesWarningLabelPl
: k_FitToRenderMeshesLabel;
static readonly string[] k_NoGeometryWarning =
{
L10n.Tr($"{k_Singular} has no enabled render meshes in its hierarchy and no custom mesh assigned."),
L10n.Tr($"{k_Plural} has no enabled render meshes in their hierarchies and no custom mesh assigned.")
};
public static string GetNoGeometryWarning(int numTargets) =>
numTargets == 1 ? k_NoGeometryWarning[0] : k_NoGeometryWarning[1];
m_NumImplicitStatic = targets.Cast<PhysicsShapeAuthoring>().Count(
shape => shape.GetPrimaryBody() == shape.gameObject
&& shape.GetComponent<PhysicsBodyAuthoring>() == null
&& shape.GetComponent<LegacyRigidBody>() == null
);
Undo.undoRedoPerformed += Repaint;
}
void OnDisable()
{
Undo.undoRedoPerformed -= Repaint;
SceneViewUtility.ClearNotificationInSceneView();
foreach (var preview in m_PreviewData.Values)
preview.Dispose();
if (m_DropDown != null)
m_DropDown.CloseWithoutUndo();
}
class PreviewMeshData : IDisposable
{
[BurstCompile]
struct CreateTempHullJob : IJob
{
public ConvexHullGenerationParameters GenerationParameters;
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<float3> Points;
public NativeArray<BlobAssetReference<Collider>> Output;
public void Execute() => Output[0] = ConvexCollider.Create(Points, GenerationParameters, CollisionFilter.Default);
}
[BurstCompile]
struct CreateTempMeshJob : IJob
{
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<float3> Points;
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<int3> Triangles;
public NativeArray<BlobAssetReference<Collider>> Output;
public void Execute() => Output[0] = MeshCollider.Create(Points, Triangles);
}
static readonly List<Vector3> s_ReusableEdges = new List<Vector3>(1024);
public Vector3[] Edges = Array.Empty<Vector3>();
public Aabb Bounds = new Aabb();
bool m_Disposed;
uint m_InputHash;
ConvexHullGenerationParameters m_HashedConvexParameters;
NativeArray<float3> m_HashedPoints = new NativeArray<float3>(0, Allocator.Persistent);
// multiple preview jobs might be running if user assigned a different mesh before previous job completed
JobHandle m_MostRecentlyScheduledJob;
Dictionary<JobHandle, NativeArray<BlobAssetReference<Collider>>> m_PreviewJobsOutput =
new Dictionary<JobHandle, NativeArray<BlobAssetReference<Collider>>>();
unsafe uint GetInputHash(
PhysicsShapeAuthoring shape,
NativeList<float3> currentPoints,
NativeArray<float3> hashedPoints,
ConvexHullGenerationParameters hashedConvexParameters,
out ConvexHullGenerationParameters currentConvexProperties
)
{
currentConvexProperties = default;
switch (shape.ShapeType)
{
case ShapeType.ConvexHull:
shape.GetBakedConvexProperties(currentPoints); // TODO: use HashableShapeInputs
currentConvexProperties = shape.ConvexHullGenerationParameters;
return math.hash(
new uint3(
(uint)shape.ShapeType,
currentConvexProperties.GetStableHash(hashedConvexParameters),
currentPoints.GetStableHash(hashedPoints)
)
);
case ShapeType.Mesh:
var triangles = new NativeList<int3>(1024, Allocator.Temp);
shape.GetBakedMeshProperties(currentPoints, triangles); // TODO: use HashableShapeInputs
return math.hash(
new uint3(
(uint)shape.ShapeType,
currentPoints.GetStableHash(hashedPoints),
math.hash(triangles.GetUnsafePtr(), UnsafeUtility.SizeOf<int3>() * triangles.Length)
)
);
default:
return (uint)shape.ShapeType;
}
}
public void SchedulePreviewIfChanged(PhysicsShapeAuthoring shape)
{
using (var currentPoints = new NativeList<float3>(65535, Allocator.Temp))
{
var hash = GetInputHash(
shape, currentPoints, m_HashedPoints, m_HashedConvexParameters, out var currentConvexParameters
);
if (m_InputHash == hash)
return;
m_InputHash = hash;
m_HashedConvexParameters = currentConvexParameters;
m_HashedPoints.Dispose();
m_HashedPoints = new NativeArray<float3>(currentPoints.Length, Allocator.Persistent);
m_HashedPoints.CopyFrom(currentPoints.AsArray());
}
if (shape.ShapeType != ShapeType.ConvexHull && shape.ShapeType != ShapeType.Mesh)
return;
// TODO: cache results per input data hash, and simply use existing data (e.g., to make undo/redo faster)
var output = new NativeArray<BlobAssetReference<Collider>>(1, Allocator.Persistent);
m_MostRecentlyScheduledJob = shape.ShapeType == ShapeType.Mesh
? ScheduleMeshPreview(shape, output)
: ScheduleConvexHullPreview(shape, output);
m_PreviewJobsOutput.Add(m_MostRecentlyScheduledJob, output);
if (m_PreviewJobsOutput.Count == 1)
{
CheckPreviewJobsForCompletion();
if (m_MostRecentlyScheduledJob.Equals(default(JobHandle)))
return;
EditorApplication.update += CheckPreviewJobsForCompletion;
EditorApplication.delayCall += () =>
{
SceneViewUtility.DisplayProgressNotification(
Styles.PreviewGenerationNotification, () => float.PositiveInfinity
);
};
}
}
JobHandle ScheduleConvexHullPreview(PhysicsShapeAuthoring shape, NativeArray<BlobAssetReference<Collider>> output)
{
var pointCloud = new NativeList<float3>(65535, Allocator.Temp);
shape.GetBakedConvexProperties(pointCloud);
if (pointCloud.Length == 0)
return default;
// copy to NativeArray because NativeList not yet compatible with DeallocateOnJobCompletion
var pointsArray = new NativeArray<float3>(
pointCloud.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory
);
pointsArray.CopyFrom(pointCloud.AsArray());
// TODO: if there is still an active job with the same input data hash, then just set it to be most recently scheduled job
return new CreateTempHullJob
{
GenerationParameters = shape.ConvexHullGenerationParameters.ToRunTime(),
Points = pointsArray,
Output = output
}.Schedule();
}
JobHandle ScheduleMeshPreview(PhysicsShapeAuthoring shape, NativeArray<BlobAssetReference<Collider>> output)
{
var points = new NativeList<float3>(1024, Allocator.Temp);
var triangles = new NativeList<int3>(1024, Allocator.Temp);
shape.GetBakedMeshProperties(points, triangles);
if (points.Length == 0 || triangles.Length == 0)
return default;
// copy to NativeArray because NativeList not yet compatible with DeallocateOnJobCompletion
var pointsArray = new NativeArray<float3>(
points.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory
);
pointsArray.CopyFrom(points.AsArray());
var triangleArray = new NativeArray<int3>(
triangles.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory
);
triangleArray.CopyFrom(triangles.AsArray());
// TODO: if there is still an active job with the same input data hash, then just set it to be most recently scheduled job
return new CreateTempMeshJob
{
Points = pointsArray,
Triangles = triangleArray,
Output = output
}.Schedule();
}
unsafe void CheckPreviewJobsForCompletion()
{
var repaintSceneViews = false;
foreach (var job in m_PreviewJobsOutput.Keys.ToArray()) // TODO: don't allocate on heap
{
// repaint scene views to indicate progress if most recent preview job is still in the queue
var mostRecentlyScheduledJob = m_MostRecentlyScheduledJob.Equals(job);
repaintSceneViews |= mostRecentlyScheduledJob;
if (!job.IsCompleted)
continue;
var output = m_PreviewJobsOutput[job];
m_PreviewJobsOutput.Remove(job);
job.Complete();
// only populate preview edge data if not already disposed and this job was actually the most recent
if (!m_Disposed && mostRecentlyScheduledJob)
{
if (!output[0].IsCreated)
{
Edges = Array.Empty<Vector3>();
Bounds = new Aabb();
}
else
{
switch (output[0].Value.Type)
{
case ColliderType.Convex:
ref var convex = ref output[0].As<ConvexCollider>();
DrawingUtility.GetConvexColliderEdges(
ref convex, s_ReusableEdges
);
Bounds = convex.CalculateAabb();
break;
case ColliderType.Mesh:
ref var mesh = ref output[0].As<MeshCollider>();
DrawingUtility.GetMeshColliderEdges(
ref mesh, s_ReusableEdges
);
Bounds = mesh.CalculateAabb();
break;
}
Edges = s_ReusableEdges.ToArray();
}
EditorApplication.delayCall += SceneViewUtility.ClearNotificationInSceneView;
}
if (output.IsCreated)
{
if (output[0].IsCreated)
output[0].Dispose();
output.Dispose();
}
}
if (repaintSceneViews)
SceneView.RepaintAll();
if (m_PreviewJobsOutput.Count == 0)
EditorApplication.update -= CheckPreviewJobsForCompletion;
}
public void Dispose()
{
m_Disposed = true;
m_HashedPoints.Dispose();
}
}
Dictionary<PhysicsShapeAuthoring, PreviewMeshData> m_PreviewData = new Dictionary<PhysicsShapeAuthoring, PreviewMeshData>();
PreviewMeshData GetPreviewData(PhysicsShapeAuthoring shape)
{
if (shape.ShapeType != ShapeType.ConvexHull && shape.ShapeType != ShapeType.Mesh)
return null;
if (!m_PreviewData.TryGetValue(shape, out var preview))
{
preview = m_PreviewData[shape] = new PreviewMeshData();
preview.SchedulePreviewIfChanged(shape);
}
// do not generate a new preview until the user has finished dragging a control handle (e.g., scale)
if (m_DraggingControlID == 0 && !EditorGUIUtility.editingTextField)
preview.SchedulePreviewIfChanged(shape);
return preview;
}
void UpdateGeometryState()
{
m_GeometryState = GeometryState.Okay;
var skinnedPoints = new NativeList<float3>(8192, Allocator.Temp);
foreach (PhysicsShapeAuthoring shape in targets)
{
// if a custom mesh is assigned, only check it
using (var so = new SerializedObject(shape))
{
var customMesh = so.FindProperty(m_CustomMesh.propertyPath).objectReferenceValue as UnityMesh;
if (customMesh != null)
{
m_GeometryState |= GetGeometryState(customMesh, shape.gameObject);
continue;
}
}
// otherwise check all mesh filters in the hierarchy that might be included
var geometryState = GeometryState.Okay;
using (var scope = new GetActiveChildrenScope<MeshFilter>(shape, shape.transform))
{
foreach (var meshFilter in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(meshFilter, filterOutInvalid: false))
geometryState |= GetGeometryState(meshFilter.sharedMesh, shape.gameObject);
}
}
if (shape.ShapeType == ShapeType.Mesh)
{
PhysicsShapeAuthoring.GetAllSkinnedPointsInHierarchyBelongingToShape(
shape, skinnedPoints, false, default, default, default
);
if (skinnedPoints.Length > 0)
geometryState |= GeometryState.MeshWithSkinnedPoints;
}
m_GeometryState |= geometryState;
}
skinnedPoints.Dispose();
}
static GeometryState GetGeometryState(UnityMesh mesh, GameObject host)
{
if (mesh == null)
return GeometryState.NoGeometry;
if (!mesh.IsValidForConversion(host))
return GeometryState.NonReadableGeometry;
return GeometryState.Okay;
}
public override void OnInspectorGUI()
{
var hotControl = GUIUtility.hotControl;
switch (Event.current.GetTypeForControl(hotControl))
{
case EventType.MouseDrag:
m_DraggingControlID = hotControl;
break;
case EventType.MouseUp:
m_DraggingControlID = 0;
break;
}
UpdateGeometryState();
serializedObject.Update();
UpdateStatusMessages();
EditorGUI.BeginChangeCheck();
DisplayShapeSelector();
++EditorGUI.indentLevel;
if (m_ShapeType.hasMultipleDifferentValues)
EditorGUILayout.HelpBox(Styles.MultipleShapeTypesLabel, MessageType.None);
else
{
switch ((ShapeType)m_ShapeType.intValue)
{
case ShapeType.Box:
AutomaticPrimitiveControls();
DisplayBoxControls();
break;
case ShapeType.Capsule:
AutomaticPrimitiveControls();
DisplayCapsuleControls();
break;
case ShapeType.Sphere:
AutomaticPrimitiveControls();
DisplaySphereControls();
break;
case ShapeType.Cylinder:
AutomaticPrimitiveControls();
DisplayCylinderControls();
break;
case ShapeType.Plane:
AutomaticPrimitiveControls();
DisplayPlaneControls();
break;
case ShapeType.ConvexHull:
RecommendedConvexValuesButton();
EditorGUILayout.PropertyField(m_ConvexHullGenerationParameters);
EditorGUILayout.PropertyField(m_MinimumSkinnedVertexWeight);
DisplayMeshControls();
break;
case ShapeType.Mesh:
DisplayMeshControls();
break;
default:
throw new UnimplementedShapeException((ShapeType)m_ShapeType.intValue);
}
EditorGUILayout.PropertyField(m_ForceUnique, Styles.ForceUniqueLabel);
}
--EditorGUI.indentLevel;
EditorGUILayout.LabelField(Styles.MaterialLabel);
++EditorGUI.indentLevel;
EditorGUILayout.PropertyField(m_Material);
--EditorGUI.indentLevel;
if (m_StatusMessages.Count > 0)
EditorGUILayout.HelpBox(string.Join("\n\n", m_StatusMessages), m_Status);
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
}
void RecommendedConvexValuesButton()
{
EditorGUI.BeginDisabledGroup(
(m_GeometryState & GeometryState.NoGeometry) == GeometryState.NoGeometry ||
EditorUtility.IsPersistent(target)
);
var rect = EditorGUI.IndentedRect(
EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight, EditorStyles.miniButton)
);
var buttonLabel = Styles.SetRecommendedConvexValues;
if (GUI.Button(rect, buttonLabel, Styles.Button))
{
Undo.RecordObjects(targets, buttonLabel.text);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.InitializeConvexHullGenerationParameters();
EditorUtility.SetDirty(shape);
}
}
EditorGUI.EndDisabledGroup();
}
MessageType m_GeometryStatus;
List<string> m_GeometryStatusMessages = new List<string>();
HashSet<string> m_ShapeSuggestions = new HashSet<string>();
MessageType m_Status;
List<string> m_StatusMessages = new List<string>(8);
MessageType m_MatrixStatus;
List<MatrixState> m_MatrixStates = new List<MatrixState>();
void UpdateStatusMessages()
{
m_Status = MessageType.None;
m_StatusMessages.Clear();
if (m_NumImplicitStatic != 0)
m_StatusMessages.Add(Styles.GetStaticColliderStatusMessage(targets.Length));
m_ShapeSuggestions.Clear();
foreach (PhysicsShapeAuthoring shape in targets)
{
const float k_Epsilon = HashableShapeInputs.k_DefaultLinearPrecision;
switch (shape.ShapeType)
{
case ShapeType.Box:
var box = shape.GetBakedBoxProperties();
var max = math.cmax(box.Size);
var min = math.cmin(box.Size);
if (min < k_Epsilon)
m_ShapeSuggestions.Add(Styles.BoxPlaneSuggestion);
else if (math.abs(box.BevelRadius - min * 0.5f) < k_Epsilon)
{
if (math.abs(max - min) < k_Epsilon)
m_ShapeSuggestions.Add(Styles.BoxSphereSuggestion);
else if (math.abs(math.lengthsq(box.Size - new float3(min)) - math.pow(max - min, 2f)) < k_Epsilon)
m_ShapeSuggestions.Add(Styles.BoxCapsuleSuggestion);
}
break;
case ShapeType.Capsule:
var capsule = shape.GetBakedCapsuleProperties();
if (math.abs(capsule.Height - 2f * capsule.Radius) < k_Epsilon)
m_ShapeSuggestions.Add(Styles.CapsuleSphereSuggestion);
break;
case ShapeType.Cylinder:
var cylinder = shape.GetBakedCylinderProperties();
if (math.abs(cylinder.BevelRadius - cylinder.Radius) < k_Epsilon)
{
m_ShapeSuggestions.Add(math.abs(cylinder.Height - 2f * cylinder.Radius) < k_Epsilon
? Styles.CylinderSphereSuggestion
: Styles.CylinderCapsuleSuggestion);
}
break;
}
}
foreach (var suggestion in m_ShapeSuggestions)
m_StatusMessages.Add(suggestion);
var hierarchyStatus = StatusMessageUtility.GetHierarchyStatusMessage(targets, out var hierarchyStatusMessage);
if (!string.IsNullOrEmpty(hierarchyStatusMessage))
{
m_StatusMessages.Add(hierarchyStatusMessage);
m_Status = (MessageType)math.max((int)m_Status, (int)hierarchyStatus);
}
m_MatrixStates.Clear();
foreach (var t in targets)
{
var localToWorld = (float4x4)(t as Component).transform.localToWorldMatrix;
m_MatrixStates.Add(ManipulatorUtility.GetMatrixState(ref localToWorld));
}
m_MatrixStatus = StatusMessageUtility.GetMatrixStatusMessage(m_MatrixStates, out var matrixStatusMessage);
if (m_MatrixStatus != MessageType.None)
{
m_StatusMessages.Add(matrixStatusMessage);
m_Status = (MessageType)math.max((int)m_Status, (int)m_MatrixStatus);
}
m_GeometryStatus = MessageType.None;
m_GeometryStatusMessages.Clear();
if ((m_GeometryState & GeometryState.NoGeometry) == GeometryState.NoGeometry)
{
m_GeometryStatusMessages.Add(Styles.GetNoGeometryWarning(targets.Length));
m_GeometryStatus = (MessageType)math.max((int)m_GeometryStatus, (int)MessageType.Error);
}
if ((m_GeometryState & GeometryState.NonReadableGeometry) == GeometryState.NonReadableGeometry)
{
m_GeometryStatusMessages.Add(Styles.GetNonReadableGeometryWarning(targets.Length));
m_GeometryStatus = (MessageType)math.max((int)m_GeometryStatus, (int)MessageType.Warning);
}
if ((m_GeometryState & GeometryState.MeshWithSkinnedPoints) == GeometryState.MeshWithSkinnedPoints)
{
m_GeometryStatusMessages.Add(Styles.GetMeshWithSkinnedPointsWarning(targets.Length));
m_GeometryStatus = (MessageType)math.max((int)m_GeometryStatus, (int)MessageType.Warning);
}
}
void DisplayShapeSelector()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_ShapeType);
if (!EditorGUI.EndChangeCheck())
return;
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
switch ((ShapeType)m_ShapeType.intValue)
{
case ShapeType.Box:
shape.SetBox(shape.GetBoxProperties(out var orientation), orientation);
break;
case ShapeType.Capsule:
shape.SetCapsule(shape.GetCapsuleProperties());
break;
case ShapeType.Sphere:
shape.SetSphere(shape.GetSphereProperties(out orientation), orientation);
break;
case ShapeType.Cylinder:
shape.SetCylinder(shape.GetCylinderProperties(out orientation), orientation);
break;
case ShapeType.Plane:
shape.GetPlaneProperties(out var center, out var size2D, out orientation);
shape.SetPlane(center, size2D, orientation);
break;
case ShapeType.ConvexHull:
case ShapeType.Mesh:
return;
default:
throw new UnimplementedShapeException((ShapeType)m_ShapeType.intValue);
}
EditorUtility.SetDirty(shape);
}
GUIUtility.ExitGUI();
}
void AutomaticPrimitiveControls()
{
EditorGUI.BeginDisabledGroup(
(m_GeometryState & GeometryState.NoGeometry) == GeometryState.NoGeometry || EditorUtility.IsPersistent(target)
);
var buttonLabel = Styles.GetFitToRenderMeshesLabel(targets.Length, m_MatrixStatus);
var rect = EditorGUI.IndentedRect(
EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight, EditorStyles.miniButton)
);
if (GUI.Button(rect, buttonLabel, Styles.ButtonDropDown))
m_DropDown = FitToRenderMeshesDropDown.Show(rect, buttonLabel.text, m_MinimumSkinnedVertexWeight);
EditorGUI.EndDisabledGroup();
}
class FitToRenderMeshesDropDown : EditorWindow
{
static class Styles
{
public const float WindowWidth = 400f;
public const float LabelWidth = 200f;
public static GUIStyle Button => PhysicsShapeAuthoringEditor.Styles.Button;
}
static class Content
{
public static readonly string ApplyLabel = L10n.Tr("Apply");
public static readonly string CancelLabel = L10n.Tr("Cancel");
}
bool m_ApplyChanges;
bool m_ClosedWithoutUndo;
int m_UndoGroup;
SerializedProperty m_MinimumSkinnedVertexWeight;
public static FitToRenderMeshesDropDown Show(
Rect buttonRect, string title, SerializedProperty minimumSkinnedVertexWeight
)
{
var window = CreateInstance<FitToRenderMeshesDropDown>();
window.titleContent = EditorGUIUtility.TrTextContent(title);
window.m_UndoGroup = Undo.GetCurrentGroup();
window.m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
var size = new Vector2(
math.max(buttonRect.width, Styles.WindowWidth),
(EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing) * 3f
);
window.maxSize = window.minSize = size;
window.ShowAsDropDown(GUIUtility.GUIToScreenRect(buttonRect), size);
return window;
}
void OnGUI()
{
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = Styles.LabelWidth;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_MinimumSkinnedVertexWeight);
if (EditorGUI.EndChangeCheck())
ApplyChanges();
EditorGUIUtility.labelWidth = labelWidth;
GUILayout.FlexibleSpace();
var buttonRect = GUILayoutUtility.GetRect(0f, EditorGUIUtility.singleLineHeight);
var buttonLeft = new Rect(buttonRect)
{
width = 0.5f * (buttonRect.width - EditorGUIUtility.standardVerticalSpacing)
};
var buttonRight = new Rect(buttonLeft)
{
x = buttonLeft.xMax + EditorGUIUtility.standardVerticalSpacing
};
var close = false;
buttonRect = Application.platform == RuntimePlatform.OSXEditor ? buttonLeft : buttonRight;
if (
GUI.Button(buttonRect, Content.CancelLabel, Styles.Button)
|| Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape
)
{
close = true;
}
buttonRect = Application.platform == RuntimePlatform.OSXEditor ? buttonRight : buttonLeft;
if (GUI.Button(buttonRect, Content.ApplyLabel, Styles.Button))
{
close = true;
m_ApplyChanges = true;
}
if (close)
{
Close();
EditorGUIUtility.ExitGUI();
}
}
void ApplyChanges()
{
m_MinimumSkinnedVertexWeight.serializedObject.ApplyModifiedProperties();
Undo.RecordObjects(m_MinimumSkinnedVertexWeight.serializedObject.targetObjects, titleContent.text);
foreach (PhysicsShapeAuthoring shape in m_MinimumSkinnedVertexWeight.serializedObject.targetObjects)
{
using (var so = new SerializedObject(shape))
{
shape.FitToEnabledRenderMeshes(
so.FindProperty(m_MinimumSkinnedVertexWeight.propertyPath).floatValue
);
EditorUtility.SetDirty(shape);
}
}
m_MinimumSkinnedVertexWeight.serializedObject.Update();
}
public void CloseWithoutUndo()
{
m_ApplyChanges = true;
Close();
}
void OnDestroy()
{
if (m_ApplyChanges)
ApplyChanges();
else
Undo.RevertAllDownToGroup(m_UndoGroup);
}
}
void DisplayBoxControls()
{
EditorGUILayout.PropertyField(m_PrimitiveSize, Styles.SizeLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
EditorGUILayout.PropertyField(m_BevelRadius);
}
void DisplayCapsuleControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Capsule);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.SetCapsule(shape.GetCapsuleProperties());
EditorUtility.SetDirty(shape);
}
}
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
}
void DisplaySphereControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_SphereRadius, Styles.RadiusLabel);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.SetSphere(shape.GetSphereProperties(out EulerAngles orientation), orientation);
EditorUtility.SetDirty(shape);
}
}
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
}
void DisplayCylinderControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Cylinder);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.SetCylinder(shape.GetCylinderProperties(out var orientation), orientation);
EditorUtility.SetDirty(shape);
}
}
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
EditorGUILayout.PropertyField(m_CylinderSideCount, Styles.CylinderSideCountLabel);
EditorGUILayout.PropertyField(m_BevelRadius);
}
void DisplayPlaneControls()
{
EditorGUILayout.PropertyField(m_PrimitiveSize, Styles.SizeLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
}
void DisplayMeshControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_CustomMesh);
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
}
if (m_GeometryStatusMessages.Count > 0)
EditorGUILayout.HelpBox(string.Join("\n\n", m_GeometryStatusMessages), m_GeometryStatus);
}
static readonly BeveledBoxBoundsHandle s_Box = new BeveledBoxBoundsHandle();
static readonly PhysicsCapsuleBoundsHandle s_Capsule =
new PhysicsCapsuleBoundsHandle { heightAxis = CapsuleBoundsHandle.HeightAxis.Z };
static readonly BeveledCylinderBoundsHandle s_Cylinder = new BeveledCylinderBoundsHandle();
static readonly PhysicsSphereBoundsHandle s_Sphere = new PhysicsSphereBoundsHandle();
static readonly BoxBoundsHandle s_Plane =
new BoxBoundsHandle { axes = PrimitiveBoundsHandle.Axes.X | PrimitiveBoundsHandle.Axes.Z };
static readonly Color k_ShapeHandleColor = new Color32(145, 244, 139, 210);
static readonly Color k_ShapeHandleColorDisabled = new Color32(84, 200, 77, 140);
void OnSceneGUI()
{
var hotControl = GUIUtility.hotControl;
switch (Event.current.GetTypeForControl(hotControl))
{
case EventType.MouseDrag:
m_DraggingControlID = hotControl;
break;
case EventType.MouseUp:
m_DraggingControlID = 0;
break;
}
var shape = target as PhysicsShapeAuthoring;
var handleColor = shape.enabled ? k_ShapeHandleColor : k_ShapeHandleColorDisabled;
var handleMatrix = shape.GetShapeToWorldMatrix();
using (new Handles.DrawingScope(handleColor, handleMatrix))
{
switch (shape.ShapeType)
{
case ShapeType.Box:
var boxGeometry = shape.GetBakedBoxProperties();
s_Box.bevelRadius = boxGeometry.BevelRadius;
s_Box.center = float3.zero;
s_Box.size = boxGeometry.Size;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(boxGeometry.Center, boxGeometry.Orientation, 1f))))
s_Box.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedBoxSize(s_Box.size, s_Box.bevelRadius);
}
break;
case ShapeType.Capsule:
s_Capsule.center = float3.zero;
var capsuleGeometry = shape.GetBakedCapsuleProperties();
s_Capsule.height = capsuleGeometry.Height;
s_Capsule.radius = capsuleGeometry.Radius;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(capsuleGeometry.Center, capsuleGeometry.Orientation, 1f))))
s_Capsule.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedCapsuleSize(s_Capsule.height, s_Capsule.radius);
}
break;
case ShapeType.Sphere:
var sphereGeometry = shape.GetBakedSphereProperties(out EulerAngles orientation);
s_Sphere.center = float3.zero;
s_Sphere.radius = sphereGeometry.Radius;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(sphereGeometry.Center, orientation, 1f))))
s_Sphere.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedSphereRadius(s_Sphere.radius);
}
break;
case ShapeType.Cylinder:
var cylinderGeometry = shape.GetBakedCylinderProperties();
s_Cylinder.center = float3.zero;
s_Cylinder.height = cylinderGeometry.Height;
s_Cylinder.radius = cylinderGeometry.Radius;
s_Cylinder.sideCount = cylinderGeometry.SideCount;
s_Cylinder.bevelRadius = cylinderGeometry.BevelRadius;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(cylinderGeometry.Center, cylinderGeometry.Orientation, 1f))))
s_Cylinder.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedCylinderSize(s_Cylinder.height, s_Cylinder.radius, s_Cylinder.bevelRadius);
}
break;
case ShapeType.Plane:
shape.GetPlaneProperties(out var center, out var size2, out orientation);
s_Plane.center = float3.zero;
s_Plane.size = new float3(size2.x, 0f, size2.y);
EditorGUI.BeginChangeCheck();
{
var m = math.mul(shape.transform.localToWorldMatrix, float4x4.TRS(center, orientation, 1f));
using (new Handles.DrawingScope(m))
s_Plane.DrawHandle();
var right = math.mul(m, new float4 { x = 1f }).xyz;
var forward = math.mul(m, new float4 { z = 1f }).xyz;
var normal = math.cross(math.normalizesafe(forward), math.normalizesafe(right))
* 0.5f * math.lerp(math.length(right) * size2.x, math.length(forward) * size2.y, 0.5f);
using (new Handles.DrawingScope(float4x4.identity))
Handles.DrawLine(m.c3.xyz, m.c3.xyz + normal);
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedPlaneSize(((float3)s_Plane.size).xz);
}
break;
case ShapeType.ConvexHull:
if (Event.current.type != EventType.Repaint)
break;
var points = GetPreviewData(shape).Edges;
// TODO: follow transformation until new preview is generated if e.g., user is dragging handles
if (points.Length > 0)
Handles.DrawLines(points);
break;
case ShapeType.Mesh:
if (Event.current.type != EventType.Repaint)
break;
points = GetPreviewData(shape).Edges;
if (points.Length > 0)
Handles.DrawLines(points);
break;
default:
throw new UnimplementedShapeException(shape.ShapeType);
}
}
}
// ReSharper disable once UnusedMember.Global - magic method called by unity inspector
public bool HasFrameBounds()
{
return true;
}
static Bounds TransformBounds(Bounds localBounds, float4x4 matrix)
{
var center = new float4(localBounds.center, 1);
Bounds bounds = new Bounds(math.mul(matrix, center).xyz, Vector3.zero);
var extent = new float4(localBounds.extents, 0);
for (int i = 0; i < 8; ++i)
{
extent.x = (i & 1) == 0 ? -extent.x : extent.x;
extent.y = (i & 2) == 0 ? -extent.y : extent.y;
extent.z = (i & 4) == 0 ? -extent.z : extent.z;
var worldPoint = math.mul(matrix, center + extent).xyz;
bounds.Encapsulate(worldPoint);
}
return bounds;
}
// ReSharper disable once UnusedMember.Global - magic method called by unity inspector
public Bounds OnGetFrameBounds()
{
var shape = target as PhysicsShapeAuthoring;
var shapeMatrix = shape.GetShapeToWorldMatrix();
Bounds bounds = new Bounds();
switch (shape.ShapeType)
{
case ShapeType.Box:
var boxGeometry = shape.GetBakedBoxProperties();
bounds = new Bounds(float3.zero, boxGeometry.Size);
bounds = TransformBounds(bounds, float4x4.TRS(boxGeometry.Center, boxGeometry.Orientation, 1f));
break;
case ShapeType.Capsule:
var capsuleGeometry = shape.GetBakedCapsuleProperties();
var cd = capsuleGeometry.Radius * 2;
bounds = new Bounds(float3.zero, new float3(cd, cd, capsuleGeometry.Height));
bounds = TransformBounds(bounds, float4x4.TRS(capsuleGeometry.Center, capsuleGeometry.Orientation, 1f));
break;
case ShapeType.Sphere:
var sphereGeometry = shape.GetBakedSphereProperties(out var orientation);
var sd = sphereGeometry.Radius * 2;
bounds = new Bounds(sphereGeometry.Center, new float3(sd, sd, sd));
break;
case ShapeType.Cylinder:
var cylinderGeometry = shape.GetBakedCylinderProperties();
var cyld = cylinderGeometry.Radius * 2;
bounds = new Bounds(float3.zero, new float3(cyld, cyld, cylinderGeometry.Height));
bounds = TransformBounds(bounds, float4x4.TRS(cylinderGeometry.Center, cylinderGeometry.Orientation, 1f));
break;
case ShapeType.Plane:
shape.GetPlaneProperties(out var center, out var size2, out orientation);
bounds = new Bounds(float3.zero, new float3(size2.x, 0, size2.y));
bounds = TransformBounds(bounds, float4x4.TRS(center, orientation, 1f));
break;
case ShapeType.ConvexHull:
case ShapeType.Mesh:
var previewData = GetPreviewData(shape);
if (previewData != null)
bounds = new Bounds(previewData.Bounds.Center, previewData.Bounds.Extents);
break;
default:
throw new UnimplementedShapeException(shape.ShapeType);
}
return TransformBounds(bounds, shapeMatrix);
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/RagdollJointEditor.cs
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using Unity.Mathematics;
using Unity.Physics.Authoring;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(RagdollJoint))]
public class RagdollJointEditor : UnityEditor.Editor
{
private EditorUtilities.AxisEditor m_AxisEditor = new EditorUtilities.AxisEditor();
private JointAngularLimitHandle m_LimitHandle = new JointAngularLimitHandle();
public override void OnInspectorGUI()
{
RagdollJoint ragdoll = (RagdollJoint)target;
EditorGUI.BeginChangeCheck();
GUILayout.BeginVertical();
GUILayout.Space(10.0f);
GUILayout.BeginHorizontal();
GUILayout.Label("Editors:");
ragdoll.EditPivots = GUILayout.Toggle(ragdoll.EditPivots, new GUIContent("Pivot"), "Button");
ragdoll.EditAxes = GUILayout.Toggle(ragdoll.EditAxes, new GUIContent("Axis"), "Button");
ragdoll.EditLimits = GUILayout.Toggle(ragdoll.EditLimits, new GUIContent("Limits"), "Button");
GUILayout.EndHorizontal();
GUILayout.Space(10.0f);
GUILayout.EndVertical();
DrawDefaultInspector();
if (EditorGUI.EndChangeCheck())
{
SceneView.RepaintAll();
}
}
private static void DrawCone(float3 point, float3 axis, float angle, Color color)
{
#if UNITY_EDITOR
Handles.color = color;
float3 dir;
float scale = Math.NormalizeWithLength(axis, out dir);
float3 arm;
{
float3 perp1, perp2;
Math.CalculatePerpendicularNormalized(dir, out perp1, out perp2);
arm = math.mul(quaternion.AxisAngle(perp1, angle), dir) * scale;
}
const int res = 16;
quaternion q = quaternion.AxisAngle(dir, 2.0f * (float)math.PI / res);
for (int i = 0; i < res; i++)
{
float3 nextArm = math.mul(q, arm);
Handles.DrawLine(point, point + arm);
Handles.DrawLine(point + arm, point + nextArm);
arm = nextArm;
}
#endif
}
protected virtual void OnSceneGUI()
{
RagdollJoint ragdoll = (RagdollJoint)target;
bool drawCones = false;
if (ragdoll.EditPivots)
{
EditorUtilities.EditPivot(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.AutoSetConnected,
ref ragdoll.PositionLocal, ref ragdoll.PositionInConnectedEntity, ragdoll);
}
if (ragdoll.EditAxes)
{
m_AxisEditor.Update(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.AutoSetConnected,
ragdoll.PositionLocal, ragdoll.PositionInConnectedEntity, ref ragdoll.TwistAxisLocal, ref ragdoll.TwistAxisInConnectedEntity,
ref ragdoll.PerpendicularAxisLocal, ref ragdoll.PerpendicularAxisInConnectedEntity, ragdoll);
drawCones = true;
}
if (ragdoll.EditLimits)
{
EditorUtilities.EditLimits(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.PositionLocal, ragdoll.TwistAxisLocal, ragdoll.TwistAxisInConnectedEntity,
ragdoll.PerpendicularAxisLocal, ragdoll.PerpendicularAxisInConnectedEntity, ref ragdoll.MinTwistAngle, ref ragdoll.MaxTwistAngle, m_LimitHandle, ragdoll);
}
if (drawCones)
{
float3 pivotB = math.transform(ragdoll.worldFromB, ragdoll.PositionInConnectedEntity);
float3 axisB = math.rotate(ragdoll.worldFromB, ragdoll.TwistAxisInConnectedEntity);
DrawCone(pivotB, axisB, math.radians(ragdoll.MaxConeAngle), Color.yellow);
float3 perpendicularB = math.rotate(ragdoll.worldFromB, ragdoll.PerpendicularAxisInConnectedEntity);
DrawCone(pivotB, perpendicularB, math.radians(ragdoll.MinPerpendicularAngle + 90f), Color.red);
DrawCone(pivotB, perpendicularB, math.radians(ragdoll.MaxPerpendicularAngle + 90f), Color.red);
}
}
}
}
#endif
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/BeveledBoxBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class BeveledBoxBoundsHandle : BoxBoundsHandle
{
public float bevelRadius
{
get => math.min(m_BevelRadius, math.cmin(GetSize()) * 0.5f);
set
{
if (!m_IsDragging)
m_BevelRadius = math.max(0f, value);
}
}
float m_BevelRadius = ConvexHullGenerationParameters.Default.BevelRadius;
bool m_IsDragging = false;
static PhysicsBoundsHandleUtility.Corner[] s_Corners = new PhysicsBoundsHandleUtility.Corner[8];
public new void DrawHandle()
{
int prevHotControl = GUIUtility.hotControl;
if (prevHotControl == 0)
m_IsDragging = false;
base.DrawHandle();
int currHotcontrol = GUIUtility.hotControl;
if (currHotcontrol != prevHotControl)
m_IsDragging = currHotcontrol != 0;
}
protected override void DrawWireframe()
{
if (this.bevelRadius <= 0f)
{
base.DrawWireframe();
return;
}
var cameraPosition = float3.zero;
var cameraForward = new float3 { z = 1f };
if (Camera.current != null)
{
cameraPosition = Camera.current.transform.position;
cameraForward = Camera.current.transform.forward;
}
var bounds = new Bounds(this.center, this.size);
bool isCameraInsideBox = Camera.current != null && bounds.Contains(Handles.inverseMatrix.MultiplyPoint(cameraPosition));
var bevelRadius = this.bevelRadius;
var origin = (float3)this.center;
var size = (float3)this.size;
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(-1f, 1f, 1f), bevelRadius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, -1f, 1f), bevelRadius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 2, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, -1f), bevelRadius, 2, axes, isCameraInsideBox);
var corner = 0.5f * size - new float3(1f) * bevelRadius;
var axisx = new float3(1f, 0f, 0f);
var axisy = new float3(0f, 1f, 0f);
var axisz = new float3(0f, 0f, 1f);
// Since the geometry is transformed by Handles.matrix during rendering, we transform the camera position
// by the inverse matrix so that the two-shaded wireframe will have the proper orientation.
var invMatrix = Handles.inverseMatrix;
cameraPosition = invMatrix.MultiplyPoint(cameraPosition);
cameraForward = invMatrix.MultiplyVector(cameraForward);
var cameraOrtho = Camera.current == null || Camera.current.orthographic;
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, -1f), quaternion.LookRotation(-axisz, axisy), cameraPosition, cameraForward,
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, 1f), quaternion.LookRotation(-axisx, axisy), cameraPosition, cameraForward,
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, 1f), quaternion.LookRotation(axisz, axisy), cameraPosition, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, -1f), quaternion.LookRotation(axisx, axisy), cameraPosition, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, -1f), quaternion.LookRotation(-axisx, -axisy), cameraPosition, cameraForward,
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, 1f), quaternion.LookRotation(axisz, -axisy), cameraPosition, cameraForward, cameraOrth
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, 1f), quaternion.LookRotation(axisx, -axisy), cameraPosition, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, -1f), quaternion.LookRotation(-axisz, -axisy), cameraPosition, cameraForward, cameraOrth
for (int i = 0; i < s_Corners.Length; i++)
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[i], true);
// Draw the horizon edges between the corners
for (int upA = 3, upB = 0; upB < 4; upA = upB, upB++)
{
int dnA = upA + 4;
int dnB = upB + 4;
if (s_Corners[upA].splitAxis[0].z && s_Corners[upB].splitAxis[1].x) Handles.DrawLine(s_Corners[upA].points[0], s_Corners[upB].points[1]);
if (s_Corners[upA].splitAxis[1].z && s_Corners[upB].splitAxis[0].x) Handles.DrawLine(s_Corners[upA].points[1], s_Corners[upB].points[0]);
if (s_Corners[dnA].splitAxis[0].x && s_Corners[dnB].splitAxis[1].z) Handles.DrawLine(s_Corners[dnA].points[0], s_Corners[dnB].points[1]);
if (s_Corners[dnA].splitAxis[1].x && s_Corners[dnB].splitAxis[0].z) Handles.DrawLine(s_Corners[dnA].points[1], s_Corners[dnB].points[0]);
if (s_Corners[dnA].splitAxis[0].y && s_Corners[upA].splitAxis[1].y) Handles.DrawLine(s_Corners[dnA].points[0], s_Corners[upA].points[1]);
if (s_Corners[dnA].splitAxis[1].y && s_Corners[upA].splitAxis[0].y) Handles.DrawLine(s_Corners[dnA].points[1], s_Corners[upA].points[0]);
}
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/BeveledCylinderBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class BeveledCylinderBoundsHandle : PrimitiveBoundsHandle
{
public float bevelRadius
{
get => math.min(m_BevelRadius, math.cmin(GetSize()) * 0.5f);
set
{
if (!m_IsDragging)
m_BevelRadius = math.max(0f, value);
}
}
m_SideCount = value;
// Since the geometry is transformed by Handles.matrix during rendering, we transform the camera position
// by the inverse matrix so that the two-shaded wireframe will have the proper orientation.
var invMatrix = Handles.inverseMatrix;
cameraCenter = invMatrix.MultiplyPoint(cameraCenter);
cameraForward = invMatrix.MultiplyVector(cameraForward);
var cameraOrtho = Camera.current != null && Camera.current.orthographic;
t = (m_SideCount - 1) * angleStep;
var xyAngle1 = new float3(math.cos(t), math.sin(t), 0f);
var sideways1 = new float3(math.cos(t + kHalfPI - halfAngleStep), math.sin(t + kHalfPI - halfAngleStep), 0f);
var direction1 = new float3(math.cos(t + halfAngleStep), math.sin(t + halfAngleStep), 0f);
var bevelGreaterThanZero = bevelRadius > 0f;
var bevelLessThanCylinderRadius = bevelRadius < radius;
for (var i = 0; i < m_SideCount; ++i)
{
t = i * angleStep;
var xyAngle2 = new float3(math.cos(t), math.sin(t), 0f);
var sideways2 = new float3(math.cos(t + kHalfPI - halfAngleStep), math.sin(t + kHalfPI - halfAngleStep), 0f);
var direction2 = new float3(math.cos(t + halfAngleStep), math.sin(t + halfAngleStep), 0f);
var cornerIndex0 = i;
var cornerIndex1 = i + m_SideCount;
{
var orientation = quaternion.LookRotation(xyAngle2, up);
var cornerNormal = math.normalize(math.mul(orientation, new float3(0f, 1f, 1f)));
PhysicsBoundsHandleUtility.CalculateCornerHorizon(top2,
new float3x3(direction1, up, direction2),
cornerNormal, cameraCenter, cameraForward, cameraOrtho,
bevelRadius, out m_Corners[cornerIndex0]);
}
{
var orientation = quaternion.LookRotation(xyAngle2, -up);
var cornerNormal = math.normalize(math.mul(orientation, new float3(0f, 1f, 1f)));
PhysicsBoundsHandleUtility.CalculateCornerHorizon(bottom2,
new float3x3(direction2, -up, direction1),
cornerNormal, cameraCenter, cameraForward, cameraOrtho,
bevelRadius, out m_Corners[cornerIndex1]);
}
}
direction1 = direction2;
sideways1 = sideways2;
xyAngle0 = xyAngle1;
xyAngle1 = xyAngle2;
}
if (bevelGreaterThanZero)
{
Handles.color = frontfacedColor;
for (int a = m_SideCount - 1, b = 0; b < m_SideCount; a = b, ++b)
{
var up0 = a;
var dn0 = a + m_SideCount;
var up1 = b;
var dn1 = b + m_SideCount;
namespace Unity.Physics.Editor
{
abstract class BaseDrawer : PropertyDrawer
{
protected abstract bool IsCompatible(SerializedProperty property);
EditorGUI.EndProperty();
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/EulerAnglesDrawer.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(EulerAngles))]
class EulerAnglesDrawer : BaseDrawer
{
protected override bool IsCompatible(SerializedProperty property) => true;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var value = property.FindPropertyRelative(nameof(EulerAngles.Value));
return EditorGUI.GetPropertyHeight(value);
}
protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)
{
var value = property.FindPropertyRelative(nameof(EulerAngles.Value));
EditorGUI.PropertyField(position, value, label, true);
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/ExpandChildrenDrawer.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(ExpandChildrenAttribute))]
class ExpandChildrenDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
property.isExpanded = true;
return EditorGUI.GetPropertyHeight(property)
- EditorGUIUtility.standardVerticalSpacing
- EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var endProperty = property.GetEndProperty();
var childProperty = property.Copy();
childProperty.NextVisible(true);
while (!SerializedProperty.EqualContents(childProperty, endProperty))
{
position.height = EditorGUI.GetPropertyHeight(childProperty);
OnChildPropertyGUI(position, childProperty);
position.y += position.height + EditorGUIUtility.standardVerticalSpacing;
childProperty.NextVisible(false);
}
}
protected virtual void OnChildPropertyGUI(Rect position, SerializedProperty childProperty)
{
EditorGUI.PropertyField(position, childProperty, true);
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/PhysicsMaterialCoefficientDrawer.cs
using System;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(PhysicsMaterialCoefficient))]
class PhysicsMaterialCoefficientDrawer : BaseDrawer
{
static class Styles
{
public const float PopupWidth = 100f;
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) =>
EditorGUIUtility.singleLineHeight;
protected override bool IsCompatible(SerializedProperty property) => true;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(PhysicsMaterialProperties))]
class PhysicsMaterialPropertiesDrawer : BaseDrawer
{
static class Content
{
public static readonly GUIContent AdvancedGroupFoldout = EditorGUIUtility.TrTextContent("Advanced");
public static readonly GUIContent BelongsToLabel = EditorGUIUtility.TrTextContent(
"Belongs To",
"Specifies the categories to which this object belongs."
);
public static readonly GUIContent CollidesWithLabel = EditorGUIUtility.TrTextContent(
"Collides With",
"Specifies the categories of objects with which this object will collide, " +
"or with which it will raise events if intersecting a trigger."
);
public static readonly GUIContent CollisionFilterGroupFoldout =
EditorGUIUtility.TrTextContent("Collision Filter");
public static readonly GUIContent CustomFlagsLabel =
EditorGUIUtility.TrTextContent("Custom Tags", "Specify custom tags to read at run-time.");
public static readonly GUIContent FrictionLabel = EditorGUIUtility.TrTextContent(
"Friction",
"Specifies how resistant the body is to motion when sliding along other surfaces, " +
"as well as what value should be used when colliding with an object that has a different value."
);
public static readonly GUIContent RestitutionLabel = EditorGUIUtility.TrTextContent(
"Restitution",
"Specifies how bouncy the object will be when colliding with other surfaces, " +
"as well as what value should be used when colliding with an object that has a different value."
);
public static readonly GUIContent CollisionResponseLabel = EditorGUIUtility.TrTextContent(
"Collision Response",
"Specifies whether the shape should collide normally, raise trigger events when intersecting other shapes, " +
"collide normally and raise notifications of collision events with other shapes, " +
"or completely ignore collisions (but still move and intercept queries)."
);
}
void FindToggleAndValueProperties(
SerializedProperty property, SerializedProperty templateValueProperty, string relativePath,
out SerializedProperty toggle, out SerializedProperty value
)
{
var relative = property.FindPropertyRelative(relativePath);
toggle = relative.FindPropertyRelative("m_Override");
value = toggle.boolValue || templateValueProperty == null
? relative.FindPropertyRelative("m_Value")
: templateValueProperty.FindPropertyRelative(relativePath).FindPropertyRelative("m_Value");
}
// m_BelongsTo, m_CollidesWith
var group = property.FindPropertyRelative(k_CollisionFilterGroupKey);
if (group.isExpanded)
height += 2f * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
// m_CustomTags
group = property.FindPropertyRelative(k_AdvancedGroupKey);
if (group.isExpanded)
height += (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
// m_Template
if (property.FindPropertyRelative("m_SupportsTemplate").boolValue)
height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
// m_Friction, m_Restitution
FindToggleAndValueProperties(property, templateValueProperty, "m_CollisionResponse", out _, out var collisionResponse);
// Check if regular collider
CollisionResponsePolicy collisionResponseEnum = (CollisionResponsePolicy)collisionResponse.intValue;
if (collisionResponseEnum == CollisionResponsePolicy.Collide ||
collisionResponseEnum == CollisionResponsePolicy.CollideRaiseCollisionEvents)
height += 2f * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
return height;
}
EditorGUI.BeginDisabledGroup(!toggle.boolValue);
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
EditorGUI.PropertyField(new Rect(position) { xMin = togglePosition.xMax }, value, GUIContent.none, true);
EditorGUI.indentLevel = indent;
EditorGUI.EndDisabledGroup();
}
else
{
EditorGUI.PropertyField(position, value, label, true);
}
}
--EditorGUI.indentLevel;
}
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/SoftRangeDrawer.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(SoftRangeAttribute))]
class SoftRangeDrawer : BaseDrawer
{
protected override bool IsCompatible(SerializedProperty property)
{
return property.propertyType == SerializedPropertyType.Float;
}
protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)
{
var attr = attribute as SoftRangeAttribute;
EditorGUIControls.SoftSlider(
position, label, property, attr.SliderMin, attr.SliderMax, attr.TextFieldMin, attr.TextFieldMax
);
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/TagsDrawer.cs
using System.Collections.Generic;
using System.Linq;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
abstract class TagsDrawer<T> : PropertyDrawer where T : ScriptableObject, ITagNames
{
static class Styles
{
public static readonly string EverythingName = L10n.Tr("Everything");
public static readonly string MixedName = L10n.Tr("Mixed...");
public static readonly string NothingName = L10n.Tr("Nothing");
string[] GetOptions()
{
if (m_Options != null)
return m_Options;
var guids = AssetDatabase.FindAssets($"t:{typeof(T).Name}");
m_NamesAssets = guids
.Select(AssetDatabase.GUIDToAssetPath)
.Select(AssetDatabase.LoadAssetAtPath<T>)
.Where(c => c != null)
.ToArray();
m_Options = m_NamesAssets.FirstOrDefault()?.TagNames.ToArray() ?? DefaultOptions;
for (var i = 0; i < m_Options.Length; ++i)
{
if (string.IsNullOrEmpty(m_Options[i]))
m_Options[i] = DefaultOptions[i];
string[] m_Options;
T[] m_NamesAssets;
// TODO: remove when all usages of bool[] are migrated
SerializedProperty GetFirstChildProperty(SerializedProperty property)
{
if (!string.IsNullOrEmpty(FirstChildPropertyPath))
return property.FindPropertyRelative(FirstChildPropertyPath);
var sp = property.Copy();
sp.NextVisible(true);
return sp;
}
var value = 0;
var everything = 0;
var sp = GetFirstChildProperty(property);
for (int i = 0, count = MaxNumCategories; i < count; ++i)
{
EditorGUI.showMixedValue |= sp.hasMultipleDifferentValues;
value |= sp.boolValue ? 1 << i : 0;
everything |= 1 << i;
sp.NextVisible(false);
}
// in case size is smaller than 32
if (value == everything)
value = ~0;
var options = GetOptions();
if (
EditorGUI.DropdownButton(
controlPosition,
EditorGUIUtility.TrTempContent(GetButtonLabel(value, options)),
FocusType.Passive,
EditorStyles.popup
)
)
{
var menu = new GenericMenu();
menu.AddItem(
new GUIContent(Styles.NothingName),
value == 0,
() =>
{
sp = GetFirstChildProperty(property);
for (int i = 0, count = MaxNumCategories; i < count; ++i)
{
sp.boolValue = false;
sp.NextVisible(false);
}
sp.serializedObject.ApplyModifiedProperties();
}
);
menu.AddItem(
new GUIContent(Styles.EverythingName),
value == ~0,
() =>
{
sp = GetFirstChildProperty(property);
for (int i = 0, count = MaxNumCategories; i < count; ++i)
{
sp.boolValue = true;
sp.NextVisible(false);
}
sp.serializedObject.ApplyModifiedProperties();
}
);
for (var option = 0; option < options.Length; ++option)
{
var callbackValue = option;
menu.AddItem(
EditorGUIUtility.TrTextContent(options[option]),
((1 << option) & value) != 0,
args =>
{
var changedBitAndValue = (KeyValuePair<int, bool>)args;
sp = GetFirstChildProperty(property);
for (int i = 0, count = changedBitAndValue.Key; i < count; ++i)
sp.NextVisible(false);
sp.boolValue = changedBitAndValue.Value;
sp.serializedObject.ApplyModifiedProperties();
},
new KeyValuePair<int, bool>(callbackValue, ((1 << option) & value) == 0)
);
}
menu.AddSeparator(string.Empty);
menu.AddItem(
EditorGUIUtility.TrTempContent($"Edit {ObjectNames.NicifyVariableName(typeof(T).Name)}"),
false,
() =>
{
if (m_NamesAssets.Length > 0)
Selection.activeObject = m_NamesAssets[0];
else
{
var assetPath = AssetDatabase.GenerateUniqueAssetPath($"Assets/{typeof(T).Name}.asset");
AssetDatabase.CreateAsset(ScriptableObject.CreateInstance<T>(), assetPath);
Selection.activeObject = AssetDatabase.LoadAssetAtPath<T>(assetPath);
m_Options = null;
}
}
);
menu.DropDown(controlPosition);
}
EditorGUI.showMixedValue = showMixed;
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
if (m_NamesAssets?.Length > 1)
{
var id = GUIUtility.GetControlID(FocusType.Passive);
if (Event.current.type == EventType.Repaint)
{
position.width = EditorGUIUtility.singleLineHeight;
position.x = controlPosition.xMax + EditorGUIUtility.standardVerticalSpacing;
Styles.MultipleAssetsWarning.tooltip = string.Format(
Styles.MultipleAssetsTooltip,
ObjectNames.NicifyVariableName(typeof(T).Name),
m_NamesAssets.FirstOrDefault(n => n != null)?.name
);
GUIStyle.none.Draw(position, Styles.MultipleAssetsWarning, id);
}
}
}
}
[CustomPropertyDrawer(typeof(CustomPhysicsBodyTags))]
class CustomBodyTagsDrawer : TagsDrawer<CustomPhysicsBodyTagNames>
{
protected override string DefaultCategoryName => "Custom Physics Body Tag";
protected override int MaxNumCategories => 8;
}
[CustomPropertyDrawer(typeof(CustomPhysicsMaterialTags))]
class CustomMaterialTagsDrawer : TagsDrawer<CustomPhysicsMaterialTagNames>
{
protected override string DefaultCategoryName => "Custom Physics Material Tag";
protected override int MaxNumCategories => 8;
}
[CustomPropertyDrawer(typeof(PhysicsCategoryTags))]
class PhysicsCategoryTagsDrawer : TagsDrawer<PhysicsCategoryNames>
{
protected override string DefaultCategoryName => "Physics Category";
protected override int MaxNumCategories => 32;
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/EditorGUIControls.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[InitializeOnLoad]
static class EditorGUIControls
{
static EditorGUIControls()
{
if (k_SoftSlider == null)
Debug.LogException(new MissingMemberException("Could not find expected signature of EditorGUI.Slider() for soft slider."));
}
static class Styles
{
public static readonly string CompatibilityWarning = L10n.Tr("Not compatible with {0}.");
}
public static void DisplayCompatibilityWarning(Rect position, GUIContent label, string incompatibleType)
{
EditorGUI.HelpBox(
EditorGUI.PrefixLabel(position, label),
string.Format(Styles.CompatibilityWarning, incompatibleType),
MessageType.Error
);
}
static readonly MethodInfo k_SoftSlider = typeof(EditorGUI).GetMethod(
"Slider",
BindingFlags.Static | BindingFlags.NonPublic,
null,
new[]
{
typeof(Rect), // position
typeof(GUIContent), // label
typeof(float), // value
typeof(float), // sliderMin
typeof(float), // sliderMax
typeof(float), // textFieldMin
typeof(float) // textFieldMax
},
Array.Empty<ParameterModifier>()
);
static readonly object[] k_SoftSliderArgs = new object[7];
namespace Unity.Physics.Editor
{
static class SceneViewUtility
{
static class Styles
{
public static readonly GUIStyle ProgressBarTrack = new GUIStyle
{
fixedHeight = 4f,
normal = new GUIStyleState { background = Texture2D.whiteTexture }
};
public static readonly GUIStyle ProgressBarIndicator = new GUIStyle
{
fixedHeight = 4f,
normal = new GUIStyleState { background = Texture2D.whiteTexture }
};
public static readonly GUIStyle SceneViewStatusMessage = new GUIStyle("NotificationBackground")
{
fontSize = EditorStyles.label.fontSize
};
static Styles() => SceneViewStatusMessage.padding = SceneViewStatusMessage.border;
}
namespace Unity.Physics.Editor
{
static class StatusMessageUtility
{
public static MessageType GetHierarchyStatusMessage(IReadOnlyList<UnityObject> targets, out string statusMessage)
{
statusMessage = string.Empty;
if (targets.Count == 0)
return MessageType.None;
var numChildTargets = 0;
foreach (Component c in targets)
{
// hierarchy roots and leaf shapes do not emit a message
if (
c == null
|| c.transform.parent == null
|| PhysicsShapeExtensions.GetPrimaryBody(c.gameObject) != c.gameObject
)
continue;
if (matrixStates.Contains(MatrixState.NonUniformScale))
{
statusMessage = L10n.Tr(
matrixStates.Count == 1
? "Target has non-uniform scale. Shape data will be transformed during conversion in order to bake scale into the run-time format."
: "One or more targets has non-uniform scale. Shape data will be transformed during conversion in order to bake scale into the run-time format."
);
return MessageType.Warning;
}
return MessageType.None;
}
}
}
Basic/Assets/Scripts/Physics/StatefulEvents/IStatefulSimulationEvent.cs
using Unity.Entities;
namespace Unity.Physics.Stateful
{
/// <summary>
/// Describes an event state.
/// Event state is set to:
/// 0) Undefined, when the state is unknown or not needed
/// 1) Enter, when 2 bodies are interacting in the current frame, but they did not interact the previous frame
/// 2) Stay, when 2 bodies are interacting in the current frame, and they also interacted in the previous frame
/// 3) Exit, when 2 bodies are not interacting in the current frame, but they did interact in the previous frame
/// </summary>
public enum StatefulEventState : byte
{
Undefined,
Enter,
Stay,
Exit
}
/// <summary>
/// Extends ISimulationEvent with extra <see cref="StatefulEventState"/>.
/// </summary>
public interface IStatefulSimulationEvent<T> : IBufferElementData, ISimulationEvent<T>
{
public StatefulEventState State { get; set; }
}
}
Basic/Assets/Scripts/Physics/StatefulEvents/StatefulCollisionEvent.cs
using Unity.Assertions;
using Unity.Entities;
using Unity.Mathematics;
namespace Unity.Physics.Stateful
{
// Collision Event that can be stored inside a DynamicBuffer
public struct StatefulCollisionEvent : IBufferElementData, IStatefulSimulationEvent<StatefulCollisionEvent>
{
public Entity EntityA { get; set; }
public Entity EntityB { get; set; }
public int BodyIndexA { get; set; }
public int BodyIndexB { get; set; }
public ColliderKey ColliderKeyA { get; set; }
public ColliderKey ColliderKeyB { get; set; }
public StatefulEventState State { get; set; }
public float3 Normal;
// Returns the normal pointing from passed entity to the other one in pair
public float3 GetNormalFrom(Entity entity)
{
Assert.IsTrue((entity == EntityA) || (entity == EntityB));
return math.select(-Normal, Normal, entity == EntityB);
}
public bool TryGetDetails(out Details details)
{
details = CollisionDetails;
return CollisionDetails.IsValid;
}
public int CompareTo(StatefulCollisionEvent other) => ISimulationEventUtilities.CompareEvents(this, other);
}
}
Basic/Assets/Scripts/Physics/StatefulEvents/StatefulCollisionEventBufferAuthoring.cs
using Unity.Entities;
using UnityEngine;
namespace Unity.Physics.Stateful
{
public struct StatefulCollisionEventDetails : IComponentData
{
public bool CalculateDetails;
}
public class StatefulCollisionEventBufferAuthoring : MonoBehaviour
{
[Tooltip("If selected, the details will be calculated in collision event dynamic buffer of this entity")]
public bool CalculateDetails = false;
namespace Unity.Physics.Stateful
{
/// <summary>
/// This system converts stream of CollisionEvents to StatefulCollisionEvents that can be stored in a Dynamic Buffer.
/// In order for this conversion, it is required to:
/// 1) Use the 'Collide Raise Collision Events' option of the 'Collision Response' property on a <see cref="PhysicsShapeAuthoring"/> component, and
/// 2) Add a <see cref="StatefulCollisionEventBufferAuthoring"/> component to that entity (and select if details should be calculated or not)
/// or, if this is desired on a Character Controller:
/// 1) Tick the 'Raise Collision Events' flag on the <see cref="CharacterControllerAuthoring"/> component.
/// </summary>
[UpdateInGroup(typeof(PhysicsSystemGroup))]
[UpdateAfter(typeof(PhysicsSimulationGroup))]
[BurstCompile]
public partial struct StatefulCollisionEventBufferSystem : ISystem
{
private StatefulSimulationEventBuffers<StatefulCollisionEvent> m_StateFulEventBuffers;
private ComponentHandles m_Handles;
// Component that does nothing. Made in order to use a generic job. See OnUpdate() method for details.
internal struct DummyExcludeComponent : IComponentData {};
struct ComponentHandles
{
public ComponentLookup<DummyExcludeComponent> EventExcludes;
public ComponentLookup<StatefulCollisionEventDetails> EventDetails;
public BufferLookup<StatefulCollisionEvent> EventBuffers;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
m_StateFulEventBuffers = new StatefulSimulationEventBuffers<StatefulCollisionEvent>();
m_StateFulEventBuffers.AllocateBuffers();
state.RequireForUpdate<StatefulCollisionEvent>();
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
m_Handles.Update(ref state);
m_StateFulEventBuffers.SwapBuffers();
UseExcludeComponent = false,
EventExcludeLookup = m_Handles.EventExcludes
}.Schedule(state.Dependency);
}
}
}
Basic/Assets/Scripts/Physics/StatefulEvents/StatefulSimulationEventBuffers.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
namespace Unity.Physics.Stateful
{
public struct StatefulSimulationEventBuffers<T> where T : unmanaged, IStatefulSimulationEvent<T>
{
public NativeList<T> Previous;
public NativeList<T> Current;
/// <summary>
/// Given two sorted event buffers, this function returns a single combined list with
/// all the appropriate <see cref="StatefulEventState"/> set on each event.
/// </summary>
/// <param name="previousEvents">The events buffer from the previous frame. This list should have already be sorted from the previous frame.</param>
/// <param name="currentEvents">The events buffer from the current frame. This list should be sorted before calling this function.</param>
/// <param name="statefulEvents">A single combined list of stateful events based on the previous and current frames.</param>
/// <param name="sortCurrent">Specifies whether the currentEvents list needs to be sorted first.</param>
public static void GetStatefulEvents(NativeList<T> previousEvents, NativeList<T> currentEvents, NativeList<T> statefulEvents, bool sortCurrent = true)
{
if (sortCurrent) currentEvents.Sort();
statefulEvents.Clear();
int c = 0;
int p = 0;
while (c < currentEvents.Length && p < previousEvents.Length)
{
int r = previousEvents[p].CompareTo(currentEvents[c]);
if (r == 0)
{
var currentEvent = currentEvents[c];
currentEvent.State = StatefulEventState.Stay;
statefulEvents.Add(currentEvent);
c++;
p++;
}
else if (r < 0)
{
var previousEvent = previousEvents[p];
previousEvent.State = StatefulEventState.Exit;
statefulEvents.Add(previousEvent);
p++;
}
else //(r > 0)
{
var currentEvent = currentEvents[c];
currentEvent.State = StatefulEventState.Enter;
statefulEvents.Add(currentEvent);
c++;
}
}
if (c == currentEvents.Length)
{
while (p < previousEvents.Length)
{
var previousEvent = previousEvents[p];
previousEvent.State = StatefulEventState.Exit;
statefulEvents.Add(previousEvent);
p++;
}
}
else if (p == previousEvents.Length)
{
while (c < currentEvents.Length)
{
var currentEvent = currentEvents[c];
currentEvent.State = StatefulEventState.Enter;
statefulEvents.Add(currentEvent);
c++;
}
}
}
}
public static class StatefulEventCollectionJobs
{
[BurstCompile]
public struct CollectTriggerEvents : ITriggerEventsJob
{
public NativeList<StatefulTriggerEvent> TriggerEvents;
public void Execute(TriggerEvent triggerEvent) => TriggerEvents.Add(new StatefulTriggerEvent(triggerEvent));
}
[BurstCompile]
public struct CollectCollisionEvents : ICollisionEventsJob
{
public NativeList<StatefulCollisionEvent> CollisionEvents;
public void Execute(CollisionEvent collisionEvent) => CollisionEvents.Add(new StatefulCollisionEvent(collisionEvent));
}
[BurstCompile]
public struct CollectCollisionEventsWithDetails : ICollisionEventsJob
{
public NativeList<StatefulCollisionEvent> CollisionEvents;
[ReadOnly] public PhysicsWorld PhysicsWorld;
[ReadOnly] public ComponentLookup<StatefulCollisionEventDetails> EventDetails;
public bool ForceCalculateDetails;
CollisionEvents.Add(statefulCollisionEvent);
}
}
[BurstCompile]
public struct ConvertEventStreamToDynamicBufferJob<T, C> : IJob
where T : unmanaged, IBufferElementData, IStatefulSimulationEvent<T>
where C : unmanaged, IComponentData
{
public NativeList<T> PreviousEvents;
public NativeList<T> CurrentEvents;
public BufferLookup<T> EventBuffers;
public bool UseExcludeComponent;
[ReadOnly] public ComponentLookup<C> EventExcludeLookup;
public void Execute()
{
var statefulEvents = new NativeList<T>(CurrentEvents.Length, Allocator.Temp);
if (addToEntityA)
{
EventBuffers[statefulEvent.EntityA].Add(statefulEvent);
}
if (addToEntityB)
{
EventBuffers[statefulEvent.EntityB].Add(statefulEvent);
}
}
}
}
}
}
Basic/Assets/Scripts/Physics/StatefulEvents/StatefulTriggerEvent.cs
using Unity.Entities;
using Unity.Assertions;
namespace Unity.Physics.Stateful
{
// Trigger Event that can be stored inside a DynamicBuffer
public struct StatefulTriggerEvent : IBufferElementData, IStatefulSimulationEvent<StatefulTriggerEvent>
{
public Entity EntityA { get; set; }
public Entity EntityB { get; set; }
public int BodyIndexA { get; set; }
public int BodyIndexB { get; set; }
public ColliderKey ColliderKeyA { get; set; }
public ColliderKey ColliderKeyB { get; set; }
public StatefulEventState State { get; set; }
namespace Unity.Physics.Stateful
{
// If this component is added to an entity, trigger events won't be added to a dynamic buffer
// of that entity by the StatefulTriggerEventBufferSystem. This component is by default added to
// CharacterController entity, so that CharacterControllerSystem can add trigger events to
// CharacterController on its own, without StatefulTriggerEventBufferSystem interference.
public struct StatefulTriggerEventExclude : IComponentData {}
public class StatefulTriggerEventBufferAuthoring : MonoBehaviour
{
}
class StatefulTriggerEventBufferAuthoringBaker : Baker<StatefulTriggerEventBufferAuthoring>
{
public override void Bake(StatefulTriggerEventBufferAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddBuffer<StatefulTriggerEvent>(entity);
}
}
}
Basic/Assets/Scripts/Physics/StatefulEvents/StatefulTriggerEventBufferSystem.cs
using Unity.Entities;
using Unity.Jobs;
using Unity.Physics;
using Unity.Physics.Systems;
using Unity.Collections;
using Unity.Burst;
namespace Unity.Physics.Stateful
{
/// <summary>
/// This system converts stream of TriggerEvents to StatefulTriggerEvents that can be stored in a Dynamic Buffer.
/// In order for this conversion, it is required to:
/// 1) Use the 'Raise Trigger Events' option of the 'Collision Response' property on a <see cref="PhysicsShapeAuthoring"/> component, and
/// 2) Add a <see cref="StatefulTriggerEventBufferAuthoring"/> component to that entity
/// or, if this is desired on a Character Controller:
/// 1) Tick the 'Raise Trigger Events' flag on the <see cref="CharacterControllerAuthoring"/> component.
/// Note: the Character Controller will not become a trigger, it will raise events when overlapping with one
/// </summary>
[UpdateInGroup(typeof(PhysicsSystemGroup))]
[UpdateAfter(typeof(PhysicsSimulationGroup))]
[BurstCompile]
public partial struct StatefulTriggerEventBufferSystem : ISystem
{
private StatefulSimulationEventBuffers<StatefulTriggerEvent> m_StateFulEventBuffers;
private ComponentHandles m_ComponentHandles;
private EntityQuery m_TriggerEventQuery;
struct ComponentHandles
{
public ComponentLookup<StatefulTriggerEventExclude> EventExcludes;
public BufferLookup<StatefulTriggerEvent> EventBuffers;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
EntityQueryBuilder builder = new EntityQueryBuilder(Allocator.Temp)
.WithAllRW<StatefulTriggerEvent>()
.WithNone<StatefulTriggerEventExclude>();
m_TriggerEventQuery = state.GetEntityQuery(builder);
state.RequireForUpdate(m_TriggerEventQuery);
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
m_StateFulEventBuffers.Dispose();
}
[BurstCompile]
public partial struct ClearTriggerEventDynamicBufferJob : IJobEntity
{
public void Execute(ref DynamicBuffer<StatefulTriggerEvent> eventBuffer) => eventBuffer.Clear();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
m_ComponentHandles.Update(ref state);
m_StateFulEventBuffers.SwapBuffers();
UseExcludeComponent = true,
EventExcludeLookup = m_ComponentHandles.EventExcludes
}.Schedule(state.Dependency);
}
}
}
Basic/Assets/Scripts/Player/BasicPlayer.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
[Serializable]
public struct BasicPlayer : IComponentData
{
public Entity ControlledCharacter;
public Entity ControlledCamera;
}
[Serializable]
public struct BasicPlayerInputs : IComponentData
{
public float2 MoveInput;
public float2 CameraLookInput;
public float CameraZoomInput;
public FixedInputEvent JumpPressed;
}
Basic/Assets/Scripts/Player/BasicPlayerAuthoring.cs
using UnityEngine;
using Unity.Entities;
[DisallowMultipleComponent]
public class BasicPlayerAuthoring : MonoBehaviour
{
public GameObject ControlledCharacter;
public GameObject ControlledCamera;
public class Baker : Baker<BasicPlayerAuthoring>
{
public override void Bake(BasicPlayerAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new BasicPlayer
{
ControlledCharacter = GetEntity(authoring.ControlledCharacter, TransformUsageFlags.Dynamic),
ControlledCamera = GetEntity(authoring.ControlledCamera, TransformUsageFlags.Dynamic),
});
AddComponent(entity, new BasicPlayerInputs());
}
}
}
Basic/Assets/Scripts/Player/BasicPlayerSystems.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
using Unity.CharacterController;
using Unity.Physics.Systems;
[UpdateInGroup(typeof(InitializationSystemGroup))]
public partial class BasicPlayerInputsSystem : SystemBase
{
private BasicInputActions InputActions;
RequireForUpdate<FixedTickSystem.Singleton>();
RequireForUpdate(SystemAPI.QueryBuilder().WithAll<BasicPlayer, BasicPlayerInputs>().Build());
cameraControl.FollowedCharacterEntity = player.ControlledCharacter;
cameraControl.Look = playerInputs.CameraLookInput;
cameraControl.Zoom = playerInputs.CameraZoomInput;
SystemAPI.SetComponent(player.ControlledCamera, cameraControl);
}
}
}
}
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup), OrderFirst = true)]
[BurstCompile]
public partial struct BasicFixedStepPlayerControlSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<FixedTickSystem.Singleton>();
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<BasicPlayer, BasicPlayerInputs>().Build());
}
// Jump
// We detect a jump event if the jump counter has changed since the last fixed update.
// This is part of a strategy for proper handling of button press events that are consumed during the fixed update group
characterControl.Jump = playerInputs.ValueRW.JumpPressed.IsSet(fixedTick);
SystemAPI.SetComponent(player.ControlledCharacter, characterControl);
}
}
}
}
OnlineFPS/Assets/Data/Input/FPSInputActions.cs
//------------------------------------------------------------------------------
// <auto-generated>
// This code was auto-generated by com.unity.inputsystem:InputActionCodeGenerator
// version 1.5.1
// from Assets/Data/Input/FPSInputActions.inputactions
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Utilities;
[Serializable]
public struct MainEntityCamera : IComponentData
{
public MainEntityCamera(float fov)
{
BaseFoV = fov;
CurrentFoV = fov;
}
[DisallowMultipleComponent]
public class MainEntityCameraAuthoring : MonoBehaviour
{
public float FOV = 75f;
void Awake()
{
Instance = GetComponent<UnityEngine.Camera>();
}
}
OnlineFPS/Assets/Scripts/Character/FirstPersonCharacterAspect.cs
using System;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Authoring;
using Unity.Physics.Extensions;
using Unity.Physics.Systems;
using Unity.Transforms;
using Unity.CharacterController;
using Unity.NetCode;
using UnityEngine;
public struct FirstPersonCharacterUpdateContext
{
// Here, you may add additional global data for your character updates, such as ComponentLookups, Singletons, NativeCollections, etc...
// The data you add here will be accessible in your character updates and all of your character "callbacks".
[ReadOnly]
public ComponentLookup<WeaponVisualFeedback> WeaponVisualFeedbackLookup;
[ReadOnly]
public ComponentLookup<WeaponControl> WeaponControlLookup;
public void OnSystemCreate(ref SystemState state)
{
WeaponVisualFeedbackLookup = state.GetComponentLookup<WeaponVisualFeedback>(true);
WeaponControlLookup = state.GetComponentLookup<WeaponControl>(true);
}
public void OnSystemUpdate(ref SystemState state)
{
WeaponVisualFeedbackLookup.Update(ref state);
WeaponControlLookup.Update(ref state);
}
}
public readonly partial struct FirstPersonCharacterAspect : IAspect, IKinematicCharacterProcessor<FirstPersonCharacterUpdateContext>
{
public readonly KinematicCharacterAspect CharacterAspect;
public readonly RefRW<FirstPersonCharacterComponent> CharacterComponent;
public readonly RefRW<FirstPersonCharacterControl> CharacterControl;
public readonly RefRW<ActiveWeapon> ActiveWeapon;
public void PhysicsUpdate(ref FirstPersonCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
ref FirstPersonCharacterComponent characterComponent = ref CharacterComponent.ValueRW;
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref float3 characterPosition = ref CharacterAspect.LocalTransform.ValueRW.Position;
// First phase of default character update
CharacterAspect.Update_Initialize(in this, ref context, ref baseContext, ref characterBody, baseContext.Time.DeltaTime);
CharacterAspect.Update_ParentMovement(in this, ref context, ref baseContext, ref characterBody, ref characterPosition, characterBody.WasGroundedBeforeCharacterUpdate);
CharacterAspect.Update_Grounding(in this, ref context, ref baseContext, ref characterBody, ref characterPosition);
// Update desired character velocity after grounding was detected, but before doing additional processing that depends on velocity
HandleVelocityControl(ref context, ref baseContext);
// Second phase of default character update
CharacterAspect.Update_PreventGroundingFromFutureSlopeChange(in this, ref context, ref baseContext, ref characterBody, in characterComponent.StepAndSlopeHandling);
CharacterAspect.Update_GroundPushing(in this, ref context, ref baseContext, characterComponent.Gravity);
CharacterAspect.Update_MovementAndDecollisions(in this, ref context, ref baseContext, ref characterBody, ref characterPosition);
CharacterAspect.Update_MovingPlatformDetection(ref baseContext, ref characterBody);
CharacterAspect.Update_ParentMomentum(ref baseContext, ref characterBody);
CharacterAspect.Update_ProcessStatefulCharacterHits();
}
private void HandleVelocityControl(ref FirstPersonCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
float deltaTime = baseContext.Time.DeltaTime;
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref FirstPersonCharacterComponent characterComponent = ref CharacterComponent.ValueRW;
ref FirstPersonCharacterControl characterControl = ref CharacterControl.ValueRW;
// Rotate move input and velocity to take into account parent rotation
if(characterBody.ParentEntity != Entity.Null)
{
characterControl.MoveVector = math.rotate(characterBody.RotationFromParent, characterControl.MoveVector);
characterBody.RelativeVelocity = math.rotate(characterBody.RotationFromParent, characterBody.RelativeVelocity);
}
if (characterBody.IsGrounded)
{
// Move on ground
float3 targetVelocity = characterControl.MoveVector * characterComponent.GroundMaxSpeed;
CharacterControlUtilities.StandardGroundMove_Interpolated(ref characterBody.RelativeVelocity, targetVelocity, characterComponent.GroundedMovementSharpness, deltaTime,
// Jump
if (characterControl.Jump)
{
CharacterControlUtilities.StandardJump(ref characterBody, characterBody.GroundingUp * characterComponent.JumpSpeed, true, characterBody.GroundingUp);
}
}
else
{
// Move in air
float3 airAcceleration = characterControl.MoveVector * characterComponent.AirAcceleration;
if (math.lengthsq(airAcceleration) > 0f)
{
float3 tmpVelocity = characterBody.RelativeVelocity;
CharacterControlUtilities.StandardAirMove(ref characterBody.RelativeVelocity, airAcceleration, characterComponent.AirMaxSpeed, characterBody.GroundingUp, deltaTime
// Cancel air acceleration from input if we would hit a non-grounded surface (prevents air-climbing slopes at high air accelerations)
if (characterComponent.PreventAirAccelerationAgainstUngroundedHits && CharacterAspect.MovementWouldHitNonGroundedObstruction(in this, ref context, ref baseContext,
{
characterBody.RelativeVelocity = tmpVelocity;
}
}
// Gravity
CharacterControlUtilities.AccelerateVelocity(ref characterBody.RelativeVelocity, characterComponent.Gravity, deltaTime);
// Drag
CharacterControlUtilities.ApplyDragToVelocity(ref characterBody.RelativeVelocity, deltaTime, characterComponent.AirDrag);
}
}
public void VariableUpdate(ref FirstPersonCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref FirstPersonCharacterComponent characterComponent = ref CharacterComponent.ValueRW;
ref quaternion characterRotation = ref CharacterAspect.LocalTransform.ValueRW.Rotation;
FirstPersonCharacterControl characterControl = CharacterControl.ValueRO;
ActiveWeapon activeWeapon = ActiveWeapon.ValueRO;
// Add rotation from parent body to the character rotation
// (this is for allowing a rotating moving platform to rotate your character as well, and handle interpolation properly)
KinematicCharacterUtilities.AddVariableRateRotationFromFixedRateRotation(ref characterRotation, characterBody.RotationFromParent, baseContext.Time.DeltaTime, characterBody
// View roll angles
{
float3 characterRight = MathUtilities.GetRightFromRotation(characterRotation);
float characterMaxSpeed = characterBody.IsGrounded ? characterComponent.GroundMaxSpeed : characterComponent.AirMaxSpeed;
float3 characterLateralVelocity = math.projectsafe(characterBody.RelativeVelocity, characterRight);
float characterLateralVelocityRatio = math.clamp(math.length(characterLateralVelocity) / characterMaxSpeed, 0f, 1f);
bool velocityIsRight = math.dot(characterBody.RelativeVelocity, characterRight) > 0f;
float targetTiltAngle = math.lerp(0f, characterComponent.ViewRollAmount, characterLateralVelocityRatio);
targetTiltAngle = velocityIsRight ? -targetTiltAngle : targetTiltAngle;
characterComponent.ViewRollDegrees = math.lerp(characterComponent.ViewRollDegrees, targetTiltAngle, math.saturate(characterComponent.ViewRollSharpness * baseContext.Time
}
// Handle aiming look sensitivity
if (context.WeaponControlLookup.TryGetComponent(activeWeapon.Entity, out WeaponControl weaponControl))
{
if (weaponControl.AimHeld)
{
if (context.WeaponVisualFeedbackLookup.TryGetComponent(activeWeapon.Entity, out WeaponVisualFeedback weaponFeedback))
{
characterControl.LookYawPitchDegrees *= weaponFeedback.LookSensitivityMultiplierWhileAiming;
}
}
}
// Compute character & view rotations from rotation input
FirstPersonCharacterUtilities.ComputeFinalRotationsFromRotationDelta(
ref characterComponent.ViewPitchDegrees,
ref characterComponent.CharacterYDegrees,
math.up(),
characterControl.LookYawPitchDegrees,
characterComponent.ViewRollDegrees,
characterComponent.MinViewAngle,
characterComponent.MaxViewAngle,
out characterRotation,
out float canceledPitchDegrees,
out characterComponent.ViewLocalRotation);
}
[Serializable]
[GhostComponent()]
public struct FirstPersonCharacterComponent : IComponentData
{
public float BaseFoV;
public float GroundMaxSpeed;
public float GroundedMovementSharpness;
public float AirAcceleration;
public float AirMaxSpeed;
public float AirDrag;
public float JumpSpeed;
public float3 Gravity;
public bool PreventAirAccelerationAgainstUngroundedHits;
public BasicStepAndSlopeHandlingParameters StepAndSlopeHandling;
MinViewAngle = -90f,
MaxViewAngle = 90f,
};
}
}
[Serializable]
public struct FirstPersonCharacterControl : IComponentData
{
public float3 MoveVector;
public float2 LookYawPitchDegrees;
public bool Jump;
}
[Serializable]
public struct FirstPersonCharacterView : IComponentData
{
public Entity CharacterEntity;
}
[Serializable]
public struct CharacterClientCleanup : ICleanupComponentData
{
public Entity DeathVFX;
public float3 DeathVFXSpawnWorldPosition;
}
[Serializable]
[GhostComponent()]
public struct OwningPlayer : IComponentData
{
[GhostField()]
public Entity Entity;
}
OnlineFPS/Assets/Scripts/Character/FirstPersonCharacterSystems.cs
using Unity.Burst;
using Unity.Burst.Intrinsics;
using Unity.Entities;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Transforms;
using Unity.CharacterController;
using Unity.NetCode;
using UnityEngine;
[UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
[BurstCompile]
public partial struct BuildCharacterRotationSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<LocalTransform, FirstPersonCharacterComponent>().Build());
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
BuildCharacterRotationJob job = new BuildCharacterRotationJob
{ };
job.Schedule();
}
[BurstCompile]
public partial struct BuildCharacterRotationJob : IJobEntity
{
void Execute(ref LocalTransform localTransform, in FirstPersonCharacterComponent characterComponent)
{
FirstPersonCharacterUtilities.ComputeRotationFromYAngleAndUp(characterComponent.CharacterYDegrees, math.up(), out quaternion tmpRotation);
localTransform.Rotation = tmpRotation;
}
}
}
[UpdateInGroup(typeof(KinematicCharacterPhysicsUpdateGroup))]
[BurstCompile]
public partial struct FirstPersonCharacterPhysicsUpdateSystem : ISystem
{
private EntityQuery _characterQuery;
private FirstPersonCharacterUpdateContext _context;
private KinematicCharacterUpdateContext _baseContext;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
_characterQuery = KinematicCharacterUtilities.GetBaseCharacterQueryBuilder()
.WithAll<
FirstPersonCharacterComponent,
FirstPersonCharacterControl>()
.Build(ref state);
_context = new FirstPersonCharacterUpdateContext();
_context.OnSystemCreate(ref state);
_baseContext = new KinematicCharacterUpdateContext();
_baseContext.OnSystemCreate(ref state);
state.RequireForUpdate(_characterQuery);
state.RequireForUpdate<NetworkTime>();
state.RequireForUpdate<PhysicsWorldSingleton>();
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
if (!SystemAPI.HasSingleton<NetworkTime>())
return;
_context.OnSystemUpdate(ref state);
_baseContext.OnSystemUpdate(ref state, SystemAPI.Time, SystemAPI.GetSingleton<PhysicsWorldSingleton>());
FirstPersonCharacterPhysicsUpdateJob job = new FirstPersonCharacterPhysicsUpdateJob
{
Context = _context,
BaseContext = _baseContext,
};
job.ScheduleParallel();
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct FirstPersonCharacterPhysicsUpdateJob : IJobEntity, IJobEntityChunkBeginEnd
{
public FirstPersonCharacterUpdateContext Context;
public KinematicCharacterUpdateContext BaseContext;
public bool OnChunkBegin(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
BaseContext.EnsureCreationOfTmpCollections();
return true;
}
public void OnChunkEnd(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask, bool chunkWasExecuted)
{ }
}
}
[UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
[UpdateAfter(typeof(PredictedFixedStepSimulationSystemGroup))]
[BurstCompile]
public partial struct FirstPersonCharacterVariableUpdateSystem : ISystem
{
private EntityQuery _characterQuery;
private FirstPersonCharacterUpdateContext _context;
private KinematicCharacterUpdateContext _baseContext;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
_characterQuery = KinematicCharacterUtilities.GetBaseCharacterQueryBuilder()
.WithAll<
FirstPersonCharacterComponent,
FirstPersonCharacterControl>()
.Build(ref state);
state.RequireForUpdate(_characterQuery);
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
_context.OnSystemUpdate(ref state);
_baseContext.OnSystemUpdate(ref state, SystemAPI.Time, SystemAPI.GetSingleton<PhysicsWorldSingleton>());
FirstPersonCharacterVariableUpdateJob variableUpdateJob = new FirstPersonCharacterVariableUpdateJob
{
Context = _context,
BaseContext = _baseContext,
};
variableUpdateJob.ScheduleParallel();
FirstPersonCharacterViewJob viewJob = new FirstPersonCharacterViewJob
{
FirstPersonCharacterLookup = SystemAPI.GetComponentLookup<FirstPersonCharacterComponent>(true),
};
viewJob.ScheduleParallel();
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct FirstPersonCharacterVariableUpdateJob : IJobEntity, IJobEntityChunkBeginEnd
{
public FirstPersonCharacterUpdateContext Context;
public KinematicCharacterUpdateContext BaseContext;
void Execute(FirstPersonCharacterAspect characterAspect)
{
characterAspect.VariableUpdate(ref Context, ref BaseContext);
}
public bool OnChunkBegin(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
BaseContext.EnsureCreationOfTmpCollections();
return true;
}
public void OnChunkEnd(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask, bool chunkWasExecuted)
{ }
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct FirstPersonCharacterViewJob : IJobEntity
{
[ReadOnly]
public ComponentLookup<FirstPersonCharacterComponent> FirstPersonCharacterLookup;
void Execute(ref LocalTransform localTransform, in FirstPersonCharacterView characterView)
{
if (FirstPersonCharacterLookup.TryGetComponent(characterView.CharacterEntity, out FirstPersonCharacterComponent character))
{
localTransform.Rotation = character.ViewLocalRotation;
}
}
}
}
OnlineFPS/Assets/Scripts/Character/FirstPersonCharacterUtilities.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.CharacterController;
public static class FirstPersonCharacterUtilities
{
public static quaternion GetCurrentWorldViewRotation(quaternion characterRotation, quaternion localCharacterViewRotation)
{
return math.mul(characterRotation, localCharacterViewRotation);
}
public static void GetCurrentWorldViewDirectionAndRotation(
quaternion characterRotation,
quaternion localCharacterViewRotation,
out float3 worldCharacterViewDirection,
out quaternion worldCharacterViewRotation)
{
worldCharacterViewRotation = GetCurrentWorldViewRotation(characterRotation, localCharacterViewRotation);
worldCharacterViewDirection = math.mul(worldCharacterViewRotation, math.forward());
}
public static void ComputeFinalRotationsFromTargetLookDirection(
ref quaternion characterRotation,
ref quaternion localCharacterViewRotation,
ref float3 targetLookDirection,
ref float viewPitchDegrees,
float viewRollDegrees,
float minViewPitchDegrees,
float maxViewPitchDegrees,
float3 characterUp)
{
// rotate character root to point at look direction on character up plane
float3 newCharacterForward = math.normalizesafe(MathUtilities.ProjectOnPlane(targetLookDirection, characterUp));
characterRotation = quaternion.LookRotationSafe(newCharacterForward, characterUp);
// calculate view pitch angles to look at target direction
viewPitchDegrees = math.degrees(MathUtilities.AngleRadians(newCharacterForward, targetLookDirection));
if (math.dot(characterUp, targetLookDirection) < 0f)
{
viewPitchDegrees *= -1f;
}
viewPitchDegrees = math.clamp(viewPitchDegrees, minViewPitchDegrees, maxViewPitchDegrees);
localCharacterViewRotation = CalculateLocalViewRotation(viewPitchDegrees, viewRollDegrees);
}
public static void ComputeFinalRotationsFromRotationDelta(
ref quaternion characterRotation,
ref float viewPitchDegrees,
float2 yawPitchDeltaDegrees,
float viewRollDegrees,
float minPitchDegrees,
float maxPitchDegrees,
out float canceledPitchDegrees,
out quaternion viewLocalRotation)
{
// Yaw
quaternion yawRotation = quaternion.Euler(math.up() * math.radians(yawPitchDeltaDegrees.x));
characterRotation = math.mul(characterRotation, yawRotation);
// Pitch
viewPitchDegrees += yawPitchDeltaDegrees.y;
float viewPitchAngleDegreesBeforeClamp = viewPitchDegrees;
viewPitchDegrees = math.clamp(viewPitchDegrees, minPitchDegrees, maxPitchDegrees);
canceledPitchDegrees = yawPitchDeltaDegrees.y - (viewPitchAngleDegreesBeforeClamp - viewPitchDegrees);
viewLocalRotation = CalculateLocalViewRotation(viewPitchDegrees, viewRollDegrees);
}
public static void ComputeFinalRotationsFromRotationDelta(
ref float viewPitchDegrees,
ref float characterRotationYDegrees,
float3 characterTransformUp,
float2 yawPitchDeltaDegrees,
float viewRollDegrees,
float minPitchDegrees,
float maxPitchDegrees,
out quaternion characterRotation,
out float canceledPitchDegrees,
out quaternion viewLocalRotation)
{
// Yaw
characterRotationYDegrees += yawPitchDeltaDegrees.x;
ComputeRotationFromYAngleAndUp(characterRotationYDegrees, characterTransformUp, out characterRotation);
// Pitch
viewPitchDegrees += yawPitchDeltaDegrees.y;
float viewPitchAngleDegreesBeforeClamp = viewPitchDegrees;
viewPitchDegrees = math.clamp(viewPitchDegrees, minPitchDegrees, maxPitchDegrees);
canceledPitchDegrees = yawPitchDeltaDegrees.y - (viewPitchAngleDegreesBeforeClamp - viewPitchDegrees);
viewLocalRotation = CalculateLocalViewRotation(viewPitchDegrees, viewRollDegrees);
}
public static void ComputeRotationFromYAngleAndUp(
float characterRotationYDegrees,
float3 characterTransformUp,
out quaternion characterRotation)
{
characterRotation = math.mul(MathUtilities.CreateRotationWithUpPriority(characterTransformUp, math.forward()), quaternion.Euler(0f, math.radians(characterRotationYDegrees),
}
public static quaternion CalculateLocalViewRotation(float viewPitchDegrees, float viewRollDegrees)
{
// Pitch
quaternion viewLocalRotation = quaternion.AxisAngle(-math.right(), math.radians(viewPitchDegrees));
// Roll
viewLocalRotation = math.mul(viewLocalRotation, quaternion.AxisAngle(math.forward(), math.radians(viewRollDegrees)));
return viewLocalRotation;
}
public static float3 ComputeTargetLookDirectionFromRotationAngles(
ref float viewPitchDegrees,
float minViewPitchDegrees,
float maxViewPitchDegrees,
float2 pitchYawDegrees,
quaternion characterRotation)
{
// Yaw
quaternion yawRotation = quaternion.Euler(math.up() * math.radians(pitchYawDegrees.x));
quaternion targetRotation = math.mul(characterRotation, yawRotation);
// Pitch
float tmpViewPitchAngleDegrees = viewPitchDegrees + pitchYawDegrees.y;
tmpViewPitchAngleDegrees = math.clamp(tmpViewPitchAngleDegrees, minViewPitchDegrees, maxViewPitchDegrees);
quaternion pitchRotation = quaternion.Euler(-math.right() * math.radians(tmpViewPitchAngleDegrees));
targetRotation = math.mul(targetRotation, pitchRotation);
return math.mul(targetRotation, math.forward());
}
}
OnlineFPS/Assets/Scripts/Character/FirstPersonCharacterViewAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
[DisallowMultipleComponent]
public class FirstPersonCharacterViewAuthoring : MonoBehaviour
{
public GameObject Character;
[Serializable]
public struct HostRequest : IComponentData
{
public NetworkEndpoint EndPoint;
}
[Serializable]
public struct DisconnectRequest : IComponentData
{ }
[Serializable]
public struct DisposeClientWorldRequest : IComponentData
{ }
[Serializable]
public struct DisposeServerWorldRequest : IComponentData
{ }
[Serializable]
public struct Singleton : IComponentData
{
public MenuState MenuState;
public Entity MenuVisualsSceneInstance;
}
// Auto-create singleton
EntityManager.CreateEntity(typeof(Singleton));
RequireForUpdate<GameResources>();
RequireForUpdate<Singleton>();
}
protected override void OnStartRunning()
{
base.OnStartRunning();
// Start a tmp server just once so we can get a firewall prompt when running the game for the first time
{
NetworkDriver tmpNetDriver = NetworkDriver.Create();
NetworkEndpoint tmpEndPoint = NetworkEndpoint.Parse(LocalHost, 7777);
if (tmpNetDriver.Bind(tmpEndPoint) == 0)
{
tmpNetDriver.Listen();
}
tmpNetDriver.Dispose();
}
}
protected override void OnUpdate()
{
ref Singleton singleton = ref SystemAPI.GetSingletonRW<Singleton>().ValueRW;
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(World.Unmanaged);
GameResources gameResources = SystemAPI.GetSingleton<GameResources>();
ProcessHostRequests(ref singleton, ref ecb, gameResources);
ProcessJoinRequests(ref singleton, ref ecb, gameResources);
ProcessDisconnectRequests(ref singleton, ref ecb);
HandleMenuState(ref singleton);
HandleDisposeClientServerWorldsAndReturnToMenu(ref singleton, ref ecb);
}
private void ProcessHostRequests(ref Singleton singleton, ref EntityCommandBuffer ecb, GameResources gameResources)
{
EntityCommandBuffer serverECB = new EntityCommandBuffer(Allocator.Temp);
if (WorldUtilities.IsValidAndCreated(ServerWorld))
{
serverECB.Playback(ServerWorld.EntityManager);
}
serverECB.Dispose();
}
private void ProcessJoinRequests(ref Singleton singleton, ref EntityCommandBuffer ecb, GameResources gameResources)
{
EntityCommandBuffer clientECB = new EntityCommandBuffer(Allocator.Temp);
foreach (var (request, entity) in SystemAPI.Query<RefRO<JoinRequest>>().WithEntityAccess())
{
if (!WorldUtilities.IsValidAndCreated(ClientWorld))
{
// Create client world
ClientWorld = NetCodeBootstrap.CreateClientWorld("ClientWorld");
// Tickrate
Entity tickRateEntity = clientECB.CreateEntity();
clientECB.AddComponent(tickRateEntity, gameResources.GetClientServerTickRate());
// Connect to endpoint
EntityQuery clientNetworkDriverQuery = new EntityQueryBuilder(Allocator.Temp).WithAllRW<NetworkStreamDriver>().Build(ClientWorld.EntityManager);
clientNetworkDriverQuery.GetSingletonRW<NetworkStreamDriver>().ValueRW.Connect(ClientWorld.EntityManager, request.ValueRO.EndPoint);
// Create local game data singleton in client world
Entity localGameDataEntity = ClientWorld.EntityManager.CreateEntity();
ClientWorld.EntityManager.AddComponentData(localGameDataEntity, new LocalGameData
{
LocalPlayerName = request.ValueRO.LocalPlayerName,
});
// Create a request to join once the game scenes have been loaded
{
EntityQuery clientGameSingletonQuery = new EntityQueryBuilder(Allocator.Temp).WithAllRW<ClientGameSystem.Singleton>().Build(ClientWorld.EntityManager);
ref ClientGameSystem.Singleton clientGameSingleton = ref clientGameSingletonQuery.GetSingletonRW<ClientGameSystem.Singleton>().ValueRW;
clientGameSingleton.Spectator = request.ValueRO.Spectator;
if (WorldUtilities.IsValidAndCreated(ClientWorld))
{
clientECB.Playback(ClientWorld.EntityManager);
}
clientECB.Dispose();
}
EntityManager.DestroyEntity(disposeClientRequestQuery);
}
EntityManager.DestroyEntity(disposeServerRequestQuery);
}
}
private void HandleMenuState(ref Singleton singleton)
{
// Detect state changes
{
if (WorldUtilities.IsValidAndCreated(ClientWorld))
{
EntityQuery connectionInGameQuery = new EntityQueryBuilder(Allocator.Temp).WithAll<NetworkId, NetworkStreamInGame>().Build(ClientWorld.EntityManager);
if (connectionInGameQuery.CalculateEntityCount() == 0)
{
singleton.MenuState = MenuState.Connecting;
}
else
{
singleton.MenuState = MenuState.InGame;
}
}
else if (WorldUtilities.IsValidAndCreated(ServerWorld))
{
EntityQuery serverGameSingletonQuery = new EntityQueryBuilder(Allocator.Temp).WithAll<ServerGameSystem.Singleton>().Build(ServerWorld.EntityManager);
ServerGameSystem.Singleton serverGameSingleton = serverGameSingletonQuery.GetSingleton<ServerGameSystem.Singleton>();
if (serverGameSingleton.AcceptJoins)
{
singleton.MenuState = MenuState.InGame;
}
else
{
singleton.MenuState = MenuState.Connecting;
}
}
else
{
singleton.MenuState = MenuState.InMenu;
}
}
[WorldSystemFilter(WorldSystemFilterFlags.LocalSimulation)]
public partial class GameUISystem : SystemBase
{
private FPSInputActions InputActions;
private UIDocument MenuDocument;
private UIDocument CrosshairDocument;
private UIDocument RespawnScreenDocument;
private MenuState LastKnownMenuState;
private float RespawnCounter = -1f;
private int PreviousRespawnCounterValue = -1;
private VisualElement MainPanel;
private VisualElement ConnectionPanel;
private VisualElement ConnectingPanel;
private VisualElement InGamePanel;
private Button JoinButton;
private Button HostButton;
private Button DisconnectButton;
private TextField NameTextField;
private TextField JoinIPTextField;
private TextField JoinPortTextField;
private TextField HostPortTextField;
private Toggle SpectatorToggle;
private Slider LookSensitivitySlider;
private Label RespawnMessageLabel;
private const string UIName_MainPanel = "MainPanel";
private const string UIName_ConnectionPanel = "ConnectionPanel";
private const string UIName_ConnectingPanel = "ConnectingPanel";
private const string UIName_InGamePanel = "InGamePanel";
private const string UIName_JoinButton = "JoinButton";
private const string UIName_HostButton = "HostButton";
private const string UIName_DisconnectButton = "DisconnectButton";
private const string UIName_NameTextField = "NameTextField";
private const string UIName_JoinIPTextField = "JoinIPTextField";
private const string UIName_JoinPortTextField = "JoinPortTextField";
private const string UIName_HostPortTextField = "HostPortTextField";
private const string UIName_SpectatorToggle = "SpectatorToggle";
private const string UIName_LookSensitivitySlider = "LookSensitivitySlider";
private const string UIName_RespawnMessageLabel = "RespawnMessageLabel";
protected override void OnStartRunning()
{
base.OnStartRunning();
InputActions = new FPSInputActions();
InputActions.Enable();
InputActions.DefaultMap.Enable();
}
public void SetUIReferences(UIReferences references)
{
MenuDocument = references.MenuDocument;
CrosshairDocument = references.CrosshairDocument;
RespawnScreenDocument = references.RespawnScreenDocument;
// Get element refs
MainPanel = MenuDocument.rootVisualElement.Q<VisualElement>(UIName_MainPanel);
ConnectionPanel = MenuDocument.rootVisualElement.Q<VisualElement>(UIName_ConnectionPanel);
ConnectingPanel = MenuDocument.rootVisualElement.Q<VisualElement>(UIName_ConnectingPanel);
InGamePanel = MenuDocument.rootVisualElement.Q<VisualElement>(UIName_InGamePanel);
JoinButton = MenuDocument.rootVisualElement.Q<Button>(UIName_JoinButton);
HostButton = MenuDocument.rootVisualElement.Q<Button>(UIName_HostButton);
DisconnectButton = MenuDocument.rootVisualElement.Q<Button>(UIName_DisconnectButton);
NameTextField = MenuDocument.rootVisualElement.Q<TextField>(UIName_NameTextField);
JoinIPTextField = MenuDocument.rootVisualElement.Q<TextField>(UIName_JoinIPTextField);
JoinPortTextField = MenuDocument.rootVisualElement.Q<TextField>(UIName_JoinPortTextField);
HostPortTextField = MenuDocument.rootVisualElement.Q<TextField>(UIName_HostPortTextField);
SpectatorToggle = MenuDocument.rootVisualElement.Q<Toggle>(UIName_SpectatorToggle);
LookSensitivitySlider = MenuDocument.rootVisualElement.Q<Slider>(UIName_LookSensitivitySlider);
// Subscribe events
JoinButton.clicked += JoinButtonPressed;
HostButton.clicked += HostButtonPressed;
DisconnectButton.clicked += DisconnectButtonPressed;
LookSensitivitySlider.RegisterValueChangedCallback(LookSensitivitySliderChanged);
// Initial state
LookSensitivitySlider.value = GameSettings.LookSensitivity;
LastKnownMenuState = MenuState.InMenu;
JoinIPTextField.value = references.InitialJoinAddress;
SetState(LastKnownMenuState);
// Start with crosshair disabled
CrosshairDocument.enabled = false;
RespawnScreenDocument.enabled = false;
// When launched in server mode, auto-host
#if UNITY_SERVER
HostButtonPressed();
#endif
}
protected override void OnUpdate()
{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(World.Unmanaged);
FPSInputActions.DefaultMapActions inputsMap = InputActions.DefaultMap;
GameManagementSystem.Singleton gameManagementSingleton = SystemAPI.GetSingleton<GameManagementSystem.Singleton>();
HandleMenuStateChanges(inputsMap, gameManagementSingleton);
HandleCrosshair(ref ecb);
HandleRespawnMessage(ref ecb);
}
private void HandleMenuStateChanges(FPSInputActions.DefaultMapActions inputsMap, GameManagementSystem.Singleton gameManagementSingleton)
{
// Check for state changes
if (gameManagementSingleton.MenuState != LastKnownMenuState)
{
SetState(gameManagementSingleton.MenuState);
if (gameManagementSingleton.MenuState == MenuState.InGame)
{
// Set invisible the first time we enter play
SetVisibleRecursive(MainPanel, false, gameManagementSingleton.MenuState);
}
if (gameManagementSingleton.MenuState == MenuState.InMenu)
{
// Set visible when we enter menu
SetVisibleRecursive(MainPanel, true, gameManagementSingleton.MenuState);
RespawnCounter = -1f;
CrosshairDocument.enabled = false;
RespawnScreenDocument.enabled = false;
}
LastKnownMenuState = gameManagementSingleton.MenuState;
}
// Toggle visibility
if (inputsMap.ToggleMenu.WasPressedThisFrame())
{
SetVisibleRecursive(MainPanel, !MainPanel.enabledSelf, gameManagementSingleton.MenuState);
}
}
private void HandleCrosshair(ref EntityCommandBuffer ecb)
{
foreach (var (crosshairRequest, entity) in SystemAPI.Query<CrosshairRequest>().WithEntityAccess())
{
CrosshairDocument.enabled = crosshairRequest.Enable;
ecb.DestroyEntity(entity);
}
}
private void HandleRespawnMessage(ref EntityCommandBuffer ecb)
{
foreach (var (respawnRequest, entity) in SystemAPI.Query<RespawnMessageRequest>().WithEntityAccess())
{
if (respawnRequest.Start)
{
RespawnScreenDocument.enabled = true;
// Must get the label reference whenever the document is re-enabled
RespawnMessageLabel = RespawnScreenDocument.rootVisualElement.Q<Label>(UIName_RespawnMessageLabel);
RespawnCounter = respawnRequest.CountdownTime;
}
else
{
RespawnScreenDocument.enabled = false;
}
ecb.DestroyEntity(entity);
}
if (RespawnCounter >= 0f)
{
int respawnCounterValue = (int)math.ceil(RespawnCounter);
if (respawnCounterValue != PreviousRespawnCounterValue)
{
RespawnMessageLabel.text = $"Respawning in {respawnCounterValue}...";
}
PreviousRespawnCounterValue = respawnCounterValue;
RespawnCounter -= SystemAPI.Time.DeltaTime;
}
}
private void SetState(MenuState state)
{
switch (state)
{
case MenuState.InMenu:
ConnectionPanel.SetDisplay(true);
ConnectingPanel.SetDisplay(false);
InGamePanel.SetDisplay(false);
break;
case MenuState.Connecting:
ConnectionPanel.SetDisplay(false);
ConnectingPanel.SetDisplay(true);
InGamePanel.SetDisplay(false);
break;
case MenuState.InGame:
ConnectionPanel.SetDisplay(false);
ConnectingPanel.SetDisplay(false);
InGamePanel.SetDisplay(true);
break;
}
}
private void JoinButtonPressed()
{
if (ushort.TryParse(JoinPortTextField.text, out ushort port) && NetworkEndpoint.TryParse(JoinIPTextField.text, port, out NetworkEndpoint newEndPoint))
{
GameManagementSystem.JoinRequest joinRequest = new GameManagementSystem.JoinRequest
{
LocalPlayerName = new FixedString128Bytes(NameTextField.text),
EndPoint = newEndPoint,
Spectator = SpectatorToggle.value,
};
Entity joinRequestEntity = World.EntityManager.CreateEntity();
World.EntityManager.AddComponentData(joinRequestEntity, joinRequest);
}
else
{
Debug.LogError("Unable to parse Join IP or Port fields");
}
}
private void HostButtonPressed()
{
if (ushort.TryParse(HostPortTextField.text, out ushort port) && NetworkEndpoint.TryParse(GameManagementSystem.LocalHost, port, out NetworkEndpoint newLocalClientEndPoint))
{
NetworkEndpoint newServerEndPoint = NetworkEndpoint.AnyIpv4;
newServerEndPoint.Port = port;
GameManagementSystem.HostRequest hostRequest = new GameManagementSystem.HostRequest
{
EndPoint = newServerEndPoint,
};
Entity hostRequestEntity = World.EntityManager.CreateEntity();
World.EntityManager.AddComponentData(hostRequestEntity, hostRequest);
// Only create local client if not in server mode
#if !UNITY_SERVER
GameManagementSystem.JoinRequest joinRequest = new GameManagementSystem.JoinRequest
{
LocalPlayerName = new FixedString128Bytes(NameTextField.text),
EndPoint = newLocalClientEndPoint,
Spectator = SpectatorToggle.value,
};
Entity joinRequestEntity = World.EntityManager.CreateEntity();
World.EntityManager.AddComponentData(joinRequestEntity, joinRequest);
#endif
}
else
{
Debug.LogError("Unable to parse Host Port field");
}
}
private void DisconnectButtonPressed()
{
Entity disconnectRequestEntity = World.EntityManager.CreateEntity();
World.EntityManager.AddComponentData(disconnectRequestEntity, new GameManagementSystem.DisconnectRequest());
}
private void LookSensitivitySliderChanged(ChangeEvent<float> value)
{
GameSettings.LookSensitivity = value.newValue;
}
private void SetVisibleRecursive(VisualElement root, bool visible, MenuState menuState)
{
root.visible = visible;
root.SetEnabled(visible);
if(visible)
{
SetState(menuState);
}
Cursor.visible = visible;
Cursor.lockState = visible ? CursorLockMode.None : CursorLockMode.Locked;
foreach (var child in root.Children())
{
SetVisibleRecursive(child, visible, menuState);
}
}
}
OnlineFPS/Assets/Scripts/GameManagement/NetcodeBootstrap.cs
using System.Collections;
using System.Collections.Generic;
using Unity.NetCode;
using UnityEngine;
[UnityEngine.Scripting.Preserve]
public class NetCodeBootstrap : ClientServerBootstrap
{
public override bool Initialize(string defaultWorldName)
{
CreateLocalWorld(defaultWorldName);
return true;
}
}
OnlineFPS/Assets/Scripts/GameManagement/ServerGameSystem.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Entities.Serialization;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Networking.Transport;
using Unity.Scenes;
using Unity.Transforms;
using UnityEngine;
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
[BurstCompile]
public partial struct ServerGameSystem : ISystem
{
public struct Singleton : IComponentData
{
public bool RequestingDisconnect;
public Unity.Mathematics.Random Random;
public bool AcceptJoins;
// Auto-create singleton
uint randomSeed = (uint)DateTime.Now.Millisecond;
Entity singletonEntity = state.EntityManager.CreateEntity();
state.EntityManager.AddComponentData(singletonEntity, new Singleton
{
Random = Unity.Mathematics.Random.CreateFromIndex(randomSeed),
});
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
ref Singleton singleton = ref _singletonQuery.GetSingletonRW<Singleton>().ValueRW;
GameResources gameResources = SystemAPI.GetSingleton<GameResources>();
private void HandleJoinRequests(ref SystemState state, ref Singleton singleton, GameResources gameResources)
{
if (singleton.AcceptJoins && _joinRequestQuery.CalculateEntityCount() > 0)
{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged);
if (!request.Spectator)
{
// Request to spawn character
Entity spawnCharacterRequestEntity = ecb.CreateEntity();
ecb.AddComponent(spawnCharacterRequestEntity, new CharacterSpawnRequest { ForConnection = rpcReceive.SourceConnection, Delay = -1f });
}
// Stream in game
ecb.AddComponent(rpcReceive.SourceConnection, new NetworkStreamInGame());
}
ecb.DestroyEntity(entity);
}
}
}
// Client disconnect
foreach (var (connectionState, ownedEntities, entity) in SystemAPI.Query<ConnectionState, DynamicBuffer<ClientOwnedEntities>>().WithEntityAccess())
{
if (connectionState.CurrentState == ConnectionState.State.Disconnected)
{
// Destroy all entities owned by client
for (int i = 0; i < ownedEntities.Length; i++)
{
ecb.DestroyEntity(ownedEntities[i].Entity);
}
ecb.RemoveComponent<ClientOwnedEntities>(entity);
ecb.RemoveComponent<ConnectionState>(entity);
}
}
// Disconnect requests
EntityQuery disconnectRequestQuery = SystemAPI.QueryBuilder().WithAll<DisconnectRequest>().Build();
if (disconnectRequestQuery.CalculateEntityCount() > 0)
{
// Make sure all renderEnvironments are disposed before initiating disconnect
EntityQuery renderEnvironmentsQuery = SystemAPI.QueryBuilder().WithAll<RenderEnvironment>().Build();
if (renderEnvironmentsQuery.CalculateEntityCount() > 0)
{
state.EntityManager.DestroyEntity(renderEnvironmentsQuery);
singleton.DisconnectionFramesCounter = 0;
}
singleton.DisconnectionFramesCounter++;
}
}
private void HandleSpawnCharacter(ref SystemState state, ref Singleton singleton, GameResources gameResources)
{
if (SystemAPI.QueryBuilder().WithAll<CharacterSpawnRequest>().Build().CalculateEntityCount() > 0)
{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged);
EntityQuery spawnPointsQuery = SystemAPI.QueryBuilder().WithAll<SpawnPoint, LocalToWorld>().Build();
NativeArray<LocalToWorld> spawnPointLtWs = spawnPointsQuery.ToComponentDataArray<LocalToWorld>(Allocator.Temp);
// Spawn character
Entity characterEntity = ecb.Instantiate(gameResources.CharacterGhost);
ecb.SetComponent(characterEntity, new GhostOwner { NetworkId = connectionId });
ecb.SetComponent(characterEntity, LocalTransform.FromPosition(randomSpawnPosition));
ecb.SetComponent(characterEntity, new OwningPlayer { Entity = playerEntity });
ecb.AppendToBuffer(spawnRequest.ValueRW.ForConnection, new ClientOwnedEntities { Entity = characterEntity });
ecb.DestroyEntity(entity);
}
}
spawnPointLtWs.Dispose();
}
}
}
OnlineFPS/Assets/Scripts/Misc/BakedGameObjectSceneReference.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Entities.Serialization;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UIElements;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Build.Content;
using UnityEditor.UIElements;
#endif
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(BakedGameObjectSceneReference))]
public class BakedGameObjectSceneReferencePropertyDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
VisualElement myInspector = new VisualElement();
PropertyField sceneField = new PropertyField(property.FindPropertyRelative("SceneAsset"), property.name);
myInspector.Add(sceneField);
return myInspector;
}
}
#endif
[Serializable]
public struct BakedGameObjectSceneReference
{
#if UNITY_EDITOR
public SceneAsset SceneAsset;
#endif
public int GetIndexInBuildScenes()
{
#if UNITY_EDITOR
if (SceneAsset != null)
{
string scenePath = AssetDatabase.GetAssetPath(SceneAsset);
for (int i = 0; i < EditorBuildSettings.scenes.Length; i++)
{
EditorBuildSettingsScene buildScene = EditorBuildSettings.scenes[i];
if (buildScene.path == scenePath)
{
return i;
}
}
Debug.LogError($"Error: Scene \"{SceneAsset.name}\" assigned in {typeof(BakedGameObjectSceneReference)} has invalid build index. Make sure this scene is added to build settings
}
#endif
return -1;
}
}
OnlineFPS/Assets/Scripts/Misc/BakedSubSceneReference.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Entities.Serialization;
using UnityEngine;
using UnityEngine.UIElements;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.UIElements;
#endif
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(BakedSubSceneReference))]
public class BakedSubSceneReferencePropertyDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
VisualElement myInspector = new VisualElement();
PropertyField sceneField = new PropertyField(property.FindPropertyRelative("SceneAsset"), property.name);
myInspector.Add(sceneField);
return myInspector;
}
}
#endif
[Serializable]
public struct BakedSubSceneReference
{
#if UNITY_EDITOR
public SceneAsset SceneAsset;
#endif
public EntitySceneReference GetEntitySceneReference()
{
#if UNITY_EDITOR
return new EntitySceneReference(SceneAsset);
#else
return default;
#endif
}
}
OnlineFPS/Assets/Scripts/Misc/CharacterDeathSystem.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Networking.Transport;
using Unity.Transforms;
using UnityEngine;
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
[UpdateInGroup(typeof(SimulationSystemGroup))]
[BurstCompile]
public partial struct CharacterDeathServerSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<Health, FirstPersonCharacterComponent>().Build());
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
MiscUtilities.GetConnectionsArrays(ref state, Allocator.TempJob, out NativeArray<Entity> connectionEntities, out NativeArray<NetworkId> connections);
CharacterDeathServerJob serverJob = new CharacterDeathServerJob
{
ECB = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged),
GameResources = SystemAPI.GetSingleton<GameResources>(),
ConnectionEntities = connectionEntities,
Connections = connections,
};
state.Dependency = serverJob.Schedule(state.Dependency);
connectionEntities.Dispose(state.Dependency);
connections.Dispose(state.Dependency);
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct CharacterDeathServerJob : IJobEntity
{
public EntityCommandBuffer ECB;
public GameResources GameResources;
public NativeArray<Entity> ConnectionEntities;
public NativeArray<NetworkId> Connections;
void Execute(Entity entity, in FirstPersonCharacterComponent character, in Health health, in GhostOwner ghostOwner)
{
if (health.IsDead())
{
// TODO: we could do something more efficient for this
// Find the entity of the owning connection
Entity owningConnectionEntity = Entity.Null;
for (int i = 0; i < Connections.Length; i++)
{
if (Connections[i].Value == ghostOwner.NetworkId)
{
owningConnectionEntity = ConnectionEntities[i];
break;
}
}
if (owningConnectionEntity != Entity.Null)
{
// Send character's owning client a message to start respawn countdown
Entity respawnScreenRequestEntity = ECB.CreateEntity();
ECB.AddComponent(respawnScreenRequestEntity, new RespawnMessageRequest { Start = true, CountdownTime = GameResources.RespawnTime });
ECB.AddComponent(respawnScreenRequestEntity, new SendRpcCommandRequest { TargetConnection = owningConnectionEntity });
// Request to spawn character for the owning client
Entity spawnCharacterRequestEntity = ECB.CreateEntity();
ECB.AddComponent(spawnCharacterRequestEntity, new ServerGameSystem.CharacterSpawnRequest { ForConnection = owningConnectionEntity, Delay = GameResources.RespawnTime
}
ECB.DestroyEntity(entity);
}
}
}
}
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(TransformSystemGroup))]
[BurstCompile]
public partial struct CharacterDeathClientSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<Health, FirstPersonCharacterComponent, CharacterClientCleanup>().Build());
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
CharacterDeathVFXSpawnPointJob job = new CharacterDeathVFXSpawnPointJob
{
LtWLookup = SystemAPI.GetComponentLookup<LocalToWorld>(true),
};
state.Dependency = job.Schedule(state.Dependency);
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct CharacterDeathVFXSpawnPointJob : IJobEntity
{
[ReadOnly]
public ComponentLookup<LocalToWorld> LtWLookup;
void Execute(ref CharacterClientCleanup characterCleanup, in FirstPersonCharacterComponent character)
{
if (LtWLookup.TryGetComponent(character.DeathVFXSpawnPoint, out LocalToWorld spawnPointLtW))
{
characterCleanup.DeathVFXSpawnWorldPosition = spawnPointLtW.Position;
}
}
}
}
OnlineFPS/Assets/Scripts/Misc/ConstantRotation.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[Serializable]
public struct ConstantRotation : IComponentData
{
public float3 RotationSpeed;
}
OnlineFPS/Assets/Scripts/Misc/ConstantRotationAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
public class ConstantRotationAuthoring : MonoBehaviour
{
public ConstantRotation ConstantRotation;
[BurstCompile]
public partial struct ConstantRotationSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{ }
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
foreach (var (localTransform, constantRotation) in SystemAPI.Query<RefRW<LocalTransform>, ConstantRotation>())
{
localTransform.ValueRW.Rotation = math.mul(quaternion.Euler(constantRotation.RotationSpeed * SystemAPI.Time.DeltaTime), localTransform.ValueRO.Rotation);
}
}
}
OnlineFPS/Assets/Scripts/Misc/CustomECBSystems.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.NetCode;
using Unity.Transforms;
using UnityEngine;
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(PredictedSimulationSystemGroup))]
[UpdateBefore(typeof(TransformSystemGroup))]
public partial class PostPredictionPreTransformsECBSystem : EntityCommandBufferSystem
{
public unsafe struct Singleton : IComponentData, IECBSingleton
{
internal UnsafeList<EntityCommandBuffer>* pendingBuffers;
internal Allocator allocator;
[Header("General Parameters")]
public float RespawnTime = 4f;
[Header("Scenes")]
public BakedSubSceneReference MenuVisualsScene;
public BakedSubSceneReference GameResourcesScene;
public BakedSubSceneReference GameScene;
[Header("Ghost Prefabs")]
public GameObject PlayerGhost;
public GameObject CharacterGhost;
public GameObject RailgunGhost;
public GameObject MachineGunGhost;
[Header("Other Prefabs")]
public GameObject SpectatorPrefab;
RespawnTime = authoring.RespawnTime,
MenuVisualsScene = authoring.MenuVisualsScene.GetEntitySceneReference(),
GameResourcesScene = authoring.GameResourcesScene.GetEntitySceneReference(),
GameScene = authoring.GameScene.GetEntitySceneReference(),
PlayerGhost = GetEntity(authoring.PlayerGhost, TransformUsageFlags.Dynamic),
CharacterGhost = GetEntity(authoring.CharacterGhost, TransformUsageFlags.Dynamic),
RailgunGhost = GetEntity(authoring.RailgunGhost, TransformUsageFlags.Dynamic),
MachineGunGhost = GetEntity(authoring.MachineGunGhost, TransformUsageFlags.Dynamic),
SpectatorPrefab = GetEntity(authoring.SpectatorPrefab, TransformUsageFlags.Dynamic),
});
}
}
}
OnlineFPS/Assets/Scripts/Misc/GameResourcesManaged.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[GhostComponentVariation(typeof(KinematicCharacterBody))]
[GhostComponent()]
public struct KinematicCharacterBody_GhostVariant
{
[GhostField()]
public float3 RelativeVelocity;
[GhostField()]
public bool IsGrounded;
}
// Character interpolation must be Client-only, because it would prevent proper LocalToWorld updates on server otherwise
[GhostComponentVariation(typeof(CharacterInterpolation))]
[GhostComponent(PrefabType = GhostPrefabType.PredictedClient)]
public struct CharacterInterpolation_GhostVariant
{
}
[GhostComponentVariation(typeof(LocalTransform))]
[GhostComponent()]
public struct LocalTransform_Character
{
[GhostField(Smoothing = SmoothingAction.InterpolateAndExtrapolate)]
public float3 Position;
}
OnlineFPS/Assets/Scripts/Misc/Health.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.NetCode;
using UnityEngine;
[Serializable]
[GhostComponent()]
public struct Health : IComponentData
{
public float MaxHealth;
[GhostField()]
public float CurrentHealth;
ServerWorld.EntityManager.MoveEntitiesFrom(EntityManager, pendingMoveQuery);
EntityManager.DestroyEntity(pendingMoveQuery);
}
}
}
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation | WorldSystemFilterFlags.ThinClientSimulation | WorldSystemFilterFlags.ServerSimulation)]
public partial class MoveClientServerEntitiesToLocalSystem : SystemBase
{
private World LocalWorld;
protected override void OnUpdate()
{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(World.Unmanaged);
if (!WorldUtilities.IsValidAndCreated(LocalWorld))
{
World.NoAllocReadOnlyCollection<World> worlds = World.All;
for (int i = 0; i < worlds.Count; i++)
{
World tmpWorld = worlds[i];
if (WorldUtilities.IsValidAndCreated(tmpWorld) &&
!(tmpWorld.IsClient() || tmpWorld.IsThinClient()) &&
!tmpWorld.IsServer() &&
tmpWorld.GetExistingSystemManaged<GameManagementSystem>() != null)
{
LocalWorld = tmpWorld;
break;
}
}
}
// Move entities
if (WorldUtilities.IsValidAndCreated(LocalWorld))
{
EntityQuery pendingMoveQuery = SystemAPI.QueryBuilder().WithAll<MoveToLocalWorld, EntityPendingMove>().Build();
NativeArray<Entity> moveEntities = SystemAPI.QueryBuilder().WithAll<MoveToLocalWorld>().Build().ToEntityArray(Allocator.Temp);
for (int i = 0; i < moveEntities.Length; i++)
{
Entity original = moveEntities[i];
Entity copy = EntityManager.Instantiate(original);
EntityManager.AddComponentData(copy, new EntityPendingMove());
ecb.DestroyEntity(original);
}
moveEntities.Dispose();
LocalWorld.EntityManager.MoveEntitiesFrom(EntityManager, pendingMoveQuery);
EntityManager.DestroyEntity(pendingMoveQuery);
}
}
}
OnlineFPS/Assets/Scripts/Misc/NameTagUpdateSystem.cs
using System.Collections;
using System.Collections.Generic;
using TMPro;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
public partial class NameTagUpdateSystem : SystemBase
{
protected override void OnUpdate()
{
if (GameResourcesManaged.Instance == null)
return;
// Init (spawn gameObject)
EntityQuery initNameTagQuery = SystemAPI.QueryBuilder().WithAll<NameTagProxy>().WithNone<NameTagProxyCleanup>().Build();
if (initNameTagQuery.CalculateChunkCount() > 0)
{
NativeArray<Entity> entities = initNameTagQuery.ToEntityArray(Allocator.Temp);
NativeArray<NameTagProxy> nameTags = initNameTagQuery.ToComponentDataArray<NameTagProxy>(Allocator.Temp);
for (int i = 0; i < entities.Length; i++)
{
Entity playerEntity = nameTags[i].PlayerEntity;
string playerName = "Player";
if (EntityManager.HasComponent<FirstPersonPlayer>(playerEntity))
{
playerName = EntityManager.GetComponentData<FirstPersonPlayer>(playerEntity).Name.ToString();
}
GameObject nameTagInstance = GameObject.Instantiate(GameResourcesManaged.Instance.NameTagPrefab);
nameTagInstance.GetComponentInChildren<TextMeshProUGUI>().text = playerName;
entities.Dispose();
nameTags.Dispose();
}
[Serializable]
public struct Particle : IComponentData
{
public float Lifetime;
public float StartingScale;
[Serializable]
public struct ParticleSpawner : IComponentData
{
public Entity ParticlePrefab;
public int ParticleCount;
public float2 ParticleLifetimeMinMax;
public float2 ParticleScaleMinMax;
public float2 ParticleSpeedMinMax;
}
OnlineFPS/Assets/Scripts/Misc/ParticleSpawnerAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
state.RequireForUpdate<Singleton>();
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
ParticleSpawnersJob spawnerJob = new ParticleSpawnersJob
{
ECB = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged),
SingletonEntity = SystemAPI.GetSingletonEntity<Singleton>(),
SingletonLookup = SystemAPI.GetComponentLookup<Singleton>(false),
};
spawnerJob.Schedule();
ParticlesJob particleJob = new ParticlesJob
{
DeltaTime = SystemAPI.Time.DeltaTime,
ECB = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter(),
};
particleJob.ScheduleParallel();
}
[BurstCompile]
public partial struct ParticleSpawnersJob : IJobEntity
{
public EntityCommandBuffer ECB;
public Entity SingletonEntity;
public ComponentLookup<Singleton> SingletonLookup;
void Execute(Entity entity, in LocalTransform transform, in ParticleSpawner spawner)
{
float3 pos = transform.Position;
quaternion rot = transform.Rotation;
Singleton singleton = SingletonLookup[SingletonEntity];
for (int i = 0; i < spawner.ParticleCount; i++)
{
Entity particle = ECB.Instantiate(spawner.ParticlePrefab);
ECB.SetComponent(particle, new Particle
{
Lifetime = singleton.Random.NextFloat(spawner.ParticleLifetimeMinMax.x, spawner.ParticleLifetimeMinMax.y),
Velocity = singleton.Random.NextFloat3Direction() * singleton.Random.NextFloat(spawner.ParticleSpeedMinMax.x, spawner.ParticleSpeedMinMax.y),
StartingScale = singleton.Random.NextFloat(spawner.ParticleScaleMinMax.x, spawner.ParticleScaleMinMax.y),
CurrentLifetime = 0f,
});
ECB.SetComponent(particle, LocalTransform.FromPositionRotation(pos, rot));
}
ECB.DestroyEntity(entity);
SingletonLookup[SingletonEntity] = singleton;
}
}
[BurstCompile]
public partial struct ParticlesJob : IJobEntity
{
public float DeltaTime;
public EntityCommandBuffer.ParallelWriter ECB;
void Execute(Entity entity, [ChunkIndexInQuery] int chunkIndexInQuery, ref Particle particle, ref LocalTransform transform)
{
if (particle.Lifetime <= 0f || particle.CurrentLifetime >= particle.Lifetime)
{
ECB.DestroyEntity(chunkIndexInQuery, entity);
return;
}
float lifetimeRatio = math.clamp(particle.CurrentLifetime / particle.Lifetime, 0f, 1f);
float invLifetimeRatio = 1f - lifetimeRatio ;
transform.Position += particle.Velocity * DeltaTime;
transform.Scale = invLifetimeRatio * particle.StartingScale;
particle.CurrentLifetime += DeltaTime;
}
}
}
OnlineFPS/Assets/Scripts/Misc/RenderEnvironment.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
[Serializable]
public class RenderEnvironment : IComponentData
{
public int LightingSceneIndex;
}
[Serializable]
public class RenderEnvironmentCleanup : ICleanupComponentData
{
public int LightingSceneIndex;
}
OnlineFPS/Assets/Scripts/Misc/RenderEnvironmentAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
RequireForUpdate<Singleton>();
SceneManager.sceneLoaded -= OnSceneLoaded;
SceneManager.sceneLoaded += OnSceneLoaded;
}
protected override void OnDestroy()
{
base.OnDestroy();
SceneManager.sceneLoaded -= OnSceneLoaded;
}
protected override void OnUpdate()
{
ref Singleton singleton = ref SystemAPI.GetSingletonRW<Singleton>().ValueRW;
EntityCommandBuffer ecb = new EntityCommandBuffer(Allocator.Temp);
if (renderEnvironment.LightingSceneIndex >= 0)
{
AsyncOperation loadSceneOperation = SceneManager.LoadSceneAsync(renderEnvironment.LightingSceneIndex, LoadSceneMode.Additive);
loadSceneOperation.allowSceneActivation = true;
singleton.ActiveLightingScene = renderEnvironment.LightingSceneIndex;
}
}
[Serializable]
public struct SceneLoadRequest : IComponentData
{
public bool IsLoaded;
}
[Serializable]
public struct SceneIdentifier : IBufferElementData
{
public EntitySceneReference SceneReference;
public Entity SceneEntity;
[Serializable]
public struct SpectatorController : IComponentData
{
[Serializable]
public struct Parameters
{
public float MoveSpeed;
public float MoveSharpness;
public float RotationSpeed;
}
RequireForUpdate<GameResources>();
// Create the input user
InputActions = new FPSInputActions();
InputActions.Enable();
InputActions.DefaultMap.Enable();
}
protected override void OnUpdate()
{
float deltaTime = SystemAPI.Time.DeltaTime;
GameResources gameResources = SystemAPI.GetSingleton<GameResources>();
FPSInputActions.DefaultMapActions defaultActionsMap = InputActions.DefaultMap;
// Velocity
float3 worldMoveInput = math.mul(localTransform.ValueRW.Rotation, moveInput);
float3 targetVelocity = worldMoveInput * spectatorController.ValueRW.Params.MoveSpeed;
spectatorController.ValueRW.Velocity = math.lerp(spectatorController.ValueRW.Velocity, targetVelocity, spectatorController.ValueRW.Params.MoveSharpness * deltaTime);
localTransform.ValueRW.Position += spectatorController.ValueRW.Velocity * deltaTime;
// Rotation
quaternion rotation = localTransform.ValueRW.Rotation;
quaternion rotationDeltaVertical = quaternion.Euler(math.radians(-lookInput.y) * spectatorController.ValueRW.Params.RotationSpeed, 0f, 0f);
quaternion rotationDeltaHorizontal = quaternion.Euler(0f, math.radians(lookInput.x) * spectatorController.ValueRW.Params.RotationSpeed, 0f);
rotation = math.mul(rotation, rotationDeltaVertical); // local rotation
rotation = math.mul(rotationDeltaHorizontal, rotation); // world rotation
localTransform.ValueRW.Rotation = rotation;
}
}
}
OnlineFPS/Assets/Scripts/Misc/SpectatorSpawnPoint.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
void Start()
{
World.DefaultGameObjectInjectionWorld.GetOrCreateSystemManaged<GameUISystem>().SetUIReferences(this);
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/AssemblyInfo.cs
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.Physics.Custom.Editor")]
[assembly: InternalsVisibleTo("Unity.Physics.Custom.EditModeTests")]
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/BaseBodyPairConnector.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
[RequireComponent(typeof(PhysicsBodyAuthoring))]
public abstract class BaseBodyPairConnector : MonoBehaviour
{
public PhysicsBodyAuthoring LocalBody => GetComponent<PhysicsBodyAuthoring>();
public PhysicsBodyAuthoring ConnectedBody;
void OnEnable()
{
// included so tick box appears in Editor
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/CustomPhysicsMaterialTagNames.cs
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Serialization;
namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Custom Physics Material Tag Names", fileName = "Custom Material Tag Names", order = 506)]
public sealed partial class CustomPhysicsMaterialTagNames : ScriptableObject, ITagNames
{
CustomPhysicsMaterialTagNames() {}
public IReadOnlyList<string> TagNames => m_TagNames;
[SerializeField]
[FormerlySerializedAs("m_FlagNames")]
string[] m_TagNames = Enumerable.Range(0, 8).Select(i => string.Empty).ToArray();
void OnValidate()
{
if (m_TagNames.Length != 8)
Array.Resize(ref m_TagNames, 8);
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/CustomPhysicsMaterialTags.cs
using System;
using Unity.Mathematics;
namespace Unity.Physics.Authoring
{
[Serializable]
public struct CustomPhysicsMaterialTags : IEquatable<CustomPhysicsMaterialTags>
{
public static CustomPhysicsMaterialTags Everything => new CustomPhysicsMaterialTags { Value = unchecked((byte)~0) };
public static CustomPhysicsMaterialTags Nothing => new CustomPhysicsMaterialTags { Value = 0 };
public bool Tag00;
public bool Tag01;
public bool Tag02;
public bool Tag03;
public bool Tag04;
public bool Tag05;
public bool Tag06;
public bool Tag07;
internal bool this[int i]
{
get
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 7), nameof(i));
switch (i)
{
case 0: return Tag00;
case 1: return Tag01;
case 2: return Tag02;
case 3: return Tag03;
case 4: return Tag04;
case 5: return Tag05;
case 6: return Tag06;
case 7: return Tag07;
default: return default;
}
}
set
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 7), nameof(i));
switch (i)
{
case 0: Tag00 = value; break;
case 1: Tag01 = value; break;
case 2: Tag02 = value; break;
case 3: Tag03 = value; break;
case 4: Tag04 = value; break;
case 5: Tag05 = value; break;
case 6: Tag06 = value; break;
case 7: Tag07 = value; break;
}
}
}
public byte Value
{
get
{
var result = 0;
result |= (Tag00 ? 1 : 0) << 0;
result |= (Tag01 ? 1 : 0) << 1;
result |= (Tag02 ? 1 : 0) << 2;
result |= (Tag03 ? 1 : 0) << 3;
result |= (Tag04 ? 1 : 0) << 4;
result |= (Tag05 ? 1 : 0) << 5;
result |= (Tag06 ? 1 : 0) << 6;
result |= (Tag07 ? 1 : 0) << 7;
return (byte)result;
}
set
{
Tag00 = (value & (1 << 0)) != 0;
Tag01 = (value & (1 << 1)) != 0;
Tag02 = (value & (1 << 2)) != 0;
Tag03 = (value & (1 << 3)) != 0;
Tag04 = (value & (1 << 4)) != 0;
Tag05 = (value & (1 << 5)) != 0;
Tag06 = (value & (1 << 6)) != 0;
Tag07 = (value & (1 << 7)) != 0;
}
}
public bool Equals(CustomPhysicsMaterialTags other) => Value == other.Value;
public override bool Equals(object obj) => obj is CustomPhysicsMaterialTags other && Equals(other);
namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Physics Category Names", fileName = "Physics Category Names", order = 507)]
public sealed class PhysicsCategoryNames : ScriptableObject, ITagNames
{
PhysicsCategoryNames() {}
void OnValidate()
{
if (m_CategoryNames.Length != 32)
Array.Resize(ref m_CategoryNames, 32);
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsCategoryTags.cs
using System;
using Unity.Mathematics;
namespace Unity.Physics.Authoring
{
[Serializable]
public struct PhysicsCategoryTags : IEquatable<PhysicsCategoryTags>
{
public static PhysicsCategoryTags Everything => new PhysicsCategoryTags { Value = unchecked((uint)~0) };
public static PhysicsCategoryTags Nothing => new PhysicsCategoryTags { Value = 0 };
public bool Category00;
public bool Category01;
public bool Category02;
public bool Category03;
public bool Category04;
public bool Category05;
public bool Category06;
public bool Category07;
public bool Category08;
public bool Category09;
public bool Category10;
public bool Category11;
public bool Category12;
public bool Category13;
public bool Category14;
public bool Category15;
public bool Category16;
public bool Category17;
public bool Category18;
public bool Category19;
public bool Category20;
public bool Category21;
public bool Category22;
public bool Category23;
public bool Category24;
public bool Category25;
public bool Category26;
public bool Category27;
public bool Category28;
public bool Category29;
public bool Category30;
public bool Category31;
internal bool this[int i]
{
get
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 31), nameof(i));
switch (i)
{
case 0: return Category00;
case 1: return Category01;
case 2: return Category02;
case 3: return Category03;
case 4: return Category04;
case 5: return Category05;
case 6: return Category06;
case 7: return Category07;
case 8: return Category08;
case 9: return Category09;
case 10: return Category10;
case 11: return Category11;
case 12: return Category12;
case 13: return Category13;
case 14: return Category14;
case 15: return Category15;
case 16: return Category16;
case 17: return Category17;
case 18: return Category18;
case 19: return Category19;
case 20: return Category20;
case 21: return Category21;
case 22: return Category22;
case 23: return Category23;
case 24: return Category24;
case 25: return Category25;
case 26: return Category26;
case 27: return Category27;
case 28: return Category28;
case 29: return Category29;
case 30: return Category30;
case 31: return Category31;
default: return default;
}
}
set
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 31), nameof(i));
switch (i)
{
case 0: Category00 = value; break;
case 1: Category01 = value; break;
case 2: Category02 = value; break;
case 3: Category03 = value; break;
case 4: Category04 = value; break;
case 5: Category05 = value; break;
case 6: Category06 = value; break;
case 7: Category07 = value; break;
case 8: Category08 = value; break;
case 9: Category09 = value; break;
case 10: Category10 = value; break;
case 11: Category11 = value; break;
case 12: Category12 = value; break;
case 13: Category13 = value; break;
case 14: Category14 = value; break;
case 15: Category15 = value; break;
case 16: Category16 = value; break;
case 17: Category17 = value; break;
case 18: Category18 = value; break;
case 19: Category19 = value; break;
case 20: Category20 = value; break;
case 21: Category21 = value; break;
case 22: Category22 = value; break;
case 23: Category23 = value; break;
case 24: Category24 = value; break;
case 25: Category25 = value; break;
case 26: Category26 = value; break;
case 27: Category27 = value; break;
case 28: Category28 = value; break;
case 29: Category29 = value; break;
case 30: Category30 = value; break;
case 31: Category31 = value; break;
}
}
}
public uint Value
{
get
{
var result = 0;
result |= (Category00 ? 1 : 0) << 0;
result |= (Category01 ? 1 : 0) << 1;
result |= (Category02 ? 1 : 0) << 2;
result |= (Category03 ? 1 : 0) << 3;
result |= (Category04 ? 1 : 0) << 4;
result |= (Category05 ? 1 : 0) << 5;
result |= (Category06 ? 1 : 0) << 6;
result |= (Category07 ? 1 : 0) << 7;
result |= (Category08 ? 1 : 0) << 8;
result |= (Category09 ? 1 : 0) << 9;
result |= (Category10 ? 1 : 0) << 10;
result |= (Category11 ? 1 : 0) << 11;
result |= (Category12 ? 1 : 0) << 12;
result |= (Category13 ? 1 : 0) << 13;
result |= (Category14 ? 1 : 0) << 14;
result |= (Category15 ? 1 : 0) << 15;
result |= (Category16 ? 1 : 0) << 16;
result |= (Category17 ? 1 : 0) << 17;
result |= (Category18 ? 1 : 0) << 18;
result |= (Category19 ? 1 : 0) << 19;
result |= (Category20 ? 1 : 0) << 20;
result |= (Category21 ? 1 : 0) << 21;
result |= (Category22 ? 1 : 0) << 22;
result |= (Category23 ? 1 : 0) << 23;
result |= (Category24 ? 1 : 0) << 24;
result |= (Category25 ? 1 : 0) << 25;
result |= (Category26 ? 1 : 0) << 26;
result |= (Category27 ? 1 : 0) << 27;
result |= (Category28 ? 1 : 0) << 28;
result |= (Category29 ? 1 : 0) << 29;
result |= (Category30 ? 1 : 0) << 30;
result |= (Category31 ? 1 : 0) << 31;
return unchecked((uint)result);
}
set
{
Category00 = (value & (1 << 0)) != 0;
Category01 = (value & (1 << 1)) != 0;
Category02 = (value & (1 << 2)) != 0;
Category03 = (value & (1 << 3)) != 0;
Category04 = (value & (1 << 4)) != 0;
Category05 = (value & (1 << 5)) != 0;
Category06 = (value & (1 << 6)) != 0;
Category07 = (value & (1 << 7)) != 0;
Category08 = (value & (1 << 8)) != 0;
Category09 = (value & (1 << 9)) != 0;
Category10 = (value & (1 << 10)) != 0;
Category11 = (value & (1 << 11)) != 0;
Category12 = (value & (1 << 12)) != 0;
Category13 = (value & (1 << 13)) != 0;
Category14 = (value & (1 << 14)) != 0;
Category15 = (value & (1 << 15)) != 0;
Category16 = (value & (1 << 16)) != 0;
Category17 = (value & (1 << 17)) != 0;
Category18 = (value & (1 << 18)) != 0;
Category19 = (value & (1 << 19)) != 0;
Category20 = (value & (1 << 20)) != 0;
Category21 = (value & (1 << 21)) != 0;
Category22 = (value & (1 << 22)) != 0;
Category23 = (value & (1 << 23)) != 0;
Category24 = (value & (1 << 24)) != 0;
Category25 = (value & (1 << 25)) != 0;
Category26 = (value & (1 << 26)) != 0;
Category27 = (value & (1 << 27)) != 0;
Category28 = (value & (1 << 28)) != 0;
Category29 = (value & (1 << 29)) != 0;
Category30 = (value & (1 << 30)) != 0;
Category31 = (value & (1 << 31)) != 0;
}
}
public bool Equals(PhysicsCategoryTags other) => Value == other.Value;
public override bool Equals(object obj) => obj is PhysicsCategoryTags other && Equals(other);
[Serializable]
public struct PhysicsMaterialCoefficient
{
[SoftRange(0f, 1f, TextFieldMax = float.MaxValue)]
public float Value;
public Material.CombinePolicy CombineMode;
}
public T Value
{
get => m_Value;
set
{
m_Value = value;
Override = true;
}
}
[SerializeField]
T m_Value;
[Serializable]
class OverridableCollisionResponse : OverridableValue<CollisionResponsePolicy> {}
[Serializable]
class OverridableMaterialCoefficient : OverridableValue<PhysicsMaterialCoefficient>
{
protected override void OnValidate(ref PhysicsMaterialCoefficient value) =>
value.Value = math.max(0f, value.Value);
}
[Serializable]
class OverridableCategoryTags : OverridableValue<PhysicsCategoryTags> {}
[Serializable]
class OverridableCustomMaterialTags : OverridableValue<CustomPhysicsMaterialTags> {}
[Serializable]
class PhysicsMaterialProperties : IInheritPhysicsMaterialProperties, ISerializationCallbackReceiver
{
public PhysicsMaterialProperties(bool supportsTemplate) => m_SupportsTemplate = supportsTemplate;
[SerializeField, HideInInspector]
bool m_SupportsTemplate;
public bool OverrideCollisionResponse { get => m_CollisionResponse.Override; set => m_CollisionResponse.Override = value; }
public CollisionResponsePolicy CollisionResponse
{
get => Get(m_CollisionResponse, m_Template == null ? null : m_Template?.CollisionResponse);
set => m_CollisionResponse.Value = value;
}
[SerializeField]
OverridableCollisionResponse m_CollisionResponse = new OverridableCollisionResponse
{
Value = CollisionResponsePolicy.Collide,
Override = false
};
public bool OverrideFriction { get => m_Friction.Override; set => m_Friction.Override = value; }
public PhysicsMaterialCoefficient Friction
{
get => Get(m_Friction, m_Template == null ? null : m_Template?.Friction);
set => m_Friction.Value = value;
}
[SerializeField]
OverridableMaterialCoefficient m_Friction = new OverridableMaterialCoefficient
{
Value = new PhysicsMaterialCoefficient { Value = 0.5f, CombineMode = Material.CombinePolicy.GeometricMean },
Override = false
};
public bool OverrideRestitution { get => m_Restitution.Override; set => m_Restitution.Override = value; }
public PhysicsMaterialCoefficient Restitution
{
get => Get(m_Restitution, m_Template == null ? null : m_Template?.Restitution);
set => m_Restitution.Value = value;
}
[SerializeField]
OverridableMaterialCoefficient m_Restitution = new OverridableMaterialCoefficient
{
Value = new PhysicsMaterialCoefficient { Value = 0f, CombineMode = Material.CombinePolicy.Maximum },
Override = false
};
public bool OverrideBelongsTo { get => m_BelongsToCategories.Override; set => m_BelongsToCategories.Override = value; }
public PhysicsCategoryTags BelongsTo
{
get => Get(m_BelongsToCategories, m_Template == null ? null : m_Template?.BelongsTo);
set => m_BelongsToCategories.Value = value;
}
[SerializeField]
OverridableCategoryTags m_BelongsToCategories =
new OverridableCategoryTags { Value = PhysicsCategoryTags.Everything, Override = false };
public bool OverrideCollidesWith { get => m_CollidesWithCategories.Override; set => m_CollidesWithCategories.Override = value; }
public PhysicsCategoryTags CollidesWith
{
get => Get(m_CollidesWithCategories, m_Template == null ? null : m_Template?.CollidesWith);
set => m_CollidesWithCategories.Value = value;
}
[SerializeField]
OverridableCategoryTags m_CollidesWithCategories =
new OverridableCategoryTags { Value = PhysicsCategoryTags.Everything, Override = false };
public bool OverrideCustomTags { get => m_CustomMaterialTags.Override; set => m_CustomMaterialTags.Override = value; }
public CustomPhysicsMaterialTags CustomTags
{
get => Get(m_CustomMaterialTags, m_Template == null ? null : m_Template?.CustomTags);
set => m_CustomMaterialTags.Value = value;
}
[SerializeField]
OverridableCustomMaterialTags m_CustomMaterialTags =
new OverridableCustomMaterialTags { Value = default, Override = false };
material.m_SupportsTemplate = supportsTemplate;
if (!supportsTemplate)
{
material.m_Template = null;
material.m_CollisionResponse.Override = true;
material.m_Friction.Override = true;
material.m_Restitution.Override = true;
}
material.m_Friction.OnValidate();
material.m_Restitution.OnValidate();
}
[SerializeField]
int m_SerializedVersion = 0;
void ISerializationCallbackReceiver.OnBeforeSerialize() {}
void ISerializationCallbackReceiver.OnAfterDeserialize() => UpgradeVersionIfNecessary();
namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Physics Material Template", fileName = "Physics Material Template", order = 508)]
public sealed class PhysicsMaterialTemplate : ScriptableObject, IPhysicsMaterialProperties
{
PhysicsMaterialTemplate() {}
public CollisionResponsePolicy CollisionResponse { get => m_Value.CollisionResponse; set => m_Value.CollisionResponse = value; }
public PhysicsMaterialCoefficient Friction { get => m_Value.Friction; set => m_Value.Friction = value; }
public PhysicsMaterialCoefficient Restitution { get => m_Value.Restitution; set => m_Value.Restitution = value; }
public PhysicsCategoryTags BelongsTo { get => m_Value.BelongsTo; set => m_Value.BelongsTo = value; }
public PhysicsCategoryTags CollidesWith { get => m_Value.CollidesWith; set => m_Value.CollidesWith = value; }
public CustomPhysicsMaterialTags CustomTags { get => m_Value.CustomTags; set => m_Value.CustomTags = value; }
[SerializeField]
PhysicsMaterialProperties m_Value = new PhysicsMaterialProperties(false);
PhysicsBodyAuthoring() {}
public BodyMotionType MotionType { get => m_MotionType; set => m_MotionType = value; }
[SerializeField]
[Tooltip("Specifies whether the body should be fully physically simulated, moved directly, or fixed in place.")]
BodyMotionType m_MotionType;
public BodySmoothing Smoothing { get => m_Smoothing; set => m_Smoothing = value; }
[SerializeField]
[Tooltip("Specifies how this body's motion in its graphics representation should be smoothed when the rendering framerate is greater than the fixed step rate used by physics.")]
BodySmoothing m_Smoothing = BodySmoothing.None;
const float k_MinimumMass = 0.001f;
public float Mass
{
get => m_MotionType == BodyMotionType.Dynamic ? m_Mass : float.PositiveInfinity;
set => m_Mass = math.max(k_MinimumMass, value);
}
[SerializeField]
float m_Mass = 1.0f;
public float LinearDamping { get => m_LinearDamping; set => m_LinearDamping = math.max(0f, value); }
[SerializeField]
[Tooltip("This is applied to a body's linear velocity reducing it over time.")]
float m_LinearDamping = 0.01f;
public float AngularDamping { get => m_AngularDamping; set => m_AngularDamping = math.max(0f, value); }
[SerializeField]
[Tooltip("This is applied to a body's angular velocity reducing it over time.")]
float m_AngularDamping = 0.05f;
public float3 InitialLinearVelocity { get => m_InitialLinearVelocity; set => m_InitialLinearVelocity = value; }
[SerializeField]
[Tooltip("The initial linear velocity of the body in world space")]
float3 m_InitialLinearVelocity = float3.zero;
public float3 InitialAngularVelocity { get => m_InitialAngularVelocity; set => m_InitialAngularVelocity = value; }
[SerializeField]
[Tooltip("This represents the initial rotation speed around each axis in the local motion space of the body i.e. around the center of mass")]
float3 m_InitialAngularVelocity = float3.zero;
public float GravityFactor
{
get => m_MotionType == BodyMotionType.Dynamic ? m_GravityFactor : 0f;
set => m_GravityFactor = value;
}
[SerializeField]
[Tooltip("Scales the amount of gravity to apply to this body.")]
float m_GravityFactor = 1f;
public bool OverrideDefaultMassDistribution
{
#pragma warning disable 618
get => m_OverrideDefaultMassDistribution;
set => m_OverrideDefaultMassDistribution = value;
#pragma warning restore 618
}
[SerializeField]
[Tooltip("Default mass distribution is based on the shapes associated with this body.")]
bool m_OverrideDefaultMassDistribution;
public MassDistribution CustomMassDistribution
{
get => new MassDistribution
{
Transform = new RigidTransform(m_Orientation, m_CenterOfMass),
InertiaTensor =
m_MotionType == BodyMotionType.Dynamic ? m_InertiaTensor : new float3(float.PositiveInfinity)
};
set
{
m_CenterOfMass = value.Transform.pos;
m_Orientation.SetValue(value.Transform.rot);
m_InertiaTensor = value.InertiaTensor;
#pragma warning disable 618
m_OverrideDefaultMassDistribution = true;
#pragma warning restore 618
}
}
[SerializeField]
float3 m_CenterOfMass;
[SerializeField]
EulerAngles m_Orientation = EulerAngles.Default;
[SerializeField]
// Default value to solid unit sphere : https://en.wikipedia.org/wiki/List_of_moments_of_inertia
float3 m_InertiaTensor = new float3(2f / 5f);
public uint WorldIndex { get => m_WorldIndex; set => m_WorldIndex = value; }
[SerializeField]
[Tooltip("The index of the physics world this body belongs to. Default physics world has index 0.")]
uint m_WorldIndex = 0;
public CustomPhysicsBodyTags CustomTags { get => m_CustomTags; set => m_CustomTags = value; }
[SerializeField]
CustomPhysicsBodyTags m_CustomTags = CustomPhysicsBodyTags.Nothing;
void OnEnable()
{
// included so tick box appears in Editor
}
void OnValidate()
{
m_Mass = math.max(k_MinimumMass, m_Mass);
m_LinearDamping = math.max(m_LinearDamping, 0f);
m_AngularDamping = math.max(m_AngularDamping, 0f);
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/PhysicsShapeAuthoring.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using UnityMesh = UnityEngine.Mesh;
namespace Unity.Physics.Authoring
{
public sealed class UnimplementedShapeException : NotImplementedException
{
public UnimplementedShapeException(ShapeType shapeType)
: base($"Unknown shape type {shapeType} requires explicit implementation") {}
}
#if UNITY_2021_2_OR_NEWER
[Icon(k_IconPath)]
#endif
[AddComponentMenu("Entities/Physics/Physics Shape")]
public sealed class PhysicsShapeAuthoring : MonoBehaviour, IInheritPhysicsMaterialProperties, ISerializationCallbackReceiver
{
const string k_IconPath = "Packages/com.unity.physics/Unity.Physics.Editor/Editor Default Resources/Icons/d_BoxCollider@64.png";
PhysicsShapeAuthoring() {}
[Serializable]
struct CylindricalProperties
{
public float Height;
public float Radius;
[HideInInspector]
public int Axis;
}
[SerializeField]
[ExpandChildren]
CylindricalProperties m_Capsule = new CylindricalProperties { Height = 1f, Radius = 0.5f, Axis = 2 };
[SerializeField]
[ExpandChildren]
CylindricalProperties m_Cylinder = new CylindricalProperties { Height = 1f, Radius = 0.5f, Axis = 2 };
[SerializeField]
[Tooltip("How many sides the convex cylinder shape should have.")]
[Range(CylinderGeometry.MinSideCount, CylinderGeometry.MaxSideCount)]
int m_CylinderSideCount = 20;
[SerializeField]
float m_SphereRadius = 0.5f;
public ConvexHullGenerationParameters ConvexHullGenerationParameters => m_ConvexHullGenerationParameters;
[SerializeField]
[Tooltip(
"Specifies the minimum weight of a skinned vertex assigned to this shape and/or its transform children required for it to be included for automatic detection. " +
"A value of 0 will include all points with any weight assigned to this shape's hierarchy."
)]
[Range(0f, 1f)]
float m_MinimumSkinnedVertexWeight = 0.1f;
[SerializeField]
[ExpandChildren]
ConvexHullGenerationParameters m_ConvexHullGenerationParameters = ConvexHullGenerationParameters.Default.ToAuthoring();
// TODO: remove this accessor in favor of GetRawVertices() when blob data is serializable
internal UnityMesh CustomMesh => m_CustomMesh;
[SerializeField]
[Tooltip("If no custom mesh is specified, then one will be generated using this body's rendered meshes.")]
UnityMesh m_CustomMesh;
public bool ForceUnique { get => m_ForceUnique; set => m_ForceUnique = value; }
[SerializeField]
bool m_ForceUnique;
public PhysicsMaterialTemplate MaterialTemplate { get => m_Material.Template; set => m_Material.Template = value; }
PhysicsMaterialTemplate IInheritPhysicsMaterialProperties.Template
{
get => m_Material.Template;
set => m_Material.Template = value;
}
public bool OverrideCollisionResponse { get => m_Material.OverrideCollisionResponse; set => m_Material.OverrideCollisionResponse = value; }
public CollisionResponsePolicy CollisionResponse { get => m_Material.CollisionResponse; set => m_Material.CollisionResponse = value; }
public bool OverrideFriction { get => m_Material.OverrideFriction; set => m_Material.OverrideFriction = value; }
public PhysicsMaterialCoefficient Friction { get => m_Material.Friction; set => m_Material.Friction = value; }
public bool OverrideRestitution
{
get => m_Material.OverrideRestitution;
set => m_Material.OverrideRestitution = value;
}
public PhysicsMaterialCoefficient Restitution
{
get => m_Material.Restitution;
set => m_Material.Restitution = value;
}
public bool OverrideBelongsTo
{
get => m_Material.OverrideBelongsTo;
set => m_Material.OverrideBelongsTo = value;
}
void AppendMeshPropertiesToNativeBuffers(
float4x4 localToWorld, UnityMesh mesh, NativeList<float3> vertices, NativeList<int3> triangles, bool validate,
NativeList<HashableShapeInputs> inputs, HashSet<UnityMesh> meshAssets
)
{
if (mesh == null || validate && !mesh.IsValidForConversion(gameObject))
return;
var childToShape = math.mul(transform.worldToLocalMatrix, localToWorld);
AppendMeshPropertiesToNativeBuffers(childToShape, mesh, vertices, triangles, inputs, meshAssets);
}
internal static void AppendMeshPropertiesToNativeBuffers(
float4x4 childToShape, UnityMesh mesh, NativeList<float3> vertices, NativeList<int3> triangles,
NativeList<HashableShapeInputs> inputs, HashSet<UnityMesh> meshAssets
)
{
var offset = 0u;
#if UNITY_EDITOR
// TODO: when min spec is 2020.1, collect all meshes and their data via single Burst job rather than one at a time
using (var meshData = UnityEditor.MeshUtility.AcquireReadOnlyMeshData(mesh))
#else
using (var meshData = UnityMesh.AcquireReadOnlyMeshData(mesh))
#endif
{
if (vertices.IsCreated)
{
offset = (uint)vertices.Length;
var tmpVertices = new NativeArray<Vector3>(meshData[0].vertexCount, Allocator.Temp);
meshData[0].GetVertices(tmpVertices);
if (vertices.Capacity < vertices.Length + tmpVertices.Length)
vertices.Capacity = vertices.Length + tmpVertices.Length;
foreach (var v in tmpVertices)
vertices.Add(math.mul(childToShape, new float4(v, 1f)).xyz);
}
if (triangles.IsCreated)
{
switch (meshData[0].indexFormat)
{
case IndexFormat.UInt16:
var indices16 = meshData[0].GetIndexData<ushort>();
var numTriangles = indices16.Length / 3;
if (triangles.Capacity < triangles.Length + numTriangles)
triangles.Capacity = triangles.Length + numTriangles;
for (var sm = 0; sm < meshData[0].subMeshCount; ++sm)
{
var subMesh = meshData[0].GetSubMesh(sm);
for (int i = subMesh.indexStart, count = 0; count < subMesh.indexCount; i += 3, count += 3)
triangles.Add((int3) new uint3(offset + indices16[i], offset + indices16[i + 1], offset + indices16[i + 2]));
}
break;
case IndexFormat.UInt32:
var indices32 = meshData[0].GetIndexData<uint>();
numTriangles = indices32.Length / 3;
if (triangles.Capacity < triangles.Length + numTriangles)
triangles.Capacity = triangles.Length + numTriangles;
for (var sm = 0; sm < meshData[0].subMeshCount; ++sm)
{
var subMesh = meshData[0].GetSubMesh(sm);
for (int i = subMesh.indexStart, count = 0; count < subMesh.indexCount; i += 3, count += 3)
triangles.Add((int3) new uint3(offset + indices32[i], offset + indices32[i + 1], offset + indices32[i + 2]));
}
break;
}
}
}
if (inputs.IsCreated)
inputs.Add(HashableShapeInputs.FromMesh(mesh, childToShape));
meshAssets?.Add(mesh);
}
void UpdateCapsuleAxis()
{
var cmax = math.cmax(m_PrimitiveSize);
var cmin = math.cmin(m_PrimitiveSize);
if (cmin == cmax)
return;
m_Capsule.Axis = m_PrimitiveSize.GetMaxAxis();
}
void UpdateCylinderAxis() => m_Cylinder.Axis = m_PrimitiveSize.GetDeviantAxis();
m_ConvexHullGenerationParameters.BevelRadius = geometry.BevelRadius;
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetSphere(SphereGeometry geometry, quaternion orientation)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(orientation);
SetSphere(geometry, euler);
}
internal void SetSphere(SphereGeometry geometry)
{
SetSphere(geometry, m_PrimitiveOrientation);
}
internal void SetSphere(SphereGeometry geometry, EulerAngles orientation)
{
m_ShapeType = ShapeType.Sphere;
m_PrimitiveCenter = geometry.Center;
var radius = math.max(0f, geometry.Radius);
m_PrimitiveSize = new float3(2f * radius, 2f * radius, 2f * radius);
m_PrimitiveOrientation = orientation;
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetPlane(float3 center, float2 size, quaternion orientation)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(orientation);
SetPlane(center, size, euler);
}
internal void SetPlane(float3 center, float2 size, EulerAngles orientation)
{
m_ShapeType = ShapeType.Plane;
m_PrimitiveCenter = center;
m_PrimitiveOrientation = orientation;
m_PrimitiveSize = new float3(size.x, 0f, size.y);
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetConvexHull(
ConvexHullGenerationParameters hullGenerationParameters, float minimumSkinnedVertexWeight
)
{
m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
SetConvexHull(hullGenerationParameters);
}
public void SetConvexHull(ConvexHullGenerationParameters hullGenerationParameters, UnityMesh customMesh = null)
{
m_ShapeType = ShapeType.ConvexHull;
m_CustomMesh = customMesh;
hullGenerationParameters.OnValidate();
m_ConvexHullGenerationParameters = hullGenerationParameters;
}
public void SetMesh(UnityMesh mesh = null)
{
m_ShapeType = ShapeType.Mesh;
m_CustomMesh = mesh;
}
void OnEnable()
{
// included so tick box appears in Editor
}
const int k_LatestVersion = 1;
[SerializeField]
int m_SerializedVersion = 0;
void ISerializationCallbackReceiver.OnBeforeSerialize() {}
void ISerializationCallbackReceiver.OnAfterDeserialize() => UpgradeVersionIfNecessary();
#pragma warning disable 618
void UpgradeVersionIfNecessary()
{
if (m_SerializedVersion < k_LatestVersion)
{
// old data from version < 1 have been removed
if (m_SerializedVersion < 1)
m_SerializedVersion = 1;
}
}
#pragma warning restore 618
static void Validate(ref CylindricalProperties props)
{
props.Height = math.max(0f, props.Height);
props.Radius = math.max(0f, props.Radius);
}
void OnValidate()
{
UpgradeVersionIfNecessary();
m_PrimitiveSize = math.max(m_PrimitiveSize, new float3());
Validate(ref m_Capsule);
Validate(ref m_Cylinder);
switch (m_ShapeType)
{
case ShapeType.Box:
SetBox(GetBoxProperties(out var orientation), orientation);
break;
case ShapeType.Capsule:
SetCapsule(GetCapsuleProperties());
break;
case ShapeType.Cylinder:
SetCylinder(GetCylinderProperties(out orientation), orientation);
break;
case ShapeType.Sphere:
SetSphere(GetSphereProperties(out orientation), orientation);
break;
case ShapeType.Plane:
GetPlaneProperties(out var center, out var size2D, out orientation);
SetPlane(center, size2D, orientation);
break;
case ShapeType.ConvexHull:
case ShapeType.Mesh:
break;
default:
throw new UnimplementedShapeException(m_ShapeType);
}
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
m_CylinderSideCount =
math.clamp(m_CylinderSideCount, CylinderGeometry.MinSideCount, CylinderGeometry.MaxSideCount);
m_ConvexHullGenerationParameters.OnValidate();
PhysicsMaterialProperties.OnValidate(ref m_Material, true);
}
// matrix to transform point from shape space into world space
public float4x4 GetShapeToWorldMatrix() =>
new float4x4(Math.DecomposeRigidBodyTransform(transform.localToWorldMatrix));
// matrix to transform point from object's local transform matrix into shape space
internal float4x4 GetLocalToShapeMatrix() =>
math.mul(math.inverse(GetShapeToWorldMatrix()), transform.localToWorldMatrix);
internal unsafe void BakePoints(NativeArray<float3> points)
{
var localToShapeQuantized = GetLocalToShapeMatrix();
using (var aabb = new NativeArray<Aabb>(1, Allocator.TempJob))
{
new PhysicsShapeExtensions.GetAabbJob { Points = points, Aabb = aabb }.Run();
HashableShapeInputs.GetQuantizedTransformations(localToShapeQuantized, aabb[0], out localToShapeQuantized);
}
using (var bakedPoints = new NativeArray<float3>(points.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory))
{
new BakePointsJob
{
Points = points,
LocalToShape = localToShapeQuantized,
Output = bakedPoints
}.Schedule(points.Length, 16).Complete();
UnsafeUtility.MemCpy(points.GetUnsafePtr(), bakedPoints.GetUnsafePtr(), points.Length * UnsafeUtility.SizeOf<float3>());
}
}
[BurstCompile]
struct BakePointsJob : IJobParallelFor
{
[Collections.ReadOnly]
public NativeArray<float3> Points;
public float4x4 LocalToShape;
public NativeArray<float3> Output;
public void Execute(int index) => Output[index] = math.mul(LocalToShape, new float4(Points[index], 1f)).xyz;
}
/// <summary>
/// Fit this shape to render geometry in its GameObject hierarchy. Children in the hierarchy will
/// influence the result if they have enabled MeshRenderer components or have vertices bound to
/// them on a SkinnedMeshRenderer. Children will only count as influences if this shape is the
/// first ancestor shape in their hierarchy. As such, you should add shape components to all
/// GameObjects that should have them before you call this method on any of them.
/// </summary>
///
/// <exception cref="UnimplementedShapeException"> Thrown when an Unimplemented Shape error
/// condition occurs. </exception>
///
/// <param name="minimumSkinnedVertexWeight"> (Optional)
/// The minimum total weight that a vertex in a skinned mesh must have assigned to this object
/// and/or any of its influencing children. </param>
public void FitToEnabledRenderMeshes(float minimumSkinnedVertexWeight = 0f)
{
var shapeType = m_ShapeType;
m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
using (var points = new NativeList<float3>(65535, Allocator.Persistent))
{
// temporarily un-assign custom mesh and assume this shape is a convex hull
var customMesh = m_CustomMesh;
m_CustomMesh = null;
GetConvexHullProperties(points, Application.isPlaying, default, default, default, default);
m_CustomMesh = customMesh;
if (points.Length == 0)
return;
// TODO: find best rotation, particularly if any points came from skinned mesh
var orientation = quaternion.identity;
var bounds = new Bounds(points[0], float3.zero);
for (int i = 1, count = points.Length; i < count; ++i)
bounds.Encapsulate(points[i]);
SetBox(
new BoxGeometry
{
Center = bounds.center,
Size = bounds.size,
Orientation = orientation,
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius
}
);
}
switch (shapeType)
{
case ShapeType.Capsule:
SetCapsule(GetCapsuleProperties());
break;
case ShapeType.Cylinder:
SetCylinder(GetCylinderProperties(out var orientation), orientation);
break;
case ShapeType.Sphere:
SetSphere(GetSphereProperties(out orientation), orientation);
break;
case ShapeType.Plane:
// force recalculation of plane orientation by making it think shape type is out of date
m_ShapeType = ShapeType.Box;
GetPlaneProperties(out var center, out var size2D, out orientation);
SetPlane(center, size2D, orientation);
break;
case ShapeType.Box:
case ShapeType.ConvexHull:
case ShapeType.Mesh:
m_ShapeType = shapeType;
break;
default:
throw new UnimplementedShapeException(shapeType);
}
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
[Conditional("UNITY_EDITOR")]
void Reset()
{
#if UNITY_EDITOR
InitializeConvexHullGenerationParameters();
FitToEnabledRenderMeshes(m_MinimumSkinnedVertexWeight);
// TODO: also pick best primitive shape
UnityEditor.SceneView.RepaintAll();
#endif
}
public void InitializeConvexHullGenerationParameters()
{
var pointCloud = new NativeList<float3>(65535, Allocator.Temp);
GetConvexHullProperties(pointCloud, false, default, default, default, default);
m_ConvexHullGenerationParameters.InitializeToRecommendedAuthoringValues(pointCloud.AsArray());
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/BakingSystems/PhysicsBodyBakingSystem.cs
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics.GraphicsIntegration;
using UnityEngine;
using LegacyRigidbody = UnityEngine.Rigidbody;
using LegacyCollider = UnityEngine.Collider;
namespace Unity.Physics.Authoring
{
[TemporaryBakingType]
public struct PhysicsBodyAuthoringData : IComponentData
{
public bool IsDynamic;
public float Mass;
public bool OverrideDefaultMassDistribution;
public MassDistribution CustomMassDistribution;
}
class PhysicsBodyAuthoringBaker : BasePhysicsBaker<PhysicsBodyAuthoring>
{
internal List<UnityEngine.Collider> colliderComponents = new List<UnityEngine.Collider>();
internal List<PhysicsShapeAuthoring> physicsShapeComponents = new List<PhysicsShapeAuthoring>();
public override void Bake(PhysicsBodyAuthoring authoring)
{
// Priority is to Legacy Components. Ignore if baked by Legacy.
if (GetComponent<LegacyRigidbody>() || GetComponent<LegacyCollider>())
{
return;
}
var entity = GetEntity(TransformUsageFlags.Dynamic);
// To process later in the Baking System
AddComponent(entity, new PhysicsBodyAuthoringData
{
IsDynamic = (authoring.MotionType == BodyMotionType.Dynamic),
Mass = authoring.Mass,
OverrideDefaultMassDistribution = authoring.OverrideDefaultMassDistribution,
CustomMassDistribution = authoring.CustomMassDistribution
});
AddSharedComponent(entity, new PhysicsWorldIndex(authoring.WorldIndex));
var bodyTransform = GetComponent<Transform>();
var motionType = authoring.MotionType;
var hasSmoothing = authoring.Smoothing != BodySmoothing.None;
PostProcessTransform(bodyTransform, motionType);
var customTags = authoring.CustomTags;
if (!customTags.Equals(CustomPhysicsBodyTags.Nothing))
AddComponent(entity, new PhysicsCustomTags { Value = customTags.Value });
// Check that there is at least one collider in the hierarchy to add these three
GetComponentsInChildren(colliderComponents);
GetComponentsInChildren(physicsShapeComponents);
if (colliderComponents.Count > 0 || physicsShapeComponents.Count > 0)
{
AddComponent(entity, new PhysicsCompoundData()
{
AssociateBlobToBody = false,
ConvertedBodyInstanceID = authoring.GetInstanceID(),
Hash = default,
});
AddComponent<PhysicsRootBaked>(entity);
AddComponent<PhysicsCollider>(entity);
AddBuffer<PhysicsColliderKeyEntityPair>(entity);
}
if (authoring.MotionType == BodyMotionType.Static || IsStatic())
return;
var massProperties = MassProperties.UnitSphere;
AddComponent(entity, authoring.MotionType == BodyMotionType.Dynamic ?
PhysicsMass.CreateDynamic(massProperties, authoring.Mass) :
PhysicsMass.CreateKinematic(massProperties));
var physicsVelocity = new PhysicsVelocity
{
Linear = authoring.InitialLinearVelocity,
Angular = authoring.InitialAngularVelocity
};
AddComponent(entity, physicsVelocity);
if (authoring.MotionType == BodyMotionType.Dynamic)
{
// TODO make these optional in editor?
AddComponent(entity, new PhysicsDamping
{
Linear = authoring.LinearDamping,
Angular = authoring.AngularDamping
});
if (authoring.GravityFactor != 1)
{
AddComponent(entity, new PhysicsGravityFactor
{
Value = authoring.GravityFactor
});
}
}
else if (authoring.MotionType == BodyMotionType.Kinematic)
{
AddComponent(entity, new PhysicsGravityFactor
{
Value = 0
});
}
if (hasSmoothing)
{
AddComponent(entity, new PhysicsGraphicalSmoothing());
if (authoring.Smoothing == BodySmoothing.Interpolation)
{
AddComponent(entity, new PhysicsGraphicalInterpolationBuffer
{
PreviousTransform = Math.DecomposeRigidBodyTransform(bodyTransform.localToWorldMatrix),
PreviousVelocity = physicsVelocity,
});
}
}
}
}
[RequireMatchingQueriesForUpdate]
[UpdateAfter(typeof(EndColliderBakingSystem))]
[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
public partial class PhysicsBodyBakingSystem : SystemBase
{
protected override void OnUpdate()
{
// Fill in the MassProperties based on the potential calculated value by BuildCompoundColliderBakingSystem
foreach (var(physicsMass, bodyData, collider) in
SystemAPI.Query<RefRW<PhysicsMass>, RefRO<PhysicsBodyAuthoringData>, RefRO<PhysicsCollider>>().WithOptions(EntityQueryOptions.IncludePrefab | EntityQueryOptions
{
// Build mass component
var massProperties = collider.ValueRO.MassProperties;
if (bodyData.ValueRO.OverrideDefaultMassDistribution)
{
massProperties.MassDistribution = bodyData.ValueRO.CustomMassDistribution;
// Increase the angular expansion factor to account for the shift in center of mass
massProperties.AngularExpansionFactor += math.length(massProperties.MassDistribution.Transform.pos - bodyData.ValueRO.CustomMassDistribution.Transform.pos);
}
physicsMass.ValueRW = bodyData.ValueRO.IsDynamic ?
PhysicsMass.CreateDynamic(massProperties, bodyData.ValueRO.Mass) :
PhysicsMass.CreateKinematic(massProperties);
}
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/BakingSystems/PhysicsShapeBakingSystem.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Profiling;
using LegacyCollider = UnityEngine.Collider;
using UnityMesh = UnityEngine.Mesh;
namespace Unity.Physics.Authoring
{
class PhysicsShapeBaker : BaseColliderBaker<PhysicsShapeAuthoring>
{
public static List<PhysicsShapeAuthoring> physicsShapeComponents = new List<PhysicsShapeAuthoring>();
public static List<LegacyCollider> legacyColliderComponents = new List<LegacyCollider>();
bool ShouldConvertShape(PhysicsShapeAuthoring authoring)
{
return authoring.enabled;
}
private GameObject GetPrimaryBody(GameObject shape, out bool hasBodyComponent, out bool isStaticBody)
{
var pb = FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
var rb = FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
hasBodyComponent = (pb != null || rb != null);
isStaticBody = false;
if (pb != null)
{
return rb == null ? pb.gameObject :
pb.transform.IsChildOf(rb.transform) ? pb.gameObject : rb.gameObject;
}
if (rb != null)
return rb.gameObject;
// for implicit static shape, first see if it is part of static optimized hierarchy
isStaticBody = FindTopmostStaticEnabledAncestor(shape, out var topStatic);
if (topStatic != null)
return topStatic;
// otherwise, find topmost enabled Collider or PhysicsShapeAuthoring
var topCollider = FindTopmostEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_CollidersBuffer);
var topShape = FindTopmostEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_ShapesBuffer);
return topCollider == null
? topShape == null ? shape.gameObject : topShape
: topShape == null
? topCollider
: topShape.transform.IsChildOf(topCollider.transform)
? topCollider
: topShape;
}
ShapeComputationDataBaking GetInputDataFromAuthoringComponent(PhysicsShapeAuthoring shape, Entity colliderEntity)
{
GameObject shapeGameObject = shape.gameObject;
var body = GetPrimaryBody(shapeGameObject, out bool hasBodyComponent, out bool isStaticBody);
var child = shapeGameObject;
var shapeInstanceID = shape.GetInstanceID();
var bodyEntity = GetEntity(body, TransformUsageFlags.Dynamic);
// prepare the static root
if (isStaticBody)
{
var staticRootMarker = CreateAdditionalEntity(TransformUsageFlags.Dynamic, true, "StaticRootBakeMarker");
AddComponent(staticRootMarker, new BakeStaticRoot() { Body = bodyEntity, ConvertedBodyInstanceID = body.transform.GetInstanceID() });
}
// Track dependencies to the transforms
Transform shapeTransform = GetComponent<Transform>(shape);
Transform bodyTransform = GetComponent<Transform>(body);
var instance = new ColliderInstanceBaking
{
AuthoringComponentId = shapeInstanceID,
BodyEntity = bodyEntity,
ShapeEntity = GetEntity(shapeGameObject, TransformUsageFlags.Dynamic),
ChildEntity = GetEntity(child, TransformUsageFlags.Dynamic),
BodyFromShape = ColliderInstanceBaking.GetCompoundFromChild(shapeTransform, bodyTransform),
};
var data = GenerateComputationData(shape, instance, colliderEntity);
data.Instance.ConvertedAuthoringInstanceID = shapeInstanceID;
data.Instance.ConvertedBodyInstanceID = bodyTransform.GetInstanceID();
var rb = FindFirstEnabledAncestor(shapeGameObject, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
var pb = FindFirstEnabledAncestor(shapeGameObject, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
// The LegacyRigidBody cannot know about the ShapeAuthoringComponent. We need to take responsibility of baking the collider.
if (rb || (!rb && !pb) && body == shapeGameObject)
{
GetComponents(physicsShapeComponents);
GetComponents(legacyColliderComponents);
// We need to check that there are no other colliders in the same object, if so, only the first one should do this, otherwise there will be 2 bakers adding this to the enti
// This will be needed to trigger BuildCompoundColliderBakingSystem
// If they are legacy Colliders and PhysicsShapeAuthoring in the same object, the PhysicsShapeAuthoring will add this
if (legacyColliderComponents.Count == 0 && physicsShapeComponents.Count > 0 && physicsShapeComponents[0].GetInstanceID() == shapeInstanceID)
{
var entity = GetEntity(TransformUsageFlags.Dynamic);
// // Rigid Body bakes always add the PhysicsWorldIndex component and process transform
if (!hasBodyComponent)
{
AddSharedComponent(entity, new PhysicsWorldIndex());
PostProcessTransform(bodyTransform);
}
AddComponent(entity, new PhysicsCompoundData()
{
AssociateBlobToBody = false,
ConvertedBodyInstanceID = shapeInstanceID,
Hash = default,
});
AddComponent<PhysicsRootBaked>(entity);
AddComponent<PhysicsCollider>(entity);
AddBuffer<PhysicsColliderKeyEntityPair>(entity);
}
}
return data;
}
Material ProduceMaterial(PhysicsShapeAuthoring shape)
{
var materialTemplate = shape.MaterialTemplate;
if (materialTemplate != null)
DependsOn(materialTemplate);
return shape.GetMaterial();
}
CollisionFilter ProduceCollisionFilter(PhysicsShapeAuthoring shape)
{
return shape.GetFilter();
}
UnityEngine.Mesh GetMesh(PhysicsShapeAuthoring shape, out float4x4 childToShape)
{
var mesh = shape.CustomMesh;
childToShape = float4x4.identity;
if (mesh == null)
{
// Try to get a mesh in the children
var filter = GetComponentInChildren<MeshFilter>();
if (filter != null && filter.sharedMesh != null)
{
mesh = filter.sharedMesh;
var childTransform = GetComponent<Transform>(filter);
childToShape = math.mul(shape.transform.worldToLocalMatrix, childTransform.localToWorldMatrix);;
}
}
if (mesh == null)
{
throw new InvalidOperationException(
$"No {nameof(PhysicsShapeAuthoring.CustomMesh)} assigned on {shape.name}."
);
}
DependsOn(mesh);
return mesh;
}
namespace Unity.Physics.Authoring
{
[BakingType]
public struct JointEntityBaking : IComponentData
{
public Entity Entity;
}
public class BallAndSocketJoint : BaseJoint
{
// Editor only settings
[HideInInspector]
public bool EditPivots;
[Tooltip("If checked, PositionLocal will snap to match PositionInConnectedEntity")]
public bool AutoSetConnected = true;
namespace Unity.Physics.Authoring
{
public abstract class BaseJoint : BaseBodyPairConnector
{
public bool EnableCollision;
public float3 MaxImpulse = float.PositiveInfinity;
void OnEnable()
{
// included so tick box appears in Editor
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/FreeHingeJoint.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
public class FreeHingeJoint : BallAndSocketJoint
{
// Editor only settings
[HideInInspector]
public bool EditAxes;
public float3 HingeAxisLocal;
public float3 HingeAxisInConnectedEntity;
public override void UpdateAuto()
{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
HingeAxisInConnectedEntity = math.mul(bFromA.rot, HingeAxisLocal);
}
}
}
class FreeHingeJointBaker : JointBaker<FreeHingeJoint>
{
public override void Bake(FreeHingeJoint authoring)
{
authoring.UpdateAuto();
Math.CalculatePerpendicularNormalized(authoring.HingeAxisLocal, out var perpendicularLocal, out _);
Math.CalculatePerpendicularNormalized(authoring.HingeAxisInConnectedEntity, out var perpendicularConnected, out _);
var physicsJoint = PhysicsJoint.CreateHinge(
new BodyFrame {Axis = authoring.HingeAxisLocal, Position = authoring.PositionLocal, PerpendicularAxis = perpendicularLocal},
new BodyFrame {Axis = authoring.HingeAxisInConnectedEntity, Position = authoring.PositionInConnectedEntity, PerpendicularAxis = perpendicularConnected }
);
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntity(worldIndex, constraintBodyPair, physicsJoint);
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/LimitDOFJoint.cs
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
// This Joint allows you to lock one or more of the 6 degrees of freedom of a constrained body.
// This is achieved by combining the appropriate lower level 'constraint atoms' to form the higher level Joint.
// In this case Linear and Angular constraint atoms are combined.
// One use-case for this Joint could be to restrict a 3d simulation to a 2d plane.
public class LimitDOFJoint : BaseJoint
{
public bool3 LockLinearAxes;
public bool3 LockAngularAxes;
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
namespace Unity.Physics.Authoring
{
// stores an initial value and a pair of scalar curves to apply to relevant constraints on the joint
struct ModifyJointLimits : ISharedComponentData, IEquatable<ModifyJointLimits>
{
public PhysicsJoint InitialValue;
public ParticleSystem.MinMaxCurve AngularRangeScalar;
public ParticleSystem.MinMaxCurve LinearRangeScalar;
public bool Equals(ModifyJointLimits other) =>
AngularRangeScalar.Equals(other.AngularRangeScalar) && LinearRangeScalar.Equals(other.LinearRangeScalar);
public override bool Equals(object obj) => obj is ModifyJointLimits other && Equals(other);
_ModifyJointLimitsBakingDataQuery.AddChangedVersionFilter(typeof(ModifyJointLimitsBakingData));
_JointEntityBakingQuery.AddChangedVersionFilter(typeof(JointEntityBaking));
}
public void OnUpdate(ref SystemState state)
{
if (_ModifyJointLimitsBakingDataQuery.IsEmpty && _JointEntityBakingQuery.IsEmpty)
{
return;
}
// Collect all the joints
NativeParallelMultiHashMap<Entity, (Entity, PhysicsJoint)> jointsLookUp =
new NativeParallelMultiHashMap<Entity, (Entity, PhysicsJoint)>(10, Allocator.TempJob);
foreach (var(jointEntity, physicsJoint, entity) in SystemAPI
.Query<RefRO<JointEntityBaking>, RefRO<PhysicsJoint>>().WithEntityAccess()
.WithOptions(EntityQueryOptions.IncludeDisabledEntities | EntityQueryOptions.IncludePrefab))
{
jointsLookUp.Add(jointEntity.ValueRO.Entity, (entity, physicsJoint.ValueRO));
}
foreach (var(modifyJointLimits, entity) in SystemAPI.Query<ModifyJointLimitsBakingData>()
.WithEntityAccess().WithOptions(EntityQueryOptions.IncludeDisabledEntities |
EntityQueryOptions.IncludePrefab))
{
var angularModification = new ParticleSystem.MinMaxCurve(
multiplier: math.radians(modifyJointLimits.AngularRangeScalar.curveMultiplier),
min: modifyJointLimits.AngularRangeScalar.curveMin,
max: modifyJointLimits.AngularRangeScalar.curveMax
);
foreach (var joint in jointsLookUp.GetValuesForKey(entity))
{
state.EntityManager.SetSharedComponentManaged(joint.Item1, new ModifyJointLimits
{
InitialValue = joint.Item2,
AngularRangeScalar = angularModification,
LinearRangeScalar = modifyJointLimits.LinearRangeScalar
});
}
}
jointsLookUp.Dispose();
}
}
// apply an animated effect to the limits on supported types of joints
[RequireMatchingQueriesForUpdate]
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
[UpdateAfter(typeof(PhysicsSystemGroup))]
partial struct ModifyJointLimitsSystem : ISystem
{
public void OnUpdate(ref SystemState state)
{
var time = (float)SystemAPI.Time.ElapsedTime;
foreach (var(joint, modification) in SystemAPI.Query<RefRW<PhysicsJoint>, ModifyJointLimits>())
{
var animatedAngularScalar = new FloatRange(
modification.AngularRangeScalar.curveMin.Evaluate(time),
modification.AngularRangeScalar.curveMax.Evaluate(time)
);
var animatedLinearScalar = new FloatRange(
modification.LinearRangeScalar.curveMin.Evaluate(time),
modification.LinearRangeScalar.curveMax.Evaluate(time)
);
// in each case, get relevant properties from the initial value based on joint type, and apply scalar
switch (joint.ValueRW.JointType)
{
// Custom type could be anything, so this demo just applies changes to all constraints
case JointType.Custom:
var constraints = modification.InitialValue.GetConstraints();
for (var i = 0; i < constraints.Length; i++)
{
var constraint = constraints[i];
var isAngular = constraint.Type == ConstraintType.Angular;
var scalar = math.select(animatedLinearScalar, animatedAngularScalar, isAngular);
var constraintRange = (FloatRange)(new float2(constraint.Min, constraint.Max) * scalar);
constraint.Min = constraintRange.Min;
constraint.Max = constraintRange.Max;
constraints[i] = constraint;
}
joint.ValueRW.SetConstraints(constraints);
break;
// other types have corresponding getters/setters to retrieve more meaningful data
case JointType.LimitedDistance:
var distanceRange = modification.InitialValue.GetLimitedDistanceRange();
joint.ValueRW.SetLimitedDistanceRange(distanceRange * (float2)animatedLinearScalar);
break;
case JointType.LimitedHinge:
var angularRange = modification.InitialValue.GetLimitedHingeRange();
joint.ValueRW.SetLimitedHingeRange(angularRange * (float2)animatedAngularScalar);
break;
case JointType.Prismatic:
var distanceOnAxis = modification.InitialValue.GetPrismaticRange();
joint.ValueRW.SetPrismaticRange(distanceOnAxis * (float2)animatedLinearScalar);
break;
// ragdoll joints are composed of two separate joints with different meanings
case JointType.RagdollPrimaryCone:
modification.InitialValue.GetRagdollPrimaryConeAndTwistRange(
out var maxConeAngle,
out var angularTwistRange
);
joint.ValueRW.SetRagdollPrimaryConeAndTwistRange(
maxConeAngle * animatedAngularScalar.Max,
angularTwistRange * (float2)animatedAngularScalar
);
break;
case JointType.RagdollPerpendicularCone:
var angularPlaneRange = modification.InitialValue.GetRagdollPerpendicularConeRange();
joint.ValueRW.SetRagdollPerpendicularConeRange(angularPlaneRange *
(float2)animatedAngularScalar);
break;
// remaining types have no limits on their Constraint atoms to meaningfully modify
case JointType.BallAndSocket:
case JointType.Fixed:
case JointType.Hinge:
break;
}
}
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/PrismaticJoint.cs
using Unity.Entities;
using Unity.Mathematics;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class PrismaticJoint : BallAndSocketJoint
{
public float3 AxisLocal;
public float3 AxisInConnectedEntity;
public float3 PerpendicularAxisLocal;
public float3 PerpendicularAxisInConnectedEntity;
public float MinDistanceOnAxis;
public float MaxDistanceOnAxis;
MinPerpendicularAngle -= 90f;
MaxPerpendicularAngle -= 90f;
m_Version = k_LatestVersion;
}
void OnValidate()
{
UpgradeVersionIfNecessary();
MaxConeAngle = math.clamp(MaxConeAngle, 0f, 180f);
MaxPerpendicularAngle = math.clamp(MaxPerpendicularAngle, -90f, 90f);
MinPerpendicularAngle = math.clamp(MinPerpendicularAngle, -90f, 90f);
if (MaxPerpendicularAngle < MinPerpendicularAngle)
{
var swap = new FloatRange(MinPerpendicularAngle, MaxPerpendicularAngle).Sorted();
MinPerpendicularAngle = swap.Min;
MaxPerpendicularAngle = swap.Max;
}
MinTwistAngle = math.clamp(MinTwistAngle, -180f, 180f);
MaxTwistAngle = math.clamp(MaxTwistAngle, -180f, 180f);
if (MaxTwistAngle < MinTwistAngle)
{
var swap = new FloatRange(MinTwistAngle, MaxTwistAngle).Sorted();
MinTwistAngle = swap.Min;
MaxTwistAngle = swap.Max;
}
}
public override void UpdateAuto()
{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
TwistAxisInConnectedEntity = math.mul(bFromA.rot, TwistAxisLocal);
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
}
}
}
class RagdollJointBaker : JointBaker<RagdollJoint>
{
public override void Bake(RagdollJoint authoring)
{
authoring.UpdateAuto();
authoring.UpgradeVersionIfNecessary();
PhysicsJoint.CreateRagdoll(
new BodyFrame { Axis = authoring.TwistAxisLocal, PerpendicularAxis = authoring.PerpendicularAxisLocal, Position = authoring.PositionLocal },
new BodyFrame { Axis = authoring.TwistAxisInConnectedEntity, PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity, Position = authoring.PositionInConnectedEntit
math.radians(authoring.MaxConeAngle),
math.radians(new FloatRange(authoring.MinPerpendicularAngle, authoring.MaxPerpendicularAngle)),
math.radians(new FloatRange(authoring.MinTwistAngle, authoring.MaxTwistAngle)),
out var primaryCone,
out var perpendicularCone
);
primaryCone.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
perpendicularCone.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
using NativeList<Entity> entities = new NativeList<Entity>(1, Allocator.TempJob);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntities(worldIndex,
constraintBodyPair,
new NativeArray<PhysicsJoint>(2, Allocator.Temp) { [0] = primaryCone, [1] = perpendicularCone }, entities);
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/RigidJoint.cs
using Unity.Entities;
using Unity.Mathematics;
namespace Unity.Physics.Authoring
{
public class RigidJoint : BallAndSocketJoint
{
public quaternion OrientationLocal = quaternion.identity;
public quaternion OrientationInConnectedEntity = quaternion.identity;
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
namespace Unity.Physics.Authoring
{
public class AngularVelocityMotor : BaseJoint
{
[Tooltip("An offset from center of entity with motor. Representing the anchor/pivot point of rotation")]
public float3 PivotPosition;
[Tooltip("The axis of rotation of the motor. Value will be normalized")]
public float3 AxisOfRotation;
[Tooltip("Target speed for the motor to maintain, in degrees/s")]
public float TargetSpeed;
[Tooltip("The magnitude of the maximum impulse the motor can exert in a single step. Applies only to the motor constraint.")]
public float MaxImpulseAppliedByMotor = math.INFINITY;
private float3 PerpendicularAxisLocal;
private float3 PositionInConnectedEntity;
private float3 HingeAxisInConnectedEntity;
private float3 PerpendicularAxisInConnectedEntity;
class AngularVelocityMotorBaker : JointBaker<AngularVelocityMotor>
{
public override void Bake(AngularVelocityMotor authoring)
{
float3 axisInA = math.normalize(authoring.AxisOfRotation);
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.PivotPosition); //position of motored body pivot relative to Connected Entity in world space
authoring.HingeAxisInConnectedEntity = math.mul(bFromA.rot, axisInA); //motor axis in Connected Entity space
// Always calculate the perpendicular axes
Math.CalculatePerpendicularNormalized(axisInA, out var perpendicularLocal, out _);
authoring.PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, perpendicularLocal); //perp motor axis in Connected Entity space
namespace Unity.Physics.Authoring
{
public class PositionMotor : BaseJoint
{
[Tooltip("An offset from the center of the body with the motor, representing the anchor point of translation.")]
public float3 AnchorPosition;
[Tooltip("The direction of the motor, relative to the orientation of the Connected Body (bodyB). Value will be normalized")]
public float3 DirectionOfMovement;
[Tooltip("Motor will drive this length away from the anchor position of bodyA.")]
public float TargetDistance;
[Tooltip("The magnitude of the maximum impulse the motor can exert in a single step. Applies only to the motor constraint.")]
public float MaxImpulseAppliedByMotor = math.INFINITY;
private float3 PerpendicularAxisLocal;
private float3 PositionInConnectedEntity;
private float3 AxisInConnectedEntity;
private float3 PerpendicularAxisInConnectedEntity;
joint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntity(worldIndex, constraintBodyPair, joint);
}
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Motors/RotationalMotor.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
public class RotationalMotor : BaseJoint
{
[Tooltip("An offset from center of entity with motor. Representing the anchor/pivot point of rotation")]
public float3 PivotPosition;
[Tooltip("The axis of rotation of the motor. Value will be normalized")]
public float3 AxisOfRotation;
[Tooltip("Motor will maintain this target angle around the AxisOfRotation, in degrees")]
public float TargetAngle;
[Tooltip("The magnitude of the maximum impulse the motor can exert in one step. Applies only to the motor constraint.")]
public float MaxImpulseAppliedByMotor = math.INFINITY;
private float3 PerpendicularAxisLocal;
private float3 PositionInConnectedEntity;
private float3 HingeAxisInConnectedEntity;
private float3 PerpendicularAxisInConnectedEntity;
class RotationalMotorBaker : JointBaker<RotationalMotor>
{
public override void Bake(RotationalMotor authoring)
{
float3 axisInA = math.normalize(authoring.AxisOfRotation);
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.PivotPosition); //position of motored body pivot relative to Connected Entity in world space
authoring.HingeAxisInConnectedEntity = math.mul(bFromA.rot, axisInA); //motor axis in Connected Entity space
// Always calculate the perpendicular axes
Math.CalculatePerpendicularNormalized(axisInA, out var perpendicularLocal, out _);
authoring.PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, perpendicularLocal); //perp motor axis in Connected Entity space
#region Sphere
[BurstCompile]
struct BakeSphereJob : IJob
{
public NativeArray<SphereGeometry> Sphere;
public NativeArray<EulerAngles> Orientation;
// TODO: make members PascalCase after merging static query fixes
public float4x4 localToWorld;
public float4x4 shapeToWorld;
public void Execute()
{
var center = Sphere[0].Center;
var radius = Sphere[0].Radius;
var orientation = Orientation[0];
var basisToWorld = GetBasisToWorldMatrix(localToWorld, center, orientation, 1f);
var basisPriority = basisToWorld.HasShear() ? GetBasisAxisPriority(basisToWorld) : k_DefaultAxisPriority;
var bakeToShape = GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center, ref orientation, 1f, basisPriority);
radius *= math.cmax(bakeToShape.DecomposeScale());
Sphere[0] = new SphereGeometry
{
Center = center,
Radius = radius
};
Orientation[0] = orientation;
}
}
internal static SphereGeometry BakeToBodySpace(
this SphereGeometry sphere, float4x4 localToWorld, float4x4 shapeToWorld, ref EulerAngles orientation
)
{
using (var geometry = new NativeArray<SphereGeometry>(1, Allocator.TempJob) { [0] = sphere })
using (var outOrientation = new NativeArray<EulerAngles>(1, Allocator.TempJob) { [0] = orientation })
{
var job = new BakeSphereJob
{
Sphere = geometry,
Orientation = outOrientation,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld
};
job.Run();
orientation = outOrientation[0];
return geometry[0];
}
}
#endregion
#region Plane
[BurstCompile]
struct BakePlaneJob : IJob
{
public NativeArray<float3x4> Vertices;
// TODO: make members PascalCase after merging static query fixes
public float3 center;
public float2 size;
public EulerAngles orientation;
public float4x4 localToWorld;
public float4x4 shapeToWorld;
#endregion
#region ShapeInputHash
#if !(UNITY_ANDROID && !UNITY_64) // !Android32
// Getting memory alignment errors from HashUtility.Hash128 on Android32
[BurstCompile]
#endif
internal struct GetShapeInputsHashJob : IJob
{
public NativeArray<Hash128> Result;
#region AABB
[BurstCompile]
internal struct GetAabbJob : IJob
{
[ReadOnly] public NativeArray<float3> Points;
public NativeArray<Aabb> Aabb;
public void Execute()
{
var aabb = new Aabb { Min = float.MaxValue, Max = float.MinValue };
for (var i = 0; i < Points.Length; ++i)
aabb.Include(Points[i]);
Aabb[0] = aabb;
}
}
#endregion
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/BakeGeometryJobsExtensions.cs
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
static partial class PhysicsShapeExtensions
{
public static class BakeBoxJobExtension
{
internal static float4x4 GetBakeToShape(PhysicsShapeAuthoring shape, float3 center, EulerAngles orientation)
{
var transform = shape.transform;
var localToWorld = (float4x4)transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
return BakeBoxJob.GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientation);
}
}
public static class BakeCapsuleJobExtension
{
internal static float4x4 GetBakeToShape(PhysicsShapeAuthoring shape, float3 center, EulerAngles orientation)
{
var transform = shape.transform;
var localToWorld = (float4x4)transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
return BakeCapsuleJob.GetBakeToShape(localToWorld, shapeToWorld, ref center,
ref orientation);
}
}
public static void SetBakedCapsuleSize(this PhysicsShapeAuthoring shape, float height, float radius)
{
var capsule = shape.GetCapsuleProperties();
var center = capsule.Center;
namespace Unity.Physics.Authoring
{
/// <summary>
/// A structure for storing authoring data for a capsule shape. In contrast to the
/// CapsuleGeometry struct in the run-time, this structure permits storing stable orientation
/// values, as well as height values that can be retained when the source data are defined with
/// respect to a non-uniformly scaled object.
/// </summary>
[Serializable]
public struct CapsuleGeometryAuthoring : IEquatable<CapsuleGeometryAuthoring>
{
/// <summary>
/// The local orientation of the capsule. It is aligned with the forward axis (z) when it is
/// identity.
/// </summary>
public quaternion Orientation { get => m_OrientationEuler; set => m_OrientationEuler.SetValue(value); }
internal EulerAngles OrientationEuler { get => m_OrientationEuler; set => m_OrientationEuler = value; }
[SerializeField]
EulerAngles m_OrientationEuler;
/// <summary> The local position offset of the capsule. </summary>
public float3 Center { get => m_Center; set => m_Center = value; }
[SerializeField]
float3 m_Center;
/// <summary>
/// The height of the capsule. It may store any value, but will ultimately always be converted
/// into a value that is at least twice the radius.
/// </summary>
public float Height { get => m_Height; set => m_Height = value; }
[SerializeField]
float m_Height;
namespace Unity.Physics.Authoring
{
public static class ConvexHullGenerationParametersExtensions
{
// recommended simplification tolerance is at least 1 centimeter
internal const float k_MinRecommendedSimplificationTolerance = 0.01f;
if (points.Length <= 1)
return;
internal static void OnValidate(ref this ConvexHullGenerationParameters generationParameters, float maxAngle = 180f)
{
generationParameters.SimplificationTolerance = math.max(0f, generationParameters.SimplificationTolerance);
generationParameters.BevelRadius = math.max(0f, generationParameters.BevelRadius);
generationParameters.MinimumAngle = math.clamp(generationParameters.MinimumAngle, 0f, maxAngle);
}
namespace Unity.Physics.Authoring
{
[Serializable]
internal struct EulerAngles : IEquatable<EulerAngles>
{
public static EulerAngles Default => new EulerAngles { RotationOrder = math.RotationOrder.ZXY };
public float3 Value;
[HideInInspector]
public math.RotationOrder RotationOrder;
internal void SetValue(quaternion value) => Value = math.degrees(Math.ToEulerAngles(value, RotationOrder));
if (m_CheckIfComponentBelongsToShape)
{
if (PhysicsShapeExtensions.GetPrimaryBody(child.gameObject) != m_PrimaryBody)
return false;
child.gameObject.GetComponentsInParent(true, s_PhysicsShapes);
if (s_PhysicsShapes[0] != m_Shape)
{
s_PhysicsShapes.Clear();
return false;
}
}
// do not simply use GameObject.activeInHierarchy because it will be false when instantiating a prefab
var t = child.transform;
var activeInHierarchy = t.gameObject.activeSelf;
while (activeInHierarchy && t != m_Root)
{
t = t.parent;
activeInHierarchy &= t.gameObject.activeSelf;
}
return activeInHierarchy;
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/PhysicsShapeExtensions.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
// put static UnityObject buffers in separate utility class so other methods can Burst compile
static class PhysicsShapeExtensions_NonBursted
{
internal static readonly List<PhysicsBodyAuthoring> s_PhysicsBodiesBuffer = new List<PhysicsBodyAuthoring>(16);
internal static readonly List<PhysicsShapeAuthoring> s_ShapesBuffer = new List<PhysicsShapeAuthoring>(16);
internal static readonly List<Rigidbody> s_RigidbodiesBuffer = new List<Rigidbody>(16);
internal static readonly List<UnityEngine.Collider> s_CollidersBuffer = new List<UnityEngine.Collider>(16);
}
public static partial class PhysicsShapeExtensions
{
// used for de-skewing basis vectors; default priority assumes primary axis is z, secondary axis is y
public static readonly int3 k_DefaultAxisPriority = new int3(2, 1, 0);
// matrix to transform point from shape's local basis into world space
public static float4x4 GetBasisToWorldMatrix(
float4x4 localToWorld, float3 center, quaternion orientation, float3 size
) =>
math.mul(localToWorld, float4x4.TRS(center, orientation, size));
static float4 DeskewSecondaryAxis(float4 primaryAxis, float4 secondaryAxis)
{
var n0 = math.normalizesafe(primaryAxis);
var dot = math.dot(secondaryAxis, n0);
return secondaryAxis - n0 * dot;
}
// priority is determined by length of each size dimension in the shape's basis after applying localToWorld transformation
public static int3 GetBasisAxisPriority(float4x4 basisToWorld)
{
var basisAxisLengths = basisToWorld.DecomposeScale();
var max = math.cmax(basisAxisLengths);
var min = math.cmin(basisAxisLengths);
if (max == min)
return k_DefaultAxisPriority;
basisAxisLengths = basisToWorld.DecomposeScale();
min = math.cmin(basisAxisLengths);
var imin = min == basisAxisLengths.x ? 0 : min == basisAxisLengths.y ? 1 : 2;
if (imin == imax)
imin = k_NextAxis[imax];
var imid = k_NextAxis[imax] == imin ? k_PrevAxis[imax] : k_NextAxis[imax];
[Conditional(CompilationSymbols.CollectionsChecksSymbol), Conditional(CompilationSymbols.DebugChecksSymbol)]
static void CheckBasisPriorityAndThrow(int3 basisPriority)
{
if (
basisPriority.x == basisPriority.y
|| basisPriority.x == basisPriority.z
|| basisPriority.y == basisPriority.z
)
throw new ArgumentException(nameof(basisPriority));
}
public static bool HasNonUniformScale(this float4x4 m)
{
var s = new float3(math.lengthsq(m.c0.xyz), math.lengthsq(m.c1.xyz), math.lengthsq(m.c2.xyz));
return math.cmin(s) != math.cmax(s);
}
// matrix to transform point on a primitive from bake space into space of the shape
internal static float4x4 GetPrimitiveBakeToShapeMatrix(
float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center, ref EulerAngles orientation, float3 scale, int3 basisPriority
)
{
CheckBasisPriorityAndThrow(basisPriority);
var localToBasis = float4x4.TRS(center, orientation, scale);
// correct for imprecision in cases of no scale to prevent e.g., convex radius from being altered
if (scale.Equals(new float3(1f)))
{
localToBasis.c0 = math.normalizesafe(localToBasis.c0);
localToBasis.c1 = math.normalizesafe(localToBasis.c1);
localToBasis.c2 = math.normalizesafe(localToBasis.c2);
}
var localToBake = math.mul(localToWorld, localToBasis);
if (localToBake.HasNonUniformScale() || localToBake.HasShear())
{
// deskew second longest axis with respect to longest axis
localToBake[basisPriority[1]] =
DeskewSecondaryAxis(localToBake[basisPriority[0]], localToBake[basisPriority[1]]);
return bakeToShape;
}
public static void SetBakedCylinderSize(this PhysicsShapeAuthoring shape, float height, float radius, float bevelRadius)
{
var cylinder = shape.GetCylinderProperties(out EulerAngles orientation);
var center = cylinder.Center;
var bakeToShape = BakeCylinderJobExtension.GetBakeToShape(shape, center, orientation);
var scale = bakeToShape.DecomposeScale();
namespace Unity.Physics.Authoring
{
sealed class EnumFlagsAttribute : PropertyAttribute {}
sealed class ExpandChildrenAttribute : PropertyAttribute {}
sealed class SoftRangeAttribute : PropertyAttribute
{
public readonly float SliderMin;
public readonly float SliderMax;
public float TextFieldMin { get; set; }
public float TextFieldMax { get; set; }
[assembly: InternalsVisibleTo("Unity.Physics.Custom.EditModeTests")]
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/BallAndSocketJointEditor.cs
#if UNITY_EDITOR
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(BallAndSocketJoint))]
public class BallAndSocketEditor : UnityEditor.Editor
{
protected virtual void OnSceneGUI()
{
BallAndSocketJoint ballAndSocket = (BallAndSocketJoint)target;
EditorGUI.BeginChangeCheck();
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(CustomPhysicsMaterialTagNames))]
[CanEditMultipleObjects]
class CustomPhysicsMaterialTagNamesEditor : BaseEditor
{
#pragma warning disable 649
[AutoPopulate(ElementFormatString = "Custom Physics Material Tag {0}", Resizable = false, Reorderable = false)]
ReorderableList m_TagNames;
#pragma warning restore 649
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/EditorUtilities.cs
using UnityEngine;
using Unity.Mathematics;
using static Unity.Physics.Math;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.IMGUI.Controls;
namespace Unity.Physics.Editor
{
/// <summary>
/// Provides utilities that use Handles to set positions and axes,
/// </summary>
public class EditorUtilities
{
// Editor for a joint pivot or pivot pair
public static void EditPivot(RigidTransform worldFromA, RigidTransform worldFromB, bool lockBtoA,
ref float3 pivotA, ref float3 pivotB, Object target)
{
EditorGUI.BeginChangeCheck();
float3 pivotAinW = Handles.PositionHandle(math.transform(worldFromA, pivotA), quaternion.identity);
float3 pivotBinW;
if (lockBtoA)
{
pivotBinW = pivotAinW;
pivotB = math.transform(math.inverse(worldFromB), pivotBinW);
}
else
{
pivotBinW = Handles.PositionHandle(math.transform(worldFromB, pivotB), quaternion.identity);
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Edit joint pivot");
pivotA = math.transform(math.inverse(worldFromA), pivotAinW);
pivotB = math.transform(math.inverse(worldFromB), pivotBinW);
}
Handles.DrawLine(worldFromA.pos, pivotAinW);
Handles.DrawLine(worldFromB.pos, pivotBinW);
}
// Editor for a joint axis or axis pair
public class AxisEditor
{
// Even though we're only editing axes and not rotations, we need to track a full rotation in order to keep the rotation handle stable
private quaternion m_RefA = quaternion.identity;
private quaternion m_RefB = quaternion.identity;
// Detect changes in the object being edited to reset the reference orientations
private Object m_LastTarget;
private static bool NormalizeSafe(ref float3 x)
{
float lengthSq = math.lengthsq(x);
const float epsSq = 1e-8f;
if (math.abs(lengthSq - 1) > epsSq)
{
if (lengthSq > epsSq)
{
x *= math.rsqrt(lengthSq);
}
else
{
x = new float3(1, 0, 0);
}
return true;
}
return false;
}
private static bool NormalizePerpendicular(float3 axis, ref float3 perpendicular)
{
// make sure perpendicular is actually perpendicular to direction
float dot = math.dot(axis, perpendicular);
float absDot = math.abs(dot);
if (absDot > 1.0f - 1e-5f)
{
// parallel, choose an arbitrary perpendicular
float3 dummy;
CalculatePerpendicularNormalized(axis, out perpendicular, out dummy);
return true;
}
if (absDot > 1e-5f)
{
// reject direction
perpendicular -= dot * axis;
NormalizeSafe(ref perpendicular);
return true;
}
return NormalizeSafe(ref perpendicular);
}
public void Update(RigidTransform worldFromA, RigidTransform worldFromB, bool lockBtoA, float3 pivotA, float3 pivotB,
ref float3 directionA, ref float3 directionB, ref float3 perpendicularA, ref float3 perpendicularB, Object target)
{
// Work in world space
float3 directionAinW = math.rotate(worldFromA, directionA);
float3 directionBinW = math.rotate(worldFromB, directionB);
float3 perpendicularAinW = math.rotate(worldFromB, perpendicularA);
float3 perpendicularBinW = math.rotate(worldFromB, perpendicularB);
bool changed = false;
// If the target changed, fix up the inputs and reset the reference orientations to align with the new target's axes
if (target != m_LastTarget)
{
m_LastTarget = target;
// Enforce normalized directions
changed |= NormalizeSafe(ref directionAinW);
changed |= NormalizeSafe(ref directionBinW);
// Enforce normalized perpendiculars, orthogonal to their respective directions
changed |= NormalizePerpendicular(directionAinW, ref perpendicularAinW);
changed |= NormalizePerpendicular(directionBinW, ref perpendicularBinW);
// Calculate the rotation of the joint in A from direction and perpendicular
float3x3 rotationA = new float3x3(directionAinW, perpendicularAinW, math.cross(directionAinW, perpendicularAinW));
m_RefA = new quaternion(rotationA);
if (lockBtoA)
{
m_RefB = m_RefA;
}
else
{
// Calculate the rotation of the joint in B from direction and perpendicular
float3x3 rotationB = new float3x3(directionBinW, perpendicularBinW, math.cross(directionBinW, perpendicularBinW));
m_RefB = new quaternion(rotationB);
}
}
EditorGUI.BeginChangeCheck();
// Make rotators
quaternion oldRefA = m_RefA;
quaternion oldRefB = m_RefB;
float3 pivotAinW = math.transform(worldFromA, pivotA);
m_RefA = Handles.RotationHandle(m_RefA, pivotAinW);
float3 pivotBinW;
if (lockBtoA)
{
directionB = math.rotate(math.inverse(worldFromB), directionAinW);
perpendicularB = math.rotate(math.inverse(worldFromB), perpendicularAinW);
pivotBinW = pivotAinW;
m_RefB = m_RefA;
}
else
{
pivotBinW = math.transform(worldFromB, pivotB);
m_RefB = Handles.RotationHandle(m_RefB, pivotBinW);
}
// Apply changes from the rotators
if (EditorGUI.EndChangeCheck())
{
quaternion dqA = math.mul(m_RefA, math.inverse(oldRefA));
quaternion dqB = math.mul(m_RefB, math.inverse(oldRefB));
directionAinW = math.mul(dqA, directionAinW);
directionBinW = math.mul(dqB, directionBinW);
perpendicularAinW = math.mul(dqB, perpendicularAinW);
perpendicularBinW = math.mul(dqB, perpendicularBinW);
changed = true;
}
// Write back if the axes changed
if (changed)
{
Undo.RecordObject(target, "Edit joint axis");
directionA = math.rotate(math.inverse(worldFromA), directionAinW);
directionB = math.rotate(math.inverse(worldFromB), directionBinW);
perpendicularA = math.rotate(math.inverse(worldFromB), perpendicularAinW);
perpendicularB = math.rotate(math.inverse(worldFromB), perpendicularBinW);
}
// Draw the updated axes
float3 z = new float3(0, 0, 1); // ArrowHandleCap() draws an arrow pointing in (0, 0, 1)
Handles.ArrowHandleCap(0, pivotAinW, Quaternion.FromToRotation(z, directionAinW), HandleUtility.GetHandleSize(pivotAinW) * 0.75f, Event.current.type);
Handles.ArrowHandleCap(0, pivotAinW, Quaternion.FromToRotation(z, perpendicularAinW), HandleUtility.GetHandleSize(pivotAinW) * 0.75f, Event.current.type);
if (!lockBtoA)
{
Handles.ArrowHandleCap(0, pivotBinW, Quaternion.FromToRotation(z, directionBinW), HandleUtility.GetHandleSize(pivotBinW) * 0.75f, Event.current.type);
Handles.ArrowHandleCap(0, pivotBinW, Quaternion.FromToRotation(z, perpendicularBinW), HandleUtility.GetHandleSize(pivotBinW) * 0.75f, Event.current.type);
}
}
}
public static void EditLimits(RigidTransform worldFromA, RigidTransform worldFromB, float3 pivotA, float3 axisA, float3 axisB, float3 perpendicularA, float3 perpendicularB
ref float minLimit, ref float maxLimit, JointAngularLimitHandle limitHandle, Object target)
{
// Transform to world space
float3 pivotAinW = math.transform(worldFromA, pivotA);
float3 axisAinW = math.rotate(worldFromA, axisA);
float3 perpendicularAinW = math.rotate(worldFromA, perpendicularA);
float3 axisBinW = math.rotate(worldFromA, axisB);
float3 perpendicularBinW = math.rotate(worldFromB, perpendicularB);
// Get rotations from joint space
// JointAngularLimitHandle uses axis = (1, 0, 0) with angle = 0 at (0, 0, 1), so choose the rotations to point those in the directions of our axis and perpendicular
float3x3 worldFromJointA = new float3x3(axisAinW, -math.cross(axisAinW, perpendicularAinW), perpendicularAinW);
float3x3 worldFromJointB = new float3x3(axisBinW, -math.cross(axisBinW, perpendicularBinW), perpendicularBinW);
float3x3 jointBFromA = math.mul(math.transpose(worldFromJointB), worldFromJointA);
// Set orientation for the angular limit control
float angle = CalculateTwistAngle(new quaternion(jointBFromA), 0); // index = 0 because axis is the first column in worldFromJoint
quaternion limitOrientation = math.mul(quaternion.AxisAngle(axisAinW, angle), new quaternion(worldFromJointA));
Matrix4x4 handleMatrix = Matrix4x4.TRS(pivotAinW, limitOrientation, Vector3.one);
float size = HandleUtility.GetHandleSize(pivotAinW) * 0.75f;
limitHandle.xMin = -maxLimit;
limitHandle.xMax = -minLimit;
limitHandle.xMotion = ConfigurableJointMotion.Limited;
limitHandle.yMotion = ConfigurableJointMotion.Locked;
limitHandle.zMotion = ConfigurableJointMotion.Locked;
limitHandle.yHandleColor = new Color(0, 0, 0, 0);
limitHandle.zHandleColor = new Color(0, 0, 0, 0);
limitHandle.radius = size;
using (new Handles.DrawingScope(handleMatrix))
{
// Draw the reference axis
float3 z = new float3(0, 0, 1); // ArrowHandleCap() draws an arrow pointing in (0, 0, 1)
Handles.ArrowHandleCap(0, float3.zero, Quaternion.FromToRotation(z, new float3(1, 0, 0)), size, Event.current.type);
// Draw the limit editor handle
EditorGUI.BeginChangeCheck();
limitHandle.DrawHandle();
if (EditorGUI.EndChangeCheck())
{
// Record the target object before setting new limits so changes can be undone/redone
Undo.RecordObject(target, "Edit joint angular limits");
minLimit = -limitHandle.xMax;
maxLimit = -limitHandle.xMin;
}
}
}
}
}
#endif
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/LimitedHingeJointEditor.cs
#if UNITY_EDITOR
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(LimitedHingeJoint))]
public class LimitedHingeEditor : UnityEditor.Editor
{
private EditorUtilities.AxisEditor m_AxisEditor = new EditorUtilities.AxisEditor();
private JointAngularLimitHandle m_LimitHandle = new JointAngularLimitHandle();
EditorGUI.BeginChangeCheck();
GUILayout.BeginHorizontal();
GUILayout.Label("Editors:");
limitedHinge.EditPivots = GUILayout.Toggle(limitedHinge.EditPivots, new GUIContent("Pivot"), "Button");
limitedHinge.EditAxes = GUILayout.Toggle(limitedHinge.EditAxes, new GUIContent("Axis"), "Button");
limitedHinge.EditLimits = GUILayout.Toggle(limitedHinge.EditLimits, new GUIContent("Limits"), "Button");
GUILayout.EndHorizontal();
DrawDefaultInspector();
if (EditorGUI.EndChangeCheck())
{
SceneView.RepaintAll();
}
}
if (limitedHinge.EditPivots)
{
EditorUtilities.EditPivot(limitedHinge.worldFromA, limitedHinge.worldFromB, limitedHinge.AutoSetConnected,
ref limitedHinge.PositionLocal, ref limitedHinge.PositionInConnectedEntity, limitedHinge);
}
if (limitedHinge.EditAxes)
{
m_AxisEditor.Update(limitedHinge.worldFromA, limitedHinge.worldFromB,
limitedHinge.AutoSetConnected,
limitedHinge.PositionLocal, limitedHinge.PositionInConnectedEntity,
ref limitedHinge.HingeAxisLocal, ref limitedHinge.HingeAxisInConnectedEntity,
ref limitedHinge.PerpendicularAxisLocal, ref limitedHinge.PerpendicularAxisInConnectedEntity,
limitedHinge);
}
if (limitedHinge.EditLimits)
{
EditorUtilities.EditLimits(limitedHinge.worldFromA, limitedHinge.worldFromB,
limitedHinge.PositionLocal,
limitedHinge.HingeAxisLocal, limitedHinge.HingeAxisInConnectedEntity,
limitedHinge.PerpendicularAxisLocal, limitedHinge.PerpendicularAxisInConnectedEntity,
ref limitedHinge.MinAngle, ref limitedHinge.MaxAngle, m_LimitHandle, limitedHinge);
}
}
}
}
#endif
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/PhysicsBodyAuthoringEditor.cs
using System.Collections.Generic;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(PhysicsBodyAuthoring))]
[CanEditMultipleObjects]
class PhysicsBodyAuthoringEditor : BaseEditor
{
static class Content
{
public static readonly GUIContent MassLabel = EditorGUIUtility.TrTextContent("Mass");
public static readonly GUIContent CenterOfMassLabel = EditorGUIUtility.TrTextContent(
"Center of Mass", "Center of mass in the space of this body's transform."
);
public static readonly GUIContent InertiaTensorLabel = EditorGUIUtility.TrTextContent(
"Inertia Tensor", "Resistance to angular motion about each axis of rotation."
);
public static readonly GUIContent OrientationLabel = EditorGUIUtility.TrTextContent(
"Orientation", "Orientation of the body's inertia tensor in the space of its transform."
);
public static readonly GUIContent AdvancedLabel = EditorGUIUtility.TrTextContent(
"Advanced", "Advanced options"
);
}
#pragma warning disable 649
[AutoPopulate] SerializedProperty m_MotionType;
[AutoPopulate] SerializedProperty m_Smoothing;
[AutoPopulate] SerializedProperty m_Mass;
[AutoPopulate] SerializedProperty m_GravityFactor;
[AutoPopulate] SerializedProperty m_LinearDamping;
[AutoPopulate] SerializedProperty m_AngularDamping;
[AutoPopulate] SerializedProperty m_InitialLinearVelocity;
[AutoPopulate] SerializedProperty m_InitialAngularVelocity;
[AutoPopulate] SerializedProperty m_OverrideDefaultMassDistribution;
[AutoPopulate] SerializedProperty m_CenterOfMass;
[AutoPopulate] SerializedProperty m_Orientation;
[AutoPopulate] SerializedProperty m_InertiaTensor;
[AutoPopulate] SerializedProperty m_WorldIndex;
[AutoPopulate] SerializedProperty m_CustomTags;
#pragma warning restore 649
bool showAdvanced;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_MotionType);
if (m_MotionType.intValue != (int)BodyMotionType.Static)
EditorGUILayout.PropertyField(m_Smoothing);
if (m_MotionType.intValue == (int)BodyMotionType.Dynamic)
{
EditorGUILayout.PropertyField(m_LinearDamping, true);
EditorGUILayout.PropertyField(m_AngularDamping, true);
}
if (m_MotionType.intValue != (int)BodyMotionType.Static)
{
EditorGUILayout.PropertyField(m_InitialLinearVelocity, true);
EditorGUILayout.PropertyField(m_InitialAngularVelocity, true);
}
if (m_MotionType.intValue == (int)BodyMotionType.Dynamic)
{
EditorGUILayout.PropertyField(m_GravityFactor, true);
}
EditorGUI.BeginDisabledGroup(!dynamic);
if (dynamic)
{
EditorGUILayout.PropertyField(m_Orientation, Content.OrientationLabel);
EditorGUILayout.PropertyField(m_InertiaTensor, Content.InertiaTensorLabel);
}
else
{
EditorGUI.BeginDisabledGroup(true);
var position =
EditorGUILayout.GetControlRect(true, EditorGUI.GetPropertyHeight(m_InertiaTensor));
EditorGUI.BeginProperty(position, Content.InertiaTensorLabel, m_InertiaTensor);
EditorGUI.Vector3Field(position, Content.InertiaTensorLabel,
Vector3.one * float.PositiveInfinity);
EditorGUI.EndProperty();
EditorGUI.EndDisabledGroup();
}
EditorGUI.EndDisabledGroup();
--EditorGUI.indentLevel;
}
}
EditorGUILayout.PropertyField(m_CustomTags);
--EditorGUI.indentLevel;
}
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
DisplayStatusMessages();
}
MessageType m_Status;
List<string> m_StatusMessages = new List<string>(8);
void DisplayStatusMessages()
{
m_Status = MessageType.None;
m_StatusMessages.Clear();
var hierarchyStatus = StatusMessageUtility.GetHierarchyStatusMessage(targets, out var hierarchyStatusMessage);
if (!string.IsNullOrEmpty(hierarchyStatusMessage))
{
m_StatusMessages.Add(hierarchyStatusMessage);
m_Status = (MessageType)math.max((int)m_Status, (int)hierarchyStatus);
}
if (m_StatusMessages.Count > 0)
EditorGUILayout.HelpBox(string.Join("\n\n", m_StatusMessages), m_Status);
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/PhysicsCategoryNamesEditor.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(PhysicsCategoryNames))]
[CanEditMultipleObjects]
class PhysicsCategoryNamesEditor : BaseEditor
{
#pragma warning disable 649
[AutoPopulate(ElementFormatString = "Category {0}", Resizable = false, Reorderable = false)]
ReorderableList m_CategoryNames;
#pragma warning restore 649
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/PhysicsShapeAuthoringEditor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityMesh = UnityEngine.Mesh;
using Unity.Physics.Extensions;
using LegacyRigidBody = UnityEngine.Rigidbody;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(PhysicsShapeAuthoring))]
[CanEditMultipleObjects]
class PhysicsShapeAuthoringEditor : BaseEditor
{
static class Styles
{
const string k_Plural = "One or more selected objects";
const string k_Singular = "This object";
public static readonly string GenericUndoMessage = L10n.Tr("Change Shape");
public static readonly string MultipleShapeTypesLabel =
L10n.Tr("Multiple shape types in current selection.");
public static readonly string PreviewGenerationNotification =
L10n.Tr("Generating collision geometry preview...");
static readonly GUIContent k_FitToRenderMeshesLabel =
EditorGUIUtility.TrTextContent("Fit to Enabled Render Meshes");
static readonly GUIContent k_FitToRenderMeshesWarningLabelSg = new GUIContent(
k_FitToRenderMeshesLabel.text,
EditorGUIUtility.Load("console.warnicon") as Texture,
L10n.Tr($"{k_Singular} has non-uniform scale. Trying to fit the shape to render meshes might produce unexpected results.")
);
static readonly GUIContent k_FitToRenderMeshesWarningLabelPl = new GUIContent(
k_FitToRenderMeshesLabel.text,
EditorGUIUtility.Load("console.warnicon") as Texture,
L10n.Tr($"{k_Plural} has non-uniform scale. Trying to fit the shape to render meshes might produce unexpected results.")
);
public static readonly GUIContent CenterLabel = EditorGUIUtility.TrTextContent("Center");
public static readonly GUIContent SizeLabel = EditorGUIUtility.TrTextContent("Size");
public static readonly GUIContent OrientationLabel = EditorGUIUtility.TrTextContent(
"Orientation", "Euler orientation in the shape's local space (ZXY order)."
);
public static readonly GUIContent CylinderSideCountLabel = EditorGUIUtility.TrTextContent("Side Count");
public static readonly GUIContent RadiusLabel = EditorGUIUtility.TrTextContent("Radius");
public static readonly GUIContent ForceUniqueLabel = EditorGUIUtility.TrTextContent(
"Force Unique",
"If set to true, then this object will always produce a unique collider for run-time during conversion. " +
"If set to false, then this object may share its collider data with other objects if they have the same inputs. " +
"You should enable this option if you plan to modify this instance's collider at run-time."
);
public static readonly GUIContent MaterialLabel = EditorGUIUtility.TrTextContent("Material");
public static readonly GUIContent SetRecommendedConvexValues = EditorGUIUtility.TrTextContent(
"Set Recommended Default Values",
"Set recommended values for convex hull generation parameters based on either render meshes or custom mesh."
);
public static GUIContent GetFitToRenderMeshesLabel(int numTargets, MessageType status) =>
status >= MessageType.Warning
? numTargets == 1 ? k_FitToRenderMeshesWarningLabelSg : k_FitToRenderMeshesWarningLabelPl
: k_FitToRenderMeshesLabel;
static readonly string[] k_NoGeometryWarning =
{
L10n.Tr($"{k_Singular} has no enabled render meshes in its hierarchy and no custom mesh assigned."),
L10n.Tr($"{k_Plural} has no enabled render meshes in their hierarchies and no custom mesh assigned.")
};
public static string GetNoGeometryWarning(int numTargets) =>
numTargets == 1 ? k_NoGeometryWarning[0] : k_NoGeometryWarning[1];
m_NumImplicitStatic = targets.Cast<PhysicsShapeAuthoring>().Count(
shape => shape.GetPrimaryBody() == shape.gameObject
&& shape.GetComponent<PhysicsBodyAuthoring>() == null
&& shape.GetComponent<LegacyRigidBody>() == null
);
Undo.undoRedoPerformed += Repaint;
}
void OnDisable()
{
Undo.undoRedoPerformed -= Repaint;
SceneViewUtility.ClearNotificationInSceneView();
foreach (var preview in m_PreviewData.Values)
preview.Dispose();
if (m_DropDown != null)
m_DropDown.CloseWithoutUndo();
}
class PreviewMeshData : IDisposable
{
[BurstCompile]
struct CreateTempHullJob : IJob
{
public ConvexHullGenerationParameters GenerationParameters;
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<float3> Points;
public NativeArray<BlobAssetReference<Collider>> Output;
public void Execute() => Output[0] = ConvexCollider.Create(Points, GenerationParameters, CollisionFilter.Default);
}
[BurstCompile]
struct CreateTempMeshJob : IJob
{
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<float3> Points;
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<int3> Triangles;
public NativeArray<BlobAssetReference<Collider>> Output;
public void Execute() => Output[0] = MeshCollider.Create(Points, Triangles);
}
static readonly List<Vector3> s_ReusableEdges = new List<Vector3>(1024);
public Vector3[] Edges = Array.Empty<Vector3>();
public Aabb Bounds = new Aabb();
bool m_Disposed;
uint m_InputHash;
ConvexHullGenerationParameters m_HashedConvexParameters;
NativeArray<float3> m_HashedPoints = new NativeArray<float3>(0, Allocator.Persistent);
// multiple preview jobs might be running if user assigned a different mesh before previous job completed
JobHandle m_MostRecentlyScheduledJob;
Dictionary<JobHandle, NativeArray<BlobAssetReference<Collider>>> m_PreviewJobsOutput =
new Dictionary<JobHandle, NativeArray<BlobAssetReference<Collider>>>();
unsafe uint GetInputHash(
PhysicsShapeAuthoring shape,
NativeList<float3> currentPoints,
NativeArray<float3> hashedPoints,
ConvexHullGenerationParameters hashedConvexParameters,
out ConvexHullGenerationParameters currentConvexProperties
)
{
currentConvexProperties = default;
switch (shape.ShapeType)
{
case ShapeType.ConvexHull:
shape.GetBakedConvexProperties(currentPoints); // TODO: use HashableShapeInputs
currentConvexProperties = shape.ConvexHullGenerationParameters;
return math.hash(
new uint3(
(uint)shape.ShapeType,
currentConvexProperties.GetStableHash(hashedConvexParameters),
currentPoints.GetStableHash(hashedPoints)
)
);
case ShapeType.Mesh:
var triangles = new NativeList<int3>(1024, Allocator.Temp);
shape.GetBakedMeshProperties(currentPoints, triangles); // TODO: use HashableShapeInputs
return math.hash(
new uint3(
(uint)shape.ShapeType,
currentPoints.GetStableHash(hashedPoints),
math.hash(triangles.GetUnsafePtr(), UnsafeUtility.SizeOf<int3>() * triangles.Length)
)
);
default:
return (uint)shape.ShapeType;
}
}
public void SchedulePreviewIfChanged(PhysicsShapeAuthoring shape)
{
using (var currentPoints = new NativeList<float3>(65535, Allocator.Temp))
{
var hash = GetInputHash(
shape, currentPoints, m_HashedPoints, m_HashedConvexParameters, out var currentConvexParameters
);
if (m_InputHash == hash)
return;
m_InputHash = hash;
m_HashedConvexParameters = currentConvexParameters;
m_HashedPoints.Dispose();
m_HashedPoints = new NativeArray<float3>(currentPoints.Length, Allocator.Persistent);
m_HashedPoints.CopyFrom(currentPoints.AsArray());
}
if (shape.ShapeType != ShapeType.ConvexHull && shape.ShapeType != ShapeType.Mesh)
return;
// TODO: cache results per input data hash, and simply use existing data (e.g., to make undo/redo faster)
var output = new NativeArray<BlobAssetReference<Collider>>(1, Allocator.Persistent);
m_MostRecentlyScheduledJob = shape.ShapeType == ShapeType.Mesh
? ScheduleMeshPreview(shape, output)
: ScheduleConvexHullPreview(shape, output);
m_PreviewJobsOutput.Add(m_MostRecentlyScheduledJob, output);
if (m_PreviewJobsOutput.Count == 1)
{
CheckPreviewJobsForCompletion();
if (m_MostRecentlyScheduledJob.Equals(default(JobHandle)))
return;
EditorApplication.update += CheckPreviewJobsForCompletion;
EditorApplication.delayCall += () =>
{
SceneViewUtility.DisplayProgressNotification(
Styles.PreviewGenerationNotification, () => float.PositiveInfinity
);
};
}
}
JobHandle ScheduleConvexHullPreview(PhysicsShapeAuthoring shape, NativeArray<BlobAssetReference<Collider>> output)
{
var pointCloud = new NativeList<float3>(65535, Allocator.Temp);
shape.GetBakedConvexProperties(pointCloud);
if (pointCloud.Length == 0)
return default;
// copy to NativeArray because NativeList not yet compatible with DeallocateOnJobCompletion
var pointsArray = new NativeArray<float3>(
pointCloud.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory
);
pointsArray.CopyFrom(pointCloud.AsArray());
// TODO: if there is still an active job with the same input data hash, then just set it to be most recently scheduled job
return new CreateTempHullJob
{
GenerationParameters = shape.ConvexHullGenerationParameters.ToRunTime(),
Points = pointsArray,
Output = output
}.Schedule();
}
JobHandle ScheduleMeshPreview(PhysicsShapeAuthoring shape, NativeArray<BlobAssetReference<Collider>> output)
{
var points = new NativeList<float3>(1024, Allocator.Temp);
var triangles = new NativeList<int3>(1024, Allocator.Temp);
shape.GetBakedMeshProperties(points, triangles);
if (points.Length == 0 || triangles.Length == 0)
return default;
// copy to NativeArray because NativeList not yet compatible with DeallocateOnJobCompletion
var pointsArray = new NativeArray<float3>(
points.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory
);
pointsArray.CopyFrom(points.AsArray());
var triangleArray = new NativeArray<int3>(
triangles.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory
);
triangleArray.CopyFrom(triangles.AsArray());
// TODO: if there is still an active job with the same input data hash, then just set it to be most recently scheduled job
return new CreateTempMeshJob
{
Points = pointsArray,
Triangles = triangleArray,
Output = output
}.Schedule();
}
unsafe void CheckPreviewJobsForCompletion()
{
var repaintSceneViews = false;
foreach (var job in m_PreviewJobsOutput.Keys.ToArray()) // TODO: don't allocate on heap
{
// repaint scene views to indicate progress if most recent preview job is still in the queue
var mostRecentlyScheduledJob = m_MostRecentlyScheduledJob.Equals(job);
repaintSceneViews |= mostRecentlyScheduledJob;
if (!job.IsCompleted)
continue;
var output = m_PreviewJobsOutput[job];
m_PreviewJobsOutput.Remove(job);
job.Complete();
// only populate preview edge data if not already disposed and this job was actually the most recent
if (!m_Disposed && mostRecentlyScheduledJob)
{
if (!output[0].IsCreated)
{
Edges = Array.Empty<Vector3>();
Bounds = new Aabb();
}
else
{
switch (output[0].Value.Type)
{
case ColliderType.Convex:
ref var convex = ref output[0].As<ConvexCollider>();
DrawingUtility.GetConvexColliderEdges(
ref convex, s_ReusableEdges
);
Bounds = convex.CalculateAabb();
break;
case ColliderType.Mesh:
ref var mesh = ref output[0].As<MeshCollider>();
DrawingUtility.GetMeshColliderEdges(
ref mesh, s_ReusableEdges
);
Bounds = mesh.CalculateAabb();
break;
}
Edges = s_ReusableEdges.ToArray();
}
EditorApplication.delayCall += SceneViewUtility.ClearNotificationInSceneView;
}
if (output.IsCreated)
{
if (output[0].IsCreated)
output[0].Dispose();
output.Dispose();
}
}
if (repaintSceneViews)
SceneView.RepaintAll();
if (m_PreviewJobsOutput.Count == 0)
EditorApplication.update -= CheckPreviewJobsForCompletion;
}
public void Dispose()
{
m_Disposed = true;
m_HashedPoints.Dispose();
}
}
Dictionary<PhysicsShapeAuthoring, PreviewMeshData> m_PreviewData = new Dictionary<PhysicsShapeAuthoring, PreviewMeshData>();
PreviewMeshData GetPreviewData(PhysicsShapeAuthoring shape)
{
if (shape.ShapeType != ShapeType.ConvexHull && shape.ShapeType != ShapeType.Mesh)
return null;
if (!m_PreviewData.TryGetValue(shape, out var preview))
{
preview = m_PreviewData[shape] = new PreviewMeshData();
preview.SchedulePreviewIfChanged(shape);
}
// do not generate a new preview until the user has finished dragging a control handle (e.g., scale)
if (m_DraggingControlID == 0 && !EditorGUIUtility.editingTextField)
preview.SchedulePreviewIfChanged(shape);
return preview;
}
void UpdateGeometryState()
{
m_GeometryState = GeometryState.Okay;
var skinnedPoints = new NativeList<float3>(8192, Allocator.Temp);
foreach (PhysicsShapeAuthoring shape in targets)
{
// if a custom mesh is assigned, only check it
using (var so = new SerializedObject(shape))
{
var customMesh = so.FindProperty(m_CustomMesh.propertyPath).objectReferenceValue as UnityMesh;
if (customMesh != null)
{
m_GeometryState |= GetGeometryState(customMesh, shape.gameObject);
continue;
}
}
// otherwise check all mesh filters in the hierarchy that might be included
var geometryState = GeometryState.Okay;
using (var scope = new GetActiveChildrenScope<MeshFilter>(shape, shape.transform))
{
foreach (var meshFilter in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(meshFilter, filterOutInvalid: false))
geometryState |= GetGeometryState(meshFilter.sharedMesh, shape.gameObject);
}
}
if (shape.ShapeType == ShapeType.Mesh)
{
PhysicsShapeAuthoring.GetAllSkinnedPointsInHierarchyBelongingToShape(
shape, skinnedPoints, false, default, default, default
);
if (skinnedPoints.Length > 0)
geometryState |= GeometryState.MeshWithSkinnedPoints;
}
m_GeometryState |= geometryState;
}
skinnedPoints.Dispose();
}
static GeometryState GetGeometryState(UnityMesh mesh, GameObject host)
{
if (mesh == null)
return GeometryState.NoGeometry;
if (!mesh.IsValidForConversion(host))
return GeometryState.NonReadableGeometry;
return GeometryState.Okay;
}
public override void OnInspectorGUI()
{
var hotControl = GUIUtility.hotControl;
switch (Event.current.GetTypeForControl(hotControl))
{
case EventType.MouseDrag:
m_DraggingControlID = hotControl;
break;
case EventType.MouseUp:
m_DraggingControlID = 0;
break;
}
UpdateGeometryState();
serializedObject.Update();
UpdateStatusMessages();
EditorGUI.BeginChangeCheck();
DisplayShapeSelector();
++EditorGUI.indentLevel;
if (m_ShapeType.hasMultipleDifferentValues)
EditorGUILayout.HelpBox(Styles.MultipleShapeTypesLabel, MessageType.None);
else
{
switch ((ShapeType)m_ShapeType.intValue)
{
case ShapeType.Box:
AutomaticPrimitiveControls();
DisplayBoxControls();
break;
case ShapeType.Capsule:
AutomaticPrimitiveControls();
DisplayCapsuleControls();
break;
case ShapeType.Sphere:
AutomaticPrimitiveControls();
DisplaySphereControls();
break;
case ShapeType.Cylinder:
AutomaticPrimitiveControls();
DisplayCylinderControls();
break;
case ShapeType.Plane:
AutomaticPrimitiveControls();
DisplayPlaneControls();
break;
case ShapeType.ConvexHull:
RecommendedConvexValuesButton();
EditorGUILayout.PropertyField(m_ConvexHullGenerationParameters);
EditorGUILayout.PropertyField(m_MinimumSkinnedVertexWeight);
DisplayMeshControls();
break;
case ShapeType.Mesh:
DisplayMeshControls();
break;
default:
throw new UnimplementedShapeException((ShapeType)m_ShapeType.intValue);
}
EditorGUILayout.PropertyField(m_ForceUnique, Styles.ForceUniqueLabel);
}
--EditorGUI.indentLevel;
EditorGUILayout.LabelField(Styles.MaterialLabel);
++EditorGUI.indentLevel;
EditorGUILayout.PropertyField(m_Material);
--EditorGUI.indentLevel;
if (m_StatusMessages.Count > 0)
EditorGUILayout.HelpBox(string.Join("\n\n", m_StatusMessages), m_Status);
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
}
void RecommendedConvexValuesButton()
{
EditorGUI.BeginDisabledGroup(
(m_GeometryState & GeometryState.NoGeometry) == GeometryState.NoGeometry ||
EditorUtility.IsPersistent(target)
);
var rect = EditorGUI.IndentedRect(
EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight, EditorStyles.miniButton)
);
var buttonLabel = Styles.SetRecommendedConvexValues;
if (GUI.Button(rect, buttonLabel, Styles.Button))
{
Undo.RecordObjects(targets, buttonLabel.text);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.InitializeConvexHullGenerationParameters();
EditorUtility.SetDirty(shape);
}
}
EditorGUI.EndDisabledGroup();
}
MessageType m_GeometryStatus;
List<string> m_GeometryStatusMessages = new List<string>();
HashSet<string> m_ShapeSuggestions = new HashSet<string>();
MessageType m_Status;
List<string> m_StatusMessages = new List<string>(8);
MessageType m_MatrixStatus;
List<MatrixState> m_MatrixStates = new List<MatrixState>();
void UpdateStatusMessages()
{
m_Status = MessageType.None;
m_StatusMessages.Clear();
if (m_NumImplicitStatic != 0)
m_StatusMessages.Add(Styles.GetStaticColliderStatusMessage(targets.Length));
m_ShapeSuggestions.Clear();
foreach (PhysicsShapeAuthoring shape in targets)
{
const float k_Epsilon = HashableShapeInputs.k_DefaultLinearPrecision;
switch (shape.ShapeType)
{
case ShapeType.Box:
var box = shape.GetBakedBoxProperties();
var max = math.cmax(box.Size);
var min = math.cmin(box.Size);
if (min < k_Epsilon)
m_ShapeSuggestions.Add(Styles.BoxPlaneSuggestion);
else if (math.abs(box.BevelRadius - min * 0.5f) < k_Epsilon)
{
if (math.abs(max - min) < k_Epsilon)
m_ShapeSuggestions.Add(Styles.BoxSphereSuggestion);
else if (math.abs(math.lengthsq(box.Size - new float3(min)) - math.pow(max - min, 2f)) < k_Epsilon)
m_ShapeSuggestions.Add(Styles.BoxCapsuleSuggestion);
}
break;
case ShapeType.Capsule:
var capsule = shape.GetBakedCapsuleProperties();
if (math.abs(capsule.Height - 2f * capsule.Radius) < k_Epsilon)
m_ShapeSuggestions.Add(Styles.CapsuleSphereSuggestion);
break;
case ShapeType.Cylinder:
var cylinder = shape.GetBakedCylinderProperties();
if (math.abs(cylinder.BevelRadius - cylinder.Radius) < k_Epsilon)
{
m_ShapeSuggestions.Add(math.abs(cylinder.Height - 2f * cylinder.Radius) < k_Epsilon
? Styles.CylinderSphereSuggestion
: Styles.CylinderCapsuleSuggestion);
}
break;
}
}
foreach (var suggestion in m_ShapeSuggestions)
m_StatusMessages.Add(suggestion);
var hierarchyStatus = StatusMessageUtility.GetHierarchyStatusMessage(targets, out var hierarchyStatusMessage);
if (!string.IsNullOrEmpty(hierarchyStatusMessage))
{
m_StatusMessages.Add(hierarchyStatusMessage);
m_Status = (MessageType)math.max((int)m_Status, (int)hierarchyStatus);
}
m_MatrixStates.Clear();
foreach (var t in targets)
{
var localToWorld = (float4x4)(t as Component).transform.localToWorldMatrix;
m_MatrixStates.Add(ManipulatorUtility.GetMatrixState(ref localToWorld));
}
m_MatrixStatus = StatusMessageUtility.GetMatrixStatusMessage(m_MatrixStates, out var matrixStatusMessage);
if (m_MatrixStatus != MessageType.None)
{
m_StatusMessages.Add(matrixStatusMessage);
m_Status = (MessageType)math.max((int)m_Status, (int)m_MatrixStatus);
}
m_GeometryStatus = MessageType.None;
m_GeometryStatusMessages.Clear();
if ((m_GeometryState & GeometryState.NoGeometry) == GeometryState.NoGeometry)
{
m_GeometryStatusMessages.Add(Styles.GetNoGeometryWarning(targets.Length));
m_GeometryStatus = (MessageType)math.max((int)m_GeometryStatus, (int)MessageType.Error);
}
if ((m_GeometryState & GeometryState.NonReadableGeometry) == GeometryState.NonReadableGeometry)
{
m_GeometryStatusMessages.Add(Styles.GetNonReadableGeometryWarning(targets.Length));
m_GeometryStatus = (MessageType)math.max((int)m_GeometryStatus, (int)MessageType.Warning);
}
if ((m_GeometryState & GeometryState.MeshWithSkinnedPoints) == GeometryState.MeshWithSkinnedPoints)
{
m_GeometryStatusMessages.Add(Styles.GetMeshWithSkinnedPointsWarning(targets.Length));
m_GeometryStatus = (MessageType)math.max((int)m_GeometryStatus, (int)MessageType.Warning);
}
}
void DisplayShapeSelector()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_ShapeType);
if (!EditorGUI.EndChangeCheck())
return;
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
switch ((ShapeType)m_ShapeType.intValue)
{
case ShapeType.Box:
shape.SetBox(shape.GetBoxProperties(out var orientation), orientation);
break;
case ShapeType.Capsule:
shape.SetCapsule(shape.GetCapsuleProperties());
break;
case ShapeType.Sphere:
shape.SetSphere(shape.GetSphereProperties(out orientation), orientation);
break;
case ShapeType.Cylinder:
shape.SetCylinder(shape.GetCylinderProperties(out orientation), orientation);
break;
case ShapeType.Plane:
shape.GetPlaneProperties(out var center, out var size2D, out orientation);
shape.SetPlane(center, size2D, orientation);
break;
case ShapeType.ConvexHull:
case ShapeType.Mesh:
return;
default:
throw new UnimplementedShapeException((ShapeType)m_ShapeType.intValue);
}
EditorUtility.SetDirty(shape);
}
GUIUtility.ExitGUI();
}
void AutomaticPrimitiveControls()
{
EditorGUI.BeginDisabledGroup(
(m_GeometryState & GeometryState.NoGeometry) == GeometryState.NoGeometry || EditorUtility.IsPersistent(target)
);
var buttonLabel = Styles.GetFitToRenderMeshesLabel(targets.Length, m_MatrixStatus);
var rect = EditorGUI.IndentedRect(
EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight, EditorStyles.miniButton)
);
if (GUI.Button(rect, buttonLabel, Styles.ButtonDropDown))
m_DropDown = FitToRenderMeshesDropDown.Show(rect, buttonLabel.text, m_MinimumSkinnedVertexWeight);
EditorGUI.EndDisabledGroup();
}
class FitToRenderMeshesDropDown : EditorWindow
{
static class Styles
{
public const float WindowWidth = 400f;
public const float LabelWidth = 200f;
public static GUIStyle Button => PhysicsShapeAuthoringEditor.Styles.Button;
}
static class Content
{
public static readonly string ApplyLabel = L10n.Tr("Apply");
public static readonly string CancelLabel = L10n.Tr("Cancel");
}
bool m_ApplyChanges;
bool m_ClosedWithoutUndo;
int m_UndoGroup;
SerializedProperty m_MinimumSkinnedVertexWeight;
public static FitToRenderMeshesDropDown Show(
Rect buttonRect, string title, SerializedProperty minimumSkinnedVertexWeight
)
{
var window = CreateInstance<FitToRenderMeshesDropDown>();
window.titleContent = EditorGUIUtility.TrTextContent(title);
window.m_UndoGroup = Undo.GetCurrentGroup();
window.m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
var size = new Vector2(
math.max(buttonRect.width, Styles.WindowWidth),
(EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing) * 3f
);
window.maxSize = window.minSize = size;
window.ShowAsDropDown(GUIUtility.GUIToScreenRect(buttonRect), size);
return window;
}
void OnGUI()
{
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = Styles.LabelWidth;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_MinimumSkinnedVertexWeight);
if (EditorGUI.EndChangeCheck())
ApplyChanges();
EditorGUIUtility.labelWidth = labelWidth;
GUILayout.FlexibleSpace();
var buttonRect = GUILayoutUtility.GetRect(0f, EditorGUIUtility.singleLineHeight);
var buttonLeft = new Rect(buttonRect)
{
width = 0.5f * (buttonRect.width - EditorGUIUtility.standardVerticalSpacing)
};
var buttonRight = new Rect(buttonLeft)
{
x = buttonLeft.xMax + EditorGUIUtility.standardVerticalSpacing
};
var close = false;
buttonRect = Application.platform == RuntimePlatform.OSXEditor ? buttonLeft : buttonRight;
if (
GUI.Button(buttonRect, Content.CancelLabel, Styles.Button)
|| Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape
)
{
close = true;
}
buttonRect = Application.platform == RuntimePlatform.OSXEditor ? buttonRight : buttonLeft;
if (GUI.Button(buttonRect, Content.ApplyLabel, Styles.Button))
{
close = true;
m_ApplyChanges = true;
}
if (close)
{
Close();
EditorGUIUtility.ExitGUI();
}
}
void ApplyChanges()
{
m_MinimumSkinnedVertexWeight.serializedObject.ApplyModifiedProperties();
Undo.RecordObjects(m_MinimumSkinnedVertexWeight.serializedObject.targetObjects, titleContent.text);
foreach (PhysicsShapeAuthoring shape in m_MinimumSkinnedVertexWeight.serializedObject.targetObjects)
{
using (var so = new SerializedObject(shape))
{
shape.FitToEnabledRenderMeshes(
so.FindProperty(m_MinimumSkinnedVertexWeight.propertyPath).floatValue
);
EditorUtility.SetDirty(shape);
}
}
m_MinimumSkinnedVertexWeight.serializedObject.Update();
}
public void CloseWithoutUndo()
{
m_ApplyChanges = true;
Close();
}
void OnDestroy()
{
if (m_ApplyChanges)
ApplyChanges();
else
Undo.RevertAllDownToGroup(m_UndoGroup);
}
}
void DisplayBoxControls()
{
EditorGUILayout.PropertyField(m_PrimitiveSize, Styles.SizeLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
EditorGUILayout.PropertyField(m_BevelRadius);
}
void DisplayCapsuleControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Capsule);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.SetCapsule(shape.GetCapsuleProperties());
EditorUtility.SetDirty(shape);
}
}
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
}
void DisplaySphereControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_SphereRadius, Styles.RadiusLabel);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.SetSphere(shape.GetSphereProperties(out EulerAngles orientation), orientation);
EditorUtility.SetDirty(shape);
}
}
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
}
void DisplayCylinderControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Cylinder);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.SetCylinder(shape.GetCylinderProperties(out var orientation), orientation);
EditorUtility.SetDirty(shape);
}
}
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
EditorGUILayout.PropertyField(m_CylinderSideCount, Styles.CylinderSideCountLabel);
EditorGUILayout.PropertyField(m_BevelRadius);
}
void DisplayPlaneControls()
{
EditorGUILayout.PropertyField(m_PrimitiveSize, Styles.SizeLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
}
void DisplayMeshControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_CustomMesh);
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
}
if (m_GeometryStatusMessages.Count > 0)
EditorGUILayout.HelpBox(string.Join("\n\n", m_GeometryStatusMessages), m_GeometryStatus);
}
static readonly BeveledBoxBoundsHandle s_Box = new BeveledBoxBoundsHandle();
static readonly PhysicsCapsuleBoundsHandle s_Capsule =
new PhysicsCapsuleBoundsHandle { heightAxis = CapsuleBoundsHandle.HeightAxis.Z };
static readonly BeveledCylinderBoundsHandle s_Cylinder = new BeveledCylinderBoundsHandle();
static readonly PhysicsSphereBoundsHandle s_Sphere = new PhysicsSphereBoundsHandle();
static readonly BoxBoundsHandle s_Plane =
new BoxBoundsHandle { axes = PrimitiveBoundsHandle.Axes.X | PrimitiveBoundsHandle.Axes.Z };
static readonly Color k_ShapeHandleColor = new Color32(145, 244, 139, 210);
static readonly Color k_ShapeHandleColorDisabled = new Color32(84, 200, 77, 140);
void OnSceneGUI()
{
var hotControl = GUIUtility.hotControl;
switch (Event.current.GetTypeForControl(hotControl))
{
case EventType.MouseDrag:
m_DraggingControlID = hotControl;
break;
case EventType.MouseUp:
m_DraggingControlID = 0;
break;
}
var shape = target as PhysicsShapeAuthoring;
var handleColor = shape.enabled ? k_ShapeHandleColor : k_ShapeHandleColorDisabled;
var handleMatrix = shape.GetShapeToWorldMatrix();
using (new Handles.DrawingScope(handleColor, handleMatrix))
{
switch (shape.ShapeType)
{
case ShapeType.Box:
var boxGeometry = shape.GetBakedBoxProperties();
s_Box.bevelRadius = boxGeometry.BevelRadius;
s_Box.center = float3.zero;
s_Box.size = boxGeometry.Size;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(boxGeometry.Center, boxGeometry.Orientation, 1f))))
s_Box.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedBoxSize(s_Box.size, s_Box.bevelRadius);
}
break;
case ShapeType.Capsule:
s_Capsule.center = float3.zero;
var capsuleGeometry = shape.GetBakedCapsuleProperties();
s_Capsule.height = capsuleGeometry.Height;
s_Capsule.radius = capsuleGeometry.Radius;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(capsuleGeometry.Center, capsuleGeometry.Orientation, 1f))))
s_Capsule.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedCapsuleSize(s_Capsule.height, s_Capsule.radius);
}
break;
case ShapeType.Sphere:
var sphereGeometry = shape.GetBakedSphereProperties(out EulerAngles orientation);
s_Sphere.center = float3.zero;
s_Sphere.radius = sphereGeometry.Radius;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(sphereGeometry.Center, orientation, 1f))))
s_Sphere.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedSphereRadius(s_Sphere.radius);
}
break;
case ShapeType.Cylinder:
var cylinderGeometry = shape.GetBakedCylinderProperties();
s_Cylinder.center = float3.zero;
s_Cylinder.height = cylinderGeometry.Height;
s_Cylinder.radius = cylinderGeometry.Radius;
s_Cylinder.sideCount = cylinderGeometry.SideCount;
s_Cylinder.bevelRadius = cylinderGeometry.BevelRadius;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(cylinderGeometry.Center, cylinderGeometry.Orientation, 1f))))
s_Cylinder.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedCylinderSize(s_Cylinder.height, s_Cylinder.radius, s_Cylinder.bevelRadius);
}
break;
case ShapeType.Plane:
shape.GetPlaneProperties(out var center, out var size2, out orientation);
s_Plane.center = float3.zero;
s_Plane.size = new float3(size2.x, 0f, size2.y);
EditorGUI.BeginChangeCheck();
{
var m = math.mul(shape.transform.localToWorldMatrix, float4x4.TRS(center, orientation, 1f));
using (new Handles.DrawingScope(m))
s_Plane.DrawHandle();
var right = math.mul(m, new float4 { x = 1f }).xyz;
var forward = math.mul(m, new float4 { z = 1f }).xyz;
var normal = math.cross(math.normalizesafe(forward), math.normalizesafe(right))
* 0.5f * math.lerp(math.length(right) * size2.x, math.length(forward) * size2.y, 0.5f);
using (new Handles.DrawingScope(float4x4.identity))
Handles.DrawLine(m.c3.xyz, m.c3.xyz + normal);
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedPlaneSize(((float3)s_Plane.size).xz);
}
break;
case ShapeType.ConvexHull:
if (Event.current.type != EventType.Repaint)
break;
var points = GetPreviewData(shape).Edges;
// TODO: follow transformation until new preview is generated if e.g., user is dragging handles
if (points.Length > 0)
Handles.DrawLines(points);
break;
case ShapeType.Mesh:
if (Event.current.type != EventType.Repaint)
break;
points = GetPreviewData(shape).Edges;
if (points.Length > 0)
Handles.DrawLines(points);
break;
default:
throw new UnimplementedShapeException(shape.ShapeType);
}
}
}
// ReSharper disable once UnusedMember.Global - magic method called by unity inspector
public bool HasFrameBounds()
{
return true;
}
static Bounds TransformBounds(Bounds localBounds, float4x4 matrix)
{
var center = new float4(localBounds.center, 1);
Bounds bounds = new Bounds(math.mul(matrix, center).xyz, Vector3.zero);
var extent = new float4(localBounds.extents, 0);
for (int i = 0; i < 8; ++i)
{
extent.x = (i & 1) == 0 ? -extent.x : extent.x;
extent.y = (i & 2) == 0 ? -extent.y : extent.y;
extent.z = (i & 4) == 0 ? -extent.z : extent.z;
var worldPoint = math.mul(matrix, center + extent).xyz;
bounds.Encapsulate(worldPoint);
}
return bounds;
}
// ReSharper disable once UnusedMember.Global - magic method called by unity inspector
public Bounds OnGetFrameBounds()
{
var shape = target as PhysicsShapeAuthoring;
var shapeMatrix = shape.GetShapeToWorldMatrix();
Bounds bounds = new Bounds();
switch (shape.ShapeType)
{
case ShapeType.Box:
var boxGeometry = shape.GetBakedBoxProperties();
bounds = new Bounds(float3.zero, boxGeometry.Size);
bounds = TransformBounds(bounds, float4x4.TRS(boxGeometry.Center, boxGeometry.Orientation, 1f));
break;
case ShapeType.Capsule:
var capsuleGeometry = shape.GetBakedCapsuleProperties();
var cd = capsuleGeometry.Radius * 2;
bounds = new Bounds(float3.zero, new float3(cd, cd, capsuleGeometry.Height));
bounds = TransformBounds(bounds, float4x4.TRS(capsuleGeometry.Center, capsuleGeometry.Orientation, 1f));
break;
case ShapeType.Sphere:
var sphereGeometry = shape.GetBakedSphereProperties(out var orientation);
var sd = sphereGeometry.Radius * 2;
bounds = new Bounds(sphereGeometry.Center, new float3(sd, sd, sd));
break;
case ShapeType.Cylinder:
var cylinderGeometry = shape.GetBakedCylinderProperties();
var cyld = cylinderGeometry.Radius * 2;
bounds = new Bounds(float3.zero, new float3(cyld, cyld, cylinderGeometry.Height));
bounds = TransformBounds(bounds, float4x4.TRS(cylinderGeometry.Center, cylinderGeometry.Orientation, 1f));
break;
case ShapeType.Plane:
shape.GetPlaneProperties(out var center, out var size2, out orientation);
bounds = new Bounds(float3.zero, new float3(size2.x, 0, size2.y));
bounds = TransformBounds(bounds, float4x4.TRS(center, orientation, 1f));
break;
case ShapeType.ConvexHull:
case ShapeType.Mesh:
var previewData = GetPreviewData(shape);
if (previewData != null)
bounds = new Bounds(previewData.Bounds.Center, previewData.Bounds.Extents);
break;
default:
throw new UnimplementedShapeException(shape.ShapeType);
}
return TransformBounds(bounds, shapeMatrix);
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/RagdollJointEditor.cs
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using Unity.Mathematics;
using Unity.Physics.Authoring;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(RagdollJoint))]
public class RagdollJointEditor : UnityEditor.Editor
{
private EditorUtilities.AxisEditor m_AxisEditor = new EditorUtilities.AxisEditor();
private JointAngularLimitHandle m_LimitHandle = new JointAngularLimitHandle();
public override void OnInspectorGUI()
{
RagdollJoint ragdoll = (RagdollJoint)target;
EditorGUI.BeginChangeCheck();
GUILayout.BeginVertical();
GUILayout.Space(10.0f);
GUILayout.BeginHorizontal();
GUILayout.Label("Editors:");
ragdoll.EditPivots = GUILayout.Toggle(ragdoll.EditPivots, new GUIContent("Pivot"), "Button");
ragdoll.EditAxes = GUILayout.Toggle(ragdoll.EditAxes, new GUIContent("Axis"), "Button");
ragdoll.EditLimits = GUILayout.Toggle(ragdoll.EditLimits, new GUIContent("Limits"), "Button");
GUILayout.EndHorizontal();
GUILayout.Space(10.0f);
GUILayout.EndVertical();
DrawDefaultInspector();
if (EditorGUI.EndChangeCheck())
{
SceneView.RepaintAll();
}
}
private static void DrawCone(float3 point, float3 axis, float angle, Color color)
{
#if UNITY_EDITOR
Handles.color = color;
float3 dir;
float scale = Math.NormalizeWithLength(axis, out dir);
float3 arm;
{
float3 perp1, perp2;
Math.CalculatePerpendicularNormalized(dir, out perp1, out perp2);
arm = math.mul(quaternion.AxisAngle(perp1, angle), dir) * scale;
}
const int res = 16;
quaternion q = quaternion.AxisAngle(dir, 2.0f * (float)math.PI / res);
for (int i = 0; i < res; i++)
{
float3 nextArm = math.mul(q, arm);
Handles.DrawLine(point, point + arm);
Handles.DrawLine(point + arm, point + nextArm);
arm = nextArm;
}
#endif
}
protected virtual void OnSceneGUI()
{
RagdollJoint ragdoll = (RagdollJoint)target;
bool drawCones = false;
if (ragdoll.EditPivots)
{
EditorUtilities.EditPivot(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.AutoSetConnected,
ref ragdoll.PositionLocal, ref ragdoll.PositionInConnectedEntity, ragdoll);
}
if (ragdoll.EditAxes)
{
m_AxisEditor.Update(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.AutoSetConnected,
ragdoll.PositionLocal, ragdoll.PositionInConnectedEntity, ref ragdoll.TwistAxisLocal, ref ragdoll.TwistAxisInConnectedEntity,
ref ragdoll.PerpendicularAxisLocal, ref ragdoll.PerpendicularAxisInConnectedEntity, ragdoll);
drawCones = true;
}
if (ragdoll.EditLimits)
{
EditorUtilities.EditLimits(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.PositionLocal, ragdoll.TwistAxisLocal, ragdoll.TwistAxisInConnectedEntity,
ragdoll.PerpendicularAxisLocal, ragdoll.PerpendicularAxisInConnectedEntity, ref ragdoll.MinTwistAngle, ref ragdoll.MaxTwistAngle, m_LimitHandle, ragdoll);
}
if (drawCones)
{
float3 pivotB = math.transform(ragdoll.worldFromB, ragdoll.PositionInConnectedEntity);
float3 axisB = math.rotate(ragdoll.worldFromB, ragdoll.TwistAxisInConnectedEntity);
DrawCone(pivotB, axisB, math.radians(ragdoll.MaxConeAngle), Color.yellow);
float3 perpendicularB = math.rotate(ragdoll.worldFromB, ragdoll.PerpendicularAxisInConnectedEntity);
DrawCone(pivotB, perpendicularB, math.radians(ragdoll.MinPerpendicularAngle + 90f), Color.red);
DrawCone(pivotB, perpendicularB, math.radians(ragdoll.MaxPerpendicularAngle + 90f), Color.red);
}
}
}
}
#endif
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/BeveledBoxBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class BeveledBoxBoundsHandle : BoxBoundsHandle
{
public float bevelRadius
{
get => math.min(m_BevelRadius, math.cmin(GetSize()) * 0.5f);
set
{
if (!m_IsDragging)
m_BevelRadius = math.max(0f, value);
}
}
float m_BevelRadius = ConvexHullGenerationParameters.Default.BevelRadius;
bool m_IsDragging = false;
static PhysicsBoundsHandleUtility.Corner[] s_Corners = new PhysicsBoundsHandleUtility.Corner[8];
public new void DrawHandle()
{
int prevHotControl = GUIUtility.hotControl;
if (prevHotControl == 0)
m_IsDragging = false;
base.DrawHandle();
int currHotcontrol = GUIUtility.hotControl;
if (currHotcontrol != prevHotControl)
m_IsDragging = currHotcontrol != 0;
}
protected override void DrawWireframe()
{
if (this.bevelRadius <= 0f)
{
base.DrawWireframe();
return;
}
var cameraPosition = float3.zero;
var cameraForward = new float3 { z = 1f };
if (Camera.current != null)
{
cameraPosition = Camera.current.transform.position;
cameraForward = Camera.current.transform.forward;
}
var bounds = new Bounds(this.center, this.size);
bool isCameraInsideBox = Camera.current != null && bounds.Contains(Handles.inverseMatrix.MultiplyPoint(cameraPosition));
var bevelRadius = this.bevelRadius;
var origin = (float3)this.center;
var size = (float3)this.size;
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(-1f, 1f, 1f), bevelRadius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, -1f, 1f), bevelRadius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 2, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, -1f), bevelRadius, 2, axes, isCameraInsideBox);
var corner = 0.5f * size - new float3(1f) * bevelRadius;
var axisx = new float3(1f, 0f, 0f);
var axisy = new float3(0f, 1f, 0f);
var axisz = new float3(0f, 0f, 1f);
// Since the geometry is transformed by Handles.matrix during rendering, we transform the camera position
// by the inverse matrix so that the two-shaded wireframe will have the proper orientation.
var invMatrix = Handles.inverseMatrix;
cameraPosition = invMatrix.MultiplyPoint(cameraPosition);
cameraForward = invMatrix.MultiplyVector(cameraForward);
var cameraOrtho = Camera.current == null || Camera.current.orthographic;
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, -1f), quaternion.LookRotation(-axisz, axisy), cameraPosition, cameraForward,
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, 1f), quaternion.LookRotation(-axisx, axisy), cameraPosition, cameraForward,
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, 1f), quaternion.LookRotation(axisz, axisy), cameraPosition, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, -1f), quaternion.LookRotation(axisx, axisy), cameraPosition, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, -1f), quaternion.LookRotation(-axisx, -axisy), cameraPosition, cameraForward,
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, 1f), quaternion.LookRotation(axisz, -axisy), cameraPosition, cameraForward, cameraOrth
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, 1f), quaternion.LookRotation(axisx, -axisy), cameraPosition, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, -1f), quaternion.LookRotation(-axisz, -axisy), cameraPosition, cameraForward, cameraOrth
for (int i = 0; i < s_Corners.Length; i++)
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[i], true);
// Draw the horizon edges between the corners
for (int upA = 3, upB = 0; upB < 4; upA = upB, upB++)
{
int dnA = upA + 4;
int dnB = upB + 4;
if (s_Corners[upA].splitAxis[0].z && s_Corners[upB].splitAxis[1].x) Handles.DrawLine(s_Corners[upA].points[0], s_Corners[upB].points[1]);
if (s_Corners[upA].splitAxis[1].z && s_Corners[upB].splitAxis[0].x) Handles.DrawLine(s_Corners[upA].points[1], s_Corners[upB].points[0]);
if (s_Corners[dnA].splitAxis[0].x && s_Corners[dnB].splitAxis[1].z) Handles.DrawLine(s_Corners[dnA].points[0], s_Corners[dnB].points[1]);
if (s_Corners[dnA].splitAxis[1].x && s_Corners[dnB].splitAxis[0].z) Handles.DrawLine(s_Corners[dnA].points[1], s_Corners[dnB].points[0]);
if (s_Corners[dnA].splitAxis[0].y && s_Corners[upA].splitAxis[1].y) Handles.DrawLine(s_Corners[dnA].points[0], s_Corners[upA].points[1]);
if (s_Corners[dnA].splitAxis[1].y && s_Corners[upA].splitAxis[0].y) Handles.DrawLine(s_Corners[dnA].points[1], s_Corners[upA].points[0]);
}
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/BeveledCylinderBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class BeveledCylinderBoundsHandle : PrimitiveBoundsHandle
{
public float bevelRadius
{
get => math.min(m_BevelRadius, math.cmin(GetSize()) * 0.5f);
set
{
if (!m_IsDragging)
m_BevelRadius = math.max(0f, value);
}
}
m_SideCount = value;
// Since the geometry is transformed by Handles.matrix during rendering, we transform the camera position
// by the inverse matrix so that the two-shaded wireframe will have the proper orientation.
var invMatrix = Handles.inverseMatrix;
cameraCenter = invMatrix.MultiplyPoint(cameraCenter);
cameraForward = invMatrix.MultiplyVector(cameraForward);
var cameraOrtho = Camera.current != null && Camera.current.orthographic;
t = (m_SideCount - 1) * angleStep;
var xyAngle1 = new float3(math.cos(t), math.sin(t), 0f);
var sideways1 = new float3(math.cos(t + kHalfPI - halfAngleStep), math.sin(t + kHalfPI - halfAngleStep), 0f);
var direction1 = new float3(math.cos(t + halfAngleStep), math.sin(t + halfAngleStep), 0f);
var bevelGreaterThanZero = bevelRadius > 0f;
var bevelLessThanCylinderRadius = bevelRadius < radius;
for (var i = 0; i < m_SideCount; ++i)
{
t = i * angleStep;
var xyAngle2 = new float3(math.cos(t), math.sin(t), 0f);
var sideways2 = new float3(math.cos(t + kHalfPI - halfAngleStep), math.sin(t + kHalfPI - halfAngleStep), 0f);
var direction2 = new float3(math.cos(t + halfAngleStep), math.sin(t + halfAngleStep), 0f);
var cornerIndex0 = i;
var cornerIndex1 = i + m_SideCount;
{
var orientation = quaternion.LookRotation(xyAngle2, up);
var cornerNormal = math.normalize(math.mul(orientation, new float3(0f, 1f, 1f)));
PhysicsBoundsHandleUtility.CalculateCornerHorizon(top2,
new float3x3(direction1, up, direction2),
cornerNormal, cameraCenter, cameraForward, cameraOrtho,
bevelRadius, out m_Corners[cornerIndex0]);
}
{
var orientation = quaternion.LookRotation(xyAngle2, -up);
var cornerNormal = math.normalize(math.mul(orientation, new float3(0f, 1f, 1f)));
PhysicsBoundsHandleUtility.CalculateCornerHorizon(bottom2,
new float3x3(direction2, -up, direction1),
cornerNormal, cameraCenter, cameraForward, cameraOrtho,
bevelRadius, out m_Corners[cornerIndex1]);
}
}
direction1 = direction2;
sideways1 = sideways2;
xyAngle0 = xyAngle1;
xyAngle1 = xyAngle2;
}
if (bevelGreaterThanZero)
{
Handles.color = frontfacedColor;
for (int a = m_SideCount - 1, b = 0; b < m_SideCount; a = b, ++b)
{
var up0 = a;
var dn0 = a + m_SideCount;
var up1 = b;
var dn1 = b + m_SideCount;
namespace Unity.Physics.Editor
{
abstract class BaseDrawer : PropertyDrawer
{
protected abstract bool IsCompatible(SerializedProperty property);
EditorGUI.EndProperty();
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/EulerAnglesDrawer.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(EulerAngles))]
class EulerAnglesDrawer : BaseDrawer
{
protected override bool IsCompatible(SerializedProperty property) => true;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var value = property.FindPropertyRelative(nameof(EulerAngles.Value));
return EditorGUI.GetPropertyHeight(value);
}
protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)
{
var value = property.FindPropertyRelative(nameof(EulerAngles.Value));
EditorGUI.PropertyField(position, value, label, true);
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/ExpandChildrenDrawer.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(ExpandChildrenAttribute))]
class ExpandChildrenDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
property.isExpanded = true;
return EditorGUI.GetPropertyHeight(property)
- EditorGUIUtility.standardVerticalSpacing
- EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var endProperty = property.GetEndProperty();
var childProperty = property.Copy();
childProperty.NextVisible(true);
while (!SerializedProperty.EqualContents(childProperty, endProperty))
{
position.height = EditorGUI.GetPropertyHeight(childProperty);
OnChildPropertyGUI(position, childProperty);
position.y += position.height + EditorGUIUtility.standardVerticalSpacing;
childProperty.NextVisible(false);
}
}
protected virtual void OnChildPropertyGUI(Rect position, SerializedProperty childProperty)
{
EditorGUI.PropertyField(position, childProperty, true);
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/PhysicsMaterialCoefficientDrawer.cs
using System;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(PhysicsMaterialCoefficient))]
class PhysicsMaterialCoefficientDrawer : BaseDrawer
{
static class Styles
{
public const float PopupWidth = 100f;
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) =>
EditorGUIUtility.singleLineHeight;
protected override bool IsCompatible(SerializedProperty property) => true;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(PhysicsMaterialProperties))]
class PhysicsMaterialPropertiesDrawer : BaseDrawer
{
static class Content
{
public static readonly GUIContent AdvancedGroupFoldout = EditorGUIUtility.TrTextContent("Advanced");
public static readonly GUIContent BelongsToLabel = EditorGUIUtility.TrTextContent(
"Belongs To",
"Specifies the categories to which this object belongs."
);
public static readonly GUIContent CollidesWithLabel = EditorGUIUtility.TrTextContent(
"Collides With",
"Specifies the categories of objects with which this object will collide, " +
"or with which it will raise events if intersecting a trigger."
);
public static readonly GUIContent CollisionFilterGroupFoldout =
EditorGUIUtility.TrTextContent("Collision Filter");
public static readonly GUIContent CustomFlagsLabel =
EditorGUIUtility.TrTextContent("Custom Tags", "Specify custom tags to read at run-time.");
public static readonly GUIContent FrictionLabel = EditorGUIUtility.TrTextContent(
"Friction",
"Specifies how resistant the body is to motion when sliding along other surfaces, " +
"as well as what value should be used when colliding with an object that has a different value."
);
public static readonly GUIContent RestitutionLabel = EditorGUIUtility.TrTextContent(
"Restitution",
"Specifies how bouncy the object will be when colliding with other surfaces, " +
"as well as what value should be used when colliding with an object that has a different value."
);
public static readonly GUIContent CollisionResponseLabel = EditorGUIUtility.TrTextContent(
"Collision Response",
"Specifies whether the shape should collide normally, raise trigger events when intersecting other shapes, " +
"collide normally and raise notifications of collision events with other shapes, " +
"or completely ignore collisions (but still move and intercept queries)."
);
}
void FindToggleAndValueProperties(
SerializedProperty property, SerializedProperty templateValueProperty, string relativePath,
out SerializedProperty toggle, out SerializedProperty value
)
{
var relative = property.FindPropertyRelative(relativePath);
toggle = relative.FindPropertyRelative("m_Override");
value = toggle.boolValue || templateValueProperty == null
? relative.FindPropertyRelative("m_Value")
: templateValueProperty.FindPropertyRelative(relativePath).FindPropertyRelative("m_Value");
}
// m_BelongsTo, m_CollidesWith
var group = property.FindPropertyRelative(k_CollisionFilterGroupKey);
if (group.isExpanded)
height += 2f * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
// m_CustomTags
group = property.FindPropertyRelative(k_AdvancedGroupKey);
if (group.isExpanded)
height += (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
// m_Template
if (property.FindPropertyRelative("m_SupportsTemplate").boolValue)
height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
// m_Friction, m_Restitution
FindToggleAndValueProperties(property, templateValueProperty, "m_CollisionResponse", out _, out var collisionResponse);
// Check if regular collider
CollisionResponsePolicy collisionResponseEnum = (CollisionResponsePolicy)collisionResponse.intValue;
if (collisionResponseEnum == CollisionResponsePolicy.Collide ||
collisionResponseEnum == CollisionResponsePolicy.CollideRaiseCollisionEvents)
height += 2f * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
return height;
}
EditorGUI.BeginDisabledGroup(!toggle.boolValue);
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
EditorGUI.PropertyField(new Rect(position) { xMin = togglePosition.xMax }, value, GUIContent.none, true);
EditorGUI.indentLevel = indent;
EditorGUI.EndDisabledGroup();
}
else
{
EditorGUI.PropertyField(position, value, label, true);
}
}
--EditorGUI.indentLevel;
}
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/SoftRangeDrawer.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(SoftRangeAttribute))]
class SoftRangeDrawer : BaseDrawer
{
protected override bool IsCompatible(SerializedProperty property)
{
return property.propertyType == SerializedPropertyType.Float;
}
protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)
{
var attr = attribute as SoftRangeAttribute;
EditorGUIControls.SoftSlider(
position, label, property, attr.SliderMin, attr.SliderMax, attr.TextFieldMin, attr.TextFieldMax
);
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/TagsDrawer.cs
using System.Collections.Generic;
using System.Linq;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
abstract class TagsDrawer<T> : PropertyDrawer where T : ScriptableObject, ITagNames
{
static class Styles
{
public static readonly string EverythingName = L10n.Tr("Everything");
public static readonly string MixedName = L10n.Tr("Mixed...");
public static readonly string NothingName = L10n.Tr("Nothing");
string[] GetOptions()
{
if (m_Options != null)
return m_Options;
var guids = AssetDatabase.FindAssets($"t:{typeof(T).Name}");
m_NamesAssets = guids
.Select(AssetDatabase.GUIDToAssetPath)
.Select(AssetDatabase.LoadAssetAtPath<T>)
.Where(c => c != null)
.ToArray();
m_Options = m_NamesAssets.FirstOrDefault()?.TagNames.ToArray() ?? DefaultOptions;
for (var i = 0; i < m_Options.Length; ++i)
{
if (string.IsNullOrEmpty(m_Options[i]))
m_Options[i] = DefaultOptions[i];
string[] m_Options;
T[] m_NamesAssets;
// TODO: remove when all usages of bool[] are migrated
SerializedProperty GetFirstChildProperty(SerializedProperty property)
{
if (!string.IsNullOrEmpty(FirstChildPropertyPath))
return property.FindPropertyRelative(FirstChildPropertyPath);
var sp = property.Copy();
sp.NextVisible(true);
return sp;
}
var value = 0;
var everything = 0;
var sp = GetFirstChildProperty(property);
for (int i = 0, count = MaxNumCategories; i < count; ++i)
{
EditorGUI.showMixedValue |= sp.hasMultipleDifferentValues;
value |= sp.boolValue ? 1 << i : 0;
everything |= 1 << i;
sp.NextVisible(false);
}
// in case size is smaller than 32
if (value == everything)
value = ~0;
var options = GetOptions();
if (
EditorGUI.DropdownButton(
controlPosition,
EditorGUIUtility.TrTempContent(GetButtonLabel(value, options)),
FocusType.Passive,
EditorStyles.popup
)
)
{
var menu = new GenericMenu();
menu.AddItem(
new GUIContent(Styles.NothingName),
value == 0,
() =>
{
sp = GetFirstChildProperty(property);
for (int i = 0, count = MaxNumCategories; i < count; ++i)
{
sp.boolValue = false;
sp.NextVisible(false);
}
sp.serializedObject.ApplyModifiedProperties();
}
);
menu.AddItem(
new GUIContent(Styles.EverythingName),
value == ~0,
() =>
{
sp = GetFirstChildProperty(property);
for (int i = 0, count = MaxNumCategories; i < count; ++i)
{
sp.boolValue = true;
sp.NextVisible(false);
}
sp.serializedObject.ApplyModifiedProperties();
}
);
for (var option = 0; option < options.Length; ++option)
{
var callbackValue = option;
menu.AddItem(
EditorGUIUtility.TrTextContent(options[option]),
((1 << option) & value) != 0,
args =>
{
var changedBitAndValue = (KeyValuePair<int, bool>)args;
sp = GetFirstChildProperty(property);
for (int i = 0, count = changedBitAndValue.Key; i < count; ++i)
sp.NextVisible(false);
sp.boolValue = changedBitAndValue.Value;
sp.serializedObject.ApplyModifiedProperties();
},
new KeyValuePair<int, bool>(callbackValue, ((1 << option) & value) == 0)
);
}
menu.AddSeparator(string.Empty);
menu.AddItem(
EditorGUIUtility.TrTempContent($"Edit {ObjectNames.NicifyVariableName(typeof(T).Name)}"),
false,
() =>
{
if (m_NamesAssets.Length > 0)
Selection.activeObject = m_NamesAssets[0];
else
{
var assetPath = AssetDatabase.GenerateUniqueAssetPath($"Assets/{typeof(T).Name}.asset");
AssetDatabase.CreateAsset(ScriptableObject.CreateInstance<T>(), assetPath);
Selection.activeObject = AssetDatabase.LoadAssetAtPath<T>(assetPath);
m_Options = null;
}
}
);
menu.DropDown(controlPosition);
}
EditorGUI.showMixedValue = showMixed;
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
if (m_NamesAssets?.Length > 1)
{
var id = GUIUtility.GetControlID(FocusType.Passive);
if (Event.current.type == EventType.Repaint)
{
position.width = EditorGUIUtility.singleLineHeight;
position.x = controlPosition.xMax + EditorGUIUtility.standardVerticalSpacing;
Styles.MultipleAssetsWarning.tooltip = string.Format(
Styles.MultipleAssetsTooltip,
ObjectNames.NicifyVariableName(typeof(T).Name),
m_NamesAssets.FirstOrDefault(n => n != null)?.name
);
GUIStyle.none.Draw(position, Styles.MultipleAssetsWarning, id);
}
}
}
}
[CustomPropertyDrawer(typeof(CustomPhysicsBodyTags))]
class CustomBodyTagsDrawer : TagsDrawer<CustomPhysicsBodyTagNames>
{
protected override string DefaultCategoryName => "Custom Physics Body Tag";
protected override int MaxNumCategories => 8;
}
[CustomPropertyDrawer(typeof(CustomPhysicsMaterialTags))]
class CustomMaterialTagsDrawer : TagsDrawer<CustomPhysicsMaterialTagNames>
{
protected override string DefaultCategoryName => "Custom Physics Material Tag";
protected override int MaxNumCategories => 8;
}
[CustomPropertyDrawer(typeof(PhysicsCategoryTags))]
class PhysicsCategoryTagsDrawer : TagsDrawer<PhysicsCategoryNames>
{
protected override string DefaultCategoryName => "Physics Category";
protected override int MaxNumCategories => 32;
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/EditorGUIControls.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[InitializeOnLoad]
static class EditorGUIControls
{
static EditorGUIControls()
{
if (k_SoftSlider == null)
Debug.LogException(new MissingMemberException("Could not find expected signature of EditorGUI.Slider() for soft slider."));
}
static class Styles
{
public static readonly string CompatibilityWarning = L10n.Tr("Not compatible with {0}.");
}
public static void DisplayCompatibilityWarning(Rect position, GUIContent label, string incompatibleType)
{
EditorGUI.HelpBox(
EditorGUI.PrefixLabel(position, label),
string.Format(Styles.CompatibilityWarning, incompatibleType),
MessageType.Error
);
}
static readonly MethodInfo k_SoftSlider = typeof(EditorGUI).GetMethod(
"Slider",
BindingFlags.Static | BindingFlags.NonPublic,
null,
new[]
{
typeof(Rect), // position
typeof(GUIContent), // label
typeof(float), // value
typeof(float), // sliderMin
typeof(float), // sliderMax
typeof(float), // textFieldMin
typeof(float) // textFieldMax
},
Array.Empty<ParameterModifier>()
);
static readonly object[] k_SoftSliderArgs = new object[7];
namespace Unity.Physics.Editor
{
static class SceneViewUtility
{
static class Styles
{
public static readonly GUIStyle ProgressBarTrack = new GUIStyle
{
fixedHeight = 4f,
normal = new GUIStyleState { background = Texture2D.whiteTexture }
};
public static readonly GUIStyle ProgressBarIndicator = new GUIStyle
{
fixedHeight = 4f,
normal = new GUIStyleState { background = Texture2D.whiteTexture }
};
public static readonly GUIStyle SceneViewStatusMessage = new GUIStyle("NotificationBackground")
{
fontSize = EditorStyles.label.fontSize
};
static Styles() => SceneViewStatusMessage.padding = SceneViewStatusMessage.border;
}
namespace Unity.Physics.Editor
{
static class StatusMessageUtility
{
public static MessageType GetHierarchyStatusMessage(IReadOnlyList<UnityObject> targets, out string statusMessage)
{
statusMessage = string.Empty;
if (targets.Count == 0)
return MessageType.None;
var numChildTargets = 0;
foreach (Component c in targets)
{
// hierarchy roots and leaf shapes do not emit a message
if (
c == null
|| c.transform.parent == null
|| PhysicsShapeExtensions.GetPrimaryBody(c.gameObject) != c.gameObject
)
continue;
if (matrixStates.Contains(MatrixState.NonUniformScale))
{
statusMessage = L10n.Tr(
matrixStates.Count == 1
? "Target has non-uniform scale. Shape data will be transformed during conversion in order to bake scale into the run-time format."
: "One or more targets has non-uniform scale. Shape data will be transformed during conversion in order to bake scale into the run-time format."
);
return MessageType.Warning;
}
return MessageType.None;
}
}
}
OnlineFPS/Assets/Scripts/Player/FirstPersonPlayer.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
[Serializable]
[GhostComponent()]
public struct FirstPersonPlayer : IComponentData
{
[GhostField()]
public FixedString128Bytes Name;
[GhostField()]
public Entity ControlledCharacter;
[Serializable]
public struct FirstPersonPlayerCommands : IInputComponentData
{
public float2 MoveInput;
public float2 LookInputDelta;
public InputEvent JumpPressed;
public InputEvent ShootPressed;
public InputEvent ShootReleased;
public bool AimHeld;
}
OnlineFPS/Assets/Scripts/Player/FirstPersonPlayerAuthoring.cs
using UnityEngine;
using Unity.Entities;
[DisallowMultipleComponent]
public class FirstPersonPlayerAuthoring : MonoBehaviour
{
public GameObject ControlledCharacter;
// Shoot
if (defaultActionsMap.Shoot.WasPressedThisFrame())
{
playerCommands.ValueRW.ShootPressed.Set();
}
if (defaultActionsMap.Shoot.WasReleasedThisFrame())
{
playerCommands.ValueRW.ShootReleased.Set();
}
// Aim
playerCommands.ValueRW.AimHeld = defaultActionsMap.Aim.IsPressed();
player.ValueRW.LastKnownCommandsTick = tick;
player.ValueRW.LastKnownCommands = playerCommands.ValueRW;
}
}
}
[UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
[UpdateBefore(typeof(FirstPersonCharacterVariableUpdateSystem))]
[UpdateAfter(typeof(BuildCharacterRotationSystem))]
[BurstCompile]
public partial struct FirstPersonPlayerVariableStepControlSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<FirstPersonPlayer, FirstPersonPlayerCommands>().Build());
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
foreach (var (playerCommands, player) in SystemAPI.Query<FirstPersonPlayerCommands, FirstPersonPlayer>().WithAll<Simulate>())
{
if (SystemAPI.HasComponent<FirstPersonCharacterControl>(player.ControlledCharacter))
{
FirstPersonCharacterControl characterControl = SystemAPI.GetComponent<FirstPersonCharacterControl>(player.ControlledCharacter);
// Look
characterControl.LookYawPitchDegrees = playerCommands.LookInputDelta;
SystemAPI.SetComponent(player.ControlledCharacter, characterControl);
}
}
}
}
[UpdateInGroup(typeof(PredictedFixedStepSimulationSystemGroup), OrderFirst = true)]
[BurstCompile]
public partial struct FirstPersonPlayerFixedStepControlSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<FirstPersonPlayer, FirstPersonPlayerCommands>().Build());
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
foreach (var (playerCommands, player, commandInterpolationDelay, entity) in SystemAPI.Query<FirstPersonPlayerCommands, FirstPersonPlayer, CommandDataInterpolationDelay>().
{
// Character
if (SystemAPI.HasComponent<FirstPersonCharacterControl>(player.ControlledCharacter))
{
FirstPersonCharacterControl characterControl = SystemAPI.GetComponent<FirstPersonCharacterControl>(player.ControlledCharacter);
quaternion characterRotation = SystemAPI.GetComponent<LocalTransform>(player.ControlledCharacter).Rotation;
// Move
float3 characterForward = math.mul(characterRotation, math.forward());
float3 characterRight = math.mul(characterRotation, math.right());
characterControl.MoveVector = (playerCommands.MoveInput.y * characterForward) + (playerCommands.MoveInput.x * characterRight);
characterControl.MoveVector = MathUtilities.ClampToMaxLength(characterControl.MoveVector, 1f);
// Jump
characterControl.Jump = playerCommands.JumpPressed.IsSet;
SystemAPI.SetComponent(player.ControlledCharacter, characterControl);
}
// Weapon
if (SystemAPI.HasComponent<ActiveWeapon>(player.ControlledCharacter))
{
ActiveWeapon activeWeapon = SystemAPI.GetComponent<ActiveWeapon>(player.ControlledCharacter);
if (SystemAPI.HasComponent<WeaponControl>(activeWeapon.Entity))
{
WeaponControl weaponControl = SystemAPI.GetComponent<WeaponControl>(activeWeapon.Entity);
InterpolationDelay interpolationDelay = SystemAPI.GetComponent<InterpolationDelay>(activeWeapon.Entity);
// Shoot
weaponControl.FirePressed = playerCommands.ShootPressed.IsSet;
weaponControl.FireReleased = playerCommands.ShootReleased.IsSet;
// Aim
weaponControl.AimHeld = playerCommands.AimHeld;
// Interp delay
interpolationDelay.Value = commandInterpolationDelay.Delay;
SystemAPI.SetComponent(activeWeapon.Entity, weaponControl);
SystemAPI.SetComponent(activeWeapon.Entity, interpolationDelay);
}
}
}
}
}
OnlineFPS/Assets/Scripts/Weapons/ActiveWeaponSystem.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.NetCode;
using Unity.Transforms;
using UnityEngine;
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
ActiveWeaponSetupJob setupJob = new ActiveWeaponSetupJob
{
ECB = SystemAPI.GetSingletonRW<PostPredictionPreTransformsECBSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged),
WeaponControlLookup = SystemAPI.GetComponentLookup<WeaponControl>(true),
FirstPersonCharacterComponentLookup = SystemAPI.GetComponentLookup<FirstPersonCharacterComponent>(true),
WeaponSimulationShotOriginOverrideLookup = SystemAPI.GetComponentLookup<WeaponShotSimulationOriginOverride>(false),
LinkedEntityGroupLookup = SystemAPI.GetBufferLookup<LinkedEntityGroup>(false),
WeaponShotIgnoredEntityLookup = SystemAPI.GetBufferLookup<WeaponShotIgnoredEntity>(false),
};
setupJob.Schedule();
}
[BurstCompile]
public partial struct ActiveWeaponSetupJob : IJobEntity
{
public EntityCommandBuffer ECB;
[ReadOnly]
public ComponentLookup<WeaponControl> WeaponControlLookup;
[ReadOnly]
public ComponentLookup<FirstPersonCharacterComponent> FirstPersonCharacterComponentLookup;
public ComponentLookup<WeaponShotSimulationOriginOverride> WeaponSimulationShotOriginOverrideLookup;
public BufferLookup<LinkedEntityGroup> LinkedEntityGroupLookup;
public BufferLookup<WeaponShotIgnoredEntity> WeaponShotIgnoredEntityLookup;
activeWeapon.PreviousEntity = activeWeapon.Entity;
}
}
}
OnlineFPS/Assets/Scripts/Weapons/StandardRaycastWeapon.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Physics;
using Unity.Physics.GraphicsIntegration;
using UnityEngine;
using Random = Unity.Mathematics.Random;
using RaycastHit = Unity.Physics.RaycastHit;
[Serializable]
[GhostComponent()]
public struct StandardRaycastWeapon : IComponentData, IEnableableComponent
{
public Entity ShotOrigin;
public Entity ProjectileVisualPrefab;
// Calculation data
[GhostField()]
public Random Random;
[GhostField()]
public uint RemoteShotsCount;
public uint LastRemoteShotsCount;
}
[Serializable]
public struct StandardRaycastWeaponShotVFXRequest : IBufferElementData
{
public StandardRaycastWeaponShotVisualsData ShotVisualsData;
}
[Serializable]
public struct StandardRaycastWeaponShotVisualsData : IComponentData
{
public Entity VisualOriginEntity;
public float3 SimulationOrigin;
public float3 SimulationDirection;
public float3 SimulationUp;
public float SimulationHitDistance;
public RaycastHit Hit;
public float3 SolvedVisualOrigin;
public float3 SolvedVisualOriginToHit;
}
OnlineFPS/Assets/Scripts/Weapons/StandardRaycastWeaponAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Authoring;
using UnityEngine;
using Random = Unity.Mathematics.Random;
public class StandardRaycastWeaponAuthoring : MonoBehaviour
{
public StandardWeaponFiringMecanism.Authoring FiringMecanism = StandardWeaponFiringMecanism.Authoring.GetDefault();
public WeaponVisualFeedback.Authoring VisualFeedback = WeaponVisualFeedback.Authoring.GetDefault();
public GameObject ShotOrigin;
public GameObject ProjectileVisualPrefab;
public float Range = 1000f;
public float Damage = 1000f;
public float SpreadDegrees = 0f;
public int ProjectilesCount = 1;
public PhysicsCategoryTags HitCollisionFilter;
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
if (_hits.IsCreated)
{
_hits.Dispose();
}
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
StandardRaycastWeaponPredictionJob predictionJob = new StandardRaycastWeaponPredictionJob
{
IsServer = state.WorldUnmanaged.IsServer(),
NetworkTime = SystemAPI.GetSingleton<NetworkTime>(),
PhysicsWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().PhysicsWorld,
PhysicsWorldHistory = SystemAPI.GetSingleton<PhysicsWorldHistorySingleton>(),
HealthLookup = SystemAPI.GetComponentLookup<Health>(false),
Hits = _hits,
LocalTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(true),
ParentLookup = SystemAPI.GetComponentLookup<Parent>(true),
PostTransformMatrixLookup = SystemAPI.GetComponentLookup<PostTransformMatrix>(true),
};
predictionJob.Schedule();
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct StandardRaycastWeaponPredictionJob : IJobEntity
{
public bool IsServer;
public NetworkTime NetworkTime;
[ReadOnly]
public PhysicsWorld PhysicsWorld;
[ReadOnly]
public PhysicsWorldHistorySingleton PhysicsWorldHistory;
public ComponentLookup<Health> HealthLookup;
[ReadOnly]
public ComponentLookup<LocalTransform> LocalTransformLookup;
[ReadOnly]
public ComponentLookup<Parent> ParentLookup;
[ReadOnly]
public ComponentLookup<PostTransformMatrix> PostTransformMatrixLookup;
public NativeList<RaycastHit> Hits;
void Execute(
Entity entity,
ref StandardRaycastWeapon weapon,
ref WeaponVisualFeedback weaponFeedback,
ref DynamicBuffer<StandardRaycastWeaponShotVFXRequest> shotVFXRequestsBuffer,
in InterpolationDelay interpolationDelay,
in StandardWeaponFiringMecanism mecanism,
in WeaponShotSimulationOriginOverride shotSimulationOriginOverride,
in DynamicBuffer<WeaponShotIgnoredEntity> ignoredEntities)
{
PhysicsWorldHistory.GetCollisionWorldFromTick(NetworkTime.ServerTick, interpolationDelay.Value, ref PhysicsWorld, out var collisionWorld);
[Serializable]
public struct WeaponVisualFeedback : IComponentData
{
[Serializable]
public struct Authoring
{
[Header("Bobbing")]
public float WeaponBobHAmount;
public float WeaponBobVAmount;
public float WeaponBobFrequency;
public float WeaponBobSharpness;
public float WeaponBobAimRatio;
[Header("Recoil")]
public float RecoilStrength;
public float RecoilMaxDistance;
public float RecoilSharpness;
public float RecoilRestitutionSharpness;
[Header("Aiming")]
public float AimFOVRatio;
public float AimFOVSharpness;
public float LookSensitivityMultiplierWhileAiming;
[Header("FoV Kick")]
public float RecoilFOVKick;
public float RecoilMaxFOVKick;
public float RecoilFOVKickSharpness;
public float RecoilFOVKickRestitutionSharpness;
RecoilStrength = authoring.RecoilStrength;
RecoilMaxDistance = authoring.RecoilMaxDistance;
RecoilSharpness = authoring.RecoilSharpness;
RecoilRestitutionSharpness = authoring.RecoilRestitutionSharpness;
AimFOVRatio = authoring.AimFOVRatio;
AimFOVSharpness = authoring.AimFOVSharpness;
LookSensitivityMultiplierWhileAiming = authoring.LookSensitivityMultiplierWhileAiming;
RecoilFOVKick = authoring.RecoilFOVKick;
RecoilMaxFOVKick = authoring.RecoilMaxFOVKick;
RecoilFOVKickSharpness = authoring.RecoilFOVKickSharpness;
RecoilFOVKickRestitutionSharpness = authoring.RecoilFOVKickRestitutionSharpness;
ShotFeedbackRequests = 0;
}
public float WeaponBobHAmount;
public float WeaponBobVAmount;
public float WeaponBobFrequency;
public float WeaponBobSharpness;
public float WeaponBobAimRatio;
public float RecoilStrength;
public float RecoilMaxDistance;
public float RecoilSharpness;
public float RecoilRestitutionSharpness;
public float AimFOVRatio;
public float AimFOVSharpness;
public float LookSensitivityMultiplierWhileAiming;
public float RecoilFOVKick;
public float RecoilMaxFOVKick;
public float RecoilFOVKickSharpness;
public float RecoilFOVKickRestitutionSharpness;
[Serializable]
public struct WeaponShotIgnoredEntity : IBufferElementData
{
public Entity Entity;
}
OnlineFPS/Assets/Scripts/Weapons/WeaponFiringMecanisms.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.NetCode;
using UnityEngine;
[Serializable]
[GhostComponent()]
public struct StandardWeaponFiringMecanism : IComponentData, IEnableableComponent
{
[Serializable]
public struct Authoring
{
public bool AutoFire;
public float FiringRate;
[GhostField()]
public float ShotTimer;
[GhostField()]
public bool IsFiring;
public uint ShotsToFire;
}
OnlineFPS/Assets/Scripts/Weapons/WeaponFiringMecanismSystem.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
using UnityEngine;
using Random = Unity.Mathematics.Random;
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation | WorldSystemFilterFlags.ThinClientSimulation | WorldSystemFilterFlags.ServerSimulation)]
[UpdateInGroup(typeof(WeaponPredictionUpdateGroup), OrderFirst = true)]
[BurstCompile]
public partial struct WeaponFiringMecanismSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<StandardWeaponFiringMecanism, WeaponControl>().Build());
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
StandardWeaponFiringMecanismJob standardMecanismJob = new StandardWeaponFiringMecanismJob
{
DeltaTime = SystemAPI.Time.DeltaTime,
};
state.Dependency = standardMecanismJob.Schedule(state.Dependency);
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct StandardWeaponFiringMecanismJob : IJobEntity
{
public float DeltaTime;
void Execute(Entity entity, ref StandardWeaponFiringMecanism mecanism, ref WeaponControl weaponControl, in GhostOwner ghostOwner)
{
mecanism.ShotsToFire = 0;
mecanism.ShotTimer += DeltaTime;
// Detect starting to fire
if (weaponControl.FirePressed)
{
mecanism.IsFiring = true;
}
// Handle firing
if (mecanism.FiringRate > 0f)
{
float delayBetweenShots = 1f / mecanism.FiringRate;
// Clamp shot timer in order to shoot at most the maximum amount of shots that can be shot in one frame based on the firing rate.
// This also prevents needlessly dirtying the timer ghostfield (saves bandwidth).
mecanism.ShotTimer = math.clamp(mecanism.ShotTimer, 0f, math.max(delayBetweenShots + 0.01f ,DeltaTime));
// This loop is done to allow firing rates that would trigger more than one shot per tick
while (mecanism.IsFiring && mecanism.ShotTimer > delayBetweenShots)
{
mecanism.ShotsToFire++;
// Consume shoot time
mecanism.ShotTimer -= delayBetweenShots;
// Stop firing after initial shot for non-auto fire
if (!mecanism.Automatic)
{
mecanism.IsFiring = false;
}
}
}
// Detect stopping fire
if (!mecanism.Automatic || weaponControl.FireReleased)
{
mecanism.IsFiring = false;
}
}
}
}
OnlineFPS/Assets/Scripts/Weapons/WeaponPredictionUpdateGroup.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.NetCode;
using Unity.Physics.Systems;
using UnityEngine;
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation | WorldSystemFilterFlags.ThinClientSimulation | WorldSystemFilterFlags.ServerSimulation)]
[UpdateInGroup(typeof(PredictedFixedStepSimulationSystemGroup))]
[UpdateAfter(typeof(PhysicsSystemGroup))]
public partial class WeaponPredictionUpdateGroup : ComponentSystemGroup
{ }
OnlineFPS/Assets/Scripts/Weapons/WeaponUtilities.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Entities;
using Unity.CharacterController;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Physics;
using Unity.Transforms;
using UnityEngine;
using RaycastHit = Unity.Physics.RaycastHit;
public static class WeaponUtilities
{
public static void AddBasicWeaponBakingComponents<T>(Baker<T> baker) where T : MonoBehaviour
{
Entity entity = baker.GetEntity(TransformUsageFlags.Dynamic);
baker.AddComponent(entity, new WeaponControl());
baker.AddComponent(entity, new WeaponOwner());
baker.AddComponent(entity, new WeaponShotSimulationOriginOverride());
baker.AddBuffer<WeaponShotIgnoredEntity>(entity);
}
public static bool GetClosestValidWeaponRaycastHit(
in NativeList<RaycastHit> hits,
in DynamicBuffer<WeaponShotIgnoredEntity> ignoredEntities,
out RaycastHit closestValidHit)
{
closestValidHit = default;
closestValidHit.Fraction = float.MaxValue;
for (int j = 0; j < hits.Length; j++)
{
RaycastHit tmpHit = hits[j];
// Check closest so far
if (tmpHit.Fraction < closestValidHit.Fraction)
{
// Check collidable
if (PhysicsUtilities.IsCollidable(tmpHit.Material))
{
// Check entity ignore
bool entityValid = true;
for (int k = 0; k < ignoredEntities.Length; k++)
{
if (tmpHit.Entity == ignoredEntities[k].Entity)
{
entityValid = false;
break;
}
}
// Final hit
if (entityValid)
{
closestValidHit = tmpHit;
}
}
}
}
return closestValidHit.Entity != Entity.Null;
}
public static void ComputeShotDetails(
ref StandardRaycastWeapon weapon,
in WeaponShotSimulationOriginOverride shotSimulationOriginOverride,
in DynamicBuffer<WeaponShotIgnoredEntity> ignoredEntities,
ref NativeList<RaycastHit> Hits,
ref ComponentLookup<LocalTransform> localTransformLookup,
ref ComponentLookup<Parent> parentLookup,
ref ComponentLookup<PostTransformMatrix> postTransformMatrixLookup,
in CollisionWorld CollisionWorld,
bool computeShotVisuals,
out bool hitFound,
out RaycastHit closestValidHit,
out StandardRaycastWeaponShotVisualsData shotVisualsData)
{
hitFound = default;
closestValidHit = default;
shotVisualsData = default;
// In a FPS game, it is often desirable for the weapon shot raycast to start from the camera (screen center) rather than from the actual barrel of the weapon mesh.
// This is because it will precisely match the crosshair at the center of the screen.
// The shot "Simulation" represents the camera point for the raycast, while the shot "Visual" represents the point where the shot mesh is spawned.
Entity shotSimulationOriginEntity = localTransformLookup.HasComponent(shotSimulationOriginOverride.Entity) ? shotSimulationOriginOverride.Entity : weapon.ShotOrigin;
TransformHelpers.ComputeWorldTransformMatrix(shotSimulationOriginEntity, out float4x4 shotSimulationOriginTransform, ref localTransformLookup, ref parentLookup, ref postTransformMa
float3 shotSimulationOriginPosition = shotSimulationOriginTransform.Translation();
// Allow firing multiple projectiles per shot
for (int s = 0; s < weapon.ProjectilesCount; s++)
{
// Calculate spread
quaternion shotSpreadRotation = quaternion.identity;
if (weapon.SpreadRadians > 0f)
{
shotSpreadRotation = math.slerp(weapon.Random.NextQuaternionRotation(), quaternion.identity, (math.PI - math.clamp(weapon.SpreadRadians, 0f, math.PI)) / math.PI);
}
float3 finalShotSimulationDirection = math.rotate(shotSpreadRotation, shotSimulationOriginTransform.Forward());
// Hit detection
Hits.Clear();
RaycastInput rayInput = new RaycastInput
{
Start = shotSimulationOriginPosition,
End = shotSimulationOriginPosition + (finalShotSimulationDirection * weapon.Range),
Filter = weapon.HitCollisionFilter,
};
CollisionWorld.CastRay(rayInput, ref Hits);
hitFound = WeaponUtilities.GetClosestValidWeaponRaycastHit(in Hits, in ignoredEntities, out closestValidHit);
// Hit processing
float hitDistance = weapon.Range;
if (hitFound)
{
hitDistance = closestValidHit.Fraction * weapon.Range;
hitFound = true;
}
// Shot visuals
if(computeShotVisuals)
{
shotVisualsData = new StandardRaycastWeaponShotVisualsData
{
VisualOriginEntity = weapon.ShotOrigin,
SimulationOrigin = shotSimulationOriginPosition,
SimulationDirection = finalShotSimulationDirection,
SimulationUp = shotSimulationOriginTransform.Up(),
SimulationHitDistance = hitDistance,
Hit = closestValidHit,
};
}
}
}
}
OnlineFPS/Assets/Scripts/Weapons/Visuals/BulletShotVisuals.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
[UpdateInGroup(typeof(WeaponShotVisualsGroup))]
[UpdateAfter(typeof(WeaponShotVisualsSpawnECBSystem))]
[BurstCompile]
public partial struct BulletShotVisualsSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<BulletShotVisuals, StandardRaycastWeaponShotVisualsData>().Build());
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
BulletShotVisualsJob job = new BulletShotVisualsJob
{
DeltaTime = SystemAPI.Time.DeltaTime,
ECB = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter(),
};
job.ScheduleParallel();
}
[BurstCompile]
public partial struct BulletShotVisualsJob : IJobEntity
{
public float DeltaTime;
public EntityCommandBuffer.ParallelWriter ECB;
void Execute(Entity entity, [ChunkIndexInQuery] int chunkIndexInQuery, ref BulletShotVisuals shotVisuals, ref LocalTransform localTransform, ref LocalToWorld ltw, in StandardRaycas
{
if (!shotVisuals.IsInitialized)
{
// Hit VFX
if (shotData.Hit.Entity != Entity.Null)
{
Entity hitVisualsEntity = ECB.Instantiate(chunkIndexInQuery, shotVisuals.HitVisualsPrefab);
ECB.SetComponent(chunkIndexInQuery, hitVisualsEntity, LocalTransform.FromPositionRotation(shotData.Hit.Position, quaternion.LookRotationSafe(shotData.Hit.SurfaceNormal
}
// Orient bullet
localTransform.Rotation = quaternion.LookRotationSafe(shotData.SimulationDirection, math.up());
shotVisuals.IsInitialized = true;
}
// Speed
float3 movedDistance = math.mul(localTransform.Rotation, math.forward()) * shotVisuals.Speed * DeltaTime;
localTransform.Position += movedDistance;
shotVisuals.DistanceTraveled += math.length(movedDistance);
// Stretch
float zScale = math.clamp(shotVisuals.Speed * shotVisuals.StretchFromSpeed, 0f, math.min(shotVisuals.DistanceTraveled, shotVisuals.MaxStretch));
// On reached hit
if (shotVisuals.DistanceTraveled >= shotData.SimulationHitDistance)
{
// clamp position to max dist
float preClampDistFromOrigin = math.length(localTransform.Position - shotData.SolvedVisualOrigin);
localTransform.Position = shotData.SolvedVisualOrigin + shotData.SolvedVisualOriginToHit;
// adjust scale stretch for clamped pos
zScale *= math.length(localTransform.Position - shotData.SolvedVisualOrigin) / preClampDistFromOrigin;
ECB.DestroyEntity(chunkIndexInQuery, entity);
}
// Calculating the LocalToWorld manually since this is happening after the transforms group update
ltw.Value = float4x4.TRS(localTransform.Position, localTransform.Rotation, new float3(localTransform.Scale, localTransform.Scale, zScale));
}
}
}
OnlineFPS/Assets/Scripts/Weapons/Visuals/CharacterWeaponVisualFeedback.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[Serializable]
public struct CharacterWeaponVisualFeedback : IComponentData
{
public float3 WeaponLocalPosBob;
public float3 WeaponLocalPosRecoil;
public float CurrentRecoil;
public float TargetRecoilFOVKick;
public float CurrentRecoilFOVKick;
}
OnlineFPS/Assets/Scripts/Weapons/Visuals/CharacterWeaponVisualFeedbackSystem.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.CharacterController;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Transforms;
using UnityEngine;
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateBefore(typeof(TransformSystemGroup))]
[BurstCompile]
public partial struct CharacterWeaponVisualFeedbackSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{ }
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
CharacterWeaponVisualFeedbackJob job = new CharacterWeaponVisualFeedbackJob
{
DeltaTime = SystemAPI.Time.DeltaTime,
ElapsedTime = (float)SystemAPI.Time.ElapsedTime,
WeaponVisualFeedbackLookup = SystemAPI.GetComponentLookup<WeaponVisualFeedback>(true),
WeaponControlLookup = SystemAPI.GetComponentLookup<WeaponControl>(true),
LocalTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(false),
MainEntityCameraLookup = SystemAPI.GetComponentLookup<MainEntityCamera>(false),
};
job.Schedule();
}
[BurstCompile]
public partial struct CharacterWeaponVisualFeedbackJob : IJobEntity
{
public float DeltaTime;
public float ElapsedTime;
[ReadOnly]
public ComponentLookup<WeaponVisualFeedback> WeaponVisualFeedbackLookup;
[ReadOnly]
public ComponentLookup<WeaponControl> WeaponControlLookup;
public ComponentLookup<LocalTransform> LocalTransformLookup;
public ComponentLookup<MainEntityCamera> MainEntityCameraLookup;
void Execute(Entity entity, ref CharacterWeaponVisualFeedback characterWeaponVisualFeedback, in FirstPersonCharacterComponent character, in KinematicCharacterBody characterBody
{
bool isAiming = false;
float characterMaxSpeed = characterBody.IsGrounded ? character.GroundMaxSpeed : character.AirMaxSpeed;
if (WeaponVisualFeedbackLookup.TryGetComponent(activeWeapon.Entity, out WeaponVisualFeedback weaponFeedback))
{
float characterVelocityRatio = math.length(characterBody.RelativeVelocity) / characterMaxSpeed;
// Weapon bob
{
float3 targetBobPos = default;
if (characterBody.IsGrounded)
{
float bobSpeedMultiplier = isAiming ? weaponFeedback.WeaponBobAimRatio : 1f;
float hBob = math.sin(ElapsedTime * weaponFeedback.WeaponBobFrequency) * weaponFeedback.WeaponBobHAmount * bobSpeedMultiplier * characterVelocityRatio;
float vBob = ((math.sin(ElapsedTime * weaponFeedback.WeaponBobFrequency * 2f) * 0.5f) + 0.5f) * weaponFeedback.WeaponBobVAmount * bobSpeedMultiplier * characterVelo
targetBobPos = new float3(hBob, vBob, 0f);
}
characterWeaponVisualFeedback.WeaponLocalPosBob = math.lerp(characterWeaponVisualFeedback.WeaponLocalPosBob, targetBobPos, math.saturate(weaponFeedback.WeaponBobSharpne
}
// Weapon recoil
{
// Clamp current recoil
characterWeaponVisualFeedback.CurrentRecoil = math.clamp(characterWeaponVisualFeedback.CurrentRecoil, 0f, weaponFeedback.RecoilMaxDistance);
// go towards recoil
if (characterWeaponVisualFeedback.WeaponLocalPosRecoil.z >= -characterWeaponVisualFeedback.CurrentRecoil * 0.99f)
{
characterWeaponVisualFeedback.WeaponLocalPosRecoil = math.lerp(characterWeaponVisualFeedback.WeaponLocalPosRecoil, math.forward() * -characterWeaponVisualFeedback
}
// go towards restitution
else
{
characterWeaponVisualFeedback.WeaponLocalPosRecoil = math.lerp(characterWeaponVisualFeedback.WeaponLocalPosRecoil, float3.zero, math.saturate(weaponFeedback
characterWeaponVisualFeedback.CurrentRecoil = -characterWeaponVisualFeedback.WeaponLocalPosRecoil.z;
}
}
// Final weapon pose
float3 targetWeaponAnimSocketLocalPosition = characterWeaponVisualFeedback.WeaponLocalPosBob + characterWeaponVisualFeedback.WeaponLocalPosRecoil;
LocalTransformLookup[character.WeaponAnimationSocketEntity] = LocalTransform.FromPosition(targetWeaponAnimSocketLocalPosition);
// FoV modifications
if (MainEntityCameraLookup.TryGetComponent(character.ViewEntity, out MainEntityCamera entityCamera))
{
// FoV kick
{
// Clamp current
characterWeaponVisualFeedback.TargetRecoilFOVKick = math.clamp(characterWeaponVisualFeedback.TargetRecoilFOVKick, 0f, weaponFeedback.RecoilMaxFOVKick);
// FoV go towards recoil
if (characterWeaponVisualFeedback.CurrentRecoilFOVKick <= characterWeaponVisualFeedback.TargetRecoilFOVKick * 0.99f)
{
characterWeaponVisualFeedback.CurrentRecoilFOVKick = math.lerp(characterWeaponVisualFeedback.CurrentRecoilFOVKick, characterWeaponVisualFeedback.TargetRecoilFOV
}
// FoV go towards restitution
else
{
characterWeaponVisualFeedback.CurrentRecoilFOVKick = math.lerp(characterWeaponVisualFeedback.CurrentRecoilFOVKick, 0f, math.saturate(weaponFeedback.RecoilFOVKic
characterWeaponVisualFeedback.TargetRecoilFOVKick = characterWeaponVisualFeedback.CurrentRecoilFOVKick;
}
}
// Aiming
if (WeaponControlLookup.TryGetComponent(activeWeapon.Entity, out WeaponControl weaponControl))
{
float targetFOV = weaponControl.AimHeld ? (entityCamera.BaseFoV * weaponFeedback.AimFOVRatio) : entityCamera.BaseFoV;
entityCamera.CurrentFoV = math.lerp(entityCamera.CurrentFoV, targetFOV + characterWeaponVisualFeedback.CurrentRecoilFOVKick, math.saturate(weaponFeedback.AimFOVShar
}
MainEntityCameraLookup[character.ViewEntity] = entityCamera;
}
}
}
}
}
OnlineFPS/Assets/Scripts/Weapons/Visuals/LazerShotVisuals.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
public struct LazerShotVisuals : IComponentData
{
public float LifeTime;
public float Width;
public Entity HitVisualsPrefab;
[UpdateInGroup(typeof(WeaponShotVisualsGroup))]
[UpdateAfter(typeof(WeaponShotVisualsSpawnECBSystem))]
[BurstCompile]
public partial struct LazerShotVisualsSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<LazerShotVisuals, LocalTransform, PostTransformMatrix, StandardRaycastWeaponShotVisualsData>().Build());
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
LazerShotVisualsJob job = new LazerShotVisualsJob
{
ElapsedTime = (float)SystemAPI.Time.ElapsedTime,
ECB = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged),
};
job.Schedule();
}
[BurstCompile]
public partial struct LazerShotVisualsJob : IJobEntity
{
public float ElapsedTime;
public EntityCommandBuffer ECB;
void Execute(Entity entity, ref LazerShotVisuals shotVisuals, ref LocalTransform localTransform, ref LocalToWorld ltw, in StandardRaycastWeaponShotVisualsData shotData)
{
if (!shotVisuals.HasInitialized)
{
shotVisuals.StartTime = ElapsedTime;
// Scale
shotVisuals.StartingScale = new float3(shotVisuals.Width, shotVisuals.Width, math.length(shotData.SolvedVisualOriginToHit));
// Orientation
localTransform.Rotation = quaternion.LookRotationSafe(math.normalizesafe(shotData.SolvedVisualOriginToHit), shotData.SimulationUp);
// Hit VFX
if (shotData.Hit.Entity != Entity.Null)
{
Entity hitVisualsEntity = ECB.Instantiate(shotVisuals.HitVisualsPrefab);
ECB.SetComponent(hitVisualsEntity, LocalTransform.FromPositionRotation(shotData.Hit.Position, quaternion.LookRotationSafe(shotData.Hit.SurfaceNormal, math.up())));
}
shotVisuals.HasInitialized = true;
}
if (shotVisuals.LifeTime > 0f)
{
float timeRatio = (ElapsedTime - shotVisuals.StartTime) / shotVisuals.LifeTime;
float clampedTimeRatio = math.clamp(timeRatio, 0f, 1f);
float invTimeRatio = 1f - clampedTimeRatio;
if (timeRatio >= 1f)
{
ECB.DestroyEntity(entity);
}
// Calculating the LocalToWorld manually since this is happening after the transforms group update
ltw.Value = float4x4.TRS(localTransform.Position, localTransform.Rotation, new float3(shotVisuals.StartingScale.x * invTimeRatio, shotVisuals.StartingScale.y * invTimeRatio
}
else
{
ECB.DestroyEntity(entity);
}
}
}
}
OnlineFPS/Assets/Scripts/Weapons/Visuals/MachineGunVisuals.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateBefore(typeof(TransformSystemGroup))]
[BurstCompile]
public partial struct MachineGunVisualsSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{ }
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
MachineGunVisualsJob job = new MachineGunVisualsJob
{
DeltaTime = SystemAPI.Time.DeltaTime,
LocalTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(false),
};
job.Schedule();
}
[BurstCompile]
public partial struct MachineGunVisualsJob : IJobEntity
{
public float DeltaTime;
public ComponentLookup<LocalTransform> LocalTransformLookup;
void Execute(Entity entity, ref MachineGunVisuals visuals, in StandardWeaponFiringMecanism firingMecanism)
{
if (LocalTransformLookup.TryGetComponent(visuals.BarrelEntity, out LocalTransform localTransform))
{
if (firingMecanism.ShotsToFire > 0)
{
visuals.CurrentSpinVelocity = visuals.SpinVelocity;
}
else
{
visuals.CurrentSpinVelocity -= visuals.SpinVelocityDecay * DeltaTime;
visuals.CurrentSpinVelocity = math.clamp(visuals.CurrentSpinVelocity, 0f, float.MaxValue);
}
[BurstCompile]
public partial struct StandardRaycastWeaponShotVisualsJob : IJobEntity
{
public EntityCommandBuffer ECB;
public ComponentLookup<CharacterWeaponVisualFeedback> CharacterWeaponVisualFeedbackLookup;
[ReadOnly]
public ComponentLookup<LocalToWorld> LocalToWorldLookup;
void Execute(
Entity entity,
ref StandardRaycastWeapon weapon,
ref WeaponVisualFeedback weaponFeedback,
ref DynamicBuffer<StandardRaycastWeaponShotVFXRequest> shotVFXRequestsBuffer,
in WeaponOwner owner)
{
// Shot VFX
for (int i = 0; i < shotVFXRequestsBuffer.Length; i++)
{
StandardRaycastWeaponShotVisualsData shotVisualsData = shotVFXRequestsBuffer[i].ShotVisualsData;
if (LocalToWorldLookup.TryGetComponent(shotVisualsData.VisualOriginEntity, out LocalToWorld originLtW))
{
shotVisualsData.SolvedVisualOrigin = originLtW.Position;
shotVisualsData.SolvedVisualOriginToHit = (shotVisualsData.SimulationOrigin + (shotVisualsData.SimulationDirection * shotVisualsData.SimulationHitDistance)) -
Entity shotVisualsEntity = ECB.Instantiate(weapon.ProjectileVisualPrefab);
ECB.SetComponent(shotVisualsEntity, LocalTransform.FromPositionRotation(shotVisualsData.SolvedVisualOrigin, quaternion.LookRotationSafe(shotVisualsData.SimulationDirect
ECB.AddComponent(shotVisualsEntity, shotVisualsData);
}
}
shotVFXRequestsBuffer.Clear();
// Shot feedback
for (int i = 0; i < weaponFeedback.ShotFeedbackRequests; i++)
{
if (CharacterWeaponVisualFeedbackLookup.TryGetComponent(owner.Entity, out CharacterWeaponVisualFeedback characterFeedback))
{
characterFeedback.CurrentRecoil += weaponFeedback.RecoilStrength;
characterFeedback.TargetRecoilFOVKick += weaponFeedback.RecoilFOVKick;
CharacterWeaponVisualFeedbackLookup[owner.Entity] = characterFeedback;
}
}
weaponFeedback.ShotFeedbackRequests = 0;
}
}
}
OnlineFPS/Assets/Scripts/Weapons/Visuals/WeaponShotVisualsGroup.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Transforms;
using UnityEngine;
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(TransformSystemGroup))] // Updates after transforms, because shots must spawn at the interpolated weapon position
[UpdateBefore(typeof(EndSimulationEntityCommandBufferSystem))]
public partial class WeaponShotVisualsGroup : ComponentSystemGroup
{
}
[UpdateInGroup(typeof(WeaponShotVisualsGroup))]
public partial class WeaponShotVisualsSpawnECBSystem : EntityCommandBufferSystem
{
public unsafe struct Singleton : IComponentData, IECBSingleton
{
internal UnsafeList<EntityCommandBuffer>* pendingBuffers;
internal Allocator allocator;
public EntityCommandBuffer CreateCommandBuffer(WorldUnmanaged world)
{
return EntityCommandBufferSystem.CreateCommandBuffer(ref *pendingBuffers, allocator, world);
}
public void SetPendingBufferList(ref UnsafeList<EntityCommandBuffer> buffers)
{
pendingBuffers = (UnsafeList<EntityCommandBuffer>*)UnsafeUtility.AddressOf(ref buffers);
}
public void SetAllocator(Allocator allocatorIn)
{
allocator = allocatorIn;
}
}
protected override unsafe void OnCreate()
{
base.OnCreate();
ref UnsafeList<EntityCommandBuffer> pendingBuffers = ref *m_PendingBuffers;
this.RegisterSingleton<Singleton>(ref pendingBuffers, World.Unmanaged);
}
}
Platformer/Assets/Data/Inputs/PlatformerInputActions.cs
//------------------------------------------------------------------------------
// <auto-generated>
// This code was auto-generated by com.unity.inputsystem:InputActionCodeGenerator
// version 1.5.1
// from Assets/Data/Inputs/PlatformerInputActions.inputactions
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Utilities;
[Serializable]
public struct MainEntityCamera : IComponentData
{
}
Platformer/Assets/Scripts/Camera/MainEntityCameraAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
[DisallowMultipleComponent]
public class MainEntityCameraAuthoring : MonoBehaviour
{
public class Baker : Baker<MainEntityCameraAuthoring>
{
public override void Bake(MainEntityCameraAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent<MainEntityCamera>(entity);
}
}
}
Platformer/Assets/Scripts/Camera/MainGameObjectCamera.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
void Awake()
{
Instance = GetComponent<UnityEngine.Camera>();
}
}
Platformer/Assets/Scripts/Camera/OrbitCamera.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
[Serializable]
public struct OrbitCamera : IComponentData
{
[Header("Rotation")]
public float RotationSpeed;
public float MaxVAngle;
public float MinVAngle;
public bool RotateWithCharacterParent;
[Header("Zooming")]
public float TargetDistance;
public float MinDistance;
public float MaxDistance;
public float DistanceMovementSpeed;
public float DistanceMovementSharpness;
[Header("Obstructions")]
public float ObstructionRadius;
public float ObstructionInnerSmoothingSharpness;
public float ObstructionOuterSmoothingSharpness;
public bool PreventFixedUpdateJitter;
[Header("Misc")]
public float CameraTargetTransitionTime;
// Data in calculations
[HideInInspector]
public float CurrentDistanceFromMovement;
[HideInInspector]
public float CurrentDistanceFromObstruction;
[HideInInspector]
public float PitchAngle;
[HideInInspector]
public float3 PlanarForward;
[HideInInspector]
public Entity ActiveCameraTarget;
[HideInInspector]
public Entity PreviousCameraTarget;
[HideInInspector]
public float CameraTargetTransitionStartTime;
[HideInInspector]
public RigidTransform CameraTargetTransform;
[HideInInspector]
public RigidTransform CameraTargetTransitionFromTransform;
[HideInInspector]
public bool PreviousCalculateUpFromGravity;
public static OrbitCamera GetDefault()
{
OrbitCamera c = new OrbitCamera
{
RotationSpeed = 150f,
MaxVAngle = 89f,
MinVAngle = -89f,
TargetDistance = 5f,
MinDistance = 0f,
MaxDistance = 10f,
DistanceMovementSpeed = 50f,
DistanceMovementSharpness = 20f,
ObstructionRadius = 0.1f,
ObstructionInnerSmoothingSharpness = float.MaxValue,
ObstructionOuterSmoothingSharpness = 5f,
PreventFixedUpdateJitter = true,
CameraTargetTransitionTime = 0.4f,
CurrentDistanceFromMovement = 5f,
CurrentDistanceFromObstruction = 5f,
};
return c;
}
}
[Serializable]
public struct OrbitCameraControl : IComponentData
{
public Entity FollowedCharacterEntity;
public float2 Look;
public float Zoom;
}
[Serializable]
public struct OrbitCameraIgnoredEntityBufferElement : IBufferElementData
{
public Entity Entity;
}
Platformer/Assets/Scripts/Camera/OrbitCameraAuthoring.cs
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
[DisallowMultipleComponent]
public class OrbitCameraAuthoring : MonoBehaviour
{
public List<GameObject> IgnoredEntities = new List<GameObject>();
public OrbitCamera OrbitCamera = OrbitCamera.GetDefault();
public class Baker : Baker<OrbitCameraAuthoring>
{
public override void Bake(OrbitCameraAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
authoring.OrbitCamera.CurrentDistanceFromMovement = authoring.OrbitCamera.TargetDistance;
authoring.OrbitCamera.CurrentDistanceFromObstruction = authoring.OrbitCamera.TargetDistance;
authoring.OrbitCamera.PlanarForward = -math.forward();
AddComponent(entity, authoring.OrbitCamera);
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),
});
}
}
}
}
Platformer/Assets/Scripts/Camera/OrbitCameraSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Core;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Systems;
using Unity.Transforms;
using Unity.CharacterController;
using UnityEngine;
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(TransformSystemGroup))]
[UpdateBefore(typeof(EndSimulationEntityCommandBufferSystem))]
public partial struct OrbitCameraSystem : ISystem
{
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;
}
}
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<PhysicsWorldSingleton>();
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<OrbitCamera, OrbitCameraControl>().Build());
}
public void OnDestroy(ref SystemState state)
{
}
public void OnUpdate(ref SystemState state)
{
OrbitCameraJob job = new OrbitCameraJob
{
TimeData = SystemAPI.Time,
PhysicsWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().PhysicsWorld,
LocalToWorldLookup = SystemAPI.GetComponentLookup<LocalToWorld>(false),
KinematicCharacterBodyLookup = SystemAPI.GetComponentLookup<KinematicCharacterBody>(true),
PlatformerCharacterComponentLookup = SystemAPI.GetComponentLookup<PlatformerCharacterComponent>(true),
PlatformerCharacterStateMachineLookup = SystemAPI.GetComponentLookup<PlatformerCharacterStateMachine>(true),
CustomGravityLookup = SystemAPI.GetComponentLookup<CustomGravity>(true),
};
job.Schedule();
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct OrbitCameraJob : IJobEntity
{
public TimeData TimeData;
public PhysicsWorld PhysicsWorld;
public ComponentLookup<LocalToWorld> LocalToWorldLookup;
[ReadOnly] public ComponentLookup<KinematicCharacterBody> KinematicCharacterBodyLookup;
[ReadOnly] public ComponentLookup<PlatformerCharacterComponent> PlatformerCharacterComponentLookup;
[ReadOnly] public ComponentLookup<PlatformerCharacterStateMachine> PlatformerCharacterStateMachineLookup;
[ReadOnly] public ComponentLookup<CustomGravity> CustomGravityLookup;
void Execute(
Entity entity,
ref LocalTransform localTransform,
ref OrbitCamera orbitCamera,
in OrbitCameraControl cameraControl,
in DynamicBuffer<OrbitCameraIgnoredEntityBufferElement> ignoredEntitiesBuffer)
{
float elapsedTime = (float)TimeData.ElapsedTime;
if (LocalToWorldLookup.TryGetComponent(cameraControl.FollowedCharacterEntity, out LocalToWorld characterLTW) &&
CustomGravityLookup.TryGetComponent(cameraControl.FollowedCharacterEntity, out CustomGravity characterCustomGravity) &&
PlatformerCharacterComponentLookup.TryGetComponent(cameraControl.FollowedCharacterEntity, out PlatformerCharacterComponent characterComponent) &&
PlatformerCharacterStateMachineLookup.TryGetComponent(cameraControl.FollowedCharacterEntity, out PlatformerCharacterStateMachine characterStateMachine))
{
// Camera target handling
{
characterStateMachine.GetCameraParameters(characterStateMachine.CurrentState, in characterComponent, out Entity selectedCameraTarget, out bool calculateUpFromGravity
RigidTransform selectedCameraTargetTransform = default;
if (LocalToWorldLookup.TryGetComponent(selectedCameraTarget, out LocalToWorld camTargetLTW))
{
selectedCameraTargetTransform = new RigidTransform(camTargetLTW.Rotation, camTargetLTW.Position);
}
else
{
selectedCameraTargetTransform = new RigidTransform(characterLTW.Rotation, characterLTW.Position);
}
if (calculateUpFromGravity)
{
selectedCameraTargetTransform.rot = MathUtilities.CreateRotationWithUpPriority(math.normalizesafe(-characterCustomGravity.Gravity), math.mul(selectedCameraTargetTra
}
// Detect transition
if (orbitCamera.ActiveCameraTarget != selectedCameraTarget ||
orbitCamera.PreviousCalculateUpFromGravity != calculateUpFromGravity)
{
orbitCamera.CameraTargetTransitionStartTime = elapsedTime;
orbitCamera.CameraTargetTransitionFromTransform = orbitCamera.CameraTargetTransform;
orbitCamera.ActiveCameraTarget = selectedCameraTarget;
orbitCamera.PreviousCalculateUpFromGravity = calculateUpFromGravity;
}
// Update transitions
if (elapsedTime < orbitCamera.CameraTargetTransitionStartTime + orbitCamera.CameraTargetTransitionTime)
{
float3 previousCameraTargetPosition = default;
if (LocalToWorldLookup.TryGetComponent(orbitCamera.PreviousCameraTarget, out LocalToWorld previousCamTargetLTW))
{
previousCameraTargetPosition = previousCamTargetLTW.Position;
}
else
{
previousCameraTargetPosition = characterLTW.Position;
}
float transitionRatio = math.saturate((elapsedTime - orbitCamera.CameraTargetTransitionStartTime) / orbitCamera.CameraTargetTransitionTime);
orbitCamera.CameraTargetTransform.pos = math.lerp(previousCameraTargetPosition, selectedCameraTargetTransform.pos, transitionRatio);
orbitCamera.CameraTargetTransform.rot = math.slerp(orbitCamera.CameraTargetTransitionFromTransform.rot, selectedCameraTargetTransform.rot, transitionRatio);
}
else
{
orbitCamera.CameraTargetTransform = selectedCameraTargetTransform;
orbitCamera.PreviousCameraTarget = orbitCamera.ActiveCameraTarget;
}
}
float3 cameraTargetUp = math.mul(orbitCamera.CameraTargetTransform.rot, math.up());
// Rotation
{
localTransform.Rotation = quaternion.LookRotationSafe(orbitCamera.PlanarForward, cameraTargetUp);
// Handle rotating the camera along with character's parent entity (moving platform)
if (orbitCamera.RotateWithCharacterParent && KinematicCharacterBodyLookup.TryGetComponent(cameraControl.FollowedCharacterEntity, out KinematicCharacterBody characterBod
{
KinematicCharacterUtilities.AddVariableRateRotationFromFixedRateRotation(ref localTransform.Rotation, characterBody.RotationFromParent, TimeData.DeltaTime,
orbitCamera.PlanarForward = math.normalizesafe(MathUtilities.ProjectOnPlane(MathUtilities.GetForwardFromRotation(localTransform.Rotation), cameraTargetUp));
}
// Yaw
float yawAngleChange = cameraControl.Look.x * orbitCamera.RotationSpeed;
quaternion yawRotation = quaternion.Euler(cameraTargetUp * math.radians(yawAngleChange));
orbitCamera.PlanarForward = math.rotate(yawRotation, orbitCamera.PlanarForward);
// Pitch
orbitCamera.PitchAngle += -cameraControl.Look.y * orbitCamera.RotationSpeed;
orbitCamera.PitchAngle = math.clamp(orbitCamera.PitchAngle, orbitCamera.MinVAngle, orbitCamera.MaxVAngle);
quaternion pitchRotation = quaternion.Euler(math.right() * math.radians(orbitCamera.PitchAngle));
// Final rotation
localTransform.Rotation = quaternion.LookRotationSafe(orbitCamera.PlanarForward, cameraTargetUp);
localTransform.Rotation = math.mul(localTransform.Rotation, pitchRotation);
}
float3 cameraForward = MathUtilities.GetForwardFromRotation(localTransform.Rotation);
// Distance input
float desiredDistanceMovementFromInput = cameraControl.Zoom * orbitCamera.DistanceMovementSpeed;
orbitCamera.TargetDistance = math.clamp(orbitCamera.TargetDistance + desiredDistanceMovementFromInput, orbitCamera.MinDistance, orbitCamera.MaxDistance);
orbitCamera.CurrentDistanceFromMovement = math.lerp(orbitCamera.CurrentDistanceFromMovement, orbitCamera.TargetDistance, MathUtilities.GetSharpnessInterpolant(orbitCamera
// Obstructions
if (orbitCamera.ObstructionRadius > 0f)
{
float obstructionCheckDistance = orbitCamera.CurrentDistanceFromMovement;
CameraObstructionHitsCollector collector = new CameraObstructionHitsCollector(cameraControl.FollowedCharacterEntity, ignoredEntitiesBuffer, cameraForward);
PhysicsWorld.SphereCastCustom<CameraObstructionHitsCollector>(
orbitCamera.CameraTargetTransform.pos,
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))
{
hitBody.WorldFromBody = new RigidTransform(quaternion.LookRotationSafe(hitBodyLocalToWorld.Forward, hitBodyLocalToWorld.Up), hitBodyLocalToWorld.Position
collector = new CameraObstructionHitsCollector(cameraControl.FollowedCharacterEntity, ignoredEntitiesBuffer, cameraForward);
hitBody.SphereCastCustom<CameraObstructionHitsCollector>(
orbitCamera.CameraTargetTransform.pos,
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.CurrentDistanceFromObstruction < newObstructedDistance)
{
// Move outer
orbitCamera.CurrentDistanceFromObstruction = math.lerp(orbitCamera.CurrentDistanceFromObstruction, newObstructedDistance, MathUtilities.GetSharpnessInterpolant
}
else if (orbitCamera.CurrentDistanceFromObstruction > newObstructedDistance)
{
// Move inner
orbitCamera.CurrentDistanceFromObstruction = math.lerp(orbitCamera.CurrentDistanceFromObstruction, newObstructedDistance, MathUtilities.GetSharpnessInterpolant
}
}
else
{
orbitCamera.CurrentDistanceFromObstruction = orbitCamera.CurrentDistanceFromMovement;
}
// Calculate final camera position from targetposition + rotation + distance
localTransform.Position = orbitCamera.CameraTargetTransform.pos + (-cameraForward * orbitCamera.CurrentDistanceFromObstruction);
// Manually calculate the LocalToWorld since this is updating after the Transform systems, and the LtW is what rendering uses
LocalToWorld cameraLocalToWorld = new LocalToWorld();
cameraLocalToWorld.Value = new float4x4(localTransform.Rotation, localTransform.Position);
LocalToWorldLookup[entity] = cameraLocalToWorld;
}
}
}
}
Platformer/Assets/Scripts/Character/CharacterState.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
using Unity.CharacterController;
using Unity.Mathematics;
[Serializable]
public struct PlatformerCharacterAnimation : IComponentData
{
[HideInInspector] public int ClipIndexParameterHash;
[HideInInspector] public int IdleClip;
[HideInInspector] public int RunClip;
[HideInInspector] public int SprintClip;
[HideInInspector] public int InAirClip;
[HideInInspector] public int LedgeGrabMoveClip;
[HideInInspector] public int LedgeStandUpClip;
[HideInInspector] public int WallRunLeftClip;
[HideInInspector] public int WallRunRightClip;
[HideInInspector] public int CrouchIdleClip;
[HideInInspector] public int CrouchMoveClip;
[HideInInspector] public int ClimbingMoveClip;
[HideInInspector] public int SwimmingIdleClip;
[HideInInspector] public int SwimmingMoveClip;
[HideInInspector] public int DashClip;
[HideInInspector] public int RopeHangClip;
[HideInInspector] public int SlidingClip;
[DisallowMultipleComponent]
[RequireComponent(typeof(PhysicsShapeAuthoring))]
public class PlatformerCharacterAuthoring : MonoBehaviour
{
public AuthoringKinematicCharacterProperties CharacterProperties = AuthoringKinematicCharacterProperties.GetDefault();
public PlatformerCharacterComponent Character = default;
[Header("References")]
public GameObject MeshPrefab;
public GameObject DefaultCameraTarget;
public GameObject SwimmingCameraTarget;
public GameObject ClimbingCameraTarget;
public GameObject CrouchingCameraTarget;
public GameObject MeshRoot;
public GameObject RollballMesh;
public GameObject RopePrefab;
public GameObject SwimmingDetectionPoint;
public GameObject LedgeDetectionPoint;
[Header("Debug")]
public bool DebugStandingGeometry;
public bool DebugCrouchingGeometry;
public bool DebugRollingGeometry;
public bool DebugClimbingGeometry;
public bool DebugSwimmingGeometry;
AddComponent(entity, authoring.Character);
AddComponent(entity, new PlatformerCharacterControl());
AddComponent(entity, new PlatformerCharacterStateMachine());
AddComponentObject(entity, new PlatformerCharacterHybridData { MeshPrefab = authoring.MeshPrefab });
}
}
Gizmos.DrawWireSphere(bottomHemiCenter, capsuleGeo.Radius);
Gizmos.DrawWireSphere(topHemiCenter, capsuleGeo.Radius);
Gizmos.DrawLine(bottomHemiCenter + (characterFwd * capsuleGeo.Radius), topHemiCenter + (characterFwd * capsuleGeo.Radius));
Gizmos.DrawLine(bottomHemiCenter - (characterFwd * capsuleGeo.Radius), topHemiCenter - (characterFwd * capsuleGeo.Radius));
Gizmos.DrawLine(bottomHemiCenter + (characterRight * capsuleGeo.Radius), topHemiCenter + (characterRight * capsuleGeo.Radius));
Gizmos.DrawLine(bottomHemiCenter - (characterRight * capsuleGeo.Radius), topHemiCenter - (characterRight * capsuleGeo.Radius));
}
}
Platformer/Assets/Scripts/Character/PlatformerCharacterComponent.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using Unity.Physics.Authoring;
using Unity.CharacterController;
using Unity.Physics;
[Serializable]
public struct PlatformerCharacterComponent : IComponentData
{
[Header("References")]
public Entity DefaultCameraTargetEntity;
public Entity ClimbingCameraTargetEntity;
public Entity SwimmingCameraTargetEntity;
public Entity CrouchingCameraTargetEntity;
public Entity MeshRootEntity;
public Entity RopePrefabEntity;
public Entity RollballMeshEntity;
[Header("Ground movement")]
public float GroundRunMaxSpeed;
public float GroundSprintMaxSpeed;
public float GroundedMovementSharpness;
public float GroundedRotationSharpness;
[Header("Crouching")]
public float CrouchedMaxSpeed;
public float CrouchedMovementSharpness;
public float CrouchedRotationSharpness;
[Header("Air movement")]
public float AirAcceleration;
public float AirMaxSpeed;
public float AirDrag;
public float AirRotationSharpness;
[Header("Rolling")]
public float RollingAcceleration;
[Header("Wall run")]
public float WallRunAcceleration;
public float WallRunMaxSpeed;
public float WallRunDrag;
public float WallRunGravityFactor;
public float WallRunJumpRatioFromCharacterUp;
public float WallRunDetectionDistance;
[Header("Flying")]
public float FlyingMaxSpeed;
public float FlyingMovementSharpness;
[Header("Jumping")]
public float GroundJumpSpeed;
public float AirJumpSpeed;
public float WallRunJumpSpeed;
public float JumpHeldAcceleration;
public float MaxHeldJumpTime;
public byte MaxUngroundedJumps;
public float JumpAfterUngroundedGraceTime;
public float JumpBeforeGroundedGraceTime;
[Header("Ledge Detection")]
public float LedgeMoveSpeed;
public float LedgeRotationSharpness;
public float LedgeSurfaceProbingHeight;
public float LedgeSurfaceObstructionProbingHeight;
public float LedgeSideProbingLength;
[Header("Dashing")]
public float DashDuration;
public float DashSpeed;
[Header("Swimming")]
public float SwimmingAcceleration;
public float SwimmingMaxSpeed;
public float SwimmingDrag;
public float SwimmingRotationSharpness;
public float SwimmingStandUpDistanceFromSurface;
public float WaterDetectionDistance;
public float SwimmingJumpSpeed;
public float SwimmingSurfaceDiveThreshold;
[Header("RopeSwing")]
public float RopeSwingAcceleration;
public float RopeSwingMaxSpeed;
public float RopeSwingDrag;
public float RopeLength;
public float3 LocalRopeAnchorPoint;
[Header("Climbing")]
public float ClimbingDistanceFromSurface;
public float ClimbingSpeed;
public float ClimbingMovementSharpness;
public float ClimbingRotationSharpness;
// Create
foreach (var (characterAnimation, hybridData, entity) in SystemAPI.Query<RefRW<PlatformerCharacterAnimation>, PlatformerCharacterHybridData>()
.WithNone<PlatformerCharacterHybridLink>()
.WithEntityAccess())
{
GameObject tmpObject = GameObject.Instantiate(hybridData.MeshPrefab);
Animator animator = tmpObject.GetComponent<Animator>();
// Mesh enabling
if (characterStateMachine.CurrentState == CharacterState.Rolling)
{
if (hybridLink.Object.activeSelf)
{
hybridLink.Object.SetActive(false);
}
}
else
{
if (!hybridLink.Object.activeSelf)
{
hybridLink.Object.SetActive(true);
}
}
}
}
// Destroy
foreach (var (hybridLink, entity) in SystemAPI.Query<PlatformerCharacterHybridLink>()
.WithNone<PlatformerCharacterHybridData>()
.WithEntityAccess())
{
GameObject.Destroy(hybridLink.Object);
ecb.RemoveComponent<PlatformerCharacterHybridLink>(entity);
}
}
}
Platformer/Assets/Scripts/Character/PlatformerCharacterStateMachine.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.CharacterController;
using Unity.Mathematics;
using UnityEngine;
[Serializable]
public struct PlatformerCharacterStateMachine : IComponentData
{
public CharacterState CurrentState;
public CharacterState PreviousState;
public GroundMoveState GroundMoveState;
public CrouchedState CrouchedState;
public AirMoveState AirMoveState;
public WallRunState WallRunState;
public RollingState RollingState;
public ClimbingState ClimbingState;
public DashingState DashingState;
public SwimmingState SwimmingState;
public LedgeGrabState LedgeGrabState;
public LedgeStandingUpState LedgeStandingUpState;
public FlyingNoCollisionsState FlyingNoCollisionsState;
public RopeSwingState RopeSwingState;
public void TransitionToState(CharacterState newState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect
{
PreviousState = CurrentState;
CurrentState = newState;
OnStateExit(PreviousState, CurrentState, ref context, ref baseContext, in aspect);
OnStateEnter(CurrentState, PreviousState, ref context, ref baseContext, in aspect);
}
public void OnStateEnter(CharacterState state, CharacterState previousState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in
{
switch (state)
{
case CharacterState.GroundMove:
GroundMoveState.OnStateEnter(previousState, ref context, ref baseContext, in aspect);
break;
case CharacterState.Crouched:
CrouchedState.OnStateEnter(previousState, ref context, ref baseContext, in aspect);
break;
case CharacterState.AirMove:
AirMoveState.OnStateEnter(previousState, ref context, ref baseContext, in aspect);
break;
case CharacterState.WallRun:
WallRunState.OnStateEnter(previousState, ref context, ref baseContext, in aspect);
break;
case CharacterState.Rolling:
RollingState.OnStateEnter(previousState, ref context, ref baseContext, in aspect);
break;
case CharacterState.LedgeGrab:
LedgeGrabState.OnStateEnter(previousState, ref context, ref baseContext, in aspect);
break;
case CharacterState.LedgeStandingUp:
LedgeStandingUpState.OnStateEnter(previousState, ref context, ref baseContext, in aspect);
break;
case CharacterState.Dashing:
DashingState.OnStateEnter(previousState, ref context, ref baseContext, in aspect);
break;
case CharacterState.Swimming:
SwimmingState.OnStateEnter(previousState, ref context, ref baseContext, in aspect);
break;
case CharacterState.Climbing:
ClimbingState.OnStateEnter(previousState, ref context, ref baseContext, in aspect);
break;
case CharacterState.FlyingNoCollisions:
FlyingNoCollisionsState.OnStateEnter(previousState, ref context, ref baseContext, in aspect);
break;
case CharacterState.RopeSwing:
RopeSwingState.OnStateEnter(previousState, ref context, ref baseContext, in aspect);
break;
}
}
public void OnStateExit(CharacterState state, CharacterState newState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerChara
{
switch (state)
{
case CharacterState.GroundMove:
GroundMoveState.OnStateExit(newState, ref context, ref baseContext, in aspect);
break;
case CharacterState.Crouched:
CrouchedState.OnStateExit(newState, ref context, ref baseContext, in aspect);
break;
case CharacterState.AirMove:
AirMoveState.OnStateExit(newState, ref context, ref baseContext, in aspect);
break;
case CharacterState.WallRun:
WallRunState.OnStateExit(newState, ref context, ref baseContext, in aspect);
break;
case CharacterState.Rolling:
RollingState.OnStateExit(newState, ref context, ref baseContext, in aspect);
break;
case CharacterState.LedgeGrab:
LedgeGrabState.OnStateExit(newState, ref context, ref baseContext, in aspect);
break;
case CharacterState.LedgeStandingUp:
LedgeStandingUpState.OnStateExit(newState, ref context, ref baseContext, in aspect);
break;
case CharacterState.Dashing:
DashingState.OnStateExit(newState, ref context, ref baseContext, in aspect);
break;
case CharacterState.Swimming:
SwimmingState.OnStateExit(newState, ref context, ref baseContext, in aspect);
break;
case CharacterState.Climbing:
ClimbingState.OnStateExit(newState, ref context, ref baseContext, in aspect);
break;
case CharacterState.FlyingNoCollisions:
FlyingNoCollisionsState.OnStateExit(newState, ref context, ref baseContext, in aspect);
break;
case CharacterState.RopeSwing:
RopeSwingState.OnStateExit(newState, ref context, ref baseContext, in aspect);
break;
}
}
public void OnStatePhysicsUpdate(CharacterState state, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect
{
switch (state)
{
case CharacterState.GroundMove:
GroundMoveState.OnStatePhysicsUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.Crouched:
CrouchedState.OnStatePhysicsUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.AirMove:
AirMoveState.OnStatePhysicsUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.WallRun:
WallRunState.OnStatePhysicsUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.Rolling:
RollingState.OnStatePhysicsUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.LedgeGrab:
LedgeGrabState.OnStatePhysicsUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.LedgeStandingUp:
LedgeStandingUpState.OnStatePhysicsUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.Dashing:
DashingState.OnStatePhysicsUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.Swimming:
SwimmingState.OnStatePhysicsUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.Climbing:
ClimbingState.OnStatePhysicsUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.FlyingNoCollisions:
FlyingNoCollisionsState.OnStatePhysicsUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.RopeSwing:
RopeSwingState.OnStatePhysicsUpdate(ref context, ref baseContext, in aspect);
break;
}
}
public void OnStateVariableUpdate(CharacterState state, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect
{
switch (state)
{
case CharacterState.GroundMove:
GroundMoveState.OnStateVariableUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.Crouched:
CrouchedState.OnStateVariableUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.AirMove:
AirMoveState.OnStateVariableUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.WallRun:
WallRunState.OnStateVariableUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.Rolling:
RollingState.OnStateVariableUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.LedgeGrab:
LedgeGrabState.OnStateVariableUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.LedgeStandingUp:
LedgeStandingUpState.OnStateVariableUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.Dashing:
DashingState.OnStateVariableUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.Swimming:
SwimmingState.OnStateVariableUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.Climbing:
ClimbingState.OnStateVariableUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.FlyingNoCollisions:
FlyingNoCollisionsState.OnStateVariableUpdate(ref context, ref baseContext, in aspect);
break;
case CharacterState.RopeSwing:
RopeSwingState.OnStateVariableUpdate(ref context, ref baseContext, in aspect);
break;
}
}
public void GetCameraParameters(CharacterState state, in PlatformerCharacterComponent character, out Entity cameraTarget, out bool calculateUpFromGravity)
{
cameraTarget = default;
calculateUpFromGravity = default;
switch (state)
{
case CharacterState.GroundMove:
GroundMoveState.GetCameraParameters(in character, out cameraTarget, out calculateUpFromGravity);
break;
case CharacterState.Crouched:
CrouchedState.GetCameraParameters(in character, out cameraTarget, out calculateUpFromGravity);
break;
case CharacterState.AirMove:
AirMoveState.GetCameraParameters(in character, out cameraTarget, out calculateUpFromGravity);
break;
case CharacterState.WallRun:
WallRunState.GetCameraParameters(in character, out cameraTarget, out calculateUpFromGravity);
break;
case CharacterState.Rolling:
RollingState.GetCameraParameters(in character, out cameraTarget, out calculateUpFromGravity);
break;
case CharacterState.LedgeGrab:
LedgeGrabState.GetCameraParameters(in character, out cameraTarget, out calculateUpFromGravity);
break;
case CharacterState.LedgeStandingUp:
LedgeStandingUpState.GetCameraParameters(in character, out cameraTarget, out calculateUpFromGravity);
break;
case CharacterState.Dashing:
DashingState.GetCameraParameters(in character, out cameraTarget, out calculateUpFromGravity);
break;
case CharacterState.Swimming:
SwimmingState.GetCameraParameters(in character, out cameraTarget, out calculateUpFromGravity);
break;
case CharacterState.Climbing:
ClimbingState.GetCameraParameters(in character, out cameraTarget, out calculateUpFromGravity);
break;
case CharacterState.FlyingNoCollisions:
FlyingNoCollisionsState.GetCameraParameters(in character, out cameraTarget, out calculateUpFromGravity);
break;
case CharacterState.RopeSwing:
RopeSwingState.GetCameraParameters(in character, out cameraTarget, out calculateUpFromGravity);
break;
}
}
public void GetMoveVectorFromPlayerInput(CharacterState state, in PlatformerPlayerInputs inputs, quaternion cameraRotation, out float3 moveVector)
{
moveVector = default;
switch (state)
{
case CharacterState.GroundMove:
GroundMoveState.GetMoveVectorFromPlayerInput(in inputs, cameraRotation, out moveVector);
break;
case CharacterState.Crouched:
CrouchedState.GetMoveVectorFromPlayerInput(in inputs, cameraRotation, out moveVector);
break;
case CharacterState.AirMove:
AirMoveState.GetMoveVectorFromPlayerInput(in inputs, cameraRotation, out moveVector);
break;
case CharacterState.WallRun:
WallRunState.GetMoveVectorFromPlayerInput(in inputs, cameraRotation, out moveVector);
break;
case CharacterState.Rolling:
RollingState.GetMoveVectorFromPlayerInput(in inputs, cameraRotation, out moveVector);
break;
case CharacterState.LedgeGrab:
LedgeGrabState.GetMoveVectorFromPlayerInput(in inputs, cameraRotation, out moveVector);
break;
case CharacterState.LedgeStandingUp:
LedgeStandingUpState.GetMoveVectorFromPlayerInput(in inputs, cameraRotation, out moveVector);
break;
case CharacterState.Dashing:
DashingState.GetMoveVectorFromPlayerInput(in inputs, cameraRotation, out moveVector);
break;
case CharacterState.Swimming:
SwimmingState.GetMoveVectorFromPlayerInput(in inputs, cameraRotation, out moveVector);
break;
case CharacterState.Climbing:
ClimbingState.GetMoveVectorFromPlayerInput(in inputs, cameraRotation, out moveVector);
break;
case CharacterState.FlyingNoCollisions:
FlyingNoCollisionsState.GetMoveVectorFromPlayerInput(in inputs, cameraRotation, out moveVector);
break;
case CharacterState.RopeSwing:
RopeSwingState.GetMoveVectorFromPlayerInput(in inputs, cameraRotation, out moveVector);
break;
}
}
}
Platformer/Assets/Scripts/Character/PlatformerCharacterSystems.cs
using System;
using Unity.Burst;
using Unity.Burst.Intrinsics;
using Unity.Entities;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Transforms;
using Unity.CharacterController;
[UpdateInGroup(typeof(InitializationSystemGroup))]
[RequireMatchingQueriesForUpdate]
[BurstCompile]
public partial struct PlatformerCharacterInitializationSystem : ISystem
{
public void OnCreate(ref SystemState state)
{ }
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<EndSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged);
BufferLookup<LinkedEntityGroup> linkedEntitiesLookup = SystemAPI.GetBufferLookup<LinkedEntityGroup>(true);
foreach (var (character, stateMachine, entity) in SystemAPI.Query<RefRW<PlatformerCharacterComponent>, RefRW<PlatformerCharacterStateMachine>>().WithNone<PlatformerCharacterInitial
{
// Make sure the transform system has done a pass on it first
if (linkedEntitiesLookup.HasBuffer(entity))
{
// Disable alternative meshes
PlatformerUtilities.SetEntityHierarchyEnabled(false, character.ValueRO.RollballMeshEntity, ecb, linkedEntitiesLookup);
ecb.AddComponent<PlatformerCharacterInitialized>(entity);
}
}
}
}
[UpdateInGroup(typeof(KinematicCharacterPhysicsUpdateGroup))]
[BurstCompile]
public partial struct PlatformerCharacterPhysicsUpdateSystem : ISystem
{
private EntityQuery _characterQuery;
private PlatformerCharacterUpdateContext _context;
private KinematicCharacterUpdateContext _baseContext;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
_characterQuery = KinematicCharacterUtilities.GetBaseCharacterQueryBuilder()
.WithAll<
PlatformerCharacterComponent,
PlatformerCharacterControl,
PlatformerCharacterStateMachine>()
.Build(ref state);
_context = new PlatformerCharacterUpdateContext();
_context.OnSystemCreate(ref state);
_baseContext = new KinematicCharacterUpdateContext();
_baseContext.OnSystemCreate(ref state);
state.RequireForUpdate(_characterQuery);
state.RequireForUpdate<PhysicsWorldSingleton>();
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
_context.OnSystemUpdate(ref state, SystemAPI.GetSingletonRW<EndSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged));
_baseContext.OnSystemUpdate(ref state, SystemAPI.Time, SystemAPI.GetSingleton<PhysicsWorldSingleton>());
PlatformerCharacterPhysicsUpdateJob job = new PlatformerCharacterPhysicsUpdateJob
{
Context = _context,
BaseContext = _baseContext,
};
job.ScheduleParallel();
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct PlatformerCharacterPhysicsUpdateJob : IJobEntity, IJobEntityChunkBeginEnd
{
public PlatformerCharacterUpdateContext Context;
public KinematicCharacterUpdateContext BaseContext;
void Execute([ChunkIndexInQuery] int chunkIndex, PlatformerCharacterAspect characterAspect)
{
Context.SetChunkIndex(chunkIndex);
characterAspect.PhysicsUpdate(ref Context, ref BaseContext);
}
public bool OnChunkBegin(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
BaseContext.EnsureCreationOfTmpCollections();
return true;
}
public void OnChunkEnd(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask, bool chunkWasExecuted)
{ }
}
}
[UpdateInGroup(typeof(KinematicCharacterVariableUpdateGroup))]
[BurstCompile]
public partial struct PlatformerCharacterVariableUpdateSystem : ISystem
{
private EntityQuery _characterQuery;
private PlatformerCharacterUpdateContext _context;
private KinematicCharacterUpdateContext _baseContext;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
_characterQuery = KinematicCharacterUtilities.GetBaseCharacterQueryBuilder()
.WithAll<
PlatformerCharacterComponent,
PlatformerCharacterControl>()
.Build(ref state);
_context = new PlatformerCharacterUpdateContext();
_context.OnSystemCreate(ref state);
_baseContext = new KinematicCharacterUpdateContext();
_baseContext.OnSystemCreate(ref state);
state.RequireForUpdate(_characterQuery);
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
_context.OnSystemUpdate(ref state, SystemAPI.GetSingletonRW<EndSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged));
_baseContext.OnSystemUpdate(ref state, SystemAPI.Time, SystemAPI.GetSingleton<PhysicsWorldSingleton>());
PlatformerCharacterVariableUpdateJob job = new PlatformerCharacterVariableUpdateJob
{
Context = _context,
BaseContext = _baseContext,
};
job.ScheduleParallel();
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct PlatformerCharacterVariableUpdateJob : IJobEntity, IJobEntityChunkBeginEnd
{
public PlatformerCharacterUpdateContext Context;
public KinematicCharacterUpdateContext BaseContext;
void Execute([ChunkIndexInQuery] int chunkIndex, PlatformerCharacterAspect characterAspect)
{
Context.SetChunkIndex(chunkIndex);
characterAspect.VariableUpdate(ref Context, ref BaseContext);
}
public bool OnChunkBegin(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
BaseContext.EnsureCreationOfTmpCollections();
return true;
}
public void OnChunkEnd(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask, bool chunkWasExecuted)
{ }
}
}
Platformer/Assets/Scripts/Character/States/AirMoveState.cs
using Unity.Mathematics;
using Unity.Physics;
using Unity.Entities;
using Unity.CharacterController;
public struct AirMoveState : IPlatformerCharacterState
{
public void OnStateEnter(CharacterState previousState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect
{
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
aspect.SetCapsuleGeometry(character.StandingGeometry.ToCapsuleGeometry());
}
public void OnStateExit(CharacterState nextState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect
{ }
public void OnStatePhysicsUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
float deltaTime = baseContext.Time.DeltaTime;
float elapsedTime = (float)baseContext.Time.ElapsedTime;
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref PlatformerCharacterControl characterControl = ref aspect.CharacterControl.ValueRW;
CustomGravity customGravity = aspect.CustomGravity.ValueRO;
aspect.HandlePhysicsUpdatePhase1(ref context, ref baseContext, true, true);
// Move
float3 airAcceleration = characterControl.MoveVector * character.AirAcceleration;
if (math.lengthsq(airAcceleration) > 0f)
{
float3 tmpVelocity = characterBody.RelativeVelocity;
CharacterControlUtilities.StandardAirMove(ref characterBody.RelativeVelocity, airAcceleration, character.AirMaxSpeed, characterBody.GroundingUp, deltaTime, false);
// Cancel air acceleration from input if we would hit a non-grounded surface (prevents air-climbing slopes at high air accelerations)
if (aspect.CharacterAspect.MovementWouldHitNonGroundedObstruction(in aspect, ref context, ref baseContext, characterBody.RelativeVelocity * deltaTime, out ColliderCastHit
{
characterBody.RelativeVelocity = tmpVelocity;
character.HasDetectedMoveAgainstWall = true;
character.LastKnownWallNormal = hit.SurfaceNormal;
}
}
// Jumping
{
if (characterControl.JumpPressed)
{
// Allow jumping shortly after getting degrounded
if (character.AllowJumpAfterBecameUngrounded && elapsedTime < character.LastTimeWasGrounded + character.JumpAfterUngroundedGraceTime)
{
CharacterControlUtilities.StandardJump(ref characterBody, characterBody.GroundingUp * character.GroundJumpSpeed, true, characterBody.GroundingUp);
character.HeldJumpTimeCounter = 0f;
}
// Air jumps
else if (character.CurrentUngroundedJumps < character.MaxUngroundedJumps)
{
CharacterControlUtilities.StandardJump(ref characterBody, characterBody.GroundingUp * character.AirJumpSpeed, true, characterBody.GroundingUp);
character.CurrentUngroundedJumps++;
}
// Remember that we wanted to jump before we became grounded
else
{
character.JumpPressedBeforeBecameGrounded = true;
}
character.AllowJumpAfterBecameUngrounded = false;
}
// Additional jump power when holding jump
if (character.AllowHeldJumpInAir && characterControl.JumpHeld && character.HeldJumpTimeCounter < character.MaxHeldJumpTime)
{
characterBody.RelativeVelocity += characterBody.GroundingUp * character.JumpHeldAcceleration * deltaTime;
}
}
// Gravity
CharacterControlUtilities.AccelerateVelocity(ref characterBody.RelativeVelocity, customGravity.Gravity, deltaTime);
// Drag
CharacterControlUtilities.ApplyDragToVelocity(ref characterBody.RelativeVelocity, deltaTime, character.AirDrag);
aspect.HandlePhysicsUpdatePhase2(ref context, ref baseContext, true, true, true, true, true);
DetectTransitions(ref context, ref baseContext, in aspect);
}
public void OnStateVariableUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
float deltaTime = baseContext.Time.DeltaTime;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref PlatformerCharacterControl characterControl = ref aspect.CharacterControl.ValueRW;
ref quaternion characterRotation = ref aspect.CharacterAspect.LocalTransform.ValueRW.Rotation;
CustomGravity customGravity = aspect.CustomGravity.ValueRO;
if (math.lengthsq(characterControl.MoveVector) > 0f)
{
CharacterControlUtilities.SlerpRotationTowardsDirectionAroundUp(ref characterRotation, deltaTime, math.normalizesafe(characterControl.MoveVector), MathUtilities.GetUpFromRotati
}
CharacterControlUtilities.SlerpCharacterUpTowardsDirection(ref characterRotation, deltaTime, math.normalizesafe(-customGravity.Gravity), character.UpOrientationAdaptationSharpness
}
public void GetCameraParameters(in PlatformerCharacterComponent character, out Entity cameraTarget, out bool calculateUpFromGravity)
{
cameraTarget = character.DefaultCameraTargetEntity;
calculateUpFromGravity = true;
}
public void GetMoveVectorFromPlayerInput(in PlatformerPlayerInputs inputs, quaternion cameraRotation, out float3 moveVector)
{
PlatformerCharacterAspect.GetCommonMoveVectorFromPlayerInput(in inputs, cameraRotation, out moveVector);
}
public bool DetectTransitions(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref PlatformerCharacterControl characterControl = ref aspect.CharacterControl.ValueRW;
ref PlatformerCharacterStateMachine stateMachine = ref aspect.StateMachine.ValueRW;
if (characterControl.RopePressed && RopeSwingState.DetectRopePoints(in baseContext.PhysicsWorld, in aspect, out float3 detectedRopeAnchorPoint))
{
stateMachine.RopeSwingState.AnchorPoint = detectedRopeAnchorPoint;
stateMachine.TransitionToState(CharacterState.RopeSwing, ref context, ref baseContext, in aspect);
return true;
}
if (characterControl.RollHeld)
{
stateMachine.TransitionToState(CharacterState.Rolling, ref context, ref baseContext, in aspect);
return true;
}
if (characterControl.DashPressed)
{
stateMachine.TransitionToState(CharacterState.Dashing, ref context, ref baseContext, in aspect);
return true;
}
if (characterBody.IsGrounded)
{
stateMachine.TransitionToState(CharacterState.GroundMove, ref context, ref baseContext, in aspect);
return true;
}
if (characterControl.SprintHeld && character.HasDetectedMoveAgainstWall)
{
stateMachine.TransitionToState(CharacterState.WallRun, ref context, ref baseContext, in aspect);
return true;
}
if (LedgeGrabState.CanGrabLedge(ref context, ref baseContext, in aspect, out Entity ledgeEntity, out ColliderCastHit ledgeSurfaceHit))
{
stateMachine.TransitionToState(CharacterState.LedgeGrab, ref context, ref baseContext, in aspect);
aspect.CharacterAspect.SetOrUpdateParentBody(ref baseContext, ref characterBody, ledgeEntity, ledgeSurfaceHit.Position);
return true;
}
if (characterControl.ClimbPressed)
{
if (ClimbingState.CanStartClimbing(ref context, ref baseContext, in aspect))
{
stateMachine.TransitionToState(CharacterState.Climbing, ref context, ref baseContext, in aspect);
return true;
}
}
// Movement
float3 targetVelocity = characterControl.MoveVector * character.FlyingMaxSpeed;
CharacterControlUtilities.InterpolateVelocityTowardsTarget(ref characterBody.RelativeVelocity, targetVelocity, deltaTime, character.FlyingMovementSharpness);
characterPosition += characterBody.RelativeVelocity * deltaTime;
aspect.DetectGlobalTransitions(ref context, ref baseContext);
}
public void OnStateVariableUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
ref quaternion characterRotation = ref aspect.CharacterAspect.LocalTransform.ValueRW.Rotation;
characterRotation = quaternion.identity;
}
public void GetCameraParameters(in PlatformerCharacterComponent character, out Entity cameraTarget, out bool calculateUpFromGravity)
{
cameraTarget = character.DefaultCameraTargetEntity;
calculateUpFromGravity = false;
}
public void GetMoveVectorFromPlayerInput(in PlatformerPlayerInputs inputs, quaternion cameraRotation, out float3 moveVector)
{
PlatformerCharacterAspect.GetCommonMoveVectorFromPlayerInput(in inputs, cameraRotation, out moveVector);
float verticalInput = (inputs.JumpHeld ? 1f : 0f) + (inputs.RollHeld ? -1f : 0f);
moveVector = MathUtilities.ClampToMaxLength(moveVector + (math.mul(cameraRotation, math.up()) * verticalInput), 1f);
}
}
Platformer/Assets/Scripts/Character/States/GroundMoveState.cs
using Unity.Entities;
using Unity.CharacterController;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Transforms;
public struct GroundMoveState : IPlatformerCharacterState
{
public void OnStateEnter(CharacterState previousState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect
{
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
aspect.SetCapsuleGeometry(character.StandingGeometry.ToCapsuleGeometry());
}
public void OnStateExit(CharacterState nextState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect
{
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
character.IsOnStickySurface = false;
character.IsSprinting = false;
}
public void OnStatePhysicsUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
float deltaTime = baseContext.Time.DeltaTime;
float elapsedTime = (float)baseContext.Time.ElapsedTime;
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref PlatformerCharacterControl characterControl = ref aspect.CharacterControl.ValueRW;
aspect.HandlePhysicsUpdatePhase1(ref context, ref baseContext, true, true);
// Rotate move input and velocity to take into account parent rotation
if(characterBody.ParentEntity != Entity.Null)
{
characterControl.MoveVector = math.rotate(characterBody.RotationFromParent, characterControl.MoveVector);
characterBody.RelativeVelocity = math.rotate(characterBody.RotationFromParent, characterBody.RelativeVelocity);
}
if (characterBody.IsGrounded)
{
character.IsSprinting = characterControl.SprintHeld;
// Move on ground
{
float chosenMaxSpeed = character.IsSprinting ? character.GroundSprintMaxSpeed : character.GroundRunMaxSpeed;
float chosenSharpness = character.GroundedMovementSharpness;
if (context.CharacterFrictionModifierLookup.TryGetComponent(characterBody.GroundHit.Entity, out CharacterFrictionModifier frictionModifier))
{
chosenSharpness *= frictionModifier.Friction;
}
float3 moveVectorOnPlane = math.normalizesafe(MathUtilities.ProjectOnPlane(characterControl.MoveVector, characterBody.GroundingUp)) * math.length(characterControl.
float3 targetVelocity = moveVectorOnPlane * chosenMaxSpeed;
CharacterControlUtilities.StandardGroundMove_Interpolated(ref characterBody.RelativeVelocity, targetVelocity, chosenSharpness, deltaTime, characterBody.GroundingUp
}
// Jumping
if (characterControl.JumpPressed ||
(character.JumpPressedBeforeBecameGrounded && elapsedTime < character.LastTimeJumpPressed + character.JumpBeforeGroundedGraceTime)) // this is for allowing jumps that were
{
CharacterControlUtilities.StandardJump(ref characterBody, characterBody.GroundingUp * character.GroundJumpSpeed, true, characterBody.GroundingUp);
character.AllowJumpAfterBecameUngrounded = false;
}
}
aspect.HandlePhysicsUpdatePhase2(ref context, ref baseContext, true, true, true, true, true);
DetectTransitions(ref context, ref baseContext, in aspect);
}
public void OnStateVariableUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
float deltaTime = baseContext.Time.DeltaTime;
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref PlatformerCharacterControl characterControl = ref aspect.CharacterControl.ValueRW;
ref quaternion characterRotation = ref aspect.CharacterAspect.LocalTransform.ValueRW.Rotation;
CustomGravity customGravity = aspect.CustomGravity.ValueRO;
aspect.SetCapsuleGeometry(character.StandingGeometry.ToCapsuleGeometry());
characterBody.RelativeVelocity = default;
characterBody.IsGrounded = false;
characterProperties.EvaluateGrounding = false;
characterProperties.DetectMovementCollisions = false;
characterProperties.DecollideFromOverlaps = false;
ShouldExitState = false;
}
public void OnStateExit(CharacterState nextState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect
{
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref KinematicCharacterProperties characterProperties = ref aspect.CharacterAspect.CharacterProperties.ValueRW;
characterProperties.EvaluateGrounding = true;
characterProperties.DetectMovementCollisions = true;
characterProperties.DecollideFromOverlaps = true;
characterProperties.EvaluateGrounding = false;
// Spawn rope
Entity ropeInstanceEntity = context.EndFrameECB.Instantiate(context.ChunkIndex, character.RopePrefabEntity);
context.EndFrameECB.AddComponent(context.ChunkIndex, ropeInstanceEntity, new CharacterRope { OwningCharacterEntity = entity });
}
public void OnStateExit(CharacterState nextState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect
{
ref KinematicCharacterProperties characterProperties = ref aspect.CharacterAspect.CharacterProperties.ValueRW;
characterProperties.EvaluateGrounding = true;
// Note: rope despawning is handled by the rope system itself
}
public void OnStatePhysicsUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
float deltaTime = baseContext.Time.DeltaTime;
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref PlatformerCharacterControl characterControl = ref aspect.CharacterControl.ValueRW;
ref float3 characterPosition = ref aspect.CharacterAspect.LocalTransform.ValueRW.Position;
CustomGravity customGravity = aspect.CustomGravity.ValueRO;
quaternion characterRotation = aspect.CharacterAspect.LocalTransform.ValueRW.Rotation;
aspect.HandlePhysicsUpdatePhase1(ref context, ref baseContext, false, false);
// Move
float3 moveVectorOnPlane = math.normalizesafe(MathUtilities.ProjectOnPlane(characterControl.MoveVector, characterBody.GroundingUp)) * math.length(characterControl.MoveVector
float3 acceleration = moveVectorOnPlane * character.RopeSwingAcceleration;
CharacterControlUtilities.StandardAirMove(ref characterBody.RelativeVelocity, acceleration, character.RopeSwingMaxSpeed, characterBody.GroundingUp, deltaTime, false);
// Gravity
CharacterControlUtilities.AccelerateVelocity(ref characterBody.RelativeVelocity, customGravity.Gravity, deltaTime);
// Drag
CharacterControlUtilities.ApplyDragToVelocity(ref characterBody.RelativeVelocity, deltaTime, character.RopeSwingDrag);
// Rope constraint
RigidTransform characterTransform = new RigidTransform(characterRotation, characterPosition);
ConstrainToRope(ref characterPosition, ref characterBody.RelativeVelocity, character.RopeLength, AnchorPoint, math.transform(characterTransform, character.LocalRopeAnchorPoint
aspect.HandlePhysicsUpdatePhase2(ref context, ref baseContext, false, false, true, false, false);
DetectTransitions(ref context, ref baseContext, in aspect);
}
public void OnStateVariableUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
float deltaTime = baseContext.Time.DeltaTime;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref PlatformerCharacterControl characterControl = ref aspect.CharacterControl.ValueRW;
ref float3 characterPosition = ref aspect.CharacterAspect.LocalTransform.ValueRW.Position;
ref quaternion characterRotation = ref aspect.CharacterAspect.LocalTransform.ValueRW.Rotation;
if (math.lengthsq(characterControl.MoveVector) > 0f)
{
CharacterControlUtilities.SlerpRotationTowardsDirectionAroundUp(ref characterRotation, deltaTime, math.normalizesafe(characterControl.MoveVector), MathUtilities.GetUpFromRotati
}
CharacterControlUtilities.SlerpCharacterUpTowardsDirection(ref characterRotation, deltaTime, math.normalizesafe(AnchorPoint - characterPosition), character.UpOrientationAdaptationS
}
public void GetCameraParameters(in PlatformerCharacterComponent character, out Entity cameraTarget, out bool calculateUpFromGravity)
{
cameraTarget = character.DefaultCameraTargetEntity;
calculateUpFromGravity = true;
}
public void GetMoveVectorFromPlayerInput(in PlatformerPlayerInputs inputs, quaternion cameraRotation, out float3 moveVector)
{
PlatformerCharacterAspect.GetCommonMoveVectorFromPlayerInput(in inputs, cameraRotation, out moveVector);
}
public bool DetectTransitions(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
ref PlatformerCharacterControl characterControl = ref aspect.CharacterControl.ValueRW;
ref PlatformerCharacterStateMachine stateMachine = ref aspect.StateMachine.ValueRW;
if (characterControl.JumpPressed || characterControl.DashPressed)
{
stateMachine.TransitionToState(CharacterState.AirMove, ref context, ref baseContext, in aspect);
return true;
}
return aspect.DetectGlobalTransitions(ref context, ref baseContext);
}
public static bool DetectRopePoints(in PhysicsWorld physicsWorld, in PlatformerCharacterAspect aspect, out float3 point)
{
point = default;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref float3 characterPosition = ref aspect.CharacterAspect.LocalTransform.ValueRW.Position;
quaternion characterRotation = aspect.CharacterAspect.LocalTransform.ValueRW.Rotation;
RigidTransform characterTransform = new RigidTransform(characterRotation, characterPosition);
float3 ropeDetectionPoint = math.transform(characterTransform, character.LocalRopeAnchorPoint);
CollisionFilter ropeAnchorDetectionFilter = CollisionFilter.Default;
ropeAnchorDetectionFilter.CollidesWith = character.RopeAnchorCategory.Value;
PointDistanceInput pointInput = new PointDistanceInput
{
Filter = ropeAnchorDetectionFilter,
MaxDistance = character.RopeLength,
Position = ropeDetectionPoint,
};
if (physicsWorld.CalculateDistance(pointInput, out DistanceHit closestHit))
{
point = closestHit.Position;
return true;
}
return false;
}
public static void ConstrainToRope(
ref float3 translation,
ref float3 velocity,
float ropeLength,
float3 ropeAnchorPoint,
float3 ropeAnchorPointOnCharacter)
{
float3 characterToRopeVector = ropeAnchorPoint - ropeAnchorPointOnCharacter;
float3 ropeNormal = math.normalizesafe(characterToRopeVector);
if (math.length(characterToRopeVector) >= ropeLength)
{
float3 targetAnchorPointOnCharacter = ropeAnchorPoint - MathUtilities.ClampToMaxLength(characterToRopeVector, ropeLength);
translation += (targetAnchorPointOnCharacter - ropeAnchorPointOnCharacter);
if (math.dot(velocity, ropeNormal) < 0f)
{
velocity = MathUtilities.ProjectOnPlane(velocity, ropeNormal);
}
}
}
}
Platformer/Assets/Scripts/Character/States/SwimmingState.cs
using Unity.Entities;
using Unity.CharacterController;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Transforms;
public struct SwimmingState : IPlatformerCharacterState
{
public bool HasJumpedWhileSwimming;
public bool HasDetectedGrounding;
public bool ShouldExitSwimming;
private const float kDistanceFromSurfaceToAllowJumping = -0.05f;
private const float kForcedDistanceFromSurface = 0.01f;
public void OnStateEnter(CharacterState previousState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect
{
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref KinematicCharacterProperties characterProperties = ref aspect.CharacterAspect.CharacterProperties.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
aspect.SetCapsuleGeometry(character.SwimmingGeometry.ToCapsuleGeometry());
characterProperties.SnapToGround = false;
characterBody.IsGrounded = false;
HasJumpedWhileSwimming = false;
ShouldExitSwimming = false;
}
public void OnStateExit(CharacterState nextState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect
{
ref KinematicCharacterProperties characterProperties = ref aspect.CharacterAspect.CharacterProperties.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
characterProperties.SnapToGround = true;
}
public void OnStatePhysicsUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
aspect.HandlePhysicsUpdatePhase1(ref context, ref baseContext, true, true);
PreMovementUpdate(ref context, ref baseContext, in aspect);
aspect.HandlePhysicsUpdatePhase2(ref context, ref baseContext, false, false, true, false, true);
PostMovementUpdate(ref context, ref baseContext, in aspect);
DetectTransitions(ref context, ref baseContext, in aspect);
}
public void OnStateVariableUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
float deltaTime = baseContext.Time.DeltaTime;
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref PlatformerCharacterControl characterControl = ref aspect.CharacterControl.ValueRW;
ref float3 characterPosition = ref aspect.CharacterAspect.LocalTransform.ValueRW.Position;
ref quaternion characterRotation = ref aspect.CharacterAspect.LocalTransform.ValueRW.Rotation;
CustomGravity customGravity = aspect.CustomGravity.ValueRO;
if (!ShouldExitSwimming)
{
if (character.DistanceFromWaterSurface > character.SwimmingStandUpDistanceFromSurface)
{
// when close to surface, orient self up
float3 upPlane = -math.normalizesafe(customGravity.Gravity);
float3 targetForward = default;
if (math.lengthsq(characterControl.MoveVector) > 0f)
{
targetForward = math.normalizesafe(MathUtilities.ProjectOnPlane(characterControl.MoveVector, upPlane));
}
else
{
targetForward = math.normalizesafe(MathUtilities.ProjectOnPlane(MathUtilities.GetForwardFromRotation(characterRotation), upPlane));
if (math.dot(characterBody.GroundingUp, upPlane) < 0f)
{
targetForward = -targetForward;
}
}
quaternion targetRotation = MathUtilities.CreateRotationWithUpPriority(upPlane, targetForward);
targetRotation = math.slerp(characterRotation, targetRotation, MathUtilities.GetSharpnessInterpolant(character.SwimmingRotationSharpness, deltaTime));
MathUtilities.SetRotationAroundPoint(ref characterRotation, ref characterPosition, aspect.GetGeometryCenter(character.SwimmingGeometry), targetRotation);
}
else
{
if (math.lengthsq(characterControl.MoveVector) > 0f)
{
// Make character up face the movement direction, and character forward face gravity direction as much as it can
quaternion targetRotation = MathUtilities.CreateRotationWithUpPriority(math.normalizesafe(characterControl.MoveVector), math.normalizesafe(customGravity.Gravity
targetRotation = math.slerp(characterRotation, targetRotation, MathUtilities.GetSharpnessInterpolant(character.SwimmingRotationSharpness, deltaTime));
MathUtilities.SetRotationAroundPoint(ref characterRotation, ref characterPosition, aspect.GetGeometryCenter(character.SwimmingGeometry), targetRotation);
}
}
}
}
public void GetCameraParameters(in PlatformerCharacterComponent character, out Entity cameraTarget, out bool calculateUpFromGravity)
{
cameraTarget = character.SwimmingCameraTargetEntity;
calculateUpFromGravity = true;
}
public void GetMoveVectorFromPlayerInput(in PlatformerPlayerInputs inputs, quaternion cameraRotation, out float3 moveVector)
{
float3 cameraFwd = math.mul(cameraRotation, math.forward());
float3 cameraRight = math.mul(cameraRotation, math.right());
float3 cameraUp = math.mul(cameraRotation, math.up());
moveVector = (cameraRight * inputs.Move.x) + (cameraFwd * inputs.Move.y);
if (inputs.JumpHeld)
{
moveVector += cameraUp;
}
if (inputs.RollHeld)
{
moveVector -= cameraUp;
}
moveVector = MathUtilities.ClampToMaxLength(moveVector, 1f);
}
public void PreMovementUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
float deltaTime = baseContext.Time.DeltaTime;
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref PlatformerCharacterControl characterControl = ref aspect.CharacterControl.ValueRW;
ref quaternion characterRotation = ref aspect.CharacterAspect.LocalTransform.ValueRW.Rotation;
HasDetectedGrounding = characterBody.IsGrounded;
characterBody.IsGrounded = false;
if (DetectWaterZones(ref context, ref baseContext, in aspect, out character.DirectionToWaterSurface, out character.DistanceFromWaterSurface))
{
// Movement
float3 addedMoveVector = float3.zero;
if (character.DistanceFromWaterSurface > character.SwimmingStandUpDistanceFromSurface)
{
// When close to water surface, prevent moving down unless the input points strongly down
float dotMoveDirectionWithSurface = math.dot(math.normalizesafe(characterControl.MoveVector), character.DirectionToWaterSurface);
if (dotMoveDirectionWithSurface > character.SwimmingSurfaceDiveThreshold)
{
characterControl.MoveVector = MathUtilities.ProjectOnPlane(characterControl.MoveVector, character.DirectionToWaterSurface);
}
// Add an automatic move towards surface
addedMoveVector = character.DirectionToWaterSurface * 0.1f;
}
float3 acceleration = (characterControl.MoveVector + addedMoveVector) * character.SwimmingAcceleration;
CharacterControlUtilities.StandardAirMove(ref characterBody.RelativeVelocity, acceleration, character.SwimmingMaxSpeed, -MathUtilities.GetForwardFromRotation(characterRotation
// Water drag
CharacterControlUtilities.ApplyDragToVelocity(ref characterBody.RelativeVelocity, deltaTime, character.SwimmingDrag);
// Handle jumping out of water when close to water surface
HasJumpedWhileSwimming = false;
if (characterControl.JumpPressed && character.DistanceFromWaterSurface > kDistanceFromSurfaceToAllowJumping)
{
CharacterControlUtilities.StandardJump(ref characterBody, characterBody.GroundingUp * character.SwimmingJumpSpeed, true, characterBody.GroundingUp);
HasJumpedWhileSwimming = true;
}
}
else
{
ShouldExitSwimming = true;
}
}
public void PostMovementUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref float3 characterPosition = ref aspect.CharacterAspect.LocalTransform.ValueRW.Position;
bool determinedHasExitedWater = false;
if (DetectWaterZones(ref context, ref baseContext, in aspect, out character.DirectionToWaterSurface, out character.DistanceFromWaterSurface))
{
// Handle snapping to water surface when trying to swim out of the water
if (character.DistanceFromWaterSurface > -kForcedDistanceFromSurface)
{
float currentDistanceToTargetDistance = -kForcedDistanceFromSurface - character.DistanceFromWaterSurface;
float3 translationSnappedToWaterSurface = characterPosition + (character.DirectionToWaterSurface * currentDistanceToTargetDistance);
// Only snap to water surface if we're not jumping out of the water, or if we'd be obstructed when trying to snap back (allows us to walk out of water)
if (HasJumpedWhileSwimming || characterBody.GroundHit.Entity != Entity.Null)
{
determinedHasExitedWater = true;
}
else
{
// Snap position bact to water surface
characterPosition = translationSnappedToWaterSurface;
// Project velocity on water surface normal
characterBody.RelativeVelocity = MathUtilities.ProjectOnPlane(characterBody.RelativeVelocity, character.DirectionToWaterSurface);
}
}
}
ShouldExitSwimming = determinedHasExitedWater;
}
public bool DetectTransitions(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
ref PlatformerCharacterStateMachine stateMachine = ref aspect.StateMachine.ValueRW;
if (ShouldExitSwimming || HasDetectedGrounding)
{
if (HasDetectedGrounding)
{
stateMachine.TransitionToState(CharacterState.GroundMove, ref context, ref baseContext, in aspect);
return true;
}
else
{
stateMachine.TransitionToState(CharacterState.AirMove, ref context, ref baseContext, in aspect);
return true;
}
}
return aspect.DetectGlobalTransitions(ref context, ref baseContext);
}
public unsafe static bool DetectWaterZones(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect,
{
directionToWaterSurface = default;
waterSurfaceDistance = 0f;
ref PhysicsCollider physicsCollider = ref aspect.CharacterAspect.PhysicsCollider.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref float3 characterPosition = ref aspect.CharacterAspect.LocalTransform.ValueRW.Position;
ref quaternion characterRotation = ref aspect.CharacterAspect.LocalTransform.ValueRW.Rotation;
RigidTransform characterRigidTransform = new RigidTransform(characterRotation, characterPosition);
float3 swimmingDetectionPointWorldPosition = math.transform(characterRigidTransform, character.LocalSwimmingDetectionPoint);
CollisionFilter waterDetectionFilter = new CollisionFilter
{
BelongsTo = physicsCollider.ColliderPtr->GetCollisionFilter().BelongsTo,
CollidesWith = character.WaterPhysicsCategory.Value,
};
PointDistanceInput pointInput = new PointDistanceInput
{
Filter = waterDetectionFilter,
MaxDistance = character.WaterDetectionDistance,
Position = swimmingDetectionPointWorldPosition,
};
if (baseContext.PhysicsWorld.CalculateDistance(pointInput, out DistanceHit closestHit))
{
directionToWaterSurface = closestHit.SurfaceNormal; // always goes in the direction of decolliding from the target collider
waterSurfaceDistance = closestHit.Distance; // positive means above surface
return true;
}
return false;
}
}
Platformer/Assets/Scripts/Character/States/WallRunState.cs
using Unity.Entities;
using Unity.CharacterController;
using Unity.Mathematics;
using Unity.Physics;
public struct WallRunState : IPlatformerCharacterState
{
public void OnStateEnter(CharacterState previousState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect
{
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
aspect.SetCapsuleGeometry(character.StandingGeometry.ToCapsuleGeometry());
}
public void OnStateExit(CharacterState nextState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect
{ }
public void OnStatePhysicsUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
float deltaTime = baseContext.Time.DeltaTime;
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref PlatformerCharacterControl characterControl = ref aspect.CharacterControl.ValueRW;
CustomGravity customGravity = aspect.CustomGravity.ValueRO;
aspect.HandlePhysicsUpdatePhase1(ref context, ref baseContext, true, true);
// Detect if still moving against ungrounded surface
if (aspect.CharacterAspect.MovementWouldHitNonGroundedObstruction(in aspect, ref context, ref baseContext, -character.LastKnownWallNormal * character.WallRunDetectionDistance
{
character.HasDetectedMoveAgainstWall = true;
character.LastKnownWallNormal = detectedHit.SurfaceNormal;
}
else
{
character.LastKnownWallNormal = default;
}
if (character.HasDetectedMoveAgainstWall)
{
float3 constrainedMoveDirection = math.normalizesafe(math.cross(character.LastKnownWallNormal, characterBody.GroundingUp));
float3 moveVectorOnPlane = math.normalizesafe(MathUtilities.ProjectOnPlane(characterControl.MoveVector, characterBody.GroundingUp)) * math.length(characterControl.MoveVector
float3 acceleration = moveVectorOnPlane * character.WallRunAcceleration;
acceleration = math.projectsafe(acceleration, constrainedMoveDirection);
CharacterControlUtilities.StandardAirMove(ref characterBody.RelativeVelocity, acceleration, character.WallRunMaxSpeed, characterBody.GroundingUp, deltaTime, false);
// Jumping
if (character.HasDetectedMoveAgainstWall && characterControl.JumpPressed)
{
float3 jumpDirection = math.normalizesafe(math.lerp(characterBody.GroundingUp, character.LastKnownWallNormal, character.WallRunJumpRatioFromCharacterUp));
CharacterControlUtilities.StandardJump(ref characterBody, jumpDirection * character.WallRunJumpSpeed, true, jumpDirection);
}
if (characterControl.JumpHeld && character.HeldJumpTimeCounter < character.MaxHeldJumpTime)
{
characterBody.RelativeVelocity += characterBody.GroundingUp * character.JumpHeldAcceleration * deltaTime;
}
}
// Gravity
CharacterControlUtilities.AccelerateVelocity(ref characterBody.RelativeVelocity, (customGravity.Gravity * character.WallRunGravityFactor), deltaTime);
// Drag
CharacterControlUtilities.ApplyDragToVelocity(ref characterBody.RelativeVelocity, deltaTime, character.WallRunDrag);
aspect.HandlePhysicsUpdatePhase2(ref context, ref baseContext, false, true, true, true, true);
DetectTransitions(ref context, ref baseContext, in aspect);
}
public void OnStateVariableUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
float deltaTime = baseContext.Time.DeltaTime;
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref quaternion characterRotation = ref aspect.CharacterAspect.LocalTransform.ValueRW.Rotation;
CustomGravity customGravity = aspect.CustomGravity.ValueRO;
// Orientation
if (character.HasDetectedMoveAgainstWall)
{
float3 rotationDirection = math.normalizesafe(math.cross(character.LastKnownWallNormal, characterBody.GroundingUp));
if (math.dot(rotationDirection, characterBody.RelativeVelocity) < 0f)
{
rotationDirection *= -1f;
}
CharacterControlUtilities.SlerpRotationTowardsDirectionAroundUp(ref characterRotation, deltaTime, rotationDirection, characterBody.GroundingUp, character.GroundedRotationSharpn
}
CharacterControlUtilities.SlerpCharacterUpTowardsDirection(ref characterRotation, deltaTime, math.normalizesafe(-customGravity.Gravity), character.UpOrientationAdaptationSharpness
}
public void GetCameraParameters(in PlatformerCharacterComponent character, out Entity cameraTarget, out bool calculateUpFromGravity)
{
cameraTarget = character.DefaultCameraTargetEntity;
calculateUpFromGravity = true;
}
public void GetMoveVectorFromPlayerInput(in PlatformerPlayerInputs inputs, quaternion cameraRotation, out float3 moveVector)
{
PlatformerCharacterAspect.GetCommonMoveVectorFromPlayerInput(in inputs, cameraRotation, out moveVector);
}
public bool DetectTransitions(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref PlatformerCharacterControl characterControl = ref aspect.CharacterControl.ValueRW;
ref PlatformerCharacterStateMachine stateMachine = ref aspect.StateMachine.ValueRW;
if (characterControl.RollHeld)
{
stateMachine.TransitionToState(CharacterState.Rolling, ref context, ref baseContext, in aspect);
return true;
}
if (characterControl.DashPressed)
{
stateMachine.TransitionToState(CharacterState.Dashing, ref context, ref baseContext, in aspect);
return true;
}
if (characterBody.IsGrounded)
{
stateMachine.TransitionToState(CharacterState.GroundMove, ref context, ref baseContext, in aspect);
return true;
}
if (!character.HasDetectedMoveAgainstWall)
{
stateMachine.TransitionToState(CharacterState.AirMove, ref context, ref baseContext, in aspect);
return true;
}
return aspect.DetectGlobalTransitions(ref context, ref baseContext);
}
}
Platformer/Assets/Scripts/Misc/CharacterFrictionModifier.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
[Serializable]
public struct CharacterFrictionModifier : IComponentData
{
public float Friction;
}
Platformer/Assets/Scripts/Misc/CharacterFrictionModifierAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
[Serializable]
public struct CharacterRope : IComponentData
{
public Entity OwningCharacterEntity;
}
Platformer/Assets/Scripts/Misc/CharacterRopeSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using Unity.CharacterController;
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(TransformSystemGroup))]
[BurstCompile]
public partial struct CharacterRopeSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{ }
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<EndSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged);
foreach (var (characterRope, entity) in SystemAPI.Query<CharacterRope>().WithEntityAccess())
{
if (characterRope.OwningCharacterEntity == Entity.Null)
{
return;
}
if (SystemAPI.HasComponent<PlatformerCharacterComponent>(characterRope.OwningCharacterEntity) && SystemAPI.HasComponent<PlatformerCharacterStateMachine>(characterRope.
{
PlatformerCharacterComponent platformerCharacter = SystemAPI.GetComponent<PlatformerCharacterComponent>(characterRope.OwningCharacterEntity);
PlatformerCharacterStateMachine characterStateMachine = SystemAPI.GetComponent<PlatformerCharacterStateMachine>(characterRope.OwningCharacterEntity);
LocalToWorld characterLocalToWorld = SystemAPI.GetComponent<LocalToWorld>(characterRope.OwningCharacterEntity);
return false;
}
}
Platformer/Assets/Scripts/Misc/FixedTickSystem.cs
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using Unity.Burst;
using Unity.Entities;
using UnityEngine;
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
if (!SystemAPI.HasSingleton<Singleton>())
{
Entity singletonEntity = state.EntityManager.CreateEntity();
state.EntityManager.AddComponentData(singletonEntity, new Singleton());
}
_framesDeltaSum = 0f;
_framesCount = 0;
_minDeltaTimeForAvg = Mathf.Infinity;
_maxDeltaTimeForAvg = Mathf.NegativeInfinity;
}
}
void Update()
{
// show hide
if (Input.GetKeyDown(KeyCode.F1))
{
MainCanvas.gameObject.SetActive(!MainCanvas.gameObject.activeSelf);
}
if (Input.GetKeyDown(KeyCode.F3))
{
_hasVSync = !_hasVSync;
UpdateRenderSettings();
}
// FPS
_framerateCalculator.Update();
if (Time.time >= _lastTimePolledFPS + FPSPollRate)
{
_framerateCalculator.PollFramerate(out string avg, out string worst, out string best);
AvgFPS.text = avg;
WorstFPS.text = worst;
BestFPS.text = best;
_lastTimePolledFPS = Time.time;
}
}
private void UpdateRenderSettings()
{
QualitySettings.vSyncCount = _hasVSync ? 1 : 0;
}
}
Platformer/Assets/Scripts/Misc/FramerateSetter.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
void Start()
{
Application.targetFrameRate = Framerate;
[Serializable]
public struct GlobalGravityZone : IComponentData
{
public float3 Gravity;
}
Platformer/Assets/Scripts/Misc/GlobalGravityZoneAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
public class GlobalGravityZoneAuthoring : MonoBehaviour
{
public float3 Gravity;
[UpdateInGroup(typeof(SimulationSystemGroup))] // update in variable update because the camera can use gravity to adjust its up direction
[UpdateBefore(typeof(KinematicCharacterVariableUpdateGroup))]
public partial class GravityZonesSystem : SystemBase
{
protected override void OnUpdate()
{
// Update transforms so we have the proper interpolated position of our entities to calculate spherical gravities from
// (without this, we'd see jitter on the planet)
World.GetOrCreateSystem<TransformSystemGroup>().Update(World.Unmanaged);
[BurstCompile]
public unsafe partial struct SphericalGravityJob : IJobEntity
{
public ComponentLookup<CustomGravity> CustomGravityFromEntity;
[ReadOnly]
public ComponentLookup<LocalToWorld> LocalToWorldFromEntity;
[BurstCompile]
public partial struct GlobalGravityJob : IJobEntity
{
public GlobalGravityZone GlobalGravityZone;
void Execute(Entity entity, ref CustomGravity customGravity)
{
if (!customGravity.TouchedByNonGlobalGravity)
{
customGravity.Gravity = GlobalGravityZone.Gravity * customGravity.GravityMultiplier;
customGravity.CurrentZoneEntity = Entity.Null;
}
}
}
[BurstCompile]
public partial struct ApplyGravityJob : IJobEntity
{
public float DeltaTime;
void Execute(Entity entity, ref PhysicsVelocity physicsVelocity, in PhysicsMass physicsMass, in CustomGravity customGravity)
{
if (physicsMass.InverseMass > 0f)
{
CharacterControlUtilities.AccelerateVelocity(ref physicsVelocity.Linear, customGravity.Gravity, DeltaTime);
}
}
}
}
Platformer/Assets/Scripts/Misc/JumpPad.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
[Serializable]
public struct JumpPad : IComponentData
{
public float JumpPower;
public float UngroundingDotThreshold;
}
Platformer/Assets/Scripts/Misc/JumpPadAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
[UpdateInGroup(typeof(AfterPhysicsSystemGroup))]
[UpdateBefore(typeof(KinematicCharacterPhysicsUpdateGroup))]
[BurstCompile]
public partial struct JumpPadSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{ }
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
JumpPadJob job = new JumpPadJob
{
KinematicCharacterBodyLookup = SystemAPI.GetComponentLookup<KinematicCharacterBody>(false),
};
job.Schedule();
}
[BurstCompile]
public partial struct JumpPadJob : IJobEntity
{
public ComponentLookup<KinematicCharacterBody> KinematicCharacterBodyLookup;
void Execute(Entity entity, in LocalTransform localTransform, in JumpPad jumpPad, in DynamicBuffer<StatefulTriggerEvent> triggerEventsBuffer)
{
for (int i = 0; i < triggerEventsBuffer.Length; i++)
{
StatefulTriggerEvent triggerEvent = triggerEventsBuffer[i];
Entity otherEntity = triggerEvent.GetOtherEntity(entity);
KinematicCharacterBodyLookup[otherEntity] = characterBody;
}
}
}
}
}
Platformer/Assets/Scripts/Misc/PlatformerUtilities.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
public static class PlatformerUtilities
{
public static void SetEntityHierarchyEnabled(bool enabled, Entity parent, EntityCommandBuffer commandBuffer, BufferLookup<LinkedEntityGroup> linkedEntityGroupFromEntity)
{
if (enabled)
{
commandBuffer.RemoveComponent<Disabled>(parent);
}
else
{
commandBuffer.AddComponent<Disabled>(parent);
}
if (linkedEntityGroupFromEntity.HasBuffer(parent))
{
DynamicBuffer<LinkedEntityGroup> parentLinkedEntities = linkedEntityGroupFromEntity[parent];
for (int i = 0; i < parentLinkedEntities.Length; i++)
{
if (enabled)
{
commandBuffer.RemoveComponent<Disabled>(parentLinkedEntities[i].Value);
}
else
{
commandBuffer.AddComponent<Disabled>(parentLinkedEntities[i].Value);
}
}
}
}
public static void SetEntityHierarchyEnabledParallel(bool enabled, Entity parent, EntityCommandBuffer.ParallelWriter ecb, int chunkIndex, BufferLookup<LinkedEntityGroup> linkedEntityGr
{
if (enabled)
{
ecb.RemoveComponent<Disabled>(chunkIndex, parent);
}
else
{
ecb.AddComponent<Disabled>(chunkIndex, parent);
}
if (linkedEntityGroupFromEntity.HasBuffer(parent))
{
DynamicBuffer<LinkedEntityGroup> parentLinkedEntities = linkedEntityGroupFromEntity[parent];
for (int i = 0; i < parentLinkedEntities.Length; i++)
{
if (enabled)
{
ecb.RemoveComponent<Disabled>(chunkIndex, parentLinkedEntities[i].Value);
}
else
{
ecb.AddComponent<Disabled>(chunkIndex, parentLinkedEntities[i].Value);
}
}
}
}
}
Platformer/Assets/Scripts/Misc/SceneInitialization.cs
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[System.Serializable]
public struct SceneInitialization : IComponentData
{
public Entity CharacterSpawnPointEntity;
public Entity CharacterPrefabEntity;
public Entity CameraPrefabEntity;
public Entity PlayerPrefabEntity;
}
Platformer/Assets/Scripts/Misc/SceneInitializationAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
// Spawn player
Entity playerEntity = state.EntityManager.Instantiate(sceneInitializer.PlayerPrefabEntity);
state.EntityManager.DestroyEntity(SystemAPI.GetSingletonEntity<SceneInitialization>());
}
}
}
Platformer/Assets/Scripts/Misc/SelfDestructAfterTime.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[Serializable]
public struct SelfDestructAfterTime : IComponentData
{
public float LifeTime;
public float TimeSinceAlive;
}
Platformer/Assets/Scripts/Misc/SelfDestructAfterTimeAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
[BurstCompile]
public partial struct SelfDestructAfterTimeSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
SelfDestructAfterTimeJob job = new SelfDestructAfterTimeJob
{
DeltaTime = SystemAPI.Time.DeltaTime,
ECB = SystemAPI.GetSingletonRW<EndSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged),
};
job.Schedule();
}
[BurstCompile]
public partial struct SelfDestructAfterTimeJob : IJobEntity
{
public float DeltaTime;
public EntityCommandBuffer ECB;
[Serializable]
public struct SphericalGravityZone : IComponentData
{
public float GravityStrengthAtCenter;
}
Platformer/Assets/Scripts/Misc/SphericalGravityZoneAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
public class SphericalGravityZoneAuthoring : MonoBehaviour
{
public float GravityStrengthAtCenter;
[Serializable]
public struct Teleporter : IComponentData
{
public Entity DestinationEntity;
}
Platformer/Assets/Scripts/Misc/TeleporterAuthoring.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[DisallowMultipleComponent]
public class TeleporterAuthoring : MonoBehaviour
{
public GameObject Destination;
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
TeleporterJob job = new TeleporterJob
{
LocalTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(false),
CharacterBodyLookup = SystemAPI.GetComponentLookup<KinematicCharacterBody>(true),
CharacterInterpolationLookup = SystemAPI.GetComponentLookup<CharacterInterpolation>(false),
};
job.Schedule();
}
[BurstCompile]
public partial struct TeleporterJob : IJobEntity
{
public ComponentLookup<LocalTransform> LocalTransformLookup;
[ReadOnly] public ComponentLookup<KinematicCharacterBody> CharacterBodyLookup;
public ComponentLookup<CharacterInterpolation> CharacterInterpolationLookup;
// If a character has entered the trigger, move its translation to the destination
if (triggerEvent.State == StatefulEventState.Enter && CharacterBodyLookup.HasComponent(otherEntity))
{
LocalTransform t = LocalTransformLookup[otherEntity];
t.Position = LocalTransformLookup[teleporter.DestinationEntity].Position;
t.Rotation = LocalTransformLookup[teleporter.DestinationEntity].Rotation;
LocalTransformLookup[otherEntity] = t;
// Bypass interpolation
if (CharacterInterpolationLookup.HasComponent(otherEntity))
{
CharacterInterpolation interpolation = CharacterInterpolationLookup[otherEntity];
interpolation.SkipNextInterpolation();
CharacterInterpolationLookup[otherEntity] = interpolation;
}
}
}
}
}
}
}
Platformer/Assets/Scripts/Misc/TestMovingPlatform.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
[Serializable]
public struct TestMovingPlatform : IComponentData
{
[Serializable]
public struct AuthoringData
{
public float3 TranslationAxis;
public float TranslationAmplitude;
public float TranslationSpeed;
public float3 RotationAxis;
public float RotationSpeed;
public float3 OscillationAxis;
public float OscillationAmplitude;
public float OscillationSpeed;
}
[BurstCompile]
public partial struct TestMovingPlatformJob : IJobEntity
{
public float Time;
public float InvDeltaTime;
void Execute(Entity entity, ref PhysicsVelocity physicsVelocity, in PhysicsMass physicsMass, in LocalTransform localTransform, in TestMovingPlatform movingPlatform)
{
float3 targetPos = movingPlatform.OriginalPosition + (math.normalizesafe(movingPlatform.Data.TranslationAxis) * math.sin(Time * movingPlatform.Data.TranslationSpeed) *
quaternion rotationFromRotation = quaternion.Euler(math.normalizesafe(movingPlatform.Data.RotationAxis) * movingPlatform.Data.RotationSpeed * Time);
quaternion rotationFromOscillation = quaternion.Euler(math.normalizesafe(movingPlatform.Data.OscillationAxis) * (math.sin(Time * movingPlatform.Data.OscillationSpeed)
quaternion totalRotation = math.mul(rotationFromRotation, rotationFromOscillation);
quaternion targetRot = math.mul(totalRotation, movingPlatform.OriginalRotation);
RigidTransform targetTransform = new RigidTransform(targetRot, targetPos);
physicsVelocity = PhysicsVelocity.CalculateVelocityToTarget(in physicsMass, localTransform.Position, localTransform.Rotation, in targetTransform, InvDeltaTime);
}
}
}
Platformer/Assets/Scripts/Misc/WindZone.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
[Serializable]
public struct WindZone : IComponentData
{
public float3 WindForce;
}
Platformer/Assets/Scripts/Misc/WindZoneAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
public class WindZoneAuthoring : MonoBehaviour
{
public float3 WindForce;
[assembly: InternalsVisibleTo("Unity.Physics.Custom.Editor")]
[assembly: InternalsVisibleTo("Unity.Physics.Custom.EditModeTests")]
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/BaseBodyPairConnector.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
[RequireComponent(typeof(PhysicsBodyAuthoring))]
public abstract class BaseBodyPairConnector : MonoBehaviour
{
public PhysicsBodyAuthoring LocalBody => GetComponent<PhysicsBodyAuthoring>();
public PhysicsBodyAuthoring ConnectedBody;
void OnEnable()
{
// included so tick box appears in Editor
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/CustomPhysicsMaterialTagNames.cs
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Serialization;
namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Custom Physics Material Tag Names", fileName = "Custom Material Tag Names", order = 506)]
public sealed partial class CustomPhysicsMaterialTagNames : ScriptableObject, ITagNames
{
CustomPhysicsMaterialTagNames() {}
public IReadOnlyList<string> TagNames => m_TagNames;
[SerializeField]
[FormerlySerializedAs("m_FlagNames")]
string[] m_TagNames = Enumerable.Range(0, 8).Select(i => string.Empty).ToArray();
void OnValidate()
{
if (m_TagNames.Length != 8)
Array.Resize(ref m_TagNames, 8);
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/CustomPhysicsMaterialTags.cs
using System;
using Unity.Mathematics;
namespace Unity.Physics.Authoring
{
[Serializable]
public struct CustomPhysicsMaterialTags : IEquatable<CustomPhysicsMaterialTags>
{
public static CustomPhysicsMaterialTags Everything => new CustomPhysicsMaterialTags { Value = unchecked((byte)~0) };
public static CustomPhysicsMaterialTags Nothing => new CustomPhysicsMaterialTags { Value = 0 };
public bool Tag00;
public bool Tag01;
public bool Tag02;
public bool Tag03;
public bool Tag04;
public bool Tag05;
public bool Tag06;
public bool Tag07;
internal bool this[int i]
{
get
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 7), nameof(i));
switch (i)
{
case 0: return Tag00;
case 1: return Tag01;
case 2: return Tag02;
case 3: return Tag03;
case 4: return Tag04;
case 5: return Tag05;
case 6: return Tag06;
case 7: return Tag07;
default: return default;
}
}
set
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 7), nameof(i));
switch (i)
{
case 0: Tag00 = value; break;
case 1: Tag01 = value; break;
case 2: Tag02 = value; break;
case 3: Tag03 = value; break;
case 4: Tag04 = value; break;
case 5: Tag05 = value; break;
case 6: Tag06 = value; break;
case 7: Tag07 = value; break;
}
}
}
public byte Value
{
get
{
var result = 0;
result |= (Tag00 ? 1 : 0) << 0;
result |= (Tag01 ? 1 : 0) << 1;
result |= (Tag02 ? 1 : 0) << 2;
result |= (Tag03 ? 1 : 0) << 3;
result |= (Tag04 ? 1 : 0) << 4;
result |= (Tag05 ? 1 : 0) << 5;
result |= (Tag06 ? 1 : 0) << 6;
result |= (Tag07 ? 1 : 0) << 7;
return (byte)result;
}
set
{
Tag00 = (value & (1 << 0)) != 0;
Tag01 = (value & (1 << 1)) != 0;
Tag02 = (value & (1 << 2)) != 0;
Tag03 = (value & (1 << 3)) != 0;
Tag04 = (value & (1 << 4)) != 0;
Tag05 = (value & (1 << 5)) != 0;
Tag06 = (value & (1 << 6)) != 0;
Tag07 = (value & (1 << 7)) != 0;
}
}
public bool Equals(CustomPhysicsMaterialTags other) => Value == other.Value;
public override bool Equals(object obj) => obj is CustomPhysicsMaterialTags other && Equals(other);
namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Physics Category Names", fileName = "Physics Category Names", order = 507)]
public sealed class PhysicsCategoryNames : ScriptableObject, ITagNames
{
PhysicsCategoryNames() {}
void OnValidate()
{
if (m_CategoryNames.Length != 32)
Array.Resize(ref m_CategoryNames, 32);
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsCategoryTags.cs
using System;
using Unity.Mathematics;
namespace Unity.Physics.Authoring
{
[Serializable]
public struct PhysicsCategoryTags : IEquatable<PhysicsCategoryTags>
{
public static PhysicsCategoryTags Everything => new PhysicsCategoryTags { Value = unchecked((uint)~0) };
public static PhysicsCategoryTags Nothing => new PhysicsCategoryTags { Value = 0 };
public bool Category00;
public bool Category01;
public bool Category02;
public bool Category03;
public bool Category04;
public bool Category05;
public bool Category06;
public bool Category07;
public bool Category08;
public bool Category09;
public bool Category10;
public bool Category11;
public bool Category12;
public bool Category13;
public bool Category14;
public bool Category15;
public bool Category16;
public bool Category17;
public bool Category18;
public bool Category19;
public bool Category20;
public bool Category21;
public bool Category22;
public bool Category23;
public bool Category24;
public bool Category25;
public bool Category26;
public bool Category27;
public bool Category28;
public bool Category29;
public bool Category30;
public bool Category31;
internal bool this[int i]
{
get
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 31), nameof(i));
switch (i)
{
case 0: return Category00;
case 1: return Category01;
case 2: return Category02;
case 3: return Category03;
case 4: return Category04;
case 5: return Category05;
case 6: return Category06;
case 7: return Category07;
case 8: return Category08;
case 9: return Category09;
case 10: return Category10;
case 11: return Category11;
case 12: return Category12;
case 13: return Category13;
case 14: return Category14;
case 15: return Category15;
case 16: return Category16;
case 17: return Category17;
case 18: return Category18;
case 19: return Category19;
case 20: return Category20;
case 21: return Category21;
case 22: return Category22;
case 23: return Category23;
case 24: return Category24;
case 25: return Category25;
case 26: return Category26;
case 27: return Category27;
case 28: return Category28;
case 29: return Category29;
case 30: return Category30;
case 31: return Category31;
default: return default;
}
}
set
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 31), nameof(i));
switch (i)
{
case 0: Category00 = value; break;
case 1: Category01 = value; break;
case 2: Category02 = value; break;
case 3: Category03 = value; break;
case 4: Category04 = value; break;
case 5: Category05 = value; break;
case 6: Category06 = value; break;
case 7: Category07 = value; break;
case 8: Category08 = value; break;
case 9: Category09 = value; break;
case 10: Category10 = value; break;
case 11: Category11 = value; break;
case 12: Category12 = value; break;
case 13: Category13 = value; break;
case 14: Category14 = value; break;
case 15: Category15 = value; break;
case 16: Category16 = value; break;
case 17: Category17 = value; break;
case 18: Category18 = value; break;
case 19: Category19 = value; break;
case 20: Category20 = value; break;
case 21: Category21 = value; break;
case 22: Category22 = value; break;
case 23: Category23 = value; break;
case 24: Category24 = value; break;
case 25: Category25 = value; break;
case 26: Category26 = value; break;
case 27: Category27 = value; break;
case 28: Category28 = value; break;
case 29: Category29 = value; break;
case 30: Category30 = value; break;
case 31: Category31 = value; break;
}
}
}
public uint Value
{
get
{
var result = 0;
result |= (Category00 ? 1 : 0) << 0;
result |= (Category01 ? 1 : 0) << 1;
result |= (Category02 ? 1 : 0) << 2;
result |= (Category03 ? 1 : 0) << 3;
result |= (Category04 ? 1 : 0) << 4;
result |= (Category05 ? 1 : 0) << 5;
result |= (Category06 ? 1 : 0) << 6;
result |= (Category07 ? 1 : 0) << 7;
result |= (Category08 ? 1 : 0) << 8;
result |= (Category09 ? 1 : 0) << 9;
result |= (Category10 ? 1 : 0) << 10;
result |= (Category11 ? 1 : 0) << 11;
result |= (Category12 ? 1 : 0) << 12;
result |= (Category13 ? 1 : 0) << 13;
result |= (Category14 ? 1 : 0) << 14;
result |= (Category15 ? 1 : 0) << 15;
result |= (Category16 ? 1 : 0) << 16;
result |= (Category17 ? 1 : 0) << 17;
result |= (Category18 ? 1 : 0) << 18;
result |= (Category19 ? 1 : 0) << 19;
result |= (Category20 ? 1 : 0) << 20;
result |= (Category21 ? 1 : 0) << 21;
result |= (Category22 ? 1 : 0) << 22;
result |= (Category23 ? 1 : 0) << 23;
result |= (Category24 ? 1 : 0) << 24;
result |= (Category25 ? 1 : 0) << 25;
result |= (Category26 ? 1 : 0) << 26;
result |= (Category27 ? 1 : 0) << 27;
result |= (Category28 ? 1 : 0) << 28;
result |= (Category29 ? 1 : 0) << 29;
result |= (Category30 ? 1 : 0) << 30;
result |= (Category31 ? 1 : 0) << 31;
return unchecked((uint)result);
}
set
{
Category00 = (value & (1 << 0)) != 0;
Category01 = (value & (1 << 1)) != 0;
Category02 = (value & (1 << 2)) != 0;
Category03 = (value & (1 << 3)) != 0;
Category04 = (value & (1 << 4)) != 0;
Category05 = (value & (1 << 5)) != 0;
Category06 = (value & (1 << 6)) != 0;
Category07 = (value & (1 << 7)) != 0;
Category08 = (value & (1 << 8)) != 0;
Category09 = (value & (1 << 9)) != 0;
Category10 = (value & (1 << 10)) != 0;
Category11 = (value & (1 << 11)) != 0;
Category12 = (value & (1 << 12)) != 0;
Category13 = (value & (1 << 13)) != 0;
Category14 = (value & (1 << 14)) != 0;
Category15 = (value & (1 << 15)) != 0;
Category16 = (value & (1 << 16)) != 0;
Category17 = (value & (1 << 17)) != 0;
Category18 = (value & (1 << 18)) != 0;
Category19 = (value & (1 << 19)) != 0;
Category20 = (value & (1 << 20)) != 0;
Category21 = (value & (1 << 21)) != 0;
Category22 = (value & (1 << 22)) != 0;
Category23 = (value & (1 << 23)) != 0;
Category24 = (value & (1 << 24)) != 0;
Category25 = (value & (1 << 25)) != 0;
Category26 = (value & (1 << 26)) != 0;
Category27 = (value & (1 << 27)) != 0;
Category28 = (value & (1 << 28)) != 0;
Category29 = (value & (1 << 29)) != 0;
Category30 = (value & (1 << 30)) != 0;
Category31 = (value & (1 << 31)) != 0;
}
}
public bool Equals(PhysicsCategoryTags other) => Value == other.Value;
public override bool Equals(object obj) => obj is PhysicsCategoryTags other && Equals(other);
[Serializable]
public struct PhysicsMaterialCoefficient
{
[SoftRange(0f, 1f, TextFieldMax = float.MaxValue)]
public float Value;
public Material.CombinePolicy CombineMode;
}
public T Value
{
get => m_Value;
set
{
m_Value = value;
Override = true;
}
}
[SerializeField]
T m_Value;
[Serializable]
class OverridableCollisionResponse : OverridableValue<CollisionResponsePolicy> {}
[Serializable]
class OverridableMaterialCoefficient : OverridableValue<PhysicsMaterialCoefficient>
{
protected override void OnValidate(ref PhysicsMaterialCoefficient value) =>
value.Value = math.max(0f, value.Value);
}
[Serializable]
class OverridableCategoryTags : OverridableValue<PhysicsCategoryTags> {}
[Serializable]
class OverridableCustomMaterialTags : OverridableValue<CustomPhysicsMaterialTags> {}
[Serializable]
class PhysicsMaterialProperties : IInheritPhysicsMaterialProperties, ISerializationCallbackReceiver
{
public PhysicsMaterialProperties(bool supportsTemplate) => m_SupportsTemplate = supportsTemplate;
[SerializeField, HideInInspector]
bool m_SupportsTemplate;
public bool OverrideCollisionResponse { get => m_CollisionResponse.Override; set => m_CollisionResponse.Override = value; }
public CollisionResponsePolicy CollisionResponse
{
get => Get(m_CollisionResponse, m_Template == null ? null : m_Template?.CollisionResponse);
set => m_CollisionResponse.Value = value;
}
[SerializeField]
OverridableCollisionResponse m_CollisionResponse = new OverridableCollisionResponse
{
Value = CollisionResponsePolicy.Collide,
Override = false
};
public bool OverrideFriction { get => m_Friction.Override; set => m_Friction.Override = value; }
public PhysicsMaterialCoefficient Friction
{
get => Get(m_Friction, m_Template == null ? null : m_Template?.Friction);
set => m_Friction.Value = value;
}
[SerializeField]
OverridableMaterialCoefficient m_Friction = new OverridableMaterialCoefficient
{
Value = new PhysicsMaterialCoefficient { Value = 0.5f, CombineMode = Material.CombinePolicy.GeometricMean },
Override = false
};
public bool OverrideRestitution { get => m_Restitution.Override; set => m_Restitution.Override = value; }
public PhysicsMaterialCoefficient Restitution
{
get => Get(m_Restitution, m_Template == null ? null : m_Template?.Restitution);
set => m_Restitution.Value = value;
}
[SerializeField]
OverridableMaterialCoefficient m_Restitution = new OverridableMaterialCoefficient
{
Value = new PhysicsMaterialCoefficient { Value = 0f, CombineMode = Material.CombinePolicy.Maximum },
Override = false
};
public bool OverrideBelongsTo { get => m_BelongsToCategories.Override; set => m_BelongsToCategories.Override = value; }
public PhysicsCategoryTags BelongsTo
{
get => Get(m_BelongsToCategories, m_Template == null ? null : m_Template?.BelongsTo);
set => m_BelongsToCategories.Value = value;
}
[SerializeField]
OverridableCategoryTags m_BelongsToCategories =
new OverridableCategoryTags { Value = PhysicsCategoryTags.Everything, Override = false };
public bool OverrideCollidesWith { get => m_CollidesWithCategories.Override; set => m_CollidesWithCategories.Override = value; }
public PhysicsCategoryTags CollidesWith
{
get => Get(m_CollidesWithCategories, m_Template == null ? null : m_Template?.CollidesWith);
set => m_CollidesWithCategories.Value = value;
}
[SerializeField]
OverridableCategoryTags m_CollidesWithCategories =
new OverridableCategoryTags { Value = PhysicsCategoryTags.Everything, Override = false };
public bool OverrideCustomTags { get => m_CustomMaterialTags.Override; set => m_CustomMaterialTags.Override = value; }
public CustomPhysicsMaterialTags CustomTags
{
get => Get(m_CustomMaterialTags, m_Template == null ? null : m_Template?.CustomTags);
set => m_CustomMaterialTags.Value = value;
}
[SerializeField]
OverridableCustomMaterialTags m_CustomMaterialTags =
new OverridableCustomMaterialTags { Value = default, Override = false };
material.m_SupportsTemplate = supportsTemplate;
if (!supportsTemplate)
{
material.m_Template = null;
material.m_CollisionResponse.Override = true;
material.m_Friction.Override = true;
material.m_Restitution.Override = true;
}
material.m_Friction.OnValidate();
material.m_Restitution.OnValidate();
}
[SerializeField]
int m_SerializedVersion = 0;
void ISerializationCallbackReceiver.OnBeforeSerialize() {}
void ISerializationCallbackReceiver.OnAfterDeserialize() => UpgradeVersionIfNecessary();
namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Physics Material Template", fileName = "Physics Material Template", order = 508)]
public sealed class PhysicsMaterialTemplate : ScriptableObject, IPhysicsMaterialProperties
{
PhysicsMaterialTemplate() {}
public CollisionResponsePolicy CollisionResponse { get => m_Value.CollisionResponse; set => m_Value.CollisionResponse = value; }
public PhysicsMaterialCoefficient Friction { get => m_Value.Friction; set => m_Value.Friction = value; }
public PhysicsMaterialCoefficient Restitution { get => m_Value.Restitution; set => m_Value.Restitution = value; }
public PhysicsCategoryTags BelongsTo { get => m_Value.BelongsTo; set => m_Value.BelongsTo = value; }
public PhysicsCategoryTags CollidesWith { get => m_Value.CollidesWith; set => m_Value.CollidesWith = value; }
public CustomPhysicsMaterialTags CustomTags { get => m_Value.CustomTags; set => m_Value.CustomTags = value; }
[SerializeField]
PhysicsMaterialProperties m_Value = new PhysicsMaterialProperties(false);
PhysicsBodyAuthoring() {}
public BodyMotionType MotionType { get => m_MotionType; set => m_MotionType = value; }
[SerializeField]
[Tooltip("Specifies whether the body should be fully physically simulated, moved directly, or fixed in place.")]
BodyMotionType m_MotionType;
public BodySmoothing Smoothing { get => m_Smoothing; set => m_Smoothing = value; }
[SerializeField]
[Tooltip("Specifies how this body's motion in its graphics representation should be smoothed when the rendering framerate is greater than the fixed step rate used by physics.")]
BodySmoothing m_Smoothing = BodySmoothing.None;
const float k_MinimumMass = 0.001f;
public float Mass
{
get => m_MotionType == BodyMotionType.Dynamic ? m_Mass : float.PositiveInfinity;
set => m_Mass = math.max(k_MinimumMass, value);
}
[SerializeField]
float m_Mass = 1.0f;
public float LinearDamping { get => m_LinearDamping; set => m_LinearDamping = math.max(0f, value); }
[SerializeField]
[Tooltip("This is applied to a body's linear velocity reducing it over time.")]
float m_LinearDamping = 0.01f;
public float AngularDamping { get => m_AngularDamping; set => m_AngularDamping = math.max(0f, value); }
[SerializeField]
[Tooltip("This is applied to a body's angular velocity reducing it over time.")]
float m_AngularDamping = 0.05f;
public float3 InitialLinearVelocity { get => m_InitialLinearVelocity; set => m_InitialLinearVelocity = value; }
[SerializeField]
[Tooltip("The initial linear velocity of the body in world space")]
float3 m_InitialLinearVelocity = float3.zero;
public float3 InitialAngularVelocity { get => m_InitialAngularVelocity; set => m_InitialAngularVelocity = value; }
[SerializeField]
[Tooltip("This represents the initial rotation speed around each axis in the local motion space of the body i.e. around the center of mass")]
float3 m_InitialAngularVelocity = float3.zero;
public float GravityFactor
{
get => m_MotionType == BodyMotionType.Dynamic ? m_GravityFactor : 0f;
set => m_GravityFactor = value;
}
[SerializeField]
[Tooltip("Scales the amount of gravity to apply to this body.")]
float m_GravityFactor = 1f;
public bool OverrideDefaultMassDistribution
{
#pragma warning disable 618
get => m_OverrideDefaultMassDistribution;
set => m_OverrideDefaultMassDistribution = value;
#pragma warning restore 618
}
[SerializeField]
[Tooltip("Default mass distribution is based on the shapes associated with this body.")]
bool m_OverrideDefaultMassDistribution;
public MassDistribution CustomMassDistribution
{
get => new MassDistribution
{
Transform = new RigidTransform(m_Orientation, m_CenterOfMass),
InertiaTensor =
m_MotionType == BodyMotionType.Dynamic ? m_InertiaTensor : new float3(float.PositiveInfinity)
};
set
{
m_CenterOfMass = value.Transform.pos;
m_Orientation.SetValue(value.Transform.rot);
m_InertiaTensor = value.InertiaTensor;
#pragma warning disable 618
m_OverrideDefaultMassDistribution = true;
#pragma warning restore 618
}
}
[SerializeField]
float3 m_CenterOfMass;
[SerializeField]
EulerAngles m_Orientation = EulerAngles.Default;
[SerializeField]
// Default value to solid unit sphere : https://en.wikipedia.org/wiki/List_of_moments_of_inertia
float3 m_InertiaTensor = new float3(2f / 5f);
public uint WorldIndex { get => m_WorldIndex; set => m_WorldIndex = value; }
[SerializeField]
[Tooltip("The index of the physics world this body belongs to. Default physics world has index 0.")]
uint m_WorldIndex = 0;
public CustomPhysicsBodyTags CustomTags { get => m_CustomTags; set => m_CustomTags = value; }
[SerializeField]
CustomPhysicsBodyTags m_CustomTags = CustomPhysicsBodyTags.Nothing;
void OnEnable()
{
// included so tick box appears in Editor
}
void OnValidate()
{
m_Mass = math.max(k_MinimumMass, m_Mass);
m_LinearDamping = math.max(m_LinearDamping, 0f);
m_AngularDamping = math.max(m_AngularDamping, 0f);
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/PhysicsShapeAuthoring.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using UnityMesh = UnityEngine.Mesh;
namespace Unity.Physics.Authoring
{
public sealed class UnimplementedShapeException : NotImplementedException
{
public UnimplementedShapeException(ShapeType shapeType)
: base($"Unknown shape type {shapeType} requires explicit implementation") {}
}
#if UNITY_2021_2_OR_NEWER
[Icon(k_IconPath)]
#endif
[AddComponentMenu("Entities/Physics/Physics Shape")]
public sealed class PhysicsShapeAuthoring : MonoBehaviour, IInheritPhysicsMaterialProperties, ISerializationCallbackReceiver
{
const string k_IconPath = "Packages/com.unity.physics/Unity.Physics.Editor/Editor Default Resources/Icons/d_BoxCollider@64.png";
PhysicsShapeAuthoring() {}
[Serializable]
struct CylindricalProperties
{
public float Height;
public float Radius;
[HideInInspector]
public int Axis;
}
[SerializeField]
[ExpandChildren]
CylindricalProperties m_Capsule = new CylindricalProperties { Height = 1f, Radius = 0.5f, Axis = 2 };
[SerializeField]
[ExpandChildren]
CylindricalProperties m_Cylinder = new CylindricalProperties { Height = 1f, Radius = 0.5f, Axis = 2 };
[SerializeField]
[Tooltip("How many sides the convex cylinder shape should have.")]
[Range(CylinderGeometry.MinSideCount, CylinderGeometry.MaxSideCount)]
int m_CylinderSideCount = 20;
[SerializeField]
float m_SphereRadius = 0.5f;
public ConvexHullGenerationParameters ConvexHullGenerationParameters => m_ConvexHullGenerationParameters;
[SerializeField]
[Tooltip(
"Specifies the minimum weight of a skinned vertex assigned to this shape and/or its transform children required for it to be included for automatic detection. " +
"A value of 0 will include all points with any weight assigned to this shape's hierarchy."
)]
[Range(0f, 1f)]
float m_MinimumSkinnedVertexWeight = 0.1f;
[SerializeField]
[ExpandChildren]
ConvexHullGenerationParameters m_ConvexHullGenerationParameters = ConvexHullGenerationParameters.Default.ToAuthoring();
// TODO: remove this accessor in favor of GetRawVertices() when blob data is serializable
internal UnityMesh CustomMesh => m_CustomMesh;
[SerializeField]
[Tooltip("If no custom mesh is specified, then one will be generated using this body's rendered meshes.")]
UnityMesh m_CustomMesh;
public bool ForceUnique { get => m_ForceUnique; set => m_ForceUnique = value; }
[SerializeField]
bool m_ForceUnique;
public PhysicsMaterialTemplate MaterialTemplate { get => m_Material.Template; set => m_Material.Template = value; }
PhysicsMaterialTemplate IInheritPhysicsMaterialProperties.Template
{
get => m_Material.Template;
set => m_Material.Template = value;
}
public bool OverrideCollisionResponse { get => m_Material.OverrideCollisionResponse; set => m_Material.OverrideCollisionResponse = value; }
public CollisionResponsePolicy CollisionResponse { get => m_Material.CollisionResponse; set => m_Material.CollisionResponse = value; }
public bool OverrideFriction { get => m_Material.OverrideFriction; set => m_Material.OverrideFriction = value; }
public PhysicsMaterialCoefficient Friction { get => m_Material.Friction; set => m_Material.Friction = value; }
public bool OverrideRestitution
{
get => m_Material.OverrideRestitution;
set => m_Material.OverrideRestitution = value;
}
public PhysicsMaterialCoefficient Restitution
{
get => m_Material.Restitution;
set => m_Material.Restitution = value;
}
public bool OverrideBelongsTo
{
get => m_Material.OverrideBelongsTo;
set => m_Material.OverrideBelongsTo = value;
}
void AppendMeshPropertiesToNativeBuffers(
float4x4 localToWorld, UnityMesh mesh, NativeList<float3> vertices, NativeList<int3> triangles, bool validate,
NativeList<HashableShapeInputs> inputs, HashSet<UnityMesh> meshAssets
)
{
if (mesh == null || validate && !mesh.IsValidForConversion(gameObject))
return;
var childToShape = math.mul(transform.worldToLocalMatrix, localToWorld);
AppendMeshPropertiesToNativeBuffers(childToShape, mesh, vertices, triangles, inputs, meshAssets);
}
internal static void AppendMeshPropertiesToNativeBuffers(
float4x4 childToShape, UnityMesh mesh, NativeList<float3> vertices, NativeList<int3> triangles,
NativeList<HashableShapeInputs> inputs, HashSet<UnityMesh> meshAssets
)
{
var offset = 0u;
#if UNITY_EDITOR
// TODO: when min spec is 2020.1, collect all meshes and their data via single Burst job rather than one at a time
using (var meshData = UnityEditor.MeshUtility.AcquireReadOnlyMeshData(mesh))
#else
using (var meshData = UnityMesh.AcquireReadOnlyMeshData(mesh))
#endif
{
if (vertices.IsCreated)
{
offset = (uint)vertices.Length;
var tmpVertices = new NativeArray<Vector3>(meshData[0].vertexCount, Allocator.Temp);
meshData[0].GetVertices(tmpVertices);
if (vertices.Capacity < vertices.Length + tmpVertices.Length)
vertices.Capacity = vertices.Length + tmpVertices.Length;
foreach (var v in tmpVertices)
vertices.Add(math.mul(childToShape, new float4(v, 1f)).xyz);
}
if (triangles.IsCreated)
{
switch (meshData[0].indexFormat)
{
case IndexFormat.UInt16:
var indices16 = meshData[0].GetIndexData<ushort>();
var numTriangles = indices16.Length / 3;
if (triangles.Capacity < triangles.Length + numTriangles)
triangles.Capacity = triangles.Length + numTriangles;
for (var sm = 0; sm < meshData[0].subMeshCount; ++sm)
{
var subMesh = meshData[0].GetSubMesh(sm);
for (int i = subMesh.indexStart, count = 0; count < subMesh.indexCount; i += 3, count += 3)
triangles.Add((int3) new uint3(offset + indices16[i], offset + indices16[i + 1], offset + indices16[i + 2]));
}
break;
case IndexFormat.UInt32:
var indices32 = meshData[0].GetIndexData<uint>();
numTriangles = indices32.Length / 3;
if (triangles.Capacity < triangles.Length + numTriangles)
triangles.Capacity = triangles.Length + numTriangles;
for (var sm = 0; sm < meshData[0].subMeshCount; ++sm)
{
var subMesh = meshData[0].GetSubMesh(sm);
for (int i = subMesh.indexStart, count = 0; count < subMesh.indexCount; i += 3, count += 3)
triangles.Add((int3) new uint3(offset + indices32[i], offset + indices32[i + 1], offset + indices32[i + 2]));
}
break;
}
}
}
if (inputs.IsCreated)
inputs.Add(HashableShapeInputs.FromMesh(mesh, childToShape));
meshAssets?.Add(mesh);
}
void UpdateCapsuleAxis()
{
var cmax = math.cmax(m_PrimitiveSize);
var cmin = math.cmin(m_PrimitiveSize);
if (cmin == cmax)
return;
m_Capsule.Axis = m_PrimitiveSize.GetMaxAxis();
}
void UpdateCylinderAxis() => m_Cylinder.Axis = m_PrimitiveSize.GetDeviantAxis();
m_ConvexHullGenerationParameters.BevelRadius = geometry.BevelRadius;
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetSphere(SphereGeometry geometry, quaternion orientation)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(orientation);
SetSphere(geometry, euler);
}
internal void SetSphere(SphereGeometry geometry)
{
SetSphere(geometry, m_PrimitiveOrientation);
}
internal void SetSphere(SphereGeometry geometry, EulerAngles orientation)
{
m_ShapeType = ShapeType.Sphere;
m_PrimitiveCenter = geometry.Center;
var radius = math.max(0f, geometry.Radius);
m_PrimitiveSize = new float3(2f * radius, 2f * radius, 2f * radius);
m_PrimitiveOrientation = orientation;
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetPlane(float3 center, float2 size, quaternion orientation)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(orientation);
SetPlane(center, size, euler);
}
internal void SetPlane(float3 center, float2 size, EulerAngles orientation)
{
m_ShapeType = ShapeType.Plane;
m_PrimitiveCenter = center;
m_PrimitiveOrientation = orientation;
m_PrimitiveSize = new float3(size.x, 0f, size.y);
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetConvexHull(
ConvexHullGenerationParameters hullGenerationParameters, float minimumSkinnedVertexWeight
)
{
m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
SetConvexHull(hullGenerationParameters);
}
public void SetConvexHull(ConvexHullGenerationParameters hullGenerationParameters, UnityMesh customMesh = null)
{
m_ShapeType = ShapeType.ConvexHull;
m_CustomMesh = customMesh;
hullGenerationParameters.OnValidate();
m_ConvexHullGenerationParameters = hullGenerationParameters;
}
public void SetMesh(UnityMesh mesh = null)
{
m_ShapeType = ShapeType.Mesh;
m_CustomMesh = mesh;
}
void OnEnable()
{
// included so tick box appears in Editor
}
const int k_LatestVersion = 1;
[SerializeField]
int m_SerializedVersion = 0;
void ISerializationCallbackReceiver.OnBeforeSerialize() {}
void ISerializationCallbackReceiver.OnAfterDeserialize() => UpgradeVersionIfNecessary();
#pragma warning disable 618
void UpgradeVersionIfNecessary()
{
if (m_SerializedVersion < k_LatestVersion)
{
// old data from version < 1 have been removed
if (m_SerializedVersion < 1)
m_SerializedVersion = 1;
}
}
#pragma warning restore 618
static void Validate(ref CylindricalProperties props)
{
props.Height = math.max(0f, props.Height);
props.Radius = math.max(0f, props.Radius);
}
void OnValidate()
{
UpgradeVersionIfNecessary();
m_PrimitiveSize = math.max(m_PrimitiveSize, new float3());
Validate(ref m_Capsule);
Validate(ref m_Cylinder);
switch (m_ShapeType)
{
case ShapeType.Box:
SetBox(GetBoxProperties(out var orientation), orientation);
break;
case ShapeType.Capsule:
SetCapsule(GetCapsuleProperties());
break;
case ShapeType.Cylinder:
SetCylinder(GetCylinderProperties(out orientation), orientation);
break;
case ShapeType.Sphere:
SetSphere(GetSphereProperties(out orientation), orientation);
break;
case ShapeType.Plane:
GetPlaneProperties(out var center, out var size2D, out orientation);
SetPlane(center, size2D, orientation);
break;
case ShapeType.ConvexHull:
case ShapeType.Mesh:
break;
default:
throw new UnimplementedShapeException(m_ShapeType);
}
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
m_CylinderSideCount =
math.clamp(m_CylinderSideCount, CylinderGeometry.MinSideCount, CylinderGeometry.MaxSideCount);
m_ConvexHullGenerationParameters.OnValidate();
PhysicsMaterialProperties.OnValidate(ref m_Material, true);
}
// matrix to transform point from shape space into world space
public float4x4 GetShapeToWorldMatrix() =>
new float4x4(Math.DecomposeRigidBodyTransform(transform.localToWorldMatrix));
// matrix to transform point from object's local transform matrix into shape space
internal float4x4 GetLocalToShapeMatrix() =>
math.mul(math.inverse(GetShapeToWorldMatrix()), transform.localToWorldMatrix);
internal unsafe void BakePoints(NativeArray<float3> points)
{
var localToShapeQuantized = GetLocalToShapeMatrix();
using (var aabb = new NativeArray<Aabb>(1, Allocator.TempJob))
{
new PhysicsShapeExtensions.GetAabbJob { Points = points, Aabb = aabb }.Run();
HashableShapeInputs.GetQuantizedTransformations(localToShapeQuantized, aabb[0], out localToShapeQuantized);
}
using (var bakedPoints = new NativeArray<float3>(points.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory))
{
new BakePointsJob
{
Points = points,
LocalToShape = localToShapeQuantized,
Output = bakedPoints
}.Schedule(points.Length, 16).Complete();
UnsafeUtility.MemCpy(points.GetUnsafePtr(), bakedPoints.GetUnsafePtr(), points.Length * UnsafeUtility.SizeOf<float3>());
}
}
[BurstCompile]
struct BakePointsJob : IJobParallelFor
{
[Collections.ReadOnly]
public NativeArray<float3> Points;
public float4x4 LocalToShape;
public NativeArray<float3> Output;
public void Execute(int index) => Output[index] = math.mul(LocalToShape, new float4(Points[index], 1f)).xyz;
}
/// <summary>
/// Fit this shape to render geometry in its GameObject hierarchy. Children in the hierarchy will
/// influence the result if they have enabled MeshRenderer components or have vertices bound to
/// them on a SkinnedMeshRenderer. Children will only count as influences if this shape is the
/// first ancestor shape in their hierarchy. As such, you should add shape components to all
/// GameObjects that should have them before you call this method on any of them.
/// </summary>
///
/// <exception cref="UnimplementedShapeException"> Thrown when an Unimplemented Shape error
/// condition occurs. </exception>
///
/// <param name="minimumSkinnedVertexWeight"> (Optional)
/// The minimum total weight that a vertex in a skinned mesh must have assigned to this object
/// and/or any of its influencing children. </param>
public void FitToEnabledRenderMeshes(float minimumSkinnedVertexWeight = 0f)
{
var shapeType = m_ShapeType;
m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
using (var points = new NativeList<float3>(65535, Allocator.Persistent))
{
// temporarily un-assign custom mesh and assume this shape is a convex hull
var customMesh = m_CustomMesh;
m_CustomMesh = null;
GetConvexHullProperties(points, Application.isPlaying, default, default, default, default);
m_CustomMesh = customMesh;
if (points.Length == 0)
return;
// TODO: find best rotation, particularly if any points came from skinned mesh
var orientation = quaternion.identity;
var bounds = new Bounds(points[0], float3.zero);
for (int i = 1, count = points.Length; i < count; ++i)
bounds.Encapsulate(points[i]);
SetBox(
new BoxGeometry
{
Center = bounds.center,
Size = bounds.size,
Orientation = orientation,
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius
}
);
}
switch (shapeType)
{
case ShapeType.Capsule:
SetCapsule(GetCapsuleProperties());
break;
case ShapeType.Cylinder:
SetCylinder(GetCylinderProperties(out var orientation), orientation);
break;
case ShapeType.Sphere:
SetSphere(GetSphereProperties(out orientation), orientation);
break;
case ShapeType.Plane:
// force recalculation of plane orientation by making it think shape type is out of date
m_ShapeType = ShapeType.Box;
GetPlaneProperties(out var center, out var size2D, out orientation);
SetPlane(center, size2D, orientation);
break;
case ShapeType.Box:
case ShapeType.ConvexHull:
case ShapeType.Mesh:
m_ShapeType = shapeType;
break;
default:
throw new UnimplementedShapeException(shapeType);
}
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
[Conditional("UNITY_EDITOR")]
void Reset()
{
#if UNITY_EDITOR
InitializeConvexHullGenerationParameters();
FitToEnabledRenderMeshes(m_MinimumSkinnedVertexWeight);
// TODO: also pick best primitive shape
UnityEditor.SceneView.RepaintAll();
#endif
}
public void InitializeConvexHullGenerationParameters()
{
var pointCloud = new NativeList<float3>(65535, Allocator.Temp);
GetConvexHullProperties(pointCloud, false, default, default, default, default);
m_ConvexHullGenerationParameters.InitializeToRecommendedAuthoringValues(pointCloud.AsArray());
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/BakingSystems/PhysicsBodyBakingSystem.cs
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics.GraphicsIntegration;
using UnityEngine;
using LegacyRigidbody = UnityEngine.Rigidbody;
using LegacyCollider = UnityEngine.Collider;
namespace Unity.Physics.Authoring
{
[TemporaryBakingType]
public struct PhysicsBodyAuthoringData : IComponentData
{
public bool IsDynamic;
public float Mass;
public bool OverrideDefaultMassDistribution;
public MassDistribution CustomMassDistribution;
}
class PhysicsBodyAuthoringBaker : BasePhysicsBaker<PhysicsBodyAuthoring>
{
internal List<UnityEngine.Collider> colliderComponents = new List<UnityEngine.Collider>();
internal List<PhysicsShapeAuthoring> physicsShapeComponents = new List<PhysicsShapeAuthoring>();
public override void Bake(PhysicsBodyAuthoring authoring)
{
// Priority is to Legacy Components. Ignore if baked by Legacy.
if (GetComponent<LegacyRigidbody>() || GetComponent<LegacyCollider>())
{
return;
}
var entity = GetEntity(TransformUsageFlags.Dynamic);
// To process later in the Baking System
AddComponent(entity, new PhysicsBodyAuthoringData
{
IsDynamic = (authoring.MotionType == BodyMotionType.Dynamic),
Mass = authoring.Mass,
OverrideDefaultMassDistribution = authoring.OverrideDefaultMassDistribution,
CustomMassDistribution = authoring.CustomMassDistribution
});
AddSharedComponent(entity, new PhysicsWorldIndex(authoring.WorldIndex));
var bodyTransform = GetComponent<Transform>();
var motionType = authoring.MotionType;
var hasSmoothing = authoring.Smoothing != BodySmoothing.None;
PostProcessTransform(bodyTransform, motionType);
var customTags = authoring.CustomTags;
if (!customTags.Equals(CustomPhysicsBodyTags.Nothing))
AddComponent(entity, new PhysicsCustomTags { Value = customTags.Value });
// Check that there is at least one collider in the hierarchy to add these three
GetComponentsInChildren(colliderComponents);
GetComponentsInChildren(physicsShapeComponents);
if (colliderComponents.Count > 0 || physicsShapeComponents.Count > 0)
{
AddComponent(entity, new PhysicsCompoundData()
{
AssociateBlobToBody = false,
ConvertedBodyInstanceID = authoring.GetInstanceID(),
Hash = default,
});
AddComponent<PhysicsRootBaked>(entity);
AddComponent<PhysicsCollider>(entity);
AddBuffer<PhysicsColliderKeyEntityPair>(entity);
}
if (authoring.MotionType == BodyMotionType.Static || IsStatic())
return;
var massProperties = MassProperties.UnitSphere;
AddComponent(entity, authoring.MotionType == BodyMotionType.Dynamic ?
PhysicsMass.CreateDynamic(massProperties, authoring.Mass) :
PhysicsMass.CreateKinematic(massProperties));
var physicsVelocity = new PhysicsVelocity
{
Linear = authoring.InitialLinearVelocity,
Angular = authoring.InitialAngularVelocity
};
AddComponent(entity, physicsVelocity);
if (authoring.MotionType == BodyMotionType.Dynamic)
{
// TODO make these optional in editor?
AddComponent(entity, new PhysicsDamping
{
Linear = authoring.LinearDamping,
Angular = authoring.AngularDamping
});
if (authoring.GravityFactor != 1)
{
AddComponent(entity, new PhysicsGravityFactor
{
Value = authoring.GravityFactor
});
}
}
else if (authoring.MotionType == BodyMotionType.Kinematic)
{
AddComponent(entity, new PhysicsGravityFactor
{
Value = 0
});
}
if (hasSmoothing)
{
AddComponent(entity, new PhysicsGraphicalSmoothing());
if (authoring.Smoothing == BodySmoothing.Interpolation)
{
AddComponent(entity, new PhysicsGraphicalInterpolationBuffer
{
PreviousTransform = Math.DecomposeRigidBodyTransform(bodyTransform.localToWorldMatrix),
PreviousVelocity = physicsVelocity,
});
}
}
}
}
[RequireMatchingQueriesForUpdate]
[UpdateAfter(typeof(EndColliderBakingSystem))]
[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
public partial class PhysicsBodyBakingSystem : SystemBase
{
protected override void OnUpdate()
{
// Fill in the MassProperties based on the potential calculated value by BuildCompoundColliderBakingSystem
foreach (var(physicsMass, bodyData, collider) in
SystemAPI.Query<RefRW<PhysicsMass>, RefRO<PhysicsBodyAuthoringData>, RefRO<PhysicsCollider>>().WithOptions(EntityQueryOptions.IncludePrefab | EntityQueryOptions
{
// Build mass component
var massProperties = collider.ValueRO.MassProperties;
if (bodyData.ValueRO.OverrideDefaultMassDistribution)
{
massProperties.MassDistribution = bodyData.ValueRO.CustomMassDistribution;
// Increase the angular expansion factor to account for the shift in center of mass
massProperties.AngularExpansionFactor += math.length(massProperties.MassDistribution.Transform.pos - bodyData.ValueRO.CustomMassDistribution.Transform.pos);
}
physicsMass.ValueRW = bodyData.ValueRO.IsDynamic ?
PhysicsMass.CreateDynamic(massProperties, bodyData.ValueRO.Mass) :
PhysicsMass.CreateKinematic(massProperties);
}
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/BakingSystems/PhysicsShapeBakingSystem.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Profiling;
using LegacyCollider = UnityEngine.Collider;
using UnityMesh = UnityEngine.Mesh;
namespace Unity.Physics.Authoring
{
class PhysicsShapeBaker : BaseColliderBaker<PhysicsShapeAuthoring>
{
public static List<PhysicsShapeAuthoring> physicsShapeComponents = new List<PhysicsShapeAuthoring>();
public static List<LegacyCollider> legacyColliderComponents = new List<LegacyCollider>();
bool ShouldConvertShape(PhysicsShapeAuthoring authoring)
{
return authoring.enabled;
}
private GameObject GetPrimaryBody(GameObject shape, out bool hasBodyComponent, out bool isStaticBody)
{
var pb = FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
var rb = FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
hasBodyComponent = (pb != null || rb != null);
isStaticBody = false;
if (pb != null)
{
return rb == null ? pb.gameObject :
pb.transform.IsChildOf(rb.transform) ? pb.gameObject : rb.gameObject;
}
if (rb != null)
return rb.gameObject;
// for implicit static shape, first see if it is part of static optimized hierarchy
isStaticBody = FindTopmostStaticEnabledAncestor(shape, out var topStatic);
if (topStatic != null)
return topStatic;
// otherwise, find topmost enabled Collider or PhysicsShapeAuthoring
var topCollider = FindTopmostEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_CollidersBuffer);
var topShape = FindTopmostEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_ShapesBuffer);
return topCollider == null
? topShape == null ? shape.gameObject : topShape
: topShape == null
? topCollider
: topShape.transform.IsChildOf(topCollider.transform)
? topCollider
: topShape;
}
ShapeComputationDataBaking GetInputDataFromAuthoringComponent(PhysicsShapeAuthoring shape, Entity colliderEntity)
{
GameObject shapeGameObject = shape.gameObject;
var body = GetPrimaryBody(shapeGameObject, out bool hasBodyComponent, out bool isStaticBody);
var child = shapeGameObject;
var shapeInstanceID = shape.GetInstanceID();
var bodyEntity = GetEntity(body, TransformUsageFlags.Dynamic);
// prepare the static root
if (isStaticBody)
{
var staticRootMarker = CreateAdditionalEntity(TransformUsageFlags.Dynamic, true, "StaticRootBakeMarker");
AddComponent(staticRootMarker, new BakeStaticRoot() { Body = bodyEntity, ConvertedBodyInstanceID = body.transform.GetInstanceID() });
}
// Track dependencies to the transforms
Transform shapeTransform = GetComponent<Transform>(shape);
Transform bodyTransform = GetComponent<Transform>(body);
var instance = new ColliderInstanceBaking
{
AuthoringComponentId = shapeInstanceID,
BodyEntity = bodyEntity,
ShapeEntity = GetEntity(shapeGameObject, TransformUsageFlags.Dynamic),
ChildEntity = GetEntity(child, TransformUsageFlags.Dynamic),
BodyFromShape = ColliderInstanceBaking.GetCompoundFromChild(shapeTransform, bodyTransform),
};
var data = GenerateComputationData(shape, instance, colliderEntity);
data.Instance.ConvertedAuthoringInstanceID = shapeInstanceID;
data.Instance.ConvertedBodyInstanceID = bodyTransform.GetInstanceID();
var rb = FindFirstEnabledAncestor(shapeGameObject, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
var pb = FindFirstEnabledAncestor(shapeGameObject, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
// The LegacyRigidBody cannot know about the ShapeAuthoringComponent. We need to take responsibility of baking the collider.
if (rb || (!rb && !pb) && body == shapeGameObject)
{
GetComponents(physicsShapeComponents);
GetComponents(legacyColliderComponents);
// We need to check that there are no other colliders in the same object, if so, only the first one should do this, otherwise there will be 2 bakers adding this to the enti
// This will be needed to trigger BuildCompoundColliderBakingSystem
// If they are legacy Colliders and PhysicsShapeAuthoring in the same object, the PhysicsShapeAuthoring will add this
if (legacyColliderComponents.Count == 0 && physicsShapeComponents.Count > 0 && physicsShapeComponents[0].GetInstanceID() == shapeInstanceID)
{
var entity = GetEntity(TransformUsageFlags.Dynamic);
// // Rigid Body bakes always add the PhysicsWorldIndex component and process transform
if (!hasBodyComponent)
{
AddSharedComponent(entity, new PhysicsWorldIndex());
PostProcessTransform(bodyTransform);
}
AddComponent(entity, new PhysicsCompoundData()
{
AssociateBlobToBody = false,
ConvertedBodyInstanceID = shapeInstanceID,
Hash = default,
});
AddComponent<PhysicsRootBaked>(entity);
AddComponent<PhysicsCollider>(entity);
AddBuffer<PhysicsColliderKeyEntityPair>(entity);
}
}
return data;
}
Material ProduceMaterial(PhysicsShapeAuthoring shape)
{
var materialTemplate = shape.MaterialTemplate;
if (materialTemplate != null)
DependsOn(materialTemplate);
return shape.GetMaterial();
}
CollisionFilter ProduceCollisionFilter(PhysicsShapeAuthoring shape)
{
return shape.GetFilter();
}
UnityEngine.Mesh GetMesh(PhysicsShapeAuthoring shape, out float4x4 childToShape)
{
var mesh = shape.CustomMesh;
childToShape = float4x4.identity;
if (mesh == null)
{
// Try to get a mesh in the children
var filter = GetComponentInChildren<MeshFilter>();
if (filter != null && filter.sharedMesh != null)
{
mesh = filter.sharedMesh;
var childTransform = GetComponent<Transform>(filter);
childToShape = math.mul(shape.transform.worldToLocalMatrix, childTransform.localToWorldMatrix);;
}
}
if (mesh == null)
{
throw new InvalidOperationException(
$"No {nameof(PhysicsShapeAuthoring.CustomMesh)} assigned on {shape.name}."
);
}
DependsOn(mesh);
return mesh;
}
namespace Unity.Physics.Authoring
{
[BakingType]
public struct JointEntityBaking : IComponentData
{
public Entity Entity;
}
public class BallAndSocketJoint : BaseJoint
{
// Editor only settings
[HideInInspector]
public bool EditPivots;
[Tooltip("If checked, PositionLocal will snap to match PositionInConnectedEntity")]
public bool AutoSetConnected = true;
namespace Unity.Physics.Authoring
{
public abstract class BaseJoint : BaseBodyPairConnector
{
public bool EnableCollision;
public float3 MaxImpulse = float.PositiveInfinity;
void OnEnable()
{
// included so tick box appears in Editor
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/FreeHingeJoint.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
public class FreeHingeJoint : BallAndSocketJoint
{
// Editor only settings
[HideInInspector]
public bool EditAxes;
public float3 HingeAxisLocal;
public float3 HingeAxisInConnectedEntity;
public override void UpdateAuto()
{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
HingeAxisInConnectedEntity = math.mul(bFromA.rot, HingeAxisLocal);
}
}
}
class FreeHingeJointBaker : JointBaker<FreeHingeJoint>
{
public override void Bake(FreeHingeJoint authoring)
{
authoring.UpdateAuto();
Math.CalculatePerpendicularNormalized(authoring.HingeAxisLocal, out var perpendicularLocal, out _);
Math.CalculatePerpendicularNormalized(authoring.HingeAxisInConnectedEntity, out var perpendicularConnected, out _);
var physicsJoint = PhysicsJoint.CreateHinge(
new BodyFrame {Axis = authoring.HingeAxisLocal, Position = authoring.PositionLocal, PerpendicularAxis = perpendicularLocal},
new BodyFrame {Axis = authoring.HingeAxisInConnectedEntity, Position = authoring.PositionInConnectedEntity, PerpendicularAxis = perpendicularConnected }
);
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntity(worldIndex, constraintBodyPair, physicsJoint);
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/LimitDOFJoint.cs
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
// This Joint allows you to lock one or more of the 6 degrees of freedom of a constrained body.
// This is achieved by combining the appropriate lower level 'constraint atoms' to form the higher level Joint.
// In this case Linear and Angular constraint atoms are combined.
// One use-case for this Joint could be to restrict a 3d simulation to a 2d plane.
public class LimitDOFJoint : BaseJoint
{
public bool3 LockLinearAxes;
public bool3 LockAngularAxes;
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
namespace Unity.Physics.Authoring
{
// stores an initial value and a pair of scalar curves to apply to relevant constraints on the joint
struct ModifyJointLimits : ISharedComponentData, IEquatable<ModifyJointLimits>
{
public PhysicsJoint InitialValue;
public ParticleSystem.MinMaxCurve AngularRangeScalar;
public ParticleSystem.MinMaxCurve LinearRangeScalar;
public bool Equals(ModifyJointLimits other) =>
AngularRangeScalar.Equals(other.AngularRangeScalar) && LinearRangeScalar.Equals(other.LinearRangeScalar);
public override bool Equals(object obj) => obj is ModifyJointLimits other && Equals(other);
_ModifyJointLimitsBakingDataQuery.AddChangedVersionFilter(typeof(ModifyJointLimitsBakingData));
_JointEntityBakingQuery.AddChangedVersionFilter(typeof(JointEntityBaking));
}
public void OnUpdate(ref SystemState state)
{
if (_ModifyJointLimitsBakingDataQuery.IsEmpty && _JointEntityBakingQuery.IsEmpty)
{
return;
}
// Collect all the joints
NativeParallelMultiHashMap<Entity, (Entity, PhysicsJoint)> jointsLookUp =
new NativeParallelMultiHashMap<Entity, (Entity, PhysicsJoint)>(10, Allocator.TempJob);
foreach (var(jointEntity, physicsJoint, entity) in SystemAPI
.Query<RefRO<JointEntityBaking>, RefRO<PhysicsJoint>>().WithEntityAccess()
.WithOptions(EntityQueryOptions.IncludeDisabledEntities | EntityQueryOptions.IncludePrefab))
{
jointsLookUp.Add(jointEntity.ValueRO.Entity, (entity, physicsJoint.ValueRO));
}
foreach (var(modifyJointLimits, entity) in SystemAPI.Query<ModifyJointLimitsBakingData>()
.WithEntityAccess().WithOptions(EntityQueryOptions.IncludeDisabledEntities |
EntityQueryOptions.IncludePrefab))
{
var angularModification = new ParticleSystem.MinMaxCurve(
multiplier: math.radians(modifyJointLimits.AngularRangeScalar.curveMultiplier),
min: modifyJointLimits.AngularRangeScalar.curveMin,
max: modifyJointLimits.AngularRangeScalar.curveMax
);
foreach (var joint in jointsLookUp.GetValuesForKey(entity))
{
state.EntityManager.SetSharedComponentManaged(joint.Item1, new ModifyJointLimits
{
InitialValue = joint.Item2,
AngularRangeScalar = angularModification,
LinearRangeScalar = modifyJointLimits.LinearRangeScalar
});
}
}
jointsLookUp.Dispose();
}
}
// apply an animated effect to the limits on supported types of joints
[RequireMatchingQueriesForUpdate]
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
[UpdateAfter(typeof(PhysicsSystemGroup))]
partial struct ModifyJointLimitsSystem : ISystem
{
public void OnUpdate(ref SystemState state)
{
var time = (float)SystemAPI.Time.ElapsedTime;
foreach (var(joint, modification) in SystemAPI.Query<RefRW<PhysicsJoint>, ModifyJointLimits>())
{
var animatedAngularScalar = new FloatRange(
modification.AngularRangeScalar.curveMin.Evaluate(time),
modification.AngularRangeScalar.curveMax.Evaluate(time)
);
var animatedLinearScalar = new FloatRange(
modification.LinearRangeScalar.curveMin.Evaluate(time),
modification.LinearRangeScalar.curveMax.Evaluate(time)
);
// in each case, get relevant properties from the initial value based on joint type, and apply scalar
switch (joint.ValueRW.JointType)
{
// Custom type could be anything, so this demo just applies changes to all constraints
case JointType.Custom:
var constraints = modification.InitialValue.GetConstraints();
for (var i = 0; i < constraints.Length; i++)
{
var constraint = constraints[i];
var isAngular = constraint.Type == ConstraintType.Angular;
var scalar = math.select(animatedLinearScalar, animatedAngularScalar, isAngular);
var constraintRange = (FloatRange)(new float2(constraint.Min, constraint.Max) * scalar);
constraint.Min = constraintRange.Min;
constraint.Max = constraintRange.Max;
constraints[i] = constraint;
}
joint.ValueRW.SetConstraints(constraints);
break;
// other types have corresponding getters/setters to retrieve more meaningful data
case JointType.LimitedDistance:
var distanceRange = modification.InitialValue.GetLimitedDistanceRange();
joint.ValueRW.SetLimitedDistanceRange(distanceRange * (float2)animatedLinearScalar);
break;
case JointType.LimitedHinge:
var angularRange = modification.InitialValue.GetLimitedHingeRange();
joint.ValueRW.SetLimitedHingeRange(angularRange * (float2)animatedAngularScalar);
break;
case JointType.Prismatic:
var distanceOnAxis = modification.InitialValue.GetPrismaticRange();
joint.ValueRW.SetPrismaticRange(distanceOnAxis * (float2)animatedLinearScalar);
break;
// ragdoll joints are composed of two separate joints with different meanings
case JointType.RagdollPrimaryCone:
modification.InitialValue.GetRagdollPrimaryConeAndTwistRange(
out var maxConeAngle,
out var angularTwistRange
);
joint.ValueRW.SetRagdollPrimaryConeAndTwistRange(
maxConeAngle * animatedAngularScalar.Max,
angularTwistRange * (float2)animatedAngularScalar
);
break;
case JointType.RagdollPerpendicularCone:
var angularPlaneRange = modification.InitialValue.GetRagdollPerpendicularConeRange();
joint.ValueRW.SetRagdollPerpendicularConeRange(angularPlaneRange *
(float2)animatedAngularScalar);
break;
// remaining types have no limits on their Constraint atoms to meaningfully modify
case JointType.BallAndSocket:
case JointType.Fixed:
case JointType.Hinge:
break;
}
}
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/PrismaticJoint.cs
using Unity.Entities;
using Unity.Mathematics;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class PrismaticJoint : BallAndSocketJoint
{
public float3 AxisLocal;
public float3 AxisInConnectedEntity;
public float3 PerpendicularAxisLocal;
public float3 PerpendicularAxisInConnectedEntity;
public float MinDistanceOnAxis;
public float MaxDistanceOnAxis;
MinPerpendicularAngle -= 90f;
MaxPerpendicularAngle -= 90f;
m_Version = k_LatestVersion;
}
void OnValidate()
{
UpgradeVersionIfNecessary();
MaxConeAngle = math.clamp(MaxConeAngle, 0f, 180f);
MaxPerpendicularAngle = math.clamp(MaxPerpendicularAngle, -90f, 90f);
MinPerpendicularAngle = math.clamp(MinPerpendicularAngle, -90f, 90f);
if (MaxPerpendicularAngle < MinPerpendicularAngle)
{
var swap = new FloatRange(MinPerpendicularAngle, MaxPerpendicularAngle).Sorted();
MinPerpendicularAngle = swap.Min;
MaxPerpendicularAngle = swap.Max;
}
MinTwistAngle = math.clamp(MinTwistAngle, -180f, 180f);
MaxTwistAngle = math.clamp(MaxTwistAngle, -180f, 180f);
if (MaxTwistAngle < MinTwistAngle)
{
var swap = new FloatRange(MinTwistAngle, MaxTwistAngle).Sorted();
MinTwistAngle = swap.Min;
MaxTwistAngle = swap.Max;
}
}
public override void UpdateAuto()
{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
TwistAxisInConnectedEntity = math.mul(bFromA.rot, TwistAxisLocal);
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
}
}
}
class RagdollJointBaker : JointBaker<RagdollJoint>
{
public override void Bake(RagdollJoint authoring)
{
authoring.UpdateAuto();
authoring.UpgradeVersionIfNecessary();
PhysicsJoint.CreateRagdoll(
new BodyFrame { Axis = authoring.TwistAxisLocal, PerpendicularAxis = authoring.PerpendicularAxisLocal, Position = authoring.PositionLocal },
new BodyFrame { Axis = authoring.TwistAxisInConnectedEntity, PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity, Position = authoring.PositionInConnectedEntit
math.radians(authoring.MaxConeAngle),
math.radians(new FloatRange(authoring.MinPerpendicularAngle, authoring.MaxPerpendicularAngle)),
math.radians(new FloatRange(authoring.MinTwistAngle, authoring.MaxTwistAngle)),
out var primaryCone,
out var perpendicularCone
);
primaryCone.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
perpendicularCone.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
using NativeList<Entity> entities = new NativeList<Entity>(1, Allocator.TempJob);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntities(worldIndex,
constraintBodyPair,
new NativeArray<PhysicsJoint>(2, Allocator.Temp) { [0] = primaryCone, [1] = perpendicularCone }, entities);
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/RigidJoint.cs
using Unity.Entities;
using Unity.Mathematics;
namespace Unity.Physics.Authoring
{
public class RigidJoint : BallAndSocketJoint
{
public quaternion OrientationLocal = quaternion.identity;
public quaternion OrientationInConnectedEntity = quaternion.identity;
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
namespace Unity.Physics.Authoring
{
public class AngularVelocityMotor : BaseJoint
{
[Tooltip("An offset from center of entity with motor. Representing the anchor/pivot point of rotation")]
public float3 PivotPosition;
[Tooltip("The axis of rotation of the motor. Value will be normalized")]
public float3 AxisOfRotation;
[Tooltip("Target speed for the motor to maintain, in degrees/s")]
public float TargetSpeed;
[Tooltip("The magnitude of the maximum impulse the motor can exert in a single step. Applies only to the motor constraint.")]
public float MaxImpulseAppliedByMotor = math.INFINITY;
private float3 PerpendicularAxisLocal;
private float3 PositionInConnectedEntity;
private float3 HingeAxisInConnectedEntity;
private float3 PerpendicularAxisInConnectedEntity;
class AngularVelocityMotorBaker : JointBaker<AngularVelocityMotor>
{
public override void Bake(AngularVelocityMotor authoring)
{
float3 axisInA = math.normalize(authoring.AxisOfRotation);
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.PivotPosition); //position of motored body pivot relative to Connected Entity in world space
authoring.HingeAxisInConnectedEntity = math.mul(bFromA.rot, axisInA); //motor axis in Connected Entity space
// Always calculate the perpendicular axes
Math.CalculatePerpendicularNormalized(axisInA, out var perpendicularLocal, out _);
authoring.PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, perpendicularLocal); //perp motor axis in Connected Entity space
namespace Unity.Physics.Authoring
{
public class PositionMotor : BaseJoint
{
[Tooltip("An offset from the center of the body with the motor, representing the anchor point of translation.")]
public float3 AnchorPosition;
[Tooltip("The direction of the motor, relative to the orientation of the Connected Body (bodyB). Value will be normalized")]
public float3 DirectionOfMovement;
[Tooltip("Motor will drive this length away from the anchor position of bodyA.")]
public float TargetDistance;
[Tooltip("The magnitude of the maximum impulse the motor can exert in a single step. Applies only to the motor constraint.")]
public float MaxImpulseAppliedByMotor = math.INFINITY;
private float3 PerpendicularAxisLocal;
private float3 PositionInConnectedEntity;
private float3 AxisInConnectedEntity;
private float3 PerpendicularAxisInConnectedEntity;
joint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntity(worldIndex, constraintBodyPair, joint);
}
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Motors/RotationalMotor.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
public class RotationalMotor : BaseJoint
{
[Tooltip("An offset from center of entity with motor. Representing the anchor/pivot point of rotation")]
public float3 PivotPosition;
[Tooltip("The axis of rotation of the motor. Value will be normalized")]
public float3 AxisOfRotation;
[Tooltip("Motor will maintain this target angle around the AxisOfRotation, in degrees")]
public float TargetAngle;
[Tooltip("The magnitude of the maximum impulse the motor can exert in one step. Applies only to the motor constraint.")]
public float MaxImpulseAppliedByMotor = math.INFINITY;
private float3 PerpendicularAxisLocal;
private float3 PositionInConnectedEntity;
private float3 HingeAxisInConnectedEntity;
private float3 PerpendicularAxisInConnectedEntity;
class RotationalMotorBaker : JointBaker<RotationalMotor>
{
public override void Bake(RotationalMotor authoring)
{
float3 axisInA = math.normalize(authoring.AxisOfRotation);
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.PivotPosition); //position of motored body pivot relative to Connected Entity in world space
authoring.HingeAxisInConnectedEntity = math.mul(bFromA.rot, axisInA); //motor axis in Connected Entity space
// Always calculate the perpendicular axes
Math.CalculatePerpendicularNormalized(axisInA, out var perpendicularLocal, out _);
authoring.PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, perpendicularLocal); //perp motor axis in Connected Entity space
#region Sphere
[BurstCompile]
struct BakeSphereJob : IJob
{
public NativeArray<SphereGeometry> Sphere;
public NativeArray<EulerAngles> Orientation;
// TODO: make members PascalCase after merging static query fixes
public float4x4 localToWorld;
public float4x4 shapeToWorld;
public void Execute()
{
var center = Sphere[0].Center;
var radius = Sphere[0].Radius;
var orientation = Orientation[0];
var basisToWorld = GetBasisToWorldMatrix(localToWorld, center, orientation, 1f);
var basisPriority = basisToWorld.HasShear() ? GetBasisAxisPriority(basisToWorld) : k_DefaultAxisPriority;
var bakeToShape = GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center, ref orientation, 1f, basisPriority);
radius *= math.cmax(bakeToShape.DecomposeScale());
Sphere[0] = new SphereGeometry
{
Center = center,
Radius = radius
};
Orientation[0] = orientation;
}
}
internal static SphereGeometry BakeToBodySpace(
this SphereGeometry sphere, float4x4 localToWorld, float4x4 shapeToWorld, ref EulerAngles orientation
)
{
using (var geometry = new NativeArray<SphereGeometry>(1, Allocator.TempJob) { [0] = sphere })
using (var outOrientation = new NativeArray<EulerAngles>(1, Allocator.TempJob) { [0] = orientation })
{
var job = new BakeSphereJob
{
Sphere = geometry,
Orientation = outOrientation,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld
};
job.Run();
orientation = outOrientation[0];
return geometry[0];
}
}
#endregion
#region Plane
[BurstCompile]
struct BakePlaneJob : IJob
{
public NativeArray<float3x4> Vertices;
// TODO: make members PascalCase after merging static query fixes
public float3 center;
public float2 size;
public EulerAngles orientation;
public float4x4 localToWorld;
public float4x4 shapeToWorld;
#endregion
#region ShapeInputHash
#if !(UNITY_ANDROID && !UNITY_64) // !Android32
// Getting memory alignment errors from HashUtility.Hash128 on Android32
[BurstCompile]
#endif
internal struct GetShapeInputsHashJob : IJob
{
public NativeArray<Hash128> Result;
#region AABB
[BurstCompile]
internal struct GetAabbJob : IJob
{
[ReadOnly] public NativeArray<float3> Points;
public NativeArray<Aabb> Aabb;
public void Execute()
{
var aabb = new Aabb { Min = float.MaxValue, Max = float.MinValue };
for (var i = 0; i < Points.Length; ++i)
aabb.Include(Points[i]);
Aabb[0] = aabb;
}
}
#endregion
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/BakeGeometryJobsExtensions.cs
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
static partial class PhysicsShapeExtensions
{
public static class BakeBoxJobExtension
{
internal static float4x4 GetBakeToShape(PhysicsShapeAuthoring shape, float3 center, EulerAngles orientation)
{
var transform = shape.transform;
var localToWorld = (float4x4)transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
return BakeBoxJob.GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientation);
}
}
public static class BakeCapsuleJobExtension
{
internal static float4x4 GetBakeToShape(PhysicsShapeAuthoring shape, float3 center, EulerAngles orientation)
{
var transform = shape.transform;
var localToWorld = (float4x4)transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
return BakeCapsuleJob.GetBakeToShape(localToWorld, shapeToWorld, ref center,
ref orientation);
}
}
public static void SetBakedCapsuleSize(this PhysicsShapeAuthoring shape, float height, float radius)
{
var capsule = shape.GetCapsuleProperties();
var center = capsule.Center;
namespace Unity.Physics.Authoring
{
/// <summary>
/// A structure for storing authoring data for a capsule shape. In contrast to the
/// CapsuleGeometry struct in the run-time, this structure permits storing stable orientation
/// values, as well as height values that can be retained when the source data are defined with
/// respect to a non-uniformly scaled object.
/// </summary>
[Serializable]
public struct CapsuleGeometryAuthoring : IEquatable<CapsuleGeometryAuthoring>
{
/// <summary>
/// The local orientation of the capsule. It is aligned with the forward axis (z) when it is
/// identity.
/// </summary>
public quaternion Orientation { get => m_OrientationEuler; set => m_OrientationEuler.SetValue(value); }
internal EulerAngles OrientationEuler { get => m_OrientationEuler; set => m_OrientationEuler = value; }
[SerializeField]
EulerAngles m_OrientationEuler;
/// <summary> The local position offset of the capsule. </summary>
public float3 Center { get => m_Center; set => m_Center = value; }
[SerializeField]
float3 m_Center;
/// <summary>
/// The height of the capsule. It may store any value, but will ultimately always be converted
/// into a value that is at least twice the radius.
/// </summary>
public float Height { get => m_Height; set => m_Height = value; }
[SerializeField]
float m_Height;
namespace Unity.Physics.Authoring
{
public static class ConvexHullGenerationParametersExtensions
{
// recommended simplification tolerance is at least 1 centimeter
internal const float k_MinRecommendedSimplificationTolerance = 0.01f;
if (points.Length <= 1)
return;
internal static void OnValidate(ref this ConvexHullGenerationParameters generationParameters, float maxAngle = 180f)
{
generationParameters.SimplificationTolerance = math.max(0f, generationParameters.SimplificationTolerance);
generationParameters.BevelRadius = math.max(0f, generationParameters.BevelRadius);
generationParameters.MinimumAngle = math.clamp(generationParameters.MinimumAngle, 0f, maxAngle);
}
namespace Unity.Physics.Authoring
{
[Serializable]
internal struct EulerAngles : IEquatable<EulerAngles>
{
public static EulerAngles Default => new EulerAngles { RotationOrder = math.RotationOrder.ZXY };
public float3 Value;
[HideInInspector]
public math.RotationOrder RotationOrder;
internal void SetValue(quaternion value) => Value = math.degrees(Math.ToEulerAngles(value, RotationOrder));
if (m_CheckIfComponentBelongsToShape)
{
if (PhysicsShapeExtensions.GetPrimaryBody(child.gameObject) != m_PrimaryBody)
return false;
child.gameObject.GetComponentsInParent(true, s_PhysicsShapes);
if (s_PhysicsShapes[0] != m_Shape)
{
s_PhysicsShapes.Clear();
return false;
}
}
// do not simply use GameObject.activeInHierarchy because it will be false when instantiating a prefab
var t = child.transform;
var activeInHierarchy = t.gameObject.activeSelf;
while (activeInHierarchy && t != m_Root)
{
t = t.parent;
activeInHierarchy &= t.gameObject.activeSelf;
}
return activeInHierarchy;
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/PhysicsShapeExtensions.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
// put static UnityObject buffers in separate utility class so other methods can Burst compile
static class PhysicsShapeExtensions_NonBursted
{
internal static readonly List<PhysicsBodyAuthoring> s_PhysicsBodiesBuffer = new List<PhysicsBodyAuthoring>(16);
internal static readonly List<PhysicsShapeAuthoring> s_ShapesBuffer = new List<PhysicsShapeAuthoring>(16);
internal static readonly List<Rigidbody> s_RigidbodiesBuffer = new List<Rigidbody>(16);
internal static readonly List<UnityEngine.Collider> s_CollidersBuffer = new List<UnityEngine.Collider>(16);
}
public static partial class PhysicsShapeExtensions
{
// used for de-skewing basis vectors; default priority assumes primary axis is z, secondary axis is y
public static readonly int3 k_DefaultAxisPriority = new int3(2, 1, 0);
// matrix to transform point from shape's local basis into world space
public static float4x4 GetBasisToWorldMatrix(
float4x4 localToWorld, float3 center, quaternion orientation, float3 size
) =>
math.mul(localToWorld, float4x4.TRS(center, orientation, size));
static float4 DeskewSecondaryAxis(float4 primaryAxis, float4 secondaryAxis)
{
var n0 = math.normalizesafe(primaryAxis);
var dot = math.dot(secondaryAxis, n0);
return secondaryAxis - n0 * dot;
}
// priority is determined by length of each size dimension in the shape's basis after applying localToWorld transformation
public static int3 GetBasisAxisPriority(float4x4 basisToWorld)
{
var basisAxisLengths = basisToWorld.DecomposeScale();
var max = math.cmax(basisAxisLengths);
var min = math.cmin(basisAxisLengths);
if (max == min)
return k_DefaultAxisPriority;
basisAxisLengths = basisToWorld.DecomposeScale();
min = math.cmin(basisAxisLengths);
var imin = min == basisAxisLengths.x ? 0 : min == basisAxisLengths.y ? 1 : 2;
if (imin == imax)
imin = k_NextAxis[imax];
var imid = k_NextAxis[imax] == imin ? k_PrevAxis[imax] : k_NextAxis[imax];
[Conditional(CompilationSymbols.CollectionsChecksSymbol), Conditional(CompilationSymbols.DebugChecksSymbol)]
static void CheckBasisPriorityAndThrow(int3 basisPriority)
{
if (
basisPriority.x == basisPriority.y
|| basisPriority.x == basisPriority.z
|| basisPriority.y == basisPriority.z
)
throw new ArgumentException(nameof(basisPriority));
}
public static bool HasNonUniformScale(this float4x4 m)
{
var s = new float3(math.lengthsq(m.c0.xyz), math.lengthsq(m.c1.xyz), math.lengthsq(m.c2.xyz));
return math.cmin(s) != math.cmax(s);
}
// matrix to transform point on a primitive from bake space into space of the shape
internal static float4x4 GetPrimitiveBakeToShapeMatrix(
float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center, ref EulerAngles orientation, float3 scale, int3 basisPriority
)
{
CheckBasisPriorityAndThrow(basisPriority);
var localToBasis = float4x4.TRS(center, orientation, scale);
// correct for imprecision in cases of no scale to prevent e.g., convex radius from being altered
if (scale.Equals(new float3(1f)))
{
localToBasis.c0 = math.normalizesafe(localToBasis.c0);
localToBasis.c1 = math.normalizesafe(localToBasis.c1);
localToBasis.c2 = math.normalizesafe(localToBasis.c2);
}
var localToBake = math.mul(localToWorld, localToBasis);
if (localToBake.HasNonUniformScale() || localToBake.HasShear())
{
// deskew second longest axis with respect to longest axis
localToBake[basisPriority[1]] =
DeskewSecondaryAxis(localToBake[basisPriority[0]], localToBake[basisPriority[1]]);
return bakeToShape;
}
public static void SetBakedCylinderSize(this PhysicsShapeAuthoring shape, float height, float radius, float bevelRadius)
{
var cylinder = shape.GetCylinderProperties(out EulerAngles orientation);
var center = cylinder.Center;
var bakeToShape = BakeCylinderJobExtension.GetBakeToShape(shape, center, orientation);
var scale = bakeToShape.DecomposeScale();
namespace Unity.Physics.Authoring
{
sealed class EnumFlagsAttribute : PropertyAttribute {}
sealed class ExpandChildrenAttribute : PropertyAttribute {}
sealed class SoftRangeAttribute : PropertyAttribute
{
public readonly float SliderMin;
public readonly float SliderMax;
public float TextFieldMin { get; set; }
public float TextFieldMax { get; set; }
[assembly: InternalsVisibleTo("Unity.Physics.Custom.EditModeTests")]
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/BallAndSocketJointEditor.cs
#if UNITY_EDITOR
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(BallAndSocketJoint))]
public class BallAndSocketEditor : UnityEditor.Editor
{
protected virtual void OnSceneGUI()
{
BallAndSocketJoint ballAndSocket = (BallAndSocketJoint)target;
EditorGUI.BeginChangeCheck();
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(CustomPhysicsMaterialTagNames))]
[CanEditMultipleObjects]
class CustomPhysicsMaterialTagNamesEditor : BaseEditor
{
#pragma warning disable 649
[AutoPopulate(ElementFormatString = "Custom Physics Material Tag {0}", Resizable = false, Reorderable = false)]
ReorderableList m_TagNames;
#pragma warning restore 649
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/EditorUtilities.cs
using UnityEngine;
using Unity.Mathematics;
using static Unity.Physics.Math;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.IMGUI.Controls;
namespace Unity.Physics.Editor
{
/// <summary>
/// Provides utilities that use Handles to set positions and axes,
/// </summary>
public class EditorUtilities
{
// Editor for a joint pivot or pivot pair
public static void EditPivot(RigidTransform worldFromA, RigidTransform worldFromB, bool lockBtoA,
ref float3 pivotA, ref float3 pivotB, Object target)
{
EditorGUI.BeginChangeCheck();
float3 pivotAinW = Handles.PositionHandle(math.transform(worldFromA, pivotA), quaternion.identity);
float3 pivotBinW;
if (lockBtoA)
{
pivotBinW = pivotAinW;
pivotB = math.transform(math.inverse(worldFromB), pivotBinW);
}
else
{
pivotBinW = Handles.PositionHandle(math.transform(worldFromB, pivotB), quaternion.identity);
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Edit joint pivot");
pivotA = math.transform(math.inverse(worldFromA), pivotAinW);
pivotB = math.transform(math.inverse(worldFromB), pivotBinW);
}
Handles.DrawLine(worldFromA.pos, pivotAinW);
Handles.DrawLine(worldFromB.pos, pivotBinW);
}
// Editor for a joint axis or axis pair
public class AxisEditor
{
// Even though we're only editing axes and not rotations, we need to track a full rotation in order to keep the rotation handle stable
private quaternion m_RefA = quaternion.identity;
private quaternion m_RefB = quaternion.identity;
// Detect changes in the object being edited to reset the reference orientations
private Object m_LastTarget;
private static bool NormalizeSafe(ref float3 x)
{
float lengthSq = math.lengthsq(x);
const float epsSq = 1e-8f;
if (math.abs(lengthSq - 1) > epsSq)
{
if (lengthSq > epsSq)
{
x *= math.rsqrt(lengthSq);
}
else
{
x = new float3(1, 0, 0);
}
return true;
}
return false;
}
private static bool NormalizePerpendicular(float3 axis, ref float3 perpendicular)
{
// make sure perpendicular is actually perpendicular to direction
float dot = math.dot(axis, perpendicular);
float absDot = math.abs(dot);
if (absDot > 1.0f - 1e-5f)
{
// parallel, choose an arbitrary perpendicular
float3 dummy;
CalculatePerpendicularNormalized(axis, out perpendicular, out dummy);
return true;
}
if (absDot > 1e-5f)
{
// reject direction
perpendicular -= dot * axis;
NormalizeSafe(ref perpendicular);
return true;
}
return NormalizeSafe(ref perpendicular);
}
public void Update(RigidTransform worldFromA, RigidTransform worldFromB, bool lockBtoA, float3 pivotA, float3 pivotB,
ref float3 directionA, ref float3 directionB, ref float3 perpendicularA, ref float3 perpendicularB, Object target)
{
// Work in world space
float3 directionAinW = math.rotate(worldFromA, directionA);
float3 directionBinW = math.rotate(worldFromB, directionB);
float3 perpendicularAinW = math.rotate(worldFromB, perpendicularA);
float3 perpendicularBinW = math.rotate(worldFromB, perpendicularB);
bool changed = false;
// If the target changed, fix up the inputs and reset the reference orientations to align with the new target's axes
if (target != m_LastTarget)
{
m_LastTarget = target;
// Enforce normalized directions
changed |= NormalizeSafe(ref directionAinW);
changed |= NormalizeSafe(ref directionBinW);
// Enforce normalized perpendiculars, orthogonal to their respective directions
changed |= NormalizePerpendicular(directionAinW, ref perpendicularAinW);
changed |= NormalizePerpendicular(directionBinW, ref perpendicularBinW);
// Calculate the rotation of the joint in A from direction and perpendicular
float3x3 rotationA = new float3x3(directionAinW, perpendicularAinW, math.cross(directionAinW, perpendicularAinW));
m_RefA = new quaternion(rotationA);
if (lockBtoA)
{
m_RefB = m_RefA;
}
else
{
// Calculate the rotation of the joint in B from direction and perpendicular
float3x3 rotationB = new float3x3(directionBinW, perpendicularBinW, math.cross(directionBinW, perpendicularBinW));
m_RefB = new quaternion(rotationB);
}
}
EditorGUI.BeginChangeCheck();
// Make rotators
quaternion oldRefA = m_RefA;
quaternion oldRefB = m_RefB;
float3 pivotAinW = math.transform(worldFromA, pivotA);
m_RefA = Handles.RotationHandle(m_RefA, pivotAinW);
float3 pivotBinW;
if (lockBtoA)
{
directionB = math.rotate(math.inverse(worldFromB), directionAinW);
perpendicularB = math.rotate(math.inverse(worldFromB), perpendicularAinW);
pivotBinW = pivotAinW;
m_RefB = m_RefA;
}
else
{
pivotBinW = math.transform(worldFromB, pivotB);
m_RefB = Handles.RotationHandle(m_RefB, pivotBinW);
}
// Apply changes from the rotators
if (EditorGUI.EndChangeCheck())
{
quaternion dqA = math.mul(m_RefA, math.inverse(oldRefA));
quaternion dqB = math.mul(m_RefB, math.inverse(oldRefB));
directionAinW = math.mul(dqA, directionAinW);
directionBinW = math.mul(dqB, directionBinW);
perpendicularAinW = math.mul(dqB, perpendicularAinW);
perpendicularBinW = math.mul(dqB, perpendicularBinW);
changed = true;
}
// Write back if the axes changed
if (changed)
{
Undo.RecordObject(target, "Edit joint axis");
directionA = math.rotate(math.inverse(worldFromA), directionAinW);
directionB = math.rotate(math.inverse(worldFromB), directionBinW);
perpendicularA = math.rotate(math.inverse(worldFromB), perpendicularAinW);
perpendicularB = math.rotate(math.inverse(worldFromB), perpendicularBinW);
}
// Draw the updated axes
float3 z = new float3(0, 0, 1); // ArrowHandleCap() draws an arrow pointing in (0, 0, 1)
Handles.ArrowHandleCap(0, pivotAinW, Quaternion.FromToRotation(z, directionAinW), HandleUtility.GetHandleSize(pivotAinW) * 0.75f, Event.current.type);
Handles.ArrowHandleCap(0, pivotAinW, Quaternion.FromToRotation(z, perpendicularAinW), HandleUtility.GetHandleSize(pivotAinW) * 0.75f, Event.current.type);
if (!lockBtoA)
{
Handles.ArrowHandleCap(0, pivotBinW, Quaternion.FromToRotation(z, directionBinW), HandleUtility.GetHandleSize(pivotBinW) * 0.75f, Event.current.type);
Handles.ArrowHandleCap(0, pivotBinW, Quaternion.FromToRotation(z, perpendicularBinW), HandleUtility.GetHandleSize(pivotBinW) * 0.75f, Event.current.type);
}
}
}
public static void EditLimits(RigidTransform worldFromA, RigidTransform worldFromB, float3 pivotA, float3 axisA, float3 axisB, float3 perpendicularA, float3 perpendicularB
ref float minLimit, ref float maxLimit, JointAngularLimitHandle limitHandle, Object target)
{
// Transform to world space
float3 pivotAinW = math.transform(worldFromA, pivotA);
float3 axisAinW = math.rotate(worldFromA, axisA);
float3 perpendicularAinW = math.rotate(worldFromA, perpendicularA);
float3 axisBinW = math.rotate(worldFromA, axisB);
float3 perpendicularBinW = math.rotate(worldFromB, perpendicularB);
// Get rotations from joint space
// JointAngularLimitHandle uses axis = (1, 0, 0) with angle = 0 at (0, 0, 1), so choose the rotations to point those in the directions of our axis and perpendicular
float3x3 worldFromJointA = new float3x3(axisAinW, -math.cross(axisAinW, perpendicularAinW), perpendicularAinW);
float3x3 worldFromJointB = new float3x3(axisBinW, -math.cross(axisBinW, perpendicularBinW), perpendicularBinW);
float3x3 jointBFromA = math.mul(math.transpose(worldFromJointB), worldFromJointA);
// Set orientation for the angular limit control
float angle = CalculateTwistAngle(new quaternion(jointBFromA), 0); // index = 0 because axis is the first column in worldFromJoint
quaternion limitOrientation = math.mul(quaternion.AxisAngle(axisAinW, angle), new quaternion(worldFromJointA));
Matrix4x4 handleMatrix = Matrix4x4.TRS(pivotAinW, limitOrientation, Vector3.one);
float size = HandleUtility.GetHandleSize(pivotAinW) * 0.75f;
limitHandle.xMin = -maxLimit;
limitHandle.xMax = -minLimit;
limitHandle.xMotion = ConfigurableJointMotion.Limited;
limitHandle.yMotion = ConfigurableJointMotion.Locked;
limitHandle.zMotion = ConfigurableJointMotion.Locked;
limitHandle.yHandleColor = new Color(0, 0, 0, 0);
limitHandle.zHandleColor = new Color(0, 0, 0, 0);
limitHandle.radius = size;
using (new Handles.DrawingScope(handleMatrix))
{
// Draw the reference axis
float3 z = new float3(0, 0, 1); // ArrowHandleCap() draws an arrow pointing in (0, 0, 1)
Handles.ArrowHandleCap(0, float3.zero, Quaternion.FromToRotation(z, new float3(1, 0, 0)), size, Event.current.type);
// Draw the limit editor handle
EditorGUI.BeginChangeCheck();
limitHandle.DrawHandle();
if (EditorGUI.EndChangeCheck())
{
// Record the target object before setting new limits so changes can be undone/redone
Undo.RecordObject(target, "Edit joint angular limits");
minLimit = -limitHandle.xMax;
maxLimit = -limitHandle.xMin;
}
}
}
}
}
#endif
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/LimitedHingeJointEditor.cs
#if UNITY_EDITOR
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(LimitedHingeJoint))]
public class LimitedHingeEditor : UnityEditor.Editor
{
private EditorUtilities.AxisEditor m_AxisEditor = new EditorUtilities.AxisEditor();
private JointAngularLimitHandle m_LimitHandle = new JointAngularLimitHandle();
EditorGUI.BeginChangeCheck();
GUILayout.BeginHorizontal();
GUILayout.Label("Editors:");
limitedHinge.EditPivots = GUILayout.Toggle(limitedHinge.EditPivots, new GUIContent("Pivot"), "Button");
limitedHinge.EditAxes = GUILayout.Toggle(limitedHinge.EditAxes, new GUIContent("Axis"), "Button");
limitedHinge.EditLimits = GUILayout.Toggle(limitedHinge.EditLimits, new GUIContent("Limits"), "Button");
GUILayout.EndHorizontal();
DrawDefaultInspector();
if (EditorGUI.EndChangeCheck())
{
SceneView.RepaintAll();
}
}
if (limitedHinge.EditPivots)
{
EditorUtilities.EditPivot(limitedHinge.worldFromA, limitedHinge.worldFromB, limitedHinge.AutoSetConnected,
ref limitedHinge.PositionLocal, ref limitedHinge.PositionInConnectedEntity, limitedHinge);
}
if (limitedHinge.EditAxes)
{
m_AxisEditor.Update(limitedHinge.worldFromA, limitedHinge.worldFromB,
limitedHinge.AutoSetConnected,
limitedHinge.PositionLocal, limitedHinge.PositionInConnectedEntity,
ref limitedHinge.HingeAxisLocal, ref limitedHinge.HingeAxisInConnectedEntity,
ref limitedHinge.PerpendicularAxisLocal, ref limitedHinge.PerpendicularAxisInConnectedEntity,
limitedHinge);
}
if (limitedHinge.EditLimits)
{
EditorUtilities.EditLimits(limitedHinge.worldFromA, limitedHinge.worldFromB,
limitedHinge.PositionLocal,
limitedHinge.HingeAxisLocal, limitedHinge.HingeAxisInConnectedEntity,
limitedHinge.PerpendicularAxisLocal, limitedHinge.PerpendicularAxisInConnectedEntity,
ref limitedHinge.MinAngle, ref limitedHinge.MaxAngle, m_LimitHandle, limitedHinge);
}
}
}
}
#endif
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/PhysicsBodyAuthoringEditor.cs
using System.Collections.Generic;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(PhysicsBodyAuthoring))]
[CanEditMultipleObjects]
class PhysicsBodyAuthoringEditor : BaseEditor
{
static class Content
{
public static readonly GUIContent MassLabel = EditorGUIUtility.TrTextContent("Mass");
public static readonly GUIContent CenterOfMassLabel = EditorGUIUtility.TrTextContent(
"Center of Mass", "Center of mass in the space of this body's transform."
);
public static readonly GUIContent InertiaTensorLabel = EditorGUIUtility.TrTextContent(
"Inertia Tensor", "Resistance to angular motion about each axis of rotation."
);
public static readonly GUIContent OrientationLabel = EditorGUIUtility.TrTextContent(
"Orientation", "Orientation of the body's inertia tensor in the space of its transform."
);
public static readonly GUIContent AdvancedLabel = EditorGUIUtility.TrTextContent(
"Advanced", "Advanced options"
);
}
#pragma warning disable 649
[AutoPopulate] SerializedProperty m_MotionType;
[AutoPopulate] SerializedProperty m_Smoothing;
[AutoPopulate] SerializedProperty m_Mass;
[AutoPopulate] SerializedProperty m_GravityFactor;
[AutoPopulate] SerializedProperty m_LinearDamping;
[AutoPopulate] SerializedProperty m_AngularDamping;
[AutoPopulate] SerializedProperty m_InitialLinearVelocity;
[AutoPopulate] SerializedProperty m_InitialAngularVelocity;
[AutoPopulate] SerializedProperty m_OverrideDefaultMassDistribution;
[AutoPopulate] SerializedProperty m_CenterOfMass;
[AutoPopulate] SerializedProperty m_Orientation;
[AutoPopulate] SerializedProperty m_InertiaTensor;
[AutoPopulate] SerializedProperty m_WorldIndex;
[AutoPopulate] SerializedProperty m_CustomTags;
#pragma warning restore 649
bool showAdvanced;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_MotionType);
if (m_MotionType.intValue != (int)BodyMotionType.Static)
EditorGUILayout.PropertyField(m_Smoothing);
if (m_MotionType.intValue == (int)BodyMotionType.Dynamic)
{
EditorGUILayout.PropertyField(m_LinearDamping, true);
EditorGUILayout.PropertyField(m_AngularDamping, true);
}
if (m_MotionType.intValue != (int)BodyMotionType.Static)
{
EditorGUILayout.PropertyField(m_InitialLinearVelocity, true);
EditorGUILayout.PropertyField(m_InitialAngularVelocity, true);
}
if (m_MotionType.intValue == (int)BodyMotionType.Dynamic)
{
EditorGUILayout.PropertyField(m_GravityFactor, true);
}
EditorGUI.BeginDisabledGroup(!dynamic);
if (dynamic)
{
EditorGUILayout.PropertyField(m_Orientation, Content.OrientationLabel);
EditorGUILayout.PropertyField(m_InertiaTensor, Content.InertiaTensorLabel);
}
else
{
EditorGUI.BeginDisabledGroup(true);
var position =
EditorGUILayout.GetControlRect(true, EditorGUI.GetPropertyHeight(m_InertiaTensor));
EditorGUI.BeginProperty(position, Content.InertiaTensorLabel, m_InertiaTensor);
EditorGUI.Vector3Field(position, Content.InertiaTensorLabel,
Vector3.one * float.PositiveInfinity);
EditorGUI.EndProperty();
EditorGUI.EndDisabledGroup();
}
EditorGUI.EndDisabledGroup();
--EditorGUI.indentLevel;
}
}
EditorGUILayout.PropertyField(m_CustomTags);
--EditorGUI.indentLevel;
}
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
DisplayStatusMessages();
}
MessageType m_Status;
List<string> m_StatusMessages = new List<string>(8);
void DisplayStatusMessages()
{
m_Status = MessageType.None;
m_StatusMessages.Clear();
var hierarchyStatus = StatusMessageUtility.GetHierarchyStatusMessage(targets, out var hierarchyStatusMessage);
if (!string.IsNullOrEmpty(hierarchyStatusMessage))
{
m_StatusMessages.Add(hierarchyStatusMessage);
m_Status = (MessageType)math.max((int)m_Status, (int)hierarchyStatus);
}
if (m_StatusMessages.Count > 0)
EditorGUILayout.HelpBox(string.Join("\n\n", m_StatusMessages), m_Status);
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/PhysicsCategoryNamesEditor.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(PhysicsCategoryNames))]
[CanEditMultipleObjects]
class PhysicsCategoryNamesEditor : BaseEditor
{
#pragma warning disable 649
[AutoPopulate(ElementFormatString = "Category {0}", Resizable = false, Reorderable = false)]
ReorderableList m_CategoryNames;
#pragma warning restore 649
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/PhysicsShapeAuthoringEditor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityMesh = UnityEngine.Mesh;
using Unity.Physics.Extensions;
using LegacyRigidBody = UnityEngine.Rigidbody;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(PhysicsShapeAuthoring))]
[CanEditMultipleObjects]
class PhysicsShapeAuthoringEditor : BaseEditor
{
static class Styles
{
const string k_Plural = "One or more selected objects";
const string k_Singular = "This object";
public static readonly string GenericUndoMessage = L10n.Tr("Change Shape");
public static readonly string MultipleShapeTypesLabel =
L10n.Tr("Multiple shape types in current selection.");
public static readonly string PreviewGenerationNotification =
L10n.Tr("Generating collision geometry preview...");
static readonly GUIContent k_FitToRenderMeshesLabel =
EditorGUIUtility.TrTextContent("Fit to Enabled Render Meshes");
static readonly GUIContent k_FitToRenderMeshesWarningLabelSg = new GUIContent(
k_FitToRenderMeshesLabel.text,
EditorGUIUtility.Load("console.warnicon") as Texture,
L10n.Tr($"{k_Singular} has non-uniform scale. Trying to fit the shape to render meshes might produce unexpected results.")
);
static readonly GUIContent k_FitToRenderMeshesWarningLabelPl = new GUIContent(
k_FitToRenderMeshesLabel.text,
EditorGUIUtility.Load("console.warnicon") as Texture,
L10n.Tr($"{k_Plural} has non-uniform scale. Trying to fit the shape to render meshes might produce unexpected results.")
);
public static readonly GUIContent CenterLabel = EditorGUIUtility.TrTextContent("Center");
public static readonly GUIContent SizeLabel = EditorGUIUtility.TrTextContent("Size");
public static readonly GUIContent OrientationLabel = EditorGUIUtility.TrTextContent(
"Orientation", "Euler orientation in the shape's local space (ZXY order)."
);
public static readonly GUIContent CylinderSideCountLabel = EditorGUIUtility.TrTextContent("Side Count");
public static readonly GUIContent RadiusLabel = EditorGUIUtility.TrTextContent("Radius");
public static readonly GUIContent ForceUniqueLabel = EditorGUIUtility.TrTextContent(
"Force Unique",
"If set to true, then this object will always produce a unique collider for run-time during conversion. " +
"If set to false, then this object may share its collider data with other objects if they have the same inputs. " +
"You should enable this option if you plan to modify this instance's collider at run-time."
);
public static readonly GUIContent MaterialLabel = EditorGUIUtility.TrTextContent("Material");
public static readonly GUIContent SetRecommendedConvexValues = EditorGUIUtility.TrTextContent(
"Set Recommended Default Values",
"Set recommended values for convex hull generation parameters based on either render meshes or custom mesh."
);
public static GUIContent GetFitToRenderMeshesLabel(int numTargets, MessageType status) =>
status >= MessageType.Warning
? numTargets == 1 ? k_FitToRenderMeshesWarningLabelSg : k_FitToRenderMeshesWarningLabelPl
: k_FitToRenderMeshesLabel;
static readonly string[] k_NoGeometryWarning =
{
L10n.Tr($"{k_Singular} has no enabled render meshes in its hierarchy and no custom mesh assigned."),
L10n.Tr($"{k_Plural} has no enabled render meshes in their hierarchies and no custom mesh assigned.")
};
public static string GetNoGeometryWarning(int numTargets) =>
numTargets == 1 ? k_NoGeometryWarning[0] : k_NoGeometryWarning[1];
m_NumImplicitStatic = targets.Cast<PhysicsShapeAuthoring>().Count(
shape => shape.GetPrimaryBody() == shape.gameObject
&& shape.GetComponent<PhysicsBodyAuthoring>() == null
&& shape.GetComponent<LegacyRigidBody>() == null
);
Undo.undoRedoPerformed += Repaint;
}
void OnDisable()
{
Undo.undoRedoPerformed -= Repaint;
SceneViewUtility.ClearNotificationInSceneView();
foreach (var preview in m_PreviewData.Values)
preview.Dispose();
if (m_DropDown != null)
m_DropDown.CloseWithoutUndo();
}
class PreviewMeshData : IDisposable
{
[BurstCompile]
struct CreateTempHullJob : IJob
{
public ConvexHullGenerationParameters GenerationParameters;
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<float3> Points;
public NativeArray<BlobAssetReference<Collider>> Output;
public void Execute() => Output[0] = ConvexCollider.Create(Points, GenerationParameters, CollisionFilter.Default);
}
[BurstCompile]
struct CreateTempMeshJob : IJob
{
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<float3> Points;
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<int3> Triangles;
public NativeArray<BlobAssetReference<Collider>> Output;
public void Execute() => Output[0] = MeshCollider.Create(Points, Triangles);
}
static readonly List<Vector3> s_ReusableEdges = new List<Vector3>(1024);
public Vector3[] Edges = Array.Empty<Vector3>();
public Aabb Bounds = new Aabb();
bool m_Disposed;
uint m_InputHash;
ConvexHullGenerationParameters m_HashedConvexParameters;
NativeArray<float3> m_HashedPoints = new NativeArray<float3>(0, Allocator.Persistent);
// multiple preview jobs might be running if user assigned a different mesh before previous job completed
JobHandle m_MostRecentlyScheduledJob;
Dictionary<JobHandle, NativeArray<BlobAssetReference<Collider>>> m_PreviewJobsOutput =
new Dictionary<JobHandle, NativeArray<BlobAssetReference<Collider>>>();
unsafe uint GetInputHash(
PhysicsShapeAuthoring shape,
NativeList<float3> currentPoints,
NativeArray<float3> hashedPoints,
ConvexHullGenerationParameters hashedConvexParameters,
out ConvexHullGenerationParameters currentConvexProperties
)
{
currentConvexProperties = default;
switch (shape.ShapeType)
{
case ShapeType.ConvexHull:
shape.GetBakedConvexProperties(currentPoints); // TODO: use HashableShapeInputs
currentConvexProperties = shape.ConvexHullGenerationParameters;
return math.hash(
new uint3(
(uint)shape.ShapeType,
currentConvexProperties.GetStableHash(hashedConvexParameters),
currentPoints.GetStableHash(hashedPoints)
)
);
case ShapeType.Mesh:
var triangles = new NativeList<int3>(1024, Allocator.Temp);
shape.GetBakedMeshProperties(currentPoints, triangles); // TODO: use HashableShapeInputs
return math.hash(
new uint3(
(uint)shape.ShapeType,
currentPoints.GetStableHash(hashedPoints),
math.hash(triangles.GetUnsafePtr(), UnsafeUtility.SizeOf<int3>() * triangles.Length)
)
);
default:
return (uint)shape.ShapeType;
}
}
public void SchedulePreviewIfChanged(PhysicsShapeAuthoring shape)
{
using (var currentPoints = new NativeList<float3>(65535, Allocator.Temp))
{
var hash = GetInputHash(
shape, currentPoints, m_HashedPoints, m_HashedConvexParameters, out var currentConvexParameters
);
if (m_InputHash == hash)
return;
m_InputHash = hash;
m_HashedConvexParameters = currentConvexParameters;
m_HashedPoints.Dispose();
m_HashedPoints = new NativeArray<float3>(currentPoints.Length, Allocator.Persistent);
m_HashedPoints.CopyFrom(currentPoints.AsArray());
}
if (shape.ShapeType != ShapeType.ConvexHull && shape.ShapeType != ShapeType.Mesh)
return;
// TODO: cache results per input data hash, and simply use existing data (e.g., to make undo/redo faster)
var output = new NativeArray<BlobAssetReference<Collider>>(1, Allocator.Persistent);
m_MostRecentlyScheduledJob = shape.ShapeType == ShapeType.Mesh
? ScheduleMeshPreview(shape, output)
: ScheduleConvexHullPreview(shape, output);
m_PreviewJobsOutput.Add(m_MostRecentlyScheduledJob, output);
if (m_PreviewJobsOutput.Count == 1)
{
CheckPreviewJobsForCompletion();
if (m_MostRecentlyScheduledJob.Equals(default(JobHandle)))
return;
EditorApplication.update += CheckPreviewJobsForCompletion;
EditorApplication.delayCall += () =>
{
SceneViewUtility.DisplayProgressNotification(
Styles.PreviewGenerationNotification, () => float.PositiveInfinity
);
};
}
}
JobHandle ScheduleConvexHullPreview(PhysicsShapeAuthoring shape, NativeArray<BlobAssetReference<Collider>> output)
{
var pointCloud = new NativeList<float3>(65535, Allocator.Temp);
shape.GetBakedConvexProperties(pointCloud);
if (pointCloud.Length == 0)
return default;
// copy to NativeArray because NativeList not yet compatible with DeallocateOnJobCompletion
var pointsArray = new NativeArray<float3>(
pointCloud.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory
);
pointsArray.CopyFrom(pointCloud.AsArray());
// TODO: if there is still an active job with the same input data hash, then just set it to be most recently scheduled job
return new CreateTempHullJob
{
GenerationParameters = shape.ConvexHullGenerationParameters.ToRunTime(),
Points = pointsArray,
Output = output
}.Schedule();
}
JobHandle ScheduleMeshPreview(PhysicsShapeAuthoring shape, NativeArray<BlobAssetReference<Collider>> output)
{
var points = new NativeList<float3>(1024, Allocator.Temp);
var triangles = new NativeList<int3>(1024, Allocator.Temp);
shape.GetBakedMeshProperties(points, triangles);
if (points.Length == 0 || triangles.Length == 0)
return default;
// copy to NativeArray because NativeList not yet compatible with DeallocateOnJobCompletion
var pointsArray = new NativeArray<float3>(
points.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory
);
pointsArray.CopyFrom(points.AsArray());
var triangleArray = new NativeArray<int3>(
triangles.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory
);
triangleArray.CopyFrom(triangles.AsArray());
// TODO: if there is still an active job with the same input data hash, then just set it to be most recently scheduled job
return new CreateTempMeshJob
{
Points = pointsArray,
Triangles = triangleArray,
Output = output
}.Schedule();
}
unsafe void CheckPreviewJobsForCompletion()
{
var repaintSceneViews = false;
foreach (var job in m_PreviewJobsOutput.Keys.ToArray()) // TODO: don't allocate on heap
{
// repaint scene views to indicate progress if most recent preview job is still in the queue
var mostRecentlyScheduledJob = m_MostRecentlyScheduledJob.Equals(job);
repaintSceneViews |= mostRecentlyScheduledJob;
if (!job.IsCompleted)
continue;
var output = m_PreviewJobsOutput[job];
m_PreviewJobsOutput.Remove(job);
job.Complete();
// only populate preview edge data if not already disposed and this job was actually the most recent
if (!m_Disposed && mostRecentlyScheduledJob)
{
if (!output[0].IsCreated)
{
Edges = Array.Empty<Vector3>();
Bounds = new Aabb();
}
else
{
switch (output[0].Value.Type)
{
case ColliderType.Convex:
ref var convex = ref output[0].As<ConvexCollider>();
DrawingUtility.GetConvexColliderEdges(
ref convex, s_ReusableEdges
);
Bounds = convex.CalculateAabb();
break;
case ColliderType.Mesh:
ref var mesh = ref output[0].As<MeshCollider>();
DrawingUtility.GetMeshColliderEdges(
ref mesh, s_ReusableEdges
);
Bounds = mesh.CalculateAabb();
break;
}
Edges = s_ReusableEdges.ToArray();
}
EditorApplication.delayCall += SceneViewUtility.ClearNotificationInSceneView;
}
if (output.IsCreated)
{
if (output[0].IsCreated)
output[0].Dispose();
output.Dispose();
}
}
if (repaintSceneViews)
SceneView.RepaintAll();
if (m_PreviewJobsOutput.Count == 0)
EditorApplication.update -= CheckPreviewJobsForCompletion;
}
public void Dispose()
{
m_Disposed = true;
m_HashedPoints.Dispose();
}
}
Dictionary<PhysicsShapeAuthoring, PreviewMeshData> m_PreviewData = new Dictionary<PhysicsShapeAuthoring, PreviewMeshData>();
PreviewMeshData GetPreviewData(PhysicsShapeAuthoring shape)
{
if (shape.ShapeType != ShapeType.ConvexHull && shape.ShapeType != ShapeType.Mesh)
return null;
if (!m_PreviewData.TryGetValue(shape, out var preview))
{
preview = m_PreviewData[shape] = new PreviewMeshData();
preview.SchedulePreviewIfChanged(shape);
}
// do not generate a new preview until the user has finished dragging a control handle (e.g., scale)
if (m_DraggingControlID == 0 && !EditorGUIUtility.editingTextField)
preview.SchedulePreviewIfChanged(shape);
return preview;
}
void UpdateGeometryState()
{
m_GeometryState = GeometryState.Okay;
var skinnedPoints = new NativeList<float3>(8192, Allocator.Temp);
foreach (PhysicsShapeAuthoring shape in targets)
{
// if a custom mesh is assigned, only check it
using (var so = new SerializedObject(shape))
{
var customMesh = so.FindProperty(m_CustomMesh.propertyPath).objectReferenceValue as UnityMesh;
if (customMesh != null)
{
m_GeometryState |= GetGeometryState(customMesh, shape.gameObject);
continue;
}
}
// otherwise check all mesh filters in the hierarchy that might be included
var geometryState = GeometryState.Okay;
using (var scope = new GetActiveChildrenScope<MeshFilter>(shape, shape.transform))
{
foreach (var meshFilter in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(meshFilter, filterOutInvalid: false))
geometryState |= GetGeometryState(meshFilter.sharedMesh, shape.gameObject);
}
}
if (shape.ShapeType == ShapeType.Mesh)
{
PhysicsShapeAuthoring.GetAllSkinnedPointsInHierarchyBelongingToShape(
shape, skinnedPoints, false, default, default, default
);
if (skinnedPoints.Length > 0)
geometryState |= GeometryState.MeshWithSkinnedPoints;
}
m_GeometryState |= geometryState;
}
skinnedPoints.Dispose();
}
static GeometryState GetGeometryState(UnityMesh mesh, GameObject host)
{
if (mesh == null)
return GeometryState.NoGeometry;
if (!mesh.IsValidForConversion(host))
return GeometryState.NonReadableGeometry;
return GeometryState.Okay;
}
public override void OnInspectorGUI()
{
var hotControl = GUIUtility.hotControl;
switch (Event.current.GetTypeForControl(hotControl))
{
case EventType.MouseDrag:
m_DraggingControlID = hotControl;
break;
case EventType.MouseUp:
m_DraggingControlID = 0;
break;
}
UpdateGeometryState();
serializedObject.Update();
UpdateStatusMessages();
EditorGUI.BeginChangeCheck();
DisplayShapeSelector();
++EditorGUI.indentLevel;
if (m_ShapeType.hasMultipleDifferentValues)
EditorGUILayout.HelpBox(Styles.MultipleShapeTypesLabel, MessageType.None);
else
{
switch ((ShapeType)m_ShapeType.intValue)
{
case ShapeType.Box:
AutomaticPrimitiveControls();
DisplayBoxControls();
break;
case ShapeType.Capsule:
AutomaticPrimitiveControls();
DisplayCapsuleControls();
break;
case ShapeType.Sphere:
AutomaticPrimitiveControls();
DisplaySphereControls();
break;
case ShapeType.Cylinder:
AutomaticPrimitiveControls();
DisplayCylinderControls();
break;
case ShapeType.Plane:
AutomaticPrimitiveControls();
DisplayPlaneControls();
break;
case ShapeType.ConvexHull:
RecommendedConvexValuesButton();
EditorGUILayout.PropertyField(m_ConvexHullGenerationParameters);
EditorGUILayout.PropertyField(m_MinimumSkinnedVertexWeight);
DisplayMeshControls();
break;
case ShapeType.Mesh:
DisplayMeshControls();
break;
default:
throw new UnimplementedShapeException((ShapeType)m_ShapeType.intValue);
}
EditorGUILayout.PropertyField(m_ForceUnique, Styles.ForceUniqueLabel);
}
--EditorGUI.indentLevel;
EditorGUILayout.LabelField(Styles.MaterialLabel);
++EditorGUI.indentLevel;
EditorGUILayout.PropertyField(m_Material);
--EditorGUI.indentLevel;
if (m_StatusMessages.Count > 0)
EditorGUILayout.HelpBox(string.Join("\n\n", m_StatusMessages), m_Status);
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
}
void RecommendedConvexValuesButton()
{
EditorGUI.BeginDisabledGroup(
(m_GeometryState & GeometryState.NoGeometry) == GeometryState.NoGeometry ||
EditorUtility.IsPersistent(target)
);
var rect = EditorGUI.IndentedRect(
EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight, EditorStyles.miniButton)
);
var buttonLabel = Styles.SetRecommendedConvexValues;
if (GUI.Button(rect, buttonLabel, Styles.Button))
{
Undo.RecordObjects(targets, buttonLabel.text);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.InitializeConvexHullGenerationParameters();
EditorUtility.SetDirty(shape);
}
}
EditorGUI.EndDisabledGroup();
}
MessageType m_GeometryStatus;
List<string> m_GeometryStatusMessages = new List<string>();
HashSet<string> m_ShapeSuggestions = new HashSet<string>();
MessageType m_Status;
List<string> m_StatusMessages = new List<string>(8);
MessageType m_MatrixStatus;
List<MatrixState> m_MatrixStates = new List<MatrixState>();
void UpdateStatusMessages()
{
m_Status = MessageType.None;
m_StatusMessages.Clear();
if (m_NumImplicitStatic != 0)
m_StatusMessages.Add(Styles.GetStaticColliderStatusMessage(targets.Length));
m_ShapeSuggestions.Clear();
foreach (PhysicsShapeAuthoring shape in targets)
{
const float k_Epsilon = HashableShapeInputs.k_DefaultLinearPrecision;
switch (shape.ShapeType)
{
case ShapeType.Box:
var box = shape.GetBakedBoxProperties();
var max = math.cmax(box.Size);
var min = math.cmin(box.Size);
if (min < k_Epsilon)
m_ShapeSuggestions.Add(Styles.BoxPlaneSuggestion);
else if (math.abs(box.BevelRadius - min * 0.5f) < k_Epsilon)
{
if (math.abs(max - min) < k_Epsilon)
m_ShapeSuggestions.Add(Styles.BoxSphereSuggestion);
else if (math.abs(math.lengthsq(box.Size - new float3(min)) - math.pow(max - min, 2f)) < k_Epsilon)
m_ShapeSuggestions.Add(Styles.BoxCapsuleSuggestion);
}
break;
case ShapeType.Capsule:
var capsule = shape.GetBakedCapsuleProperties();
if (math.abs(capsule.Height - 2f * capsule.Radius) < k_Epsilon)
m_ShapeSuggestions.Add(Styles.CapsuleSphereSuggestion);
break;
case ShapeType.Cylinder:
var cylinder = shape.GetBakedCylinderProperties();
if (math.abs(cylinder.BevelRadius - cylinder.Radius) < k_Epsilon)
{
m_ShapeSuggestions.Add(math.abs(cylinder.Height - 2f * cylinder.Radius) < k_Epsilon
? Styles.CylinderSphereSuggestion
: Styles.CylinderCapsuleSuggestion);
}
break;
}
}
foreach (var suggestion in m_ShapeSuggestions)
m_StatusMessages.Add(suggestion);
var hierarchyStatus = StatusMessageUtility.GetHierarchyStatusMessage(targets, out var hierarchyStatusMessage);
if (!string.IsNullOrEmpty(hierarchyStatusMessage))
{
m_StatusMessages.Add(hierarchyStatusMessage);
m_Status = (MessageType)math.max((int)m_Status, (int)hierarchyStatus);
}
m_MatrixStates.Clear();
foreach (var t in targets)
{
var localToWorld = (float4x4)(t as Component).transform.localToWorldMatrix;
m_MatrixStates.Add(ManipulatorUtility.GetMatrixState(ref localToWorld));
}
m_MatrixStatus = StatusMessageUtility.GetMatrixStatusMessage(m_MatrixStates, out var matrixStatusMessage);
if (m_MatrixStatus != MessageType.None)
{
m_StatusMessages.Add(matrixStatusMessage);
m_Status = (MessageType)math.max((int)m_Status, (int)m_MatrixStatus);
}
m_GeometryStatus = MessageType.None;
m_GeometryStatusMessages.Clear();
if ((m_GeometryState & GeometryState.NoGeometry) == GeometryState.NoGeometry)
{
m_GeometryStatusMessages.Add(Styles.GetNoGeometryWarning(targets.Length));
m_GeometryStatus = (MessageType)math.max((int)m_GeometryStatus, (int)MessageType.Error);
}
if ((m_GeometryState & GeometryState.NonReadableGeometry) == GeometryState.NonReadableGeometry)
{
m_GeometryStatusMessages.Add(Styles.GetNonReadableGeometryWarning(targets.Length));
m_GeometryStatus = (MessageType)math.max((int)m_GeometryStatus, (int)MessageType.Warning);
}
if ((m_GeometryState & GeometryState.MeshWithSkinnedPoints) == GeometryState.MeshWithSkinnedPoints)
{
m_GeometryStatusMessages.Add(Styles.GetMeshWithSkinnedPointsWarning(targets.Length));
m_GeometryStatus = (MessageType)math.max((int)m_GeometryStatus, (int)MessageType.Warning);
}
}
void DisplayShapeSelector()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_ShapeType);
if (!EditorGUI.EndChangeCheck())
return;
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
switch ((ShapeType)m_ShapeType.intValue)
{
case ShapeType.Box:
shape.SetBox(shape.GetBoxProperties(out var orientation), orientation);
break;
case ShapeType.Capsule:
shape.SetCapsule(shape.GetCapsuleProperties());
break;
case ShapeType.Sphere:
shape.SetSphere(shape.GetSphereProperties(out orientation), orientation);
break;
case ShapeType.Cylinder:
shape.SetCylinder(shape.GetCylinderProperties(out orientation), orientation);
break;
case ShapeType.Plane:
shape.GetPlaneProperties(out var center, out var size2D, out orientation);
shape.SetPlane(center, size2D, orientation);
break;
case ShapeType.ConvexHull:
case ShapeType.Mesh:
return;
default:
throw new UnimplementedShapeException((ShapeType)m_ShapeType.intValue);
}
EditorUtility.SetDirty(shape);
}
GUIUtility.ExitGUI();
}
void AutomaticPrimitiveControls()
{
EditorGUI.BeginDisabledGroup(
(m_GeometryState & GeometryState.NoGeometry) == GeometryState.NoGeometry || EditorUtility.IsPersistent(target)
);
var buttonLabel = Styles.GetFitToRenderMeshesLabel(targets.Length, m_MatrixStatus);
var rect = EditorGUI.IndentedRect(
EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight, EditorStyles.miniButton)
);
if (GUI.Button(rect, buttonLabel, Styles.ButtonDropDown))
m_DropDown = FitToRenderMeshesDropDown.Show(rect, buttonLabel.text, m_MinimumSkinnedVertexWeight);
EditorGUI.EndDisabledGroup();
}
class FitToRenderMeshesDropDown : EditorWindow
{
static class Styles
{
public const float WindowWidth = 400f;
public const float LabelWidth = 200f;
public static GUIStyle Button => PhysicsShapeAuthoringEditor.Styles.Button;
}
static class Content
{
public static readonly string ApplyLabel = L10n.Tr("Apply");
public static readonly string CancelLabel = L10n.Tr("Cancel");
}
bool m_ApplyChanges;
bool m_ClosedWithoutUndo;
int m_UndoGroup;
SerializedProperty m_MinimumSkinnedVertexWeight;
public static FitToRenderMeshesDropDown Show(
Rect buttonRect, string title, SerializedProperty minimumSkinnedVertexWeight
)
{
var window = CreateInstance<FitToRenderMeshesDropDown>();
window.titleContent = EditorGUIUtility.TrTextContent(title);
window.m_UndoGroup = Undo.GetCurrentGroup();
window.m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
var size = new Vector2(
math.max(buttonRect.width, Styles.WindowWidth),
(EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing) * 3f
);
window.maxSize = window.minSize = size;
window.ShowAsDropDown(GUIUtility.GUIToScreenRect(buttonRect), size);
return window;
}
void OnGUI()
{
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = Styles.LabelWidth;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_MinimumSkinnedVertexWeight);
if (EditorGUI.EndChangeCheck())
ApplyChanges();
EditorGUIUtility.labelWidth = labelWidth;
GUILayout.FlexibleSpace();
var buttonRect = GUILayoutUtility.GetRect(0f, EditorGUIUtility.singleLineHeight);
var buttonLeft = new Rect(buttonRect)
{
width = 0.5f * (buttonRect.width - EditorGUIUtility.standardVerticalSpacing)
};
var buttonRight = new Rect(buttonLeft)
{
x = buttonLeft.xMax + EditorGUIUtility.standardVerticalSpacing
};
var close = false;
buttonRect = Application.platform == RuntimePlatform.OSXEditor ? buttonLeft : buttonRight;
if (
GUI.Button(buttonRect, Content.CancelLabel, Styles.Button)
|| Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape
)
{
close = true;
}
buttonRect = Application.platform == RuntimePlatform.OSXEditor ? buttonRight : buttonLeft;
if (GUI.Button(buttonRect, Content.ApplyLabel, Styles.Button))
{
close = true;
m_ApplyChanges = true;
}
if (close)
{
Close();
EditorGUIUtility.ExitGUI();
}
}
void ApplyChanges()
{
m_MinimumSkinnedVertexWeight.serializedObject.ApplyModifiedProperties();
Undo.RecordObjects(m_MinimumSkinnedVertexWeight.serializedObject.targetObjects, titleContent.text);
foreach (PhysicsShapeAuthoring shape in m_MinimumSkinnedVertexWeight.serializedObject.targetObjects)
{
using (var so = new SerializedObject(shape))
{
shape.FitToEnabledRenderMeshes(
so.FindProperty(m_MinimumSkinnedVertexWeight.propertyPath).floatValue
);
EditorUtility.SetDirty(shape);
}
}
m_MinimumSkinnedVertexWeight.serializedObject.Update();
}
public void CloseWithoutUndo()
{
m_ApplyChanges = true;
Close();
}
void OnDestroy()
{
if (m_ApplyChanges)
ApplyChanges();
else
Undo.RevertAllDownToGroup(m_UndoGroup);
}
}
void DisplayBoxControls()
{
EditorGUILayout.PropertyField(m_PrimitiveSize, Styles.SizeLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
EditorGUILayout.PropertyField(m_BevelRadius);
}
void DisplayCapsuleControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Capsule);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.SetCapsule(shape.GetCapsuleProperties());
EditorUtility.SetDirty(shape);
}
}
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
}
void DisplaySphereControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_SphereRadius, Styles.RadiusLabel);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.SetSphere(shape.GetSphereProperties(out EulerAngles orientation), orientation);
EditorUtility.SetDirty(shape);
}
}
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
}
void DisplayCylinderControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Cylinder);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.SetCylinder(shape.GetCylinderProperties(out var orientation), orientation);
EditorUtility.SetDirty(shape);
}
}
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
EditorGUILayout.PropertyField(m_CylinderSideCount, Styles.CylinderSideCountLabel);
EditorGUILayout.PropertyField(m_BevelRadius);
}
void DisplayPlaneControls()
{
EditorGUILayout.PropertyField(m_PrimitiveSize, Styles.SizeLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
}
void DisplayMeshControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_CustomMesh);
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
}
if (m_GeometryStatusMessages.Count > 0)
EditorGUILayout.HelpBox(string.Join("\n\n", m_GeometryStatusMessages), m_GeometryStatus);
}
static readonly BeveledBoxBoundsHandle s_Box = new BeveledBoxBoundsHandle();
static readonly PhysicsCapsuleBoundsHandle s_Capsule =
new PhysicsCapsuleBoundsHandle { heightAxis = CapsuleBoundsHandle.HeightAxis.Z };
static readonly BeveledCylinderBoundsHandle s_Cylinder = new BeveledCylinderBoundsHandle();
static readonly PhysicsSphereBoundsHandle s_Sphere = new PhysicsSphereBoundsHandle();
static readonly BoxBoundsHandle s_Plane =
new BoxBoundsHandle { axes = PrimitiveBoundsHandle.Axes.X | PrimitiveBoundsHandle.Axes.Z };
static readonly Color k_ShapeHandleColor = new Color32(145, 244, 139, 210);
static readonly Color k_ShapeHandleColorDisabled = new Color32(84, 200, 77, 140);
void OnSceneGUI()
{
var hotControl = GUIUtility.hotControl;
switch (Event.current.GetTypeForControl(hotControl))
{
case EventType.MouseDrag:
m_DraggingControlID = hotControl;
break;
case EventType.MouseUp:
m_DraggingControlID = 0;
break;
}
var shape = target as PhysicsShapeAuthoring;
var handleColor = shape.enabled ? k_ShapeHandleColor : k_ShapeHandleColorDisabled;
var handleMatrix = shape.GetShapeToWorldMatrix();
using (new Handles.DrawingScope(handleColor, handleMatrix))
{
switch (shape.ShapeType)
{
case ShapeType.Box:
var boxGeometry = shape.GetBakedBoxProperties();
s_Box.bevelRadius = boxGeometry.BevelRadius;
s_Box.center = float3.zero;
s_Box.size = boxGeometry.Size;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(boxGeometry.Center, boxGeometry.Orientation, 1f))))
s_Box.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedBoxSize(s_Box.size, s_Box.bevelRadius);
}
break;
case ShapeType.Capsule:
s_Capsule.center = float3.zero;
var capsuleGeometry = shape.GetBakedCapsuleProperties();
s_Capsule.height = capsuleGeometry.Height;
s_Capsule.radius = capsuleGeometry.Radius;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(capsuleGeometry.Center, capsuleGeometry.Orientation, 1f))))
s_Capsule.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedCapsuleSize(s_Capsule.height, s_Capsule.radius);
}
break;
case ShapeType.Sphere:
var sphereGeometry = shape.GetBakedSphereProperties(out EulerAngles orientation);
s_Sphere.center = float3.zero;
s_Sphere.radius = sphereGeometry.Radius;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(sphereGeometry.Center, orientation, 1f))))
s_Sphere.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedSphereRadius(s_Sphere.radius);
}
break;
case ShapeType.Cylinder:
var cylinderGeometry = shape.GetBakedCylinderProperties();
s_Cylinder.center = float3.zero;
s_Cylinder.height = cylinderGeometry.Height;
s_Cylinder.radius = cylinderGeometry.Radius;
s_Cylinder.sideCount = cylinderGeometry.SideCount;
s_Cylinder.bevelRadius = cylinderGeometry.BevelRadius;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(cylinderGeometry.Center, cylinderGeometry.Orientation, 1f))))
s_Cylinder.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedCylinderSize(s_Cylinder.height, s_Cylinder.radius, s_Cylinder.bevelRadius);
}
break;
case ShapeType.Plane:
shape.GetPlaneProperties(out var center, out var size2, out orientation);
s_Plane.center = float3.zero;
s_Plane.size = new float3(size2.x, 0f, size2.y);
EditorGUI.BeginChangeCheck();
{
var m = math.mul(shape.transform.localToWorldMatrix, float4x4.TRS(center, orientation, 1f));
using (new Handles.DrawingScope(m))
s_Plane.DrawHandle();
var right = math.mul(m, new float4 { x = 1f }).xyz;
var forward = math.mul(m, new float4 { z = 1f }).xyz;
var normal = math.cross(math.normalizesafe(forward), math.normalizesafe(right))
* 0.5f * math.lerp(math.length(right) * size2.x, math.length(forward) * size2.y, 0.5f);
using (new Handles.DrawingScope(float4x4.identity))
Handles.DrawLine(m.c3.xyz, m.c3.xyz + normal);
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedPlaneSize(((float3)s_Plane.size).xz);
}
break;
case ShapeType.ConvexHull:
if (Event.current.type != EventType.Repaint)
break;
var points = GetPreviewData(shape).Edges;
// TODO: follow transformation until new preview is generated if e.g., user is dragging handles
if (points.Length > 0)
Handles.DrawLines(points);
break;
case ShapeType.Mesh:
if (Event.current.type != EventType.Repaint)
break;
points = GetPreviewData(shape).Edges;
if (points.Length > 0)
Handles.DrawLines(points);
break;
default:
throw new UnimplementedShapeException(shape.ShapeType);
}
}
}
// ReSharper disable once UnusedMember.Global - magic method called by unity inspector
public bool HasFrameBounds()
{
return true;
}
static Bounds TransformBounds(Bounds localBounds, float4x4 matrix)
{
var center = new float4(localBounds.center, 1);
Bounds bounds = new Bounds(math.mul(matrix, center).xyz, Vector3.zero);
var extent = new float4(localBounds.extents, 0);
for (int i = 0; i < 8; ++i)
{
extent.x = (i & 1) == 0 ? -extent.x : extent.x;
extent.y = (i & 2) == 0 ? -extent.y : extent.y;
extent.z = (i & 4) == 0 ? -extent.z : extent.z;
var worldPoint = math.mul(matrix, center + extent).xyz;
bounds.Encapsulate(worldPoint);
}
return bounds;
}
// ReSharper disable once UnusedMember.Global - magic method called by unity inspector
public Bounds OnGetFrameBounds()
{
var shape = target as PhysicsShapeAuthoring;
var shapeMatrix = shape.GetShapeToWorldMatrix();
Bounds bounds = new Bounds();
switch (shape.ShapeType)
{
case ShapeType.Box:
var boxGeometry = shape.GetBakedBoxProperties();
bounds = new Bounds(float3.zero, boxGeometry.Size);
bounds = TransformBounds(bounds, float4x4.TRS(boxGeometry.Center, boxGeometry.Orientation, 1f));
break;
case ShapeType.Capsule:
var capsuleGeometry = shape.GetBakedCapsuleProperties();
var cd = capsuleGeometry.Radius * 2;
bounds = new Bounds(float3.zero, new float3(cd, cd, capsuleGeometry.Height));
bounds = TransformBounds(bounds, float4x4.TRS(capsuleGeometry.Center, capsuleGeometry.Orientation, 1f));
break;
case ShapeType.Sphere:
var sphereGeometry = shape.GetBakedSphereProperties(out var orientation);
var sd = sphereGeometry.Radius * 2;
bounds = new Bounds(sphereGeometry.Center, new float3(sd, sd, sd));
break;
case ShapeType.Cylinder:
var cylinderGeometry = shape.GetBakedCylinderProperties();
var cyld = cylinderGeometry.Radius * 2;
bounds = new Bounds(float3.zero, new float3(cyld, cyld, cylinderGeometry.Height));
bounds = TransformBounds(bounds, float4x4.TRS(cylinderGeometry.Center, cylinderGeometry.Orientation, 1f));
break;
case ShapeType.Plane:
shape.GetPlaneProperties(out var center, out var size2, out orientation);
bounds = new Bounds(float3.zero, new float3(size2.x, 0, size2.y));
bounds = TransformBounds(bounds, float4x4.TRS(center, orientation, 1f));
break;
case ShapeType.ConvexHull:
case ShapeType.Mesh:
var previewData = GetPreviewData(shape);
if (previewData != null)
bounds = new Bounds(previewData.Bounds.Center, previewData.Bounds.Extents);
break;
default:
throw new UnimplementedShapeException(shape.ShapeType);
}
return TransformBounds(bounds, shapeMatrix);
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/RagdollJointEditor.cs
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using Unity.Mathematics;
using Unity.Physics.Authoring;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(RagdollJoint))]
public class RagdollJointEditor : UnityEditor.Editor
{
private EditorUtilities.AxisEditor m_AxisEditor = new EditorUtilities.AxisEditor();
private JointAngularLimitHandle m_LimitHandle = new JointAngularLimitHandle();
public override void OnInspectorGUI()
{
RagdollJoint ragdoll = (RagdollJoint)target;
EditorGUI.BeginChangeCheck();
GUILayout.BeginVertical();
GUILayout.Space(10.0f);
GUILayout.BeginHorizontal();
GUILayout.Label("Editors:");
ragdoll.EditPivots = GUILayout.Toggle(ragdoll.EditPivots, new GUIContent("Pivot"), "Button");
ragdoll.EditAxes = GUILayout.Toggle(ragdoll.EditAxes, new GUIContent("Axis"), "Button");
ragdoll.EditLimits = GUILayout.Toggle(ragdoll.EditLimits, new GUIContent("Limits"), "Button");
GUILayout.EndHorizontal();
GUILayout.Space(10.0f);
GUILayout.EndVertical();
DrawDefaultInspector();
if (EditorGUI.EndChangeCheck())
{
SceneView.RepaintAll();
}
}
private static void DrawCone(float3 point, float3 axis, float angle, Color color)
{
#if UNITY_EDITOR
Handles.color = color;
float3 dir;
float scale = Math.NormalizeWithLength(axis, out dir);
float3 arm;
{
float3 perp1, perp2;
Math.CalculatePerpendicularNormalized(dir, out perp1, out perp2);
arm = math.mul(quaternion.AxisAngle(perp1, angle), dir) * scale;
}
const int res = 16;
quaternion q = quaternion.AxisAngle(dir, 2.0f * (float)math.PI / res);
for (int i = 0; i < res; i++)
{
float3 nextArm = math.mul(q, arm);
Handles.DrawLine(point, point + arm);
Handles.DrawLine(point + arm, point + nextArm);
arm = nextArm;
}
#endif
}
protected virtual void OnSceneGUI()
{
RagdollJoint ragdoll = (RagdollJoint)target;
bool drawCones = false;
if (ragdoll.EditPivots)
{
EditorUtilities.EditPivot(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.AutoSetConnected,
ref ragdoll.PositionLocal, ref ragdoll.PositionInConnectedEntity, ragdoll);
}
if (ragdoll.EditAxes)
{
m_AxisEditor.Update(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.AutoSetConnected,
ragdoll.PositionLocal, ragdoll.PositionInConnectedEntity, ref ragdoll.TwistAxisLocal, ref ragdoll.TwistAxisInConnectedEntity,
ref ragdoll.PerpendicularAxisLocal, ref ragdoll.PerpendicularAxisInConnectedEntity, ragdoll);
drawCones = true;
}
if (ragdoll.EditLimits)
{
EditorUtilities.EditLimits(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.PositionLocal, ragdoll.TwistAxisLocal, ragdoll.TwistAxisInConnectedEntity,
ragdoll.PerpendicularAxisLocal, ragdoll.PerpendicularAxisInConnectedEntity, ref ragdoll.MinTwistAngle, ref ragdoll.MaxTwistAngle, m_LimitHandle, ragdoll);
}
if (drawCones)
{
float3 pivotB = math.transform(ragdoll.worldFromB, ragdoll.PositionInConnectedEntity);
float3 axisB = math.rotate(ragdoll.worldFromB, ragdoll.TwistAxisInConnectedEntity);
DrawCone(pivotB, axisB, math.radians(ragdoll.MaxConeAngle), Color.yellow);
float3 perpendicularB = math.rotate(ragdoll.worldFromB, ragdoll.PerpendicularAxisInConnectedEntity);
DrawCone(pivotB, perpendicularB, math.radians(ragdoll.MinPerpendicularAngle + 90f), Color.red);
DrawCone(pivotB, perpendicularB, math.radians(ragdoll.MaxPerpendicularAngle + 90f), Color.red);
}
}
}
}
#endif
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/BeveledBoxBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class BeveledBoxBoundsHandle : BoxBoundsHandle
{
public float bevelRadius
{
get => math.min(m_BevelRadius, math.cmin(GetSize()) * 0.5f);
set
{
if (!m_IsDragging)
m_BevelRadius = math.max(0f, value);
}
}
float m_BevelRadius = ConvexHullGenerationParameters.Default.BevelRadius;
bool m_IsDragging = false;
static PhysicsBoundsHandleUtility.Corner[] s_Corners = new PhysicsBoundsHandleUtility.Corner[8];
public new void DrawHandle()
{
int prevHotControl = GUIUtility.hotControl;
if (prevHotControl == 0)
m_IsDragging = false;
base.DrawHandle();
int currHotcontrol = GUIUtility.hotControl;
if (currHotcontrol != prevHotControl)
m_IsDragging = currHotcontrol != 0;
}
protected override void DrawWireframe()
{
if (this.bevelRadius <= 0f)
{
base.DrawWireframe();
return;
}
var cameraPosition = float3.zero;
var cameraForward = new float3 { z = 1f };
if (Camera.current != null)
{
cameraPosition = Camera.current.transform.position;
cameraForward = Camera.current.transform.forward;
}
var bounds = new Bounds(this.center, this.size);
bool isCameraInsideBox = Camera.current != null && bounds.Contains(Handles.inverseMatrix.MultiplyPoint(cameraPosition));
var bevelRadius = this.bevelRadius;
var origin = (float3)this.center;
var size = (float3)this.size;
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(-1f, 1f, 1f), bevelRadius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, -1f, 1f), bevelRadius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 2, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, -1f), bevelRadius, 2, axes, isCameraInsideBox);
var corner = 0.5f * size - new float3(1f) * bevelRadius;
var axisx = new float3(1f, 0f, 0f);
var axisy = new float3(0f, 1f, 0f);
var axisz = new float3(0f, 0f, 1f);
// Since the geometry is transformed by Handles.matrix during rendering, we transform the camera position
// by the inverse matrix so that the two-shaded wireframe will have the proper orientation.
var invMatrix = Handles.inverseMatrix;
cameraPosition = invMatrix.MultiplyPoint(cameraPosition);
cameraForward = invMatrix.MultiplyVector(cameraForward);
var cameraOrtho = Camera.current == null || Camera.current.orthographic;
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, -1f), quaternion.LookRotation(-axisz, axisy), cameraPosition, cameraForward,
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, 1f), quaternion.LookRotation(-axisx, axisy), cameraPosition, cameraForward,
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, 1f), quaternion.LookRotation(axisz, axisy), cameraPosition, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, -1f), quaternion.LookRotation(axisx, axisy), cameraPosition, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, -1f), quaternion.LookRotation(-axisx, -axisy), cameraPosition, cameraForward,
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, 1f), quaternion.LookRotation(axisz, -axisy), cameraPosition, cameraForward, cameraOrth
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, 1f), quaternion.LookRotation(axisx, -axisy), cameraPosition, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, -1f), quaternion.LookRotation(-axisz, -axisy), cameraPosition, cameraForward, cameraOrth
for (int i = 0; i < s_Corners.Length; i++)
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[i], true);
// Draw the horizon edges between the corners
for (int upA = 3, upB = 0; upB < 4; upA = upB, upB++)
{
int dnA = upA + 4;
int dnB = upB + 4;
if (s_Corners[upA].splitAxis[0].z && s_Corners[upB].splitAxis[1].x) Handles.DrawLine(s_Corners[upA].points[0], s_Corners[upB].points[1]);
if (s_Corners[upA].splitAxis[1].z && s_Corners[upB].splitAxis[0].x) Handles.DrawLine(s_Corners[upA].points[1], s_Corners[upB].points[0]);
if (s_Corners[dnA].splitAxis[0].x && s_Corners[dnB].splitAxis[1].z) Handles.DrawLine(s_Corners[dnA].points[0], s_Corners[dnB].points[1]);
if (s_Corners[dnA].splitAxis[1].x && s_Corners[dnB].splitAxis[0].z) Handles.DrawLine(s_Corners[dnA].points[1], s_Corners[dnB].points[0]);
if (s_Corners[dnA].splitAxis[0].y && s_Corners[upA].splitAxis[1].y) Handles.DrawLine(s_Corners[dnA].points[0], s_Corners[upA].points[1]);
if (s_Corners[dnA].splitAxis[1].y && s_Corners[upA].splitAxis[0].y) Handles.DrawLine(s_Corners[dnA].points[1], s_Corners[upA].points[0]);
}
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/BeveledCylinderBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class BeveledCylinderBoundsHandle : PrimitiveBoundsHandle
{
public float bevelRadius
{
get => math.min(m_BevelRadius, math.cmin(GetSize()) * 0.5f);
set
{
if (!m_IsDragging)
m_BevelRadius = math.max(0f, value);
}
}
m_SideCount = value;
// Since the geometry is transformed by Handles.matrix during rendering, we transform the camera position
// by the inverse matrix so that the two-shaded wireframe will have the proper orientation.
var invMatrix = Handles.inverseMatrix;
cameraCenter = invMatrix.MultiplyPoint(cameraCenter);
cameraForward = invMatrix.MultiplyVector(cameraForward);
var cameraOrtho = Camera.current != null && Camera.current.orthographic;
t = (m_SideCount - 1) * angleStep;
var xyAngle1 = new float3(math.cos(t), math.sin(t), 0f);
var sideways1 = new float3(math.cos(t + kHalfPI - halfAngleStep), math.sin(t + kHalfPI - halfAngleStep), 0f);
var direction1 = new float3(math.cos(t + halfAngleStep), math.sin(t + halfAngleStep), 0f);
var bevelGreaterThanZero = bevelRadius > 0f;
var bevelLessThanCylinderRadius = bevelRadius < radius;
for (var i = 0; i < m_SideCount; ++i)
{
t = i * angleStep;
var xyAngle2 = new float3(math.cos(t), math.sin(t), 0f);
var sideways2 = new float3(math.cos(t + kHalfPI - halfAngleStep), math.sin(t + kHalfPI - halfAngleStep), 0f);
var direction2 = new float3(math.cos(t + halfAngleStep), math.sin(t + halfAngleStep), 0f);
var cornerIndex0 = i;
var cornerIndex1 = i + m_SideCount;
{
var orientation = quaternion.LookRotation(xyAngle2, up);
var cornerNormal = math.normalize(math.mul(orientation, new float3(0f, 1f, 1f)));
PhysicsBoundsHandleUtility.CalculateCornerHorizon(top2,
new float3x3(direction1, up, direction2),
cornerNormal, cameraCenter, cameraForward, cameraOrtho,
bevelRadius, out m_Corners[cornerIndex0]);
}
{
var orientation = quaternion.LookRotation(xyAngle2, -up);
var cornerNormal = math.normalize(math.mul(orientation, new float3(0f, 1f, 1f)));
PhysicsBoundsHandleUtility.CalculateCornerHorizon(bottom2,
new float3x3(direction2, -up, direction1),
cornerNormal, cameraCenter, cameraForward, cameraOrtho,
bevelRadius, out m_Corners[cornerIndex1]);
}
}
direction1 = direction2;
sideways1 = sideways2;
xyAngle0 = xyAngle1;
xyAngle1 = xyAngle2;
}
if (bevelGreaterThanZero)
{
Handles.color = frontfacedColor;
for (int a = m_SideCount - 1, b = 0; b < m_SideCount; a = b, ++b)
{
var up0 = a;
var dn0 = a + m_SideCount;
var up1 = b;
var dn1 = b + m_SideCount;
namespace Unity.Physics.Editor
{
abstract class BaseDrawer : PropertyDrawer
{
protected abstract bool IsCompatible(SerializedProperty property);
EditorGUI.EndProperty();
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/EulerAnglesDrawer.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(EulerAngles))]
class EulerAnglesDrawer : BaseDrawer
{
protected override bool IsCompatible(SerializedProperty property) => true;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var value = property.FindPropertyRelative(nameof(EulerAngles.Value));
return EditorGUI.GetPropertyHeight(value);
}
protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)
{
var value = property.FindPropertyRelative(nameof(EulerAngles.Value));
EditorGUI.PropertyField(position, value, label, true);
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/ExpandChildrenDrawer.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(ExpandChildrenAttribute))]
class ExpandChildrenDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
property.isExpanded = true;
return EditorGUI.GetPropertyHeight(property)
- EditorGUIUtility.standardVerticalSpacing
- EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var endProperty = property.GetEndProperty();
var childProperty = property.Copy();
childProperty.NextVisible(true);
while (!SerializedProperty.EqualContents(childProperty, endProperty))
{
position.height = EditorGUI.GetPropertyHeight(childProperty);
OnChildPropertyGUI(position, childProperty);
position.y += position.height + EditorGUIUtility.standardVerticalSpacing;
childProperty.NextVisible(false);
}
}
protected virtual void OnChildPropertyGUI(Rect position, SerializedProperty childProperty)
{
EditorGUI.PropertyField(position, childProperty, true);
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/PhysicsMaterialCoefficientDrawer.cs
using System;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(PhysicsMaterialCoefficient))]
class PhysicsMaterialCoefficientDrawer : BaseDrawer
{
static class Styles
{
public const float PopupWidth = 100f;
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) =>
EditorGUIUtility.singleLineHeight;
protected override bool IsCompatible(SerializedProperty property) => true;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(PhysicsMaterialProperties))]
class PhysicsMaterialPropertiesDrawer : BaseDrawer
{
static class Content
{
public static readonly GUIContent AdvancedGroupFoldout = EditorGUIUtility.TrTextContent("Advanced");
public static readonly GUIContent BelongsToLabel = EditorGUIUtility.TrTextContent(
"Belongs To",
"Specifies the categories to which this object belongs."
);
public static readonly GUIContent CollidesWithLabel = EditorGUIUtility.TrTextContent(
"Collides With",
"Specifies the categories of objects with which this object will collide, " +
"or with which it will raise events if intersecting a trigger."
);
public static readonly GUIContent CollisionFilterGroupFoldout =
EditorGUIUtility.TrTextContent("Collision Filter");
public static readonly GUIContent CustomFlagsLabel =
EditorGUIUtility.TrTextContent("Custom Tags", "Specify custom tags to read at run-time.");
public static readonly GUIContent FrictionLabel = EditorGUIUtility.TrTextContent(
"Friction",
"Specifies how resistant the body is to motion when sliding along other surfaces, " +
"as well as what value should be used when colliding with an object that has a different value."
);
public static readonly GUIContent RestitutionLabel = EditorGUIUtility.TrTextContent(
"Restitution",
"Specifies how bouncy the object will be when colliding with other surfaces, " +
"as well as what value should be used when colliding with an object that has a different value."
);
public static readonly GUIContent CollisionResponseLabel = EditorGUIUtility.TrTextContent(
"Collision Response",
"Specifies whether the shape should collide normally, raise trigger events when intersecting other shapes, " +
"collide normally and raise notifications of collision events with other shapes, " +
"or completely ignore collisions (but still move and intercept queries)."
);
}
void FindToggleAndValueProperties(
SerializedProperty property, SerializedProperty templateValueProperty, string relativePath,
out SerializedProperty toggle, out SerializedProperty value
)
{
var relative = property.FindPropertyRelative(relativePath);
toggle = relative.FindPropertyRelative("m_Override");
value = toggle.boolValue || templateValueProperty == null
? relative.FindPropertyRelative("m_Value")
: templateValueProperty.FindPropertyRelative(relativePath).FindPropertyRelative("m_Value");
}
// m_BelongsTo, m_CollidesWith
var group = property.FindPropertyRelative(k_CollisionFilterGroupKey);
if (group.isExpanded)
height += 2f * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
// m_CustomTags
group = property.FindPropertyRelative(k_AdvancedGroupKey);
if (group.isExpanded)
height += (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
// m_Template
if (property.FindPropertyRelative("m_SupportsTemplate").boolValue)
height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
// m_Friction, m_Restitution
FindToggleAndValueProperties(property, templateValueProperty, "m_CollisionResponse", out _, out var collisionResponse);
// Check if regular collider
CollisionResponsePolicy collisionResponseEnum = (CollisionResponsePolicy)collisionResponse.intValue;
if (collisionResponseEnum == CollisionResponsePolicy.Collide ||
collisionResponseEnum == CollisionResponsePolicy.CollideRaiseCollisionEvents)
height += 2f * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
return height;
}
EditorGUI.BeginDisabledGroup(!toggle.boolValue);
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
EditorGUI.PropertyField(new Rect(position) { xMin = togglePosition.xMax }, value, GUIContent.none, true);
EditorGUI.indentLevel = indent;
EditorGUI.EndDisabledGroup();
}
else
{
EditorGUI.PropertyField(position, value, label, true);
}
}
--EditorGUI.indentLevel;
}
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/SoftRangeDrawer.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(SoftRangeAttribute))]
class SoftRangeDrawer : BaseDrawer
{
protected override bool IsCompatible(SerializedProperty property)
{
return property.propertyType == SerializedPropertyType.Float;
}
protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)
{
var attr = attribute as SoftRangeAttribute;
EditorGUIControls.SoftSlider(
position, label, property, attr.SliderMin, attr.SliderMax, attr.TextFieldMin, attr.TextFieldMax
);
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/TagsDrawer.cs
using System.Collections.Generic;
using System.Linq;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
abstract class TagsDrawer<T> : PropertyDrawer where T : ScriptableObject, ITagNames
{
static class Styles
{
public static readonly string EverythingName = L10n.Tr("Everything");
public static readonly string MixedName = L10n.Tr("Mixed...");
public static readonly string NothingName = L10n.Tr("Nothing");
string[] GetOptions()
{
if (m_Options != null)
return m_Options;
var guids = AssetDatabase.FindAssets($"t:{typeof(T).Name}");
m_NamesAssets = guids
.Select(AssetDatabase.GUIDToAssetPath)
.Select(AssetDatabase.LoadAssetAtPath<T>)
.Where(c => c != null)
.ToArray();
m_Options = m_NamesAssets.FirstOrDefault()?.TagNames.ToArray() ?? DefaultOptions;
for (var i = 0; i < m_Options.Length; ++i)
{
if (string.IsNullOrEmpty(m_Options[i]))
m_Options[i] = DefaultOptions[i];
string[] m_Options;
T[] m_NamesAssets;
// TODO: remove when all usages of bool[] are migrated
SerializedProperty GetFirstChildProperty(SerializedProperty property)
{
if (!string.IsNullOrEmpty(FirstChildPropertyPath))
return property.FindPropertyRelative(FirstChildPropertyPath);
var sp = property.Copy();
sp.NextVisible(true);
return sp;
}
var value = 0;
var everything = 0;
var sp = GetFirstChildProperty(property);
for (int i = 0, count = MaxNumCategories; i < count; ++i)
{
EditorGUI.showMixedValue |= sp.hasMultipleDifferentValues;
value |= sp.boolValue ? 1 << i : 0;
everything |= 1 << i;
sp.NextVisible(false);
}
// in case size is smaller than 32
if (value == everything)
value = ~0;
var options = GetOptions();
if (
EditorGUI.DropdownButton(
controlPosition,
EditorGUIUtility.TrTempContent(GetButtonLabel(value, options)),
FocusType.Passive,
EditorStyles.popup
)
)
{
var menu = new GenericMenu();
menu.AddItem(
new GUIContent(Styles.NothingName),
value == 0,
() =>
{
sp = GetFirstChildProperty(property);
for (int i = 0, count = MaxNumCategories; i < count; ++i)
{
sp.boolValue = false;
sp.NextVisible(false);
}
sp.serializedObject.ApplyModifiedProperties();
}
);
menu.AddItem(
new GUIContent(Styles.EverythingName),
value == ~0,
() =>
{
sp = GetFirstChildProperty(property);
for (int i = 0, count = MaxNumCategories; i < count; ++i)
{
sp.boolValue = true;
sp.NextVisible(false);
}
sp.serializedObject.ApplyModifiedProperties();
}
);
for (var option = 0; option < options.Length; ++option)
{
var callbackValue = option;
menu.AddItem(
EditorGUIUtility.TrTextContent(options[option]),
((1 << option) & value) != 0,
args =>
{
var changedBitAndValue = (KeyValuePair<int, bool>)args;
sp = GetFirstChildProperty(property);
for (int i = 0, count = changedBitAndValue.Key; i < count; ++i)
sp.NextVisible(false);
sp.boolValue = changedBitAndValue.Value;
sp.serializedObject.ApplyModifiedProperties();
},
new KeyValuePair<int, bool>(callbackValue, ((1 << option) & value) == 0)
);
}
menu.AddSeparator(string.Empty);
menu.AddItem(
EditorGUIUtility.TrTempContent($"Edit {ObjectNames.NicifyVariableName(typeof(T).Name)}"),
false,
() =>
{
if (m_NamesAssets.Length > 0)
Selection.activeObject = m_NamesAssets[0];
else
{
var assetPath = AssetDatabase.GenerateUniqueAssetPath($"Assets/{typeof(T).Name}.asset");
AssetDatabase.CreateAsset(ScriptableObject.CreateInstance<T>(), assetPath);
Selection.activeObject = AssetDatabase.LoadAssetAtPath<T>(assetPath);
m_Options = null;
}
}
);
menu.DropDown(controlPosition);
}
EditorGUI.showMixedValue = showMixed;
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
if (m_NamesAssets?.Length > 1)
{
var id = GUIUtility.GetControlID(FocusType.Passive);
if (Event.current.type == EventType.Repaint)
{
position.width = EditorGUIUtility.singleLineHeight;
position.x = controlPosition.xMax + EditorGUIUtility.standardVerticalSpacing;
Styles.MultipleAssetsWarning.tooltip = string.Format(
Styles.MultipleAssetsTooltip,
ObjectNames.NicifyVariableName(typeof(T).Name),
m_NamesAssets.FirstOrDefault(n => n != null)?.name
);
GUIStyle.none.Draw(position, Styles.MultipleAssetsWarning, id);
}
}
}
}
[CustomPropertyDrawer(typeof(CustomPhysicsBodyTags))]
class CustomBodyTagsDrawer : TagsDrawer<CustomPhysicsBodyTagNames>
{
protected override string DefaultCategoryName => "Custom Physics Body Tag";
protected override int MaxNumCategories => 8;
}
[CustomPropertyDrawer(typeof(CustomPhysicsMaterialTags))]
class CustomMaterialTagsDrawer : TagsDrawer<CustomPhysicsMaterialTagNames>
{
protected override string DefaultCategoryName => "Custom Physics Material Tag";
protected override int MaxNumCategories => 8;
}
[CustomPropertyDrawer(typeof(PhysicsCategoryTags))]
class PhysicsCategoryTagsDrawer : TagsDrawer<PhysicsCategoryNames>
{
protected override string DefaultCategoryName => "Physics Category";
protected override int MaxNumCategories => 32;
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/EditorGUIControls.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[InitializeOnLoad]
static class EditorGUIControls
{
static EditorGUIControls()
{
if (k_SoftSlider == null)
Debug.LogException(new MissingMemberException("Could not find expected signature of EditorGUI.Slider() for soft slider."));
}
static class Styles
{
public static readonly string CompatibilityWarning = L10n.Tr("Not compatible with {0}.");
}
public static void DisplayCompatibilityWarning(Rect position, GUIContent label, string incompatibleType)
{
EditorGUI.HelpBox(
EditorGUI.PrefixLabel(position, label),
string.Format(Styles.CompatibilityWarning, incompatibleType),
MessageType.Error
);
}
static readonly MethodInfo k_SoftSlider = typeof(EditorGUI).GetMethod(
"Slider",
BindingFlags.Static | BindingFlags.NonPublic,
null,
new[]
{
typeof(Rect), // position
typeof(GUIContent), // label
typeof(float), // value
typeof(float), // sliderMin
typeof(float), // sliderMax
typeof(float), // textFieldMin
typeof(float) // textFieldMax
},
Array.Empty<ParameterModifier>()
);
static readonly object[] k_SoftSliderArgs = new object[7];
namespace Unity.Physics.Editor
{
static class SceneViewUtility
{
static class Styles
{
public static readonly GUIStyle ProgressBarTrack = new GUIStyle
{
fixedHeight = 4f,
normal = new GUIStyleState { background = Texture2D.whiteTexture }
};
public static readonly GUIStyle ProgressBarIndicator = new GUIStyle
{
fixedHeight = 4f,
normal = new GUIStyleState { background = Texture2D.whiteTexture }
};
public static readonly GUIStyle SceneViewStatusMessage = new GUIStyle("NotificationBackground")
{
fontSize = EditorStyles.label.fontSize
};
static Styles() => SceneViewStatusMessage.padding = SceneViewStatusMessage.border;
}
namespace Unity.Physics.Editor
{
static class StatusMessageUtility
{
public static MessageType GetHierarchyStatusMessage(IReadOnlyList<UnityObject> targets, out string statusMessage)
{
statusMessage = string.Empty;
if (targets.Count == 0)
return MessageType.None;
var numChildTargets = 0;
foreach (Component c in targets)
{
// hierarchy roots and leaf shapes do not emit a message
if (
c == null
|| c.transform.parent == null
|| PhysicsShapeExtensions.GetPrimaryBody(c.gameObject) != c.gameObject
)
continue;
if (matrixStates.Contains(MatrixState.NonUniformScale))
{
statusMessage = L10n.Tr(
matrixStates.Count == 1
? "Target has non-uniform scale. Shape data will be transformed during conversion in order to bake scale into the run-time format."
: "One or more targets has non-uniform scale. Shape data will be transformed during conversion in order to bake scale into the run-time format."
);
return MessageType.Warning;
}
return MessageType.None;
}
}
}
Platformer/Assets/Scripts/Physics/StatefulEvents/IStatefulSimulationEvent.cs
using Unity.Entities;
namespace Unity.Physics.Stateful
{
/// <summary>
/// Describes an event state.
/// Event state is set to:
/// 0) Undefined, when the state is unknown or not needed
/// 1) Enter, when 2 bodies are interacting in the current frame, but they did not interact the previous frame
/// 2) Stay, when 2 bodies are interacting in the current frame, and they also interacted in the previous frame
/// 3) Exit, when 2 bodies are not interacting in the current frame, but they did interact in the previous frame
/// </summary>
public enum StatefulEventState : byte
{
Undefined,
Enter,
Stay,
Exit
}
/// <summary>
/// Extends ISimulationEvent with extra <see cref="StatefulEventState"/>.
/// </summary>
public interface IStatefulSimulationEvent<T> : IBufferElementData, ISimulationEvent<T>
{
public StatefulEventState State { get; set; }
}
}
Platformer/Assets/Scripts/Physics/StatefulEvents/StatefulCollisionEvent.cs
using Unity.Assertions;
using Unity.Entities;
using Unity.Mathematics;
namespace Unity.Physics.Stateful
{
// Collision Event that can be stored inside a DynamicBuffer
public struct StatefulCollisionEvent : IBufferElementData, IStatefulSimulationEvent<StatefulCollisionEvent>
{
public Entity EntityA { get; set; }
public Entity EntityB { get; set; }
public int BodyIndexA { get; set; }
public int BodyIndexB { get; set; }
public ColliderKey ColliderKeyA { get; set; }
public ColliderKey ColliderKeyB { get; set; }
public StatefulEventState State { get; set; }
public float3 Normal;
// Only if CalculateDetails is checked on PhysicsCollisionEventBuffer of selected entity,
// this field will have valid value, otherwise it will be zero initialized
internal Details CollisionDetails;
// Returns the normal pointing from passed entity to the other one in pair
public float3 GetNormalFrom(Entity entity)
{
Assert.IsTrue((entity == EntityA) || (entity == EntityB));
return math.select(-Normal, Normal, entity == EntityB);
}
namespace Unity.Physics.Stateful
{
public struct StatefulCollisionEventDetails : IComponentData
{
public bool CalculateDetails;
}
public class StatefulCollisionEventBufferAuthoring : MonoBehaviour
{
[Tooltip("If selected, the details will be calculated in collision event dynamic buffer of this entity")]
public bool CalculateDetails = false;
class StatefulCollisionEventBufferBaker : Baker<StatefulCollisionEventBufferAuthoring>
{
public override void Bake(StatefulCollisionEventBufferAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
if (authoring.CalculateDetails)
{
var dynamicBufferTag = new StatefulCollisionEventDetails
{
CalculateDetails = authoring.CalculateDetails
};
AddComponent(entity, dynamicBufferTag);
}
AddBuffer<StatefulCollisionEvent>(entity);
}
}
}
}
Platformer/Assets/Scripts/Physics/StatefulEvents/StatefulCollisionEventBufferSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Physics;
using Unity.Physics.Systems;
namespace Unity.Physics.Stateful
{
/// <summary>
/// This system converts stream of CollisionEvents to StatefulCollisionEvents that can be stored in a Dynamic Buffer.
/// In order for this conversion, it is required to:
/// 1) Use the 'Collide Raise Collision Events' option of the 'Collision Response' property on a <see cref="PhysicsShapeAuthoring"/> component, and
/// 2) Add a <see cref="StatefulCollisionEventBufferAuthoring"/> component to that entity (and select if details should be calculated or not)
/// or, if this is desired on a Character Controller:
/// 1) Tick the 'Raise Collision Events' flag on the <see cref="CharacterControllerAuthoring"/> component.
/// </summary>
[UpdateInGroup(typeof(PhysicsSystemGroup))]
[UpdateAfter(typeof(PhysicsSimulationGroup))]
[BurstCompile]
public partial struct StatefulCollisionEventBufferSystem : ISystem
{
private StatefulSimulationEventBuffers<StatefulCollisionEvent> m_StateFulEventBuffers;
private ComponentHandles m_Handles;
// Component that does nothing. Made in order to use a generic job. See OnUpdate() method for details.
internal struct DummyExcludeComponent : IComponentData {};
struct ComponentHandles
{
public ComponentLookup<DummyExcludeComponent> EventExcludes;
public ComponentLookup<StatefulCollisionEventDetails> EventDetails;
public BufferLookup<StatefulCollisionEvent> EventBuffers;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
m_StateFulEventBuffers = new StatefulSimulationEventBuffers<StatefulCollisionEvent>();
m_StateFulEventBuffers.AllocateBuffers();
state.RequireForUpdate<StatefulCollisionEvent>();
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
m_Handles.Update(ref state);
m_StateFulEventBuffers.SwapBuffers();
UseExcludeComponent = false,
EventExcludeLookup = m_Handles.EventExcludes
}.Schedule(state.Dependency);
}
}
}
Platformer/Assets/Scripts/Physics/StatefulEvents/StatefulSimulationEventBuffers.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
namespace Unity.Physics.Stateful
{
public struct StatefulSimulationEventBuffers<T> where T : unmanaged, IStatefulSimulationEvent<T>
{
public NativeList<T> Previous;
public NativeList<T> Current;
/// <summary>
/// Given two sorted event buffers, this function returns a single combined list with
/// all the appropriate <see cref="StatefulEventState"/> set on each event.
/// </summary>
/// <param name="previousEvents">The events buffer from the previous frame. This list should have already be sorted from the previous frame.</param>
/// <param name="currentEvents">The events buffer from the current frame. This list should be sorted before calling this function.</param>
/// <param name="statefulEvents">A single combined list of stateful events based on the previous and current frames.</param>
/// <param name="sortCurrent">Specifies whether the currentEvents list needs to be sorted first.</param>
public static void GetStatefulEvents(NativeList<T> previousEvents, NativeList<T> currentEvents, NativeList<T> statefulEvents, bool sortCurrent = true)
{
if (sortCurrent) currentEvents.Sort();
statefulEvents.Clear();
int c = 0;
int p = 0;
while (c < currentEvents.Length && p < previousEvents.Length)
{
int r = previousEvents[p].CompareTo(currentEvents[c]);
if (r == 0)
{
var currentEvent = currentEvents[c];
currentEvent.State = StatefulEventState.Stay;
statefulEvents.Add(currentEvent);
c++;
p++;
}
else if (r < 0)
{
var previousEvent = previousEvents[p];
previousEvent.State = StatefulEventState.Exit;
statefulEvents.Add(previousEvent);
p++;
}
else //(r > 0)
{
var currentEvent = currentEvents[c];
currentEvent.State = StatefulEventState.Enter;
statefulEvents.Add(currentEvent);
c++;
}
}
if (c == currentEvents.Length)
{
while (p < previousEvents.Length)
{
var previousEvent = previousEvents[p];
previousEvent.State = StatefulEventState.Exit;
statefulEvents.Add(previousEvent);
p++;
}
}
else if (p == previousEvents.Length)
{
while (c < currentEvents.Length)
{
var currentEvent = currentEvents[c];
currentEvent.State = StatefulEventState.Enter;
statefulEvents.Add(currentEvent);
c++;
}
}
}
}
public static class StatefulEventCollectionJobs
{
[BurstCompile]
public struct CollectTriggerEvents : ITriggerEventsJob
{
public NativeList<StatefulTriggerEvent> TriggerEvents;
public void Execute(TriggerEvent triggerEvent) => TriggerEvents.Add(new StatefulTriggerEvent(triggerEvent));
}
[BurstCompile]
public struct CollectCollisionEvents : ICollisionEventsJob
{
public NativeList<StatefulCollisionEvent> CollisionEvents;
public void Execute(CollisionEvent collisionEvent) => CollisionEvents.Add(new StatefulCollisionEvent(collisionEvent));
}
[BurstCompile]
public struct CollectCollisionEventsWithDetails : ICollisionEventsJob
{
public NativeList<StatefulCollisionEvent> CollisionEvents;
[ReadOnly] public PhysicsWorld PhysicsWorld;
[ReadOnly] public ComponentLookup<StatefulCollisionEventDetails> EventDetails;
public bool ForceCalculateDetails;
CollisionEvents.Add(statefulCollisionEvent);
}
}
[BurstCompile]
public struct ConvertEventStreamToDynamicBufferJob<T, C> : IJob
where T : unmanaged, IBufferElementData, IStatefulSimulationEvent<T>
where C : unmanaged, IComponentData
{
public NativeList<T> PreviousEvents;
public NativeList<T> CurrentEvents;
public BufferLookup<T> EventBuffers;
public bool UseExcludeComponent;
[ReadOnly] public ComponentLookup<C> EventExcludeLookup;
public void Execute()
{
var statefulEvents = new NativeList<T>(CurrentEvents.Length, Allocator.Temp);
if (addToEntityA)
{
EventBuffers[statefulEvent.EntityA].Add(statefulEvent);
}
if (addToEntityB)
{
EventBuffers[statefulEvent.EntityB].Add(statefulEvent);
}
}
}
}
}
}
Platformer/Assets/Scripts/Physics/StatefulEvents/StatefulTriggerEvent.cs
using Unity.Entities;
using Unity.Assertions;
namespace Unity.Physics.Stateful
{
// Trigger Event that can be stored inside a DynamicBuffer
public struct StatefulTriggerEvent : IBufferElementData, IStatefulSimulationEvent<StatefulTriggerEvent>
{
public Entity EntityA { get; set; }
public Entity EntityB { get; set; }
public int BodyIndexA { get; set; }
public int BodyIndexB { get; set; }
public ColliderKey ColliderKeyA { get; set; }
public ColliderKey ColliderKeyB { get; set; }
public StatefulEventState State { get; set; }
namespace Unity.Physics.Stateful
{
/// <summary>
/// This system converts stream of TriggerEvents to StatefulTriggerEvents that can be stored in a Dynamic Buffer.
/// In order for this conversion, it is required to:
/// 1) Use the 'Raise Trigger Events' option of the 'Collision Response' property on a <see cref="PhysicsShapeAuthoring"/> component, and
/// 2) Add a <see cref="StatefulTriggerEventBufferAuthoring"/> component to that entity
/// or, if this is desired on a Character Controller:
/// 1) Tick the 'Raise Trigger Events' flag on the <see cref="CharacterControllerAuthoring"/> component.
/// Note: the Character Controller will not become a trigger, it will raise events when overlapping with one
/// </summary>
[UpdateInGroup(typeof(PhysicsSystemGroup))]
[UpdateAfter(typeof(PhysicsSimulationGroup))]
[BurstCompile]
public partial struct StatefulTriggerEventBufferSystem : ISystem
{
private StatefulSimulationEventBuffers<StatefulTriggerEvent> m_StateFulEventBuffers;
private ComponentHandles m_ComponentHandles;
private EntityQuery m_TriggerEventQuery;
struct ComponentHandles
{
public ComponentLookup<StatefulTriggerEventExclude> EventExcludes;
public BufferLookup<StatefulTriggerEvent> EventBuffers;
public ComponentHandles(ref SystemState systemState)
{
EventExcludes = systemState.GetComponentLookup<StatefulTriggerEventExclude>(true);
EventBuffers = systemState.GetBufferLookup<StatefulTriggerEvent>();
}
[BurstCompile]
public partial struct ClearTriggerEventDynamicBufferJob : IJobEntity
{
public void Execute(ref DynamicBuffer<StatefulTriggerEvent> eventBuffer) => eventBuffer.Clear();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
m_ComponentHandles.Update(ref state);
state.Dependency = new ClearTriggerEventDynamicBufferJob()
.ScheduleParallel(m_TriggerEventQuery, state.Dependency);
m_StateFulEventBuffers.SwapBuffers();
[Serializable]
public struct PlatformerPlayer : IComponentData
{
public Entity ControlledCharacter;
public Entity ControlledCamera;
}
[Serializable]
public struct PlatformerPlayerInputs : IComponentData
{
public float2 Move;
public float2 Look;
public float CameraZoom;
[DisallowMultipleComponent]
public class PlatformerPlayerAuthoring : MonoBehaviour
{
public GameObject ControlledCharacter;
public GameObject ControlledCamera;
public class Baker : Baker<PlatformerPlayerAuthoring>
{
public override void Bake(PlatformerPlayerAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.None);
AddComponent(entity, new PlatformerPlayer
{
ControlledCharacter = GetEntity(authoring.ControlledCharacter, TransformUsageFlags.Dynamic),
ControlledCamera = GetEntity(authoring.ControlledCamera, TransformUsageFlags.Dynamic),
});
AddComponent(entity, new PlatformerPlayerInputs());
}
}
}
Platformer/Assets/Scripts/Player/PlatformerPlayerSystems.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
using Unity.Physics.Systems;
using Unity.CharacterController;
[UpdateInGroup(typeof(InitializationSystemGroup))]
public partial class PlatformerPlayerInputsSystem : SystemBase
{
private PlatformerInputActions.GameplayMapActions _defaultActionsMap;
protected override void OnCreate()
{
PlatformerInputActions inputActions = new PlatformerInputActions();
inputActions.Enable();
inputActions.GameplayMap.Enable();
_defaultActionsMap = inputActions.GameplayMap;
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
RequireForUpdate<FixedTickSystem.Singleton>();
RequireForUpdate(SystemAPI.QueryBuilder().WithAll<PlatformerPlayer, PlatformerPlayerInputs>().Build());
}
protected override void OnUpdate()
{
uint fixedTick = SystemAPI.GetSingleton<FixedTickSystem.Singleton>().Tick;
foreach (var (playerInputs, player) in SystemAPI.Query<RefRW<PlatformerPlayerInputs>, PlatformerPlayer>())
{
playerInputs.ValueRW.Move = Vector2.ClampMagnitude(_defaultActionsMap.Move.ReadValue<Vector2>(), 1f);
playerInputs.ValueRW.Look = _defaultActionsMap.LookDelta.ReadValue<Vector2>();
if (math.lengthsq(_defaultActionsMap.LookConst.ReadValue<Vector2>()) > math.lengthsq(_defaultActionsMap.LookDelta.ReadValue<Vector2>()))
{
playerInputs.ValueRW.Look = _defaultActionsMap.LookConst.ReadValue<Vector2>() * SystemAPI.Time.DeltaTime;
}
playerInputs.ValueRW.CameraZoom = _defaultActionsMap.CameraZoom.ReadValue<float>();
playerInputs.ValueRW.SprintHeld = _defaultActionsMap.Sprint.IsPressed();
playerInputs.ValueRW.RollHeld = _defaultActionsMap.Roll.IsPressed();
playerInputs.ValueRW.JumpHeld = _defaultActionsMap.Jump.IsPressed();
if (_defaultActionsMap.Jump.WasPressedThisFrame())
{
playerInputs.ValueRW.JumpPressed.Set(fixedTick);
}
if (_defaultActionsMap.Dash.WasPressedThisFrame())
{
playerInputs.ValueRW.DashPressed.Set(fixedTick);
}
if (_defaultActionsMap.Crouch.WasPressedThisFrame())
{
playerInputs.ValueRW.CrouchPressed.Set(fixedTick);
}
if (_defaultActionsMap.Rope.WasPressedThisFrame())
{
playerInputs.ValueRW.RopePressed.Set(fixedTick);
}
if (_defaultActionsMap.Climb.WasPressedThisFrame())
{
playerInputs.ValueRW.ClimbPressed.Set(fixedTick);
}
if (_defaultActionsMap.FlyNoCollisions.WasPressedThisFrame())
{
playerInputs.ValueRW.FlyNoCollisionsPressed.Set(fixedTick);
}
}
}
}
/// <summary>
/// Apply inputs that need to be read at a variable rate
/// </summary>
[UpdateInGroup(typeof(SimulationSystemGroup), OrderFirst = true)]
[UpdateBefore(typeof(FixedStepSimulationSystemGroup))]
[BurstCompile]
public partial struct PlatformerPlayerVariableStepControlSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<PlatformerPlayer, PlatformerPlayerInputs>().Build());
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
foreach (var (playerInputs, player) in SystemAPI.Query<PlatformerPlayerInputs, PlatformerPlayer>().WithAll<Simulate>())
{
if (SystemAPI.HasComponent<OrbitCameraControl>(player.ControlledCamera))
{
OrbitCameraControl cameraControl = SystemAPI.GetComponent<OrbitCameraControl>(player.ControlledCamera);
cameraControl.FollowedCharacterEntity = player.ControlledCharacter;
cameraControl.Look = playerInputs.Look;
cameraControl.Zoom = playerInputs.CameraZoom;
SystemAPI.SetComponent(player.ControlledCamera, cameraControl);
}
}
}
}
/// <summary>
/// Apply inputs that need to be read at a fixed rate.
/// It is necessary to handle this as part of the fixed step group, in case your framerate is lower than the fixed step rate.
/// </summary>
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup), OrderFirst = true)]
[BurstCompile]
public partial struct PlatformerPlayerFixedStepControlSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<FixedTickSystem.Singleton>();
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<PlatformerPlayer, PlatformerPlayerInputs>().Build());
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
uint fixedTick = SystemAPI.GetSingleton<FixedTickSystem.Singleton>().Tick;
foreach (var (playerInputs, player) in SystemAPI.Query<RefRW<PlatformerPlayerInputs>, PlatformerPlayer>()
.WithAll<Simulate>())
{
if (SystemAPI.HasComponent<PlatformerCharacterControl>(player.ControlledCharacter) && SystemAPI.HasComponent<PlatformerCharacterStateMachine>(player.ControlledCharacter
{
PlatformerCharacterControl characterControl = SystemAPI.GetComponent<PlatformerCharacterControl>(player.ControlledCharacter);
PlatformerCharacterStateMachine stateMachine = SystemAPI.GetComponent<PlatformerCharacterStateMachine>(player.ControlledCharacter);
// Get camera rotation data, since our movement is relative to it
quaternion cameraRotation = quaternion.identity;
if (SystemAPI.HasComponent<LocalTransform>(player.ControlledCamera))
{
cameraRotation = SystemAPI.GetComponent<LocalTransform>(player.ControlledCamera).Rotation;
}
stateMachine.GetMoveVectorFromPlayerInput(stateMachine.CurrentState, in playerInputs.ValueRO, cameraRotation, out characterControl.MoveVector);
characterControl.JumpHeld = playerInputs.ValueRW.JumpHeld;
characterControl.RollHeld = playerInputs.ValueRW.RollHeld;
characterControl.SprintHeld = playerInputs.ValueRW.SprintHeld;
characterControl.JumpPressed = playerInputs.ValueRW.JumpPressed.IsSet(fixedTick);
characterControl.DashPressed = playerInputs.ValueRW.DashPressed.IsSet(fixedTick);
characterControl.CrouchPressed = playerInputs.ValueRW.CrouchPressed.IsSet(fixedTick);
characterControl.RopePressed = playerInputs.ValueRW.RopePressed.IsSet(fixedTick);
characterControl.ClimbPressed = playerInputs.ValueRW.ClimbPressed.IsSet(fixedTick);
characterControl.FlyNoCollisionsPressed = playerInputs.ValueRW.FlyNoCollisionsPressed.IsSet(fixedTick);
SystemAPI.SetComponent(player.ControlledCharacter, characterControl);
}
}
}
}
StressTest/Assets/Scripts/FlyCam.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FlyCam : MonoBehaviour
{
public float MaxMoveSpeed = 10f;
public float MoveSharpness = 10f;
public float SprintSpeedBoost = 5f;
void Start()
{
_planarForward = Vector3.ProjectOnPlane(transform.forward, Vector3.up).normalized;
_pitchAngle = Vector3.SignedAngle(_planarForward, transform.forward, transform.right);
}
void Update()
{
if (Input.GetMouseButtonDown(1))
{
_previousMousePos = Input.mousePosition;
}
if (Input.GetMouseButton(1))
{
// Rotation Input
Vector3 mouseDelta = Input.mousePosition - _previousMousePos;
_previousMousePos = Input.mousePosition;
// Yaw
float yawAngleChange = mouseDelta.x * RotationSpeed * Time.deltaTime;
Quaternion yawRotation = Quaternion.Euler(Vector3.up * yawAngleChange);
_planarForward = yawRotation * _planarForward;
// Pitch
_pitchAngle += -mouseDelta.y * RotationSpeed * Time.deltaTime;
_pitchAngle = Mathf.Clamp(_pitchAngle, -89f, 89f);
Quaternion pitchRotation = Quaternion.Euler(Vector3.right * _pitchAngle);
// Final rotation
Quaternion targetRotation = Quaternion.LookRotation(_planarForward, Vector3.up) * pitchRotation;
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, RotationSharpness * Time.deltaTime);
// Move Input
Vector3 forwardInput = transform.forward * ((Input.GetKey(KeyCode.W) ? 1f : 0f) + (Input.GetKey(KeyCode.S) ? -1f : 0f));
Vector3 rightInput = transform.right * ((Input.GetKey(KeyCode.D) ? 1f : 0f) + (Input.GetKey(KeyCode.A) ? -1f : 0f));
Vector3 upInput = transform.up * ((Input.GetKey(KeyCode.E) ? 1f : 0f) + (Input.GetKey(KeyCode.Q) ? -1f : 0f));
Vector3 directionalInput = Vector3.ClampMagnitude(forwardInput + rightInput + upInput, 1f);
// Move
float finalMaxSpeed = MaxMoveSpeed;
if (Input.GetKey(KeyCode.LeftShift))
{
finalMaxSpeed *= SprintSpeedBoost;
}
_framesDeltaSum = 0f;
_framesCount = 0;
_minDeltaTimeForAvg = Mathf.Infinity;
_maxDeltaTimeForAvg = Mathf.NegativeInfinity;
}
}
void Update()
{
// show hide
if (Input.GetKeyDown(KeyCode.F1))
{
MainCanvas.gameObject.SetActive(!MainCanvas.gameObject.activeSelf);
}
if (Input.GetKeyDown(KeyCode.F3))
{
_hasVSync = !_hasVSync;
UpdateRenderSettings();
}
// FPS
_framerateCalculator.Update();
if (Time.time >= _lastTimePolledFPS + FPSPollRate)
{
_framerateCalculator.PollFramerate(out string avg, out string worst, out string best);
AvgFPS.text = avg;
WorstFPS.text = worst;
BestFPS.text = best;
_lastTimePolledFPS = Time.time;
}
}
private void UpdateRenderSettings()
{
QualitySettings.vSyncCount = _hasVSync ? 1 : 0;
}
}
StressTest/Assets/Scripts/FramerateSetter.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
void Start()
{
Application.targetFrameRate = Framerate;
[Serializable]
public struct GameObjectName : IComponentData
{
public FixedString128Bytes Name;
}
void Start()
{
_world = World.DefaultGameObjectInjectionWorld;
_entityManager = _world.EntityManager;
_managerSingletonQuery = new EntityQueryBuilder(Allocator.Temp)
.WithAll<
StressTestManagerSystem.EnvironmentPrefabs>()
.WithAllRW<
StressTestManagerSystem.Singleton,
StressTestManagerSystem.Event>()
.Build(_entityManager);
entity = default;
return false;
}
ApplyCharacterSettings();
HasInitializedFromEntities = true;
}
}
}
public void SpawnCharacters()
{
if (int.TryParse(SpawnCountInputField.text, out int spawnCount))
{
ref var singleton = ref GetManagerSingletonRef();
singleton.CharacterCount = spawnCount;
var eventsBuffer = GetManagerSingletonEventsBuffer();
eventsBuffer.Add(new StressTestManagerSystem.Event { Type = StressTestManagerSystem.EventType.SpawnCharacters });
ApplyCharacterSettings();
}
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
if (!SystemAPI.HasSingleton<Singleton>())
return;
Singleton singleton = SystemAPI.GetSingleton<Singleton>();
NativeArray<Event> events = SystemAPI.GetSingletonBuffer<Event>().ToNativeArray(Allocator.Temp);
for (int i = 0; i < events.Length; i++)
{
switch (events[i].Type)
{
case EventType.SpawnCharacters:
SpawnCharacters(ref state, singleton);
break;
case EventType.SpawnEnvironment:
SpawnEnvironment(ref state, singleton);
break;
case EventType.ApplyPhysicsStep:
ApplyPhysicsStep(ref state, singleton);
break;
case EventType.ApplyStepHandling:
ApplyStepHandling(ref state, singleton);
break;
case EventType.ApplySlopeChanges:
ApplySlopeChanges(ref state, singleton);
break;
case EventType.ApplyProjectVelocityOnOverlaps:
ApplyProjectVelocityOnOverlaps(ref state, singleton);
break;
case EventType.ApplyStatefulHits:
ApplyStatefulHits(ref state, singleton);
break;
case EventType.ApplySimulateDynamic:
ApplySimulateDynamic(ref state, singleton);
break;
case EventType.ApplySaveRestoreState:
ApplySaveRestoreState(ref state, singleton);
break;
case EventType.ApplyEnhancedGroundPrecision:
ApplyEnhancedGroundPrecision(ref state, singleton);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
events.Dispose();
SystemAPI.GetSingletonBuffer<Event>().Clear();
}
[BurstCompile]
private void SpawnCharacters(ref SystemState state, Singleton singleton)
{
EntityCommandBuffer ecb = new EntityCommandBuffer(Allocator.Temp);
DynamicBuffer<Entity> spawnedCharacters = SystemAPI.GetSingletonBuffer<SpawnedCharacter>().Reinterpret<Entity>();
// Clear spawned characters
for (int i = 0; i < spawnedCharacters.Length; i++)
{
ecb.DestroyEntity(spawnedCharacters[i]);
}
// Spawn new characters
int spawnResolution = Mathf.CeilToInt(Mathf.Sqrt(singleton.CharacterCount));
float totalWidth = (spawnResolution - 1) * singleton.CharacterSpacing;
float3 spawnBottomCorner = (-math.right() * totalWidth * 0.5f) + (-math.forward() * totalWidth * 0.5f);
int counter = 0;
for (int x = 0; x < spawnResolution; x++)
{
for (int z = 0; z < spawnResolution; z++)
{
if (counter >= singleton.CharacterCount)
{
break;
}
Entity spawnedCharacter = ecb.Instantiate(singleton.CharacterPrefab);
ecb.AppendToBuffer(SystemAPI.GetSingletonEntity<Singleton>(),new SpawnedCharacter { Entity = spawnedCharacter });
float3 spawnPos = spawnBottomCorner + (math.right() * x * singleton.CharacterSpacing) + (math.forward() * z * singleton.CharacterSpacing);
ecb.SetComponent(spawnedCharacter, new LocalTransform { Position = spawnPos, Rotation = quaternion.identity, Scale = 1f});
counter++;
}
}
ecb.Playback(state.EntityManager);
ecb.Dispose();
}
[BurstCompile]
private void SpawnEnvironment(ref SystemState state, Singleton singleton)
{
EntityCommandBuffer ecb = new EntityCommandBuffer(Allocator.Temp);
DynamicBuffer<Entity> spawnedEnvironments = SystemAPI.GetSingletonBuffer<SpawnedEnvironment>().Reinterpret<Entity>();
DynamicBuffer<Entity> environmentPrefabs = SystemAPI.GetSingletonBuffer<EnvironmentPrefabs>().Reinterpret<Entity>();
for (int i = 0; i < spawnedEnvironments.Length; i++)
{
ecb.DestroyEntity(spawnedEnvironments[i]);
}
ecb.AppendToBuffer(SystemAPI.GetSingletonEntity<Singleton>(),new SpawnedEnvironment { Entity = ecb.Instantiate(environmentPrefabs[singleton.EnvironmentIndex])});
ecb.Playback(state.EntityManager);
ecb.Dispose();
}
[BurstCompile]
private void ApplyPhysicsStep(ref SystemState state, Singleton singleton)
{
ref PhysicsStep physicsStep = ref SystemAPI.GetSingletonRW<PhysicsStep>().ValueRW;
physicsStep.SimulationType = singleton.PhysicsStep ? SimulationType.UnityPhysics : SimulationType.NoPhysics;
// TODO: havok
}
[BurstCompile]
private void ApplyStepHandling(ref SystemState state, Singleton singleton)
{
NativeArray<Entity> entities = _characterQuery.ToEntityArray(Allocator.TempJob);
for (int i = 0; i < entities.Length; i++)
{
StressTestCharacterComponent character = state.EntityManager.GetComponentData<StressTestCharacterComponent>(entities[i]);
character.StepAndSlopeHandling.StepHandling = singleton.StepHandling;
state.EntityManager.SetComponentData(entities[i], character);
}
entities.Dispose();
}
[BurstCompile]
private void ApplySlopeChanges(ref SystemState state, Singleton singleton)
{
NativeArray<Entity> entities = _characterQuery.ToEntityArray(Allocator.TempJob);
for (int i = 0; i < entities.Length; i++)
{
StressTestCharacterComponent character = state.EntityManager.GetComponentData<StressTestCharacterComponent>(entities[i]);
character.StepAndSlopeHandling.PreventGroundingWhenMovingTowardsNoGrounding = singleton.SlopeChanges;
character.StepAndSlopeHandling.HasMaxDownwardSlopeChangeAngle = singleton.SlopeChanges;
state.EntityManager.SetComponentData(entities[i], character);
}
entities.Dispose();
}
[BurstCompile]
private void ApplyProjectVelocityOnOverlaps(ref SystemState state, Singleton singleton)
{
NativeArray<Entity> entities = _characterQuery.ToEntityArray(Allocator.TempJob);
for (int i = 0; i < entities.Length; i++)
{
KinematicCharacterProperties characterProperties = state.EntityManager.GetComponentData<KinematicCharacterProperties>(entities[i]);
characterProperties.ProjectVelocityOnInitialOverlaps = singleton.ProjectVelocityOnOverlaps;
state.EntityManager.SetComponentData(entities[i], characterProperties);
}
entities.Dispose();
}
[BurstCompile]
private void ApplyStatefulHits(ref SystemState state, Singleton singleton)
{
NativeArray<Entity> entities = _characterQuery.ToEntityArray(Allocator.TempJob);
for (int i = 0; i < entities.Length; i++)
{
StressTestCharacterComponent character = state.EntityManager.GetComponentData<StressTestCharacterComponent>(entities[i]);
character.UseStatefulHits = singleton.StatefulHits;
state.EntityManager.SetComponentData(entities[i], character);
}
entities.Dispose();
}
[BurstCompile]
private unsafe void ApplySimulateDynamic(ref SystemState state, Singleton singleton)
{
NativeArray<Entity> entities = _characterQuery.ToEntityArray(Allocator.TempJob);
for (int i = 0; i < entities.Length; i++)
{
KinematicCharacterProperties characterProperties = state.EntityManager.GetComponentData<KinematicCharacterProperties>(entities[i]);
characterProperties.SimulateDynamicBody = singleton.SimulateDynamic;
state.EntityManager.SetComponentData(entities[i], characterProperties);
PhysicsCollider physicsCollider = state.EntityManager.GetComponentData<PhysicsCollider>(entities[i]);
Unity.Physics.ConvexCollider* collider = (Unity.Physics.ConvexCollider*)physicsCollider.ColliderPtr;
Unity.Physics.Material material = collider->Material;
collider->Material = material;
state.EntityManager.SetComponentData(entities[i], physicsCollider);
}
entities.Dispose();
}
[BurstCompile]
private void ApplySaveRestoreState(ref SystemState state, Singleton singleton)
{
NativeArray<Entity> entities = _characterQuery.ToEntityArray(Allocator.TempJob);
for (int i = 0; i < entities.Length; i++)
{
StressTestCharacterComponent character = state.EntityManager.GetComponentData<StressTestCharacterComponent>(entities[i]);
character.UseSaveRestoreState = singleton.SaveRestoreState;
state.EntityManager.SetComponentData(entities[i], character);
}
entities.Dispose();
}
[BurstCompile]
private void ApplyEnhancedGroundPrecision(ref SystemState state, Singleton singleton)
{
NativeArray<Entity> entities = _characterQuery.ToEntityArray(Allocator.TempJob);
for (int i = 0; i < entities.Length; i++)
{
KinematicCharacterProperties characterProperties = state.EntityManager.GetComponentData<KinematicCharacterProperties>(entities[i]);
characterProperties.EnhancedGroundPrecision = singleton.SimulateDynamic;
state.EntityManager.SetComponentData(entities[i], characterProperties);
}
entities.Dispose();
}
}
StressTest/Assets/Scripts/StressTestManagerAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Entities;
using UnityEngine;
public class StressTestManagerAuthoring : MonoBehaviour
{
public GameObject CharacterPrefab;
public List<GameObject> EnvironmentPrefabs = new List<GameObject>();
public float CharacterSpacing = 5f;
public class Baker : Baker<StressTestManagerAuthoring>
{
public override void Bake(StressTestManagerAuthoring authoring)
{
Entity selfEntity = GetEntity(TransformUsageFlags.None);
AddComponent(selfEntity, authoring.Character);
AddComponent(selfEntity, new StressTestCharacterControl());
}
}
}
StressTest/Assets/Scripts/Character/StressTestCharacterComponent.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using Unity.CharacterController;
[Serializable]
public struct StressTestCharacterComponent : IComponentData
{
[Header("Movement")]
public float RotationSharpness;
public float GroundMaxSpeed;
public float GroundedMovementSharpness;
public float AirAcceleration;
public float AirMaxSpeed;
public float AirDrag;
public float JumpSpeed;
public float3 Gravity;
public bool PreventAirAccelerationAgainstUngroundedHits;
[Serializable]
public struct StressTestCharacterControl : IComponentData
{
public float3 MoveVector;
public bool Jump;
}
StressTest/Assets/Scripts/Character/StressTestCharacterControlSystem.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[BurstCompile]
public partial struct StressTestCharacterControlSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
if (!SystemAPI.HasSingleton<StressTestManagerSystem.Singleton>())
return;
[BurstCompile]
public partial struct StressTestCharacterControlJob : IJobEntity
{
public float3 WorldMoveVector;
[UpdateInGroup(typeof(KinematicCharacterPhysicsUpdateGroup))]
[BurstCompile]
public partial struct StressTestCharacterPhysicsUpdateSystem : ISystem
{
private EntityQuery _characterQuery;
private StressTestCharacterUpdateContext _context;
private KinematicCharacterUpdateContext _baseContext;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
_characterQuery = KinematicCharacterUtilities.GetBaseCharacterQueryBuilder()
.WithAll<
StressTestCharacterComponent,
StressTestCharacterControl>()
.Build(ref state);
state.RequireForUpdate(_characterQuery);
state.RequireForUpdate<PhysicsWorldSingleton>();
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
if (!SystemAPI.HasSingleton<StressTestManagerSystem.Singleton>())
return;
_context.OnSystemUpdate(ref state);
_baseContext.OnSystemUpdate(ref state, SystemAPI.Time, SystemAPI.GetSingleton<PhysicsWorldSingleton>());
[BurstCompile]
public void OnCreate(ref SystemState state)
{
_characterQuery = KinematicCharacterUtilities.GetBaseCharacterQueryBuilder()
.WithAll<
StressTestCharacterComponent,
StressTestCharacterControl>()
.Build(ref state);
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
if (!SystemAPI.HasSingleton<StressTestManagerSystem.Singleton>())
return;
bool multithreaded = SystemAPI.GetSingleton<StressTestManagerSystem.Singleton>().Multithreaded;
_context.OnSystemUpdate(ref state);
_baseContext.OnSystemUpdate(ref state, SystemAPI.Time, SystemAPI.GetSingleton<PhysicsWorldSingleton>());
StressTestCharacterVariableUpdateJob job = new StressTestCharacterVariableUpdateJob
{
Context = _context,
BaseContext = _baseContext,
};
if (multithreaded)
{
job.ScheduleParallel();
}
else
{
job.Schedule();
}
}
[BurstCompile]
public partial struct StressTestCharacterVariableUpdateJob : IJobEntity, IJobEntityChunkBeginEnd
{
public StressTestCharacterUpdateContext Context;
public KinematicCharacterUpdateContext BaseContext;
[assembly: InternalsVisibleTo("Unity.Physics.Custom.Editor")]
[assembly: InternalsVisibleTo("Unity.Physics.Custom.EditModeTests")]
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/BaseBodyPairConnector.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
[RequireComponent(typeof(PhysicsBodyAuthoring))]
public abstract class BaseBodyPairConnector : MonoBehaviour
{
public PhysicsBodyAuthoring LocalBody => GetComponent<PhysicsBodyAuthoring>();
public PhysicsBodyAuthoring ConnectedBody;
void OnEnable()
{
// included so tick box appears in Editor
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/CustomPhysicsMaterialTagNames.cs
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Serialization;
namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Custom Physics Material Tag Names", fileName = "Custom Material Tag Names", order = 506)]
public sealed partial class CustomPhysicsMaterialTagNames : ScriptableObject, ITagNames
{
CustomPhysicsMaterialTagNames() {}
public IReadOnlyList<string> TagNames => m_TagNames;
[SerializeField]
[FormerlySerializedAs("m_FlagNames")]
string[] m_TagNames = Enumerable.Range(0, 8).Select(i => string.Empty).ToArray();
void OnValidate()
{
if (m_TagNames.Length != 8)
Array.Resize(ref m_TagNames, 8);
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/CustomPhysicsMaterialTags.cs
using System;
using Unity.Mathematics;
namespace Unity.Physics.Authoring
{
[Serializable]
public struct CustomPhysicsMaterialTags : IEquatable<CustomPhysicsMaterialTags>
{
public static CustomPhysicsMaterialTags Everything => new CustomPhysicsMaterialTags { Value = unchecked((byte)~0) };
public static CustomPhysicsMaterialTags Nothing => new CustomPhysicsMaterialTags { Value = 0 };
public bool Tag00;
public bool Tag01;
public bool Tag02;
public bool Tag03;
public bool Tag04;
public bool Tag05;
public bool Tag06;
public bool Tag07;
internal bool this[int i]
{
get
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 7), nameof(i));
switch (i)
{
case 0: return Tag00;
case 1: return Tag01;
case 2: return Tag02;
case 3: return Tag03;
case 4: return Tag04;
case 5: return Tag05;
case 6: return Tag06;
case 7: return Tag07;
default: return default;
}
}
set
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 7), nameof(i));
switch (i)
{
case 0: Tag00 = value; break;
case 1: Tag01 = value; break;
case 2: Tag02 = value; break;
case 3: Tag03 = value; break;
case 4: Tag04 = value; break;
case 5: Tag05 = value; break;
case 6: Tag06 = value; break;
case 7: Tag07 = value; break;
}
}
}
public byte Value
{
get
{
var result = 0;
result |= (Tag00 ? 1 : 0) << 0;
result |= (Tag01 ? 1 : 0) << 1;
result |= (Tag02 ? 1 : 0) << 2;
result |= (Tag03 ? 1 : 0) << 3;
result |= (Tag04 ? 1 : 0) << 4;
result |= (Tag05 ? 1 : 0) << 5;
result |= (Tag06 ? 1 : 0) << 6;
result |= (Tag07 ? 1 : 0) << 7;
return (byte)result;
}
set
{
Tag00 = (value & (1 << 0)) != 0;
Tag01 = (value & (1 << 1)) != 0;
Tag02 = (value & (1 << 2)) != 0;
Tag03 = (value & (1 << 3)) != 0;
Tag04 = (value & (1 << 4)) != 0;
Tag05 = (value & (1 << 5)) != 0;
Tag06 = (value & (1 << 6)) != 0;
Tag07 = (value & (1 << 7)) != 0;
}
}
public bool Equals(CustomPhysicsMaterialTags other) => Value == other.Value;
public override bool Equals(object obj) => obj is CustomPhysicsMaterialTags other && Equals(other);
namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Physics Category Names", fileName = "Physics Category Names", order = 507)]
public sealed class PhysicsCategoryNames : ScriptableObject, ITagNames
{
PhysicsCategoryNames() {}
void OnValidate()
{
if (m_CategoryNames.Length != 32)
Array.Resize(ref m_CategoryNames, 32);
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsCategoryTags.cs
using System;
using Unity.Mathematics;
namespace Unity.Physics.Authoring
{
[Serializable]
public struct PhysicsCategoryTags : IEquatable<PhysicsCategoryTags>
{
public static PhysicsCategoryTags Everything => new PhysicsCategoryTags { Value = unchecked((uint)~0) };
public static PhysicsCategoryTags Nothing => new PhysicsCategoryTags { Value = 0 };
public bool Category00;
public bool Category01;
public bool Category02;
public bool Category03;
public bool Category04;
public bool Category05;
public bool Category06;
public bool Category07;
public bool Category08;
public bool Category09;
public bool Category10;
public bool Category11;
public bool Category12;
public bool Category13;
public bool Category14;
public bool Category15;
public bool Category16;
public bool Category17;
public bool Category18;
public bool Category19;
public bool Category20;
public bool Category21;
public bool Category22;
public bool Category23;
public bool Category24;
public bool Category25;
public bool Category26;
public bool Category27;
public bool Category28;
public bool Category29;
public bool Category30;
public bool Category31;
internal bool this[int i]
{
get
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 31), nameof(i));
switch (i)
{
case 0: return Category00;
case 1: return Category01;
case 2: return Category02;
case 3: return Category03;
case 4: return Category04;
case 5: return Category05;
case 6: return Category06;
case 7: return Category07;
case 8: return Category08;
case 9: return Category09;
case 10: return Category10;
case 11: return Category11;
case 12: return Category12;
case 13: return Category13;
case 14: return Category14;
case 15: return Category15;
case 16: return Category16;
case 17: return Category17;
case 18: return Category18;
case 19: return Category19;
case 20: return Category20;
case 21: return Category21;
case 22: return Category22;
case 23: return Category23;
case 24: return Category24;
case 25: return Category25;
case 26: return Category26;
case 27: return Category27;
case 28: return Category28;
case 29: return Category29;
case 30: return Category30;
case 31: return Category31;
default: return default;
}
}
set
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 31), nameof(i));
switch (i)
{
case 0: Category00 = value; break;
case 1: Category01 = value; break;
case 2: Category02 = value; break;
case 3: Category03 = value; break;
case 4: Category04 = value; break;
case 5: Category05 = value; break;
case 6: Category06 = value; break;
case 7: Category07 = value; break;
case 8: Category08 = value; break;
case 9: Category09 = value; break;
case 10: Category10 = value; break;
case 11: Category11 = value; break;
case 12: Category12 = value; break;
case 13: Category13 = value; break;
case 14: Category14 = value; break;
case 15: Category15 = value; break;
case 16: Category16 = value; break;
case 17: Category17 = value; break;
case 18: Category18 = value; break;
case 19: Category19 = value; break;
case 20: Category20 = value; break;
case 21: Category21 = value; break;
case 22: Category22 = value; break;
case 23: Category23 = value; break;
case 24: Category24 = value; break;
case 25: Category25 = value; break;
case 26: Category26 = value; break;
case 27: Category27 = value; break;
case 28: Category28 = value; break;
case 29: Category29 = value; break;
case 30: Category30 = value; break;
case 31: Category31 = value; break;
}
}
}
public uint Value
{
get
{
var result = 0;
result |= (Category00 ? 1 : 0) << 0;
result |= (Category01 ? 1 : 0) << 1;
result |= (Category02 ? 1 : 0) << 2;
result |= (Category03 ? 1 : 0) << 3;
result |= (Category04 ? 1 : 0) << 4;
result |= (Category05 ? 1 : 0) << 5;
result |= (Category06 ? 1 : 0) << 6;
result |= (Category07 ? 1 : 0) << 7;
result |= (Category08 ? 1 : 0) << 8;
result |= (Category09 ? 1 : 0) << 9;
result |= (Category10 ? 1 : 0) << 10;
result |= (Category11 ? 1 : 0) << 11;
result |= (Category12 ? 1 : 0) << 12;
result |= (Category13 ? 1 : 0) << 13;
result |= (Category14 ? 1 : 0) << 14;
result |= (Category15 ? 1 : 0) << 15;
result |= (Category16 ? 1 : 0) << 16;
result |= (Category17 ? 1 : 0) << 17;
result |= (Category18 ? 1 : 0) << 18;
result |= (Category19 ? 1 : 0) << 19;
result |= (Category20 ? 1 : 0) << 20;
result |= (Category21 ? 1 : 0) << 21;
result |= (Category22 ? 1 : 0) << 22;
result |= (Category23 ? 1 : 0) << 23;
result |= (Category24 ? 1 : 0) << 24;
result |= (Category25 ? 1 : 0) << 25;
result |= (Category26 ? 1 : 0) << 26;
result |= (Category27 ? 1 : 0) << 27;
result |= (Category28 ? 1 : 0) << 28;
result |= (Category29 ? 1 : 0) << 29;
result |= (Category30 ? 1 : 0) << 30;
result |= (Category31 ? 1 : 0) << 31;
return unchecked((uint)result);
}
set
{
Category00 = (value & (1 << 0)) != 0;
Category01 = (value & (1 << 1)) != 0;
Category02 = (value & (1 << 2)) != 0;
Category03 = (value & (1 << 3)) != 0;
Category04 = (value & (1 << 4)) != 0;
Category05 = (value & (1 << 5)) != 0;
Category06 = (value & (1 << 6)) != 0;
Category07 = (value & (1 << 7)) != 0;
Category08 = (value & (1 << 8)) != 0;
Category09 = (value & (1 << 9)) != 0;
Category10 = (value & (1 << 10)) != 0;
Category11 = (value & (1 << 11)) != 0;
Category12 = (value & (1 << 12)) != 0;
Category13 = (value & (1 << 13)) != 0;
Category14 = (value & (1 << 14)) != 0;
Category15 = (value & (1 << 15)) != 0;
Category16 = (value & (1 << 16)) != 0;
Category17 = (value & (1 << 17)) != 0;
Category18 = (value & (1 << 18)) != 0;
Category19 = (value & (1 << 19)) != 0;
Category20 = (value & (1 << 20)) != 0;
Category21 = (value & (1 << 21)) != 0;
Category22 = (value & (1 << 22)) != 0;
Category23 = (value & (1 << 23)) != 0;
Category24 = (value & (1 << 24)) != 0;
Category25 = (value & (1 << 25)) != 0;
Category26 = (value & (1 << 26)) != 0;
Category27 = (value & (1 << 27)) != 0;
Category28 = (value & (1 << 28)) != 0;
Category29 = (value & (1 << 29)) != 0;
Category30 = (value & (1 << 30)) != 0;
Category31 = (value & (1 << 31)) != 0;
}
}
public bool Equals(PhysicsCategoryTags other) => Value == other.Value;
public override bool Equals(object obj) => obj is PhysicsCategoryTags other && Equals(other);
[Serializable]
public struct PhysicsMaterialCoefficient
{
[SoftRange(0f, 1f, TextFieldMax = float.MaxValue)]
public float Value;
public Material.CombinePolicy CombineMode;
}
public T Value
{
get => m_Value;
set
{
m_Value = value;
Override = true;
}
}
[SerializeField]
T m_Value;
[Serializable]
class OverridableCollisionResponse : OverridableValue<CollisionResponsePolicy> {}
[Serializable]
class OverridableMaterialCoefficient : OverridableValue<PhysicsMaterialCoefficient>
{
protected override void OnValidate(ref PhysicsMaterialCoefficient value) =>
value.Value = math.max(0f, value.Value);
}
[Serializable]
class OverridableCategoryTags : OverridableValue<PhysicsCategoryTags> {}
[Serializable]
class OverridableCustomMaterialTags : OverridableValue<CustomPhysicsMaterialTags> {}
[Serializable]
class PhysicsMaterialProperties : IInheritPhysicsMaterialProperties, ISerializationCallbackReceiver
{
public PhysicsMaterialProperties(bool supportsTemplate) => m_SupportsTemplate = supportsTemplate;
[SerializeField, HideInInspector]
bool m_SupportsTemplate;
public bool OverrideCollisionResponse { get => m_CollisionResponse.Override; set => m_CollisionResponse.Override = value; }
public CollisionResponsePolicy CollisionResponse
{
get => Get(m_CollisionResponse, m_Template == null ? null : m_Template?.CollisionResponse);
set => m_CollisionResponse.Value = value;
}
[SerializeField]
OverridableCollisionResponse m_CollisionResponse = new OverridableCollisionResponse
{
Value = CollisionResponsePolicy.Collide,
Override = false
};
public bool OverrideFriction { get => m_Friction.Override; set => m_Friction.Override = value; }
public PhysicsMaterialCoefficient Friction
{
get => Get(m_Friction, m_Template == null ? null : m_Template?.Friction);
set => m_Friction.Value = value;
}
[SerializeField]
OverridableMaterialCoefficient m_Friction = new OverridableMaterialCoefficient
{
Value = new PhysicsMaterialCoefficient { Value = 0.5f, CombineMode = Material.CombinePolicy.GeometricMean },
Override = false
};
public bool OverrideRestitution { get => m_Restitution.Override; set => m_Restitution.Override = value; }
public PhysicsMaterialCoefficient Restitution
{
get => Get(m_Restitution, m_Template == null ? null : m_Template?.Restitution);
set => m_Restitution.Value = value;
}
[SerializeField]
OverridableMaterialCoefficient m_Restitution = new OverridableMaterialCoefficient
{
Value = new PhysicsMaterialCoefficient { Value = 0f, CombineMode = Material.CombinePolicy.Maximum },
Override = false
};
public bool OverrideBelongsTo { get => m_BelongsToCategories.Override; set => m_BelongsToCategories.Override = value; }
public PhysicsCategoryTags BelongsTo
{
get => Get(m_BelongsToCategories, m_Template == null ? null : m_Template?.BelongsTo);
set => m_BelongsToCategories.Value = value;
}
[SerializeField]
OverridableCategoryTags m_BelongsToCategories =
new OverridableCategoryTags { Value = PhysicsCategoryTags.Everything, Override = false };
public bool OverrideCollidesWith { get => m_CollidesWithCategories.Override; set => m_CollidesWithCategories.Override = value; }
public PhysicsCategoryTags CollidesWith
{
get => Get(m_CollidesWithCategories, m_Template == null ? null : m_Template?.CollidesWith);
set => m_CollidesWithCategories.Value = value;
}
[SerializeField]
OverridableCategoryTags m_CollidesWithCategories =
new OverridableCategoryTags { Value = PhysicsCategoryTags.Everything, Override = false };
public bool OverrideCustomTags { get => m_CustomMaterialTags.Override; set => m_CustomMaterialTags.Override = value; }
public CustomPhysicsMaterialTags CustomTags
{
get => Get(m_CustomMaterialTags, m_Template == null ? null : m_Template?.CustomTags);
set => m_CustomMaterialTags.Value = value;
}
[SerializeField]
OverridableCustomMaterialTags m_CustomMaterialTags =
new OverridableCustomMaterialTags { Value = default, Override = false };
material.m_SupportsTemplate = supportsTemplate;
if (!supportsTemplate)
{
material.m_Template = null;
material.m_CollisionResponse.Override = true;
material.m_Friction.Override = true;
material.m_Restitution.Override = true;
}
material.m_Friction.OnValidate();
material.m_Restitution.OnValidate();
}
[SerializeField]
int m_SerializedVersion = 0;
void ISerializationCallbackReceiver.OnBeforeSerialize() {}
void ISerializationCallbackReceiver.OnAfterDeserialize() => UpgradeVersionIfNecessary();
namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Physics Material Template", fileName = "Physics Material Template", order = 508)]
public sealed class PhysicsMaterialTemplate : ScriptableObject, IPhysicsMaterialProperties
{
PhysicsMaterialTemplate() {}
public CollisionResponsePolicy CollisionResponse { get => m_Value.CollisionResponse; set => m_Value.CollisionResponse = value; }
public PhysicsMaterialCoefficient Friction { get => m_Value.Friction; set => m_Value.Friction = value; }
public PhysicsMaterialCoefficient Restitution { get => m_Value.Restitution; set => m_Value.Restitution = value; }
public PhysicsCategoryTags BelongsTo { get => m_Value.BelongsTo; set => m_Value.BelongsTo = value; }
public PhysicsCategoryTags CollidesWith { get => m_Value.CollidesWith; set => m_Value.CollidesWith = value; }
public CustomPhysicsMaterialTags CustomTags { get => m_Value.CustomTags; set => m_Value.CustomTags = value; }
[SerializeField]
PhysicsMaterialProperties m_Value = new PhysicsMaterialProperties(false);
PhysicsBodyAuthoring() {}
public BodyMotionType MotionType { get => m_MotionType; set => m_MotionType = value; }
[SerializeField]
[Tooltip("Specifies whether the body should be fully physically simulated, moved directly, or fixed in place.")]
BodyMotionType m_MotionType;
public BodySmoothing Smoothing { get => m_Smoothing; set => m_Smoothing = value; }
[SerializeField]
[Tooltip("Specifies how this body's motion in its graphics representation should be smoothed when the rendering framerate is greater than the fixed step rate used by physics.")]
BodySmoothing m_Smoothing = BodySmoothing.None;
const float k_MinimumMass = 0.001f;
public float Mass
{
get => m_MotionType == BodyMotionType.Dynamic ? m_Mass : float.PositiveInfinity;
set => m_Mass = math.max(k_MinimumMass, value);
}
[SerializeField]
float m_Mass = 1.0f;
public float LinearDamping { get => m_LinearDamping; set => m_LinearDamping = math.max(0f, value); }
[SerializeField]
[Tooltip("This is applied to a body's linear velocity reducing it over time.")]
float m_LinearDamping = 0.01f;
public float AngularDamping { get => m_AngularDamping; set => m_AngularDamping = math.max(0f, value); }
[SerializeField]
[Tooltip("This is applied to a body's angular velocity reducing it over time.")]
float m_AngularDamping = 0.05f;
public float3 InitialLinearVelocity { get => m_InitialLinearVelocity; set => m_InitialLinearVelocity = value; }
[SerializeField]
[Tooltip("The initial linear velocity of the body in world space")]
float3 m_InitialLinearVelocity = float3.zero;
public float3 InitialAngularVelocity { get => m_InitialAngularVelocity; set => m_InitialAngularVelocity = value; }
[SerializeField]
[Tooltip("This represents the initial rotation speed around each axis in the local motion space of the body i.e. around the center of mass")]
float3 m_InitialAngularVelocity = float3.zero;
public float GravityFactor
{
get => m_MotionType == BodyMotionType.Dynamic ? m_GravityFactor : 0f;
set => m_GravityFactor = value;
}
[SerializeField]
[Tooltip("Scales the amount of gravity to apply to this body.")]
float m_GravityFactor = 1f;
public bool OverrideDefaultMassDistribution
{
#pragma warning disable 618
get => m_OverrideDefaultMassDistribution;
set => m_OverrideDefaultMassDistribution = value;
#pragma warning restore 618
}
[SerializeField]
[Tooltip("Default mass distribution is based on the shapes associated with this body.")]
bool m_OverrideDefaultMassDistribution;
public MassDistribution CustomMassDistribution
{
get => new MassDistribution
{
Transform = new RigidTransform(m_Orientation, m_CenterOfMass),
InertiaTensor =
m_MotionType == BodyMotionType.Dynamic ? m_InertiaTensor : new float3(float.PositiveInfinity)
};
set
{
m_CenterOfMass = value.Transform.pos;
m_Orientation.SetValue(value.Transform.rot);
m_InertiaTensor = value.InertiaTensor;
#pragma warning disable 618
m_OverrideDefaultMassDistribution = true;
#pragma warning restore 618
}
}
[SerializeField]
float3 m_CenterOfMass;
[SerializeField]
EulerAngles m_Orientation = EulerAngles.Default;
[SerializeField]
// Default value to solid unit sphere : https://en.wikipedia.org/wiki/List_of_moments_of_inertia
float3 m_InertiaTensor = new float3(2f / 5f);
public uint WorldIndex { get => m_WorldIndex; set => m_WorldIndex = value; }
[SerializeField]
[Tooltip("The index of the physics world this body belongs to. Default physics world has index 0.")]
uint m_WorldIndex = 0;
public CustomPhysicsBodyTags CustomTags { get => m_CustomTags; set => m_CustomTags = value; }
[SerializeField]
CustomPhysicsBodyTags m_CustomTags = CustomPhysicsBodyTags.Nothing;
void OnEnable()
{
// included so tick box appears in Editor
}
void OnValidate()
{
m_Mass = math.max(k_MinimumMass, m_Mass);
m_LinearDamping = math.max(m_LinearDamping, 0f);
m_AngularDamping = math.max(m_AngularDamping, 0f);
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/PhysicsShapeAuthoring.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using UnityMesh = UnityEngine.Mesh;
namespace Unity.Physics.Authoring
{
public sealed class UnimplementedShapeException : NotImplementedException
{
public UnimplementedShapeException(ShapeType shapeType)
: base($"Unknown shape type {shapeType} requires explicit implementation") {}
}
#if UNITY_2021_2_OR_NEWER
[Icon(k_IconPath)]
#endif
[AddComponentMenu("Entities/Physics/Physics Shape")]
public sealed class PhysicsShapeAuthoring : MonoBehaviour, IInheritPhysicsMaterialProperties, ISerializationCallbackReceiver
{
const string k_IconPath = "Packages/com.unity.physics/Unity.Physics.Editor/Editor Default Resources/Icons/d_BoxCollider@64.png";
PhysicsShapeAuthoring() {}
[Serializable]
struct CylindricalProperties
{
public float Height;
public float Radius;
[HideInInspector]
public int Axis;
}
[SerializeField]
[ExpandChildren]
CylindricalProperties m_Capsule = new CylindricalProperties { Height = 1f, Radius = 0.5f, Axis = 2 };
[SerializeField]
[ExpandChildren]
CylindricalProperties m_Cylinder = new CylindricalProperties { Height = 1f, Radius = 0.5f, Axis = 2 };
[SerializeField]
[Tooltip("How many sides the convex cylinder shape should have.")]
[Range(CylinderGeometry.MinSideCount, CylinderGeometry.MaxSideCount)]
int m_CylinderSideCount = 20;
[SerializeField]
float m_SphereRadius = 0.5f;
public ConvexHullGenerationParameters ConvexHullGenerationParameters => m_ConvexHullGenerationParameters;
[SerializeField]
[Tooltip(
"Specifies the minimum weight of a skinned vertex assigned to this shape and/or its transform children required for it to be included for automatic detection. " +
"A value of 0 will include all points with any weight assigned to this shape's hierarchy."
)]
[Range(0f, 1f)]
float m_MinimumSkinnedVertexWeight = 0.1f;
[SerializeField]
[ExpandChildren]
ConvexHullGenerationParameters m_ConvexHullGenerationParameters = ConvexHullGenerationParameters.Default.ToAuthoring();
// TODO: remove this accessor in favor of GetRawVertices() when blob data is serializable
internal UnityMesh CustomMesh => m_CustomMesh;
[SerializeField]
[Tooltip("If no custom mesh is specified, then one will be generated using this body's rendered meshes.")]
UnityMesh m_CustomMesh;
public bool ForceUnique { get => m_ForceUnique; set => m_ForceUnique = value; }
[SerializeField]
bool m_ForceUnique;
public PhysicsMaterialTemplate MaterialTemplate { get => m_Material.Template; set => m_Material.Template = value; }
PhysicsMaterialTemplate IInheritPhysicsMaterialProperties.Template
{
get => m_Material.Template;
set => m_Material.Template = value;
}
public bool OverrideCollisionResponse { get => m_Material.OverrideCollisionResponse; set => m_Material.OverrideCollisionResponse = value; }
public CollisionResponsePolicy CollisionResponse { get => m_Material.CollisionResponse; set => m_Material.CollisionResponse = value; }
public bool OverrideFriction { get => m_Material.OverrideFriction; set => m_Material.OverrideFriction = value; }
public PhysicsMaterialCoefficient Friction { get => m_Material.Friction; set => m_Material.Friction = value; }
public bool OverrideRestitution
{
get => m_Material.OverrideRestitution;
set => m_Material.OverrideRestitution = value;
}
public PhysicsMaterialCoefficient Restitution
{
get => m_Material.Restitution;
set => m_Material.Restitution = value;
}
public bool OverrideBelongsTo
{
get => m_Material.OverrideBelongsTo;
set => m_Material.OverrideBelongsTo = value;
}
void AppendMeshPropertiesToNativeBuffers(
float4x4 localToWorld, UnityMesh mesh, NativeList<float3> vertices, NativeList<int3> triangles, bool validate,
NativeList<HashableShapeInputs> inputs, HashSet<UnityMesh> meshAssets
)
{
if (mesh == null || validate && !mesh.IsValidForConversion(gameObject))
return;
var childToShape = math.mul(transform.worldToLocalMatrix, localToWorld);
AppendMeshPropertiesToNativeBuffers(childToShape, mesh, vertices, triangles, inputs, meshAssets);
}
internal static void AppendMeshPropertiesToNativeBuffers(
float4x4 childToShape, UnityMesh mesh, NativeList<float3> vertices, NativeList<int3> triangles,
NativeList<HashableShapeInputs> inputs, HashSet<UnityMesh> meshAssets
)
{
var offset = 0u;
#if UNITY_EDITOR
// TODO: when min spec is 2020.1, collect all meshes and their data via single Burst job rather than one at a time
using (var meshData = UnityEditor.MeshUtility.AcquireReadOnlyMeshData(mesh))
#else
using (var meshData = UnityMesh.AcquireReadOnlyMeshData(mesh))
#endif
{
if (vertices.IsCreated)
{
offset = (uint)vertices.Length;
var tmpVertices = new NativeArray<Vector3>(meshData[0].vertexCount, Allocator.Temp);
meshData[0].GetVertices(tmpVertices);
if (vertices.Capacity < vertices.Length + tmpVertices.Length)
vertices.Capacity = vertices.Length + tmpVertices.Length;
foreach (var v in tmpVertices)
vertices.Add(math.mul(childToShape, new float4(v, 1f)).xyz);
}
if (triangles.IsCreated)
{
switch (meshData[0].indexFormat)
{
case IndexFormat.UInt16:
var indices16 = meshData[0].GetIndexData<ushort>();
var numTriangles = indices16.Length / 3;
if (triangles.Capacity < triangles.Length + numTriangles)
triangles.Capacity = triangles.Length + numTriangles;
for (var sm = 0; sm < meshData[0].subMeshCount; ++sm)
{
var subMesh = meshData[0].GetSubMesh(sm);
for (int i = subMesh.indexStart, count = 0; count < subMesh.indexCount; i += 3, count += 3)
triangles.Add((int3) new uint3(offset + indices16[i], offset + indices16[i + 1], offset + indices16[i + 2]));
}
break;
case IndexFormat.UInt32:
var indices32 = meshData[0].GetIndexData<uint>();
numTriangles = indices32.Length / 3;
if (triangles.Capacity < triangles.Length + numTriangles)
triangles.Capacity = triangles.Length + numTriangles;
for (var sm = 0; sm < meshData[0].subMeshCount; ++sm)
{
var subMesh = meshData[0].GetSubMesh(sm);
for (int i = subMesh.indexStart, count = 0; count < subMesh.indexCount; i += 3, count += 3)
triangles.Add((int3) new uint3(offset + indices32[i], offset + indices32[i + 1], offset + indices32[i + 2]));
}
break;
}
}
}
if (inputs.IsCreated)
inputs.Add(HashableShapeInputs.FromMesh(mesh, childToShape));
meshAssets?.Add(mesh);
}
void UpdateCapsuleAxis()
{
var cmax = math.cmax(m_PrimitiveSize);
var cmin = math.cmin(m_PrimitiveSize);
if (cmin == cmax)
return;
m_Capsule.Axis = m_PrimitiveSize.GetMaxAxis();
}
void UpdateCylinderAxis() => m_Cylinder.Axis = m_PrimitiveSize.GetDeviantAxis();
m_ConvexHullGenerationParameters.BevelRadius = geometry.BevelRadius;
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetSphere(SphereGeometry geometry, quaternion orientation)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(orientation);
SetSphere(geometry, euler);
}
internal void SetSphere(SphereGeometry geometry)
{
SetSphere(geometry, m_PrimitiveOrientation);
}
internal void SetSphere(SphereGeometry geometry, EulerAngles orientation)
{
m_ShapeType = ShapeType.Sphere;
m_PrimitiveCenter = geometry.Center;
var radius = math.max(0f, geometry.Radius);
m_PrimitiveSize = new float3(2f * radius, 2f * radius, 2f * radius);
m_PrimitiveOrientation = orientation;
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetPlane(float3 center, float2 size, quaternion orientation)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(orientation);
SetPlane(center, size, euler);
}
internal void SetPlane(float3 center, float2 size, EulerAngles orientation)
{
m_ShapeType = ShapeType.Plane;
m_PrimitiveCenter = center;
m_PrimitiveOrientation = orientation;
m_PrimitiveSize = new float3(size.x, 0f, size.y);
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetConvexHull(
ConvexHullGenerationParameters hullGenerationParameters, float minimumSkinnedVertexWeight
)
{
m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
SetConvexHull(hullGenerationParameters);
}
public void SetConvexHull(ConvexHullGenerationParameters hullGenerationParameters, UnityMesh customMesh = null)
{
m_ShapeType = ShapeType.ConvexHull;
m_CustomMesh = customMesh;
hullGenerationParameters.OnValidate();
m_ConvexHullGenerationParameters = hullGenerationParameters;
}
public void SetMesh(UnityMesh mesh = null)
{
m_ShapeType = ShapeType.Mesh;
m_CustomMesh = mesh;
}
void OnEnable()
{
// included so tick box appears in Editor
}
const int k_LatestVersion = 1;
[SerializeField]
int m_SerializedVersion = 0;
void ISerializationCallbackReceiver.OnBeforeSerialize() {}
void ISerializationCallbackReceiver.OnAfterDeserialize() => UpgradeVersionIfNecessary();
#pragma warning disable 618
void UpgradeVersionIfNecessary()
{
if (m_SerializedVersion < k_LatestVersion)
{
// old data from version < 1 have been removed
if (m_SerializedVersion < 1)
m_SerializedVersion = 1;
}
}
#pragma warning restore 618
static void Validate(ref CylindricalProperties props)
{
props.Height = math.max(0f, props.Height);
props.Radius = math.max(0f, props.Radius);
}
void OnValidate()
{
UpgradeVersionIfNecessary();
m_PrimitiveSize = math.max(m_PrimitiveSize, new float3());
Validate(ref m_Capsule);
Validate(ref m_Cylinder);
switch (m_ShapeType)
{
case ShapeType.Box:
SetBox(GetBoxProperties(out var orientation), orientation);
break;
case ShapeType.Capsule:
SetCapsule(GetCapsuleProperties());
break;
case ShapeType.Cylinder:
SetCylinder(GetCylinderProperties(out orientation), orientation);
break;
case ShapeType.Sphere:
SetSphere(GetSphereProperties(out orientation), orientation);
break;
case ShapeType.Plane:
GetPlaneProperties(out var center, out var size2D, out orientation);
SetPlane(center, size2D, orientation);
break;
case ShapeType.ConvexHull:
case ShapeType.Mesh:
break;
default:
throw new UnimplementedShapeException(m_ShapeType);
}
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
m_CylinderSideCount =
math.clamp(m_CylinderSideCount, CylinderGeometry.MinSideCount, CylinderGeometry.MaxSideCount);
m_ConvexHullGenerationParameters.OnValidate();
PhysicsMaterialProperties.OnValidate(ref m_Material, true);
}
// matrix to transform point from shape space into world space
public float4x4 GetShapeToWorldMatrix() =>
new float4x4(Math.DecomposeRigidBodyTransform(transform.localToWorldMatrix));
// matrix to transform point from object's local transform matrix into shape space
internal float4x4 GetLocalToShapeMatrix() =>
math.mul(math.inverse(GetShapeToWorldMatrix()), transform.localToWorldMatrix);
internal unsafe void BakePoints(NativeArray<float3> points)
{
var localToShapeQuantized = GetLocalToShapeMatrix();
using (var aabb = new NativeArray<Aabb>(1, Allocator.TempJob))
{
new PhysicsShapeExtensions.GetAabbJob { Points = points, Aabb = aabb }.Run();
HashableShapeInputs.GetQuantizedTransformations(localToShapeQuantized, aabb[0], out localToShapeQuantized);
}
using (var bakedPoints = new NativeArray<float3>(points.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory))
{
new BakePointsJob
{
Points = points,
LocalToShape = localToShapeQuantized,
Output = bakedPoints
}.Schedule(points.Length, 16).Complete();
UnsafeUtility.MemCpy(points.GetUnsafePtr(), bakedPoints.GetUnsafePtr(), points.Length * UnsafeUtility.SizeOf<float3>());
}
}
[BurstCompile]
struct BakePointsJob : IJobParallelFor
{
[Collections.ReadOnly]
public NativeArray<float3> Points;
public float4x4 LocalToShape;
public NativeArray<float3> Output;
public void Execute(int index) => Output[index] = math.mul(LocalToShape, new float4(Points[index], 1f)).xyz;
}
/// <summary>
/// Fit this shape to render geometry in its GameObject hierarchy. Children in the hierarchy will
/// influence the result if they have enabled MeshRenderer components or have vertices bound to
/// them on a SkinnedMeshRenderer. Children will only count as influences if this shape is the
/// first ancestor shape in their hierarchy. As such, you should add shape components to all
/// GameObjects that should have them before you call this method on any of them.
/// </summary>
///
/// <exception cref="UnimplementedShapeException"> Thrown when an Unimplemented Shape error
/// condition occurs. </exception>
///
/// <param name="minimumSkinnedVertexWeight"> (Optional)
/// The minimum total weight that a vertex in a skinned mesh must have assigned to this object
/// and/or any of its influencing children. </param>
public void FitToEnabledRenderMeshes(float minimumSkinnedVertexWeight = 0f)
{
var shapeType = m_ShapeType;
m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
using (var points = new NativeList<float3>(65535, Allocator.Persistent))
{
// temporarily un-assign custom mesh and assume this shape is a convex hull
var customMesh = m_CustomMesh;
m_CustomMesh = null;
GetConvexHullProperties(points, Application.isPlaying, default, default, default, default);
m_CustomMesh = customMesh;
if (points.Length == 0)
return;
// TODO: find best rotation, particularly if any points came from skinned mesh
var orientation = quaternion.identity;
var bounds = new Bounds(points[0], float3.zero);
for (int i = 1, count = points.Length; i < count; ++i)
bounds.Encapsulate(points[i]);
SetBox(
new BoxGeometry
{
Center = bounds.center,
Size = bounds.size,
Orientation = orientation,
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius
}
);
}
switch (shapeType)
{
case ShapeType.Capsule:
SetCapsule(GetCapsuleProperties());
break;
case ShapeType.Cylinder:
SetCylinder(GetCylinderProperties(out var orientation), orientation);
break;
case ShapeType.Sphere:
SetSphere(GetSphereProperties(out orientation), orientation);
break;
case ShapeType.Plane:
// force recalculation of plane orientation by making it think shape type is out of date
m_ShapeType = ShapeType.Box;
GetPlaneProperties(out var center, out var size2D, out orientation);
SetPlane(center, size2D, orientation);
break;
case ShapeType.Box:
case ShapeType.ConvexHull:
case ShapeType.Mesh:
m_ShapeType = shapeType;
break;
default:
throw new UnimplementedShapeException(shapeType);
}
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
[Conditional("UNITY_EDITOR")]
void Reset()
{
#if UNITY_EDITOR
InitializeConvexHullGenerationParameters();
FitToEnabledRenderMeshes(m_MinimumSkinnedVertexWeight);
// TODO: also pick best primitive shape
UnityEditor.SceneView.RepaintAll();
#endif
}
public void InitializeConvexHullGenerationParameters()
{
var pointCloud = new NativeList<float3>(65535, Allocator.Temp);
GetConvexHullProperties(pointCloud, false, default, default, default, default);
m_ConvexHullGenerationParameters.InitializeToRecommendedAuthoringValues(pointCloud.AsArray());
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/BakingSystems/PhysicsBodyBakingSystem.cs
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics.GraphicsIntegration;
using UnityEngine;
using LegacyRigidbody = UnityEngine.Rigidbody;
using LegacyCollider = UnityEngine.Collider;
namespace Unity.Physics.Authoring
{
[TemporaryBakingType]
public struct PhysicsBodyAuthoringData : IComponentData
{
public bool IsDynamic;
public float Mass;
public bool OverrideDefaultMassDistribution;
public MassDistribution CustomMassDistribution;
}
class PhysicsBodyAuthoringBaker : BasePhysicsBaker<PhysicsBodyAuthoring>
{
internal List<UnityEngine.Collider> colliderComponents = new List<UnityEngine.Collider>();
internal List<PhysicsShapeAuthoring> physicsShapeComponents = new List<PhysicsShapeAuthoring>();
public override void Bake(PhysicsBodyAuthoring authoring)
{
// Priority is to Legacy Components. Ignore if baked by Legacy.
if (GetComponent<LegacyRigidbody>() || GetComponent<LegacyCollider>())
{
return;
}
var entity = GetEntity(TransformUsageFlags.Dynamic);
// To process later in the Baking System
AddComponent(entity, new PhysicsBodyAuthoringData
{
IsDynamic = (authoring.MotionType == BodyMotionType.Dynamic),
Mass = authoring.Mass,
OverrideDefaultMassDistribution = authoring.OverrideDefaultMassDistribution,
CustomMassDistribution = authoring.CustomMassDistribution
});
AddSharedComponent(entity, new PhysicsWorldIndex(authoring.WorldIndex));
var bodyTransform = GetComponent<Transform>();
var motionType = authoring.MotionType;
var hasSmoothing = authoring.Smoothing != BodySmoothing.None;
PostProcessTransform(bodyTransform, motionType);
var customTags = authoring.CustomTags;
if (!customTags.Equals(CustomPhysicsBodyTags.Nothing))
AddComponent(entity, new PhysicsCustomTags { Value = customTags.Value });
// Check that there is at least one collider in the hierarchy to add these three
GetComponentsInChildren(colliderComponents);
GetComponentsInChildren(physicsShapeComponents);
if (colliderComponents.Count > 0 || physicsShapeComponents.Count > 0)
{
AddComponent(entity, new PhysicsCompoundData()
{
AssociateBlobToBody = false,
ConvertedBodyInstanceID = authoring.GetInstanceID(),
Hash = default,
});
AddComponent<PhysicsRootBaked>(entity);
AddComponent<PhysicsCollider>(entity);
AddBuffer<PhysicsColliderKeyEntityPair>(entity);
}
if (authoring.MotionType == BodyMotionType.Static || IsStatic())
return;
var massProperties = MassProperties.UnitSphere;
AddComponent(entity, authoring.MotionType == BodyMotionType.Dynamic ?
PhysicsMass.CreateDynamic(massProperties, authoring.Mass) :
PhysicsMass.CreateKinematic(massProperties));
var physicsVelocity = new PhysicsVelocity
{
Linear = authoring.InitialLinearVelocity,
Angular = authoring.InitialAngularVelocity
};
AddComponent(entity, physicsVelocity);
if (authoring.MotionType == BodyMotionType.Dynamic)
{
// TODO make these optional in editor?
AddComponent(entity, new PhysicsDamping
{
Linear = authoring.LinearDamping,
Angular = authoring.AngularDamping
});
if (authoring.GravityFactor != 1)
{
AddComponent(entity, new PhysicsGravityFactor
{
Value = authoring.GravityFactor
});
}
}
else if (authoring.MotionType == BodyMotionType.Kinematic)
{
AddComponent(entity, new PhysicsGravityFactor
{
Value = 0
});
}
if (hasSmoothing)
{
AddComponent(entity, new PhysicsGraphicalSmoothing());
if (authoring.Smoothing == BodySmoothing.Interpolation)
{
AddComponent(entity, new PhysicsGraphicalInterpolationBuffer
{
PreviousTransform = Math.DecomposeRigidBodyTransform(bodyTransform.localToWorldMatrix),
PreviousVelocity = physicsVelocity,
});
}
}
}
}
[RequireMatchingQueriesForUpdate]
[UpdateAfter(typeof(EndColliderBakingSystem))]
[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
public partial class PhysicsBodyBakingSystem : SystemBase
{
protected override void OnUpdate()
{
// Fill in the MassProperties based on the potential calculated value by BuildCompoundColliderBakingSystem
foreach (var(physicsMass, bodyData, collider) in
SystemAPI.Query<RefRW<PhysicsMass>, RefRO<PhysicsBodyAuthoringData>, RefRO<PhysicsCollider>>().WithOptions(EntityQueryOptions.IncludePrefab | EntityQueryOptions
{
// Build mass component
var massProperties = collider.ValueRO.MassProperties;
if (bodyData.ValueRO.OverrideDefaultMassDistribution)
{
massProperties.MassDistribution = bodyData.ValueRO.CustomMassDistribution;
// Increase the angular expansion factor to account for the shift in center of mass
massProperties.AngularExpansionFactor += math.length(massProperties.MassDistribution.Transform.pos - bodyData.ValueRO.CustomMassDistribution.Transform.pos);
}
physicsMass.ValueRW = bodyData.ValueRO.IsDynamic ?
PhysicsMass.CreateDynamic(massProperties, bodyData.ValueRO.Mass) :
PhysicsMass.CreateKinematic(massProperties);
}
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/BakingSystems/PhysicsShapeBakingSystem.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Profiling;
using LegacyCollider = UnityEngine.Collider;
using UnityMesh = UnityEngine.Mesh;
namespace Unity.Physics.Authoring
{
class PhysicsShapeBaker : BaseColliderBaker<PhysicsShapeAuthoring>
{
public static List<PhysicsShapeAuthoring> physicsShapeComponents = new List<PhysicsShapeAuthoring>();
public static List<LegacyCollider> legacyColliderComponents = new List<LegacyCollider>();
bool ShouldConvertShape(PhysicsShapeAuthoring authoring)
{
return authoring.enabled;
}
private GameObject GetPrimaryBody(GameObject shape, out bool hasBodyComponent, out bool isStaticBody)
{
var pb = FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
var rb = FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
hasBodyComponent = (pb != null || rb != null);
isStaticBody = false;
if (pb != null)
{
return rb == null ? pb.gameObject :
pb.transform.IsChildOf(rb.transform) ? pb.gameObject : rb.gameObject;
}
if (rb != null)
return rb.gameObject;
// for implicit static shape, first see if it is part of static optimized hierarchy
isStaticBody = FindTopmostStaticEnabledAncestor(shape, out var topStatic);
if (topStatic != null)
return topStatic;
// otherwise, find topmost enabled Collider or PhysicsShapeAuthoring
var topCollider = FindTopmostEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_CollidersBuffer);
var topShape = FindTopmostEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_ShapesBuffer);
return topCollider == null
? topShape == null ? shape.gameObject : topShape
: topShape == null
? topCollider
: topShape.transform.IsChildOf(topCollider.transform)
? topCollider
: topShape;
}
ShapeComputationDataBaking GetInputDataFromAuthoringComponent(PhysicsShapeAuthoring shape, Entity colliderEntity)
{
GameObject shapeGameObject = shape.gameObject;
var body = GetPrimaryBody(shapeGameObject, out bool hasBodyComponent, out bool isStaticBody);
var child = shapeGameObject;
var shapeInstanceID = shape.GetInstanceID();
var bodyEntity = GetEntity(body, TransformUsageFlags.Dynamic);
// prepare the static root
if (isStaticBody)
{
var staticRootMarker = CreateAdditionalEntity(TransformUsageFlags.Dynamic, true, "StaticRootBakeMarker");
AddComponent(staticRootMarker, new BakeStaticRoot() { Body = bodyEntity, ConvertedBodyInstanceID = body.transform.GetInstanceID() });
}
// Track dependencies to the transforms
Transform shapeTransform = GetComponent<Transform>(shape);
Transform bodyTransform = GetComponent<Transform>(body);
var instance = new ColliderInstanceBaking
{
AuthoringComponentId = shapeInstanceID,
BodyEntity = bodyEntity,
ShapeEntity = GetEntity(shapeGameObject, TransformUsageFlags.Dynamic),
ChildEntity = GetEntity(child, TransformUsageFlags.Dynamic),
BodyFromShape = ColliderInstanceBaking.GetCompoundFromChild(shapeTransform, bodyTransform),
};
var data = GenerateComputationData(shape, instance, colliderEntity);
data.Instance.ConvertedAuthoringInstanceID = shapeInstanceID;
data.Instance.ConvertedBodyInstanceID = bodyTransform.GetInstanceID();
var rb = FindFirstEnabledAncestor(shapeGameObject, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
var pb = FindFirstEnabledAncestor(shapeGameObject, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
// The LegacyRigidBody cannot know about the ShapeAuthoringComponent. We need to take responsibility of baking the collider.
if (rb || (!rb && !pb) && body == shapeGameObject)
{
GetComponents(physicsShapeComponents);
GetComponents(legacyColliderComponents);
// We need to check that there are no other colliders in the same object, if so, only the first one should do this, otherwise there will be 2 bakers adding this to the enti
// This will be needed to trigger BuildCompoundColliderBakingSystem
// If they are legacy Colliders and PhysicsShapeAuthoring in the same object, the PhysicsShapeAuthoring will add this
if (legacyColliderComponents.Count == 0 && physicsShapeComponents.Count > 0 && physicsShapeComponents[0].GetInstanceID() == shapeInstanceID)
{
var entity = GetEntity(TransformUsageFlags.Dynamic);
// // Rigid Body bakes always add the PhysicsWorldIndex component and process transform
if (!hasBodyComponent)
{
AddSharedComponent(entity, new PhysicsWorldIndex());
PostProcessTransform(bodyTransform);
}
AddComponent(entity, new PhysicsCompoundData()
{
AssociateBlobToBody = false,
ConvertedBodyInstanceID = shapeInstanceID,
Hash = default,
});
AddComponent<PhysicsRootBaked>(entity);
AddComponent<PhysicsCollider>(entity);
AddBuffer<PhysicsColliderKeyEntityPair>(entity);
}
}
return data;
}
Material ProduceMaterial(PhysicsShapeAuthoring shape)
{
var materialTemplate = shape.MaterialTemplate;
if (materialTemplate != null)
DependsOn(materialTemplate);
return shape.GetMaterial();
}
CollisionFilter ProduceCollisionFilter(PhysicsShapeAuthoring shape)
{
return shape.GetFilter();
}
UnityEngine.Mesh GetMesh(PhysicsShapeAuthoring shape, out float4x4 childToShape)
{
var mesh = shape.CustomMesh;
childToShape = float4x4.identity;
if (mesh == null)
{
// Try to get a mesh in the children
var filter = GetComponentInChildren<MeshFilter>();
if (filter != null && filter.sharedMesh != null)
{
mesh = filter.sharedMesh;
var childTransform = GetComponent<Transform>(filter);
childToShape = math.mul(shape.transform.worldToLocalMatrix, childTransform.localToWorldMatrix);;
}
}
if (mesh == null)
{
throw new InvalidOperationException(
$"No {nameof(PhysicsShapeAuthoring.CustomMesh)} assigned on {shape.name}."
);
}
DependsOn(mesh);
return mesh;
}
namespace Unity.Physics.Authoring
{
[BakingType]
public struct JointEntityBaking : IComponentData
{
public Entity Entity;
}
public class BallAndSocketJoint : BaseJoint
{
// Editor only settings
[HideInInspector]
public bool EditPivots;
[Tooltip("If checked, PositionLocal will snap to match PositionInConnectedEntity")]
public bool AutoSetConnected = true;
namespace Unity.Physics.Authoring
{
public abstract class BaseJoint : BaseBodyPairConnector
{
public bool EnableCollision;
public float3 MaxImpulse = float.PositiveInfinity;
void OnEnable()
{
// included so tick box appears in Editor
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Joints/FreeHingeJoint.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
public class FreeHingeJoint : BallAndSocketJoint
{
// Editor only settings
[HideInInspector]
public bool EditAxes;
public float3 HingeAxisLocal;
public float3 HingeAxisInConnectedEntity;
public override void UpdateAuto()
{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
HingeAxisInConnectedEntity = math.mul(bFromA.rot, HingeAxisLocal);
}
}
}
class FreeHingeJointBaker : JointBaker<FreeHingeJoint>
{
public override void Bake(FreeHingeJoint authoring)
{
authoring.UpdateAuto();
Math.CalculatePerpendicularNormalized(authoring.HingeAxisLocal, out var perpendicularLocal, out _);
Math.CalculatePerpendicularNormalized(authoring.HingeAxisInConnectedEntity, out var perpendicularConnected, out _);
var physicsJoint = PhysicsJoint.CreateHinge(
new BodyFrame {Axis = authoring.HingeAxisLocal, Position = authoring.PositionLocal, PerpendicularAxis = perpendicularLocal},
new BodyFrame {Axis = authoring.HingeAxisInConnectedEntity, Position = authoring.PositionInConnectedEntity, PerpendicularAxis = perpendicularConnected }
);
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntity(worldIndex, constraintBodyPair, physicsJoint);
}
}
}
StressTest/Assets/Scripts/Custom Physics Authoring/Unity.Physics.Custom/Joints/LimitDOFJoint.cs
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
// This Joint allows you to lock one or more of the 6 degrees of freedom of a constrained body.
// This is achieved by combining the appropriate lower level 'constraint atoms' to form the higher level Joint.
// In this case Linear and Angular constraint atoms are combined.
// One use-case for this Joint could be to restrict a 3d simulation to a 2d plane.
public class LimitDOFJoint : BaseJoint
{
public bool3 LockLinearAxes;
public bool3 LockAngularAxes;
public PhysicsJoint CreateLimitDOFJoint(RigidTransform offset)
{
var constraints = new FixedList512Bytes<Constraint>();
if (math.any(LockLinearAxes))
{
constraints.Add(new Constraint
{
ConstrainedAxes = LockLinearAxes,
Type = ConstraintType.Linear,
Min = 0,
Max = 0,
SpringFrequency = Constraint.DefaultSpringFrequency,
SpringDamping = Constraint.DefaultSpringDamping,
MaxImpulse = MaxImpulse,
});
}
if (math.any(LockAngularAxes))
{
constraints.Add(new Constraint
{
ConstrainedAxes = LockAngularAxes,
Type = ConstraintType.Angular,
Min = 0,
Max = 0,
SpringFrequency = Constraint.DefaultSpringFrequency,
SpringDamping = Constraint.DefaultSpringDamping,
MaxImpulse = MaxImpulse,
});
}
var joint = new PhysicsJoint
{
BodyAFromJoint = BodyFrame.Identity,
BodyBFromJoint = offset
};
joint.SetConstraints(constraints);
return joint;
}
}
class LimitDOFJointBaker : Baker<LimitDOFJoint>
{
public Entity CreateJointEntity(uint worldIndex, PhysicsConstrainedBodyPair constrainedBodyPair, PhysicsJoint joint)
{
using (var joints = new NativeArray<PhysicsJoint>(1, Allocator.Temp) { [0] = joint })
using (var jointEntities = new NativeList<Entity>(1, Allocator.Temp))
{
CreateJointEntities(worldIndex, constrainedBodyPair, joints, jointEntities);
return jointEntities[0];
}
}
public void CreateJointEntities(uint worldIndex, PhysicsConstrainedBodyPair constrainedBodyPair, NativeArray<PhysicsJoint> joints, NativeList<Entity> newJointEntities = default
{
if (!joints.IsCreated || joints.Length == 0)
return;
if (newJointEntities.IsCreated)
newJointEntities.Clear();
else
newJointEntities = new NativeList<Entity>(joints.Length, Allocator.Temp);
// create all new joints
var multipleJoints = joints.Length > 1;
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
namespace Unity.Physics.Authoring
{
// stores an initial value and a pair of scalar curves to apply to relevant constraints on the joint
struct ModifyJointLimits : ISharedComponentData, IEquatable<ModifyJointLimits>
{
public PhysicsJoint InitialValue;
public ParticleSystem.MinMaxCurve AngularRangeScalar;
public ParticleSystem.MinMaxCurve LinearRangeScalar;
public bool Equals(ModifyJointLimits other) =>
AngularRangeScalar.Equals(other.AngularRangeScalar) && LinearRangeScalar.Equals(other.LinearRangeScalar);
public override bool Equals(object obj) => obj is ModifyJointLimits other && Equals(other);
_ModifyJointLimitsBakingDataQuery.AddChangedVersionFilter(typeof(ModifyJointLimitsBakingData));
_JointEntityBakingQuery.AddChangedVersionFilter(typeof(JointEntityBaking));
}
public void OnUpdate(ref SystemState state)
{
if (_ModifyJointLimitsBakingDataQuery.IsEmpty && _JointEntityBakingQuery.IsEmpty)
{
return;
}
// Collect all the joints
NativeParallelMultiHashMap<Entity, (Entity, PhysicsJoint)> jointsLookUp =
new NativeParallelMultiHashMap<Entity, (Entity, PhysicsJoint)>(10, Allocator.TempJob);
foreach (var(jointEntity, physicsJoint, entity) in SystemAPI
.Query<RefRO<JointEntityBaking>, RefRO<PhysicsJoint>>().WithEntityAccess()
.WithOptions(EntityQueryOptions.IncludeDisabledEntities | EntityQueryOptions.IncludePrefab))
{
jointsLookUp.Add(jointEntity.ValueRO.Entity, (entity, physicsJoint.ValueRO));
}
foreach (var(modifyJointLimits, entity) in SystemAPI.Query<ModifyJointLimitsBakingData>()
.WithEntityAccess().WithOptions(EntityQueryOptions.IncludeDisabledEntities |
EntityQueryOptions.IncludePrefab))
{
var angularModification = new ParticleSystem.MinMaxCurve(
multiplier: math.radians(modifyJointLimits.AngularRangeScalar.curveMultiplier),
min: modifyJointLimits.AngularRangeScalar.curveMin,
max: modifyJointLimits.AngularRangeScalar.curveMax
);
foreach (var joint in jointsLookUp.GetValuesForKey(entity))
{
state.EntityManager.SetSharedComponentManaged(joint.Item1, new ModifyJointLimits
{
InitialValue = joint.Item2,
AngularRangeScalar = angularModification,
LinearRangeScalar = modifyJointLimits.LinearRangeScalar
});
}
}
jointsLookUp.Dispose();
}
}
// apply an animated effect to the limits on supported types of joints
[RequireMatchingQueriesForUpdate]
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
[UpdateAfter(typeof(PhysicsSystemGroup))]
partial struct ModifyJointLimitsSystem : ISystem
{
public void OnUpdate(ref SystemState state)
{
var time = (float)SystemAPI.Time.ElapsedTime;
foreach (var(joint, modification) in SystemAPI.Query<RefRW<PhysicsJoint>, ModifyJointLimits>())
{
var animatedAngularScalar = new FloatRange(
modification.AngularRangeScalar.curveMin.Evaluate(time),
modification.AngularRangeScalar.curveMax.Evaluate(time)
);
var animatedLinearScalar = new FloatRange(
modification.LinearRangeScalar.curveMin.Evaluate(time),
modification.LinearRangeScalar.curveMax.Evaluate(time)
);
// in each case, get relevant properties from the initial value based on joint type, and apply scalar
switch (joint.ValueRW.JointType)
{
// Custom type could be anything, so this demo just applies changes to all constraints
case JointType.Custom:
var constraints = modification.InitialValue.GetConstraints();
for (var i = 0; i < constraints.Length; i++)
{
var constraint = constraints[i];
var isAngular = constraint.Type == ConstraintType.Angular;
var scalar = math.select(animatedLinearScalar, animatedAngularScalar, isAngular);
var constraintRange = (FloatRange)(new float2(constraint.Min, constraint.Max) * scalar);
constraint.Min = constraintRange.Min;
constraint.Max = constraintRange.Max;
constraints[i] = constraint;
}
joint.ValueRW.SetConstraints(constraints);
break;
// other types have corresponding getters/setters to retrieve more meaningful data
case JointType.LimitedDistance:
var distanceRange = modification.InitialValue.GetLimitedDistanceRange();
joint.ValueRW.SetLimitedDistanceRange(distanceRange * (float2)animatedLinearScalar);
break;
case JointType.LimitedHinge:
var angularRange = modification.InitialValue.GetLimitedHingeRange();
joint.ValueRW.SetLimitedHingeRange(angularRange * (float2)animatedAngularScalar);
break;
case JointType.Prismatic:
var distanceOnAxis = modification.InitialValue.GetPrismaticRange();
joint.ValueRW.SetPrismaticRange(distanceOnAxis * (float2)animatedLinearScalar);
break;
// ragdoll joints are composed of two separate joints with different meanings
case JointType.RagdollPrimaryCone:
modification.InitialValue.GetRagdollPrimaryConeAndTwistRange(
out var maxConeAngle,
out var angularTwistRange
);
joint.ValueRW.SetRagdollPrimaryConeAndTwistRange(
maxConeAngle * animatedAngularScalar.Max,
angularTwistRange * (float2)animatedAngularScalar
);
break;
case JointType.RagdollPerpendicularCone:
var angularPlaneRange = modification.InitialValue.GetRagdollPerpendicularConeRange();
joint.ValueRW.SetRagdollPerpendicularConeRange(angularPlaneRange *
(float2)animatedAngularScalar);
break;
// remaining types have no limits on their Constraint atoms to meaningfully modify
case JointType.BallAndSocket:
case JointType.Fixed:
case JointType.Hinge:
break;
}
}
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Joints/PrismaticJoint.cs
using Unity.Entities;
using Unity.Mathematics;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class PrismaticJoint : BallAndSocketJoint
{
public float3 AxisLocal;
public float3 AxisInConnectedEntity;
public float3 PerpendicularAxisLocal;
public float3 PerpendicularAxisInConnectedEntity;
public float MinDistanceOnAxis;
public float MaxDistanceOnAxis;
[SerializeField]
int m_Version;
public float3 TwistAxisLocal;
public float3 TwistAxisInConnectedEntity;
public float3 PerpendicularAxisLocal;
public float3 PerpendicularAxisInConnectedEntity;
public float MaxConeAngle;
public float MinPerpendicularAngle;
public float MaxPerpendicularAngle;
public float MinTwistAngle;
public float MaxTwistAngle;
internal void UpgradeVersionIfNecessary()
{
if (m_Version >= k_LatestVersion)
return;
MinPerpendicularAngle -= 90f;
MaxPerpendicularAngle -= 90f;
m_Version = k_LatestVersion;
}
void OnValidate()
{
UpgradeVersionIfNecessary();
MaxConeAngle = math.clamp(MaxConeAngle, 0f, 180f);
MaxPerpendicularAngle = math.clamp(MaxPerpendicularAngle, -90f, 90f);
MinPerpendicularAngle = math.clamp(MinPerpendicularAngle, -90f, 90f);
if (MaxPerpendicularAngle < MinPerpendicularAngle)
{
var swap = new FloatRange(MinPerpendicularAngle, MaxPerpendicularAngle).Sorted();
MinPerpendicularAngle = swap.Min;
MaxPerpendicularAngle = swap.Max;
}
MinTwistAngle = math.clamp(MinTwistAngle, -180f, 180f);
MaxTwistAngle = math.clamp(MaxTwistAngle, -180f, 180f);
if (MaxTwistAngle < MinTwistAngle)
{
var swap = new FloatRange(MinTwistAngle, MaxTwistAngle).Sorted();
MinTwistAngle = swap.Min;
MaxTwistAngle = swap.Max;
}
}
public override void UpdateAuto()
{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
TwistAxisInConnectedEntity = math.mul(bFromA.rot, TwistAxisLocal);
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
}
}
}
class RagdollJointBaker : JointBaker<RagdollJoint>
{
public override void Bake(RagdollJoint authoring)
{
authoring.UpdateAuto();
authoring.UpgradeVersionIfNecessary();
PhysicsJoint.CreateRagdoll(
new BodyFrame { Axis = authoring.TwistAxisLocal, PerpendicularAxis = authoring.PerpendicularAxisLocal, Position = authoring.PositionLocal },
new BodyFrame { Axis = authoring.TwistAxisInConnectedEntity, PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity, Position = authoring.PositionInConnectedEntit
math.radians(authoring.MaxConeAngle),
math.radians(new FloatRange(authoring.MinPerpendicularAngle, authoring.MaxPerpendicularAngle)),
math.radians(new FloatRange(authoring.MinTwistAngle, authoring.MaxTwistAngle)),
out var primaryCone,
out var perpendicularCone
);
primaryCone.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
perpendicularCone.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
using NativeList<Entity> entities = new NativeList<Entity>(1, Allocator.TempJob);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntities(worldIndex,
constraintBodyPair,
new NativeArray<PhysicsJoint>(2, Allocator.Temp) { [0] = primaryCone, [1] = perpendicularCone }, entities);
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Joints/RigidJoint.cs
using Unity.Entities;
using Unity.Mathematics;
namespace Unity.Physics.Authoring
{
public class RigidJoint : BallAndSocketJoint
{
public quaternion OrientationLocal = quaternion.identity;
public quaternion OrientationInConnectedEntity = quaternion.identity;
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
namespace Unity.Physics.Authoring
{
public class AngularVelocityMotor : BaseJoint
{
[Tooltip("An offset from center of entity with motor. Representing the anchor/pivot point of rotation")]
public float3 PivotPosition;
[Tooltip("The axis of rotation of the motor. Value will be normalized")]
public float3 AxisOfRotation;
[Tooltip("Target speed for the motor to maintain, in degrees/s")]
public float TargetSpeed;
[Tooltip("The magnitude of the maximum impulse the motor can exert in a single step. Applies only to the motor constraint.")]
public float MaxImpulseAppliedByMotor = math.INFINITY;
private float3 PerpendicularAxisLocal;
private float3 PositionInConnectedEntity;
private float3 HingeAxisInConnectedEntity;
private float3 PerpendicularAxisInConnectedEntity;
class AngularVelocityMotorBaker : JointBaker<AngularVelocityMotor>
{
public override void Bake(AngularVelocityMotor authoring)
{
float3 axisInA = math.normalize(authoring.AxisOfRotation);
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.PivotPosition); //position of motored body pivot relative to Connected Entity in world space
authoring.HingeAxisInConnectedEntity = math.mul(bFromA.rot, axisInA); //motor axis in Connected Entity space
// Always calculate the perpendicular axes
Math.CalculatePerpendicularNormalized(axisInA, out var perpendicularLocal, out _);
authoring.PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, perpendicularLocal); //perp motor axis in Connected Entity space
#region Sphere
[BurstCompile]
struct BakeSphereJob : IJob
{
public NativeArray<SphereGeometry> Sphere;
public NativeArray<EulerAngles> Orientation;
// TODO: make members PascalCase after merging static query fixes
public float4x4 localToWorld;
public float4x4 shapeToWorld;
public void Execute()
{
var center = Sphere[0].Center;
var radius = Sphere[0].Radius;
var orientation = Orientation[0];
var basisToWorld = GetBasisToWorldMatrix(localToWorld, center, orientation, 1f);
var basisPriority = basisToWorld.HasShear() ? GetBasisAxisPriority(basisToWorld) : k_DefaultAxisPriority;
var bakeToShape = GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center, ref orientation, 1f, basisPriority);
radius *= math.cmax(bakeToShape.DecomposeScale());
Sphere[0] = new SphereGeometry
{
Center = center,
Radius = radius
};
Orientation[0] = orientation;
}
}
internal static SphereGeometry BakeToBodySpace(
this SphereGeometry sphere, float4x4 localToWorld, float4x4 shapeToWorld, ref EulerAngles orientation
)
{
using (var geometry = new NativeArray<SphereGeometry>(1, Allocator.TempJob) { [0] = sphere })
using (var outOrientation = new NativeArray<EulerAngles>(1, Allocator.TempJob) { [0] = orientation })
{
var job = new BakeSphereJob
{
Sphere = geometry,
Orientation = outOrientation,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld
};
job.Run();
orientation = outOrientation[0];
return geometry[0];
}
}
#endregion
#region Plane
[BurstCompile]
struct BakePlaneJob : IJob
{
public NativeArray<float3x4> Vertices;
// TODO: make members PascalCase after merging static query fixes
public float3 center;
public float2 size;
public EulerAngles orientation;
public float4x4 localToWorld;
public float4x4 shapeToWorld;
#endregion
#region ShapeInputHash
#if !(UNITY_ANDROID && !UNITY_64) // !Android32
// Getting memory alignment errors from HashUtility.Hash128 on Android32
[BurstCompile]
#endif
internal struct GetShapeInputsHashJob : IJob
{
public NativeArray<Hash128> Result;
#region AABB
[BurstCompile]
internal struct GetAabbJob : IJob
{
[ReadOnly] public NativeArray<float3> Points;
public NativeArray<Aabb> Aabb;
public void Execute()
{
var aabb = new Aabb { Min = float.MaxValue, Max = float.MinValue };
for (var i = 0; i < Points.Length; ++i)
aabb.Include(Points[i]);
Aabb[0] = aabb;
}
}
#endregion
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/BakeGeometryJobsExtensions.cs
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
static partial class PhysicsShapeExtensions
{
public static class BakeBoxJobExtension
{
internal static float4x4 GetBakeToShape(PhysicsShapeAuthoring shape, float3 center, EulerAngles orientation)
{
var transform = shape.transform;
var localToWorld = (float4x4)transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
return BakeBoxJob.GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientation);
}
}
public static class BakeCapsuleJobExtension
{
internal static float4x4 GetBakeToShape(PhysicsShapeAuthoring shape, float3 center, EulerAngles orientation)
{
var transform = shape.transform;
var localToWorld = (float4x4)transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
return BakeCapsuleJob.GetBakeToShape(localToWorld, shapeToWorld, ref center,
ref orientation);
}
}
public static void SetBakedCapsuleSize(this PhysicsShapeAuthoring shape, float height, float radius)
{
var capsule = shape.GetCapsuleProperties();
var center = capsule.Center;
namespace Unity.Physics.Authoring
{
/// <summary>
/// A structure for storing authoring data for a capsule shape. In contrast to the
/// CapsuleGeometry struct in the run-time, this structure permits storing stable orientation
/// values, as well as height values that can be retained when the source data are defined with
/// respect to a non-uniformly scaled object.
/// </summary>
[Serializable]
public struct CapsuleGeometryAuthoring : IEquatable<CapsuleGeometryAuthoring>
{
/// <summary>
/// The local orientation of the capsule. It is aligned with the forward axis (z) when it is
/// identity.
/// </summary>
public quaternion Orientation { get => m_OrientationEuler; set => m_OrientationEuler.SetValue(value); }
internal EulerAngles OrientationEuler { get => m_OrientationEuler; set => m_OrientationEuler = value; }
[SerializeField]
EulerAngles m_OrientationEuler;
/// <summary> The local position offset of the capsule. </summary>
public float3 Center { get => m_Center; set => m_Center = value; }
[SerializeField]
float3 m_Center;
/// <summary>
/// The height of the capsule. It may store any value, but will ultimately always be converted
/// into a value that is at least twice the radius.
/// </summary>
public float Height { get => m_Height; set => m_Height = value; }
[SerializeField]
float m_Height;
namespace Unity.Physics.Authoring
{
public static class ConvexHullGenerationParametersExtensions
{
// recommended simplification tolerance is at least 1 centimeter
internal const float k_MinRecommendedSimplificationTolerance = 0.01f;
if (points.Length <= 1)
return;
internal static void OnValidate(ref this ConvexHullGenerationParameters generationParameters, float maxAngle = 180f)
{
generationParameters.SimplificationTolerance = math.max(0f, generationParameters.SimplificationTolerance);
generationParameters.BevelRadius = math.max(0f, generationParameters.BevelRadius);
generationParameters.MinimumAngle = math.clamp(generationParameters.MinimumAngle, 0f, maxAngle);
}
namespace Unity.Physics.Authoring
{
[Serializable]
internal struct EulerAngles : IEquatable<EulerAngles>
{
public static EulerAngles Default => new EulerAngles { RotationOrder = math.RotationOrder.ZXY };
public float3 Value;
[HideInInspector]
public math.RotationOrder RotationOrder;
internal void SetValue(quaternion value) => Value = math.degrees(Math.ToEulerAngles(value, RotationOrder));
if (m_CheckIfComponentBelongsToShape)
{
if (PhysicsShapeExtensions.GetPrimaryBody(child.gameObject) != m_PrimaryBody)
return false;
child.gameObject.GetComponentsInParent(true, s_PhysicsShapes);
if (s_PhysicsShapes[0] != m_Shape)
{
s_PhysicsShapes.Clear();
return false;
}
}
// do not simply use GameObject.activeInHierarchy because it will be false when instantiating a prefab
var t = child.transform;
var activeInHierarchy = t.gameObject.activeSelf;
while (activeInHierarchy && t != m_Root)
{
t = t.parent;
activeInHierarchy &= t.gameObject.activeSelf;
}
return activeInHierarchy;
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/PhysicsShapeExtensions.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
// put static UnityObject buffers in separate utility class so other methods can Burst compile
static class PhysicsShapeExtensions_NonBursted
{
internal static readonly List<PhysicsBodyAuthoring> s_PhysicsBodiesBuffer = new List<PhysicsBodyAuthoring>(16);
internal static readonly List<PhysicsShapeAuthoring> s_ShapesBuffer = new List<PhysicsShapeAuthoring>(16);
internal static readonly List<Rigidbody> s_RigidbodiesBuffer = new List<Rigidbody>(16);
internal static readonly List<UnityEngine.Collider> s_CollidersBuffer = new List<UnityEngine.Collider>(16);
}
public static partial class PhysicsShapeExtensions
{
// used for de-skewing basis vectors; default priority assumes primary axis is z, secondary axis is y
public static readonly int3 k_DefaultAxisPriority = new int3(2, 1, 0);
// matrix to transform point from shape's local basis into world space
public static float4x4 GetBasisToWorldMatrix(
float4x4 localToWorld, float3 center, quaternion orientation, float3 size
) =>
math.mul(localToWorld, float4x4.TRS(center, orientation, size));
static float4 DeskewSecondaryAxis(float4 primaryAxis, float4 secondaryAxis)
{
var n0 = math.normalizesafe(primaryAxis);
var dot = math.dot(secondaryAxis, n0);
return secondaryAxis - n0 * dot;
}
// priority is determined by length of each size dimension in the shape's basis after applying localToWorld transformation
public static int3 GetBasisAxisPriority(float4x4 basisToWorld)
{
var basisAxisLengths = basisToWorld.DecomposeScale();
var max = math.cmax(basisAxisLengths);
var min = math.cmin(basisAxisLengths);
if (max == min)
return k_DefaultAxisPriority;
basisAxisLengths = basisToWorld.DecomposeScale();
min = math.cmin(basisAxisLengths);
var imin = min == basisAxisLengths.x ? 0 : min == basisAxisLengths.y ? 1 : 2;
if (imin == imax)
imin = k_NextAxis[imax];
var imid = k_NextAxis[imax] == imin ? k_PrevAxis[imax] : k_NextAxis[imax];
[Conditional(CompilationSymbols.CollectionsChecksSymbol), Conditional(CompilationSymbols.DebugChecksSymbol)]
static void CheckBasisPriorityAndThrow(int3 basisPriority)
{
if (
basisPriority.x == basisPriority.y
|| basisPriority.x == basisPriority.z
|| basisPriority.y == basisPriority.z
)
throw new ArgumentException(nameof(basisPriority));
}
public static bool HasNonUniformScale(this float4x4 m)
{
var s = new float3(math.lengthsq(m.c0.xyz), math.lengthsq(m.c1.xyz), math.lengthsq(m.c2.xyz));
return math.cmin(s) != math.cmax(s);
}
// matrix to transform point on a primitive from bake space into space of the shape
internal static float4x4 GetPrimitiveBakeToShapeMatrix(
float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center, ref EulerAngles orientation, float3 scale, int3 basisPriority
)
{
CheckBasisPriorityAndThrow(basisPriority);
var localToBasis = float4x4.TRS(center, orientation, scale);
// correct for imprecision in cases of no scale to prevent e.g., convex radius from being altered
if (scale.Equals(new float3(1f)))
{
localToBasis.c0 = math.normalizesafe(localToBasis.c0);
localToBasis.c1 = math.normalizesafe(localToBasis.c1);
localToBasis.c2 = math.normalizesafe(localToBasis.c2);
}
var localToBake = math.mul(localToWorld, localToBasis);
if (localToBake.HasNonUniformScale() || localToBake.HasShear())
{
// deskew second longest axis with respect to longest axis
localToBake[basisPriority[1]] =
DeskewSecondaryAxis(localToBake[basisPriority[0]], localToBake[basisPriority[1]]);
return bakeToShape;
}
public static void SetBakedCylinderSize(this PhysicsShapeAuthoring shape, float height, float radius, float bevelRadius)
{
var cylinder = shape.GetCylinderProperties(out EulerAngles orientation);
var center = cylinder.Center;
var bakeToShape = BakeCylinderJobExtension.GetBakeToShape(shape, center, orientation);
var scale = bakeToShape.DecomposeScale();
namespace Unity.Physics.Authoring
{
sealed class EnumFlagsAttribute : PropertyAttribute {}
sealed class ExpandChildrenAttribute : PropertyAttribute {}
sealed class SoftRangeAttribute : PropertyAttribute
{
public readonly float SliderMin;
public readonly float SliderMax;
public float TextFieldMin { get; set; }
public float TextFieldMax { get; set; }
[assembly: InternalsVisibleTo("Unity.Physics.Custom.EditModeTests")]
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/BallAndSocketJointEditor.cs
#if UNITY_EDITOR
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(BallAndSocketJoint))]
public class BallAndSocketEditor : UnityEditor.Editor
{
protected virtual void OnSceneGUI()
{
BallAndSocketJoint ballAndSocket = (BallAndSocketJoint)target;
EditorGUI.BeginChangeCheck();
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(CustomPhysicsMaterialTagNames))]
[CanEditMultipleObjects]
class CustomPhysicsMaterialTagNamesEditor : BaseEditor
{
#pragma warning disable 649
[AutoPopulate(ElementFormatString = "Custom Physics Material Tag {0}", Resizable = false, Reorderable = false)]
ReorderableList m_TagNames;
#pragma warning restore 649
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/EditorUtilities.cs
using UnityEngine;
using Unity.Mathematics;
using static Unity.Physics.Math;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.IMGUI.Controls;
namespace Unity.Physics.Editor
{
/// <summary>
/// Provides utilities that use Handles to set positions and axes,
/// </summary>
public class EditorUtilities
{
// Editor for a joint pivot or pivot pair
public static void EditPivot(RigidTransform worldFromA, RigidTransform worldFromB, bool lockBtoA,
ref float3 pivotA, ref float3 pivotB, Object target)
{
EditorGUI.BeginChangeCheck();
float3 pivotAinW = Handles.PositionHandle(math.transform(worldFromA, pivotA), quaternion.identity);
float3 pivotBinW;
if (lockBtoA)
{
pivotBinW = pivotAinW;
pivotB = math.transform(math.inverse(worldFromB), pivotBinW);
}
else
{
pivotBinW = Handles.PositionHandle(math.transform(worldFromB, pivotB), quaternion.identity);
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Edit joint pivot");
pivotA = math.transform(math.inverse(worldFromA), pivotAinW);
pivotB = math.transform(math.inverse(worldFromB), pivotBinW);
}
Handles.DrawLine(worldFromA.pos, pivotAinW);
Handles.DrawLine(worldFromB.pos, pivotBinW);
}
// Editor for a joint axis or axis pair
public class AxisEditor
{
// Even though we're only editing axes and not rotations, we need to track a full rotation in order to keep the rotation handle stable
private quaternion m_RefA = quaternion.identity;
private quaternion m_RefB = quaternion.identity;
// Detect changes in the object being edited to reset the reference orientations
private Object m_LastTarget;
private static bool NormalizeSafe(ref float3 x)
{
float lengthSq = math.lengthsq(x);
const float epsSq = 1e-8f;
if (math.abs(lengthSq - 1) > epsSq)
{
if (lengthSq > epsSq)
{
x *= math.rsqrt(lengthSq);
}
else
{
x = new float3(1, 0, 0);
}
return true;
}
return false;
}
private static bool NormalizePerpendicular(float3 axis, ref float3 perpendicular)
{
// make sure perpendicular is actually perpendicular to direction
float dot = math.dot(axis, perpendicular);
float absDot = math.abs(dot);
if (absDot > 1.0f - 1e-5f)
{
// parallel, choose an arbitrary perpendicular
float3 dummy;
CalculatePerpendicularNormalized(axis, out perpendicular, out dummy);
return true;
}
if (absDot > 1e-5f)
{
// reject direction
perpendicular -= dot * axis;
NormalizeSafe(ref perpendicular);
return true;
}
return NormalizeSafe(ref perpendicular);
}
public void Update(RigidTransform worldFromA, RigidTransform worldFromB, bool lockBtoA, float3 pivotA, float3 pivotB,
ref float3 directionA, ref float3 directionB, ref float3 perpendicularA, ref float3 perpendicularB, Object target)
{
// Work in world space
float3 directionAinW = math.rotate(worldFromA, directionA);
float3 directionBinW = math.rotate(worldFromB, directionB);
float3 perpendicularAinW = math.rotate(worldFromB, perpendicularA);
float3 perpendicularBinW = math.rotate(worldFromB, perpendicularB);
bool changed = false;
// If the target changed, fix up the inputs and reset the reference orientations to align with the new target's axes
if (target != m_LastTarget)
{
m_LastTarget = target;
// Enforce normalized directions
changed |= NormalizeSafe(ref directionAinW);
changed |= NormalizeSafe(ref directionBinW);
// Enforce normalized perpendiculars, orthogonal to their respective directions
changed |= NormalizePerpendicular(directionAinW, ref perpendicularAinW);
changed |= NormalizePerpendicular(directionBinW, ref perpendicularBinW);
// Calculate the rotation of the joint in A from direction and perpendicular
float3x3 rotationA = new float3x3(directionAinW, perpendicularAinW, math.cross(directionAinW, perpendicularAinW));
m_RefA = new quaternion(rotationA);
if (lockBtoA)
{
m_RefB = m_RefA;
}
else
{
// Calculate the rotation of the joint in B from direction and perpendicular
float3x3 rotationB = new float3x3(directionBinW, perpendicularBinW, math.cross(directionBinW, perpendicularBinW));
m_RefB = new quaternion(rotationB);
}
}
EditorGUI.BeginChangeCheck();
// Make rotators
quaternion oldRefA = m_RefA;
quaternion oldRefB = m_RefB;
float3 pivotAinW = math.transform(worldFromA, pivotA);
m_RefA = Handles.RotationHandle(m_RefA, pivotAinW);
float3 pivotBinW;
if (lockBtoA)
{
directionB = math.rotate(math.inverse(worldFromB), directionAinW);
perpendicularB = math.rotate(math.inverse(worldFromB), perpendicularAinW);
pivotBinW = pivotAinW;
m_RefB = m_RefA;
}
else
{
pivotBinW = math.transform(worldFromB, pivotB);
m_RefB = Handles.RotationHandle(m_RefB, pivotBinW);
}
// Apply changes from the rotators
if (EditorGUI.EndChangeCheck())
{
quaternion dqA = math.mul(m_RefA, math.inverse(oldRefA));
quaternion dqB = math.mul(m_RefB, math.inverse(oldRefB));
directionAinW = math.mul(dqA, directionAinW);
directionBinW = math.mul(dqB, directionBinW);
perpendicularAinW = math.mul(dqB, perpendicularAinW);
perpendicularBinW = math.mul(dqB, perpendicularBinW);
changed = true;
}
// Write back if the axes changed
if (changed)
{
Undo.RecordObject(target, "Edit joint axis");
directionA = math.rotate(math.inverse(worldFromA), directionAinW);
directionB = math.rotate(math.inverse(worldFromB), directionBinW);
perpendicularA = math.rotate(math.inverse(worldFromB), perpendicularAinW);
perpendicularB = math.rotate(math.inverse(worldFromB), perpendicularBinW);
}
// Draw the updated axes
float3 z = new float3(0, 0, 1); // ArrowHandleCap() draws an arrow pointing in (0, 0, 1)
Handles.ArrowHandleCap(0, pivotAinW, Quaternion.FromToRotation(z, directionAinW), HandleUtility.GetHandleSize(pivotAinW) * 0.75f, Event.current.type);
Handles.ArrowHandleCap(0, pivotAinW, Quaternion.FromToRotation(z, perpendicularAinW), HandleUtility.GetHandleSize(pivotAinW) * 0.75f, Event.current.type);
if (!lockBtoA)
{
Handles.ArrowHandleCap(0, pivotBinW, Quaternion.FromToRotation(z, directionBinW), HandleUtility.GetHandleSize(pivotBinW) * 0.75f, Event.current.type);
Handles.ArrowHandleCap(0, pivotBinW, Quaternion.FromToRotation(z, perpendicularBinW), HandleUtility.GetHandleSize(pivotBinW) * 0.75f, Event.current.type);
}
}
}
public static void EditLimits(RigidTransform worldFromA, RigidTransform worldFromB, float3 pivotA, float3 axisA, float3 axisB, float3 perpendicularA, float3 perpendicularB
ref float minLimit, ref float maxLimit, JointAngularLimitHandle limitHandle, Object target)
{
// Transform to world space
float3 pivotAinW = math.transform(worldFromA, pivotA);
float3 axisAinW = math.rotate(worldFromA, axisA);
float3 perpendicularAinW = math.rotate(worldFromA, perpendicularA);
float3 axisBinW = math.rotate(worldFromA, axisB);
float3 perpendicularBinW = math.rotate(worldFromB, perpendicularB);
// Get rotations from joint space
// JointAngularLimitHandle uses axis = (1, 0, 0) with angle = 0 at (0, 0, 1), so choose the rotations to point those in the directions of our axis and perpendicular
float3x3 worldFromJointA = new float3x3(axisAinW, -math.cross(axisAinW, perpendicularAinW), perpendicularAinW);
float3x3 worldFromJointB = new float3x3(axisBinW, -math.cross(axisBinW, perpendicularBinW), perpendicularBinW);
float3x3 jointBFromA = math.mul(math.transpose(worldFromJointB), worldFromJointA);
// Set orientation for the angular limit control
float angle = CalculateTwistAngle(new quaternion(jointBFromA), 0); // index = 0 because axis is the first column in worldFromJoint
quaternion limitOrientation = math.mul(quaternion.AxisAngle(axisAinW, angle), new quaternion(worldFromJointA));
Matrix4x4 handleMatrix = Matrix4x4.TRS(pivotAinW, limitOrientation, Vector3.one);
float size = HandleUtility.GetHandleSize(pivotAinW) * 0.75f;
limitHandle.xMin = -maxLimit;
limitHandle.xMax = -minLimit;
limitHandle.xMotion = ConfigurableJointMotion.Limited;
limitHandle.yMotion = ConfigurableJointMotion.Locked;
limitHandle.zMotion = ConfigurableJointMotion.Locked;
limitHandle.yHandleColor = new Color(0, 0, 0, 0);
limitHandle.zHandleColor = new Color(0, 0, 0, 0);
limitHandle.radius = size;
using (new Handles.DrawingScope(handleMatrix))
{
// Draw the reference axis
float3 z = new float3(0, 0, 1); // ArrowHandleCap() draws an arrow pointing in (0, 0, 1)
Handles.ArrowHandleCap(0, float3.zero, Quaternion.FromToRotation(z, new float3(1, 0, 0)), size, Event.current.type);
// Draw the limit editor handle
EditorGUI.BeginChangeCheck();
limitHandle.DrawHandle();
if (EditorGUI.EndChangeCheck())
{
// Record the target object before setting new limits so changes can be undone/redone
Undo.RecordObject(target, "Edit joint angular limits");
minLimit = -limitHandle.xMax;
maxLimit = -limitHandle.xMin;
}
}
}
}
}
#endif
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/LimitedHingeJointEditor.cs
#if UNITY_EDITOR
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(LimitedHingeJoint))]
public class LimitedHingeEditor : UnityEditor.Editor
{
private EditorUtilities.AxisEditor m_AxisEditor = new EditorUtilities.AxisEditor();
private JointAngularLimitHandle m_LimitHandle = new JointAngularLimitHandle();
EditorGUI.BeginChangeCheck();
GUILayout.BeginHorizontal();
GUILayout.Label("Editors:");
limitedHinge.EditPivots = GUILayout.Toggle(limitedHinge.EditPivots, new GUIContent("Pivot"), "Button");
limitedHinge.EditAxes = GUILayout.Toggle(limitedHinge.EditAxes, new GUIContent("Axis"), "Button");
limitedHinge.EditLimits = GUILayout.Toggle(limitedHinge.EditLimits, new GUIContent("Limits"), "Button");
GUILayout.EndHorizontal();
DrawDefaultInspector();
if (EditorGUI.EndChangeCheck())
{
SceneView.RepaintAll();
}
}
if (limitedHinge.EditPivots)
{
EditorUtilities.EditPivot(limitedHinge.worldFromA, limitedHinge.worldFromB, limitedHinge.AutoSetConnected,
ref limitedHinge.PositionLocal, ref limitedHinge.PositionInConnectedEntity, limitedHinge);
}
if (limitedHinge.EditAxes)
{
m_AxisEditor.Update(limitedHinge.worldFromA, limitedHinge.worldFromB,
limitedHinge.AutoSetConnected,
limitedHinge.PositionLocal, limitedHinge.PositionInConnectedEntity,
ref limitedHinge.HingeAxisLocal, ref limitedHinge.HingeAxisInConnectedEntity,
ref limitedHinge.PerpendicularAxisLocal, ref limitedHinge.PerpendicularAxisInConnectedEntity,
limitedHinge);
}
if (limitedHinge.EditLimits)
{
EditorUtilities.EditLimits(limitedHinge.worldFromA, limitedHinge.worldFromB,
limitedHinge.PositionLocal,
limitedHinge.HingeAxisLocal, limitedHinge.HingeAxisInConnectedEntity,
limitedHinge.PerpendicularAxisLocal, limitedHinge.PerpendicularAxisInConnectedEntity,
ref limitedHinge.MinAngle, ref limitedHinge.MaxAngle, m_LimitHandle, limitedHinge);
}
}
}
}
#endif
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/PhysicsBodyAuthoringEditor.cs
using System.Collections.Generic;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(PhysicsBodyAuthoring))]
[CanEditMultipleObjects]
class PhysicsBodyAuthoringEditor : BaseEditor
{
static class Content
{
public static readonly GUIContent MassLabel = EditorGUIUtility.TrTextContent("Mass");
public static readonly GUIContent CenterOfMassLabel = EditorGUIUtility.TrTextContent(
"Center of Mass", "Center of mass in the space of this body's transform."
);
public static readonly GUIContent InertiaTensorLabel = EditorGUIUtility.TrTextContent(
"Inertia Tensor", "Resistance to angular motion about each axis of rotation."
);
public static readonly GUIContent OrientationLabel = EditorGUIUtility.TrTextContent(
"Orientation", "Orientation of the body's inertia tensor in the space of its transform."
);
public static readonly GUIContent AdvancedLabel = EditorGUIUtility.TrTextContent(
"Advanced", "Advanced options"
);
}
#pragma warning disable 649
[AutoPopulate] SerializedProperty m_MotionType;
[AutoPopulate] SerializedProperty m_Smoothing;
[AutoPopulate] SerializedProperty m_Mass;
[AutoPopulate] SerializedProperty m_GravityFactor;
[AutoPopulate] SerializedProperty m_LinearDamping;
[AutoPopulate] SerializedProperty m_AngularDamping;
[AutoPopulate] SerializedProperty m_InitialLinearVelocity;
[AutoPopulate] SerializedProperty m_InitialAngularVelocity;
[AutoPopulate] SerializedProperty m_OverrideDefaultMassDistribution;
[AutoPopulate] SerializedProperty m_CenterOfMass;
[AutoPopulate] SerializedProperty m_Orientation;
[AutoPopulate] SerializedProperty m_InertiaTensor;
[AutoPopulate] SerializedProperty m_WorldIndex;
[AutoPopulate] SerializedProperty m_CustomTags;
#pragma warning restore 649
bool showAdvanced;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_MotionType);
if (m_MotionType.intValue != (int)BodyMotionType.Static)
EditorGUILayout.PropertyField(m_Smoothing);
if (m_MotionType.intValue == (int)BodyMotionType.Dynamic)
{
EditorGUILayout.PropertyField(m_LinearDamping, true);
EditorGUILayout.PropertyField(m_AngularDamping, true);
}
if (m_MotionType.intValue != (int)BodyMotionType.Static)
{
EditorGUILayout.PropertyField(m_InitialLinearVelocity, true);
EditorGUILayout.PropertyField(m_InitialAngularVelocity, true);
}
if (m_MotionType.intValue == (int)BodyMotionType.Dynamic)
{
EditorGUILayout.PropertyField(m_GravityFactor, true);
}
EditorGUI.BeginDisabledGroup(!dynamic);
if (dynamic)
{
EditorGUILayout.PropertyField(m_Orientation, Content.OrientationLabel);
EditorGUILayout.PropertyField(m_InertiaTensor, Content.InertiaTensorLabel);
}
else
{
EditorGUI.BeginDisabledGroup(true);
var position =
EditorGUILayout.GetControlRect(true, EditorGUI.GetPropertyHeight(m_InertiaTensor));
EditorGUI.BeginProperty(position, Content.InertiaTensorLabel, m_InertiaTensor);
EditorGUI.Vector3Field(position, Content.InertiaTensorLabel,
Vector3.one * float.PositiveInfinity);
EditorGUI.EndProperty();
EditorGUI.EndDisabledGroup();
}
EditorGUI.EndDisabledGroup();
--EditorGUI.indentLevel;
}
}
EditorGUILayout.PropertyField(m_CustomTags);
--EditorGUI.indentLevel;
}
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
DisplayStatusMessages();
}
MessageType m_Status;
List<string> m_StatusMessages = new List<string>(8);
void DisplayStatusMessages()
{
m_Status = MessageType.None;
m_StatusMessages.Clear();
var hierarchyStatus = StatusMessageUtility.GetHierarchyStatusMessage(targets, out var hierarchyStatusMessage);
if (!string.IsNullOrEmpty(hierarchyStatusMessage))
{
m_StatusMessages.Add(hierarchyStatusMessage);
m_Status = (MessageType)math.max((int)m_Status, (int)hierarchyStatus);
}
if (m_StatusMessages.Count > 0)
EditorGUILayout.HelpBox(string.Join("\n\n", m_StatusMessages), m_Status);
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/PhysicsCategoryNamesEditor.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(PhysicsCategoryNames))]
[CanEditMultipleObjects]
class PhysicsCategoryNamesEditor : BaseEditor
{
#pragma warning disable 649
[AutoPopulate(ElementFormatString = "Category {0}", Resizable = false, Reorderable = false)]
ReorderableList m_CategoryNames;
#pragma warning restore 649
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/PhysicsShapeAuthoringEditor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityMesh = UnityEngine.Mesh;
using Unity.Physics.Extensions;
using LegacyRigidBody = UnityEngine.Rigidbody;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(PhysicsShapeAuthoring))]
[CanEditMultipleObjects]
class PhysicsShapeAuthoringEditor : BaseEditor
{
static class Styles
{
const string k_Plural = "One or more selected objects";
const string k_Singular = "This object";
public static readonly string GenericUndoMessage = L10n.Tr("Change Shape");
public static readonly string MultipleShapeTypesLabel =
L10n.Tr("Multiple shape types in current selection.");
public static readonly string PreviewGenerationNotification =
L10n.Tr("Generating collision geometry preview...");
static readonly GUIContent k_FitToRenderMeshesLabel =
EditorGUIUtility.TrTextContent("Fit to Enabled Render Meshes");
static readonly GUIContent k_FitToRenderMeshesWarningLabelSg = new GUIContent(
k_FitToRenderMeshesLabel.text,
EditorGUIUtility.Load("console.warnicon") as Texture,
L10n.Tr($"{k_Singular} has non-uniform scale. Trying to fit the shape to render meshes might produce unexpected results.")
);
static readonly GUIContent k_FitToRenderMeshesWarningLabelPl = new GUIContent(
k_FitToRenderMeshesLabel.text,
EditorGUIUtility.Load("console.warnicon") as Texture,
L10n.Tr($"{k_Plural} has non-uniform scale. Trying to fit the shape to render meshes might produce unexpected results.")
);
public static readonly GUIContent CenterLabel = EditorGUIUtility.TrTextContent("Center");
public static readonly GUIContent SizeLabel = EditorGUIUtility.TrTextContent("Size");
public static readonly GUIContent OrientationLabel = EditorGUIUtility.TrTextContent(
"Orientation", "Euler orientation in the shape's local space (ZXY order)."
);
public static readonly GUIContent CylinderSideCountLabel = EditorGUIUtility.TrTextContent("Side Count");
public static readonly GUIContent RadiusLabel = EditorGUIUtility.TrTextContent("Radius");
public static readonly GUIContent ForceUniqueLabel = EditorGUIUtility.TrTextContent(
"Force Unique",
"If set to true, then this object will always produce a unique collider for run-time during conversion. " +
"If set to false, then this object may share its collider data with other objects if they have the same inputs. " +
"You should enable this option if you plan to modify this instance's collider at run-time."
);
public static readonly GUIContent MaterialLabel = EditorGUIUtility.TrTextContent("Material");
public static readonly GUIContent SetRecommendedConvexValues = EditorGUIUtility.TrTextContent(
"Set Recommended Default Values",
"Set recommended values for convex hull generation parameters based on either render meshes or custom mesh."
);
public static GUIContent GetFitToRenderMeshesLabel(int numTargets, MessageType status) =>
status >= MessageType.Warning
? numTargets == 1 ? k_FitToRenderMeshesWarningLabelSg : k_FitToRenderMeshesWarningLabelPl
: k_FitToRenderMeshesLabel;
static readonly string[] k_NoGeometryWarning =
{
L10n.Tr($"{k_Singular} has no enabled render meshes in its hierarchy and no custom mesh assigned."),
L10n.Tr($"{k_Plural} has no enabled render meshes in their hierarchies and no custom mesh assigned.")
};
public static string GetNoGeometryWarning(int numTargets) =>
numTargets == 1 ? k_NoGeometryWarning[0] : k_NoGeometryWarning[1];
m_NumImplicitStatic = targets.Cast<PhysicsShapeAuthoring>().Count(
shape => shape.GetPrimaryBody() == shape.gameObject
&& shape.GetComponent<PhysicsBodyAuthoring>() == null
&& shape.GetComponent<LegacyRigidBody>() == null
);
Undo.undoRedoPerformed += Repaint;
}
void OnDisable()
{
Undo.undoRedoPerformed -= Repaint;
SceneViewUtility.ClearNotificationInSceneView();
foreach (var preview in m_PreviewData.Values)
preview.Dispose();
if (m_DropDown != null)
m_DropDown.CloseWithoutUndo();
}
class PreviewMeshData : IDisposable
{
[BurstCompile]
struct CreateTempHullJob : IJob
{
public ConvexHullGenerationParameters GenerationParameters;
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<float3> Points;
public NativeArray<BlobAssetReference<Collider>> Output;
public void Execute() => Output[0] = ConvexCollider.Create(Points, GenerationParameters, CollisionFilter.Default);
}
[BurstCompile]
struct CreateTempMeshJob : IJob
{
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<float3> Points;
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<int3> Triangles;
public NativeArray<BlobAssetReference<Collider>> Output;
public void Execute() => Output[0] = MeshCollider.Create(Points, Triangles);
}
static readonly List<Vector3> s_ReusableEdges = new List<Vector3>(1024);
public Vector3[] Edges = Array.Empty<Vector3>();
public Aabb Bounds = new Aabb();
bool m_Disposed;
uint m_InputHash;
ConvexHullGenerationParameters m_HashedConvexParameters;
NativeArray<float3> m_HashedPoints = new NativeArray<float3>(0, Allocator.Persistent);
// multiple preview jobs might be running if user assigned a different mesh before previous job completed
JobHandle m_MostRecentlyScheduledJob;
Dictionary<JobHandle, NativeArray<BlobAssetReference<Collider>>> m_PreviewJobsOutput =
new Dictionary<JobHandle, NativeArray<BlobAssetReference<Collider>>>();
unsafe uint GetInputHash(
PhysicsShapeAuthoring shape,
NativeList<float3> currentPoints,
NativeArray<float3> hashedPoints,
ConvexHullGenerationParameters hashedConvexParameters,
out ConvexHullGenerationParameters currentConvexProperties
)
{
currentConvexProperties = default;
switch (shape.ShapeType)
{
case ShapeType.ConvexHull:
shape.GetBakedConvexProperties(currentPoints); // TODO: use HashableShapeInputs
currentConvexProperties = shape.ConvexHullGenerationParameters;
return math.hash(
new uint3(
(uint)shape.ShapeType,
currentConvexProperties.GetStableHash(hashedConvexParameters),
currentPoints.GetStableHash(hashedPoints)
)
);
case ShapeType.Mesh:
var triangles = new NativeList<int3>(1024, Allocator.Temp);
shape.GetBakedMeshProperties(currentPoints, triangles); // TODO: use HashableShapeInputs
return math.hash(
new uint3(
(uint)shape.ShapeType,
currentPoints.GetStableHash(hashedPoints),
math.hash(triangles.GetUnsafePtr(), UnsafeUtility.SizeOf<int3>() * triangles.Length)
)
);
default:
return (uint)shape.ShapeType;
}
}
public void SchedulePreviewIfChanged(PhysicsShapeAuthoring shape)
{
using (var currentPoints = new NativeList<float3>(65535, Allocator.Temp))
{
var hash = GetInputHash(
shape, currentPoints, m_HashedPoints, m_HashedConvexParameters, out var currentConvexParameters
);
if (m_InputHash == hash)
return;
m_InputHash = hash;
m_HashedConvexParameters = currentConvexParameters;
m_HashedPoints.Dispose();
m_HashedPoints = new NativeArray<float3>(currentPoints.Length, Allocator.Persistent);
m_HashedPoints.CopyFrom(currentPoints.AsArray());
}
if (shape.ShapeType != ShapeType.ConvexHull && shape.ShapeType != ShapeType.Mesh)
return;
// TODO: cache results per input data hash, and simply use existing data (e.g., to make undo/redo faster)
var output = new NativeArray<BlobAssetReference<Collider>>(1, Allocator.Persistent);
m_MostRecentlyScheduledJob = shape.ShapeType == ShapeType.Mesh
? ScheduleMeshPreview(shape, output)
: ScheduleConvexHullPreview(shape, output);
m_PreviewJobsOutput.Add(m_MostRecentlyScheduledJob, output);
if (m_PreviewJobsOutput.Count == 1)
{
CheckPreviewJobsForCompletion();
if (m_MostRecentlyScheduledJob.Equals(default(JobHandle)))
return;
EditorApplication.update += CheckPreviewJobsForCompletion;
EditorApplication.delayCall += () =>
{
SceneViewUtility.DisplayProgressNotification(
Styles.PreviewGenerationNotification, () => float.PositiveInfinity
);
};
}
}
JobHandle ScheduleConvexHullPreview(PhysicsShapeAuthoring shape, NativeArray<BlobAssetReference<Collider>> output)
{
var pointCloud = new NativeList<float3>(65535, Allocator.Temp);
shape.GetBakedConvexProperties(pointCloud);
if (pointCloud.Length == 0)
return default;
// copy to NativeArray because NativeList not yet compatible with DeallocateOnJobCompletion
var pointsArray = new NativeArray<float3>(
pointCloud.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory
);
pointsArray.CopyFrom(pointCloud.AsArray());
// TODO: if there is still an active job with the same input data hash, then just set it to be most recently scheduled job
return new CreateTempHullJob
{
GenerationParameters = shape.ConvexHullGenerationParameters.ToRunTime(),
Points = pointsArray,
Output = output
}.Schedule();
}
JobHandle ScheduleMeshPreview(PhysicsShapeAuthoring shape, NativeArray<BlobAssetReference<Collider>> output)
{
var points = new NativeList<float3>(1024, Allocator.Temp);
var triangles = new NativeList<int3>(1024, Allocator.Temp);
shape.GetBakedMeshProperties(points, triangles);
if (points.Length == 0 || triangles.Length == 0)
return default;
// copy to NativeArray because NativeList not yet compatible with DeallocateOnJobCompletion
var pointsArray = new NativeArray<float3>(
points.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory
);
pointsArray.CopyFrom(points.AsArray());
var triangleArray = new NativeArray<int3>(
triangles.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory
);
triangleArray.CopyFrom(triangles.AsArray());
// TODO: if there is still an active job with the same input data hash, then just set it to be most recently scheduled job
return new CreateTempMeshJob
{
Points = pointsArray,
Triangles = triangleArray,
Output = output
}.Schedule();
}
unsafe void CheckPreviewJobsForCompletion()
{
var repaintSceneViews = false;
foreach (var job in m_PreviewJobsOutput.Keys.ToArray()) // TODO: don't allocate on heap
{
// repaint scene views to indicate progress if most recent preview job is still in the queue
var mostRecentlyScheduledJob = m_MostRecentlyScheduledJob.Equals(job);
repaintSceneViews |= mostRecentlyScheduledJob;
if (!job.IsCompleted)
continue;
var output = m_PreviewJobsOutput[job];
m_PreviewJobsOutput.Remove(job);
job.Complete();
// only populate preview edge data if not already disposed and this job was actually the most recent
if (!m_Disposed && mostRecentlyScheduledJob)
{
if (!output[0].IsCreated)
{
Edges = Array.Empty<Vector3>();
Bounds = new Aabb();
}
else
{
switch (output[0].Value.Type)
{
case ColliderType.Convex:
ref var convex = ref output[0].As<ConvexCollider>();
DrawingUtility.GetConvexColliderEdges(
ref convex, s_ReusableEdges
);
Bounds = convex.CalculateAabb();
break;
case ColliderType.Mesh:
ref var mesh = ref output[0].As<MeshCollider>();
DrawingUtility.GetMeshColliderEdges(
ref mesh, s_ReusableEdges
);
Bounds = mesh.CalculateAabb();
break;
}
Edges = s_ReusableEdges.ToArray();
}
EditorApplication.delayCall += SceneViewUtility.ClearNotificationInSceneView;
}
if (output.IsCreated)
{
if (output[0].IsCreated)
output[0].Dispose();
output.Dispose();
}
}
if (repaintSceneViews)
SceneView.RepaintAll();
if (m_PreviewJobsOutput.Count == 0)
EditorApplication.update -= CheckPreviewJobsForCompletion;
}
public void Dispose()
{
m_Disposed = true;
m_HashedPoints.Dispose();
}
}
Dictionary<PhysicsShapeAuthoring, PreviewMeshData> m_PreviewData = new Dictionary<PhysicsShapeAuthoring, PreviewMeshData>();
PreviewMeshData GetPreviewData(PhysicsShapeAuthoring shape)
{
if (shape.ShapeType != ShapeType.ConvexHull && shape.ShapeType != ShapeType.Mesh)
return null;
if (!m_PreviewData.TryGetValue(shape, out var preview))
{
preview = m_PreviewData[shape] = new PreviewMeshData();
preview.SchedulePreviewIfChanged(shape);
}
// do not generate a new preview until the user has finished dragging a control handle (e.g., scale)
if (m_DraggingControlID == 0 && !EditorGUIUtility.editingTextField)
preview.SchedulePreviewIfChanged(shape);
return preview;
}
void UpdateGeometryState()
{
m_GeometryState = GeometryState.Okay;
var skinnedPoints = new NativeList<float3>(8192, Allocator.Temp);
foreach (PhysicsShapeAuthoring shape in targets)
{
// if a custom mesh is assigned, only check it
using (var so = new SerializedObject(shape))
{
var customMesh = so.FindProperty(m_CustomMesh.propertyPath).objectReferenceValue as UnityMesh;
if (customMesh != null)
{
m_GeometryState |= GetGeometryState(customMesh, shape.gameObject);
continue;
}
}
// otherwise check all mesh filters in the hierarchy that might be included
var geometryState = GeometryState.Okay;
using (var scope = new GetActiveChildrenScope<MeshFilter>(shape, shape.transform))
{
foreach (var meshFilter in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(meshFilter, filterOutInvalid: false))
geometryState |= GetGeometryState(meshFilter.sharedMesh, shape.gameObject);
}
}
if (shape.ShapeType == ShapeType.Mesh)
{
PhysicsShapeAuthoring.GetAllSkinnedPointsInHierarchyBelongingToShape(
shape, skinnedPoints, false, default, default, default
);
if (skinnedPoints.Length > 0)
geometryState |= GeometryState.MeshWithSkinnedPoints;
}
m_GeometryState |= geometryState;
}
skinnedPoints.Dispose();
}
static GeometryState GetGeometryState(UnityMesh mesh, GameObject host)
{
if (mesh == null)
return GeometryState.NoGeometry;
if (!mesh.IsValidForConversion(host))
return GeometryState.NonReadableGeometry;
return GeometryState.Okay;
}
public override void OnInspectorGUI()
{
var hotControl = GUIUtility.hotControl;
switch (Event.current.GetTypeForControl(hotControl))
{
case EventType.MouseDrag:
m_DraggingControlID = hotControl;
break;
case EventType.MouseUp:
m_DraggingControlID = 0;
break;
}
UpdateGeometryState();
serializedObject.Update();
UpdateStatusMessages();
EditorGUI.BeginChangeCheck();
DisplayShapeSelector();
++EditorGUI.indentLevel;
if (m_ShapeType.hasMultipleDifferentValues)
EditorGUILayout.HelpBox(Styles.MultipleShapeTypesLabel, MessageType.None);
else
{
switch ((ShapeType)m_ShapeType.intValue)
{
case ShapeType.Box:
AutomaticPrimitiveControls();
DisplayBoxControls();
break;
case ShapeType.Capsule:
AutomaticPrimitiveControls();
DisplayCapsuleControls();
break;
case ShapeType.Sphere:
AutomaticPrimitiveControls();
DisplaySphereControls();
break;
case ShapeType.Cylinder:
AutomaticPrimitiveControls();
DisplayCylinderControls();
break;
case ShapeType.Plane:
AutomaticPrimitiveControls();
DisplayPlaneControls();
break;
case ShapeType.ConvexHull:
RecommendedConvexValuesButton();
EditorGUILayout.PropertyField(m_ConvexHullGenerationParameters);
EditorGUILayout.PropertyField(m_MinimumSkinnedVertexWeight);
DisplayMeshControls();
break;
case ShapeType.Mesh:
DisplayMeshControls();
break;
default:
throw new UnimplementedShapeException((ShapeType)m_ShapeType.intValue);
}
EditorGUILayout.PropertyField(m_ForceUnique, Styles.ForceUniqueLabel);
}
--EditorGUI.indentLevel;
EditorGUILayout.LabelField(Styles.MaterialLabel);
++EditorGUI.indentLevel;
EditorGUILayout.PropertyField(m_Material);
--EditorGUI.indentLevel;
if (m_StatusMessages.Count > 0)
EditorGUILayout.HelpBox(string.Join("\n\n", m_StatusMessages), m_Status);
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
}
void RecommendedConvexValuesButton()
{
EditorGUI.BeginDisabledGroup(
(m_GeometryState & GeometryState.NoGeometry) == GeometryState.NoGeometry ||
EditorUtility.IsPersistent(target)
);
var rect = EditorGUI.IndentedRect(
EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight, EditorStyles.miniButton)
);
var buttonLabel = Styles.SetRecommendedConvexValues;
if (GUI.Button(rect, buttonLabel, Styles.Button))
{
Undo.RecordObjects(targets, buttonLabel.text);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.InitializeConvexHullGenerationParameters();
EditorUtility.SetDirty(shape);
}
}
EditorGUI.EndDisabledGroup();
}
MessageType m_GeometryStatus;
List<string> m_GeometryStatusMessages = new List<string>();
HashSet<string> m_ShapeSuggestions = new HashSet<string>();
MessageType m_Status;
List<string> m_StatusMessages = new List<string>(8);
MessageType m_MatrixStatus;
List<MatrixState> m_MatrixStates = new List<MatrixState>();
void UpdateStatusMessages()
{
m_Status = MessageType.None;
m_StatusMessages.Clear();
if (m_NumImplicitStatic != 0)
m_StatusMessages.Add(Styles.GetStaticColliderStatusMessage(targets.Length));
m_ShapeSuggestions.Clear();
foreach (PhysicsShapeAuthoring shape in targets)
{
const float k_Epsilon = HashableShapeInputs.k_DefaultLinearPrecision;
switch (shape.ShapeType)
{
case ShapeType.Box:
var box = shape.GetBakedBoxProperties();
var max = math.cmax(box.Size);
var min = math.cmin(box.Size);
if (min < k_Epsilon)
m_ShapeSuggestions.Add(Styles.BoxPlaneSuggestion);
else if (math.abs(box.BevelRadius - min * 0.5f) < k_Epsilon)
{
if (math.abs(max - min) < k_Epsilon)
m_ShapeSuggestions.Add(Styles.BoxSphereSuggestion);
else if (math.abs(math.lengthsq(box.Size - new float3(min)) - math.pow(max - min, 2f)) < k_Epsilon)
m_ShapeSuggestions.Add(Styles.BoxCapsuleSuggestion);
}
break;
case ShapeType.Capsule:
var capsule = shape.GetBakedCapsuleProperties();
if (math.abs(capsule.Height - 2f * capsule.Radius) < k_Epsilon)
m_ShapeSuggestions.Add(Styles.CapsuleSphereSuggestion);
break;
case ShapeType.Cylinder:
var cylinder = shape.GetBakedCylinderProperties();
if (math.abs(cylinder.BevelRadius - cylinder.Radius) < k_Epsilon)
{
m_ShapeSuggestions.Add(math.abs(cylinder.Height - 2f * cylinder.Radius) < k_Epsilon
? Styles.CylinderSphereSuggestion
: Styles.CylinderCapsuleSuggestion);
}
break;
}
}
foreach (var suggestion in m_ShapeSuggestions)
m_StatusMessages.Add(suggestion);
var hierarchyStatus = StatusMessageUtility.GetHierarchyStatusMessage(targets, out var hierarchyStatusMessage);
if (!string.IsNullOrEmpty(hierarchyStatusMessage))
{
m_StatusMessages.Add(hierarchyStatusMessage);
m_Status = (MessageType)math.max((int)m_Status, (int)hierarchyStatus);
}
m_MatrixStates.Clear();
foreach (var t in targets)
{
var localToWorld = (float4x4)(t as Component).transform.localToWorldMatrix;
m_MatrixStates.Add(ManipulatorUtility.GetMatrixState(ref localToWorld));
}
m_MatrixStatus = StatusMessageUtility.GetMatrixStatusMessage(m_MatrixStates, out var matrixStatusMessage);
if (m_MatrixStatus != MessageType.None)
{
m_StatusMessages.Add(matrixStatusMessage);
m_Status = (MessageType)math.max((int)m_Status, (int)m_MatrixStatus);
}
m_GeometryStatus = MessageType.None;
m_GeometryStatusMessages.Clear();
if ((m_GeometryState & GeometryState.NoGeometry) == GeometryState.NoGeometry)
{
m_GeometryStatusMessages.Add(Styles.GetNoGeometryWarning(targets.Length));
m_GeometryStatus = (MessageType)math.max((int)m_GeometryStatus, (int)MessageType.Error);
}
if ((m_GeometryState & GeometryState.NonReadableGeometry) == GeometryState.NonReadableGeometry)
{
m_GeometryStatusMessages.Add(Styles.GetNonReadableGeometryWarning(targets.Length));
m_GeometryStatus = (MessageType)math.max((int)m_GeometryStatus, (int)MessageType.Warning);
}
if ((m_GeometryState & GeometryState.MeshWithSkinnedPoints) == GeometryState.MeshWithSkinnedPoints)
{
m_GeometryStatusMessages.Add(Styles.GetMeshWithSkinnedPointsWarning(targets.Length));
m_GeometryStatus = (MessageType)math.max((int)m_GeometryStatus, (int)MessageType.Warning);
}
}
void DisplayShapeSelector()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_ShapeType);
if (!EditorGUI.EndChangeCheck())
return;
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
switch ((ShapeType)m_ShapeType.intValue)
{
case ShapeType.Box:
shape.SetBox(shape.GetBoxProperties(out var orientation), orientation);
break;
case ShapeType.Capsule:
shape.SetCapsule(shape.GetCapsuleProperties());
break;
case ShapeType.Sphere:
shape.SetSphere(shape.GetSphereProperties(out orientation), orientation);
break;
case ShapeType.Cylinder:
shape.SetCylinder(shape.GetCylinderProperties(out orientation), orientation);
break;
case ShapeType.Plane:
shape.GetPlaneProperties(out var center, out var size2D, out orientation);
shape.SetPlane(center, size2D, orientation);
break;
case ShapeType.ConvexHull:
case ShapeType.Mesh:
return;
default:
throw new UnimplementedShapeException((ShapeType)m_ShapeType.intValue);
}
EditorUtility.SetDirty(shape);
}
GUIUtility.ExitGUI();
}
void AutomaticPrimitiveControls()
{
EditorGUI.BeginDisabledGroup(
(m_GeometryState & GeometryState.NoGeometry) == GeometryState.NoGeometry || EditorUtility.IsPersistent(target)
);
var buttonLabel = Styles.GetFitToRenderMeshesLabel(targets.Length, m_MatrixStatus);
var rect = EditorGUI.IndentedRect(
EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight, EditorStyles.miniButton)
);
if (GUI.Button(rect, buttonLabel, Styles.ButtonDropDown))
m_DropDown = FitToRenderMeshesDropDown.Show(rect, buttonLabel.text, m_MinimumSkinnedVertexWeight);
EditorGUI.EndDisabledGroup();
}
class FitToRenderMeshesDropDown : EditorWindow
{
static class Styles
{
public const float WindowWidth = 400f;
public const float LabelWidth = 200f;
public static GUIStyle Button => PhysicsShapeAuthoringEditor.Styles.Button;
}
static class Content
{
public static readonly string ApplyLabel = L10n.Tr("Apply");
public static readonly string CancelLabel = L10n.Tr("Cancel");
}
bool m_ApplyChanges;
bool m_ClosedWithoutUndo;
int m_UndoGroup;
SerializedProperty m_MinimumSkinnedVertexWeight;
public static FitToRenderMeshesDropDown Show(
Rect buttonRect, string title, SerializedProperty minimumSkinnedVertexWeight
)
{
var window = CreateInstance<FitToRenderMeshesDropDown>();
window.titleContent = EditorGUIUtility.TrTextContent(title);
window.m_UndoGroup = Undo.GetCurrentGroup();
window.m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
var size = new Vector2(
math.max(buttonRect.width, Styles.WindowWidth),
(EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing) * 3f
);
window.maxSize = window.minSize = size;
window.ShowAsDropDown(GUIUtility.GUIToScreenRect(buttonRect), size);
return window;
}
void OnGUI()
{
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = Styles.LabelWidth;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_MinimumSkinnedVertexWeight);
if (EditorGUI.EndChangeCheck())
ApplyChanges();
EditorGUIUtility.labelWidth = labelWidth;
GUILayout.FlexibleSpace();
var buttonRect = GUILayoutUtility.GetRect(0f, EditorGUIUtility.singleLineHeight);
var buttonLeft = new Rect(buttonRect)
{
width = 0.5f * (buttonRect.width - EditorGUIUtility.standardVerticalSpacing)
};
var buttonRight = new Rect(buttonLeft)
{
x = buttonLeft.xMax + EditorGUIUtility.standardVerticalSpacing
};
var close = false;
buttonRect = Application.platform == RuntimePlatform.OSXEditor ? buttonLeft : buttonRight;
if (
GUI.Button(buttonRect, Content.CancelLabel, Styles.Button)
|| Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape
)
{
close = true;
}
buttonRect = Application.platform == RuntimePlatform.OSXEditor ? buttonRight : buttonLeft;
if (GUI.Button(buttonRect, Content.ApplyLabel, Styles.Button))
{
close = true;
m_ApplyChanges = true;
}
if (close)
{
Close();
EditorGUIUtility.ExitGUI();
}
}
void ApplyChanges()
{
m_MinimumSkinnedVertexWeight.serializedObject.ApplyModifiedProperties();
Undo.RecordObjects(m_MinimumSkinnedVertexWeight.serializedObject.targetObjects, titleContent.text);
foreach (PhysicsShapeAuthoring shape in m_MinimumSkinnedVertexWeight.serializedObject.targetObjects)
{
using (var so = new SerializedObject(shape))
{
shape.FitToEnabledRenderMeshes(
so.FindProperty(m_MinimumSkinnedVertexWeight.propertyPath).floatValue
);
EditorUtility.SetDirty(shape);
}
}
m_MinimumSkinnedVertexWeight.serializedObject.Update();
}
public void CloseWithoutUndo()
{
m_ApplyChanges = true;
Close();
}
void OnDestroy()
{
if (m_ApplyChanges)
ApplyChanges();
else
Undo.RevertAllDownToGroup(m_UndoGroup);
}
}
void DisplayBoxControls()
{
EditorGUILayout.PropertyField(m_PrimitiveSize, Styles.SizeLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
EditorGUILayout.PropertyField(m_BevelRadius);
}
void DisplayCapsuleControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Capsule);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.SetCapsule(shape.GetCapsuleProperties());
EditorUtility.SetDirty(shape);
}
}
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
}
void DisplaySphereControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_SphereRadius, Styles.RadiusLabel);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.SetSphere(shape.GetSphereProperties(out EulerAngles orientation), orientation);
EditorUtility.SetDirty(shape);
}
}
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
}
void DisplayCylinderControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Cylinder);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.SetCylinder(shape.GetCylinderProperties(out var orientation), orientation);
EditorUtility.SetDirty(shape);
}
}
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
EditorGUILayout.PropertyField(m_CylinderSideCount, Styles.CylinderSideCountLabel);
EditorGUILayout.PropertyField(m_BevelRadius);
}
void DisplayPlaneControls()
{
EditorGUILayout.PropertyField(m_PrimitiveSize, Styles.SizeLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
}
void DisplayMeshControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_CustomMesh);
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
}
if (m_GeometryStatusMessages.Count > 0)
EditorGUILayout.HelpBox(string.Join("\n\n", m_GeometryStatusMessages), m_GeometryStatus);
}
static readonly BeveledBoxBoundsHandle s_Box = new BeveledBoxBoundsHandle();
static readonly PhysicsCapsuleBoundsHandle s_Capsule =
new PhysicsCapsuleBoundsHandle { heightAxis = CapsuleBoundsHandle.HeightAxis.Z };
static readonly BeveledCylinderBoundsHandle s_Cylinder = new BeveledCylinderBoundsHandle();
static readonly PhysicsSphereBoundsHandle s_Sphere = new PhysicsSphereBoundsHandle();
static readonly BoxBoundsHandle s_Plane =
new BoxBoundsHandle { axes = PrimitiveBoundsHandle.Axes.X | PrimitiveBoundsHandle.Axes.Z };
static readonly Color k_ShapeHandleColor = new Color32(145, 244, 139, 210);
static readonly Color k_ShapeHandleColorDisabled = new Color32(84, 200, 77, 140);
void OnSceneGUI()
{
var hotControl = GUIUtility.hotControl;
switch (Event.current.GetTypeForControl(hotControl))
{
case EventType.MouseDrag:
m_DraggingControlID = hotControl;
break;
case EventType.MouseUp:
m_DraggingControlID = 0;
break;
}
var shape = target as PhysicsShapeAuthoring;
var handleColor = shape.enabled ? k_ShapeHandleColor : k_ShapeHandleColorDisabled;
var handleMatrix = shape.GetShapeToWorldMatrix();
using (new Handles.DrawingScope(handleColor, handleMatrix))
{
switch (shape.ShapeType)
{
case ShapeType.Box:
var boxGeometry = shape.GetBakedBoxProperties();
s_Box.bevelRadius = boxGeometry.BevelRadius;
s_Box.center = float3.zero;
s_Box.size = boxGeometry.Size;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(boxGeometry.Center, boxGeometry.Orientation, 1f))))
s_Box.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedBoxSize(s_Box.size, s_Box.bevelRadius);
}
break;
case ShapeType.Capsule:
s_Capsule.center = float3.zero;
var capsuleGeometry = shape.GetBakedCapsuleProperties();
s_Capsule.height = capsuleGeometry.Height;
s_Capsule.radius = capsuleGeometry.Radius;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(capsuleGeometry.Center, capsuleGeometry.Orientation, 1f))))
s_Capsule.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedCapsuleSize(s_Capsule.height, s_Capsule.radius);
}
break;
case ShapeType.Sphere:
var sphereGeometry = shape.GetBakedSphereProperties(out EulerAngles orientation);
s_Sphere.center = float3.zero;
s_Sphere.radius = sphereGeometry.Radius;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(sphereGeometry.Center, orientation, 1f))))
s_Sphere.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedSphereRadius(s_Sphere.radius);
}
break;
case ShapeType.Cylinder:
var cylinderGeometry = shape.GetBakedCylinderProperties();
s_Cylinder.center = float3.zero;
s_Cylinder.height = cylinderGeometry.Height;
s_Cylinder.radius = cylinderGeometry.Radius;
s_Cylinder.sideCount = cylinderGeometry.SideCount;
s_Cylinder.bevelRadius = cylinderGeometry.BevelRadius;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(cylinderGeometry.Center, cylinderGeometry.Orientation, 1f))))
s_Cylinder.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedCylinderSize(s_Cylinder.height, s_Cylinder.radius, s_Cylinder.bevelRadius);
}
break;
case ShapeType.Plane:
shape.GetPlaneProperties(out var center, out var size2, out orientation);
s_Plane.center = float3.zero;
s_Plane.size = new float3(size2.x, 0f, size2.y);
EditorGUI.BeginChangeCheck();
{
var m = math.mul(shape.transform.localToWorldMatrix, float4x4.TRS(center, orientation, 1f));
using (new Handles.DrawingScope(m))
s_Plane.DrawHandle();
var right = math.mul(m, new float4 { x = 1f }).xyz;
var forward = math.mul(m, new float4 { z = 1f }).xyz;
var normal = math.cross(math.normalizesafe(forward), math.normalizesafe(right))
* 0.5f * math.lerp(math.length(right) * size2.x, math.length(forward) * size2.y, 0.5f);
using (new Handles.DrawingScope(float4x4.identity))
Handles.DrawLine(m.c3.xyz, m.c3.xyz + normal);
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedPlaneSize(((float3)s_Plane.size).xz);
}
break;
case ShapeType.ConvexHull:
if (Event.current.type != EventType.Repaint)
break;
var points = GetPreviewData(shape).Edges;
// TODO: follow transformation until new preview is generated if e.g., user is dragging handles
if (points.Length > 0)
Handles.DrawLines(points);
break;
case ShapeType.Mesh:
if (Event.current.type != EventType.Repaint)
break;
points = GetPreviewData(shape).Edges;
if (points.Length > 0)
Handles.DrawLines(points);
break;
default:
throw new UnimplementedShapeException(shape.ShapeType);
}
}
}
// ReSharper disable once UnusedMember.Global - magic method called by unity inspector
public bool HasFrameBounds()
{
return true;
}
static Bounds TransformBounds(Bounds localBounds, float4x4 matrix)
{
var center = new float4(localBounds.center, 1);
Bounds bounds = new Bounds(math.mul(matrix, center).xyz, Vector3.zero);
var extent = new float4(localBounds.extents, 0);
for (int i = 0; i < 8; ++i)
{
extent.x = (i & 1) == 0 ? -extent.x : extent.x;
extent.y = (i & 2) == 0 ? -extent.y : extent.y;
extent.z = (i & 4) == 0 ? -extent.z : extent.z;
var worldPoint = math.mul(matrix, center + extent).xyz;
bounds.Encapsulate(worldPoint);
}
return bounds;
}
// ReSharper disable once UnusedMember.Global - magic method called by unity inspector
public Bounds OnGetFrameBounds()
{
var shape = target as PhysicsShapeAuthoring;
var shapeMatrix = shape.GetShapeToWorldMatrix();
Bounds bounds = new Bounds();
switch (shape.ShapeType)
{
case ShapeType.Box:
var boxGeometry = shape.GetBakedBoxProperties();
bounds = new Bounds(float3.zero, boxGeometry.Size);
bounds = TransformBounds(bounds, float4x4.TRS(boxGeometry.Center, boxGeometry.Orientation, 1f));
break;
case ShapeType.Capsule:
var capsuleGeometry = shape.GetBakedCapsuleProperties();
var cd = capsuleGeometry.Radius * 2;
bounds = new Bounds(float3.zero, new float3(cd, cd, capsuleGeometry.Height));
bounds = TransformBounds(bounds, float4x4.TRS(capsuleGeometry.Center, capsuleGeometry.Orientation, 1f));
break;
case ShapeType.Sphere:
var sphereGeometry = shape.GetBakedSphereProperties(out var orientation);
var sd = sphereGeometry.Radius * 2;
bounds = new Bounds(sphereGeometry.Center, new float3(sd, sd, sd));
break;
case ShapeType.Cylinder:
var cylinderGeometry = shape.GetBakedCylinderProperties();
var cyld = cylinderGeometry.Radius * 2;
bounds = new Bounds(float3.zero, new float3(cyld, cyld, cylinderGeometry.Height));
bounds = TransformBounds(bounds, float4x4.TRS(cylinderGeometry.Center, cylinderGeometry.Orientation, 1f));
break;
case ShapeType.Plane:
shape.GetPlaneProperties(out var center, out var size2, out orientation);
bounds = new Bounds(float3.zero, new float3(size2.x, 0, size2.y));
bounds = TransformBounds(bounds, float4x4.TRS(center, orientation, 1f));
break;
case ShapeType.ConvexHull:
case ShapeType.Mesh:
var previewData = GetPreviewData(shape);
if (previewData != null)
bounds = new Bounds(previewData.Bounds.Center, previewData.Bounds.Extents);
break;
default:
throw new UnimplementedShapeException(shape.ShapeType);
}
return TransformBounds(bounds, shapeMatrix);
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/RagdollJointEditor.cs
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using Unity.Mathematics;
using Unity.Physics.Authoring;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(RagdollJoint))]
public class RagdollJointEditor : UnityEditor.Editor
{
private EditorUtilities.AxisEditor m_AxisEditor = new EditorUtilities.AxisEditor();
private JointAngularLimitHandle m_LimitHandle = new JointAngularLimitHandle();
public override void OnInspectorGUI()
{
RagdollJoint ragdoll = (RagdollJoint)target;
EditorGUI.BeginChangeCheck();
GUILayout.BeginVertical();
GUILayout.Space(10.0f);
GUILayout.BeginHorizontal();
GUILayout.Label("Editors:");
ragdoll.EditPivots = GUILayout.Toggle(ragdoll.EditPivots, new GUIContent("Pivot"), "Button");
ragdoll.EditAxes = GUILayout.Toggle(ragdoll.EditAxes, new GUIContent("Axis"), "Button");
ragdoll.EditLimits = GUILayout.Toggle(ragdoll.EditLimits, new GUIContent("Limits"), "Button");
GUILayout.EndHorizontal();
GUILayout.Space(10.0f);
GUILayout.EndVertical();
DrawDefaultInspector();
if (EditorGUI.EndChangeCheck())
{
SceneView.RepaintAll();
}
}
private static void DrawCone(float3 point, float3 axis, float angle, Color color)
{
#if UNITY_EDITOR
Handles.color = color;
float3 dir;
float scale = Math.NormalizeWithLength(axis, out dir);
float3 arm;
{
float3 perp1, perp2;
Math.CalculatePerpendicularNormalized(dir, out perp1, out perp2);
arm = math.mul(quaternion.AxisAngle(perp1, angle), dir) * scale;
}
const int res = 16;
quaternion q = quaternion.AxisAngle(dir, 2.0f * (float)math.PI / res);
for (int i = 0; i < res; i++)
{
float3 nextArm = math.mul(q, arm);
Handles.DrawLine(point, point + arm);
Handles.DrawLine(point + arm, point + nextArm);
arm = nextArm;
}
#endif
}
protected virtual void OnSceneGUI()
{
RagdollJoint ragdoll = (RagdollJoint)target;
bool drawCones = false;
if (ragdoll.EditPivots)
{
EditorUtilities.EditPivot(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.AutoSetConnected,
ref ragdoll.PositionLocal, ref ragdoll.PositionInConnectedEntity, ragdoll);
}
if (ragdoll.EditAxes)
{
m_AxisEditor.Update(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.AutoSetConnected,
ragdoll.PositionLocal, ragdoll.PositionInConnectedEntity, ref ragdoll.TwistAxisLocal, ref ragdoll.TwistAxisInConnectedEntity,
ref ragdoll.PerpendicularAxisLocal, ref ragdoll.PerpendicularAxisInConnectedEntity, ragdoll);
drawCones = true;
}
if (ragdoll.EditLimits)
{
EditorUtilities.EditLimits(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.PositionLocal, ragdoll.TwistAxisLocal, ragdoll.TwistAxisInConnectedEntity,
ragdoll.PerpendicularAxisLocal, ragdoll.PerpendicularAxisInConnectedEntity, ref ragdoll.MinTwistAngle, ref ragdoll.MaxTwistAngle, m_LimitHandle, ragdoll);
}
if (drawCones)
{
float3 pivotB = math.transform(ragdoll.worldFromB, ragdoll.PositionInConnectedEntity);
float3 axisB = math.rotate(ragdoll.worldFromB, ragdoll.TwistAxisInConnectedEntity);
DrawCone(pivotB, axisB, math.radians(ragdoll.MaxConeAngle), Color.yellow);
float3 perpendicularB = math.rotate(ragdoll.worldFromB, ragdoll.PerpendicularAxisInConnectedEntity);
DrawCone(pivotB, perpendicularB, math.radians(ragdoll.MinPerpendicularAngle + 90f), Color.red);
DrawCone(pivotB, perpendicularB, math.radians(ragdoll.MaxPerpendicularAngle + 90f), Color.red);
}
}
}
}
#endif
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/BeveledBoxBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class BeveledBoxBoundsHandle : BoxBoundsHandle
{
public float bevelRadius
{
get => math.min(m_BevelRadius, math.cmin(GetSize()) * 0.5f);
set
{
if (!m_IsDragging)
m_BevelRadius = math.max(0f, value);
}
}
float m_BevelRadius = ConvexHullGenerationParameters.Default.BevelRadius;
bool m_IsDragging = false;
static PhysicsBoundsHandleUtility.Corner[] s_Corners = new PhysicsBoundsHandleUtility.Corner[8];
public new void DrawHandle()
{
int prevHotControl = GUIUtility.hotControl;
if (prevHotControl == 0)
m_IsDragging = false;
base.DrawHandle();
int currHotcontrol = GUIUtility.hotControl;
if (currHotcontrol != prevHotControl)
m_IsDragging = currHotcontrol != 0;
}
protected override void DrawWireframe()
{
if (this.bevelRadius <= 0f)
{
base.DrawWireframe();
return;
}
var cameraPosition = float3.zero;
var cameraForward = new float3 { z = 1f };
if (Camera.current != null)
{
cameraPosition = Camera.current.transform.position;
cameraForward = Camera.current.transform.forward;
}
var bounds = new Bounds(this.center, this.size);
bool isCameraInsideBox = Camera.current != null && bounds.Contains(Handles.inverseMatrix.MultiplyPoint(cameraPosition));
var bevelRadius = this.bevelRadius;
var origin = (float3)this.center;
var size = (float3)this.size;
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(-1f, 1f, 1f), bevelRadius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, -1f, 1f), bevelRadius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 2, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, -1f), bevelRadius, 2, axes, isCameraInsideBox);
var corner = 0.5f * size - new float3(1f) * bevelRadius;
var axisx = new float3(1f, 0f, 0f);
var axisy = new float3(0f, 1f, 0f);
var axisz = new float3(0f, 0f, 1f);
// Since the geometry is transformed by Handles.matrix during rendering, we transform the camera position
// by the inverse matrix so that the two-shaded wireframe will have the proper orientation.
var invMatrix = Handles.inverseMatrix;
cameraPosition = invMatrix.MultiplyPoint(cameraPosition);
cameraForward = invMatrix.MultiplyVector(cameraForward);
var cameraOrtho = Camera.current == null || Camera.current.orthographic;
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, -1f), quaternion.LookRotation(-axisz, axisy), cameraPosition, cameraForward,
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, 1f), quaternion.LookRotation(-axisx, axisy), cameraPosition, cameraForward,
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, 1f), quaternion.LookRotation(axisz, axisy), cameraPosition, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, -1f), quaternion.LookRotation(axisx, axisy), cameraPosition, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, -1f), quaternion.LookRotation(-axisx, -axisy), cameraPosition, cameraForward,
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, 1f), quaternion.LookRotation(axisz, -axisy), cameraPosition, cameraForward, cameraOrth
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, 1f), quaternion.LookRotation(axisx, -axisy), cameraPosition, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, -1f), quaternion.LookRotation(-axisz, -axisy), cameraPosition, cameraForward, cameraOrth
for (int i = 0; i < s_Corners.Length; i++)
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[i], true);
// Draw the horizon edges between the corners
for (int upA = 3, upB = 0; upB < 4; upA = upB, upB++)
{
int dnA = upA + 4;
int dnB = upB + 4;
if (s_Corners[upA].splitAxis[0].z && s_Corners[upB].splitAxis[1].x) Handles.DrawLine(s_Corners[upA].points[0], s_Corners[upB].points[1]);
if (s_Corners[upA].splitAxis[1].z && s_Corners[upB].splitAxis[0].x) Handles.DrawLine(s_Corners[upA].points[1], s_Corners[upB].points[0]);
if (s_Corners[dnA].splitAxis[0].x && s_Corners[dnB].splitAxis[1].z) Handles.DrawLine(s_Corners[dnA].points[0], s_Corners[dnB].points[1]);
if (s_Corners[dnA].splitAxis[1].x && s_Corners[dnB].splitAxis[0].z) Handles.DrawLine(s_Corners[dnA].points[1], s_Corners[dnB].points[0]);
if (s_Corners[dnA].splitAxis[0].y && s_Corners[upA].splitAxis[1].y) Handles.DrawLine(s_Corners[dnA].points[0], s_Corners[upA].points[1]);
if (s_Corners[dnA].splitAxis[1].y && s_Corners[upA].splitAxis[0].y) Handles.DrawLine(s_Corners[dnA].points[1], s_Corners[upA].points[0]);
}
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/BeveledCylinderBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class BeveledCylinderBoundsHandle : PrimitiveBoundsHandle
{
public float bevelRadius
{
get => math.min(m_BevelRadius, math.cmin(GetSize()) * 0.5f);
set
{
if (!m_IsDragging)
m_BevelRadius = math.max(0f, value);
}
}
m_SideCount = value;
// Since the geometry is transformed by Handles.matrix during rendering, we transform the camera position
// by the inverse matrix so that the two-shaded wireframe will have the proper orientation.
var invMatrix = Handles.inverseMatrix;
cameraCenter = invMatrix.MultiplyPoint(cameraCenter);
cameraForward = invMatrix.MultiplyVector(cameraForward);
var cameraOrtho = Camera.current != null && Camera.current.orthographic;
t = (m_SideCount - 1) * angleStep;
var xyAngle1 = new float3(math.cos(t), math.sin(t), 0f);
var sideways1 = new float3(math.cos(t + kHalfPI - halfAngleStep), math.sin(t + kHalfPI - halfAngleStep), 0f);
var direction1 = new float3(math.cos(t + halfAngleStep), math.sin(t + halfAngleStep), 0f);
var bevelGreaterThanZero = bevelRadius > 0f;
var bevelLessThanCylinderRadius = bevelRadius < radius;
for (var i = 0; i < m_SideCount; ++i)
{
t = i * angleStep;
var xyAngle2 = new float3(math.cos(t), math.sin(t), 0f);
var sideways2 = new float3(math.cos(t + kHalfPI - halfAngleStep), math.sin(t + kHalfPI - halfAngleStep), 0f);
var direction2 = new float3(math.cos(t + halfAngleStep), math.sin(t + halfAngleStep), 0f);
var cornerIndex0 = i;
var cornerIndex1 = i + m_SideCount;
{
var orientation = quaternion.LookRotation(xyAngle2, up);
var cornerNormal = math.normalize(math.mul(orientation, new float3(0f, 1f, 1f)));
PhysicsBoundsHandleUtility.CalculateCornerHorizon(top2,
new float3x3(direction1, up, direction2),
cornerNormal, cameraCenter, cameraForward, cameraOrtho,
bevelRadius, out m_Corners[cornerIndex0]);
}
{
var orientation = quaternion.LookRotation(xyAngle2, -up);
var cornerNormal = math.normalize(math.mul(orientation, new float3(0f, 1f, 1f)));
PhysicsBoundsHandleUtility.CalculateCornerHorizon(bottom2,
new float3x3(direction2, -up, direction1),
cornerNormal, cameraCenter, cameraForward, cameraOrtho,
bevelRadius, out m_Corners[cornerIndex1]);
}
}
direction1 = direction2;
sideways1 = sideways2;
xyAngle0 = xyAngle1;
xyAngle1 = xyAngle2;
}
if (bevelGreaterThanZero)
{
Handles.color = frontfacedColor;
for (int a = m_SideCount - 1, b = 0; b < m_SideCount; a = b, ++b)
{
var up0 = a;
var dn0 = a + m_SideCount;
var up1 = b;
var dn1 = b + m_SideCount;
namespace Unity.Physics.Editor
{
abstract class BaseDrawer : PropertyDrawer
{
protected abstract bool IsCompatible(SerializedProperty property);
EditorGUI.EndProperty();
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/EulerAnglesDrawer.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(EulerAngles))]
class EulerAnglesDrawer : BaseDrawer
{
protected override bool IsCompatible(SerializedProperty property) => true;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var value = property.FindPropertyRelative(nameof(EulerAngles.Value));
return EditorGUI.GetPropertyHeight(value);
}
protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)
{
var value = property.FindPropertyRelative(nameof(EulerAngles.Value));
EditorGUI.PropertyField(position, value, label, true);
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/ExpandChildrenDrawer.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(ExpandChildrenAttribute))]
class ExpandChildrenDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
property.isExpanded = true;
return EditorGUI.GetPropertyHeight(property)
- EditorGUIUtility.standardVerticalSpacing
- EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var endProperty = property.GetEndProperty();
var childProperty = property.Copy();
childProperty.NextVisible(true);
while (!SerializedProperty.EqualContents(childProperty, endProperty))
{
position.height = EditorGUI.GetPropertyHeight(childProperty);
OnChildPropertyGUI(position, childProperty);
position.y += position.height + EditorGUIUtility.standardVerticalSpacing;
childProperty.NextVisible(false);
}
}
protected virtual void OnChildPropertyGUI(Rect position, SerializedProperty childProperty)
{
EditorGUI.PropertyField(position, childProperty, true);
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/PhysicsMaterialCoefficientDrawer.cs
using System;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(PhysicsMaterialCoefficient))]
class PhysicsMaterialCoefficientDrawer : BaseDrawer
{
static class Styles
{
public const float PopupWidth = 100f;
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) =>
EditorGUIUtility.singleLineHeight;
protected override bool IsCompatible(SerializedProperty property) => true;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(PhysicsMaterialProperties))]
class PhysicsMaterialPropertiesDrawer : BaseDrawer
{
static class Content
{
public static readonly GUIContent AdvancedGroupFoldout = EditorGUIUtility.TrTextContent("Advanced");
public static readonly GUIContent BelongsToLabel = EditorGUIUtility.TrTextContent(
"Belongs To",
"Specifies the categories to which this object belongs."
);
public static readonly GUIContent CollidesWithLabel = EditorGUIUtility.TrTextContent(
"Collides With",
"Specifies the categories of objects with which this object will collide, " +
"or with which it will raise events if intersecting a trigger."
);
public static readonly GUIContent CollisionFilterGroupFoldout =
EditorGUIUtility.TrTextContent("Collision Filter");
public static readonly GUIContent CustomFlagsLabel =
EditorGUIUtility.TrTextContent("Custom Tags", "Specify custom tags to read at run-time.");
public static readonly GUIContent FrictionLabel = EditorGUIUtility.TrTextContent(
"Friction",
"Specifies how resistant the body is to motion when sliding along other surfaces, " +
"as well as what value should be used when colliding with an object that has a different value."
);
public static readonly GUIContent RestitutionLabel = EditorGUIUtility.TrTextContent(
"Restitution",
"Specifies how bouncy the object will be when colliding with other surfaces, " +
"as well as what value should be used when colliding with an object that has a different value."
);
public static readonly GUIContent CollisionResponseLabel = EditorGUIUtility.TrTextContent(
"Collision Response",
"Specifies whether the shape should collide normally, raise trigger events when intersecting other shapes, " +
"collide normally and raise notifications of collision events with other shapes, " +
"or completely ignore collisions (but still move and intercept queries)."
);
}
void FindToggleAndValueProperties(
SerializedProperty property, SerializedProperty templateValueProperty, string relativePath,
out SerializedProperty toggle, out SerializedProperty value
)
{
var relative = property.FindPropertyRelative(relativePath);
toggle = relative.FindPropertyRelative("m_Override");
value = toggle.boolValue || templateValueProperty == null
? relative.FindPropertyRelative("m_Value")
: templateValueProperty.FindPropertyRelative(relativePath).FindPropertyRelative("m_Value");
}
// m_BelongsTo, m_CollidesWith
var group = property.FindPropertyRelative(k_CollisionFilterGroupKey);
if (group.isExpanded)
height += 2f * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
// m_CustomTags
group = property.FindPropertyRelative(k_AdvancedGroupKey);
if (group.isExpanded)
height += (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
// m_Template
if (property.FindPropertyRelative("m_SupportsTemplate").boolValue)
height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
// m_Friction, m_Restitution
FindToggleAndValueProperties(property, templateValueProperty, "m_CollisionResponse", out _, out var collisionResponse);
// Check if regular collider
CollisionResponsePolicy collisionResponseEnum = (CollisionResponsePolicy)collisionResponse.intValue;
if (collisionResponseEnum == CollisionResponsePolicy.Collide ||
collisionResponseEnum == CollisionResponsePolicy.CollideRaiseCollisionEvents)
height += 2f * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
return height;
}
EditorGUI.BeginDisabledGroup(!toggle.boolValue);
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
EditorGUI.PropertyField(new Rect(position) { xMin = togglePosition.xMax }, value, GUIContent.none, true);
EditorGUI.indentLevel = indent;
EditorGUI.EndDisabledGroup();
}
else
{
EditorGUI.PropertyField(position, value, label, true);
}
}
--EditorGUI.indentLevel;
}
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/SoftRangeDrawer.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(SoftRangeAttribute))]
class SoftRangeDrawer : BaseDrawer
{
protected override bool IsCompatible(SerializedProperty property)
{
return property.propertyType == SerializedPropertyType.Float;
}
protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)
{
var attr = attribute as SoftRangeAttribute;
EditorGUIControls.SoftSlider(
position, label, property, attr.SliderMin, attr.SliderMax, attr.TextFieldMin, attr.TextFieldMax
);
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/TagsDrawer.cs
using System.Collections.Generic;
using System.Linq;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
abstract class TagsDrawer<T> : PropertyDrawer where T : ScriptableObject, ITagNames
{
static class Styles
{
public static readonly string EverythingName = L10n.Tr("Everything");
public static readonly string MixedName = L10n.Tr("Mixed...");
public static readonly string NothingName = L10n.Tr("Nothing");
string[] GetOptions()
{
if (m_Options != null)
return m_Options;
var guids = AssetDatabase.FindAssets($"t:{typeof(T).Name}");
m_NamesAssets = guids
.Select(AssetDatabase.GUIDToAssetPath)
.Select(AssetDatabase.LoadAssetAtPath<T>)
.Where(c => c != null)
.ToArray();
m_Options = m_NamesAssets.FirstOrDefault()?.TagNames.ToArray() ?? DefaultOptions;
for (var i = 0; i < m_Options.Length; ++i)
{
if (string.IsNullOrEmpty(m_Options[i]))
m_Options[i] = DefaultOptions[i];
string[] m_Options;
T[] m_NamesAssets;
// TODO: remove when all usages of bool[] are migrated
SerializedProperty GetFirstChildProperty(SerializedProperty property)
{
if (!string.IsNullOrEmpty(FirstChildPropertyPath))
return property.FindPropertyRelative(FirstChildPropertyPath);
var sp = property.Copy();
sp.NextVisible(true);
return sp;
}
var value = 0;
var everything = 0;
var sp = GetFirstChildProperty(property);
for (int i = 0, count = MaxNumCategories; i < count; ++i)
{
EditorGUI.showMixedValue |= sp.hasMultipleDifferentValues;
value |= sp.boolValue ? 1 << i : 0;
everything |= 1 << i;
sp.NextVisible(false);
}
// in case size is smaller than 32
if (value == everything)
value = ~0;
var options = GetOptions();
if (
EditorGUI.DropdownButton(
controlPosition,
EditorGUIUtility.TrTempContent(GetButtonLabel(value, options)),
FocusType.Passive,
EditorStyles.popup
)
)
{
var menu = new GenericMenu();
menu.AddItem(
new GUIContent(Styles.NothingName),
value == 0,
() =>
{
sp = GetFirstChildProperty(property);
for (int i = 0, count = MaxNumCategories; i < count; ++i)
{
sp.boolValue = false;
sp.NextVisible(false);
}
sp.serializedObject.ApplyModifiedProperties();
}
);
menu.AddItem(
new GUIContent(Styles.EverythingName),
value == ~0,
() =>
{
sp = GetFirstChildProperty(property);
for (int i = 0, count = MaxNumCategories; i < count; ++i)
{
sp.boolValue = true;
sp.NextVisible(false);
}
sp.serializedObject.ApplyModifiedProperties();
}
);
for (var option = 0; option < options.Length; ++option)
{
var callbackValue = option;
menu.AddItem(
EditorGUIUtility.TrTextContent(options[option]),
((1 << option) & value) != 0,
args =>
{
var changedBitAndValue = (KeyValuePair<int, bool>)args;
sp = GetFirstChildProperty(property);
for (int i = 0, count = changedBitAndValue.Key; i < count; ++i)
sp.NextVisible(false);
sp.boolValue = changedBitAndValue.Value;
sp.serializedObject.ApplyModifiedProperties();
},
new KeyValuePair<int, bool>(callbackValue, ((1 << option) & value) == 0)
);
}
menu.AddSeparator(string.Empty);
menu.AddItem(
EditorGUIUtility.TrTempContent($"Edit {ObjectNames.NicifyVariableName(typeof(T).Name)}"),
false,
() =>
{
if (m_NamesAssets.Length > 0)
Selection.activeObject = m_NamesAssets[0];
else
{
var assetPath = AssetDatabase.GenerateUniqueAssetPath($"Assets/{typeof(T).Name}.asset");
AssetDatabase.CreateAsset(ScriptableObject.CreateInstance<T>(), assetPath);
Selection.activeObject = AssetDatabase.LoadAssetAtPath<T>(assetPath);
m_Options = null;
}
}
);
menu.DropDown(controlPosition);
}
EditorGUI.showMixedValue = showMixed;
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
if (m_NamesAssets?.Length > 1)
{
var id = GUIUtility.GetControlID(FocusType.Passive);
if (Event.current.type == EventType.Repaint)
{
position.width = EditorGUIUtility.singleLineHeight;
position.x = controlPosition.xMax + EditorGUIUtility.standardVerticalSpacing;
Styles.MultipleAssetsWarning.tooltip = string.Format(
Styles.MultipleAssetsTooltip,
ObjectNames.NicifyVariableName(typeof(T).Name),
m_NamesAssets.FirstOrDefault(n => n != null)?.name
);
GUIStyle.none.Draw(position, Styles.MultipleAssetsWarning, id);
}
}
}
}
[CustomPropertyDrawer(typeof(CustomPhysicsBodyTags))]
class CustomBodyTagsDrawer : TagsDrawer<CustomPhysicsBodyTagNames>
{
protected override string DefaultCategoryName => "Custom Physics Body Tag";
protected override int MaxNumCategories => 8;
}
[CustomPropertyDrawer(typeof(CustomPhysicsMaterialTags))]
class CustomMaterialTagsDrawer : TagsDrawer<CustomPhysicsMaterialTagNames>
{
protected override string DefaultCategoryName => "Custom Physics Material Tag";
protected override int MaxNumCategories => 8;
}
[CustomPropertyDrawer(typeof(PhysicsCategoryTags))]
class PhysicsCategoryTagsDrawer : TagsDrawer<PhysicsCategoryNames>
{
protected override string DefaultCategoryName => "Physics Category";
protected override int MaxNumCategories => 32;
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/EditorGUIControls.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[InitializeOnLoad]
static class EditorGUIControls
{
static EditorGUIControls()
{
if (k_SoftSlider == null)
Debug.LogException(new MissingMemberException("Could not find expected signature of EditorGUI.Slider() for soft slider."));
}
static class Styles
{
public static readonly string CompatibilityWarning = L10n.Tr("Not compatible with {0}.");
}
public static void DisplayCompatibilityWarning(Rect position, GUIContent label, string incompatibleType)
{
EditorGUI.HelpBox(
EditorGUI.PrefixLabel(position, label),
string.Format(Styles.CompatibilityWarning, incompatibleType),
MessageType.Error
);
}
static readonly MethodInfo k_SoftSlider = typeof(EditorGUI).GetMethod(
"Slider",
BindingFlags.Static | BindingFlags.NonPublic,
null,
new[]
{
typeof(Rect), // position
typeof(GUIContent), // label
typeof(float), // value
typeof(float), // sliderMin
typeof(float), // sliderMax
typeof(float), // textFieldMin
typeof(float) // textFieldMax
},
Array.Empty<ParameterModifier>()
);
static readonly object[] k_SoftSliderArgs = new object[7];
namespace Unity.Physics.Editor
{
static class SceneViewUtility
{
static class Styles
{
public static readonly GUIStyle ProgressBarTrack = new GUIStyle
{
fixedHeight = 4f,
normal = new GUIStyleState { background = Texture2D.whiteTexture }
};
public static readonly GUIStyle ProgressBarIndicator = new GUIStyle
{
fixedHeight = 4f,
normal = new GUIStyleState { background = Texture2D.whiteTexture }
};
public static readonly GUIStyle SceneViewStatusMessage = new GUIStyle("NotificationBackground")
{
fontSize = EditorStyles.label.fontSize
};
static Styles() => SceneViewStatusMessage.padding = SceneViewStatusMessage.border;
}
namespace Unity.Physics.Editor
{
static class StatusMessageUtility
{
public static MessageType GetHierarchyStatusMessage(IReadOnlyList<UnityObject> targets, out string statusMessage)
{
statusMessage = string.Empty;
if (targets.Count == 0)
return MessageType.None;
var numChildTargets = 0;
foreach (Component c in targets)
{
// hierarchy roots and leaf shapes do not emit a message
if (
c == null
|| c.transform.parent == null
|| PhysicsShapeExtensions.GetPrimaryBody(c.gameObject) != c.gameObject
)
continue;
if (matrixStates.Contains(MatrixState.NonUniformScale))
{
statusMessage = L10n.Tr(
matrixStates.Count == 1
? "Target has non-uniform scale. Shape data will be transformed during conversion in order to bake scale into the run-time format."
: "One or more targets has non-uniform scale. Shape data will be transformed during conversion in order to bake scale into the run-time format."
);
return MessageType.Warning;
}
return MessageType.None;
}
}
}
Tutorial/Assets/Scripts/AIController.cs
using System;
using Unity.Entities;
using Unity.Physics;
using Unity.Physics.Authoring;
[Serializable]
public struct AIController : IComponentData
{
public float DetectionDistance;
public PhysicsCategoryTags DetectionFilter;
}
Tutorial/Assets/Scripts/AIControllerAuthoring.cs
using UnityEngine;
using Unity.Entities;
// If it has a character component but no AIController component, that means it's a human player character
if (SystemAPI.HasComponent<ThirdPersonCharacterComponent>(hitEntity) && !SystemAPI.HasComponent<AIController>(hitEntity))
{
selectedTarget = hitEntity;
break; // early out
}
}
// In the character control component, set a movement vector that will make the ai character move towards the selected target
if (selectedTarget != Entity.Null)
{
characterControl.ValueRW.MoveVector = math.normalizesafe(SystemAPI.GetComponent<LocalTransform>(selectedTarget).Position - localTransform.Position);
}
else
{
characterControl.ValueRW.MoveVector = float3.zero;
}
}
}
}
Tutorial/Assets/Scripts/CharacterFrictionSurface.cs
using System;
using Unity.Entities;
[Serializable]
public struct CharacterFrictionSurface : IComponentData
{
public float VelocityFactor;
}
Tutorial/Assets/Scripts/CharacterFrictionSurfaceAuthoring.cs
using UnityEngine;
using Unity.Entities;
public class CharacterFrictionSurfaceAuthoring : MonoBehaviour
{
public float VelocityFactor;
class Baker : Baker<CharacterFrictionSurfaceAuthoring>
{
public override void Bake(CharacterFrictionSurfaceAuthoring authoring)
{
AddComponent(GetEntity(TransformUsageFlags.None), new CharacterFrictionSurface { VelocityFactor = authoring.VelocityFactor });
}
}
}
Tutorial/Assets/Scripts/CharacterFrictionSurfaceSystem.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
}
}
Tutorial/Assets/Scripts/JumpPad.cs
using Unity.Entities;
using Unity.Mathematics;
[Serializable]
public struct MovingPlatform : IComponentData
{
public float3 TranslationAxis;
public float TranslationAmplitude;
public float TranslationSpeed;
public float3 RotationAxis;
public float RotationSpeed;
[HideInInspector]
public bool IsInitialized;
[HideInInspector]
public float3 OriginalPosition;
[HideInInspector]
public quaternion OriginalRotation;
}
Tutorial/Assets/Scripts/MovingPlatformAuthoring.cs
using Unity.Entities;
using UnityEngine;
[assembly: InternalsVisibleTo("Unity.Physics.Custom.Editor")]
[assembly: InternalsVisibleTo("Unity.Physics.Custom.EditModeTests")]
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/BaseBodyPairConnector.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
[RequireComponent(typeof(PhysicsBodyAuthoring))]
public abstract class BaseBodyPairConnector : MonoBehaviour
{
public PhysicsBodyAuthoring LocalBody => GetComponent<PhysicsBodyAuthoring>();
public PhysicsBodyAuthoring ConnectedBody;
void OnEnable()
{
// included so tick box appears in Editor
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/CustomPhysicsMaterialTagNames.cs
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Serialization;
namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Custom Physics Material Tag Names", fileName = "Custom Material Tag Names", order = 506)]
public sealed partial class CustomPhysicsMaterialTagNames : ScriptableObject, ITagNames
{
CustomPhysicsMaterialTagNames() {}
public IReadOnlyList<string> TagNames => m_TagNames;
[SerializeField]
[FormerlySerializedAs("m_FlagNames")]
string[] m_TagNames = Enumerable.Range(0, 8).Select(i => string.Empty).ToArray();
void OnValidate()
{
if (m_TagNames.Length != 8)
Array.Resize(ref m_TagNames, 8);
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/CustomPhysicsMaterialTags.cs
using System;
using Unity.Mathematics;
namespace Unity.Physics.Authoring
{
[Serializable]
public struct CustomPhysicsMaterialTags : IEquatable<CustomPhysicsMaterialTags>
{
public static CustomPhysicsMaterialTags Everything => new CustomPhysicsMaterialTags { Value = unchecked((byte)~0) };
public static CustomPhysicsMaterialTags Nothing => new CustomPhysicsMaterialTags { Value = 0 };
public bool Tag00;
public bool Tag01;
public bool Tag02;
public bool Tag03;
public bool Tag04;
public bool Tag05;
public bool Tag06;
public bool Tag07;
internal bool this[int i]
{
get
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 7), nameof(i));
switch (i)
{
case 0: return Tag00;
case 1: return Tag01;
case 2: return Tag02;
case 3: return Tag03;
case 4: return Tag04;
case 5: return Tag05;
case 6: return Tag06;
case 7: return Tag07;
default: return default;
}
}
set
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 7), nameof(i));
switch (i)
{
case 0: Tag00 = value; break;
case 1: Tag01 = value; break;
case 2: Tag02 = value; break;
case 3: Tag03 = value; break;
case 4: Tag04 = value; break;
case 5: Tag05 = value; break;
case 6: Tag06 = value; break;
case 7: Tag07 = value; break;
}
}
}
public byte Value
{
get
{
var result = 0;
result |= (Tag00 ? 1 : 0) << 0;
result |= (Tag01 ? 1 : 0) << 1;
result |= (Tag02 ? 1 : 0) << 2;
result |= (Tag03 ? 1 : 0) << 3;
result |= (Tag04 ? 1 : 0) << 4;
result |= (Tag05 ? 1 : 0) << 5;
result |= (Tag06 ? 1 : 0) << 6;
result |= (Tag07 ? 1 : 0) << 7;
return (byte)result;
}
set
{
Tag00 = (value & (1 << 0)) != 0;
Tag01 = (value & (1 << 1)) != 0;
Tag02 = (value & (1 << 2)) != 0;
Tag03 = (value & (1 << 3)) != 0;
Tag04 = (value & (1 << 4)) != 0;
Tag05 = (value & (1 << 5)) != 0;
Tag06 = (value & (1 << 6)) != 0;
Tag07 = (value & (1 << 7)) != 0;
}
}
public bool Equals(CustomPhysicsMaterialTags other) => Value == other.Value;
public override bool Equals(object obj) => obj is CustomPhysicsMaterialTags other && Equals(other);
namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Physics Category Names", fileName = "Physics Category Names", order = 507)]
public sealed class PhysicsCategoryNames : ScriptableObject, ITagNames
{
PhysicsCategoryNames() {}
void OnValidate()
{
if (m_CategoryNames.Length != 32)
Array.Resize(ref m_CategoryNames, 32);
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsCategoryTags.cs
using System;
using Unity.Mathematics;
namespace Unity.Physics.Authoring
{
[Serializable]
public struct PhysicsCategoryTags : IEquatable<PhysicsCategoryTags>
{
public static PhysicsCategoryTags Everything => new PhysicsCategoryTags { Value = unchecked((uint)~0) };
public static PhysicsCategoryTags Nothing => new PhysicsCategoryTags { Value = 0 };
public bool Category00;
public bool Category01;
public bool Category02;
public bool Category03;
public bool Category04;
public bool Category05;
public bool Category06;
public bool Category07;
public bool Category08;
public bool Category09;
public bool Category10;
public bool Category11;
public bool Category12;
public bool Category13;
public bool Category14;
public bool Category15;
public bool Category16;
public bool Category17;
public bool Category18;
public bool Category19;
public bool Category20;
public bool Category21;
public bool Category22;
public bool Category23;
public bool Category24;
public bool Category25;
public bool Category26;
public bool Category27;
public bool Category28;
public bool Category29;
public bool Category30;
public bool Category31;
internal bool this[int i]
{
get
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 31), nameof(i));
switch (i)
{
case 0: return Category00;
case 1: return Category01;
case 2: return Category02;
case 3: return Category03;
case 4: return Category04;
case 5: return Category05;
case 6: return Category06;
case 7: return Category07;
case 8: return Category08;
case 9: return Category09;
case 10: return Category10;
case 11: return Category11;
case 12: return Category12;
case 13: return Category13;
case 14: return Category14;
case 15: return Category15;
case 16: return Category16;
case 17: return Category17;
case 18: return Category18;
case 19: return Category19;
case 20: return Category20;
case 21: return Category21;
case 22: return Category22;
case 23: return Category23;
case 24: return Category24;
case 25: return Category25;
case 26: return Category26;
case 27: return Category27;
case 28: return Category28;
case 29: return Category29;
case 30: return Category30;
case 31: return Category31;
default: return default;
}
}
set
{
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 31), nameof(i));
switch (i)
{
case 0: Category00 = value; break;
case 1: Category01 = value; break;
case 2: Category02 = value; break;
case 3: Category03 = value; break;
case 4: Category04 = value; break;
case 5: Category05 = value; break;
case 6: Category06 = value; break;
case 7: Category07 = value; break;
case 8: Category08 = value; break;
case 9: Category09 = value; break;
case 10: Category10 = value; break;
case 11: Category11 = value; break;
case 12: Category12 = value; break;
case 13: Category13 = value; break;
case 14: Category14 = value; break;
case 15: Category15 = value; break;
case 16: Category16 = value; break;
case 17: Category17 = value; break;
case 18: Category18 = value; break;
case 19: Category19 = value; break;
case 20: Category20 = value; break;
case 21: Category21 = value; break;
case 22: Category22 = value; break;
case 23: Category23 = value; break;
case 24: Category24 = value; break;
case 25: Category25 = value; break;
case 26: Category26 = value; break;
case 27: Category27 = value; break;
case 28: Category28 = value; break;
case 29: Category29 = value; break;
case 30: Category30 = value; break;
case 31: Category31 = value; break;
}
}
}
public uint Value
{
get
{
var result = 0;
result |= (Category00 ? 1 : 0) << 0;
result |= (Category01 ? 1 : 0) << 1;
result |= (Category02 ? 1 : 0) << 2;
result |= (Category03 ? 1 : 0) << 3;
result |= (Category04 ? 1 : 0) << 4;
result |= (Category05 ? 1 : 0) << 5;
result |= (Category06 ? 1 : 0) << 6;
result |= (Category07 ? 1 : 0) << 7;
result |= (Category08 ? 1 : 0) << 8;
result |= (Category09 ? 1 : 0) << 9;
result |= (Category10 ? 1 : 0) << 10;
result |= (Category11 ? 1 : 0) << 11;
result |= (Category12 ? 1 : 0) << 12;
result |= (Category13 ? 1 : 0) << 13;
result |= (Category14 ? 1 : 0) << 14;
result |= (Category15 ? 1 : 0) << 15;
result |= (Category16 ? 1 : 0) << 16;
result |= (Category17 ? 1 : 0) << 17;
result |= (Category18 ? 1 : 0) << 18;
result |= (Category19 ? 1 : 0) << 19;
result |= (Category20 ? 1 : 0) << 20;
result |= (Category21 ? 1 : 0) << 21;
result |= (Category22 ? 1 : 0) << 22;
result |= (Category23 ? 1 : 0) << 23;
result |= (Category24 ? 1 : 0) << 24;
result |= (Category25 ? 1 : 0) << 25;
result |= (Category26 ? 1 : 0) << 26;
result |= (Category27 ? 1 : 0) << 27;
result |= (Category28 ? 1 : 0) << 28;
result |= (Category29 ? 1 : 0) << 29;
result |= (Category30 ? 1 : 0) << 30;
result |= (Category31 ? 1 : 0) << 31;
return unchecked((uint)result);
}
set
{
Category00 = (value & (1 << 0)) != 0;
Category01 = (value & (1 << 1)) != 0;
Category02 = (value & (1 << 2)) != 0;
Category03 = (value & (1 << 3)) != 0;
Category04 = (value & (1 << 4)) != 0;
Category05 = (value & (1 << 5)) != 0;
Category06 = (value & (1 << 6)) != 0;
Category07 = (value & (1 << 7)) != 0;
Category08 = (value & (1 << 8)) != 0;
Category09 = (value & (1 << 9)) != 0;
Category10 = (value & (1 << 10)) != 0;
Category11 = (value & (1 << 11)) != 0;
Category12 = (value & (1 << 12)) != 0;
Category13 = (value & (1 << 13)) != 0;
Category14 = (value & (1 << 14)) != 0;
Category15 = (value & (1 << 15)) != 0;
Category16 = (value & (1 << 16)) != 0;
Category17 = (value & (1 << 17)) != 0;
Category18 = (value & (1 << 18)) != 0;
Category19 = (value & (1 << 19)) != 0;
Category20 = (value & (1 << 20)) != 0;
Category21 = (value & (1 << 21)) != 0;
Category22 = (value & (1 << 22)) != 0;
Category23 = (value & (1 << 23)) != 0;
Category24 = (value & (1 << 24)) != 0;
Category25 = (value & (1 << 25)) != 0;
Category26 = (value & (1 << 26)) != 0;
Category27 = (value & (1 << 27)) != 0;
Category28 = (value & (1 << 28)) != 0;
Category29 = (value & (1 << 29)) != 0;
Category30 = (value & (1 << 30)) != 0;
Category31 = (value & (1 << 31)) != 0;
}
}
public bool Equals(PhysicsCategoryTags other) => Value == other.Value;
public override bool Equals(object obj) => obj is PhysicsCategoryTags other && Equals(other);
[Serializable]
public struct PhysicsMaterialCoefficient
{
[SoftRange(0f, 1f, TextFieldMax = float.MaxValue)]
public float Value;
public Material.CombinePolicy CombineMode;
}
public T Value
{
get => m_Value;
set
{
m_Value = value;
Override = true;
}
}
[SerializeField]
T m_Value;
[Serializable]
class OverridableCollisionResponse : OverridableValue<CollisionResponsePolicy> {}
[Serializable]
class OverridableMaterialCoefficient : OverridableValue<PhysicsMaterialCoefficient>
{
protected override void OnValidate(ref PhysicsMaterialCoefficient value) =>
value.Value = math.max(0f, value.Value);
}
[Serializable]
class OverridableCategoryTags : OverridableValue<PhysicsCategoryTags> {}
[Serializable]
class OverridableCustomMaterialTags : OverridableValue<CustomPhysicsMaterialTags> {}
[Serializable]
class PhysicsMaterialProperties : IInheritPhysicsMaterialProperties, ISerializationCallbackReceiver
{
public PhysicsMaterialProperties(bool supportsTemplate) => m_SupportsTemplate = supportsTemplate;
[SerializeField, HideInInspector]
bool m_SupportsTemplate;
public bool OverrideCollisionResponse { get => m_CollisionResponse.Override; set => m_CollisionResponse.Override = value; }
public CollisionResponsePolicy CollisionResponse
{
get => Get(m_CollisionResponse, m_Template == null ? null : m_Template?.CollisionResponse);
set => m_CollisionResponse.Value = value;
}
[SerializeField]
OverridableCollisionResponse m_CollisionResponse = new OverridableCollisionResponse
{
Value = CollisionResponsePolicy.Collide,
Override = false
};
public bool OverrideFriction { get => m_Friction.Override; set => m_Friction.Override = value; }
public PhysicsMaterialCoefficient Friction
{
get => Get(m_Friction, m_Template == null ? null : m_Template?.Friction);
set => m_Friction.Value = value;
}
[SerializeField]
OverridableMaterialCoefficient m_Friction = new OverridableMaterialCoefficient
{
Value = new PhysicsMaterialCoefficient { Value = 0.5f, CombineMode = Material.CombinePolicy.GeometricMean },
Override = false
};
public bool OverrideRestitution { get => m_Restitution.Override; set => m_Restitution.Override = value; }
public PhysicsMaterialCoefficient Restitution
{
get => Get(m_Restitution, m_Template == null ? null : m_Template?.Restitution);
set => m_Restitution.Value = value;
}
[SerializeField]
OverridableMaterialCoefficient m_Restitution = new OverridableMaterialCoefficient
{
Value = new PhysicsMaterialCoefficient { Value = 0f, CombineMode = Material.CombinePolicy.Maximum },
Override = false
};
public bool OverrideBelongsTo { get => m_BelongsToCategories.Override; set => m_BelongsToCategories.Override = value; }
public PhysicsCategoryTags BelongsTo
{
get => Get(m_BelongsToCategories, m_Template == null ? null : m_Template?.BelongsTo);
set => m_BelongsToCategories.Value = value;
}
[SerializeField]
OverridableCategoryTags m_BelongsToCategories =
new OverridableCategoryTags { Value = PhysicsCategoryTags.Everything, Override = false };
public bool OverrideCollidesWith { get => m_CollidesWithCategories.Override; set => m_CollidesWithCategories.Override = value; }
public PhysicsCategoryTags CollidesWith
{
get => Get(m_CollidesWithCategories, m_Template == null ? null : m_Template?.CollidesWith);
set => m_CollidesWithCategories.Value = value;
}
[SerializeField]
OverridableCategoryTags m_CollidesWithCategories =
new OverridableCategoryTags { Value = PhysicsCategoryTags.Everything, Override = false };
public bool OverrideCustomTags { get => m_CustomMaterialTags.Override; set => m_CustomMaterialTags.Override = value; }
public CustomPhysicsMaterialTags CustomTags
{
get => Get(m_CustomMaterialTags, m_Template == null ? null : m_Template?.CustomTags);
set => m_CustomMaterialTags.Value = value;
}
[SerializeField]
OverridableCustomMaterialTags m_CustomMaterialTags =
new OverridableCustomMaterialTags { Value = default, Override = false };
material.m_SupportsTemplate = supportsTemplate;
if (!supportsTemplate)
{
material.m_Template = null;
material.m_CollisionResponse.Override = true;
material.m_Friction.Override = true;
material.m_Restitution.Override = true;
}
material.m_Friction.OnValidate();
material.m_Restitution.OnValidate();
}
[SerializeField]
int m_SerializedVersion = 0;
void ISerializationCallbackReceiver.OnBeforeSerialize() {}
void ISerializationCallbackReceiver.OnAfterDeserialize() => UpgradeVersionIfNecessary();
namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Physics Material Template", fileName = "Physics Material Template", order = 508)]
public sealed class PhysicsMaterialTemplate : ScriptableObject, IPhysicsMaterialProperties
{
PhysicsMaterialTemplate() {}
public CollisionResponsePolicy CollisionResponse { get => m_Value.CollisionResponse; set => m_Value.CollisionResponse = value; }
public PhysicsMaterialCoefficient Friction { get => m_Value.Friction; set => m_Value.Friction = value; }
public PhysicsMaterialCoefficient Restitution { get => m_Value.Restitution; set => m_Value.Restitution = value; }
public PhysicsCategoryTags BelongsTo { get => m_Value.BelongsTo; set => m_Value.BelongsTo = value; }
public PhysicsCategoryTags CollidesWith { get => m_Value.CollidesWith; set => m_Value.CollidesWith = value; }
public CustomPhysicsMaterialTags CustomTags { get => m_Value.CustomTags; set => m_Value.CustomTags = value; }
[SerializeField]
PhysicsMaterialProperties m_Value = new PhysicsMaterialProperties(false);
PhysicsBodyAuthoring() {}
public BodyMotionType MotionType { get => m_MotionType; set => m_MotionType = value; }
[SerializeField]
[Tooltip("Specifies whether the body should be fully physically simulated, moved directly, or fixed in place.")]
BodyMotionType m_MotionType;
public BodySmoothing Smoothing { get => m_Smoothing; set => m_Smoothing = value; }
[SerializeField]
[Tooltip("Specifies how this body's motion in its graphics representation should be smoothed when the rendering framerate is greater than the fixed step rate used by physics.")]
BodySmoothing m_Smoothing = BodySmoothing.None;
const float k_MinimumMass = 0.001f;
public float Mass
{
get => m_MotionType == BodyMotionType.Dynamic ? m_Mass : float.PositiveInfinity;
set => m_Mass = math.max(k_MinimumMass, value);
}
[SerializeField]
float m_Mass = 1.0f;
public float LinearDamping { get => m_LinearDamping; set => m_LinearDamping = math.max(0f, value); }
[SerializeField]
[Tooltip("This is applied to a body's linear velocity reducing it over time.")]
float m_LinearDamping = 0.01f;
public float AngularDamping { get => m_AngularDamping; set => m_AngularDamping = math.max(0f, value); }
[SerializeField]
[Tooltip("This is applied to a body's angular velocity reducing it over time.")]
float m_AngularDamping = 0.05f;
public float3 InitialLinearVelocity { get => m_InitialLinearVelocity; set => m_InitialLinearVelocity = value; }
[SerializeField]
[Tooltip("The initial linear velocity of the body in world space")]
float3 m_InitialLinearVelocity = float3.zero;
public float3 InitialAngularVelocity { get => m_InitialAngularVelocity; set => m_InitialAngularVelocity = value; }
[SerializeField]
[Tooltip("This represents the initial rotation speed around each axis in the local motion space of the body i.e. around the center of mass")]
float3 m_InitialAngularVelocity = float3.zero;
public float GravityFactor
{
get => m_MotionType == BodyMotionType.Dynamic ? m_GravityFactor : 0f;
set => m_GravityFactor = value;
}
[SerializeField]
[Tooltip("Scales the amount of gravity to apply to this body.")]
float m_GravityFactor = 1f;
public bool OverrideDefaultMassDistribution
{
#pragma warning disable 618
get => m_OverrideDefaultMassDistribution;
set => m_OverrideDefaultMassDistribution = value;
#pragma warning restore 618
}
[SerializeField]
[Tooltip("Default mass distribution is based on the shapes associated with this body.")]
bool m_OverrideDefaultMassDistribution;
public MassDistribution CustomMassDistribution
{
get => new MassDistribution
{
Transform = new RigidTransform(m_Orientation, m_CenterOfMass),
InertiaTensor =
m_MotionType == BodyMotionType.Dynamic ? m_InertiaTensor : new float3(float.PositiveInfinity)
};
set
{
m_CenterOfMass = value.Transform.pos;
m_Orientation.SetValue(value.Transform.rot);
m_InertiaTensor = value.InertiaTensor;
#pragma warning disable 618
m_OverrideDefaultMassDistribution = true;
#pragma warning restore 618
}
}
[SerializeField]
float3 m_CenterOfMass;
[SerializeField]
EulerAngles m_Orientation = EulerAngles.Default;
[SerializeField]
// Default value to solid unit sphere : https://en.wikipedia.org/wiki/List_of_moments_of_inertia
float3 m_InertiaTensor = new float3(2f / 5f);
public uint WorldIndex { get => m_WorldIndex; set => m_WorldIndex = value; }
[SerializeField]
[Tooltip("The index of the physics world this body belongs to. Default physics world has index 0.")]
uint m_WorldIndex = 0;
public CustomPhysicsBodyTags CustomTags { get => m_CustomTags; set => m_CustomTags = value; }
[SerializeField]
CustomPhysicsBodyTags m_CustomTags = CustomPhysicsBodyTags.Nothing;
void OnEnable()
{
// included so tick box appears in Editor
}
void OnValidate()
{
m_Mass = math.max(k_MinimumMass, m_Mass);
m_LinearDamping = math.max(m_LinearDamping, 0f);
m_AngularDamping = math.max(m_AngularDamping, 0f);
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/PhysicsShapeAuthoring.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using UnityMesh = UnityEngine.Mesh;
namespace Unity.Physics.Authoring
{
public sealed class UnimplementedShapeException : NotImplementedException
{
public UnimplementedShapeException(ShapeType shapeType)
: base($"Unknown shape type {shapeType} requires explicit implementation") {}
}
#if UNITY_2021_2_OR_NEWER
[Icon(k_IconPath)]
#endif
[AddComponentMenu("Entities/Physics/Physics Shape")]
public sealed class PhysicsShapeAuthoring : MonoBehaviour, IInheritPhysicsMaterialProperties, ISerializationCallbackReceiver
{
const string k_IconPath = "Packages/com.unity.physics/Unity.Physics.Editor/Editor Default Resources/Icons/d_BoxCollider@64.png";
PhysicsShapeAuthoring() {}
[Serializable]
struct CylindricalProperties
{
public float Height;
public float Radius;
[HideInInspector]
public int Axis;
}
[SerializeField]
[ExpandChildren]
CylindricalProperties m_Capsule = new CylindricalProperties { Height = 1f, Radius = 0.5f, Axis = 2 };
[SerializeField]
[ExpandChildren]
CylindricalProperties m_Cylinder = new CylindricalProperties { Height = 1f, Radius = 0.5f, Axis = 2 };
[SerializeField]
[Tooltip("How many sides the convex cylinder shape should have.")]
[Range(CylinderGeometry.MinSideCount, CylinderGeometry.MaxSideCount)]
int m_CylinderSideCount = 20;
[SerializeField]
float m_SphereRadius = 0.5f;
public ConvexHullGenerationParameters ConvexHullGenerationParameters => m_ConvexHullGenerationParameters;
[SerializeField]
[Tooltip(
"Specifies the minimum weight of a skinned vertex assigned to this shape and/or its transform children required for it to be included for automatic detection. " +
"A value of 0 will include all points with any weight assigned to this shape's hierarchy."
)]
[Range(0f, 1f)]
float m_MinimumSkinnedVertexWeight = 0.1f;
[SerializeField]
[ExpandChildren]
ConvexHullGenerationParameters m_ConvexHullGenerationParameters = ConvexHullGenerationParameters.Default.ToAuthoring();
// TODO: remove this accessor in favor of GetRawVertices() when blob data is serializable
internal UnityMesh CustomMesh => m_CustomMesh;
[SerializeField]
[Tooltip("If no custom mesh is specified, then one will be generated using this body's rendered meshes.")]
UnityMesh m_CustomMesh;
public bool ForceUnique { get => m_ForceUnique; set => m_ForceUnique = value; }
[SerializeField]
bool m_ForceUnique;
public PhysicsMaterialTemplate MaterialTemplate { get => m_Material.Template; set => m_Material.Template = value; }
PhysicsMaterialTemplate IInheritPhysicsMaterialProperties.Template
{
get => m_Material.Template;
set => m_Material.Template = value;
}
public bool OverrideCollisionResponse { get => m_Material.OverrideCollisionResponse; set => m_Material.OverrideCollisionResponse = value; }
public CollisionResponsePolicy CollisionResponse { get => m_Material.CollisionResponse; set => m_Material.CollisionResponse = value; }
public bool OverrideFriction { get => m_Material.OverrideFriction; set => m_Material.OverrideFriction = value; }
public PhysicsMaterialCoefficient Friction { get => m_Material.Friction; set => m_Material.Friction = value; }
public bool OverrideRestitution
{
get => m_Material.OverrideRestitution;
set => m_Material.OverrideRestitution = value;
}
public PhysicsMaterialCoefficient Restitution
{
get => m_Material.Restitution;
set => m_Material.Restitution = value;
}
public bool OverrideBelongsTo
{
get => m_Material.OverrideBelongsTo;
set => m_Material.OverrideBelongsTo = value;
}
void AppendMeshPropertiesToNativeBuffers(
float4x4 localToWorld, UnityMesh mesh, NativeList<float3> vertices, NativeList<int3> triangles, bool validate,
NativeList<HashableShapeInputs> inputs, HashSet<UnityMesh> meshAssets
)
{
if (mesh == null || validate && !mesh.IsValidForConversion(gameObject))
return;
var childToShape = math.mul(transform.worldToLocalMatrix, localToWorld);
AppendMeshPropertiesToNativeBuffers(childToShape, mesh, vertices, triangles, inputs, meshAssets);
}
internal static void AppendMeshPropertiesToNativeBuffers(
float4x4 childToShape, UnityMesh mesh, NativeList<float3> vertices, NativeList<int3> triangles,
NativeList<HashableShapeInputs> inputs, HashSet<UnityMesh> meshAssets
)
{
var offset = 0u;
#if UNITY_EDITOR
// TODO: when min spec is 2020.1, collect all meshes and their data via single Burst job rather than one at a time
using (var meshData = UnityEditor.MeshUtility.AcquireReadOnlyMeshData(mesh))
#else
using (var meshData = UnityMesh.AcquireReadOnlyMeshData(mesh))
#endif
{
if (vertices.IsCreated)
{
offset = (uint)vertices.Length;
var tmpVertices = new NativeArray<Vector3>(meshData[0].vertexCount, Allocator.Temp);
meshData[0].GetVertices(tmpVertices);
if (vertices.Capacity < vertices.Length + tmpVertices.Length)
vertices.Capacity = vertices.Length + tmpVertices.Length;
foreach (var v in tmpVertices)
vertices.Add(math.mul(childToShape, new float4(v, 1f)).xyz);
}
if (triangles.IsCreated)
{
switch (meshData[0].indexFormat)
{
case IndexFormat.UInt16:
var indices16 = meshData[0].GetIndexData<ushort>();
var numTriangles = indices16.Length / 3;
if (triangles.Capacity < triangles.Length + numTriangles)
triangles.Capacity = triangles.Length + numTriangles;
for (var sm = 0; sm < meshData[0].subMeshCount; ++sm)
{
var subMesh = meshData[0].GetSubMesh(sm);
for (int i = subMesh.indexStart, count = 0; count < subMesh.indexCount; i += 3, count += 3)
triangles.Add((int3) new uint3(offset + indices16[i], offset + indices16[i + 1], offset + indices16[i + 2]));
}
break;
case IndexFormat.UInt32:
var indices32 = meshData[0].GetIndexData<uint>();
numTriangles = indices32.Length / 3;
if (triangles.Capacity < triangles.Length + numTriangles)
triangles.Capacity = triangles.Length + numTriangles;
for (var sm = 0; sm < meshData[0].subMeshCount; ++sm)
{
var subMesh = meshData[0].GetSubMesh(sm);
for (int i = subMesh.indexStart, count = 0; count < subMesh.indexCount; i += 3, count += 3)
triangles.Add((int3) new uint3(offset + indices32[i], offset + indices32[i + 1], offset + indices32[i + 2]));
}
break;
}
}
}
if (inputs.IsCreated)
inputs.Add(HashableShapeInputs.FromMesh(mesh, childToShape));
meshAssets?.Add(mesh);
}
void UpdateCapsuleAxis()
{
var cmax = math.cmax(m_PrimitiveSize);
var cmin = math.cmin(m_PrimitiveSize);
if (cmin == cmax)
return;
m_Capsule.Axis = m_PrimitiveSize.GetMaxAxis();
}
void UpdateCylinderAxis() => m_Cylinder.Axis = m_PrimitiveSize.GetDeviantAxis();
m_ConvexHullGenerationParameters.BevelRadius = geometry.BevelRadius;
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetSphere(SphereGeometry geometry, quaternion orientation)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(orientation);
SetSphere(geometry, euler);
}
internal void SetSphere(SphereGeometry geometry)
{
SetSphere(geometry, m_PrimitiveOrientation);
}
internal void SetSphere(SphereGeometry geometry, EulerAngles orientation)
{
m_ShapeType = ShapeType.Sphere;
m_PrimitiveCenter = geometry.Center;
var radius = math.max(0f, geometry.Radius);
m_PrimitiveSize = new float3(2f * radius, 2f * radius, 2f * radius);
m_PrimitiveOrientation = orientation;
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetPlane(float3 center, float2 size, quaternion orientation)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(orientation);
SetPlane(center, size, euler);
}
internal void SetPlane(float3 center, float2 size, EulerAngles orientation)
{
m_ShapeType = ShapeType.Plane;
m_PrimitiveCenter = center;
m_PrimitiveOrientation = orientation;
m_PrimitiveSize = new float3(size.x, 0f, size.y);
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetConvexHull(
ConvexHullGenerationParameters hullGenerationParameters, float minimumSkinnedVertexWeight
)
{
m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
SetConvexHull(hullGenerationParameters);
}
public void SetConvexHull(ConvexHullGenerationParameters hullGenerationParameters, UnityMesh customMesh = null)
{
m_ShapeType = ShapeType.ConvexHull;
m_CustomMesh = customMesh;
hullGenerationParameters.OnValidate();
m_ConvexHullGenerationParameters = hullGenerationParameters;
}
public void SetMesh(UnityMesh mesh = null)
{
m_ShapeType = ShapeType.Mesh;
m_CustomMesh = mesh;
}
void OnEnable()
{
// included so tick box appears in Editor
}
const int k_LatestVersion = 1;
[SerializeField]
int m_SerializedVersion = 0;
void ISerializationCallbackReceiver.OnBeforeSerialize() {}
void ISerializationCallbackReceiver.OnAfterDeserialize() => UpgradeVersionIfNecessary();
#pragma warning disable 618
void UpgradeVersionIfNecessary()
{
if (m_SerializedVersion < k_LatestVersion)
{
// old data from version < 1 have been removed
if (m_SerializedVersion < 1)
m_SerializedVersion = 1;
}
}
#pragma warning restore 618
static void Validate(ref CylindricalProperties props)
{
props.Height = math.max(0f, props.Height);
props.Radius = math.max(0f, props.Radius);
}
void OnValidate()
{
UpgradeVersionIfNecessary();
m_PrimitiveSize = math.max(m_PrimitiveSize, new float3());
Validate(ref m_Capsule);
Validate(ref m_Cylinder);
switch (m_ShapeType)
{
case ShapeType.Box:
SetBox(GetBoxProperties(out var orientation), orientation);
break;
case ShapeType.Capsule:
SetCapsule(GetCapsuleProperties());
break;
case ShapeType.Cylinder:
SetCylinder(GetCylinderProperties(out orientation), orientation);
break;
case ShapeType.Sphere:
SetSphere(GetSphereProperties(out orientation), orientation);
break;
case ShapeType.Plane:
GetPlaneProperties(out var center, out var size2D, out orientation);
SetPlane(center, size2D, orientation);
break;
case ShapeType.ConvexHull:
case ShapeType.Mesh:
break;
default:
throw new UnimplementedShapeException(m_ShapeType);
}
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
m_CylinderSideCount =
math.clamp(m_CylinderSideCount, CylinderGeometry.MinSideCount, CylinderGeometry.MaxSideCount);
m_ConvexHullGenerationParameters.OnValidate();
PhysicsMaterialProperties.OnValidate(ref m_Material, true);
}
// matrix to transform point from shape space into world space
public float4x4 GetShapeToWorldMatrix() =>
new float4x4(Math.DecomposeRigidBodyTransform(transform.localToWorldMatrix));
// matrix to transform point from object's local transform matrix into shape space
internal float4x4 GetLocalToShapeMatrix() =>
math.mul(math.inverse(GetShapeToWorldMatrix()), transform.localToWorldMatrix);
internal unsafe void BakePoints(NativeArray<float3> points)
{
var localToShapeQuantized = GetLocalToShapeMatrix();
using (var aabb = new NativeArray<Aabb>(1, Allocator.TempJob))
{
new PhysicsShapeExtensions.GetAabbJob { Points = points, Aabb = aabb }.Run();
HashableShapeInputs.GetQuantizedTransformations(localToShapeQuantized, aabb[0], out localToShapeQuantized);
}
using (var bakedPoints = new NativeArray<float3>(points.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory))
{
new BakePointsJob
{
Points = points,
LocalToShape = localToShapeQuantized,
Output = bakedPoints
}.Schedule(points.Length, 16).Complete();
UnsafeUtility.MemCpy(points.GetUnsafePtr(), bakedPoints.GetUnsafePtr(), points.Length * UnsafeUtility.SizeOf<float3>());
}
}
[BurstCompile]
struct BakePointsJob : IJobParallelFor
{
[Collections.ReadOnly]
public NativeArray<float3> Points;
public float4x4 LocalToShape;
public NativeArray<float3> Output;
public void Execute(int index) => Output[index] = math.mul(LocalToShape, new float4(Points[index], 1f)).xyz;
}
/// <summary>
/// Fit this shape to render geometry in its GameObject hierarchy. Children in the hierarchy will
/// influence the result if they have enabled MeshRenderer components or have vertices bound to
/// them on a SkinnedMeshRenderer. Children will only count as influences if this shape is the
/// first ancestor shape in their hierarchy. As such, you should add shape components to all
/// GameObjects that should have them before you call this method on any of them.
/// </summary>
///
/// <exception cref="UnimplementedShapeException"> Thrown when an Unimplemented Shape error
/// condition occurs. </exception>
///
/// <param name="minimumSkinnedVertexWeight"> (Optional)
/// The minimum total weight that a vertex in a skinned mesh must have assigned to this object
/// and/or any of its influencing children. </param>
public void FitToEnabledRenderMeshes(float minimumSkinnedVertexWeight = 0f)
{
var shapeType = m_ShapeType;
m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
using (var points = new NativeList<float3>(65535, Allocator.Persistent))
{
// temporarily un-assign custom mesh and assume this shape is a convex hull
var customMesh = m_CustomMesh;
m_CustomMesh = null;
GetConvexHullProperties(points, Application.isPlaying, default, default, default, default);
m_CustomMesh = customMesh;
if (points.Length == 0)
return;
// TODO: find best rotation, particularly if any points came from skinned mesh
var orientation = quaternion.identity;
var bounds = new Bounds(points[0], float3.zero);
for (int i = 1, count = points.Length; i < count; ++i)
bounds.Encapsulate(points[i]);
SetBox(
new BoxGeometry
{
Center = bounds.center,
Size = bounds.size,
Orientation = orientation,
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius
}
);
}
switch (shapeType)
{
case ShapeType.Capsule:
SetCapsule(GetCapsuleProperties());
break;
case ShapeType.Cylinder:
SetCylinder(GetCylinderProperties(out var orientation), orientation);
break;
case ShapeType.Sphere:
SetSphere(GetSphereProperties(out orientation), orientation);
break;
case ShapeType.Plane:
// force recalculation of plane orientation by making it think shape type is out of date
m_ShapeType = ShapeType.Box;
GetPlaneProperties(out var center, out var size2D, out orientation);
SetPlane(center, size2D, orientation);
break;
case ShapeType.Box:
case ShapeType.ConvexHull:
case ShapeType.Mesh:
m_ShapeType = shapeType;
break;
default:
throw new UnimplementedShapeException(shapeType);
}
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
[Conditional("UNITY_EDITOR")]
void Reset()
{
#if UNITY_EDITOR
InitializeConvexHullGenerationParameters();
FitToEnabledRenderMeshes(m_MinimumSkinnedVertexWeight);
// TODO: also pick best primitive shape
UnityEditor.SceneView.RepaintAll();
#endif
}
public void InitializeConvexHullGenerationParameters()
{
var pointCloud = new NativeList<float3>(65535, Allocator.Temp);
GetConvexHullProperties(pointCloud, false, default, default, default, default);
m_ConvexHullGenerationParameters.InitializeToRecommendedAuthoringValues(pointCloud.AsArray());
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/BakingSystems/PhysicsBodyBakingSystem.cs
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics.GraphicsIntegration;
using UnityEngine;
using LegacyRigidbody = UnityEngine.Rigidbody;
using LegacyCollider = UnityEngine.Collider;
namespace Unity.Physics.Authoring
{
[TemporaryBakingType]
public struct PhysicsBodyAuthoringData : IComponentData
{
public bool IsDynamic;
public float Mass;
public bool OverrideDefaultMassDistribution;
public MassDistribution CustomMassDistribution;
}
class PhysicsBodyAuthoringBaker : BasePhysicsBaker<PhysicsBodyAuthoring>
{
internal List<UnityEngine.Collider> colliderComponents = new List<UnityEngine.Collider>();
internal List<PhysicsShapeAuthoring> physicsShapeComponents = new List<PhysicsShapeAuthoring>();
public override void Bake(PhysicsBodyAuthoring authoring)
{
// Priority is to Legacy Components. Ignore if baked by Legacy.
if (GetComponent<LegacyRigidbody>() || GetComponent<LegacyCollider>())
{
return;
}
var entity = GetEntity(TransformUsageFlags.Dynamic);
// To process later in the Baking System
AddComponent(entity, new PhysicsBodyAuthoringData
{
IsDynamic = (authoring.MotionType == BodyMotionType.Dynamic),
Mass = authoring.Mass,
OverrideDefaultMassDistribution = authoring.OverrideDefaultMassDistribution,
CustomMassDistribution = authoring.CustomMassDistribution
});
AddSharedComponent(entity, new PhysicsWorldIndex(authoring.WorldIndex));
var bodyTransform = GetComponent<Transform>();
var motionType = authoring.MotionType;
var hasSmoothing = authoring.Smoothing != BodySmoothing.None;
PostProcessTransform(bodyTransform, motionType);
var customTags = authoring.CustomTags;
if (!customTags.Equals(CustomPhysicsBodyTags.Nothing))
AddComponent(entity, new PhysicsCustomTags { Value = customTags.Value });
// Check that there is at least one collider in the hierarchy to add these three
GetComponentsInChildren(colliderComponents);
GetComponentsInChildren(physicsShapeComponents);
if (colliderComponents.Count > 0 || physicsShapeComponents.Count > 0)
{
AddComponent(entity, new PhysicsCompoundData()
{
AssociateBlobToBody = false,
ConvertedBodyInstanceID = authoring.GetInstanceID(),
Hash = default,
});
AddComponent<PhysicsRootBaked>(entity);
AddComponent<PhysicsCollider>(entity);
AddBuffer<PhysicsColliderKeyEntityPair>(entity);
}
if (authoring.MotionType == BodyMotionType.Static || IsStatic())
return;
var massProperties = MassProperties.UnitSphere;
AddComponent(entity, authoring.MotionType == BodyMotionType.Dynamic ?
PhysicsMass.CreateDynamic(massProperties, authoring.Mass) :
PhysicsMass.CreateKinematic(massProperties));
var physicsVelocity = new PhysicsVelocity
{
Linear = authoring.InitialLinearVelocity,
Angular = authoring.InitialAngularVelocity
};
AddComponent(entity, physicsVelocity);
if (authoring.MotionType == BodyMotionType.Dynamic)
{
// TODO make these optional in editor?
AddComponent(entity, new PhysicsDamping
{
Linear = authoring.LinearDamping,
Angular = authoring.AngularDamping
});
if (authoring.GravityFactor != 1)
{
AddComponent(entity, new PhysicsGravityFactor
{
Value = authoring.GravityFactor
});
}
}
else if (authoring.MotionType == BodyMotionType.Kinematic)
{
AddComponent(entity, new PhysicsGravityFactor
{
Value = 0
});
}
if (hasSmoothing)
{
AddComponent(entity, new PhysicsGraphicalSmoothing());
if (authoring.Smoothing == BodySmoothing.Interpolation)
{
AddComponent(entity, new PhysicsGraphicalInterpolationBuffer
{
PreviousTransform = Math.DecomposeRigidBodyTransform(bodyTransform.localToWorldMatrix),
PreviousVelocity = physicsVelocity,
});
}
}
}
}
[RequireMatchingQueriesForUpdate]
[UpdateAfter(typeof(EndColliderBakingSystem))]
[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
public partial class PhysicsBodyBakingSystem : SystemBase
{
protected override void OnUpdate()
{
// Fill in the MassProperties based on the potential calculated value by BuildCompoundColliderBakingSystem
foreach (var(physicsMass, bodyData, collider) in
SystemAPI.Query<RefRW<PhysicsMass>, RefRO<PhysicsBodyAuthoringData>, RefRO<PhysicsCollider>>().WithOptions(EntityQueryOptions.IncludePrefab | EntityQueryOptions
{
// Build mass component
var massProperties = collider.ValueRO.MassProperties;
if (bodyData.ValueRO.OverrideDefaultMassDistribution)
{
massProperties.MassDistribution = bodyData.ValueRO.CustomMassDistribution;
// Increase the angular expansion factor to account for the shift in center of mass
massProperties.AngularExpansionFactor += math.length(massProperties.MassDistribution.Transform.pos - bodyData.ValueRO.CustomMassDistribution.Transform.pos);
}
physicsMass.ValueRW = bodyData.ValueRO.IsDynamic ?
PhysicsMass.CreateDynamic(massProperties, bodyData.ValueRO.Mass) :
PhysicsMass.CreateKinematic(massProperties);
}
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/BakingSystems/PhysicsShapeBakingSystem.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Profiling;
using LegacyCollider = UnityEngine.Collider;
using UnityMesh = UnityEngine.Mesh;
namespace Unity.Physics.Authoring
{
class PhysicsShapeBaker : BaseColliderBaker<PhysicsShapeAuthoring>
{
public static List<PhysicsShapeAuthoring> physicsShapeComponents = new List<PhysicsShapeAuthoring>();
public static List<LegacyCollider> legacyColliderComponents = new List<LegacyCollider>();
bool ShouldConvertShape(PhysicsShapeAuthoring authoring)
{
return authoring.enabled;
}
private GameObject GetPrimaryBody(GameObject shape, out bool hasBodyComponent, out bool isStaticBody)
{
var pb = FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
var rb = FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
hasBodyComponent = (pb != null || rb != null);
isStaticBody = false;
if (pb != null)
{
return rb == null ? pb.gameObject :
pb.transform.IsChildOf(rb.transform) ? pb.gameObject : rb.gameObject;
}
if (rb != null)
return rb.gameObject;
// for implicit static shape, first see if it is part of static optimized hierarchy
isStaticBody = FindTopmostStaticEnabledAncestor(shape, out var topStatic);
if (topStatic != null)
return topStatic;
// otherwise, find topmost enabled Collider or PhysicsShapeAuthoring
var topCollider = FindTopmostEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_CollidersBuffer);
var topShape = FindTopmostEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_ShapesBuffer);
return topCollider == null
? topShape == null ? shape.gameObject : topShape
: topShape == null
? topCollider
: topShape.transform.IsChildOf(topCollider.transform)
? topCollider
: topShape;
}
ShapeComputationDataBaking GetInputDataFromAuthoringComponent(PhysicsShapeAuthoring shape, Entity colliderEntity)
{
GameObject shapeGameObject = shape.gameObject;
var body = GetPrimaryBody(shapeGameObject, out bool hasBodyComponent, out bool isStaticBody);
var child = shapeGameObject;
var shapeInstanceID = shape.GetInstanceID();
var bodyEntity = GetEntity(body, TransformUsageFlags.Dynamic);
// prepare the static root
if (isStaticBody)
{
var staticRootMarker = CreateAdditionalEntity(TransformUsageFlags.Dynamic, true, "StaticRootBakeMarker");
AddComponent(staticRootMarker, new BakeStaticRoot() { Body = bodyEntity, ConvertedBodyInstanceID = body.transform.GetInstanceID() });
}
// Track dependencies to the transforms
Transform shapeTransform = GetComponent<Transform>(shape);
Transform bodyTransform = GetComponent<Transform>(body);
var instance = new ColliderInstanceBaking
{
AuthoringComponentId = shapeInstanceID,
BodyEntity = bodyEntity,
ShapeEntity = GetEntity(shapeGameObject, TransformUsageFlags.Dynamic),
ChildEntity = GetEntity(child, TransformUsageFlags.Dynamic),
BodyFromShape = ColliderInstanceBaking.GetCompoundFromChild(shapeTransform, bodyTransform),
};
var data = GenerateComputationData(shape, instance, colliderEntity);
data.Instance.ConvertedAuthoringInstanceID = shapeInstanceID;
data.Instance.ConvertedBodyInstanceID = bodyTransform.GetInstanceID();
var rb = FindFirstEnabledAncestor(shapeGameObject, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
var pb = FindFirstEnabledAncestor(shapeGameObject, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
// The LegacyRigidBody cannot know about the ShapeAuthoringComponent. We need to take responsibility of baking the collider.
if (rb || (!rb && !pb) && body == shapeGameObject)
{
GetComponents(physicsShapeComponents);
GetComponents(legacyColliderComponents);
// We need to check that there are no other colliders in the same object, if so, only the first one should do this, otherwise there will be 2 bakers adding this to the enti
// This will be needed to trigger BuildCompoundColliderBakingSystem
// If they are legacy Colliders and PhysicsShapeAuthoring in the same object, the PhysicsShapeAuthoring will add this
if (legacyColliderComponents.Count == 0 && physicsShapeComponents.Count > 0 && physicsShapeComponents[0].GetInstanceID() == shapeInstanceID)
{
var entity = GetEntity(TransformUsageFlags.Dynamic);
// // Rigid Body bakes always add the PhysicsWorldIndex component and process transform
if (!hasBodyComponent)
{
AddSharedComponent(entity, new PhysicsWorldIndex());
PostProcessTransform(bodyTransform);
}
AddComponent(entity, new PhysicsCompoundData()
{
AssociateBlobToBody = false,
ConvertedBodyInstanceID = shapeInstanceID,
Hash = default,
});
AddComponent<PhysicsRootBaked>(entity);
AddComponent<PhysicsCollider>(entity);
AddBuffer<PhysicsColliderKeyEntityPair>(entity);
}
}
return data;
}
Material ProduceMaterial(PhysicsShapeAuthoring shape)
{
var materialTemplate = shape.MaterialTemplate;
if (materialTemplate != null)
DependsOn(materialTemplate);
return shape.GetMaterial();
}
CollisionFilter ProduceCollisionFilter(PhysicsShapeAuthoring shape)
{
return shape.GetFilter();
}
UnityEngine.Mesh GetMesh(PhysicsShapeAuthoring shape, out float4x4 childToShape)
{
var mesh = shape.CustomMesh;
childToShape = float4x4.identity;
if (mesh == null)
{
// Try to get a mesh in the children
var filter = GetComponentInChildren<MeshFilter>();
if (filter != null && filter.sharedMesh != null)
{
mesh = filter.sharedMesh;
var childTransform = GetComponent<Transform>(filter);
childToShape = math.mul(shape.transform.worldToLocalMatrix, childTransform.localToWorldMatrix);;
}
}
if (mesh == null)
{
throw new InvalidOperationException(
$"No {nameof(PhysicsShapeAuthoring.CustomMesh)} assigned on {shape.name}."
);
}
DependsOn(mesh);
return mesh;
}
namespace Unity.Physics.Authoring
{
[BakingType]
public struct JointEntityBaking : IComponentData
{
public Entity Entity;
}
public class BallAndSocketJoint : BaseJoint
{
// Editor only settings
[HideInInspector]
public bool EditPivots;
[Tooltip("If checked, PositionLocal will snap to match PositionInConnectedEntity")]
public bool AutoSetConnected = true;
namespace Unity.Physics.Authoring
{
public abstract class BaseJoint : BaseBodyPairConnector
{
public bool EnableCollision;
public float3 MaxImpulse = float.PositiveInfinity;
void OnEnable()
{
// included so tick box appears in Editor
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Joints/FreeHingeJoint.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
public class FreeHingeJoint : BallAndSocketJoint
{
// Editor only settings
[HideInInspector]
public bool EditAxes;
public float3 HingeAxisLocal;
public float3 HingeAxisInConnectedEntity;
public override void UpdateAuto()
{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
HingeAxisInConnectedEntity = math.mul(bFromA.rot, HingeAxisLocal);
}
}
}
class FreeHingeJointBaker : JointBaker<FreeHingeJoint>
{
public override void Bake(FreeHingeJoint authoring)
{
authoring.UpdateAuto();
Math.CalculatePerpendicularNormalized(authoring.HingeAxisLocal, out var perpendicularLocal, out _);
Math.CalculatePerpendicularNormalized(authoring.HingeAxisInConnectedEntity, out var perpendicularConnected, out _);
var physicsJoint = PhysicsJoint.CreateHinge(
new BodyFrame {Axis = authoring.HingeAxisLocal, Position = authoring.PositionLocal, PerpendicularAxis = perpendicularLocal},
new BodyFrame {Axis = authoring.HingeAxisInConnectedEntity, Position = authoring.PositionInConnectedEntity, PerpendicularAxis = perpendicularConnected }
);
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntity(worldIndex, constraintBodyPair, physicsJoint);
}
}
}
Tutorial/Assets/Scripts/Custom Physics Authoring/Unity.Physics.Custom/Joints/LimitDOFJoint.cs
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
// This Joint allows you to lock one or more of the 6 degrees of freedom of a constrained body.
// This is achieved by combining the appropriate lower level 'constraint atoms' to form the higher level Joint.
// In this case Linear and Angular constraint atoms are combined.
// One use-case for this Joint could be to restrict a 3d simulation to a 2d plane.
public class LimitDOFJoint : BaseJoint
{
public bool3 LockLinearAxes;
public bool3 LockAngularAxes;
public PhysicsJoint CreateLimitDOFJoint(RigidTransform offset)
{
var constraints = new FixedList512Bytes<Constraint>();
if (math.any(LockLinearAxes))
{
constraints.Add(new Constraint
{
ConstrainedAxes = LockLinearAxes,
Type = ConstraintType.Linear,
Min = 0,
Max = 0,
SpringFrequency = Constraint.DefaultSpringFrequency,
SpringDamping = Constraint.DefaultSpringDamping,
MaxImpulse = MaxImpulse,
});
}
if (math.any(LockAngularAxes))
{
constraints.Add(new Constraint
{
ConstrainedAxes = LockAngularAxes,
Type = ConstraintType.Angular,
Min = 0,
Max = 0,
SpringFrequency = Constraint.DefaultSpringFrequency,
SpringDamping = Constraint.DefaultSpringDamping,
MaxImpulse = MaxImpulse,
});
}
var joint = new PhysicsJoint
{
BodyAFromJoint = BodyFrame.Identity,
BodyBFromJoint = offset
};
joint.SetConstraints(constraints);
return joint;
}
}
class LimitDOFJointBaker : Baker<LimitDOFJoint>
{
public Entity CreateJointEntity(uint worldIndex, PhysicsConstrainedBodyPair constrainedBodyPair, PhysicsJoint joint)
{
using (var joints = new NativeArray<PhysicsJoint>(1, Allocator.Temp) { [0] = joint })
using (var jointEntities = new NativeList<Entity>(1, Allocator.Temp))
{
CreateJointEntities(worldIndex, constrainedBodyPair, joints, jointEntities);
return jointEntities[0];
}
}
public void CreateJointEntities(uint worldIndex, PhysicsConstrainedBodyPair constrainedBodyPair, NativeArray<PhysicsJoint> joints, NativeList<Entity> newJointEntities = default
{
if (!joints.IsCreated || joints.Length == 0)
return;
if (newJointEntities.IsCreated)
newJointEntities.Clear();
else
newJointEntities = new NativeList<Entity>(joints.Length, Allocator.Temp);
// create all new joints
var multipleJoints = joints.Length > 1;
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
namespace Unity.Physics.Authoring
{
// stores an initial value and a pair of scalar curves to apply to relevant constraints on the joint
struct ModifyJointLimits : ISharedComponentData, IEquatable<ModifyJointLimits>
{
public PhysicsJoint InitialValue;
public ParticleSystem.MinMaxCurve AngularRangeScalar;
public ParticleSystem.MinMaxCurve LinearRangeScalar;
public bool Equals(ModifyJointLimits other) =>
AngularRangeScalar.Equals(other.AngularRangeScalar) && LinearRangeScalar.Equals(other.LinearRangeScalar);
public override bool Equals(object obj) => obj is ModifyJointLimits other && Equals(other);
_ModifyJointLimitsBakingDataQuery.AddChangedVersionFilter(typeof(ModifyJointLimitsBakingData));
_JointEntityBakingQuery.AddChangedVersionFilter(typeof(JointEntityBaking));
}
public void OnUpdate(ref SystemState state)
{
if (_ModifyJointLimitsBakingDataQuery.IsEmpty && _JointEntityBakingQuery.IsEmpty)
{
return;
}
// Collect all the joints
NativeParallelMultiHashMap<Entity, (Entity, PhysicsJoint)> jointsLookUp =
new NativeParallelMultiHashMap<Entity, (Entity, PhysicsJoint)>(10, Allocator.TempJob);
foreach (var(jointEntity, physicsJoint, entity) in SystemAPI
.Query<RefRO<JointEntityBaking>, RefRO<PhysicsJoint>>().WithEntityAccess()
.WithOptions(EntityQueryOptions.IncludeDisabledEntities | EntityQueryOptions.IncludePrefab))
{
jointsLookUp.Add(jointEntity.ValueRO.Entity, (entity, physicsJoint.ValueRO));
}
foreach (var(modifyJointLimits, entity) in SystemAPI.Query<ModifyJointLimitsBakingData>()
.WithEntityAccess().WithOptions(EntityQueryOptions.IncludeDisabledEntities |
EntityQueryOptions.IncludePrefab))
{
var angularModification = new ParticleSystem.MinMaxCurve(
multiplier: math.radians(modifyJointLimits.AngularRangeScalar.curveMultiplier),
min: modifyJointLimits.AngularRangeScalar.curveMin,
max: modifyJointLimits.AngularRangeScalar.curveMax
);
foreach (var joint in jointsLookUp.GetValuesForKey(entity))
{
state.EntityManager.SetSharedComponentManaged(joint.Item1, new ModifyJointLimits
{
InitialValue = joint.Item2,
AngularRangeScalar = angularModification,
LinearRangeScalar = modifyJointLimits.LinearRangeScalar
});
}
}
jointsLookUp.Dispose();
}
}
// apply an animated effect to the limits on supported types of joints
[RequireMatchingQueriesForUpdate]
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
[UpdateAfter(typeof(PhysicsSystemGroup))]
partial struct ModifyJointLimitsSystem : ISystem
{
public void OnUpdate(ref SystemState state)
{
var time = (float)SystemAPI.Time.ElapsedTime;
foreach (var(joint, modification) in SystemAPI.Query<RefRW<PhysicsJoint>, ModifyJointLimits>())
{
var animatedAngularScalar = new FloatRange(
modification.AngularRangeScalar.curveMin.Evaluate(time),
modification.AngularRangeScalar.curveMax.Evaluate(time)
);
var animatedLinearScalar = new FloatRange(
modification.LinearRangeScalar.curveMin.Evaluate(time),
modification.LinearRangeScalar.curveMax.Evaluate(time)
);
// in each case, get relevant properties from the initial value based on joint type, and apply scalar
switch (joint.ValueRW.JointType)
{
// Custom type could be anything, so this demo just applies changes to all constraints
case JointType.Custom:
var constraints = modification.InitialValue.GetConstraints();
for (var i = 0; i < constraints.Length; i++)
{
var constraint = constraints[i];
var isAngular = constraint.Type == ConstraintType.Angular;
var scalar = math.select(animatedLinearScalar, animatedAngularScalar, isAngular);
var constraintRange = (FloatRange)(new float2(constraint.Min, constraint.Max) * scalar);
constraint.Min = constraintRange.Min;
constraint.Max = constraintRange.Max;
constraints[i] = constraint;
}
joint.ValueRW.SetConstraints(constraints);
break;
// other types have corresponding getters/setters to retrieve more meaningful data
case JointType.LimitedDistance:
var distanceRange = modification.InitialValue.GetLimitedDistanceRange();
joint.ValueRW.SetLimitedDistanceRange(distanceRange * (float2)animatedLinearScalar);
break;
case JointType.LimitedHinge:
var angularRange = modification.InitialValue.GetLimitedHingeRange();
joint.ValueRW.SetLimitedHingeRange(angularRange * (float2)animatedAngularScalar);
break;
case JointType.Prismatic:
var distanceOnAxis = modification.InitialValue.GetPrismaticRange();
joint.ValueRW.SetPrismaticRange(distanceOnAxis * (float2)animatedLinearScalar);
break;
// ragdoll joints are composed of two separate joints with different meanings
case JointType.RagdollPrimaryCone:
modification.InitialValue.GetRagdollPrimaryConeAndTwistRange(
out var maxConeAngle,
out var angularTwistRange
);
joint.ValueRW.SetRagdollPrimaryConeAndTwistRange(
maxConeAngle * animatedAngularScalar.Max,
angularTwistRange * (float2)animatedAngularScalar
);
break;
case JointType.RagdollPerpendicularCone:
var angularPlaneRange = modification.InitialValue.GetRagdollPerpendicularConeRange();
joint.ValueRW.SetRagdollPerpendicularConeRange(angularPlaneRange *
(float2)animatedAngularScalar);
break;
// remaining types have no limits on their Constraint atoms to meaningfully modify
case JointType.BallAndSocket:
case JointType.Fixed:
case JointType.Hinge:
break;
}
}
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Joints/PrismaticJoint.cs
using Unity.Entities;
using Unity.Mathematics;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class PrismaticJoint : BallAndSocketJoint
{
public float3 AxisLocal;
public float3 AxisInConnectedEntity;
public float3 PerpendicularAxisLocal;
public float3 PerpendicularAxisInConnectedEntity;
public float MinDistanceOnAxis;
public float MaxDistanceOnAxis;
[SerializeField]
int m_Version;
public float3 TwistAxisLocal;
public float3 TwistAxisInConnectedEntity;
public float3 PerpendicularAxisLocal;
public float3 PerpendicularAxisInConnectedEntity;
public float MaxConeAngle;
public float MinPerpendicularAngle;
public float MaxPerpendicularAngle;
public float MinTwistAngle;
public float MaxTwistAngle;
internal void UpgradeVersionIfNecessary()
{
if (m_Version >= k_LatestVersion)
return;
MinPerpendicularAngle -= 90f;
MaxPerpendicularAngle -= 90f;
m_Version = k_LatestVersion;
}
void OnValidate()
{
UpgradeVersionIfNecessary();
MaxConeAngle = math.clamp(MaxConeAngle, 0f, 180f);
MaxPerpendicularAngle = math.clamp(MaxPerpendicularAngle, -90f, 90f);
MinPerpendicularAngle = math.clamp(MinPerpendicularAngle, -90f, 90f);
if (MaxPerpendicularAngle < MinPerpendicularAngle)
{
var swap = new FloatRange(MinPerpendicularAngle, MaxPerpendicularAngle).Sorted();
MinPerpendicularAngle = swap.Min;
MaxPerpendicularAngle = swap.Max;
}
MinTwistAngle = math.clamp(MinTwistAngle, -180f, 180f);
MaxTwistAngle = math.clamp(MaxTwistAngle, -180f, 180f);
if (MaxTwistAngle < MinTwistAngle)
{
var swap = new FloatRange(MinTwistAngle, MaxTwistAngle).Sorted();
MinTwistAngle = swap.Min;
MaxTwistAngle = swap.Max;
}
}
public override void UpdateAuto()
{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
TwistAxisInConnectedEntity = math.mul(bFromA.rot, TwistAxisLocal);
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
}
}
}
class RagdollJointBaker : JointBaker<RagdollJoint>
{
public override void Bake(RagdollJoint authoring)
{
authoring.UpdateAuto();
authoring.UpgradeVersionIfNecessary();
PhysicsJoint.CreateRagdoll(
new BodyFrame { Axis = authoring.TwistAxisLocal, PerpendicularAxis = authoring.PerpendicularAxisLocal, Position = authoring.PositionLocal },
new BodyFrame { Axis = authoring.TwistAxisInConnectedEntity, PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity, Position = authoring.PositionInConnectedEntit
math.radians(authoring.MaxConeAngle),
math.radians(new FloatRange(authoring.MinPerpendicularAngle, authoring.MaxPerpendicularAngle)),
math.radians(new FloatRange(authoring.MinTwistAngle, authoring.MaxTwistAngle)),
out var primaryCone,
out var perpendicularCone
);
primaryCone.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
perpendicularCone.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
using NativeList<Entity> entities = new NativeList<Entity>(1, Allocator.TempJob);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntities(worldIndex,
constraintBodyPair,
new NativeArray<PhysicsJoint>(2, Allocator.Temp) { [0] = primaryCone, [1] = perpendicularCone }, entities);
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Joints/RigidJoint.cs
using Unity.Entities;
using Unity.Mathematics;
namespace Unity.Physics.Authoring
{
public class RigidJoint : BallAndSocketJoint
{
public quaternion OrientationLocal = quaternion.identity;
public quaternion OrientationInConnectedEntity = quaternion.identity;
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
namespace Unity.Physics.Authoring
{
public class AngularVelocityMotor : BaseJoint
{
[Tooltip("An offset from center of entity with motor. Representing the anchor/pivot point of rotation")]
public float3 PivotPosition;
[Tooltip("The axis of rotation of the motor. Value will be normalized")]
public float3 AxisOfRotation;
[Tooltip("Target speed for the motor to maintain, in degrees/s")]
public float TargetSpeed;
[Tooltip("The magnitude of the maximum impulse the motor can exert in a single step. Applies only to the motor constraint.")]
public float MaxImpulseAppliedByMotor = math.INFINITY;
private float3 PerpendicularAxisLocal;
private float3 PositionInConnectedEntity;
private float3 HingeAxisInConnectedEntity;
private float3 PerpendicularAxisInConnectedEntity;
class AngularVelocityMotorBaker : JointBaker<AngularVelocityMotor>
{
public override void Bake(AngularVelocityMotor authoring)
{
float3 axisInA = math.normalize(authoring.AxisOfRotation);
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.PivotPosition); //position of motored body pivot relative to Connected Entity in world space
authoring.HingeAxisInConnectedEntity = math.mul(bFromA.rot, axisInA); //motor axis in Connected Entity space
// Always calculate the perpendicular axes
Math.CalculatePerpendicularNormalized(axisInA, out var perpendicularLocal, out _);
authoring.PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, perpendicularLocal); //perp motor axis in Connected Entity space
#region Sphere
[BurstCompile]
struct BakeSphereJob : IJob
{
public NativeArray<SphereGeometry> Sphere;
public NativeArray<EulerAngles> Orientation;
// TODO: make members PascalCase after merging static query fixes
public float4x4 localToWorld;
public float4x4 shapeToWorld;
public void Execute()
{
var center = Sphere[0].Center;
var radius = Sphere[0].Radius;
var orientation = Orientation[0];
var basisToWorld = GetBasisToWorldMatrix(localToWorld, center, orientation, 1f);
var basisPriority = basisToWorld.HasShear() ? GetBasisAxisPriority(basisToWorld) : k_DefaultAxisPriority;
var bakeToShape = GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center, ref orientation, 1f, basisPriority);
radius *= math.cmax(bakeToShape.DecomposeScale());
Sphere[0] = new SphereGeometry
{
Center = center,
Radius = radius
};
Orientation[0] = orientation;
}
}
internal static SphereGeometry BakeToBodySpace(
this SphereGeometry sphere, float4x4 localToWorld, float4x4 shapeToWorld, ref EulerAngles orientation
)
{
using (var geometry = new NativeArray<SphereGeometry>(1, Allocator.TempJob) { [0] = sphere })
using (var outOrientation = new NativeArray<EulerAngles>(1, Allocator.TempJob) { [0] = orientation })
{
var job = new BakeSphereJob
{
Sphere = geometry,
Orientation = outOrientation,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld
};
job.Run();
orientation = outOrientation[0];
return geometry[0];
}
}
#endregion
#region Plane
[BurstCompile]
struct BakePlaneJob : IJob
{
public NativeArray<float3x4> Vertices;
// TODO: make members PascalCase after merging static query fixes
public float3 center;
public float2 size;
public EulerAngles orientation;
public float4x4 localToWorld;
public float4x4 shapeToWorld;
#endregion
#region ShapeInputHash
#if !(UNITY_ANDROID && !UNITY_64) // !Android32
// Getting memory alignment errors from HashUtility.Hash128 on Android32
[BurstCompile]
#endif
internal struct GetShapeInputsHashJob : IJob
{
public NativeArray<Hash128> Result;
#region AABB
[BurstCompile]
internal struct GetAabbJob : IJob
{
[ReadOnly] public NativeArray<float3> Points;
public NativeArray<Aabb> Aabb;
public void Execute()
{
var aabb = new Aabb { Min = float.MaxValue, Max = float.MinValue };
for (var i = 0; i < Points.Length; ++i)
aabb.Include(Points[i]);
Aabb[0] = aabb;
}
}
#endregion
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/BakeGeometryJobsExtensions.cs
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
static partial class PhysicsShapeExtensions
{
public static class BakeBoxJobExtension
{
internal static float4x4 GetBakeToShape(PhysicsShapeAuthoring shape, float3 center, EulerAngles orientation)
{
var transform = shape.transform;
var localToWorld = (float4x4)transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
return BakeBoxJob.GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientation);
}
}
public static class BakeCapsuleJobExtension
{
internal static float4x4 GetBakeToShape(PhysicsShapeAuthoring shape, float3 center, EulerAngles orientation)
{
var transform = shape.transform;
var localToWorld = (float4x4)transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
return BakeCapsuleJob.GetBakeToShape(localToWorld, shapeToWorld, ref center,
ref orientation);
}
}
public static void SetBakedCapsuleSize(this PhysicsShapeAuthoring shape, float height, float radius)
{
var capsule = shape.GetCapsuleProperties();
var center = capsule.Center;
namespace Unity.Physics.Authoring
{
/// <summary>
/// A structure for storing authoring data for a capsule shape. In contrast to the
/// CapsuleGeometry struct in the run-time, this structure permits storing stable orientation
/// values, as well as height values that can be retained when the source data are defined with
/// respect to a non-uniformly scaled object.
/// </summary>
[Serializable]
public struct CapsuleGeometryAuthoring : IEquatable<CapsuleGeometryAuthoring>
{
/// <summary>
/// The local orientation of the capsule. It is aligned with the forward axis (z) when it is
/// identity.
/// </summary>
public quaternion Orientation { get => m_OrientationEuler; set => m_OrientationEuler.SetValue(value); }
internal EulerAngles OrientationEuler { get => m_OrientationEuler; set => m_OrientationEuler = value; }
[SerializeField]
EulerAngles m_OrientationEuler;
/// <summary> The local position offset of the capsule. </summary>
public float3 Center { get => m_Center; set => m_Center = value; }
[SerializeField]
float3 m_Center;
/// <summary>
/// The height of the capsule. It may store any value, but will ultimately always be converted
/// into a value that is at least twice the radius.
/// </summary>
public float Height { get => m_Height; set => m_Height = value; }
[SerializeField]
float m_Height;
namespace Unity.Physics.Authoring
{
public static class ConvexHullGenerationParametersExtensions
{
// recommended simplification tolerance is at least 1 centimeter
internal const float k_MinRecommendedSimplificationTolerance = 0.01f;
if (points.Length <= 1)
return;
internal static void OnValidate(ref this ConvexHullGenerationParameters generationParameters, float maxAngle = 180f)
{
generationParameters.SimplificationTolerance = math.max(0f, generationParameters.SimplificationTolerance);
generationParameters.BevelRadius = math.max(0f, generationParameters.BevelRadius);
generationParameters.MinimumAngle = math.clamp(generationParameters.MinimumAngle, 0f, maxAngle);
}
namespace Unity.Physics.Authoring
{
[Serializable]
internal struct EulerAngles : IEquatable<EulerAngles>
{
public static EulerAngles Default => new EulerAngles { RotationOrder = math.RotationOrder.ZXY };
public float3 Value;
[HideInInspector]
public math.RotationOrder RotationOrder;
internal void SetValue(quaternion value) => Value = math.degrees(Math.ToEulerAngles(value, RotationOrder));
if (m_CheckIfComponentBelongsToShape)
{
if (PhysicsShapeExtensions.GetPrimaryBody(child.gameObject) != m_PrimaryBody)
return false;
child.gameObject.GetComponentsInParent(true, s_PhysicsShapes);
if (s_PhysicsShapes[0] != m_Shape)
{
s_PhysicsShapes.Clear();
return false;
}
}
// do not simply use GameObject.activeInHierarchy because it will be false when instantiating a prefab
var t = child.transform;
var activeInHierarchy = t.gameObject.activeSelf;
while (activeInHierarchy && t != m_Root)
{
t = t.parent;
activeInHierarchy &= t.gameObject.activeSelf;
}
return activeInHierarchy;
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/PhysicsShapeExtensions.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
// put static UnityObject buffers in separate utility class so other methods can Burst compile
static class PhysicsShapeExtensions_NonBursted
{
internal static readonly List<PhysicsBodyAuthoring> s_PhysicsBodiesBuffer = new List<PhysicsBodyAuthoring>(16);
internal static readonly List<PhysicsShapeAuthoring> s_ShapesBuffer = new List<PhysicsShapeAuthoring>(16);
internal static readonly List<Rigidbody> s_RigidbodiesBuffer = new List<Rigidbody>(16);
internal static readonly List<UnityEngine.Collider> s_CollidersBuffer = new List<UnityEngine.Collider>(16);
}
public static partial class PhysicsShapeExtensions
{
// used for de-skewing basis vectors; default priority assumes primary axis is z, secondary axis is y
public static readonly int3 k_DefaultAxisPriority = new int3(2, 1, 0);
// matrix to transform point from shape's local basis into world space
public static float4x4 GetBasisToWorldMatrix(
float4x4 localToWorld, float3 center, quaternion orientation, float3 size
) =>
math.mul(localToWorld, float4x4.TRS(center, orientation, size));
static float4 DeskewSecondaryAxis(float4 primaryAxis, float4 secondaryAxis)
{
var n0 = math.normalizesafe(primaryAxis);
var dot = math.dot(secondaryAxis, n0);
return secondaryAxis - n0 * dot;
}
// priority is determined by length of each size dimension in the shape's basis after applying localToWorld transformation
public static int3 GetBasisAxisPriority(float4x4 basisToWorld)
{
var basisAxisLengths = basisToWorld.DecomposeScale();
var max = math.cmax(basisAxisLengths);
var min = math.cmin(basisAxisLengths);
if (max == min)
return k_DefaultAxisPriority;
basisAxisLengths = basisToWorld.DecomposeScale();
min = math.cmin(basisAxisLengths);
var imin = min == basisAxisLengths.x ? 0 : min == basisAxisLengths.y ? 1 : 2;
if (imin == imax)
imin = k_NextAxis[imax];
var imid = k_NextAxis[imax] == imin ? k_PrevAxis[imax] : k_NextAxis[imax];
[Conditional(CompilationSymbols.CollectionsChecksSymbol), Conditional(CompilationSymbols.DebugChecksSymbol)]
static void CheckBasisPriorityAndThrow(int3 basisPriority)
{
if (
basisPriority.x == basisPriority.y
|| basisPriority.x == basisPriority.z
|| basisPriority.y == basisPriority.z
)
throw new ArgumentException(nameof(basisPriority));
}
public static bool HasNonUniformScale(this float4x4 m)
{
var s = new float3(math.lengthsq(m.c0.xyz), math.lengthsq(m.c1.xyz), math.lengthsq(m.c2.xyz));
return math.cmin(s) != math.cmax(s);
}
// matrix to transform point on a primitive from bake space into space of the shape
internal static float4x4 GetPrimitiveBakeToShapeMatrix(
float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center, ref EulerAngles orientation, float3 scale, int3 basisPriority
)
{
CheckBasisPriorityAndThrow(basisPriority);
var localToBasis = float4x4.TRS(center, orientation, scale);
// correct for imprecision in cases of no scale to prevent e.g., convex radius from being altered
if (scale.Equals(new float3(1f)))
{
localToBasis.c0 = math.normalizesafe(localToBasis.c0);
localToBasis.c1 = math.normalizesafe(localToBasis.c1);
localToBasis.c2 = math.normalizesafe(localToBasis.c2);
}
var localToBake = math.mul(localToWorld, localToBasis);
if (localToBake.HasNonUniformScale() || localToBake.HasShear())
{
// deskew second longest axis with respect to longest axis
localToBake[basisPriority[1]] =
DeskewSecondaryAxis(localToBake[basisPriority[0]], localToBake[basisPriority[1]]);
return bakeToShape;
}
public static void SetBakedCylinderSize(this PhysicsShapeAuthoring shape, float height, float radius, float bevelRadius)
{
var cylinder = shape.GetCylinderProperties(out EulerAngles orientation);
var center = cylinder.Center;
var bakeToShape = BakeCylinderJobExtension.GetBakeToShape(shape, center, orientation);
var scale = bakeToShape.DecomposeScale();
namespace Unity.Physics.Authoring
{
sealed class EnumFlagsAttribute : PropertyAttribute {}
sealed class ExpandChildrenAttribute : PropertyAttribute {}
sealed class SoftRangeAttribute : PropertyAttribute
{
public readonly float SliderMin;
public readonly float SliderMax;
public float TextFieldMin { get; set; }
public float TextFieldMax { get; set; }
[assembly: InternalsVisibleTo("Unity.Physics.Custom.EditModeTests")]
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/BallAndSocketJointEditor.cs
#if UNITY_EDITOR
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(BallAndSocketJoint))]
public class BallAndSocketEditor : UnityEditor.Editor
{
protected virtual void OnSceneGUI()
{
BallAndSocketJoint ballAndSocket = (BallAndSocketJoint)target;
EditorGUI.BeginChangeCheck();
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(CustomPhysicsMaterialTagNames))]
[CanEditMultipleObjects]
class CustomPhysicsMaterialTagNamesEditor : BaseEditor
{
#pragma warning disable 649
[AutoPopulate(ElementFormatString = "Custom Physics Material Tag {0}", Resizable = false, Reorderable = false)]
ReorderableList m_TagNames;
#pragma warning restore 649
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/EditorUtilities.cs
using UnityEngine;
using Unity.Mathematics;
using static Unity.Physics.Math;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.IMGUI.Controls;
namespace Unity.Physics.Editor
{
/// <summary>
/// Provides utilities that use Handles to set positions and axes,
/// </summary>
public class EditorUtilities
{
// Editor for a joint pivot or pivot pair
public static void EditPivot(RigidTransform worldFromA, RigidTransform worldFromB, bool lockBtoA,
ref float3 pivotA, ref float3 pivotB, Object target)
{
EditorGUI.BeginChangeCheck();
float3 pivotAinW = Handles.PositionHandle(math.transform(worldFromA, pivotA), quaternion.identity);
float3 pivotBinW;
if (lockBtoA)
{
pivotBinW = pivotAinW;
pivotB = math.transform(math.inverse(worldFromB), pivotBinW);
}
else
{
pivotBinW = Handles.PositionHandle(math.transform(worldFromB, pivotB), quaternion.identity);
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Edit joint pivot");
pivotA = math.transform(math.inverse(worldFromA), pivotAinW);
pivotB = math.transform(math.inverse(worldFromB), pivotBinW);
}
Handles.DrawLine(worldFromA.pos, pivotAinW);
Handles.DrawLine(worldFromB.pos, pivotBinW);
}
// Editor for a joint axis or axis pair
public class AxisEditor
{
// Even though we're only editing axes and not rotations, we need to track a full rotation in order to keep the rotation handle stable
private quaternion m_RefA = quaternion.identity;
private quaternion m_RefB = quaternion.identity;
// Detect changes in the object being edited to reset the reference orientations
private Object m_LastTarget;
private static bool NormalizeSafe(ref float3 x)
{
float lengthSq = math.lengthsq(x);
const float epsSq = 1e-8f;
if (math.abs(lengthSq - 1) > epsSq)
{
if (lengthSq > epsSq)
{
x *= math.rsqrt(lengthSq);
}
else
{
x = new float3(1, 0, 0);
}
return true;
}
return false;
}
private static bool NormalizePerpendicular(float3 axis, ref float3 perpendicular)
{
// make sure perpendicular is actually perpendicular to direction
float dot = math.dot(axis, perpendicular);
float absDot = math.abs(dot);
if (absDot > 1.0f - 1e-5f)
{
// parallel, choose an arbitrary perpendicular
float3 dummy;
CalculatePerpendicularNormalized(axis, out perpendicular, out dummy);
return true;
}
if (absDot > 1e-5f)
{
// reject direction
perpendicular -= dot * axis;
NormalizeSafe(ref perpendicular);
return true;
}
return NormalizeSafe(ref perpendicular);
}
public void Update(RigidTransform worldFromA, RigidTransform worldFromB, bool lockBtoA, float3 pivotA, float3 pivotB,
ref float3 directionA, ref float3 directionB, ref float3 perpendicularA, ref float3 perpendicularB, Object target)
{
// Work in world space
float3 directionAinW = math.rotate(worldFromA, directionA);
float3 directionBinW = math.rotate(worldFromB, directionB);
float3 perpendicularAinW = math.rotate(worldFromB, perpendicularA);
float3 perpendicularBinW = math.rotate(worldFromB, perpendicularB);
bool changed = false;
// If the target changed, fix up the inputs and reset the reference orientations to align with the new target's axes
if (target != m_LastTarget)
{
m_LastTarget = target;
// Enforce normalized directions
changed |= NormalizeSafe(ref directionAinW);
changed |= NormalizeSafe(ref directionBinW);
// Enforce normalized perpendiculars, orthogonal to their respective directions
changed |= NormalizePerpendicular(directionAinW, ref perpendicularAinW);
changed |= NormalizePerpendicular(directionBinW, ref perpendicularBinW);
// Calculate the rotation of the joint in A from direction and perpendicular
float3x3 rotationA = new float3x3(directionAinW, perpendicularAinW, math.cross(directionAinW, perpendicularAinW));
m_RefA = new quaternion(rotationA);
if (lockBtoA)
{
m_RefB = m_RefA;
}
else
{
// Calculate the rotation of the joint in B from direction and perpendicular
float3x3 rotationB = new float3x3(directionBinW, perpendicularBinW, math.cross(directionBinW, perpendicularBinW));
m_RefB = new quaternion(rotationB);
}
}
EditorGUI.BeginChangeCheck();
// Make rotators
quaternion oldRefA = m_RefA;
quaternion oldRefB = m_RefB;
float3 pivotAinW = math.transform(worldFromA, pivotA);
m_RefA = Handles.RotationHandle(m_RefA, pivotAinW);
float3 pivotBinW;
if (lockBtoA)
{
directionB = math.rotate(math.inverse(worldFromB), directionAinW);
perpendicularB = math.rotate(math.inverse(worldFromB), perpendicularAinW);
pivotBinW = pivotAinW;
m_RefB = m_RefA;
}
else
{
pivotBinW = math.transform(worldFromB, pivotB);
m_RefB = Handles.RotationHandle(m_RefB, pivotBinW);
}
// Apply changes from the rotators
if (EditorGUI.EndChangeCheck())
{
quaternion dqA = math.mul(m_RefA, math.inverse(oldRefA));
quaternion dqB = math.mul(m_RefB, math.inverse(oldRefB));
directionAinW = math.mul(dqA, directionAinW);
directionBinW = math.mul(dqB, directionBinW);
perpendicularAinW = math.mul(dqB, perpendicularAinW);
perpendicularBinW = math.mul(dqB, perpendicularBinW);
changed = true;
}
// Write back if the axes changed
if (changed)
{
Undo.RecordObject(target, "Edit joint axis");
directionA = math.rotate(math.inverse(worldFromA), directionAinW);
directionB = math.rotate(math.inverse(worldFromB), directionBinW);
perpendicularA = math.rotate(math.inverse(worldFromB), perpendicularAinW);
perpendicularB = math.rotate(math.inverse(worldFromB), perpendicularBinW);
}
// Draw the updated axes
float3 z = new float3(0, 0, 1); // ArrowHandleCap() draws an arrow pointing in (0, 0, 1)
Handles.ArrowHandleCap(0, pivotAinW, Quaternion.FromToRotation(z, directionAinW), HandleUtility.GetHandleSize(pivotAinW) * 0.75f, Event.current.type);
Handles.ArrowHandleCap(0, pivotAinW, Quaternion.FromToRotation(z, perpendicularAinW), HandleUtility.GetHandleSize(pivotAinW) * 0.75f, Event.current.type);
if (!lockBtoA)
{
Handles.ArrowHandleCap(0, pivotBinW, Quaternion.FromToRotation(z, directionBinW), HandleUtility.GetHandleSize(pivotBinW) * 0.75f, Event.current.type);
Handles.ArrowHandleCap(0, pivotBinW, Quaternion.FromToRotation(z, perpendicularBinW), HandleUtility.GetHandleSize(pivotBinW) * 0.75f, Event.current.type);
}
}
}
public static void EditLimits(RigidTransform worldFromA, RigidTransform worldFromB, float3 pivotA, float3 axisA, float3 axisB, float3 perpendicularA, float3 perpendicularB
ref float minLimit, ref float maxLimit, JointAngularLimitHandle limitHandle, Object target)
{
// Transform to world space
float3 pivotAinW = math.transform(worldFromA, pivotA);
float3 axisAinW = math.rotate(worldFromA, axisA);
float3 perpendicularAinW = math.rotate(worldFromA, perpendicularA);
float3 axisBinW = math.rotate(worldFromA, axisB);
float3 perpendicularBinW = math.rotate(worldFromB, perpendicularB);
// Get rotations from joint space
// JointAngularLimitHandle uses axis = (1, 0, 0) with angle = 0 at (0, 0, 1), so choose the rotations to point those in the directions of our axis and perpendicular
float3x3 worldFromJointA = new float3x3(axisAinW, -math.cross(axisAinW, perpendicularAinW), perpendicularAinW);
float3x3 worldFromJointB = new float3x3(axisBinW, -math.cross(axisBinW, perpendicularBinW), perpendicularBinW);
float3x3 jointBFromA = math.mul(math.transpose(worldFromJointB), worldFromJointA);
// Set orientation for the angular limit control
float angle = CalculateTwistAngle(new quaternion(jointBFromA), 0); // index = 0 because axis is the first column in worldFromJoint
quaternion limitOrientation = math.mul(quaternion.AxisAngle(axisAinW, angle), new quaternion(worldFromJointA));
Matrix4x4 handleMatrix = Matrix4x4.TRS(pivotAinW, limitOrientation, Vector3.one);
float size = HandleUtility.GetHandleSize(pivotAinW) * 0.75f;
limitHandle.xMin = -maxLimit;
limitHandle.xMax = -minLimit;
limitHandle.xMotion = ConfigurableJointMotion.Limited;
limitHandle.yMotion = ConfigurableJointMotion.Locked;
limitHandle.zMotion = ConfigurableJointMotion.Locked;
limitHandle.yHandleColor = new Color(0, 0, 0, 0);
limitHandle.zHandleColor = new Color(0, 0, 0, 0);
limitHandle.radius = size;
using (new Handles.DrawingScope(handleMatrix))
{
// Draw the reference axis
float3 z = new float3(0, 0, 1); // ArrowHandleCap() draws an arrow pointing in (0, 0, 1)
Handles.ArrowHandleCap(0, float3.zero, Quaternion.FromToRotation(z, new float3(1, 0, 0)), size, Event.current.type);
// Draw the limit editor handle
EditorGUI.BeginChangeCheck();
limitHandle.DrawHandle();
if (EditorGUI.EndChangeCheck())
{
// Record the target object before setting new limits so changes can be undone/redone
Undo.RecordObject(target, "Edit joint angular limits");
minLimit = -limitHandle.xMax;
maxLimit = -limitHandle.xMin;
}
}
}
}
}
#endif
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/LimitedHingeJointEditor.cs
#if UNITY_EDITOR
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(LimitedHingeJoint))]
public class LimitedHingeEditor : UnityEditor.Editor
{
private EditorUtilities.AxisEditor m_AxisEditor = new EditorUtilities.AxisEditor();
private JointAngularLimitHandle m_LimitHandle = new JointAngularLimitHandle();
EditorGUI.BeginChangeCheck();
GUILayout.BeginHorizontal();
GUILayout.Label("Editors:");
limitedHinge.EditPivots = GUILayout.Toggle(limitedHinge.EditPivots, new GUIContent("Pivot"), "Button");
limitedHinge.EditAxes = GUILayout.Toggle(limitedHinge.EditAxes, new GUIContent("Axis"), "Button");
limitedHinge.EditLimits = GUILayout.Toggle(limitedHinge.EditLimits, new GUIContent("Limits"), "Button");
GUILayout.EndHorizontal();
DrawDefaultInspector();
if (EditorGUI.EndChangeCheck())
{
SceneView.RepaintAll();
}
}
if (limitedHinge.EditPivots)
{
EditorUtilities.EditPivot(limitedHinge.worldFromA, limitedHinge.worldFromB, limitedHinge.AutoSetConnected,
ref limitedHinge.PositionLocal, ref limitedHinge.PositionInConnectedEntity, limitedHinge);
}
if (limitedHinge.EditAxes)
{
m_AxisEditor.Update(limitedHinge.worldFromA, limitedHinge.worldFromB,
limitedHinge.AutoSetConnected,
limitedHinge.PositionLocal, limitedHinge.PositionInConnectedEntity,
ref limitedHinge.HingeAxisLocal, ref limitedHinge.HingeAxisInConnectedEntity,
ref limitedHinge.PerpendicularAxisLocal, ref limitedHinge.PerpendicularAxisInConnectedEntity,
limitedHinge);
}
if (limitedHinge.EditLimits)
{
EditorUtilities.EditLimits(limitedHinge.worldFromA, limitedHinge.worldFromB,
limitedHinge.PositionLocal,
limitedHinge.HingeAxisLocal, limitedHinge.HingeAxisInConnectedEntity,
limitedHinge.PerpendicularAxisLocal, limitedHinge.PerpendicularAxisInConnectedEntity,
ref limitedHinge.MinAngle, ref limitedHinge.MaxAngle, m_LimitHandle, limitedHinge);
}
}
}
}
#endif
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/PhysicsBodyAuthoringEditor.cs
using System.Collections.Generic;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(PhysicsBodyAuthoring))]
[CanEditMultipleObjects]
class PhysicsBodyAuthoringEditor : BaseEditor
{
static class Content
{
public static readonly GUIContent MassLabel = EditorGUIUtility.TrTextContent("Mass");
public static readonly GUIContent CenterOfMassLabel = EditorGUIUtility.TrTextContent(
"Center of Mass", "Center of mass in the space of this body's transform."
);
public static readonly GUIContent InertiaTensorLabel = EditorGUIUtility.TrTextContent(
"Inertia Tensor", "Resistance to angular motion about each axis of rotation."
);
public static readonly GUIContent OrientationLabel = EditorGUIUtility.TrTextContent(
"Orientation", "Orientation of the body's inertia tensor in the space of its transform."
);
public static readonly GUIContent AdvancedLabel = EditorGUIUtility.TrTextContent(
"Advanced", "Advanced options"
);
}
#pragma warning disable 649
[AutoPopulate] SerializedProperty m_MotionType;
[AutoPopulate] SerializedProperty m_Smoothing;
[AutoPopulate] SerializedProperty m_Mass;
[AutoPopulate] SerializedProperty m_GravityFactor;
[AutoPopulate] SerializedProperty m_LinearDamping;
[AutoPopulate] SerializedProperty m_AngularDamping;
[AutoPopulate] SerializedProperty m_InitialLinearVelocity;
[AutoPopulate] SerializedProperty m_InitialAngularVelocity;
[AutoPopulate] SerializedProperty m_OverrideDefaultMassDistribution;
[AutoPopulate] SerializedProperty m_CenterOfMass;
[AutoPopulate] SerializedProperty m_Orientation;
[AutoPopulate] SerializedProperty m_InertiaTensor;
[AutoPopulate] SerializedProperty m_WorldIndex;
[AutoPopulate] SerializedProperty m_CustomTags;
#pragma warning restore 649
bool showAdvanced;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_MotionType);
if (m_MotionType.intValue != (int)BodyMotionType.Static)
EditorGUILayout.PropertyField(m_Smoothing);
if (m_MotionType.intValue == (int)BodyMotionType.Dynamic)
{
EditorGUILayout.PropertyField(m_LinearDamping, true);
EditorGUILayout.PropertyField(m_AngularDamping, true);
}
if (m_MotionType.intValue != (int)BodyMotionType.Static)
{
EditorGUILayout.PropertyField(m_InitialLinearVelocity, true);
EditorGUILayout.PropertyField(m_InitialAngularVelocity, true);
}
if (m_MotionType.intValue == (int)BodyMotionType.Dynamic)
{
EditorGUILayout.PropertyField(m_GravityFactor, true);
}
EditorGUI.BeginDisabledGroup(!dynamic);
if (dynamic)
{
EditorGUILayout.PropertyField(m_Orientation, Content.OrientationLabel);
EditorGUILayout.PropertyField(m_InertiaTensor, Content.InertiaTensorLabel);
}
else
{
EditorGUI.BeginDisabledGroup(true);
var position =
EditorGUILayout.GetControlRect(true, EditorGUI.GetPropertyHeight(m_InertiaTensor));
EditorGUI.BeginProperty(position, Content.InertiaTensorLabel, m_InertiaTensor);
EditorGUI.Vector3Field(position, Content.InertiaTensorLabel,
Vector3.one * float.PositiveInfinity);
EditorGUI.EndProperty();
EditorGUI.EndDisabledGroup();
}
EditorGUI.EndDisabledGroup();
--EditorGUI.indentLevel;
}
}
EditorGUILayout.PropertyField(m_CustomTags);
--EditorGUI.indentLevel;
}
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
DisplayStatusMessages();
}
MessageType m_Status;
List<string> m_StatusMessages = new List<string>(8);
void DisplayStatusMessages()
{
m_Status = MessageType.None;
m_StatusMessages.Clear();
var hierarchyStatus = StatusMessageUtility.GetHierarchyStatusMessage(targets, out var hierarchyStatusMessage);
if (!string.IsNullOrEmpty(hierarchyStatusMessage))
{
m_StatusMessages.Add(hierarchyStatusMessage);
m_Status = (MessageType)math.max((int)m_Status, (int)hierarchyStatus);
}
if (m_StatusMessages.Count > 0)
EditorGUILayout.HelpBox(string.Join("\n\n", m_StatusMessages), m_Status);
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/PhysicsCategoryNamesEditor.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(PhysicsCategoryNames))]
[CanEditMultipleObjects]
class PhysicsCategoryNamesEditor : BaseEditor
{
#pragma warning disable 649
[AutoPopulate(ElementFormatString = "Category {0}", Resizable = false, Reorderable = false)]
ReorderableList m_CategoryNames;
#pragma warning restore 649
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/PhysicsShapeAuthoringEditor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityMesh = UnityEngine.Mesh;
using Unity.Physics.Extensions;
using LegacyRigidBody = UnityEngine.Rigidbody;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(PhysicsShapeAuthoring))]
[CanEditMultipleObjects]
class PhysicsShapeAuthoringEditor : BaseEditor
{
static class Styles
{
const string k_Plural = "One or more selected objects";
const string k_Singular = "This object";
public static readonly string GenericUndoMessage = L10n.Tr("Change Shape");
public static readonly string MultipleShapeTypesLabel =
L10n.Tr("Multiple shape types in current selection.");
public static readonly string PreviewGenerationNotification =
L10n.Tr("Generating collision geometry preview...");
static readonly GUIContent k_FitToRenderMeshesLabel =
EditorGUIUtility.TrTextContent("Fit to Enabled Render Meshes");
static readonly GUIContent k_FitToRenderMeshesWarningLabelSg = new GUIContent(
k_FitToRenderMeshesLabel.text,
EditorGUIUtility.Load("console.warnicon") as Texture,
L10n.Tr($"{k_Singular} has non-uniform scale. Trying to fit the shape to render meshes might produce unexpected results.")
);
static readonly GUIContent k_FitToRenderMeshesWarningLabelPl = new GUIContent(
k_FitToRenderMeshesLabel.text,
EditorGUIUtility.Load("console.warnicon") as Texture,
L10n.Tr($"{k_Plural} has non-uniform scale. Trying to fit the shape to render meshes might produce unexpected results.")
);
public static readonly GUIContent CenterLabel = EditorGUIUtility.TrTextContent("Center");
public static readonly GUIContent SizeLabel = EditorGUIUtility.TrTextContent("Size");
public static readonly GUIContent OrientationLabel = EditorGUIUtility.TrTextContent(
"Orientation", "Euler orientation in the shape's local space (ZXY order)."
);
public static readonly GUIContent CylinderSideCountLabel = EditorGUIUtility.TrTextContent("Side Count");
public static readonly GUIContent RadiusLabel = EditorGUIUtility.TrTextContent("Radius");
public static readonly GUIContent ForceUniqueLabel = EditorGUIUtility.TrTextContent(
"Force Unique",
"If set to true, then this object will always produce a unique collider for run-time during conversion. " +
"If set to false, then this object may share its collider data with other objects if they have the same inputs. " +
"You should enable this option if you plan to modify this instance's collider at run-time."
);
public static readonly GUIContent MaterialLabel = EditorGUIUtility.TrTextContent("Material");
public static readonly GUIContent SetRecommendedConvexValues = EditorGUIUtility.TrTextContent(
"Set Recommended Default Values",
"Set recommended values for convex hull generation parameters based on either render meshes or custom mesh."
);
public static GUIContent GetFitToRenderMeshesLabel(int numTargets, MessageType status) =>
status >= MessageType.Warning
? numTargets == 1 ? k_FitToRenderMeshesWarningLabelSg : k_FitToRenderMeshesWarningLabelPl
: k_FitToRenderMeshesLabel;
static readonly string[] k_NoGeometryWarning =
{
L10n.Tr($"{k_Singular} has no enabled render meshes in its hierarchy and no custom mesh assigned."),
L10n.Tr($"{k_Plural} has no enabled render meshes in their hierarchies and no custom mesh assigned.")
};
public static string GetNoGeometryWarning(int numTargets) =>
numTargets == 1 ? k_NoGeometryWarning[0] : k_NoGeometryWarning[1];
m_NumImplicitStatic = targets.Cast<PhysicsShapeAuthoring>().Count(
shape => shape.GetPrimaryBody() == shape.gameObject
&& shape.GetComponent<PhysicsBodyAuthoring>() == null
&& shape.GetComponent<LegacyRigidBody>() == null
);
Undo.undoRedoPerformed += Repaint;
}
void OnDisable()
{
Undo.undoRedoPerformed -= Repaint;
SceneViewUtility.ClearNotificationInSceneView();
foreach (var preview in m_PreviewData.Values)
preview.Dispose();
if (m_DropDown != null)
m_DropDown.CloseWithoutUndo();
}
class PreviewMeshData : IDisposable
{
[BurstCompile]
struct CreateTempHullJob : IJob
{
public ConvexHullGenerationParameters GenerationParameters;
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<float3> Points;
public NativeArray<BlobAssetReference<Collider>> Output;
public void Execute() => Output[0] = ConvexCollider.Create(Points, GenerationParameters, CollisionFilter.Default);
}
[BurstCompile]
struct CreateTempMeshJob : IJob
{
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<float3> Points;
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<int3> Triangles;
public NativeArray<BlobAssetReference<Collider>> Output;
public void Execute() => Output[0] = MeshCollider.Create(Points, Triangles);
}
static readonly List<Vector3> s_ReusableEdges = new List<Vector3>(1024);
public Vector3[] Edges = Array.Empty<Vector3>();
public Aabb Bounds = new Aabb();
bool m_Disposed;
uint m_InputHash;
ConvexHullGenerationParameters m_HashedConvexParameters;
NativeArray<float3> m_HashedPoints = new NativeArray<float3>(0, Allocator.Persistent);
// multiple preview jobs might be running if user assigned a different mesh before previous job completed
JobHandle m_MostRecentlyScheduledJob;
Dictionary<JobHandle, NativeArray<BlobAssetReference<Collider>>> m_PreviewJobsOutput =
new Dictionary<JobHandle, NativeArray<BlobAssetReference<Collider>>>();
unsafe uint GetInputHash(
PhysicsShapeAuthoring shape,
NativeList<float3> currentPoints,
NativeArray<float3> hashedPoints,
ConvexHullGenerationParameters hashedConvexParameters,
out ConvexHullGenerationParameters currentConvexProperties
)
{
currentConvexProperties = default;
switch (shape.ShapeType)
{
case ShapeType.ConvexHull:
shape.GetBakedConvexProperties(currentPoints); // TODO: use HashableShapeInputs
currentConvexProperties = shape.ConvexHullGenerationParameters;
return math.hash(
new uint3(
(uint)shape.ShapeType,
currentConvexProperties.GetStableHash(hashedConvexParameters),
currentPoints.GetStableHash(hashedPoints)
)
);
case ShapeType.Mesh:
var triangles = new NativeList<int3>(1024, Allocator.Temp);
shape.GetBakedMeshProperties(currentPoints, triangles); // TODO: use HashableShapeInputs
return math.hash(
new uint3(
(uint)shape.ShapeType,
currentPoints.GetStableHash(hashedPoints),
math.hash(triangles.GetUnsafePtr(), UnsafeUtility.SizeOf<int3>() * triangles.Length)
)
);
default:
return (uint)shape.ShapeType;
}
}
public void SchedulePreviewIfChanged(PhysicsShapeAuthoring shape)
{
using (var currentPoints = new NativeList<float3>(65535, Allocator.Temp))
{
var hash = GetInputHash(
shape, currentPoints, m_HashedPoints, m_HashedConvexParameters, out var currentConvexParameters
);
if (m_InputHash == hash)
return;
m_InputHash = hash;
m_HashedConvexParameters = currentConvexParameters;
m_HashedPoints.Dispose();
m_HashedPoints = new NativeArray<float3>(currentPoints.Length, Allocator.Persistent);
m_HashedPoints.CopyFrom(currentPoints.AsArray());
}
if (shape.ShapeType != ShapeType.ConvexHull && shape.ShapeType != ShapeType.Mesh)
return;
// TODO: cache results per input data hash, and simply use existing data (e.g., to make undo/redo faster)
var output = new NativeArray<BlobAssetReference<Collider>>(1, Allocator.Persistent);
m_MostRecentlyScheduledJob = shape.ShapeType == ShapeType.Mesh
? ScheduleMeshPreview(shape, output)
: ScheduleConvexHullPreview(shape, output);
m_PreviewJobsOutput.Add(m_MostRecentlyScheduledJob, output);
if (m_PreviewJobsOutput.Count == 1)
{
CheckPreviewJobsForCompletion();
if (m_MostRecentlyScheduledJob.Equals(default(JobHandle)))
return;
EditorApplication.update += CheckPreviewJobsForCompletion;
EditorApplication.delayCall += () =>
{
SceneViewUtility.DisplayProgressNotification(
Styles.PreviewGenerationNotification, () => float.PositiveInfinity
);
};
}
}
JobHandle ScheduleConvexHullPreview(PhysicsShapeAuthoring shape, NativeArray<BlobAssetReference<Collider>> output)
{
var pointCloud = new NativeList<float3>(65535, Allocator.Temp);
shape.GetBakedConvexProperties(pointCloud);
if (pointCloud.Length == 0)
return default;
// copy to NativeArray because NativeList not yet compatible with DeallocateOnJobCompletion
var pointsArray = new NativeArray<float3>(
pointCloud.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory
);
pointsArray.CopyFrom(pointCloud.AsArray());
// TODO: if there is still an active job with the same input data hash, then just set it to be most recently scheduled job
return new CreateTempHullJob
{
GenerationParameters = shape.ConvexHullGenerationParameters.ToRunTime(),
Points = pointsArray,
Output = output
}.Schedule();
}
JobHandle ScheduleMeshPreview(PhysicsShapeAuthoring shape, NativeArray<BlobAssetReference<Collider>> output)
{
var points = new NativeList<float3>(1024, Allocator.Temp);
var triangles = new NativeList<int3>(1024, Allocator.Temp);
shape.GetBakedMeshProperties(points, triangles);
if (points.Length == 0 || triangles.Length == 0)
return default;
// copy to NativeArray because NativeList not yet compatible with DeallocateOnJobCompletion
var pointsArray = new NativeArray<float3>(
points.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory
);
pointsArray.CopyFrom(points.AsArray());
var triangleArray = new NativeArray<int3>(
triangles.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory
);
triangleArray.CopyFrom(triangles.AsArray());
// TODO: if there is still an active job with the same input data hash, then just set it to be most recently scheduled job
return new CreateTempMeshJob
{
Points = pointsArray,
Triangles = triangleArray,
Output = output
}.Schedule();
}
unsafe void CheckPreviewJobsForCompletion()
{
var repaintSceneViews = false;
foreach (var job in m_PreviewJobsOutput.Keys.ToArray()) // TODO: don't allocate on heap
{
// repaint scene views to indicate progress if most recent preview job is still in the queue
var mostRecentlyScheduledJob = m_MostRecentlyScheduledJob.Equals(job);
repaintSceneViews |= mostRecentlyScheduledJob;
if (!job.IsCompleted)
continue;
var output = m_PreviewJobsOutput[job];
m_PreviewJobsOutput.Remove(job);
job.Complete();
// only populate preview edge data if not already disposed and this job was actually the most recent
if (!m_Disposed && mostRecentlyScheduledJob)
{
if (!output[0].IsCreated)
{
Edges = Array.Empty<Vector3>();
Bounds = new Aabb();
}
else
{
switch (output[0].Value.Type)
{
case ColliderType.Convex:
ref var convex = ref output[0].As<ConvexCollider>();
DrawingUtility.GetConvexColliderEdges(
ref convex, s_ReusableEdges
);
Bounds = convex.CalculateAabb();
break;
case ColliderType.Mesh:
ref var mesh = ref output[0].As<MeshCollider>();
DrawingUtility.GetMeshColliderEdges(
ref mesh, s_ReusableEdges
);
Bounds = mesh.CalculateAabb();
break;
}
Edges = s_ReusableEdges.ToArray();
}
EditorApplication.delayCall += SceneViewUtility.ClearNotificationInSceneView;
}
if (output.IsCreated)
{
if (output[0].IsCreated)
output[0].Dispose();
output.Dispose();
}
}
if (repaintSceneViews)
SceneView.RepaintAll();
if (m_PreviewJobsOutput.Count == 0)
EditorApplication.update -= CheckPreviewJobsForCompletion;
}
public void Dispose()
{
m_Disposed = true;
m_HashedPoints.Dispose();
}
}
Dictionary<PhysicsShapeAuthoring, PreviewMeshData> m_PreviewData = new Dictionary<PhysicsShapeAuthoring, PreviewMeshData>();
PreviewMeshData GetPreviewData(PhysicsShapeAuthoring shape)
{
if (shape.ShapeType != ShapeType.ConvexHull && shape.ShapeType != ShapeType.Mesh)
return null;
if (!m_PreviewData.TryGetValue(shape, out var preview))
{
preview = m_PreviewData[shape] = new PreviewMeshData();
preview.SchedulePreviewIfChanged(shape);
}
// do not generate a new preview until the user has finished dragging a control handle (e.g., scale)
if (m_DraggingControlID == 0 && !EditorGUIUtility.editingTextField)
preview.SchedulePreviewIfChanged(shape);
return preview;
}
void UpdateGeometryState()
{
m_GeometryState = GeometryState.Okay;
var skinnedPoints = new NativeList<float3>(8192, Allocator.Temp);
foreach (PhysicsShapeAuthoring shape in targets)
{
// if a custom mesh is assigned, only check it
using (var so = new SerializedObject(shape))
{
var customMesh = so.FindProperty(m_CustomMesh.propertyPath).objectReferenceValue as UnityMesh;
if (customMesh != null)
{
m_GeometryState |= GetGeometryState(customMesh, shape.gameObject);
continue;
}
}
// otherwise check all mesh filters in the hierarchy that might be included
var geometryState = GeometryState.Okay;
using (var scope = new GetActiveChildrenScope<MeshFilter>(shape, shape.transform))
{
foreach (var meshFilter in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(meshFilter, filterOutInvalid: false))
geometryState |= GetGeometryState(meshFilter.sharedMesh, shape.gameObject);
}
}
if (shape.ShapeType == ShapeType.Mesh)
{
PhysicsShapeAuthoring.GetAllSkinnedPointsInHierarchyBelongingToShape(
shape, skinnedPoints, false, default, default, default
);
if (skinnedPoints.Length > 0)
geometryState |= GeometryState.MeshWithSkinnedPoints;
}
m_GeometryState |= geometryState;
}
skinnedPoints.Dispose();
}
static GeometryState GetGeometryState(UnityMesh mesh, GameObject host)
{
if (mesh == null)
return GeometryState.NoGeometry;
if (!mesh.IsValidForConversion(host))
return GeometryState.NonReadableGeometry;
return GeometryState.Okay;
}
public override void OnInspectorGUI()
{
var hotControl = GUIUtility.hotControl;
switch (Event.current.GetTypeForControl(hotControl))
{
case EventType.MouseDrag:
m_DraggingControlID = hotControl;
break;
case EventType.MouseUp:
m_DraggingControlID = 0;
break;
}
UpdateGeometryState();
serializedObject.Update();
UpdateStatusMessages();
EditorGUI.BeginChangeCheck();
DisplayShapeSelector();
++EditorGUI.indentLevel;
if (m_ShapeType.hasMultipleDifferentValues)
EditorGUILayout.HelpBox(Styles.MultipleShapeTypesLabel, MessageType.None);
else
{
switch ((ShapeType)m_ShapeType.intValue)
{
case ShapeType.Box:
AutomaticPrimitiveControls();
DisplayBoxControls();
break;
case ShapeType.Capsule:
AutomaticPrimitiveControls();
DisplayCapsuleControls();
break;
case ShapeType.Sphere:
AutomaticPrimitiveControls();
DisplaySphereControls();
break;
case ShapeType.Cylinder:
AutomaticPrimitiveControls();
DisplayCylinderControls();
break;
case ShapeType.Plane:
AutomaticPrimitiveControls();
DisplayPlaneControls();
break;
case ShapeType.ConvexHull:
RecommendedConvexValuesButton();
EditorGUILayout.PropertyField(m_ConvexHullGenerationParameters);
EditorGUILayout.PropertyField(m_MinimumSkinnedVertexWeight);
DisplayMeshControls();
break;
case ShapeType.Mesh:
DisplayMeshControls();
break;
default:
throw new UnimplementedShapeException((ShapeType)m_ShapeType.intValue);
}
EditorGUILayout.PropertyField(m_ForceUnique, Styles.ForceUniqueLabel);
}
--EditorGUI.indentLevel;
EditorGUILayout.LabelField(Styles.MaterialLabel);
++EditorGUI.indentLevel;
EditorGUILayout.PropertyField(m_Material);
--EditorGUI.indentLevel;
if (m_StatusMessages.Count > 0)
EditorGUILayout.HelpBox(string.Join("\n\n", m_StatusMessages), m_Status);
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
}
void RecommendedConvexValuesButton()
{
EditorGUI.BeginDisabledGroup(
(m_GeometryState & GeometryState.NoGeometry) == GeometryState.NoGeometry ||
EditorUtility.IsPersistent(target)
);
var rect = EditorGUI.IndentedRect(
EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight, EditorStyles.miniButton)
);
var buttonLabel = Styles.SetRecommendedConvexValues;
if (GUI.Button(rect, buttonLabel, Styles.Button))
{
Undo.RecordObjects(targets, buttonLabel.text);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.InitializeConvexHullGenerationParameters();
EditorUtility.SetDirty(shape);
}
}
EditorGUI.EndDisabledGroup();
}
MessageType m_GeometryStatus;
List<string> m_GeometryStatusMessages = new List<string>();
HashSet<string> m_ShapeSuggestions = new HashSet<string>();
MessageType m_Status;
List<string> m_StatusMessages = new List<string>(8);
MessageType m_MatrixStatus;
List<MatrixState> m_MatrixStates = new List<MatrixState>();
void UpdateStatusMessages()
{
m_Status = MessageType.None;
m_StatusMessages.Clear();
if (m_NumImplicitStatic != 0)
m_StatusMessages.Add(Styles.GetStaticColliderStatusMessage(targets.Length));
m_ShapeSuggestions.Clear();
foreach (PhysicsShapeAuthoring shape in targets)
{
const float k_Epsilon = HashableShapeInputs.k_DefaultLinearPrecision;
switch (shape.ShapeType)
{
case ShapeType.Box:
var box = shape.GetBakedBoxProperties();
var max = math.cmax(box.Size);
var min = math.cmin(box.Size);
if (min < k_Epsilon)
m_ShapeSuggestions.Add(Styles.BoxPlaneSuggestion);
else if (math.abs(box.BevelRadius - min * 0.5f) < k_Epsilon)
{
if (math.abs(max - min) < k_Epsilon)
m_ShapeSuggestions.Add(Styles.BoxSphereSuggestion);
else if (math.abs(math.lengthsq(box.Size - new float3(min)) - math.pow(max - min, 2f)) < k_Epsilon)
m_ShapeSuggestions.Add(Styles.BoxCapsuleSuggestion);
}
break;
case ShapeType.Capsule:
var capsule = shape.GetBakedCapsuleProperties();
if (math.abs(capsule.Height - 2f * capsule.Radius) < k_Epsilon)
m_ShapeSuggestions.Add(Styles.CapsuleSphereSuggestion);
break;
case ShapeType.Cylinder:
var cylinder = shape.GetBakedCylinderProperties();
if (math.abs(cylinder.BevelRadius - cylinder.Radius) < k_Epsilon)
{
m_ShapeSuggestions.Add(math.abs(cylinder.Height - 2f * cylinder.Radius) < k_Epsilon
? Styles.CylinderSphereSuggestion
: Styles.CylinderCapsuleSuggestion);
}
break;
}
}
foreach (var suggestion in m_ShapeSuggestions)
m_StatusMessages.Add(suggestion);
var hierarchyStatus = StatusMessageUtility.GetHierarchyStatusMessage(targets, out var hierarchyStatusMessage);
if (!string.IsNullOrEmpty(hierarchyStatusMessage))
{
m_StatusMessages.Add(hierarchyStatusMessage);
m_Status = (MessageType)math.max((int)m_Status, (int)hierarchyStatus);
}
m_MatrixStates.Clear();
foreach (var t in targets)
{
var localToWorld = (float4x4)(t as Component).transform.localToWorldMatrix;
m_MatrixStates.Add(ManipulatorUtility.GetMatrixState(ref localToWorld));
}
m_MatrixStatus = StatusMessageUtility.GetMatrixStatusMessage(m_MatrixStates, out var matrixStatusMessage);
if (m_MatrixStatus != MessageType.None)
{
m_StatusMessages.Add(matrixStatusMessage);
m_Status = (MessageType)math.max((int)m_Status, (int)m_MatrixStatus);
}
m_GeometryStatus = MessageType.None;
m_GeometryStatusMessages.Clear();
if ((m_GeometryState & GeometryState.NoGeometry) == GeometryState.NoGeometry)
{
m_GeometryStatusMessages.Add(Styles.GetNoGeometryWarning(targets.Length));
m_GeometryStatus = (MessageType)math.max((int)m_GeometryStatus, (int)MessageType.Error);
}
if ((m_GeometryState & GeometryState.NonReadableGeometry) == GeometryState.NonReadableGeometry)
{
m_GeometryStatusMessages.Add(Styles.GetNonReadableGeometryWarning(targets.Length));
m_GeometryStatus = (MessageType)math.max((int)m_GeometryStatus, (int)MessageType.Warning);
}
if ((m_GeometryState & GeometryState.MeshWithSkinnedPoints) == GeometryState.MeshWithSkinnedPoints)
{
m_GeometryStatusMessages.Add(Styles.GetMeshWithSkinnedPointsWarning(targets.Length));
m_GeometryStatus = (MessageType)math.max((int)m_GeometryStatus, (int)MessageType.Warning);
}
}
void DisplayShapeSelector()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_ShapeType);
if (!EditorGUI.EndChangeCheck())
return;
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
switch ((ShapeType)m_ShapeType.intValue)
{
case ShapeType.Box:
shape.SetBox(shape.GetBoxProperties(out var orientation), orientation);
break;
case ShapeType.Capsule:
shape.SetCapsule(shape.GetCapsuleProperties());
break;
case ShapeType.Sphere:
shape.SetSphere(shape.GetSphereProperties(out orientation), orientation);
break;
case ShapeType.Cylinder:
shape.SetCylinder(shape.GetCylinderProperties(out orientation), orientation);
break;
case ShapeType.Plane:
shape.GetPlaneProperties(out var center, out var size2D, out orientation);
shape.SetPlane(center, size2D, orientation);
break;
case ShapeType.ConvexHull:
case ShapeType.Mesh:
return;
default:
throw new UnimplementedShapeException((ShapeType)m_ShapeType.intValue);
}
EditorUtility.SetDirty(shape);
}
GUIUtility.ExitGUI();
}
void AutomaticPrimitiveControls()
{
EditorGUI.BeginDisabledGroup(
(m_GeometryState & GeometryState.NoGeometry) == GeometryState.NoGeometry || EditorUtility.IsPersistent(target)
);
var buttonLabel = Styles.GetFitToRenderMeshesLabel(targets.Length, m_MatrixStatus);
var rect = EditorGUI.IndentedRect(
EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight, EditorStyles.miniButton)
);
if (GUI.Button(rect, buttonLabel, Styles.ButtonDropDown))
m_DropDown = FitToRenderMeshesDropDown.Show(rect, buttonLabel.text, m_MinimumSkinnedVertexWeight);
EditorGUI.EndDisabledGroup();
}
class FitToRenderMeshesDropDown : EditorWindow
{
static class Styles
{
public const float WindowWidth = 400f;
public const float LabelWidth = 200f;
public static GUIStyle Button => PhysicsShapeAuthoringEditor.Styles.Button;
}
static class Content
{
public static readonly string ApplyLabel = L10n.Tr("Apply");
public static readonly string CancelLabel = L10n.Tr("Cancel");
}
bool m_ApplyChanges;
bool m_ClosedWithoutUndo;
int m_UndoGroup;
SerializedProperty m_MinimumSkinnedVertexWeight;
public static FitToRenderMeshesDropDown Show(
Rect buttonRect, string title, SerializedProperty minimumSkinnedVertexWeight
)
{
var window = CreateInstance<FitToRenderMeshesDropDown>();
window.titleContent = EditorGUIUtility.TrTextContent(title);
window.m_UndoGroup = Undo.GetCurrentGroup();
window.m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
var size = new Vector2(
math.max(buttonRect.width, Styles.WindowWidth),
(EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing) * 3f
);
window.maxSize = window.minSize = size;
window.ShowAsDropDown(GUIUtility.GUIToScreenRect(buttonRect), size);
return window;
}
void OnGUI()
{
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = Styles.LabelWidth;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_MinimumSkinnedVertexWeight);
if (EditorGUI.EndChangeCheck())
ApplyChanges();
EditorGUIUtility.labelWidth = labelWidth;
GUILayout.FlexibleSpace();
var buttonRect = GUILayoutUtility.GetRect(0f, EditorGUIUtility.singleLineHeight);
var buttonLeft = new Rect(buttonRect)
{
width = 0.5f * (buttonRect.width - EditorGUIUtility.standardVerticalSpacing)
};
var buttonRight = new Rect(buttonLeft)
{
x = buttonLeft.xMax + EditorGUIUtility.standardVerticalSpacing
};
var close = false;
buttonRect = Application.platform == RuntimePlatform.OSXEditor ? buttonLeft : buttonRight;
if (
GUI.Button(buttonRect, Content.CancelLabel, Styles.Button)
|| Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape
)
{
close = true;
}
buttonRect = Application.platform == RuntimePlatform.OSXEditor ? buttonRight : buttonLeft;
if (GUI.Button(buttonRect, Content.ApplyLabel, Styles.Button))
{
close = true;
m_ApplyChanges = true;
}
if (close)
{
Close();
EditorGUIUtility.ExitGUI();
}
}
void ApplyChanges()
{
m_MinimumSkinnedVertexWeight.serializedObject.ApplyModifiedProperties();
Undo.RecordObjects(m_MinimumSkinnedVertexWeight.serializedObject.targetObjects, titleContent.text);
foreach (PhysicsShapeAuthoring shape in m_MinimumSkinnedVertexWeight.serializedObject.targetObjects)
{
using (var so = new SerializedObject(shape))
{
shape.FitToEnabledRenderMeshes(
so.FindProperty(m_MinimumSkinnedVertexWeight.propertyPath).floatValue
);
EditorUtility.SetDirty(shape);
}
}
m_MinimumSkinnedVertexWeight.serializedObject.Update();
}
public void CloseWithoutUndo()
{
m_ApplyChanges = true;
Close();
}
void OnDestroy()
{
if (m_ApplyChanges)
ApplyChanges();
else
Undo.RevertAllDownToGroup(m_UndoGroup);
}
}
void DisplayBoxControls()
{
EditorGUILayout.PropertyField(m_PrimitiveSize, Styles.SizeLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
EditorGUILayout.PropertyField(m_BevelRadius);
}
void DisplayCapsuleControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Capsule);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.SetCapsule(shape.GetCapsuleProperties());
EditorUtility.SetDirty(shape);
}
}
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
}
void DisplaySphereControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_SphereRadius, Styles.RadiusLabel);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.SetSphere(shape.GetSphereProperties(out EulerAngles orientation), orientation);
EditorUtility.SetDirty(shape);
}
}
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
}
void DisplayCylinderControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Cylinder);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(targets, Styles.GenericUndoMessage);
foreach (PhysicsShapeAuthoring shape in targets)
{
shape.SetCylinder(shape.GetCylinderProperties(out var orientation), orientation);
EditorUtility.SetDirty(shape);
}
}
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
EditorGUILayout.PropertyField(m_CylinderSideCount, Styles.CylinderSideCountLabel);
EditorGUILayout.PropertyField(m_BevelRadius);
}
void DisplayPlaneControls()
{
EditorGUILayout.PropertyField(m_PrimitiveSize, Styles.SizeLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveCenter, Styles.CenterLabel, true);
EditorGUILayout.PropertyField(m_PrimitiveOrientation, Styles.OrientationLabel, true);
}
void DisplayMeshControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_CustomMesh);
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
}
if (m_GeometryStatusMessages.Count > 0)
EditorGUILayout.HelpBox(string.Join("\n\n", m_GeometryStatusMessages), m_GeometryStatus);
}
static readonly BeveledBoxBoundsHandle s_Box = new BeveledBoxBoundsHandle();
static readonly PhysicsCapsuleBoundsHandle s_Capsule =
new PhysicsCapsuleBoundsHandle { heightAxis = CapsuleBoundsHandle.HeightAxis.Z };
static readonly BeveledCylinderBoundsHandle s_Cylinder = new BeveledCylinderBoundsHandle();
static readonly PhysicsSphereBoundsHandle s_Sphere = new PhysicsSphereBoundsHandle();
static readonly BoxBoundsHandle s_Plane =
new BoxBoundsHandle { axes = PrimitiveBoundsHandle.Axes.X | PrimitiveBoundsHandle.Axes.Z };
static readonly Color k_ShapeHandleColor = new Color32(145, 244, 139, 210);
static readonly Color k_ShapeHandleColorDisabled = new Color32(84, 200, 77, 140);
void OnSceneGUI()
{
var hotControl = GUIUtility.hotControl;
switch (Event.current.GetTypeForControl(hotControl))
{
case EventType.MouseDrag:
m_DraggingControlID = hotControl;
break;
case EventType.MouseUp:
m_DraggingControlID = 0;
break;
}
var shape = target as PhysicsShapeAuthoring;
var handleColor = shape.enabled ? k_ShapeHandleColor : k_ShapeHandleColorDisabled;
var handleMatrix = shape.GetShapeToWorldMatrix();
using (new Handles.DrawingScope(handleColor, handleMatrix))
{
switch (shape.ShapeType)
{
case ShapeType.Box:
var boxGeometry = shape.GetBakedBoxProperties();
s_Box.bevelRadius = boxGeometry.BevelRadius;
s_Box.center = float3.zero;
s_Box.size = boxGeometry.Size;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(boxGeometry.Center, boxGeometry.Orientation, 1f))))
s_Box.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedBoxSize(s_Box.size, s_Box.bevelRadius);
}
break;
case ShapeType.Capsule:
s_Capsule.center = float3.zero;
var capsuleGeometry = shape.GetBakedCapsuleProperties();
s_Capsule.height = capsuleGeometry.Height;
s_Capsule.radius = capsuleGeometry.Radius;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(capsuleGeometry.Center, capsuleGeometry.Orientation, 1f))))
s_Capsule.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedCapsuleSize(s_Capsule.height, s_Capsule.radius);
}
break;
case ShapeType.Sphere:
var sphereGeometry = shape.GetBakedSphereProperties(out EulerAngles orientation);
s_Sphere.center = float3.zero;
s_Sphere.radius = sphereGeometry.Radius;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(sphereGeometry.Center, orientation, 1f))))
s_Sphere.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedSphereRadius(s_Sphere.radius);
}
break;
case ShapeType.Cylinder:
var cylinderGeometry = shape.GetBakedCylinderProperties();
s_Cylinder.center = float3.zero;
s_Cylinder.height = cylinderGeometry.Height;
s_Cylinder.radius = cylinderGeometry.Radius;
s_Cylinder.sideCount = cylinderGeometry.SideCount;
s_Cylinder.bevelRadius = cylinderGeometry.BevelRadius;
EditorGUI.BeginChangeCheck();
{
using (new Handles.DrawingScope(math.mul(Handles.matrix, float4x4.TRS(cylinderGeometry.Center, cylinderGeometry.Orientation, 1f))))
s_Cylinder.DrawHandle();
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedCylinderSize(s_Cylinder.height, s_Cylinder.radius, s_Cylinder.bevelRadius);
}
break;
case ShapeType.Plane:
shape.GetPlaneProperties(out var center, out var size2, out orientation);
s_Plane.center = float3.zero;
s_Plane.size = new float3(size2.x, 0f, size2.y);
EditorGUI.BeginChangeCheck();
{
var m = math.mul(shape.transform.localToWorldMatrix, float4x4.TRS(center, orientation, 1f));
using (new Handles.DrawingScope(m))
s_Plane.DrawHandle();
var right = math.mul(m, new float4 { x = 1f }).xyz;
var forward = math.mul(m, new float4 { z = 1f }).xyz;
var normal = math.cross(math.normalizesafe(forward), math.normalizesafe(right))
* 0.5f * math.lerp(math.length(right) * size2.x, math.length(forward) * size2.y, 0.5f);
using (new Handles.DrawingScope(float4x4.identity))
Handles.DrawLine(m.c3.xyz, m.c3.xyz + normal);
}
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(shape, Styles.GenericUndoMessage);
shape.SetBakedPlaneSize(((float3)s_Plane.size).xz);
}
break;
case ShapeType.ConvexHull:
if (Event.current.type != EventType.Repaint)
break;
var points = GetPreviewData(shape).Edges;
// TODO: follow transformation until new preview is generated if e.g., user is dragging handles
if (points.Length > 0)
Handles.DrawLines(points);
break;
case ShapeType.Mesh:
if (Event.current.type != EventType.Repaint)
break;
points = GetPreviewData(shape).Edges;
if (points.Length > 0)
Handles.DrawLines(points);
break;
default:
throw new UnimplementedShapeException(shape.ShapeType);
}
}
}
// ReSharper disable once UnusedMember.Global - magic method called by unity inspector
public bool HasFrameBounds()
{
return true;
}
static Bounds TransformBounds(Bounds localBounds, float4x4 matrix)
{
var center = new float4(localBounds.center, 1);
Bounds bounds = new Bounds(math.mul(matrix, center).xyz, Vector3.zero);
var extent = new float4(localBounds.extents, 0);
for (int i = 0; i < 8; ++i)
{
extent.x = (i & 1) == 0 ? -extent.x : extent.x;
extent.y = (i & 2) == 0 ? -extent.y : extent.y;
extent.z = (i & 4) == 0 ? -extent.z : extent.z;
var worldPoint = math.mul(matrix, center + extent).xyz;
bounds.Encapsulate(worldPoint);
}
return bounds;
}
// ReSharper disable once UnusedMember.Global - magic method called by unity inspector
public Bounds OnGetFrameBounds()
{
var shape = target as PhysicsShapeAuthoring;
var shapeMatrix = shape.GetShapeToWorldMatrix();
Bounds bounds = new Bounds();
switch (shape.ShapeType)
{
case ShapeType.Box:
var boxGeometry = shape.GetBakedBoxProperties();
bounds = new Bounds(float3.zero, boxGeometry.Size);
bounds = TransformBounds(bounds, float4x4.TRS(boxGeometry.Center, boxGeometry.Orientation, 1f));
break;
case ShapeType.Capsule:
var capsuleGeometry = shape.GetBakedCapsuleProperties();
var cd = capsuleGeometry.Radius * 2;
bounds = new Bounds(float3.zero, new float3(cd, cd, capsuleGeometry.Height));
bounds = TransformBounds(bounds, float4x4.TRS(capsuleGeometry.Center, capsuleGeometry.Orientation, 1f));
break;
case ShapeType.Sphere:
var sphereGeometry = shape.GetBakedSphereProperties(out var orientation);
var sd = sphereGeometry.Radius * 2;
bounds = new Bounds(sphereGeometry.Center, new float3(sd, sd, sd));
break;
case ShapeType.Cylinder:
var cylinderGeometry = shape.GetBakedCylinderProperties();
var cyld = cylinderGeometry.Radius * 2;
bounds = new Bounds(float3.zero, new float3(cyld, cyld, cylinderGeometry.Height));
bounds = TransformBounds(bounds, float4x4.TRS(cylinderGeometry.Center, cylinderGeometry.Orientation, 1f));
break;
case ShapeType.Plane:
shape.GetPlaneProperties(out var center, out var size2, out orientation);
bounds = new Bounds(float3.zero, new float3(size2.x, 0, size2.y));
bounds = TransformBounds(bounds, float4x4.TRS(center, orientation, 1f));
break;
case ShapeType.ConvexHull:
case ShapeType.Mesh:
var previewData = GetPreviewData(shape);
if (previewData != null)
bounds = new Bounds(previewData.Bounds.Center, previewData.Bounds.Extents);
break;
default:
throw new UnimplementedShapeException(shape.ShapeType);
}
return TransformBounds(bounds, shapeMatrix);
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/RagdollJointEditor.cs
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using Unity.Mathematics;
using Unity.Physics.Authoring;
namespace Unity.Physics.Editor
{
[CustomEditor(typeof(RagdollJoint))]
public class RagdollJointEditor : UnityEditor.Editor
{
private EditorUtilities.AxisEditor m_AxisEditor = new EditorUtilities.AxisEditor();
private JointAngularLimitHandle m_LimitHandle = new JointAngularLimitHandle();
public override void OnInspectorGUI()
{
RagdollJoint ragdoll = (RagdollJoint)target;
EditorGUI.BeginChangeCheck();
GUILayout.BeginVertical();
GUILayout.Space(10.0f);
GUILayout.BeginHorizontal();
GUILayout.Label("Editors:");
ragdoll.EditPivots = GUILayout.Toggle(ragdoll.EditPivots, new GUIContent("Pivot"), "Button");
ragdoll.EditAxes = GUILayout.Toggle(ragdoll.EditAxes, new GUIContent("Axis"), "Button");
ragdoll.EditLimits = GUILayout.Toggle(ragdoll.EditLimits, new GUIContent("Limits"), "Button");
GUILayout.EndHorizontal();
GUILayout.Space(10.0f);
GUILayout.EndVertical();
DrawDefaultInspector();
if (EditorGUI.EndChangeCheck())
{
SceneView.RepaintAll();
}
}
private static void DrawCone(float3 point, float3 axis, float angle, Color color)
{
#if UNITY_EDITOR
Handles.color = color;
float3 dir;
float scale = Math.NormalizeWithLength(axis, out dir);
float3 arm;
{
float3 perp1, perp2;
Math.CalculatePerpendicularNormalized(dir, out perp1, out perp2);
arm = math.mul(quaternion.AxisAngle(perp1, angle), dir) * scale;
}
const int res = 16;
quaternion q = quaternion.AxisAngle(dir, 2.0f * (float)math.PI / res);
for (int i = 0; i < res; i++)
{
float3 nextArm = math.mul(q, arm);
Handles.DrawLine(point, point + arm);
Handles.DrawLine(point + arm, point + nextArm);
arm = nextArm;
}
#endif
}
protected virtual void OnSceneGUI()
{
RagdollJoint ragdoll = (RagdollJoint)target;
bool drawCones = false;
if (ragdoll.EditPivots)
{
EditorUtilities.EditPivot(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.AutoSetConnected,
ref ragdoll.PositionLocal, ref ragdoll.PositionInConnectedEntity, ragdoll);
}
if (ragdoll.EditAxes)
{
m_AxisEditor.Update(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.AutoSetConnected,
ragdoll.PositionLocal, ragdoll.PositionInConnectedEntity, ref ragdoll.TwistAxisLocal, ref ragdoll.TwistAxisInConnectedEntity,
ref ragdoll.PerpendicularAxisLocal, ref ragdoll.PerpendicularAxisInConnectedEntity, ragdoll);
drawCones = true;
}
if (ragdoll.EditLimits)
{
EditorUtilities.EditLimits(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.PositionLocal, ragdoll.TwistAxisLocal, ragdoll.TwistAxisInConnectedEntity,
ragdoll.PerpendicularAxisLocal, ragdoll.PerpendicularAxisInConnectedEntity, ref ragdoll.MinTwistAngle, ref ragdoll.MaxTwistAngle, m_LimitHandle, ragdoll);
}
if (drawCones)
{
float3 pivotB = math.transform(ragdoll.worldFromB, ragdoll.PositionInConnectedEntity);
float3 axisB = math.rotate(ragdoll.worldFromB, ragdoll.TwistAxisInConnectedEntity);
DrawCone(pivotB, axisB, math.radians(ragdoll.MaxConeAngle), Color.yellow);
float3 perpendicularB = math.rotate(ragdoll.worldFromB, ragdoll.PerpendicularAxisInConnectedEntity);
DrawCone(pivotB, perpendicularB, math.radians(ragdoll.MinPerpendicularAngle + 90f), Color.red);
DrawCone(pivotB, perpendicularB, math.radians(ragdoll.MaxPerpendicularAngle + 90f), Color.red);
}
}
}
}
#endif
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/BeveledBoxBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class BeveledBoxBoundsHandle : BoxBoundsHandle
{
public float bevelRadius
{
get => math.min(m_BevelRadius, math.cmin(GetSize()) * 0.5f);
set
{
if (!m_IsDragging)
m_BevelRadius = math.max(0f, value);
}
}
float m_BevelRadius = ConvexHullGenerationParameters.Default.BevelRadius;
bool m_IsDragging = false;
static PhysicsBoundsHandleUtility.Corner[] s_Corners = new PhysicsBoundsHandleUtility.Corner[8];
public new void DrawHandle()
{
int prevHotControl = GUIUtility.hotControl;
if (prevHotControl == 0)
m_IsDragging = false;
base.DrawHandle();
int currHotcontrol = GUIUtility.hotControl;
if (currHotcontrol != prevHotControl)
m_IsDragging = currHotcontrol != 0;
}
protected override void DrawWireframe()
{
if (this.bevelRadius <= 0f)
{
base.DrawWireframe();
return;
}
var cameraPosition = float3.zero;
var cameraForward = new float3 { z = 1f };
if (Camera.current != null)
{
cameraPosition = Camera.current.transform.position;
cameraForward = Camera.current.transform.forward;
}
var bounds = new Bounds(this.center, this.size);
bool isCameraInsideBox = Camera.current != null && bounds.Contains(Handles.inverseMatrix.MultiplyPoint(cameraPosition));
var bevelRadius = this.bevelRadius;
var origin = (float3)this.center;
var size = (float3)this.size;
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(-1f, 1f, 1f), bevelRadius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, -1f, 1f), bevelRadius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 2, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, -1f), bevelRadius, 2, axes, isCameraInsideBox);
var corner = 0.5f * size - new float3(1f) * bevelRadius;
var axisx = new float3(1f, 0f, 0f);
var axisy = new float3(0f, 1f, 0f);
var axisz = new float3(0f, 0f, 1f);
// Since the geometry is transformed by Handles.matrix during rendering, we transform the camera position
// by the inverse matrix so that the two-shaded wireframe will have the proper orientation.
var invMatrix = Handles.inverseMatrix;
cameraPosition = invMatrix.MultiplyPoint(cameraPosition);
cameraForward = invMatrix.MultiplyVector(cameraForward);
var cameraOrtho = Camera.current == null || Camera.current.orthographic;
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, -1f), quaternion.LookRotation(-axisz, axisy), cameraPosition, cameraForward,
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, 1f), quaternion.LookRotation(-axisx, axisy), cameraPosition, cameraForward,
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, 1f), quaternion.LookRotation(axisz, axisy), cameraPosition, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, -1f), quaternion.LookRotation(axisx, axisy), cameraPosition, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, -1f), quaternion.LookRotation(-axisx, -axisy), cameraPosition, cameraForward,
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, 1f), quaternion.LookRotation(axisz, -axisy), cameraPosition, cameraForward, cameraOrth
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, 1f), quaternion.LookRotation(axisx, -axisy), cameraPosition, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, -1f), quaternion.LookRotation(-axisz, -axisy), cameraPosition, cameraForward, cameraOrth
for (int i = 0; i < s_Corners.Length; i++)
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[i], true);
// Draw the horizon edges between the corners
for (int upA = 3, upB = 0; upB < 4; upA = upB, upB++)
{
int dnA = upA + 4;
int dnB = upB + 4;
if (s_Corners[upA].splitAxis[0].z && s_Corners[upB].splitAxis[1].x) Handles.DrawLine(s_Corners[upA].points[0], s_Corners[upB].points[1]);
if (s_Corners[upA].splitAxis[1].z && s_Corners[upB].splitAxis[0].x) Handles.DrawLine(s_Corners[upA].points[1], s_Corners[upB].points[0]);
if (s_Corners[dnA].splitAxis[0].x && s_Corners[dnB].splitAxis[1].z) Handles.DrawLine(s_Corners[dnA].points[0], s_Corners[dnB].points[1]);
if (s_Corners[dnA].splitAxis[1].x && s_Corners[dnB].splitAxis[0].z) Handles.DrawLine(s_Corners[dnA].points[1], s_Corners[dnB].points[0]);
if (s_Corners[dnA].splitAxis[0].y && s_Corners[upA].splitAxis[1].y) Handles.DrawLine(s_Corners[dnA].points[0], s_Corners[upA].points[1]);
if (s_Corners[dnA].splitAxis[1].y && s_Corners[upA].splitAxis[0].y) Handles.DrawLine(s_Corners[dnA].points[1], s_Corners[upA].points[0]);
}
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/BeveledCylinderBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class BeveledCylinderBoundsHandle : PrimitiveBoundsHandle
{
public float bevelRadius
{
get => math.min(m_BevelRadius, math.cmin(GetSize()) * 0.5f);
set
{
if (!m_IsDragging)
m_BevelRadius = math.max(0f, value);
}
}
m_SideCount = value;
// Since the geometry is transformed by Handles.matrix during rendering, we transform the camera position
// by the inverse matrix so that the two-shaded wireframe will have the proper orientation.
var invMatrix = Handles.inverseMatrix;
cameraCenter = invMatrix.MultiplyPoint(cameraCenter);
cameraForward = invMatrix.MultiplyVector(cameraForward);
var cameraOrtho = Camera.current != null && Camera.current.orthographic;
t = (m_SideCount - 1) * angleStep;
var xyAngle1 = new float3(math.cos(t), math.sin(t), 0f);
var sideways1 = new float3(math.cos(t + kHalfPI - halfAngleStep), math.sin(t + kHalfPI - halfAngleStep), 0f);
var direction1 = new float3(math.cos(t + halfAngleStep), math.sin(t + halfAngleStep), 0f);
var bevelGreaterThanZero = bevelRadius > 0f;
var bevelLessThanCylinderRadius = bevelRadius < radius;
for (var i = 0; i < m_SideCount; ++i)
{
t = i * angleStep;
var xyAngle2 = new float3(math.cos(t), math.sin(t), 0f);
var sideways2 = new float3(math.cos(t + kHalfPI - halfAngleStep), math.sin(t + kHalfPI - halfAngleStep), 0f);
var direction2 = new float3(math.cos(t + halfAngleStep), math.sin(t + halfAngleStep), 0f);
var cornerIndex0 = i;
var cornerIndex1 = i + m_SideCount;
{
var orientation = quaternion.LookRotation(xyAngle2, up);
var cornerNormal = math.normalize(math.mul(orientation, new float3(0f, 1f, 1f)));
PhysicsBoundsHandleUtility.CalculateCornerHorizon(top2,
new float3x3(direction1, up, direction2),
cornerNormal, cameraCenter, cameraForward, cameraOrtho,
bevelRadius, out m_Corners[cornerIndex0]);
}
{
var orientation = quaternion.LookRotation(xyAngle2, -up);
var cornerNormal = math.normalize(math.mul(orientation, new float3(0f, 1f, 1f)));
PhysicsBoundsHandleUtility.CalculateCornerHorizon(bottom2,
new float3x3(direction2, -up, direction1),
cornerNormal, cameraCenter, cameraForward, cameraOrtho,
bevelRadius, out m_Corners[cornerIndex1]);
}
}
direction1 = direction2;
sideways1 = sideways2;
xyAngle0 = xyAngle1;
xyAngle1 = xyAngle2;
}
if (bevelGreaterThanZero)
{
Handles.color = frontfacedColor;
for (int a = m_SideCount - 1, b = 0; b < m_SideCount; a = b, ++b)
{
var up0 = a;
var dn0 = a + m_SideCount;
var up1 = b;
var dn1 = b + m_SideCount;
namespace Unity.Physics.Editor
{
abstract class BaseDrawer : PropertyDrawer
{
protected abstract bool IsCompatible(SerializedProperty property);
EditorGUI.EndProperty();
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/EulerAnglesDrawer.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(EulerAngles))]
class EulerAnglesDrawer : BaseDrawer
{
protected override bool IsCompatible(SerializedProperty property) => true;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var value = property.FindPropertyRelative(nameof(EulerAngles.Value));
return EditorGUI.GetPropertyHeight(value);
}
protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)
{
var value = property.FindPropertyRelative(nameof(EulerAngles.Value));
EditorGUI.PropertyField(position, value, label, true);
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/ExpandChildrenDrawer.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(ExpandChildrenAttribute))]
class ExpandChildrenDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
property.isExpanded = true;
return EditorGUI.GetPropertyHeight(property)
- EditorGUIUtility.standardVerticalSpacing
- EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var endProperty = property.GetEndProperty();
var childProperty = property.Copy();
childProperty.NextVisible(true);
while (!SerializedProperty.EqualContents(childProperty, endProperty))
{
position.height = EditorGUI.GetPropertyHeight(childProperty);
OnChildPropertyGUI(position, childProperty);
position.y += position.height + EditorGUIUtility.standardVerticalSpacing;
childProperty.NextVisible(false);
}
}
protected virtual void OnChildPropertyGUI(Rect position, SerializedProperty childProperty)
{
EditorGUI.PropertyField(position, childProperty, true);
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/PhysicsMaterialCoefficientDrawer.cs
using System;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(PhysicsMaterialCoefficient))]
class PhysicsMaterialCoefficientDrawer : BaseDrawer
{
static class Styles
{
public const float PopupWidth = 100f;
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) =>
EditorGUIUtility.singleLineHeight;
protected override bool IsCompatible(SerializedProperty property) => true;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(PhysicsMaterialProperties))]
class PhysicsMaterialPropertiesDrawer : BaseDrawer
{
static class Content
{
public static readonly GUIContent AdvancedGroupFoldout = EditorGUIUtility.TrTextContent("Advanced");
public static readonly GUIContent BelongsToLabel = EditorGUIUtility.TrTextContent(
"Belongs To",
"Specifies the categories to which this object belongs."
);
public static readonly GUIContent CollidesWithLabel = EditorGUIUtility.TrTextContent(
"Collides With",
"Specifies the categories of objects with which this object will collide, " +
"or with which it will raise events if intersecting a trigger."
);
public static readonly GUIContent CollisionFilterGroupFoldout =
EditorGUIUtility.TrTextContent("Collision Filter");
public static readonly GUIContent CustomFlagsLabel =
EditorGUIUtility.TrTextContent("Custom Tags", "Specify custom tags to read at run-time.");
public static readonly GUIContent FrictionLabel = EditorGUIUtility.TrTextContent(
"Friction",
"Specifies how resistant the body is to motion when sliding along other surfaces, " +
"as well as what value should be used when colliding with an object that has a different value."
);
public static readonly GUIContent RestitutionLabel = EditorGUIUtility.TrTextContent(
"Restitution",
"Specifies how bouncy the object will be when colliding with other surfaces, " +
"as well as what value should be used when colliding with an object that has a different value."
);
public static readonly GUIContent CollisionResponseLabel = EditorGUIUtility.TrTextContent(
"Collision Response",
"Specifies whether the shape should collide normally, raise trigger events when intersecting other shapes, " +
"collide normally and raise notifications of collision events with other shapes, " +
"or completely ignore collisions (but still move and intercept queries)."
);
}
void FindToggleAndValueProperties(
SerializedProperty property, SerializedProperty templateValueProperty, string relativePath,
out SerializedProperty toggle, out SerializedProperty value
)
{
var relative = property.FindPropertyRelative(relativePath);
toggle = relative.FindPropertyRelative("m_Override");
value = toggle.boolValue || templateValueProperty == null
? relative.FindPropertyRelative("m_Value")
: templateValueProperty.FindPropertyRelative(relativePath).FindPropertyRelative("m_Value");
}
// m_BelongsTo, m_CollidesWith
var group = property.FindPropertyRelative(k_CollisionFilterGroupKey);
if (group.isExpanded)
height += 2f * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
// m_CustomTags
group = property.FindPropertyRelative(k_AdvancedGroupKey);
if (group.isExpanded)
height += (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
// m_Template
if (property.FindPropertyRelative("m_SupportsTemplate").boolValue)
height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
// m_Friction, m_Restitution
FindToggleAndValueProperties(property, templateValueProperty, "m_CollisionResponse", out _, out var collisionResponse);
// Check if regular collider
CollisionResponsePolicy collisionResponseEnum = (CollisionResponsePolicy)collisionResponse.intValue;
if (collisionResponseEnum == CollisionResponsePolicy.Collide ||
collisionResponseEnum == CollisionResponsePolicy.CollideRaiseCollisionEvents)
height += 2f * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
return height;
}
EditorGUI.BeginDisabledGroup(!toggle.boolValue);
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
EditorGUI.PropertyField(new Rect(position) { xMin = togglePosition.xMax }, value, GUIContent.none, true);
EditorGUI.indentLevel = indent;
EditorGUI.EndDisabledGroup();
}
else
{
EditorGUI.PropertyField(position, value, label, true);
}
}
--EditorGUI.indentLevel;
}
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/SoftRangeDrawer.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(SoftRangeAttribute))]
class SoftRangeDrawer : BaseDrawer
{
protected override bool IsCompatible(SerializedProperty property)
{
return property.propertyType == SerializedPropertyType.Float;
}
protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)
{
var attr = attribute as SoftRangeAttribute;
EditorGUIControls.SoftSlider(
position, label, property, attr.SliderMin, attr.SliderMax, attr.TextFieldMin, attr.TextFieldMax
);
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/TagsDrawer.cs
using System.Collections.Generic;
using System.Linq;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
abstract class TagsDrawer<T> : PropertyDrawer where T : ScriptableObject, ITagNames
{
static class Styles
{
public static readonly string EverythingName = L10n.Tr("Everything");
public static readonly string MixedName = L10n.Tr("Mixed...");
public static readonly string NothingName = L10n.Tr("Nothing");
string[] GetOptions()
{
if (m_Options != null)
return m_Options;
var guids = AssetDatabase.FindAssets($"t:{typeof(T).Name}");
m_NamesAssets = guids
.Select(AssetDatabase.GUIDToAssetPath)
.Select(AssetDatabase.LoadAssetAtPath<T>)
.Where(c => c != null)
.ToArray();
m_Options = m_NamesAssets.FirstOrDefault()?.TagNames.ToArray() ?? DefaultOptions;
for (var i = 0; i < m_Options.Length; ++i)
{
if (string.IsNullOrEmpty(m_Options[i]))
m_Options[i] = DefaultOptions[i];
string[] m_Options;
T[] m_NamesAssets;
// TODO: remove when all usages of bool[] are migrated
SerializedProperty GetFirstChildProperty(SerializedProperty property)
{
if (!string.IsNullOrEmpty(FirstChildPropertyPath))
return property.FindPropertyRelative(FirstChildPropertyPath);
var sp = property.Copy();
sp.NextVisible(true);
return sp;
}
var value = 0;
var everything = 0;
var sp = GetFirstChildProperty(property);
for (int i = 0, count = MaxNumCategories; i < count; ++i)
{
EditorGUI.showMixedValue |= sp.hasMultipleDifferentValues;
value |= sp.boolValue ? 1 << i : 0;
everything |= 1 << i;
sp.NextVisible(false);
}
// in case size is smaller than 32
if (value == everything)
value = ~0;
var options = GetOptions();
if (
EditorGUI.DropdownButton(
controlPosition,
EditorGUIUtility.TrTempContent(GetButtonLabel(value, options)),
FocusType.Passive,
EditorStyles.popup
)
)
{
var menu = new GenericMenu();
menu.AddItem(
new GUIContent(Styles.NothingName),
value == 0,
() =>
{
sp = GetFirstChildProperty(property);
for (int i = 0, count = MaxNumCategories; i < count; ++i)
{
sp.boolValue = false;
sp.NextVisible(false);
}
sp.serializedObject.ApplyModifiedProperties();
}
);
menu.AddItem(
new GUIContent(Styles.EverythingName),
value == ~0,
() =>
{
sp = GetFirstChildProperty(property);
for (int i = 0, count = MaxNumCategories; i < count; ++i)
{
sp.boolValue = true;
sp.NextVisible(false);
}
sp.serializedObject.ApplyModifiedProperties();
}
);
for (var option = 0; option < options.Length; ++option)
{
var callbackValue = option;
menu.AddItem(
EditorGUIUtility.TrTextContent(options[option]),
((1 << option) & value) != 0,
args =>
{
var changedBitAndValue = (KeyValuePair<int, bool>)args;
sp = GetFirstChildProperty(property);
for (int i = 0, count = changedBitAndValue.Key; i < count; ++i)
sp.NextVisible(false);
sp.boolValue = changedBitAndValue.Value;
sp.serializedObject.ApplyModifiedProperties();
},
new KeyValuePair<int, bool>(callbackValue, ((1 << option) & value) == 0)
);
}
menu.AddSeparator(string.Empty);
menu.AddItem(
EditorGUIUtility.TrTempContent($"Edit {ObjectNames.NicifyVariableName(typeof(T).Name)}"),
false,
() =>
{
if (m_NamesAssets.Length > 0)
Selection.activeObject = m_NamesAssets[0];
else
{
var assetPath = AssetDatabase.GenerateUniqueAssetPath($"Assets/{typeof(T).Name}.asset");
AssetDatabase.CreateAsset(ScriptableObject.CreateInstance<T>(), assetPath);
Selection.activeObject = AssetDatabase.LoadAssetAtPath<T>(assetPath);
m_Options = null;
}
}
);
menu.DropDown(controlPosition);
}
EditorGUI.showMixedValue = showMixed;
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
if (m_NamesAssets?.Length > 1)
{
var id = GUIUtility.GetControlID(FocusType.Passive);
if (Event.current.type == EventType.Repaint)
{
position.width = EditorGUIUtility.singleLineHeight;
position.x = controlPosition.xMax + EditorGUIUtility.standardVerticalSpacing;
Styles.MultipleAssetsWarning.tooltip = string.Format(
Styles.MultipleAssetsTooltip,
ObjectNames.NicifyVariableName(typeof(T).Name),
m_NamesAssets.FirstOrDefault(n => n != null)?.name
);
GUIStyle.none.Draw(position, Styles.MultipleAssetsWarning, id);
}
}
}
}
[CustomPropertyDrawer(typeof(CustomPhysicsBodyTags))]
class CustomBodyTagsDrawer : TagsDrawer<CustomPhysicsBodyTagNames>
{
protected override string DefaultCategoryName => "Custom Physics Body Tag";
protected override int MaxNumCategories => 8;
}
[CustomPropertyDrawer(typeof(CustomPhysicsMaterialTags))]
class CustomMaterialTagsDrawer : TagsDrawer<CustomPhysicsMaterialTagNames>
{
protected override string DefaultCategoryName => "Custom Physics Material Tag";
protected override int MaxNumCategories => 8;
}
[CustomPropertyDrawer(typeof(PhysicsCategoryTags))]
class PhysicsCategoryTagsDrawer : TagsDrawer<PhysicsCategoryNames>
{
protected override string DefaultCategoryName => "Physics Category";
protected override int MaxNumCategories => 32;
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/EditorGUIControls.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[InitializeOnLoad]
static class EditorGUIControls
{
static EditorGUIControls()
{
if (k_SoftSlider == null)
Debug.LogException(new MissingMemberException("Could not find expected signature of EditorGUI.Slider() for soft slider."));
}
static class Styles
{
public static readonly string CompatibilityWarning = L10n.Tr("Not compatible with {0}.");
}
public static void DisplayCompatibilityWarning(Rect position, GUIContent label, string incompatibleType)
{
EditorGUI.HelpBox(
EditorGUI.PrefixLabel(position, label),
string.Format(Styles.CompatibilityWarning, incompatibleType),
MessageType.Error
);
}
static readonly MethodInfo k_SoftSlider = typeof(EditorGUI).GetMethod(
"Slider",
BindingFlags.Static | BindingFlags.NonPublic,
null,
new[]
{
typeof(Rect), // position
typeof(GUIContent), // label
typeof(float), // value
typeof(float), // sliderMin
typeof(float), // sliderMax
typeof(float), // textFieldMin
typeof(float) // textFieldMax
},
Array.Empty<ParameterModifier>()
);
static readonly object[] k_SoftSliderArgs = new object[7];
namespace Unity.Physics.Editor
{
static class SceneViewUtility
{
static class Styles
{
public static readonly GUIStyle ProgressBarTrack = new GUIStyle
{
fixedHeight = 4f,
normal = new GUIStyleState { background = Texture2D.whiteTexture }
};
public static readonly GUIStyle ProgressBarIndicator = new GUIStyle
{
fixedHeight = 4f,
normal = new GUIStyleState { background = Texture2D.whiteTexture }
};
public static readonly GUIStyle SceneViewStatusMessage = new GUIStyle("NotificationBackground")
{
fontSize = EditorStyles.label.fontSize
};
static Styles() => SceneViewStatusMessage.padding = SceneViewStatusMessage.border;
}
namespace Unity.Physics.Editor
{
static class StatusMessageUtility
{
public static MessageType GetHierarchyStatusMessage(IReadOnlyList<UnityObject> targets, out string statusMessage)
{
statusMessage = string.Empty;
if (targets.Count == 0)
return MessageType.None;
var numChildTargets = 0;
foreach (Component c in targets)
{
// hierarchy roots and leaf shapes do not emit a message
if (
c == null
|| c.transform.parent == null
|| PhysicsShapeExtensions.GetPrimaryBody(c.gameObject) != c.gameObject
)
continue;
if (matrixStates.Contains(MatrixState.NonUniformScale))
{
statusMessage = L10n.Tr(
matrixStates.Count == 1
? "Target has non-uniform scale. Shape data will be transformed during conversion in order to bake scale into the run-time format."
: "One or more targets has non-uniform scale. Shape data will be transformed during conversion in order to bake scale into the run-time format."
);
return MessageType.Warning;
}
return MessageType.None;
}
}
}
Tutorial/Assets/Scripts/PhysicsStatefulEvents/IStatefulSimulationEvent.cs
using Unity.Entities;
namespace Unity.Physics.Stateful
{
/// <summary>
/// Describes an event state.
/// Event state is set to:
/// 0) Undefined, when the state is unknown or not needed
/// 1) Enter, when 2 bodies are interacting in the current frame, but they did not interact the previous frame
/// 2) Stay, when 2 bodies are interacting in the current frame, and they also interacted in the previous frame
/// 3) Exit, when 2 bodies are not interacting in the current frame, but they did interact in the previous frame
/// </summary>
public enum StatefulEventState : byte
{
Undefined,
Enter,
Stay,
Exit
}
/// <summary>
/// Extends ISimulationEvent with extra <see cref="StatefulEventState"/>.
/// </summary>
public interface IStatefulSimulationEvent<T> : IBufferElementData, ISimulationEvent<T>
{
public StatefulEventState State { get; set; }
}
}
Tutorial/Assets/Scripts/PhysicsStatefulEvents/StatefulCollisionEvent.cs
using Unity.Assertions;
using Unity.Entities;
using Unity.Mathematics;
namespace Unity.Physics.Stateful
{
// Collision Event that can be stored inside a DynamicBuffer
public struct StatefulCollisionEvent : IBufferElementData, IStatefulSimulationEvent<StatefulCollisionEvent>
{
public Entity EntityA { get; set; }
public Entity EntityB { get; set; }
public int BodyIndexA { get; set; }
public int BodyIndexB { get; set; }
public ColliderKey ColliderKeyA { get; set; }
public ColliderKey ColliderKeyB { get; set; }
public StatefulEventState State { get; set; }
public float3 Normal;
// Only if CalculateDetails is checked on PhysicsCollisionEventBuffer of selected entity,
// this field will have valid value, otherwise it will be zero initialized
internal Details CollisionDetails;
public StatefulCollisionEvent(CollisionEvent collisionEvent)
{
EntityA = collisionEvent.EntityA;
EntityB = collisionEvent.EntityB;
BodyIndexA = collisionEvent.BodyIndexA;
BodyIndexB = collisionEvent.BodyIndexB;
ColliderKeyA = collisionEvent.ColliderKeyA;
ColliderKeyB = collisionEvent.ColliderKeyB;
State = default;
Normal = collisionEvent.Normal;
CollisionDetails = default;
}
// This struct describes additional, optional, details about collision of 2 bodies
public struct Details
{
internal bool IsValid;
// Returns the normal pointing from passed entity to the other one in pair
public float3 GetNormalFrom(Entity entity)
{
Assert.IsTrue((entity == EntityA) || (entity == EntityB));
return math.select(-Normal, Normal, entity == EntityB);
}
public bool TryGetDetails(out Details details)
{
details = CollisionDetails;
return CollisionDetails.IsValid;
}
public int CompareTo(StatefulCollisionEvent other) => ISimulationEventUtilities.CompareEvents(this, other);
}
}
Tutorial/Assets/Scripts/PhysicsStatefulEvents/StatefulCollisionEventBufferAuthoring.cs
using Unity.Entities;
using UnityEngine;
namespace Unity.Physics.Stateful
{
public struct StatefulCollisionEventDetails : IComponentData
{
public bool CalculateDetails;
}
namespace Unity.Physics.Stateful
{
/// <summary>
/// This system converts stream of CollisionEvents to StatefulCollisionEvents that can be stored in a Dynamic Buffer.
/// In order for this conversion, it is required to:
/// 1) Use the 'Collide Raise Collision Events' option of the 'Collision Response' property on a <see cref="PhysicsShapeAuthoring"/> component, and
/// 2) Add a <see cref="StatefulCollisionEventBufferAuthoring"/> component to that entity (and select if details should be calculated or not)
/// or, if this is desired on a Character Controller:
/// 1) Tick the 'Raise Collision Events' flag on the <see cref="CharacterControllerAuthoring"/> component.
/// </summary>
[UpdateInGroup(typeof(PhysicsSystemGroup))]
[UpdateAfter(typeof(PhysicsSimulationGroup))]
[BurstCompile]
public partial struct StatefulCollisionEventBufferSystem : ISystem
{
private StatefulSimulationEventBuffers<StatefulCollisionEvent> m_StateFulEventBuffers;
private ComponentHandles m_Handles;
// Component that does nothing. Made in order to use a generic job. See OnUpdate() method for details.
internal struct DummyExcludeComponent : IComponentData {};
struct ComponentHandles
{
public ComponentLookup<DummyExcludeComponent> EventExcludes;
public ComponentLookup<StatefulCollisionEventDetails> EventDetails;
public BufferLookup<StatefulCollisionEvent> EventBuffers;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
m_StateFulEventBuffers = new StatefulSimulationEventBuffers<StatefulCollisionEvent>();
m_StateFulEventBuffers.AllocateBuffers();
state.RequireForUpdate<StatefulCollisionEvent>();
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
m_Handles.Update(ref state);
m_StateFulEventBuffers.SwapBuffers();
UseExcludeComponent = false,
EventExcludeLookup = m_Handles.EventExcludes
}.Schedule(state.Dependency);
}
}
}
Tutorial/Assets/Scripts/PhysicsStatefulEvents/StatefulSimulationEventBuffers.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
namespace Unity.Physics.Stateful
{
public struct StatefulSimulationEventBuffers<T> where T : unmanaged, IStatefulSimulationEvent<T>
{
public NativeList<T> Previous;
public NativeList<T> Current;
/// <summary>
/// Given two sorted event buffers, this function returns a single combined list with
/// all the appropriate <see cref="StatefulEventState"/> set on each event.
/// </summary>
/// <param name="previousEvents">The events buffer from the previous frame. This list should have already be sorted from the previous frame.</param>
/// <param name="currentEvents">The events buffer from the current frame. This list should be sorted before calling this function.</param>
/// <param name="statefulEvents">A single combined list of stateful events based on the previous and current frames.</param>
/// <param name="sortCurrent">Specifies whether the currentEvents list needs to be sorted first.</param>
public static void GetStatefulEvents(NativeList<T> previousEvents, NativeList<T> currentEvents, NativeList<T> statefulEvents, bool sortCurrent = true)
{
if (sortCurrent) currentEvents.Sort();
statefulEvents.Clear();
int c = 0;
int p = 0;
while (c < currentEvents.Length && p < previousEvents.Length)
{
int r = previousEvents[p].CompareTo(currentEvents[c]);
if (r == 0)
{
var currentEvent = currentEvents[c];
currentEvent.State = StatefulEventState.Stay;
statefulEvents.Add(currentEvent);
c++;
p++;
}
else if (r < 0)
{
var previousEvent = previousEvents[p];
previousEvent.State = StatefulEventState.Exit;
statefulEvents.Add(previousEvent);
p++;
}
else //(r > 0)
{
var currentEvent = currentEvents[c];
currentEvent.State = StatefulEventState.Enter;
statefulEvents.Add(currentEvent);
c++;
}
}
if (c == currentEvents.Length)
{
while (p < previousEvents.Length)
{
var previousEvent = previousEvents[p];
previousEvent.State = StatefulEventState.Exit;
statefulEvents.Add(previousEvent);
p++;
}
}
else if (p == previousEvents.Length)
{
while (c < currentEvents.Length)
{
var currentEvent = currentEvents[c];
currentEvent.State = StatefulEventState.Enter;
statefulEvents.Add(currentEvent);
c++;
}
}
}
}
public static class StatefulEventCollectionJobs
{
[BurstCompile]
public struct CollectTriggerEvents : ITriggerEventsJob
{
public NativeList<StatefulTriggerEvent> TriggerEvents;
public void Execute(TriggerEvent triggerEvent) => TriggerEvents.Add(new StatefulTriggerEvent(triggerEvent));
}
[BurstCompile]
public struct CollectCollisionEvents : ICollisionEventsJob
{
public NativeList<StatefulCollisionEvent> CollisionEvents;
public void Execute(CollisionEvent collisionEvent) => CollisionEvents.Add(new StatefulCollisionEvent(collisionEvent));
}
[BurstCompile]
public struct CollectCollisionEventsWithDetails : ICollisionEventsJob
{
public NativeList<StatefulCollisionEvent> CollisionEvents;
[ReadOnly] public PhysicsWorld PhysicsWorld;
[ReadOnly] public ComponentLookup<StatefulCollisionEventDetails> EventDetails;
public bool ForceCalculateDetails;
CollisionEvents.Add(statefulCollisionEvent);
}
}
[BurstCompile]
public struct ConvertEventStreamToDynamicBufferJob<T, C> : IJob
where T : unmanaged, IBufferElementData, IStatefulSimulationEvent<T>
where C : unmanaged, IComponentData
{
public NativeList<T> PreviousEvents;
public NativeList<T> CurrentEvents;
public BufferLookup<T> EventBuffers;
public bool UseExcludeComponent;
[ReadOnly] public ComponentLookup<C> EventExcludeLookup;
public void Execute()
{
var statefulEvents = new NativeList<T>(CurrentEvents.Length, Allocator.Temp);
if (addToEntityA)
{
EventBuffers[statefulEvent.EntityA].Add(statefulEvent);
}
if (addToEntityB)
{
EventBuffers[statefulEvent.EntityB].Add(statefulEvent);
}
}
}
}
}
}
Tutorial/Assets/Scripts/PhysicsStatefulEvents/StatefulTriggerEvent.cs
using Unity.Entities;
using Unity.Assertions;
namespace Unity.Physics.Stateful
{
// Trigger Event that can be stored inside a DynamicBuffer
public struct StatefulTriggerEvent : IBufferElementData, IStatefulSimulationEvent<StatefulTriggerEvent>
{
public Entity EntityA { get; set; }
public Entity EntityB { get; set; }
public int BodyIndexA { get; set; }
public int BodyIndexB { get; set; }
public ColliderKey ColliderKeyA { get; set; }
public ColliderKey ColliderKeyB { get; set; }
public StatefulEventState State { get; set; }
public StatefulTriggerEvent(TriggerEvent triggerEvent)
{
EntityA = triggerEvent.EntityA;
EntityB = triggerEvent.EntityB;
BodyIndexA = triggerEvent.BodyIndexA;
BodyIndexB = triggerEvent.BodyIndexB;
ColliderKeyA = triggerEvent.ColliderKeyA;
ColliderKeyB = triggerEvent.ColliderKeyB;
State = default;
}
// Returns other entity in EntityPair, if provided with one
public Entity GetOtherEntity(Entity entity)
{
Assert.IsTrue((entity == EntityA) || (entity == EntityB));
return (entity == EntityA) ? EntityB : EntityA;
}
public int CompareTo(StatefulTriggerEvent other) => ISimulationEventUtilities.CompareEvents(this, other);
}
}
Tutorial/Assets/Scripts/PhysicsStatefulEvents/StatefulTriggerEventBufferAuthoring.cs
using Unity.Entities;
using UnityEngine;
namespace Unity.Physics.Stateful
{
// If this component is added to an entity, trigger events won't be added to a dynamic buffer
// of that entity by the StatefulTriggerEventBufferSystem. This component is by default added to
// CharacterController entity, so that CharacterControllerSystem can add trigger events to
// CharacterController on its own, without StatefulTriggerEventBufferSystem interference.
public struct StatefulTriggerEventExclude : IComponentData {}
namespace Unity.Physics.Stateful
{
/// <summary>
/// This system converts stream of TriggerEvents to StatefulTriggerEvents that can be stored in a Dynamic Buffer.
/// In order for this conversion, it is required to:
/// 1) Use the 'Raise Trigger Events' option of the 'Collision Response' property on a <see cref="PhysicsShapeAuthoring"/> component, and
/// 2) Add a <see cref="StatefulTriggerEventBufferAuthoring"/> component to that entity
/// or, if this is desired on a Character Controller:
/// 1) Tick the 'Raise Trigger Events' flag on the <see cref="CharacterControllerAuthoring"/> component.
/// Note: the Character Controller will not become a trigger, it will raise events when overlapping with one
/// </summary>
[UpdateInGroup(typeof(PhysicsSystemGroup))]
[UpdateAfter(typeof(PhysicsSimulationGroup))]
[BurstCompile]
public partial struct StatefulTriggerEventBufferSystem : ISystem
{
private StatefulSimulationEventBuffers<StatefulTriggerEvent> m_StateFulEventBuffers;
private ComponentHandles m_ComponentHandles;
private EntityQuery m_TriggerEventQuery;
struct ComponentHandles
{
public ComponentLookup<StatefulTriggerEventExclude> EventExcludes;
public BufferLookup<StatefulTriggerEvent> EventBuffers;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
EntityQueryBuilder builder = new EntityQueryBuilder(Allocator.Temp)
.WithAllRW<StatefulTriggerEvent>()
.WithNone<StatefulTriggerEventExclude>();
m_TriggerEventQuery = state.GetEntityQuery(builder);
state.RequireForUpdate(m_TriggerEventQuery);
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
m_StateFulEventBuffers.Dispose();
}
[BurstCompile]
public partial struct ClearTriggerEventDynamicBufferJob : IJobEntity
{
public void Execute(ref DynamicBuffer<StatefulTriggerEvent> eventBuffer) => eventBuffer.Clear();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
m_ComponentHandles.Update(ref state);
m_StateFulEventBuffers.SwapBuffers();
UseExcludeComponent = true,
EventExcludeLookup = m_ComponentHandles.EventExcludes
}.Schedule(state.Dependency);
}
}
}
Tutorial/Assets/Scripts/StandardCharacter/Common/Scripts/FixedInputEvent.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public struct FixedInputEvent
{
private byte _wasEverSet;
private uint _lastSetTick;
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
ref Singleton singleton = ref SystemAPI.GetSingletonRW<Singleton>().ValueRW;
singleton.Tick++;
}
}
Tutorial/Assets/Scripts/StandardCharacter/Common/Scripts/Camera/MainCameraSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
[UpdateInGroup(typeof(PresentationSystemGroup))]
public partial class MainCameraSystem : SystemBase
{
protected override void OnUpdate()
{
if (MainGameObjectCamera.Instance != null && SystemAPI.HasSingleton<MainEntityCamera>())
{
Entity mainEntityCameraEntity = SystemAPI.GetSingletonEntity<MainEntityCamera>();
LocalToWorld targetLocalToWorld = SystemAPI.GetComponent<LocalToWorld>(mainEntityCameraEntity);
MainGameObjectCamera.Instance.transform.SetPositionAndRotation(targetLocalToWorld.Position, targetLocalToWorld.Rotation);
}
}
}
Tutorial/Assets/Scripts/StandardCharacter/Common/Scripts/Camera/MainEntityCamera.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
[Serializable]
public struct MainEntityCamera : IComponentData
{
}
Tutorial/Assets/Scripts/StandardCharacter/Common/Scripts/Camera/MainEntityCameraAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
[DisallowMultipleComponent]
public class MainEntityCameraAuthoring : MonoBehaviour
{
public class Baker : Baker<MainEntityCameraAuthoring>
{
public override void Bake(MainEntityCameraAuthoring authoring)
{
AddComponent<MainEntityCamera>(GetEntity(TransformUsageFlags.Dynamic));
}
}
}
Tutorial/Assets/Scripts/StandardCharacter/Common/Scripts/Camera/MainGameObjectCamera.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MainGameObjectCamera : MonoBehaviour
{
public static Camera Instance;
void Awake()
{
Instance = GetComponent<UnityEngine.Camera>();
}
}
Tutorial/Assets/Scripts/StandardCharacter/ThirdPerson/Scripts/CharacterHitsDetectionSystem.cs
using Unity.Entities;
using Unity.CharacterController;
using Unity.Physics.Systems;
[UpdateInGroup(typeof(AfterPhysicsSystemGroup))]
[UpdateAfter(typeof(KinematicCharacterPhysicsUpdateGroup))]
public partial class CharacterHitsDetectionSystem : SystemBase
{
protected override void OnUpdate()
{
// Iterate on non-stateful hits
foreach (var hitsBuffer in SystemAPI.Query<DynamicBuffer<KinematicCharacterHit>>())
{
for (int i = 0; i < hitsBuffer.Length; i++)
{
KinematicCharacterHit hit = hitsBuffer[i];
if(!hit.IsGroundedOnHit)
{
// UnityEngine.Debug.Log($"Detected an ungrounded hit {hit.Entity.Index}");
}
}
}
// Iterate on stateful hits
foreach (var statefulHitsBuffer in SystemAPI.Query<DynamicBuffer<StatefulKinematicCharacterHit>>())
{
for (int i = 0; i < statefulHitsBuffer.Length; i++)
{
StatefulKinematicCharacterHit hit = statefulHitsBuffer[i];
if (hit.State == CharacterHitState.Enter)
{
// UnityEngine.Debug.Log($"Entered new hit {hit.Hit.Entity.Index}");
}
else if (hit.State == CharacterHitState.Exit)
{
// UnityEngine.Debug.Log($"Exited a hit {hit.Hit.Entity.Index}");
}
}
}
}
}
Tutorial/Assets/Scripts/StandardCharacter/ThirdPerson/Scripts/ThirdPersonCharacterAspect.cs
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Authoring;
using Unity.Physics.Extensions;
using Unity.Physics.Systems;
using Unity.Transforms;
using Unity.CharacterController;
using UnityEngine;
public struct ThirdPersonCharacterUpdateContext
{
// Here, you may add additional global data for your character updates, such as ComponentLookups, Singletons, NativeCollections, etc...
// The data you add here will be accessible in your character updates and all of your character "callbacks".
[ReadOnly]
public ComponentLookup<CharacterFrictionSurface> CharacterFrictionSurfaceLookup;
// This is called by systems that schedule jobs that update the character aspect, in their OnCreate().
// Here, you can get the component lookups.
public void OnSystemCreate(ref SystemState state)
{
CharacterFrictionSurfaceLookup = state.GetComponentLookup<CharacterFrictionSurface>(true);
}
// This is called by systems that schedule jobs that update the character aspect, in their OnUpdate()
// Here, you can update the component lookups.
public void OnSystemUpdate(ref SystemState state)
{
CharacterFrictionSurfaceLookup.Update(ref state);
}
}
public readonly partial struct ThirdPersonCharacterAspect : IAspect, IKinematicCharacterProcessor<ThirdPersonCharacterUpdateContext>
{
public readonly KinematicCharacterAspect CharacterAspect;
public readonly RefRW<ThirdPersonCharacterComponent> CharacterComponent;
public readonly RefRW<ThirdPersonCharacterControl> CharacterControl;
public void PhysicsUpdate(ref ThirdPersonCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
ref ThirdPersonCharacterComponent characterComponent = ref CharacterComponent.ValueRW;
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref float3 characterPosition = ref CharacterAspect.LocalTransform.ValueRW.Position;
// First phase of default character update
CharacterAspect.Update_Initialize(in this, ref context, ref baseContext, ref characterBody, baseContext.Time.DeltaTime);
CharacterAspect.Update_ParentMovement(in this, ref context, ref baseContext, ref characterBody, ref characterPosition, characterBody.WasGroundedBeforeCharacterUpdate);
CharacterAspect.Update_Grounding(in this, ref context, ref baseContext, ref characterBody, ref characterPosition);
// Update desired character velocity after grounding was detected, but before doing additional processing that depends on velocity
HandleVelocityControl(ref context, ref baseContext);
// Second phase of default character update
CharacterAspect.Update_PreventGroundingFromFutureSlopeChange(in this, ref context, ref baseContext, ref characterBody, in characterComponent.StepAndSlopeHandling);
CharacterAspect.Update_GroundPushing(in this, ref context, ref baseContext, characterComponent.Gravity);
CharacterAspect.Update_MovementAndDecollisions(in this, ref context, ref baseContext, ref characterBody, ref characterPosition);
CharacterAspect.Update_MovingPlatformDetection(ref baseContext, ref characterBody);
CharacterAspect.Update_ParentMomentum(ref baseContext, ref characterBody);
CharacterAspect.Update_ProcessStatefulCharacterHits();
}
private void HandleVelocityControl(ref ThirdPersonCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
float deltaTime = baseContext.Time.DeltaTime;
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref ThirdPersonCharacterComponent characterComponent = ref CharacterComponent.ValueRW;
ref ThirdPersonCharacterControl characterControl = ref CharacterControl.ValueRW;
// Rotate move input and velocity to take into account parent rotation
if(characterBody.ParentEntity != Entity.Null)
{
characterControl.MoveVector = math.rotate(characterBody.RotationFromParent, characterControl.MoveVector);
characterBody.RelativeVelocity = math.rotate(characterBody.RotationFromParent, characterBody.RelativeVelocity);
}
if (characterBody.IsGrounded)
{
// Move on ground
float3 targetVelocity = characterControl.MoveVector * characterComponent.GroundMaxSpeed;
// Sprint
if (characterControl.Sprint)
{
targetVelocity *= characterComponent.SprintSpeedMultiplier;
}
// Friction surfaces
if (context.CharacterFrictionSurfaceLookup.TryGetComponent(characterBody.GroundHit.Entity, out CharacterFrictionSurface frictionSurface))
{
targetVelocity *= frictionSurface.VelocityFactor;
}
CharacterControlUtilities.StandardGroundMove_Interpolated(ref characterBody.RelativeVelocity, targetVelocity, characterComponent.GroundedMovementSharpness, deltaTime,
// Jump
if (characterControl.Jump)
{
CharacterControlUtilities.StandardJump(ref characterBody, characterBody.GroundingUp * characterComponent.JumpSpeed, true, characterBody.GroundingUp);
}
// Reset air jumps when grounded
characterComponent.CurrentAirJumps = 0;
}
else
{
// Move in air
float3 airAcceleration = characterControl.MoveVector * characterComponent.AirAcceleration;
if (math.lengthsq(airAcceleration) > 0f)
{
float3 tmpVelocity = characterBody.RelativeVelocity;
CharacterControlUtilities.StandardAirMove(ref characterBody.RelativeVelocity, airAcceleration, characterComponent.AirMaxSpeed, characterBody.GroundingUp, deltaTime
// Cancel air acceleration from input if we would hit a non-grounded surface (prevents air-climbing slopes at high air accelerations)
if (characterComponent.PreventAirAccelerationAgainstUngroundedHits && CharacterAspect.MovementWouldHitNonGroundedObstruction(in this, ref context, ref baseContext,
{
characterBody.RelativeVelocity = tmpVelocity;
}
}
// Air Jumps
if (characterControl.Jump && characterComponent.CurrentAirJumps < characterComponent.MaxAirJumps)
{
CharacterControlUtilities.StandardJump(ref characterBody, characterBody.GroundingUp * characterComponent.JumpSpeed, true, characterBody.GroundingUp);
characterComponent.CurrentAirJumps++;
}
// Gravity
CharacterControlUtilities.AccelerateVelocity(ref characterBody.RelativeVelocity, characterComponent.Gravity, deltaTime);
// Drag
CharacterControlUtilities.ApplyDragToVelocity(ref characterBody.RelativeVelocity, deltaTime, characterComponent.AirDrag);
}
}
public void VariableUpdate(ref ThirdPersonCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref ThirdPersonCharacterComponent characterComponent = ref CharacterComponent.ValueRW;
ref ThirdPersonCharacterControl characterControl = ref CharacterControl.ValueRW;
ref quaternion characterRotation = ref CharacterAspect.LocalTransform.ValueRW.Rotation;
// Add rotation from parent body to the character rotation
// (this is for allowing a rotating moving platform to rotate your character as well, and handle interpolation properly)
KinematicCharacterUtilities.AddVariableRateRotationFromFixedRateRotation(ref characterRotation, characterBody.RotationFromParent, baseContext.Time.DeltaTime, characterBody
// Rotate towards move direction
if (math.lengthsq(characterControl.MoveVector) > 0f)
{
CharacterControlUtilities.SlerpRotationTowardsDirectionAroundUp(ref characterRotation, baseContext.Time.DeltaTime, math.normalizesafe(characterControl.MoveVector), MathUtilitie
}
}
#region Character Processor Callbacks
public void UpdateGroundingUp(
ref ThirdPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext)
{
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
CharacterAspect.Default_UpdateGroundingUp(ref characterBody);
}
AddComponent(selfEntity, authoring.Character);
AddComponent(selfEntity, new ThirdPersonCharacterControl());
}
}
}
Tutorial/Assets/Scripts/StandardCharacter/ThirdPerson/Scripts/ThirdPersonCharacterComponent.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using Unity.CharacterController;
using Unity.Physics;
using Unity.Physics.Authoring;
[Serializable]
public struct ThirdPersonCharacterComponent : IComponentData
{
public float RotationSharpness;
public float GroundMaxSpeed;
public float GroundedMovementSharpness;
public float SprintSpeedMultiplier;
public float AirAcceleration;
public float AirMaxSpeed;
public float AirDrag;
public float JumpSpeed;
public int MaxAirJumps;
public float3 Gravity;
public bool PreventAirAccelerationAgainstUngroundedHits;
public BasicStepAndSlopeHandlingParameters StepAndSlopeHandling;
public CustomPhysicsBodyTags IgnoredPhysicsTags;
[UnityEngine.HideInInspector] // we don't want this field to appear in the inspector
public int CurrentAirJumps;
public static ThirdPersonCharacterComponent GetDefault()
{
return new ThirdPersonCharacterComponent
{
RotationSharpness = 25f,
GroundMaxSpeed = 10f,
GroundedMovementSharpness = 15f,
AirAcceleration = 50f,
AirMaxSpeed = 10f,
AirDrag = 0f,
JumpSpeed = 10f,
Gravity = math.up() * -30f,
PreventAirAccelerationAgainstUngroundedHits = true,
StepAndSlopeHandling = BasicStepAndSlopeHandlingParameters.GetDefault(),
};
}
}
[Serializable]
public struct ThirdPersonCharacterControl : IComponentData
{
public float3 MoveVector;
public bool Jump;
public bool Sprint;
}
Tutorial/Assets/Scripts/StandardCharacter/ThirdPerson/Scripts/ThirdPersonCharacterSystems.cs
using Unity.Burst;
using Unity.Burst.Intrinsics;
using Unity.Entities;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Transforms;
using Unity.CharacterController;
[UpdateInGroup(typeof(KinematicCharacterPhysicsUpdateGroup))]
[BurstCompile]
public partial struct ThirdPersonCharacterPhysicsUpdateSystem : ISystem
{
private EntityQuery _characterQuery;
private ThirdPersonCharacterUpdateContext _context;
private KinematicCharacterUpdateContext _baseContext;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
_characterQuery = KinematicCharacterUtilities.GetBaseCharacterQueryBuilder()
.WithAll<
ThirdPersonCharacterComponent,
ThirdPersonCharacterControl>()
.Build(ref state);
_context = new ThirdPersonCharacterUpdateContext();
_context.OnSystemCreate(ref state);
_baseContext = new KinematicCharacterUpdateContext();
_baseContext.OnSystemCreate(ref state);
state.RequireForUpdate(_characterQuery);
state.RequireForUpdate<PhysicsWorldSingleton>();
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
_context.OnSystemUpdate(ref state);
_baseContext.OnSystemUpdate(ref state, SystemAPI.Time, SystemAPI.GetSingleton<PhysicsWorldSingleton>());
ThirdPersonCharacterPhysicsUpdateJob job = new ThirdPersonCharacterPhysicsUpdateJob
{
Context = _context,
BaseContext = _baseContext,
};
job.ScheduleParallel();
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct ThirdPersonCharacterPhysicsUpdateJob : IJobEntity, IJobEntityChunkBeginEnd
{
public ThirdPersonCharacterUpdateContext Context;
public KinematicCharacterUpdateContext BaseContext;
void Execute(ThirdPersonCharacterAspect characterAspect)
{
characterAspect.PhysicsUpdate(ref Context, ref BaseContext);
}
public bool OnChunkBegin(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
BaseContext.EnsureCreationOfTmpCollections();
return true;
}
public void OnChunkEnd(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask, bool chunkWasExecuted)
{ }
}
}
[UpdateInGroup(typeof(KinematicCharacterVariableUpdateGroup))]
[BurstCompile]
public partial struct ThirdPersonCharacterVariableUpdateSystem : ISystem
{
private EntityQuery _characterQuery;
private ThirdPersonCharacterUpdateContext _context;
private KinematicCharacterUpdateContext _baseContext;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
_characterQuery = KinematicCharacterUtilities.GetBaseCharacterQueryBuilder()
.WithAll<
ThirdPersonCharacterComponent,
ThirdPersonCharacterControl>()
.Build(ref state);
_context = new ThirdPersonCharacterUpdateContext();
_context.OnSystemCreate(ref state);
_baseContext = new KinematicCharacterUpdateContext();
_baseContext.OnSystemCreate(ref state);
state.RequireForUpdate(_characterQuery);
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
_context.OnSystemUpdate(ref state);
_baseContext.OnSystemUpdate(ref state, SystemAPI.Time, SystemAPI.GetSingleton<PhysicsWorldSingleton>());
ThirdPersonCharacterVariableUpdateJob job = new ThirdPersonCharacterVariableUpdateJob
{
Context = _context,
BaseContext = _baseContext,
};
job.ScheduleParallel();
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct ThirdPersonCharacterVariableUpdateJob : IJobEntity, IJobEntityChunkBeginEnd
{
public ThirdPersonCharacterUpdateContext Context;
public KinematicCharacterUpdateContext BaseContext;
void Execute(ThirdPersonCharacterAspect characterAspect)
{
characterAspect.VariableUpdate(ref Context, ref BaseContext);
}
public bool OnChunkBegin(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
BaseContext.EnsureCreationOfTmpCollections();
return true;
}
public void OnChunkEnd(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask, bool chunkWasExecuted)
{ }
}
}
Tutorial/Assets/Scripts/StandardCharacter/ThirdPerson/Scripts/ThirdPersonPlayer.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
[Serializable]
public struct ThirdPersonPlayer : IComponentData
{
public Entity ControlledCharacter;
public Entity ControlledCamera;
}
[Serializable]
public struct ThirdPersonPlayerInputs : IComponentData
{
public float2 MoveInput;
public float2 CameraLookInput;
public float CameraZoomInput;
public FixedInputEvent JumpPressed;
public bool SprintHeld;
}
Tutorial/Assets/Scripts/StandardCharacter/ThirdPerson/Scripts/ThirdPersonPlayerAuthoring.cs
using UnityEngine;
using Unity.Entities;
[DisallowMultipleComponent]
public class ThirdPersonPlayerAuthoring : MonoBehaviour
{
public GameObject ControlledCharacter;
public GameObject ControlledCamera;
cameraControl.FollowedCharacterEntity = player.ControlledCharacter;
cameraControl.Look = playerInputs.CameraLookInput;
cameraControl.Zoom = playerInputs.CameraZoomInput;
SystemAPI.SetComponent(player.ControlledCamera, cameraControl);
}
}
}
}
/// <summary>
/// Apply inputs that need to be read at a fixed rate.
/// It is necessary to handle this as part of the fixed step group, in case your framerate is lower than the fixed step rate.
/// </summary>
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup), OrderFirst = true)]
[BurstCompile]
public partial struct ThirdPersonPlayerFixedStepControlSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<FixedTickSystem.Singleton>();
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<ThirdPersonPlayer, ThirdPersonPlayerInputs>().Build());
}
// Jump
// We use the "FixedInputEvent" helper struct here to detect if the event needs to be processed.
// This is part of a strategy for proper handling of button press events that are consumed during the fixed update group.
characterControl.Jump = playerInputs.ValueRW.JumpPressed.IsSet(fixedTick);
// Sprint
characterControl.Sprint = playerInputs.ValueRW.SprintHeld;
SystemAPI.SetComponent(player.ControlledCharacter, characterControl);
}
}
}
}
Tutorial/Assets/Scripts/StandardCharacter/ThirdPerson/Scripts/OrbitCamera/CameraTarget.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
[Serializable]
public struct CameraTarget : IComponentData
{
public Entity TargetEntity;
}
Tutorial/Assets/Scripts/StandardCharacter/ThirdPerson/Scripts/OrbitCamera/CameraTargetAuthoring.cs
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)
{
AddComponent(GetEntity(TransformUsageFlags.None), new CameraTarget
{
TargetEntity = GetEntity(authoring.Target, TransformUsageFlags.Dynamic),
});
}
}
}
Tutorial/Assets/Scripts/StandardCharacter/ThirdPerson/Scripts/OrbitCamera/OrbitCamera.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[Serializable]
public struct OrbitCamera : IComponentData
{
[Header("Rotation")]
public float RotationSpeed;
public float MaxVAngle;
public float MinVAngle;
public bool RotateWithCharacterParent;
[Header("Zooming")]
public float TargetDistance;
public float MinDistance;
public float MaxDistance;
public float DistanceMovementSpeed;
public float DistanceMovementSharpness;
[Header("Obstructions")]
public float ObstructionRadius;
public float ObstructionInnerSmoothingSharpness;
public float ObstructionOuterSmoothingSharpness;
public bool PreventFixedUpdateJitter;
// Data in calculations
[HideInInspector]
public float CurrentDistanceFromMovement;
[HideInInspector]
public float CurrentDistanceFromObstruction;
[HideInInspector]
public float PitchAngle;
[HideInInspector]
public float3 PlanarForward;
TargetDistance = 5f,
MinDistance = 0f,
MaxDistance = 10f,
DistanceMovementSpeed = 50f,
DistanceMovementSharpness = 20f,
ObstructionRadius = 0.1f,
ObstructionInnerSmoothingSharpness = float.MaxValue,
ObstructionOuterSmoothingSharpness = 5f,
PreventFixedUpdateJitter = true,
CurrentDistanceFromObstruction = 0f,
};
return c;
}
}
[Serializable]
public struct OrbitCameraControl : IComponentData
{
public Entity FollowedCharacterEntity;
public float2 Look;
public float Zoom;
}
[Serializable]
public struct OrbitCameraIgnoredEntityBufferElement : IBufferElementData
{
public Entity Entity;
}
Tutorial/Assets/Scripts/StandardCharacter/ThirdPerson/Scripts/OrbitCamera/OrbitCameraAuthoring.cs
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
[DisallowMultipleComponent]
public class OrbitCameraAuthoring : MonoBehaviour
{
public List<GameObject> IgnoredEntities = new List<GameObject>();
public OrbitCamera OrbitCamera = OrbitCamera.GetDefault();
public class Baker : Baker<OrbitCameraAuthoring>
{
public override void Bake(OrbitCameraAuthoring authoring)
{
authoring.OrbitCamera.CurrentDistanceFromMovement = authoring.OrbitCamera.TargetDistance;
authoring.OrbitCamera.CurrentDistanceFromObstruction = authoring.OrbitCamera.TargetDistance;
authoring.OrbitCamera.PlanarForward = -math.forward();
Entity selfEntity = GetEntity(TransformUsageFlags.Dynamic | TransformUsageFlags.WorldSpace);
AddComponent(selfEntity, authoring.OrbitCamera);
AddComponent(selfEntity, new OrbitCameraControl());
DynamicBuffer<OrbitCameraIgnoredEntityBufferElement> ignoredEntitiesBuffer = AddBuffer<OrbitCameraIgnoredEntityBufferElement>(selfEntity);
for (int i = 0; i < authoring.IgnoredEntities.Count; i++)
{
ignoredEntitiesBuffer.Add(new OrbitCameraIgnoredEntityBufferElement
{
Entity = GetEntity(authoring.IgnoredEntities[i], TransformUsageFlags.None),
});
}
}
}
}
Tutorial/Assets/Scripts/StandardCharacter/ThirdPerson/Scripts/OrbitCamera/OrbitCameraSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Systems;
using Unity.Transforms;
using Unity.CharacterController;
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(TransformSystemGroup))]
[UpdateBefore(typeof(EndSimulationEntityCommandBufferSystem))]
public partial struct OrbitCameraSystem : ISystem
{
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;
}
Tutorial Sections
1. Project Setup
2. Standard Character Setup
3. Double-Jump
4. Sprinting
5. Character Hits
6. Enable Dynamics
7. Moving Platforms
8. Step Handling and Slope Changes
9. Ignore Collisions With Tag
10. Friction Surfaces
11. Jump Pad (add forces to character)
12. AI Character
_Documentation/Samples/basic.md
Basic Sample
This sample provides a basic character controller and a scene containing a wide variety of obstacles and scenarios: slopes, terrain-like surfaces,
dynamic bodies, moving platforms, controllable dynamic vehicle, etc...
Game Initialization
SceneInitializationSystem is responsible game initialization. It looks for the presence of a SceneInitialization singleton in the world (it
is created by a SceneInitialization authoring object in the subscene). If present, it spawns the Player, Character, and Camera prefabs that
are stored in the SceneInitialization singleton, and sets up references between these. This is how the character gets set-up in the scene when
pressing Play.
Character
The Basic character uses a structure that is very similar to the third-person standard character. The main additions are found in the
BasicCharacterApect, where logic is implemented to allow the following features: * Ignore grounding on physics bodies that have a certain
physics tag (implemented in IsGroundedOnHit) * Ignore collisions on physics bodies that have a certain physics tag (implemented in
CanCollideWithHit) * Ignore step handling on physics bodies that have a certain physics tag (implemented in IsGroundedOnHit and
OnMovementHit) * Modify mass ratios against certain dynamic physics bodies based on a physics tag (implemented in
OverrideDynamicHitMasses) * Modify velocity projection to allow bouncing against certain physics bodies based on a physics tag
(implemented in ProjectVelocityOnHits)
AI characters use the exact same character Aspect as the main player-controlled character, but their BasicCharacterControl component is
being controlled by the BasicAICharacterSystem instead of Player systems. They also have no camera.
Input
Input handling is mostly the same as in the third-person standard character, with the main difference that it uses Unity's new input system package
instead of the built-in one. Input is polled from the input system in the BasicPlayerInputsSystem.
Camera
Camera handling is the same as in the third-person standard character. It is handled in OrbitCameraSystem.
Vehicle
The vehicle logic is all in the VehicleSystem. It uses cylinder collider casts to detect wheel collisions and apply suspension forces to the vehicle
physics body accordingly. For the sake of simplicity, the input-handling for the vehicle is simply hard-coded in the VehicleSystem class.
_Documentation/Samples/onlinefps.md
OnlineFPS Sample
This sample demonstrates a fast-paced, client-predicted online FPS game implemented using DOTS Netcode. - Building & Playing the Game -
Game Management - Character & Camera - Input & Commands - Weapons
_Documentation/Samples/platformer.md
Platformer Sample
This sample demonstrates a complex character controller with a wide variety of states, as well as animation.
Game Initialization
SceneInitializationSystem is responsible game initialization. It looks for the presence of a SceneInitialization singleton in the world (it
is created by a SceneInitialization authoring object in the subscene). If present, it spawns the Player, Character, and Camera prefabs that
are stored in the SceneInitialization singleton, and sets up references between these. This is how the character gets set-up in the scene when
pressing Play.
Character
See implementation details in the Platformer Character page.
Camera
Camera handling is mostly the same as in the third-person standard character. It is handled in OrbitCameraSystem. The main addition in this
sample is the management of smooth transitions between different camera targets & parameters that are specific to which state our character is in.
This is all handled in OrbitCameraSystem, under the // Camera target handling comment.
Inputs
Input handling is mostly the same as in the third-person standard character, with the main difference that it uses Unity's new input system package
instead of the built-in one. Input is polled from the input system in the PlatformerPlayerInputsSystem.
Animation
Since an Entities-based animation solution is not presently available, animation in this sample is handled with a hybrid GameObject/ECS approach.
Here are the main files responsible for animation handling: - PlatformerCharacterAnimationAuthoring: Defines the clip indexes of the
various animation clips, as defined in the mecanim animation controller. - PlatformerCharacterAnimation: Holds the clip indexes of the
various animation clips, as well as the hash for the "clipIndex" parameter in the mecanim animation controller. -
PlatformerCharacterHybridLink: An ECS hybrid component whose purpose is to remember references to the character skinned mesh
GameObject, as well as the mecanim Animator component. Notice that this component is of class type -
PlatformerCharacterHybridData: An ECS hybrid component whose purpose is to remember the prefab of the character skinned mesh
GameObject. Notice that this component is of class type - PlatformerCharacterHybridSystem: A system that handles
instantiating/destroying the character skinned mesh GameObject along with its associated Entity. It also handles updating animation every frame by
calling PlatformerCharacterAnimationHandler.UpdateAnimation() - PlatformerCharacterAnimationHandler: A static class that
handles deciding which animation state the character should have right now. It does this by observing the character state, calculating animation
speeds when needed, and setting the "clipIndex" parameter in the mecanim animator accordingly
_Documentation/Samples/stresstest.md
StressTest Sample
This sample provides you with a simple scene where you can spawn X amount of characters that move in circles. There are also options for
switching the level prefab, and tweaking various character settings that commonly affect performance.
Character
The character implementation in this sample is very close to the standard characters, with the exception that it has options to turn on and off certain
features. All characters are controlled by a StressTestCharacterControlSystem rather than being controlled by "Players" gathering input.
StressTestManager
StressTestManager handles all the rest of the logic in this sample. Here are the various features & character options it manages via the sample
UI at runtime: - Spawn button: spawns characters. - Spawn Count: amount of characters to be spawned. - Environment Prefab: select which
environment will be spawned. - Multithreaded: toggle scheduling parallel jobs instead of a single job. - Physics Step: toggle the simulation of the
physics step. - Rendering: toggle rendering. - StepHandling: toggles step handling. - SlopeChanges: toggles slope changes detection in order to
deground the character in certain scenarios. - ProjectVelocityOnInitialOverlaps: toggles projection of velocity on overlaps before the character
moves (can help preventing tunneling for rotating character shapes). - StatefulHits: toggles processing of stateful character hits in the character's
update. - SimulatedDynamic: toggles KinematicCharacterProperties.SimulateDynamicBody, which controls whether or not the
character can push or be pushed by dynamic bodies. - Save and Restore state during update: when active, the character will save and restore
its state during its update, using the KinematicCharacterStateSave feature. - Enhanced Ground Precision: toggles calculating a more
precise ground hitz.
_Documentation/Samples/OnlineFPSSample/building-
playing.md
OnlineFPS Sample - Building & Playing
In order to play the game, you need to either host a game, or connect to a game. This is done through the connection menu at the top-left of the
screen when launching the game.
Hosting a game
To host a game, you need to specify a port on which to listen to connections (the "Host Port"), and then click the "Host Game" button. This will
start a server for the game, as well as a local client for you to play as.
NOTE: When hosting a game, if you want others to be able to connect, it is important that you forward the port you specified as your "Host Port"
in your router settings. The exact procedure varies depending on the router. If you don't know how to do it, search for "port forwarding" + the
brand of router you have.
Connecting to a game
To connect to a game/server, you must enter the IP and port of the desired server in the "Join IP" and "Join Port" fields. Once this is done, press
the "Join Game" button.
The "Join IP" is the public IP of the server. You can get the public IP by searching for "my ip" on google on the computer that is hosting the game.
However, if you try playing by launching multiple builds on the same machine, the "Join IP" should always be "127.0.0.1"
The "Join Port" is the port that the server specified as their "Host Port".
In our FirstPersonCharacterAspect.VariableUpdate, when we update our character rotation, we do so by working directly with
ViewPitchDegrees and CharacterYDegrees (look for their usage in the parameters of the
FirstPersonCharacterUtilities.ComputeFinalRotationsFromRotationDelta call). This function handles modifying those angles, and
then computing final character & view rotations from them.
However, since this rotation update is only handled for simulated characters on owning client & server, we also need a way for non-owning clients
to synchronize character rotations as well. This is done in BuildCharacterRotationSystem. This system handles rebuilding the rotation of all
characters based on the CharacterYDegrees at the beginning the prediction update. With this, all character rotations will be synchronized on all
clients, and all character rotations will also be accurately reconstructed on all rollback & resimulation updates
Interpolation
Character interpolation is a special case in netcode characters. Because netcode manages its own interpolation of interpolated ghosts, we need the
built-in character interpolation to not interfere with it. We can do this with the CharacterInterpolation_GhostVariant, which specifies that
the CharacterInterpolation component should only exist on predicted client ghosts.
_Documentation/Samples/OnlineFPSSample/game-
management.md
OnlineFPS Sample - Game Management
Connection Menu
The game's main menu allows you to join or host a game. Here's how this menu is implemented: * A UI gameObject is present in the scene, Under
it, there is a UIDocument named Menu for the connection menu. This uses UI Toolkit * The UI gameObject has a UIReferences monobehaviour
on it. It holds references to UI documents * On Start, the UIReferences monobehaviour gets the GameUISystem and calls SetUIReferences
on it. This is what gives the GameUISystem the necessary UI references that it needs to find in the scene * GameUISystem handles all UI logic. If
finds all UI element references in various UI documents, and subscribes methods to UI events such as button clicks or value changes. Based on the
state of the game, it also handles showing/hiding certain UI elements * Joining is handled by creating an entity with a
GameManagementSystem.JoinRequest component in GameUISystem.JoinButtonPressed. These requests are processed by the
GameManagementSystem later * Hosting is handled by creating an entity with a GameManagementSystem.HostRequest component in
GameUISystem.HostButtonPressed. These requests are processed by the GameManagementSystem later
Game Management
GameManagementSystem updates in the default GameObject world, and is mainly responsible for handling joining, hosting, and disconnecting. It
creates/destroys server & client worlds as needed, loads the game scene, etc...
ServerGameSystem updates in the server world, and has the following responsibilities: * Handle join requests from clients
(HandleAcceptJoinsOncePendingScenesAreLoaded, HandleJoinRequests, HandlePendingJoinClientTimeout) * Handle cleanup
on client disconnect (HandleDisconnect) * Handle triggering a respawn countdown on character death (HandleCharacterDeath) * Handle
character spawning (HandleSpawnCharacter)
ClientGameSystem updates in the client world, and has the following responsibilities: * Handle sending a join request with player info to the
server (HandleSendJoinRequestOncePendingScenesLoaded, HandlePendingJoinRequest) * Handle setting up newly-spawned
character ghosts (HandleCharacterSetupAndDestruction) * Handle returning to menu on connection timeout (HandleDisconnect) *
Handle requests to activate the respawn screen when the local character dies (HandleRespawnScreen)
Inter-world communication
Sometimes, a client world needs to communicate with the default world, or the default world needs to communicate with the client/server worlds,
etc... In this sample, we handle this via the following systems: * MoveLocalEntitiesToClientServerSystem *
MoveClientServerEntitiesToLocalSystem
These systems look for entities that have tags such as MoveToClientWorld, MoveToServerWorld or MoveToLocalWorld, and handles moving
the entity to the appropriate world(s).
This is used in several places in the sample, where we create entities as "events" or "requests" to do something: * Sending a disconnection request
from the default world (where the UI is) to the client/server worlds * Sending a request to display respawn timer screen from the client world to
the default world (where the UI is) * etc...
_Documentation/Samples/OnlineFPSSample/input-and-
commands.md
OnlineFPS Sample - Input and Player Commands
Character input handling in this sample is structured in a very similar way to the first-person standard character, but with a few key differences: *
Inputs are stored in a FirstPersonPlayerCommands component, which implements IInputComponentData (see NetCode Documentation on
player commands) * FirstPersonPlayerInputsSystem updates in GhostInputSystemGroup, and writes to
FirstPersonPlayerCommands * FirstPersonPlayerVariableStepControlSystem and
FirstPersonPlayerFixedStepControlSystem update in the PredictedSimulationSystemGroup and
PredictedFixedStepSimulationSystemGroup respectively, because they handle getting the commands at the prediction tick and applying
them to the character. They are therefore part of prediction. * Camera "look" input (FirstPersonPlayerCommands.LookInputDelta) has
special handling in FirstPersonPlayerInputsSystem. See the next section for details
Weapon shooting
1. The FirstPersonPlayerFixedStepControlSystem writes fire press/release inputs to the WeaponControl component on the
character's active weapon
2. The WeaponFiringMecanismSystem reads firing inputs from the WeaponControl component, and uses them to determine how many
shots should be fired this frame based on the weapon's firing rate and other parameters.
3. The StandardRaycastWeaponPredictionSystem reads the amount of shots to fire determined in the previous step, and for each shot,
it`
Handles shot raycast & damage (only on the server)
Handles shot VFX & recoil requests (only on clients, and on the first time the tick is simulated)
Weapon animation
CharacterWeaponVisualFeedbackSystem is responsible for animating the weapon socket entity and camera FoV based on character velocity
and weapon shots. This includes weapon bobbing, weapon recoil, weapon aiming, FoV kick, etc...
_Documentation/Samples/PlatformerSample/character.md
Platformer Sample - Character
The character in this sample is fairly complex. Due to its level of complexity, we've decided to separate its movement logic into various "states".
The implementation of the character logic resembles the third-person standard character at its core, but
PlatformerCharacterAspect.PhysicsUpdate and PlatformerCharacterAspect.VariableUpdate is where it starts to differ.
PlatformerCharacterAspect.PhysicsUpdate starts by handling logic that is common to all states, then calls the current state's physics
update via stateMachine.OnStatePhysicsUpdate, and finally does more logic common to all states after the state machine update. Similarly,
PlatformerCharacterAspect.VariableUpdate calls the current state's variable update via stateMachine.OnStateVariableUpdate.
In short; instead of having all character update logic in the PlatformerCharacterAspect directly, we've implemented the logic of each state in
their own struct. PlatformerCharacterStateMachine is responsible for calling the state update methods on the current state struct. For each
state method, we pass the PlatformerCharacterAspect by reference, along with the character update contexts, so that state updates can have
access to all the data they need.
Every character state checks for state transitions, typically at the end of their OnStatePhysicsUpdate. When they determine that they should
transition, they call stateMachine.TransitionToState.
States
Here are the various states of the character:
Ground Move
Air Move
Crouched
Wall Run
Flying
Dashing
Rope Swing
Rolling
Swimming
Climbing
Ledge Grab
Misc Features
Here is a quick description of how several other features were implemented:
Planet Gravity: The gravity of all physics objects in this sample is handled by a CustomGravity component, a GravityZonesSystem, and
special gravity zones such as GlobalGravityZone and SphericalGravityZone. The GravityZonesSystem will handle calculating a
spherical gravity for all CustomGravity entities in its sphere trigger zone. Then, it will apply a global gravity to all CustomGravity entities that
are not in any other kind of gravity zone. Finally, for all dynamic body entities that have a CustomGravity and a PhysicsVelocity, it will add
that calculated gravity to the PhysicsVelocity.Linear. The character doesn't get its velocity modified even though it has a CustomGravity,
because it is not a dynamic body. The character takes care of adding its custom gravity to its KinematicCharacterBody.RelativeVelocity
in its state updates. And finally, the character orients its rotation so that it always points towards the opposite of the custom gravity's direction.
Ice Surface: A CharacterFrictionModifier component can be placed on rigidbodies to modify the character's simulated friction when it
walks on that surface. In its state movement updates, the character tries to see if the ground hit entity has a CharacterFrictionModifier
component, and if so, it'll change the way it controls its velocity based on the CharacterFrictionModifier.Friction
Wind Zone: A WindZoneSystem iterates on all entities that have a WindZone component and a trigger collider that raises trigger events. For each
teleporter, if a trigger event with a KinematicCharacterBody was detected, we add a wind acceleration to the
KinematicCharacterBody.RelativeVelocity
Jump Pad: A JumpPadSystem iterates on all entities that have a JumpPad component and a trigger collider that raises trigger events. For each
teleporter, if a trigger event with a character was detected, we unground the character and add a velocity impulse to the
KinematicCharacterBody.RelativeVelocity
Teleporter: A TeleporterSystem iterates on all entities that have a Teleporter component and a trigger collider that raises trigger events. For
each teleporter, if a trigger event with a KinematicCharacterBody was detected, we move the character position to the teleportation destination
point. Additionally, since we don't want character interpolation to be active for the teleportation, we call SkipNextInterpolation() on the
CharacterInterpolation component of the character entity. This will skip the interpolation for the remainder of the frames until next fixed
update.
Sticky Surfaces (walk on walls): PlatformerCharacterComponent.StickySurfaceTag allows us to assign a physics tag that represents
sticky surfaces that the character can walk on. In the character's state update, it will see if the rigidbody of the ground hit entity has that tag, and if
so, it will orient its rotation so that it always points towards the ground normal. Moreover, since the character's GroundingUp is always set to be
the character rotation's up direction, our grounding reference direction will always be relative to the character orientation, which means the
character could consider itself grounded even if it was standing upside down on the ceiling.
_Documentation/Samples/PlatformerSample/CharacterStates/air-
move.md
Platformer Sample - Air Move
The AirMoveState handles regular air movement, air jumping, and detecting ungrounded walls (required for wall-running transitions). It also deals
with jumping "grace times": allowing a jump input to be pressed slightly after becoming ungrounded, but still allowing the jump to happen as though we
were grounded. This is controlled by the JumpAfterUngroundedGraceTime.
Typically, this state is transitioned to when the character is not grounded and not doing any other special action such as wall running.
_Documentation/Samples/PlatformerSample/CharacterStates/climbing.md
Platformer Sample - Climbing
The ClimbingState allows the character to climb on surfaces designated with the correct physics tag. Due to the highly-specialized collision detection required in this
state, it turns off the character's collision detection in its OnStateEnter. Instead, this state will manage collision detection and velocity projection manually.
The ClimbingState.ClimbingDetection function handles detecting overlaps with climbable surfaces, and calculating the average normal of the climbing surface. It
also adds every non-climbable hit to the character's VelocityProjectionHits buffer. This buffer is used during the state's movement update in order to project the
character's velocity against all unclimbable obstructions while climbing, using PlatformerCharacterAspect.ProjectVelocityOnHits.
The state's movement update consists of detecting climbing hits, stiching close to the climbing surface, moving towards the input direction projected on the climbing
surface normal, projecting velocity against unclimbable hits, and finally orienting self towards the average climbing normal.
It is very important for ClimbingState.ClimbingDetection to detect the hits with a perfect sphere shape (the character capsule collider is changed to a sphere shape
in the OnStateEnter). The reason for that is because when the character is climbing, it must make sure that adapting its rotation to the climbing normal will not
result in changing the hits that are detected. With a non-sphere shape, the character will keep jittering as it tries to orient itself towards the surface, because it keeps
detect different hits as a result of its rotation.
This state is transitioned to when pressing the climb input while being close to a climbable surfaces. Other states call ClimbingState.CanStartClimbing in order to
detect this.
_Documentation/Samples/PlatformerSample/CharacterStates/crouched.md
Platformer Sample - Crouched
The CrouchedState handles regular moving while crouched. It is in many ways similar to the GroundMoveState, but it handles resizing the character collider in its
OnStateEnter and OnStateExit.
This state is transitioned to when grounded and when the the crouch input is pressed.
This state handles adapting the character rotation to match any detected "friction surface".
_Documentation/Samples/PlatformerSample/CharacterStates/dashing.md
Platformer Sample - Dashing
The DashingState allows the character to perform a quick dash in a given direction. It works by remembering a _dashStartTime and _dashDirection when it
enters the state, and then it moves in that _dashDirection until its DashDuration has expired.
This state is transitioned to with the dash input.
_Documentation/Samples/PlatformerSample/CharacterStates/flying.md
Platformer Sample - Flying
The FlyingNoCollisionsState allows the character to fly without any collisions. It handles turning off character collisions in its OnStateEnter, and turning
them back on in its OnStateExit. The movement is handled by simply moving the character transform position directly.
This state is transitioned to by pressing the flying input.
_Documentation/Samples/PlatformerSample/CharacterStates/ground-
move.md
Platformer Sample - Ground Move
The GroundMoveState handles regular grounded movement, jumping, sprinting, detecting "sticky surfaces" (walk on walls), and detecting "friction surfaces"
(like ice). It also deals with jumping "grace times": allowing a jump input to be pressed slightly before landing on the ground, but still allowing the jump to
happen. This is controlled by the JumpBeforeGroundedGraceTime.
Typically, this state is transitioned to when the character is grounded and not doing any other special action such as crouching.
This state handles adapting the character rotation to match any detected "friction surface".
_Documentation/Samples/PlatformerSample/CharacterStates/ledge-
grab.md
Platformer Sample - Ledge Grab
The LedgeGrabState allows the character to "snap" to a ledge and move along that ledge. Due to the highly-specialized collision detection required in this
state, it turns off the character's collision detection in its OnStateEnter. Instead, this state will manage collision detection manually.
LedgeGrabState.LedgeDetection is what handles detecting valid ledges & information related to them. It casts the character collider forward to
detect the ledge wall, and then raycasts downward at the ledge detection point to try to find a valid surface at the top of the ledge. Once it has found a
valid surface, it will do a distance check with the character collider at the top of the ledge to see if our character would have space to get up there. Finally,
it evaluates if we would be grounded on that surface hit.
The character's movement update in this state first calls LedgeGrabState.LedgeDetection to get up-to-date information about the current ledge. Then,
if we have still found a valid ledge, we adjust the character position & rotation to make it stick to the ledge wall. Then, we calculate a movement that is
parallel to the ledge wall, and determine the final position where this movement would take us. We do a LedgeGrabState.LedgeDetection at that final
calculated position to see if there would still be a valid ledge over there. If not, we cancel the movement.
This state is transitioned to when other states (such as AirMoveState) call LedgeGrabState.CanGrabLedge, and a ledge is successfully detected.
_Documentation/Samples/PlatformerSample/CharacterStates/rolling.md
Platformer Sample - Rolling
The RollingState allows the character to turn into a rolling ball that can accelerate and has no notion of grounding. In OnStateEnter, the character's grounding
evaluation is disabled, the character collider geometry is changed to a sphere, and the character mesh is swapped for a ball mesh. The opposite effect is done in
OnStateExit. Velocity is handled simply by accelerating the velocity in the desired direction.
The rope entity is spawned in the state's OnStateEnter. The CharacterRopeSystem handles positioning the rope entity from character to rope
attachment point, and destroying the rope when its owning character is not in RopeSwingState anymore.
_Documentation/Samples/PlatformerSample/CharacterStates/swimming.md
Platformer Sample - Swimming
The SwimmingState allows the character to accelerate and rotate freely towards any direction when it is in water. The state has a SwimmingState.DetectWaterZones
function to detect when it is in the water.
The state update is split into two main phases: PreMovementUpdate and PostMovementUpdate. PreMovementUpdate uses SwimmingState.DetectWaterZones to
try to see if it is still in a water zone, and to calculate some details about the direction and distance to the water surface. If it is close to the water surface, the character will
rotate upright and prevent moving downward unless the movement input is very perpenticular to the water surface. This helps create a good "swimming at the surface of the
water" mechanic. Character rotation control when underwater will simply rotate the character around its capsule geometry center towards the movement direction. After the
character movement update is done, PostMovementUpdate will call SwimmingState.DetectWaterZones one more time. This is because it needs to know if the
movement we just did brought us out of the water, and if so, we must snap back to the water surface.
This state is transitioned to when any other state detects that we are in a water zone, by calling SwimmingState.DetectWaterZones.
_Documentation/Samples/PlatformerSample/CharacterStates/wall-
run.md
Platformer Sample - Wall Run
The WallRunState first detects if it is still moving against an ungrounded surface (a wall). If not, it transitions out to another state. But if it is, it will
restrict move the move input vector so that it is tangent to the wall, and it will move the character in that direction. It also handles jumping against the wall
(the jump direction is calculated based on the wall normal).
Reducing the PlatformerCharacterComponent.WallRunGravityFactor will increase the efficiency of wall-running. This basically reduces the
effect of gravity while in that state, and therefore makes you stay in air longer than if you were just free-falling.
This state is transitioned to when in AirMoveState, and the sprint input is true, and HasDetectedMoveAgainstWall is true.
_Documentation/Tutorial/tutorial-ai.md
Tutorial - AI Character
We will now go over the implementation of very rudimentary AI characters.
We want to have characters that detect human-controlled characters at a distance, and start moving towards them as long as they are in their detection range. We will also demonstrate how we can structure things
so that we don't have to create an entirely new character controller for AIs, and we can simply reuse the same that our player character uses.
Let's first create our AIController component & authoring. This component represents an "AI Brain" that controls a specific character: ```cs using System; using Unity.Entities; using Unity.Physics.Authoring;
[Serializable] public struct AIController : IComponentData { public float DetectionDistance; public PhysicsCategoryTags DetectionFilter; } ```
```cs using UnityEngine; using Unity.Entities;
public class AIControllerAuthoring : MonoBehaviour { public AIController AIController;
class Baker : Baker<AIControllerAuthoring>
{
public override void Bake(AIControllerAuthoring authoring)
{
AddComponent(authoring.AIController);
}
}
} ```
And now the AIControllerSystem. This system iterates on entities that have AIController and character components, handles detecting player characters, and handles writing move inputs to the
ThirdPersonCharacterControl component so that the AI character move towards detected player characters: ```cs using Unity.Collections; using Unity.Entities; using Unity.Mathematics; using Unity.Physics;
using Unity.Transforms;
public partial class AIControllerSystem : SystemBase { protected override void OnUpdate() { PhysicsWorld physicsWorld = SystemAPI.GetSingleton().PhysicsWorld; NativeList distanceHits = new
NativeList(Allocator.TempJob);
foreach (var (characterControl, aiController, localTransform) in SystemAPI.Query<RefRW<ThirdPersonCharacterControl>, AIController, LocalTransform>())
{
// Clear our detected hits list between each use
distanceHits.Clear();
// Detect hits that are within the detection range of the AI character
PointDistanceInput distInput = new PointDistanceInput
{
Position = localTransform.Position,
MaxDistance = aiController.DetectionDistance,
Filter = new CollisionFilter { BelongsTo = CollisionFilter.Default.BelongsTo, CollidesWith = aiController.DetectionFilter.Value },
};
physicsWorld.CalculateDistance(distInput, ref hitsCollector);
// If it has a character component but no AIController component, that means it's a human player character
if (SystemAPI.HasComponent<ThirdPersonCharacterComponent>(hitEntity) && !SystemAPI.HasComponent<AIController>(hitEntity))
{
selectedTarget = hitEntity;
break; // early out
}
}
// In the character control component, set a movement vector that will make the ai character move towards the selected target
if (selectedTarget != Entity.Null)
{
characterControl.ValueRW.MoveVector = math.normalizesafe(SystemAPI.GetComponent<LocalTransform>(selectedTarget).Position - localTransform.Position);
}
else
{
characterControl.ValueRW.MoveVector = float3.zero;
}
}
}
} ```
Note: you could also choose to make the AI detection zones work with trigger colliders instead of a CalculateDistance query, if you prefer that approach.
Now you can create a copy of your character object in the Subscene, name it "AICharacter", and add an AIController component to it. Set the DetectionDistance to 8 for example, and the
DetectionFilter to "Everything".
If you press Play, AICharacters should start chasing you once you get within detection range
_Documentation/Tutorial/tutorial-characterhits.md
Tutorial - Character Hits
We will now look at how we can iterate on character hits that happened during the frame. You can think of these as the character's "collision
events".
We will create a new system that updates after the KinematicCharacterPhysicsUpdateGroup, and iterates on all
DynamicBuffer<KinematicCharacterHit>. During the character update, all hits that have been detected are added to this buffer. Therefore
we must iterate on it after the entire character update is finished if we want all detected hits to be present in the buffer.
We will also create a second job within that new system, which iterates on DynamicBuffer<StatefulKinematicCharacterHit>. These hits
are similar to KinematicCharacterHit, except they also keep track of the state of the hit (Enter, Exit, Stay).
```cs using Unity.Entities; using Unity.Physics.Systems; using Unity.CharacterController;
[UpdateInGroup(typeof(AfterPhysicsSystemGroup))] [UpdateAfter(typeof(KinematicCharacterPhysicsUpdateGroup))] public partial class
CharacterHitsDetectionSystem : SystemBase { protected override void OnUpdate() { // Iterate on non-stateful hits foreach (var hitsBuffer in
SystemAPI.Query>()) { for (int i = 0; i < hitsBuffer.Length; i++) { KinematicCharacterHit hit = hitsBuffer[i]; if(!hit.IsGroundedOnHit) {
UnityEngine.Debug.Log($"Detected an ungrounded hit {hit.Entity.Index}"); } } }
// Iterate on stateful hits
foreach (var statefulHitsBuffer in SystemAPI.Query<DynamicBuffer<StatefulKinematicCharacterHit>>())
{
for (int i = 0; i < statefulHitsBuffer.Length; i++)
{
StatefulKinematicCharacterHit hit = statefulHitsBuffer[i];
if (hit.State == CharacterHitState.Enter)
{
UnityEngine.Debug.Log($"Entered new hit {hit.Hit.Entity.Index}");
}
else if (hit.State == CharacterHitState.Exit)
{
UnityEngine.Debug.Log($"Exited a hit {hit.Hit.Entity.Index}");
}
}
}
}
} ```
This system demonstrates how to iterate over those character hits. In this example, the system will print a message in the console whenever the
character detected an ungrounded hit, and every time we have entered any new hit.
Note: if you don't want to use the stateful hits and don't want to pay any processing cost for them, you can remove the feature by removing the
CharacterAspect.Update_ProcessStatefulCharacterHits(); line in your ThirdPersonCharacterAspect.PhysicsUpdate
_Documentation/Tutorial/tutorial-charactersetup.md
Tutorial - Standard Character Setup
We will now go through the process of creating a new basic character, using the "Standard Characters" as a starting point. For this tutorial, we'll be
using the Third-Person character.
We will first create a basic scene containing a rudimentary environment for our character to be in: - Create a new scene. This scene should have a
camera and a directional light already in it - Add a Subscene - Add floors and walls with collisions to this subscene
Next, we will import the Third-Person standard character into our project: - Follow the Third-Person standard character instructions in order to
add the character to your subscene. - Read the Standard character implementation overview, to get a general idea of how this character works.
Once you're done and the character has been added to the subscene, you can try pressing Play, and you should be able to control your new
character using mouse and keyboard (WASD)
_Documentation/Tutorial/tutorial-doublejump.md
Tutorial - Double Jump
We will now add the ability to allow up to X additional jumps while in air.
We will start by adding an int field in ThirdPersonCharacterComponent to determine how many air jumps we are allowed to do, and we will also add another int field to keep track of how many air jumps
we've done so far.
```cs [Serializable] public struct ThirdPersonCharacterComponent : IComponentData { // (...)
public int MaxAirJumps;
[UnityEngine.HideInInspector] // we don't want this field to appear in the inspector
public int CurrentAirJumps;
} ```
The rest of the implementation will be done in ThirdPersonCharacterAspect.HandleVelocityControl. If we are not grounded and a jump is requested, we will check if we have reached our max in-air
jumps count, and if not, we will jump. Also, when we are grounded, we always reset our CurrentAirJumps to 0.
```cs public readonly partial struct ThirdPersonCharacterAspect : IAspect, IKinematicCharacterProcessor { // (...)
private void HandleVelocityControl(ref ThirdPersonCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
// (...)
if (characterBody.IsGrounded)
{
// (...)
} ```
Now if you set MaxAirJumps to 1 in your character prefab and press Play, you should be able to double jump. Additional air jumps can be performed with a value of more than 1.
_Documentation/Tutorial/tutorial-enabledynamics.md
Tutorial - Enable Dynamics
This character controller supports simulating a dynamic body for characters, which means the character can push & be pushed by other dynamic
bodies with a given mass.
By default, the standard characters are already configured to support this. You can read more about the required setup for this here: Dynamic
Body Interaction
With our character already setup for dynamic rigidbody interactions, we need to add a dynamic box to the scene that we can push with our
character. Add a cube to your Subscene, and add a collider and a rigidbody to it. Make sure the rigidbody is not set to "Kinematic".
Press Play, and try to push it. Play with the masses of the character and the box to see how different mass ratios affect the pushing.
Note: If you want your character to be able to stand on a dynamic rigidbody & follow its movement, simply add a
TrackedTransformAuthoring to that rigidbody entity. We will elaborate more on this in the tutorial section on moving platforms.
SynchronizeCollisionWorld
By default, the physics systems don't update the CollisionWorld after the physics step has moved bodies with their velocity. Since our character
updates after physics, and relies on the CollisionWorld in order to detect hits, leaving things like this may result in a slight visual lag when a
character pushes or gets pushed by another body.
We can solve this by enabling the SynchronizeCollisionWorld option in the PhysicsStep's inspector. In your Subscene, add a GameObject
with the PhysicsStep component on it [Screenshot]. Then, enable SynchronizeCollisionWorld. This will take care of updating the
CollisionWorld properly and get rid of any lag, but keep in mind this can add a certain performance cost.
_Documentation/Tutorial/tutorial-frictionsurface.md
Tutorial - Friction Surface
Now, we want to be able to create surfaces where the character has a limited movement speed.
To do this, we will first create a new CharacterFrictionSurface component (along with a corresponding authoring component) that will be assignable to certain objects in the world:
```cs using System; using Unity.Entities;
[Serializable] public struct CharacterFrictionSurface : IComponentData { public float VelocityFactor; } ```
```cs using UnityEngine; using Unity.Entities;
public class CharacterFrictionSurfaceAuthoring : MonoBehaviour { public float VelocityFactor;
class Baker : Baker<CharacterFrictionSurfaceAuthoring>
{
public override void Bake(CharacterFrictionSurfaceAuthoring authoring)
{
AddComponent(new CharacterFrictionSurface { VelocityFactor = authoring.VelocityFactor });
}
}
} ```
At this point, we'd want to modify our ThirdPersonCharacterAspect.HandleVelocityControl so that if our GroundHit.Entity has a CharacterFrictionSurface, then we apply a VelocityFactor to our character's
desired velocity. However, we can't do this just yet, because we currently have no way to access CharacterFrictionSurface components on other entities during our character aspect's updates. This is where
ThirdPersonCharacterUpdateContext can become useful. ThirdPersonCharacterUpdateContext is a struct that gets initialized by character systems, and passed as parameter to the update methods of the character Aspect.
This makes it the ideal place to store ComponentLookups, or any singleton data that must be accessed during the character update.
We will therefore add a ComponentLookup<CharacterFrictionSurface> in our ThirdPersonCharacterUpdateContext, in order to be able to look up these components on other entities (the
ThirdPersonCharacterUpdateContext struct is located in the same file as our ThirdPersonCharacterAspect). The struct should look like this (see comments in code for additional details):
```cs public struct ThirdPersonCharacterUpdateContext { // Here, you may add additional global data for your character updates, such as ComponentLookups, Singletons, NativeCollections, etc... // The data you add here will be
accessible in your character updates and all of your character "callbacks". [ReadOnly] public ComponentLookup CharacterFrictionSurfaceLookup;
// This is called by systems that schedule jobs that update the character aspect, in their OnCreate().
// Here, you can get the component lookups.
public void OnSystemCreate(ref SystemState state)
{
CharacterFrictionSurfaceLookup = state.GetComponentLookup<CharacterFrictionSurface>(true);
}
// This is called by systems that schedule jobs that update the character aspect, in their OnUpdate()
// Here, you can update the component lookups.
public void OnSystemUpdate(ref SystemState state)
{
CharacterFrictionSurfaceLookup.Update(ref state);
}
} ```
With this, we are ready to access the CharacterFrictionSurface component on hit entities in our character updated. We will proceed with modifying ThirdPersonCharacterAspect.HandleVelocityControl so that we apply
a VelocityFactor reduction to our velocity when the GroundHit.Entity has a `CharacterFrictionSurface:
```cs public readonly partial struct ThirdPersonCharacterAspect : IAspect, IKinematicCharacterProcessor { private void HandleVelocityControl(ref ThirdPersonCharacterUpdateContext context, ref KinematicCharacterUpdateContext
baseContext) { // (...)
if (characterBody.IsGrounded)
{
// Move on ground
// (...)
// Sprint
// (...)
// Friction surfaces
if (context.CharacterFrictionSurfaceLookup.TryGetComponent(characterBody.GroundHit.Entity, out CharacterFrictionSurface frictionSurface))
{
targetVelocity *= frictionSurface.VelocityFactor;
}
CharacterControlUtilities.StandardGroundMove_Interpolated(ref characterBody.RelativeVelocity, targetVelocity, characterComponent.GroundedMovementSharpness, deltaTime, characterBody
// Jump
// (...)
}
else
{
// (...)
}
}
} ```
Finally, you can add a new box object to the Subscene, with a Box collider, and a CharacterFrictionSurface component. If you set the VelocityFactor to something like 0.2 on that CharacterFrictionSurfaceAuthoring
and you press Play, you should see your character move much slower on that surface.
_Documentation/Tutorial/tutorial-ignorecollisions.md
Tutorial - Ignore Collisions Tag
We will now make the character ignore all collisions with colliders that have a certain physics custom tag.
For this section, we will need to import custom physics authoring components in order to be able to set custom tags on rigidbodies. Go to the package manager
window, find the "Unity Physics" package, and go to the "Samples" tab of that package. In this window, import the "Custom Physics Authoring" sample.
Once the custom physics authoring components are imported, we will add a box with a PhysicsShape and a PhysicsBody to the Subscene, and we will assign the
custom tag '0' to the "Custom Tags" of its PhysicsBody. This '0' tag is the tag we want to ignore collisions with:
Next, we will add a field to our ThirdPersonCharacterComponent so we can specify which tag we want to ignore collisions with. You will have to add using
Unity.Physics.Authoring; at the top of the file for this to work.
// (...)
} ```
Then, we will modify ThirdPersonCharacterAspect.CanCollideWithHit so that it also ignores collisions with colliders that have that physics tag. (You will have
to add using Unity.Physics.Authoring; at the top of the file for this to work.)
```cs public readonly partial struct ThirdPersonCharacterAspect : IAspect, IKinematicCharacterProcessor { // (...)
public bool CanCollideWithHit(
ref ThirdPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
in BasicHit hit)
{
ThirdPersonCharacterComponent characterComponent = CharacterComponent.ValueRO;
return true;
}
} ```
You can now set the proper ignored tag (tag 0) in your character authoring's inspector.
Finally, you can press Play, and try to collide with the box marked with the ignored tag. You should go right through.
Note: since the callbacks of the ThirdPersonCharacterAspect can potentially be called multiple times per character per frame, it is always a good idea to try to
make their logic as inexpensive as possible. If you can, it is still better to ignore collisions with physics categories instead of by checking for a tag or component. But
there are times when procedural collision filtering like this can come in very handy.
_Documentation/Tutorial/tutorial-jumppad.md
Tutorial - Jump Pad (add forces to character)
We will now go over the implementation of a jump pad - a trigger zone that applies a sudden impulse to the character and sends it flying in a direction.
For this section, we will need to add some files to our project: Physics Stateful Events. These come from ECS Physics Samples, and they provide an easy way to get collision/trigger events in
DOTS.
Now let's create our JumpPad component and its authoring: ```cs using Unity.Entities; using Unity.Mathematics;
public struct JumpPad : IComponentData { public float3 JumpForce; } ```
```cs using UnityEngine; using Unity.Entities;
public class JumpPadAuthoring : MonoBehaviour { public float JumpForce;
class Baker : Baker<JumpPadAuthoring>
{
public override void Bake(JumpPadAuthoring authoring)
{
AddComponent(new JumpPad { JumpForce = authoring.JumpForce });
}
}
} ```
Then, add a cube to the Subscene with the following components: * a box PhysicsShape with a "Raise Trigger Events" collision response * a JumpPadAuthoring component, with its JumpForce
set to (0, 20, 0) for example (an upwards force of 20) * a StatefulTriggerEventBufferAuthoring (this component is from the downloaded files at the top of this page)
Now let's create our JumpPadSystem. See comments in code for explanations: ```cs using Unity.Entities; using Unity.Mathematics; using Unity.Physics.Stateful; using Unity.Physics.Systems; using
Unity.CharacterController;
// Update after events processing system [UpdateInGroup(typeof(AfterPhysicsSystemGroup))] [UpdateBefore(typeof(KinematicCharacterPhysicsUpdateGroup))] public partial class
JumpPadSystem : SystemBase { protected override void OnUpdate() { // Iterate on all jump pads with trigger event buffers foreach (var (jumpPad, triggerEventsBuffer, entity) in
SystemAPI.Query>().WithEntityAccess()) { // Go through each trigger event of the jump pad... for (int i = 0; i < triggerEventsBuffer.Length; i++) { StatefulTriggerEvent triggerEvent =
triggerEventsBuffer[i]; Entity otherEntity = triggerEvent.GetOtherEntity(entity);
// If a character has entered the trigger...
if (triggerEvent.State == StatefulEventState.Enter && SystemAPI.HasComponent<KinematicCharacterBody>(otherEntity))
{
KinematicCharacterBody characterBody = SystemAPI.GetComponent<KinematicCharacterBody>(otherEntity);
// Cancel out character velocity in the jump force's direction
// (this helps make the character jump up even if it is falling down on the jump pad at high speed)
characterBody.RelativeVelocity = MathUtilities.ProjectOnPlane(characterBody.RelativeVelocity, math.normalizesafe(jumpPad.JumpForce));
// Add the jump pad force to the character
characterBody.RelativeVelocity += jumpPad.JumpForce;
} ```
Now, press Play and move your character through the jump pad. Your character should get pushed into the air
_Documentation/Tutorial/tutorial-movingplatforms.md
Tutorial - Moving platforms
We will now go over how to implement moving platforms.
The key points to remember for moving platforms are: * The moving platform entity must have a TrackedTransformAuthoring component (on the object that will be baked to a rigidbody). * The moving platform rigidbody should be
interpolated if your character position is also interpolated. * The moving platform can be moved in any way you want, but moving it with its PhysicsVelocity as a kinematic rigidbody will have the advantage of allowing it to push the
character properly & transfer its velocity to it.
The code for a simple moving platform is provided here (component, authoring & system):
```cs using System; using Unity.Entities; using Unity.Mathematics; using UnityEngine;
[Serializable] public struct MovingPlatform : IComponentData { public float3 TranslationAxis; public float TranslationAmplitude; public float TranslationSpeed; public float3 RotationAxis; public float RotationSpeed;
[HideInInspector]
public bool IsInitialized;
[HideInInspector]
public float3 OriginalPosition;
[HideInInspector]
public quaternion OriginalRotation;
} ```
```cs using Unity.Entities; using UnityEngine;
public class MovingPlatformAuthoring : MonoBehaviour { public MovingPlatform MovingPlatform;
public class Baker : Baker<MovingPlatformAuthoring>
{
public override void Bake(MovingPlatformAuthoring authoring)
{
AddComponent(authoring.MovingPlatform);
}
}
} ```
```cs using Unity.Entities; using Unity.Mathematics; using Unity.Physics; using Unity.Physics.Systems; using Unity.Transforms;
[UpdateInGroup(typeof(BeforePhysicsSystemGroup))] public partial class MovingPlatformSystem : SystemBase { protected override void OnUpdate() { float deltaTime = SystemAPI.Time.DeltaTime; float invDeltaTime = 1f /
deltaTime; float time = (float)World.Time.ElapsedTime;
foreach(var (movingPlatform, physicsVelocity, physicsMass, localTransform, entity) in SystemAPI.Query<RefRW<MovingPlatform>, RefRW<PhysicsVelocity>, PhysicsMass, LocalTransform>().With
{
if(!movingPlatform.ValueRW.IsInitialized)
{
// Remember initial pos/rot, because our calculations depend on them
movingPlatform.ValueRW.OriginalPosition = localTransform.Position;
movingPlatform.ValueRW.OriginalRotation = localTransform.Rotation;
movingPlatform.ValueRW.IsInitialized = true;
}
float3 targetPos = movingPlatform.ValueRW.OriginalPosition + (math.normalizesafe(movingPlatform.ValueRW.TranslationAxis) * math.sin(time * movingPlatform.ValueRW.TranslationSpeed)
quaternion rotationFromMovement = quaternion.Euler(math.normalizesafe(movingPlatform.ValueRW.RotationAxis) * movingPlatform.ValueRW.RotationSpeed * time);
quaternion targetRot = math.mul(rotationFromMovement, movingPlatform.ValueRW.OriginalRotation);
// Move with velocity
physicsVelocity.ValueRW = PhysicsVelocity.CalculateVelocityToTarget(in physicsMass, localTransform.Position, localTransform.Rotation, new RigidTransform(targetRot, targetPos), invD
}
}
} ```
This system will make kinematic physics bodies move with a given translation & rotation speed at a fixed timestep using a math function. It calculates a targetPos and a targetRot, and then calls
PhysicsVelocity.CalculateVelocityToTarget to calculate & apply a physics velocity that would bring the rigidbody to that target position/rotation over the next fixed update. Compared to moving the object directly with
transform position/rotation, this has the added benefit of playing nicely with pushing other physics objects (and characters). The fact that it has the correct physics velocity means that it'll accurately apply impulses on other bodies it collides
with.
Now, let's set up a moving platform in our scene: * Create the MovingPlatform, MovingPlatformAuthoring and MovingPlatformSystem scripts in your project using the code above. * Add a new box with a collider and a
kinematic rigidbody to your subscene. Call this object "MovingPlatform". * Make sure that this "MovingPlatform"'s rigidbody uses interpolation (also known as "smoothing" in the custom physics authoring components). * Add a
TrackedTransformAuthoring component to the "MovingPlatform". * Set some parameters in the MovingPlatform component.
At this point, you can press Play and jump onto the moving platform. Your character should be able to stand on it
Additional Notes
If you want your character to be able to stand on a dynamic rigidbody, simply add a TrackedTransformAuthoring component to that rigidbody.
If you really don't want to have to manually add a TrackedTransformAuthoring component to everything that a character can stand on, you could choose to write a job that iterates on all entities that have a PhysicsVelocity
but don't have a TrackedTransform, and then add a TrackedTransform on those. This would take care of automatically making all kinematic or dynamic bodies in your scene able to be stood on by the character.
Even though it is recommended to move kinematic moving platforms with their PhysicsVelocity for accurate physics interactions between the platform and the characters, a moving platform moved directly with Translation
and Rotation can still be stood on nonetheless if it has a TrackedTransform component. It's just that the pushing and being pushed logic between the platform and the character will be flawed because it won't take impulses into
account.
_Documentation/Tutorial/tutorial-setup.md
Tutorial - Project Setup
We will start by creating a project that is set-up for DOTS.
Create/Open a Unity project. Make sure it also uses either the URP or HDRP.
Import the com.unity.charactercontroller package from the package manager. This will take care of importing the
com.unity.entities and com.unity.physics packages into your project, if not already present.
Import the com.unity.entities.graphics package from the package manager.
(RECOMMENDED) Go to Edit > Project Settings > Editor. Enable Enter Play Mode Options, and make sure the Reload
Domain and Reload Scene underneath are both disabled. This will make entering play mode much faster
Note: For best performance in editor, pay attention to these settings: * Jobs > Burst > Enable Compilation should be enabled in the top
bar menu * Edit > Preferences > Jobs > Enable Jobs Debugger should be disabled
_Documentation/Tutorial/tutorial-sprint.md
Tutorial - Sprint
We will now add a Sprint functionality to the character. When pressing the LeftShift key, we want to apply a multiplier to our character velocity.
First, we will modify the ThirdPersonPlayerInputs component to add a field for sprint input. This component is meant to hold the raw input associated with a human player
cs [Serializable] public struct ThirdPersonPlayerInputs : IComponentData { // (...) public bool SprintHeld; }
Next, we will modify the ThirdPersonPlayerInputsSystem so that it queries that input from the input system, and stores it in the component
```cs [UpdateInGroup(typeof(InitializationSystemGroup))] public partial class ThirdPersonPlayerInputsSystem : SystemBase { // (...)
protected override void OnUpdate()
{
foreach (var (playerInputs, player) in SystemAPI.Query<RefRW<ThirdPersonPlayerInputs>, ThirdPersonPlayer>())
{
// (...)
playerInputs.ValueRW.SprintHeld = Input.GetKey(KeyCode.LeftShift);
}
}
} ```
Next, we will modify the ThirdPersonCharacterControl component to add a field for whether the character should be sprinting or not
cs [Serializable] public struct ThirdPersonCharacterControl : IComponentData { // (...) public bool Sprint; }
Next, we will modify ThirdPersonPlayerFixedStepControlSystem so that it takes the sprint input stored in ThirdPersonPlayerInputs on the player, and writes it to the ThirdPersonCharacterControl component on the
character. Make sure this happens before we write back to the component using SystemAPI.SetComponent
```cs [UpdateInGroup(typeof(FixedStepSimulationSystemGroup), OrderFirst = true)] [BurstCompile] public partial struct ThirdPersonPlayerFixedStepControlSystem : ISystem { // (...)
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
foreach (var (playerInputs, player) in SystemAPI.Query<RefRW<ThirdPersonPlayerInputs>, ThirdPersonPlayer>().WithAll<Simulate>())
{
if (SystemAPI.HasComponent<ThirdPersonCharacterControl>(player.ControlledCharacter))
{
// (...)
// Sprint
characterControl.Sprint = playerInputs.ValueRW.SprintHeld;
SystemAPI.SetComponent(player.ControlledCharacter, characterControl);
}
// (...)
}
}
} ```
To summarize what we've done at this point: we've stored raw inputs for sprinting in a component on the entity that represents the human player controlling the character (the "Player" prefab), and we've modified its input systems in order
to pass on that sprint input to a component that tells the character what to do (the ThirdPersonCharacterControl component on the "Character" prefab).
We've taken care of the sprint inputs, but now we must take care of the sprint implementation.
We will add a field that represents the multiplier to apply to our velocity when we're sprinting. This field will be in ThirdPersonCharacterComponent (this component is meant to represent the data that's specific to our character
implementation)
cs [Serializable] public struct ThirdPersonCharacterComponent : IComponentData { // (...) public float SprintSpeedMultiplier; }
Then, we will use that ThirdPersonCharacterComponent.SprintSpeedMultiplier field to modify our character velocity in the character update, but only when ThirdPersonCharacterControl.Sprint is true, and only when
the character is grounded. This will be done in ThirdPersonCharacterAspect.HandleVelocityControl
```cs public readonly partial struct ThirdPersonCharacterAspect : IAspect, IKinematicCharacterProcessor { // (...)
private void HandleVelocityControl(ref ThirdPersonCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
// (...)
if (characterBody.IsGrounded)
{
// Move on ground
float3 targetVelocity = characterControl.MoveVector * characterComponent.GroundMaxSpeed;
// Sprint
if (characterControl.Sprint)
{
targetVelocity *= characterComponent.SprintSpeedMultiplier;
}
CharacterControlUtilities.StandardGroundMove_Interpolated(ref characterBody.RelativeVelocity, targetVelocity, characterComponent.GroundedMovementSharpness, deltaTime, characterBody
// (...)
}
else
{
// (...)
}
}
} ```
At this point, we can assign a value to the SprintSpeedMultiplier on the character prefab (a value of 2 will do, which means the spring speed will be 2x the regular speed). You can do this in the
ThirdPersonCharacterAuthoring component on your prefab, under the Character section.
Now if you press play, and if you hold the LeftShift key, your character should be moving faster.
_Documentation/Tutorial/tutorial-steps-and-slopes.md
Tutorial - Step Handling and Slope Changes
We will now take a moment to try and better understand the step-handling and slope change options that come with the standard character. The
stardard characters' authoring components have a "Step And Slope Handling" subsection, under the "Character" section in the inspector. This is
where you'll find all sorts of options for step-handling and slope-handling.
Step Handling
Step handling is the concept of allowing the character to step onto elevated surfaces when it moves against them.
The "Step And Slope Handling" section contains the following fields for step-handling: * Step Handling: determines if step handling is enabled *
Max Step Height: the max height a character can step over * Extra Steps Check Distance: the horizontal distance of extra raycasts used to
detect valid steps
With step handling disabled, we get this behaviour:
And with step handling enabled:
NOTE: in order to be able to "step down" without going airborne, the character must have a
KinematicCharacterBody.GroundSnappingDistance that is high enough to match the step height.
Slope Changes
The standard character component comes with the following fields: * Prevent Grounding When Moving Towards No Grounding: Makes the
character not consider itself grounded when moving off of a ledge * Has Max Downward Slope Change Angle: Determines if we'll prevent
grounding based on slope angle change * Max Downward Slope Change Angle: the angle (degrees) used for preventing grounding based on
slope angle change
With PreventGroundingWhenMovingTowardsNoGrounding off:
With PreventGroundingWhenMovingTowardsNoGrounding on (notice how our character trajectory seems more natural, due to not trying to
stick to the ground at the top of the ramp):
With HasMaxDownwardSlopeChangeAngle off:
With HasMaxDownwardSlopeChangeAngle on and a MaxDownwardSlopeChangeAngle of 60:
See Slope Management for a more in-depth explanation