0% found this document useful (0 votes)
477 views946 pages

CharacterControllerSamples PDF

The document is a license file for Unity Character Controller samples. It describes that the samples are provided under a Unity Companion License and without any warranty. It also includes README files that describe how to open and use the sample projects, and reference documentation for the Character Controller package.

Uploaded by

Diakou Staee
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
477 views946 pages

CharacterControllerSamples PDF

The document is a license file for Unity Character Controller samples. It describes that the samples are provided under a Unity Companion License and without any warranty. It also includes README files that describe how to open and use the sample projects, and reference documentation for the Character Controller package.

Uploaded by

Diakou Staee
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 946

LICENSE.

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;

public partial class @BasicInputActions: IInputActionCollection2, IDisposable


{
public InputActionAsset asset { get; }
public @BasicInputActions()
{
asset = InputActionAsset.FromJson(@"{
""name"": ""BasicInputActions"",
""maps"": [
{
""name"": ""DefaultMap"",
""id"": ""46bf95ba-b7a8-4da1-a4d5-ad49677d3765"",
""actions"": [
{
""name"": ""Move"",
""type"": ""Value"",
""id"": ""1b319d9c-b705-46f9-b440-deabe5108883"",
""expectedControlType"": """",
""processors"": ""Clamp(max=1)"",
""interactions"": """",
""initialStateCheck"": true
},
{
""name"": ""LookDelta"",
""type"": ""Value"",
""id"": ""9ac4461b-a0bd-43bc-9258-03bf2a17ca60"",
""expectedControlType"": ""Vector2"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": true
},
{
""name"": ""LookConst"",
""type"": ""Value"",
""id"": ""2c9dbca0-7b97-44f8-ae57-87c7bca92d66"",
""expectedControlType"": """",
""processors"": """",
""interactions"": """",
""initialStateCheck"": true
},
{
""name"": ""Scroll"",
""type"": ""Value"",
""id"": ""b58e7016-1fbe-440f-851d-fe5dc0b9bdf5"",
""expectedControlType"": ""Analog"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": true
},
{
""name"": ""Jump"",
""type"": ""Value"",
""id"": ""73c5ed6d-6e83-4693-857f-7a639b926770"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": true
}
],
""bindings"": [
{
""name"": ""Arrows"",
""id"": ""57a3c129-4935-42e8-a713-862f74ef7291"",
""path"": ""2DVector(normalize=false)"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Move"",
""isComposite"": true,
""isPartOfComposite"": false
},
{
""name"": ""up"",
""id"": ""df9b328d-3e19-4d55-8b87-b363ce2180f5"",
""path"": ""<Keyboard>/w"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": ""down"",
""id"": ""e12587eb-6fef-49da-8ea2-5639a419e541"",
""path"": ""<Keyboard>/s"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": ""left"",
""id"": ""b4c41209-0011-46e0-91fe-210de67b2e01"",
""path"": ""<Keyboard>/a"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": ""right"",
""id"": ""787aa252-db2b-485b-91df-c886ca6889fe"",
""path"": ""<Keyboard>/d"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": """",
""id"": ""f706f9e6-91c2-47f7-a528-f703615fe4f0"",
""path"": ""<Gamepad>/leftStick"",
""interactions"": """",
""processors"": ""StickDeadzone(min=0.1,max=1)"",
""groups"": """",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""f10408b2-1e7d-4dbe-b86f-fc66f618ec70"",
""path"": ""<Mouse>/delta"",
""interactions"": """",
""processors"": ""ScaleVector2(x=0.05,y=0.05)"",
""groups"": """",
""action"": ""LookDelta"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""14ff7398-fb3b-4aad-a46c-5776489455e5"",
""path"": ""<Keyboard>/space"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Jump"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""c29791b5-ad84-48b8-a28b-7f9fc41ff7f9"",
""path"": ""<Gamepad>/buttonSouth"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Jump"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""ddf3d29a-c6cc-4cd7-af23-85f44d1ec93a"",
""path"": ""<Mouse>/scroll/y"",
""interactions"": """",
""processors"": ""Scale(factor=0.1),Invert"",
""groups"": """",
""action"": ""Scroll"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""c381834f-30fa-4ea9-a1cd-f5103af901d3"",
""path"": ""<Gamepad>/rightStick"",
""interactions"": """",
""processors"": ""StickDeadzone(min=0.2,max=1),ScaleVector2(x=70,y=70)"",
""groups"": """",
""action"": ""LookConst"",
""isComposite"": false,
""isPartOfComposite"": false
}
]
}
],
""controlSchemes"": []
}");
// DefaultMap
m_DefaultMap = asset.FindActionMap("DefaultMap", throwIfNotFound: true);
m_DefaultMap_Move = m_DefaultMap.FindAction("Move", throwIfNotFound: true);
m_DefaultMap_LookDelta = m_DefaultMap.FindAction("LookDelta", throwIfNotFound: true);
m_DefaultMap_LookConst = m_DefaultMap.FindAction("LookConst", throwIfNotFound: true);
m_DefaultMap_Scroll = m_DefaultMap.FindAction("Scroll", throwIfNotFound: true);
m_DefaultMap_Jump = m_DefaultMap.FindAction("Jump", throwIfNotFound: true);
}
public void Dispose()
{
UnityEngine.Object.Destroy(asset);
}

public InputBinding? bindingMask


{
get => asset.bindingMask;
set => asset.bindingMask = value;
}
public ReadOnlyArray<InputDevice>? devices
{
get => asset.devices;
set => asset.devices = value;
}
public ReadOnlyArray<InputControlScheme> controlSchemes => asset.controlSchemes;
public bool Contains(InputAction action)
{
return asset.Contains(action);
}
public IEnumerator<InputAction> GetEnumerator()
{
return asset.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Enable()
{
asset.Enable();
}
public void Disable()
{
asset.Disable();
}
public IEnumerable<InputBinding> bindings => asset.bindings;
public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false)
{
return asset.FindAction(actionNameOrId, throwIfNotFound);
}
public int FindBinding(InputBinding bindingMask, out InputAction action)
{
return asset.FindBinding(bindingMask, out action);
}
// DefaultMap
private readonly InputActionMap m_DefaultMap;
private List<IDefaultMapActions> m_DefaultMapActionsCallbackInterfaces = new List<IDefaultMapActions>();
private readonly InputAction m_DefaultMap_Move;
private readonly InputAction m_DefaultMap_LookDelta;
private readonly InputAction m_DefaultMap_LookConst;
private readonly InputAction m_DefaultMap_Scroll;
private readonly InputAction m_DefaultMap_Jump;
public struct DefaultMapActions
{
private @BasicInputActions m_Wrapper;
public DefaultMapActions(@BasicInputActions wrapper) { m_Wrapper = wrapper; }
public InputAction @Move => m_Wrapper.m_DefaultMap_Move;
public InputAction @LookDelta => m_Wrapper.m_DefaultMap_LookDelta;
public InputAction @LookConst => m_Wrapper.m_DefaultMap_LookConst;
public InputAction @Scroll => m_Wrapper.m_DefaultMap_Scroll;
public InputAction @Jump => m_Wrapper.m_DefaultMap_Jump;
public InputActionMap Get() { return m_Wrapper.m_DefaultMap; }
public void Enable() { Get().Enable(); }
public void Disable() { Get().Disable(); }
public bool enabled => Get().enabled;
public static implicit operator InputActionMap(DefaultMapActions set) { return set.Get(); }
public void AddCallbacks(IDefaultMapActions instance)
{
if (instance == null || m_Wrapper.m_DefaultMapActionsCallbackInterfaces.Contains(instance)) return;
m_Wrapper.m_DefaultMapActionsCallbackInterfaces.Add(instance);
@Move.started += instance.OnMove;
@Move.performed += instance.OnMove;
@Move.canceled += instance.OnMove;
@LookDelta.started += instance.OnLookDelta;
@LookDelta.performed += instance.OnLookDelta;
@LookDelta.canceled += instance.OnLookDelta;
@LookConst.started += instance.OnLookConst;
@LookConst.performed += instance.OnLookConst;
@LookConst.canceled += instance.OnLookConst;
@Scroll.started += instance.OnScroll;
@Scroll.performed += instance.OnScroll;
@Scroll.canceled += instance.OnScroll;
@Jump.started += instance.OnJump;
@Jump.performed += instance.OnJump;
@Jump.canceled += instance.OnJump;
}
private void UnregisterCallbacks(IDefaultMapActions instance)
{
@Move.started -= instance.OnMove;
@Move.performed -= instance.OnMove;
@Move.canceled -= instance.OnMove;
@LookDelta.started -= instance.OnLookDelta;
@LookDelta.performed -= instance.OnLookDelta;
@LookDelta.canceled -= instance.OnLookDelta;
@LookConst.started -= instance.OnLookConst;
@LookConst.performed -= instance.OnLookConst;
@LookConst.canceled -= instance.OnLookConst;
@Scroll.started -= instance.OnScroll;
@Scroll.performed -= instance.OnScroll;
@Scroll.canceled -= instance.OnScroll;
@Jump.started -= instance.OnJump;
@Jump.performed -= instance.OnJump;
@Jump.canceled -= instance.OnJump;
}
public void RemoveCallbacks(IDefaultMapActions instance)
{
if (m_Wrapper.m_DefaultMapActionsCallbackInterfaces.Remove(instance))
UnregisterCallbacks(instance);
}

public void SetCallbacks(IDefaultMapActions instance)


{
foreach (var item in m_Wrapper.m_DefaultMapActionsCallbackInterfaces)
UnregisterCallbacks(item);
m_Wrapper.m_DefaultMapActionsCallbackInterfaces.Clear();
AddCallbacks(instance);
}
}
public DefaultMapActions @DefaultMap => new DefaultMapActions(this);
public interface IDefaultMapActions
{
void OnMove(InputAction.CallbackContext context);
void OnLookDelta(InputAction.CallbackContext context);
void OnLookConst(InputAction.CallbackContext context);
void OnScroll(InputAction.CallbackContext context);
void OnJump(InputAction.CallbackContext context);
}
}
Basic/Assets/Scripts/Camera/CameraTarget.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;

[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;

public class Baker : Baker<CameraTargetAuthoring>


{
public override void Bake(CameraTargetAuthoring authoring)
{
AddComponent(GetEntity(TransformUsageFlags.Dynamic), new CameraTarget
{
TargetEntity = GetEntity(authoring.Target, TransformUsageFlags.Dynamic),
});
}
}
}
Basic/Assets/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);
}
}
}
Basic/Assets/Scripts/Camera/MainEntityCamera.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;

[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;

public class MainGameObjectCamera : MonoBehaviour


{
public static Camera Instance;

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;

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,

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();

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),
});
}
}
}
}
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;

public class BasicAICharacterAuthoring : MonoBehaviour


{
public BasicAICharacter Data;
public class Baker : Baker<BasicAICharacterAuthoring>
{
public override void Bake(BasicAICharacterAuthoring authoring)
{
AddComponent(GetEntity(TransformUsageFlags.Dynamic), authoring.Data);
}
}
}
Basic/Assets/Scripts/Character/BasicAICharacterSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
[UpdateInGroup(typeof(SimulationSystemGroup), OrderFirst = true)]
[UpdateBefore(typeof(FixedStepSimulationSystemGroup))]
[BurstCompile]
public partial struct BasicAICharacterSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
float time = (float)SystemAPI.Time.ElapsedTime;
BasicAICharacterJob job = new BasicAICharacterJob
{
Time = time,
};
job.Schedule();
}
[BurstCompile]
public partial struct BasicAICharacterJob : IJobEntity
{
public float Time;
void Execute(ref BasicAICharacter aiCharacter, ref BasicCharacterControl characterInputs)
{
characterInputs.MoveVector = math.sin(Time * aiCharacter.MovementPeriod) * aiCharacter.MovementDirection;
}
}
}
Basic/Assets/Scripts/Character/BasicCharacterAspect.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 BasicCharacterUpdateContext
{
[ReadOnly]
public ComponentLookup<BouncySurface> BouncySurfaceLookup;
public void OnSystemCreate(ref SystemState state)
{
BouncySurfaceLookup = state.GetComponentLookup<BouncySurface>(true);
}
public void OnSystemUpdate(ref SystemState state)
{
BouncySurfaceLookup.Update(ref state);
}
}
public readonly partial struct BasicCharacterAspect : IAspect, IKinematicCharacterProcessor<BasicCharacterUpdateContext>
{
public readonly KinematicCharacterAspect CharacterAspect;
public readonly RefRW<BasicCharacterComponent> CharacterComponent;
public readonly RefRW<BasicCharacterControl> CharacterControl;
public void PhysicsUpdate(ref BasicCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
ref BasicCharacterComponent characterComponent = ref CharacterComponent.ValueRW;
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref float3 characterPosition = ref CharacterAspect.LocalTransform.ValueRW.Position;
int bodyIndex = baseContext.PhysicsWorld.GetRigidBodyIndex(CharacterAspect.Entity);
// 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 BasicCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
float deltaTime = baseContext.Time.DeltaTime;
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref BasicCharacterComponent characterComponent = ref CharacterComponent.ValueRW;
ref BasicCharacterControl 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);
}
characterComponent.CurrentJumpsInAir = 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;
}
}
// Jump in air
if (characterControl.Jump && characterComponent.CurrentJumpsInAir < characterComponent.MaxJumpsInAir)
{
CharacterControlUtilities.StandardJump(ref characterBody, characterBody.GroundingUp * characterComponent.JumpSpeed, true, characterBody.GroundingUp);
characterComponent.CurrentJumpsInAir++;
}
// Gravity
CharacterControlUtilities.AccelerateVelocity(ref characterBody.RelativeVelocity, characterComponent.Gravity, deltaTime);
// Drag
CharacterControlUtilities.ApplyDragToVelocity(ref characterBody.RelativeVelocity, deltaTime, characterComponent.AirDrag);
}
}
public void VariableUpdate(ref BasicCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref BasicCharacterComponent characterComponent = ref CharacterComponent.ValueRW;
ref BasicCharacterControl 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 BasicCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext)
{
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
CharacterAspect.Default_UpdateGroundingUp(ref characterBody);
}
public bool CanCollideWithHit(
ref BasicCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
in BasicHit hit)
{
if (!PhysicsUtilities.IsCollidable(hit.Material))
{
return false;
}
BasicCharacterComponent characterComponent = CharacterComponent.ValueRO;
if (PhysicsUtilities.HasPhysicsTag(in baseContext.PhysicsWorld, hit.RigidBodyIndex, characterComponent.IgnoreCollisionsTag))
{
return false;
}

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();

public class Baker : Baker<BasicCharacterAuthoring>


{
public override void Bake(BasicCharacterAuthoring authoring)
{
KinematicCharacterUtilities.BakeCharacter(this, authoring, authoring.CharacterProperties);

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;

public static BasicCharacterComponent GetDefault()


{
return new BasicCharacterComponent
{
RotationSharpness = 25f,
GroundMaxSpeed = 10f,
GroundedMovementSharpness = 15f,
AirAcceleration = 50f,
AirMaxSpeed = 10f,
AirDrag = 0f,
JumpSpeed = 10f,
Gravity = math.up() * -30f,
PreventAirAccelerationAgainstUngroundedHits = true,
MaxJumpsInAir = 0,
StepAndSlopeHandling = BasicStepAndSlopeHandlingParameters.GetDefault(),
};
}
}
[Serializable]
public struct BasicCharacterControl : IComponentData
{
public float3 MoveVector;
public bool Jump;
}
Basic/Assets/Scripts/Character/BasicCharacterSystems.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 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);

_context = new BasicCharacterUpdateContext();


_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>());

BasicCharacterPhysicsUpdateJob job = new BasicCharacterPhysicsUpdateJob


{
Context = _context,
BaseContext = _baseContext,
};
job.ScheduleParallel();
}

[BurstCompile]
public partial struct BasicCharacterPhysicsUpdateJob : IJobEntity, IJobEntityChunkBeginEnd
{
public BasicCharacterUpdateContext Context;
public KinematicCharacterUpdateContext BaseContext;

void Execute(BasicCharacterAspect 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 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;

void Execute(BasicCharacterAspect 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)
{ }
}
}
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;

public class BouncySurfaceAuthoring : MonoBehaviour


{
public float BounceEnergyMultiplier = 1f;
public class Baker : Baker<BouncySurfaceAuthoring>
{
public override void Bake(BouncySurfaceAuthoring authoring)
{
AddComponent(GetEntity(TransformUsageFlags.None), new BouncySurface
{
BounceEnergyMultiplier = authoring.BounceEnergyMultiplier,
});
}
}
}
Basic/Assets/Scripts/Misc/CharacterTriggerEventDebugger.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;

public struct CharacterTriggerEventDebugger : IComponentData


{
}
Basic/Assets/Scripts/Misc/CharacterTriggerEventDebuggerAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;

public class CharacterTriggerEventDebuggerAuthoring : MonoBehaviour


{
class Baker : Baker<CharacterTriggerEventDebuggerAuthoring>
{
public override void Bake(CharacterTriggerEventDebuggerAuthoring authoring)
{
AddComponent(GetEntity(authoring, TransformUsageFlags.None), new CharacterTriggerEventDebugger());
}
}
}
Basic/Assets/Scripts/Misc/CharacterTriggerEventDebuggerSystem.cs
using System.Collections;
using System.Collections.Generic;
using Unity.CharacterController;
using Unity.Entities;
using Unity.Physics.Stateful;
using Unity.Physics.Systems;
using UnityEngine;
[UpdateInGroup(typeof(AfterPhysicsSystemGroup))]
public partial struct CharacterTriggerEventDebuggerSystem : ISystem
{
void OnUpdate(ref SystemState state)
{
foreach (var (triggerEvents, entity) in SystemAPI.Query<DynamicBuffer<StatefulTriggerEvent>>().WithEntityAccess())
{
for (int i = 0; i < triggerEvents.Length; i++)
{
if (triggerEvents[i].State == StatefulEventState.Enter)
{
Entity otherEntity = triggerEvents[i].GetOtherEntity(entity);
if (SystemAPI.HasComponent<KinematicCharacterBody>(otherEntity))
{
UnityEngine.Debug.Log($"Entity {entity.Index} detected trigger enter with {otherEntity.Index}");
}
}
}
}
}
}
Basic/Assets/Scripts/Misc/FixedInputEvent.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public struct FixedInputEvent


{
private byte _wasEverSet;
private uint _lastSetTick;
public void Set(uint tick)
{
_lastSetTick = tick;
_wasEverSet = 1;
}

public bool IsSet(uint tick)


{
if (_wasEverSet == 1)
{
return tick == _lastSetTick;
}

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;

[UpdateInGroup(typeof(FixedStepSimulationSystemGroup), OrderLast = true)]


[BurstCompile]
public partial struct FixedTickSystem : ISystem
{
public struct Singleton : IComponentData
{
public uint Tick;
}

public void OnCreate(ref SystemState state)


{ }

public void OnDestroy(ref SystemState state)


{ }

[BurstCompile]
public void OnUpdate(ref SystemState state)
{
if (!SystemAPI.HasSingleton<Singleton>())
{
Entity singletonEntity = state.EntityManager.CreateEntity();
state.EntityManager.AddComponentData(singletonEntity, new Singleton());
}

ref Singleton singleton = ref SystemAPI.GetSingletonRW<Singleton>().ValueRW;


singleton.Tick++;
}
}
Basic/Assets/Scripts/Misc/FramerateMenuManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Rendering;
using System;

public struct FramerateCalculator


{
private int _framesCount;
private float _framesDeltaSum;
private float _minDeltaTimeForAvg;
private float _maxDeltaTimeForAvg;
private string[] _framerateStrings;

public void Initialize()


{
_minDeltaTimeForAvg = Mathf.Infinity;
_maxDeltaTimeForAvg = Mathf.NegativeInfinity;
_framerateStrings = new string[1001];
for (int i = 0; i < _framerateStrings.Length; i++)
{
if (i >= _framerateStrings.Length - 1)
{
_framerateStrings[i] = i.ToString() + "+" + " (<" + (1000f / (float)i).ToString("F") + "ms)";
}
else
{
_framerateStrings[i] = i.ToString() + " (" + (1000f / (float)i).ToString("F") + "ms)";
}
}
}

public void Update()


{
// Regular frames
_framesCount++;
_framesDeltaSum += Time.deltaTime;

// Max and min


if (Time.deltaTime < _minDeltaTimeForAvg)
{
_minDeltaTimeForAvg = Time.deltaTime;
}

if (Time.deltaTime > _maxDeltaTimeForAvg)


{
_maxDeltaTimeForAvg = Time.deltaTime;
}
}
private string GetNumberString(int fps)
{
if (fps < _framerateStrings.Length - 1 && fps >= 0)
{
return _framerateStrings[fps];
}
else
{
return _framerateStrings[_framerateStrings.Length - 1];
}
}
public void PollFramerate(out string avg, out string worst, out string best)
{
avg = GetNumberString(Mathf.RoundToInt(1f / (_framesDeltaSum / _framesCount)));
worst = GetNumberString(Mathf.RoundToInt(1f / _maxDeltaTimeForAvg));
best = GetNumberString(Mathf.RoundToInt(1f / _minDeltaTimeForAvg));

_framesDeltaSum = 0f;
_framesCount = 0;
_minDeltaTimeForAvg = Mathf.Infinity;
_maxDeltaTimeForAvg = Mathf.NegativeInfinity;
}
}

public class FramerateMenuManager : MonoBehaviour


{
[Header("Components")] public Canvas MainCanvas;
public Text AvgFPS;
public Text WorstFPS;
public Text BestFPS;

[Header("Misc")] public float FPSPollRate = 1f;


private FramerateCalculator _framerateCalculator = default;
private float _lastTimePolledFPS = float.MinValue;
private bool _hasVSync = false;
void Start()
{
_framerateCalculator.Initialize();
UpdateRenderSettings();
}

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;

public class FramerateSetter : MonoBehaviour


{
public int Framerate = -1;
public float FixedFramerate = 60;

void Start()
{
Application.targetFrameRate = Framerate;

FixedStepSimulationSystemGroup fixedSystem = World.DefaultGameObjectInjectionWorld.GetOrCreateSystemManaged<FixedStepSimulationSystemGroup>();


fixedSystem.Timestep = 1f / FixedFramerate;
}
}
Basic/Assets/Scripts/Misc/PrefabThrower.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[Serializable]
public struct PrefabThrower : IComponentData
{
public Entity PrefabEntity;
public float3 InitialEulerAngles;
public float ThrowForce;
}
Basic/Assets/Scripts/Misc/PrefabThrowerAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
public class PrefabThrowerAuthoring : MonoBehaviour
{
public GameObject PrefabEntity;
public float3 InitialEulerAngles;
public float ThrowForce;

public class Baker : Baker<PrefabThrowerAuthoring>


{
public override void Bake(PrefabThrowerAuthoring authoring)
{
AddComponent(GetEntity(TransformUsageFlags.Dynamic), new PrefabThrower
{
PrefabEntity = GetEntity(authoring.PrefabEntity, TransformUsageFlags.Dynamic),
ThrowForce = authoring.ThrowForce,
InitialEulerAngles = authoring.InitialEulerAngles,
});
}
}
}
Basic/Assets/Scripts/Misc/PrefabThrowerSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Transforms;
using UnityEngine;

[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;

public class SceneInitializationAuthoring : MonoBehaviour


{
public GameObject CharacterSpawnPointEntity;
public GameObject CharacterPrefabEntity;
public GameObject CameraPrefabEntity;
public GameObject PlayerPrefabEntity;
public class Baker : Baker<SceneInitializationAuthoring>
{
public override void Bake(SceneInitializationAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.None);
AddComponent(entity, new SceneInitialization
{
CharacterSpawnPointEntity = GetEntity(authoring.CharacterSpawnPointEntity, TransformUsageFlags.Dynamic),
CharacterPrefabEntity = GetEntity(authoring.CharacterPrefabEntity, TransformUsageFlags.Dynamic),
CameraPrefabEntity = GetEntity(authoring.CameraPrefabEntity, TransformUsageFlags.Dynamic),
PlayerPrefabEntity = GetEntity(authoring.PlayerPrefabEntity, TransformUsageFlags.None),
});
}
}
}
Basic/Assets/Scripts/Misc/SceneInitializationSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using Unity.Physics.GraphicsIntegration;
using Unity.Physics.Systems;
using Unity.Transforms;
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 character at spawn point


Entity characterEntity = state.EntityManager.Instantiate(sceneInitializer.CharacterPrefabEntity);
SystemAPI.SetComponent(characterEntity, SystemAPI.GetComponent<LocalTransform>(sceneInitializer.CharacterSpawnPointEntity));

// 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;

public class SelfDestructAfterTimeAuthoring : MonoBehaviour


{
public float LifeTime = 1f;
public class Baker : Baker<SelfDestructAfterTimeAuthoring>
{
public override void Bake(SelfDestructAfterTimeAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.None);
AddComponent(entity, new SelfDestructAfterTime
{
LifeTime = authoring.LifeTime,
});
}
}
}
Basic/Assets/Scripts/Misc/SelfDestructAfterTimeSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;

[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;

void Execute(Entity entity, ref SelfDestructAfterTime selfDestructAfterTime)


{
selfDestructAfterTime.TimeSinceAlive += DeltaTime;
if (selfDestructAfterTime.TimeSinceAlive > selfDestructAfterTime.LifeTime)
{
ECB.DestroyEntity(entity);
}
}
}
}
Basic/Assets/Scripts/Misc/Teleporter.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;

[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;

public class Baker : Baker<TeleporterAuthoring>


{
public override void Bake(TeleporterAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new Teleporter { DestinationEntity = GetEntity(authoring.Destination, TransformUsageFlags.Dynamic) });
}
}
}
Basic/Assets/Scripts/Misc/TeleporterSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using Unity.Physics;
using Unity.Physics.Systems;
using Unity.CharacterController;
using Unity.Physics.Stateful;
[UpdateInGroup(typeof(AfterPhysicsSystemGroup))]
[BurstCompile]
public partial struct TeleporterSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
}

[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;

void Execute(Entity entity, in Teleporter teleporter, in DynamicBuffer<StatefulTriggerEvent> triggerEventsBuffer)


{
// Only teleport if there is a destination
if (teleporter.DestinationEntity != Entity.Null)
{
for (int i = 0; i < triggerEventsBuffer.Length; i++)
{
StatefulTriggerEvent triggerEvent = triggerEventsBuffer[i];
Entity otherEntity = triggerEvent.GetOtherEntity(entity);
// 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;
}
}
}
}
}
}
}
Basic/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;
}

public AuthoringData Data;


public float3 OriginalPosition;
public quaternion OriginalRotation;
}
Basic/Assets/Scripts/Misc/TestMovingPlatformAuthoring.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

[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;

public float WheelFriction;


public float WheelRollResistance;
public float RotationDamping;

public float WheelSuspensionDistance;


public float WheelSuspensionStrength;
}

public struct VehicleWheels : IBufferElementData


{
public Entity MeshEntity;
public Entity CollisionEntity;
}
Basic/Assets/Scripts/Misc/VehicleAuthoring.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using Unity.Physics;
using Unity.Physics.Authoring;
using System.Collections.Generic;

[DisallowMultipleComponent]
public class VehicleAuthoring : MonoBehaviour
{
public Vehicle Vehicle;
public List<GameObject> Wheels;

public class Baker : Baker<VehicleAuthoring>


{
public override void Bake(VehicleAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);

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;

public RigidTransform worldFromA => LocalBody == null


? RigidTransform.identity
: Math.DecomposeRigidBodyTransform(LocalBody.transform.localToWorldMatrix);

public RigidTransform worldFromB => ConnectedBody == null


? RigidTransform.identity
: Math.DecomposeRigidBodyTransform(ConnectedBody.transform.localToWorldMatrix);

public Entity EntityA { get; set; }

public Entity EntityB { get; set; }

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);

public override int GetHashCode() => Value;


}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/EditorInitialization.cs
#if UNITY_EDITOR
using System.Linq;
using UnityEditor;
namespace Unity.Physics.Authoring
{
[InitializeOnLoad]
class EditorInitialization
{
static readonly string k_CustomDefine = "UNITY_PHYSICS_CUSTOM";
static EditorInitialization()
{
var definesStr = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
var defines = definesStr.Split(';').ToList();
var found = defines.Find(define => define.Equals(k_CustomDefine));
if (found == null)
{
defines.Add(k_CustomDefine);
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup,
string.Join(";", defines.ToArray()));
}
}
}
}
#endif
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsCategoryNames.cs
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Physics Category Names", fileName = "Physics Category Names", order = 507)]
public sealed class PhysicsCategoryNames : ScriptableObject, ITagNames
{
PhysicsCategoryNames() {}

IReadOnlyList<string> ITagNames.TagNames => CategoryNames;

public IReadOnlyList<string> CategoryNames => m_CategoryNames;


[SerializeField]
string[] m_CategoryNames = Enumerable.Range(0, 32).Select(i => string.Empty).ToArray();

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);

public override int GetHashCode() => unchecked((int)Value);


}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsMaterialProperties.cs
using System;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
interface IPhysicsMaterialProperties
{
CollisionResponsePolicy CollisionResponse { get; set; }
PhysicsMaterialCoefficient Friction { get; set; }

PhysicsMaterialCoefficient Restitution { get; set; }


PhysicsCategoryTags BelongsTo { get; set; }
PhysicsCategoryTags CollidesWith { get; set; }
// TODO: Enable Mass Factors?
// TODO: Surface Velocity?
CustomPhysicsMaterialTags CustomTags { get; set; }
}

interface IInheritPhysicsMaterialProperties : IPhysicsMaterialProperties


{
PhysicsMaterialTemplate Template { get; set; }
bool OverrideCollisionResponse { get; set; }
bool OverrideFriction { get; set; }
bool OverrideRestitution { get; set; }
bool OverrideBelongsTo { get; set; }
bool OverrideCollidesWith { get; set; }
bool OverrideCustomTags { get; set; }
}

[Serializable]
public struct PhysicsMaterialCoefficient
{
[SoftRange(0f, 1f, TextFieldMax = float.MaxValue)]
public float Value;
public Material.CombinePolicy CombineMode;
}

abstract class OverridableValue<T> where T : struct


{
public bool Override { get => m_Override; set => m_Override = value; }
[SerializeField]
bool m_Override;

public T Value
{
get => m_Value;
set
{
m_Value = value;
Override = true;
}
}
[SerializeField]
T m_Value;

public void OnValidate() => OnValidate(ref m_Value);


protected virtual void OnValidate(ref T 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 PhysicsMaterialTemplate Template


{
get => m_Template;
set => m_Template = m_SupportsTemplate ? value : null;
}
[SerializeField]
[Tooltip("Assign a template to use its values.")]
PhysicsMaterialTemplate m_Template;

static T Get<T>(OverridableValue<T> value, T? templateValue) where T : struct =>


value.Override || templateValue == null ? value.Value : templateValue.Value;

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 };

internal static void OnValidate(ref PhysicsMaterialProperties material, bool supportsTemplate)


{
material.UpgradeVersionIfNecessary();

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();
}

const int k_LatestVersion = 1;

[SerializeField]
int m_SerializedVersion = 0;

void ISerializationCallbackReceiver.OnBeforeSerialize() {}
void ISerializationCallbackReceiver.OnAfterDeserialize() => UpgradeVersionIfNecessary();

internal static bool s_SuppressUpgradeWarnings;

#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


}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsMaterialTemplate.cs
using UnityEngine;

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);

void Reset() => OnValidate();


void OnValidate() => PhysicsMaterialProperties.OnValidate(ref m_Value, false);
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsRenderEntityAuthoring.cs
using UnityEngine;
using Unity.Entities;
using Unity.Physics.GraphicsIntegration;
namespace Unity.Physics.Authoring
{
[AddComponentMenu("Entities/Physics/Physics Render Entity")]
[DisallowMultipleComponent]
public sealed class PhysicsRenderEntityAuthoring : MonoBehaviour
{
[Tooltip("Specifies an Entity in a different branch of the hierarchy that holds the graphical representation of this PhysicsShape.")]
public GameObject RenderEntity;
}
internal class PhysicsRenderEntityBaker : Baker<PhysicsRenderEntityAuthoring>
{
public override void Bake(PhysicsRenderEntityAuthoring authoring)
{
var renderEntity = new PhysicsRenderEntity { Entity = GetEntity(authoring.RenderEntity, TransformUsageFlags.Dynamic) };
var entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, renderEntity);
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/PhysicsBodyAuthoring.cs
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
/// <summary> The physics body authoring. This class cannot be inherited. </summary>
#if UNITY_2021_2_OR_NEWER
[Icon(k_IconPath)]
#endif
[AddComponentMenu("Entities/Physics/Physics Body")]
[DisallowMultipleComponent]
public sealed class PhysicsBodyAuthoring : MonoBehaviour
{
const string k_IconPath = "Packages/com.unity.physics/Unity.Physics.Editor/Editor Default Resources/Icons/d_Rigidbody@64.png";

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;
}

static readonly int[] k_NextAxis = { 1, 2, 0 };


public ShapeType ShapeType => m_ShapeType;
[SerializeField]
ShapeType m_ShapeType = ShapeType.Box;
[SerializeField]
float3 m_PrimitiveCenter;
[SerializeField]
float3 m_PrimitiveSize = new float3(1f, 1f, 1f);
[SerializeField]
EulerAngles m_PrimitiveOrientation = EulerAngles.Default;

[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;
}

public PhysicsCategoryTags BelongsTo


{
get => m_Material.BelongsTo;
set => m_Material.BelongsTo = value;
}
public bool OverrideCollidesWith
{
get => m_Material.OverrideCollidesWith;
set => m_Material.OverrideCollidesWith = value;
}
public PhysicsCategoryTags CollidesWith
{
get => m_Material.CollidesWith;
set => m_Material.CollidesWith = value;
}
public bool OverrideCustomTags
{
get => m_Material.OverrideCustomTags;
set => m_Material.OverrideCustomTags = value;
}
public CustomPhysicsMaterialTags CustomTags { get => m_Material.CustomTags; set => m_Material.CustomTags = value; }
[SerializeField]
PhysicsMaterialProperties m_Material = new PhysicsMaterialProperties(true);

public BoxGeometry GetBoxProperties() => GetBoxProperties(out _);


internal BoxGeometry GetBoxProperties(out EulerAngles orientation)
{
orientation = m_PrimitiveOrientation;
return new BoxGeometry
{
Center = m_PrimitiveCenter,
Size = m_PrimitiveSize,
Orientation = m_PrimitiveOrientation,
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius
};
}
void GetCylindricalProperties(
CylindricalProperties props,
out float3 center, out float height, out float radius, out EulerAngles orientation,
bool rebuildOrientation
)
{
center = m_PrimitiveCenter;
var lookVector = math.mul(m_PrimitiveOrientation, new float3 { [props.Axis] = 1f });
// use previous axis so forward will prefer up
var upVector = math.mul(m_PrimitiveOrientation, new float3 { [k_NextAxis[k_NextAxis[props.Axis]]] = 1f });
orientation = m_PrimitiveOrientation;
if (rebuildOrientation && props.Axis != 2)
orientation.SetValue(quaternion.LookRotation(lookVector, upVector));
radius = props.Radius;
height = props.Height;
}
internal CapsuleGeometryAuthoring GetCapsuleProperties()
{
GetCylindricalProperties(
m_Capsule, out var center, out var height, out var radius, out var orientationEuler, m_ShapeType != ShapeType.Capsule
);
return new CapsuleGeometryAuthoring
{
OrientationEuler = orientationEuler,
Center = center,
Height = height,
Radius = radius
};
}
public CylinderGeometry GetCylinderProperties() => GetCylinderProperties(out _);
internal CylinderGeometry GetCylinderProperties(out EulerAngles orientation)
{
GetCylindricalProperties(
m_Cylinder, out var center, out var height, out var radius, out orientation, m_ShapeType != ShapeType.Cylinder
);
return new CylinderGeometry
{
Center = center,
Height = height,
Radius = radius,
Orientation = orientation,
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius,
SideCount = m_CylinderSideCount
};
}
public SphereGeometry GetSphereProperties(out quaternion orientation)
{
var result = GetSphereProperties(out EulerAngles euler);
orientation = euler;
return result;
}
internal SphereGeometry GetSphereProperties(out EulerAngles orientation)
{
orientation = m_PrimitiveOrientation;
return new SphereGeometry
{
Center = m_PrimitiveCenter,
Radius = m_SphereRadius
};
}
public void GetPlaneProperties(out float3 center, out float2 size, out quaternion orientation)
{
GetPlaneProperties(out center, out size, out EulerAngles euler);
orientation = euler;
}
internal void GetPlaneProperties(out float3 center, out float2 size, out EulerAngles orientation)
{
center = m_PrimitiveCenter;
orientation = m_PrimitiveOrientation;
if (m_ShapeType == ShapeType.Plane)
{
size = m_PrimitiveSize.xz;
return;
}
UpdateCapsuleAxis();
var look = m_Capsule.Axis;
var nextAx = k_NextAxis[look];
var prevAx = k_NextAxis[k_NextAxis[look]];
var ax2 = m_PrimitiveSize[nextAx] > m_PrimitiveSize[prevAx] ? nextAx : prevAx;
size = new float2(m_PrimitiveSize[ax2], m_PrimitiveSize[look]);
var up = k_NextAxis[ax2] == look ? k_NextAxis[look] : k_NextAxis[ax2];
var offset = quaternion.LookRotation(new float3 { [look] = 1f }, new float3 { [up] = 1f });
orientation.SetValue(math.mul(m_PrimitiveOrientation, offset));
}
static readonly HashSet<int> s_BoneIDs = new HashSet<int>();
static readonly HashSet<Transform> s_BonesInHierarchy = new HashSet<Transform>();
static readonly List<Vector3> s_Vertices = new List<Vector3>(65535);
static readonly List<int> s_Indices = new List<int>(65535);
static UnityMesh ReusableBakeMesh =>
s_ReusableBakeMesh ??
(s_ReusableBakeMesh = new UnityMesh { hideFlags = HideFlags.HideAndDontSave });
static UnityMesh s_ReusableBakeMesh;
public void GetConvexHullProperties(NativeList<float3> pointCloud) =>
GetConvexHullProperties(pointCloud, true, default, default, default, default);
internal void GetConvexHullProperties(
NativeList<float3> pointCloud, bool validate,
NativeList<HashableShapeInputs> inputs, NativeList<int> allSkinIndices, NativeList<float> allBlendShapeWeights,
HashSet<UnityMesh> meshAssets
)
{
if (pointCloud.IsCreated)
pointCloud.Clear();
if (inputs.IsCreated)
inputs.Clear();
if (allSkinIndices.IsCreated)
allSkinIndices.Clear();
if (allBlendShapeWeights.IsCreated)
allBlendShapeWeights.Clear();
meshAssets?.Clear();
if (m_CustomMesh != null)
{
if (validate && !m_CustomMesh.IsValidForConversion(gameObject))
return;
AppendMeshPropertiesToNativeBuffers(
transform.localToWorldMatrix, m_CustomMesh, pointCloud, default, validate, inputs, meshAssets
);
}
else
{
using (var scope = new GetActiveChildrenScope<MeshFilter>(this, transform))
{
foreach (var meshFilter in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(meshFilter, validate))
{
AppendMeshPropertiesToNativeBuffers(
meshFilter.transform.localToWorldMatrix, meshFilter.sharedMesh, pointCloud, default, validate, inputs, meshAssets
);
}
}
}

using (var skinnedPoints = new NativeList<float3>(8192, Allocator.Temp))


using (var skinnedInputs = new NativeList<HashableShapeInputs>(8, Allocator.Temp))
{
GetAllSkinnedPointsInHierarchyBelongingToShape(
this, skinnedPoints, validate, skinnedInputs, allSkinIndices, allBlendShapeWeights
);
if (pointCloud.IsCreated)
pointCloud.AddRange(skinnedPoints.AsArray());
if (inputs.IsCreated)
inputs.AddRange(skinnedInputs.AsArray());
}
}
}

internal static void GetAllSkinnedPointsInHierarchyBelongingToShape(


PhysicsShapeAuthoring shape, NativeList<float3> pointCloud, bool validate,
NativeList<HashableShapeInputs> inputs, NativeList<int> allIncludedIndices, NativeList<float> allBlendShapeWeights
)
{
if (pointCloud.IsCreated)
pointCloud.Clear();
if (inputs.IsCreated)
inputs.Clear();
// get all the transforms that belong to this shape
s_BonesInHierarchy.Clear();
using (var scope = new GetActiveChildrenScope<Transform>(shape, shape.transform))
{
foreach (var bone in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(bone))
s_BonesInHierarchy.Add(bone);
}
}
// find all skinned mesh renderers in which this shape's transform might be a bone
using (var scope = new GetActiveChildrenScope<SkinnedMeshRenderer>(shape, shape.transform.root))
{
foreach (var skin in scope.Buffer)
{
var mesh = skin.sharedMesh;
if (
!skin.enabled
|| mesh == null
|| validate && !mesh.IsValidForConversion(shape.gameObject)
|| !scope.IsChildActiveAndBelongsToShape(skin)
)
continue;
// get indices of this shape's transform hierarchy in skinned mesh's bone array
s_BoneIDs.Clear();
var bones = skin.bones;
for (int i = 0, count = bones.Length; i < count; ++i)
{
if (s_BonesInHierarchy.Contains(bones[i]))
s_BoneIDs.Add(i);
}
if (s_BoneIDs.Count == 0)
continue;
// sample the vertices
if (pointCloud.IsCreated)
{
skin.BakeMesh(ReusableBakeMesh);
ReusableBakeMesh.GetVertices(s_Vertices);
}
// add all vertices weighted to at least one bone in this shape's transform hierarchy
var bonesPerVertex = mesh.GetBonesPerVertex(); // Allocator.None
var weights = mesh.GetAllBoneWeights(); // Allocator.None
var vertexIndex = 0;
var weightsOffset = 0;
var shapeFromSkin = math.mul(shape.transform.worldToLocalMatrix, skin.transform.localToWorldMatrix);
var includedIndices = new NativeList<int>(mesh.vertexCount, Allocator.Temp);
foreach (var weightCount in bonesPerVertex)
{
var totalWeight = 0f;
for (var i = 0; i < weightCount; ++i)
{
var weight = weights[weightsOffset + i];
if (s_BoneIDs.Contains(weight.boneIndex))
totalWeight += weight.weight;
}
if (totalWeight > shape.m_MinimumSkinnedVertexWeight)
{
if (pointCloud.IsCreated)
pointCloud.Add(math.mul(shapeFromSkin, new float4(s_Vertices[vertexIndex], 1f)).xyz);
includedIndices.Add(vertexIndex);
}
weightsOffset += weightCount;
++vertexIndex;
}
if (!inputs.IsCreated || !allIncludedIndices.IsCreated || !allBlendShapeWeights.IsCreated)
continue;
var blendShapeWeights = new NativeArray<float>(mesh.blendShapeCount, Allocator.Temp);
for (var i = 0; i < blendShapeWeights.Length; ++i)
blendShapeWeights[i] = skin.GetBlendShapeWeight(i);
var data = HashableShapeInputs.FromSkinnedMesh(
mesh, shapeFromSkin, includedIndices.AsArray(), allIncludedIndices, blendShapeWeights, allBlendShapeWeights
);
inputs.Add(data);
}
}
s_BonesInHierarchy.Clear();
}
public void GetMeshProperties(NativeList<float3> vertices, NativeList<int3> triangles) =>
GetMeshProperties(vertices, triangles, true, default);
internal void GetMeshProperties(
NativeList<float3> vertices, NativeList<int3> triangles, bool validate, NativeList<HashableShapeInputs> inputs, HashSet<UnityMesh> meshAssets = null
)
{
if (vertices.IsCreated)
vertices.Clear();
if (triangles.IsCreated)
triangles.Clear();
if (inputs.IsCreated)
inputs.Clear();
meshAssets?.Clear();
if (m_CustomMesh != null)
{
if (validate && !m_CustomMesh.IsValidForConversion(gameObject))
return;
AppendMeshPropertiesToNativeBuffers(
transform.localToWorldMatrix, m_CustomMesh, vertices, triangles, validate, inputs, meshAssets
);
}
else
{
using (var scope = new GetActiveChildrenScope<MeshFilter>(this, transform))
{
foreach (var meshFilter in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(meshFilter, validate))
{
AppendMeshPropertiesToNativeBuffers(
meshFilter.transform.localToWorldMatrix, meshFilter.sharedMesh, vertices, triangles, validate, inputs, meshAssets
);
}
}
}
}
}

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();

void Sync(ref CylindricalProperties props)


{
props.Height = m_PrimitiveSize[props.Axis];
props.Radius = 0.5f * math.max(
m_PrimitiveSize[k_NextAxis[props.Axis]],
m_PrimitiveSize[k_NextAxis[k_NextAxis[props.Axis]]]
);
}
void SyncCapsuleProperties()
{
if (m_ShapeType == ShapeType.Box || m_ShapeType == ShapeType.Plane)
UpdateCapsuleAxis();
else
m_Capsule.Axis = 2;
Sync(ref m_Capsule);
}
void SyncCylinderProperties()
{
if (m_ShapeType == ShapeType.Box || m_ShapeType == ShapeType.Plane)
UpdateCylinderAxis();
else
m_Cylinder.Axis = 2;
Sync(ref m_Cylinder);
}
void SyncSphereProperties()
{
m_SphereRadius = 0.5f * math.cmax(m_PrimitiveSize);
}
public void SetBox(BoxGeometry geometry)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(geometry.Orientation);
SetBox(geometry, euler);
}
internal void SetBox(BoxGeometry geometry, EulerAngles orientation)
{
m_ShapeType = ShapeType.Box;
m_PrimitiveCenter = geometry.Center;
m_PrimitiveSize = math.max(geometry.Size, new float3());
m_PrimitiveOrientation = orientation;
m_ConvexHullGenerationParameters.BevelRadius = geometry.BevelRadius;
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
internal void SetCapsule(CapsuleGeometryAuthoring geometry)
{
m_ShapeType = ShapeType.Capsule;
m_PrimitiveCenter = geometry.Center;
m_PrimitiveOrientation = geometry.OrientationEuler;
var radius = math.max(0f, geometry.Radius);
var height = math.max(0f, geometry.Height);
m_PrimitiveSize = new float3(radius * 2f, radius * 2f, height);
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetCylinder(CylinderGeometry geometry)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(geometry.Orientation);
SetCylinder(geometry, euler);
}
internal void SetCylinder(CylinderGeometry geometry, EulerAngles orientation)
{
m_ShapeType = ShapeType.Cylinder;
m_PrimitiveCenter = geometry.Center;
m_PrimitiveOrientation = orientation;
geometry.Radius = math.max(0f, geometry.Radius);
geometry.Height = math.max(0f, geometry.Height);
m_PrimitiveSize = new float3(geometry.Radius * 2f, geometry.Radius * 2f, geometry.Height);

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;
}

bool GetMeshes(PhysicsShapeAuthoring shape, out List<UnityEngine.Mesh> meshes, out List<float4x4> childrenToShape)


{
meshes = new List<UnityMesh>();
childrenToShape = new List<float4x4>();
if (shape.CustomMesh != null)
{
meshes.Add(shape.CustomMesh);
childrenToShape.Add(float4x4.identity);
}
// Try to get all the meshes in the children
var meshFilters = GetComponentsInChildren<MeshFilter>();
foreach (var meshFilter in meshFilters)
{
if (meshFilter != null && meshFilter.sharedMesh != null)
{
var shapeAuthoring = GetComponent<PhysicsShapeAuthoring>(meshFilter);
if (shapeAuthoring != null && shapeAuthoring != shape)
{
// Skip this case, since it will be treated independently
continue;
}
meshes.Add(meshFilter.sharedMesh);
// Don't calculate the children to shape if not needed, to avoid approximation that could prevent collider to be shared
if (shape.transform.localToWorldMatrix.Equals(meshFilter.transform.localToWorldMatrix))
childrenToShape.Add(float4x4.identity);
else
{
var transform = math.mul(shape.transform.worldToLocalMatrix, meshFilter.transform.localToWorldMatrix);
childrenToShape.Add(transform);
}
DependsOn(meshes.Last());
}
}
return meshes.Count > 0;
}
UnityEngine.Mesh CombineMeshes(PhysicsShapeAuthoring shape, List<UnityEngine.Mesh> meshes, List<float4x4> childrenToShape)
{
var instances = new List<CombineInstance>();
var numVertices = 0;
for (var i = 0; i < meshes.Count; ++i)
{
var currentMesh = meshes[i];
var currentChildToShape = childrenToShape[i];
if (!currentMesh.IsValidForConversion(shape.gameObject))
{
throw new InvalidOperationException(
$"Mesh '{currentMesh}' assigned on {shape.name} is not readable. Ensure that you have enabled Read/Write on its import settings."
);
}
// Combine submeshes manually
numVertices += meshes[i].vertexCount;
var combinedSubmeshes = new UnityEngine.Mesh();
combinedSubmeshes.vertices = currentMesh.vertices;
var combinedIndices = new List<int>();
for (int indexSubMesh = 0; indexSubMesh < meshes[i].subMeshCount; ++indexSubMesh)
{
combinedIndices.AddRange(currentMesh.GetIndices(indexSubMesh));
}
combinedSubmeshes.SetIndices(combinedIndices, MeshTopology.Triangles, 0);
combinedSubmeshes.RecalculateNormals();
var instance = new CombineInstance
{
mesh = combinedSubmeshes,
transform = currentChildToShape,
};
instances.Add(instance);
}
var mesh = new UnityEngine.Mesh();
mesh.indexFormat = numVertices > UInt16.MaxValue ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
mesh.CombineMeshes(instances.ToArray());
mesh.RecalculateBounds();
return mesh;
}
private ShapeComputationDataBaking GenerateComputationData(PhysicsShapeAuthoring shape, ColliderInstanceBaking colliderInstance, Entity colliderEntity)
{
var res = new ShapeComputationDataBaking
{
Instance = colliderInstance,
Material = ProduceMaterial(shape),
CollisionFilter = ProduceCollisionFilter(shape),
ForceUniqueIdentifier = shape.ForceUnique ? (uint)shape.GetInstanceID() : 0u
};
var transform = shape.transform;
var localToWorld = transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
EulerAngles orientation;
res.ShapeType = shape.ShapeType;
switch (shape.ShapeType)
{
case ShapeType.Box:
{
res.BoxProperties = shape.GetBoxProperties(out orientation)
.BakeToBodySpace(localToWorld, shapeToWorld, orientation);
break;
}
case ShapeType.Capsule:
{
res.CapsuleProperties = shape.GetCapsuleProperties()
.BakeToBodySpace(localToWorld, shapeToWorld)
.ToRuntime();
break;
}
case ShapeType.Sphere:
{
res.SphereProperties = shape.GetSphereProperties(out orientation)
.BakeToBodySpace(localToWorld, shapeToWorld, ref orientation);
break;
}
case ShapeType.Cylinder:
{
res.CylinderProperties = shape.GetCylinderProperties(out orientation)
.BakeToBodySpace(localToWorld, shapeToWorld, orientation);
break;
}
case ShapeType.Plane:
{
shape.GetPlaneProperties(out var center, out var size, out orientation);
PhysicsShapeExtensions.BakeToBodySpace(
center, size, orientation, localToWorld, shapeToWorld,
out res.PlaneVertices.c0, out res.PlaneVertices.c1, out res.PlaneVertices.c2, out res.PlaneVertices.c3
);
break;
}
case ShapeType.ConvexHull:
{
res.ConvexHullProperties.Filter = res.CollisionFilter;
res.ConvexHullProperties.Material = res.Material;
res.ConvexHullProperties.GenerationParameters = shape.ConvexHullGenerationParameters.ToRunTime();
CreateMeshAuthoringData(shape, colliderEntity);
break;
}
case ShapeType.Mesh:
{
res.MeshProperties.Filter = res.CollisionFilter;
res.MeshProperties.Material = res.Material;
CreateMeshAuthoringData(shape, colliderEntity);
break;
}
}
return res;
}
private void CreateMeshAuthoringData(PhysicsShapeAuthoring shape, Entity colliderEntity)
{
if (GetMeshes(shape, out var meshes, out var childrenToShape))
{
// Combine all detected meshes into a single one
var mesh = CombineMeshes(shape, meshes, childrenToShape);
if (!mesh.IsValidForConversion(shape.gameObject))
{
throw new InvalidOperationException(
$"Mesh '{mesh}' assigned on {shape.name} is not readable. Ensure that you have enabled Read/Write on its import settings."
);
}
var bakeFromShape = shape.GetLocalToShapeMatrix();
var meshBakingData = new PhysicsMeshAuthoringData()
{
Convex = shape.ShapeType == ShapeType.ConvexHull,
Mesh = mesh,
BakeFromShape = bakeFromShape,
MeshBounds = mesh.bounds,
ChildToShape = float4x4.identity
};
AddComponent(colliderEntity, meshBakingData);
}
else
{
throw new InvalidOperationException(
$"No {nameof(PhysicsShapeAuthoring.CustomMesh)} or {nameof(MeshFilter.sharedMesh)} assigned on {shape.name}."
);
}
}
public override void Bake(PhysicsShapeAuthoring authoring)
{
var shapeBakingData = new PhysicsColliderAuthoringData();
// First pass
Profiler.BeginSample("Collect Inputs from Authoring Components");
if (ShouldConvertShape(authoring))
{
// We can have multiple Colliders of the same type on the same game object, so instead of adding the components to the baking entity
// we add the components to an additional entity. These new entities will be processed by the baking system
var colliderEntity = CreateAdditionalEntity(TransformUsageFlags.None, true);
shapeBakingData.ShapeComputationalData = GetInputDataFromAuthoringComponent(authoring, colliderEntity);
AddComponent(colliderEntity, shapeBakingData);
// The data will be filled in by the BaseShapeBakingSystem, but we add it here so it gets reverted from the entity if the collider component is deleted
AddComponent(colliderEntity, new PhysicsColliderBakedData()
{
BodyEntity = shapeBakingData.ShapeComputationalData.Instance.BodyEntity,
BodyFromShape = shapeBakingData.ShapeComputationalData.Instance.BodyFromShape,
ChildEntity = shapeBakingData.ShapeComputationalData.Instance.ChildEntity,
// It is a leaf if the Shape Entity equals Body Entity
IsLeafEntityBody = (shapeBakingData.ShapeComputationalData.Instance.ShapeEntity.Equals(shapeBakingData.ShapeComputationalData.Instance.BodyEntity))
});
}
Profiler.EndSample();
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/BallAndSocketJoint.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

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;

public float3 PositionLocal;


public float3 PositionInConnectedEntity;

public virtual void UpdateAuto()


{
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
PositionInConnectedEntity = math.transform(bFromA, PositionLocal);
}
}
}
public abstract class JointBaker<T> : Baker<T> where T : BaseJoint
{
protected PhysicsConstrainedBodyPair GetConstrainedBodyPair(BaseJoint authoring)
{
return new PhysicsConstrainedBodyPair(
GetEntity(TransformUsageFlags.Dynamic),
authoring.ConnectedBody == null ? Entity.Null : GetEntity(authoring.ConnectedBody, TransformUsageFlags.Dynamic),
authoring.EnableCollision
);
}
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 uint GetWorldIndex(Component c)


{
uint worldIndex = 0;
if (c)
{
var physicsBody = GetComponent<PhysicsBodyAuthoring>(c);
if (physicsBody != null)
{
worldIndex = physicsBody.WorldIndex;
}
}
return worldIndex;
}
public uint GetWorldIndexFromBaseJoint(BaseJoint authoring)
{
var physicsBody = GetComponent<PhysicsBodyAuthoring>(authoring);
uint worldIndex = physicsBody.WorldIndex;
if (authoring.ConnectedBody == null)
{
return worldIndex;
}
var connectedBody = GetComponent<PhysicsBodyAuthoring>(authoring.ConnectedBody);
if (connectedBody != null)
{
Assertions.Assert.AreEqual(worldIndex, connectedBody.WorldIndex);
}
return worldIndex;
}
public void CreateJointEntities(uint worldIndex, PhysicsConstrainedBodyPair constrainedBodyPair, NativeArray<PhysicsJoint> joints, NativeList<Entity> newJointEntities)
{
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;
var entity = GetEntity(TransformUsageFlags.Dynamic);
for (var i = 0; i < joints.Length; ++i)
{
var jointEntity = CreateAdditionalEntity(TransformUsageFlags.Dynamic);
AddSharedComponent(jointEntity, new PhysicsWorldIndex(worldIndex));
AddComponent(jointEntity, constrainedBodyPair);
AddComponent(jointEntity, joints[i]);
newJointEntities.Add(jointEntity);
if (GetComponent<ModifyJointLimitsAuthoring>() != null)
{
AddComponent(jointEntity, new JointEntityBaking()
{
Entity = entity
});
AddSharedComponentManaged(jointEntity, new ModifyJointLimits());
}
}
if (multipleJoints)
{
// set companion buffers for new joints
for (var i = 0; i < joints.Length; ++i)
{
var companions = AddBuffer<PhysicsJointCompanion>(newJointEntities[i]);
for (var j = 0; j < joints.Length; ++j)
{
if (i == j)
continue;
companions.Add(new PhysicsJointCompanion {JointEntity = newJointEntities[j]});
}
}
}
}
}
class BallAndSocketJointBaker : JointBaker<BallAndSocketJoint>
{
public override void Bake(BallAndSocketJoint authoring)
{
authoring.UpdateAuto();
var physicsJoint = PhysicsJoint.CreateBallAndSocket(authoring.PositionLocal, authoring.PositionInConnectedEntity);
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/BaseJoint.cs
using Unity.Mathematics;

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;

for (var i = 0; i < joints.Length; ++i)


{
var jointEntity = CreateAdditionalEntity(TransformUsageFlags.Dynamic);
AddSharedComponent(jointEntity, new PhysicsWorldIndex(worldIndex));
AddComponent(jointEntity, constrainedBodyPair);
AddComponent(jointEntity, joints[i]);
newJointEntities.Add(jointEntity);
}
if (multipleJoints)
{
// set companion buffers for new joints
for (var i = 0; i < joints.Length; ++i)
{
var companions = AddBuffer<PhysicsJointCompanion>(newJointEntities[i]);
for (var j = 0; j < joints.Length; ++j)
{
if (i == j)
continue;
companions.Add(new PhysicsJointCompanion {JointEntity = newJointEntities[j]});
}
}
}
}
protected PhysicsConstrainedBodyPair GetConstrainedBodyPair(LimitDOFJoint authoring)
{
return new PhysicsConstrainedBodyPair(
GetEntity(TransformUsageFlags.Dynamic),
authoring.ConnectedBody == null ? Entity.Null : GetEntity(authoring.ConnectedBody, TransformUsageFlags.Dynamic),
authoring.EnableCollision
);
}
public uint GetWorldIndex(Component c)
{
uint worldIndex = 0;
var physicsBody = GetComponent<PhysicsBodyAuthoring>(c);
if (physicsBody != null)
{
worldIndex = physicsBody.WorldIndex;
}
return worldIndex;
}
public override void Bake(LimitDOFJoint authoring)
{
if (!math.any(authoring.LockLinearAxes) && !math.any(authoring.LockAngularAxes))
return;
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
PhysicsJoint physicsJoint = authoring.CreateLimitDOFJoint(bFromA);
var worldIndex = GetWorldIndex(authoring);
CreateJointEntity(
worldIndex,
GetConstrainedBodyPair(authoring),
physicsJoint
);
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/LimitedDistanceJoint.cs
using Unity.Entities;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class LimitedDistanceJoint : BallAndSocketJoint
{
public float MinDistance;
public float MaxDistance;
}
class LimitedDistanceJointBaker : JointBaker<LimitedDistanceJoint>
{
public override void Bake(LimitedDistanceJoint authoring)
{
authoring.UpdateAuto();
var physicsJoint = PhysicsJoint.CreateLimitedDistance(authoring.PositionLocal, authoring.PositionInConnectedEntity, new FloatRange(authoring.MinDistance, authoring.MaxDistance
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/LimitedHingeJoint.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class LimitedHingeJoint : FreeHingeJoint
{
// Editor only settings
[HideInInspector]
public bool EditLimits;

public float3 PerpendicularAxisLocal;


public float3 PerpendicularAxisInConnectedEntity;
public float MinAngle;
public float MaxAngle;

public override void UpdateAuto()


{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
HingeAxisInConnectedEntity = math.mul(bFromA.rot, HingeAxisLocal);
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
}
}
}

class LimitedHingeJointBaker : JointBaker<LimitedHingeJoint>


{
public override void Bake(LimitedHingeJoint authoring)
{
authoring.UpdateAuto();

var physicsJoint = PhysicsJoint.CreateLimitedHinge(


new BodyFrame
{
Axis = math.normalize(authoring.HingeAxisLocal),
PerpendicularAxis = math.normalize(authoring.PerpendicularAxisLocal),
Position = authoring.PositionLocal
},
new BodyFrame
{
Axis = math.normalize(authoring.HingeAxisInConnectedEntity),
PerpendicularAxis = math.normalize(authoring.PerpendicularAxisInConnectedEntity),
Position = authoring.PositionInConnectedEntity
},
math.radians(new FloatRange(authoring.MinAngle, authoring.MaxAngle))
);

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/ModifyJointLimitsAuthoring.cs
using System;
using System.Collections.Generic;
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 UnityEngine;
using LegacyJoint = UnityEngine.Joint;
using FloatRange = Unity.Physics.Math.FloatRange;

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);

public override int GetHashCode() =>


unchecked((AngularRangeScalar.GetHashCode() * 397) ^ LinearRangeScalar.GetHashCode());
}

// an authoring component to add to a GameObject with one or more Joint


public class ModifyJointLimitsAuthoring : MonoBehaviour
{
public ParticleSystem.MinMaxCurve AngularRangeScalar = new ParticleSystem.MinMaxCurve(
1f,
min: new AnimationCurve(
new Keyframe(0f, 0f, 0f, 0f),
new Keyframe(2f, -2f, 0f, 0f),
new Keyframe(4f, 0f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
},
max: new AnimationCurve(
new Keyframe(0f, 1f, 0f, 0f),
new Keyframe(2f, -1f, 0f, 0f),
new Keyframe(4f, 1f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
}
);
public ParticleSystem.MinMaxCurve LinearRangeScalar = new ParticleSystem.MinMaxCurve(
1f,
min: new AnimationCurve(
new Keyframe(0f, 1f, 0f, 0f),
new Keyframe(2f, 0.5f, 0f, 0f),
new Keyframe(4f, 1f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
},
max: new AnimationCurve(
new Keyframe(0f, 0.5f, 0f, 0f),
new Keyframe(2f, 0f, 0f, 0f),
new Keyframe(4f, 0.5f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
}
);
}
[BakingType]
public class ModifyJointLimitsBakingData : IComponentData
{
public ParticleSystem.MinMaxCurve AngularRangeScalar;
public ParticleSystem.MinMaxCurve LinearRangeScalar;
}
class ModifyJointLimitsBaker : Baker<ModifyJointLimitsAuthoring>
{
public override void Bake(ModifyJointLimitsAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponentObject(entity, new ModifyJointLimitsBakingData
{
AngularRangeScalar = authoring.AngularRangeScalar,
LinearRangeScalar = authoring.LinearRangeScalar
});
}
}
// after joints have been converted, find the entities they produced and add ModifyJointLimits to them
[UpdateAfter(typeof(EndJointBakingSystem))]
[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
partial struct ModifyJointLimitsBakingSystem : ISystem
{
private EntityQuery _ModifyJointLimitsBakingDataQuery;
private EntityQuery _JointEntityBakingQuery;
public void OnCreate(ref SystemState state)
{
_ModifyJointLimitsBakingDataQuery = state.GetEntityQuery(new EntityQueryDesc
{
All = new[] {ComponentType.ReadOnly<ModifyJointLimitsBakingData>()},
Options = EntityQueryOptions.IncludeDisabledEntities | EntityQueryOptions.IncludePrefab
});
_JointEntityBakingQuery = state.GetEntityQuery(new EntityQueryDesc
{
All = new[] {ComponentType.ReadOnly<JointEntityBaking>()}
});

_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;

public override void UpdateAuto()


{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
AxisInConnectedEntity = math.mul(bFromA.rot, AxisLocal);
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
}
}
}

class PrismaticJointBaker : JointBaker<PrismaticJoint>


{
public override void Bake(PrismaticJoint authoring)
{
authoring.UpdateAuto();

var physicsJoint = PhysicsJoint.CreatePrismatic(


new BodyFrame
{
Axis = authoring.AxisLocal,
PerpendicularAxis = authoring.PerpendicularAxisLocal,
Position = authoring.PositionLocal
},
new BodyFrame
{
Axis = authoring.AxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
new FloatRange(authoring.MinDistanceOnAxis, authoring.MaxDistanceOnAxis)
);
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/RagdollJoint.cs
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class RagdollJoint : BallAndSocketJoint
{
const int k_LatestVersion = 1;
// Editor only settings
[HideInInspector]
public bool EditAxes;
[HideInInspector]
public bool EditLimits;

[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;

public override void UpdateAuto()


{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
OrientationInConnectedEntity = math.mul(bFromA.rot, OrientationLocal);
}
{
OrientationLocal = math.normalize(OrientationLocal);
OrientationInConnectedEntity = math.normalize(OrientationInConnectedEntity);
}
}
}

class RigidJointBaker : JointBaker<RigidJoint>


{
public override void Bake(RigidJoint authoring)
{
authoring.UpdateAuto();

var physicsJoint = PhysicsJoint.CreateFixed(


new RigidTransform(authoring.OrientationLocal, authoring.PositionLocal),
new RigidTransform(authoring.OrientationInConnectedEntity, authoring.PositionInConnectedEntity)
);

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/Motors/AngularVelocityMotor.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

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

var joint = PhysicsJoint.CreateAngularVelocityMotor(


new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularLocal,
Position = authoring.PivotPosition
},
new BodyFrame
{
Axis = authoring.HingeAxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
math.radians(authoring.TargetSpeed),
authoring.MaxImpulseAppliedByMotor
);
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/LinearVelocityMotor.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
public class LinearVelocityMotor : BaseJoint
{
[Tooltip("An offset from the center of the body with the motor (bodyA), 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 at this speed from the initial position of bodyA, along the Direction of Movement, in m/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 AxisInConnectedEntity;
private float3 PerpendicularAxisInConnectedEntity;
class LinearVelocityMotorBaker : JointBaker<LinearVelocityMotor>
{
public override void Bake(LinearVelocityMotor authoring)
{
float3 axisInB = math.normalize(authoring.DirectionOfMovement);

RigidTransform aFromB = math.mul(math.inverse(authoring.worldFromA), authoring.worldFromB);


float3 axisInA = math.mul(aFromB.rot, axisInB); //motor axis relative to bodyA
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.AnchorPosition); //position of motored body relative to Connected Entity in world space
authoring.AxisInConnectedEntity = axisInB; //motor axis in Connected Entity space

// Always calculate the perpendicular axes


Math.CalculatePerpendicularNormalized(axisInA, out var perpendicularAxisLocal, out _);
authoring.PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, perpendicularAxisLocal); //perp motor axis in Connected Entity space
var joint = PhysicsJoint.CreateLinearVelocityMotor(
new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularAxisLocal,
Position = authoring.AnchorPosition
},
new BodyFrame
{
Axis = authoring.AxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
authoring.TargetSpeed,
authoring.MaxImpulseAppliedByMotor
);
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/PositionMotor.cs
using Unity.Mathematics;
using UnityEngine;

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;

class PositionMotorBaker : JointBaker<PositionMotor>


{
public override void Bake(PositionMotor authoring)
{
float3 axisInB = math.normalize(authoring.DirectionOfMovement);
RigidTransform aFromB = math.mul(math.inverse(authoring.worldFromA), authoring.worldFromB);
float3 axisInA = math.mul(aFromB.rot, axisInB); //motor axis relative to bodyA
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.AnchorPosition); //position of motored body relative to Connected Entity in world space
authoring.AxisInConnectedEntity = axisInB; //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
var joint = PhysicsJoint.CreatePositionMotor(
new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularLocal,
Position = authoring.AnchorPosition
},
new BodyFrame
{
Axis = authoring.AxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
authoring.TargetDistance,
authoring.MaxImpulseAppliedByMotor
);

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

var joint = PhysicsJoint.CreateRotationalMotor(


new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularLocal,
Position = authoring.PivotPosition
},
new BodyFrame
{
Axis = authoring.HingeAxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
math.radians(authoring.TargetAngle),
authoring.MaxImpulseAppliedByMotor
);
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/Utilities/BakeGeometryJobs.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
static partial class PhysicsShapeExtensions
{
static void MakeZAxisPrimaryBasis(ref int3 basisPriority)
{
if (basisPriority[1] == 2)
basisPriority = basisPriority.yxz;
else if (basisPriority[2] == 2)
basisPriority = basisPriority.zxy;
}
#region Box
[BurstCompile]
internal struct BakeBoxJob : IJob
{
public NativeArray<BoxGeometry> Box;

// TODO: make members PascalCase after merging static query fixes


public float4x4 localToWorld;
public float4x4 shapeToWorld;
public EulerAngles orientation;
public static float4x4 GetBakeToShape(float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center,
ref EulerAngles orientation)
{
float4x4 bakeToShape;
float4x4 rotationMatrix = float4x4.identity;
var basisPriority = k_DefaultAxisPriority;
var sheared = localToWorld.HasShear();
if (localToWorld.HasNonUniformScale() || sheared)
{
if (sheared)
{
var transformScale = localToWorld.DecomposeScale();
var basisToWorld =
GetBasisToWorldMatrix(localToWorld, center, orientation, transformScale);
basisPriority = GetBasisAxisPriority(basisToWorld);
}
rotationMatrix = new float4x4(
new float4 { [basisPriority[2]] = 1 },
new float4 { [basisPriority[1]] = 1 },
new float4 { [basisPriority[0]] = 1 },
new float4 { [3] = 1 }
);
}
bakeToShape = GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center,
ref orientation, 1f, basisPriority);
bakeToShape = math.mul(bakeToShape, rotationMatrix);
return bakeToShape;
}
public void Execute()
{
var center = Box[0].Center;
var size = Box[0].Size;
var bevelRadius = Box[0].BevelRadius;
var bakeToShape = GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientation);
bakeToShape = math.mul(bakeToShape, float4x4.Scale(size));
var scale = bakeToShape.DecomposeScale();
size = scale;
Box[0] = new BoxGeometry
{
Center = center,
Orientation = orientation,
Size = size,
BevelRadius = math.clamp(bevelRadius, 0f, 0.5f * math.cmin(size))
};
}
}
#endregion
#region Capsule
[BurstCompile]
internal struct BakeCapsuleJob : IJob
{
public NativeArray<CapsuleGeometryAuthoring> Capsule;
// TODO: make members PascalCase after merging static query fixes
public float4x4 localToWorld;
public float4x4 shapeToWorld;
public static float4x4 GetBakeToShape(float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center,
ref EulerAngles orientation)
{
var basisPriority = k_DefaultAxisPriority;
var sheared = localToWorld.HasShear();
if (localToWorld.HasNonUniformScale() || sheared)
{
if (sheared)
{
var transformScale = localToWorld.DecomposeScale();
var basisToWorld = GetBasisToWorldMatrix(localToWorld, center, orientation, transformScale);
basisPriority = GetBasisAxisPriority(basisToWorld);
}
MakeZAxisPrimaryBasis(ref basisPriority);
}
return GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center, ref orientation, 1f,
basisPriority);
}
public void Execute()
{
var radius = Capsule[0].Radius;
var center = Capsule[0].Center;
var height = Capsule[0].Height;
var orientationEuler = Capsule[0].OrientationEuler;
var bakeToShape = GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientationEuler);
var scale = bakeToShape.DecomposeScale();
radius *= math.cmax(scale.xy);
height = math.max(0, height * scale.z);
Capsule[0] = new CapsuleGeometryAuthoring
{
OrientationEuler = orientationEuler,
Center = center,
Height = height,
Radius = radius
};
}
}
#endregion
#region Cylinder
[BurstCompile]
internal struct BakeCylinderJob : IJob
{
public NativeArray<CylinderGeometry> Cylinder;
// TODO: make members PascalCase after merging static query fixes
public float4x4 localToWorld;
public float4x4 shapeToWorld;
public EulerAngles orientation;
public static float4x4 GetBakeToShape(float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center,
ref EulerAngles orientation)
{
var basisPriority = k_DefaultAxisPriority;
var sheared = localToWorld.HasShear();
if (localToWorld.HasNonUniformScale() || sheared)
{
if (sheared)
{
var transformScale = localToWorld.DecomposeScale();
var basisToWorld = GetBasisToWorldMatrix(localToWorld, center, orientation, transformScale);
basisPriority = GetBasisAxisPriority(basisToWorld);
}
MakeZAxisPrimaryBasis(ref basisPriority);
}
return GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center, ref orientation, 1f,
basisPriority);
}
public void Execute()
{
var center = Cylinder[0].Center;
var height = Cylinder[0].Height;
var radius = Cylinder[0].Radius;
var bevelRadius = Cylinder[0].BevelRadius;
var bakeToShape = GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientation);
var scale = bakeToShape.DecomposeScale();
height *= scale.z;
radius *= math.cmax(scale.xy);
Cylinder[0] = new CylinderGeometry
{
Center = center,
Orientation = orientation,
Height = height,
Radius = radius,
BevelRadius = math.min(bevelRadius, math.min(height * 0.5f, radius)),
SideCount = Cylinder[0].SideCount
};
}
}
internal static CylinderGeometry BakeToBodySpace(
this CylinderGeometry cylinder, float4x4 localToWorld, float4x4 shapeToWorld, EulerAngles orientation
)
{
using (var geometry = new NativeArray<CylinderGeometry>(1, Allocator.TempJob) { [0] = cylinder })
{
var job = new BakeCylinderJob
{
Cylinder = geometry,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld,
orientation = orientation
};
job.Run();
return geometry[0];
}
}
#endregion

#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;

public void Execute()


{
var v = Vertices[0];
GetPlanePoints(center, size, orientation, out v.c0, out v.c1, out v.c2, out v.c3);
var localToShape = math.mul(math.inverse(shapeToWorld), localToWorld);
v.c0 = math.mul(localToShape, new float4(v.c0, 1f)).xyz;
v.c1 = math.mul(localToShape, new float4(v.c1, 1f)).xyz;
v.c2 = math.mul(localToShape, new float4(v.c2, 1f)).xyz;
v.c3 = math.mul(localToShape, new float4(v.c3, 1f)).xyz;
Vertices[0] = v;
}
}

internal static void BakeToBodySpace(


float3 center, float2 size, EulerAngles orientation, float4x4 localToWorld, float4x4 shapeToWorld,
out float3 vertex0, out float3 vertex1, out float3 vertex2, out float3 vertex3
)
{
using (var geometry = new NativeArray<float3x4>(1, Allocator.TempJob))
{
var job = new BakePlaneJob
{
Vertices = geometry,
center = center,
size = size,
orientation = orientation,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld
};
job.Run();
vertex0 = geometry[0].c0;
vertex1 = geometry[0].c1;
vertex2 = geometry[0].c2;
vertex3 = geometry[0].c3;
}
}

internal static void GetPlanePoints(


float3 center, float2 size, EulerAngles orientation,
out float3 vertex0, out float3 vertex1, out float3 vertex2, out float3 vertex3
)
{
var sizeYUp = math.float3(size.x, 0, size.y);
vertex0 = center + math.mul(orientation, sizeYUp * math.float3(-0.5f, 0, 0.5f));
vertex1 = center + math.mul(orientation, sizeYUp * math.float3(0.5f, 0, 0.5f));
vertex2 = center + math.mul(orientation, sizeYUp * math.float3(0.5f, 0, -0.5f));
vertex3 = center + math.mul(orientation, sizeYUp * math.float3(-0.5f, 0, -0.5f));
}

#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;

public uint ForceUniqueIdentifier;


public ConvexHullGenerationParameters GenerationParameters;
public Material Material;
public CollisionFilter CollisionFilter;
public float4x4 BakeFromShape;

[ReadOnly] public NativeArray<HashableShapeInputs> Inputs;


[ReadOnly] public NativeArray<int> AllSkinIndices;
[ReadOnly] public NativeArray<float> AllBlendShapeWeights;

public void Execute()


{
Result[0] = HashableShapeInputs.GetHash128(
ForceUniqueIdentifier, GenerationParameters, Material, CollisionFilter, BakeFromShape,
Inputs, AllSkinIndices, AllBlendShapeWeights
);
}
}
#endregion

#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;

var bakeToShape = BakeCapsuleJobExtension.GetBakeToShape(shape, center, capsule.OrientationEuler);


var scale = bakeToShape.DecomposeScale();
var newRadius = radius / math.cmax(scale.xy);
if (math.abs(capsule.Radius - newRadius) > kMinimumChange)
capsule.Radius = newRadius;
height /= scale.z;
if (math.abs(math.length(capsule.Height - height)) > kMinimumChange)
capsule.Height = height;
shape.SetCapsule(capsule);
}
internal static CapsuleGeometryAuthoring BakeToBodySpace(
this CapsuleGeometryAuthoring capsule, float4x4 localToWorld, float4x4 shapeToWorld
)
{
using (var geometry = new NativeArray<CapsuleGeometryAuthoring>(1, Allocator.TempJob) { [0] = capsule })
{
var job = new BakeCapsuleJob
{
Capsule = geometry,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld
};
job.Run();
return geometry[0];
}
}
public static class BakeCylinderJobExtension
{
internal static float4x4 GetBakeToShape(PhysicsShapeAuthoring shape, float3 center, EulerAngles orientation)
{
var transform = shape.transform;
var localToWorld = (float4x4)transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
return BakeCylinderJob.GetBakeToShape(localToWorld, shapeToWorld, ref center,
ref orientation);
}
}
public static CylinderGeometry GetBakedCylinderProperties(this PhysicsShapeAuthoring shape)
{
var cylinder = shape.GetCylinderProperties(out var orientation);
return cylinder.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(),
orientation);
}
public static void SetBakedSphereRadius(this PhysicsShapeAuthoring shape, float radius)
{
var sphere = shape.GetSphereProperties(out EulerAngles eulerAngles);
var center = sphere.Center;
radius = math.abs(radius);
var basisToWorld = GetBasisToWorldMatrix(shape.transform.localToWorldMatrix, center, eulerAngles, 1f);
var basisPriority = basisToWorld.HasShear() ? GetBasisAxisPriority(basisToWorld) : k_DefaultAxisPriority;
var bakeToShape = GetPrimitiveBakeToShapeMatrix(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(), ref center, ref eulerAngles, 1f, basisPriority);
var scale = math.cmax(bakeToShape.DecomposeScale());
var newRadius = radius / scale;
sphere.Radius = newRadius;
shape.SetSphere(sphere);
}
public static void SetBakedPlaneSize(this PhysicsShapeAuthoring shape, float2 size)
{
shape.GetPlaneProperties(out var center, out var planeSize, out EulerAngles orientation);
var prevSize = math.abs(planeSize);
size = math.abs(size);
if (math.abs(size[0] - prevSize[0]) < kMinimumChange) size[0] = prevSize[0];
if (math.abs(size[1] - prevSize[1]) < kMinimumChange) size[1] = prevSize[1];
planeSize = size;
shape.SetPlane(center, planeSize, orientation);
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/CapsuleGeometryAuthoring.cs
using System;
using Unity.Mathematics;
using UnityEngine;

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;

/// <summary> The radius of the capsule. </summary>


///
/// <value> The radius. </value>
public float Radius { get => m_Radius; set => m_Radius = value; }
[SerializeField]
float m_Radius;
public bool Equals(CapsuleGeometryAuthoring other)
{
return m_Height.Equals(other.m_Height)
&& m_Center.Equals(other.m_Center)
&& m_Radius.Equals(other.m_Radius)
&& m_OrientationEuler.Equals(other.m_OrientationEuler);
}
public override int GetHashCode()
{
return unchecked((int)math.hash(
new float3x3(
Center,
m_OrientationEuler.Value,
new float3((float)m_OrientationEuler.RotationOrder, m_Height, m_Radius)
)
));
}
}
public static class CapsuleGeometryAuthoringExtensions
{
/// <summary>
/// Construct a CapsuleGeometryAuthoring instance from a run-time CapsuleGeometry instance.
/// </summary>
public static CapsuleGeometryAuthoring ToAuthoring(this CapsuleGeometry input)
{
var orientationEuler = EulerAngles.Default;
orientationEuler.SetValue(quaternion.LookRotationSafe(input.Vertex1 - input.Vertex0, math.up()));
return new CapsuleGeometryAuthoring
{
Height = input.GetHeight(),
OrientationEuler = orientationEuler,
Center = input.GetCenter(),
Radius = input.Radius
};
}
/// <summary>
/// Construct a run-time CapsuleGeometry instance from a CapsuleGeometryAuthoring instance.
/// </summary>
public static CapsuleGeometry ToRuntime(this CapsuleGeometryAuthoring input)
{
var halfHeight = 0.5f * input.Height;
var halfDistance = halfHeight - input.Radius;
var axis = math.normalize(math.mul(input.Orientation, new float3 { z = 1f }));
var halfAxis = axis * halfDistance;
var vertex0 = input.Center + halfAxis;
var vertex1 = input.Center - halfAxis;
return new CapsuleGeometry
{
Vertex0 = vertex0,
Vertex1 = vertex1,
Radius = input.Radius
};
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/ConvexHullGenerationParametersExtensions.cs
using System;
using Unity.Collections;
using Unity.Mathematics;

namespace Unity.Physics.Authoring
{
public static class ConvexHullGenerationParametersExtensions
{
// recommended simplification tolerance is at least 1 centimeter
internal const float k_MinRecommendedSimplificationTolerance = 0.01f;

internal static void InitializeToRecommendedAuthoringValues(


ref this ConvexHullGenerationParameters generationParameters, NativeArray<float3> points
)
{
generationParameters = ConvexHullGenerationParameters.Default.ToAuthoring();

if (points.Length <= 1)
return;

var bounds = new Aabb { Min = points[0], Max = points[0] };


for (var i = 1; i < points.Length; ++i)
bounds.Include(points[i]);
generationParameters.SimplificationTolerance = math.max(
k_MinRecommendedSimplificationTolerance,
ConvexHullGenerationParameters.Default.SimplificationTolerance * math.cmax(bounds.Extents)
);
// TODO: initialize other properties based on input points?
}

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);
}

public static ConvexHullGenerationParameters ToAuthoring(this ConvexHullGenerationParameters generationParameters)


{
generationParameters.MinimumAngle = math.degrees(generationParameters.MinimumAngle);
return generationParameters;
}

public static ConvexHullGenerationParameters ToRunTime(this ConvexHullGenerationParameters generationParameters)


{
generationParameters.MinimumAngle = math.radians(generationParameters.MinimumAngle);
return generationParameters;
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/EulerAngles.cs
using System;
using Unity.Mathematics;
using UnityEngine;

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));

public static implicit operator quaternion(EulerAngles euler) =>


math.normalize(quaternion.Euler(math.radians(euler.Value), euler.RotationOrder));
public bool Equals(EulerAngles other) => Value.Equals(other.Value) && RotationOrder == other.RotationOrder;
public override bool Equals(object obj) => obj is EulerAngles other && Equals(other);
public override int GetHashCode() => unchecked((int)math.hash(new float4(Value, (float)RotationOrder)));
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/GetActiveChildrenScope.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityComponent = UnityEngine.Component;
namespace Unity.Physics.Authoring
{
public struct GetActiveChildrenScope<T> : IDisposable where T : UnityComponent
{
static readonly List<PhysicsShapeAuthoring> s_PhysicsShapes = new List<PhysicsShapeAuthoring>(8);

static bool s_BufferUsed;


static List<T> s_Buffer = new List<T>(8);

public List<T> Buffer => m_Disposed ? null : s_Buffer;


bool m_Disposed;
PhysicsShapeAuthoring m_Shape;
Transform m_Root;
GameObject m_PrimaryBody;
bool m_CheckIfComponentBelongsToShape;
public GetActiveChildrenScope(PhysicsShapeAuthoring shape, Transform root)
{
m_Disposed = false;
m_Shape = shape;
m_Root = root;
m_PrimaryBody = PhysicsShapeExtensions.GetPrimaryBody(root.gameObject);
m_CheckIfComponentBelongsToShape = root.transform.IsChildOf(shape.transform);
if (s_BufferUsed)
throw new InvalidOperationException($"Cannot nest two {GetType()}");
s_BufferUsed = true;
root.GetComponentsInChildren(true, s_Buffer);
}
public void Dispose()
{
if (m_Disposed)
return;
m_Disposed = true;
s_BufferUsed = false;
s_Buffer.Clear();
}

public bool IsChildActiveAndBelongsToShape(T child, bool filterOutInvalid = true)


{
var meshFilter = (UnityComponent)child as MeshFilter;
if (meshFilter != null)
{
if (meshFilter.sharedMesh == null)
return false;

var renderer = meshFilter.GetComponent<MeshRenderer>();


if (renderer == null || !renderer.enabled)
return false;
if (filterOutInvalid && !meshFilter.sharedMesh.IsValidForConversion(m_Shape.gameObject))
return false;
}

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);

// avoids drift in axes we're not actually changing


public const float kMinimumChange = HashableShapeInputs.k_DefaultLinearPrecision;

static readonly int[] k_NextAxis = { 1, 2, 0 };


static readonly int[] k_PrevAxis = { 2, 0, 1 };

// 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;

var imax = max == basisAxisLengths.x ? 0 : max == basisAxisLengths.y ? 1 : 2;


basisToWorld[k_NextAxis[imax]] = DeskewSecondaryAxis(basisToWorld[imax], basisToWorld[k_NextAxis[imax]]);
basisToWorld[k_PrevAxis[imax]] = DeskewSecondaryAxis(basisToWorld[imax], basisToWorld[k_PrevAxis[imax]]);

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];

return new int3(imax, imid, imin);


}

[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]]);

// recompute third axes from first two


var n2 = math.normalizesafe(
new float4(math.cross(localToBake[basisPriority[0]].xyz, localToBake[basisPriority[1]].xyz), 0f)
);
localToBake[basisPriority[2]] = n2 * math.dot(localToBake[basisPriority[2]], n2);
}
var bakeToShape = math.mul(math.inverse(shapeToWorld), localToBake);
// transform baked center/orientation (i.e. primitive basis) into shape space
orientation.SetValue(
quaternion.LookRotationSafe(bakeToShape[basisPriority[0]].xyz, bakeToShape[basisPriority[1]].xyz)
);
center = bakeToShape.c3.xyz;

return bakeToShape;
}

internal static CollisionFilter GetFilter(this PhysicsShapeAuthoring shape)


{
// TODO: determine optimal workflow for specifying group index
return new CollisionFilter
{
BelongsTo = shape.BelongsTo.Value,
CollidesWith = shape.CollidesWith.Value
};
}
internal static Material GetMaterial(this PhysicsShapeAuthoring shape)
{
// TODO: TBD how we will author editor content for other shape flags
return new Material
{
Friction = shape.Friction.Value,
FrictionCombinePolicy = shape.Friction.CombineMode,
Restitution = shape.Restitution.Value,
RestitutionCombinePolicy = shape.Restitution.CombineMode,
CollisionResponse = shape.CollisionResponse,
CustomTags = shape.CustomTags.Value
};
}
public static GameObject FindTopmostEnabledAncestor<T>(GameObject shape, List<T> buffer) where T : Component
{
// include inactive in case the supplied shape GameObject is a prefab that has not been instantiated
shape.GetComponentsInParent(true, buffer);
GameObject result = null;
for (var i = buffer.Count - 1; i >= 0; --i)
{
if (
(buffer[i] as UnityEngine.Collider)?.enabled ??
(buffer[i] as MonoBehaviour)?.enabled ?? true
)
{
result = buffer[i].gameObject;
break;
}
}
buffer.Clear();
return result;
}
public static GameObject GetPrimaryBody(this PhysicsShapeAuthoring shape) => GetPrimaryBody(shape.gameObject);
public static GameObject GetPrimaryBody(GameObject shape)
{
var pb = ColliderExtensions.FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
var rb = ColliderExtensions.FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
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
ColliderExtensions.FindTopmostStaticEnabledAncestor(shape, out GameObject 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;
}

public static BoxGeometry GetBakedBoxProperties(this PhysicsShapeAuthoring shape)


{
var box = shape.GetBoxProperties(out var orientation);
return box.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(), orientation);
}
internal static BoxGeometry BakeToBodySpace(
this BoxGeometry box, float4x4 localToWorld, float4x4 shapeToWorld, EulerAngles orientation
)
{
using (var geometry = new NativeArray<BoxGeometry>(1, Allocator.TempJob) { [0] = box })
{
var job = new BakeBoxJob
{
Box = geometry,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld,
orientation = orientation
};
job.Run();
return geometry[0];
}
}
public static void SetBakedBoxSize(this PhysicsShapeAuthoring shape, float3 size, float bevelRadius)
{
var box = shape.GetBoxProperties(out var orientation);
var center = box.Center;
var prevSize = math.abs(box.Size);
size = math.abs(size);

var bakeToShape = BakeBoxJobExtension.GetBakeToShape(shape, center, orientation);


var scale = bakeToShape.DecomposeScale();
size /= scale;

if (math.abs(size[0] - prevSize[0]) < kMinimumChange) size[0] = prevSize[0];


if (math.abs(size[1] - prevSize[1]) < kMinimumChange) size[1] = prevSize[1];
if (math.abs(size[2] - prevSize[2]) < kMinimumChange) size[2] = prevSize[2];
box.BevelRadius = bevelRadius;
box.Size = size;
shape.SetBox(box, orientation);
}

internal static CapsuleGeometryAuthoring GetBakedCapsuleProperties(this PhysicsShapeAuthoring shape)


{
var capsule = shape.GetCapsuleProperties();
return capsule.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix());
}

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();

var newRadius = radius / math.cmax(scale.xy);


if (math.abs(cylinder.Radius - newRadius) > kMinimumChange) cylinder.Radius = newRadius;
if (math.abs(cylinder.BevelRadius - bevelRadius) > kMinimumChange) cylinder.BevelRadius = bevelRadius;

var newHeight = math.max(0, height / scale.z);


if (math.abs(cylinder.Height - newHeight) > kMinimumChange) cylinder.Height = newHeight;
shape.SetCylinder(cylinder, orientation);
}

internal static SphereGeometry GetBakedSphereProperties(this PhysicsShapeAuthoring shape, out EulerAngles orientation)


{
var sphere = shape.GetSphereProperties(out orientation);
return sphere.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(), ref orientation);
}
internal static void GetBakedPlaneProperties(
this PhysicsShapeAuthoring shape, out float3 vertex0, out float3 vertex1, out float3 vertex2, out float3 vertex3
)
{
shape.GetPlaneProperties(out var center, out var size, out EulerAngles orientation);
BakeToBodySpace(
center, size, orientation, shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(),
out vertex0, out vertex1, out vertex2, out vertex3
);
}
public static void GetBakedConvexProperties(this PhysicsShapeAuthoring shape, NativeList<float3> pointCloud)
{
shape.GetConvexHullProperties(pointCloud, true, default, default, default, default);
shape.BakePoints(pointCloud.AsArray());
}
internal static Hash128 GetBakedMeshInputs(this PhysicsShapeAuthoring shape, HashSet<UnityEngine.Mesh> meshAssets = null)
{
using (var inputs = new NativeList<HashableShapeInputs>(8, Allocator.TempJob))
{
shape.GetMeshProperties(default, default, true, inputs, meshAssets);
using (var hash = new NativeArray<Hash128>(1, Allocator.TempJob))
using (var allSkinIndices = new NativeArray<int>(0, Allocator.TempJob))
using (var allBlendShapeWeights = new NativeArray<float>(0, Allocator.TempJob))
{
var job = new GetShapeInputsHashJob
{
Result = hash,
ForceUniqueIdentifier = (uint)(shape.ForceUnique ? shape.GetInstanceID() : 0),
Material = shape.GetMaterial(),
CollisionFilter = shape.GetFilter(),
BakeFromShape = shape.GetLocalToShapeMatrix(),
Inputs = inputs.AsArray(),
AllSkinIndices = allSkinIndices,
AllBlendShapeWeights = allBlendShapeWeights
};
job.Run();
return hash[0];
}
}
}
internal static Hash128 GetBakedConvexInputs(this PhysicsShapeAuthoring shape, HashSet<UnityEngine.Mesh> meshAssets)
{
using (var inputs = new NativeList<HashableShapeInputs>(8, Allocator.TempJob))
using (var allSkinIndices = new NativeList<int>(4096, Allocator.TempJob))
using (var allBlendShapeWeights = new NativeList<float>(64, Allocator.TempJob))
{
shape.GetConvexHullProperties(default, true, inputs, allSkinIndices, allBlendShapeWeights, meshAssets);
using (var hash = new NativeArray<Hash128>(1, Allocator.TempJob))
{
var job = new GetShapeInputsHashJob
{
Result = hash,
ForceUniqueIdentifier = (uint)(shape.ForceUnique ? shape.GetInstanceID() : 0),
GenerationParameters = shape.ConvexHullGenerationParameters,
Material = shape.GetMaterial(),
CollisionFilter = shape.GetFilter(),
BakeFromShape = shape.GetLocalToShapeMatrix(),
Inputs = inputs.AsArray(),
AllSkinIndices = allSkinIndices.AsArray(),
AllBlendShapeWeights = allBlendShapeWeights.AsArray()
};
job.Run();
return hash[0];
}
}
}
public static void GetBakedMeshProperties(
this PhysicsShapeAuthoring shape, NativeList<float3> vertices, NativeList<int3> triangles,
HashSet<UnityEngine.Mesh> meshAssets = null
)
{
shape.GetMeshProperties(vertices, triangles, true, default, meshAssets);
shape.BakePoints(vertices.AsArray());
}
const float k_HashFloatTolerance = 0.01f;
// used to hash convex hull generation properties in a way that is robust to imprecision
public static uint GetStableHash(
this ConvexHullGenerationParameters generationParameters,
ConvexHullGenerationParameters hashedParameters,
float tolerance = k_HashFloatTolerance
)
{
var differences = new float3(
generationParameters.BevelRadius - hashedParameters.BevelRadius,
generationParameters.MinimumAngle - hashedParameters.MinimumAngle,
generationParameters.SimplificationTolerance - hashedParameters.SimplificationTolerance
);
return math.cmax(math.abs(differences)) < tolerance
? unchecked((uint)hashedParameters.GetHashCode())
: unchecked((uint)generationParameters.GetHashCode());
}
// used to hash an array of points in a way that is robust to imprecision
public static unsafe uint GetStableHash(
this NativeList<float3> points, NativeArray<float3> hashedPoints, float tolerance = k_HashFloatTolerance
)
{
if (points.Length != hashedPoints.Length)
return math.hash(points.GetUnsafePtr(), UnsafeUtility.SizeOf<float3>() * points.Length);

for (int i = 0, count = points.Length; i < count; ++i)


{
if (math.cmax(math.abs(points[i] - hashedPoints[i])) > tolerance)
return math.hash(points.GetUnsafePtr(), UnsafeUtility.SizeOf<float3>() * points.Length);
}
return math.hash(hashedPoints.GetUnsafePtr(), UnsafeUtility.SizeOf<float3>() * hashedPoints.Length);
}
public static int GetMaxAxis(this float3 v)
{
var cmax = math.cmax(v);
return cmax == v.z ? 2 : cmax == v.y ? 1 : 0;
}
public static int GetDeviantAxis(this float3 v)
{
var deviation = math.abs(v - math.csum(v) / 3f);
return math.cmax(deviation) == deviation.z ? 2 : math.cmax(deviation) == deviation.y ? 1 : 0;
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/PropertyAttributes.cs
using UnityEngine;

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; }

public SoftRangeAttribute(float min, float max)


{
SliderMin = TextFieldMin = min;
SliderMax = TextFieldMax = max;
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/AssemblyInfo.cs
using System.Runtime.CompilerServices;

[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();

EditorUtilities.EditPivot(ballAndSocket.worldFromA, ballAndSocket.worldFromB, ballAndSocket.AutoSetConnected,


ref ballAndSocket.PositionLocal, ref ballAndSocket.PositionInConnectedEntity, ballAndSocket);
}
}
}
#endif
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/CustomPhysicsMaterialTagNamesEditor.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;

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();

public override void OnInspectorGUI()


{
LimitedHingeJoint limitedHinge = (LimitedHingeJoint)target;

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();
}
}

protected virtual void OnSceneGUI()


{
LimitedHingeJoint limitedHinge = (LimitedHingeJoint)target;

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;

public override void OnInspectorGUI()


{
serializedObject.Update();

EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_MotionType);
if (m_MotionType.intValue != (int)BodyMotionType.Static)
EditorGUILayout.PropertyField(m_Smoothing);

var dynamic = m_MotionType.intValue == (int)BodyMotionType.Dynamic;


if (dynamic)
EditorGUILayout.PropertyField(m_Mass, Content.MassLabel);
else
{
EditorGUI.BeginDisabledGroup(true);
var position = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
EditorGUI.BeginProperty(position, Content.MassLabel, m_Mass);
EditorGUI.FloatField(position, Content.MassLabel, float.PositiveInfinity);
EditorGUI.EndProperty();
EditorGUI.EndDisabledGroup();
}

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);
}

showAdvanced = EditorGUILayout.Foldout(showAdvanced, Content.AdvancedLabel);


if (showAdvanced)
{
++EditorGUI.indentLevel;
EditorGUILayout.PropertyField(m_WorldIndex);
if (m_MotionType.intValue != (int)BodyMotionType.Static)
{
EditorGUILayout.PropertyField(m_OverrideDefaultMassDistribution);
if (m_OverrideDefaultMassDistribution.boolValue)
{
++EditorGUI.indentLevel;
EditorGUILayout.PropertyField(m_CenterOfMass, Content.CenterOfMassLabel);

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];

static readonly string[] k_NonReadableGeometryWarning =


{
L10n.Tr($"{k_Singular} has a non-readable mesh in its hierarchy. Move it into a sub-scene or assign a custom mesh with Read/Write enabled in its import settings if it needs
L10n.Tr($"{k_Plural} has a non-readable mesh in its hierarchy. Move it into a sub-scene or assign a custom mesh with Read/Write enabled in its import settings if it needs t
};
public static string GetNonReadableGeometryWarning(int numTargets) =>
numTargets == 1 ? k_NonReadableGeometryWarning[0] : k_NonReadableGeometryWarning[1];
static readonly string[] k_MeshWithSkinnedPointsWarning =
{
L10n.Tr($"{k_Singular} is a mesh based on its render geometry, but its render geometry includes skinned points. These points will be excluded from the automatically generat
L10n.Tr($"{k_Plural} is a mesh based on its render geometry, but its render geometry includes skinned points. These points will be excluded from the automatically generated
};
public static string GetMeshWithSkinnedPointsWarning(int numTargets) =>
numTargets == 1 ? k_MeshWithSkinnedPointsWarning[0] : k_MeshWithSkinnedPointsWarning[1];
static readonly string[] k_StaticColliderStatusMessage =
{
L10n.Tr($"{k_Singular} will be considered static. Add a {ObjectNames.NicifyVariableName(typeof(PhysicsBodyAuthoring).Name)} component if you will move it at run-time."
L10n.Tr($"{k_Plural} will be considered static. Add a {ObjectNames.NicifyVariableName(typeof(PhysicsBodyAuthoring).Name)} component if you will move them at run-time."
};
public static string GetStaticColliderStatusMessage(int numTargets) =>
numTargets == 1 ? k_StaticColliderStatusMessage[0] : k_StaticColliderStatusMessage[1];
public static readonly string BoxCapsuleSuggestion =
L10n.Tr($"Target {ShapeType.Box} has uniform size on its two short axes and a large convex radius. Consider using a {ShapeType.Capsule} instead.");
public static readonly string BoxPlaneSuggestion =
L10n.Tr($"Target {ShapeType.Box} is flat. Consider using a {ShapeType.Plane} instead.");
public static readonly string BoxSphereSuggestion =
L10n.Tr($"Target {ShapeType.Box} has uniform size and large convex radius. Consider using a {ShapeType.Sphere} instead.");
public static readonly string CapsuleSphereSuggestion =
L10n.Tr($"Target {ShapeType.Capsule}'s diameter is equal to its height. Consider using a {ShapeType.Sphere} instead.");
public static readonly string CylinderCapsuleSuggestion =
L10n.Tr($"Target {ShapeType.Cylinder} has a large convex radius. Consider using a {ShapeType.Capsule} instead.");
public static readonly string CylinderSphereSuggestion =
L10n.Tr($"Target {ShapeType.Cylinder} has a large convex radius and its diameter is equal to its height. Consider using a {ShapeType.Sphere} instead.");
public static readonly GUIStyle Button =
new GUIStyle(EditorStyles.miniButton) { padding = new RectOffset() };
public static readonly GUIStyle ButtonDropDown =
new GUIStyle(EditorStyles.popup) { alignment = TextAnchor.MiddleCenter };
}
#pragma warning disable 649
[AutoPopulate] SerializedProperty m_ShapeType;
[AutoPopulate] SerializedProperty m_PrimitiveCenter;
[AutoPopulate] SerializedProperty m_PrimitiveSize;
[AutoPopulate] SerializedProperty m_PrimitiveOrientation;
[AutoPopulate] SerializedProperty m_Capsule;
[AutoPopulate] SerializedProperty m_Cylinder;
[AutoPopulate] SerializedProperty m_CylinderSideCount;
[AutoPopulate] SerializedProperty m_SphereRadius;
[AutoPopulate] SerializedProperty m_ConvexHullGenerationParameters;
[AutoPopulate(PropertyPath = "m_ConvexHullGenerationParameters.m_BevelRadius")] SerializedProperty m_BevelRadius;
[AutoPopulate] SerializedProperty m_MinimumSkinnedVertexWeight;
[AutoPopulate] SerializedProperty m_CustomMesh;
[AutoPopulate] SerializedProperty m_Material;
[AutoPopulate] SerializedProperty m_ForceUnique;
#pragma warning restore 649
[Flags]
enum GeometryState
{
Okay = 0,
NoGeometry = 1 << 0,
NonReadableGeometry = 1 << 1,
MeshWithSkinnedPoints = 1 << 2
}
GeometryState m_GeometryState;
int m_NumImplicitStatic;
// keep track of when the user is dragging some control to prevent continually rebuilding preview geometry
[NonSerialized]
int m_DraggingControlID;
[NonSerialized]
FitToRenderMeshesDropDown m_DropDown;
protected override void OnEnable()
{
base.OnEnable();
HashUtility.Initialize();

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);
}
}

float m_BevelRadius = ConvexHullGenerationParameters.Default.BevelRadius;


bool m_IsDragging = false;

public float height


{
get => GetSize().z;
set
{
var size = GetSize();
size.z = math.max(0f, value);
SetSize(size);
}
}

public float radius


{
get
{
var size = (float3)GetSize();
var diameter = 0f;
// only consider size values on enabled axes
if (IsAxisEnabled(0)) diameter = math.max(diameter, math.abs(size.x));
else if (IsAxisEnabled(1)) diameter = math.max(diameter, math.abs(size.y));
return diameter * 0.5f;
}
set
{
var size = (float3)GetSize();
size.x = size.y = math.max(0f, value * 2.0f);
SetSize(size);
}
}

public int sideCount


{
get => m_SideCount;
set
{
if (value == m_SideCount)
return;

m_SideCount = value;

Array.Resize(ref m_TopPoints, m_SideCount * 2);


Array.Resize(ref m_BottomPoints, m_SideCount * 2);
Array.Resize(ref m_Corners, m_SideCount * 2);
}
}
int m_SideCount;

PhysicsBoundsHandleUtility.Corner[] m_Corners = Array.Empty<PhysicsBoundsHandleUtility.Corner>();


Vector3[] m_TopPoints = Array.Empty<Vector3>();
Vector3[] m_BottomPoints = Array.Empty<Vector3>();

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()


{
using (new Handles.DrawingScope(Handles.matrix))
{
var backfacedColor = PhysicsBoundsHandleUtility.GetStateColor(true);
var frontfacedColor = Handles.color;
bool isCameraInsideBox = false;

var radius = this.radius;


var bevelRadius = this.bevelRadius;

var halfHeight = new float3(0f, 0f, height * 0.5f);


var ctr = (float3)center;
var halfAngleStep = math.PI / m_SideCount;
var angleStep = 2f * halfAngleStep;
const float kHalfPI = (math.PI * 0.5f);
var bottom = ctr + halfHeight + new float3 { z = bevelRadius };
var top = ctr - halfHeight - new float3 { z = bevelRadius };
var tangent = new float3(1, 0, 0);
var binormal = new float3(0, 1, 0);
var topBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(top, -tangent, binormal, axes, isCameraInsideBox);
var bottomBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(bottom, tangent, binormal, axes, isCameraInsideBox);

var cameraCenter = float3.zero;


var cameraForward = new float3 { z = 1f };
if (Camera.current != null)
{
cameraCenter = Camera.current.transform.position;
cameraForward = Camera.current.transform.forward;
}

// 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;

var noSides = (radius - bevelRadius) < PhysicsBoundsHandleUtility.kDistanceEpsilon;


var up = new float3(0, 0, -1f);

var t = ((m_SideCount - 2) * angleStep);


var xyAngle0 = new float3(math.cos(t), math.sin(t), 0f);

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 offset0 = xyAngle0 * (radius - bevelRadius);


var offset1 = xyAngle1 * (radius - bevelRadius);
var offset2 = xyAngle2 * (radius - bevelRadius);
var top1 = ctr + offset1 - (halfHeight - new float3 { z = bevelRadius });
var bottom1 = ctr + offset1 + (halfHeight - new float3 { z = bevelRadius });
var top2 = ctr + offset2 - (halfHeight - new float3 { z = bevelRadius });
var bottom2 = ctr + offset2 + (halfHeight - new float3 { z = bevelRadius });

var startOffset = direction1 * bevelRadius;


if (bevelGreaterThanZero)
{
var upOffset = up * bevelRadius;
// top/bottom caps
if (bevelLessThanCylinderRadius)
{
Handles.color = topBackFaced ? backfacedColor : frontfacedColor;
Handles.DrawLine(top1 + upOffset, top2 + upOffset);

Handles.color = bottomBackFaced ? backfacedColor : frontfacedColor;


Handles.DrawLine(bottom1 - upOffset, bottom2 - upOffset);
}

var currSideMidPoint = ctr + ((top1 + bottom1 + top2 + bottom2) * 0.25f) + startOffset;


var currSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(currSideMidPoint, up, sideways2, axes, isCameraInsideBox);
Handles.color = currSideBackFaced ? backfacedColor : frontfacedColor;
if (!noSides)
{
// Square side of bevelled cylinder
Handles.DrawLine(top2 + startOffset, bottom2 + startOffset);
Handles.DrawLine(bottom2 + startOffset, bottom1 + startOffset);
Handles.DrawLine(bottom1 + startOffset, top1 + startOffset);
Handles.DrawLine(top1 + startOffset, top2 + startOffset);
}
else
{
// Square side of bevelled cylinder, when squashed to a single line
Handles.DrawLine(top2 + startOffset, bottom2 + startOffset);
}
}
else
{
var top0 = ctr + offset0 - (halfHeight - new float3 { z = bevelRadius });
var bottom0 = ctr + offset0 + (halfHeight - new float3 { z = bevelRadius });
var prevMidPoint = ctr + ((top0 + top1 + bottom0 + bottom1) * 0.25f) + startOffset;
var prevSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(prevMidPoint, up, sideways1, axes, isCameraInsideBox);

var currMidPoint = ctr + ((top1 + top2 + bottom1 + bottom2) * 0.25f) + startOffset;


var currSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(currMidPoint, up, sideways2, axes, isCameraInsideBox);
// Square side of bevelled cylinder
Handles.color = (currSideBackFaced && prevSideBackFaced) ? backfacedColor : frontfacedColor;
Handles.DrawLine(bottom1 + startOffset, top1 + startOffset);
Handles.color = (currSideBackFaced && topBackFaced) ? backfacedColor : frontfacedColor;
Handles.DrawLine(top1 + startOffset, top2 + startOffset);
Handles.color = (currSideBackFaced && bottomBackFaced) ? backfacedColor : frontfacedColor;
Handles.DrawLine(bottom2 + startOffset, bottom1 + startOffset);
}
if (bevelGreaterThanZero)
{
Handles.color = frontfacedColor;

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;

// Side horizon on vertical curved edge


if (m_Corners[up1].splitCount > 1 &&
m_Corners[dn1].splitCount > 1)
{
if ((m_Corners[up1].splitAxis[0].y || m_Corners[up1].splitAxis[1].y) &&
(m_Corners[dn1].splitAxis[0].y || m_Corners[dn1].splitAxis[1].y))
{
var point0 = m_Corners[up1].splitAxis[0].y ? m_Corners[up1].points[0] : m_Corners[up1].points[1];
var point1 = m_Corners[dn1].splitAxis[0].y ? m_Corners[dn1].points[0] : m_Corners[dn1].points[1];
Handles.DrawLine(point0, point1);
}
}
// Top horizon on horizontal curved edge
if (m_Corners[up0].splitCount > 1 &&
m_Corners[up1].splitCount > 1)
{
if ((m_Corners[up0].splitAxis[0].x || m_Corners[up0].splitAxis[1].x) &&
(m_Corners[up1].splitAxis[0].z || m_Corners[up1].splitAxis[1].z))
{
var point0 = m_Corners[up0].splitAxis[0].x ? m_Corners[up0].points[0] : m_Corners[up0].points[1];
var point1 = m_Corners[up1].splitAxis[0].z ? m_Corners[up1].points[0] : m_Corners[up1].points[1];
Handles.DrawLine(point0, point1);
}
}
// Bottom horizon on horizontal curved edge
if (m_Corners[dn0].splitCount > 1 &&
m_Corners[dn1].splitCount > 1)
{
if ((m_Corners[dn0].splitAxis[0].z || m_Corners[dn0].splitAxis[1].z) &&
(m_Corners[dn1].splitAxis[0].x || m_Corners[dn1].splitAxis[1].x))
{
var point0 = m_Corners[dn0].splitAxis[0].z ? m_Corners[dn0].points[0] : m_Corners[dn0].points[1];
var point1 = m_Corners[dn1].splitAxis[0].x ? m_Corners[dn1].points[0] : m_Corners[dn1].points[1];
Handles.DrawLine(point0, point1);
}
}
}
for (var i = 0; i < m_Corners.Length; ++i)
PhysicsBoundsHandleUtility.DrawCorner(m_Corners[i], new bool3(true, true, !noSides));
}
}
}
protected override Bounds OnHandleChanged(HandleDirection handle, Bounds boundsOnClick, Bounds newBounds)
{
const int k_DirectionX = 0;
const int k_DirectionY = 1;
const int k_DirectionZ = 2;
var changedAxis = k_DirectionX;
var otherRadiusAxis = k_DirectionY;
switch (handle)
{
case HandleDirection.NegativeY:
case HandleDirection.PositiveY:
changedAxis = k_DirectionY;
otherRadiusAxis = k_DirectionX;
break;
case HandleDirection.NegativeZ:
case HandleDirection.PositiveZ:
changedAxis = k_DirectionZ;
break;
}
var upperBound = newBounds.max;
var lowerBound = newBounds.min;
var convexDiameter = 2f * bevelRadius;
// ensure changed dimension cannot be made less than convex diameter
if (upperBound[changedAxis] - lowerBound[changedAxis] < convexDiameter)
{
switch (handle)
{
case HandleDirection.PositiveX:
case HandleDirection.PositiveY:
case HandleDirection.PositiveZ:
upperBound[changedAxis] = lowerBound[changedAxis] + convexDiameter;
break;
default:
lowerBound[changedAxis] = upperBound[changedAxis] - convexDiameter;
break;
}
}
// ensure radius changes uniformly
if (changedAxis != k_DirectionZ)
{
var rad = 0.5f * (upperBound[changedAxis] - lowerBound[changedAxis]);
lowerBound[otherRadiusAxis] = center[otherRadiusAxis] - rad;
upperBound[otherRadiusAxis] = center[otherRadiusAxis] + rad;
}
return new Bounds((upperBound + lowerBound) * 0.5f, upperBound - lowerBound);
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/PhysicsBoundsHandleUtility.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEngine;
using static UnityEditor.IMGUI.Controls.PrimitiveBoundsHandle;
namespace Unity.Physics.Editor
{
static class PhysicsBoundsHandleUtility
{
internal const float kBackfaceAlphaMultiplier = 0.2f;
const float kDegreeEpsilon = 0.001f;
public const float kDistanceEpsilon = 0.0001f;
public static bool IsBackfaced(float3 localPos, float3 localTangent, float3 localBinormal, Axes axes, bool isCameraInsideBox)
{
// if inside the box then ignore back facing alpha multiplier (otherwise all handles will look disabled)
if (isCameraInsideBox || axes != Axes.All)
return false;
// use tangent and binormal to calculate normal in case handle matrix is skewed
float3 worldTangent = math.normalize(Handles.matrix.MultiplyVector(localTangent));
float3 worldBinormal = math.normalize(Handles.matrix.MultiplyVector(localBinormal));
float3 worldDir = math.normalize(math.cross(worldTangent, worldBinormal));
// adjust color if handle is back facing
float cosV;
var currentCamera = Camera.current;
if (currentCamera != null && !currentCamera.orthographic)
{
float3 cameraPos = currentCamera.transform.position;
float3 worldPos = Handles.matrix.MultiplyPoint(localPos);
cosV = math.dot(math.normalize(cameraPos - worldPos), worldDir);
}
else
{
float3 cameraForward = currentCamera == null ? Vector3.forward : currentCamera.transform.forward;
cosV = math.dot(-cameraForward, worldDir);
}
return cosV < -0.0001f;
}
public static Color GetStateColor(bool isBackfaced)
{
float alphaMultiplier = isBackfaced ? kBackfaceAlphaMultiplier : 1f;
return Handles.color * new Color(1f, 1f, 1f, alphaMultiplier);
}
static void AdjustMidpointHandleColor(bool isBackfaced)
{
Handles.color = GetStateColor(isBackfaced);
}
static readonly Vector3[] s_FacePoints = new Vector3[8];
static readonly Vector3[] s_LinePoints = new Vector3[2];
static readonly int[] k_NextAxis = { 1, 2, 0 };
public static void DrawFace(float3 center, float3 size, float cornerRadius, int normalAxis, Axes axes, bool isCameraInsideBox)
{
// 0 = 0 1 2
// 1 = 1 2 0
// 2 = 2 0 1
int a = normalAxis;
int b = k_NextAxis[a];
int c = k_NextAxis[b];
cornerRadius = math.abs(cornerRadius);
size *= 0.5f;
var normal = new float3 { [a] = size[a] };
var ctr = center + normal;
size -= new float3(cornerRadius);
// check if our face is a point
if (math.abs(size[c]) < kDistanceEpsilon &&
math.abs(size[b]) < kDistanceEpsilon)
return;
Vector3[] points;
// check if our face is a line or not
if (math.abs(size[c]) >= kDistanceEpsilon &&
math.abs(size[b]) >= kDistanceEpsilon)
{
var i = 0;
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
points = s_FacePoints;
}
else if (math.abs(size[c]) >= kDistanceEpsilon)
{
var i = 0;
s_LinePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
s_LinePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
points = s_LinePoints;
}
else
{
var i = 0;
s_LinePoints[i++] = ctr + new float3 { [c] = size[c], [b] = size[b] };
s_LinePoints[i++] = ctr + new float3 { [c] = size[c], [b] = -size[b] };
points = s_LinePoints;
}
float3 tangent, biNormal;
if (size[c] > 0)
{
tangent = math.cross(normal, math.normalizesafe(new float3 { [c] = size[c] }));
biNormal = math.cross(normal, tangent);
}
else
{
tangent = math.cross(normal, math.normalizesafe(new float3 { [b] = size[b] }));
biNormal = math.cross(normal, tangent);
}
using (new Handles.DrawingScope(Handles.color))
{
AdjustMidpointHandleColor(IsBackfaced(ctr, tangent, biNormal, axes, isCameraInsideBox));
Handles.DrawLines(points);
}
}
public struct Corner
{
public float3 angle;
public float3x2 intersections;
public float3x2 points;
public float3x3 axes;
public float3x3 normals;
public bool3x2 splitAxis;
public int splitCount;
public float3 position;
public float radius;
public bool isBackFaced;
public float3 cameraForward;
}
public static void CalculateCornerHorizon(float3 cornerPosition, quaternion orientation, float3 cameraCenter, float3 cameraForward, bool cameraOrtho, float radius, out Corner
{
var axisx = new float3(1f, 0f, 0f);
var axisy = new float3(0f, 1f, 0f);
var axisz = new float3(0f, 0f, 1f);
// a vector pointing away from the center of the corner
var cornerNormal = math.normalize(math.mul(orientation, new float3(1f, 1f, 1f)));
var axes = math.mul(new float3x3(orientation), new float3x3(axisx, axisy, axisz));
CalculateCornerHorizon(cornerPosition,
axes,
cornerNormal,
cameraCenter, cameraForward, cameraOrtho,
radius, out corner);
}
public static void CalculateCornerHorizon(float3 cornerPosition, float3x3 axes, float3 cornerNormal, float3 cameraCenter, float3 cameraForward, bool cameraOrtho, float radius
{
var cameraToCenter = cornerPosition - cameraCenter; // vector from camera to center
var sqrRadius = radius * radius;
var sqrDistCameraToCenter = math.lengthsq(cameraToCenter);
var sqrOffset = (sqrRadius * sqrRadius / sqrDistCameraToCenter); // squared distance from actual center to drawn disc center
if (!cameraOrtho)
cameraForward = cameraToCenter;
var normals = new float3x3
{
c0 = math.normalize(math.cross(axes[1], axes[2])),
c1 = math.normalize(math.cross(axes[2], axes[0])),
c2 = math.normalize(math.cross(axes[0], axes[1]))
};
corner = new Corner
{
angle = new float3(
Vector3.Angle(axes[0], axes[1]),
Vector3.Angle(axes[1], axes[2]),
Vector3.Angle(axes[2], axes[0])
),
intersections = default,
points = default,
splitAxis = default,
axes = axes,
normals = normals,
position = cornerPosition,
radius = radius,
cameraForward = cameraForward,
isBackFaced = math.dot(cornerNormal, cameraForward) > 0,
splitCount = 0
};
if (math.abs(sqrDistCameraToCenter) <= sqrRadius)
return;
for (int n = 0, sign = -1; n < 2; n++, sign += 2)
{
for (int i = 0; i < 3; i++)
{
var axis1 = normals[i] * sign;
var axis2 = axes[(i + 1) % 3] * sign;
var axis3 = axes[(i + 2) % 3] * sign;
var Q = Vector3.Angle(cameraForward, axis1);
var f = math.tan(math.radians(90 - math.min(Q, 180 - Q)));
var g = sqrOffset + f * f * sqrOffset;
if (g >= sqrRadius)
continue;
var e = math.degrees(math.asin(math.sqrt(g) / radius));
var vectorToPointOnHorizon = Quaternion.AngleAxis(e, axis1) * math.normalize(math.cross(axis1, cameraForward));
vectorToPointOnHorizon = math.normalize(Vector3.ProjectOnPlane(vectorToPointOnHorizon, axis1));
var intersectionDirection = vectorToPointOnHorizon;
var angle1 = Vector3.SignedAngle(axis2, intersectionDirection, axis1);
var angle2 = Vector3.SignedAngle(axis3, intersectionDirection, axis1);
if (angle1 <= 0 || angle2 >= 0)
continue;
var point = corner.position + (float3)(intersectionDirection * radius);
if (corner.splitCount < 2)
{
corner.splitAxis[corner.splitCount][i] = true;
corner.intersections[corner.splitCount] = intersectionDirection;
corner.points[corner.splitCount] = point;
corner.splitCount++;
}
}
}
if (!math.any(corner.splitAxis[0]) &&
!math.any(corner.splitAxis[1]))
{
corner.splitCount = 0;
corner.splitAxis[0] = false;
corner.splitAxis[1] = false;
}
}
public static void DrawCorner(Corner corner, bool3 showAxis)
{
var color = Handles.color;
var axes = corner.axes;
var intersections = corner.intersections;
var normals = corner.normals;
var origin = corner.position;
var radius = corner.radius;
if (corner.splitCount <= 1)
{
AdjustMidpointHandleColor(corner.isBackFaced);
if (showAxis[0]) Handles.DrawWireArc(origin, normals[0], axes[1], corner.angle[1], radius);
if (showAxis[1]) Handles.DrawWireArc(origin, normals[1], axes[2], corner.angle[2], radius);
if (showAxis[2]) Handles.DrawWireArc(origin, normals[2], axes[0], corner.angle[0], radius);
}
else
{
var angleLength = Vector3.SignedAngle(Vector3.ProjectOnPlane(intersections[0], corner.cameraForward),
Vector3.ProjectOnPlane(intersections[1], corner.cameraForward), corner.cameraForward);
bool reversePolarity = angleLength < 0;
if (reversePolarity)
Handles.DrawWireArc(origin, corner.cameraForward, corner.points[1] - origin, -angleLength, radius);
else
Handles.DrawWireArc(origin, corner.cameraForward, corner.points[0] - origin, angleLength, radius);

var backfacedColor = GetStateColor(true);


var axesBackfaced = new bool3(math.length(intersections[0] - axes[0]) < kDistanceEpsilon || math.length(intersections[1] - axes[0]) < kDistanceEpsilon,
math.length(intersections[0] - axes[1]) < kDistanceEpsilon || math.length(intersections[1] - axes[1]) < kDistanceEpsilon,
math.length(intersections[0] - axes[2]) < kDistanceEpsilon || math.length(intersections[1] - axes[2]) < kDistanceEpsilon);
var color1 = reversePolarity ? color : backfacedColor;
var color2 = reversePolarity ? backfacedColor : color;
for (int A = 1, B = 2, C = 0; C < 3; A = B, B = C, C++)
{
if (corner.splitAxis[0][C] == corner.splitAxis[1][C])
{
if (!axesBackfaced[A]) { angleLength = Vector3.Angle(intersections[0], axes[A]); axesBackfaced[A] = (angleLength<kDegreeEpsilon || angleLength> corner.angle
if (!axesBackfaced[B]) { angleLength = Vector3.Angle(intersections[1], axes[A]); axesBackfaced[B] = (angleLength<kDegreeEpsilon || angleLength> corner.angle
}
else if (corner.splitAxis[0][C])
{
if (showAxis[C])
{
angleLength = Vector3.Angle(intersections[0], axes[A]);
Handles.color = color1; Handles.DrawWireArc(origin, normals[C], intersections[0], -angleLength, radius);
Handles.color = color2; Handles.DrawWireArc(origin, normals[C], intersections[0], corner.angle[A] - angleLength, radius);
}
axesBackfaced[A] = true;
}
else
//if (corner.splitAxis[1][C])
{
if (showAxis[C])
{
angleLength = Vector3.Angle(intersections[1], axes[A]);
Handles.color = color2; Handles.DrawWireArc(origin, normals[C], intersections[1], -angleLength, radius);
Handles.color = color1; Handles.DrawWireArc(origin, normals[C], intersections[1], corner.angle[A] - angleLength, radius);
}
axesBackfaced[B] = true;
}
}
// check for singularity
if (math.all(axesBackfaced))
axesBackfaced = corner.isBackFaced;
for (int A = 1, B = 2, C = 0; C < 3; A = B, B = C, C++)
{
if (!showAxis[C])
continue;
if (corner.splitAxis[0][C] == corner.splitAxis[1][C])
{
Handles.color = (axesBackfaced[B] && axesBackfaced[A]) ? color1 : color2;
Handles.DrawWireArc(origin, normals[C], axes[A], corner.angle[A], radius);
}
}
}
Handles.color = color;
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/PhysicsCapsuleBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class PhysicsCapsuleBoundsHandle : CapsuleBoundsHandle
{
static PhysicsBoundsHandleUtility.Corner[] s_Corners = new PhysicsBoundsHandleUtility.Corner[8];
protected override void DrawWireframe()
{
if (this.radius <= 0f)
{
base.DrawWireframe();
return;
}
var cameraPos = default(float3);
var cameraFwd = new float3 { z = 1f };
var cameraOrtho = true;
if (Camera.current != null)
{
cameraPos = Camera.current.transform.position;
cameraFwd = Camera.current.transform.forward;
cameraOrtho = Camera.current.orthographic;
}
var size = new float3(this.radius * 2f, this.radius * 2f, height);
var radius = this.radius;
var origin = (float3)this.center;
var bounds = new Bounds(this.center, size);
// 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;
var cameraCenter = (float3)invMatrix.MultiplyPoint(cameraPos);
var cameraForward = (float3)invMatrix.MultiplyVector(cameraFwd);
bool isCameraInsideBox = Camera.current != null
&& bounds.Contains(invMatrix.MultiplyPoint(cameraPos));
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(-1f, 1f, 1f), radius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, -1f, 1f), radius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 2, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, -1f), radius, 2, axes, isCameraInsideBox);
var corner = 0.5f * size - new float3(1f) * radius;
var axisx = new float3(1f, 0f, 0f);
var axisy = new float3(0f, 1f, 0f);
var axisz = new float3(0f, 0f, 1f);
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, -1f), quaternion.LookRotation(-axisz, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, 1f), quaternion.LookRotation(-axisx, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, 1f), quaternion.LookRotation(axisz, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, -1f), quaternion.LookRotation(axisx, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, -1f), quaternion.LookRotation(-axisx, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, 1f), quaternion.LookRotation(axisz, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, 1f), quaternion.LookRotation(axisx, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, -1f), quaternion.LookRotation(-axisz, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[0], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[3], new bool3(true, false, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[4], new bool3(true, false, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[7], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[1], new bool3(true, false, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[2], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[5], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[6], new bool3(true, false, 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/PhysicsSphereBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class PhysicsSphereBoundsHandle : SphereBoundsHandle
{
protected override void DrawWireframe()
{
bool x = IsAxisEnabled(Axes.X);
bool y = IsAxisEnabled(Axes.Y);
bool z = IsAxisEnabled(Axes.Z);

if (x && !y && !z)


Handles.DrawLine(Vector3.right * radius, Vector3.left * radius);
if (!x && y && !z)
Handles.DrawLine(Vector3.up * radius, Vector3.down * radius);
if (!x && !y && z)
Handles.DrawLine(Vector3.forward * radius, Vector3.back * radius);
const float kEpsilon = 0.000001F;
if (radius > 0)
{
var frontfacedColor = Handles.color;
var backfacedColor = Handles.color * new Color(1f, 1f, 1f, PhysicsBoundsHandleUtility.kBackfaceAlphaMultiplier);
var discVisible = new bool[]
{
y && z,
x && z,
x && y
};
var discOrientations = new float3[]
{
Vector3.right,
Vector3.up,
Vector3.forward
};
// 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;
var cameraCenter = Camera.current == null ? Vector3.zero : Camera.current.transform.position;
var cameraToCenter = center - invMatrix.MultiplyPoint(cameraCenter); // vector from camera to center
var sqrDistCameraToCenter = cameraToCenter.sqrMagnitude;
var sqrRadius = radius * radius; // squared radius
var isCameraOrthographic = Camera.current == null || Camera.current.orthographic;
var sqrOffset = isCameraOrthographic ? 0 : (sqrRadius * sqrRadius / sqrDistCameraToCenter); // squared distance from actual center to drawn disc center
var insideAmount = sqrOffset / sqrRadius;
if (insideAmount < 1)
{
if (math.abs(sqrDistCameraToCenter) >= kEpsilon)
{
using (new Handles.DrawingScope(frontfacedColor))
{
if (isCameraOrthographic)
{
var horizonRadius = radius;
var horizonCenter = center;
Handles.DrawWireDisc(horizonCenter, cameraToCenter, horizonRadius);
}
else
{
var horizonRadius = math.sqrt(sqrRadius - sqrOffset);
var horizonCenter = center - sqrRadius * cameraToCenter / sqrDistCameraToCenter;
Handles.DrawWireDisc(horizonCenter, cameraToCenter, horizonRadius);
}
}
var planeNormal = cameraToCenter.normalized;
for (int i = 0; i < 3; i++)
{
if (!discVisible[i])
continue;

var discOrientation = discOrientations[i];


var angleBetweenDiscAndNormal = math.acos(math.dot(discOrientation, planeNormal));
angleBetweenDiscAndNormal = (math.PI * 0.5f) - math.min(angleBetweenDiscAndNormal, math.PI - angleBetweenDiscAndNormal);
float f = math.tan(angleBetweenDiscAndNormal);
float g = math.sqrt(sqrOffset + f * f * sqrOffset) / radius;
if (g < 1)
{
var angleToHorizon = math.degrees(math.asin(g));
var discTangent = math.cross(discOrientation, planeNormal);
var vectorToPointOnHorizon = Quaternion.AngleAxis(angleToHorizon, discOrientation) * discTangent;
var horizonArcLength = (90 - angleToHorizon) * 2.0f;
using (new Handles.DrawingScope(frontfacedColor))
Handles.DrawWireArc(center, discOrientation, vectorToPointOnHorizon, horizonArcLength, radius);
using (new Handles.DrawingScope(backfacedColor))
Handles.DrawWireArc(center, discOrientation, vectorToPointOnHorizon, horizonArcLength - 360, radius);
}
else
{
using (new Handles.DrawingScope(backfacedColor))
Handles.DrawWireDisc(center, discOrientation, radius);
}
}
}
}
else
{
using (new Handles.DrawingScope(backfacedColor))
{
for (int i = 0; i < 3; i++)
{
var discOrientation = discOrientations[i];
Handles.DrawWireDisc(center, discOrientation, radius);
}
}
}
}
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/BaseDrawer.cs
using UnityEditor;
using UnityEngine;

namespace Unity.Physics.Editor
{
abstract class BaseDrawer : PropertyDrawer
{
protected abstract bool IsCompatible(SerializedProperty property);

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)


{
return IsCompatible(property)
? EditorGUI.GetPropertyHeight(property)
: EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (IsCompatible(property))
DoGUI(position, property, label);
else
EditorGUIControls.DisplayCompatibilityWarning(position, label, ObjectNames.NicifyVariableName(GetType().Name));
}

protected abstract void DoGUI(Rect position, SerializedProperty property, GUIContent label);


}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/EnumFlagsDrawer.cs
using System;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(EnumFlagsAttribute))]
class EnumFlagsDrawer : BaseDrawer
{
protected override bool IsCompatible(SerializedProperty property)
{
return property.propertyType == SerializedPropertyType.Enum;
}

protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)


{
EditorGUI.BeginProperty(position, label, property);
var value = property.longValue;
EditorGUI.BeginChangeCheck();
value = Convert.ToInt64(
EditorGUI.EnumFlagsField(position, label, (Enum)Enum.ToObject(fieldInfo.FieldType, value))
);
if (EditorGUI.EndChangeCheck())
property.longValue = value;

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;

protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)


{
EditorGUI.BeginProperty(position, label, property);
EditorGUI.PropertyField(
new Rect(position) { xMax = position.xMax - Styles.PopupWidth },
property.FindPropertyRelative("Value"),
label
);
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
EditorGUI.PropertyField(
new Rect(position) { xMin = position.xMax - Styles.PopupWidth + EditorGUIUtility.standardVerticalSpacing },
property.FindPropertyRelative("CombineMode"),
GUIContent.none
);
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/PhysicsMaterialPropertiesDrawer.cs
using System.Collections.Generic;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;

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)."
);
}

const string k_CollisionFilterGroupKey = "m_BelongsToCategories";


const string k_AdvancedGroupKey = "m_CustomMaterialTags";
Dictionary<string, SerializedObject> m_SerializedTemplates = new Dictionary<string, SerializedObject>();

SerializedProperty GetTemplateValueProperty(SerializedProperty property)


{
var key = property.propertyPath;
var template = property.FindPropertyRelative("m_Template").objectReferenceValue;
SerializedObject serializedTemplate;
if (
!m_SerializedTemplates.TryGetValue(key, out serializedTemplate)
|| serializedTemplate?.targetObject != template
)
m_SerializedTemplates[key] = serializedTemplate = template == null ? null : new SerializedObject(template);
serializedTemplate?.Update();
return serializedTemplate?.FindProperty("m_Value");
}

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");
}

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)


{
var templateValueProperty = GetTemplateValueProperty(property);
// m_CollisionResponse, collision filter foldout, advanced foldout
var height = 3f * EditorGUIUtility.singleLineHeight + 2f * EditorGUIUtility.standardVerticalSpacing;

// 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;
}

protected override bool IsCompatible(SerializedProperty property) => true;

static void DisplayOverridableProperty(


Rect position, GUIContent label, SerializedProperty toggle, SerializedProperty value, bool templateAssigned
)
{
if (templateAssigned)
{
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth -= 16f + EditorGUIUtility.standardVerticalSpacing;
var togglePosition = new Rect(position) { width = EditorGUIUtility.labelWidth + 16f + EditorGUIUtility.standardVerticalSpacing };
EditorGUI.PropertyField(togglePosition, toggle, label);
EditorGUIUtility.labelWidth = labelWidth;

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);
}
}

protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)


{
var template = property.FindPropertyRelative("m_Template");
var templateAssigned = template.objectReferenceValue != null;
var supportsTemplate = property.FindPropertyRelative("m_SupportsTemplate");
if (supportsTemplate.boolValue)
{
position.height = EditorGUI.GetPropertyHeight(template);
EditorGUI.PropertyField(position, template);

position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;


}

var templateValue = GetTemplateValueProperty(property);


FindToggleAndValueProperties(property, templateValue, "m_CollisionResponse", out var collisionResponseDropDown, out var collisionResponse);
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.CollisionResponseLabel, collisionResponseDropDown, collisionResponse, templateAssigned);
SerializedProperty toggle;

// Check if regular collider


CollisionResponsePolicy collisionResponseEnum = (CollisionResponsePolicy)collisionResponse.intValue;
if (collisionResponseEnum == CollisionResponsePolicy.Collide ||
collisionResponseEnum == CollisionResponsePolicy.CollideRaiseCollisionEvents)
{
FindToggleAndValueProperties(property, templateValue, "m_Friction", out toggle, out var friction);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.FrictionLabel, toggle, friction, templateAssigned);
FindToggleAndValueProperties(property, templateValue, "m_Restitution", out toggle, out var restitution);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.RestitutionLabel, toggle, restitution, templateAssigned);
}

// collision filter group


var collisionFilterGroup = property.FindPropertyRelative(k_CollisionFilterGroupKey);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
collisionFilterGroup.isExpanded =
EditorGUI.Foldout(position, collisionFilterGroup.isExpanded, Content.CollisionFilterGroupFoldout, true);
if (collisionFilterGroup.isExpanded)
{
++EditorGUI.indentLevel;

FindToggleAndValueProperties(property, templateValue, "m_BelongsToCategories", out toggle, out var belongsTo);


position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.BelongsToLabel, toggle, belongsTo, templateAssigned);
FindToggleAndValueProperties(property, templateValue, "m_CollidesWithCategories", out toggle, out var collidesWith);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.CollidesWithLabel, toggle, collidesWith, templateAssigned);
--EditorGUI.indentLevel;
}
// advanced group
var advancedGroup = property.FindPropertyRelative(k_AdvancedGroupKey);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
advancedGroup.isExpanded =
EditorGUI.Foldout(position, advancedGroup.isExpanded, Content.AdvancedGroupFoldout, true);
if (advancedGroup.isExpanded)
{
++EditorGUI.indentLevel;
FindToggleAndValueProperties(property, templateValue, "m_CustomMaterialTags", out toggle, out var customFlags);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.CustomFlagsLabel, toggle, customFlags, templateAssigned);

--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");

public static readonly string MultipleAssetsTooltip =


L10n.Tr("Multiple {0} assets found. UI will display labels defined in {1}.");

public static readonly GUIContent MultipleAssetsWarning =


new GUIContent { image = EditorGUIUtility.Load("console.warnicon") as Texture };
}
protected abstract int MaxNumCategories { get; }
protected abstract string DefaultCategoryName { get; }
internal string FirstChildPropertyPath { get; set; } // TODO: remove when all usages of bool[] are migrated

string DefaultFormatString => L10n.Tr($"(Undefined {DefaultCategoryName})");


string[] DefaultOptions =>
m_DefaultOptions ?? (
m_DefaultOptions =
Enumerable.Range(0, MaxNumCategories)
.Select(i => string.Format(DefaultFormatString, i))
.ToArray()
);
string[] m_DefaultOptions;

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];

m_Options[i] = $"{i}: {m_Options[i]}";


}
return m_Options;
}

string[] m_Options;

static string GetButtonLabel(int value, IReadOnlyList<string> optionNames)


{
switch (value)
{
case 0:
return Styles.NothingName;
case ~0:
return Styles.EverythingName;
default:
{
for (var i = 0; i < 32; i++)
{
if (value == 1 << i)
return optionNames[i];
}
break;
}
}
return Styles.MixedName;
}

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;
}

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)


{
if (m_NamesAssets?.Length > 1)
position.xMax -= EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;

EditorGUI.BeginProperty(position, label, property);

var controlPosition = EditorGUI.PrefixLabel(position, label);


var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
var showMixed = EditorGUI.showMixedValue;

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];

public static void SoftSlider(


Rect position, GUIContent label, SerializedProperty property,
float sliderMin, float sliderMax,
float textFieldMin, float textFieldMax
)
{
if (property.propertyType != SerializedPropertyType.Float)
{
DisplayCompatibilityWarning(position, label, property.propertyType.ToString());
}
else if (k_SoftSlider == null)
{
EditorGUI.BeginChangeCheck();
EditorGUI.PropertyField(position, property, label);
if (EditorGUI.EndChangeCheck())
property.floatValue = math.clamp(property.floatValue, textFieldMin, textFieldMax);
}
else
{
k_SoftSliderArgs[0] = position;
k_SoftSliderArgs[1] = label;
k_SoftSliderArgs[2] = property.floatValue;
k_SoftSliderArgs[3] = sliderMin;
k_SoftSliderArgs[4] = sliderMax;
k_SoftSliderArgs[5] = textFieldMin;
k_SoftSliderArgs[6] = textFieldMax;
EditorGUI.BeginProperty(position, label, property);
EditorGUI.BeginChangeCheck();
var result = k_SoftSlider.Invoke(null, k_SoftSliderArgs);
if (EditorGUI.EndChangeCheck())
property.floatValue = (float)result;
EditorGUI.EndProperty();
}
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/ManipulatorUtility.cs
using System;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Editor
{
enum MatrixState
{
UniformScale,
NonUniformScale,
ZeroScale,
NotValidTRS
}
static class ManipulatorUtility
{
public static MatrixState GetMatrixState(ref float4x4 localToWorld)
{
if (
localToWorld.c0.w != 0f
|| localToWorld.c1.w != 0f
|| localToWorld.c2.w != 0f
|| localToWorld.c3.w != 1f
)
return MatrixState.NotValidTRS;
var m = new float3x3(localToWorld.c0.xyz, localToWorld.c1.xyz, localToWorld.c2.xyz);
var lossyScale = new float3(math.length(m.c0.xyz), math.length(m.c1.xyz), math.length(m.c2.xyz));
if (math.determinant(m) < 0f)
lossyScale.x *= -1f;
if (math.lengthsq(lossyScale) == 0f)
return MatrixState.ZeroScale;
return math.abs(math.cmax(lossyScale)) - math.abs(math.cmin(lossyScale)) > 0.000001f
? MatrixState.NonUniformScale
: MatrixState.UniformScale;
}
}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/SceneViewUtility.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEngine;

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;
}

const string k_NotificationsPrefKey = "SceneView/Tools/Notifications";


const bool k_DefaultNotifications = true;
const string k_NotificationSpeedPrefKey = "SceneView/Tools/Notification Speed";
const float k_DefaultNotificationsSpeed = 20f;

const float k_NotificationDuration = 1f;


const float k_NotificationFadeInTime = 0.04f;
const float k_NotificationFadeOutTime = 0.2f;
static readonly AnimationCurve k_NotificationFadeCurve = new AnimationCurve
{
keys = new[]
{
new Keyframe { time = 0f, value = 0f, outTangent = 1f / k_NotificationFadeInTime },
new Keyframe { time = k_NotificationFadeInTime, value = 1f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_NotificationDuration - k_NotificationFadeOutTime, value = 1f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_NotificationDuration, value = 0f, inTangent = -1f / k_NotificationFadeOutTime }
},
postWrapMode = WrapMode.Clamp,
preWrapMode = WrapMode.Clamp
};
const float k_IndeterminateProgressCurveDuration = 2f;
static readonly AnimationCurve k_IndeterminateProgressCurveLeftMargin = new AnimationCurve
{
keys = new[]
{
new Keyframe { time = 0f, value = 0f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration / 2f, value = 0.25f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration, value = 1f, inTangent = 0f, outTangent = 0f }
},
postWrapMode = WrapMode.Loop,
preWrapMode = WrapMode.Loop
};
static readonly AnimationCurve k_IndeterminateProgressCurveRightMargin = new AnimationCurve
{
keys = new[]
{
new Keyframe { time = 0f, value = 1f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration / 2f, value = 0f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration, value = 0f, inTangent = 0f, outTangent = 0f }
},
postWrapMode = WrapMode.Loop,
preWrapMode = WrapMode.Loop
};

static string s_StatusMessage;


static DateTime s_StartTime;
static bool s_IsTemporary;
static Func<float> s_GetProgress;

public static void DisplayProgressNotification(string message, Func<float> getProgress) =>


// insert an extra line to make room for progress bar
DisplayNotificationInSceneView(getProgress == null ? message : $"{message}\n", false, getProgress);
public static void DisplayPersistentNotification(string message) =>
DisplayNotificationInSceneView(message, false, null);

public static void DisplayTemporaryNotification(string message) =>


DisplayNotificationInSceneView(message, true, null);

static void DisplayNotificationInSceneView(string message, bool temporary, Func<float> getProgress)


{
s_StatusMessage = message ?? string.Empty;
s_StartTime = DateTime.Now;
s_IsTemporary = temporary;
s_GetProgress = getProgress;
ClearNotificationInSceneView();
SceneView.duringSceneGui += ToolNotificationCallback;
SceneView.RepaintAll();
}

static void ToolNotificationCallback(SceneView obj)


{
if (Camera.current == null)
return;
var duration = math.max(s_StatusMessage.Length, 1)
/ EditorPrefs.GetFloat(k_NotificationSpeedPrefKey, k_DefaultNotificationsSpeed);
var t = (float)(DateTime.Now - s_StartTime).TotalSeconds;
if (
s_IsTemporary
&& (t >= duration || !EditorPrefs.GetBool(k_NotificationsPrefKey, k_DefaultNotifications))
)
{
ClearNotificationInSceneView();
}
else
{
Handles.BeginGUI();
var color = GUI.color;
var progress = s_GetProgress?.Invoke() ?? 0f;
GUI.color *=
new Color(1f, 1f, 1f, math.max(k_NotificationFadeCurve.Evaluate(math.abs(t) / duration), progress));
var rect = new Rect { size = Camera.current.pixelRect.size / EditorGUIUtility.pixelsPerPoint };
using (new GUILayout.AreaScope(rect))
using (new GUILayout.HorizontalScope())
{
GUILayout.FlexibleSpace();
using (new GUILayout.VerticalScope())
{
GUILayout.Space(rect.height * 0.75f);
GUILayout.FlexibleSpace();
var maxWidth = rect.width * 0.5f;
GUILayout.Box(s_StatusMessage, Styles.SceneViewStatusMessage, GUILayout.MaxWidth(maxWidth));
if (s_GetProgress != null)
{
rect = GUILayoutUtility.GetLastRect();
rect = Styles.SceneViewStatusMessage.padding.Remove(rect);
rect.y = rect.yMax - Styles.ProgressBarTrack.fixedHeight;
rect.height = Styles.ProgressBarTrack.fixedHeight;
var c = GUI.color;
GUI.color *= Color.black;
GUI.Box(rect, GUIContent.none, Styles.ProgressBarTrack);
GUI.color = c;
if (progress >= 0f && progress <= 1f)
{
rect.width *= progress;
}
else
{
var w = rect.width;
rect.xMin = rect.xMin + w * k_IndeterminateProgressCurveLeftMargin.Evaluate(t);
rect.xMax = rect.xMax - w * k_IndeterminateProgressCurveRightMargin.Evaluate(t);
}
GUI.Box(rect, GUIContent.none, Styles.ProgressBarIndicator);
}
GUILayout.FlexibleSpace();
}
GUILayout.FlexibleSpace();
}
GUI.color = color;
Handles.EndGUI();
}
SceneView.RepaintAll();
}

public static void ClearNotificationInSceneView() => SceneView.duringSceneGui -= ToolNotificationCallback;


}
}
Basic/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/StatusMessageUtility.cs
using System.Collections.Generic;
using System.Linq;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
using UnityObject = UnityEngine.Object;
using LegacyRigidBody = UnityEngine.Rigidbody;

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;

var targetType = c.GetType();


// only bodies (both explicit and implicit static bodies) will emit a message
if (
targetType == typeof(PhysicsBodyAuthoring)
|| targetType == typeof(LegacyRigidBody)
|| c.GetComponent<PhysicsBodyAuthoring>() == null
&& c.GetComponent<LegacyRigidBody>() == null
)
++numChildTargets;
}
switch (numChildTargets)
{
case 0:
return MessageType.None;
case 1:
statusMessage =
L10n.Tr("Target will be un-parented during the conversion process in order to take part in physics simulation.");
return MessageType.Warning;
default:
statusMessage =
L10n.Tr("One or more targets will be un-parented during the conversion process in order to take part in physics simulation.");
return MessageType.Warning;
}
}
public static MessageType GetMatrixStatusMessage(
IReadOnlyList<MatrixState> matrixStates, out string statusMessage
)
{
statusMessage = string.Empty;
if (matrixStates.Contains(MatrixState.NotValidTRS))
{
statusMessage = L10n.Tr(
matrixStates.Count == 1
? "Target's local-to-world matrix is not a valid transformation."
: "One or more targets' local-to-world matrices are not valid transformations."
);
return MessageType.Error;
}
if (matrixStates.Contains(MatrixState.ZeroScale))
{
statusMessage =
L10n.Tr(matrixStates.Count == 1 ? "Target has zero scale." : "One or more targets has zero scale.");
return MessageType.Warning;
}

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;

// 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;
// If 1, then it is a vertex collision
// If 2, then it is an edge collision
// If 3 or more, then it is a face collision
public int NumberOfContactPoints;
// Estimated impulse applied
public float EstimatedImpulse;
// Average contact point position
public float3 AverageContactPointPosition;
public Details(int numContactPoints, float estimatedImpulse, float3 averageContactPosition)
{
IsValid = (0 < numContactPoints); // Should we add a max check?
NumberOfContactPoints = numContactPoints;
EstimatedImpulse = estimatedImpulse;
AverageContactPointPosition = averageContactPosition;
}
}

// Returns the other entity in EntityPair, if provided with other one


public Entity GetOtherEntity(Entity entity)
{
Assert.IsTrue((entity == EntityA) || (entity == EntityB));
return entity == EntityA ? EntityB : EntityA;
}

// 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;

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);
}
}
}
}
Basic/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;

public ComponentHandles(ref SystemState systemState)


{
EventExcludes = systemState.GetComponentLookup<DummyExcludeComponent>(true);
EventDetails = systemState.GetComponentLookup<StatefulCollisionEventDetails>(true);
EventBuffers = systemState.GetBufferLookup<StatefulCollisionEvent>(false);
}

public void Update(ref SystemState systemState)


{
EventExcludes.Update(ref systemState);
EventBuffers.Update(ref systemState);
EventDetails.Update(ref systemState);
}
}

[BurstCompile]
public void OnCreate(ref SystemState state)
{
m_StateFulEventBuffers = new StatefulSimulationEventBuffers<StatefulCollisionEvent>();
m_StateFulEventBuffers.AllocateBuffers();
state.RequireForUpdate<StatefulCollisionEvent>();

m_Handles = new ComponentHandles(ref state);


}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
m_StateFulEventBuffers.Dispose();
}
[BurstCompile]
public partial struct ClearCollisionEventDynamicBufferJob : IJobEntity
{
public void Execute(ref DynamicBuffer<StatefulCollisionEvent> eventBuffer) => eventBuffer.Clear();
}

[BurstCompile]
public void OnUpdate(ref SystemState state)
{
m_Handles.Update(ref state);

state.Dependency = new ClearCollisionEventDynamicBufferJob()


.ScheduleParallel(state.Dependency);

m_StateFulEventBuffers.SwapBuffers();

var currentEvents = m_StateFulEventBuffers.Current;


var previousEvents = m_StateFulEventBuffers.Previous;
state.Dependency = new StatefulEventCollectionJobs.
CollectCollisionEventsWithDetails
{
CollisionEvents = currentEvents,
PhysicsWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().PhysicsWorld,
EventDetails = m_Handles.EventDetails
}.Schedule(SystemAPI.GetSingleton<SimulationSingleton>(), state.Dependency);

state.Dependency = new StatefulEventCollectionJobs.


ConvertEventStreamToDynamicBufferJob<StatefulCollisionEvent, DummyExcludeComponent>
{
CurrentEvents = currentEvents,
PreviousEvents = previousEvents,
EventBuffers = m_Handles.EventBuffers,

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;

public void AllocateBuffers()


{
Previous = new NativeList<T>(Allocator.Persistent);
Current = new NativeList<T>(Allocator.Persistent);
}
public void Dispose()
{
if (Previous.IsCreated) Previous.Dispose();
if (Current.IsCreated) Current.Dispose();
}

public void SwapBuffers()


{
var tmp = Previous;
Previous = Current;
Current = tmp;
Current.Clear();
}
/// <summary>
/// Returns a sorted combined list of stateful events based on the Previous and Current event frames.
/// Note: ensure that the frame buffers are sorted by calling SortBuffers first.
/// </summary>
/// <param name="statefulEvents"></param>
/// <param name="sortCurrent">Specifies whether the Current events list needs to be sorted first.</param>
public void GetStatefulEvents(NativeList<T> statefulEvents, bool sortCurrent = true) => GetStatefulEvents(Previous, Current, statefulEvents, sortCurrent);

/// <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;

public void Execute(CollisionEvent collisionEvent)


{
var statefulCollisionEvent = new StatefulCollisionEvent(collisionEvent);

// Check if we should calculate the collision details


bool calculateDetails = ForceCalculateDetails;
if (!calculateDetails && EventDetails.HasComponent(collisionEvent.EntityA))
{
calculateDetails = EventDetails[collisionEvent.EntityA].CalculateDetails;
}
if (!calculateDetails && EventDetails.HasComponent(collisionEvent.EntityB))
{
calculateDetails = EventDetails[collisionEvent.EntityB].CalculateDetails;
}
if (calculateDetails)
{
var details = collisionEvent.CalculateDetails(ref PhysicsWorld);
statefulCollisionEvent.CollisionDetails = new StatefulCollisionEvent.Details(
details.EstimatedContactPointPositions.Length,
details.EstimatedImpulse,
details.AverageContactPointPosition);
}

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);

StatefulSimulationEventBuffers<T>.GetStatefulEvents(PreviousEvents, CurrentEvents, statefulEvents);

for (int i = 0; i < statefulEvents.Length; i++)


{
var statefulEvent = statefulEvents[i];

var addToEntityA = EventBuffers.HasBuffer(statefulEvent.EntityA) && (!UseExcludeComponent || !EventExcludeLookup.HasComponent(statefulEvent.EntityA));


var addToEntityB = EventBuffers.HasBuffer(statefulEvent.EntityB) && (!UseExcludeComponent || !EventExcludeLookup.HasComponent(statefulEvent.EntityA));

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; }

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);


}
}
Basic/Assets/Scripts/Physics/StatefulEvents/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 {}
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;

public ComponentHandles(ref SystemState systemState)


{
EventExcludes = systemState.GetComponentLookup<StatefulTriggerEventExclude>(true);
EventBuffers = systemState.GetBufferLookup<StatefulTriggerEvent>();
}

public void Update(ref SystemState systemState)


{
EventExcludes.Update(ref systemState);
EventBuffers.Update(ref systemState);
}
}

[BurstCompile]
public void OnCreate(ref SystemState state)
{
EntityQueryBuilder builder = new EntityQueryBuilder(Allocator.Temp)
.WithAllRW<StatefulTriggerEvent>()
.WithNone<StatefulTriggerEventExclude>();

m_StateFulEventBuffers = new StatefulSimulationEventBuffers<StatefulTriggerEvent>();


m_StateFulEventBuffers.AllocateBuffers();

m_TriggerEventQuery = state.GetEntityQuery(builder);
state.RequireForUpdate(m_TriggerEventQuery);

m_ComponentHandles = new ComponentHandles(ref state);


}

[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);

state.Dependency = new ClearTriggerEventDynamicBufferJob()


.ScheduleParallel(m_TriggerEventQuery, state.Dependency);

m_StateFulEventBuffers.SwapBuffers();

var currentEvents = m_StateFulEventBuffers.Current;


var previousEvents = m_StateFulEventBuffers.Previous;

state.Dependency = new StatefulEventCollectionJobs.CollectTriggerEvents


{
TriggerEvents = currentEvents
}.Schedule(SystemAPI.GetSingleton<SimulationSingleton>(), state.Dependency);

state.Dependency = new StatefulEventCollectionJobs


.ConvertEventStreamToDynamicBufferJob<StatefulTriggerEvent, StatefulTriggerEventExclude>
{
CurrentEvents = currentEvents,
PreviousEvents = previousEvents,
EventBuffers = m_ComponentHandles.EventBuffers,

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;

protected override void OnCreate()


{
base.OnCreate();

RequireForUpdate<FixedTickSystem.Singleton>();
RequireForUpdate(SystemAPI.QueryBuilder().WithAll<BasicPlayer, BasicPlayerInputs>().Build());

InputActions = new BasicInputActions();


InputActions.Enable();
InputActions.DefaultMap.Enable();
}
protected override void OnUpdate()
{
BasicInputActions.DefaultMapActions defaultMapActions = InputActions.DefaultMap;
uint fixedTick = SystemAPI.GetSingleton<FixedTickSystem.Singleton>().Tick;

foreach (var (playerInputs, player) in SystemAPI.Query<RefRW<BasicPlayerInputs>, BasicPlayer>())


{
playerInputs.ValueRW.MoveInput = Vector2.ClampMagnitude(defaultMapActions.Move.ReadValue<Vector2>(), 1f);
playerInputs.ValueRW.CameraLookInput = default;
if(math.lengthsq(defaultMapActions.LookConst.ReadValue<Vector2>()) > math.lengthsq(defaultMapActions.LookDelta.ReadValue<Vector2>()))
{
playerInputs.ValueRW.CameraLookInput = defaultMapActions.LookConst.ReadValue<Vector2>() * SystemAPI.Time.DeltaTime;
}
else
{
playerInputs.ValueRW.CameraLookInput = defaultMapActions.LookDelta.ReadValue<Vector2>();
}
playerInputs.ValueRW.CameraZoomInput = defaultMapActions.Scroll.ReadValue<float>();
if (defaultMapActions.Jump.WasPressedThisFrame())
{
playerInputs.ValueRW.JumpPressed.Set(fixedTick);
}
}
}
}
[UpdateInGroup(typeof(SimulationSystemGroup), OrderFirst = true)]
[UpdateBefore(typeof(FixedStepSimulationSystemGroup))]
[BurstCompile]
public partial struct BasicPlayerVariableStepControlSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<BasicPlayer, BasicPlayerInputs>().Build());
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
foreach (var (playerInputs, player) in SystemAPI.Query<BasicPlayerInputs, BasicPlayer>().WithAll<Simulate>())
{
if (SystemAPI.HasComponent<OrbitCameraControl>(player.ControlledCamera))
{
OrbitCameraControl cameraControl = SystemAPI.GetComponent<OrbitCameraControl>(player.ControlledCamera);

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());
}

public void OnDestroy(ref SystemState state)


{ }

public void OnUpdate(ref SystemState state)


{
uint fixedTick = SystemAPI.GetSingleton<FixedTickSystem.Singleton>().Tick;
foreach (var (playerInputs, player) in SystemAPI.Query<RefRW<BasicPlayerInputs>, BasicPlayer>().WithAll<Simulate>())
{
if (SystemAPI.HasComponent<BasicCharacterControl>(player.ControlledCharacter))
{
BasicCharacterControl characterControl = SystemAPI.GetComponent<BasicCharacterControl>(player.ControlledCharacter);
float3 characterUp = MathUtilities.GetUpFromRotation(SystemAPI.GetComponent<LocalTransform>(player.ControlledCharacter).Rotation);

// 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;
}
float3 cameraForwardOnUpPlane = math.normalizesafe(MathUtilities.ProjectOnPlane(MathUtilities.GetForwardFromRotation(cameraRotation), characterUp));
float3 cameraRight = MathUtilities.GetRightFromRotation(cameraRotation);
// Move
characterControl.MoveVector = (playerInputs.ValueRW.MoveInput.y * cameraForwardOnUpPlane) + (playerInputs.ValueRW.MoveInput.x * cameraRight);
characterControl.MoveVector = MathUtilities.ClampToMaxLength(characterControl.MoveVector, 1f);

// 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;

public partial class @FPSInputActions: IInputActionCollection2, IDisposable


{
public InputActionAsset asset { get; }
public @FPSInputActions()
{
asset = InputActionAsset.FromJson(@"{
""name"": ""FPSInputActions"",
""maps"": [
{
""name"": ""DefaultMap"",
""id"": ""46bf95ba-b7a8-4da1-a4d5-ad49677d3765"",
""actions"": [
{
""name"": ""Move"",
""type"": ""Value"",
""id"": ""1b319d9c-b705-46f9-b440-deabe5108883"",
""expectedControlType"": """",
""processors"": ""Clamp(max=1)"",
""interactions"": """",
""initialStateCheck"": true
},
{
""name"": ""LookDelta"",
""type"": ""Value"",
""id"": ""9ac4461b-a0bd-43bc-9258-03bf2a17ca60"",
""expectedControlType"": ""Vector2"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": true
},
{
""name"": ""LookConst"",
""type"": ""Value"",
""id"": ""847bdfb6-f46b-498d-b93b-4647302db5ac"",
""expectedControlType"": """",
""processors"": """",
""interactions"": """",
""initialStateCheck"": true
},
{
""name"": ""Scroll"",
""type"": ""Value"",
""id"": ""b58e7016-1fbe-440f-851d-fe5dc0b9bdf5"",
""expectedControlType"": ""Analog"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": true
},
{
""name"": ""Jump"",
""type"": ""Value"",
""id"": ""73c5ed6d-6e83-4693-857f-7a639b926770"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": true
},
{
""name"": ""Shoot"",
""type"": ""Button"",
""id"": ""fe797924-662a-430c-adec-0ad8e35f90c1"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
},
{
""name"": ""Aim"",
""type"": ""Value"",
""id"": ""025599b3-e820-4333-99db-6432435a6567"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": true
},
{
""name"": ""ToggleMenu"",
""type"": ""Button"",
""id"": ""c67ae77d-ac15-40e3-8f93-54aae16763f7"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
},
{
""name"": ""SpectatorVertical"",
""type"": ""Value"",
""id"": ""40dffe79-c12a-4c22-8beb-c43868c3f55b"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": true
}
],
""bindings"": [
{
""name"": ""Arrows"",
""id"": ""57a3c129-4935-42e8-a713-862f74ef7291"",
""path"": ""2DVector(normalize=false)"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Move"",
""isComposite"": true,
""isPartOfComposite"": false
},
{
""name"": ""up"",
""id"": ""df9b328d-3e19-4d55-8b87-b363ce2180f5"",
""path"": ""<Keyboard>/w"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": ""down"",
""id"": ""e12587eb-6fef-49da-8ea2-5639a419e541"",
""path"": ""<Keyboard>/s"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": ""left"",
""id"": ""b4c41209-0011-46e0-91fe-210de67b2e01"",
""path"": ""<Keyboard>/a"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": ""right"",
""id"": ""787aa252-db2b-485b-91df-c886ca6889fe"",
""path"": ""<Keyboard>/d"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": """",
""id"": ""f706f9e6-91c2-47f7-a528-f703615fe4f0"",
""path"": ""<Gamepad>/leftStick"",
""interactions"": """",
""processors"": ""StickDeadzone(min=0.1,max=1)"",
""groups"": """",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""f10408b2-1e7d-4dbe-b86f-fc66f618ec70"",
""path"": ""<Mouse>/delta"",
""interactions"": """",
""processors"": ""ScaleVector2(x=0.05,y=0.05)"",
""groups"": """",
""action"": ""LookDelta"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""14ff7398-fb3b-4aad-a46c-5776489455e5"",
""path"": ""<Keyboard>/space"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Jump"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""c29791b5-ad84-48b8-a28b-7f9fc41ff7f9"",
""path"": ""<Gamepad>/buttonSouth"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Jump"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""ddf3d29a-c6cc-4cd7-af23-85f44d1ec93a"",
""path"": ""<Mouse>/scroll/y"",
""interactions"": """",
""processors"": ""Scale(factor=0.1),Invert"",
""groups"": """",
""action"": ""Scroll"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""3789536a-16ff-4409-b7e5-8ac630a304b9"",
""path"": ""<Mouse>/leftButton"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Shoot"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""df640d72-e032-4852-808d-495f215c9c9d"",
""path"": ""<Gamepad>/rightShoulder"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Shoot"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""fa9e117b-f5d4-49e4-b431-f90b8df669fa"",
""path"": ""<Mouse>/rightButton"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Aim"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""39dce1d2-a182-4885-a5ac-fa83a5b86f7b"",
""path"": ""<Gamepad>/leftShoulder"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Aim"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""83ae0f87-a0ba-46ae-bda3-8d91c2139b3c"",
""path"": ""<Gamepad>/rightStick"",
""interactions"": """",
""processors"": ""StickDeadzone(min=0.2,max=1),ScaleVector2(x=70,y=70)"",
""groups"": """",
""action"": ""LookConst"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""5e03fc0c-1dee-4961-86a1-2083373cf03a"",
""path"": ""<Keyboard>/escape"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""ToggleMenu"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""211fc4e7-5df2-42bd-a0a8-2ba99b872579"",
""path"": ""<Gamepad>/start"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""ToggleMenu"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": ""1D Axis"",
""id"": ""ffbfe814-24d9-4578-95ef-79b6fb432177"",
""path"": ""1DAxis"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""SpectatorVertical"",
""isComposite"": true,
""isPartOfComposite"": false
},
{
""name"": ""negative"",
""id"": ""13f4b2f5-2ab5-4681-87fd-dd9aceed3773"",
""path"": ""<Keyboard>/q"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""SpectatorVertical"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": ""positive"",
""id"": ""627c7565-ac74-43f6-bc92-ee70876a24a4"",
""path"": ""<Keyboard>/e"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""SpectatorVertical"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": ""1D Axis"",
""id"": ""713ad8c1-e228-4c33-84af-3e35c0b6414f"",
""path"": ""1DAxis"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""SpectatorVertical"",
""isComposite"": true,
""isPartOfComposite"": false
},
{
""name"": ""negative"",
""id"": ""4a5e1f96-8ca1-4065-bb53-ae9597a5bcd2"",
""path"": ""<Gamepad>/leftTrigger"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""SpectatorVertical"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": ""positive"",
""id"": ""91914b98-effb-40a2-960a-1d355e60858e"",
""path"": ""<Gamepad>/rightTrigger"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""SpectatorVertical"",
""isComposite"": false,
""isPartOfComposite"": true
}
]
}
],
""controlSchemes"": []
}");
// DefaultMap
m_DefaultMap = asset.FindActionMap("DefaultMap", throwIfNotFound: true);
m_DefaultMap_Move = m_DefaultMap.FindAction("Move", throwIfNotFound: true);
m_DefaultMap_LookDelta = m_DefaultMap.FindAction("LookDelta", throwIfNotFound: true);
m_DefaultMap_LookConst = m_DefaultMap.FindAction("LookConst", throwIfNotFound: true);
m_DefaultMap_Scroll = m_DefaultMap.FindAction("Scroll", throwIfNotFound: true);
m_DefaultMap_Jump = m_DefaultMap.FindAction("Jump", throwIfNotFound: true);
m_DefaultMap_Shoot = m_DefaultMap.FindAction("Shoot", throwIfNotFound: true);
m_DefaultMap_Aim = m_DefaultMap.FindAction("Aim", throwIfNotFound: true);
m_DefaultMap_ToggleMenu = m_DefaultMap.FindAction("ToggleMenu", throwIfNotFound: true);
m_DefaultMap_SpectatorVertical = m_DefaultMap.FindAction("SpectatorVertical", throwIfNotFound: true);
}

public void Dispose()


{
UnityEngine.Object.Destroy(asset);
}
public InputBinding? bindingMask
{
get => asset.bindingMask;
set => asset.bindingMask = value;
}
public ReadOnlyArray<InputDevice>? devices
{
get => asset.devices;
set => asset.devices = value;
}
public ReadOnlyArray<InputControlScheme> controlSchemes => asset.controlSchemes;
public bool Contains(InputAction action)
{
return asset.Contains(action);
}
public IEnumerator<InputAction> GetEnumerator()
{
return asset.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Enable()
{
asset.Enable();
}
public void Disable()
{
asset.Disable();
}
public IEnumerable<InputBinding> bindings => asset.bindings;
public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false)
{
return asset.FindAction(actionNameOrId, throwIfNotFound);
}
public int FindBinding(InputBinding bindingMask, out InputAction action)
{
return asset.FindBinding(bindingMask, out action);
}
// DefaultMap
private readonly InputActionMap m_DefaultMap;
private List<IDefaultMapActions> m_DefaultMapActionsCallbackInterfaces = new List<IDefaultMapActions>();
private readonly InputAction m_DefaultMap_Move;
private readonly InputAction m_DefaultMap_LookDelta;
private readonly InputAction m_DefaultMap_LookConst;
private readonly InputAction m_DefaultMap_Scroll;
private readonly InputAction m_DefaultMap_Jump;
private readonly InputAction m_DefaultMap_Shoot;
private readonly InputAction m_DefaultMap_Aim;
private readonly InputAction m_DefaultMap_ToggleMenu;
private readonly InputAction m_DefaultMap_SpectatorVertical;
public struct DefaultMapActions
{
private @FPSInputActions m_Wrapper;
public DefaultMapActions(@FPSInputActions wrapper) { m_Wrapper = wrapper; }
public InputAction @Move => m_Wrapper.m_DefaultMap_Move;
public InputAction @LookDelta => m_Wrapper.m_DefaultMap_LookDelta;
public InputAction @LookConst => m_Wrapper.m_DefaultMap_LookConst;
public InputAction @Scroll => m_Wrapper.m_DefaultMap_Scroll;
public InputAction @Jump => m_Wrapper.m_DefaultMap_Jump;
public InputAction @Shoot => m_Wrapper.m_DefaultMap_Shoot;
public InputAction @Aim => m_Wrapper.m_DefaultMap_Aim;
public InputAction @ToggleMenu => m_Wrapper.m_DefaultMap_ToggleMenu;
public InputAction @SpectatorVertical => m_Wrapper.m_DefaultMap_SpectatorVertical;
public InputActionMap Get() { return m_Wrapper.m_DefaultMap; }
public void Enable() { Get().Enable(); }
public void Disable() { Get().Disable(); }
public bool enabled => Get().enabled;
public static implicit operator InputActionMap(DefaultMapActions set) { return set.Get(); }
public void AddCallbacks(IDefaultMapActions instance)
{
if (instance == null || m_Wrapper.m_DefaultMapActionsCallbackInterfaces.Contains(instance)) return;
m_Wrapper.m_DefaultMapActionsCallbackInterfaces.Add(instance);
@Move.started += instance.OnMove;
@Move.performed += instance.OnMove;
@Move.canceled += instance.OnMove;
@LookDelta.started += instance.OnLookDelta;
@LookDelta.performed += instance.OnLookDelta;
@LookDelta.canceled += instance.OnLookDelta;
@LookConst.started += instance.OnLookConst;
@LookConst.performed += instance.OnLookConst;
@LookConst.canceled += instance.OnLookConst;
@Scroll.started += instance.OnScroll;
@Scroll.performed += instance.OnScroll;
@Scroll.canceled += instance.OnScroll;
@Jump.started += instance.OnJump;
@Jump.performed += instance.OnJump;
@Jump.canceled += instance.OnJump;
@Shoot.started += instance.OnShoot;
@Shoot.performed += instance.OnShoot;
@Shoot.canceled += instance.OnShoot;
@Aim.started += instance.OnAim;
@Aim.performed += instance.OnAim;
@Aim.canceled += instance.OnAim;
@ToggleMenu.started += instance.OnToggleMenu;
@ToggleMenu.performed += instance.OnToggleMenu;
@ToggleMenu.canceled += instance.OnToggleMenu;
@SpectatorVertical.started += instance.OnSpectatorVertical;
@SpectatorVertical.performed += instance.OnSpectatorVertical;
@SpectatorVertical.canceled += instance.OnSpectatorVertical;
}
private void UnregisterCallbacks(IDefaultMapActions instance)
{
@Move.started -= instance.OnMove;
@Move.performed -= instance.OnMove;
@Move.canceled -= instance.OnMove;
@LookDelta.started -= instance.OnLookDelta;
@LookDelta.performed -= instance.OnLookDelta;
@LookDelta.canceled -= instance.OnLookDelta;
@LookConst.started -= instance.OnLookConst;
@LookConst.performed -= instance.OnLookConst;
@LookConst.canceled -= instance.OnLookConst;
@Scroll.started -= instance.OnScroll;
@Scroll.performed -= instance.OnScroll;
@Scroll.canceled -= instance.OnScroll;
@Jump.started -= instance.OnJump;
@Jump.performed -= instance.OnJump;
@Jump.canceled -= instance.OnJump;
@Shoot.started -= instance.OnShoot;
@Shoot.performed -= instance.OnShoot;
@Shoot.canceled -= instance.OnShoot;
@Aim.started -= instance.OnAim;
@Aim.performed -= instance.OnAim;
@Aim.canceled -= instance.OnAim;
@ToggleMenu.started -= instance.OnToggleMenu;
@ToggleMenu.performed -= instance.OnToggleMenu;
@ToggleMenu.canceled -= instance.OnToggleMenu;
@SpectatorVertical.started -= instance.OnSpectatorVertical;
@SpectatorVertical.performed -= instance.OnSpectatorVertical;
@SpectatorVertical.canceled -= instance.OnSpectatorVertical;
}
public void RemoveCallbacks(IDefaultMapActions instance)
{
if (m_Wrapper.m_DefaultMapActionsCallbackInterfaces.Remove(instance))
UnregisterCallbacks(instance);
}
public void SetCallbacks(IDefaultMapActions instance)
{
foreach (var item in m_Wrapper.m_DefaultMapActionsCallbackInterfaces)
UnregisterCallbacks(item);
m_Wrapper.m_DefaultMapActionsCallbackInterfaces.Clear();
AddCallbacks(instance);
}
}
public DefaultMapActions @DefaultMap => new DefaultMapActions(this);
public interface IDefaultMapActions
{
void OnMove(InputAction.CallbackContext context);
void OnLookDelta(InputAction.CallbackContext context);
void OnLookConst(InputAction.CallbackContext context);
void OnScroll(InputAction.CallbackContext context);
void OnJump(InputAction.CallbackContext context);
void OnShoot(InputAction.CallbackContext context);
void OnAim(InputAction.CallbackContext context);
void OnToggleMenu(InputAction.CallbackContext context);
void OnSpectatorVertical(InputAction.CallbackContext context);
}
}
OnlineFPS/Assets/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>();
MainEntityCamera mainEntityCamera = SystemAPI.GetSingleton<MainEntityCamera>();
LocalToWorld targetLocalToWorld = SystemAPI.GetComponent<LocalToWorld>(mainEntityCameraEntity);
MainGameObjectCamera.Instance.transform.SetPositionAndRotation(targetLocalToWorld.Position, targetLocalToWorld.Rotation);
MainGameObjectCamera.Instance.fieldOfView = mainEntityCamera.CurrentFoV;
}
}
}
OnlineFPS/Assets/Scripts/Camera/MainEntityCamera.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;

[Serializable]
public struct MainEntityCamera : IComponentData
{
public MainEntityCamera(float fov)
{
BaseFoV = fov;
CurrentFoV = fov;
}

public float BaseFoV;


public float CurrentFoV;
}
OnlineFPS/Assets/Scripts/Camera/MainEntityCameraAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;

[DisallowMultipleComponent]
public class MainEntityCameraAuthoring : MonoBehaviour
{
public float FOV = 75f;

public class Baker : Baker<MainEntityCameraAuthoring>


{
public override void Bake(MainEntityCameraAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new MainEntityCamera(authoring.FOV));
}
}
}
OnlineFPS/Assets/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>();
}
}
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);
}

#region Character Processor Callbacks


public void UpdateGroundingUp(
ref FirstPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext)
{
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
CharacterAspect.Default_UpdateGroundingUp(ref characterBody);
}
public bool CanCollideWithHit(
ref FirstPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
in BasicHit hit)
{
return PhysicsUtilities.IsCollidable(hit.Material);
}
public bool IsGroundedOnHit(
ref FirstPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
in BasicHit hit,
int groundingEvaluationType)
{
FirstPersonCharacterComponent characterComponent = CharacterComponent.ValueRO;
return CharacterAspect.Default_IsGroundedOnHit(
in this,
ref context,
ref baseContext,
in hit,
in characterComponent.StepAndSlopeHandling,
groundingEvaluationType);
}
public void OnMovementHit(
ref FirstPersonCharacterUpdateContext 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;
FirstPersonCharacterComponent characterComponent = CharacterComponent.ValueRO;
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 FirstPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
ref PhysicsMass characterMass,
ref PhysicsMass otherMass,
BasicHit hit)
{
}
public void ProjectVelocityOnHits(
ref FirstPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
ref float3 velocity,
ref bool characterIsGrounded,
ref BasicHit characterGroundHit,
in DynamicBuffer<KinematicVelocityProjectionHit> velocityProjectionHits,
float3 originalVelocityDirection)
{
FirstPersonCharacterComponent characterComponent = CharacterComponent.ValueRO;
CharacterAspect.Default_ProjectVelocityOnHits(
ref velocity,
ref characterIsGrounded,
ref characterGroundHit,
in velocityProjectionHits,
originalVelocityDirection,
characterComponent.StepAndSlopeHandling.ConstrainVelocityToGroundPlane);
}
#endregion
}
OnlineFPS/Assets/Scripts/Character/FirstPersonCharacterAuthoring.cs
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEngine;
using Unity.CharacterController;
using Unity.Physics;
using System.Collections.Generic;
using UnityEngine.Serialization;
[DisallowMultipleComponent]
public class FirstPersonCharacterAuthoring : MonoBehaviour
{
public GameObject ViewObject;
public GameObject NameTagSocket;
public GameObject WeaponSocket;
public GameObject WeaponAnimationSocket;
public GameObject DeathVFX;
public GameObject DeathVFXSpawnPoint;
public AuthoringKinematicCharacterProperties CharacterProperties = AuthoringKinematicCharacterProperties.GetDefault();
public FirstPersonCharacterComponent Character = FirstPersonCharacterComponent.GetDefault();

public class Baker : Baker<FirstPersonCharacterAuthoring>


{
public override void Bake(FirstPersonCharacterAuthoring authoring)
{
KinematicCharacterUtilities.BakeCharacter(this, authoring, authoring.CharacterProperties);
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
authoring.Character.ViewEntity = GetEntity(authoring.ViewObject, TransformUsageFlags.Dynamic);
authoring.Character.NameTagSocketEntity = GetEntity(authoring.NameTagSocket, TransformUsageFlags.Dynamic);
authoring.Character.WeaponSocketEntity = GetEntity(authoring.WeaponSocket, TransformUsageFlags.Dynamic);
authoring.Character.WeaponAnimationSocketEntity = GetEntity(authoring.WeaponAnimationSocket, TransformUsageFlags.Dynamic);
authoring.Character.DeathVFX = GetEntity(authoring.DeathVFX, TransformUsageFlags.Dynamic);
authoring.Character.DeathVFXSpawnPoint = GetEntity(authoring.DeathVFXSpawnPoint, TransformUsageFlags.Dynamic);
AddComponent(entity, authoring.Character);
AddComponent(entity, new FirstPersonCharacterControl());
AddComponent(entity, new OwningPlayer());
AddComponent(entity, new ActiveWeapon());
AddComponent(entity, new CharacterWeaponVisualFeedback());
}
}
}
OnlineFPS/Assets/Scripts/Character/FirstPersonCharacterComponent.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using Unity.CharacterController;
using Unity.NetCode;

[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;

public Entity NameTagSocketEntity;


public Entity WeaponSocketEntity;
public Entity WeaponAnimationSocketEntity;
public Entity ViewEntity;
public Entity DeathVFX;
public Entity DeathVFXSpawnPoint;
public float MinViewAngle;
public float MaxViewAngle;
public float ViewRollAmount;
public float ViewRollSharpness;
[HideInInspector]
[GhostField(Smoothing = SmoothingAction.InterpolateAndExtrapolate)]
public float CharacterYDegrees;
[HideInInspector]
[GhostField(Smoothing = SmoothingAction.InterpolateAndExtrapolate)]
public float ViewPitchDegrees;
[HideInInspector]
public quaternion ViewLocalRotation;
[HideInInspector]
public float ViewRollDegrees;
public static FirstPersonCharacterComponent GetDefault()
{
return new FirstPersonCharacterComponent
{
BaseFoV = 75f,
GroundMaxSpeed = 10f,
GroundedMovementSharpness = 15f,
AirAcceleration = 50f,
AirMaxSpeed = 10f,
AirDrag = 0f,
JumpSpeed = 10f,
Gravity = math.up() * -30f,
PreventAirAccelerationAgainstUngroundedHits = true,
StepAndSlopeHandling = BasicStepAndSlopeHandlingParameters.GetDefault(),

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;

void Execute(FirstPersonCharacterAspect 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(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);

_context = new FirstPersonCharacterUpdateContext();


_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>());
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;

public class Baker : Baker<FirstPersonCharacterViewAuthoring>


{
public override void Bake(FirstPersonCharacterViewAuthoring authoring)
{
if (authoring.transform.parent != authoring.Character.transform)
{
UnityEngine.Debug.LogError("ERROR: the Character View must be a direct 1st-level child of the character authoring GameObject. Conversion will be aborted");
return;
}
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new FirstPersonCharacterView { CharacterEntity = GetEntity(authoring.Character, TransformUsageFlags.Dynamic) });
}
}
}
OnlineFPS/Assets/Scripts/GameManagement/ClientGameSystem.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.Scenes;
using Unity.Transforms;
using UnityEngine;
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation | WorldSystemFilterFlags.ThinClientSimulation)]
[BurstCompile]
public partial struct ClientGameSystem : ISystem
{
public struct Singleton : IComponentData
{
public Unity.Mathematics.Random Random;
public float TimeWithoutAConnection;
public bool Spectator;
public int DisconnectionFramesCounter;
}
public struct JoinOnceScenesLoadedRequest : IComponentData
{
public Entity PendingSceneLoadRequest;
}
public struct JoinRequest : IRpcCommand
{
public FixedString128Bytes PlayerName;
public bool Spectator;
}
public struct DisconnectRequest : IComponentData
{ }

private EntityQuery _singletonQuery;


private EntityQuery _spectatorSpawnPointsQuery;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<GameResources>();
_singletonQuery = new EntityQueryBuilder(Allocator.Temp).WithAllRW<Singleton>().Build(state.EntityManager);
_spectatorSpawnPointsQuery = new EntityQueryBuilder(Allocator.Temp).WithAll<SpectatorSpawnPoint, LocalToWorld>().Build(state.EntityManager);
// Auto-create singleton
Entity singletonEntity = state.EntityManager.CreateEntity();
state.EntityManager.AddComponentData(singletonEntity, new Singleton
{
Random = Unity.Mathematics.Random.CreateFromIndex(0),
});
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
ref Singleton singleton = ref _singletonQuery.GetSingletonRW<Singleton>().ValueRW;
ComponentLookup<LocalTransform> localTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(true);
ComponentLookup<Parent> parentLookup = SystemAPI.GetComponentLookup<Parent>(true);
ComponentLookup<PostTransformMatrix> postTransformMatrixLookup = SystemAPI.GetComponentLookup<PostTransformMatrix>(true);
GameResources gameResources = SystemAPI.GetSingleton<GameResources>();
HandleSendJoinRequestOncePendingScenesLoaded(ref state, ref singleton);
HandlePendingJoinRequest(ref state, ref singleton, gameResources);
HandleCharacterSetupAndDestruction(ref state, ref singleton, ref localTransformLookup, ref parentLookup, ref postTransformMatrixLookup, gameResources);
HandleDisconnect(ref state, ref singleton, gameResources);
HandleRespawnScreen(ref state, ref singleton, gameResources);
}
private void HandleSendJoinRequestOncePendingScenesLoaded(ref SystemState state, ref Singleton singleton)
{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged);

foreach (var (request, entity) in SystemAPI.Query<JoinOnceScenesLoadedRequest>().WithEntityAccess())


{
if (SystemAPI.HasComponent<SceneLoadRequest>(request.PendingSceneLoadRequest) && SystemAPI.GetComponent<SceneLoadRequest>(request.PendingSceneLoadRequest).IsLoaded)
{
LocalGameData localData = SystemAPI.GetSingleton<LocalGameData>();
// Send join request
if (SystemAPI.HasSingleton<NetworkId>())
{
Entity joinRequestEntity = ecb.CreateEntity();
ecb.AddComponent(joinRequestEntity, new JoinRequest
{
PlayerName = localData.LocalPlayerName,
Spectator = singleton.Spectator,
});
ecb.AddComponent(joinRequestEntity, new SendRpcCommandRequest());
ecb.DestroyEntity(request.PendingSceneLoadRequest);
ecb.DestroyEntity(entity);
}
}
}
}
private void HandlePendingJoinRequest(ref SystemState state, ref Singleton singleton, GameResources gameResources)
{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged);
if (SystemAPI.HasSingleton<NetworkId>() && !SystemAPI.HasSingleton<NetworkStreamInGame>())
{
singleton.TimeWithoutAConnection = 0f;
// Check for request accept
foreach (var (requestAccepted, rpcReceive, entity) in SystemAPI.Query<ServerGameSystem.JoinRequestAccepted, ReceiveRpcCommandRequest>().WithEntityAccess())
{
// Stream in game
ecb.AddComponent(SystemAPI.GetSingletonEntity<NetworkId>(), new NetworkStreamInGame());
// Spectator mode
if (singleton.Spectator)
{
LocalToWorld spawnPoint = default;
NativeArray<LocalToWorld> spectatorSpawnPoints = _spectatorSpawnPointsQuery.ToComponentDataArray<LocalToWorld>(Allocator.Temp);
if (spectatorSpawnPoints.Length > 0)
{
spawnPoint = spectatorSpawnPoints[singleton.Random.NextInt(0, spectatorSpawnPoints.Length - 1)];
}
Entity spectatorEntity = ecb.Instantiate(gameResources.SpectatorPrefab);
ecb.SetComponent(spectatorEntity, new LocalTransform() { Position = spawnPoint.Position, Rotation = spawnPoint.Rotation, Scale = 1f });
spectatorSpawnPoints.Dispose();
}
ecb.DestroyEntity(entity);
}
}
}
private void HandleCharacterSetupAndDestruction(
ref SystemState state,
ref Singleton singleton,
ref ComponentLookup<LocalTransform> localTransformLookup,
ref ComponentLookup<Parent> parentLookup,
ref ComponentLookup<PostTransformMatrix> postTransformMatrixLookup,
GameResources gameResources)
{
if (SystemAPI.HasSingleton<NetworkId>())
{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged);
// Initialize local-owned characters
foreach (var (character, owningPlayer, ghostOwner, entity) in SystemAPI.Query<FirstPersonCharacterComponent, OwningPlayer, GhostOwner>().WithAll<GhostOwnerIsLocal>().WithNone
{
// Make camera follow character's view
ecb.AddComponent(character.ViewEntity, new MainEntityCamera { BaseFoV = character.BaseFoV });
// Make local character meshes rendering be shadow-only
MiscUtilities.SetShadowModeInHierarchy(state.EntityManager, ecb, entity, SystemAPI.GetBufferLookup<Child>(), UnityEngine.Rendering.ShadowCastingMode.ShadowsOnly);
// Enable crosshair
Entity crosshairRequestEntity = ecb.CreateEntity();
ecb.AddComponent(crosshairRequestEntity, new CrosshairRequest { Enable = true });
ecb.AddComponent(crosshairRequestEntity, new MoveToLocalWorld());

// Disable respawn screen (if any)


Entity respawnScreenRequestEntity = ecb.CreateEntity();
ecb.AddComponent(respawnScreenRequestEntity, new RespawnMessageRequest { Start = false });
ecb.AddComponent(respawnScreenRequestEntity, new MoveToLocalWorld());
InitializeCharacterCommon(
entity,
ecb,
in character,
ref localTransformLookup,
ref parentLookup,
ref postTransformMatrixLookup);
}
// Initialize remote characters
foreach (var (character, owningPlayer, ghostOwner, entity) in SystemAPI.Query<FirstPersonCharacterComponent, OwningPlayer, GhostOwner>().WithNone<GhostOwnerIsLocal>().
{
// Spawn nameTag
ecb.AddComponent(character.NameTagSocketEntity, new NameTagProxy { PlayerEntity = owningPlayer.Entity });
InitializeCharacterCommon(
entity,
ecb,
in character,
ref localTransformLookup,
ref parentLookup,
ref postTransformMatrixLookup);
}
// TODO: This wouldn't be compatible with characters despawned due to network relevancy
// Handle destroyed characters
foreach (var (characterCleanup, entity) in SystemAPI.Query<CharacterClientCleanup>().WithNone<FirstPersonCharacterComponent>().WithEntityAccess())
{
// Spawn death VFX
if(SystemAPI.Exists(characterCleanup.DeathVFX))
{
Entity deathVFXEntity = ecb.Instantiate(characterCleanup.DeathVFX);
ecb.SetComponent(deathVFXEntity, LocalTransform.FromPositionRotation(characterCleanup.DeathVFXSpawnWorldPosition, quaternion.identity));
}
ecb.RemoveComponent<CharacterClientCleanup>(entity);
}
}
}
private void InitializeCharacterCommon(
Entity entity,
EntityCommandBuffer ecb,
in FirstPersonCharacterComponent character,
ref ComponentLookup<LocalTransform> localTransformLookup,
ref ComponentLookup<Parent> parentLookup,
ref ComponentLookup<PostTransformMatrix> postTransformMatrixLookup)
{
TransformHelpers.ComputeWorldTransformMatrix(character.DeathVFXSpawnPoint, out float4x4 deathVFXspawnTransform, ref localTransformLookup, ref parentLookup, ref postTransformMatrixL
ecb.AddComponent(entity, new CharacterClientCleanup
{
DeathVFX = character.DeathVFX,
DeathVFXSpawnWorldPosition = deathVFXspawnTransform.Translation(),
});
}
private void HandleDisconnect(ref SystemState state, ref Singleton singleton, GameResources gameResources)
{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged);
// Check for connection timeout
if (!SystemAPI.HasSingleton<NetworkId>())
{
singleton.TimeWithoutAConnection += SystemAPI.Time.DeltaTime;
if (singleton.TimeWithoutAConnection > gameResources.JoinTimeout)
{
Entity disconnectEntity = ecb.CreateEntity();
ecb.AddComponent(disconnectEntity, new DisconnectRequest());
}
}
// Handle disconnecting & properly disposing world
EntityQuery disconnectRequestQuery = SystemAPI.QueryBuilder().WithAll<DisconnectRequest>().Build();
if (disconnectRequestQuery.CalculateEntityCount() > 0)
{
// Add disconnect request to connection
foreach (var (connection, entity) in SystemAPI.Query<NetworkId>().WithNone<NetworkStreamRequestDisconnect>().WithEntityAccess())
{
ecb.AddComponent(entity, new NetworkStreamRequestDisconnect());
}
// 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;
}
// Allow systems to have updated since disconnection, for cleanup
if (singleton.DisconnectionFramesCounter > 3)
{
Entity disposeRequestEntity = ecb.CreateEntity();
ecb.AddComponent(disposeRequestEntity, new GameManagementSystem.DisposeClientWorldRequest());
ecb.AddComponent(disposeRequestEntity, new MoveToLocalWorld());
ecb.DestroyEntity(disconnectRequestQuery);
}
singleton.DisconnectionFramesCounter++;
}
}
private void HandleRespawnScreen(ref SystemState state, ref Singleton singleton, GameResources gameResources)
{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged);
foreach (var (respawnScreenRequest, receiveRPC, entity) in SystemAPI.Query<RespawnMessageRequest, ReceiveRpcCommandRequest>().WithEntityAccess())
{
// Disable crosshair
Entity crosshairRequestEntity = ecb.CreateEntity();
ecb.AddComponent(crosshairRequestEntity, new CrosshairRequest { Enable = false });
ecb.AddComponent(crosshairRequestEntity, new MoveToLocalWorld());
// Send request to get processed by UI system
ecb.AddComponent(entity, new MoveToLocalWorld());
}
}
}
OnlineFPS/Assets/Scripts/GameManagement/GameManagementSystem.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.NetCode;
using Unity.Networking.Transport;
using Unity.Scenes;
using UnityEngine;
[Serializable]
public struct LocalGameData : IComponentData
{
public FixedString128Bytes LocalPlayerName;
}
[WorldSystemFilter(WorldSystemFilterFlags.LocalSimulation)]
[UpdateInGroup(typeof(InitializationSystemGroup))]
public partial class GameManagementSystem : SystemBase
{
public World ClientWorld;
public World ServerWorld;

public const string LocalHost = "127.0.0.1";


[Serializable]
public struct JoinRequest : IComponentData
{
public FixedString128Bytes LocalPlayerName;
public NetworkEndpoint EndPoint;
public bool Spectator;
}

[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;
}

protected override void OnCreate()


{
base.OnCreate();

// 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);

foreach (var (request, entity) in SystemAPI.Query<RefRO<HostRequest>>().WithEntityAccess())


{
if (!WorldUtilities.IsValidAndCreated(ServerWorld))
{
// Create server world
ServerWorld = NetCodeBootstrap.CreateServerWorld("ServerWorld");
// Tickrate
Entity tickRateEntity = serverECB.CreateEntity();
serverECB.AddComponent(tickRateEntity, gameResources.GetClientServerTickRate());
// Listen to endpoint
EntityQuery serverNetworkDriverQuery = new EntityQueryBuilder(Allocator.Temp).WithAllRW<NetworkStreamDriver>().Build(ServerWorld.EntityManager);
serverNetworkDriverQuery.GetSingletonRW<NetworkStreamDriver>().ValueRW.Listen(request.ValueRO.EndPoint);

// Load game resources subscene


SceneSystem.LoadSceneAsync(ServerWorld.Unmanaged, gameResources.GameResourcesScene);
// Create a request to accept joins once the game scenes have been loaded
{
EntityQuery serverGameSingletonQuery = new EntityQueryBuilder(Allocator.Temp).WithAllRW<ServerGameSystem.Singleton>().Build(ServerWorld.EntityManager);
ref ServerGameSystem.Singleton serverGameSingleton = ref serverGameSingletonQuery.GetSingletonRW<ServerGameSystem.Singleton>().ValueRW;
serverGameSingleton.AcceptJoins = false;

Entity requestAcceptJoinsEntity = serverECB.CreateEntity();


serverECB.AddComponent(requestAcceptJoinsEntity, new ServerGameSystem.AcceptJoinsOnceScenesLoadedRequest
{
PendingSceneLoadRequest = SceneLoadRequestSystem.CreateSceneLoadRequest(serverECB, gameResources.GameScene),
});
}
}
ecb.DestroyEntity(entity);
break;
}

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,
});

// Load game resources subscene


SceneSystem.LoadSceneAsync(ClientWorld.Unmanaged, gameResources.GameResourcesScene);

// 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;

Entity requestAcceptJoinsEntity = clientECB.CreateEntity();


clientECB.AddComponent(requestAcceptJoinsEntity, new ClientGameSystem.JoinOnceScenesLoadedRequest
{
PendingSceneLoadRequest = SceneLoadRequestSystem.CreateSceneLoadRequest(clientECB, gameResources.GameScene),
});
}
}
ecb.DestroyEntity(entity);
break;
}

if (WorldUtilities.IsValidAndCreated(ClientWorld))
{
clientECB.Playback(ClientWorld.EntityManager);
}
clientECB.Dispose();
}

private void ProcessDisconnectRequests(ref Singleton singleton, ref EntityCommandBuffer ecb)


{
EntityQuery disconnectRequestQuery = SystemAPI.QueryBuilder().WithAll<DisconnectRequest>().Build();
if (disconnectRequestQuery.CalculateEntityCount() > 0)
{
if (WorldUtilities.IsValidAndCreated(ClientWorld))
{
Entity disconnectClientRequestEntity = ecb.CreateEntity();
ecb.AddComponent(disconnectClientRequestEntity, new ClientGameSystem.DisconnectRequest());
ecb.AddComponent(disconnectClientRequestEntity, new MoveToClientWorld());
}
if (WorldUtilities.IsValidAndCreated(ServerWorld))
{
Entity disconnectServerRequestEntity = ecb.CreateEntity();
ecb.AddComponent(disconnectServerRequestEntity, new ServerGameSystem.DisconnectRequest());
ecb.AddComponent(disconnectServerRequestEntity, new MoveToServerWorld());
}
}
ecb.DestroyEntity(disconnectRequestQuery);
}

private void HandleDisposeClientServerWorldsAndReturnToMenu(ref Singleton singleton, ref EntityCommandBuffer ecb)


{
EntityQuery disposeClientRequestQuery = SystemAPI.QueryBuilder().WithAll<DisposeClientWorldRequest>().Build();
if (disposeClientRequestQuery.CalculateEntityCount() > 0)
{
if (WorldUtilities.IsValidAndCreated(ClientWorld))
{
ClientWorld.Dispose();
}

EntityManager.DestroyEntity(disposeClientRequestQuery);
}

EntityQuery disposeServerRequestQuery = SystemAPI.QueryBuilder().WithAll<DisposeServerWorldRequest>().Build();


if (disposeServerRequestQuery.CalculateEntityCount() > 0)
{
if (WorldUtilities.IsValidAndCreated(ServerWorld))
{
ServerWorld.Dispose();
}

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;
}
}

// Handle state update


if (singleton.MenuState == MenuState.InMenu)
{
// load menu scene if it doesn't exist
if (!SystemAPI.HasComponent<SceneReference>(singleton.MenuVisualsSceneInstance))
{
singleton.MenuVisualsSceneInstance = SceneSystem.LoadSceneAsync(World.Unmanaged, SystemAPI.GetSingleton<GameResources>().MenuVisualsScene);
}
}
else
{
// unload menu scene if it exists
if (SystemAPI.HasComponent<SceneReference>(singleton.MenuVisualsSceneInstance))
{
SceneSystem.UnloadScene(World.Unmanaged, singleton.MenuVisualsSceneInstance, SceneSystem.UnloadParameters.DestroyMetaEntities);
}
}
}
}
OnlineFPS/Assets/Scripts/GameManagement/GameUISystem.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Entities;
using Unity.NetCode;
using Unity.Networking.Transport;
using UnityEngine;
using UnityEngine.UIElements;
using Cursor = UnityEngine.Cursor;
using TMPro;
using Unity.Mathematics;
using Unity.Transforms;
public enum MenuState
{
InMenu,
Connecting,
InGame,
}
public struct CrosshairRequest : IComponentData
{
public bool Enable;
}
public struct RespawnMessageRequest : IRpcCommand
{
public bool Start;
public float CountdownTime;
}
public static class VisualElementExtensions
{
public static void SetDisplay(this VisualElement element, bool enabled)
{
element.style.display = enabled ? DisplayStyle.Flex : DisplayStyle.None;
}
}

[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;

public int DisconnectionFramesCounter;


}

public struct AcceptJoinsOnceScenesLoadedRequest : IComponentData


{
public Entity PendingSceneLoadRequest;
}

public struct PendingClient : IComponentData


{
public float TimeConnected;
public bool IsJoining;
}

public struct JoinedClient : IComponentData


{
public Entity PlayerEntity;
}
public struct JoinRequestAccepted : IRpcCommand
{
}
public struct ClientOwnedEntities : ICleanupBufferElementData
{
public Entity Entity;
}
public struct DisconnectRequest : IComponentData
{
}
public struct CharacterSpawnRequest : IComponentData
{
public Entity ForConnection;
public float Delay;
}
private EntityQuery _singletonQuery;
private EntityQuery _joinRequestQuery;

public void OnCreate(ref SystemState state)


{
state.RequireForUpdate<GameResources>();
_singletonQuery = new EntityQueryBuilder(Allocator.Temp).WithAllRW<Singleton>().Build(state.EntityManager);
_joinRequestQuery = new EntityQueryBuilder(Allocator.Temp).WithAll<ClientGameSystem.JoinRequest, ReceiveRpcCommandRequest>().Build(ref state);

// 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>();

HandleAcceptJoinsOncePendingScenesAreLoaded(ref state, ref singleton);


HandleJoinRequests(ref state, ref singleton, gameResources);
HandlePendingJoinClientTimeout(ref state, ref singleton, gameResources);
HandleDisconnect(ref state, ref singleton);
HandleSpawnCharacter(ref state, ref singleton, gameResources);
}

private void HandleAcceptJoinsOncePendingScenesAreLoaded(ref SystemState state, ref Singleton singleton)


{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged);

foreach (var (request, entity) in SystemAPI.Query<AcceptJoinsOnceScenesLoadedRequest>().WithEntityAccess())


{
if (SystemAPI.HasComponent<SceneLoadRequest>(request.PendingSceneLoadRequest) && SystemAPI.GetComponent<SceneLoadRequest>(request.PendingSceneLoadRequest).IsLoaded)
{
singleton.AcceptJoins = true;
ecb.DestroyEntity(request.PendingSceneLoadRequest);
ecb.DestroyEntity(entity);
}
}
}
private void HandlePendingJoinClientTimeout(ref SystemState state, ref Singleton singleton, GameResources gameResources)
{
// Add ConnectionState component
{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged);
foreach (var (netId, entity) in SystemAPI.Query<NetworkId>().WithNone<ConnectionState>().WithEntityAccess())
{
ecb.AddComponent(entity, new ConnectionState());
}
}
// Mark unjoined clients as pending
{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged);

foreach (var (netId, entity) in SystemAPI.Query<NetworkId>().WithNone<PendingClient>().WithNone<JoinedClient>().WithEntityAccess())


{
ecb.AddComponent(entity, new PendingClient());
}
}

// Handle join timeout for pending clients


{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged);
foreach (var (netId, pendingCLient, entity) in SystemAPI.Query<NetworkId, RefRW<PendingClient>>().WithEntityAccess())
{
pendingCLient.ValueRW.TimeConnected += SystemAPI.Time.DeltaTime;
if (pendingCLient.ValueRW.TimeConnected > gameResources.JoinTimeout)
{
ecb.DestroyEntity(entity);
}
}
}
}

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);

// Process join requests


foreach (var (request, rpcReceive, entity) in SystemAPI.Query<ClientGameSystem.JoinRequest, ReceiveRpcCommandRequest>().WithEntityAccess())
{
if (SystemAPI.HasComponent<NetworkId>(rpcReceive.SourceConnection) &&
!SystemAPI.HasComponent<JoinedClient>(rpcReceive.SourceConnection))
{
int ownerConnectionId = SystemAPI.GetComponent<NetworkId>(rpcReceive.SourceConnection).Value;

// Mark connection as joined


ecb.RemoveComponent<PendingClient>(rpcReceive.SourceConnection);
ecb.AddBuffer<ClientOwnedEntities>(rpcReceive.SourceConnection);

Entity playerEntity = Entity.Null;


// Spawn player
playerEntity = ecb.Instantiate(gameResources.PlayerGhost);
ecb.SetComponent(playerEntity, new GhostOwner { NetworkId = ownerConnectionId });
ecb.AppendToBuffer(rpcReceive.SourceConnection, new ClientOwnedEntities { Entity = playerEntity });

// Set player data


FirstPersonPlayer player = SystemAPI.GetComponent<FirstPersonPlayer>(gameResources.PlayerGhost);
player.Name = request.PlayerName;
ecb.SetComponent(playerEntity, player);

if (!request.Spectator)
{
// Request to spawn character
Entity spawnCharacterRequestEntity = ecb.CreateEntity();
ecb.AddComponent(spawnCharacterRequestEntity, new CharacterSpawnRequest { ForConnection = rpcReceive.SourceConnection, Delay = -1f });
}

// Remember player for connection


ecb.AddComponent(rpcReceive.SourceConnection, new JoinedClient { PlayerEntity = playerEntity });

// Accept join request


Entity joinRequestAcceptedEntity = state.EntityManager.CreateEntity();
ecb.AddComponent(joinRequestAcceptedEntity, new JoinRequestAccepted());
ecb.AddComponent(joinRequestAcceptedEntity, new SendRpcCommandRequest{ TargetConnection = rpcReceive.SourceConnection });

// Stream in game
ecb.AddComponent(rpcReceive.SourceConnection, new NetworkStreamInGame());
}

ecb.DestroyEntity(entity);
}
}
}

private void HandleDisconnect(ref SystemState state, ref Singleton singleton)


{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged);

// 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;
}

// Allow systems to have updated since disconnection, for cleanup


if (singleton.DisconnectionFramesCounter > 3)
{
Entity disposeRequestEntity = ecb.CreateEntity();
ecb.AddComponent(disposeRequestEntity, new GameManagementSystem.DisposeServerWorldRequest());
ecb.AddComponent(disposeRequestEntity, new MoveToLocalWorld());
ecb.DestroyEntity(disconnectRequestQuery);
}

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);

foreach (var (spawnRequest, entity) in SystemAPI.Query<RefRW<CharacterSpawnRequest>>().WithEntityAccess())


{
if (spawnRequest.ValueRW.Delay > 0f)
{
spawnRequest.ValueRW.Delay -= SystemAPI.Time.DeltaTime;
}
else
{
if (SystemAPI.HasComponent<NetworkId>(spawnRequest.ValueRW.ForConnection) &&
SystemAPI.HasComponent<JoinedClient>(spawnRequest.ValueRW.ForConnection))
{
int connectionId = SystemAPI.GetComponent<NetworkId>(spawnRequest.ValueRW.ForConnection).Value;
Entity playerEntity = SystemAPI.GetComponent<JoinedClient>(spawnRequest.ValueRW.ForConnection).PlayerEntity;
float3 randomSpawnPosition = spawnPointLtWs[singleton.Random.NextInt(0, spawnPointLtWs.Length - 1)].Position;

// 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 });

// Assign character to player


FirstPersonPlayer player = SystemAPI.GetComponent<FirstPersonPlayer>(playerEntity);
player.ControlledCharacter = characterEntity;
ecb.SetComponent(playerEntity, player);

// Spawn & assign starting weapon


Entity randomWeaponPrefab = default;
switch (singleton.Random.NextInt(0, 2))
{
case 0:
randomWeaponPrefab = gameResources.MachineGunGhost;
break;
case 1:
randomWeaponPrefab = gameResources.RailgunGhost;
break;
}
Entity weaponEntity = ecb.Instantiate(randomWeaponPrefab);
ecb.SetComponent(weaponEntity, new GhostOwner { NetworkId = connectionId });
ecb.SetComponent(characterEntity, new ActiveWeapon { Entity = weaponEntity });
}

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;

public class Baker : Baker<ConstantRotationAuthoring>


{
public override void Bake(ConstantRotationAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, authoring.ConstantRotation);
}
}
}
OnlineFPS/Assets/Scripts/Misc/ConstantRotationSystem.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

[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;

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);
}
}
OnlineFPS/Assets/Scripts/Misc/FramerateMenuManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Rendering;
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.NetCode;

public struct FramerateCalculator


{
private int _framesCount;
private float _framesDeltaSum;
private float _minDeltaTimeForAvg;
private float _maxDeltaTimeForAvg;
private string[] _framerateStrings;
public void Initialize()
{
_minDeltaTimeForAvg = Mathf.Infinity;
_maxDeltaTimeForAvg = Mathf.NegativeInfinity;
_framerateStrings = new string[1001];
for (int i = 0; i < _framerateStrings.Length; i++)
{
if (i >= _framerateStrings.Length - 1)
{
_framerateStrings[i] = i.ToString() + "+" + " (<" + (1000f / (float)i).ToString("F") + "ms)";
}
else
{
_framerateStrings[i] = i.ToString() + " (" + (1000f / (float)i).ToString("F") + "ms)";
}
}
}

public void Update()


{
// Regular frames
_framesCount++;
_framesDeltaSum += Time.deltaTime;
// Max and min
if (Time.deltaTime < _minDeltaTimeForAvg)
{
_minDeltaTimeForAvg = Time.deltaTime;
}
if (Time.deltaTime > _maxDeltaTimeForAvg)
{
_maxDeltaTimeForAvg = Time.deltaTime;
}
}

private string GetNumberString(int fps)


{
if (fps < _framerateStrings.Length - 1 && fps >= 0)
{
return _framerateStrings[fps];
}
else
{
return _framerateStrings[_framerateStrings.Length - 1];
}
}
public void PollFramerate(out string avg, out string worst, out string best)
{
avg = GetNumberString(Mathf.RoundToInt(1f / (_framesDeltaSum / _framesCount)));
worst = GetNumberString(Mathf.RoundToInt(1f / _maxDeltaTimeForAvg));
best = GetNumberString(Mathf.RoundToInt(1f / _minDeltaTimeForAvg));
_framesDeltaSum = 0f;
_framesCount = 0;
_minDeltaTimeForAvg = Mathf.Infinity;
_maxDeltaTimeForAvg = Mathf.NegativeInfinity;
}
}
public class FramerateMenuManager : MonoBehaviour
{
[Header("Components")] public Canvas MainCanvas;
public Text AvgFPS;
public Text WorstFPS;
public Text BestFPS;
public Text Ping;
[Header("Misc")] public float FPSPollRate = 1f;

private FramerateCalculator _framerateCalculator = default;


private float _lastTimePolledFPS = float.MinValue;
private bool _hasVSync = false;
void Start()
{
_framerateCalculator.Initialize();
UpdateRenderSettings();
}
void Update()
{
// show hide
if (Input.GetKeyDown(KeyCode.F1))
{
MainCanvas.gameObject.SetActive(!MainCanvas.gameObject.activeSelf);
}
if (Input.GetKeyDown(KeyCode.F3))
{
_hasVSync = !_hasVSync;
UpdateRenderSettings();
}
_framerateCalculator.Update();
if (Time.time >= _lastTimePolledFPS + FPSPollRate)
{
// FPS
_framerateCalculator.PollFramerate(out string avg, out string worst, out string best);
AvgFPS.text = avg;
WorstFPS.text = worst;
BestFPS.text = best;
// Ping
GameManagementSystem clientGameManagementSystem = World.DefaultGameObjectInjectionWorld.GetExistingSystemManaged<GameManagementSystem>();
if (clientGameManagementSystem != null && clientGameManagementSystem.ClientWorld != null && clientGameManagementSystem.ClientWorld.IsCreated)
{
EntityQuery networkAckQuery = new EntityQueryBuilder(Allocator.Temp).WithAll<NetworkSnapshotAck>().Build(clientGameManagementSystem.ClientWorld.EntityManager);
if (networkAckQuery.HasSingleton<NetworkSnapshotAck>())
{
NetworkSnapshotAck networkAck = networkAckQuery.GetSingleton<NetworkSnapshotAck>();
Ping.text = $"Ping: {(int)networkAck.EstimatedRTT}";
}
}
_lastTimePolledFPS = Time.time;
}
}

private void UpdateRenderSettings()


{
QualitySettings.vSyncCount = _hasVSync ? 1 : 0;
}
}
OnlineFPS/Assets/Scripts/Misc/GameResources.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Entities;
using Unity.Entities.Serialization;
using Unity.NetCode;
using UnityEngine;
[Serializable]
public struct GameResources : IComponentData
{
public int TickRate;
public int SendRate;
public int MaxSimulationStepsPerFrame;
public float JoinTimeout;

public float RespawnTime;

public EntitySceneReference MenuVisualsScene;


public EntitySceneReference GameResourcesScene;
public EntitySceneReference GameScene;

public Entity PlayerGhost;


public Entity CharacterGhost;
public Entity RailgunGhost;
public Entity MachineGunGhost;

public Entity SpectatorPrefab;

public ClientServerTickRate GetClientServerTickRate()


{
ClientServerTickRate tickRate = new ClientServerTickRate();
tickRate.ResolveDefaults();
tickRate.SimulationTickRate = TickRate;
tickRate.NetworkTickRate = SendRate;
tickRate.MaxSimulationStepsPerFrame = MaxSimulationStepsPerFrame;
return tickRate;
}
}
OnlineFPS/Assets/Scripts/Misc/GameResourcesAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Entities;
using Unity.Entities.Serialization;
using UnityEngine;

public class GameResourcesAuthoring : MonoBehaviour


{
[Header("Network Parameters")]
public int TickRate = 60;
public int SendRate = 60;
public int MaxSimulationStepsPerFrame = 4;
public float JoinTimeout = 10f;

[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;

public class Baker : Baker<GameResourcesAuthoring>


{
public override void Bake(GameResourcesAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.None);
AddComponent(entity, new GameResources
{
TickRate = authoring.TickRate,
SendRate = authoring.SendRate,
MaxSimulationStepsPerFrame = authoring.MaxSimulationStepsPerFrame,
JoinTimeout = authoring.JoinTimeout,

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;

public class GameResourcesManaged : MonoBehaviour


{
public static GameResourcesManaged Instance;
public GameObject NameTagPrefab;

private void OnEnable()


{
Instance = this;
}
}
OnlineFPS/Assets/Scripts/Misc/GameSettings.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class GameSettings


{
public static float LookSensitivity = 2f;
}
OnlineFPS/Assets/Scripts/Misc/GhostVariants.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Transforms;
using UnityEngine;
using Unity.CharacterController;
public partial class DefaultVariantSystem : DefaultVariantSystemBase
{
protected override void RegisterDefaultVariants(Dictionary<ComponentType, Rule> defaultVariants)
{
defaultVariants.Add(typeof(LocalTransform), Rule.ForAll(typeof(TransformDefaultVariant)));
defaultVariants.Add(typeof(KinematicCharacterBody), Rule.ForAll(typeof(KinematicCharacterBody_GhostVariant)));
}
}

[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;

public bool IsDead()


{
return CurrentHealth <= 0f;
}
}
OnlineFPS/Assets/Scripts/Misc/HealthAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;

public class HealthAuthoring : MonoBehaviour


{
public float MaxHealth = 100f;
public class Baker : Baker<HealthAuthoring>
{
public override void Bake(HealthAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.None);
AddComponent(entity, new Health
{
MaxHealth = authoring.MaxHealth,
CurrentHealth = authoring.MaxHealth,
});
}
}
}
OnlineFPS/Assets/Scripts/Misc/InterpolationDelay.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;

public struct InterpolationDelay : IComponentData


{
public uint Value;
}
OnlineFPS/Assets/Scripts/Misc/MiscUtilities.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Entities;
using Unity.Entities.Graphics;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Networking.Transport;
using Unity.Rendering;
using Unity.Transforms;
using UnityEngine.InputSystem;
public static class MiscUtilities
{
public static void SetShadowModeInHierarchy(EntityManager entityManager, EntityCommandBuffer ecb, Entity onEntity, BufferLookup<Child> childBufferFromEntity, UnityEngine.Rendering
{
if (entityManager.HasComponent<RenderFilterSettings>(onEntity))
{
RenderFilterSettings renderFilterSettings = entityManager.GetSharedComponent<RenderFilterSettings>(onEntity);
renderFilterSettings.ShadowCastingMode = mode;
ecb.SetSharedComponent(onEntity, renderFilterSettings);
}
if (childBufferFromEntity.HasBuffer(onEntity))
{
DynamicBuffer<Child> childBuffer = childBufferFromEntity[onEntity];
for (int i = 0; i < childBuffer.Length; i++)
{
SetShadowModeInHierarchy(entityManager, ecb, childBuffer[i].Value, childBufferFromEntity, mode);
}
}
}
public static bool HasSingleton<T>(ref SystemState state) where T : unmanaged, IComponentData
{
return new EntityQueryBuilder(Allocator.Temp).WithAll<T>().Build(ref state).HasSingleton<T>();
}
public static T GetSingleton<T>(ref SystemState state) where T : unmanaged, IComponentData
{
return new EntityQueryBuilder(Allocator.Temp).WithAll<T>().Build(ref state).GetSingleton<T>();
}
public static Entity GetSingletonEntity<T>(ref SystemState state) where T : unmanaged, IComponentData
{
return new EntityQueryBuilder(Allocator.Temp).WithAll<T>().Build(ref state).GetSingletonEntity();
}
public static void GetConnectionsArrays(ref SystemState state, Allocator allocator, out NativeArray<Entity> connectionEntities, out NativeArray<NetworkId> connections)
{
EntityQuery connectionsQuery = new EntityQueryBuilder(Allocator.Temp).WithAll<NetworkId>().Build(ref state);
connectionEntities = connectionsQuery.ToEntityArray(allocator);
connections = connectionsQuery.ToComponentDataArray<NetworkId>(allocator);
}
}
OnlineFPS/Assets/Scripts/Misc/MoveEntitiesBetweenWorldsSystem.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Entities;
using Unity.NetCode;
using UnityEngine;
public struct MoveToClientWorld : IComponentData
{ }

public struct MoveToServerWorld : IComponentData


{ }

public struct MoveToLocalWorld : IComponentData


{ }
public struct EntityPendingMove : IComponentData
{ }

public static class WorldUtilities


{
public static bool IsValidAndCreated(World world)
{
return world != null && world.IsCreated;
}
}
[WorldSystemFilter(WorldSystemFilterFlags.LocalSimulation)]
public partial class MoveLocalEntitiesToClientServerSystem : SystemBase
{
private List<World> ClientWorlds;
private World ServerWorld;

protected override void OnCreate()


{
base.OnCreate();

ClientWorlds = new List<World>();


}

protected override void OnUpdate()


{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<BeginSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(World.Unmanaged);

// Find client worlds


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()))
{
if (!ClientWorlds.Contains(tmpWorld))
{
ClientWorlds.Add(tmpWorld);
}
}
}

// Find server world


if (!WorldUtilities.IsValidAndCreated(ServerWorld))
{
for (int i = 0; i < worlds.Count; i++)
{
World tmpWorld = worlds[i];
if (WorldUtilities.IsValidAndCreated(tmpWorld) && tmpWorld.IsServer())
{
ServerWorld = tmpWorld;
break;
}
}
}
// Move entities to clients
for (int i = ClientWorlds.Count - 1; i >= 0; i--)
{
World clientWorld = ClientWorlds[i];
if (WorldUtilities.IsValidAndCreated(clientWorld))
{
EntityQuery pendingMoveQuery = SystemAPI.QueryBuilder().WithAll<MoveToClientWorld, EntityPendingMove>().Build();
NativeArray<Entity> moveEntities = SystemAPI.QueryBuilder().WithAll<MoveToClientWorld>().Build().ToEntityArray(Allocator.Temp);
for (int j = 0; j < moveEntities.Length; j++)
{
Entity original = moveEntities[j];
Entity copy = EntityManager.Instantiate(original);
EntityManager.AddComponentData(copy, new EntityPendingMove());
ecb.DestroyEntity(original);
}
moveEntities.Dispose();
clientWorld.EntityManager.MoveEntitiesFrom(EntityManager, pendingMoveQuery);
EntityManager.DestroyEntity(pendingMoveQuery);
}
else
{
ClientWorlds.RemoveAt(i);
}
}
// Move entities to server
if (WorldUtilities.IsValidAndCreated(ServerWorld))
{
EntityQuery pendingMoveQuery = SystemAPI.QueryBuilder().WithAll<MoveToServerWorld, EntityPendingMove>().Build();
NativeArray<Entity> moveEntities = SystemAPI.QueryBuilder().WithAll<MoveToServerWorld>().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();

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;

public struct NameTagProxy : IComponentData


{
public Entity PlayerEntity;
}
public class NameTagProxyCleanup : ICleanupComponentData
{
public GameObject NameTagGameObject;
}

[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;

EntityManager.AddComponentObject(entities[i], new NameTagProxyCleanup { NameTagGameObject = nameTagInstance });


}

entities.Dispose();
nameTags.Dispose();
}

// Update (follow transform & look at camera)


if (SystemAPI.HasSingleton<MainEntityCamera>())
{
Entity mainCameraEntity = SystemAPI.GetSingletonEntity<MainEntityCamera>();
float3 mainCameraPosition = SystemAPI.GetComponent<LocalToWorld>(mainCameraEntity).Position;

foreach (var (ltw, nameTag, cleanup, entity) in SystemAPI.Query<LocalToWorld, NameTagProxy, NameTagProxyCleanup>().WithEntityAccess())


{
if (cleanup.NameTagGameObject != null)
{
cleanup.NameTagGameObject.transform.position = ltw.Position;

float3 selfToCamera = mainCameraPosition - ltw.Position;


cleanup.NameTagGameObject.transform.LookAt(ltw.Position - selfToCamera, math.up());
}
}
}

// Destroy (destroy gameObject


EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<EndSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(World.Unmanaged);
foreach (var (cleanup, entity) in SystemAPI.Query<NameTagProxyCleanup>().WithNone<NameTagProxy>().WithEntityAccess())
{
if (cleanup.NameTagGameObject != null)
{
GameObject.Destroy(cleanup.NameTagGameObject);
}
ecb.RemoveComponent<NameTagProxyCleanup>(entity);
}
}
}
OnlineFPS/Assets/Scripts/Misc/Particle.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

[Serializable]
public struct Particle : IComponentData
{
public float Lifetime;
public float StartingScale;

public float3 Velocity;


public float CurrentLifetime;
}
OnlineFPS/Assets/Scripts/Misc/ParticleAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Transforms;
using UnityEngine;
public class ParticleAuthoring : MonoBehaviour
{
public class Baker : Baker<ParticleAuthoring>
{
public override void Bake(ParticleAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new Particle());
}
}
}
OnlineFPS/Assets/Scripts/Misc/ParticleSpawner.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

[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;

public class ParticleSpawnerAuthoring : MonoBehaviour


{
public GameObject ParticlePrefab;
public ParticleSpawner ParticleSpawner;
public class Baker : Baker<ParticleSpawnerAuthoring>
{
public override void Bake(ParticleSpawnerAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
authoring.ParticleSpawner.ParticlePrefab = GetEntity(authoring.ParticlePrefab, TransformUsageFlags.Dynamic);
AddComponent(entity, authoring.ParticleSpawner);
}
}
}
OnlineFPS/Assets/Scripts/Misc/ParticleSystem.cs
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
using Random = Unity.Mathematics.Random;
[BurstCompile]
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(PostPredictionPreTransformsECBSystem))]
[UpdateBefore(typeof(TransformSystemGroup))]
public partial struct ParticleSystem : ISystem
{
public struct Singleton : IComponentData
{
public Random Random;
}
[BurstCompile]
public void OnCreate(ref SystemState state)
{
Entity singleton = state.EntityManager.CreateEntity();
state.EntityManager.AddComponentData(singleton, new Singleton
{
Random = Random.CreateFromIndex(0),
});

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;

public class RenderEnvironmentAuthoring : MonoBehaviour


{
public BakedGameObjectSceneReference LightingScene;

public class Baker : Baker<RenderEnvironmentAuthoring>


{
public override void Bake(RenderEnvironmentAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.None);
AddComponentObject(entity, new RenderEnvironment
{
LightingSceneIndex = authoring.LightingScene.GetIndexInBuildScenes(),
});
}
}
}
OnlineFPS/Assets/Scripts/Misc/RenderEnvironmentSystem.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Entities;
using UnityEngine;
using UnityEngine.SceneManagement;
using RenderSettings = UnityEngine.RenderSettings;

[UpdateInGroup(typeof(PresentationSystemGroup), OrderFirst = true)]


[WorldSystemFilter(WorldSystemFilterFlags.LocalSimulation | WorldSystemFilterFlags.ClientSimulation)]
public partial class RenderEnvironmentSystem : SystemBase
{
public struct Singleton : IComponentData
{
public int ActiveLightingScene;
}

protected override void OnCreate()


{
base.OnCreate();

Entity singletonEntity = EntityManager.CreateEntity();


EntityManager.AddComponentData(singletonEntity, new Singleton
{
ActiveLightingScene = -1,
});

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);

foreach (var (renderEnvironment, entity) in SystemAPI.Query<RenderEnvironment>().WithNone<RenderEnvironmentCleanup>().WithEntityAccess())


{
// setup cleanup component
ecb.AddComponent(entity, new RenderEnvironmentCleanup { LightingSceneIndex = renderEnvironment.LightingSceneIndex });

if (renderEnvironment.LightingSceneIndex >= 0)
{
AsyncOperation loadSceneOperation = SceneManager.LoadSceneAsync(renderEnvironment.LightingSceneIndex, LoadSceneMode.Additive);
loadSceneOperation.allowSceneActivation = true;

singleton.ActiveLightingScene = renderEnvironment.LightingSceneIndex;
}
}

// Auto-unload lighting scene when RenderEnvironment is destroyed


foreach (var (renderEnvironmentCleanup, entity) in SystemAPI.Query<RenderEnvironmentCleanup>().WithNone<RenderEnvironment>().WithEntityAccess())
{
if (renderEnvironmentCleanup.LightingSceneIndex >= 0)
{
if (SceneManager.GetSceneByBuildIndex(renderEnvironmentCleanup.LightingSceneIndex) != null)
{
SceneManager.UnloadSceneAsync(singleton.ActiveLightingScene);
}
}
ecb.RemoveComponent<RenderEnvironmentCleanup>(entity);
}
ecb.Playback(EntityManager);
ecb.Dispose();
}

protected void OnSceneLoaded(Scene scene, LoadSceneMode loadSceneMode)


{
ref Singleton singleton = ref SystemAPI.GetSingletonRW<Singleton>().ValueRW;
if (scene.buildIndex == singleton.ActiveLightingScene)
{
SceneManager.SetActiveScene(scene);
}
}
}
OnlineFPS/Assets/Scripts/Misc/SceneLoadRequest.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Entities.Serialization;
using UnityEngine;

[Serializable]
public struct SceneLoadRequest : IComponentData
{
public bool IsLoaded;
}

[Serializable]
public struct SceneIdentifier : IBufferElementData
{
public EntitySceneReference SceneReference;
public Entity SceneEntity;

public SceneIdentifier(EntitySceneReference sceneReference)


{
SceneReference = sceneReference;
SceneEntity = default;
}
}
OnlineFPS/Assets/Scripts/Misc/SceneLoadRequestSystem.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Entities;
using Unity.Entities.Serialization;
using Unity.Scenes;
using UnityEngine;

public partial struct SceneLoadRequestSystem : ISystem


{
public static Entity CreateSceneLoadRequest(EntityManager entityManager, EntitySceneReference sceneReference)
{
Entity requestEntity = entityManager.CreateEntity(typeof(SceneLoadRequest));
DynamicBuffer<SceneIdentifier> scenesBuffer = entityManager.AddBuffer<SceneIdentifier>(requestEntity);
scenesBuffer.Add(new SceneIdentifier(sceneReference));
return requestEntity;
}
public static Entity CreateSceneLoadRequest(EntityManager entityManager, NativeList<EntitySceneReference> sceneReferences, bool autoDisposeList)
{
Entity requestEntity = entityManager.CreateEntity(typeof(SceneLoadRequest));
DynamicBuffer<SceneIdentifier> scenesBuffer = entityManager.AddBuffer<SceneIdentifier>(requestEntity);
for (int i = 0; i < sceneReferences.Length; i++)
{
scenesBuffer.Add(new SceneIdentifier(sceneReferences[i]));
}
if (autoDisposeList)
{
sceneReferences.Dispose();
}
return requestEntity;
}
public static Entity CreateSceneLoadRequest(EntityCommandBuffer ecb, EntitySceneReference sceneReference)
{
Entity requestEntity = ecb.CreateEntity();
ecb.AddComponent(requestEntity, new SceneLoadRequest());
DynamicBuffer<SceneIdentifier> scenesBuffer = ecb.AddBuffer<SceneIdentifier>(requestEntity);
scenesBuffer.Add(new SceneIdentifier(sceneReference));
return requestEntity;
}

public static Entity CreateSceneLoadRequest(EntityCommandBuffer ecb, NativeList<EntitySceneReference> sceneReferences, bool autoDisposeList)


{
Entity requestEntity = ecb.CreateEntity();
ecb.AddComponent(requestEntity, new SceneLoadRequest());
DynamicBuffer<SceneIdentifier> scenesBuffer = ecb.AddBuffer<SceneIdentifier>(requestEntity);
for (int i = 0; i < sceneReferences.Length; i++)
{
scenesBuffer.Add(new SceneIdentifier(sceneReferences[i]));
}
if (autoDisposeList)
{
sceneReferences.Dispose();
}
return requestEntity;
}
private EntityQuery _sceneLoadRequestQuery;
public void OnCreate(ref SystemState state)
{
_sceneLoadRequestQuery = new EntityQueryBuilder(Allocator.Temp)
.WithAll<SceneLoadRequest, SceneIdentifier>()
.Build(ref state);
state.RequireForUpdate(_sceneLoadRequestQuery);
}
public void OnDestroy(ref SystemState state)
{
}
public void OnUpdate(ref SystemState state)
{
NativeList<Entity> sceneRequestsToLoad = new NativeList<Entity>(Allocator.Temp);
BufferLookup<SceneIdentifier> sceneBufferLookup = SystemAPI.GetBufferLookup<SceneIdentifier>(false);
foreach (var (loadRequest, entity) in SystemAPI.Query<RefRW<SceneLoadRequest>>().WithEntityAccess())
{
if (sceneBufferLookup.HasBuffer(entity))
{
DynamicBuffer<SceneIdentifier> sceneBuffer = sceneBufferLookup[entity];
bool hasAnyScenesNotStartedLoading = false;
for (int i = 0; i < sceneBuffer.Length; i++)
{
SceneIdentifier scene = sceneBuffer[i];
if (scene.SceneEntity == Entity.Null)
{
hasAnyScenesNotStartedLoading = true;
}
}
if (hasAnyScenesNotStartedLoading)
{
sceneRequestsToLoad.Add(entity);
}
else
{
bool allScenesLoaded = true;
for (int i = 0; i < sceneBuffer.Length; i++)
{
SceneIdentifier scene = sceneBuffer[i];
// Start loading scene if no entity
if (scene.SceneEntity == Entity.Null)
{
}
else
{
// Check if scene loaded
if (!SceneSystem.IsSceneLoaded(state.WorldUnmanaged, scene.SceneEntity))
{
allScenesLoaded = false;
}
sceneBuffer[i] = scene;
}
}
loadRequest.ValueRW.IsLoaded = allScenesLoaded;
}
}
}
for (int i = 0; i < sceneRequestsToLoad.Length; i++)
{
Entity entity = sceneRequestsToLoad[i];
if (SystemAPI.GetBufferLookup<SceneIdentifier>(false).HasBuffer(entity))
{
NativeArray<SceneIdentifier> scenesArray = SystemAPI.GetBufferLookup<SceneIdentifier>(false)[entity].ToNativeArray(Allocator.Temp);
for (int j = 0; j < scenesArray.Length; j++)
{
SceneIdentifier sceneId = scenesArray[j];
if (sceneId.SceneEntity == Entity.Null)
{
sceneId.SceneEntity = SceneSystem.LoadSceneAsync(state.WorldUnmanaged, sceneId.SceneReference);
// Required due to structural changes
DynamicBuffer<SceneIdentifier> buffer = SystemAPI.GetBufferLookup<SceneIdentifier>(false)[entity];
buffer[j] = sceneId;
}
}
scenesArray.Dispose();
}
}
sceneRequestsToLoad.Dispose();
}
}
OnlineFPS/Assets/Scripts/Misc/SpawnPoint.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;

public struct SpawnPoint : IComponentData


{ }
OnlineFPS/Assets/Scripts/Misc/SpawnPointAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;

public class SpawnPointAuthoring : MonoBehaviour


{
public class Baker : Baker<SpawnPointAuthoring>
{
public override void Bake(SpawnPointAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new SpawnPoint());
}
}
}
OnlineFPS/Assets/Scripts/Misc/SpectatorController.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

[Serializable]
public struct SpectatorController : IComponentData
{
[Serializable]
public struct Parameters
{
public float MoveSpeed;
public float MoveSharpness;
public float RotationSpeed;
}

public Parameters Params;


public float3 Velocity;
}
OnlineFPS/Assets/Scripts/Misc/SpectatorControllerAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;

public class SpectatorControllerAuthoring : MonoBehaviour


{
public SpectatorController.Parameters Parameters;
public class Baker : Baker<SpectatorControllerAuthoring>
{
public override void Bake(SpectatorControllerAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new SpectatorController { Params = authoring.Parameters } );
}
}
}
OnlineFPS/Assets/Scripts/Misc/SpectatorControllerSystem.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateBefore(typeof(TransformSystemGroup))]
public partial class SpectatorControllerSystem : SystemBase
{
private FPSInputActions InputActions;
protected override void OnCreate()
{
base.OnCreate();

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;

foreach (var (localTransform, spectatorController) in SystemAPI.Query<RefRW<LocalTransform>, RefRW<SpectatorController>>())


{
float3 moveInput = Vector3.ClampMagnitude(new Vector3(
defaultActionsMap.Move.ReadValue<Vector2>().x,
defaultActionsMap.SpectatorVertical.ReadValue<float>(),
defaultActionsMap.Move.ReadValue<Vector2>().y),
1f);

float2 lookInput = default;


if (math.lengthsq(defaultActionsMap.LookConst.ReadValue<Vector2>()) > math.lengthsq(defaultActionsMap.LookDelta.ReadValue<Vector2>()))
{
// Gamepad look
lookInput = defaultActionsMap.LookConst.ReadValue<Vector2>() * GameSettings.LookSensitivity * deltaTime;
}
else
{
// Mouse look
lookInput = defaultActionsMap.LookDelta.ReadValue<Vector2>() * GameSettings.LookSensitivity;
}

// 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;

public struct SpectatorSpawnPoint : IComponentData


{ }
OnlineFPS/Assets/Scripts/Misc/SpectatorSpawnPointAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;

public class SpectatorSpawnPointAuthoring : MonoBehaviour


{
public class Baker : Baker<SpectatorSpawnPointAuthoring>
{
public override void Bake(SpectatorSpawnPointAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new SpectatorSpawnPoint());
}
}
}
OnlineFPS/Assets/Scripts/Misc/UIReferences.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Entities;
using Unity.Networking.Transport;
using UnityEngine;
using UnityEngine.UIElements;
using Cursor = UnityEngine.Cursor;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class UIReferences : MonoBehaviour


{
public string InitialJoinAddress = "127.0.0.1";
public UIDocument MenuDocument;
public UIDocument CrosshairDocument;
public UIDocument RespawnScreenDocument;

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;

public RigidTransform worldFromA => LocalBody == null


? RigidTransform.identity
: Math.DecomposeRigidBodyTransform(LocalBody.transform.localToWorldMatrix);

public RigidTransform worldFromB => ConnectedBody == null


? RigidTransform.identity
: Math.DecomposeRigidBodyTransform(ConnectedBody.transform.localToWorldMatrix);

public Entity EntityA { get; set; }

public Entity EntityB { get; set; }

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);

public override int GetHashCode() => Value;


}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/EditorInitialization.cs
#if UNITY_EDITOR
using System.Linq;
using UnityEditor;
namespace Unity.Physics.Authoring
{
[InitializeOnLoad]
class EditorInitialization
{
static readonly string k_CustomDefine = "UNITY_PHYSICS_CUSTOM";
static EditorInitialization()
{
var definesStr = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
var defines = definesStr.Split(';').ToList();
var found = defines.Find(define => define.Equals(k_CustomDefine));
if (found == null)
{
defines.Add(k_CustomDefine);
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup,
string.Join(";", defines.ToArray()));
}
}
}
}
#endif
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsCategoryNames.cs
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Physics Category Names", fileName = "Physics Category Names", order = 507)]
public sealed class PhysicsCategoryNames : ScriptableObject, ITagNames
{
PhysicsCategoryNames() {}

IReadOnlyList<string> ITagNames.TagNames => CategoryNames;

public IReadOnlyList<string> CategoryNames => m_CategoryNames;


[SerializeField]
string[] m_CategoryNames = Enumerable.Range(0, 32).Select(i => string.Empty).ToArray();

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);

public override int GetHashCode() => unchecked((int)Value);


}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsMaterialProperties.cs
using System;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
interface IPhysicsMaterialProperties
{
CollisionResponsePolicy CollisionResponse { get; set; }
PhysicsMaterialCoefficient Friction { get; set; }

PhysicsMaterialCoefficient Restitution { get; set; }


PhysicsCategoryTags BelongsTo { get; set; }
PhysicsCategoryTags CollidesWith { get; set; }
// TODO: Enable Mass Factors?
// TODO: Surface Velocity?
CustomPhysicsMaterialTags CustomTags { get; set; }
}

interface IInheritPhysicsMaterialProperties : IPhysicsMaterialProperties


{
PhysicsMaterialTemplate Template { get; set; }
bool OverrideCollisionResponse { get; set; }
bool OverrideFriction { get; set; }
bool OverrideRestitution { get; set; }
bool OverrideBelongsTo { get; set; }
bool OverrideCollidesWith { get; set; }
bool OverrideCustomTags { get; set; }
}

[Serializable]
public struct PhysicsMaterialCoefficient
{
[SoftRange(0f, 1f, TextFieldMax = float.MaxValue)]
public float Value;
public Material.CombinePolicy CombineMode;
}

abstract class OverridableValue<T> where T : struct


{
public bool Override { get => m_Override; set => m_Override = value; }
[SerializeField]
bool m_Override;

public T Value
{
get => m_Value;
set
{
m_Value = value;
Override = true;
}
}
[SerializeField]
T m_Value;

public void OnValidate() => OnValidate(ref m_Value);


protected virtual void OnValidate(ref T 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 PhysicsMaterialTemplate Template


{
get => m_Template;
set => m_Template = m_SupportsTemplate ? value : null;
}
[SerializeField]
[Tooltip("Assign a template to use its values.")]
PhysicsMaterialTemplate m_Template;

static T Get<T>(OverridableValue<T> value, T? templateValue) where T : struct =>


value.Override || templateValue == null ? value.Value : templateValue.Value;

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 };

internal static void OnValidate(ref PhysicsMaterialProperties material, bool supportsTemplate)


{
material.UpgradeVersionIfNecessary();

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();
}

const int k_LatestVersion = 1;

[SerializeField]
int m_SerializedVersion = 0;

void ISerializationCallbackReceiver.OnBeforeSerialize() {}
void ISerializationCallbackReceiver.OnAfterDeserialize() => UpgradeVersionIfNecessary();

internal static bool s_SuppressUpgradeWarnings;

#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


}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsMaterialTemplate.cs
using UnityEngine;

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);

void Reset() => OnValidate();


void OnValidate() => PhysicsMaterialProperties.OnValidate(ref m_Value, false);
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsRenderEntityAuthoring.cs
using UnityEngine;
using Unity.Entities;
using Unity.Physics.GraphicsIntegration;
namespace Unity.Physics.Authoring
{
[AddComponentMenu("Entities/Physics/Physics Render Entity")]
[DisallowMultipleComponent]
public sealed class PhysicsRenderEntityAuthoring : MonoBehaviour
{
[Tooltip("Specifies an Entity in a different branch of the hierarchy that holds the graphical representation of this PhysicsShape.")]
public GameObject RenderEntity;
}
internal class PhysicsRenderEntityBaker : Baker<PhysicsRenderEntityAuthoring>
{
public override void Bake(PhysicsRenderEntityAuthoring authoring)
{
var renderEntity = new PhysicsRenderEntity { Entity = GetEntity(authoring.RenderEntity, TransformUsageFlags.Dynamic) };
var entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, renderEntity);
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/PhysicsBodyAuthoring.cs
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
/// <summary> The physics body authoring. This class cannot be inherited. </summary>
#if UNITY_2021_2_OR_NEWER
[Icon(k_IconPath)]
#endif
[AddComponentMenu("Entities/Physics/Physics Body")]
[DisallowMultipleComponent]
public sealed class PhysicsBodyAuthoring : MonoBehaviour
{
const string k_IconPath = "Packages/com.unity.physics/Unity.Physics.Editor/Editor Default Resources/Icons/d_Rigidbody@64.png";

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;
}

static readonly int[] k_NextAxis = { 1, 2, 0 };


public ShapeType ShapeType => m_ShapeType;
[SerializeField]
ShapeType m_ShapeType = ShapeType.Box;
[SerializeField]
float3 m_PrimitiveCenter;
[SerializeField]
float3 m_PrimitiveSize = new float3(1f, 1f, 1f);
[SerializeField]
EulerAngles m_PrimitiveOrientation = EulerAngles.Default;

[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;
}

public PhysicsCategoryTags BelongsTo


{
get => m_Material.BelongsTo;
set => m_Material.BelongsTo = value;
}
public bool OverrideCollidesWith
{
get => m_Material.OverrideCollidesWith;
set => m_Material.OverrideCollidesWith = value;
}
public PhysicsCategoryTags CollidesWith
{
get => m_Material.CollidesWith;
set => m_Material.CollidesWith = value;
}
public bool OverrideCustomTags
{
get => m_Material.OverrideCustomTags;
set => m_Material.OverrideCustomTags = value;
}
public CustomPhysicsMaterialTags CustomTags { get => m_Material.CustomTags; set => m_Material.CustomTags = value; }
[SerializeField]
PhysicsMaterialProperties m_Material = new PhysicsMaterialProperties(true);

public BoxGeometry GetBoxProperties() => GetBoxProperties(out _);


internal BoxGeometry GetBoxProperties(out EulerAngles orientation)
{
orientation = m_PrimitiveOrientation;
return new BoxGeometry
{
Center = m_PrimitiveCenter,
Size = m_PrimitiveSize,
Orientation = m_PrimitiveOrientation,
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius
};
}
void GetCylindricalProperties(
CylindricalProperties props,
out float3 center, out float height, out float radius, out EulerAngles orientation,
bool rebuildOrientation
)
{
center = m_PrimitiveCenter;
var lookVector = math.mul(m_PrimitiveOrientation, new float3 { [props.Axis] = 1f });
// use previous axis so forward will prefer up
var upVector = math.mul(m_PrimitiveOrientation, new float3 { [k_NextAxis[k_NextAxis[props.Axis]]] = 1f });
orientation = m_PrimitiveOrientation;
if (rebuildOrientation && props.Axis != 2)
orientation.SetValue(quaternion.LookRotation(lookVector, upVector));
radius = props.Radius;
height = props.Height;
}
internal CapsuleGeometryAuthoring GetCapsuleProperties()
{
GetCylindricalProperties(
m_Capsule, out var center, out var height, out var radius, out var orientationEuler, m_ShapeType != ShapeType.Capsule
);
return new CapsuleGeometryAuthoring
{
OrientationEuler = orientationEuler,
Center = center,
Height = height,
Radius = radius
};
}
public CylinderGeometry GetCylinderProperties() => GetCylinderProperties(out _);
internal CylinderGeometry GetCylinderProperties(out EulerAngles orientation)
{
GetCylindricalProperties(
m_Cylinder, out var center, out var height, out var radius, out orientation, m_ShapeType != ShapeType.Cylinder
);
return new CylinderGeometry
{
Center = center,
Height = height,
Radius = radius,
Orientation = orientation,
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius,
SideCount = m_CylinderSideCount
};
}
public SphereGeometry GetSphereProperties(out quaternion orientation)
{
var result = GetSphereProperties(out EulerAngles euler);
orientation = euler;
return result;
}
internal SphereGeometry GetSphereProperties(out EulerAngles orientation)
{
orientation = m_PrimitiveOrientation;
return new SphereGeometry
{
Center = m_PrimitiveCenter,
Radius = m_SphereRadius
};
}
public void GetPlaneProperties(out float3 center, out float2 size, out quaternion orientation)
{
GetPlaneProperties(out center, out size, out EulerAngles euler);
orientation = euler;
}
internal void GetPlaneProperties(out float3 center, out float2 size, out EulerAngles orientation)
{
center = m_PrimitiveCenter;
orientation = m_PrimitiveOrientation;
if (m_ShapeType == ShapeType.Plane)
{
size = m_PrimitiveSize.xz;
return;
}
UpdateCapsuleAxis();
var look = m_Capsule.Axis;
var nextAx = k_NextAxis[look];
var prevAx = k_NextAxis[k_NextAxis[look]];
var ax2 = m_PrimitiveSize[nextAx] > m_PrimitiveSize[prevAx] ? nextAx : prevAx;
size = new float2(m_PrimitiveSize[ax2], m_PrimitiveSize[look]);
var up = k_NextAxis[ax2] == look ? k_NextAxis[look] : k_NextAxis[ax2];
var offset = quaternion.LookRotation(new float3 { [look] = 1f }, new float3 { [up] = 1f });
orientation.SetValue(math.mul(m_PrimitiveOrientation, offset));
}
static readonly HashSet<int> s_BoneIDs = new HashSet<int>();
static readonly HashSet<Transform> s_BonesInHierarchy = new HashSet<Transform>();
static readonly List<Vector3> s_Vertices = new List<Vector3>(65535);
static readonly List<int> s_Indices = new List<int>(65535);
static UnityMesh ReusableBakeMesh =>
s_ReusableBakeMesh ??
(s_ReusableBakeMesh = new UnityMesh { hideFlags = HideFlags.HideAndDontSave });
static UnityMesh s_ReusableBakeMesh;
public void GetConvexHullProperties(NativeList<float3> pointCloud) =>
GetConvexHullProperties(pointCloud, true, default, default, default, default);
internal void GetConvexHullProperties(
NativeList<float3> pointCloud, bool validate,
NativeList<HashableShapeInputs> inputs, NativeList<int> allSkinIndices, NativeList<float> allBlendShapeWeights,
HashSet<UnityMesh> meshAssets
)
{
if (pointCloud.IsCreated)
pointCloud.Clear();
if (inputs.IsCreated)
inputs.Clear();
if (allSkinIndices.IsCreated)
allSkinIndices.Clear();
if (allBlendShapeWeights.IsCreated)
allBlendShapeWeights.Clear();
meshAssets?.Clear();
if (m_CustomMesh != null)
{
if (validate && !m_CustomMesh.IsValidForConversion(gameObject))
return;
AppendMeshPropertiesToNativeBuffers(
transform.localToWorldMatrix, m_CustomMesh, pointCloud, default, validate, inputs, meshAssets
);
}
else
{
using (var scope = new GetActiveChildrenScope<MeshFilter>(this, transform))
{
foreach (var meshFilter in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(meshFilter, validate))
{
AppendMeshPropertiesToNativeBuffers(
meshFilter.transform.localToWorldMatrix, meshFilter.sharedMesh, pointCloud, default, validate, inputs, meshAssets
);
}
}
}

using (var skinnedPoints = new NativeList<float3>(8192, Allocator.Temp))


using (var skinnedInputs = new NativeList<HashableShapeInputs>(8, Allocator.Temp))
{
GetAllSkinnedPointsInHierarchyBelongingToShape(
this, skinnedPoints, validate, skinnedInputs, allSkinIndices, allBlendShapeWeights
);
if (pointCloud.IsCreated)
pointCloud.AddRange(skinnedPoints.AsArray());
if (inputs.IsCreated)
inputs.AddRange(skinnedInputs.AsArray());
}
}
}

internal static void GetAllSkinnedPointsInHierarchyBelongingToShape(


PhysicsShapeAuthoring shape, NativeList<float3> pointCloud, bool validate,
NativeList<HashableShapeInputs> inputs, NativeList<int> allIncludedIndices, NativeList<float> allBlendShapeWeights
)
{
if (pointCloud.IsCreated)
pointCloud.Clear();
if (inputs.IsCreated)
inputs.Clear();
// get all the transforms that belong to this shape
s_BonesInHierarchy.Clear();
using (var scope = new GetActiveChildrenScope<Transform>(shape, shape.transform))
{
foreach (var bone in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(bone))
s_BonesInHierarchy.Add(bone);
}
}
// find all skinned mesh renderers in which this shape's transform might be a bone
using (var scope = new GetActiveChildrenScope<SkinnedMeshRenderer>(shape, shape.transform.root))
{
foreach (var skin in scope.Buffer)
{
var mesh = skin.sharedMesh;
if (
!skin.enabled
|| mesh == null
|| validate && !mesh.IsValidForConversion(shape.gameObject)
|| !scope.IsChildActiveAndBelongsToShape(skin)
)
continue;
// get indices of this shape's transform hierarchy in skinned mesh's bone array
s_BoneIDs.Clear();
var bones = skin.bones;
for (int i = 0, count = bones.Length; i < count; ++i)
{
if (s_BonesInHierarchy.Contains(bones[i]))
s_BoneIDs.Add(i);
}
if (s_BoneIDs.Count == 0)
continue;
// sample the vertices
if (pointCloud.IsCreated)
{
skin.BakeMesh(ReusableBakeMesh);
ReusableBakeMesh.GetVertices(s_Vertices);
}
// add all vertices weighted to at least one bone in this shape's transform hierarchy
var bonesPerVertex = mesh.GetBonesPerVertex(); // Allocator.None
var weights = mesh.GetAllBoneWeights(); // Allocator.None
var vertexIndex = 0;
var weightsOffset = 0;
var shapeFromSkin = math.mul(shape.transform.worldToLocalMatrix, skin.transform.localToWorldMatrix);
var includedIndices = new NativeList<int>(mesh.vertexCount, Allocator.Temp);
foreach (var weightCount in bonesPerVertex)
{
var totalWeight = 0f;
for (var i = 0; i < weightCount; ++i)
{
var weight = weights[weightsOffset + i];
if (s_BoneIDs.Contains(weight.boneIndex))
totalWeight += weight.weight;
}
if (totalWeight > shape.m_MinimumSkinnedVertexWeight)
{
if (pointCloud.IsCreated)
pointCloud.Add(math.mul(shapeFromSkin, new float4(s_Vertices[vertexIndex], 1f)).xyz);
includedIndices.Add(vertexIndex);
}
weightsOffset += weightCount;
++vertexIndex;
}
if (!inputs.IsCreated || !allIncludedIndices.IsCreated || !allBlendShapeWeights.IsCreated)
continue;
var blendShapeWeights = new NativeArray<float>(mesh.blendShapeCount, Allocator.Temp);
for (var i = 0; i < blendShapeWeights.Length; ++i)
blendShapeWeights[i] = skin.GetBlendShapeWeight(i);
var data = HashableShapeInputs.FromSkinnedMesh(
mesh, shapeFromSkin, includedIndices.AsArray(), allIncludedIndices, blendShapeWeights, allBlendShapeWeights
);
inputs.Add(data);
}
}
s_BonesInHierarchy.Clear();
}
public void GetMeshProperties(NativeList<float3> vertices, NativeList<int3> triangles) =>
GetMeshProperties(vertices, triangles, true, default);
internal void GetMeshProperties(
NativeList<float3> vertices, NativeList<int3> triangles, bool validate, NativeList<HashableShapeInputs> inputs, HashSet<UnityMesh> meshAssets = null
)
{
if (vertices.IsCreated)
vertices.Clear();
if (triangles.IsCreated)
triangles.Clear();
if (inputs.IsCreated)
inputs.Clear();
meshAssets?.Clear();
if (m_CustomMesh != null)
{
if (validate && !m_CustomMesh.IsValidForConversion(gameObject))
return;
AppendMeshPropertiesToNativeBuffers(
transform.localToWorldMatrix, m_CustomMesh, vertices, triangles, validate, inputs, meshAssets
);
}
else
{
using (var scope = new GetActiveChildrenScope<MeshFilter>(this, transform))
{
foreach (var meshFilter in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(meshFilter, validate))
{
AppendMeshPropertiesToNativeBuffers(
meshFilter.transform.localToWorldMatrix, meshFilter.sharedMesh, vertices, triangles, validate, inputs, meshAssets
);
}
}
}
}
}

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();

void Sync(ref CylindricalProperties props)


{
props.Height = m_PrimitiveSize[props.Axis];
props.Radius = 0.5f * math.max(
m_PrimitiveSize[k_NextAxis[props.Axis]],
m_PrimitiveSize[k_NextAxis[k_NextAxis[props.Axis]]]
);
}
void SyncCapsuleProperties()
{
if (m_ShapeType == ShapeType.Box || m_ShapeType == ShapeType.Plane)
UpdateCapsuleAxis();
else
m_Capsule.Axis = 2;
Sync(ref m_Capsule);
}
void SyncCylinderProperties()
{
if (m_ShapeType == ShapeType.Box || m_ShapeType == ShapeType.Plane)
UpdateCylinderAxis();
else
m_Cylinder.Axis = 2;
Sync(ref m_Cylinder);
}
void SyncSphereProperties()
{
m_SphereRadius = 0.5f * math.cmax(m_PrimitiveSize);
}
public void SetBox(BoxGeometry geometry)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(geometry.Orientation);
SetBox(geometry, euler);
}
internal void SetBox(BoxGeometry geometry, EulerAngles orientation)
{
m_ShapeType = ShapeType.Box;
m_PrimitiveCenter = geometry.Center;
m_PrimitiveSize = math.max(geometry.Size, new float3());
m_PrimitiveOrientation = orientation;
m_ConvexHullGenerationParameters.BevelRadius = geometry.BevelRadius;
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
internal void SetCapsule(CapsuleGeometryAuthoring geometry)
{
m_ShapeType = ShapeType.Capsule;
m_PrimitiveCenter = geometry.Center;
m_PrimitiveOrientation = geometry.OrientationEuler;
var radius = math.max(0f, geometry.Radius);
var height = math.max(0f, geometry.Height);
m_PrimitiveSize = new float3(radius * 2f, radius * 2f, height);
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetCylinder(CylinderGeometry geometry)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(geometry.Orientation);
SetCylinder(geometry, euler);
}
internal void SetCylinder(CylinderGeometry geometry, EulerAngles orientation)
{
m_ShapeType = ShapeType.Cylinder;
m_PrimitiveCenter = geometry.Center;
m_PrimitiveOrientation = orientation;
geometry.Radius = math.max(0f, geometry.Radius);
geometry.Height = math.max(0f, geometry.Height);
m_PrimitiveSize = new float3(geometry.Radius * 2f, geometry.Radius * 2f, geometry.Height);

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;
}

bool GetMeshes(PhysicsShapeAuthoring shape, out List<UnityEngine.Mesh> meshes, out List<float4x4> childrenToShape)


{
meshes = new List<UnityMesh>();
childrenToShape = new List<float4x4>();
if (shape.CustomMesh != null)
{
meshes.Add(shape.CustomMesh);
childrenToShape.Add(float4x4.identity);
}
// Try to get all the meshes in the children
var meshFilters = GetComponentsInChildren<MeshFilter>();
foreach (var meshFilter in meshFilters)
{
if (meshFilter != null && meshFilter.sharedMesh != null)
{
var shapeAuthoring = GetComponent<PhysicsShapeAuthoring>(meshFilter);
if (shapeAuthoring != null && shapeAuthoring != shape)
{
// Skip this case, since it will be treated independently
continue;
}
meshes.Add(meshFilter.sharedMesh);
// Don't calculate the children to shape if not needed, to avoid approximation that could prevent collider to be shared
if (shape.transform.localToWorldMatrix.Equals(meshFilter.transform.localToWorldMatrix))
childrenToShape.Add(float4x4.identity);
else
{
var transform = math.mul(shape.transform.worldToLocalMatrix, meshFilter.transform.localToWorldMatrix);
childrenToShape.Add(transform);
}
DependsOn(meshes.Last());
}
}
return meshes.Count > 0;
}
UnityEngine.Mesh CombineMeshes(PhysicsShapeAuthoring shape, List<UnityEngine.Mesh> meshes, List<float4x4> childrenToShape)
{
var instances = new List<CombineInstance>();
var numVertices = 0;
for (var i = 0; i < meshes.Count; ++i)
{
var currentMesh = meshes[i];
var currentChildToShape = childrenToShape[i];
if (!currentMesh.IsValidForConversion(shape.gameObject))
{
throw new InvalidOperationException(
$"Mesh '{currentMesh}' assigned on {shape.name} is not readable. Ensure that you have enabled Read/Write on its import settings."
);
}
// Combine submeshes manually
numVertices += meshes[i].vertexCount;
var combinedSubmeshes = new UnityEngine.Mesh();
combinedSubmeshes.vertices = currentMesh.vertices;
var combinedIndices = new List<int>();
for (int indexSubMesh = 0; indexSubMesh < meshes[i].subMeshCount; ++indexSubMesh)
{
combinedIndices.AddRange(currentMesh.GetIndices(indexSubMesh));
}
combinedSubmeshes.SetIndices(combinedIndices, MeshTopology.Triangles, 0);
combinedSubmeshes.RecalculateNormals();
var instance = new CombineInstance
{
mesh = combinedSubmeshes,
transform = currentChildToShape,
};
instances.Add(instance);
}
var mesh = new UnityEngine.Mesh();
mesh.indexFormat = numVertices > UInt16.MaxValue ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
mesh.CombineMeshes(instances.ToArray());
mesh.RecalculateBounds();
return mesh;
}
private ShapeComputationDataBaking GenerateComputationData(PhysicsShapeAuthoring shape, ColliderInstanceBaking colliderInstance, Entity colliderEntity)
{
var res = new ShapeComputationDataBaking
{
Instance = colliderInstance,
Material = ProduceMaterial(shape),
CollisionFilter = ProduceCollisionFilter(shape),
ForceUniqueIdentifier = shape.ForceUnique ? (uint)shape.GetInstanceID() : 0u
};
var transform = shape.transform;
var localToWorld = transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
EulerAngles orientation;
res.ShapeType = shape.ShapeType;
switch (shape.ShapeType)
{
case ShapeType.Box:
{
res.BoxProperties = shape.GetBoxProperties(out orientation)
.BakeToBodySpace(localToWorld, shapeToWorld, orientation);
break;
}
case ShapeType.Capsule:
{
res.CapsuleProperties = shape.GetCapsuleProperties()
.BakeToBodySpace(localToWorld, shapeToWorld)
.ToRuntime();
break;
}
case ShapeType.Sphere:
{
res.SphereProperties = shape.GetSphereProperties(out orientation)
.BakeToBodySpace(localToWorld, shapeToWorld, ref orientation);
break;
}
case ShapeType.Cylinder:
{
res.CylinderProperties = shape.GetCylinderProperties(out orientation)
.BakeToBodySpace(localToWorld, shapeToWorld, orientation);
break;
}
case ShapeType.Plane:
{
shape.GetPlaneProperties(out var center, out var size, out orientation);
PhysicsShapeExtensions.BakeToBodySpace(
center, size, orientation, localToWorld, shapeToWorld,
out res.PlaneVertices.c0, out res.PlaneVertices.c1, out res.PlaneVertices.c2, out res.PlaneVertices.c3
);
break;
}
case ShapeType.ConvexHull:
{
res.ConvexHullProperties.Filter = res.CollisionFilter;
res.ConvexHullProperties.Material = res.Material;
res.ConvexHullProperties.GenerationParameters = shape.ConvexHullGenerationParameters.ToRunTime();
CreateMeshAuthoringData(shape, colliderEntity);
break;
}
case ShapeType.Mesh:
{
res.MeshProperties.Filter = res.CollisionFilter;
res.MeshProperties.Material = res.Material;
CreateMeshAuthoringData(shape, colliderEntity);
break;
}
}
return res;
}
private void CreateMeshAuthoringData(PhysicsShapeAuthoring shape, Entity colliderEntity)
{
if (GetMeshes(shape, out var meshes, out var childrenToShape))
{
// Combine all detected meshes into a single one
var mesh = CombineMeshes(shape, meshes, childrenToShape);
if (!mesh.IsValidForConversion(shape.gameObject))
{
throw new InvalidOperationException(
$"Mesh '{mesh}' assigned on {shape.name} is not readable. Ensure that you have enabled Read/Write on its import settings."
);
}
var bakeFromShape = shape.GetLocalToShapeMatrix();
var meshBakingData = new PhysicsMeshAuthoringData()
{
Convex = shape.ShapeType == ShapeType.ConvexHull,
Mesh = mesh,
BakeFromShape = bakeFromShape,
MeshBounds = mesh.bounds,
ChildToShape = float4x4.identity
};
AddComponent(colliderEntity, meshBakingData);
}
else
{
throw new InvalidOperationException(
$"No {nameof(PhysicsShapeAuthoring.CustomMesh)} or {nameof(MeshFilter.sharedMesh)} assigned on {shape.name}."
);
}
}
public override void Bake(PhysicsShapeAuthoring authoring)
{
var shapeBakingData = new PhysicsColliderAuthoringData();
// First pass
Profiler.BeginSample("Collect Inputs from Authoring Components");
if (ShouldConvertShape(authoring))
{
// We can have multiple Colliders of the same type on the same game object, so instead of adding the components to the baking entity
// we add the components to an additional entity. These new entities will be processed by the baking system
var colliderEntity = CreateAdditionalEntity(TransformUsageFlags.None, true);
shapeBakingData.ShapeComputationalData = GetInputDataFromAuthoringComponent(authoring, colliderEntity);
AddComponent(colliderEntity, shapeBakingData);
// The data will be filled in by the BaseShapeBakingSystem, but we add it here so it gets reverted from the entity if the collider component is deleted
AddComponent(colliderEntity, new PhysicsColliderBakedData()
{
BodyEntity = shapeBakingData.ShapeComputationalData.Instance.BodyEntity,
BodyFromShape = shapeBakingData.ShapeComputationalData.Instance.BodyFromShape,
ChildEntity = shapeBakingData.ShapeComputationalData.Instance.ChildEntity,
// It is a leaf if the Shape Entity equals Body Entity
IsLeafEntityBody = (shapeBakingData.ShapeComputationalData.Instance.ShapeEntity.Equals(shapeBakingData.ShapeComputationalData.Instance.BodyEntity))
});
}
Profiler.EndSample();
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/BallAndSocketJoint.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

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;

public float3 PositionLocal;


public float3 PositionInConnectedEntity;

public virtual void UpdateAuto()


{
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
PositionInConnectedEntity = math.transform(bFromA, PositionLocal);
}
}
}
public abstract class JointBaker<T> : Baker<T> where T : BaseJoint
{
protected PhysicsConstrainedBodyPair GetConstrainedBodyPair(BaseJoint authoring)
{
return new PhysicsConstrainedBodyPair(
GetEntity(TransformUsageFlags.Dynamic),
authoring.ConnectedBody == null ? Entity.Null : GetEntity(authoring.ConnectedBody, TransformUsageFlags.Dynamic),
authoring.EnableCollision
);
}
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 uint GetWorldIndex(Component c)


{
uint worldIndex = 0;
if (c)
{
var physicsBody = GetComponent<PhysicsBodyAuthoring>(c);
if (physicsBody != null)
{
worldIndex = physicsBody.WorldIndex;
}
}
return worldIndex;
}
public uint GetWorldIndexFromBaseJoint(BaseJoint authoring)
{
var physicsBody = GetComponent<PhysicsBodyAuthoring>(authoring);
uint worldIndex = physicsBody.WorldIndex;
if (authoring.ConnectedBody == null)
{
return worldIndex;
}
var connectedBody = GetComponent<PhysicsBodyAuthoring>(authoring.ConnectedBody);
if (connectedBody != null)
{
Assertions.Assert.AreEqual(worldIndex, connectedBody.WorldIndex);
}
return worldIndex;
}
public void CreateJointEntities(uint worldIndex, PhysicsConstrainedBodyPair constrainedBodyPair, NativeArray<PhysicsJoint> joints, NativeList<Entity> newJointEntities)
{
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;
var entity = GetEntity(TransformUsageFlags.Dynamic);
for (var i = 0; i < joints.Length; ++i)
{
var jointEntity = CreateAdditionalEntity(TransformUsageFlags.Dynamic);
AddSharedComponent(jointEntity, new PhysicsWorldIndex(worldIndex));
AddComponent(jointEntity, constrainedBodyPair);
AddComponent(jointEntity, joints[i]);
newJointEntities.Add(jointEntity);
if (GetComponent<ModifyJointLimitsAuthoring>() != null)
{
AddComponent(jointEntity, new JointEntityBaking()
{
Entity = entity
});
AddSharedComponentManaged(jointEntity, new ModifyJointLimits());
}
}
if (multipleJoints)
{
// set companion buffers for new joints
for (var i = 0; i < joints.Length; ++i)
{
var companions = AddBuffer<PhysicsJointCompanion>(newJointEntities[i]);
for (var j = 0; j < joints.Length; ++j)
{
if (i == j)
continue;
companions.Add(new PhysicsJointCompanion {JointEntity = newJointEntities[j]});
}
}
}
}
}
class BallAndSocketJointBaker : JointBaker<BallAndSocketJoint>
{
public override void Bake(BallAndSocketJoint authoring)
{
authoring.UpdateAuto();
var physicsJoint = PhysicsJoint.CreateBallAndSocket(authoring.PositionLocal, authoring.PositionInConnectedEntity);
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/BaseJoint.cs
using Unity.Mathematics;

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;

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;
for (var i = 0; i < joints.Length; ++i)
{
var jointEntity = CreateAdditionalEntity(TransformUsageFlags.Dynamic);
AddSharedComponent(jointEntity, new PhysicsWorldIndex(worldIndex));
AddComponent(jointEntity, constrainedBodyPair);
AddComponent(jointEntity, joints[i]);
newJointEntities.Add(jointEntity);
}
if (multipleJoints)
{
// set companion buffers for new joints
for (var i = 0; i < joints.Length; ++i)
{
var companions = AddBuffer<PhysicsJointCompanion>(newJointEntities[i]);
for (var j = 0; j < joints.Length; ++j)
{
if (i == j)
continue;
companions.Add(new PhysicsJointCompanion {JointEntity = newJointEntities[j]});
}
}
}
}
protected PhysicsConstrainedBodyPair GetConstrainedBodyPair(LimitDOFJoint authoring)
{
return new PhysicsConstrainedBodyPair(
GetEntity(TransformUsageFlags.Dynamic),
authoring.ConnectedBody == null ? Entity.Null : GetEntity(authoring.ConnectedBody, TransformUsageFlags.Dynamic),
authoring.EnableCollision
);
}
public uint GetWorldIndex(Component c)
{
uint worldIndex = 0;
var physicsBody = GetComponent<PhysicsBodyAuthoring>(c);
if (physicsBody != null)
{
worldIndex = physicsBody.WorldIndex;
}
return worldIndex;
}
public override void Bake(LimitDOFJoint authoring)
{
if (!math.any(authoring.LockLinearAxes) && !math.any(authoring.LockAngularAxes))
return;
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
PhysicsJoint physicsJoint = authoring.CreateLimitDOFJoint(bFromA);
var worldIndex = GetWorldIndex(authoring);
CreateJointEntity(
worldIndex,
GetConstrainedBodyPair(authoring),
physicsJoint
);
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/LimitedDistanceJoint.cs
using Unity.Entities;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class LimitedDistanceJoint : BallAndSocketJoint
{
public float MinDistance;
public float MaxDistance;
}
class LimitedDistanceJointBaker : JointBaker<LimitedDistanceJoint>
{
public override void Bake(LimitedDistanceJoint authoring)
{
authoring.UpdateAuto();
var physicsJoint = PhysicsJoint.CreateLimitedDistance(authoring.PositionLocal, authoring.PositionInConnectedEntity, new FloatRange(authoring.MinDistance, authoring.MaxDistance
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/LimitedHingeJoint.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class LimitedHingeJoint : FreeHingeJoint
{
// Editor only settings
[HideInInspector]
public bool EditLimits;

public float3 PerpendicularAxisLocal;


public float3 PerpendicularAxisInConnectedEntity;
public float MinAngle;
public float MaxAngle;

public override void UpdateAuto()


{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
HingeAxisInConnectedEntity = math.mul(bFromA.rot, HingeAxisLocal);
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
}
}
}

class LimitedHingeJointBaker : JointBaker<LimitedHingeJoint>


{
public override void Bake(LimitedHingeJoint authoring)
{
authoring.UpdateAuto();

var physicsJoint = PhysicsJoint.CreateLimitedHinge(


new BodyFrame
{
Axis = math.normalize(authoring.HingeAxisLocal),
PerpendicularAxis = math.normalize(authoring.PerpendicularAxisLocal),
Position = authoring.PositionLocal
},
new BodyFrame
{
Axis = math.normalize(authoring.HingeAxisInConnectedEntity),
PerpendicularAxis = math.normalize(authoring.PerpendicularAxisInConnectedEntity),
Position = authoring.PositionInConnectedEntity
},
math.radians(new FloatRange(authoring.MinAngle, authoring.MaxAngle))
);

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/ModifyJointLimitsAuthoring.cs
using System;
using System.Collections.Generic;
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 UnityEngine;
using LegacyJoint = UnityEngine.Joint;
using FloatRange = Unity.Physics.Math.FloatRange;

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);

public override int GetHashCode() =>


unchecked((AngularRangeScalar.GetHashCode() * 397) ^ LinearRangeScalar.GetHashCode());
}

// an authoring component to add to a GameObject with one or more Joint


public class ModifyJointLimitsAuthoring : MonoBehaviour
{
public ParticleSystem.MinMaxCurve AngularRangeScalar = new ParticleSystem.MinMaxCurve(
1f,
min: new AnimationCurve(
new Keyframe(0f, 0f, 0f, 0f),
new Keyframe(2f, -2f, 0f, 0f),
new Keyframe(4f, 0f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
},
max: new AnimationCurve(
new Keyframe(0f, 1f, 0f, 0f),
new Keyframe(2f, -1f, 0f, 0f),
new Keyframe(4f, 1f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
}
);
public ParticleSystem.MinMaxCurve LinearRangeScalar = new ParticleSystem.MinMaxCurve(
1f,
min: new AnimationCurve(
new Keyframe(0f, 1f, 0f, 0f),
new Keyframe(2f, 0.5f, 0f, 0f),
new Keyframe(4f, 1f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
},
max: new AnimationCurve(
new Keyframe(0f, 0.5f, 0f, 0f),
new Keyframe(2f, 0f, 0f, 0f),
new Keyframe(4f, 0.5f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
}
);
}
[BakingType]
public class ModifyJointLimitsBakingData : IComponentData
{
public ParticleSystem.MinMaxCurve AngularRangeScalar;
public ParticleSystem.MinMaxCurve LinearRangeScalar;
}
class ModifyJointLimitsBaker : Baker<ModifyJointLimitsAuthoring>
{
public override void Bake(ModifyJointLimitsAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponentObject(entity, new ModifyJointLimitsBakingData
{
AngularRangeScalar = authoring.AngularRangeScalar,
LinearRangeScalar = authoring.LinearRangeScalar
});
}
}
// after joints have been converted, find the entities they produced and add ModifyJointLimits to them
[UpdateAfter(typeof(EndJointBakingSystem))]
[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
partial struct ModifyJointLimitsBakingSystem : ISystem
{
private EntityQuery _ModifyJointLimitsBakingDataQuery;
private EntityQuery _JointEntityBakingQuery;
public void OnCreate(ref SystemState state)
{
_ModifyJointLimitsBakingDataQuery = state.GetEntityQuery(new EntityQueryDesc
{
All = new[] {ComponentType.ReadOnly<ModifyJointLimitsBakingData>()},
Options = EntityQueryOptions.IncludeDisabledEntities | EntityQueryOptions.IncludePrefab
});
_JointEntityBakingQuery = state.GetEntityQuery(new EntityQueryDesc
{
All = new[] {ComponentType.ReadOnly<JointEntityBaking>()}
});

_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;

public override void UpdateAuto()


{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
AxisInConnectedEntity = math.mul(bFromA.rot, AxisLocal);
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
}
}
}

class PrismaticJointBaker : JointBaker<PrismaticJoint>


{
public override void Bake(PrismaticJoint authoring)
{
authoring.UpdateAuto();

var physicsJoint = PhysicsJoint.CreatePrismatic(


new BodyFrame
{
Axis = authoring.AxisLocal,
PerpendicularAxis = authoring.PerpendicularAxisLocal,
Position = authoring.PositionLocal
},
new BodyFrame
{
Axis = authoring.AxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
new FloatRange(authoring.MinDistanceOnAxis, authoring.MaxDistanceOnAxis)
);
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/RagdollJoint.cs
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class RagdollJoint : BallAndSocketJoint
{
const int k_LatestVersion = 1;
// Editor only settings
[HideInInspector]
public bool EditAxes;
[HideInInspector]
public bool EditLimits;
[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);
}
}
}
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;

public override void UpdateAuto()


{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
OrientationInConnectedEntity = math.mul(bFromA.rot, OrientationLocal);
}
{
OrientationLocal = math.normalize(OrientationLocal);
OrientationInConnectedEntity = math.normalize(OrientationInConnectedEntity);
}
}
}

class RigidJointBaker : JointBaker<RigidJoint>


{
public override void Bake(RigidJoint authoring)
{
authoring.UpdateAuto();

var physicsJoint = PhysicsJoint.CreateFixed(


new RigidTransform(authoring.OrientationLocal, authoring.PositionLocal),
new RigidTransform(authoring.OrientationInConnectedEntity, authoring.PositionInConnectedEntity)
);

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/Motors/AngularVelocityMotor.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

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

var joint = PhysicsJoint.CreateAngularVelocityMotor(


new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularLocal,
Position = authoring.PivotPosition
},
new BodyFrame
{
Axis = authoring.HingeAxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
math.radians(authoring.TargetSpeed),
authoring.MaxImpulseAppliedByMotor
);
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/LinearVelocityMotor.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
public class LinearVelocityMotor : BaseJoint
{
[Tooltip("An offset from the center of the body with the motor (bodyA), 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 at this speed from the initial position of bodyA, along the Direction of Movement, in m/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 AxisInConnectedEntity;
private float3 PerpendicularAxisInConnectedEntity;
class LinearVelocityMotorBaker : JointBaker<LinearVelocityMotor>
{
public override void Bake(LinearVelocityMotor authoring)
{
float3 axisInB = math.normalize(authoring.DirectionOfMovement);

RigidTransform aFromB = math.mul(math.inverse(authoring.worldFromA), authoring.worldFromB);


float3 axisInA = math.mul(aFromB.rot, axisInB); //motor axis relative to bodyA
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.AnchorPosition); //position of motored body relative to Connected Entity in world space
authoring.AxisInConnectedEntity = axisInB; //motor axis in Connected Entity space

// Always calculate the perpendicular axes


Math.CalculatePerpendicularNormalized(axisInA, out var perpendicularAxisLocal, out _);
authoring.PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, perpendicularAxisLocal); //perp motor axis in Connected Entity space
var joint = PhysicsJoint.CreateLinearVelocityMotor(
new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularAxisLocal,
Position = authoring.AnchorPosition
},
new BodyFrame
{
Axis = authoring.AxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
authoring.TargetSpeed,
authoring.MaxImpulseAppliedByMotor
);
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/PositionMotor.cs
using Unity.Mathematics;
using UnityEngine;

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;

class PositionMotorBaker : JointBaker<PositionMotor>


{
public override void Bake(PositionMotor authoring)
{
float3 axisInB = math.normalize(authoring.DirectionOfMovement);
RigidTransform aFromB = math.mul(math.inverse(authoring.worldFromA), authoring.worldFromB);
float3 axisInA = math.mul(aFromB.rot, axisInB); //motor axis relative to bodyA
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.AnchorPosition); //position of motored body relative to Connected Entity in world space
authoring.AxisInConnectedEntity = axisInB; //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
var joint = PhysicsJoint.CreatePositionMotor(
new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularLocal,
Position = authoring.AnchorPosition
},
new BodyFrame
{
Axis = authoring.AxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
authoring.TargetDistance,
authoring.MaxImpulseAppliedByMotor
);

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

var joint = PhysicsJoint.CreateRotationalMotor(


new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularLocal,
Position = authoring.PivotPosition
},
new BodyFrame
{
Axis = authoring.HingeAxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
math.radians(authoring.TargetAngle),
authoring.MaxImpulseAppliedByMotor
);
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/Utilities/BakeGeometryJobs.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
static partial class PhysicsShapeExtensions
{
static void MakeZAxisPrimaryBasis(ref int3 basisPriority)
{
if (basisPriority[1] == 2)
basisPriority = basisPriority.yxz;
else if (basisPriority[2] == 2)
basisPriority = basisPriority.zxy;
}
#region Box
[BurstCompile]
internal struct BakeBoxJob : IJob
{
public NativeArray<BoxGeometry> Box;

// TODO: make members PascalCase after merging static query fixes


public float4x4 localToWorld;
public float4x4 shapeToWorld;
public EulerAngles orientation;
public static float4x4 GetBakeToShape(float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center,
ref EulerAngles orientation)
{
float4x4 bakeToShape;
float4x4 rotationMatrix = float4x4.identity;
var basisPriority = k_DefaultAxisPriority;
var sheared = localToWorld.HasShear();
if (localToWorld.HasNonUniformScale() || sheared)
{
if (sheared)
{
var transformScale = localToWorld.DecomposeScale();
var basisToWorld =
GetBasisToWorldMatrix(localToWorld, center, orientation, transformScale);
basisPriority = GetBasisAxisPriority(basisToWorld);
}
rotationMatrix = new float4x4(
new float4 { [basisPriority[2]] = 1 },
new float4 { [basisPriority[1]] = 1 },
new float4 { [basisPriority[0]] = 1 },
new float4 { [3] = 1 }
);
}
bakeToShape = GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center,
ref orientation, 1f, basisPriority);
bakeToShape = math.mul(bakeToShape, rotationMatrix);
return bakeToShape;
}
public void Execute()
{
var center = Box[0].Center;
var size = Box[0].Size;
var bevelRadius = Box[0].BevelRadius;
var bakeToShape = GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientation);
bakeToShape = math.mul(bakeToShape, float4x4.Scale(size));
var scale = bakeToShape.DecomposeScale();
size = scale;
Box[0] = new BoxGeometry
{
Center = center,
Orientation = orientation,
Size = size,
BevelRadius = math.clamp(bevelRadius, 0f, 0.5f * math.cmin(size))
};
}
}
#endregion
#region Capsule
[BurstCompile]
internal struct BakeCapsuleJob : IJob
{
public NativeArray<CapsuleGeometryAuthoring> Capsule;
// TODO: make members PascalCase after merging static query fixes
public float4x4 localToWorld;
public float4x4 shapeToWorld;
public static float4x4 GetBakeToShape(float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center,
ref EulerAngles orientation)
{
var basisPriority = k_DefaultAxisPriority;
var sheared = localToWorld.HasShear();
if (localToWorld.HasNonUniformScale() || sheared)
{
if (sheared)
{
var transformScale = localToWorld.DecomposeScale();
var basisToWorld = GetBasisToWorldMatrix(localToWorld, center, orientation, transformScale);
basisPriority = GetBasisAxisPriority(basisToWorld);
}
MakeZAxisPrimaryBasis(ref basisPriority);
}
return GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center, ref orientation, 1f,
basisPriority);
}
public void Execute()
{
var radius = Capsule[0].Radius;
var center = Capsule[0].Center;
var height = Capsule[0].Height;
var orientationEuler = Capsule[0].OrientationEuler;
var bakeToShape = GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientationEuler);
var scale = bakeToShape.DecomposeScale();
radius *= math.cmax(scale.xy);
height = math.max(0, height * scale.z);
Capsule[0] = new CapsuleGeometryAuthoring
{
OrientationEuler = orientationEuler,
Center = center,
Height = height,
Radius = radius
};
}
}
#endregion
#region Cylinder
[BurstCompile]
internal struct BakeCylinderJob : IJob
{
public NativeArray<CylinderGeometry> Cylinder;
// TODO: make members PascalCase after merging static query fixes
public float4x4 localToWorld;
public float4x4 shapeToWorld;
public EulerAngles orientation;
public static float4x4 GetBakeToShape(float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center,
ref EulerAngles orientation)
{
var basisPriority = k_DefaultAxisPriority;
var sheared = localToWorld.HasShear();
if (localToWorld.HasNonUniformScale() || sheared)
{
if (sheared)
{
var transformScale = localToWorld.DecomposeScale();
var basisToWorld = GetBasisToWorldMatrix(localToWorld, center, orientation, transformScale);
basisPriority = GetBasisAxisPriority(basisToWorld);
}
MakeZAxisPrimaryBasis(ref basisPriority);
}
return GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center, ref orientation, 1f,
basisPriority);
}
public void Execute()
{
var center = Cylinder[0].Center;
var height = Cylinder[0].Height;
var radius = Cylinder[0].Radius;
var bevelRadius = Cylinder[0].BevelRadius;
var bakeToShape = GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientation);
var scale = bakeToShape.DecomposeScale();
height *= scale.z;
radius *= math.cmax(scale.xy);
Cylinder[0] = new CylinderGeometry
{
Center = center,
Orientation = orientation,
Height = height,
Radius = radius,
BevelRadius = math.min(bevelRadius, math.min(height * 0.5f, radius)),
SideCount = Cylinder[0].SideCount
};
}
}
internal static CylinderGeometry BakeToBodySpace(
this CylinderGeometry cylinder, float4x4 localToWorld, float4x4 shapeToWorld, EulerAngles orientation
)
{
using (var geometry = new NativeArray<CylinderGeometry>(1, Allocator.TempJob) { [0] = cylinder })
{
var job = new BakeCylinderJob
{
Cylinder = geometry,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld,
orientation = orientation
};
job.Run();
return geometry[0];
}
}
#endregion

#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;

public void Execute()


{
var v = Vertices[0];
GetPlanePoints(center, size, orientation, out v.c0, out v.c1, out v.c2, out v.c3);
var localToShape = math.mul(math.inverse(shapeToWorld), localToWorld);
v.c0 = math.mul(localToShape, new float4(v.c0, 1f)).xyz;
v.c1 = math.mul(localToShape, new float4(v.c1, 1f)).xyz;
v.c2 = math.mul(localToShape, new float4(v.c2, 1f)).xyz;
v.c3 = math.mul(localToShape, new float4(v.c3, 1f)).xyz;
Vertices[0] = v;
}
}

internal static void BakeToBodySpace(


float3 center, float2 size, EulerAngles orientation, float4x4 localToWorld, float4x4 shapeToWorld,
out float3 vertex0, out float3 vertex1, out float3 vertex2, out float3 vertex3
)
{
using (var geometry = new NativeArray<float3x4>(1, Allocator.TempJob))
{
var job = new BakePlaneJob
{
Vertices = geometry,
center = center,
size = size,
orientation = orientation,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld
};
job.Run();
vertex0 = geometry[0].c0;
vertex1 = geometry[0].c1;
vertex2 = geometry[0].c2;
vertex3 = geometry[0].c3;
}
}

internal static void GetPlanePoints(


float3 center, float2 size, EulerAngles orientation,
out float3 vertex0, out float3 vertex1, out float3 vertex2, out float3 vertex3
)
{
var sizeYUp = math.float3(size.x, 0, size.y);
vertex0 = center + math.mul(orientation, sizeYUp * math.float3(-0.5f, 0, 0.5f));
vertex1 = center + math.mul(orientation, sizeYUp * math.float3(0.5f, 0, 0.5f));
vertex2 = center + math.mul(orientation, sizeYUp * math.float3(0.5f, 0, -0.5f));
vertex3 = center + math.mul(orientation, sizeYUp * math.float3(-0.5f, 0, -0.5f));
}

#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;

public uint ForceUniqueIdentifier;


public ConvexHullGenerationParameters GenerationParameters;
public Material Material;
public CollisionFilter CollisionFilter;
public float4x4 BakeFromShape;

[ReadOnly] public NativeArray<HashableShapeInputs> Inputs;


[ReadOnly] public NativeArray<int> AllSkinIndices;
[ReadOnly] public NativeArray<float> AllBlendShapeWeights;

public void Execute()


{
Result[0] = HashableShapeInputs.GetHash128(
ForceUniqueIdentifier, GenerationParameters, Material, CollisionFilter, BakeFromShape,
Inputs, AllSkinIndices, AllBlendShapeWeights
);
}
}
#endregion

#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;

var bakeToShape = BakeCapsuleJobExtension.GetBakeToShape(shape, center, capsule.OrientationEuler);


var scale = bakeToShape.DecomposeScale();
var newRadius = radius / math.cmax(scale.xy);
if (math.abs(capsule.Radius - newRadius) > kMinimumChange)
capsule.Radius = newRadius;
height /= scale.z;
if (math.abs(math.length(capsule.Height - height)) > kMinimumChange)
capsule.Height = height;
shape.SetCapsule(capsule);
}
internal static CapsuleGeometryAuthoring BakeToBodySpace(
this CapsuleGeometryAuthoring capsule, float4x4 localToWorld, float4x4 shapeToWorld
)
{
using (var geometry = new NativeArray<CapsuleGeometryAuthoring>(1, Allocator.TempJob) { [0] = capsule })
{
var job = new BakeCapsuleJob
{
Capsule = geometry,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld
};
job.Run();
return geometry[0];
}
}
public static class BakeCylinderJobExtension
{
internal static float4x4 GetBakeToShape(PhysicsShapeAuthoring shape, float3 center, EulerAngles orientation)
{
var transform = shape.transform;
var localToWorld = (float4x4)transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
return BakeCylinderJob.GetBakeToShape(localToWorld, shapeToWorld, ref center,
ref orientation);
}
}
public static CylinderGeometry GetBakedCylinderProperties(this PhysicsShapeAuthoring shape)
{
var cylinder = shape.GetCylinderProperties(out var orientation);
return cylinder.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(),
orientation);
}
public static void SetBakedSphereRadius(this PhysicsShapeAuthoring shape, float radius)
{
var sphere = shape.GetSphereProperties(out EulerAngles eulerAngles);
var center = sphere.Center;
radius = math.abs(radius);
var basisToWorld = GetBasisToWorldMatrix(shape.transform.localToWorldMatrix, center, eulerAngles, 1f);
var basisPriority = basisToWorld.HasShear() ? GetBasisAxisPriority(basisToWorld) : k_DefaultAxisPriority;
var bakeToShape = GetPrimitiveBakeToShapeMatrix(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(), ref center, ref eulerAngles, 1f, basisPriority);
var scale = math.cmax(bakeToShape.DecomposeScale());
var newRadius = radius / scale;
sphere.Radius = newRadius;
shape.SetSphere(sphere);
}
public static void SetBakedPlaneSize(this PhysicsShapeAuthoring shape, float2 size)
{
shape.GetPlaneProperties(out var center, out var planeSize, out EulerAngles orientation);
var prevSize = math.abs(planeSize);
size = math.abs(size);
if (math.abs(size[0] - prevSize[0]) < kMinimumChange) size[0] = prevSize[0];
if (math.abs(size[1] - prevSize[1]) < kMinimumChange) size[1] = prevSize[1];
planeSize = size;
shape.SetPlane(center, planeSize, orientation);
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/CapsuleGeometryAuthoring.cs
using System;
using Unity.Mathematics;
using UnityEngine;

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;

/// <summary> The radius of the capsule. </summary>


///
/// <value> The radius. </value>
public float Radius { get => m_Radius; set => m_Radius = value; }
[SerializeField]
float m_Radius;
public bool Equals(CapsuleGeometryAuthoring other)
{
return m_Height.Equals(other.m_Height)
&& m_Center.Equals(other.m_Center)
&& m_Radius.Equals(other.m_Radius)
&& m_OrientationEuler.Equals(other.m_OrientationEuler);
}
public override int GetHashCode()
{
return unchecked((int)math.hash(
new float3x3(
Center,
m_OrientationEuler.Value,
new float3((float)m_OrientationEuler.RotationOrder, m_Height, m_Radius)
)
));
}
}
public static class CapsuleGeometryAuthoringExtensions
{
/// <summary>
/// Construct a CapsuleGeometryAuthoring instance from a run-time CapsuleGeometry instance.
/// </summary>
public static CapsuleGeometryAuthoring ToAuthoring(this CapsuleGeometry input)
{
var orientationEuler = EulerAngles.Default;
orientationEuler.SetValue(quaternion.LookRotationSafe(input.Vertex1 - input.Vertex0, math.up()));
return new CapsuleGeometryAuthoring
{
Height = input.GetHeight(),
OrientationEuler = orientationEuler,
Center = input.GetCenter(),
Radius = input.Radius
};
}
/// <summary>
/// Construct a run-time CapsuleGeometry instance from a CapsuleGeometryAuthoring instance.
/// </summary>
public static CapsuleGeometry ToRuntime(this CapsuleGeometryAuthoring input)
{
var halfHeight = 0.5f * input.Height;
var halfDistance = halfHeight - input.Radius;
var axis = math.normalize(math.mul(input.Orientation, new float3 { z = 1f }));
var halfAxis = axis * halfDistance;
var vertex0 = input.Center + halfAxis;
var vertex1 = input.Center - halfAxis;
return new CapsuleGeometry
{
Vertex0 = vertex0,
Vertex1 = vertex1,
Radius = input.Radius
};
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/ConvexHullGenerationParametersExtensions.cs
using System;
using Unity.Collections;
using Unity.Mathematics;

namespace Unity.Physics.Authoring
{
public static class ConvexHullGenerationParametersExtensions
{
// recommended simplification tolerance is at least 1 centimeter
internal const float k_MinRecommendedSimplificationTolerance = 0.01f;

internal static void InitializeToRecommendedAuthoringValues(


ref this ConvexHullGenerationParameters generationParameters, NativeArray<float3> points
)
{
generationParameters = ConvexHullGenerationParameters.Default.ToAuthoring();

if (points.Length <= 1)
return;

var bounds = new Aabb { Min = points[0], Max = points[0] };


for (var i = 1; i < points.Length; ++i)
bounds.Include(points[i]);
generationParameters.SimplificationTolerance = math.max(
k_MinRecommendedSimplificationTolerance,
ConvexHullGenerationParameters.Default.SimplificationTolerance * math.cmax(bounds.Extents)
);
// TODO: initialize other properties based on input points?
}

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);
}

public static ConvexHullGenerationParameters ToAuthoring(this ConvexHullGenerationParameters generationParameters)


{
generationParameters.MinimumAngle = math.degrees(generationParameters.MinimumAngle);
return generationParameters;
}

public static ConvexHullGenerationParameters ToRunTime(this ConvexHullGenerationParameters generationParameters)


{
generationParameters.MinimumAngle = math.radians(generationParameters.MinimumAngle);
return generationParameters;
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/EulerAngles.cs
using System;
using Unity.Mathematics;
using UnityEngine;

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));

public static implicit operator quaternion(EulerAngles euler) =>


math.normalize(quaternion.Euler(math.radians(euler.Value), euler.RotationOrder));
public bool Equals(EulerAngles other) => Value.Equals(other.Value) && RotationOrder == other.RotationOrder;
public override bool Equals(object obj) => obj is EulerAngles other && Equals(other);
public override int GetHashCode() => unchecked((int)math.hash(new float4(Value, (float)RotationOrder)));
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/GetActiveChildrenScope.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityComponent = UnityEngine.Component;
namespace Unity.Physics.Authoring
{
public struct GetActiveChildrenScope<T> : IDisposable where T : UnityComponent
{
static readonly List<PhysicsShapeAuthoring> s_PhysicsShapes = new List<PhysicsShapeAuthoring>(8);

static bool s_BufferUsed;


static List<T> s_Buffer = new List<T>(8);

public List<T> Buffer => m_Disposed ? null : s_Buffer;


bool m_Disposed;
PhysicsShapeAuthoring m_Shape;
Transform m_Root;
GameObject m_PrimaryBody;
bool m_CheckIfComponentBelongsToShape;
public GetActiveChildrenScope(PhysicsShapeAuthoring shape, Transform root)
{
m_Disposed = false;
m_Shape = shape;
m_Root = root;
m_PrimaryBody = PhysicsShapeExtensions.GetPrimaryBody(root.gameObject);
m_CheckIfComponentBelongsToShape = root.transform.IsChildOf(shape.transform);
if (s_BufferUsed)
throw new InvalidOperationException($"Cannot nest two {GetType()}");
s_BufferUsed = true;
root.GetComponentsInChildren(true, s_Buffer);
}
public void Dispose()
{
if (m_Disposed)
return;
m_Disposed = true;
s_BufferUsed = false;
s_Buffer.Clear();
}

public bool IsChildActiveAndBelongsToShape(T child, bool filterOutInvalid = true)


{
var meshFilter = (UnityComponent)child as MeshFilter;
if (meshFilter != null)
{
if (meshFilter.sharedMesh == null)
return false;

var renderer = meshFilter.GetComponent<MeshRenderer>();


if (renderer == null || !renderer.enabled)
return false;
if (filterOutInvalid && !meshFilter.sharedMesh.IsValidForConversion(m_Shape.gameObject))
return false;
}

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);

// avoids drift in axes we're not actually changing


public const float kMinimumChange = HashableShapeInputs.k_DefaultLinearPrecision;

static readonly int[] k_NextAxis = { 1, 2, 0 };


static readonly int[] k_PrevAxis = { 2, 0, 1 };

// 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;

var imax = max == basisAxisLengths.x ? 0 : max == basisAxisLengths.y ? 1 : 2;


basisToWorld[k_NextAxis[imax]] = DeskewSecondaryAxis(basisToWorld[imax], basisToWorld[k_NextAxis[imax]]);
basisToWorld[k_PrevAxis[imax]] = DeskewSecondaryAxis(basisToWorld[imax], basisToWorld[k_PrevAxis[imax]]);

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];

return new int3(imax, imid, imin);


}

[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]]);

// recompute third axes from first two


var n2 = math.normalizesafe(
new float4(math.cross(localToBake[basisPriority[0]].xyz, localToBake[basisPriority[1]].xyz), 0f)
);
localToBake[basisPriority[2]] = n2 * math.dot(localToBake[basisPriority[2]], n2);
}
var bakeToShape = math.mul(math.inverse(shapeToWorld), localToBake);
// transform baked center/orientation (i.e. primitive basis) into shape space
orientation.SetValue(
quaternion.LookRotationSafe(bakeToShape[basisPriority[0]].xyz, bakeToShape[basisPriority[1]].xyz)
);
center = bakeToShape.c3.xyz;

return bakeToShape;
}

internal static CollisionFilter GetFilter(this PhysicsShapeAuthoring shape)


{
// TODO: determine optimal workflow for specifying group index
return new CollisionFilter
{
BelongsTo = shape.BelongsTo.Value,
CollidesWith = shape.CollidesWith.Value
};
}
internal static Material GetMaterial(this PhysicsShapeAuthoring shape)
{
// TODO: TBD how we will author editor content for other shape flags
return new Material
{
Friction = shape.Friction.Value,
FrictionCombinePolicy = shape.Friction.CombineMode,
Restitution = shape.Restitution.Value,
RestitutionCombinePolicy = shape.Restitution.CombineMode,
CollisionResponse = shape.CollisionResponse,
CustomTags = shape.CustomTags.Value
};
}
public static GameObject FindTopmostEnabledAncestor<T>(GameObject shape, List<T> buffer) where T : Component
{
// include inactive in case the supplied shape GameObject is a prefab that has not been instantiated
shape.GetComponentsInParent(true, buffer);
GameObject result = null;
for (var i = buffer.Count - 1; i >= 0; --i)
{
if (
(buffer[i] as UnityEngine.Collider)?.enabled ??
(buffer[i] as MonoBehaviour)?.enabled ?? true
)
{
result = buffer[i].gameObject;
break;
}
}
buffer.Clear();
return result;
}
public static GameObject GetPrimaryBody(this PhysicsShapeAuthoring shape) => GetPrimaryBody(shape.gameObject);
public static GameObject GetPrimaryBody(GameObject shape)
{
var pb = ColliderExtensions.FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
var rb = ColliderExtensions.FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
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
ColliderExtensions.FindTopmostStaticEnabledAncestor(shape, out GameObject 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;
}

public static BoxGeometry GetBakedBoxProperties(this PhysicsShapeAuthoring shape)


{
var box = shape.GetBoxProperties(out var orientation);
return box.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(), orientation);
}
internal static BoxGeometry BakeToBodySpace(
this BoxGeometry box, float4x4 localToWorld, float4x4 shapeToWorld, EulerAngles orientation
)
{
using (var geometry = new NativeArray<BoxGeometry>(1, Allocator.TempJob) { [0] = box })
{
var job = new BakeBoxJob
{
Box = geometry,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld,
orientation = orientation
};
job.Run();
return geometry[0];
}
}
public static void SetBakedBoxSize(this PhysicsShapeAuthoring shape, float3 size, float bevelRadius)
{
var box = shape.GetBoxProperties(out var orientation);
var center = box.Center;
var prevSize = math.abs(box.Size);
size = math.abs(size);

var bakeToShape = BakeBoxJobExtension.GetBakeToShape(shape, center, orientation);


var scale = bakeToShape.DecomposeScale();
size /= scale;

if (math.abs(size[0] - prevSize[0]) < kMinimumChange) size[0] = prevSize[0];


if (math.abs(size[1] - prevSize[1]) < kMinimumChange) size[1] = prevSize[1];
if (math.abs(size[2] - prevSize[2]) < kMinimumChange) size[2] = prevSize[2];
box.BevelRadius = bevelRadius;
box.Size = size;
shape.SetBox(box, orientation);
}

internal static CapsuleGeometryAuthoring GetBakedCapsuleProperties(this PhysicsShapeAuthoring shape)


{
var capsule = shape.GetCapsuleProperties();
return capsule.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix());
}

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();

var newRadius = radius / math.cmax(scale.xy);


if (math.abs(cylinder.Radius - newRadius) > kMinimumChange) cylinder.Radius = newRadius;
if (math.abs(cylinder.BevelRadius - bevelRadius) > kMinimumChange) cylinder.BevelRadius = bevelRadius;

var newHeight = math.max(0, height / scale.z);


if (math.abs(cylinder.Height - newHeight) > kMinimumChange) cylinder.Height = newHeight;
shape.SetCylinder(cylinder, orientation);
}

internal static SphereGeometry GetBakedSphereProperties(this PhysicsShapeAuthoring shape, out EulerAngles orientation)


{
var sphere = shape.GetSphereProperties(out orientation);
return sphere.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(), ref orientation);
}
internal static void GetBakedPlaneProperties(
this PhysicsShapeAuthoring shape, out float3 vertex0, out float3 vertex1, out float3 vertex2, out float3 vertex3
)
{
shape.GetPlaneProperties(out var center, out var size, out EulerAngles orientation);
BakeToBodySpace(
center, size, orientation, shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(),
out vertex0, out vertex1, out vertex2, out vertex3
);
}
public static void GetBakedConvexProperties(this PhysicsShapeAuthoring shape, NativeList<float3> pointCloud)
{
shape.GetConvexHullProperties(pointCloud, true, default, default, default, default);
shape.BakePoints(pointCloud.AsArray());
}
internal static Hash128 GetBakedMeshInputs(this PhysicsShapeAuthoring shape, HashSet<UnityEngine.Mesh> meshAssets = null)
{
using (var inputs = new NativeList<HashableShapeInputs>(8, Allocator.TempJob))
{
shape.GetMeshProperties(default, default, true, inputs, meshAssets);
using (var hash = new NativeArray<Hash128>(1, Allocator.TempJob))
using (var allSkinIndices = new NativeArray<int>(0, Allocator.TempJob))
using (var allBlendShapeWeights = new NativeArray<float>(0, Allocator.TempJob))
{
var job = new GetShapeInputsHashJob
{
Result = hash,
ForceUniqueIdentifier = (uint)(shape.ForceUnique ? shape.GetInstanceID() : 0),
Material = shape.GetMaterial(),
CollisionFilter = shape.GetFilter(),
BakeFromShape = shape.GetLocalToShapeMatrix(),
Inputs = inputs.AsArray(),
AllSkinIndices = allSkinIndices,
AllBlendShapeWeights = allBlendShapeWeights
};
job.Run();
return hash[0];
}
}
}
internal static Hash128 GetBakedConvexInputs(this PhysicsShapeAuthoring shape, HashSet<UnityEngine.Mesh> meshAssets)
{
using (var inputs = new NativeList<HashableShapeInputs>(8, Allocator.TempJob))
using (var allSkinIndices = new NativeList<int>(4096, Allocator.TempJob))
using (var allBlendShapeWeights = new NativeList<float>(64, Allocator.TempJob))
{
shape.GetConvexHullProperties(default, true, inputs, allSkinIndices, allBlendShapeWeights, meshAssets);
using (var hash = new NativeArray<Hash128>(1, Allocator.TempJob))
{
var job = new GetShapeInputsHashJob
{
Result = hash,
ForceUniqueIdentifier = (uint)(shape.ForceUnique ? shape.GetInstanceID() : 0),
GenerationParameters = shape.ConvexHullGenerationParameters,
Material = shape.GetMaterial(),
CollisionFilter = shape.GetFilter(),
BakeFromShape = shape.GetLocalToShapeMatrix(),
Inputs = inputs.AsArray(),
AllSkinIndices = allSkinIndices.AsArray(),
AllBlendShapeWeights = allBlendShapeWeights.AsArray()
};
job.Run();
return hash[0];
}
}
}
public static void GetBakedMeshProperties(
this PhysicsShapeAuthoring shape, NativeList<float3> vertices, NativeList<int3> triangles,
HashSet<UnityEngine.Mesh> meshAssets = null
)
{
shape.GetMeshProperties(vertices, triangles, true, default, meshAssets);
shape.BakePoints(vertices.AsArray());
}
const float k_HashFloatTolerance = 0.01f;
// used to hash convex hull generation properties in a way that is robust to imprecision
public static uint GetStableHash(
this ConvexHullGenerationParameters generationParameters,
ConvexHullGenerationParameters hashedParameters,
float tolerance = k_HashFloatTolerance
)
{
var differences = new float3(
generationParameters.BevelRadius - hashedParameters.BevelRadius,
generationParameters.MinimumAngle - hashedParameters.MinimumAngle,
generationParameters.SimplificationTolerance - hashedParameters.SimplificationTolerance
);
return math.cmax(math.abs(differences)) < tolerance
? unchecked((uint)hashedParameters.GetHashCode())
: unchecked((uint)generationParameters.GetHashCode());
}
// used to hash an array of points in a way that is robust to imprecision
public static unsafe uint GetStableHash(
this NativeList<float3> points, NativeArray<float3> hashedPoints, float tolerance = k_HashFloatTolerance
)
{
if (points.Length != hashedPoints.Length)
return math.hash(points.GetUnsafePtr(), UnsafeUtility.SizeOf<float3>() * points.Length);

for (int i = 0, count = points.Length; i < count; ++i)


{
if (math.cmax(math.abs(points[i] - hashedPoints[i])) > tolerance)
return math.hash(points.GetUnsafePtr(), UnsafeUtility.SizeOf<float3>() * points.Length);
}
return math.hash(hashedPoints.GetUnsafePtr(), UnsafeUtility.SizeOf<float3>() * hashedPoints.Length);
}
public static int GetMaxAxis(this float3 v)
{
var cmax = math.cmax(v);
return cmax == v.z ? 2 : cmax == v.y ? 1 : 0;
}
public static int GetDeviantAxis(this float3 v)
{
var deviation = math.abs(v - math.csum(v) / 3f);
return math.cmax(deviation) == deviation.z ? 2 : math.cmax(deviation) == deviation.y ? 1 : 0;
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/PropertyAttributes.cs
using UnityEngine;

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; }

public SoftRangeAttribute(float min, float max)


{
SliderMin = TextFieldMin = min;
SliderMax = TextFieldMax = max;
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/AssemblyInfo.cs
using System.Runtime.CompilerServices;

[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();

EditorUtilities.EditPivot(ballAndSocket.worldFromA, ballAndSocket.worldFromB, ballAndSocket.AutoSetConnected,


ref ballAndSocket.PositionLocal, ref ballAndSocket.PositionInConnectedEntity, ballAndSocket);
}
}
}
#endif
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/CustomPhysicsMaterialTagNamesEditor.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;

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();

public override void OnInspectorGUI()


{
LimitedHingeJoint limitedHinge = (LimitedHingeJoint)target;

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();
}
}

protected virtual void OnSceneGUI()


{
LimitedHingeJoint limitedHinge = (LimitedHingeJoint)target;

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;

public override void OnInspectorGUI()


{
serializedObject.Update();

EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_MotionType);
if (m_MotionType.intValue != (int)BodyMotionType.Static)
EditorGUILayout.PropertyField(m_Smoothing);

var dynamic = m_MotionType.intValue == (int)BodyMotionType.Dynamic;


if (dynamic)
EditorGUILayout.PropertyField(m_Mass, Content.MassLabel);
else
{
EditorGUI.BeginDisabledGroup(true);
var position = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
EditorGUI.BeginProperty(position, Content.MassLabel, m_Mass);
EditorGUI.FloatField(position, Content.MassLabel, float.PositiveInfinity);
EditorGUI.EndProperty();
EditorGUI.EndDisabledGroup();
}

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);
}

showAdvanced = EditorGUILayout.Foldout(showAdvanced, Content.AdvancedLabel);


if (showAdvanced)
{
++EditorGUI.indentLevel;
EditorGUILayout.PropertyField(m_WorldIndex);
if (m_MotionType.intValue != (int)BodyMotionType.Static)
{
EditorGUILayout.PropertyField(m_OverrideDefaultMassDistribution);
if (m_OverrideDefaultMassDistribution.boolValue)
{
++EditorGUI.indentLevel;
EditorGUILayout.PropertyField(m_CenterOfMass, Content.CenterOfMassLabel);

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];

static readonly string[] k_NonReadableGeometryWarning =


{
L10n.Tr($"{k_Singular} has a non-readable mesh in its hierarchy. Move it into a sub-scene or assign a custom mesh with Read/Write enabled in its import settings if it needs
L10n.Tr($"{k_Plural} has a non-readable mesh in its hierarchy. Move it into a sub-scene or assign a custom mesh with Read/Write enabled in its import settings if it needs t
};
public static string GetNonReadableGeometryWarning(int numTargets) =>
numTargets == 1 ? k_NonReadableGeometryWarning[0] : k_NonReadableGeometryWarning[1];
static readonly string[] k_MeshWithSkinnedPointsWarning =
{
L10n.Tr($"{k_Singular} is a mesh based on its render geometry, but its render geometry includes skinned points. These points will be excluded from the automatically generat
L10n.Tr($"{k_Plural} is a mesh based on its render geometry, but its render geometry includes skinned points. These points will be excluded from the automatically generated
};
public static string GetMeshWithSkinnedPointsWarning(int numTargets) =>
numTargets == 1 ? k_MeshWithSkinnedPointsWarning[0] : k_MeshWithSkinnedPointsWarning[1];
static readonly string[] k_StaticColliderStatusMessage =
{
L10n.Tr($"{k_Singular} will be considered static. Add a {ObjectNames.NicifyVariableName(typeof(PhysicsBodyAuthoring).Name)} component if you will move it at run-time."
L10n.Tr($"{k_Plural} will be considered static. Add a {ObjectNames.NicifyVariableName(typeof(PhysicsBodyAuthoring).Name)} component if you will move them at run-time."
};
public static string GetStaticColliderStatusMessage(int numTargets) =>
numTargets == 1 ? k_StaticColliderStatusMessage[0] : k_StaticColliderStatusMessage[1];
public static readonly string BoxCapsuleSuggestion =
L10n.Tr($"Target {ShapeType.Box} has uniform size on its two short axes and a large convex radius. Consider using a {ShapeType.Capsule} instead.");
public static readonly string BoxPlaneSuggestion =
L10n.Tr($"Target {ShapeType.Box} is flat. Consider using a {ShapeType.Plane} instead.");
public static readonly string BoxSphereSuggestion =
L10n.Tr($"Target {ShapeType.Box} has uniform size and large convex radius. Consider using a {ShapeType.Sphere} instead.");
public static readonly string CapsuleSphereSuggestion =
L10n.Tr($"Target {ShapeType.Capsule}'s diameter is equal to its height. Consider using a {ShapeType.Sphere} instead.");
public static readonly string CylinderCapsuleSuggestion =
L10n.Tr($"Target {ShapeType.Cylinder} has a large convex radius. Consider using a {ShapeType.Capsule} instead.");
public static readonly string CylinderSphereSuggestion =
L10n.Tr($"Target {ShapeType.Cylinder} has a large convex radius and its diameter is equal to its height. Consider using a {ShapeType.Sphere} instead.");
public static readonly GUIStyle Button =
new GUIStyle(EditorStyles.miniButton) { padding = new RectOffset() };
public static readonly GUIStyle ButtonDropDown =
new GUIStyle(EditorStyles.popup) { alignment = TextAnchor.MiddleCenter };
}
#pragma warning disable 649
[AutoPopulate] SerializedProperty m_ShapeType;
[AutoPopulate] SerializedProperty m_PrimitiveCenter;
[AutoPopulate] SerializedProperty m_PrimitiveSize;
[AutoPopulate] SerializedProperty m_PrimitiveOrientation;
[AutoPopulate] SerializedProperty m_Capsule;
[AutoPopulate] SerializedProperty m_Cylinder;
[AutoPopulate] SerializedProperty m_CylinderSideCount;
[AutoPopulate] SerializedProperty m_SphereRadius;
[AutoPopulate] SerializedProperty m_ConvexHullGenerationParameters;
[AutoPopulate(PropertyPath = "m_ConvexHullGenerationParameters.m_BevelRadius")] SerializedProperty m_BevelRadius;
[AutoPopulate] SerializedProperty m_MinimumSkinnedVertexWeight;
[AutoPopulate] SerializedProperty m_CustomMesh;
[AutoPopulate] SerializedProperty m_Material;
[AutoPopulate] SerializedProperty m_ForceUnique;
#pragma warning restore 649
[Flags]
enum GeometryState
{
Okay = 0,
NoGeometry = 1 << 0,
NonReadableGeometry = 1 << 1,
MeshWithSkinnedPoints = 1 << 2
}
GeometryState m_GeometryState;
int m_NumImplicitStatic;
// keep track of when the user is dragging some control to prevent continually rebuilding preview geometry
[NonSerialized]
int m_DraggingControlID;
[NonSerialized]
FitToRenderMeshesDropDown m_DropDown;
protected override void OnEnable()
{
base.OnEnable();
HashUtility.Initialize();

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);
}
}

float m_BevelRadius = ConvexHullGenerationParameters.Default.BevelRadius;


bool m_IsDragging = false;

public float height


{
get => GetSize().z;
set
{
var size = GetSize();
size.z = math.max(0f, value);
SetSize(size);
}
}

public float radius


{
get
{
var size = (float3)GetSize();
var diameter = 0f;
// only consider size values on enabled axes
if (IsAxisEnabled(0)) diameter = math.max(diameter, math.abs(size.x));
else if (IsAxisEnabled(1)) diameter = math.max(diameter, math.abs(size.y));
return diameter * 0.5f;
}
set
{
var size = (float3)GetSize();
size.x = size.y = math.max(0f, value * 2.0f);
SetSize(size);
}
}

public int sideCount


{
get => m_SideCount;
set
{
if (value == m_SideCount)
return;

m_SideCount = value;

Array.Resize(ref m_TopPoints, m_SideCount * 2);


Array.Resize(ref m_BottomPoints, m_SideCount * 2);
Array.Resize(ref m_Corners, m_SideCount * 2);
}
}
int m_SideCount;

PhysicsBoundsHandleUtility.Corner[] m_Corners = Array.Empty<PhysicsBoundsHandleUtility.Corner>();


Vector3[] m_TopPoints = Array.Empty<Vector3>();
Vector3[] m_BottomPoints = Array.Empty<Vector3>();

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()


{
using (new Handles.DrawingScope(Handles.matrix))
{
var backfacedColor = PhysicsBoundsHandleUtility.GetStateColor(true);
var frontfacedColor = Handles.color;
bool isCameraInsideBox = false;

var radius = this.radius;


var bevelRadius = this.bevelRadius;

var halfHeight = new float3(0f, 0f, height * 0.5f);


var ctr = (float3)center;
var halfAngleStep = math.PI / m_SideCount;
var angleStep = 2f * halfAngleStep;
const float kHalfPI = (math.PI * 0.5f);
var bottom = ctr + halfHeight + new float3 { z = bevelRadius };
var top = ctr - halfHeight - new float3 { z = bevelRadius };
var tangent = new float3(1, 0, 0);
var binormal = new float3(0, 1, 0);
var topBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(top, -tangent, binormal, axes, isCameraInsideBox);
var bottomBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(bottom, tangent, binormal, axes, isCameraInsideBox);

var cameraCenter = float3.zero;


var cameraForward = new float3 { z = 1f };
if (Camera.current != null)
{
cameraCenter = Camera.current.transform.position;
cameraForward = Camera.current.transform.forward;
}

// 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;

var noSides = (radius - bevelRadius) < PhysicsBoundsHandleUtility.kDistanceEpsilon;


var up = new float3(0, 0, -1f);

var t = ((m_SideCount - 2) * angleStep);


var xyAngle0 = new float3(math.cos(t), math.sin(t), 0f);

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 offset0 = xyAngle0 * (radius - bevelRadius);


var offset1 = xyAngle1 * (radius - bevelRadius);
var offset2 = xyAngle2 * (radius - bevelRadius);
var top1 = ctr + offset1 - (halfHeight - new float3 { z = bevelRadius });
var bottom1 = ctr + offset1 + (halfHeight - new float3 { z = bevelRadius });
var top2 = ctr + offset2 - (halfHeight - new float3 { z = bevelRadius });
var bottom2 = ctr + offset2 + (halfHeight - new float3 { z = bevelRadius });

var startOffset = direction1 * bevelRadius;


if (bevelGreaterThanZero)
{
var upOffset = up * bevelRadius;
// top/bottom caps
if (bevelLessThanCylinderRadius)
{
Handles.color = topBackFaced ? backfacedColor : frontfacedColor;
Handles.DrawLine(top1 + upOffset, top2 + upOffset);

Handles.color = bottomBackFaced ? backfacedColor : frontfacedColor;


Handles.DrawLine(bottom1 - upOffset, bottom2 - upOffset);
}

var currSideMidPoint = ctr + ((top1 + bottom1 + top2 + bottom2) * 0.25f) + startOffset;


var currSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(currSideMidPoint, up, sideways2, axes, isCameraInsideBox);
Handles.color = currSideBackFaced ? backfacedColor : frontfacedColor;
if (!noSides)
{
// Square side of bevelled cylinder
Handles.DrawLine(top2 + startOffset, bottom2 + startOffset);
Handles.DrawLine(bottom2 + startOffset, bottom1 + startOffset);
Handles.DrawLine(bottom1 + startOffset, top1 + startOffset);
Handles.DrawLine(top1 + startOffset, top2 + startOffset);
}
else
{
// Square side of bevelled cylinder, when squashed to a single line
Handles.DrawLine(top2 + startOffset, bottom2 + startOffset);
}
}
else
{
var top0 = ctr + offset0 - (halfHeight - new float3 { z = bevelRadius });
var bottom0 = ctr + offset0 + (halfHeight - new float3 { z = bevelRadius });
var prevMidPoint = ctr + ((top0 + top1 + bottom0 + bottom1) * 0.25f) + startOffset;
var prevSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(prevMidPoint, up, sideways1, axes, isCameraInsideBox);

var currMidPoint = ctr + ((top1 + top2 + bottom1 + bottom2) * 0.25f) + startOffset;


var currSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(currMidPoint, up, sideways2, axes, isCameraInsideBox);
// Square side of bevelled cylinder
Handles.color = (currSideBackFaced && prevSideBackFaced) ? backfacedColor : frontfacedColor;
Handles.DrawLine(bottom1 + startOffset, top1 + startOffset);
Handles.color = (currSideBackFaced && topBackFaced) ? backfacedColor : frontfacedColor;
Handles.DrawLine(top1 + startOffset, top2 + startOffset);
Handles.color = (currSideBackFaced && bottomBackFaced) ? backfacedColor : frontfacedColor;
Handles.DrawLine(bottom2 + startOffset, bottom1 + startOffset);
}
if (bevelGreaterThanZero)
{
Handles.color = frontfacedColor;

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;

// Side horizon on vertical curved edge


if (m_Corners[up1].splitCount > 1 &&
m_Corners[dn1].splitCount > 1)
{
if ((m_Corners[up1].splitAxis[0].y || m_Corners[up1].splitAxis[1].y) &&
(m_Corners[dn1].splitAxis[0].y || m_Corners[dn1].splitAxis[1].y))
{
var point0 = m_Corners[up1].splitAxis[0].y ? m_Corners[up1].points[0] : m_Corners[up1].points[1];
var point1 = m_Corners[dn1].splitAxis[0].y ? m_Corners[dn1].points[0] : m_Corners[dn1].points[1];
Handles.DrawLine(point0, point1);
}
}
// Top horizon on horizontal curved edge
if (m_Corners[up0].splitCount > 1 &&
m_Corners[up1].splitCount > 1)
{
if ((m_Corners[up0].splitAxis[0].x || m_Corners[up0].splitAxis[1].x) &&
(m_Corners[up1].splitAxis[0].z || m_Corners[up1].splitAxis[1].z))
{
var point0 = m_Corners[up0].splitAxis[0].x ? m_Corners[up0].points[0] : m_Corners[up0].points[1];
var point1 = m_Corners[up1].splitAxis[0].z ? m_Corners[up1].points[0] : m_Corners[up1].points[1];
Handles.DrawLine(point0, point1);
}
}
// Bottom horizon on horizontal curved edge
if (m_Corners[dn0].splitCount > 1 &&
m_Corners[dn1].splitCount > 1)
{
if ((m_Corners[dn0].splitAxis[0].z || m_Corners[dn0].splitAxis[1].z) &&
(m_Corners[dn1].splitAxis[0].x || m_Corners[dn1].splitAxis[1].x))
{
var point0 = m_Corners[dn0].splitAxis[0].z ? m_Corners[dn0].points[0] : m_Corners[dn0].points[1];
var point1 = m_Corners[dn1].splitAxis[0].x ? m_Corners[dn1].points[0] : m_Corners[dn1].points[1];
Handles.DrawLine(point0, point1);
}
}
}
for (var i = 0; i < m_Corners.Length; ++i)
PhysicsBoundsHandleUtility.DrawCorner(m_Corners[i], new bool3(true, true, !noSides));
}
}
}
protected override Bounds OnHandleChanged(HandleDirection handle, Bounds boundsOnClick, Bounds newBounds)
{
const int k_DirectionX = 0;
const int k_DirectionY = 1;
const int k_DirectionZ = 2;
var changedAxis = k_DirectionX;
var otherRadiusAxis = k_DirectionY;
switch (handle)
{
case HandleDirection.NegativeY:
case HandleDirection.PositiveY:
changedAxis = k_DirectionY;
otherRadiusAxis = k_DirectionX;
break;
case HandleDirection.NegativeZ:
case HandleDirection.PositiveZ:
changedAxis = k_DirectionZ;
break;
}
var upperBound = newBounds.max;
var lowerBound = newBounds.min;
var convexDiameter = 2f * bevelRadius;
// ensure changed dimension cannot be made less than convex diameter
if (upperBound[changedAxis] - lowerBound[changedAxis] < convexDiameter)
{
switch (handle)
{
case HandleDirection.PositiveX:
case HandleDirection.PositiveY:
case HandleDirection.PositiveZ:
upperBound[changedAxis] = lowerBound[changedAxis] + convexDiameter;
break;
default:
lowerBound[changedAxis] = upperBound[changedAxis] - convexDiameter;
break;
}
}
// ensure radius changes uniformly
if (changedAxis != k_DirectionZ)
{
var rad = 0.5f * (upperBound[changedAxis] - lowerBound[changedAxis]);
lowerBound[otherRadiusAxis] = center[otherRadiusAxis] - rad;
upperBound[otherRadiusAxis] = center[otherRadiusAxis] + rad;
}
return new Bounds((upperBound + lowerBound) * 0.5f, upperBound - lowerBound);
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/PhysicsBoundsHandleUtility.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEngine;
using static UnityEditor.IMGUI.Controls.PrimitiveBoundsHandle;
namespace Unity.Physics.Editor
{
static class PhysicsBoundsHandleUtility
{
internal const float kBackfaceAlphaMultiplier = 0.2f;
const float kDegreeEpsilon = 0.001f;
public const float kDistanceEpsilon = 0.0001f;
public static bool IsBackfaced(float3 localPos, float3 localTangent, float3 localBinormal, Axes axes, bool isCameraInsideBox)
{
// if inside the box then ignore back facing alpha multiplier (otherwise all handles will look disabled)
if (isCameraInsideBox || axes != Axes.All)
return false;
// use tangent and binormal to calculate normal in case handle matrix is skewed
float3 worldTangent = math.normalize(Handles.matrix.MultiplyVector(localTangent));
float3 worldBinormal = math.normalize(Handles.matrix.MultiplyVector(localBinormal));
float3 worldDir = math.normalize(math.cross(worldTangent, worldBinormal));
// adjust color if handle is back facing
float cosV;
var currentCamera = Camera.current;
if (currentCamera != null && !currentCamera.orthographic)
{
float3 cameraPos = currentCamera.transform.position;
float3 worldPos = Handles.matrix.MultiplyPoint(localPos);
cosV = math.dot(math.normalize(cameraPos - worldPos), worldDir);
}
else
{
float3 cameraForward = currentCamera == null ? Vector3.forward : currentCamera.transform.forward;
cosV = math.dot(-cameraForward, worldDir);
}
return cosV < -0.0001f;
}
public static Color GetStateColor(bool isBackfaced)
{
float alphaMultiplier = isBackfaced ? kBackfaceAlphaMultiplier : 1f;
return Handles.color * new Color(1f, 1f, 1f, alphaMultiplier);
}
static void AdjustMidpointHandleColor(bool isBackfaced)
{
Handles.color = GetStateColor(isBackfaced);
}
static readonly Vector3[] s_FacePoints = new Vector3[8];
static readonly Vector3[] s_LinePoints = new Vector3[2];
static readonly int[] k_NextAxis = { 1, 2, 0 };
public static void DrawFace(float3 center, float3 size, float cornerRadius, int normalAxis, Axes axes, bool isCameraInsideBox)
{
// 0 = 0 1 2
// 1 = 1 2 0
// 2 = 2 0 1
int a = normalAxis;
int b = k_NextAxis[a];
int c = k_NextAxis[b];
cornerRadius = math.abs(cornerRadius);
size *= 0.5f;
var normal = new float3 { [a] = size[a] };
var ctr = center + normal;
size -= new float3(cornerRadius);
// check if our face is a point
if (math.abs(size[c]) < kDistanceEpsilon &&
math.abs(size[b]) < kDistanceEpsilon)
return;
Vector3[] points;
// check if our face is a line or not
if (math.abs(size[c]) >= kDistanceEpsilon &&
math.abs(size[b]) >= kDistanceEpsilon)
{
var i = 0;
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
points = s_FacePoints;
}
else if (math.abs(size[c]) >= kDistanceEpsilon)
{
var i = 0;
s_LinePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
s_LinePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
points = s_LinePoints;
}
else
{
var i = 0;
s_LinePoints[i++] = ctr + new float3 { [c] = size[c], [b] = size[b] };
s_LinePoints[i++] = ctr + new float3 { [c] = size[c], [b] = -size[b] };
points = s_LinePoints;
}
float3 tangent, biNormal;
if (size[c] > 0)
{
tangent = math.cross(normal, math.normalizesafe(new float3 { [c] = size[c] }));
biNormal = math.cross(normal, tangent);
}
else
{
tangent = math.cross(normal, math.normalizesafe(new float3 { [b] = size[b] }));
biNormal = math.cross(normal, tangent);
}
using (new Handles.DrawingScope(Handles.color))
{
AdjustMidpointHandleColor(IsBackfaced(ctr, tangent, biNormal, axes, isCameraInsideBox));
Handles.DrawLines(points);
}
}
public struct Corner
{
public float3 angle;
public float3x2 intersections;
public float3x2 points;
public float3x3 axes;
public float3x3 normals;
public bool3x2 splitAxis;
public int splitCount;
public float3 position;
public float radius;
public bool isBackFaced;
public float3 cameraForward;
}
public static void CalculateCornerHorizon(float3 cornerPosition, quaternion orientation, float3 cameraCenter, float3 cameraForward, bool cameraOrtho, float radius, out Corner
{
var axisx = new float3(1f, 0f, 0f);
var axisy = new float3(0f, 1f, 0f);
var axisz = new float3(0f, 0f, 1f);
// a vector pointing away from the center of the corner
var cornerNormal = math.normalize(math.mul(orientation, new float3(1f, 1f, 1f)));
var axes = math.mul(new float3x3(orientation), new float3x3(axisx, axisy, axisz));
CalculateCornerHorizon(cornerPosition,
axes,
cornerNormal,
cameraCenter, cameraForward, cameraOrtho,
radius, out corner);
}
public static void CalculateCornerHorizon(float3 cornerPosition, float3x3 axes, float3 cornerNormal, float3 cameraCenter, float3 cameraForward, bool cameraOrtho, float radius
{
var cameraToCenter = cornerPosition - cameraCenter; // vector from camera to center
var sqrRadius = radius * radius;
var sqrDistCameraToCenter = math.lengthsq(cameraToCenter);
var sqrOffset = (sqrRadius * sqrRadius / sqrDistCameraToCenter); // squared distance from actual center to drawn disc center
if (!cameraOrtho)
cameraForward = cameraToCenter;
var normals = new float3x3
{
c0 = math.normalize(math.cross(axes[1], axes[2])),
c1 = math.normalize(math.cross(axes[2], axes[0])),
c2 = math.normalize(math.cross(axes[0], axes[1]))
};
corner = new Corner
{
angle = new float3(
Vector3.Angle(axes[0], axes[1]),
Vector3.Angle(axes[1], axes[2]),
Vector3.Angle(axes[2], axes[0])
),
intersections = default,
points = default,
splitAxis = default,
axes = axes,
normals = normals,
position = cornerPosition,
radius = radius,
cameraForward = cameraForward,
isBackFaced = math.dot(cornerNormal, cameraForward) > 0,
splitCount = 0
};
if (math.abs(sqrDistCameraToCenter) <= sqrRadius)
return;
for (int n = 0, sign = -1; n < 2; n++, sign += 2)
{
for (int i = 0; i < 3; i++)
{
var axis1 = normals[i] * sign;
var axis2 = axes[(i + 1) % 3] * sign;
var axis3 = axes[(i + 2) % 3] * sign;
var Q = Vector3.Angle(cameraForward, axis1);
var f = math.tan(math.radians(90 - math.min(Q, 180 - Q)));
var g = sqrOffset + f * f * sqrOffset;
if (g >= sqrRadius)
continue;
var e = math.degrees(math.asin(math.sqrt(g) / radius));
var vectorToPointOnHorizon = Quaternion.AngleAxis(e, axis1) * math.normalize(math.cross(axis1, cameraForward));
vectorToPointOnHorizon = math.normalize(Vector3.ProjectOnPlane(vectorToPointOnHorizon, axis1));
var intersectionDirection = vectorToPointOnHorizon;
var angle1 = Vector3.SignedAngle(axis2, intersectionDirection, axis1);
var angle2 = Vector3.SignedAngle(axis3, intersectionDirection, axis1);
if (angle1 <= 0 || angle2 >= 0)
continue;
var point = corner.position + (float3)(intersectionDirection * radius);
if (corner.splitCount < 2)
{
corner.splitAxis[corner.splitCount][i] = true;
corner.intersections[corner.splitCount] = intersectionDirection;
corner.points[corner.splitCount] = point;
corner.splitCount++;
}
}
}
if (!math.any(corner.splitAxis[0]) &&
!math.any(corner.splitAxis[1]))
{
corner.splitCount = 0;
corner.splitAxis[0] = false;
corner.splitAxis[1] = false;
}
}
public static void DrawCorner(Corner corner, bool3 showAxis)
{
var color = Handles.color;
var axes = corner.axes;
var intersections = corner.intersections;
var normals = corner.normals;
var origin = corner.position;
var radius = corner.radius;
if (corner.splitCount <= 1)
{
AdjustMidpointHandleColor(corner.isBackFaced);
if (showAxis[0]) Handles.DrawWireArc(origin, normals[0], axes[1], corner.angle[1], radius);
if (showAxis[1]) Handles.DrawWireArc(origin, normals[1], axes[2], corner.angle[2], radius);
if (showAxis[2]) Handles.DrawWireArc(origin, normals[2], axes[0], corner.angle[0], radius);
}
else
{
var angleLength = Vector3.SignedAngle(Vector3.ProjectOnPlane(intersections[0], corner.cameraForward),
Vector3.ProjectOnPlane(intersections[1], corner.cameraForward), corner.cameraForward);
bool reversePolarity = angleLength < 0;
if (reversePolarity)
Handles.DrawWireArc(origin, corner.cameraForward, corner.points[1] - origin, -angleLength, radius);
else
Handles.DrawWireArc(origin, corner.cameraForward, corner.points[0] - origin, angleLength, radius);

var backfacedColor = GetStateColor(true);


var axesBackfaced = new bool3(math.length(intersections[0] - axes[0]) < kDistanceEpsilon || math.length(intersections[1] - axes[0]) < kDistanceEpsilon,
math.length(intersections[0] - axes[1]) < kDistanceEpsilon || math.length(intersections[1] - axes[1]) < kDistanceEpsilon,
math.length(intersections[0] - axes[2]) < kDistanceEpsilon || math.length(intersections[1] - axes[2]) < kDistanceEpsilon);
var color1 = reversePolarity ? color : backfacedColor;
var color2 = reversePolarity ? backfacedColor : color;
for (int A = 1, B = 2, C = 0; C < 3; A = B, B = C, C++)
{
if (corner.splitAxis[0][C] == corner.splitAxis[1][C])
{
if (!axesBackfaced[A]) { angleLength = Vector3.Angle(intersections[0], axes[A]); axesBackfaced[A] = (angleLength<kDegreeEpsilon || angleLength> corner.angle
if (!axesBackfaced[B]) { angleLength = Vector3.Angle(intersections[1], axes[A]); axesBackfaced[B] = (angleLength<kDegreeEpsilon || angleLength> corner.angle
}
else if (corner.splitAxis[0][C])
{
if (showAxis[C])
{
angleLength = Vector3.Angle(intersections[0], axes[A]);
Handles.color = color1; Handles.DrawWireArc(origin, normals[C], intersections[0], -angleLength, radius);
Handles.color = color2; Handles.DrawWireArc(origin, normals[C], intersections[0], corner.angle[A] - angleLength, radius);
}
axesBackfaced[A] = true;
}
else
//if (corner.splitAxis[1][C])
{
if (showAxis[C])
{
angleLength = Vector3.Angle(intersections[1], axes[A]);
Handles.color = color2; Handles.DrawWireArc(origin, normals[C], intersections[1], -angleLength, radius);
Handles.color = color1; Handles.DrawWireArc(origin, normals[C], intersections[1], corner.angle[A] - angleLength, radius);
}
axesBackfaced[B] = true;
}
}
// check for singularity
if (math.all(axesBackfaced))
axesBackfaced = corner.isBackFaced;
for (int A = 1, B = 2, C = 0; C < 3; A = B, B = C, C++)
{
if (!showAxis[C])
continue;
if (corner.splitAxis[0][C] == corner.splitAxis[1][C])
{
Handles.color = (axesBackfaced[B] && axesBackfaced[A]) ? color1 : color2;
Handles.DrawWireArc(origin, normals[C], axes[A], corner.angle[A], radius);
}
}
}
Handles.color = color;
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/PhysicsCapsuleBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class PhysicsCapsuleBoundsHandle : CapsuleBoundsHandle
{
static PhysicsBoundsHandleUtility.Corner[] s_Corners = new PhysicsBoundsHandleUtility.Corner[8];
protected override void DrawWireframe()
{
if (this.radius <= 0f)
{
base.DrawWireframe();
return;
}
var cameraPos = default(float3);
var cameraFwd = new float3 { z = 1f };
var cameraOrtho = true;
if (Camera.current != null)
{
cameraPos = Camera.current.transform.position;
cameraFwd = Camera.current.transform.forward;
cameraOrtho = Camera.current.orthographic;
}
var size = new float3(this.radius * 2f, this.radius * 2f, height);
var radius = this.radius;
var origin = (float3)this.center;
var bounds = new Bounds(this.center, size);
// 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;
var cameraCenter = (float3)invMatrix.MultiplyPoint(cameraPos);
var cameraForward = (float3)invMatrix.MultiplyVector(cameraFwd);
bool isCameraInsideBox = Camera.current != null
&& bounds.Contains(invMatrix.MultiplyPoint(cameraPos));
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(-1f, 1f, 1f), radius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, -1f, 1f), radius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 2, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, -1f), radius, 2, axes, isCameraInsideBox);
var corner = 0.5f * size - new float3(1f) * radius;
var axisx = new float3(1f, 0f, 0f);
var axisy = new float3(0f, 1f, 0f);
var axisz = new float3(0f, 0f, 1f);
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, -1f), quaternion.LookRotation(-axisz, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, 1f), quaternion.LookRotation(-axisx, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, 1f), quaternion.LookRotation(axisz, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, -1f), quaternion.LookRotation(axisx, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, -1f), quaternion.LookRotation(-axisx, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, 1f), quaternion.LookRotation(axisz, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, 1f), quaternion.LookRotation(axisx, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, -1f), quaternion.LookRotation(-axisz, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[0], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[3], new bool3(true, false, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[4], new bool3(true, false, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[7], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[1], new bool3(true, false, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[2], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[5], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[6], new bool3(true, false, 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/PhysicsSphereBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class PhysicsSphereBoundsHandle : SphereBoundsHandle
{
protected override void DrawWireframe()
{
bool x = IsAxisEnabled(Axes.X);
bool y = IsAxisEnabled(Axes.Y);
bool z = IsAxisEnabled(Axes.Z);

if (x && !y && !z)


Handles.DrawLine(Vector3.right * radius, Vector3.left * radius);
if (!x && y && !z)
Handles.DrawLine(Vector3.up * radius, Vector3.down * radius);
if (!x && !y && z)
Handles.DrawLine(Vector3.forward * radius, Vector3.back * radius);
const float kEpsilon = 0.000001F;
if (radius > 0)
{
var frontfacedColor = Handles.color;
var backfacedColor = Handles.color * new Color(1f, 1f, 1f, PhysicsBoundsHandleUtility.kBackfaceAlphaMultiplier);
var discVisible = new bool[]
{
y && z,
x && z,
x && y
};
var discOrientations = new float3[]
{
Vector3.right,
Vector3.up,
Vector3.forward
};
// 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;
var cameraCenter = Camera.current == null ? Vector3.zero : Camera.current.transform.position;
var cameraToCenter = center - invMatrix.MultiplyPoint(cameraCenter); // vector from camera to center
var sqrDistCameraToCenter = cameraToCenter.sqrMagnitude;
var sqrRadius = radius * radius; // squared radius
var isCameraOrthographic = Camera.current == null || Camera.current.orthographic;
var sqrOffset = isCameraOrthographic ? 0 : (sqrRadius * sqrRadius / sqrDistCameraToCenter); // squared distance from actual center to drawn disc center
var insideAmount = sqrOffset / sqrRadius;
if (insideAmount < 1)
{
if (math.abs(sqrDistCameraToCenter) >= kEpsilon)
{
using (new Handles.DrawingScope(frontfacedColor))
{
if (isCameraOrthographic)
{
var horizonRadius = radius;
var horizonCenter = center;
Handles.DrawWireDisc(horizonCenter, cameraToCenter, horizonRadius);
}
else
{
var horizonRadius = math.sqrt(sqrRadius - sqrOffset);
var horizonCenter = center - sqrRadius * cameraToCenter / sqrDistCameraToCenter;
Handles.DrawWireDisc(horizonCenter, cameraToCenter, horizonRadius);
}
}
var planeNormal = cameraToCenter.normalized;
for (int i = 0; i < 3; i++)
{
if (!discVisible[i])
continue;

var discOrientation = discOrientations[i];


var angleBetweenDiscAndNormal = math.acos(math.dot(discOrientation, planeNormal));
angleBetweenDiscAndNormal = (math.PI * 0.5f) - math.min(angleBetweenDiscAndNormal, math.PI - angleBetweenDiscAndNormal);
float f = math.tan(angleBetweenDiscAndNormal);
float g = math.sqrt(sqrOffset + f * f * sqrOffset) / radius;
if (g < 1)
{
var angleToHorizon = math.degrees(math.asin(g));
var discTangent = math.cross(discOrientation, planeNormal);
var vectorToPointOnHorizon = Quaternion.AngleAxis(angleToHorizon, discOrientation) * discTangent;
var horizonArcLength = (90 - angleToHorizon) * 2.0f;
using (new Handles.DrawingScope(frontfacedColor))
Handles.DrawWireArc(center, discOrientation, vectorToPointOnHorizon, horizonArcLength, radius);
using (new Handles.DrawingScope(backfacedColor))
Handles.DrawWireArc(center, discOrientation, vectorToPointOnHorizon, horizonArcLength - 360, radius);
}
else
{
using (new Handles.DrawingScope(backfacedColor))
Handles.DrawWireDisc(center, discOrientation, radius);
}
}
}
}
else
{
using (new Handles.DrawingScope(backfacedColor))
{
for (int i = 0; i < 3; i++)
{
var discOrientation = discOrientations[i];
Handles.DrawWireDisc(center, discOrientation, radius);
}
}
}
}
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/BaseDrawer.cs
using UnityEditor;
using UnityEngine;

namespace Unity.Physics.Editor
{
abstract class BaseDrawer : PropertyDrawer
{
protected abstract bool IsCompatible(SerializedProperty property);

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)


{
return IsCompatible(property)
? EditorGUI.GetPropertyHeight(property)
: EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (IsCompatible(property))
DoGUI(position, property, label);
else
EditorGUIControls.DisplayCompatibilityWarning(position, label, ObjectNames.NicifyVariableName(GetType().Name));
}

protected abstract void DoGUI(Rect position, SerializedProperty property, GUIContent label);


}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/EnumFlagsDrawer.cs
using System;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(EnumFlagsAttribute))]
class EnumFlagsDrawer : BaseDrawer
{
protected override bool IsCompatible(SerializedProperty property)
{
return property.propertyType == SerializedPropertyType.Enum;
}

protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)


{
EditorGUI.BeginProperty(position, label, property);
var value = property.longValue;
EditorGUI.BeginChangeCheck();
value = Convert.ToInt64(
EditorGUI.EnumFlagsField(position, label, (Enum)Enum.ToObject(fieldInfo.FieldType, value))
);
if (EditorGUI.EndChangeCheck())
property.longValue = value;

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;

protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)


{
EditorGUI.BeginProperty(position, label, property);
EditorGUI.PropertyField(
new Rect(position) { xMax = position.xMax - Styles.PopupWidth },
property.FindPropertyRelative("Value"),
label
);
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
EditorGUI.PropertyField(
new Rect(position) { xMin = position.xMax - Styles.PopupWidth + EditorGUIUtility.standardVerticalSpacing },
property.FindPropertyRelative("CombineMode"),
GUIContent.none
);
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/PhysicsMaterialPropertiesDrawer.cs
using System.Collections.Generic;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;

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)."
);
}

const string k_CollisionFilterGroupKey = "m_BelongsToCategories";


const string k_AdvancedGroupKey = "m_CustomMaterialTags";
Dictionary<string, SerializedObject> m_SerializedTemplates = new Dictionary<string, SerializedObject>();

SerializedProperty GetTemplateValueProperty(SerializedProperty property)


{
var key = property.propertyPath;
var template = property.FindPropertyRelative("m_Template").objectReferenceValue;
SerializedObject serializedTemplate;
if (
!m_SerializedTemplates.TryGetValue(key, out serializedTemplate)
|| serializedTemplate?.targetObject != template
)
m_SerializedTemplates[key] = serializedTemplate = template == null ? null : new SerializedObject(template);
serializedTemplate?.Update();
return serializedTemplate?.FindProperty("m_Value");
}

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");
}

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)


{
var templateValueProperty = GetTemplateValueProperty(property);
// m_CollisionResponse, collision filter foldout, advanced foldout
var height = 3f * EditorGUIUtility.singleLineHeight + 2f * EditorGUIUtility.standardVerticalSpacing;

// 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;
}

protected override bool IsCompatible(SerializedProperty property) => true;

static void DisplayOverridableProperty(


Rect position, GUIContent label, SerializedProperty toggle, SerializedProperty value, bool templateAssigned
)
{
if (templateAssigned)
{
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth -= 16f + EditorGUIUtility.standardVerticalSpacing;
var togglePosition = new Rect(position) { width = EditorGUIUtility.labelWidth + 16f + EditorGUIUtility.standardVerticalSpacing };
EditorGUI.PropertyField(togglePosition, toggle, label);
EditorGUIUtility.labelWidth = labelWidth;

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);
}
}

protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)


{
var template = property.FindPropertyRelative("m_Template");
var templateAssigned = template.objectReferenceValue != null;
var supportsTemplate = property.FindPropertyRelative("m_SupportsTemplate");
if (supportsTemplate.boolValue)
{
position.height = EditorGUI.GetPropertyHeight(template);
EditorGUI.PropertyField(position, template);

position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;


}

var templateValue = GetTemplateValueProperty(property);


FindToggleAndValueProperties(property, templateValue, "m_CollisionResponse", out var collisionResponseDropDown, out var collisionResponse);
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.CollisionResponseLabel, collisionResponseDropDown, collisionResponse, templateAssigned);
SerializedProperty toggle;

// Check if regular collider


CollisionResponsePolicy collisionResponseEnum = (CollisionResponsePolicy)collisionResponse.intValue;
if (collisionResponseEnum == CollisionResponsePolicy.Collide ||
collisionResponseEnum == CollisionResponsePolicy.CollideRaiseCollisionEvents)
{
FindToggleAndValueProperties(property, templateValue, "m_Friction", out toggle, out var friction);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.FrictionLabel, toggle, friction, templateAssigned);
FindToggleAndValueProperties(property, templateValue, "m_Restitution", out toggle, out var restitution);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.RestitutionLabel, toggle, restitution, templateAssigned);
}

// collision filter group


var collisionFilterGroup = property.FindPropertyRelative(k_CollisionFilterGroupKey);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
collisionFilterGroup.isExpanded =
EditorGUI.Foldout(position, collisionFilterGroup.isExpanded, Content.CollisionFilterGroupFoldout, true);
if (collisionFilterGroup.isExpanded)
{
++EditorGUI.indentLevel;

FindToggleAndValueProperties(property, templateValue, "m_BelongsToCategories", out toggle, out var belongsTo);


position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.BelongsToLabel, toggle, belongsTo, templateAssigned);
FindToggleAndValueProperties(property, templateValue, "m_CollidesWithCategories", out toggle, out var collidesWith);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.CollidesWithLabel, toggle, collidesWith, templateAssigned);
--EditorGUI.indentLevel;
}
// advanced group
var advancedGroup = property.FindPropertyRelative(k_AdvancedGroupKey);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
advancedGroup.isExpanded =
EditorGUI.Foldout(position, advancedGroup.isExpanded, Content.AdvancedGroupFoldout, true);
if (advancedGroup.isExpanded)
{
++EditorGUI.indentLevel;
FindToggleAndValueProperties(property, templateValue, "m_CustomMaterialTags", out toggle, out var customFlags);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.CustomFlagsLabel, toggle, customFlags, templateAssigned);

--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");

public static readonly string MultipleAssetsTooltip =


L10n.Tr("Multiple {0} assets found. UI will display labels defined in {1}.");

public static readonly GUIContent MultipleAssetsWarning =


new GUIContent { image = EditorGUIUtility.Load("console.warnicon") as Texture };
}
protected abstract int MaxNumCategories { get; }
protected abstract string DefaultCategoryName { get; }
internal string FirstChildPropertyPath { get; set; } // TODO: remove when all usages of bool[] are migrated

string DefaultFormatString => L10n.Tr($"(Undefined {DefaultCategoryName})");


string[] DefaultOptions =>
m_DefaultOptions ?? (
m_DefaultOptions =
Enumerable.Range(0, MaxNumCategories)
.Select(i => string.Format(DefaultFormatString, i))
.ToArray()
);
string[] m_DefaultOptions;

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];

m_Options[i] = $"{i}: {m_Options[i]}";


}
return m_Options;
}

string[] m_Options;

static string GetButtonLabel(int value, IReadOnlyList<string> optionNames)


{
switch (value)
{
case 0:
return Styles.NothingName;
case ~0:
return Styles.EverythingName;
default:
{
for (var i = 0; i < 32; i++)
{
if (value == 1 << i)
return optionNames[i];
}
break;
}
}
return Styles.MixedName;
}

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;
}

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)


{
if (m_NamesAssets?.Length > 1)
position.xMax -= EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;

EditorGUI.BeginProperty(position, label, property);

var controlPosition = EditorGUI.PrefixLabel(position, label);


var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
var showMixed = EditorGUI.showMixedValue;

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];

public static void SoftSlider(


Rect position, GUIContent label, SerializedProperty property,
float sliderMin, float sliderMax,
float textFieldMin, float textFieldMax
)
{
if (property.propertyType != SerializedPropertyType.Float)
{
DisplayCompatibilityWarning(position, label, property.propertyType.ToString());
}
else if (k_SoftSlider == null)
{
EditorGUI.BeginChangeCheck();
EditorGUI.PropertyField(position, property, label);
if (EditorGUI.EndChangeCheck())
property.floatValue = math.clamp(property.floatValue, textFieldMin, textFieldMax);
}
else
{
k_SoftSliderArgs[0] = position;
k_SoftSliderArgs[1] = label;
k_SoftSliderArgs[2] = property.floatValue;
k_SoftSliderArgs[3] = sliderMin;
k_SoftSliderArgs[4] = sliderMax;
k_SoftSliderArgs[5] = textFieldMin;
k_SoftSliderArgs[6] = textFieldMax;
EditorGUI.BeginProperty(position, label, property);
EditorGUI.BeginChangeCheck();
var result = k_SoftSlider.Invoke(null, k_SoftSliderArgs);
if (EditorGUI.EndChangeCheck())
property.floatValue = (float)result;
EditorGUI.EndProperty();
}
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/ManipulatorUtility.cs
using System;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Editor
{
enum MatrixState
{
UniformScale,
NonUniformScale,
ZeroScale,
NotValidTRS
}
static class ManipulatorUtility
{
public static MatrixState GetMatrixState(ref float4x4 localToWorld)
{
if (
localToWorld.c0.w != 0f
|| localToWorld.c1.w != 0f
|| localToWorld.c2.w != 0f
|| localToWorld.c3.w != 1f
)
return MatrixState.NotValidTRS;
var m = new float3x3(localToWorld.c0.xyz, localToWorld.c1.xyz, localToWorld.c2.xyz);
var lossyScale = new float3(math.length(m.c0.xyz), math.length(m.c1.xyz), math.length(m.c2.xyz));
if (math.determinant(m) < 0f)
lossyScale.x *= -1f;
if (math.lengthsq(lossyScale) == 0f)
return MatrixState.ZeroScale;
return math.abs(math.cmax(lossyScale)) - math.abs(math.cmin(lossyScale)) > 0.000001f
? MatrixState.NonUniformScale
: MatrixState.UniformScale;
}
}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/SceneViewUtility.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEngine;

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;
}

const string k_NotificationsPrefKey = "SceneView/Tools/Notifications";


const bool k_DefaultNotifications = true;
const string k_NotificationSpeedPrefKey = "SceneView/Tools/Notification Speed";
const float k_DefaultNotificationsSpeed = 20f;

const float k_NotificationDuration = 1f;


const float k_NotificationFadeInTime = 0.04f;
const float k_NotificationFadeOutTime = 0.2f;
static readonly AnimationCurve k_NotificationFadeCurve = new AnimationCurve
{
keys = new[]
{
new Keyframe { time = 0f, value = 0f, outTangent = 1f / k_NotificationFadeInTime },
new Keyframe { time = k_NotificationFadeInTime, value = 1f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_NotificationDuration - k_NotificationFadeOutTime, value = 1f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_NotificationDuration, value = 0f, inTangent = -1f / k_NotificationFadeOutTime }
},
postWrapMode = WrapMode.Clamp,
preWrapMode = WrapMode.Clamp
};
const float k_IndeterminateProgressCurveDuration = 2f;
static readonly AnimationCurve k_IndeterminateProgressCurveLeftMargin = new AnimationCurve
{
keys = new[]
{
new Keyframe { time = 0f, value = 0f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration / 2f, value = 0.25f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration, value = 1f, inTangent = 0f, outTangent = 0f }
},
postWrapMode = WrapMode.Loop,
preWrapMode = WrapMode.Loop
};
static readonly AnimationCurve k_IndeterminateProgressCurveRightMargin = new AnimationCurve
{
keys = new[]
{
new Keyframe { time = 0f, value = 1f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration / 2f, value = 0f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration, value = 0f, inTangent = 0f, outTangent = 0f }
},
postWrapMode = WrapMode.Loop,
preWrapMode = WrapMode.Loop
};

static string s_StatusMessage;


static DateTime s_StartTime;
static bool s_IsTemporary;
static Func<float> s_GetProgress;

public static void DisplayProgressNotification(string message, Func<float> getProgress) =>


// insert an extra line to make room for progress bar
DisplayNotificationInSceneView(getProgress == null ? message : $"{message}\n", false, getProgress);
public static void DisplayPersistentNotification(string message) =>
DisplayNotificationInSceneView(message, false, null);

public static void DisplayTemporaryNotification(string message) =>


DisplayNotificationInSceneView(message, true, null);

static void DisplayNotificationInSceneView(string message, bool temporary, Func<float> getProgress)


{
s_StatusMessage = message ?? string.Empty;
s_StartTime = DateTime.Now;
s_IsTemporary = temporary;
s_GetProgress = getProgress;
ClearNotificationInSceneView();
SceneView.duringSceneGui += ToolNotificationCallback;
SceneView.RepaintAll();
}

static void ToolNotificationCallback(SceneView obj)


{
if (Camera.current == null)
return;
var duration = math.max(s_StatusMessage.Length, 1)
/ EditorPrefs.GetFloat(k_NotificationSpeedPrefKey, k_DefaultNotificationsSpeed);
var t = (float)(DateTime.Now - s_StartTime).TotalSeconds;
if (
s_IsTemporary
&& (t >= duration || !EditorPrefs.GetBool(k_NotificationsPrefKey, k_DefaultNotifications))
)
{
ClearNotificationInSceneView();
}
else
{
Handles.BeginGUI();
var color = GUI.color;
var progress = s_GetProgress?.Invoke() ?? 0f;
GUI.color *=
new Color(1f, 1f, 1f, math.max(k_NotificationFadeCurve.Evaluate(math.abs(t) / duration), progress));
var rect = new Rect { size = Camera.current.pixelRect.size / EditorGUIUtility.pixelsPerPoint };
using (new GUILayout.AreaScope(rect))
using (new GUILayout.HorizontalScope())
{
GUILayout.FlexibleSpace();
using (new GUILayout.VerticalScope())
{
GUILayout.Space(rect.height * 0.75f);
GUILayout.FlexibleSpace();
var maxWidth = rect.width * 0.5f;
GUILayout.Box(s_StatusMessage, Styles.SceneViewStatusMessage, GUILayout.MaxWidth(maxWidth));
if (s_GetProgress != null)
{
rect = GUILayoutUtility.GetLastRect();
rect = Styles.SceneViewStatusMessage.padding.Remove(rect);
rect.y = rect.yMax - Styles.ProgressBarTrack.fixedHeight;
rect.height = Styles.ProgressBarTrack.fixedHeight;
var c = GUI.color;
GUI.color *= Color.black;
GUI.Box(rect, GUIContent.none, Styles.ProgressBarTrack);
GUI.color = c;
if (progress >= 0f && progress <= 1f)
{
rect.width *= progress;
}
else
{
var w = rect.width;
rect.xMin = rect.xMin + w * k_IndeterminateProgressCurveLeftMargin.Evaluate(t);
rect.xMax = rect.xMax - w * k_IndeterminateProgressCurveRightMargin.Evaluate(t);
}
GUI.Box(rect, GUIContent.none, Styles.ProgressBarIndicator);
}
GUILayout.FlexibleSpace();
}
GUILayout.FlexibleSpace();
}
GUI.color = color;
Handles.EndGUI();
}
SceneView.RepaintAll();
}

public static void ClearNotificationInSceneView() => SceneView.duringSceneGui -= ToolNotificationCallback;


}
}
OnlineFPS/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/StatusMessageUtility.cs
using System.Collections.Generic;
using System.Linq;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
using UnityObject = UnityEngine.Object;
using LegacyRigidBody = UnityEngine.Rigidbody;

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;

var targetType = c.GetType();


// only bodies (both explicit and implicit static bodies) will emit a message
if (
targetType == typeof(PhysicsBodyAuthoring)
|| targetType == typeof(LegacyRigidBody)
|| c.GetComponent<PhysicsBodyAuthoring>() == null
&& c.GetComponent<LegacyRigidBody>() == null
)
++numChildTargets;
}
switch (numChildTargets)
{
case 0:
return MessageType.None;
case 1:
statusMessage =
L10n.Tr("Target will be un-parented during the conversion process in order to take part in physics simulation.");
return MessageType.Warning;
default:
statusMessage =
L10n.Tr("One or more targets will be un-parented during the conversion process in order to take part in physics simulation.");
return MessageType.Warning;
}
}
public static MessageType GetMatrixStatusMessage(
IReadOnlyList<MatrixState> matrixStates, out string statusMessage
)
{
statusMessage = string.Empty;
if (matrixStates.Contains(MatrixState.NotValidTRS))
{
statusMessage = L10n.Tr(
matrixStates.Count == 1
? "Target's local-to-world matrix is not a valid transformation."
: "One or more targets' local-to-world matrices are not valid transformations."
);
return MessageType.Error;
}
if (matrixStates.Contains(MatrixState.ZeroScale))
{
statusMessage =
L10n.Tr(matrixStates.Count == 1 ? "Target has zero scale." : "One or more targets has zero scale.");
return MessageType.Warning;
}

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;

public NetworkTick LastKnownCommandsTick;


public FirstPersonPlayerCommands LastKnownCommands;
public bool IsAutoMoving;
}

[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;

public class Baker : Baker<FirstPersonPlayerAuthoring>


{
public override void Bake(FirstPersonPlayerAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.None);
AddComponent(entity, new FirstPersonPlayer
{
ControlledCharacter = GetEntity(authoring.ControlledCharacter, TransformUsageFlags.Dynamic),
});
AddComponent<FirstPersonPlayerCommands>(entity);
}
}
}
OnlineFPS/Assets/Scripts/Player/FirstPersonPlayerSystems.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;
using Unity.NetCode;
[UpdateInGroup(typeof(GhostInputSystemGroup))]
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation | WorldSystemFilterFlags.ThinClientSimulation)]
public partial class FirstPersonPlayerInputsSystem : SystemBase
{
private FPSInputActions InputActions;

protected override void OnCreate()


{
RequireForUpdate(SystemAPI.QueryBuilder().WithAll<FirstPersonPlayer, FirstPersonPlayerCommands>().Build());
RequireForUpdate<GameResources>();
RequireForUpdate<NetworkTime>();
RequireForUpdate<NetworkId>();
// Create the input user
InputActions = new FPSInputActions();
InputActions.Enable();
InputActions.DefaultMap.Enable();
}
protected override void OnUpdate()
{
float deltaTime = SystemAPI.Time.DeltaTime;
float elapsedTime = (float)SystemAPI.Time.ElapsedTime;
NetworkTick tick = SystemAPI.GetSingleton<NetworkTime>().ServerTick;
FPSInputActions.DefaultMapActions defaultActionsMap = InputActions.DefaultMap;
foreach (var (playerCommands, player, ghostOwner, entity) in SystemAPI.Query<RefRW<FirstPersonPlayerCommands>, RefRW<FirstPersonPlayer>, GhostOwner>().WithAll<GhostOwnerIsLocal
{
// Remember if new tick because some inputs need to be reset when we just started a new tick
bool isOnNewTick = !player.ValueRW.LastKnownCommandsTick.IsValid || tick.IsNewerThan(player.ValueRW.LastKnownCommandsTick);
playerCommands.ValueRW = default;
// Toggle auto-movement (used for testing purposes)
if (Input.GetKeyDown(KeyCode.F8))
{
player.ValueRW.IsAutoMoving = !player.ValueRW.IsAutoMoving;
}
// Move
if (player.ValueRW.IsAutoMoving)
{
playerCommands.ValueRW.MoveInput =Vector2.ClampMagnitude(new float2(math.sin(elapsedTime), math.sin(elapsedTime)), 1f);
}
else
{
playerCommands.ValueRW.MoveInput = Vector2.ClampMagnitude(defaultActionsMap.Move.ReadValue<Vector2>(), 1f);
}
// Look input must be accumulated on each update belonging to the same tick, because it is a delta and will be processed at a variable update
if (!isOnNewTick)
{
playerCommands.ValueRW.LookInputDelta = player.ValueRW.LastKnownCommands.LookInputDelta;
}
if (math.lengthsq(defaultActionsMap.LookConst.ReadValue<Vector2>()) > math.lengthsq(defaultActionsMap.LookDelta.ReadValue<Vector2>()))
{
// Gamepad look with a constant stick value
playerCommands.ValueRW.LookInputDelta += (float2)(defaultActionsMap.LookConst.ReadValue<Vector2>() * GameSettings.LookSensitivity * deltaTime);
}
else
{
// Mouse look with a mouse move delta value
playerCommands.ValueRW.LookInputDelta += (float2)(defaultActionsMap.LookDelta.ReadValue<Vector2>() * GameSettings.LookSensitivity);
}
// Jump
if (defaultActionsMap.Jump.WasPressedThisFrame())
{
playerCommands.ValueRW.JumpPressed.Set();
}

// 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;

[UpdateInGroup(typeof(PredictedSimulationSystemGroup), OrderFirst = true)]


[UpdateBefore(typeof(PredictedFixedStepSimulationSystemGroup))]
[BurstCompile]
public partial struct ActiveWeaponSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{ }

[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;

void Execute(Entity entity, ref ActiveWeapon activeWeapon)


{
// Detect changes in active weapon
if (activeWeapon.Entity != activeWeapon.PreviousEntity)
{
// Setup new weapon
if (WeaponControlLookup.HasComponent(activeWeapon.Entity))
{
// Setup for characters
if (FirstPersonCharacterComponentLookup.TryGetComponent(entity, out FirstPersonCharacterComponent character))
{
// Make character View entity be the weapon's raycast start point
if (WeaponSimulationShotOriginOverrideLookup.TryGetComponent(activeWeapon.Entity, out WeaponShotSimulationOriginOverride shotOriginOverride))
{
shotOriginOverride.Entity = character.ViewEntity;
WeaponSimulationShotOriginOverrideLookup[activeWeapon.Entity] = shotOriginOverride;
}
// Make weapon be child of character's weapon socket
ECB.AddComponent(activeWeapon.Entity, new Parent { Value = character.WeaponAnimationSocketEntity });
// Remember weapon owner
ECB.SetComponent(activeWeapon.Entity, new WeaponOwner { Entity = entity });
// Make weapon linked to the character
DynamicBuffer<LinkedEntityGroup> linkedEntityBuffer = LinkedEntityGroupLookup[entity];
linkedEntityBuffer.Add(new LinkedEntityGroup { Value = activeWeapon.Entity });

// Add character as ignored shot entities


if (WeaponShotIgnoredEntityLookup.TryGetBuffer(activeWeapon.Entity, out DynamicBuffer<WeaponShotIgnoredEntity> ignoredEntities))
{
ignoredEntities.Add(new WeaponShotIgnoredEntity { Entity = entity });
}
}
}

// TODO: Un-setup previous weapon


// if (WeaponControlLookup.HasComponent(activeWeapon.PreviousEntity))
// {
// Disable weapon update, reset owner, reset data, unparent, etc...
// }
}

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;

public float Range;


public float Damage;
public float SpreadRadians;
public int ProjectilesCount;
public CollisionFilter HitCollisionFilter;

// 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;

public class Baker : Baker<StandardRaycastWeaponAuthoring>


{
public override void Bake(StandardRaycastWeaponAuthoring authoring)
{
WeaponUtilities.AddBasicWeaponBakingComponents(this);
Entity entity = GetEntity(TransformUsageFlags.Dynamic);

AddComponent(entity, new WeaponVisualFeedback(authoring.VisualFeedback));


AddComponent(entity, new StandardWeaponFiringMecanism(authoring.FiringMecanism));
AddComponent(entity, new StandardRaycastWeapon
{
ShotOrigin = GetEntity(authoring.ShotOrigin, TransformUsageFlags.Dynamic),
ProjectileVisualPrefab = GetEntity(authoring.ProjectileVisualPrefab, TransformUsageFlags.Dynamic),
Range = authoring.Range,
Damage = authoring.Damage,
SpreadRadians = math.radians(authoring.SpreadDegrees),
ProjectilesCount = authoring.ProjectilesCount,
HitCollisionFilter = new CollisionFilter { BelongsTo = CollisionFilter.Default.BelongsTo, CollidesWith = authoring.HitCollisionFilter.Value },
Random = Random.CreateFromIndex(0),
});
AddComponent<InterpolationDelay>(entity);
AddBuffer<StandardRaycastWeaponShotVFXRequest>(entity);
}
}
}
OnlineFPS/Assets/Scripts/Weapons/StandardRaycastWeaponPredictionSystem.cs
using System;
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.Physics;
using Unity.Transforms;
using UnityEngine;
using RaycastHit = Unity.Physics.RaycastHit;

[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation | WorldSystemFilterFlags.ThinClientSimulation | WorldSystemFilterFlags.ServerSimulation)]


[UpdateInGroup(typeof(WeaponPredictionUpdateGroup))]
[BurstCompile]
public partial struct StandardRaycastWeaponPredictionSystem : ISystem
{
private NativeList<RaycastHit> _hits;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<NetworkTime>();
state.RequireForUpdate<PhysicsWorldSingleton>();
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<StandardRaycastWeapon, StandardWeaponFiringMecanism>().Build());
_hits = new NativeList<RaycastHit>(Allocator.Persistent);
}

[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);

bool computeShotVisuals = !IsServer && NetworkTime.IsFirstTimeFullyPredictingTick;


for (int i = 0; i < mecanism.ShotsToFire; i++)
{
WeaponUtilities.ComputeShotDetails(
ref weapon,
in shotSimulationOriginOverride,
in ignoredEntities,
ref Hits,
ref LocalTransformLookup,
ref ParentLookup,
ref PostTransformMatrixLookup,
in collisionWorld,
computeShotVisuals,
out bool hitFound,
out RaycastHit closestValidHit,
out StandardRaycastWeaponShotVisualsData shotVisualsData);
// Damage
if (IsServer && hitFound)
{
if (HealthLookup.TryGetComponent(closestValidHit.Entity, out Health health))
{
health.CurrentHealth -= weapon.Damage;
HealthLookup[closestValidHit.Entity] = health;
}
}
// Shot visuals request
if (computeShotVisuals)
{
shotVFXRequestsBuffer.Add(new StandardRaycastWeaponShotVFXRequest { ShotVisualsData = shotVisualsData});
}
// Recoil & FOV kick
if (IsServer)
{
weapon.RemoteShotsCount++;
}
else if (NetworkTime.IsFirstTimeFullyPredictingTick)
{
weaponFeedback.ShotFeedbackRequests++;
}
}
}
}
}
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
[UpdateInGroup(typeof(SimulationSystemGroup))]
[BurstCompile]
public partial struct StandardRaycastWeaponVisualsSystem : ISystem
{
private NativeList<RaycastHit> _hits;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<NetworkTime>();
state.RequireForUpdate<PhysicsWorldSingleton>();
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<StandardRaycastWeapon, StandardWeaponFiringMecanism>().Build());
_hits = new NativeList<RaycastHit>(Allocator.Persistent);
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
if (_hits.IsCreated)
{
_hits.Dispose();
}
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
StandardRaycastWeaponRemoteShotsJob remoteShotsJob = new StandardRaycastWeaponRemoteShotsJob
{
CollisionWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().CollisionWorld,
StoredKinematicCharacterDataLookup = SystemAPI.GetComponentLookup<StoredKinematicCharacterData>(true),
Hits = _hits,
LocalTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(true),
ParentLookup = SystemAPI.GetComponentLookup<Parent>(true),
PostTransformMatrixLookup = SystemAPI.GetComponentLookup<PostTransformMatrix>(true),
};
remoteShotsJob.Schedule();
}
[BurstCompile]
[WithNone(typeof(GhostOwnerIsLocal))]
public partial struct StandardRaycastWeaponRemoteShotsJob : IJobEntity
{
[ReadOnly]
public CollisionWorld CollisionWorld;
[ReadOnly]
public ComponentLookup<StoredKinematicCharacterData> StoredKinematicCharacterDataLookup;
[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 WeaponShotSimulationOriginOverride shotSimulationOriginOverride,
in DynamicBuffer<WeaponShotIgnoredEntity> ignoredEntities)
{
// TODO: should handle the case where a weapon goes out of client's area-of-interest and then comes back later with a high shots count diff
uint shotsToProcess = weapon.RemoteShotsCount - weapon.LastRemoteShotsCount;
weapon.LastRemoteShotsCount = weapon.RemoteShotsCount;
for (int i = 0; i < shotsToProcess; i++)
{
WeaponUtilities.ComputeShotDetails(
ref weapon,
in shotSimulationOriginOverride,
in ignoredEntities,
ref Hits,
ref LocalTransformLookup,
ref ParentLookup,
ref PostTransformMatrixLookup,
in CollisionWorld,
true,
out bool hitFound,
out RaycastHit closestValidHit,
out StandardRaycastWeaponShotVisualsData shotVisualsData);
shotVFXRequestsBuffer.Add(new StandardRaycastWeaponShotVFXRequest { ShotVisualsData = shotVisualsData });
weaponFeedback.ShotFeedbackRequests++;
}
}
}
}
OnlineFPS/Assets/Scripts/Weapons/Weapon.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
using UnityEngine;
using Random = Unity.Mathematics.Random;
[Serializable]
[GhostComponent()]
public struct ActiveWeapon : IComponentData
{
[GhostField()]
public Entity Entity;
public Entity PreviousEntity;
}

[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;

public static Authoring GetDefault()


{
return new Authoring
{
WeaponBobHAmount = 0.08f,
WeaponBobVAmount = 0.06f,
WeaponBobFrequency = 10f,
WeaponBobSharpness = 10f,
WeaponBobAimRatio = 0.25f,
RecoilStrength = 1f,
RecoilMaxDistance = 0.5f,
RecoilSharpness = 100f,
RecoilRestitutionSharpness = 5f,
AimFOVRatio = 0.5f,
AimFOVSharpness = 10f,
LookSensitivityMultiplierWhileAiming = 0.7f,
RecoilFOVKick = 10f,
RecoilMaxFOVKick = 10f,
RecoilFOVKickSharpness = 150f,
RecoilFOVKickRestitutionSharpness = 15f,
};
}
}
public WeaponVisualFeedback(Authoring authoring)
{
WeaponBobHAmount = authoring.WeaponBobHAmount;
WeaponBobVAmount = authoring.WeaponBobVAmount;
WeaponBobFrequency = authoring.WeaponBobFrequency;
WeaponBobSharpness = authoring.WeaponBobSharpness;
WeaponBobAimRatio = authoring.WeaponBobAimRatio;

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;

public int ShotFeedbackRequests;


}
[Serializable]
[GhostComponent(OwnerSendType = SendToOwnerType.SendToNonOwner)]
public struct WeaponControl : IComponentData
{
public bool FirePressed;
public bool FireReleased;
public bool AimHeld;
}
[Serializable]
public struct WeaponOwner: IComponentData
{
public Entity Entity;
}
[Serializable]
public struct WeaponShotSimulationOriginOverride : IComponentData
{
public Entity Entity;
}

[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;

public static Authoring GetDefault()


{
return new Authoring
{
AutoFire = false,
FiringRate = 1f,
};
}
}

public StandardWeaponFiringMecanism(Authoring authoring)


{
Automatic = authoring.AutoFire;
FiringRate = authoring.FiringRate;
ShotTimer = 0f;
if (FiringRate > 0f)
{
ShotTimer = 1f / authoring.FiringRate;
}
IsFiring = false;
ShotsToFire = 0;
}

public bool Automatic;


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;

public struct BulletShotVisuals : IComponentData


{
public Entity HitVisualsPrefab;
public float Speed;
public float StretchFromSpeed;
public float MaxStretch;

public bool IsInitialized;


public float DistanceTraveled;
}
OnlineFPS/Assets/Scripts/Weapons/Visuals/BulletShotVisualsAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;

public class BulletShotVisualsAuthoring : MonoBehaviour


{
public GameObject HitVisualsPrefab;
public float Speed = 10f;
public float StretchFromSpeed = 1f;
public float MaxStretch = 1f;
public class Baker : Baker<BulletShotVisualsAuthoring>
{
public override void Bake(BulletShotVisualsAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic | TransformUsageFlags.NonUniformScale);
AddComponent(entity, new BulletShotVisuals
{
HitVisualsPrefab = GetEntity(authoring.HitVisualsPrefab, TransformUsageFlags.Dynamic),
Speed = authoring.Speed,
StretchFromSpeed = authoring.StretchFromSpeed,
MaxStretch = authoring.MaxStretch,
});
}
}
}
OnlineFPS/Assets/Scripts/Weapons/Visuals/BulletShotVisualsSystem.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
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;

public float StartTime;


public float3 StartingScale;
public bool HasInitialized;
}
OnlineFPS/Assets/Scripts/Weapons/Visuals/LazerShotVisualsAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
public class LazerShotVisualsAuthoring : MonoBehaviour
{
public float Lifetime = 1f;
public float Width = 1f;
public GameObject HitVisualPrefab;

class Baker : Baker<LazerShotVisualsAuthoring>


{
public override void Bake(LazerShotVisualsAuthoring authoring)
{
Entity selfEntity = GetEntity(TransformUsageFlags.Dynamic | TransformUsageFlags.NonUniformScale);
AddComponent(selfEntity, new LazerShotVisuals
{
LifeTime = authoring.Lifetime,
Width = authoring.Width,
HitVisualsPrefab = GetEntity(authoring.HitVisualPrefab, TransformUsageFlags.Dynamic),
});
AddComponent(selfEntity, new PostTransformMatrix { Value = float4x4.Scale(1f) });
}
}
}
OnlineFPS/Assets/Scripts/Weapons/Visuals/LazerShotVisualsSystem.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

[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;

public struct MachineGunVisuals : IComponentData


{
public Entity BarrelEntity;
public float SpinVelocity;
public float SpinVelocityDecay;

public float CurrentSpinVelocity;


}
OnlineFPS/Assets/Scripts/Weapons/Visuals/MachineGunVisualsAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
public class MachineGunVisualsAuthoring : MonoBehaviour
{
public GameObject BarrelEntity;
public float SpinVelocity = math.PI * 2f;
public float SpinVelocityDecay = 3f;
public class Baker : Baker<MachineGunVisualsAuthoring>
{
public override void Bake(MachineGunVisualsAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new MachineGunVisuals
{
BarrelEntity = GetEntity(authoring.BarrelEntity, TransformUsageFlags.Dynamic),
SpinVelocity = authoring.SpinVelocity,
SpinVelocityDecay = authoring.SpinVelocityDecay,
});
}
}
}
OnlineFPS/Assets/Scripts/Weapons/Visuals/MachineGunVisualsSystem.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
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);
}

localTransform.Rotation = math.mul(localTransform.Rotation, quaternion.Euler(0f, 0f, visuals.CurrentSpinVelocity * DeltaTime));


LocalTransformLookup[visuals.BarrelEntity] = localTransform;
}
}
}
}
OnlineFPS/Assets/Scripts/Weapons/Visuals/StandardRaycastWeaponShotVisualsSystem.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
[BurstCompile]
[UpdateInGroup(typeof(WeaponShotVisualsGroup))]
[UpdateBefore(typeof(WeaponShotVisualsSpawnECBSystem))]
public partial struct StandardRaycastWeaponShotVisualsSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
StandardRaycastWeaponShotVisualsJob visualsJob = new StandardRaycastWeaponShotVisualsJob
{
ECB = SystemAPI.GetSingletonRW<WeaponShotVisualsSpawnECBSystem.Singleton>().ValueRW.CreateCommandBuffer(state.WorldUnmanaged),
CharacterWeaponVisualFeedbackLookup = SystemAPI.GetComponentLookup<CharacterWeaponVisualFeedback>(false),
LocalToWorldLookup = SystemAPI.GetComponentLookup<LocalToWorld>(true),
};
visualsJob.Schedule();
}

[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;

public partial class @PlatformerInputActions: IInputActionCollection2, IDisposable


{
public InputActionAsset asset { get; }
public @PlatformerInputActions()
{
asset = InputActionAsset.FromJson(@"{
""name"": ""PlatformerInputActions"",
""maps"": [
{
""name"": ""GameplayMap"",
""id"": ""34ad15fe-9aca-4100-b22a-63eced3d7198"",
""actions"": [
{
""name"": ""Move"",
""type"": ""Value"",
""id"": ""5926d1fd-8bc7-48e8-a671-ff59c7d8fae6"",
""expectedControlType"": """",
""processors"": ""Clamp(max=1)"",
""interactions"": """",
""initialStateCheck"": true
},
{
""name"": ""LookDelta"",
""type"": ""Value"",
""id"": ""8313772a-4db2-4934-89d6-3410caf6d19c"",
""expectedControlType"": ""Vector2"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": true
},
{
""name"": ""LookConst"",
""type"": ""Value"",
""id"": ""c7859773-cf29-4a35-8002-7294da914c20"",
""expectedControlType"": """",
""processors"": """",
""interactions"": """",
""initialStateCheck"": true
},
{
""name"": ""Jump"",
""type"": ""Button"",
""id"": ""e72bb4f0-769e-419a-b126-e5cceb2c9b10"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
},
{
""name"": ""Roll"",
""type"": ""Button"",
""id"": ""12d27c10-f0e3-42fc-a7b2-ba873ab82682"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
},
{
""name"": ""Sprint"",
""type"": ""Button"",
""id"": ""fe3235ec-7c88-4156-8908-e23daba138a4"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
},
{
""name"": ""CameraZoom"",
""type"": ""Value"",
""id"": ""b9342fd8-9c52-4100-bb70-6c6d361609b7"",
""expectedControlType"": """",
""processors"": """",
""interactions"": """",
""initialStateCheck"": true
},
{
""name"": ""Dash"",
""type"": ""Button"",
""id"": ""12230fe0-5d15-4f77-90f7-e62d57d5940e"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
},
{
""name"": ""Crouch"",
""type"": ""Button"",
""id"": ""c25ed409-b6b4-4e95-a936-3284cb98bd8f"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
},
{
""name"": ""Rope"",
""type"": ""Button"",
""id"": ""56cac786-2ffc-454b-9435-dc8361542e44"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
},
{
""name"": ""Climb"",
""type"": ""Button"",
""id"": ""1361423f-7b42-42bd-b1b3-ba59d5ef7212"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
},
{
""name"": ""FlyNoCollisions"",
""type"": ""Button"",
""id"": ""70df9dca-62d7-4902-9c9e-8629a25d62b2"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
}
],
""bindings"": [
{
""name"": """",
""id"": ""e4976612-b5ff-40fd-936f-e69fc0bd3f82"",
""path"": ""<Mouse>/delta"",
""interactions"": """",
""processors"": ""ScaleVector2(x=0.05,y=0.05)"",
""groups"": """",
""action"": ""LookDelta"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""8f2809af-0cc9-4cf9-a30d-41e59cc01524"",
""path"": ""<Keyboard>/space"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Jump"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""a82e4e39-40fd-48fb-8fa6-6b8ceea0bc27"",
""path"": ""<Gamepad>/buttonSouth"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Jump"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""343b41fc-355f-42c2-b00a-c8ae30ad612a"",
""path"": ""<Keyboard>/leftCtrl"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Roll"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""dab2fec4-1ac5-4df6-b615-e90912460c46"",
""path"": ""<Gamepad>/buttonWest"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Roll"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": ""2D Vector"",
""id"": ""2e2dc72a-9e80-4b08-96de-571c9cd72773"",
""path"": ""2DVector"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Move"",
""isComposite"": true,
""isPartOfComposite"": false
},
{
""name"": ""up"",
""id"": ""b9bfcc8c-1a2c-41f9-9230-d96b226090d9"",
""path"": ""<Keyboard>/w"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": ""down"",
""id"": ""d44b095e-aed9-481d-aae4-eab8119e9854"",
""path"": ""<Keyboard>/s"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": ""left"",
""id"": ""37210dad-672f-4306-a3d3-355b7547b03f"",
""path"": ""<Keyboard>/a"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": ""right"",
""id"": ""51a570f0-8ea8-43fc-ae72-b41792b8d971"",
""path"": ""<Keyboard>/d"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": """",
""id"": ""6ec1be08-235d-437e-9d3c-ca301bc06aeb"",
""path"": ""<Gamepad>/leftStick"",
""interactions"": """",
""processors"": ""StickDeadzone"",
""groups"": """",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""b143f747-5e51-4c9c-9267-39ff22088fdf"",
""path"": ""<Keyboard>/leftShift"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Sprint"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""e023a517-781e-496f-96e1-62a85c46f78e"",
""path"": ""<Gamepad>/rightTrigger"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Sprint"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""e601188a-0696-45cd-80b7-ba3eac4314ab"",
""path"": ""<Mouse>/scroll/y"",
""interactions"": """",
""processors"": ""Scale(factor=0.1),Invert"",
""groups"": """",
""action"": ""CameraZoom"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""404415c5-c8d3-4ecd-acaa-7f7037641a0e"",
""path"": ""<Gamepad>/dpad/y"",
""interactions"": """",
""processors"": ""Invert"",
""groups"": """",
""action"": ""CameraZoom"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""922282e4-fb57-400d-aaa9-cb143fa897ce"",
""path"": ""<Keyboard>/e"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Dash"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""b9844999-9546-46f6-8859-bea770cbbb69"",
""path"": ""<Gamepad>/buttonEast"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Dash"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""b4fc3d14-c1f9-485d-81b7-5869df204075"",
""path"": ""<Keyboard>/c"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Crouch"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""3d5a467e-67c3-4320-ad53-5c7fb09bec0b"",
""path"": ""<Gamepad>/rightStickPress"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Crouch"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""6c4217fc-df5f-40b3-bde8-524d2d9c1a57"",
""path"": ""<Keyboard>/q"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Rope"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""a892b883-92b3-45bf-b2c8-4e0a81b4fdda"",
""path"": ""<Gamepad>/rightShoulder"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Rope"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""8c686a2f-07d8-40d3-ab87-dfb197892165"",
""path"": ""<Keyboard>/f"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Climb"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""af69a741-4607-4b4c-9e46-6f53158ba1e5"",
""path"": ""<Gamepad>/leftShoulder"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Climb"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""22707d2a-a98c-4a5f-aecd-4777414fff8d"",
""path"": ""<Keyboard>/z"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""FlyNoCollisions"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""644381cf-4f4c-480d-a1cf-2b25df88971f"",
""path"": ""<Gamepad>/select"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""FlyNoCollisions"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""c896ef92-dd57-40e7-bb64-9e096d169c1b"",
""path"": ""<Gamepad>/rightStick"",
""interactions"": """",
""processors"": ""StickDeadzone,ScaleVector2(x=70,y=70)"",
""groups"": """",
""action"": ""LookConst"",
""isComposite"": false,
""isPartOfComposite"": false
}
]
}
],
""controlSchemes"": []
}");
// GameplayMap
m_GameplayMap = asset.FindActionMap("GameplayMap", throwIfNotFound: true);
m_GameplayMap_Move = m_GameplayMap.FindAction("Move", throwIfNotFound: true);
m_GameplayMap_LookDelta = m_GameplayMap.FindAction("LookDelta", throwIfNotFound: true);
m_GameplayMap_LookConst = m_GameplayMap.FindAction("LookConst", throwIfNotFound: true);
m_GameplayMap_Jump = m_GameplayMap.FindAction("Jump", throwIfNotFound: true);
m_GameplayMap_Roll = m_GameplayMap.FindAction("Roll", throwIfNotFound: true);
m_GameplayMap_Sprint = m_GameplayMap.FindAction("Sprint", throwIfNotFound: true);
m_GameplayMap_CameraZoom = m_GameplayMap.FindAction("CameraZoom", throwIfNotFound: true);
m_GameplayMap_Dash = m_GameplayMap.FindAction("Dash", throwIfNotFound: true);
m_GameplayMap_Crouch = m_GameplayMap.FindAction("Crouch", throwIfNotFound: true);
m_GameplayMap_Rope = m_GameplayMap.FindAction("Rope", throwIfNotFound: true);
m_GameplayMap_Climb = m_GameplayMap.FindAction("Climb", throwIfNotFound: true);
m_GameplayMap_FlyNoCollisions = m_GameplayMap.FindAction("FlyNoCollisions", throwIfNotFound: true);
}

public void Dispose()


{
UnityEngine.Object.Destroy(asset);
}

public InputBinding? bindingMask


{
get => asset.bindingMask;
set => asset.bindingMask = value;
}
public ReadOnlyArray<InputDevice>? devices
{
get => asset.devices;
set => asset.devices = value;
}
public ReadOnlyArray<InputControlScheme> controlSchemes => asset.controlSchemes;

public bool Contains(InputAction action)


{
return asset.Contains(action);
}
public IEnumerator<InputAction> GetEnumerator()
{
return asset.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Enable()
{
asset.Enable();
}

public void Disable()


{
asset.Disable();
}
public IEnumerable<InputBinding> bindings => asset.bindings;

public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false)


{
return asset.FindAction(actionNameOrId, throwIfNotFound);
}
public int FindBinding(InputBinding bindingMask, out InputAction action)
{
return asset.FindBinding(bindingMask, out action);
}
// GameplayMap
private readonly InputActionMap m_GameplayMap;
private List<IGameplayMapActions> m_GameplayMapActionsCallbackInterfaces = new List<IGameplayMapActions>();
private readonly InputAction m_GameplayMap_Move;
private readonly InputAction m_GameplayMap_LookDelta;
private readonly InputAction m_GameplayMap_LookConst;
private readonly InputAction m_GameplayMap_Jump;
private readonly InputAction m_GameplayMap_Roll;
private readonly InputAction m_GameplayMap_Sprint;
private readonly InputAction m_GameplayMap_CameraZoom;
private readonly InputAction m_GameplayMap_Dash;
private readonly InputAction m_GameplayMap_Crouch;
private readonly InputAction m_GameplayMap_Rope;
private readonly InputAction m_GameplayMap_Climb;
private readonly InputAction m_GameplayMap_FlyNoCollisions;
public struct GameplayMapActions
{
private @PlatformerInputActions m_Wrapper;
public GameplayMapActions(@PlatformerInputActions wrapper) { m_Wrapper = wrapper; }
public InputAction @Move => m_Wrapper.m_GameplayMap_Move;
public InputAction @LookDelta => m_Wrapper.m_GameplayMap_LookDelta;
public InputAction @LookConst => m_Wrapper.m_GameplayMap_LookConst;
public InputAction @Jump => m_Wrapper.m_GameplayMap_Jump;
public InputAction @Roll => m_Wrapper.m_GameplayMap_Roll;
public InputAction @Sprint => m_Wrapper.m_GameplayMap_Sprint;
public InputAction @CameraZoom => m_Wrapper.m_GameplayMap_CameraZoom;
public InputAction @Dash => m_Wrapper.m_GameplayMap_Dash;
public InputAction @Crouch => m_Wrapper.m_GameplayMap_Crouch;
public InputAction @Rope => m_Wrapper.m_GameplayMap_Rope;
public InputAction @Climb => m_Wrapper.m_GameplayMap_Climb;
public InputAction @FlyNoCollisions => m_Wrapper.m_GameplayMap_FlyNoCollisions;
public InputActionMap Get() { return m_Wrapper.m_GameplayMap; }
public void Enable() { Get().Enable(); }
public void Disable() { Get().Disable(); }
public bool enabled => Get().enabled;
public static implicit operator InputActionMap(GameplayMapActions set) { return set.Get(); }
public void AddCallbacks(IGameplayMapActions instance)
{
if (instance == null || m_Wrapper.m_GameplayMapActionsCallbackInterfaces.Contains(instance)) return;
m_Wrapper.m_GameplayMapActionsCallbackInterfaces.Add(instance);
@Move.started += instance.OnMove;
@Move.performed += instance.OnMove;
@Move.canceled += instance.OnMove;
@LookDelta.started += instance.OnLookDelta;
@LookDelta.performed += instance.OnLookDelta;
@LookDelta.canceled += instance.OnLookDelta;
@LookConst.started += instance.OnLookConst;
@LookConst.performed += instance.OnLookConst;
@LookConst.canceled += instance.OnLookConst;
@Jump.started += instance.OnJump;
@Jump.performed += instance.OnJump;
@Jump.canceled += instance.OnJump;
@Roll.started += instance.OnRoll;
@Roll.performed += instance.OnRoll;
@Roll.canceled += instance.OnRoll;
@Sprint.started += instance.OnSprint;
@Sprint.performed += instance.OnSprint;
@Sprint.canceled += instance.OnSprint;
@CameraZoom.started += instance.OnCameraZoom;
@CameraZoom.performed += instance.OnCameraZoom;
@CameraZoom.canceled += instance.OnCameraZoom;
@Dash.started += instance.OnDash;
@Dash.performed += instance.OnDash;
@Dash.canceled += instance.OnDash;
@Crouch.started += instance.OnCrouch;
@Crouch.performed += instance.OnCrouch;
@Crouch.canceled += instance.OnCrouch;
@Rope.started += instance.OnRope;
@Rope.performed += instance.OnRope;
@Rope.canceled += instance.OnRope;
@Climb.started += instance.OnClimb;
@Climb.performed += instance.OnClimb;
@Climb.canceled += instance.OnClimb;
@FlyNoCollisions.started += instance.OnFlyNoCollisions;
@FlyNoCollisions.performed += instance.OnFlyNoCollisions;
@FlyNoCollisions.canceled += instance.OnFlyNoCollisions;
}
private void UnregisterCallbacks(IGameplayMapActions instance)
{
@Move.started -= instance.OnMove;
@Move.performed -= instance.OnMove;
@Move.canceled -= instance.OnMove;
@LookDelta.started -= instance.OnLookDelta;
@LookDelta.performed -= instance.OnLookDelta;
@LookDelta.canceled -= instance.OnLookDelta;
@LookConst.started -= instance.OnLookConst;
@LookConst.performed -= instance.OnLookConst;
@LookConst.canceled -= instance.OnLookConst;
@Jump.started -= instance.OnJump;
@Jump.performed -= instance.OnJump;
@Jump.canceled -= instance.OnJump;
@Roll.started -= instance.OnRoll;
@Roll.performed -= instance.OnRoll;
@Roll.canceled -= instance.OnRoll;
@Sprint.started -= instance.OnSprint;
@Sprint.performed -= instance.OnSprint;
@Sprint.canceled -= instance.OnSprint;
@CameraZoom.started -= instance.OnCameraZoom;
@CameraZoom.performed -= instance.OnCameraZoom;
@CameraZoom.canceled -= instance.OnCameraZoom;
@Dash.started -= instance.OnDash;
@Dash.performed -= instance.OnDash;
@Dash.canceled -= instance.OnDash;
@Crouch.started -= instance.OnCrouch;
@Crouch.performed -= instance.OnCrouch;
@Crouch.canceled -= instance.OnCrouch;
@Rope.started -= instance.OnRope;
@Rope.performed -= instance.OnRope;
@Rope.canceled -= instance.OnRope;
@Climb.started -= instance.OnClimb;
@Climb.performed -= instance.OnClimb;
@Climb.canceled -= instance.OnClimb;
@FlyNoCollisions.started -= instance.OnFlyNoCollisions;
@FlyNoCollisions.performed -= instance.OnFlyNoCollisions;
@FlyNoCollisions.canceled -= instance.OnFlyNoCollisions;
}

public void RemoveCallbacks(IGameplayMapActions instance)


{
if (m_Wrapper.m_GameplayMapActionsCallbackInterfaces.Remove(instance))
UnregisterCallbacks(instance);
}

public void SetCallbacks(IGameplayMapActions instance)


{
foreach (var item in m_Wrapper.m_GameplayMapActionsCallbackInterfaces)
UnregisterCallbacks(item);
m_Wrapper.m_GameplayMapActionsCallbackInterfaces.Clear();
AddCallbacks(instance);
}
}
public GameplayMapActions @GameplayMap => new GameplayMapActions(this);
public interface IGameplayMapActions
{
void OnMove(InputAction.CallbackContext context);
void OnLookDelta(InputAction.CallbackContext context);
void OnLookConst(InputAction.CallbackContext context);
void OnJump(InputAction.CallbackContext context);
void OnRoll(InputAction.CallbackContext context);
void OnSprint(InputAction.CallbackContext context);
void OnCameraZoom(InputAction.CallbackContext context);
void OnDash(InputAction.CallbackContext context);
void OnCrouch(InputAction.CallbackContext context);
void OnRope(InputAction.CallbackContext context);
void OnClimb(InputAction.CallbackContext context);
void OnFlyNoCollisions(InputAction.CallbackContext context);
}
}
Platformer/Assets/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);
}
}
}
Platformer/Assets/Scripts/Camera/MainEntityCamera.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;

[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;

public class MainGameObjectCamera : MonoBehaviour


{
public static Camera Instance;

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;

public enum CharacterState


{
Uninitialized,
GroundMove,
Crouched,
AirMove,
WallRun,
Rolling,
LedgeGrab,
LedgeStandingUp,
Dashing,
Swimming,
Climbing,
FlyingNoCollisions,
RopeSwing,
}
public interface IPlatformerCharacterState
{
void OnStateEnter(CharacterState previousState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect
void OnStateExit(CharacterState nextState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect);
void OnStatePhysicsUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect);
void OnStateVariableUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect);
void GetCameraParameters(in PlatformerCharacterComponent character, out Entity cameraTarget, out bool calculateUpFromGravity);
void GetMoveVectorFromPlayerInput(in PlatformerPlayerInputs inputs, quaternion cameraRotation, out float3 moveVector);
}
Platformer/Assets/Scripts/Character/PlatformerCharacterAnimation.cs
using System;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
using Unity.CharacterController;

[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;

[HideInInspector] public CharacterState LastAnimationCharacterState;


}

public static class PlatformerCharacterAnimationHandler


{
public static void UpdateAnimation(
Animator animator,
ref PlatformerCharacterAnimation characterAnimation,
in KinematicCharacterBody characterBody,
in PlatformerCharacterComponent characterComponent,
in PlatformerCharacterStateMachine characterStateMachine,
in PlatformerCharacterControl characterControl,
in LocalTransform localTransform)
{
float velocityMagnitude = math.length(characterBody.RelativeVelocity);
switch (characterStateMachine.CurrentState)
{
case CharacterState.GroundMove:
{
if (math.length(characterControl.MoveVector) < 0.01f)
{
animator.speed = 1f;
animator.SetInteger(characterAnimation.ClipIndexParameterHash, characterAnimation.IdleClip);
}
else
{
if (characterComponent.IsSprinting)
{
float velocityRatio = velocityMagnitude / characterComponent.GroundSprintMaxSpeed;
animator.speed = velocityRatio;
animator.SetInteger(characterAnimation.ClipIndexParameterHash, characterAnimation.SprintClip);
}
else
{
float velocityRatio = velocityMagnitude / characterComponent.GroundRunMaxSpeed;
animator.speed = velocityRatio;
animator.SetInteger(characterAnimation.ClipIndexParameterHash, characterAnimation.RunClip);
}
}
}
break;
case CharacterState.Crouched:
{
if (math.length(characterControl.MoveVector) < 0.01f)
{
animator.speed = 1f;
animator.SetInteger(characterAnimation.ClipIndexParameterHash, characterAnimation.CrouchIdleClip);
}
else
{
float velocityRatio = velocityMagnitude / characterComponent.CrouchedMaxSpeed;
animator.speed = velocityRatio;
animator.SetInteger(characterAnimation.ClipIndexParameterHash, characterAnimation.CrouchMoveClip);
}
}
break;
case CharacterState.AirMove:
{
animator.speed = 1f;
animator.SetInteger(characterAnimation.ClipIndexParameterHash, characterAnimation.InAirClip);
}
break;
case CharacterState.Dashing:
{
animator.speed = 1f;
animator.SetInteger(characterAnimation.ClipIndexParameterHash, characterAnimation.DashClip);
}
break;
case CharacterState.WallRun:
{
bool wallIsOnTheLeft = math.dot(MathUtilities.GetRightFromRotation(localTransform.Rotation), characterComponent.LastKnownWallNormal) > 0f;
animator.speed = 1f;
animator.SetInteger(characterAnimation.ClipIndexParameterHash, wallIsOnTheLeft ? characterAnimation.WallRunLeftClip : characterAnimation.WallRunRightClip);
}
break;
case CharacterState.RopeSwing:
{
animator.speed = 1f;
animator.SetInteger(characterAnimation.ClipIndexParameterHash, characterAnimation.RopeHangClip);
}
break;
case CharacterState.Climbing:
{
float velocityRatio = velocityMagnitude / characterComponent.ClimbingSpeed;
animator.speed = velocityRatio;
animator.SetInteger(characterAnimation.ClipIndexParameterHash, characterAnimation.ClimbingMoveClip);
}
break;
case CharacterState.LedgeGrab:
{
float velocityRatio = velocityMagnitude / characterComponent.LedgeMoveSpeed;
animator.speed = velocityRatio;
animator.SetInteger(characterAnimation.ClipIndexParameterHash, characterAnimation.LedgeGrabMoveClip);
}
break;
case CharacterState.LedgeStandingUp:
{
animator.speed = 1f;
//animator.SetInteger(characterAnimation.ClipIndexParameterHash, characterAnimation.LedgeStandUpClip);
}
break;
case CharacterState.Swimming:
{
float velocityRatio = velocityMagnitude / characterComponent.SwimmingMaxSpeed;
if (velocityRatio < 0.1f)
{
animator.speed = 1f;
animator.SetInteger(characterAnimation.ClipIndexParameterHash, characterAnimation.SwimmingIdleClip);
}
else
{
animator.speed = velocityRatio;
animator.SetInteger(characterAnimation.ClipIndexParameterHash, characterAnimation.SwimmingMoveClip);
}
}
break;
case CharacterState.Rolling:
case CharacterState.FlyingNoCollisions:
{
animator.speed = 1f;
animator.SetInteger(characterAnimation.ClipIndexParameterHash, characterAnimation.IdleClip);
}
break;
}
characterAnimation.LastAnimationCharacterState = characterStateMachine.CurrentState;
}
}
Platformer/Assets/Scripts/Character/PlatformerCharacterAnimationAuthoring.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[DisallowMultipleComponent]
public class PlatformerCharacterAnimationAuthoring : MonoBehaviour
{
public class Baker : Baker<PlatformerCharacterAnimationAuthoring>
{
public override void Bake(PlatformerCharacterAnimationAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
PlatformerCharacterAnimation characterAnimation = new PlatformerCharacterAnimation();

// Set clip indexes


characterAnimation.IdleClip = 0;
characterAnimation.RunClip = 1;
characterAnimation.SprintClip = 2;
characterAnimation.InAirClip = 3;
characterAnimation.LedgeGrabMoveClip = 4;
characterAnimation.LedgeStandUpClip = 5;
characterAnimation.WallRunLeftClip = 6;
characterAnimation.WallRunRightClip = 7;
characterAnimation.CrouchIdleClip = 8;
characterAnimation.CrouchMoveClip = 9;
characterAnimation.ClimbingMoveClip = 10;
characterAnimation.SwimmingIdleClip = 11;
characterAnimation.SwimmingMoveClip = 12;
characterAnimation.DashClip = 13;
characterAnimation.RopeHangClip = 14;
characterAnimation.SlidingClip = 15;
AddComponent(entity, characterAnimation);
}
}
}
Platformer/Assets/Scripts/Character/PlatformerCharacterAspect.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;
using UnityEngine.SocialPlatforms;
using CapsuleCollider = Unity.Physics.CapsuleCollider;
using Material = Unity.Physics.Material;
public struct PlatformerCharacterUpdateContext
{
public int ChunkIndex;
public EntityCommandBuffer.ParallelWriter EndFrameECB;
[ReadOnly]
public ComponentLookup<CharacterFrictionModifier> CharacterFrictionModifierLookup;
[ReadOnly]
public BufferLookup<LinkedEntityGroup> LinkedEntityGroupLookup;
public void SetChunkIndex(int chunkIndex)
{
ChunkIndex = chunkIndex;
}
public void OnSystemCreate(ref SystemState state)
{
CharacterFrictionModifierLookup = state.GetComponentLookup<CharacterFrictionModifier>(true);
LinkedEntityGroupLookup = state.GetBufferLookup<LinkedEntityGroup>(true);
}

public void OnSystemUpdate(ref SystemState state, EntityCommandBuffer endFrameECB)


{
EndFrameECB = endFrameECB.AsParallelWriter();
CharacterFrictionModifierLookup.Update(ref state);
LinkedEntityGroupLookup.Update(ref state);
}
}
public readonly partial struct PlatformerCharacterAspect : IAspect, IKinematicCharacterProcessor<PlatformerCharacterUpdateContext>
{
public readonly KinematicCharacterAspect CharacterAspect;
public readonly RefRW<PlatformerCharacterComponent> Character;
public readonly RefRW<PlatformerCharacterControl> CharacterControl;
public readonly RefRW<PlatformerCharacterStateMachine> StateMachine;
public readonly RefRW<CustomGravity> CustomGravity;
public void PhysicsUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref PlatformerCharacterComponent character = ref Character.ValueRW;
ref PlatformerCharacterControl characterControl = ref CharacterControl.ValueRW;
ref PlatformerCharacterStateMachine stateMachine = ref StateMachine.ValueRW;
// Common pre-update logic across states
{
// Handle initial state transition
if (stateMachine.CurrentState == CharacterState.Uninitialized)
{
stateMachine.TransitionToState(CharacterState.AirMove, ref context, ref baseContext, in this);
}
if (characterControl.JumpHeld)
{
character.HeldJumpTimeCounter += baseContext.Time.DeltaTime;
}
else
{
character.HeldJumpTimeCounter = 0f;
character.AllowHeldJumpInAir = false;
}
if (characterControl.JumpPressed)
{
character.LastTimeJumpPressed = (float)baseContext.Time.ElapsedTime;
}
character.HasDetectedMoveAgainstWall = false;
if (characterBody.IsGrounded)
{
character.LastTimeWasGrounded = (float)baseContext.Time.ElapsedTime;
character.CurrentUngroundedJumps = 0;
character.AllowJumpAfterBecameUngrounded = true;
character.AllowHeldJumpInAir = true;
}
if (character.LedgeGrabBlockCounter > 0f)
{
character.LedgeGrabBlockCounter -= baseContext.Time.DeltaTime;
}
}
stateMachine.OnStatePhysicsUpdate(stateMachine.CurrentState, ref context, ref baseContext, in this);
// Common post-update logic across states
{
character.JumpPressedBeforeBecameGrounded = false;
}
}
public void VariableUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref PlatformerCharacterStateMachine stateMachine = ref StateMachine.ValueRW;
ref quaternion characterRotation = ref CharacterAspect.LocalTransform.ValueRW.Rotation;
KinematicCharacterUtilities.AddVariableRateRotationFromFixedRateRotation(ref characterRotation, characterBody.RotationFromParent, baseContext.Time.DeltaTime, characterBody
stateMachine.OnStateVariableUpdate(stateMachine.CurrentState, ref context, ref baseContext, in this);
}
public bool DetectGlobalTransitions(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
ref PlatformerCharacterStateMachine stateMachine = ref StateMachine.ValueRW;
ref PlatformerCharacterControl characterControl = ref CharacterControl.ValueRW;
if (stateMachine.CurrentState != CharacterState.Swimming && stateMachine.CurrentState != CharacterState.FlyingNoCollisions)
{
if (SwimmingState.DetectWaterZones(ref context, ref baseContext, in this, out float3 tmpDirection, out float tmpDistance))
{
if (tmpDistance < 0f)
{
stateMachine.TransitionToState(CharacterState.Swimming, ref context, ref baseContext, in this);
return true;
}
}
}
if (characterControl.FlyNoCollisionsPressed)
{
if (stateMachine.CurrentState == CharacterState.FlyingNoCollisions)
{
stateMachine.TransitionToState(CharacterState.AirMove, ref context, ref baseContext, in this);
return true;
}
else
{
stateMachine.TransitionToState(CharacterState.FlyingNoCollisions, ref context, ref baseContext, in this);
return true;
}
}
return false;
}
public void HandlePhysicsUpdatePhase1(
ref PlatformerCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
bool allowParentHandling,
bool allowGroundingDetection)
{
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref float3 characterPosition = ref CharacterAspect.LocalTransform.ValueRW.Position;
CharacterAspect.Update_Initialize(in this, ref context, ref baseContext, ref characterBody, baseContext.Time.DeltaTime);
if (allowParentHandling)
{
CharacterAspect.Update_ParentMovement(in this, ref context, ref baseContext, ref characterBody, ref characterPosition, characterBody.WasGroundedBeforeCharacterUpdate);
}
if (allowGroundingDetection)
{
CharacterAspect.Update_Grounding(in this, ref context, ref baseContext, ref characterBody, ref characterPosition);
}
}
public void HandlePhysicsUpdatePhase2(
ref PlatformerCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
bool allowPreventGroundingFromFutureSlopeChange,
bool allowGroundingPushing,
bool allowMovementAndDecollisions,
bool allowMovingPlatformDetection,
bool allowParentHandling)
{
ref PlatformerCharacterComponent character = ref Character.ValueRW;
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref float3 characterPosition = ref CharacterAspect.LocalTransform.ValueRW.Position;
CustomGravity customGravity = CustomGravity.ValueRO;
if (allowPreventGroundingFromFutureSlopeChange)
{
CharacterAspect.Update_PreventGroundingFromFutureSlopeChange(in this, ref context, ref baseContext, ref characterBody, in character.StepAndSlopeHandling);
}
if (allowGroundingPushing)
{
CharacterAspect.Update_GroundPushing(in this, ref context, ref baseContext, customGravity.Gravity);
}
if (allowMovementAndDecollisions)
{
CharacterAspect.Update_MovementAndDecollisions(in this, ref context, ref baseContext, ref characterBody, ref characterPosition);
}
if (allowMovingPlatformDetection)
{
CharacterAspect.Update_MovingPlatformDetection(ref baseContext, ref characterBody);
}
if (allowParentHandling)
{
CharacterAspect.Update_ParentMomentum(ref baseContext, ref characterBody);
}
CharacterAspect.Update_ProcessStatefulCharacterHits();
}
public unsafe void SetCapsuleGeometry(CapsuleGeometry capsuleGeometry)
{
ref PhysicsCollider physicsCollider = ref CharacterAspect.PhysicsCollider.ValueRW;
CapsuleCollider* capsuleCollider = (CapsuleCollider*)physicsCollider.ColliderPtr;
capsuleCollider->Geometry = capsuleGeometry;
}
public float3 GetGeometryCenter(CapsuleGeometryDefinition geometry)
{
float3 characterPosition = CharacterAspect.LocalTransform.ValueRW.Position;
quaternion characterRotation = CharacterAspect.LocalTransform.ValueRW.Rotation;
RigidTransform characterTransform = new RigidTransform(characterRotation, characterPosition);
float3 geometryCenter = math.transform(characterTransform, geometry.Center);
return geometryCenter;
}
public unsafe bool CanStandUp(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
ref PhysicsCollider physicsCollider = ref CharacterAspect.PhysicsCollider.ValueRW;
ref PlatformerCharacterComponent character = ref Character.ValueRW;
ref float3 characterPosition = ref CharacterAspect.LocalTransform.ValueRW.Position;
ref quaternion characterRotation = ref CharacterAspect.LocalTransform.ValueRW.Rotation;
ref KinematicCharacterProperties characterProperties = ref CharacterAspect.CharacterProperties.ValueRW;
// Overlap test with standing geometry to see if we have space to stand
CapsuleCollider* capsuleCollider = ((CapsuleCollider*)physicsCollider.ColliderPtr);
CapsuleGeometry initialGeometry = capsuleCollider->Geometry;
capsuleCollider->Geometry = character.StandingGeometry.ToCapsuleGeometry();
bool isObstructed = false;
if (CharacterAspect.CalculateDistanceClosestCollisions(
in this,
ref context,
ref baseContext,
characterPosition,
characterRotation,
0f,
characterProperties.ShouldIgnoreDynamicBodies(),
out DistanceHit hit))
{
isObstructed = true;
}
capsuleCollider->Geometry = initialGeometry;
return !isObstructed;
}
public static bool CanBeAffectedByWindZone(CharacterState currentCharacterState)
{
if (currentCharacterState == CharacterState.GroundMove ||
currentCharacterState == CharacterState.AirMove ||
currentCharacterState == CharacterState.Crouched ||
currentCharacterState == CharacterState.Rolling)
{
return true;
}
return false;
}
public static CapsuleGeometry CreateCharacterCapsuleGeometry(float radius, float height, bool centered)
{
height = math.max(height, radius * 2f);
float halfHeight = height * 0.5f;
return new CapsuleGeometry
{
Radius = radius,
Vertex0 = centered ? (-math.up() * (halfHeight - radius)) : (math.up() * radius),
Vertex1 = centered ? (math.up() * (halfHeight - radius)) : (math.up() * (height - radius)),
};
}
public static void GetCommonMoveVectorFromPlayerInput(in PlatformerPlayerInputs inputs, quaternion cameraRotation, out float3 moveVector)
{
moveVector = (math.mul(cameraRotation, math.right()) * inputs.Move.x) + (math.mul(cameraRotation, math.forward()) * inputs.Move.y);
}
#region Character Processor Callbacks
public void UpdateGroundingUp(
ref PlatformerCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext)
{
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
CharacterAspect.Default_UpdateGroundingUp(ref characterBody);
}
public bool CanCollideWithHit(
ref PlatformerCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
in BasicHit hit)
{
return PhysicsUtilities.IsCollidable(hit.Material);
}
public bool IsGroundedOnHit(
ref PlatformerCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
in BasicHit hit,
int groundingEvaluationType)
{
PlatformerCharacterComponent characterComponent = Character.ValueRO;
return CharacterAspect.Default_IsGroundedOnHit(
in this,
ref context,
ref baseContext,
in hit,
in characterComponent.StepAndSlopeHandling,
groundingEvaluationType);
}
public void OnMovementHit(
ref PlatformerCharacterUpdateContext 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;
PlatformerCharacterComponent characterComponent = Character.ValueRO;
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 PlatformerCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
ref PhysicsMass characterMass,
ref PhysicsMass otherMass,
BasicHit hit)
{
}
public void ProjectVelocityOnHits(
ref PlatformerCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
ref float3 velocity,
ref bool characterIsGrounded,
ref BasicHit characterGroundHit,
in DynamicBuffer<KinematicVelocityProjectionHit> velocityProjectionHits,
float3 originalVelocityDirection)
{
PlatformerCharacterComponent characterComponent = Character.ValueRO;
CharacterAspect.Default_ProjectVelocityOnHits(
ref velocity,
ref characterIsGrounded,
ref characterGroundHit,
in velocityProjectionHits,
originalVelocityDirection,
characterComponent.StepAndSlopeHandling.ConstrainVelocityToGroundPlane);
}
#endregion
}
Platformer/Assets/Scripts/Character/PlatformerCharacterAuthoring.cs
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using UnityEngine;
using Unity.CharacterController;
using Unity.Physics;
using UnityEngine.Serialization;

[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;

public class Baker : Baker<PlatformerCharacterAuthoring>


{
public override void Bake(PlatformerCharacterAuthoring authoring)
{
KinematicCharacterUtilities.BakeCharacter(this, authoring, authoring.CharacterProperties);

authoring.Character.DefaultCameraTargetEntity = GetEntity(authoring.DefaultCameraTarget, TransformUsageFlags.Dynamic);


authoring.Character.SwimmingCameraTargetEntity = GetEntity(authoring.SwimmingCameraTarget, TransformUsageFlags.Dynamic);
authoring.Character.ClimbingCameraTargetEntity = GetEntity(authoring.ClimbingCameraTarget, TransformUsageFlags.Dynamic);
authoring.Character.CrouchingCameraTargetEntity = GetEntity(authoring.CrouchingCameraTarget, TransformUsageFlags.Dynamic);
authoring.Character.MeshRootEntity = GetEntity(authoring.MeshRoot, TransformUsageFlags.Dynamic);
authoring.Character.RollballMeshEntity = GetEntity(authoring.RollballMesh, TransformUsageFlags.Dynamic);
authoring.Character.RopePrefabEntity = GetEntity(authoring.RopePrefab, TransformUsageFlags.Dynamic);
authoring.Character.LocalSwimmingDetectionPoint = authoring.SwimmingDetectionPoint.transform.localPosition;
authoring.Character.LocalLedgeDetectionPoint = authoring.LedgeDetectionPoint.transform.localPosition;

Entity entity = GetEntity(TransformUsageFlags.Dynamic);

AddComponent(entity, authoring.Character);
AddComponent(entity, new PlatformerCharacterControl());
AddComponent(entity, new PlatformerCharacterStateMachine());
AddComponentObject(entity, new PlatformerCharacterHybridData { MeshPrefab = authoring.MeshPrefab });
}
}

private void OnDrawGizmosSelected()


{
if (DebugStandingGeometry)
{
Gizmos.color = Color.cyan;
DrawCapsuleGizmo(Character.StandingGeometry);
}
if (DebugCrouchingGeometry)
{
Gizmos.color = Color.cyan;
DrawCapsuleGizmo(Character.CrouchingGeometry);
}
if (DebugRollingGeometry)
{
Gizmos.color = Color.cyan;
DrawCapsuleGizmo(Character.RollingGeometry);
}
if (DebugClimbingGeometry)
{
Gizmos.color = Color.cyan;
DrawCapsuleGizmo(Character.ClimbingGeometry);
}
if (DebugSwimmingGeometry)
{
Gizmos.color = Color.cyan;
DrawCapsuleGizmo(Character.SwimmingGeometry);
}
}
private void DrawCapsuleGizmo(CapsuleGeometryDefinition capsuleGeo)
{
RigidTransform characterTransform = new RigidTransform(transform.rotation, transform.position);
float3 characterUp = transform.up;
float3 characterFwd = transform.forward;
float3 characterRight = transform.right;
float3 capsuleCenter = math.transform(characterTransform, capsuleGeo.Center);
float halfHeight = capsuleGeo.Height * 0.5f;
float3 bottomHemiCenter = capsuleCenter - (characterUp * (halfHeight - capsuleGeo.Radius));
float3 topHemiCenter = capsuleCenter + (characterUp * (halfHeight - capsuleGeo.Radius));

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;

[Header("Step & Slope")]


public BasicStepAndSlopeHandlingParameters StepAndSlopeHandling;
[Header("Misc")]
public CustomPhysicsBodyTags StickySurfaceTag;
public CustomPhysicsBodyTags ClimbableTag;
public PhysicsCategoryTags WaterPhysicsCategory;
public PhysicsCategoryTags RopeAnchorCategory;
public float UpOrientationAdaptationSharpness;
public CapsuleGeometryDefinition StandingGeometry;
public CapsuleGeometryDefinition CrouchingGeometry;
public CapsuleGeometryDefinition RollingGeometry;
public CapsuleGeometryDefinition ClimbingGeometry;
public CapsuleGeometryDefinition SwimmingGeometry;
[HideInInspector]
public float3 LocalLedgeDetectionPoint;
[HideInInspector]
public float3 LocalSwimmingDetectionPoint;
[HideInInspector]
public byte CurrentUngroundedJumps;
[HideInInspector]
public float HeldJumpTimeCounter;
[HideInInspector]
public bool JumpPressedBeforeBecameGrounded;
[HideInInspector]
public bool AllowJumpAfterBecameUngrounded;
[HideInInspector]
public bool AllowHeldJumpInAir;
[HideInInspector]
public float LastTimeJumpPressed;
[HideInInspector]
public float LastTimeWasGrounded;
[HideInInspector]
public bool HasDetectedMoveAgainstWall;
[HideInInspector]
public float3 LastKnownWallNormal;
[HideInInspector]
public float LedgeGrabBlockCounter;
[HideInInspector]
public float DistanceFromWaterSurface;
[HideInInspector]
public float3 DirectionToWaterSurface;
[HideInInspector]
public bool IsSprinting;
[HideInInspector]
public bool IsOnStickySurface;
}
[Serializable]
public struct PlatformerCharacterControl : IComponentData
{
public float3 MoveVector;
public bool JumpHeld;
public bool RollHeld;
public bool SprintHeld;
public bool JumpPressed;
public bool DashPressed;
public bool CrouchPressed;
public bool RopePressed;
public bool ClimbPressed;
public bool FlyNoCollisionsPressed;
}
public struct PlatformerCharacterInitialized : IComponentData
{ }
[Serializable]
public struct CapsuleGeometryDefinition
{
public float Radius;
public float Height;
public float3 Center;
public CapsuleGeometry ToCapsuleGeometry()
{
Height = math.max(Height, (Radius + math.EPSILON) * 2f);
float halfHeight = Height * 0.5f;
return new CapsuleGeometry
{
Radius = Radius,
Vertex0 = Center + (-math.up() * (halfHeight - Radius)),
Vertex1 = Center + (math.up() * (halfHeight - Radius)),
};
}
}
Platformer/Assets/Scripts/Character/PlatformerCharacterHybridLink.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[Serializable]
public class PlatformerCharacterHybridData : IComponentData
{
public GameObject MeshPrefab;
}
[Serializable]
public class PlatformerCharacterHybridLink : ICleanupComponentData
{
public GameObject Object;
public Animator Animator;
}
Platformer/Assets/Scripts/Character/PlatformerCharacterHybridSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.CharacterController;
using Unity.Transforms;
using UnityEngine;
[AlwaysSynchronizeSystem]
[UpdateInGroup(typeof(SimulationSystemGroup), OrderLast = true)]
[UpdateAfter(typeof(EndSimulationEntityCommandBufferSystem))]
public partial class PlatformerCharacterHybridSystem : SystemBase
{
protected override void OnUpdate()
{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<EndSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(World.Unmanaged);

// 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>();

ecb.AddComponent(entity, new PlatformerCharacterHybridLink


{
Object = tmpObject,
Animator = animator,
});
// Find the clipIndex param
for (int i = 0; i < animator.parameters.Length; i++)
{
if (animator.parameters[i].name == "ClipIndex")
{
characterAnimation.ValueRW.ClipIndexParameterHash = animator.parameters[i].nameHash;
break;
}
}
}
// Update
foreach (var (characterAnimation, characterBody, characterTransform, characterComponent, characterStateMachine, characterControl, hybridLink, entity) in SystemAPI.Query<
RefRW<PlatformerCharacterAnimation>,
KinematicCharacterBody,
LocalTransform,
PlatformerCharacterComponent,
PlatformerCharacterStateMachine,
PlatformerCharacterControl,
PlatformerCharacterHybridLink>()
.WithEntityAccess())
{
if (hybridLink.Object)
{
// Transform
LocalToWorld meshRootLTW = SystemAPI.GetComponent<LocalToWorld>(characterComponent.MeshRootEntity);
hybridLink.Object.transform.position = meshRootLTW.Position;
hybridLink.Object.transform.rotation = meshRootLTW.Rotation;
// Animation
if (hybridLink.Animator)
{
PlatformerCharacterAnimationHandler.UpdateAnimation(
hybridLink.Animator,
ref characterAnimation.ValueRW,
in characterBody,
in characterComponent,
in characterStateMachine,
in characterControl,
in characterTransform);
}

// 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;
}
}

return aspect.DetectGlobalTransitions(ref context, ref baseContext);


}
}
Platformer/Assets/Scripts/Character/States/ClimbingState.cs
using Unity.Entities;
using Unity.CharacterController;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Authoring;
using Unity.Transforms;

public struct ClimbingState : IPlatformerCharacterState


{
public float3 LastKnownClimbNormal;
private bool _foundValidClimbSurface;
public void OnStateEnter(CharacterState previousState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect
{
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref KinematicCharacterProperties characterProperties = ref aspect.CharacterAspect.CharacterProperties.ValueRW;
ref quaternion characterRotation = ref aspect.CharacterAspect.LocalTransform.ValueRW.Rotation;
aspect.SetCapsuleGeometry(character.ClimbingGeometry.ToCapsuleGeometry());
characterProperties.EvaluateGrounding = false;
characterProperties.DetectMovementCollisions = false;
characterProperties.DecollideFromOverlaps = false;
characterBody.IsGrounded = false;
LastKnownClimbNormal = -MathUtilities.GetForwardFromRotation(characterRotation);
}
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;
aspect.CharacterAspect.SetOrUpdateParentBody(ref baseContext, ref characterBody, default, default);
characterProperties.EvaluateGrounding = true;
characterProperties.DetectMovementCollisions = true;
characterProperties.DecollideFromOverlaps = true;
}
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;
aspect.HandlePhysicsUpdatePhase1(ref context, ref baseContext, true, false);
// Quad climbing surface detection raycasts
_foundValidClimbSurface = false;
if (ClimbingDetection(ref context, ref baseContext, in aspect, true, out LastKnownClimbNormal, out DistanceHit closestClimbableHit, out DistanceHit closestUnclimbableHit))
{
_foundValidClimbSurface = true;
// Adjust distance of character to surface
characterPosition += -closestClimbableHit.Distance * closestClimbableHit.SurfaceNormal;
characterPosition += (character.ClimbingGeometry.Radius - character.ClimbingDistanceFromSurface) * -closestClimbableHit.SurfaceNormal;
// decollide from most penetrating non-climbable hit
if (closestUnclimbableHit.Entity != Entity.Null)
{
characterPosition += -closestUnclimbableHit.Distance * closestUnclimbableHit.SurfaceNormal;
}
// Move
float3 climbMoveVector = math.normalizesafe(MathUtilities.ProjectOnPlane(characterControl.MoveVector, LastKnownClimbNormal)) * math.length(characterControl.MoveVector);
float3 targetVelocity = climbMoveVector * character.ClimbingSpeed;
CharacterControlUtilities.InterpolateVelocityTowardsTarget(ref characterBody.RelativeVelocity, targetVelocity, deltaTime, character.ClimbingMovementSharpness);
characterBody.RelativeVelocity = MathUtilities.ProjectOnPlane(characterBody.RelativeVelocity, LastKnownClimbNormal);
// Project velocity on non-climbable obstacles
if (aspect.CharacterAspect.VelocityProjectionHits.Length > 0)
{
bool tmpCharacterGrounded = false;
BasicHit tmpCharacterGroundHit = default;
aspect.ProjectVelocityOnHits(
ref context,
ref baseContext,
ref characterBody.RelativeVelocity,
ref tmpCharacterGrounded,
ref tmpCharacterGroundHit,
in aspect.CharacterAspect.VelocityProjectionHits,
math.normalizesafe(characterBody.RelativeVelocity));
}
// Apply velocity to position
characterPosition += characterBody.RelativeVelocity * baseContext.Time.DeltaTime;
aspect.CharacterAspect.SetOrUpdateParentBody(ref baseContext, ref characterBody, closestClimbableHit.Entity, closestClimbableHit.Position);
}
else
{
aspect.CharacterAspect.SetOrUpdateParentBody(ref baseContext, ref characterBody, default, default);
}
aspect.HandlePhysicsUpdatePhase2(ref context, ref baseContext, false, false, false, false, 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 float3 characterPosition = ref aspect.CharacterAspect.LocalTransform.ValueRW.Position;
ref quaternion characterRotation = ref aspect.CharacterAspect.LocalTransform.ValueRW.Rotation;
float3 geometryCenter = GetGeometryCenter(in aspect);
// Rotate
float3 targetCharacterUp = characterBody.GroundingUp;
if (math.lengthsq(characterControl.MoveVector) > 0f)
{
targetCharacterUp = math.normalizesafe(MathUtilities.ProjectOnPlane(characterControl.MoveVector, LastKnownClimbNormal));
}
quaternion targetRotation = quaternion.LookRotationSafe(-LastKnownClimbNormal, targetCharacterUp);
quaternion smoothedRotation = math.slerp(characterRotation, targetRotation, MathUtilities.GetSharpnessInterpolant(character.ClimbingRotationSharpness, deltaTime));
MathUtilities.SetRotationAroundPoint(ref characterRotation, ref characterPosition, geometryCenter, smoothedRotation);
}
public void GetCameraParameters(in PlatformerCharacterComponent character, out Entity cameraTarget, out bool calculateUpFromGravity)
{
cameraTarget = character.ClimbingCameraTargetEntity;
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());
// Only use input if the camera is pointing towards the normal
if (math.dot(LastKnownClimbNormal, cameraFwd) < -0.05f)
{
moveVector = (cameraRight * inputs.Move.x) + (cameraUp * inputs.Move.y);
}
else
{
moveVector = (cameraRight * inputs.Move.x) + (cameraFwd * inputs.Move.y);
}
}
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 (!_foundValidClimbSurface || characterControl.JumpPressed || characterControl.DashPressed || characterControl.ClimbPressed)
{
stateMachine.TransitionToState(CharacterState.AirMove, ref context, ref baseContext, in aspect);
return true;
}
return aspect.DetectGlobalTransitions(ref context, ref baseContext);
}
public static float3 GetGeometryCenter(in PlatformerCharacterAspect aspect)
{
float3 characterPosition = aspect.CharacterAspect.LocalTransform.ValueRW.Position;
quaternion characterRotation = aspect.CharacterAspect.LocalTransform.ValueRW.Rotation;
PlatformerCharacterComponent character = aspect.Character.ValueRW;

RigidTransform characterTransform = new RigidTransform(characterRotation, characterPosition);


float3 geometryCenter = math.transform(characterTransform, math.up() * character.ClimbingGeometry.Height * 0.5f);
return geometryCenter;
}
public static bool CanStartClimbing(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
aspect.SetCapsuleGeometry(character.ClimbingGeometry.ToCapsuleGeometry());
bool canStart = ClimbingDetection(ref context, ref baseContext, in aspect, false, out float3 avgClimbingSurfaceNormal, out DistanceHit closestClimbableHit, out DistanceHit
aspect.SetCapsuleGeometry(character.StandingGeometry.ToCapsuleGeometry());
return canStart;
}
public static bool ClimbingDetection(
ref PlatformerCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
in PlatformerCharacterAspect aspect,
bool addUnclimbableHitsAsVelocityProjectionHits,
out float3 avgClimbingSurfaceNormal,
out DistanceHit closestClimbableHit,
out DistanceHit closestUnclimbableHit)
{
int climbableNormalsCounter = 0;
avgClimbingSurfaceNormal = default;
closestClimbableHit = default;
closestUnclimbableHit = default;
ref KinematicCharacterProperties characterProperties = ref aspect.CharacterAspect.CharacterProperties.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;
aspect.CharacterAspect.CalculateDistanceAllCollisions(
in aspect,
ref context,
ref baseContext,
characterPosition,
characterRotation,
0f,
characterProperties.ShouldIgnoreDynamicBodies(),
out baseContext.TmpDistanceHits);
if (baseContext.TmpDistanceHits.Length > 0)
{
closestClimbableHit.Fraction = float.MaxValue;
closestUnclimbableHit.Fraction = float.MaxValue;
for (int i = 0; i < baseContext.TmpDistanceHits.Length; i++)
{
DistanceHit tmpHit = baseContext.TmpDistanceHits[i];
float3 faceNormal = tmpHit.SurfaceNormal;
// This is necessary for cases where the detected hit is the edge of a triangle/plane
if (PhysicsUtilities.GetHitFaceNormal(baseContext.PhysicsWorld.Bodies[tmpHit.RigidBodyIndex], tmpHit.ColliderKey, out float3 tmpFaceNormal))
{
faceNormal = tmpFaceNormal;
}
// Ignore back faces
if (math.dot(faceNormal, tmpHit.SurfaceNormal) > KinematicCharacterAspect.Constants.DotProductSimilarityEpsilon)
{
bool isClimbable = false;
if (character.ClimbableTag.Value > CustomPhysicsBodyTags.Nothing.Value)
{
if ((baseContext.PhysicsWorld.Bodies[tmpHit.RigidBodyIndex].CustomTags & character.ClimbableTag.Value) > 0)
{
isClimbable = true;
}
}
// Add virtual velocityProjection hit in direction of unclimbable hit
if (isClimbable)
{
if (tmpHit.Fraction < closestClimbableHit.Fraction)
{
closestClimbableHit = tmpHit;
}
avgClimbingSurfaceNormal += faceNormal;
climbableNormalsCounter++;
}
else
{
if (tmpHit.Fraction < closestUnclimbableHit.Fraction)
{
closestUnclimbableHit = tmpHit;
}
if (addUnclimbableHitsAsVelocityProjectionHits)
{
KinematicVelocityProjectionHit velProjHit = new KinematicVelocityProjectionHit(new BasicHit(tmpHit), false);
aspect.CharacterAspect.VelocityProjectionHits.Add(velProjHit);
}
}
}
}
if (climbableNormalsCounter > 0)
{
avgClimbingSurfaceNormal = avgClimbingSurfaceNormal / climbableNormalsCounter;
return true;
}
return false;
}
return false;
}
}
Platformer/Assets/Scripts/Character/States/CrouchedState.cs
using Unity.Entities;
using Unity.CharacterController;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Transforms;
public struct CrouchedState : IPlatformerCharacterState
{
public void OnStateEnter(CharacterState previousState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect
{
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
aspect.SetCapsuleGeometry(character.CrouchingGeometry.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;
}
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;
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);
}
float chosenMaxSpeed = character.CrouchedMaxSpeed;

float chosenSharpness = character.CrouchedMovementSharpness;


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.MoveVector
float3 targetVelocity = moveVectorOnPlane * chosenMaxSpeed;
CharacterControlUtilities.StandardGroundMove_Interpolated(ref characterBody.RelativeVelocity, targetVelocity, chosenSharpness, deltaTime, characterBody.GroundingUp, characterBody
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;
if (math.lengthsq(characterControl.MoveVector) > 0f)
{
CharacterControlUtilities.SlerpRotationTowardsDirectionAroundUp(ref characterRotation, deltaTime, math.normalizesafe(characterControl.MoveVector), MathUtilities.GetUpFromRotati
}
character.IsOnStickySurface = PhysicsUtilities.HasPhysicsTag(in baseContext.PhysicsWorld, characterBody.GroundHit.RigidBodyIndex, character.StickySurfaceTag);
if (character.IsOnStickySurface)
{
CharacterControlUtilities.SlerpCharacterUpTowardsDirection(ref characterRotation, deltaTime, characterBody.GroundHit.Normal, character.UpOrientationAdaptationSharpness
}
else
{
CharacterControlUtilities.SlerpCharacterUpTowardsDirection(ref characterRotation, deltaTime, math.normalizesafe(-customGravity.Gravity), character.UpOrientationAdaptationSharpn
}
}
public void GetCameraParameters(in PlatformerCharacterComponent character, out Entity cameraTarget, out bool calculateUpFromGravity)
{
cameraTarget = character.CrouchingCameraTargetEntity;
calculateUpFromGravity = !character.IsOnStickySurface;
}
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 PlatformerCharacterControl characterControl = ref aspect.CharacterControl.ValueRW;
ref PlatformerCharacterStateMachine stateMachine = ref aspect.StateMachine.ValueRW;
if (characterControl.CrouchPressed)
{
if (aspect.CanStandUp(ref context, ref baseContext))
{
if (characterBody.IsGrounded)
{
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;
}
}
}
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.AirMove, ref context, ref baseContext, in aspect);
return true;
}
return aspect.DetectGlobalTransitions(ref context, ref baseContext);
}
}
Platformer/Assets/Scripts/Character/States/DashingState.cs
using Unity.Entities;
using Unity.CharacterController;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Transforms;
public struct DashingState : IPlatformerCharacterState
{
private float _dashStartTime;
private float3 _dashDirection;
public void OnStateEnter(CharacterState previousState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect
{
float elapsedTime = (float)baseContext.Time.ElapsedTime;
ref KinematicCharacterProperties characterProperties = ref aspect.CharacterAspect.CharacterProperties.ValueRW;
ref PlatformerCharacterControl characterControl = ref aspect.CharacterControl.ValueRW;
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref quaternion characterRotation = ref aspect.CharacterAspect.LocalTransform.ValueRW.Rotation;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
aspect.SetCapsuleGeometry(character.StandingGeometry.ToCapsuleGeometry());
_dashStartTime = elapsedTime;
characterProperties.EvaluateGrounding = false;
float3 moveVectorOnPlane = math.normalizesafe(MathUtilities.ProjectOnPlane(characterControl.MoveVector, characterBody.GroundingUp)) * math.length(characterControl.MoveVector
if (math.lengthsq(moveVectorOnPlane) > 0f)
{
_dashDirection = math.normalizesafe(moveVectorOnPlane);
}
else
{
_dashDirection = MathUtilities.GetForwardFromRotation(characterRotation);
}
}
public void OnStateExit(CharacterState nextState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect
{
ref KinematicCharacterProperties characterProperties = ref aspect.CharacterAspect.CharacterProperties.ValueRW;
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
characterProperties.EvaluateGrounding = true;
characterBody.RelativeVelocity = float3.zero;
}
public void OnStatePhysicsUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
aspect.HandlePhysicsUpdatePhase1(ref context, ref baseContext, true, false);
characterBody.RelativeVelocity = _dashDirection * character.DashSpeed;
aspect.HandlePhysicsUpdatePhase2(ref context, ref baseContext, false, false, true, false, true);
DetectTransitions(ref context, ref baseContext, in aspect);
}
public void OnStateVariableUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
}
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)
{
float elapsedTime = (float)baseContext.Time.ElapsedTime;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref PlatformerCharacterStateMachine stateMachine = ref aspect.StateMachine.ValueRW;

if (elapsedTime > _dashStartTime + character.DashDuration)


{
if (characterBody.IsGrounded)
{
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 false;
}
}
Platformer/Assets/Scripts/Character/States/FlyingNoCollisionsState.cs
using Unity.Entities;
using Unity.CharacterController;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Transforms;
public struct FlyingNoCollisionsState : IPlatformerCharacterState
{
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 PhysicsCollider characterCollider = ref aspect.CharacterAspect.PhysicsCollider.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
aspect.SetCapsuleGeometry(character.StandingGeometry.ToCapsuleGeometry());

KinematicCharacterUtilities.SetCollisionDetectionActive(false, ref characterProperties, ref characterCollider);


characterBody.IsGrounded = false;
}
public void OnStateExit(CharacterState nextState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect
{
ref KinematicCharacterProperties characterProperties = ref aspect.CharacterAspect.CharacterProperties.ValueRW;
ref PhysicsCollider characterCollider = ref aspect.CharacterAspect.PhysicsCollider.ValueRW;
KinematicCharacterUtilities.SetCollisionDetectionActive(true, ref characterProperties, ref characterCollider);
}
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;
aspect.CharacterAspect.Update_Initialize(in aspect, ref context, ref baseContext, ref characterBody, deltaTime);

// 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;

if (math.lengthsq(characterControl.MoveVector) > 0f)


{
CharacterControlUtilities.SlerpRotationTowardsDirectionAroundUp(ref characterRotation, deltaTime, math.normalizesafe(characterControl.MoveVector), MathUtilities.GetUpFromRotati
}
character.IsOnStickySurface = PhysicsUtilities.HasPhysicsTag(in baseContext.PhysicsWorld, characterBody.GroundHit.RigidBodyIndex, character.StickySurfaceTag);
if (character.IsOnStickySurface)
{
CharacterControlUtilities.SlerpCharacterUpTowardsDirection(ref characterRotation, deltaTime, characterBody.GroundHit.Normal, character.UpOrientationAdaptationSharpness
}
else
{
CharacterControlUtilities.SlerpCharacterUpTowardsDirection(ref characterRotation, deltaTime, math.normalizesafe(-customGravity.Gravity), character.UpOrientationAdaptationSharpn
}
}
public void GetCameraParameters(in PlatformerCharacterComponent character, out Entity cameraTarget, out bool calculateUpFromGravity)
{
cameraTarget = character.DefaultCameraTargetEntity;
calculateUpFromGravity = !character.IsOnStickySurface;
}
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 PlatformerCharacterControl characterControl = ref aspect.CharacterControl.ValueRW;
ref PlatformerCharacterStateMachine stateMachine = ref aspect.StateMachine.ValueRW;
if (characterControl.CrouchPressed)
{
stateMachine.TransitionToState(CharacterState.Crouched, 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.AirMove, ref context, ref baseContext, in aspect);
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;
}
}
return aspect.DetectGlobalTransitions(ref context, ref baseContext);
}
}
Platformer/Assets/Scripts/Character/States/LedgeGrabState.cs
using Unity.Entities;
using Unity.CharacterController;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Transforms;
public struct LedgeGrabState : IPlatformerCharacterState
{
private bool DetectedMustExitLedge;
private float3 ForwardHitNormal;
const float collisionOffset = 0.02f;
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.StandingGeometry.ToCapsuleGeometry());
characterProperties.EvaluateGrounding = false;
characterProperties.DetectMovementCollisions = false;
characterProperties.DecollideFromOverlaps = false;
characterBody.RelativeVelocity = float3.zero;
characterBody.IsGrounded = 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;
if (nextState != CharacterState.LedgeStandingUp)
{
characterProperties.EvaluateGrounding = true;
characterProperties.DetectMovementCollisions = true;
characterProperties.DecollideFromOverlaps = true;

aspect.CharacterAspect.SetOrUpdateParentBody(ref baseContext, ref characterBody, default, default);


}
characterBody.RelativeVelocity = float3.zero;
}
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;
ref quaternion characterRotation = ref aspect.CharacterAspect.LocalTransform.ValueRW.Rotation;
aspect.HandlePhysicsUpdatePhase1(ref context, ref baseContext, true, false);
DetectedMustExitLedge = false;
characterBody.RelativeVelocity = float3.zero;
LedgeDetection(
ref context,
ref baseContext,
in aspect,
characterPosition,
characterRotation,
out bool ledgeIsValid,
out ColliderCastHit surfaceHit,
out ColliderCastHit forwardHit,
out float3 characterTranslationAtLedgeSurface,
out bool wouldBeGroundedOnLedgeSurfaceHit,
out float forwardHitDistance,
out bool isObstructedAtSurface,
out bool isObstructedAtCurrentPosition,
out float upOffsetToPlaceLedgeDetectionPointAtLedgeLevel);
if (ledgeIsValid && !isObstructedAtSurface)
{
ForwardHitNormal = forwardHit.SurfaceNormal;
// Stick to wall
float3 characterForward = MathUtilities.GetForwardFromRotation(characterRotation);
characterPosition += characterForward * (forwardHitDistance - collisionOffset);
// Adjust to ledge height
characterPosition += characterBody.GroundingUp * (upOffsetToPlaceLedgeDetectionPointAtLedgeLevel - collisionOffset);
if (math.lengthsq(characterControl.MoveVector) > 0f)
{
// Move input
float3 ledgeDirection = math.normalizesafe(math.cross(surfaceHit.SurfaceNormal, forwardHit.SurfaceNormal));
float3 moveInputOnLedgeDirection = math.projectsafe(characterControl.MoveVector, ledgeDirection);
// Check for move obstructions
float3 targetTranslationAfterMove = characterPosition + (moveInputOnLedgeDirection * character.LedgeMoveSpeed * deltaTime);
LedgeDetection(
ref context,
ref baseContext,
in aspect,
targetTranslationAfterMove,
characterRotation,
out bool afterMoveLedgeIsValid,
out ColliderCastHit afterMoveSurfaceHit,
out ColliderCastHit afterMoveForwardHit,
out float3 afterMoveCharacterTranslationAtLedgeSurface,
out bool afterMoveWouldBeGroundedOnLedgeSurfaceHit,
out float afterMoveForwardHitDistance,
out bool afterMoveIsObstructedAtSurface,
out bool afterMoveIsObstructedAtCurrentPosition,
out float afterMoveUpOffsetToPlaceLedgeDetectionPointAtLedgeLevel);
if (afterMoveLedgeIsValid && !afterMoveIsObstructedAtSurface)
{
characterBody.RelativeVelocity = moveInputOnLedgeDirection * character.LedgeMoveSpeed;
// Apply velocity to position
characterPosition += characterBody.RelativeVelocity * baseContext.Time.DeltaTime;
}
}
aspect.CharacterAspect.SetOrUpdateParentBody(ref baseContext, ref characterBody, forwardHit.Entity, forwardHit.Position);
}
else
{
DetectedMustExitLedge = true;
}
// Detect letting go of ledge
if (characterControl.CrouchPressed || characterControl.DashPressed)
{
character.LedgeGrabBlockCounter = 0.3f;
}
aspect.HandlePhysicsUpdatePhase2(ref context, ref baseContext, false, false, false, false, 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;
// Adjust rotation to face current ledge wall
quaternion targetRotation = quaternion.LookRotationSafe(math.normalizesafe(MathUtilities.ProjectOnPlane(-ForwardHitNormal, characterBody.GroundingUp)), characterBody.GroundingUp
characterRotation = math.slerp(characterRotation, targetRotation, MathUtilities.GetSharpnessInterpolant(character.LedgeRotationSharpness, deltaTime));
}
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 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;
ref PlatformerCharacterStateMachine stateMachine = ref aspect.StateMachine.ValueRW;
if (IsLedgeGrabBlocked(in character) || DetectedMustExitLedge)
{
stateMachine.TransitionToState(CharacterState.AirMove, ref context, ref baseContext, in aspect);
return true;
}
if (characterControl.JumpPressed)
{
LedgeDetection(
ref context,
ref baseContext,
in aspect,
characterPosition,
characterRotation,
out bool ledgeIsValid,
out ColliderCastHit surfaceHit,
out ColliderCastHit forwardHit,
out float3 characterTranslationAtLedgeSurface,
out bool wouldBeGroundedOnLedgeSurfaceHit,
out float forwardHitDistance,
out bool isObstructedAtSurface,
out bool isObstructedAtCurrentPosition,
out float upOffsetToPlaceLedgeDetectionPointAtLedgeLevel);
if (ledgeIsValid && !isObstructedAtSurface && wouldBeGroundedOnLedgeSurfaceHit)
{
stateMachine.LedgeStandingUpState.StandingPoint = surfaceHit.Position;
stateMachine.TransitionToState(CharacterState.LedgeStandingUp, ref context, ref baseContext, in aspect);
return true;
}
}
return aspect.DetectGlobalTransitions(ref context, ref baseContext);
}
public static bool IsLedgeGrabBlocked(in PlatformerCharacterComponent character)
{
return character.LedgeGrabBlockCounter > 0f;
}
public static bool CanGrabLedge(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect, out Entity
{
ledgeEntity = Entity.Null;
ledgeSurfaceHit = default;
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.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;
if (IsLedgeGrabBlocked(in character))
{
return false;
}
LedgeDetection(
ref context,
ref baseContext,
in aspect,
characterPosition,
characterRotation,
out bool ledgeIsValid,
out ledgeSurfaceHit,
out ColliderCastHit forwardHit,
out float3 characterTranslationAtLedgeSurface,
out bool wouldBeGroundedOnLedgeSurfaceHit,
out float forwardHitDistance,
out bool isObstructedAtSurface,
out bool isObstructedAtCurrentPosition,
out float upOffsetToPlaceLedgeDetectionPointAtLedgeLevel);
// Prevent detecting valid grab if going up
if (math.dot(characterBody.RelativeVelocity, ledgeSurfaceHit.SurfaceNormal) > 0f)
{
ledgeIsValid = false;
}
if (ledgeIsValid)
{
ledgeEntity = ledgeSurfaceHit.Entity;
}
return ledgeIsValid && !isObstructedAtSurface;
}
public static void LedgeDetection(
ref PlatformerCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
in PlatformerCharacterAspect aspect,
float3 atCharacterTranslation,
quaternion atCharacterRotation,
out bool ledgeIsValid,
out ColliderCastHit surfaceHit,
out ColliderCastHit forwardHit,
out float3 characterTranslationAtLedgeSurface,
out bool wouldBeGroundedOnLedgeSurfaceHit,
out float forwardHitDistance,
out bool isObstructedAtSurface,
out bool isObstructedAtCurrentPosition,
out float upOffsetToPlaceLedgeDetectionPointAtLedgeLevel)
{
const float ledgeProbingToleranceOffset = 0.04f;
ledgeIsValid = false;
surfaceHit = default;
forwardHit = default;
characterTranslationAtLedgeSurface = default;
wouldBeGroundedOnLedgeSurfaceHit = false;
forwardHitDistance = -1f;
isObstructedAtSurface = false;
isObstructedAtCurrentPosition = false;
upOffsetToPlaceLedgeDetectionPointAtLedgeLevel = -1f;
ref KinematicCharacterBody characterBody = ref aspect.CharacterAspect.CharacterBody.ValueRW;
ref KinematicCharacterProperties characterProperties = ref aspect.CharacterAspect.CharacterProperties.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
float3 currentCharacterForward = MathUtilities.GetForwardFromRotation(atCharacterRotation);
float3 currentCharacterRight = MathUtilities.GetRightFromRotation(atCharacterRotation);
RigidTransform currentCharacterRigidTransform = math.RigidTransform(atCharacterRotation, atCharacterTranslation);
float3 worldSpaceLedgeDetectionPoint = math.transform(currentCharacterRigidTransform, character.LocalLedgeDetectionPoint);
float forwardDepthOfLedgeDetectionPoint = math.length(math.projectsafe(worldSpaceLedgeDetectionPoint - atCharacterTranslation, currentCharacterForward));
// Forward detection against the ledge wall
bool forwardHitDetected = false;
if (aspect.CharacterAspect.CastColliderClosestCollisions(
in aspect,
ref context,
ref baseContext,
atCharacterTranslation,
atCharacterRotation,
currentCharacterForward,
forwardDepthOfLedgeDetectionPoint,
false,
characterProperties.ShouldIgnoreDynamicBodies(),
out forwardHit,
out forwardHitDistance))
{
forwardHitDetected = true;
if (aspect.CharacterAspect.CalculateDistanceClosestCollisions(
in aspect,
ref context,
ref baseContext,
atCharacterTranslation,
atCharacterRotation,
0f,
characterProperties.ShouldIgnoreDynamicBodies(),
out DistanceHit closestOverlapHit))
{
if (closestOverlapHit.Distance <= 0f)
{
isObstructedAtCurrentPosition = true;
}
}
}
// Cancel rest of detection if no forward hit detected
if (!forwardHitDetected)
{
return;
}
// Cancel rest of detection if currently obstructed
if (isObstructedAtCurrentPosition)
{
return;
}
// Raycast downward at detectionPoint to find a surface hit
bool surfaceRaycastHitDetected = false;
float3 startPointOfSurfaceDetectionRaycast = worldSpaceLedgeDetectionPoint + (characterBody.GroundingUp * character.LedgeSurfaceProbingHeight);
float surfaceRaycastLength = character.LedgeSurfaceProbingHeight + ledgeProbingToleranceOffset;
if (aspect.CharacterAspect.RaycastClosestCollisions(
in aspect,
ref context,
ref baseContext,
startPointOfSurfaceDetectionRaycast,
-characterBody.GroundingUp,
surfaceRaycastLength,
characterProperties.ShouldIgnoreDynamicBodies(),
out RaycastHit surfaceRaycastHit,
out float surfaceRaycastHitDistance))
{
if (surfaceRaycastHit.Fraction > 0f)
{
surfaceRaycastHitDetected = true;
}
}
// If no ray hit found, do more raycast tests on the sides
if (!surfaceRaycastHitDetected)
{
float3 rightStartPointOfSurfaceDetectionRaycast = startPointOfSurfaceDetectionRaycast + (currentCharacterRight * character.LedgeSideProbingLength);
if (aspect.CharacterAspect.RaycastClosestCollisions(
in aspect,
ref context,
ref baseContext,
rightStartPointOfSurfaceDetectionRaycast,
-characterBody.GroundingUp,
surfaceRaycastLength,
characterProperties.ShouldIgnoreDynamicBodies(),
out surfaceRaycastHit,
out surfaceRaycastHitDistance))
{
if (surfaceRaycastHit.Fraction > 0f)
{
surfaceRaycastHitDetected = true;
}
}
}
if (!surfaceRaycastHitDetected)
{
float3 leftStartPointOfSurfaceDetectionRaycast = startPointOfSurfaceDetectionRaycast - (currentCharacterRight * character.LedgeSideProbingLength);
if (aspect.CharacterAspect.RaycastClosestCollisions(
in aspect,
ref context,
ref baseContext,
leftStartPointOfSurfaceDetectionRaycast,
-characterBody.GroundingUp,
surfaceRaycastLength,
characterProperties.ShouldIgnoreDynamicBodies(),
out surfaceRaycastHit,
out surfaceRaycastHitDistance))
{
if (surfaceRaycastHit.Fraction > 0f)
{
surfaceRaycastHitDetected = true;
}
}
}
// Cancel rest of detection if no surface raycast hit detected
if (!surfaceRaycastHitDetected)
{
return;
}
// Cancel rest of detection if surface hit is dynamic
if (PhysicsUtilities.IsBodyDynamic(baseContext.PhysicsWorld, surfaceRaycastHit.RigidBodyIndex))
{
return;
}
ledgeIsValid = true;
upOffsetToPlaceLedgeDetectionPointAtLedgeLevel = surfaceRaycastLength - surfaceRaycastHitDistance;
// Note: this assumes that our transform pivot is at the base of our capsule collider
float3 startPointOfSurfaceObstructionDetectionCast = surfaceRaycastHit.Position + (characterBody.GroundingUp * character.LedgeSurfaceObstructionProbingHeight);
// Check obstructions at surface hit point
if (aspect.CharacterAspect.CastColliderClosestCollisions(
in aspect,
ref context,
ref baseContext,
startPointOfSurfaceObstructionDetectionCast,
atCharacterRotation,
-characterBody.GroundingUp,
character.LedgeSurfaceObstructionProbingHeight + ledgeProbingToleranceOffset,
false,
characterProperties.ShouldIgnoreDynamicBodies(),
out surfaceHit,
out float closestSurfaceObstructionHitDistance))
{
if (surfaceHit.Fraction <= 0f)
{
isObstructedAtSurface = true;
}
}
// Cancel rest of detection if obstruction at surface
if (isObstructedAtSurface)
{
return;
}
// Cancel rest of detection if found no surface hit
if (surfaceHit.Entity == Entity.Null)
{
return;
}
characterTranslationAtLedgeSurface = startPointOfSurfaceObstructionDetectionCast + (-characterBody.GroundingUp * closestSurfaceObstructionHitDistance);
wouldBeGroundedOnLedgeSurfaceHit = aspect.IsGroundedOnHit(ref context, ref baseContext, new BasicHit(surfaceHit), 0);
}
}
Platformer/Assets/Scripts/Character/States/LedgeStandingUpState.cs
using Unity.Entities;
using Unity.CharacterController;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Transforms;
public struct LedgeStandingUpState : IPlatformerCharacterState
{
public float3 StandingPoint;
private bool ShouldExitState;
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.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;

aspect.CharacterAspect.SetOrUpdateParentBody(ref baseContext, ref characterBody, default, default);


}
public void OnStatePhysicsUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
ref float3 characterPosition = ref aspect.CharacterAspect.LocalTransform.ValueRW.Position;
aspect.HandlePhysicsUpdatePhase1(ref context, ref baseContext, true, false);
// TODO: root motion standing up
characterPosition = StandingPoint;
ShouldExitState = true;
aspect.HandlePhysicsUpdatePhase2(ref context, ref baseContext, false, false, false, false, true);
DetectTransitions(ref context, ref baseContext, in aspect);
}
public void OnStateVariableUpdate(ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect aspect)
{
}
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 PlatformerCharacterStateMachine stateMachine = ref aspect.StateMachine.ValueRW;
if (ShouldExitState)
{
if (characterBody.IsGrounded)
{
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);
}
}
Platformer/Assets/Scripts/Character/States/RollingState.cs
using Unity.Entities;
using Unity.CharacterController;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Rendering;
using Unity.Transforms;

public struct RollingState : IPlatformerCharacterState


{
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.RollingGeometry.ToCapsuleGeometry());
characterProperties.EvaluateGrounding = false;
characterBody.IsGrounded = false;
PlatformerUtilities.SetEntityHierarchyEnabledParallel(true, character.RollballMeshEntity, context.EndFrameECB, context.ChunkIndex, context.LinkedEntityGroupLookup);
}
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.EvaluateGrounding = true;
PlatformerUtilities.SetEntityHierarchyEnabledParallel(false, character.RollballMeshEntity, context.EndFrameECB, context.ChunkIndex, context.LinkedEntityGroupLookup);
}
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, false);


CharacterControlUtilities.AccelerateVelocity(ref characterBody.RelativeVelocity, characterControl.MoveVector * character.RollingAcceleration, deltaTime);
CharacterControlUtilities.AccelerateVelocity(ref characterBody.RelativeVelocity, customGravity.Gravity, deltaTime);
aspect.HandlePhysicsUpdatePhase2(ref context, ref baseContext, false, false, true, false, 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 quaternion characterRotation = ref aspect.CharacterAspect.LocalTransform.ValueRW.Rotation;
CustomGravity customGravity = aspect.CustomGravity.ValueRO;
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 PlatformerCharacterControl characterControl = ref aspect.CharacterControl.ValueRW;
ref PlatformerCharacterStateMachine stateMachine = ref aspect.StateMachine.ValueRW;
if (!characterControl.RollHeld && aspect.CanStandUp(ref context, ref baseContext))
{
stateMachine.TransitionToState(CharacterState.AirMove, ref context, ref baseContext, in aspect);
return true;
}

return aspect.DetectGlobalTransitions(ref context, ref baseContext);


}
}
Platformer/Assets/Scripts/Character/States/RopeSwingState.cs
using Unity.Entities;
using Unity.CharacterController;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Transforms;
public struct RopeSwingState : IPlatformerCharacterState
{
public float3 AnchorPoint;
public void OnStateEnter(CharacterState previousState, ref PlatformerCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in PlatformerCharacterAspect
{
Entity entity = aspect.CharacterAspect.Entity;
ref KinematicCharacterProperties characterProperties = ref aspect.CharacterAspect.CharacterProperties.ValueRW;
ref PlatformerCharacterComponent character = ref aspect.Character.ValueRW;
aspect.SetCapsuleGeometry(character.StandingGeometry.ToCapsuleGeometry());

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;

public class CharacterFrictionModifierAuthoring : MonoBehaviour


{
public float Friction = 1f;

class Baker : Baker<CharacterFrictionModifierAuthoring>


{
public override void Bake(CharacterFrictionModifierAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.None);
AddComponent(entity, new CharacterFrictionModifier { Friction = authoring.Friction });
}
}
}
Platformer/Assets/Scripts/Misc/CharacterRope.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;

[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);

// Handle rope positioning


{
RigidTransform characterTransform = new RigidTransform(characterLocalToWorld.Rotation, characterLocalToWorld.Position);
float3 anchorPointOnCharacter = math.transform(characterTransform, platformerCharacter.LocalRopeAnchorPoint);
float3 ropeVector = characterStateMachine.RopeSwingState.AnchorPoint - anchorPointOnCharacter;
float ropeLength = math.length(ropeVector);
float3 ropeMidPoint = anchorPointOnCharacter + (ropeVector * 0.5f);
SystemAPI.SetComponent(entity, new LocalToWorld { Value = math.mul(new float4x4(MathUtilities.CreateRotationWithUpPriority(math.normalizesafe(ropeVector), math
}
// Destroy self when not in rope swing state anymore
if (characterStateMachine.CurrentState != CharacterState.RopeSwing)
{
ecb.DestroyEntity(entity);
}
}
}
}
}
Platformer/Assets/Scripts/Misc/CustomGravity.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[Serializable]
public struct CustomGravity : IComponentData
{
public float GravityMultiplier;

[HideInInspector] public float3 Gravity;


[HideInInspector] public bool TouchedByNonGlobalGravity;
[HideInInspector] public Entity CurrentZoneEntity;
[HideInInspector] public Entity LastZoneEntity;
}
Platformer/Assets/Scripts/Misc/CustomGravityAuthoring.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;

public class CustomGravityAuthoring : MonoBehaviour


{
public float GravityMultiplier = 1f;
class Baker : Baker<CustomGravityAuthoring>
{
public override void Bake(CustomGravityAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new CustomGravity { GravityMultiplier = authoring.GravityMultiplier });
}
}
}
Platformer/Assets/Scripts/Misc/FixedInputEvent.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public struct FixedInputEvent


{
private byte _wasEverSet;
private uint _lastSetTick;
public void Set(uint tick)
{
_lastSetTick = tick;
_wasEverSet = 1;
}

public bool IsSet(uint tick)


{
if (_wasEverSet == 1)
{
return tick == _lastSetTick;
}

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;

[UpdateInGroup(typeof(FixedStepSimulationSystemGroup), OrderLast = true)]


[BurstCompile]
public partial struct FixedTickSystem : ISystem
{
public struct Singleton : IComponentData
{
public uint Tick;
}

public void OnCreate(ref SystemState state)


{ }

public void OnDestroy(ref SystemState state)


{ }

[BurstCompile]
public void OnUpdate(ref SystemState state)
{
if (!SystemAPI.HasSingleton<Singleton>())
{
Entity singletonEntity = state.EntityManager.CreateEntity();
state.EntityManager.AddComponentData(singletonEntity, new Singleton());
}

ref Singleton singleton = ref SystemAPI.GetSingletonRW<Singleton>().ValueRW;


singleton.Tick++;
}
}
Platformer/Assets/Scripts/Misc/FramerateMenuManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Rendering;
using System;

public struct FramerateCalculator


{
private int _framesCount;
private float _framesDeltaSum;
private float _minDeltaTimeForAvg;
private float _maxDeltaTimeForAvg;
private string[] _framerateStrings;

public void Initialize()


{
_minDeltaTimeForAvg = Mathf.Infinity;
_maxDeltaTimeForAvg = Mathf.NegativeInfinity;
_framerateStrings = new string[1001];
for (int i = 0; i < _framerateStrings.Length; i++)
{
if (i >= _framerateStrings.Length - 1)
{
_framerateStrings[i] = i.ToString() + "+" + " (<" + (1000f / (float)i).ToString("F") + "ms)";
}
else
{
_framerateStrings[i] = i.ToString() + " (" + (1000f / (float)i).ToString("F") + "ms)";
}
}
}

public void Update()


{
// Regular frames
_framesCount++;
_framesDeltaSum += Time.deltaTime;

// Max and min


if (Time.deltaTime < _minDeltaTimeForAvg)
{
_minDeltaTimeForAvg = Time.deltaTime;
}

if (Time.deltaTime > _maxDeltaTimeForAvg)


{
_maxDeltaTimeForAvg = Time.deltaTime;
}
}
private string GetNumberString(int fps)
{
if (fps < _framerateStrings.Length - 1 && fps >= 0)
{
return _framerateStrings[fps];
}
else
{
return _framerateStrings[_framerateStrings.Length - 1];
}
}
public void PollFramerate(out string avg, out string worst, out string best)
{
avg = GetNumberString(Mathf.RoundToInt(1f / (_framesDeltaSum / _framesCount)));
worst = GetNumberString(Mathf.RoundToInt(1f / _maxDeltaTimeForAvg));
best = GetNumberString(Mathf.RoundToInt(1f / _minDeltaTimeForAvg));

_framesDeltaSum = 0f;
_framesCount = 0;
_minDeltaTimeForAvg = Mathf.Infinity;
_maxDeltaTimeForAvg = Mathf.NegativeInfinity;
}
}

public class FramerateMenuManager : MonoBehaviour


{
[Header("Components")] public Canvas MainCanvas;
public Text AvgFPS;
public Text WorstFPS;
public Text BestFPS;

[Header("Misc")] public float FPSPollRate = 1f;


private FramerateCalculator _framerateCalculator = default;
private float _lastTimePolledFPS = float.MinValue;
private bool _hasVSync = false;
void Start()
{
_framerateCalculator.Initialize();
UpdateRenderSettings();
}

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;

public class FramerateSetter : MonoBehaviour


{
public int Framerate = -1;
public float FixedFramerate = 60;

void Start()
{
Application.targetFrameRate = Framerate;

FixedStepSimulationSystemGroup fixedSystem = World.DefaultGameObjectInjectionWorld.GetOrCreateSystemManaged<FixedStepSimulationSystemGroup>();


fixedSystem.Timestep = 1f / FixedFramerate;
}
}
Platformer/Assets/Scripts/Misc/GlobalGravityZone.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;

[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;

class Baker : Baker<GlobalGravityZoneAuthoring>


{
public override void Bake(GlobalGravityZoneAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new GlobalGravityZone { Gravity = authoring.Gravity });
}
}
}
Platformer/Assets/Scripts/Misc/GravityZonesSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Stateful;
using Unity.Physics.Systems;
using Unity.Transforms;
using Unity.CharacterController;

[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);

ResetGravitiesJob resetGravitiesJob = new ResetGravitiesJob();


resetGravitiesJob.Schedule();

SphericalGravityJob sphericalGravityJob = new SphericalGravityJob


{
CustomGravityFromEntity = SystemAPI.GetComponentLookup<CustomGravity>(false),
LocalToWorldFromEntity = SystemAPI.GetComponentLookup<LocalToWorld>(true),
};
sphericalGravityJob.Schedule();

if (SystemAPI.TryGetSingleton(out GlobalGravityZone globalGravityZone))


{
GlobalGravityJob globalGravityJob = new GlobalGravityJob
{
GlobalGravityZone = globalGravityZone,
};
globalGravityJob.Schedule();
}

ApplyGravityJob applyGravityJob = new ApplyGravityJob


{
DeltaTime = SystemAPI.Time.DeltaTime,
};
applyGravityJob.Schedule();
}
[BurstCompile]
public partial struct ResetGravitiesJob : IJobEntity
{
void Execute(Entity entity, ref CustomGravity customGravity)
{
customGravity.LastZoneEntity = customGravity.CurrentZoneEntity;
customGravity.TouchedByNonGlobalGravity = false;
}
}

[BurstCompile]
public unsafe partial struct SphericalGravityJob : IJobEntity
{
public ComponentLookup<CustomGravity> CustomGravityFromEntity;
[ReadOnly]
public ComponentLookup<LocalToWorld> LocalToWorldFromEntity;

void Execute(Entity entity, in SphericalGravityZone sphericalGravityZone, in PhysicsCollider physicsCollider, in DynamicBuffer<StatefulTriggerEvent> triggerEventsBuffer)


{
if (triggerEventsBuffer.Length > 0)
{
SphereCollider* sphereCollider = ((SphereCollider*)physicsCollider.ColliderPtr);
SphereGeometry sphereGeometry = sphereCollider->Geometry;

for (int i = 0; i < triggerEventsBuffer.Length; i++)


{
StatefulTriggerEvent triggerEvent = triggerEventsBuffer[i];
if (triggerEvent.State == StatefulEventState.Stay)
{
Entity otherEntity = triggerEvent.GetOtherEntity(entity);

float3 fromOtherToSelfVector = LocalToWorldFromEntity[entity].Position - LocalToWorldFromEntity[otherEntity].Position;


float distanceRatio = math.clamp(math.length(fromOtherToSelfVector) / sphereGeometry.Radius, 0.01f, 0.99f);
float3 gravityToApply = ((1f - distanceRatio) * (math.normalizesafe(fromOtherToSelfVector) * sphericalGravityZone.GravityStrengthAtCenter));
if (CustomGravityFromEntity.HasComponent(otherEntity))
{
CustomGravity customGravity = CustomGravityFromEntity[otherEntity];
customGravity.Gravity = gravityToApply * customGravity.GravityMultiplier;
customGravity.TouchedByNonGlobalGravity = true;
customGravity.CurrentZoneEntity = entity;
CustomGravityFromEntity[otherEntity] = customGravity;
}
}
}
}
}
}

[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;

public class JumpPadAuthoring : MonoBehaviour


{
public JumpPad JumpPad;
class Baker : Baker<JumpPadAuthoring>
{
public override void Bake(JumpPadAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, authoring.JumpPad);
}
}
}
Platformer/Assets/Scripts/Misc/JumpPadSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Stateful;
using Unity.Physics.Systems;
using Unity.Transforms;
using Unity.CharacterController;

[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);

// If a character has entered the trigger, add jumppad power to it


if (triggerEvent.State == StatefulEventState.Enter && KinematicCharacterBodyLookup.TryGetComponent(otherEntity, out KinematicCharacterBody characterBody))
{
float3 jumpVelocity = MathUtilities.GetForwardFromRotation(localTransform.Rotation) * jumpPad.JumpPower;
characterBody.RelativeVelocity = jumpVelocity;
// Unground the character
if (characterBody.IsGrounded && math.dot(math.normalizesafe(jumpVelocity), characterBody.GroundHit.Normal) > jumpPad.UngroundingDotThreshold)
{
characterBody.IsGrounded = false;
}

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;

public class SceneInitializationAuthoring : MonoBehaviour


{
public GameObject CharacterSpawnPointEntity;
public GameObject CharacterPrefabEntity;
public GameObject CameraPrefabEntity;
public GameObject PlayerPrefabEntity;
public class Baker : Baker<SceneInitializationAuthoring>
{
public override void Bake(SceneInitializationAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new SceneInitialization
{
CharacterSpawnPointEntity = GetEntity(authoring.CharacterSpawnPointEntity, TransformUsageFlags.Dynamic),
CharacterPrefabEntity = GetEntity(authoring.CharacterPrefabEntity, TransformUsageFlags.Dynamic),
CameraPrefabEntity = GetEntity(authoring.CameraPrefabEntity, TransformUsageFlags.Dynamic),
PlayerPrefabEntity = GetEntity(authoring.PlayerPrefabEntity, TransformUsageFlags.Dynamic),
});
}
}
}
Platformer/Assets/Scripts/Misc/SceneInitializationSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics.Authoring;
using Unity.Physics.GraphicsIntegration;
using Unity.Physics.Systems;
using Unity.Transforms;
using UnityEngine;
using UnityEngine.SocialPlatforms;
[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 character at spawn point


Entity characterEntity = state.EntityManager.Instantiate(sceneInitializer.CharacterPrefabEntity);
LocalTransform spawnTransform = SystemAPI.GetComponent<LocalTransform>(sceneInitializer.CharacterSpawnPointEntity);
SystemAPI.SetComponent(characterEntity, LocalTransform.FromPositionRotation(spawnTransform.Position, spawnTransform.Rotation));
// Spawn camera
Entity cameraEntity = state.EntityManager.Instantiate(sceneInitializer.CameraPrefabEntity);
state.EntityManager.AddComponentData(cameraEntity, new MainEntityCamera());

// Assign camera & character to player


PlatformerPlayer player = SystemAPI.GetComponent<PlatformerPlayer>(playerEntity);
player.ControlledCharacter = characterEntity;
player.ControlledCamera = cameraEntity;
SystemAPI.SetComponent(playerEntity, player);

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;

public class SelfDestructAfterTimeAuthoring : MonoBehaviour


{
public float LifeTime = 1f;

public class Baker : Baker<SelfDestructAfterTimeAuthoring>


{
public override void Bake(SelfDestructAfterTimeAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.None);
AddComponent(entity, new SelfDestructAfterTime
{
LifeTime = authoring.LifeTime,
});
}
}
}
Platformer/Assets/Scripts/Misc/SelfDestructAfterTimeSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;

[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;

void Execute(Entity entity, ref SelfDestructAfterTime selfDestructAfterTime)


{
selfDestructAfterTime.TimeSinceAlive += DeltaTime;
if (selfDestructAfterTime.TimeSinceAlive > selfDestructAfterTime.LifeTime)
{
ECB.DestroyEntity(entity);
}
}
}
}
Platformer/Assets/Scripts/Misc/SphericalGravityZone.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;

[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;

class Baker : Baker<SphericalGravityZoneAuthoring>


{
public override void Bake(SphericalGravityZoneAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new SphericalGravityZone { GravityStrengthAtCenter = authoring.GravityStrengthAtCenter });
}
}
}
Platformer/Assets/Scripts/Misc/Teleporter.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;

[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;

public class Baker : Baker<TeleporterAuthoring>


{
public override void Bake(TeleporterAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new Teleporter { DestinationEntity = GetEntity(authoring.Destination, TransformUsageFlags.Dynamic) });
}
}
}
Platformer/Assets/Scripts/Misc/TeleporterSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using Unity.Physics;
using Unity.Physics.Systems;
using Unity.CharacterController;
using Unity.Physics.Stateful;
[UpdateInGroup(typeof(AfterPhysicsSystemGroup))]
[BurstCompile]
public partial struct TeleporterSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{ }

[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;

void Execute(Entity entity, in Teleporter teleporter, in DynamicBuffer<StatefulTriggerEvent> triggerEventsBuffer)


{
// Only teleport if there is a destination
if (teleporter.DestinationEntity != Entity.Null)
{
for (int i = 0; i < triggerEventsBuffer.Length; i++)
{
StatefulTriggerEvent triggerEvent = triggerEventsBuffer[i];
Entity otherEntity = triggerEvent.GetOtherEntity(entity);

// 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;
}

public AuthoringData Data;


public float3 OriginalPosition;
public quaternion OriginalRotation;
}
Platformer/Assets/Scripts/Misc/TestMovingPlatformAuthoring.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[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,
});
}
}
}
Platformer/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);
}
}
}
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;

class Baker : Baker<WindZoneAuthoring>


{
public override void Bake(WindZoneAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new WindZone { WindForce = authoring.WindForce });
}
}
}
Platformer/Assets/Scripts/Misc/WindZoneSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Stateful;
using Unity.Physics.Systems;
using Unity.Transforms;
using Unity.CharacterController;
[UpdateInGroup(typeof(AfterPhysicsSystemGroup))]
[UpdateAfter(typeof(KinematicCharacterPhysicsUpdateGroup))]
[BurstCompile]
public partial struct WindZoneSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{ }
[BurstCompile]
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
WindZoneJob job = new WindZoneJob
{
DeltaTime = SystemAPI.Time.DeltaTime,
CharacterBodyLookup = SystemAPI.GetComponentLookup<KinematicCharacterBody>(false),
CharacterStateMachineLookup = SystemAPI.GetComponentLookup<PlatformerCharacterStateMachine>(true),
PhysicsVelocityLookup = SystemAPI.GetComponentLookup<PhysicsVelocity>(false),
PhysicsMassLookup = SystemAPI.GetComponentLookup<PhysicsMass>(true),
};
job.Schedule();
}
[BurstCompile]
public partial struct WindZoneJob : IJobEntity
{
public float DeltaTime;
public ComponentLookup<KinematicCharacterBody> CharacterBodyLookup;
[ReadOnly]
public ComponentLookup<PlatformerCharacterStateMachine> CharacterStateMachineLookup;
public ComponentLookup<PhysicsVelocity> PhysicsVelocityLookup;
[ReadOnly]
public ComponentLookup<PhysicsMass> PhysicsMassLookup;
void Execute(Entity entity, in WindZone windZone, in DynamicBuffer<StatefulTriggerEvent> triggerEventsBuffer)
{
for (int i = 0; i < triggerEventsBuffer.Length; i++)
{
StatefulTriggerEvent triggerEvent = triggerEventsBuffer[i];
Entity otherEntity = triggerEvent.GetOtherEntity(entity);
if (triggerEvent.State == StatefulEventState.Stay)
{
// Characters
if (CharacterBodyLookup.TryGetComponent(otherEntity, out KinematicCharacterBody characterBody) &&
CharacterStateMachineLookup.TryGetComponent(otherEntity, out PlatformerCharacterStateMachine characterStateMachine))
{
if (PlatformerCharacterAspect.CanBeAffectedByWindZone(characterStateMachine.CurrentState))
{
characterBody.RelativeVelocity += windZone.WindForce * DeltaTime;
CharacterBodyLookup[otherEntity] = characterBody;
}
}
// Dynamic physics bodies
if (PhysicsVelocityLookup.TryGetComponent(otherEntity, out PhysicsVelocity physicsVelocity) &&
PhysicsMassLookup.TryGetComponent(otherEntity, out PhysicsMass physicsMass))
{
if (physicsMass.InverseMass > 0f)
{
physicsVelocity.Linear += windZone.WindForce * DeltaTime;
PhysicsVelocityLookup[otherEntity] = physicsVelocity;
}
}
}
}
}
}
}
Platformer/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")]
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;

public RigidTransform worldFromA => LocalBody == null


? RigidTransform.identity
: Math.DecomposeRigidBodyTransform(LocalBody.transform.localToWorldMatrix);

public RigidTransform worldFromB => ConnectedBody == null


? RigidTransform.identity
: Math.DecomposeRigidBodyTransform(ConnectedBody.transform.localToWorldMatrix);

public Entity EntityA { get; set; }

public Entity EntityB { get; set; }

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);

public override int GetHashCode() => Value;


}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/EditorInitialization.cs
#if UNITY_EDITOR
using System.Linq;
using UnityEditor;
namespace Unity.Physics.Authoring
{
[InitializeOnLoad]
class EditorInitialization
{
static readonly string k_CustomDefine = "UNITY_PHYSICS_CUSTOM";
static EditorInitialization()
{
var definesStr = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
var defines = definesStr.Split(';').ToList();
var found = defines.Find(define => define.Equals(k_CustomDefine));
if (found == null)
{
defines.Add(k_CustomDefine);
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup,
string.Join(";", defines.ToArray()));
}
}
}
}
#endif
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsCategoryNames.cs
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Physics Category Names", fileName = "Physics Category Names", order = 507)]
public sealed class PhysicsCategoryNames : ScriptableObject, ITagNames
{
PhysicsCategoryNames() {}

IReadOnlyList<string> ITagNames.TagNames => CategoryNames;

public IReadOnlyList<string> CategoryNames => m_CategoryNames;


[SerializeField]
string[] m_CategoryNames = Enumerable.Range(0, 32).Select(i => string.Empty).ToArray();

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);

public override int GetHashCode() => unchecked((int)Value);


}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsMaterialProperties.cs
using System;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
interface IPhysicsMaterialProperties
{
CollisionResponsePolicy CollisionResponse { get; set; }
PhysicsMaterialCoefficient Friction { get; set; }

PhysicsMaterialCoefficient Restitution { get; set; }


PhysicsCategoryTags BelongsTo { get; set; }
PhysicsCategoryTags CollidesWith { get; set; }
// TODO: Enable Mass Factors?
// TODO: Surface Velocity?
CustomPhysicsMaterialTags CustomTags { get; set; }
}

interface IInheritPhysicsMaterialProperties : IPhysicsMaterialProperties


{
PhysicsMaterialTemplate Template { get; set; }
bool OverrideCollisionResponse { get; set; }
bool OverrideFriction { get; set; }
bool OverrideRestitution { get; set; }
bool OverrideBelongsTo { get; set; }
bool OverrideCollidesWith { get; set; }
bool OverrideCustomTags { get; set; }
}

[Serializable]
public struct PhysicsMaterialCoefficient
{
[SoftRange(0f, 1f, TextFieldMax = float.MaxValue)]
public float Value;
public Material.CombinePolicy CombineMode;
}

abstract class OverridableValue<T> where T : struct


{
public bool Override { get => m_Override; set => m_Override = value; }
[SerializeField]
bool m_Override;

public T Value
{
get => m_Value;
set
{
m_Value = value;
Override = true;
}
}
[SerializeField]
T m_Value;

public void OnValidate() => OnValidate(ref m_Value);


protected virtual void OnValidate(ref T 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 PhysicsMaterialTemplate Template


{
get => m_Template;
set => m_Template = m_SupportsTemplate ? value : null;
}
[SerializeField]
[Tooltip("Assign a template to use its values.")]
PhysicsMaterialTemplate m_Template;

static T Get<T>(OverridableValue<T> value, T? templateValue) where T : struct =>


value.Override || templateValue == null ? value.Value : templateValue.Value;

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 };

internal static void OnValidate(ref PhysicsMaterialProperties material, bool supportsTemplate)


{
material.UpgradeVersionIfNecessary();

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();
}

const int k_LatestVersion = 1;

[SerializeField]
int m_SerializedVersion = 0;

void ISerializationCallbackReceiver.OnBeforeSerialize() {}
void ISerializationCallbackReceiver.OnAfterDeserialize() => UpgradeVersionIfNecessary();

internal static bool s_SuppressUpgradeWarnings;

#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


}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsMaterialTemplate.cs
using UnityEngine;

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);

void Reset() => OnValidate();


void OnValidate() => PhysicsMaterialProperties.OnValidate(ref m_Value, false);
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsRenderEntityAuthoring.cs
using UnityEngine;
using Unity.Entities;
using Unity.Physics.GraphicsIntegration;
namespace Unity.Physics.Authoring
{
[AddComponentMenu("Entities/Physics/Physics Render Entity")]
[DisallowMultipleComponent]
public sealed class PhysicsRenderEntityAuthoring : MonoBehaviour
{
[Tooltip("Specifies an Entity in a different branch of the hierarchy that holds the graphical representation of this PhysicsShape.")]
public GameObject RenderEntity;
}
internal class PhysicsRenderEntityBaker : Baker<PhysicsRenderEntityAuthoring>
{
public override void Bake(PhysicsRenderEntityAuthoring authoring)
{
var renderEntity = new PhysicsRenderEntity { Entity = GetEntity(authoring.RenderEntity, TransformUsageFlags.Dynamic) };
var entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, renderEntity);
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/PhysicsBodyAuthoring.cs
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
/// <summary> The physics body authoring. This class cannot be inherited. </summary>
#if UNITY_2021_2_OR_NEWER
[Icon(k_IconPath)]
#endif
[AddComponentMenu("Entities/Physics/Physics Body")]
[DisallowMultipleComponent]
public sealed class PhysicsBodyAuthoring : MonoBehaviour
{
const string k_IconPath = "Packages/com.unity.physics/Unity.Physics.Editor/Editor Default Resources/Icons/d_Rigidbody@64.png";

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;
}

static readonly int[] k_NextAxis = { 1, 2, 0 };


public ShapeType ShapeType => m_ShapeType;
[SerializeField]
ShapeType m_ShapeType = ShapeType.Box;
[SerializeField]
float3 m_PrimitiveCenter;
[SerializeField]
float3 m_PrimitiveSize = new float3(1f, 1f, 1f);
[SerializeField]
EulerAngles m_PrimitiveOrientation = EulerAngles.Default;

[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;
}

public PhysicsCategoryTags BelongsTo


{
get => m_Material.BelongsTo;
set => m_Material.BelongsTo = value;
}
public bool OverrideCollidesWith
{
get => m_Material.OverrideCollidesWith;
set => m_Material.OverrideCollidesWith = value;
}
public PhysicsCategoryTags CollidesWith
{
get => m_Material.CollidesWith;
set => m_Material.CollidesWith = value;
}
public bool OverrideCustomTags
{
get => m_Material.OverrideCustomTags;
set => m_Material.OverrideCustomTags = value;
}
public CustomPhysicsMaterialTags CustomTags { get => m_Material.CustomTags; set => m_Material.CustomTags = value; }
[SerializeField]
PhysicsMaterialProperties m_Material = new PhysicsMaterialProperties(true);

public BoxGeometry GetBoxProperties() => GetBoxProperties(out _);


internal BoxGeometry GetBoxProperties(out EulerAngles orientation)
{
orientation = m_PrimitiveOrientation;
return new BoxGeometry
{
Center = m_PrimitiveCenter,
Size = m_PrimitiveSize,
Orientation = m_PrimitiveOrientation,
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius
};
}
void GetCylindricalProperties(
CylindricalProperties props,
out float3 center, out float height, out float radius, out EulerAngles orientation,
bool rebuildOrientation
)
{
center = m_PrimitiveCenter;
var lookVector = math.mul(m_PrimitiveOrientation, new float3 { [props.Axis] = 1f });
// use previous axis so forward will prefer up
var upVector = math.mul(m_PrimitiveOrientation, new float3 { [k_NextAxis[k_NextAxis[props.Axis]]] = 1f });
orientation = m_PrimitiveOrientation;
if (rebuildOrientation && props.Axis != 2)
orientation.SetValue(quaternion.LookRotation(lookVector, upVector));
radius = props.Radius;
height = props.Height;
}
internal CapsuleGeometryAuthoring GetCapsuleProperties()
{
GetCylindricalProperties(
m_Capsule, out var center, out var height, out var radius, out var orientationEuler, m_ShapeType != ShapeType.Capsule
);
return new CapsuleGeometryAuthoring
{
OrientationEuler = orientationEuler,
Center = center,
Height = height,
Radius = radius
};
}
public CylinderGeometry GetCylinderProperties() => GetCylinderProperties(out _);
internal CylinderGeometry GetCylinderProperties(out EulerAngles orientation)
{
GetCylindricalProperties(
m_Cylinder, out var center, out var height, out var radius, out orientation, m_ShapeType != ShapeType.Cylinder
);
return new CylinderGeometry
{
Center = center,
Height = height,
Radius = radius,
Orientation = orientation,
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius,
SideCount = m_CylinderSideCount
};
}
public SphereGeometry GetSphereProperties(out quaternion orientation)
{
var result = GetSphereProperties(out EulerAngles euler);
orientation = euler;
return result;
}
internal SphereGeometry GetSphereProperties(out EulerAngles orientation)
{
orientation = m_PrimitiveOrientation;
return new SphereGeometry
{
Center = m_PrimitiveCenter,
Radius = m_SphereRadius
};
}
public void GetPlaneProperties(out float3 center, out float2 size, out quaternion orientation)
{
GetPlaneProperties(out center, out size, out EulerAngles euler);
orientation = euler;
}
internal void GetPlaneProperties(out float3 center, out float2 size, out EulerAngles orientation)
{
center = m_PrimitiveCenter;
orientation = m_PrimitiveOrientation;
if (m_ShapeType == ShapeType.Plane)
{
size = m_PrimitiveSize.xz;
return;
}
UpdateCapsuleAxis();
var look = m_Capsule.Axis;
var nextAx = k_NextAxis[look];
var prevAx = k_NextAxis[k_NextAxis[look]];
var ax2 = m_PrimitiveSize[nextAx] > m_PrimitiveSize[prevAx] ? nextAx : prevAx;
size = new float2(m_PrimitiveSize[ax2], m_PrimitiveSize[look]);
var up = k_NextAxis[ax2] == look ? k_NextAxis[look] : k_NextAxis[ax2];
var offset = quaternion.LookRotation(new float3 { [look] = 1f }, new float3 { [up] = 1f });
orientation.SetValue(math.mul(m_PrimitiveOrientation, offset));
}
static readonly HashSet<int> s_BoneIDs = new HashSet<int>();
static readonly HashSet<Transform> s_BonesInHierarchy = new HashSet<Transform>();
static readonly List<Vector3> s_Vertices = new List<Vector3>(65535);
static readonly List<int> s_Indices = new List<int>(65535);
static UnityMesh ReusableBakeMesh =>
s_ReusableBakeMesh ??
(s_ReusableBakeMesh = new UnityMesh { hideFlags = HideFlags.HideAndDontSave });
static UnityMesh s_ReusableBakeMesh;
public void GetConvexHullProperties(NativeList<float3> pointCloud) =>
GetConvexHullProperties(pointCloud, true, default, default, default, default);
internal void GetConvexHullProperties(
NativeList<float3> pointCloud, bool validate,
NativeList<HashableShapeInputs> inputs, NativeList<int> allSkinIndices, NativeList<float> allBlendShapeWeights,
HashSet<UnityMesh> meshAssets
)
{
if (pointCloud.IsCreated)
pointCloud.Clear();
if (inputs.IsCreated)
inputs.Clear();
if (allSkinIndices.IsCreated)
allSkinIndices.Clear();
if (allBlendShapeWeights.IsCreated)
allBlendShapeWeights.Clear();
meshAssets?.Clear();
if (m_CustomMesh != null)
{
if (validate && !m_CustomMesh.IsValidForConversion(gameObject))
return;
AppendMeshPropertiesToNativeBuffers(
transform.localToWorldMatrix, m_CustomMesh, pointCloud, default, validate, inputs, meshAssets
);
}
else
{
using (var scope = new GetActiveChildrenScope<MeshFilter>(this, transform))
{
foreach (var meshFilter in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(meshFilter, validate))
{
AppendMeshPropertiesToNativeBuffers(
meshFilter.transform.localToWorldMatrix, meshFilter.sharedMesh, pointCloud, default, validate, inputs, meshAssets
);
}
}
}

using (var skinnedPoints = new NativeList<float3>(8192, Allocator.Temp))


using (var skinnedInputs = new NativeList<HashableShapeInputs>(8, Allocator.Temp))
{
GetAllSkinnedPointsInHierarchyBelongingToShape(
this, skinnedPoints, validate, skinnedInputs, allSkinIndices, allBlendShapeWeights
);
if (pointCloud.IsCreated)
pointCloud.AddRange(skinnedPoints.AsArray());
if (inputs.IsCreated)
inputs.AddRange(skinnedInputs.AsArray());
}
}
}

internal static void GetAllSkinnedPointsInHierarchyBelongingToShape(


PhysicsShapeAuthoring shape, NativeList<float3> pointCloud, bool validate,
NativeList<HashableShapeInputs> inputs, NativeList<int> allIncludedIndices, NativeList<float> allBlendShapeWeights
)
{
if (pointCloud.IsCreated)
pointCloud.Clear();
if (inputs.IsCreated)
inputs.Clear();
// get all the transforms that belong to this shape
s_BonesInHierarchy.Clear();
using (var scope = new GetActiveChildrenScope<Transform>(shape, shape.transform))
{
foreach (var bone in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(bone))
s_BonesInHierarchy.Add(bone);
}
}
// find all skinned mesh renderers in which this shape's transform might be a bone
using (var scope = new GetActiveChildrenScope<SkinnedMeshRenderer>(shape, shape.transform.root))
{
foreach (var skin in scope.Buffer)
{
var mesh = skin.sharedMesh;
if (
!skin.enabled
|| mesh == null
|| validate && !mesh.IsValidForConversion(shape.gameObject)
|| !scope.IsChildActiveAndBelongsToShape(skin)
)
continue;
// get indices of this shape's transform hierarchy in skinned mesh's bone array
s_BoneIDs.Clear();
var bones = skin.bones;
for (int i = 0, count = bones.Length; i < count; ++i)
{
if (s_BonesInHierarchy.Contains(bones[i]))
s_BoneIDs.Add(i);
}
if (s_BoneIDs.Count == 0)
continue;
// sample the vertices
if (pointCloud.IsCreated)
{
skin.BakeMesh(ReusableBakeMesh);
ReusableBakeMesh.GetVertices(s_Vertices);
}
// add all vertices weighted to at least one bone in this shape's transform hierarchy
var bonesPerVertex = mesh.GetBonesPerVertex(); // Allocator.None
var weights = mesh.GetAllBoneWeights(); // Allocator.None
var vertexIndex = 0;
var weightsOffset = 0;
var shapeFromSkin = math.mul(shape.transform.worldToLocalMatrix, skin.transform.localToWorldMatrix);
var includedIndices = new NativeList<int>(mesh.vertexCount, Allocator.Temp);
foreach (var weightCount in bonesPerVertex)
{
var totalWeight = 0f;
for (var i = 0; i < weightCount; ++i)
{
var weight = weights[weightsOffset + i];
if (s_BoneIDs.Contains(weight.boneIndex))
totalWeight += weight.weight;
}
if (totalWeight > shape.m_MinimumSkinnedVertexWeight)
{
if (pointCloud.IsCreated)
pointCloud.Add(math.mul(shapeFromSkin, new float4(s_Vertices[vertexIndex], 1f)).xyz);
includedIndices.Add(vertexIndex);
}
weightsOffset += weightCount;
++vertexIndex;
}
if (!inputs.IsCreated || !allIncludedIndices.IsCreated || !allBlendShapeWeights.IsCreated)
continue;
var blendShapeWeights = new NativeArray<float>(mesh.blendShapeCount, Allocator.Temp);
for (var i = 0; i < blendShapeWeights.Length; ++i)
blendShapeWeights[i] = skin.GetBlendShapeWeight(i);
var data = HashableShapeInputs.FromSkinnedMesh(
mesh, shapeFromSkin, includedIndices.AsArray(), allIncludedIndices, blendShapeWeights, allBlendShapeWeights
);
inputs.Add(data);
}
}
s_BonesInHierarchy.Clear();
}
public void GetMeshProperties(NativeList<float3> vertices, NativeList<int3> triangles) =>
GetMeshProperties(vertices, triangles, true, default);
internal void GetMeshProperties(
NativeList<float3> vertices, NativeList<int3> triangles, bool validate, NativeList<HashableShapeInputs> inputs, HashSet<UnityMesh> meshAssets = null
)
{
if (vertices.IsCreated)
vertices.Clear();
if (triangles.IsCreated)
triangles.Clear();
if (inputs.IsCreated)
inputs.Clear();
meshAssets?.Clear();
if (m_CustomMesh != null)
{
if (validate && !m_CustomMesh.IsValidForConversion(gameObject))
return;
AppendMeshPropertiesToNativeBuffers(
transform.localToWorldMatrix, m_CustomMesh, vertices, triangles, validate, inputs, meshAssets
);
}
else
{
using (var scope = new GetActiveChildrenScope<MeshFilter>(this, transform))
{
foreach (var meshFilter in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(meshFilter, validate))
{
AppendMeshPropertiesToNativeBuffers(
meshFilter.transform.localToWorldMatrix, meshFilter.sharedMesh, vertices, triangles, validate, inputs, meshAssets
);
}
}
}
}
}

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();

void Sync(ref CylindricalProperties props)


{
props.Height = m_PrimitiveSize[props.Axis];
props.Radius = 0.5f * math.max(
m_PrimitiveSize[k_NextAxis[props.Axis]],
m_PrimitiveSize[k_NextAxis[k_NextAxis[props.Axis]]]
);
}
void SyncCapsuleProperties()
{
if (m_ShapeType == ShapeType.Box || m_ShapeType == ShapeType.Plane)
UpdateCapsuleAxis();
else
m_Capsule.Axis = 2;
Sync(ref m_Capsule);
}
void SyncCylinderProperties()
{
if (m_ShapeType == ShapeType.Box || m_ShapeType == ShapeType.Plane)
UpdateCylinderAxis();
else
m_Cylinder.Axis = 2;
Sync(ref m_Cylinder);
}
void SyncSphereProperties()
{
m_SphereRadius = 0.5f * math.cmax(m_PrimitiveSize);
}
public void SetBox(BoxGeometry geometry)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(geometry.Orientation);
SetBox(geometry, euler);
}
internal void SetBox(BoxGeometry geometry, EulerAngles orientation)
{
m_ShapeType = ShapeType.Box;
m_PrimitiveCenter = geometry.Center;
m_PrimitiveSize = math.max(geometry.Size, new float3());
m_PrimitiveOrientation = orientation;
m_ConvexHullGenerationParameters.BevelRadius = geometry.BevelRadius;
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
internal void SetCapsule(CapsuleGeometryAuthoring geometry)
{
m_ShapeType = ShapeType.Capsule;
m_PrimitiveCenter = geometry.Center;
m_PrimitiveOrientation = geometry.OrientationEuler;
var radius = math.max(0f, geometry.Radius);
var height = math.max(0f, geometry.Height);
m_PrimitiveSize = new float3(radius * 2f, radius * 2f, height);
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetCylinder(CylinderGeometry geometry)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(geometry.Orientation);
SetCylinder(geometry, euler);
}
internal void SetCylinder(CylinderGeometry geometry, EulerAngles orientation)
{
m_ShapeType = ShapeType.Cylinder;
m_PrimitiveCenter = geometry.Center;
m_PrimitiveOrientation = orientation;
geometry.Radius = math.max(0f, geometry.Radius);
geometry.Height = math.max(0f, geometry.Height);
m_PrimitiveSize = new float3(geometry.Radius * 2f, geometry.Radius * 2f, geometry.Height);

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;
}

bool GetMeshes(PhysicsShapeAuthoring shape, out List<UnityEngine.Mesh> meshes, out List<float4x4> childrenToShape)


{
meshes = new List<UnityMesh>();
childrenToShape = new List<float4x4>();
if (shape.CustomMesh != null)
{
meshes.Add(shape.CustomMesh);
childrenToShape.Add(float4x4.identity);
}
// Try to get all the meshes in the children
var meshFilters = GetComponentsInChildren<MeshFilter>();
foreach (var meshFilter in meshFilters)
{
if (meshFilter != null && meshFilter.sharedMesh != null)
{
var shapeAuthoring = GetComponent<PhysicsShapeAuthoring>(meshFilter);
if (shapeAuthoring != null && shapeAuthoring != shape)
{
// Skip this case, since it will be treated independently
continue;
}
meshes.Add(meshFilter.sharedMesh);
// Don't calculate the children to shape if not needed, to avoid approximation that could prevent collider to be shared
if (shape.transform.localToWorldMatrix.Equals(meshFilter.transform.localToWorldMatrix))
childrenToShape.Add(float4x4.identity);
else
{
var transform = math.mul(shape.transform.worldToLocalMatrix, meshFilter.transform.localToWorldMatrix);
childrenToShape.Add(transform);
}
DependsOn(meshes.Last());
}
}
return meshes.Count > 0;
}
UnityEngine.Mesh CombineMeshes(PhysicsShapeAuthoring shape, List<UnityEngine.Mesh> meshes, List<float4x4> childrenToShape)
{
var instances = new List<CombineInstance>();
var numVertices = 0;
for (var i = 0; i < meshes.Count; ++i)
{
var currentMesh = meshes[i];
var currentChildToShape = childrenToShape[i];
if (!currentMesh.IsValidForConversion(shape.gameObject))
{
throw new InvalidOperationException(
$"Mesh '{currentMesh}' assigned on {shape.name} is not readable. Ensure that you have enabled Read/Write on its import settings."
);
}
// Combine submeshes manually
numVertices += meshes[i].vertexCount;
var combinedSubmeshes = new UnityEngine.Mesh();
combinedSubmeshes.vertices = currentMesh.vertices;
var combinedIndices = new List<int>();
for (int indexSubMesh = 0; indexSubMesh < meshes[i].subMeshCount; ++indexSubMesh)
{
combinedIndices.AddRange(currentMesh.GetIndices(indexSubMesh));
}
combinedSubmeshes.SetIndices(combinedIndices, MeshTopology.Triangles, 0);
combinedSubmeshes.RecalculateNormals();
var instance = new CombineInstance
{
mesh = combinedSubmeshes,
transform = currentChildToShape,
};
instances.Add(instance);
}
var mesh = new UnityEngine.Mesh();
mesh.indexFormat = numVertices > UInt16.MaxValue ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
mesh.CombineMeshes(instances.ToArray());
mesh.RecalculateBounds();
return mesh;
}
private ShapeComputationDataBaking GenerateComputationData(PhysicsShapeAuthoring shape, ColliderInstanceBaking colliderInstance, Entity colliderEntity)
{
var res = new ShapeComputationDataBaking
{
Instance = colliderInstance,
Material = ProduceMaterial(shape),
CollisionFilter = ProduceCollisionFilter(shape),
ForceUniqueIdentifier = shape.ForceUnique ? (uint)shape.GetInstanceID() : 0u
};
var transform = shape.transform;
var localToWorld = transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
EulerAngles orientation;
res.ShapeType = shape.ShapeType;
switch (shape.ShapeType)
{
case ShapeType.Box:
{
res.BoxProperties = shape.GetBoxProperties(out orientation)
.BakeToBodySpace(localToWorld, shapeToWorld, orientation);
break;
}
case ShapeType.Capsule:
{
res.CapsuleProperties = shape.GetCapsuleProperties()
.BakeToBodySpace(localToWorld, shapeToWorld)
.ToRuntime();
break;
}
case ShapeType.Sphere:
{
res.SphereProperties = shape.GetSphereProperties(out orientation)
.BakeToBodySpace(localToWorld, shapeToWorld, ref orientation);
break;
}
case ShapeType.Cylinder:
{
res.CylinderProperties = shape.GetCylinderProperties(out orientation)
.BakeToBodySpace(localToWorld, shapeToWorld, orientation);
break;
}
case ShapeType.Plane:
{
shape.GetPlaneProperties(out var center, out var size, out orientation);
PhysicsShapeExtensions.BakeToBodySpace(
center, size, orientation, localToWorld, shapeToWorld,
out res.PlaneVertices.c0, out res.PlaneVertices.c1, out res.PlaneVertices.c2, out res.PlaneVertices.c3
);
break;
}
case ShapeType.ConvexHull:
{
res.ConvexHullProperties.Filter = res.CollisionFilter;
res.ConvexHullProperties.Material = res.Material;
res.ConvexHullProperties.GenerationParameters = shape.ConvexHullGenerationParameters.ToRunTime();
CreateMeshAuthoringData(shape, colliderEntity);
break;
}
case ShapeType.Mesh:
{
res.MeshProperties.Filter = res.CollisionFilter;
res.MeshProperties.Material = res.Material;
CreateMeshAuthoringData(shape, colliderEntity);
break;
}
}
return res;
}
private void CreateMeshAuthoringData(PhysicsShapeAuthoring shape, Entity colliderEntity)
{
if (GetMeshes(shape, out var meshes, out var childrenToShape))
{
// Combine all detected meshes into a single one
var mesh = CombineMeshes(shape, meshes, childrenToShape);
if (!mesh.IsValidForConversion(shape.gameObject))
{
throw new InvalidOperationException(
$"Mesh '{mesh}' assigned on {shape.name} is not readable. Ensure that you have enabled Read/Write on its import settings."
);
}
var bakeFromShape = shape.GetLocalToShapeMatrix();
var meshBakingData = new PhysicsMeshAuthoringData()
{
Convex = shape.ShapeType == ShapeType.ConvexHull,
Mesh = mesh,
BakeFromShape = bakeFromShape,
MeshBounds = mesh.bounds,
ChildToShape = float4x4.identity
};
AddComponent(colliderEntity, meshBakingData);
}
else
{
throw new InvalidOperationException(
$"No {nameof(PhysicsShapeAuthoring.CustomMesh)} or {nameof(MeshFilter.sharedMesh)} assigned on {shape.name}."
);
}
}
public override void Bake(PhysicsShapeAuthoring authoring)
{
var shapeBakingData = new PhysicsColliderAuthoringData();
// First pass
Profiler.BeginSample("Collect Inputs from Authoring Components");
if (ShouldConvertShape(authoring))
{
// We can have multiple Colliders of the same type on the same game object, so instead of adding the components to the baking entity
// we add the components to an additional entity. These new entities will be processed by the baking system
var colliderEntity = CreateAdditionalEntity(TransformUsageFlags.None, true);
shapeBakingData.ShapeComputationalData = GetInputDataFromAuthoringComponent(authoring, colliderEntity);
AddComponent(colliderEntity, shapeBakingData);
// The data will be filled in by the BaseShapeBakingSystem, but we add it here so it gets reverted from the entity if the collider component is deleted
AddComponent(colliderEntity, new PhysicsColliderBakedData()
{
BodyEntity = shapeBakingData.ShapeComputationalData.Instance.BodyEntity,
BodyFromShape = shapeBakingData.ShapeComputationalData.Instance.BodyFromShape,
ChildEntity = shapeBakingData.ShapeComputationalData.Instance.ChildEntity,
// It is a leaf if the Shape Entity equals Body Entity
IsLeafEntityBody = (shapeBakingData.ShapeComputationalData.Instance.ShapeEntity.Equals(shapeBakingData.ShapeComputationalData.Instance.BodyEntity))
});
}
Profiler.EndSample();
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/BallAndSocketJoint.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

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;

public float3 PositionLocal;


public float3 PositionInConnectedEntity;

public virtual void UpdateAuto()


{
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
PositionInConnectedEntity = math.transform(bFromA, PositionLocal);
}
}
}
public abstract class JointBaker<T> : Baker<T> where T : BaseJoint
{
protected PhysicsConstrainedBodyPair GetConstrainedBodyPair(BaseJoint authoring)
{
return new PhysicsConstrainedBodyPair(
GetEntity(TransformUsageFlags.Dynamic),
authoring.ConnectedBody == null ? Entity.Null : GetEntity(authoring.ConnectedBody, TransformUsageFlags.Dynamic),
authoring.EnableCollision
);
}
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 uint GetWorldIndex(Component c)


{
uint worldIndex = 0;
if (c)
{
var physicsBody = GetComponent<PhysicsBodyAuthoring>(c);
if (physicsBody != null)
{
worldIndex = physicsBody.WorldIndex;
}
}
return worldIndex;
}
public uint GetWorldIndexFromBaseJoint(BaseJoint authoring)
{
var physicsBody = GetComponent<PhysicsBodyAuthoring>(authoring);
uint worldIndex = physicsBody.WorldIndex;
if (authoring.ConnectedBody == null)
{
return worldIndex;
}
var connectedBody = GetComponent<PhysicsBodyAuthoring>(authoring.ConnectedBody);
if (connectedBody != null)
{
Assertions.Assert.AreEqual(worldIndex, connectedBody.WorldIndex);
}
return worldIndex;
}
public void CreateJointEntities(uint worldIndex, PhysicsConstrainedBodyPair constrainedBodyPair, NativeArray<PhysicsJoint> joints, NativeList<Entity> newJointEntities)
{
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;
var entity = GetEntity(TransformUsageFlags.Dynamic);
for (var i = 0; i < joints.Length; ++i)
{
var jointEntity = CreateAdditionalEntity(TransformUsageFlags.Dynamic);
AddSharedComponent(jointEntity, new PhysicsWorldIndex(worldIndex));
AddComponent(jointEntity, constrainedBodyPair);
AddComponent(jointEntity, joints[i]);
newJointEntities.Add(jointEntity);
if (GetComponent<ModifyJointLimitsAuthoring>() != null)
{
AddComponent(jointEntity, new JointEntityBaking()
{
Entity = entity
});
AddSharedComponentManaged(jointEntity, new ModifyJointLimits());
}
}
if (multipleJoints)
{
// set companion buffers for new joints
for (var i = 0; i < joints.Length; ++i)
{
var companions = AddBuffer<PhysicsJointCompanion>(newJointEntities[i]);
for (var j = 0; j < joints.Length; ++j)
{
if (i == j)
continue;
companions.Add(new PhysicsJointCompanion {JointEntity = newJointEntities[j]});
}
}
}
}
}
class BallAndSocketJointBaker : JointBaker<BallAndSocketJoint>
{
public override void Bake(BallAndSocketJoint authoring)
{
authoring.UpdateAuto();
var physicsJoint = PhysicsJoint.CreateBallAndSocket(authoring.PositionLocal, authoring.PositionInConnectedEntity);
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/BaseJoint.cs
using Unity.Mathematics;

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;

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;
for (var i = 0; i < joints.Length; ++i)
{
var jointEntity = CreateAdditionalEntity(TransformUsageFlags.Dynamic);
AddSharedComponent(jointEntity, new PhysicsWorldIndex(worldIndex));
AddComponent(jointEntity, constrainedBodyPair);
AddComponent(jointEntity, joints[i]);
newJointEntities.Add(jointEntity);
}
if (multipleJoints)
{
// set companion buffers for new joints
for (var i = 0; i < joints.Length; ++i)
{
var companions = AddBuffer<PhysicsJointCompanion>(newJointEntities[i]);
for (var j = 0; j < joints.Length; ++j)
{
if (i == j)
continue;
companions.Add(new PhysicsJointCompanion {JointEntity = newJointEntities[j]});
}
}
}
}
protected PhysicsConstrainedBodyPair GetConstrainedBodyPair(LimitDOFJoint authoring)
{
return new PhysicsConstrainedBodyPair(
GetEntity(TransformUsageFlags.Dynamic),
authoring.ConnectedBody == null ? Entity.Null : GetEntity(authoring.ConnectedBody, TransformUsageFlags.Dynamic),
authoring.EnableCollision
);
}
public uint GetWorldIndex(Component c)
{
uint worldIndex = 0;
var physicsBody = GetComponent<PhysicsBodyAuthoring>(c);
if (physicsBody != null)
{
worldIndex = physicsBody.WorldIndex;
}
return worldIndex;
}
public override void Bake(LimitDOFJoint authoring)
{
if (!math.any(authoring.LockLinearAxes) && !math.any(authoring.LockAngularAxes))
return;
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
PhysicsJoint physicsJoint = authoring.CreateLimitDOFJoint(bFromA);
var worldIndex = GetWorldIndex(authoring);
CreateJointEntity(
worldIndex,
GetConstrainedBodyPair(authoring),
physicsJoint
);
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Joints/LimitedDistanceJoint.cs
using Unity.Entities;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class LimitedDistanceJoint : BallAndSocketJoint
{
public float MinDistance;
public float MaxDistance;
}
class LimitedDistanceJointBaker : JointBaker<LimitedDistanceJoint>
{
public override void Bake(LimitedDistanceJoint authoring)
{
authoring.UpdateAuto();
var physicsJoint = PhysicsJoint.CreateLimitedDistance(authoring.PositionLocal, authoring.PositionInConnectedEntity, new FloatRange(authoring.MinDistance, authoring.MaxDistance
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/LimitedHingeJoint.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class LimitedHingeJoint : FreeHingeJoint
{
// Editor only settings
[HideInInspector]
public bool EditLimits;

public float3 PerpendicularAxisLocal;


public float3 PerpendicularAxisInConnectedEntity;
public float MinAngle;
public float MaxAngle;

public override void UpdateAuto()


{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
HingeAxisInConnectedEntity = math.mul(bFromA.rot, HingeAxisLocal);
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
}
}
}

class LimitedHingeJointBaker : JointBaker<LimitedHingeJoint>


{
public override void Bake(LimitedHingeJoint authoring)
{
authoring.UpdateAuto();

var physicsJoint = PhysicsJoint.CreateLimitedHinge(


new BodyFrame
{
Axis = math.normalize(authoring.HingeAxisLocal),
PerpendicularAxis = math.normalize(authoring.PerpendicularAxisLocal),
Position = authoring.PositionLocal
},
new BodyFrame
{
Axis = math.normalize(authoring.HingeAxisInConnectedEntity),
PerpendicularAxis = math.normalize(authoring.PerpendicularAxisInConnectedEntity),
Position = authoring.PositionInConnectedEntity
},
math.radians(new FloatRange(authoring.MinAngle, authoring.MaxAngle))
);

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/ModifyJointLimitsAuthoring.cs
using System;
using System.Collections.Generic;
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 UnityEngine;
using LegacyJoint = UnityEngine.Joint;
using FloatRange = Unity.Physics.Math.FloatRange;

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);

public override int GetHashCode() =>


unchecked((AngularRangeScalar.GetHashCode() * 397) ^ LinearRangeScalar.GetHashCode());
}

// an authoring component to add to a GameObject with one or more Joint


public class ModifyJointLimitsAuthoring : MonoBehaviour
{
public ParticleSystem.MinMaxCurve AngularRangeScalar = new ParticleSystem.MinMaxCurve(
1f,
min: new AnimationCurve(
new Keyframe(0f, 0f, 0f, 0f),
new Keyframe(2f, -2f, 0f, 0f),
new Keyframe(4f, 0f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
},
max: new AnimationCurve(
new Keyframe(0f, 1f, 0f, 0f),
new Keyframe(2f, -1f, 0f, 0f),
new Keyframe(4f, 1f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
}
);
public ParticleSystem.MinMaxCurve LinearRangeScalar = new ParticleSystem.MinMaxCurve(
1f,
min: new AnimationCurve(
new Keyframe(0f, 1f, 0f, 0f),
new Keyframe(2f, 0.5f, 0f, 0f),
new Keyframe(4f, 1f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
},
max: new AnimationCurve(
new Keyframe(0f, 0.5f, 0f, 0f),
new Keyframe(2f, 0f, 0f, 0f),
new Keyframe(4f, 0.5f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
}
);
}
[BakingType]
public class ModifyJointLimitsBakingData : IComponentData
{
public ParticleSystem.MinMaxCurve AngularRangeScalar;
public ParticleSystem.MinMaxCurve LinearRangeScalar;
}
class ModifyJointLimitsBaker : Baker<ModifyJointLimitsAuthoring>
{
public override void Bake(ModifyJointLimitsAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponentObject(entity, new ModifyJointLimitsBakingData
{
AngularRangeScalar = authoring.AngularRangeScalar,
LinearRangeScalar = authoring.LinearRangeScalar
});
}
}
// after joints have been converted, find the entities they produced and add ModifyJointLimits to them
[UpdateAfter(typeof(EndJointBakingSystem))]
[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
partial struct ModifyJointLimitsBakingSystem : ISystem
{
private EntityQuery _ModifyJointLimitsBakingDataQuery;
private EntityQuery _JointEntityBakingQuery;
public void OnCreate(ref SystemState state)
{
_ModifyJointLimitsBakingDataQuery = state.GetEntityQuery(new EntityQueryDesc
{
All = new[] {ComponentType.ReadOnly<ModifyJointLimitsBakingData>()},
Options = EntityQueryOptions.IncludeDisabledEntities | EntityQueryOptions.IncludePrefab
});
_JointEntityBakingQuery = state.GetEntityQuery(new EntityQueryDesc
{
All = new[] {ComponentType.ReadOnly<JointEntityBaking>()}
});

_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;

public override void UpdateAuto()


{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
AxisInConnectedEntity = math.mul(bFromA.rot, AxisLocal);
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
}
}
}

class PrismaticJointBaker : JointBaker<PrismaticJoint>


{
public override void Bake(PrismaticJoint authoring)
{
authoring.UpdateAuto();

var physicsJoint = PhysicsJoint.CreatePrismatic(


new BodyFrame
{
Axis = authoring.AxisLocal,
PerpendicularAxis = authoring.PerpendicularAxisLocal,
Position = authoring.PositionLocal
},
new BodyFrame
{
Axis = authoring.AxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
new FloatRange(authoring.MinDistanceOnAxis, authoring.MaxDistanceOnAxis)
);
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/RagdollJoint.cs
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class RagdollJoint : BallAndSocketJoint
{
const int k_LatestVersion = 1;
// Editor only settings
[HideInInspector]
public bool EditAxes;
[HideInInspector]
public bool EditLimits;
[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);
}
}
}
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;

public override void UpdateAuto()


{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
OrientationInConnectedEntity = math.mul(bFromA.rot, OrientationLocal);
}
{
OrientationLocal = math.normalize(OrientationLocal);
OrientationInConnectedEntity = math.normalize(OrientationInConnectedEntity);
}
}
}

class RigidJointBaker : JointBaker<RigidJoint>


{
public override void Bake(RigidJoint authoring)
{
authoring.UpdateAuto();

var physicsJoint = PhysicsJoint.CreateFixed(


new RigidTransform(authoring.OrientationLocal, authoring.PositionLocal),
new RigidTransform(authoring.OrientationInConnectedEntity, authoring.PositionInConnectedEntity)
);

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/Motors/AngularVelocityMotor.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

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

var joint = PhysicsJoint.CreateAngularVelocityMotor(


new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularLocal,
Position = authoring.PivotPosition
},
new BodyFrame
{
Axis = authoring.HingeAxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
math.radians(authoring.TargetSpeed),
authoring.MaxImpulseAppliedByMotor
);
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/LinearVelocityMotor.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
public class LinearVelocityMotor : BaseJoint
{
[Tooltip("An offset from the center of the body with the motor (bodyA), 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 at this speed from the initial position of bodyA, along the Direction of Movement, in m/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 AxisInConnectedEntity;
private float3 PerpendicularAxisInConnectedEntity;
class LinearVelocityMotorBaker : JointBaker<LinearVelocityMotor>
{
public override void Bake(LinearVelocityMotor authoring)
{
float3 axisInB = math.normalize(authoring.DirectionOfMovement);

RigidTransform aFromB = math.mul(math.inverse(authoring.worldFromA), authoring.worldFromB);


float3 axisInA = math.mul(aFromB.rot, axisInB); //motor axis relative to bodyA
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.AnchorPosition); //position of motored body relative to Connected Entity in world space
authoring.AxisInConnectedEntity = axisInB; //motor axis in Connected Entity space

// Always calculate the perpendicular axes


Math.CalculatePerpendicularNormalized(axisInA, out var perpendicularAxisLocal, out _);
authoring.PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, perpendicularAxisLocal); //perp motor axis in Connected Entity space
var joint = PhysicsJoint.CreateLinearVelocityMotor(
new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularAxisLocal,
Position = authoring.AnchorPosition
},
new BodyFrame
{
Axis = authoring.AxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
authoring.TargetSpeed,
authoring.MaxImpulseAppliedByMotor
);
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/PositionMotor.cs
using Unity.Mathematics;
using UnityEngine;

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;

class PositionMotorBaker : JointBaker<PositionMotor>


{
public override void Bake(PositionMotor authoring)
{
float3 axisInB = math.normalize(authoring.DirectionOfMovement);
RigidTransform aFromB = math.mul(math.inverse(authoring.worldFromA), authoring.worldFromB);
float3 axisInA = math.mul(aFromB.rot, axisInB); //motor axis relative to bodyA
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.AnchorPosition); //position of motored body relative to Connected Entity in world space
authoring.AxisInConnectedEntity = axisInB; //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
var joint = PhysicsJoint.CreatePositionMotor(
new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularLocal,
Position = authoring.AnchorPosition
},
new BodyFrame
{
Axis = authoring.AxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
authoring.TargetDistance,
authoring.MaxImpulseAppliedByMotor
);

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

var joint = PhysicsJoint.CreateRotationalMotor(


new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularLocal,
Position = authoring.PivotPosition
},
new BodyFrame
{
Axis = authoring.HingeAxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
math.radians(authoring.TargetAngle),
authoring.MaxImpulseAppliedByMotor
);
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/Utilities/BakeGeometryJobs.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
static partial class PhysicsShapeExtensions
{
static void MakeZAxisPrimaryBasis(ref int3 basisPriority)
{
if (basisPriority[1] == 2)
basisPriority = basisPriority.yxz;
else if (basisPriority[2] == 2)
basisPriority = basisPriority.zxy;
}
#region Box
[BurstCompile]
internal struct BakeBoxJob : IJob
{
public NativeArray<BoxGeometry> Box;

// TODO: make members PascalCase after merging static query fixes


public float4x4 localToWorld;
public float4x4 shapeToWorld;
public EulerAngles orientation;
public static float4x4 GetBakeToShape(float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center,
ref EulerAngles orientation)
{
float4x4 bakeToShape;
float4x4 rotationMatrix = float4x4.identity;
var basisPriority = k_DefaultAxisPriority;
var sheared = localToWorld.HasShear();
if (localToWorld.HasNonUniformScale() || sheared)
{
if (sheared)
{
var transformScale = localToWorld.DecomposeScale();
var basisToWorld =
GetBasisToWorldMatrix(localToWorld, center, orientation, transformScale);
basisPriority = GetBasisAxisPriority(basisToWorld);
}
rotationMatrix = new float4x4(
new float4 { [basisPriority[2]] = 1 },
new float4 { [basisPriority[1]] = 1 },
new float4 { [basisPriority[0]] = 1 },
new float4 { [3] = 1 }
);
}
bakeToShape = GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center,
ref orientation, 1f, basisPriority);
bakeToShape = math.mul(bakeToShape, rotationMatrix);
return bakeToShape;
}
public void Execute()
{
var center = Box[0].Center;
var size = Box[0].Size;
var bevelRadius = Box[0].BevelRadius;
var bakeToShape = GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientation);
bakeToShape = math.mul(bakeToShape, float4x4.Scale(size));
var scale = bakeToShape.DecomposeScale();
size = scale;
Box[0] = new BoxGeometry
{
Center = center,
Orientation = orientation,
Size = size,
BevelRadius = math.clamp(bevelRadius, 0f, 0.5f * math.cmin(size))
};
}
}
#endregion
#region Capsule
[BurstCompile]
internal struct BakeCapsuleJob : IJob
{
public NativeArray<CapsuleGeometryAuthoring> Capsule;
// TODO: make members PascalCase after merging static query fixes
public float4x4 localToWorld;
public float4x4 shapeToWorld;
public static float4x4 GetBakeToShape(float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center,
ref EulerAngles orientation)
{
var basisPriority = k_DefaultAxisPriority;
var sheared = localToWorld.HasShear();
if (localToWorld.HasNonUniformScale() || sheared)
{
if (sheared)
{
var transformScale = localToWorld.DecomposeScale();
var basisToWorld = GetBasisToWorldMatrix(localToWorld, center, orientation, transformScale);
basisPriority = GetBasisAxisPriority(basisToWorld);
}
MakeZAxisPrimaryBasis(ref basisPriority);
}
return GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center, ref orientation, 1f,
basisPriority);
}
public void Execute()
{
var radius = Capsule[0].Radius;
var center = Capsule[0].Center;
var height = Capsule[0].Height;
var orientationEuler = Capsule[0].OrientationEuler;
var bakeToShape = GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientationEuler);
var scale = bakeToShape.DecomposeScale();
radius *= math.cmax(scale.xy);
height = math.max(0, height * scale.z);
Capsule[0] = new CapsuleGeometryAuthoring
{
OrientationEuler = orientationEuler,
Center = center,
Height = height,
Radius = radius
};
}
}
#endregion
#region Cylinder
[BurstCompile]
internal struct BakeCylinderJob : IJob
{
public NativeArray<CylinderGeometry> Cylinder;
// TODO: make members PascalCase after merging static query fixes
public float4x4 localToWorld;
public float4x4 shapeToWorld;
public EulerAngles orientation;
public static float4x4 GetBakeToShape(float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center,
ref EulerAngles orientation)
{
var basisPriority = k_DefaultAxisPriority;
var sheared = localToWorld.HasShear();
if (localToWorld.HasNonUniformScale() || sheared)
{
if (sheared)
{
var transformScale = localToWorld.DecomposeScale();
var basisToWorld = GetBasisToWorldMatrix(localToWorld, center, orientation, transformScale);
basisPriority = GetBasisAxisPriority(basisToWorld);
}
MakeZAxisPrimaryBasis(ref basisPriority);
}
return GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center, ref orientation, 1f,
basisPriority);
}
public void Execute()
{
var center = Cylinder[0].Center;
var height = Cylinder[0].Height;
var radius = Cylinder[0].Radius;
var bevelRadius = Cylinder[0].BevelRadius;
var bakeToShape = GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientation);
var scale = bakeToShape.DecomposeScale();
height *= scale.z;
radius *= math.cmax(scale.xy);
Cylinder[0] = new CylinderGeometry
{
Center = center,
Orientation = orientation,
Height = height,
Radius = radius,
BevelRadius = math.min(bevelRadius, math.min(height * 0.5f, radius)),
SideCount = Cylinder[0].SideCount
};
}
}
internal static CylinderGeometry BakeToBodySpace(
this CylinderGeometry cylinder, float4x4 localToWorld, float4x4 shapeToWorld, EulerAngles orientation
)
{
using (var geometry = new NativeArray<CylinderGeometry>(1, Allocator.TempJob) { [0] = cylinder })
{
var job = new BakeCylinderJob
{
Cylinder = geometry,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld,
orientation = orientation
};
job.Run();
return geometry[0];
}
}
#endregion

#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;

public void Execute()


{
var v = Vertices[0];
GetPlanePoints(center, size, orientation, out v.c0, out v.c1, out v.c2, out v.c3);
var localToShape = math.mul(math.inverse(shapeToWorld), localToWorld);
v.c0 = math.mul(localToShape, new float4(v.c0, 1f)).xyz;
v.c1 = math.mul(localToShape, new float4(v.c1, 1f)).xyz;
v.c2 = math.mul(localToShape, new float4(v.c2, 1f)).xyz;
v.c3 = math.mul(localToShape, new float4(v.c3, 1f)).xyz;
Vertices[0] = v;
}
}

internal static void BakeToBodySpace(


float3 center, float2 size, EulerAngles orientation, float4x4 localToWorld, float4x4 shapeToWorld,
out float3 vertex0, out float3 vertex1, out float3 vertex2, out float3 vertex3
)
{
using (var geometry = new NativeArray<float3x4>(1, Allocator.TempJob))
{
var job = new BakePlaneJob
{
Vertices = geometry,
center = center,
size = size,
orientation = orientation,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld
};
job.Run();
vertex0 = geometry[0].c0;
vertex1 = geometry[0].c1;
vertex2 = geometry[0].c2;
vertex3 = geometry[0].c3;
}
}

internal static void GetPlanePoints(


float3 center, float2 size, EulerAngles orientation,
out float3 vertex0, out float3 vertex1, out float3 vertex2, out float3 vertex3
)
{
var sizeYUp = math.float3(size.x, 0, size.y);
vertex0 = center + math.mul(orientation, sizeYUp * math.float3(-0.5f, 0, 0.5f));
vertex1 = center + math.mul(orientation, sizeYUp * math.float3(0.5f, 0, 0.5f));
vertex2 = center + math.mul(orientation, sizeYUp * math.float3(0.5f, 0, -0.5f));
vertex3 = center + math.mul(orientation, sizeYUp * math.float3(-0.5f, 0, -0.5f));
}

#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;

public uint ForceUniqueIdentifier;


public ConvexHullGenerationParameters GenerationParameters;
public Material Material;
public CollisionFilter CollisionFilter;
public float4x4 BakeFromShape;

[ReadOnly] public NativeArray<HashableShapeInputs> Inputs;


[ReadOnly] public NativeArray<int> AllSkinIndices;
[ReadOnly] public NativeArray<float> AllBlendShapeWeights;

public void Execute()


{
Result[0] = HashableShapeInputs.GetHash128(
ForceUniqueIdentifier, GenerationParameters, Material, CollisionFilter, BakeFromShape,
Inputs, AllSkinIndices, AllBlendShapeWeights
);
}
}
#endregion

#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;

var bakeToShape = BakeCapsuleJobExtension.GetBakeToShape(shape, center, capsule.OrientationEuler);


var scale = bakeToShape.DecomposeScale();
var newRadius = radius / math.cmax(scale.xy);
if (math.abs(capsule.Radius - newRadius) > kMinimumChange)
capsule.Radius = newRadius;
height /= scale.z;
if (math.abs(math.length(capsule.Height - height)) > kMinimumChange)
capsule.Height = height;
shape.SetCapsule(capsule);
}
internal static CapsuleGeometryAuthoring BakeToBodySpace(
this CapsuleGeometryAuthoring capsule, float4x4 localToWorld, float4x4 shapeToWorld
)
{
using (var geometry = new NativeArray<CapsuleGeometryAuthoring>(1, Allocator.TempJob) { [0] = capsule })
{
var job = new BakeCapsuleJob
{
Capsule = geometry,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld
};
job.Run();
return geometry[0];
}
}
public static class BakeCylinderJobExtension
{
internal static float4x4 GetBakeToShape(PhysicsShapeAuthoring shape, float3 center, EulerAngles orientation)
{
var transform = shape.transform;
var localToWorld = (float4x4)transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
return BakeCylinderJob.GetBakeToShape(localToWorld, shapeToWorld, ref center,
ref orientation);
}
}
public static CylinderGeometry GetBakedCylinderProperties(this PhysicsShapeAuthoring shape)
{
var cylinder = shape.GetCylinderProperties(out var orientation);
return cylinder.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(),
orientation);
}
public static void SetBakedSphereRadius(this PhysicsShapeAuthoring shape, float radius)
{
var sphere = shape.GetSphereProperties(out EulerAngles eulerAngles);
var center = sphere.Center;
radius = math.abs(radius);
var basisToWorld = GetBasisToWorldMatrix(shape.transform.localToWorldMatrix, center, eulerAngles, 1f);
var basisPriority = basisToWorld.HasShear() ? GetBasisAxisPriority(basisToWorld) : k_DefaultAxisPriority;
var bakeToShape = GetPrimitiveBakeToShapeMatrix(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(), ref center, ref eulerAngles, 1f, basisPriority);
var scale = math.cmax(bakeToShape.DecomposeScale());
var newRadius = radius / scale;
sphere.Radius = newRadius;
shape.SetSphere(sphere);
}
public static void SetBakedPlaneSize(this PhysicsShapeAuthoring shape, float2 size)
{
shape.GetPlaneProperties(out var center, out var planeSize, out EulerAngles orientation);
var prevSize = math.abs(planeSize);
size = math.abs(size);
if (math.abs(size[0] - prevSize[0]) < kMinimumChange) size[0] = prevSize[0];
if (math.abs(size[1] - prevSize[1]) < kMinimumChange) size[1] = prevSize[1];
planeSize = size;
shape.SetPlane(center, planeSize, orientation);
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/CapsuleGeometryAuthoring.cs
using System;
using Unity.Mathematics;
using UnityEngine;

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;

/// <summary> The radius of the capsule. </summary>


///
/// <value> The radius. </value>
public float Radius { get => m_Radius; set => m_Radius = value; }
[SerializeField]
float m_Radius;
public bool Equals(CapsuleGeometryAuthoring other)
{
return m_Height.Equals(other.m_Height)
&& m_Center.Equals(other.m_Center)
&& m_Radius.Equals(other.m_Radius)
&& m_OrientationEuler.Equals(other.m_OrientationEuler);
}
public override int GetHashCode()
{
return unchecked((int)math.hash(
new float3x3(
Center,
m_OrientationEuler.Value,
new float3((float)m_OrientationEuler.RotationOrder, m_Height, m_Radius)
)
));
}
}
public static class CapsuleGeometryAuthoringExtensions
{
/// <summary>
/// Construct a CapsuleGeometryAuthoring instance from a run-time CapsuleGeometry instance.
/// </summary>
public static CapsuleGeometryAuthoring ToAuthoring(this CapsuleGeometry input)
{
var orientationEuler = EulerAngles.Default;
orientationEuler.SetValue(quaternion.LookRotationSafe(input.Vertex1 - input.Vertex0, math.up()));
return new CapsuleGeometryAuthoring
{
Height = input.GetHeight(),
OrientationEuler = orientationEuler,
Center = input.GetCenter(),
Radius = input.Radius
};
}
/// <summary>
/// Construct a run-time CapsuleGeometry instance from a CapsuleGeometryAuthoring instance.
/// </summary>
public static CapsuleGeometry ToRuntime(this CapsuleGeometryAuthoring input)
{
var halfHeight = 0.5f * input.Height;
var halfDistance = halfHeight - input.Radius;
var axis = math.normalize(math.mul(input.Orientation, new float3 { z = 1f }));
var halfAxis = axis * halfDistance;
var vertex0 = input.Center + halfAxis;
var vertex1 = input.Center - halfAxis;
return new CapsuleGeometry
{
Vertex0 = vertex0,
Vertex1 = vertex1,
Radius = input.Radius
};
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/ConvexHullGenerationParametersExtensions.cs
using System;
using Unity.Collections;
using Unity.Mathematics;

namespace Unity.Physics.Authoring
{
public static class ConvexHullGenerationParametersExtensions
{
// recommended simplification tolerance is at least 1 centimeter
internal const float k_MinRecommendedSimplificationTolerance = 0.01f;

internal static void InitializeToRecommendedAuthoringValues(


ref this ConvexHullGenerationParameters generationParameters, NativeArray<float3> points
)
{
generationParameters = ConvexHullGenerationParameters.Default.ToAuthoring();

if (points.Length <= 1)
return;

var bounds = new Aabb { Min = points[0], Max = points[0] };


for (var i = 1; i < points.Length; ++i)
bounds.Include(points[i]);
generationParameters.SimplificationTolerance = math.max(
k_MinRecommendedSimplificationTolerance,
ConvexHullGenerationParameters.Default.SimplificationTolerance * math.cmax(bounds.Extents)
);
// TODO: initialize other properties based on input points?
}

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);
}

public static ConvexHullGenerationParameters ToAuthoring(this ConvexHullGenerationParameters generationParameters)


{
generationParameters.MinimumAngle = math.degrees(generationParameters.MinimumAngle);
return generationParameters;
}

public static ConvexHullGenerationParameters ToRunTime(this ConvexHullGenerationParameters generationParameters)


{
generationParameters.MinimumAngle = math.radians(generationParameters.MinimumAngle);
return generationParameters;
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/EulerAngles.cs
using System;
using Unity.Mathematics;
using UnityEngine;

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));

public static implicit operator quaternion(EulerAngles euler) =>


math.normalize(quaternion.Euler(math.radians(euler.Value), euler.RotationOrder));
public bool Equals(EulerAngles other) => Value.Equals(other.Value) && RotationOrder == other.RotationOrder;
public override bool Equals(object obj) => obj is EulerAngles other && Equals(other);
public override int GetHashCode() => unchecked((int)math.hash(new float4(Value, (float)RotationOrder)));
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/GetActiveChildrenScope.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityComponent = UnityEngine.Component;
namespace Unity.Physics.Authoring
{
public struct GetActiveChildrenScope<T> : IDisposable where T : UnityComponent
{
static readonly List<PhysicsShapeAuthoring> s_PhysicsShapes = new List<PhysicsShapeAuthoring>(8);

static bool s_BufferUsed;


static List<T> s_Buffer = new List<T>(8);

public List<T> Buffer => m_Disposed ? null : s_Buffer;


bool m_Disposed;
PhysicsShapeAuthoring m_Shape;
Transform m_Root;
GameObject m_PrimaryBody;
bool m_CheckIfComponentBelongsToShape;
public GetActiveChildrenScope(PhysicsShapeAuthoring shape, Transform root)
{
m_Disposed = false;
m_Shape = shape;
m_Root = root;
m_PrimaryBody = PhysicsShapeExtensions.GetPrimaryBody(root.gameObject);
m_CheckIfComponentBelongsToShape = root.transform.IsChildOf(shape.transform);
if (s_BufferUsed)
throw new InvalidOperationException($"Cannot nest two {GetType()}");
s_BufferUsed = true;
root.GetComponentsInChildren(true, s_Buffer);
}
public void Dispose()
{
if (m_Disposed)
return;
m_Disposed = true;
s_BufferUsed = false;
s_Buffer.Clear();
}

public bool IsChildActiveAndBelongsToShape(T child, bool filterOutInvalid = true)


{
var meshFilter = (UnityComponent)child as MeshFilter;
if (meshFilter != null)
{
if (meshFilter.sharedMesh == null)
return false;

var renderer = meshFilter.GetComponent<MeshRenderer>();


if (renderer == null || !renderer.enabled)
return false;
if (filterOutInvalid && !meshFilter.sharedMesh.IsValidForConversion(m_Shape.gameObject))
return false;
}

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);

// avoids drift in axes we're not actually changing


public const float kMinimumChange = HashableShapeInputs.k_DefaultLinearPrecision;

static readonly int[] k_NextAxis = { 1, 2, 0 };


static readonly int[] k_PrevAxis = { 2, 0, 1 };

// 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;

var imax = max == basisAxisLengths.x ? 0 : max == basisAxisLengths.y ? 1 : 2;


basisToWorld[k_NextAxis[imax]] = DeskewSecondaryAxis(basisToWorld[imax], basisToWorld[k_NextAxis[imax]]);
basisToWorld[k_PrevAxis[imax]] = DeskewSecondaryAxis(basisToWorld[imax], basisToWorld[k_PrevAxis[imax]]);

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];

return new int3(imax, imid, imin);


}

[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]]);

// recompute third axes from first two


var n2 = math.normalizesafe(
new float4(math.cross(localToBake[basisPriority[0]].xyz, localToBake[basisPriority[1]].xyz), 0f)
);
localToBake[basisPriority[2]] = n2 * math.dot(localToBake[basisPriority[2]], n2);
}
var bakeToShape = math.mul(math.inverse(shapeToWorld), localToBake);
// transform baked center/orientation (i.e. primitive basis) into shape space
orientation.SetValue(
quaternion.LookRotationSafe(bakeToShape[basisPriority[0]].xyz, bakeToShape[basisPriority[1]].xyz)
);
center = bakeToShape.c3.xyz;

return bakeToShape;
}

internal static CollisionFilter GetFilter(this PhysicsShapeAuthoring shape)


{
// TODO: determine optimal workflow for specifying group index
return new CollisionFilter
{
BelongsTo = shape.BelongsTo.Value,
CollidesWith = shape.CollidesWith.Value
};
}
internal static Material GetMaterial(this PhysicsShapeAuthoring shape)
{
// TODO: TBD how we will author editor content for other shape flags
return new Material
{
Friction = shape.Friction.Value,
FrictionCombinePolicy = shape.Friction.CombineMode,
Restitution = shape.Restitution.Value,
RestitutionCombinePolicy = shape.Restitution.CombineMode,
CollisionResponse = shape.CollisionResponse,
CustomTags = shape.CustomTags.Value
};
}
public static GameObject FindTopmostEnabledAncestor<T>(GameObject shape, List<T> buffer) where T : Component
{
// include inactive in case the supplied shape GameObject is a prefab that has not been instantiated
shape.GetComponentsInParent(true, buffer);
GameObject result = null;
for (var i = buffer.Count - 1; i >= 0; --i)
{
if (
(buffer[i] as UnityEngine.Collider)?.enabled ??
(buffer[i] as MonoBehaviour)?.enabled ?? true
)
{
result = buffer[i].gameObject;
break;
}
}
buffer.Clear();
return result;
}
public static GameObject GetPrimaryBody(this PhysicsShapeAuthoring shape) => GetPrimaryBody(shape.gameObject);
public static GameObject GetPrimaryBody(GameObject shape)
{
var pb = ColliderExtensions.FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
var rb = ColliderExtensions.FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
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
ColliderExtensions.FindTopmostStaticEnabledAncestor(shape, out GameObject 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;
}

public static BoxGeometry GetBakedBoxProperties(this PhysicsShapeAuthoring shape)


{
var box = shape.GetBoxProperties(out var orientation);
return box.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(), orientation);
}
internal static BoxGeometry BakeToBodySpace(
this BoxGeometry box, float4x4 localToWorld, float4x4 shapeToWorld, EulerAngles orientation
)
{
using (var geometry = new NativeArray<BoxGeometry>(1, Allocator.TempJob) { [0] = box })
{
var job = new BakeBoxJob
{
Box = geometry,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld,
orientation = orientation
};
job.Run();
return geometry[0];
}
}
public static void SetBakedBoxSize(this PhysicsShapeAuthoring shape, float3 size, float bevelRadius)
{
var box = shape.GetBoxProperties(out var orientation);
var center = box.Center;
var prevSize = math.abs(box.Size);
size = math.abs(size);

var bakeToShape = BakeBoxJobExtension.GetBakeToShape(shape, center, orientation);


var scale = bakeToShape.DecomposeScale();
size /= scale;

if (math.abs(size[0] - prevSize[0]) < kMinimumChange) size[0] = prevSize[0];


if (math.abs(size[1] - prevSize[1]) < kMinimumChange) size[1] = prevSize[1];
if (math.abs(size[2] - prevSize[2]) < kMinimumChange) size[2] = prevSize[2];
box.BevelRadius = bevelRadius;
box.Size = size;
shape.SetBox(box, orientation);
}

internal static CapsuleGeometryAuthoring GetBakedCapsuleProperties(this PhysicsShapeAuthoring shape)


{
var capsule = shape.GetCapsuleProperties();
return capsule.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix());
}

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();

var newRadius = radius / math.cmax(scale.xy);


if (math.abs(cylinder.Radius - newRadius) > kMinimumChange) cylinder.Radius = newRadius;
if (math.abs(cylinder.BevelRadius - bevelRadius) > kMinimumChange) cylinder.BevelRadius = bevelRadius;

var newHeight = math.max(0, height / scale.z);


if (math.abs(cylinder.Height - newHeight) > kMinimumChange) cylinder.Height = newHeight;
shape.SetCylinder(cylinder, orientation);
}

internal static SphereGeometry GetBakedSphereProperties(this PhysicsShapeAuthoring shape, out EulerAngles orientation)


{
var sphere = shape.GetSphereProperties(out orientation);
return sphere.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(), ref orientation);
}
internal static void GetBakedPlaneProperties(
this PhysicsShapeAuthoring shape, out float3 vertex0, out float3 vertex1, out float3 vertex2, out float3 vertex3
)
{
shape.GetPlaneProperties(out var center, out var size, out EulerAngles orientation);
BakeToBodySpace(
center, size, orientation, shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(),
out vertex0, out vertex1, out vertex2, out vertex3
);
}
public static void GetBakedConvexProperties(this PhysicsShapeAuthoring shape, NativeList<float3> pointCloud)
{
shape.GetConvexHullProperties(pointCloud, true, default, default, default, default);
shape.BakePoints(pointCloud.AsArray());
}
internal static Hash128 GetBakedMeshInputs(this PhysicsShapeAuthoring shape, HashSet<UnityEngine.Mesh> meshAssets = null)
{
using (var inputs = new NativeList<HashableShapeInputs>(8, Allocator.TempJob))
{
shape.GetMeshProperties(default, default, true, inputs, meshAssets);
using (var hash = new NativeArray<Hash128>(1, Allocator.TempJob))
using (var allSkinIndices = new NativeArray<int>(0, Allocator.TempJob))
using (var allBlendShapeWeights = new NativeArray<float>(0, Allocator.TempJob))
{
var job = new GetShapeInputsHashJob
{
Result = hash,
ForceUniqueIdentifier = (uint)(shape.ForceUnique ? shape.GetInstanceID() : 0),
Material = shape.GetMaterial(),
CollisionFilter = shape.GetFilter(),
BakeFromShape = shape.GetLocalToShapeMatrix(),
Inputs = inputs.AsArray(),
AllSkinIndices = allSkinIndices,
AllBlendShapeWeights = allBlendShapeWeights
};
job.Run();
return hash[0];
}
}
}
internal static Hash128 GetBakedConvexInputs(this PhysicsShapeAuthoring shape, HashSet<UnityEngine.Mesh> meshAssets)
{
using (var inputs = new NativeList<HashableShapeInputs>(8, Allocator.TempJob))
using (var allSkinIndices = new NativeList<int>(4096, Allocator.TempJob))
using (var allBlendShapeWeights = new NativeList<float>(64, Allocator.TempJob))
{
shape.GetConvexHullProperties(default, true, inputs, allSkinIndices, allBlendShapeWeights, meshAssets);
using (var hash = new NativeArray<Hash128>(1, Allocator.TempJob))
{
var job = new GetShapeInputsHashJob
{
Result = hash,
ForceUniqueIdentifier = (uint)(shape.ForceUnique ? shape.GetInstanceID() : 0),
GenerationParameters = shape.ConvexHullGenerationParameters,
Material = shape.GetMaterial(),
CollisionFilter = shape.GetFilter(),
BakeFromShape = shape.GetLocalToShapeMatrix(),
Inputs = inputs.AsArray(),
AllSkinIndices = allSkinIndices.AsArray(),
AllBlendShapeWeights = allBlendShapeWeights.AsArray()
};
job.Run();
return hash[0];
}
}
}
public static void GetBakedMeshProperties(
this PhysicsShapeAuthoring shape, NativeList<float3> vertices, NativeList<int3> triangles,
HashSet<UnityEngine.Mesh> meshAssets = null
)
{
shape.GetMeshProperties(vertices, triangles, true, default, meshAssets);
shape.BakePoints(vertices.AsArray());
}
const float k_HashFloatTolerance = 0.01f;
// used to hash convex hull generation properties in a way that is robust to imprecision
public static uint GetStableHash(
this ConvexHullGenerationParameters generationParameters,
ConvexHullGenerationParameters hashedParameters,
float tolerance = k_HashFloatTolerance
)
{
var differences = new float3(
generationParameters.BevelRadius - hashedParameters.BevelRadius,
generationParameters.MinimumAngle - hashedParameters.MinimumAngle,
generationParameters.SimplificationTolerance - hashedParameters.SimplificationTolerance
);
return math.cmax(math.abs(differences)) < tolerance
? unchecked((uint)hashedParameters.GetHashCode())
: unchecked((uint)generationParameters.GetHashCode());
}
// used to hash an array of points in a way that is robust to imprecision
public static unsafe uint GetStableHash(
this NativeList<float3> points, NativeArray<float3> hashedPoints, float tolerance = k_HashFloatTolerance
)
{
if (points.Length != hashedPoints.Length)
return math.hash(points.GetUnsafePtr(), UnsafeUtility.SizeOf<float3>() * points.Length);

for (int i = 0, count = points.Length; i < count; ++i)


{
if (math.cmax(math.abs(points[i] - hashedPoints[i])) > tolerance)
return math.hash(points.GetUnsafePtr(), UnsafeUtility.SizeOf<float3>() * points.Length);
}
return math.hash(hashedPoints.GetUnsafePtr(), UnsafeUtility.SizeOf<float3>() * hashedPoints.Length);
}
public static int GetMaxAxis(this float3 v)
{
var cmax = math.cmax(v);
return cmax == v.z ? 2 : cmax == v.y ? 1 : 0;
}
public static int GetDeviantAxis(this float3 v)
{
var deviation = math.abs(v - math.csum(v) / 3f);
return math.cmax(deviation) == deviation.z ? 2 : math.cmax(deviation) == deviation.y ? 1 : 0;
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/PropertyAttributes.cs
using UnityEngine;

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; }

public SoftRangeAttribute(float min, float max)


{
SliderMin = TextFieldMin = min;
SliderMax = TextFieldMax = max;
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/AssemblyInfo.cs
using System.Runtime.CompilerServices;

[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();

EditorUtilities.EditPivot(ballAndSocket.worldFromA, ballAndSocket.worldFromB, ballAndSocket.AutoSetConnected,


ref ballAndSocket.PositionLocal, ref ballAndSocket.PositionInConnectedEntity, ballAndSocket);
}
}
}
#endif
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/CustomPhysicsMaterialTagNamesEditor.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;

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();

public override void OnInspectorGUI()


{
LimitedHingeJoint limitedHinge = (LimitedHingeJoint)target;

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();
}
}

protected virtual void OnSceneGUI()


{
LimitedHingeJoint limitedHinge = (LimitedHingeJoint)target;

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;

public override void OnInspectorGUI()


{
serializedObject.Update();

EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_MotionType);
if (m_MotionType.intValue != (int)BodyMotionType.Static)
EditorGUILayout.PropertyField(m_Smoothing);

var dynamic = m_MotionType.intValue == (int)BodyMotionType.Dynamic;


if (dynamic)
EditorGUILayout.PropertyField(m_Mass, Content.MassLabel);
else
{
EditorGUI.BeginDisabledGroup(true);
var position = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
EditorGUI.BeginProperty(position, Content.MassLabel, m_Mass);
EditorGUI.FloatField(position, Content.MassLabel, float.PositiveInfinity);
EditorGUI.EndProperty();
EditorGUI.EndDisabledGroup();
}

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);
}

showAdvanced = EditorGUILayout.Foldout(showAdvanced, Content.AdvancedLabel);


if (showAdvanced)
{
++EditorGUI.indentLevel;
EditorGUILayout.PropertyField(m_WorldIndex);
if (m_MotionType.intValue != (int)BodyMotionType.Static)
{
EditorGUILayout.PropertyField(m_OverrideDefaultMassDistribution);
if (m_OverrideDefaultMassDistribution.boolValue)
{
++EditorGUI.indentLevel;
EditorGUILayout.PropertyField(m_CenterOfMass, Content.CenterOfMassLabel);

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];

static readonly string[] k_NonReadableGeometryWarning =


{
L10n.Tr($"{k_Singular} has a non-readable mesh in its hierarchy. Move it into a sub-scene or assign a custom mesh with Read/Write enabled in its import settings if it needs
L10n.Tr($"{k_Plural} has a non-readable mesh in its hierarchy. Move it into a sub-scene or assign a custom mesh with Read/Write enabled in its import settings if it needs t
};
public static string GetNonReadableGeometryWarning(int numTargets) =>
numTargets == 1 ? k_NonReadableGeometryWarning[0] : k_NonReadableGeometryWarning[1];
static readonly string[] k_MeshWithSkinnedPointsWarning =
{
L10n.Tr($"{k_Singular} is a mesh based on its render geometry, but its render geometry includes skinned points. These points will be excluded from the automatically generat
L10n.Tr($"{k_Plural} is a mesh based on its render geometry, but its render geometry includes skinned points. These points will be excluded from the automatically generated
};
public static string GetMeshWithSkinnedPointsWarning(int numTargets) =>
numTargets == 1 ? k_MeshWithSkinnedPointsWarning[0] : k_MeshWithSkinnedPointsWarning[1];
static readonly string[] k_StaticColliderStatusMessage =
{
L10n.Tr($"{k_Singular} will be considered static. Add a {ObjectNames.NicifyVariableName(typeof(PhysicsBodyAuthoring).Name)} component if you will move it at run-time."
L10n.Tr($"{k_Plural} will be considered static. Add a {ObjectNames.NicifyVariableName(typeof(PhysicsBodyAuthoring).Name)} component if you will move them at run-time."
};
public static string GetStaticColliderStatusMessage(int numTargets) =>
numTargets == 1 ? k_StaticColliderStatusMessage[0] : k_StaticColliderStatusMessage[1];
public static readonly string BoxCapsuleSuggestion =
L10n.Tr($"Target {ShapeType.Box} has uniform size on its two short axes and a large convex radius. Consider using a {ShapeType.Capsule} instead.");
public static readonly string BoxPlaneSuggestion =
L10n.Tr($"Target {ShapeType.Box} is flat. Consider using a {ShapeType.Plane} instead.");
public static readonly string BoxSphereSuggestion =
L10n.Tr($"Target {ShapeType.Box} has uniform size and large convex radius. Consider using a {ShapeType.Sphere} instead.");
public static readonly string CapsuleSphereSuggestion =
L10n.Tr($"Target {ShapeType.Capsule}'s diameter is equal to its height. Consider using a {ShapeType.Sphere} instead.");
public static readonly string CylinderCapsuleSuggestion =
L10n.Tr($"Target {ShapeType.Cylinder} has a large convex radius. Consider using a {ShapeType.Capsule} instead.");
public static readonly string CylinderSphereSuggestion =
L10n.Tr($"Target {ShapeType.Cylinder} has a large convex radius and its diameter is equal to its height. Consider using a {ShapeType.Sphere} instead.");
public static readonly GUIStyle Button =
new GUIStyle(EditorStyles.miniButton) { padding = new RectOffset() };
public static readonly GUIStyle ButtonDropDown =
new GUIStyle(EditorStyles.popup) { alignment = TextAnchor.MiddleCenter };
}
#pragma warning disable 649
[AutoPopulate] SerializedProperty m_ShapeType;
[AutoPopulate] SerializedProperty m_PrimitiveCenter;
[AutoPopulate] SerializedProperty m_PrimitiveSize;
[AutoPopulate] SerializedProperty m_PrimitiveOrientation;
[AutoPopulate] SerializedProperty m_Capsule;
[AutoPopulate] SerializedProperty m_Cylinder;
[AutoPopulate] SerializedProperty m_CylinderSideCount;
[AutoPopulate] SerializedProperty m_SphereRadius;
[AutoPopulate] SerializedProperty m_ConvexHullGenerationParameters;
[AutoPopulate(PropertyPath = "m_ConvexHullGenerationParameters.m_BevelRadius")] SerializedProperty m_BevelRadius;
[AutoPopulate] SerializedProperty m_MinimumSkinnedVertexWeight;
[AutoPopulate] SerializedProperty m_CustomMesh;
[AutoPopulate] SerializedProperty m_Material;
[AutoPopulate] SerializedProperty m_ForceUnique;
#pragma warning restore 649
[Flags]
enum GeometryState
{
Okay = 0,
NoGeometry = 1 << 0,
NonReadableGeometry = 1 << 1,
MeshWithSkinnedPoints = 1 << 2
}
GeometryState m_GeometryState;
int m_NumImplicitStatic;
// keep track of when the user is dragging some control to prevent continually rebuilding preview geometry
[NonSerialized]
int m_DraggingControlID;
[NonSerialized]
FitToRenderMeshesDropDown m_DropDown;
protected override void OnEnable()
{
base.OnEnable();
HashUtility.Initialize();

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);
}
}

float m_BevelRadius = ConvexHullGenerationParameters.Default.BevelRadius;


bool m_IsDragging = false;

public float height


{
get => GetSize().z;
set
{
var size = GetSize();
size.z = math.max(0f, value);
SetSize(size);
}
}

public float radius


{
get
{
var size = (float3)GetSize();
var diameter = 0f;
// only consider size values on enabled axes
if (IsAxisEnabled(0)) diameter = math.max(diameter, math.abs(size.x));
else if (IsAxisEnabled(1)) diameter = math.max(diameter, math.abs(size.y));
return diameter * 0.5f;
}
set
{
var size = (float3)GetSize();
size.x = size.y = math.max(0f, value * 2.0f);
SetSize(size);
}
}

public int sideCount


{
get => m_SideCount;
set
{
if (value == m_SideCount)
return;

m_SideCount = value;

Array.Resize(ref m_TopPoints, m_SideCount * 2);


Array.Resize(ref m_BottomPoints, m_SideCount * 2);
Array.Resize(ref m_Corners, m_SideCount * 2);
}
}
int m_SideCount;

PhysicsBoundsHandleUtility.Corner[] m_Corners = Array.Empty<PhysicsBoundsHandleUtility.Corner>();


Vector3[] m_TopPoints = Array.Empty<Vector3>();
Vector3[] m_BottomPoints = Array.Empty<Vector3>();

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()


{
using (new Handles.DrawingScope(Handles.matrix))
{
var backfacedColor = PhysicsBoundsHandleUtility.GetStateColor(true);
var frontfacedColor = Handles.color;
bool isCameraInsideBox = false;

var radius = this.radius;


var bevelRadius = this.bevelRadius;

var halfHeight = new float3(0f, 0f, height * 0.5f);


var ctr = (float3)center;
var halfAngleStep = math.PI / m_SideCount;
var angleStep = 2f * halfAngleStep;
const float kHalfPI = (math.PI * 0.5f);
var bottom = ctr + halfHeight + new float3 { z = bevelRadius };
var top = ctr - halfHeight - new float3 { z = bevelRadius };
var tangent = new float3(1, 0, 0);
var binormal = new float3(0, 1, 0);
var topBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(top, -tangent, binormal, axes, isCameraInsideBox);
var bottomBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(bottom, tangent, binormal, axes, isCameraInsideBox);

var cameraCenter = float3.zero;


var cameraForward = new float3 { z = 1f };
if (Camera.current != null)
{
cameraCenter = Camera.current.transform.position;
cameraForward = Camera.current.transform.forward;
}

// 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;

var noSides = (radius - bevelRadius) < PhysicsBoundsHandleUtility.kDistanceEpsilon;


var up = new float3(0, 0, -1f);

var t = ((m_SideCount - 2) * angleStep);


var xyAngle0 = new float3(math.cos(t), math.sin(t), 0f);

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 offset0 = xyAngle0 * (radius - bevelRadius);


var offset1 = xyAngle1 * (radius - bevelRadius);
var offset2 = xyAngle2 * (radius - bevelRadius);
var top1 = ctr + offset1 - (halfHeight - new float3 { z = bevelRadius });
var bottom1 = ctr + offset1 + (halfHeight - new float3 { z = bevelRadius });
var top2 = ctr + offset2 - (halfHeight - new float3 { z = bevelRadius });
var bottom2 = ctr + offset2 + (halfHeight - new float3 { z = bevelRadius });

var startOffset = direction1 * bevelRadius;


if (bevelGreaterThanZero)
{
var upOffset = up * bevelRadius;
// top/bottom caps
if (bevelLessThanCylinderRadius)
{
Handles.color = topBackFaced ? backfacedColor : frontfacedColor;
Handles.DrawLine(top1 + upOffset, top2 + upOffset);

Handles.color = bottomBackFaced ? backfacedColor : frontfacedColor;


Handles.DrawLine(bottom1 - upOffset, bottom2 - upOffset);
}

var currSideMidPoint = ctr + ((top1 + bottom1 + top2 + bottom2) * 0.25f) + startOffset;


var currSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(currSideMidPoint, up, sideways2, axes, isCameraInsideBox);
Handles.color = currSideBackFaced ? backfacedColor : frontfacedColor;
if (!noSides)
{
// Square side of bevelled cylinder
Handles.DrawLine(top2 + startOffset, bottom2 + startOffset);
Handles.DrawLine(bottom2 + startOffset, bottom1 + startOffset);
Handles.DrawLine(bottom1 + startOffset, top1 + startOffset);
Handles.DrawLine(top1 + startOffset, top2 + startOffset);
}
else
{
// Square side of bevelled cylinder, when squashed to a single line
Handles.DrawLine(top2 + startOffset, bottom2 + startOffset);
}
}
else
{
var top0 = ctr + offset0 - (halfHeight - new float3 { z = bevelRadius });
var bottom0 = ctr + offset0 + (halfHeight - new float3 { z = bevelRadius });
var prevMidPoint = ctr + ((top0 + top1 + bottom0 + bottom1) * 0.25f) + startOffset;
var prevSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(prevMidPoint, up, sideways1, axes, isCameraInsideBox);

var currMidPoint = ctr + ((top1 + top2 + bottom1 + bottom2) * 0.25f) + startOffset;


var currSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(currMidPoint, up, sideways2, axes, isCameraInsideBox);
// Square side of bevelled cylinder
Handles.color = (currSideBackFaced && prevSideBackFaced) ? backfacedColor : frontfacedColor;
Handles.DrawLine(bottom1 + startOffset, top1 + startOffset);
Handles.color = (currSideBackFaced && topBackFaced) ? backfacedColor : frontfacedColor;
Handles.DrawLine(top1 + startOffset, top2 + startOffset);
Handles.color = (currSideBackFaced && bottomBackFaced) ? backfacedColor : frontfacedColor;
Handles.DrawLine(bottom2 + startOffset, bottom1 + startOffset);
}
if (bevelGreaterThanZero)
{
Handles.color = frontfacedColor;

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;

// Side horizon on vertical curved edge


if (m_Corners[up1].splitCount > 1 &&
m_Corners[dn1].splitCount > 1)
{
if ((m_Corners[up1].splitAxis[0].y || m_Corners[up1].splitAxis[1].y) &&
(m_Corners[dn1].splitAxis[0].y || m_Corners[dn1].splitAxis[1].y))
{
var point0 = m_Corners[up1].splitAxis[0].y ? m_Corners[up1].points[0] : m_Corners[up1].points[1];
var point1 = m_Corners[dn1].splitAxis[0].y ? m_Corners[dn1].points[0] : m_Corners[dn1].points[1];
Handles.DrawLine(point0, point1);
}
}
// Top horizon on horizontal curved edge
if (m_Corners[up0].splitCount > 1 &&
m_Corners[up1].splitCount > 1)
{
if ((m_Corners[up0].splitAxis[0].x || m_Corners[up0].splitAxis[1].x) &&
(m_Corners[up1].splitAxis[0].z || m_Corners[up1].splitAxis[1].z))
{
var point0 = m_Corners[up0].splitAxis[0].x ? m_Corners[up0].points[0] : m_Corners[up0].points[1];
var point1 = m_Corners[up1].splitAxis[0].z ? m_Corners[up1].points[0] : m_Corners[up1].points[1];
Handles.DrawLine(point0, point1);
}
}
// Bottom horizon on horizontal curved edge
if (m_Corners[dn0].splitCount > 1 &&
m_Corners[dn1].splitCount > 1)
{
if ((m_Corners[dn0].splitAxis[0].z || m_Corners[dn0].splitAxis[1].z) &&
(m_Corners[dn1].splitAxis[0].x || m_Corners[dn1].splitAxis[1].x))
{
var point0 = m_Corners[dn0].splitAxis[0].z ? m_Corners[dn0].points[0] : m_Corners[dn0].points[1];
var point1 = m_Corners[dn1].splitAxis[0].x ? m_Corners[dn1].points[0] : m_Corners[dn1].points[1];
Handles.DrawLine(point0, point1);
}
}
}
for (var i = 0; i < m_Corners.Length; ++i)
PhysicsBoundsHandleUtility.DrawCorner(m_Corners[i], new bool3(true, true, !noSides));
}
}
}
protected override Bounds OnHandleChanged(HandleDirection handle, Bounds boundsOnClick, Bounds newBounds)
{
const int k_DirectionX = 0;
const int k_DirectionY = 1;
const int k_DirectionZ = 2;
var changedAxis = k_DirectionX;
var otherRadiusAxis = k_DirectionY;
switch (handle)
{
case HandleDirection.NegativeY:
case HandleDirection.PositiveY:
changedAxis = k_DirectionY;
otherRadiusAxis = k_DirectionX;
break;
case HandleDirection.NegativeZ:
case HandleDirection.PositiveZ:
changedAxis = k_DirectionZ;
break;
}
var upperBound = newBounds.max;
var lowerBound = newBounds.min;
var convexDiameter = 2f * bevelRadius;
// ensure changed dimension cannot be made less than convex diameter
if (upperBound[changedAxis] - lowerBound[changedAxis] < convexDiameter)
{
switch (handle)
{
case HandleDirection.PositiveX:
case HandleDirection.PositiveY:
case HandleDirection.PositiveZ:
upperBound[changedAxis] = lowerBound[changedAxis] + convexDiameter;
break;
default:
lowerBound[changedAxis] = upperBound[changedAxis] - convexDiameter;
break;
}
}
// ensure radius changes uniformly
if (changedAxis != k_DirectionZ)
{
var rad = 0.5f * (upperBound[changedAxis] - lowerBound[changedAxis]);
lowerBound[otherRadiusAxis] = center[otherRadiusAxis] - rad;
upperBound[otherRadiusAxis] = center[otherRadiusAxis] + rad;
}
return new Bounds((upperBound + lowerBound) * 0.5f, upperBound - lowerBound);
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/PhysicsBoundsHandleUtility.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEngine;
using static UnityEditor.IMGUI.Controls.PrimitiveBoundsHandle;
namespace Unity.Physics.Editor
{
static class PhysicsBoundsHandleUtility
{
internal const float kBackfaceAlphaMultiplier = 0.2f;
const float kDegreeEpsilon = 0.001f;
public const float kDistanceEpsilon = 0.0001f;
public static bool IsBackfaced(float3 localPos, float3 localTangent, float3 localBinormal, Axes axes, bool isCameraInsideBox)
{
// if inside the box then ignore back facing alpha multiplier (otherwise all handles will look disabled)
if (isCameraInsideBox || axes != Axes.All)
return false;
// use tangent and binormal to calculate normal in case handle matrix is skewed
float3 worldTangent = math.normalize(Handles.matrix.MultiplyVector(localTangent));
float3 worldBinormal = math.normalize(Handles.matrix.MultiplyVector(localBinormal));
float3 worldDir = math.normalize(math.cross(worldTangent, worldBinormal));
// adjust color if handle is back facing
float cosV;
var currentCamera = Camera.current;
if (currentCamera != null && !currentCamera.orthographic)
{
float3 cameraPos = currentCamera.transform.position;
float3 worldPos = Handles.matrix.MultiplyPoint(localPos);
cosV = math.dot(math.normalize(cameraPos - worldPos), worldDir);
}
else
{
float3 cameraForward = currentCamera == null ? Vector3.forward : currentCamera.transform.forward;
cosV = math.dot(-cameraForward, worldDir);
}
return cosV < -0.0001f;
}
public static Color GetStateColor(bool isBackfaced)
{
float alphaMultiplier = isBackfaced ? kBackfaceAlphaMultiplier : 1f;
return Handles.color * new Color(1f, 1f, 1f, alphaMultiplier);
}
static void AdjustMidpointHandleColor(bool isBackfaced)
{
Handles.color = GetStateColor(isBackfaced);
}
static readonly Vector3[] s_FacePoints = new Vector3[8];
static readonly Vector3[] s_LinePoints = new Vector3[2];
static readonly int[] k_NextAxis = { 1, 2, 0 };
public static void DrawFace(float3 center, float3 size, float cornerRadius, int normalAxis, Axes axes, bool isCameraInsideBox)
{
// 0 = 0 1 2
// 1 = 1 2 0
// 2 = 2 0 1
int a = normalAxis;
int b = k_NextAxis[a];
int c = k_NextAxis[b];
cornerRadius = math.abs(cornerRadius);
size *= 0.5f;
var normal = new float3 { [a] = size[a] };
var ctr = center + normal;
size -= new float3(cornerRadius);
// check if our face is a point
if (math.abs(size[c]) < kDistanceEpsilon &&
math.abs(size[b]) < kDistanceEpsilon)
return;
Vector3[] points;
// check if our face is a line or not
if (math.abs(size[c]) >= kDistanceEpsilon &&
math.abs(size[b]) >= kDistanceEpsilon)
{
var i = 0;
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
points = s_FacePoints;
}
else if (math.abs(size[c]) >= kDistanceEpsilon)
{
var i = 0;
s_LinePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
s_LinePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
points = s_LinePoints;
}
else
{
var i = 0;
s_LinePoints[i++] = ctr + new float3 { [c] = size[c], [b] = size[b] };
s_LinePoints[i++] = ctr + new float3 { [c] = size[c], [b] = -size[b] };
points = s_LinePoints;
}
float3 tangent, biNormal;
if (size[c] > 0)
{
tangent = math.cross(normal, math.normalizesafe(new float3 { [c] = size[c] }));
biNormal = math.cross(normal, tangent);
}
else
{
tangent = math.cross(normal, math.normalizesafe(new float3 { [b] = size[b] }));
biNormal = math.cross(normal, tangent);
}
using (new Handles.DrawingScope(Handles.color))
{
AdjustMidpointHandleColor(IsBackfaced(ctr, tangent, biNormal, axes, isCameraInsideBox));
Handles.DrawLines(points);
}
}
public struct Corner
{
public float3 angle;
public float3x2 intersections;
public float3x2 points;
public float3x3 axes;
public float3x3 normals;
public bool3x2 splitAxis;
public int splitCount;
public float3 position;
public float radius;
public bool isBackFaced;
public float3 cameraForward;
}
public static void CalculateCornerHorizon(float3 cornerPosition, quaternion orientation, float3 cameraCenter, float3 cameraForward, bool cameraOrtho, float radius, out Corner
{
var axisx = new float3(1f, 0f, 0f);
var axisy = new float3(0f, 1f, 0f);
var axisz = new float3(0f, 0f, 1f);
// a vector pointing away from the center of the corner
var cornerNormal = math.normalize(math.mul(orientation, new float3(1f, 1f, 1f)));
var axes = math.mul(new float3x3(orientation), new float3x3(axisx, axisy, axisz));
CalculateCornerHorizon(cornerPosition,
axes,
cornerNormal,
cameraCenter, cameraForward, cameraOrtho,
radius, out corner);
}
public static void CalculateCornerHorizon(float3 cornerPosition, float3x3 axes, float3 cornerNormal, float3 cameraCenter, float3 cameraForward, bool cameraOrtho, float radius
{
var cameraToCenter = cornerPosition - cameraCenter; // vector from camera to center
var sqrRadius = radius * radius;
var sqrDistCameraToCenter = math.lengthsq(cameraToCenter);
var sqrOffset = (sqrRadius * sqrRadius / sqrDistCameraToCenter); // squared distance from actual center to drawn disc center
if (!cameraOrtho)
cameraForward = cameraToCenter;
var normals = new float3x3
{
c0 = math.normalize(math.cross(axes[1], axes[2])),
c1 = math.normalize(math.cross(axes[2], axes[0])),
c2 = math.normalize(math.cross(axes[0], axes[1]))
};
corner = new Corner
{
angle = new float3(
Vector3.Angle(axes[0], axes[1]),
Vector3.Angle(axes[1], axes[2]),
Vector3.Angle(axes[2], axes[0])
),
intersections = default,
points = default,
splitAxis = default,
axes = axes,
normals = normals,
position = cornerPosition,
radius = radius,
cameraForward = cameraForward,
isBackFaced = math.dot(cornerNormal, cameraForward) > 0,
splitCount = 0
};
if (math.abs(sqrDistCameraToCenter) <= sqrRadius)
return;
for (int n = 0, sign = -1; n < 2; n++, sign += 2)
{
for (int i = 0; i < 3; i++)
{
var axis1 = normals[i] * sign;
var axis2 = axes[(i + 1) % 3] * sign;
var axis3 = axes[(i + 2) % 3] * sign;
var Q = Vector3.Angle(cameraForward, axis1);
var f = math.tan(math.radians(90 - math.min(Q, 180 - Q)));
var g = sqrOffset + f * f * sqrOffset;
if (g >= sqrRadius)
continue;
var e = math.degrees(math.asin(math.sqrt(g) / radius));
var vectorToPointOnHorizon = Quaternion.AngleAxis(e, axis1) * math.normalize(math.cross(axis1, cameraForward));
vectorToPointOnHorizon = math.normalize(Vector3.ProjectOnPlane(vectorToPointOnHorizon, axis1));
var intersectionDirection = vectorToPointOnHorizon;
var angle1 = Vector3.SignedAngle(axis2, intersectionDirection, axis1);
var angle2 = Vector3.SignedAngle(axis3, intersectionDirection, axis1);
if (angle1 <= 0 || angle2 >= 0)
continue;
var point = corner.position + (float3)(intersectionDirection * radius);
if (corner.splitCount < 2)
{
corner.splitAxis[corner.splitCount][i] = true;
corner.intersections[corner.splitCount] = intersectionDirection;
corner.points[corner.splitCount] = point;
corner.splitCount++;
}
}
}
if (!math.any(corner.splitAxis[0]) &&
!math.any(corner.splitAxis[1]))
{
corner.splitCount = 0;
corner.splitAxis[0] = false;
corner.splitAxis[1] = false;
}
}
public static void DrawCorner(Corner corner, bool3 showAxis)
{
var color = Handles.color;
var axes = corner.axes;
var intersections = corner.intersections;
var normals = corner.normals;
var origin = corner.position;
var radius = corner.radius;
if (corner.splitCount <= 1)
{
AdjustMidpointHandleColor(corner.isBackFaced);
if (showAxis[0]) Handles.DrawWireArc(origin, normals[0], axes[1], corner.angle[1], radius);
if (showAxis[1]) Handles.DrawWireArc(origin, normals[1], axes[2], corner.angle[2], radius);
if (showAxis[2]) Handles.DrawWireArc(origin, normals[2], axes[0], corner.angle[0], radius);
}
else
{
var angleLength = Vector3.SignedAngle(Vector3.ProjectOnPlane(intersections[0], corner.cameraForward),
Vector3.ProjectOnPlane(intersections[1], corner.cameraForward), corner.cameraForward);
bool reversePolarity = angleLength < 0;
if (reversePolarity)
Handles.DrawWireArc(origin, corner.cameraForward, corner.points[1] - origin, -angleLength, radius);
else
Handles.DrawWireArc(origin, corner.cameraForward, corner.points[0] - origin, angleLength, radius);

var backfacedColor = GetStateColor(true);


var axesBackfaced = new bool3(math.length(intersections[0] - axes[0]) < kDistanceEpsilon || math.length(intersections[1] - axes[0]) < kDistanceEpsilon,
math.length(intersections[0] - axes[1]) < kDistanceEpsilon || math.length(intersections[1] - axes[1]) < kDistanceEpsilon,
math.length(intersections[0] - axes[2]) < kDistanceEpsilon || math.length(intersections[1] - axes[2]) < kDistanceEpsilon);
var color1 = reversePolarity ? color : backfacedColor;
var color2 = reversePolarity ? backfacedColor : color;
for (int A = 1, B = 2, C = 0; C < 3; A = B, B = C, C++)
{
if (corner.splitAxis[0][C] == corner.splitAxis[1][C])
{
if (!axesBackfaced[A]) { angleLength = Vector3.Angle(intersections[0], axes[A]); axesBackfaced[A] = (angleLength<kDegreeEpsilon || angleLength> corner.angle
if (!axesBackfaced[B]) { angleLength = Vector3.Angle(intersections[1], axes[A]); axesBackfaced[B] = (angleLength<kDegreeEpsilon || angleLength> corner.angle
}
else if (corner.splitAxis[0][C])
{
if (showAxis[C])
{
angleLength = Vector3.Angle(intersections[0], axes[A]);
Handles.color = color1; Handles.DrawWireArc(origin, normals[C], intersections[0], -angleLength, radius);
Handles.color = color2; Handles.DrawWireArc(origin, normals[C], intersections[0], corner.angle[A] - angleLength, radius);
}
axesBackfaced[A] = true;
}
else
//if (corner.splitAxis[1][C])
{
if (showAxis[C])
{
angleLength = Vector3.Angle(intersections[1], axes[A]);
Handles.color = color2; Handles.DrawWireArc(origin, normals[C], intersections[1], -angleLength, radius);
Handles.color = color1; Handles.DrawWireArc(origin, normals[C], intersections[1], corner.angle[A] - angleLength, radius);
}
axesBackfaced[B] = true;
}
}
// check for singularity
if (math.all(axesBackfaced))
axesBackfaced = corner.isBackFaced;
for (int A = 1, B = 2, C = 0; C < 3; A = B, B = C, C++)
{
if (!showAxis[C])
continue;
if (corner.splitAxis[0][C] == corner.splitAxis[1][C])
{
Handles.color = (axesBackfaced[B] && axesBackfaced[A]) ? color1 : color2;
Handles.DrawWireArc(origin, normals[C], axes[A], corner.angle[A], radius);
}
}
}
Handles.color = color;
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/PhysicsCapsuleBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class PhysicsCapsuleBoundsHandle : CapsuleBoundsHandle
{
static PhysicsBoundsHandleUtility.Corner[] s_Corners = new PhysicsBoundsHandleUtility.Corner[8];
protected override void DrawWireframe()
{
if (this.radius <= 0f)
{
base.DrawWireframe();
return;
}
var cameraPos = default(float3);
var cameraFwd = new float3 { z = 1f };
var cameraOrtho = true;
if (Camera.current != null)
{
cameraPos = Camera.current.transform.position;
cameraFwd = Camera.current.transform.forward;
cameraOrtho = Camera.current.orthographic;
}
var size = new float3(this.radius * 2f, this.radius * 2f, height);
var radius = this.radius;
var origin = (float3)this.center;
var bounds = new Bounds(this.center, size);
// 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;
var cameraCenter = (float3)invMatrix.MultiplyPoint(cameraPos);
var cameraForward = (float3)invMatrix.MultiplyVector(cameraFwd);
bool isCameraInsideBox = Camera.current != null
&& bounds.Contains(invMatrix.MultiplyPoint(cameraPos));
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(-1f, 1f, 1f), radius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, -1f, 1f), radius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 2, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, -1f), radius, 2, axes, isCameraInsideBox);
var corner = 0.5f * size - new float3(1f) * radius;
var axisx = new float3(1f, 0f, 0f);
var axisy = new float3(0f, 1f, 0f);
var axisz = new float3(0f, 0f, 1f);
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, -1f), quaternion.LookRotation(-axisz, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, 1f), quaternion.LookRotation(-axisx, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, 1f), quaternion.LookRotation(axisz, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, -1f), quaternion.LookRotation(axisx, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, -1f), quaternion.LookRotation(-axisx, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, 1f), quaternion.LookRotation(axisz, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, 1f), quaternion.LookRotation(axisx, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, -1f), quaternion.LookRotation(-axisz, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[0], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[3], new bool3(true, false, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[4], new bool3(true, false, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[7], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[1], new bool3(true, false, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[2], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[5], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[6], new bool3(true, false, 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/PhysicsSphereBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class PhysicsSphereBoundsHandle : SphereBoundsHandle
{
protected override void DrawWireframe()
{
bool x = IsAxisEnabled(Axes.X);
bool y = IsAxisEnabled(Axes.Y);
bool z = IsAxisEnabled(Axes.Z);

if (x && !y && !z)


Handles.DrawLine(Vector3.right * radius, Vector3.left * radius);
if (!x && y && !z)
Handles.DrawLine(Vector3.up * radius, Vector3.down * radius);
if (!x && !y && z)
Handles.DrawLine(Vector3.forward * radius, Vector3.back * radius);
const float kEpsilon = 0.000001F;
if (radius > 0)
{
var frontfacedColor = Handles.color;
var backfacedColor = Handles.color * new Color(1f, 1f, 1f, PhysicsBoundsHandleUtility.kBackfaceAlphaMultiplier);
var discVisible = new bool[]
{
y && z,
x && z,
x && y
};
var discOrientations = new float3[]
{
Vector3.right,
Vector3.up,
Vector3.forward
};
// 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;
var cameraCenter = Camera.current == null ? Vector3.zero : Camera.current.transform.position;
var cameraToCenter = center - invMatrix.MultiplyPoint(cameraCenter); // vector from camera to center
var sqrDistCameraToCenter = cameraToCenter.sqrMagnitude;
var sqrRadius = radius * radius; // squared radius
var isCameraOrthographic = Camera.current == null || Camera.current.orthographic;
var sqrOffset = isCameraOrthographic ? 0 : (sqrRadius * sqrRadius / sqrDistCameraToCenter); // squared distance from actual center to drawn disc center
var insideAmount = sqrOffset / sqrRadius;
if (insideAmount < 1)
{
if (math.abs(sqrDistCameraToCenter) >= kEpsilon)
{
using (new Handles.DrawingScope(frontfacedColor))
{
if (isCameraOrthographic)
{
var horizonRadius = radius;
var horizonCenter = center;
Handles.DrawWireDisc(horizonCenter, cameraToCenter, horizonRadius);
}
else
{
var horizonRadius = math.sqrt(sqrRadius - sqrOffset);
var horizonCenter = center - sqrRadius * cameraToCenter / sqrDistCameraToCenter;
Handles.DrawWireDisc(horizonCenter, cameraToCenter, horizonRadius);
}
}
var planeNormal = cameraToCenter.normalized;
for (int i = 0; i < 3; i++)
{
if (!discVisible[i])
continue;

var discOrientation = discOrientations[i];


var angleBetweenDiscAndNormal = math.acos(math.dot(discOrientation, planeNormal));
angleBetweenDiscAndNormal = (math.PI * 0.5f) - math.min(angleBetweenDiscAndNormal, math.PI - angleBetweenDiscAndNormal);
float f = math.tan(angleBetweenDiscAndNormal);
float g = math.sqrt(sqrOffset + f * f * sqrOffset) / radius;
if (g < 1)
{
var angleToHorizon = math.degrees(math.asin(g));
var discTangent = math.cross(discOrientation, planeNormal);
var vectorToPointOnHorizon = Quaternion.AngleAxis(angleToHorizon, discOrientation) * discTangent;
var horizonArcLength = (90 - angleToHorizon) * 2.0f;
using (new Handles.DrawingScope(frontfacedColor))
Handles.DrawWireArc(center, discOrientation, vectorToPointOnHorizon, horizonArcLength, radius);
using (new Handles.DrawingScope(backfacedColor))
Handles.DrawWireArc(center, discOrientation, vectorToPointOnHorizon, horizonArcLength - 360, radius);
}
else
{
using (new Handles.DrawingScope(backfacedColor))
Handles.DrawWireDisc(center, discOrientation, radius);
}
}
}
}
else
{
using (new Handles.DrawingScope(backfacedColor))
{
for (int i = 0; i < 3; i++)
{
var discOrientation = discOrientations[i];
Handles.DrawWireDisc(center, discOrientation, radius);
}
}
}
}
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/BaseDrawer.cs
using UnityEditor;
using UnityEngine;

namespace Unity.Physics.Editor
{
abstract class BaseDrawer : PropertyDrawer
{
protected abstract bool IsCompatible(SerializedProperty property);

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)


{
return IsCompatible(property)
? EditorGUI.GetPropertyHeight(property)
: EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (IsCompatible(property))
DoGUI(position, property, label);
else
EditorGUIControls.DisplayCompatibilityWarning(position, label, ObjectNames.NicifyVariableName(GetType().Name));
}

protected abstract void DoGUI(Rect position, SerializedProperty property, GUIContent label);


}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/EnumFlagsDrawer.cs
using System;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(EnumFlagsAttribute))]
class EnumFlagsDrawer : BaseDrawer
{
protected override bool IsCompatible(SerializedProperty property)
{
return property.propertyType == SerializedPropertyType.Enum;
}

protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)


{
EditorGUI.BeginProperty(position, label, property);
var value = property.longValue;
EditorGUI.BeginChangeCheck();
value = Convert.ToInt64(
EditorGUI.EnumFlagsField(position, label, (Enum)Enum.ToObject(fieldInfo.FieldType, value))
);
if (EditorGUI.EndChangeCheck())
property.longValue = value;

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;

protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)


{
EditorGUI.BeginProperty(position, label, property);
EditorGUI.PropertyField(
new Rect(position) { xMax = position.xMax - Styles.PopupWidth },
property.FindPropertyRelative("Value"),
label
);
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
EditorGUI.PropertyField(
new Rect(position) { xMin = position.xMax - Styles.PopupWidth + EditorGUIUtility.standardVerticalSpacing },
property.FindPropertyRelative("CombineMode"),
GUIContent.none
);
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/PhysicsMaterialPropertiesDrawer.cs
using System.Collections.Generic;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;

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)."
);
}

const string k_CollisionFilterGroupKey = "m_BelongsToCategories";


const string k_AdvancedGroupKey = "m_CustomMaterialTags";
Dictionary<string, SerializedObject> m_SerializedTemplates = new Dictionary<string, SerializedObject>();

SerializedProperty GetTemplateValueProperty(SerializedProperty property)


{
var key = property.propertyPath;
var template = property.FindPropertyRelative("m_Template").objectReferenceValue;
SerializedObject serializedTemplate;
if (
!m_SerializedTemplates.TryGetValue(key, out serializedTemplate)
|| serializedTemplate?.targetObject != template
)
m_SerializedTemplates[key] = serializedTemplate = template == null ? null : new SerializedObject(template);
serializedTemplate?.Update();
return serializedTemplate?.FindProperty("m_Value");
}

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");
}

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)


{
var templateValueProperty = GetTemplateValueProperty(property);
// m_CollisionResponse, collision filter foldout, advanced foldout
var height = 3f * EditorGUIUtility.singleLineHeight + 2f * EditorGUIUtility.standardVerticalSpacing;

// 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;
}

protected override bool IsCompatible(SerializedProperty property) => true;

static void DisplayOverridableProperty(


Rect position, GUIContent label, SerializedProperty toggle, SerializedProperty value, bool templateAssigned
)
{
if (templateAssigned)
{
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth -= 16f + EditorGUIUtility.standardVerticalSpacing;
var togglePosition = new Rect(position) { width = EditorGUIUtility.labelWidth + 16f + EditorGUIUtility.standardVerticalSpacing };
EditorGUI.PropertyField(togglePosition, toggle, label);
EditorGUIUtility.labelWidth = labelWidth;

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);
}
}

protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)


{
var template = property.FindPropertyRelative("m_Template");
var templateAssigned = template.objectReferenceValue != null;
var supportsTemplate = property.FindPropertyRelative("m_SupportsTemplate");
if (supportsTemplate.boolValue)
{
position.height = EditorGUI.GetPropertyHeight(template);
EditorGUI.PropertyField(position, template);

position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;


}

var templateValue = GetTemplateValueProperty(property);


FindToggleAndValueProperties(property, templateValue, "m_CollisionResponse", out var collisionResponseDropDown, out var collisionResponse);
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.CollisionResponseLabel, collisionResponseDropDown, collisionResponse, templateAssigned);
SerializedProperty toggle;

// Check if regular collider


CollisionResponsePolicy collisionResponseEnum = (CollisionResponsePolicy)collisionResponse.intValue;
if (collisionResponseEnum == CollisionResponsePolicy.Collide ||
collisionResponseEnum == CollisionResponsePolicy.CollideRaiseCollisionEvents)
{
FindToggleAndValueProperties(property, templateValue, "m_Friction", out toggle, out var friction);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.FrictionLabel, toggle, friction, templateAssigned);
FindToggleAndValueProperties(property, templateValue, "m_Restitution", out toggle, out var restitution);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.RestitutionLabel, toggle, restitution, templateAssigned);
}

// collision filter group


var collisionFilterGroup = property.FindPropertyRelative(k_CollisionFilterGroupKey);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
collisionFilterGroup.isExpanded =
EditorGUI.Foldout(position, collisionFilterGroup.isExpanded, Content.CollisionFilterGroupFoldout, true);
if (collisionFilterGroup.isExpanded)
{
++EditorGUI.indentLevel;

FindToggleAndValueProperties(property, templateValue, "m_BelongsToCategories", out toggle, out var belongsTo);


position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.BelongsToLabel, toggle, belongsTo, templateAssigned);
FindToggleAndValueProperties(property, templateValue, "m_CollidesWithCategories", out toggle, out var collidesWith);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.CollidesWithLabel, toggle, collidesWith, templateAssigned);
--EditorGUI.indentLevel;
}
// advanced group
var advancedGroup = property.FindPropertyRelative(k_AdvancedGroupKey);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
advancedGroup.isExpanded =
EditorGUI.Foldout(position, advancedGroup.isExpanded, Content.AdvancedGroupFoldout, true);
if (advancedGroup.isExpanded)
{
++EditorGUI.indentLevel;
FindToggleAndValueProperties(property, templateValue, "m_CustomMaterialTags", out toggle, out var customFlags);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.CustomFlagsLabel, toggle, customFlags, templateAssigned);

--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");

public static readonly string MultipleAssetsTooltip =


L10n.Tr("Multiple {0} assets found. UI will display labels defined in {1}.");

public static readonly GUIContent MultipleAssetsWarning =


new GUIContent { image = EditorGUIUtility.Load("console.warnicon") as Texture };
}
protected abstract int MaxNumCategories { get; }
protected abstract string DefaultCategoryName { get; }
internal string FirstChildPropertyPath { get; set; } // TODO: remove when all usages of bool[] are migrated

string DefaultFormatString => L10n.Tr($"(Undefined {DefaultCategoryName})");


string[] DefaultOptions =>
m_DefaultOptions ?? (
m_DefaultOptions =
Enumerable.Range(0, MaxNumCategories)
.Select(i => string.Format(DefaultFormatString, i))
.ToArray()
);
string[] m_DefaultOptions;

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];

m_Options[i] = $"{i}: {m_Options[i]}";


}
return m_Options;
}

string[] m_Options;

static string GetButtonLabel(int value, IReadOnlyList<string> optionNames)


{
switch (value)
{
case 0:
return Styles.NothingName;
case ~0:
return Styles.EverythingName;
default:
{
for (var i = 0; i < 32; i++)
{
if (value == 1 << i)
return optionNames[i];
}
break;
}
}
return Styles.MixedName;
}

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;
}

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)


{
if (m_NamesAssets?.Length > 1)
position.xMax -= EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;

EditorGUI.BeginProperty(position, label, property);

var controlPosition = EditorGUI.PrefixLabel(position, label);


var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
var showMixed = EditorGUI.showMixedValue;

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];

public static void SoftSlider(


Rect position, GUIContent label, SerializedProperty property,
float sliderMin, float sliderMax,
float textFieldMin, float textFieldMax
)
{
if (property.propertyType != SerializedPropertyType.Float)
{
DisplayCompatibilityWarning(position, label, property.propertyType.ToString());
}
else if (k_SoftSlider == null)
{
EditorGUI.BeginChangeCheck();
EditorGUI.PropertyField(position, property, label);
if (EditorGUI.EndChangeCheck())
property.floatValue = math.clamp(property.floatValue, textFieldMin, textFieldMax);
}
else
{
k_SoftSliderArgs[0] = position;
k_SoftSliderArgs[1] = label;
k_SoftSliderArgs[2] = property.floatValue;
k_SoftSliderArgs[3] = sliderMin;
k_SoftSliderArgs[4] = sliderMax;
k_SoftSliderArgs[5] = textFieldMin;
k_SoftSliderArgs[6] = textFieldMax;
EditorGUI.BeginProperty(position, label, property);
EditorGUI.BeginChangeCheck();
var result = k_SoftSlider.Invoke(null, k_SoftSliderArgs);
if (EditorGUI.EndChangeCheck())
property.floatValue = (float)result;
EditorGUI.EndProperty();
}
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/ManipulatorUtility.cs
using System;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Editor
{
enum MatrixState
{
UniformScale,
NonUniformScale,
ZeroScale,
NotValidTRS
}
static class ManipulatorUtility
{
public static MatrixState GetMatrixState(ref float4x4 localToWorld)
{
if (
localToWorld.c0.w != 0f
|| localToWorld.c1.w != 0f
|| localToWorld.c2.w != 0f
|| localToWorld.c3.w != 1f
)
return MatrixState.NotValidTRS;
var m = new float3x3(localToWorld.c0.xyz, localToWorld.c1.xyz, localToWorld.c2.xyz);
var lossyScale = new float3(math.length(m.c0.xyz), math.length(m.c1.xyz), math.length(m.c2.xyz));
if (math.determinant(m) < 0f)
lossyScale.x *= -1f;
if (math.lengthsq(lossyScale) == 0f)
return MatrixState.ZeroScale;
return math.abs(math.cmax(lossyScale)) - math.abs(math.cmin(lossyScale)) > 0.000001f
? MatrixState.NonUniformScale
: MatrixState.UniformScale;
}
}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/SceneViewUtility.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEngine;

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;
}

const string k_NotificationsPrefKey = "SceneView/Tools/Notifications";


const bool k_DefaultNotifications = true;
const string k_NotificationSpeedPrefKey = "SceneView/Tools/Notification Speed";
const float k_DefaultNotificationsSpeed = 20f;

const float k_NotificationDuration = 1f;


const float k_NotificationFadeInTime = 0.04f;
const float k_NotificationFadeOutTime = 0.2f;
static readonly AnimationCurve k_NotificationFadeCurve = new AnimationCurve
{
keys = new[]
{
new Keyframe { time = 0f, value = 0f, outTangent = 1f / k_NotificationFadeInTime },
new Keyframe { time = k_NotificationFadeInTime, value = 1f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_NotificationDuration - k_NotificationFadeOutTime, value = 1f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_NotificationDuration, value = 0f, inTangent = -1f / k_NotificationFadeOutTime }
},
postWrapMode = WrapMode.Clamp,
preWrapMode = WrapMode.Clamp
};
const float k_IndeterminateProgressCurveDuration = 2f;
static readonly AnimationCurve k_IndeterminateProgressCurveLeftMargin = new AnimationCurve
{
keys = new[]
{
new Keyframe { time = 0f, value = 0f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration / 2f, value = 0.25f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration, value = 1f, inTangent = 0f, outTangent = 0f }
},
postWrapMode = WrapMode.Loop,
preWrapMode = WrapMode.Loop
};
static readonly AnimationCurve k_IndeterminateProgressCurveRightMargin = new AnimationCurve
{
keys = new[]
{
new Keyframe { time = 0f, value = 1f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration / 2f, value = 0f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration, value = 0f, inTangent = 0f, outTangent = 0f }
},
postWrapMode = WrapMode.Loop,
preWrapMode = WrapMode.Loop
};

static string s_StatusMessage;


static DateTime s_StartTime;
static bool s_IsTemporary;
static Func<float> s_GetProgress;

public static void DisplayProgressNotification(string message, Func<float> getProgress) =>


// insert an extra line to make room for progress bar
DisplayNotificationInSceneView(getProgress == null ? message : $"{message}\n", false, getProgress);
public static void DisplayPersistentNotification(string message) =>
DisplayNotificationInSceneView(message, false, null);

public static void DisplayTemporaryNotification(string message) =>


DisplayNotificationInSceneView(message, true, null);

static void DisplayNotificationInSceneView(string message, bool temporary, Func<float> getProgress)


{
s_StatusMessage = message ?? string.Empty;
s_StartTime = DateTime.Now;
s_IsTemporary = temporary;
s_GetProgress = getProgress;
ClearNotificationInSceneView();
SceneView.duringSceneGui += ToolNotificationCallback;
SceneView.RepaintAll();
}

static void ToolNotificationCallback(SceneView obj)


{
if (Camera.current == null)
return;
var duration = math.max(s_StatusMessage.Length, 1)
/ EditorPrefs.GetFloat(k_NotificationSpeedPrefKey, k_DefaultNotificationsSpeed);
var t = (float)(DateTime.Now - s_StartTime).TotalSeconds;
if (
s_IsTemporary
&& (t >= duration || !EditorPrefs.GetBool(k_NotificationsPrefKey, k_DefaultNotifications))
)
{
ClearNotificationInSceneView();
}
else
{
Handles.BeginGUI();
var color = GUI.color;
var progress = s_GetProgress?.Invoke() ?? 0f;
GUI.color *=
new Color(1f, 1f, 1f, math.max(k_NotificationFadeCurve.Evaluate(math.abs(t) / duration), progress));
var rect = new Rect { size = Camera.current.pixelRect.size / EditorGUIUtility.pixelsPerPoint };
using (new GUILayout.AreaScope(rect))
using (new GUILayout.HorizontalScope())
{
GUILayout.FlexibleSpace();
using (new GUILayout.VerticalScope())
{
GUILayout.Space(rect.height * 0.75f);
GUILayout.FlexibleSpace();
var maxWidth = rect.width * 0.5f;
GUILayout.Box(s_StatusMessage, Styles.SceneViewStatusMessage, GUILayout.MaxWidth(maxWidth));
if (s_GetProgress != null)
{
rect = GUILayoutUtility.GetLastRect();
rect = Styles.SceneViewStatusMessage.padding.Remove(rect);
rect.y = rect.yMax - Styles.ProgressBarTrack.fixedHeight;
rect.height = Styles.ProgressBarTrack.fixedHeight;
var c = GUI.color;
GUI.color *= Color.black;
GUI.Box(rect, GUIContent.none, Styles.ProgressBarTrack);
GUI.color = c;
if (progress >= 0f && progress <= 1f)
{
rect.width *= progress;
}
else
{
var w = rect.width;
rect.xMin = rect.xMin + w * k_IndeterminateProgressCurveLeftMargin.Evaluate(t);
rect.xMax = rect.xMax - w * k_IndeterminateProgressCurveRightMargin.Evaluate(t);
}
GUI.Box(rect, GUIContent.none, Styles.ProgressBarIndicator);
}
GUILayout.FlexibleSpace();
}
GUILayout.FlexibleSpace();
}
GUI.color = color;
Handles.EndGUI();
}
SceneView.RepaintAll();
}

public static void ClearNotificationInSceneView() => SceneView.duringSceneGui -= ToolNotificationCallback;


}
}
Platformer/Assets/Scripts/Physics/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/StatusMessageUtility.cs
using System.Collections.Generic;
using System.Linq;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
using UnityObject = UnityEngine.Object;
using LegacyRigidBody = UnityEngine.Rigidbody;

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;

var targetType = c.GetType();


// only bodies (both explicit and implicit static bodies) will emit a message
if (
targetType == typeof(PhysicsBodyAuthoring)
|| targetType == typeof(LegacyRigidBody)
|| c.GetComponent<PhysicsBodyAuthoring>() == null
&& c.GetComponent<LegacyRigidBody>() == null
)
++numChildTargets;
}
switch (numChildTargets)
{
case 0:
return MessageType.None;
case 1:
statusMessage =
L10n.Tr("Target will be un-parented during the conversion process in order to take part in physics simulation.");
return MessageType.Warning;
default:
statusMessage =
L10n.Tr("One or more targets will be un-parented during the conversion process in order to take part in physics simulation.");
return MessageType.Warning;
}
}
public static MessageType GetMatrixStatusMessage(
IReadOnlyList<MatrixState> matrixStates, out string statusMessage
)
{
statusMessage = string.Empty;
if (matrixStates.Contains(MatrixState.NotValidTRS))
{
statusMessage = L10n.Tr(
matrixStates.Count == 1
? "Target's local-to-world matrix is not a valid transformation."
: "One or more targets' local-to-world matrices are not valid transformations."
);
return MessageType.Error;
}
if (matrixStates.Contains(MatrixState.ZeroScale))
{
statusMessage =
L10n.Tr(matrixStates.Count == 1 ? "Target has zero scale." : "One or more targets has zero scale.");
return MessageType.Warning;
}

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;

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;
// If 1, then it is a vertex collision
// If 2, then it is an edge collision
// If 3 or more, then it is a face collision
public int NumberOfContactPoints;
// Estimated impulse applied
public float EstimatedImpulse;
// Average contact point position
public float3 AverageContactPointPosition;

public Details(int numContactPoints, float estimatedImpulse, float3 averageContactPosition)


{
IsValid = (0 < numContactPoints); // Should we add a max check?
NumberOfContactPoints = numContactPoints;
EstimatedImpulse = estimatedImpulse;
AverageContactPointPosition = averageContactPosition;
}
}

// Returns the other entity in EntityPair, if provided with other one


public Entity GetOtherEntity(Entity entity)
{
Assert.IsTrue((entity == EntityA) || (entity == EntityB));
return entity == EntityA ? EntityB : EntityA;
}

// 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);
}
}
Platformer/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;
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;

public ComponentHandles(ref SystemState systemState)


{
EventExcludes = systemState.GetComponentLookup<DummyExcludeComponent>(true);
EventDetails = systemState.GetComponentLookup<StatefulCollisionEventDetails>(true);
EventBuffers = systemState.GetBufferLookup<StatefulCollisionEvent>(false);
}

public void Update(ref SystemState systemState)


{
EventExcludes.Update(ref systemState);
EventBuffers.Update(ref systemState);
EventDetails.Update(ref systemState);
}
}

[BurstCompile]
public void OnCreate(ref SystemState state)
{
m_StateFulEventBuffers = new StatefulSimulationEventBuffers<StatefulCollisionEvent>();
m_StateFulEventBuffers.AllocateBuffers();
state.RequireForUpdate<StatefulCollisionEvent>();

m_Handles = new ComponentHandles(ref state);


}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
m_StateFulEventBuffers.Dispose();
}
[BurstCompile]
public partial struct ClearCollisionEventDynamicBufferJob : IJobEntity
{
public void Execute(ref DynamicBuffer<StatefulCollisionEvent> eventBuffer) => eventBuffer.Clear();
}

[BurstCompile]
public void OnUpdate(ref SystemState state)
{
m_Handles.Update(ref state);

state.Dependency = new ClearCollisionEventDynamicBufferJob()


.ScheduleParallel(state.Dependency);

m_StateFulEventBuffers.SwapBuffers();

var currentEvents = m_StateFulEventBuffers.Current;


var previousEvents = m_StateFulEventBuffers.Previous;
state.Dependency = new StatefulEventCollectionJobs.
CollectCollisionEventsWithDetails
{
CollisionEvents = currentEvents,
PhysicsWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().PhysicsWorld,
EventDetails = m_Handles.EventDetails
}.Schedule(SystemAPI.GetSingleton<SimulationSingleton>(), state.Dependency);

state.Dependency = new StatefulEventCollectionJobs.


ConvertEventStreamToDynamicBufferJob<StatefulCollisionEvent, DummyExcludeComponent>
{
CurrentEvents = currentEvents,
PreviousEvents = previousEvents,
EventBuffers = m_Handles.EventBuffers,

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;

public void AllocateBuffers()


{
Previous = new NativeList<T>(Allocator.Persistent);
Current = new NativeList<T>(Allocator.Persistent);
}
public void Dispose()
{
if (Previous.IsCreated) Previous.Dispose();
if (Current.IsCreated) Current.Dispose();
}

public void SwapBuffers()


{
var tmp = Previous;
Previous = Current;
Current = tmp;
Current.Clear();
}
/// <summary>
/// Returns a sorted combined list of stateful events based on the Previous and Current event frames.
/// Note: ensure that the frame buffers are sorted by calling SortBuffers first.
/// </summary>
/// <param name="statefulEvents"></param>
/// <param name="sortCurrent">Specifies whether the Current events list needs to be sorted first.</param>
public void GetStatefulEvents(NativeList<T> statefulEvents, bool sortCurrent = true) => GetStatefulEvents(Previous, Current, statefulEvents, sortCurrent);

/// <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;

public void Execute(CollisionEvent collisionEvent)


{
var statefulCollisionEvent = new StatefulCollisionEvent(collisionEvent);

// Check if we should calculate the collision details


bool calculateDetails = ForceCalculateDetails;
if (!calculateDetails && EventDetails.HasComponent(collisionEvent.EntityA))
{
calculateDetails = EventDetails[collisionEvent.EntityA].CalculateDetails;
}
if (!calculateDetails && EventDetails.HasComponent(collisionEvent.EntityB))
{
calculateDetails = EventDetails[collisionEvent.EntityB].CalculateDetails;
}
if (calculateDetails)
{
var details = collisionEvent.CalculateDetails(ref PhysicsWorld);
statefulCollisionEvent.CollisionDetails = new StatefulCollisionEvent.Details(
details.EstimatedContactPointPositions.Length,
details.EstimatedImpulse,
details.AverageContactPointPosition);
}

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);

StatefulSimulationEventBuffers<T>.GetStatefulEvents(PreviousEvents, CurrentEvents, statefulEvents);

for (int i = 0; i < statefulEvents.Length; i++)


{
var statefulEvent = statefulEvents[i];

var addToEntityA = EventBuffers.HasBuffer(statefulEvent.EntityA) && (!UseExcludeComponent || !EventExcludeLookup.HasComponent(statefulEvent.EntityA));


var addToEntityB = EventBuffers.HasBuffer(statefulEvent.EntityB) && (!UseExcludeComponent || !EventExcludeLookup.HasComponent(statefulEvent.EntityA));

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; }

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);
}
}
Platformer/Assets/Scripts/Physics/StatefulEvents/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 {}

public class StatefulTriggerEventBufferAuthoring : MonoBehaviour


{
}

class StatefulTriggerEventBufferAuthoringBaker : Baker<StatefulTriggerEventBufferAuthoring>


{
public override void Bake(StatefulTriggerEventBufferAuthoring authoring)
{
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
AddBuffer<StatefulTriggerEvent>(entity);
}
}
}
Platformer/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;
public ComponentHandles(ref SystemState systemState)
{
EventExcludes = systemState.GetComponentLookup<StatefulTriggerEventExclude>(true);
EventBuffers = systemState.GetBufferLookup<StatefulTriggerEvent>();
}

public void Update(ref SystemState systemState)


{
EventExcludes.Update(ref systemState);
EventBuffers.Update(ref systemState);
}
}
[BurstCompile]
public void OnCreate(ref SystemState state)
{
EntityQueryBuilder builder = new EntityQueryBuilder(Allocator.Temp)
.WithAllRW<StatefulTriggerEvent>()
.WithNone<StatefulTriggerEventExclude>();
m_StateFulEventBuffers = new StatefulSimulationEventBuffers<StatefulTriggerEvent>();
m_StateFulEventBuffers.AllocateBuffers();
m_TriggerEventQuery = state.GetEntityQuery(builder);
state.RequireForUpdate(m_TriggerEventQuery);

m_ComponentHandles = new ComponentHandles(ref state);


}
[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);
state.Dependency = new ClearTriggerEventDynamicBufferJob()
.ScheduleParallel(m_TriggerEventQuery, state.Dependency);
m_StateFulEventBuffers.SwapBuffers();

var currentEvents = m_StateFulEventBuffers.Current;


var previousEvents = m_StateFulEventBuffers.Previous;

state.Dependency = new StatefulEventCollectionJobs.CollectTriggerEvents


{
TriggerEvents = currentEvents
}.Schedule(SystemAPI.GetSingleton<SimulationSingleton>(), state.Dependency);

state.Dependency = new StatefulEventCollectionJobs


.ConvertEventStreamToDynamicBufferJob<StatefulTriggerEvent, StatefulTriggerEventExclude>
{
CurrentEvents = currentEvents,
PreviousEvents = previousEvents,
EventBuffers = m_ComponentHandles.EventBuffers,
UseExcludeComponent = true,
EventExcludeLookup = m_ComponentHandles.EventExcludes
}.Schedule(state.Dependency);
}
}
}
Platformer/Assets/Scripts/Player/PlatformerPlayer.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;

[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;

public bool SprintHeld;


public bool RollHeld;
public bool JumpHeld;

public FixedInputEvent JumpPressed;


public FixedInputEvent DashPressed;
public FixedInputEvent CrouchPressed;
public FixedInputEvent RopePressed;
public FixedInputEvent ClimbPressed;
public FixedInputEvent FlyNoCollisionsPressed;
}
Platformer/Assets/Scripts/Player/PlatformerPlayerAuthoring.cs
using UnityEngine;
using Unity.Entities;

[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;

public float RotationSpeed = 10f;


public float RotationSharpness = 999999f;
private float _pitchAngle = 0f;
private Vector3 _planarForward = Vector3.forward;
private Vector3 _currentMoveVelocity = default;
private Vector3 _previousMousePos = default;

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;
}

_currentMoveVelocity = Vector3.Lerp(_currentMoveVelocity, directionalInput * finalMaxSpeed, Mathf.Clamp01(MoveSharpness * Time.deltaTime));


transform.position += _currentMoveVelocity * Time.deltaTime;
}
}
}
StressTest/Assets/Scripts/FramerateMenuManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Rendering;
using System;

public struct FramerateCalculator


{
private int _framesCount;
private float _framesDeltaSum;
private float _minDeltaTimeForAvg;
private float _maxDeltaTimeForAvg;
private string[] _framerateStrings;

public void Initialize()


{
_minDeltaTimeForAvg = Mathf.Infinity;
_maxDeltaTimeForAvg = Mathf.NegativeInfinity;
_framerateStrings = new string[1001];
for (int i = 0; i < _framerateStrings.Length; i++)
{
if (i >= _framerateStrings.Length - 1)
{
_framerateStrings[i] = i.ToString() + "+" + " (<" + (1000f / (float)i).ToString("F") + "ms)";
}
else
{
_framerateStrings[i] = i.ToString() + " (" + (1000f / (float)i).ToString("F") + "ms)";
}
}
}

public void Update()


{
// Regular frames
_framesCount++;
_framesDeltaSum += Time.deltaTime;

// Max and min


if (Time.deltaTime < _minDeltaTimeForAvg)
{
_minDeltaTimeForAvg = Time.deltaTime;
}

if (Time.deltaTime > _maxDeltaTimeForAvg)


{
_maxDeltaTimeForAvg = Time.deltaTime;
}
}
private string GetNumberString(int fps)
{
if (fps < _framerateStrings.Length - 1 && fps >= 0)
{
return _framerateStrings[fps];
}
else
{
return _framerateStrings[_framerateStrings.Length - 1];
}
}
public void PollFramerate(out string avg, out string worst, out string best)
{
avg = GetNumberString(Mathf.RoundToInt(1f / (_framesDeltaSum / _framesCount)));
worst = GetNumberString(Mathf.RoundToInt(1f / _maxDeltaTimeForAvg));
best = GetNumberString(Mathf.RoundToInt(1f / _minDeltaTimeForAvg));

_framesDeltaSum = 0f;
_framesCount = 0;
_minDeltaTimeForAvg = Mathf.Infinity;
_maxDeltaTimeForAvg = Mathf.NegativeInfinity;
}
}

public class FramerateMenuManager : MonoBehaviour


{
[Header("Components")] public Canvas MainCanvas;
public Text AvgFPS;
public Text WorstFPS;
public Text BestFPS;

[Header("Misc")] public float FPSPollRate = 1f;


private FramerateCalculator _framerateCalculator = default;
private float _lastTimePolledFPS = float.MinValue;
private bool _hasVSync = false;
void Start()
{
_framerateCalculator.Initialize();
UpdateRenderSettings();
}

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;

public class FramerateSetter : MonoBehaviour


{
public int Framerate = -1;
public float FixedFramerate = 60;

void Start()
{
Application.targetFrameRate = Framerate;

FixedStepSimulationSystemGroup fixedSystem = World.DefaultGameObjectInjectionWorld.GetOrCreateSystemManaged<FixedStepSimulationSystemGroup>();


fixedSystem.Timestep = 1f / FixedFramerate;
}
}
StressTest/Assets/Scripts/GameObjectNameAuthoring.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Entities;
using UnityEngine;

[Serializable]
public struct GameObjectName : IComponentData
{
public FixedString128Bytes Name;
}

public class GameObjectNameAuthoring : MonoBehaviour


{
public class Baker : Baker<GameObjectNameAuthoring>
{
public override void Bake(GameObjectNameAuthoring authoring)
{
AddComponent(GetEntity(TransformUsageFlags.None), new GameObjectName
{
Name = new FixedString128Bytes(authoring.gameObject.name),
});
}
}
}
StressTest/Assets/Scripts/StressTestManager.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using UnityEngine;
using UnityEngine.UI;
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
using Unity.Collections;
using Unity.Physics;
using Unity.CharacterController;

public class StressTestManager : MonoBehaviour


{
[Header("References")]
public Camera Camera;
public Button SpawnButton;
public InputField SpawnCountInputField;
public Dropdown EnvironmentPrefabDropdown;
public Toggle MultithreadedToggle;
public Toggle PhysicsStepToggle;
public Toggle RenderingToggle;
public Toggle StepHandlingToggle;
public Toggle SlopeChangesToggle;
public Toggle ProjectVelocityOnInitialOverlapsToggle;
public Toggle StatefulHitsToggle;
public Toggle SimulateDynamicToggle;
public Toggle SaveRestoreStateToggle;
public Toggle EnhancedGroundPrecision;
private bool HasInitializedFromEntities;
private World _world;
private EntityManager _entityManager;
private EntityQuery _managerSingletonQuery;

void Start()
{
_world = World.DefaultGameObjectInjectionWorld;
_entityManager = _world.EntityManager;
_managerSingletonQuery = new EntityQueryBuilder(Allocator.Temp)
.WithAll<
StressTestManagerSystem.EnvironmentPrefabs>()
.WithAllRW<
StressTestManagerSystem.Singleton,
StressTestManagerSystem.Event>()
.Build(_entityManager);

// No fixedUpdate, for performance measuring


_world.GetOrCreateSystemManaged<FixedStepSimulationSystemGroup>().RateManager = null;
// Subscribe UI
SpawnButton.onClick.AddListener(SpawnCharacters);
EnvironmentPrefabDropdown.onValueChanged.AddListener(SwitchEnvironment);
MultithreadedToggle.onValueChanged.AddListener(SetMultithreaded);
PhysicsStepToggle.onValueChanged.AddListener(SetPhysicsStep);
RenderingToggle.onValueChanged.AddListener(SetRendering);
StepHandlingToggle.onValueChanged.AddListener(SetStepHandling);
SlopeChangesToggle.onValueChanged.AddListener(SetSlopeChanges);
ProjectVelocityOnInitialOverlapsToggle.onValueChanged.AddListener(SetProjectVelocityOnOverlaps);
StatefulHitsToggle.onValueChanged.AddListener(SetStatefulHits);
SimulateDynamicToggle.onValueChanged.AddListener(SetSimulateDynamicBody);
SaveRestoreStateToggle.onValueChanged.AddListener(SetSaveRestoreState);
EnhancedGroundPrecision.onValueChanged.AddListener(SetEnhancedGroundPrecision);
}
public bool TryGetManagerSingleton(out Entity entity)
{
if (_managerSingletonQuery.HasSingleton<StressTestManagerSystem.Singleton>())
{
entity = _managerSingletonQuery.GetSingletonEntity();
return true;
}

entity = default;
return false;
}

public ref StressTestManagerSystem.Singleton GetManagerSingletonRef()


{
return ref _managerSingletonQuery.GetSingletonRW<StressTestManagerSystem.Singleton>().ValueRW;
}
public DynamicBuffer<StressTestManagerSystem.EnvironmentPrefabs> GetManagerSingletonEnvPrefabsBuffer()
{
return _managerSingletonQuery.GetSingletonBuffer<StressTestManagerSystem.EnvironmentPrefabs>();
}

public DynamicBuffer<StressTestManagerSystem.Event> GetManagerSingletonEventsBuffer()


{
return _managerSingletonQuery.GetSingletonBuffer<StressTestManagerSystem.Event>();
}
void Update()
{
if (!HasInitializedFromEntities)
{
if (TryGetManagerSingleton(out Entity entity))
{
DynamicBuffer<StressTestManagerSystem.EnvironmentPrefabs> environmentPrefabs = GetManagerSingletonEnvPrefabsBuffer();

// Add environment prefabs to dropdown choices


for (int i = 0; i < environmentPrefabs.Length; i++)
{
EnvironmentPrefabDropdown.AddOptions(new List<Dropdown.OptionData>
{
new Dropdown.OptionData(_entityManager.GetComponentData<GameObjectName>(environmentPrefabs[i].Prefab).Name.Value),
});
}
// Initial setup
SwitchEnvironment(EnvironmentPrefabDropdown.value);
SetMultithreaded(MultithreadedToggle.isOn);
SetRendering(RenderingToggle.isOn);

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();
}
}

private void ApplyCharacterSettings()


{
SetStepHandling(StepHandlingToggle.isOn);
SetSlopeChanges(SlopeChangesToggle.isOn);
SetProjectVelocityOnOverlaps(ProjectVelocityOnInitialOverlapsToggle.isOn);
SetStatefulHits(StatefulHitsToggle.isOn);
SetSimulateDynamicBody(SimulateDynamicToggle.isOn);
SetSaveRestoreState(SaveRestoreStateToggle.isOn);
}
public void SwitchEnvironment(int index)
{
ref var singleton = ref GetManagerSingletonRef();
singleton.EnvironmentIndex = index;

var eventsBuffer = GetManagerSingletonEventsBuffer();


eventsBuffer.Add(new StressTestManagerSystem.Event { Type = StressTestManagerSystem.EventType.SpawnEnvironment });
}
public void SetMultithreaded(bool active)
{
ref var singleton = ref GetManagerSingletonRef();
singleton.Multithreaded = active;
}
public void SetPhysicsStep(bool active)
{
ref var singleton = ref GetManagerSingletonRef();
singleton.PhysicsStep = active;
var eventsBuffer = GetManagerSingletonEventsBuffer();
eventsBuffer.Add(new StressTestManagerSystem.Event { Type = StressTestManagerSystem.EventType.ApplyPhysicsStep });
}
public void SetRendering(bool active)
{
Camera.enabled = active;
}

public void SetStepHandling(bool active)


{
ref var singleton = ref GetManagerSingletonRef();
singleton.StepHandling = active;
var eventsBuffer = GetManagerSingletonEventsBuffer();
eventsBuffer.Add(new StressTestManagerSystem.Event { Type = StressTestManagerSystem.EventType.ApplyStepHandling });
}
public void SetSlopeChanges(bool active)
{
ref var singleton = ref GetManagerSingletonRef();
singleton.SlopeChanges = active;

var eventsBuffer = GetManagerSingletonEventsBuffer();


eventsBuffer.Add(new StressTestManagerSystem.Event { Type = StressTestManagerSystem.EventType.ApplySlopeChanges });
}
public void SetProjectVelocityOnOverlaps(bool active)
{
ref var singleton = ref GetManagerSingletonRef();
singleton.ProjectVelocityOnOverlaps = active;
var eventsBuffer = GetManagerSingletonEventsBuffer();
eventsBuffer.Add(new StressTestManagerSystem.Event { Type = StressTestManagerSystem.EventType.ApplyProjectVelocityOnOverlaps });
}

public void SetStatefulHits(bool active)


{
ref var singleton = ref GetManagerSingletonRef();
singleton.StatefulHits = active;
var eventsBuffer = GetManagerSingletonEventsBuffer();
eventsBuffer.Add(new StressTestManagerSystem.Event { Type = StressTestManagerSystem.EventType.ApplyStatefulHits });
}
public unsafe void SetSimulateDynamicBody(bool value)
{
ref var singleton = ref GetManagerSingletonRef();
singleton.SimulateDynamic = value;

var eventsBuffer = GetManagerSingletonEventsBuffer();


eventsBuffer.Add(new StressTestManagerSystem.Event { Type = StressTestManagerSystem.EventType.ApplySimulateDynamic });
}
public void SetSaveRestoreState(bool value)
{
ref var singleton = ref GetManagerSingletonRef();
singleton.SaveRestoreState = value;
var eventsBuffer = GetManagerSingletonEventsBuffer();
eventsBuffer.Add(new StressTestManagerSystem.Event { Type = StressTestManagerSystem.EventType.ApplySaveRestoreState });
}

public void SetEnhancedGroundPrecision(bool value)


{
ref var singleton = ref GetManagerSingletonRef();
singleton.EnhancedGroundPrecision = value;
var eventsBuffer = GetManagerSingletonEventsBuffer();
eventsBuffer.Add(new StressTestManagerSystem.Event { Type = StressTestManagerSystem.EventType.ApplyEnhancedGroundPrecision });
}
}
[BurstCompile]
public partial struct StressTestManagerSystem : ISystem
{
public enum EventType
{
SpawnCharacters,
SpawnEnvironment,
ApplyPhysicsStep,
ApplyStepHandling,
ApplySlopeChanges,
ApplyProjectVelocityOnOverlaps,
ApplyStatefulHits,
ApplySimulateDynamic,
ApplySaveRestoreState,
ApplyEnhancedGroundPrecision,
}

public struct Singleton : IComponentData


{
public Entity CharacterPrefab;
public bool SpawnCharacters;
public int CharacterCount;
public float CharacterSpacing;
public bool SpawnEnvironment;
public int EnvironmentIndex;
public bool Multithreaded;
public bool PhysicsStep;

public bool StepHandling;


public bool SlopeChanges;
public bool ProjectVelocityOnOverlaps;
public bool StatefulHits;
public bool SimulateDynamic;
public bool SaveRestoreState;
public bool EnhancedGroundPrecision;
}
public struct EnvironmentPrefabs : IBufferElementData
{
public Entity Prefab;
}
public struct SpawnedCharacter : IBufferElementData
{
public Entity Entity;
}

public struct SpawnedEnvironment : IBufferElementData


{
public Entity Entity;
}
public struct Event : IBufferElementData
{
public EventType Type;
}
private EntityQuery _characterQuery;
[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<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, new StressTestManagerSystem.Singleton


{
CharacterPrefab = GetEntity(authoring.CharacterPrefab, TransformUsageFlags.None),
CharacterSpacing = authoring.CharacterSpacing,
});
DynamicBuffer<StressTestManagerSystem.EnvironmentPrefabs> environmentsBuffer = AddBuffer<StressTestManagerSystem.EnvironmentPrefabs>(selfEntity);
for (int i = 0; i < authoring.EnvironmentPrefabs.Count; i++)
{
GameObject go = authoring.EnvironmentPrefabs[i];
if (go != null)
{
environmentsBuffer.Reinterpret<Entity>().Add(GetEntity(go, TransformUsageFlags.None));
}
}
AddBuffer<StressTestManagerSystem.SpawnedCharacter>(selfEntity);
AddBuffer<StressTestManagerSystem.SpawnedEnvironment>(selfEntity);
AddBuffer<StressTestManagerSystem.Event>(selfEntity);
}
}
}
StressTest/Assets/Scripts/Character/StressTestCharacterAspect.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 StressTestCharacterUpdateContext
{
// 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".
public KinematicCharacterStateSave stateSave;
public void OnSystemCreate(ref SystemState state)
{ }
public void OnSystemUpdate(ref SystemState state)
{ }
}
public readonly partial struct StressTestCharacterAspect : IAspect, IKinematicCharacterProcessor<StressTestCharacterUpdateContext>
{
public readonly KinematicCharacterAspect CharacterAspect;
public readonly RefRW<StressTestCharacterComponent> CharacterComponent;
public readonly RefRW<StressTestCharacterControl> CharacterControl;
public void PhysicsUpdate(ref StressTestCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
ref StressTestCharacterComponent 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);
if (characterComponent.UseSaveRestoreState)
{
context.stateSave.Save(CharacterAspect);
CharacterAspect.LocalTransform.ValueRW.Position += math.up();
context.stateSave.Restore(CharacterAspect);
}
// 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();
if (characterComponent.UseStatefulHits)
{
CharacterAspect.Update_ProcessStatefulCharacterHits();
}
}
private void HandleVelocityControl(ref StressTestCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
float deltaTime = baseContext.Time.DeltaTime;
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref StressTestCharacterComponent characterComponent = ref CharacterComponent.ValueRW;
ref StressTestCharacterControl 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 StressTestCharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
{
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
ref StressTestCharacterComponent characterComponent = ref CharacterComponent.ValueRW;
ref StressTestCharacterControl 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 StressTestCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext)
{
ref KinematicCharacterBody characterBody = ref CharacterAspect.CharacterBody.ValueRW;
CharacterAspect.Default_UpdateGroundingUp(ref characterBody);
}
public bool CanCollideWithHit(
ref StressTestCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
in BasicHit hit)
{
return PhysicsUtilities.IsCollidable(hit.Material);
}
public bool IsGroundedOnHit(
ref StressTestCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
in BasicHit hit,
int groundingEvaluationType)
{
StressTestCharacterComponent characterComponent = CharacterComponent.ValueRO;
return CharacterAspect.Default_IsGroundedOnHit(
in this,
ref context,
ref baseContext,
in hit,
in characterComponent.StepAndSlopeHandling,
groundingEvaluationType);
}
public void OnMovementHit(
ref StressTestCharacterUpdateContext 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;
StressTestCharacterComponent characterComponent = CharacterComponent.ValueRO;
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 StressTestCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
ref PhysicsMass characterMass,
ref PhysicsMass otherMass,
BasicHit hit)
{
}
public void ProjectVelocityOnHits(
ref StressTestCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
ref float3 velocity,
ref bool characterIsGrounded,
ref BasicHit characterGroundHit,
in DynamicBuffer<KinematicVelocityProjectionHit> velocityProjectionHits,
float3 originalVelocityDirection)
{
StressTestCharacterComponent characterComponent = CharacterComponent.ValueRO;
CharacterAspect.Default_ProjectVelocityOnHits(
ref velocity,
ref characterIsGrounded,
ref characterGroundHit,
in velocityProjectionHits,
originalVelocityDirection,
characterComponent.StepAndSlopeHandling.ConstrainVelocityToGroundPlane);
}
#endregion
}
StressTest/Assets/Scripts/Character/StressTestCharacterAuthoring.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 StressTestCharacterAuthoring : MonoBehaviour
{
public AuthoringKinematicCharacterProperties CharacterProperties = AuthoringKinematicCharacterProperties.GetDefault();
public StressTestCharacterComponent Character = StressTestCharacterComponent.GetDefault();

public class Baker : Baker<StressTestCharacterAuthoring>


{
public override void Bake(StressTestCharacterAuthoring authoring)
{
KinematicCharacterUtilities.BakeCharacter(this, authoring, authoring.CharacterProperties);

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;

public BasicStepAndSlopeHandlingParameters StepAndSlopeHandling;


public bool UseStatefulHits;
public bool UseSaveRestoreState;

public static StressTestCharacterComponent GetDefault()


{
return new StressTestCharacterComponent
{
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 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;

bool multithreaded = SystemAPI.GetSingleton<StressTestManagerSystem.Singleton>().Multithreaded;

float3 worldMoveVector = math.mul(quaternion.Euler(math.up() * (float)SystemAPI.Time.ElapsedTime), math.forward());


StressTestCharacterControlJob job = new StressTestCharacterControlJob
{
WorldMoveVector = worldMoveVector,
};
if (multithreaded)
{
job.ScheduleParallel();
}
else
{
job.Schedule();
}
}

[BurstCompile]
public partial struct StressTestCharacterControlJob : IJobEntity
{
public float3 WorldMoveVector;

void Execute(ref StressTestCharacterControl characterControl)


{
characterControl.MoveVector = WorldMoveVector;
}
}
}
StressTest/Assets/Scripts/Character/StressTestCharacterSystems.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 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);

_context = new StressTestCharacterUpdateContext();


_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)
{
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>());

StressTestCharacterPhysicsUpdateJob job = new StressTestCharacterPhysicsUpdateJob


{
Context = _context,
BaseContext = _baseContext,
};
if (multithreaded)
{
job.ScheduleParallel();
}
else
{
job.Schedule();
}
}
[BurstCompile]
public partial struct StressTestCharacterPhysicsUpdateJob : IJobEntity, IJobEntityChunkBeginEnd
{
public StressTestCharacterUpdateContext Context;
public KinematicCharacterUpdateContext BaseContext;
void Execute(StressTestCharacterAspect 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 StressTestCharacterVariableUpdateSystem : 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);

_context = new StressTestCharacterUpdateContext();


_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)
{
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;

void Execute(StressTestCharacterAspect 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)
{ }
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/AssemblyInfo.cs
using System.Runtime.CompilerServices;

[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;

public RigidTransform worldFromA => LocalBody == null


? RigidTransform.identity
: Math.DecomposeRigidBodyTransform(LocalBody.transform.localToWorldMatrix);

public RigidTransform worldFromB => ConnectedBody == null


? RigidTransform.identity
: Math.DecomposeRigidBodyTransform(ConnectedBody.transform.localToWorldMatrix);

public Entity EntityA { get; set; }

public Entity EntityB { get; set; }

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);

public override int GetHashCode() => Value;


}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/EditorInitialization.cs
#if UNITY_EDITOR
using System.Linq;
using UnityEditor;
namespace Unity.Physics.Authoring
{
[InitializeOnLoad]
class EditorInitialization
{
static readonly string k_CustomDefine = "UNITY_PHYSICS_CUSTOM";
static EditorInitialization()
{
var definesStr = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
var defines = definesStr.Split(';').ToList();
var found = defines.Find(define => define.Equals(k_CustomDefine));
if (found == null)
{
defines.Add(k_CustomDefine);
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup,
string.Join(";", defines.ToArray()));
}
}
}
}
#endif
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsCategoryNames.cs
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Physics Category Names", fileName = "Physics Category Names", order = 507)]
public sealed class PhysicsCategoryNames : ScriptableObject, ITagNames
{
PhysicsCategoryNames() {}

IReadOnlyList<string> ITagNames.TagNames => CategoryNames;

public IReadOnlyList<string> CategoryNames => m_CategoryNames;


[SerializeField]
string[] m_CategoryNames = Enumerable.Range(0, 32).Select(i => string.Empty).ToArray();

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);

public override int GetHashCode() => unchecked((int)Value);


}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsMaterialProperties.cs
using System;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
interface IPhysicsMaterialProperties
{
CollisionResponsePolicy CollisionResponse { get; set; }
PhysicsMaterialCoefficient Friction { get; set; }

PhysicsMaterialCoefficient Restitution { get; set; }


PhysicsCategoryTags BelongsTo { get; set; }
PhysicsCategoryTags CollidesWith { get; set; }
// TODO: Enable Mass Factors?
// TODO: Surface Velocity?
CustomPhysicsMaterialTags CustomTags { get; set; }
}

interface IInheritPhysicsMaterialProperties : IPhysicsMaterialProperties


{
PhysicsMaterialTemplate Template { get; set; }
bool OverrideCollisionResponse { get; set; }
bool OverrideFriction { get; set; }
bool OverrideRestitution { get; set; }
bool OverrideBelongsTo { get; set; }
bool OverrideCollidesWith { get; set; }
bool OverrideCustomTags { get; set; }
}

[Serializable]
public struct PhysicsMaterialCoefficient
{
[SoftRange(0f, 1f, TextFieldMax = float.MaxValue)]
public float Value;
public Material.CombinePolicy CombineMode;
}

abstract class OverridableValue<T> where T : struct


{
public bool Override { get => m_Override; set => m_Override = value; }
[SerializeField]
bool m_Override;

public T Value
{
get => m_Value;
set
{
m_Value = value;
Override = true;
}
}
[SerializeField]
T m_Value;

public void OnValidate() => OnValidate(ref m_Value);


protected virtual void OnValidate(ref T 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 PhysicsMaterialTemplate Template


{
get => m_Template;
set => m_Template = m_SupportsTemplate ? value : null;
}
[SerializeField]
[Tooltip("Assign a template to use its values.")]
PhysicsMaterialTemplate m_Template;

static T Get<T>(OverridableValue<T> value, T? templateValue) where T : struct =>


value.Override || templateValue == null ? value.Value : templateValue.Value;

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 };

internal static void OnValidate(ref PhysicsMaterialProperties material, bool supportsTemplate)


{
material.UpgradeVersionIfNecessary();

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();
}

const int k_LatestVersion = 1;

[SerializeField]
int m_SerializedVersion = 0;

void ISerializationCallbackReceiver.OnBeforeSerialize() {}
void ISerializationCallbackReceiver.OnAfterDeserialize() => UpgradeVersionIfNecessary();

internal static bool s_SuppressUpgradeWarnings;

#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


}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsMaterialTemplate.cs
using UnityEngine;

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);

void Reset() => OnValidate();


void OnValidate() => PhysicsMaterialProperties.OnValidate(ref m_Value, false);
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsRenderEntityAuthoring.cs
using UnityEngine;
using Unity.Entities;
using Unity.Physics.GraphicsIntegration;
namespace Unity.Physics.Authoring
{
[AddComponentMenu("Entities/Physics/Physics Render Entity")]
[DisallowMultipleComponent]
public sealed class PhysicsRenderEntityAuthoring : MonoBehaviour
{
[Tooltip("Specifies an Entity in a different branch of the hierarchy that holds the graphical representation of this PhysicsShape.")]
public GameObject RenderEntity;
}
internal class PhysicsRenderEntityBaker : Baker<PhysicsRenderEntityAuthoring>
{
public override void Bake(PhysicsRenderEntityAuthoring authoring)
{
var renderEntity = new PhysicsRenderEntity { Entity = GetEntity(authoring.RenderEntity, TransformUsageFlags.Dynamic) };
var entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, renderEntity);
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/PhysicsBodyAuthoring.cs
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
/// <summary> The physics body authoring. This class cannot be inherited. </summary>
#if UNITY_2021_2_OR_NEWER
[Icon(k_IconPath)]
#endif
[AddComponentMenu("Entities/Physics/Physics Body")]
[DisallowMultipleComponent]
public sealed class PhysicsBodyAuthoring : MonoBehaviour
{
const string k_IconPath = "Packages/com.unity.physics/Unity.Physics.Editor/Editor Default Resources/Icons/d_Rigidbody@64.png";

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;
}

static readonly int[] k_NextAxis = { 1, 2, 0 };


public ShapeType ShapeType => m_ShapeType;
[SerializeField]
ShapeType m_ShapeType = ShapeType.Box;
[SerializeField]
float3 m_PrimitiveCenter;
[SerializeField]
float3 m_PrimitiveSize = new float3(1f, 1f, 1f);
[SerializeField]
EulerAngles m_PrimitiveOrientation = EulerAngles.Default;

[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;
}

public PhysicsCategoryTags BelongsTo


{
get => m_Material.BelongsTo;
set => m_Material.BelongsTo = value;
}
public bool OverrideCollidesWith
{
get => m_Material.OverrideCollidesWith;
set => m_Material.OverrideCollidesWith = value;
}
public PhysicsCategoryTags CollidesWith
{
get => m_Material.CollidesWith;
set => m_Material.CollidesWith = value;
}
public bool OverrideCustomTags
{
get => m_Material.OverrideCustomTags;
set => m_Material.OverrideCustomTags = value;
}
public CustomPhysicsMaterialTags CustomTags { get => m_Material.CustomTags; set => m_Material.CustomTags = value; }
[SerializeField]
PhysicsMaterialProperties m_Material = new PhysicsMaterialProperties(true);

public BoxGeometry GetBoxProperties() => GetBoxProperties(out _);


internal BoxGeometry GetBoxProperties(out EulerAngles orientation)
{
orientation = m_PrimitiveOrientation;
return new BoxGeometry
{
Center = m_PrimitiveCenter,
Size = m_PrimitiveSize,
Orientation = m_PrimitiveOrientation,
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius
};
}
void GetCylindricalProperties(
CylindricalProperties props,
out float3 center, out float height, out float radius, out EulerAngles orientation,
bool rebuildOrientation
)
{
center = m_PrimitiveCenter;
var lookVector = math.mul(m_PrimitiveOrientation, new float3 { [props.Axis] = 1f });
// use previous axis so forward will prefer up
var upVector = math.mul(m_PrimitiveOrientation, new float3 { [k_NextAxis[k_NextAxis[props.Axis]]] = 1f });
orientation = m_PrimitiveOrientation;
if (rebuildOrientation && props.Axis != 2)
orientation.SetValue(quaternion.LookRotation(lookVector, upVector));
radius = props.Radius;
height = props.Height;
}
internal CapsuleGeometryAuthoring GetCapsuleProperties()
{
GetCylindricalProperties(
m_Capsule, out var center, out var height, out var radius, out var orientationEuler, m_ShapeType != ShapeType.Capsule
);
return new CapsuleGeometryAuthoring
{
OrientationEuler = orientationEuler,
Center = center,
Height = height,
Radius = radius
};
}
public CylinderGeometry GetCylinderProperties() => GetCylinderProperties(out _);
internal CylinderGeometry GetCylinderProperties(out EulerAngles orientation)
{
GetCylindricalProperties(
m_Cylinder, out var center, out var height, out var radius, out orientation, m_ShapeType != ShapeType.Cylinder
);
return new CylinderGeometry
{
Center = center,
Height = height,
Radius = radius,
Orientation = orientation,
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius,
SideCount = m_CylinderSideCount
};
}
public SphereGeometry GetSphereProperties(out quaternion orientation)
{
var result = GetSphereProperties(out EulerAngles euler);
orientation = euler;
return result;
}
internal SphereGeometry GetSphereProperties(out EulerAngles orientation)
{
orientation = m_PrimitiveOrientation;
return new SphereGeometry
{
Center = m_PrimitiveCenter,
Radius = m_SphereRadius
};
}
public void GetPlaneProperties(out float3 center, out float2 size, out quaternion orientation)
{
GetPlaneProperties(out center, out size, out EulerAngles euler);
orientation = euler;
}
internal void GetPlaneProperties(out float3 center, out float2 size, out EulerAngles orientation)
{
center = m_PrimitiveCenter;
orientation = m_PrimitiveOrientation;
if (m_ShapeType == ShapeType.Plane)
{
size = m_PrimitiveSize.xz;
return;
}
UpdateCapsuleAxis();
var look = m_Capsule.Axis;
var nextAx = k_NextAxis[look];
var prevAx = k_NextAxis[k_NextAxis[look]];
var ax2 = m_PrimitiveSize[nextAx] > m_PrimitiveSize[prevAx] ? nextAx : prevAx;
size = new float2(m_PrimitiveSize[ax2], m_PrimitiveSize[look]);
var up = k_NextAxis[ax2] == look ? k_NextAxis[look] : k_NextAxis[ax2];
var offset = quaternion.LookRotation(new float3 { [look] = 1f }, new float3 { [up] = 1f });
orientation.SetValue(math.mul(m_PrimitiveOrientation, offset));
}
static readonly HashSet<int> s_BoneIDs = new HashSet<int>();
static readonly HashSet<Transform> s_BonesInHierarchy = new HashSet<Transform>();
static readonly List<Vector3> s_Vertices = new List<Vector3>(65535);
static readonly List<int> s_Indices = new List<int>(65535);
static UnityMesh ReusableBakeMesh =>
s_ReusableBakeMesh ??
(s_ReusableBakeMesh = new UnityMesh { hideFlags = HideFlags.HideAndDontSave });
static UnityMesh s_ReusableBakeMesh;
public void GetConvexHullProperties(NativeList<float3> pointCloud) =>
GetConvexHullProperties(pointCloud, true, default, default, default, default);
internal void GetConvexHullProperties(
NativeList<float3> pointCloud, bool validate,
NativeList<HashableShapeInputs> inputs, NativeList<int> allSkinIndices, NativeList<float> allBlendShapeWeights,
HashSet<UnityMesh> meshAssets
)
{
if (pointCloud.IsCreated)
pointCloud.Clear();
if (inputs.IsCreated)
inputs.Clear();
if (allSkinIndices.IsCreated)
allSkinIndices.Clear();
if (allBlendShapeWeights.IsCreated)
allBlendShapeWeights.Clear();
meshAssets?.Clear();
if (m_CustomMesh != null)
{
if (validate && !m_CustomMesh.IsValidForConversion(gameObject))
return;
AppendMeshPropertiesToNativeBuffers(
transform.localToWorldMatrix, m_CustomMesh, pointCloud, default, validate, inputs, meshAssets
);
}
else
{
using (var scope = new GetActiveChildrenScope<MeshFilter>(this, transform))
{
foreach (var meshFilter in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(meshFilter, validate))
{
AppendMeshPropertiesToNativeBuffers(
meshFilter.transform.localToWorldMatrix, meshFilter.sharedMesh, pointCloud, default, validate, inputs, meshAssets
);
}
}
}

using (var skinnedPoints = new NativeList<float3>(8192, Allocator.Temp))


using (var skinnedInputs = new NativeList<HashableShapeInputs>(8, Allocator.Temp))
{
GetAllSkinnedPointsInHierarchyBelongingToShape(
this, skinnedPoints, validate, skinnedInputs, allSkinIndices, allBlendShapeWeights
);
if (pointCloud.IsCreated)
pointCloud.AddRange(skinnedPoints.AsArray());
if (inputs.IsCreated)
inputs.AddRange(skinnedInputs.AsArray());
}
}
}

internal static void GetAllSkinnedPointsInHierarchyBelongingToShape(


PhysicsShapeAuthoring shape, NativeList<float3> pointCloud, bool validate,
NativeList<HashableShapeInputs> inputs, NativeList<int> allIncludedIndices, NativeList<float> allBlendShapeWeights
)
{
if (pointCloud.IsCreated)
pointCloud.Clear();
if (inputs.IsCreated)
inputs.Clear();
// get all the transforms that belong to this shape
s_BonesInHierarchy.Clear();
using (var scope = new GetActiveChildrenScope<Transform>(shape, shape.transform))
{
foreach (var bone in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(bone))
s_BonesInHierarchy.Add(bone);
}
}
// find all skinned mesh renderers in which this shape's transform might be a bone
using (var scope = new GetActiveChildrenScope<SkinnedMeshRenderer>(shape, shape.transform.root))
{
foreach (var skin in scope.Buffer)
{
var mesh = skin.sharedMesh;
if (
!skin.enabled
|| mesh == null
|| validate && !mesh.IsValidForConversion(shape.gameObject)
|| !scope.IsChildActiveAndBelongsToShape(skin)
)
continue;
// get indices of this shape's transform hierarchy in skinned mesh's bone array
s_BoneIDs.Clear();
var bones = skin.bones;
for (int i = 0, count = bones.Length; i < count; ++i)
{
if (s_BonesInHierarchy.Contains(bones[i]))
s_BoneIDs.Add(i);
}
if (s_BoneIDs.Count == 0)
continue;
// sample the vertices
if (pointCloud.IsCreated)
{
skin.BakeMesh(ReusableBakeMesh);
ReusableBakeMesh.GetVertices(s_Vertices);
}
// add all vertices weighted to at least one bone in this shape's transform hierarchy
var bonesPerVertex = mesh.GetBonesPerVertex(); // Allocator.None
var weights = mesh.GetAllBoneWeights(); // Allocator.None
var vertexIndex = 0;
var weightsOffset = 0;
var shapeFromSkin = math.mul(shape.transform.worldToLocalMatrix, skin.transform.localToWorldMatrix);
var includedIndices = new NativeList<int>(mesh.vertexCount, Allocator.Temp);
foreach (var weightCount in bonesPerVertex)
{
var totalWeight = 0f;
for (var i = 0; i < weightCount; ++i)
{
var weight = weights[weightsOffset + i];
if (s_BoneIDs.Contains(weight.boneIndex))
totalWeight += weight.weight;
}
if (totalWeight > shape.m_MinimumSkinnedVertexWeight)
{
if (pointCloud.IsCreated)
pointCloud.Add(math.mul(shapeFromSkin, new float4(s_Vertices[vertexIndex], 1f)).xyz);
includedIndices.Add(vertexIndex);
}
weightsOffset += weightCount;
++vertexIndex;
}
if (!inputs.IsCreated || !allIncludedIndices.IsCreated || !allBlendShapeWeights.IsCreated)
continue;
var blendShapeWeights = new NativeArray<float>(mesh.blendShapeCount, Allocator.Temp);
for (var i = 0; i < blendShapeWeights.Length; ++i)
blendShapeWeights[i] = skin.GetBlendShapeWeight(i);
var data = HashableShapeInputs.FromSkinnedMesh(
mesh, shapeFromSkin, includedIndices.AsArray(), allIncludedIndices, blendShapeWeights, allBlendShapeWeights
);
inputs.Add(data);
}
}
s_BonesInHierarchy.Clear();
}
public void GetMeshProperties(NativeList<float3> vertices, NativeList<int3> triangles) =>
GetMeshProperties(vertices, triangles, true, default);
internal void GetMeshProperties(
NativeList<float3> vertices, NativeList<int3> triangles, bool validate, NativeList<HashableShapeInputs> inputs, HashSet<UnityMesh> meshAssets = null
)
{
if (vertices.IsCreated)
vertices.Clear();
if (triangles.IsCreated)
triangles.Clear();
if (inputs.IsCreated)
inputs.Clear();
meshAssets?.Clear();
if (m_CustomMesh != null)
{
if (validate && !m_CustomMesh.IsValidForConversion(gameObject))
return;
AppendMeshPropertiesToNativeBuffers(
transform.localToWorldMatrix, m_CustomMesh, vertices, triangles, validate, inputs, meshAssets
);
}
else
{
using (var scope = new GetActiveChildrenScope<MeshFilter>(this, transform))
{
foreach (var meshFilter in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(meshFilter, validate))
{
AppendMeshPropertiesToNativeBuffers(
meshFilter.transform.localToWorldMatrix, meshFilter.sharedMesh, vertices, triangles, validate, inputs, meshAssets
);
}
}
}
}
}

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();

void Sync(ref CylindricalProperties props)


{
props.Height = m_PrimitiveSize[props.Axis];
props.Radius = 0.5f * math.max(
m_PrimitiveSize[k_NextAxis[props.Axis]],
m_PrimitiveSize[k_NextAxis[k_NextAxis[props.Axis]]]
);
}
void SyncCapsuleProperties()
{
if (m_ShapeType == ShapeType.Box || m_ShapeType == ShapeType.Plane)
UpdateCapsuleAxis();
else
m_Capsule.Axis = 2;
Sync(ref m_Capsule);
}
void SyncCylinderProperties()
{
if (m_ShapeType == ShapeType.Box || m_ShapeType == ShapeType.Plane)
UpdateCylinderAxis();
else
m_Cylinder.Axis = 2;
Sync(ref m_Cylinder);
}
void SyncSphereProperties()
{
m_SphereRadius = 0.5f * math.cmax(m_PrimitiveSize);
}
public void SetBox(BoxGeometry geometry)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(geometry.Orientation);
SetBox(geometry, euler);
}
internal void SetBox(BoxGeometry geometry, EulerAngles orientation)
{
m_ShapeType = ShapeType.Box;
m_PrimitiveCenter = geometry.Center;
m_PrimitiveSize = math.max(geometry.Size, new float3());
m_PrimitiveOrientation = orientation;
m_ConvexHullGenerationParameters.BevelRadius = geometry.BevelRadius;
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
internal void SetCapsule(CapsuleGeometryAuthoring geometry)
{
m_ShapeType = ShapeType.Capsule;
m_PrimitiveCenter = geometry.Center;
m_PrimitiveOrientation = geometry.OrientationEuler;
var radius = math.max(0f, geometry.Radius);
var height = math.max(0f, geometry.Height);
m_PrimitiveSize = new float3(radius * 2f, radius * 2f, height);
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetCylinder(CylinderGeometry geometry)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(geometry.Orientation);
SetCylinder(geometry, euler);
}
internal void SetCylinder(CylinderGeometry geometry, EulerAngles orientation)
{
m_ShapeType = ShapeType.Cylinder;
m_PrimitiveCenter = geometry.Center;
m_PrimitiveOrientation = orientation;
geometry.Radius = math.max(0f, geometry.Radius);
geometry.Height = math.max(0f, geometry.Height);
m_PrimitiveSize = new float3(geometry.Radius * 2f, geometry.Radius * 2f, geometry.Height);

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;
}

bool GetMeshes(PhysicsShapeAuthoring shape, out List<UnityEngine.Mesh> meshes, out List<float4x4> childrenToShape)


{
meshes = new List<UnityMesh>();
childrenToShape = new List<float4x4>();
if (shape.CustomMesh != null)
{
meshes.Add(shape.CustomMesh);
childrenToShape.Add(float4x4.identity);
}
// Try to get all the meshes in the children
var meshFilters = GetComponentsInChildren<MeshFilter>();
foreach (var meshFilter in meshFilters)
{
if (meshFilter != null && meshFilter.sharedMesh != null)
{
var shapeAuthoring = GetComponent<PhysicsShapeAuthoring>(meshFilter);
if (shapeAuthoring != null && shapeAuthoring != shape)
{
// Skip this case, since it will be treated independently
continue;
}
meshes.Add(meshFilter.sharedMesh);
// Don't calculate the children to shape if not needed, to avoid approximation that could prevent collider to be shared
if (shape.transform.localToWorldMatrix.Equals(meshFilter.transform.localToWorldMatrix))
childrenToShape.Add(float4x4.identity);
else
{
var transform = math.mul(shape.transform.worldToLocalMatrix, meshFilter.transform.localToWorldMatrix);
childrenToShape.Add(transform);
}
DependsOn(meshes.Last());
}
}
return meshes.Count > 0;
}
UnityEngine.Mesh CombineMeshes(PhysicsShapeAuthoring shape, List<UnityEngine.Mesh> meshes, List<float4x4> childrenToShape)
{
var instances = new List<CombineInstance>();
var numVertices = 0;
for (var i = 0; i < meshes.Count; ++i)
{
var currentMesh = meshes[i];
var currentChildToShape = childrenToShape[i];
if (!currentMesh.IsValidForConversion(shape.gameObject))
{
throw new InvalidOperationException(
$"Mesh '{currentMesh}' assigned on {shape.name} is not readable. Ensure that you have enabled Read/Write on its import settings."
);
}
// Combine submeshes manually
numVertices += meshes[i].vertexCount;
var combinedSubmeshes = new UnityEngine.Mesh();
combinedSubmeshes.vertices = currentMesh.vertices;
var combinedIndices = new List<int>();
for (int indexSubMesh = 0; indexSubMesh < meshes[i].subMeshCount; ++indexSubMesh)
{
combinedIndices.AddRange(currentMesh.GetIndices(indexSubMesh));
}
combinedSubmeshes.SetIndices(combinedIndices, MeshTopology.Triangles, 0);
combinedSubmeshes.RecalculateNormals();
var instance = new CombineInstance
{
mesh = combinedSubmeshes,
transform = currentChildToShape,
};
instances.Add(instance);
}
var mesh = new UnityEngine.Mesh();
mesh.indexFormat = numVertices > UInt16.MaxValue ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
mesh.CombineMeshes(instances.ToArray());
mesh.RecalculateBounds();
return mesh;
}
private ShapeComputationDataBaking GenerateComputationData(PhysicsShapeAuthoring shape, ColliderInstanceBaking colliderInstance, Entity colliderEntity)
{
var res = new ShapeComputationDataBaking
{
Instance = colliderInstance,
Material = ProduceMaterial(shape),
CollisionFilter = ProduceCollisionFilter(shape),
ForceUniqueIdentifier = shape.ForceUnique ? (uint)shape.GetInstanceID() : 0u
};
var transform = shape.transform;
var localToWorld = transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
EulerAngles orientation;
res.ShapeType = shape.ShapeType;
switch (shape.ShapeType)
{
case ShapeType.Box:
{
res.BoxProperties = shape.GetBoxProperties(out orientation)
.BakeToBodySpace(localToWorld, shapeToWorld, orientation);
break;
}
case ShapeType.Capsule:
{
res.CapsuleProperties = shape.GetCapsuleProperties()
.BakeToBodySpace(localToWorld, shapeToWorld)
.ToRuntime();
break;
}
case ShapeType.Sphere:
{
res.SphereProperties = shape.GetSphereProperties(out orientation)
.BakeToBodySpace(localToWorld, shapeToWorld, ref orientation);
break;
}
case ShapeType.Cylinder:
{
res.CylinderProperties = shape.GetCylinderProperties(out orientation)
.BakeToBodySpace(localToWorld, shapeToWorld, orientation);
break;
}
case ShapeType.Plane:
{
shape.GetPlaneProperties(out var center, out var size, out orientation);
PhysicsShapeExtensions.BakeToBodySpace(
center, size, orientation, localToWorld, shapeToWorld,
out res.PlaneVertices.c0, out res.PlaneVertices.c1, out res.PlaneVertices.c2, out res.PlaneVertices.c3
);
break;
}
case ShapeType.ConvexHull:
{
res.ConvexHullProperties.Filter = res.CollisionFilter;
res.ConvexHullProperties.Material = res.Material;
res.ConvexHullProperties.GenerationParameters = shape.ConvexHullGenerationParameters.ToRunTime();
CreateMeshAuthoringData(shape, colliderEntity);
break;
}
case ShapeType.Mesh:
{
res.MeshProperties.Filter = res.CollisionFilter;
res.MeshProperties.Material = res.Material;
CreateMeshAuthoringData(shape, colliderEntity);
break;
}
}
return res;
}
private void CreateMeshAuthoringData(PhysicsShapeAuthoring shape, Entity colliderEntity)
{
if (GetMeshes(shape, out var meshes, out var childrenToShape))
{
// Combine all detected meshes into a single one
var mesh = CombineMeshes(shape, meshes, childrenToShape);
if (!mesh.IsValidForConversion(shape.gameObject))
{
throw new InvalidOperationException(
$"Mesh '{mesh}' assigned on {shape.name} is not readable. Ensure that you have enabled Read/Write on its import settings."
);
}
var bakeFromShape = shape.GetLocalToShapeMatrix();
var meshBakingData = new PhysicsMeshAuthoringData()
{
Convex = shape.ShapeType == ShapeType.ConvexHull,
Mesh = mesh,
BakeFromShape = bakeFromShape,
MeshBounds = mesh.bounds,
ChildToShape = float4x4.identity
};
AddComponent(colliderEntity, meshBakingData);
}
else
{
throw new InvalidOperationException(
$"No {nameof(PhysicsShapeAuthoring.CustomMesh)} or {nameof(MeshFilter.sharedMesh)} assigned on {shape.name}."
);
}
}
public override void Bake(PhysicsShapeAuthoring authoring)
{
var shapeBakingData = new PhysicsColliderAuthoringData();
// First pass
Profiler.BeginSample("Collect Inputs from Authoring Components");
if (ShouldConvertShape(authoring))
{
// We can have multiple Colliders of the same type on the same game object, so instead of adding the components to the baking entity
// we add the components to an additional entity. These new entities will be processed by the baking system
var colliderEntity = CreateAdditionalEntity(TransformUsageFlags.None, true);
shapeBakingData.ShapeComputationalData = GetInputDataFromAuthoringComponent(authoring, colliderEntity);
AddComponent(colliderEntity, shapeBakingData);
// The data will be filled in by the BaseShapeBakingSystem, but we add it here so it gets reverted from the entity if the collider component is deleted
AddComponent(colliderEntity, new PhysicsColliderBakedData()
{
BodyEntity = shapeBakingData.ShapeComputationalData.Instance.BodyEntity,
BodyFromShape = shapeBakingData.ShapeComputationalData.Instance.BodyFromShape,
ChildEntity = shapeBakingData.ShapeComputationalData.Instance.ChildEntity,
// It is a leaf if the Shape Entity equals Body Entity
IsLeafEntityBody = (shapeBakingData.ShapeComputationalData.Instance.ShapeEntity.Equals(shapeBakingData.ShapeComputationalData.Instance.BodyEntity))
});
}
Profiler.EndSample();
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Joints/BallAndSocketJoint.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

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;

public float3 PositionLocal;


public float3 PositionInConnectedEntity;

public virtual void UpdateAuto()


{
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
PositionInConnectedEntity = math.transform(bFromA, PositionLocal);
}
}
}
public abstract class JointBaker<T> : Baker<T> where T : BaseJoint
{
protected PhysicsConstrainedBodyPair GetConstrainedBodyPair(BaseJoint authoring)
{
return new PhysicsConstrainedBodyPair(
GetEntity(TransformUsageFlags.Dynamic),
authoring.ConnectedBody == null ? Entity.Null : GetEntity(authoring.ConnectedBody, TransformUsageFlags.Dynamic),
authoring.EnableCollision
);
}
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 uint GetWorldIndex(Component c)


{
uint worldIndex = 0;
if (c)
{
var physicsBody = GetComponent<PhysicsBodyAuthoring>(c);
if (physicsBody != null)
{
worldIndex = physicsBody.WorldIndex;
}
}
return worldIndex;
}
public uint GetWorldIndexFromBaseJoint(BaseJoint authoring)
{
var physicsBody = GetComponent<PhysicsBodyAuthoring>(authoring);
uint worldIndex = physicsBody.WorldIndex;
if (authoring.ConnectedBody == null)
{
return worldIndex;
}
var connectedBody = GetComponent<PhysicsBodyAuthoring>(authoring.ConnectedBody);
if (connectedBody != null)
{
Assertions.Assert.AreEqual(worldIndex, connectedBody.WorldIndex);
}
return worldIndex;
}
public void CreateJointEntities(uint worldIndex, PhysicsConstrainedBodyPair constrainedBodyPair, NativeArray<PhysicsJoint> joints, NativeList<Entity> newJointEntities)
{
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;
var entity = GetEntity(TransformUsageFlags.Dynamic);
for (var i = 0; i < joints.Length; ++i)
{
var jointEntity = CreateAdditionalEntity(TransformUsageFlags.Dynamic);
AddSharedComponent(jointEntity, new PhysicsWorldIndex(worldIndex));
AddComponent(jointEntity, constrainedBodyPair);
AddComponent(jointEntity, joints[i]);
newJointEntities.Add(jointEntity);
if (GetComponent<ModifyJointLimitsAuthoring>() != null)
{
AddComponent(jointEntity, new JointEntityBaking()
{
Entity = entity
});
AddSharedComponentManaged(jointEntity, new ModifyJointLimits());
}
}
if (multipleJoints)
{
// set companion buffers for new joints
for (var i = 0; i < joints.Length; ++i)
{
var companions = AddBuffer<PhysicsJointCompanion>(newJointEntities[i]);
for (var j = 0; j < joints.Length; ++j)
{
if (i == j)
continue;
companions.Add(new PhysicsJointCompanion {JointEntity = newJointEntities[j]});
}
}
}
}
}
class BallAndSocketJointBaker : JointBaker<BallAndSocketJoint>
{
public override void Bake(BallAndSocketJoint authoring)
{
authoring.UpdateAuto();
var physicsJoint = PhysicsJoint.CreateBallAndSocket(authoring.PositionLocal, authoring.PositionInConnectedEntity);
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/BaseJoint.cs
using Unity.Mathematics;

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;

for (var i = 0; i < joints.Length; ++i)


{
var jointEntity = CreateAdditionalEntity(TransformUsageFlags.Dynamic);
AddSharedComponent(jointEntity, new PhysicsWorldIndex(worldIndex));
AddComponent(jointEntity, constrainedBodyPair);
AddComponent(jointEntity, joints[i]);
newJointEntities.Add(jointEntity);
}
if (multipleJoints)
{
// set companion buffers for new joints
for (var i = 0; i < joints.Length; ++i)
{
var companions = AddBuffer<PhysicsJointCompanion>(newJointEntities[i]);
for (var j = 0; j < joints.Length; ++j)
{
if (i == j)
continue;
companions.Add(new PhysicsJointCompanion {JointEntity = newJointEntities[j]});
}
}
}
}
protected PhysicsConstrainedBodyPair GetConstrainedBodyPair(LimitDOFJoint authoring)
{
return new PhysicsConstrainedBodyPair(
GetEntity(TransformUsageFlags.Dynamic),
authoring.ConnectedBody == null ? Entity.Null : GetEntity(authoring.ConnectedBody, TransformUsageFlags.Dynamic),
authoring.EnableCollision
);
}
public uint GetWorldIndex(Component c)
{
uint worldIndex = 0;
var physicsBody = GetComponent<PhysicsBodyAuthoring>(c);
if (physicsBody != null)
{
worldIndex = physicsBody.WorldIndex;
}
return worldIndex;
}
public override void Bake(LimitDOFJoint authoring)
{
if (!math.any(authoring.LockLinearAxes) && !math.any(authoring.LockAngularAxes))
return;
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
PhysicsJoint physicsJoint = authoring.CreateLimitDOFJoint(bFromA);
var worldIndex = GetWorldIndex(authoring);
CreateJointEntity(
worldIndex,
GetConstrainedBodyPair(authoring),
physicsJoint
);
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Joints/LimitedDistanceJoint.cs
using Unity.Entities;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class LimitedDistanceJoint : BallAndSocketJoint
{
public float MinDistance;
public float MaxDistance;
}
class LimitedDistanceJointBaker : JointBaker<LimitedDistanceJoint>
{
public override void Bake(LimitedDistanceJoint authoring)
{
authoring.UpdateAuto();
var physicsJoint = PhysicsJoint.CreateLimitedDistance(authoring.PositionLocal, authoring.PositionInConnectedEntity, new FloatRange(authoring.MinDistance, authoring.MaxDistance
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/LimitedHingeJoint.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class LimitedHingeJoint : FreeHingeJoint
{
// Editor only settings
[HideInInspector]
public bool EditLimits;

public float3 PerpendicularAxisLocal;


public float3 PerpendicularAxisInConnectedEntity;
public float MinAngle;
public float MaxAngle;

public override void UpdateAuto()


{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
HingeAxisInConnectedEntity = math.mul(bFromA.rot, HingeAxisLocal);
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
}
}
}

class LimitedHingeJointBaker : JointBaker<LimitedHingeJoint>


{
public override void Bake(LimitedHingeJoint authoring)
{
authoring.UpdateAuto();

var physicsJoint = PhysicsJoint.CreateLimitedHinge(


new BodyFrame
{
Axis = math.normalize(authoring.HingeAxisLocal),
PerpendicularAxis = math.normalize(authoring.PerpendicularAxisLocal),
Position = authoring.PositionLocal
},
new BodyFrame
{
Axis = math.normalize(authoring.HingeAxisInConnectedEntity),
PerpendicularAxis = math.normalize(authoring.PerpendicularAxisInConnectedEntity),
Position = authoring.PositionInConnectedEntity
},
math.radians(new FloatRange(authoring.MinAngle, authoring.MaxAngle))
);

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/ModifyJointLimitsAuthoring.cs
using System;
using System.Collections.Generic;
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 UnityEngine;
using LegacyJoint = UnityEngine.Joint;
using FloatRange = Unity.Physics.Math.FloatRange;

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);

public override int GetHashCode() =>


unchecked((AngularRangeScalar.GetHashCode() * 397) ^ LinearRangeScalar.GetHashCode());
}

// an authoring component to add to a GameObject with one or more Joint


public class ModifyJointLimitsAuthoring : MonoBehaviour
{
public ParticleSystem.MinMaxCurve AngularRangeScalar = new ParticleSystem.MinMaxCurve(
1f,
min: new AnimationCurve(
new Keyframe(0f, 0f, 0f, 0f),
new Keyframe(2f, -2f, 0f, 0f),
new Keyframe(4f, 0f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
},
max: new AnimationCurve(
new Keyframe(0f, 1f, 0f, 0f),
new Keyframe(2f, -1f, 0f, 0f),
new Keyframe(4f, 1f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
}
);
public ParticleSystem.MinMaxCurve LinearRangeScalar = new ParticleSystem.MinMaxCurve(
1f,
min: new AnimationCurve(
new Keyframe(0f, 1f, 0f, 0f),
new Keyframe(2f, 0.5f, 0f, 0f),
new Keyframe(4f, 1f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
},
max: new AnimationCurve(
new Keyframe(0f, 0.5f, 0f, 0f),
new Keyframe(2f, 0f, 0f, 0f),
new Keyframe(4f, 0.5f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
}
);
}
[BakingType]
public class ModifyJointLimitsBakingData : IComponentData
{
public ParticleSystem.MinMaxCurve AngularRangeScalar;
public ParticleSystem.MinMaxCurve LinearRangeScalar;
}
class ModifyJointLimitsBaker : Baker<ModifyJointLimitsAuthoring>
{
public override void Bake(ModifyJointLimitsAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponentObject(entity, new ModifyJointLimitsBakingData
{
AngularRangeScalar = authoring.AngularRangeScalar,
LinearRangeScalar = authoring.LinearRangeScalar
});
}
}
// after joints have been converted, find the entities they produced and add ModifyJointLimits to them
[UpdateAfter(typeof(EndJointBakingSystem))]
[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
partial struct ModifyJointLimitsBakingSystem : ISystem
{
private EntityQuery _ModifyJointLimitsBakingDataQuery;
private EntityQuery _JointEntityBakingQuery;
public void OnCreate(ref SystemState state)
{
_ModifyJointLimitsBakingDataQuery = state.GetEntityQuery(new EntityQueryDesc
{
All = new[] {ComponentType.ReadOnly<ModifyJointLimitsBakingData>()},
Options = EntityQueryOptions.IncludeDisabledEntities | EntityQueryOptions.IncludePrefab
});
_JointEntityBakingQuery = state.GetEntityQuery(new EntityQueryDesc
{
All = new[] {ComponentType.ReadOnly<JointEntityBaking>()}
});

_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;

public override void UpdateAuto()


{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
AxisInConnectedEntity = math.mul(bFromA.rot, AxisLocal);
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
}
}
}

class PrismaticJointBaker : JointBaker<PrismaticJoint>


{
public override void Bake(PrismaticJoint authoring)
{
authoring.UpdateAuto();

var physicsJoint = PhysicsJoint.CreatePrismatic(


new BodyFrame
{
Axis = authoring.AxisLocal,
PerpendicularAxis = authoring.PerpendicularAxisLocal,
Position = authoring.PositionLocal
},
new BodyFrame
{
Axis = authoring.AxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
new FloatRange(authoring.MinDistanceOnAxis, authoring.MaxDistanceOnAxis)
);
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/RagdollJoint.cs
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class RagdollJoint : BallAndSocketJoint
{
const int k_LatestVersion = 1;
// Editor only settings
[HideInInspector]
public bool EditAxes;
[HideInInspector]
public bool EditLimits;

[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;

public override void UpdateAuto()


{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
OrientationInConnectedEntity = math.mul(bFromA.rot, OrientationLocal);
}
{
OrientationLocal = math.normalize(OrientationLocal);
OrientationInConnectedEntity = math.normalize(OrientationInConnectedEntity);
}
}
}

class RigidJointBaker : JointBaker<RigidJoint>


{
public override void Bake(RigidJoint authoring)
{
authoring.UpdateAuto();

var physicsJoint = PhysicsJoint.CreateFixed(


new RigidTransform(authoring.OrientationLocal, authoring.PositionLocal),
new RigidTransform(authoring.OrientationInConnectedEntity, authoring.PositionInConnectedEntity)
);

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/Motors/AngularVelocityMotor.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

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

var joint = PhysicsJoint.CreateAngularVelocityMotor(


new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularLocal,
Position = authoring.PivotPosition
},
new BodyFrame
{
Axis = authoring.HingeAxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
math.radians(authoring.TargetSpeed),
authoring.MaxImpulseAppliedByMotor
);
joint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntity(worldIndex, constraintBodyPair, joint);
}
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Motors/LinearVelocityMotor.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
public class LinearVelocityMotor : BaseJoint
{
[Tooltip("An offset from the center of the body with the motor (bodyA), 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 at this speed from the initial position of bodyA, along the Direction of Movement, in m/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 AxisInConnectedEntity;
private float3 PerpendicularAxisInConnectedEntity;
class LinearVelocityMotorBaker : JointBaker<LinearVelocityMotor>
{
public override void Bake(LinearVelocityMotor authoring)
{
float3 axisInB = math.normalize(authoring.DirectionOfMovement);

RigidTransform aFromB = math.mul(math.inverse(authoring.worldFromA), authoring.worldFromB);


float3 axisInA = math.mul(aFromB.rot, axisInB); //motor axis relative to bodyA
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.AnchorPosition); //position of motored body relative to Connected Entity in world space
authoring.AxisInConnectedEntity = axisInB; //motor axis in Connected Entity space

// Always calculate the perpendicular axes


Math.CalculatePerpendicularNormalized(axisInA, out var perpendicularAxisLocal, out _);
authoring.PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, perpendicularAxisLocal); //perp motor axis in Connected Entity space
var joint = PhysicsJoint.CreateLinearVelocityMotor(
new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularAxisLocal,
Position = authoring.AnchorPosition
},
new BodyFrame
{
Axis = authoring.AxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
authoring.TargetSpeed,
authoring.MaxImpulseAppliedByMotor
);
joint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntity(worldIndex, constraintBodyPair, joint);
}
}
}
}
StressTest/Assets/Scripts/Custom Physics Authoring/Unity.Physics.Custom/Motors/PositionMotor.cs
using Unity.Mathematics;
using UnityEngine;
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;
class PositionMotorBaker : JointBaker<PositionMotor>
{
public override void Bake(PositionMotor authoring)
{
float3 axisInB = math.normalize(authoring.DirectionOfMovement);

RigidTransform aFromB = math.mul(math.inverse(authoring.worldFromA), authoring.worldFromB);


float3 axisInA = math.mul(aFromB.rot, axisInB); //motor axis relative to bodyA

RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);


authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.AnchorPosition); //position of motored body relative to Connected Entity in world space
authoring.AxisInConnectedEntity = axisInB; //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
var joint = PhysicsJoint.CreatePositionMotor(
new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularLocal,
Position = authoring.AnchorPosition
},
new BodyFrame
{
Axis = authoring.AxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
authoring.TargetDistance,
authoring.MaxImpulseAppliedByMotor
);
joint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);

var constraintBodyPair = GetConstrainedBodyPair(authoring);


uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntity(worldIndex, constraintBodyPair, joint);
}
}
}
}
StressTest/Assets/Scripts/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
var joint = PhysicsJoint.CreateRotationalMotor(
new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularLocal,
Position = authoring.PivotPosition
},
new BodyFrame
{
Axis = authoring.HingeAxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
math.radians(authoring.TargetAngle),
authoring.MaxImpulseAppliedByMotor
);
joint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntity(worldIndex, constraintBodyPair, joint);
}
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/BakeGeometryJobs.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
static partial class PhysicsShapeExtensions
{
static void MakeZAxisPrimaryBasis(ref int3 basisPriority)
{
if (basisPriority[1] == 2)
basisPriority = basisPriority.yxz;
else if (basisPriority[2] == 2)
basisPriority = basisPriority.zxy;
}
#region Box
[BurstCompile]
internal struct BakeBoxJob : IJob
{
public NativeArray<BoxGeometry> Box;

// TODO: make members PascalCase after merging static query fixes


public float4x4 localToWorld;
public float4x4 shapeToWorld;
public EulerAngles orientation;
public static float4x4 GetBakeToShape(float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center,
ref EulerAngles orientation)
{
float4x4 bakeToShape;
float4x4 rotationMatrix = float4x4.identity;
var basisPriority = k_DefaultAxisPriority;
var sheared = localToWorld.HasShear();
if (localToWorld.HasNonUniformScale() || sheared)
{
if (sheared)
{
var transformScale = localToWorld.DecomposeScale();
var basisToWorld =
GetBasisToWorldMatrix(localToWorld, center, orientation, transformScale);
basisPriority = GetBasisAxisPriority(basisToWorld);
}
rotationMatrix = new float4x4(
new float4 { [basisPriority[2]] = 1 },
new float4 { [basisPriority[1]] = 1 },
new float4 { [basisPriority[0]] = 1 },
new float4 { [3] = 1 }
);
}
bakeToShape = GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center,
ref orientation, 1f, basisPriority);
bakeToShape = math.mul(bakeToShape, rotationMatrix);
return bakeToShape;
}
public void Execute()
{
var center = Box[0].Center;
var size = Box[0].Size;
var bevelRadius = Box[0].BevelRadius;
var bakeToShape = GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientation);
bakeToShape = math.mul(bakeToShape, float4x4.Scale(size));
var scale = bakeToShape.DecomposeScale();
size = scale;
Box[0] = new BoxGeometry
{
Center = center,
Orientation = orientation,
Size = size,
BevelRadius = math.clamp(bevelRadius, 0f, 0.5f * math.cmin(size))
};
}
}
#endregion
#region Capsule
[BurstCompile]
internal struct BakeCapsuleJob : IJob
{
public NativeArray<CapsuleGeometryAuthoring> Capsule;
// TODO: make members PascalCase after merging static query fixes
public float4x4 localToWorld;
public float4x4 shapeToWorld;
public static float4x4 GetBakeToShape(float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center,
ref EulerAngles orientation)
{
var basisPriority = k_DefaultAxisPriority;
var sheared = localToWorld.HasShear();
if (localToWorld.HasNonUniformScale() || sheared)
{
if (sheared)
{
var transformScale = localToWorld.DecomposeScale();
var basisToWorld = GetBasisToWorldMatrix(localToWorld, center, orientation, transformScale);
basisPriority = GetBasisAxisPriority(basisToWorld);
}
MakeZAxisPrimaryBasis(ref basisPriority);
}
return GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center, ref orientation, 1f,
basisPriority);
}
public void Execute()
{
var radius = Capsule[0].Radius;
var center = Capsule[0].Center;
var height = Capsule[0].Height;
var orientationEuler = Capsule[0].OrientationEuler;
var bakeToShape = GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientationEuler);
var scale = bakeToShape.DecomposeScale();
radius *= math.cmax(scale.xy);
height = math.max(0, height * scale.z);
Capsule[0] = new CapsuleGeometryAuthoring
{
OrientationEuler = orientationEuler,
Center = center,
Height = height,
Radius = radius
};
}
}
#endregion
#region Cylinder
[BurstCompile]
internal struct BakeCylinderJob : IJob
{
public NativeArray<CylinderGeometry> Cylinder;
// TODO: make members PascalCase after merging static query fixes
public float4x4 localToWorld;
public float4x4 shapeToWorld;
public EulerAngles orientation;
public static float4x4 GetBakeToShape(float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center,
ref EulerAngles orientation)
{
var basisPriority = k_DefaultAxisPriority;
var sheared = localToWorld.HasShear();
if (localToWorld.HasNonUniformScale() || sheared)
{
if (sheared)
{
var transformScale = localToWorld.DecomposeScale();
var basisToWorld = GetBasisToWorldMatrix(localToWorld, center, orientation, transformScale);
basisPriority = GetBasisAxisPriority(basisToWorld);
}
MakeZAxisPrimaryBasis(ref basisPriority);
}
return GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center, ref orientation, 1f,
basisPriority);
}
public void Execute()
{
var center = Cylinder[0].Center;
var height = Cylinder[0].Height;
var radius = Cylinder[0].Radius;
var bevelRadius = Cylinder[0].BevelRadius;
var bakeToShape = GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientation);
var scale = bakeToShape.DecomposeScale();
height *= scale.z;
radius *= math.cmax(scale.xy);
Cylinder[0] = new CylinderGeometry
{
Center = center,
Orientation = orientation,
Height = height,
Radius = radius,
BevelRadius = math.min(bevelRadius, math.min(height * 0.5f, radius)),
SideCount = Cylinder[0].SideCount
};
}
}
internal static CylinderGeometry BakeToBodySpace(
this CylinderGeometry cylinder, float4x4 localToWorld, float4x4 shapeToWorld, EulerAngles orientation
)
{
using (var geometry = new NativeArray<CylinderGeometry>(1, Allocator.TempJob) { [0] = cylinder })
{
var job = new BakeCylinderJob
{
Cylinder = geometry,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld,
orientation = orientation
};
job.Run();
return geometry[0];
}
}
#endregion

#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;

public void Execute()


{
var v = Vertices[0];
GetPlanePoints(center, size, orientation, out v.c0, out v.c1, out v.c2, out v.c3);
var localToShape = math.mul(math.inverse(shapeToWorld), localToWorld);
v.c0 = math.mul(localToShape, new float4(v.c0, 1f)).xyz;
v.c1 = math.mul(localToShape, new float4(v.c1, 1f)).xyz;
v.c2 = math.mul(localToShape, new float4(v.c2, 1f)).xyz;
v.c3 = math.mul(localToShape, new float4(v.c3, 1f)).xyz;
Vertices[0] = v;
}
}

internal static void BakeToBodySpace(


float3 center, float2 size, EulerAngles orientation, float4x4 localToWorld, float4x4 shapeToWorld,
out float3 vertex0, out float3 vertex1, out float3 vertex2, out float3 vertex3
)
{
using (var geometry = new NativeArray<float3x4>(1, Allocator.TempJob))
{
var job = new BakePlaneJob
{
Vertices = geometry,
center = center,
size = size,
orientation = orientation,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld
};
job.Run();
vertex0 = geometry[0].c0;
vertex1 = geometry[0].c1;
vertex2 = geometry[0].c2;
vertex3 = geometry[0].c3;
}
}

internal static void GetPlanePoints(


float3 center, float2 size, EulerAngles orientation,
out float3 vertex0, out float3 vertex1, out float3 vertex2, out float3 vertex3
)
{
var sizeYUp = math.float3(size.x, 0, size.y);
vertex0 = center + math.mul(orientation, sizeYUp * math.float3(-0.5f, 0, 0.5f));
vertex1 = center + math.mul(orientation, sizeYUp * math.float3(0.5f, 0, 0.5f));
vertex2 = center + math.mul(orientation, sizeYUp * math.float3(0.5f, 0, -0.5f));
vertex3 = center + math.mul(orientation, sizeYUp * math.float3(-0.5f, 0, -0.5f));
}

#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;

public uint ForceUniqueIdentifier;


public ConvexHullGenerationParameters GenerationParameters;
public Material Material;
public CollisionFilter CollisionFilter;
public float4x4 BakeFromShape;

[ReadOnly] public NativeArray<HashableShapeInputs> Inputs;


[ReadOnly] public NativeArray<int> AllSkinIndices;
[ReadOnly] public NativeArray<float> AllBlendShapeWeights;

public void Execute()


{
Result[0] = HashableShapeInputs.GetHash128(
ForceUniqueIdentifier, GenerationParameters, Material, CollisionFilter, BakeFromShape,
Inputs, AllSkinIndices, AllBlendShapeWeights
);
}
}
#endregion

#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;

var bakeToShape = BakeCapsuleJobExtension.GetBakeToShape(shape, center, capsule.OrientationEuler);


var scale = bakeToShape.DecomposeScale();
var newRadius = radius / math.cmax(scale.xy);
if (math.abs(capsule.Radius - newRadius) > kMinimumChange)
capsule.Radius = newRadius;
height /= scale.z;
if (math.abs(math.length(capsule.Height - height)) > kMinimumChange)
capsule.Height = height;
shape.SetCapsule(capsule);
}
internal static CapsuleGeometryAuthoring BakeToBodySpace(
this CapsuleGeometryAuthoring capsule, float4x4 localToWorld, float4x4 shapeToWorld
)
{
using (var geometry = new NativeArray<CapsuleGeometryAuthoring>(1, Allocator.TempJob) { [0] = capsule })
{
var job = new BakeCapsuleJob
{
Capsule = geometry,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld
};
job.Run();
return geometry[0];
}
}
public static class BakeCylinderJobExtension
{
internal static float4x4 GetBakeToShape(PhysicsShapeAuthoring shape, float3 center, EulerAngles orientation)
{
var transform = shape.transform;
var localToWorld = (float4x4)transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
return BakeCylinderJob.GetBakeToShape(localToWorld, shapeToWorld, ref center,
ref orientation);
}
}
public static CylinderGeometry GetBakedCylinderProperties(this PhysicsShapeAuthoring shape)
{
var cylinder = shape.GetCylinderProperties(out var orientation);
return cylinder.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(),
orientation);
}
public static void SetBakedSphereRadius(this PhysicsShapeAuthoring shape, float radius)
{
var sphere = shape.GetSphereProperties(out EulerAngles eulerAngles);
var center = sphere.Center;
radius = math.abs(radius);
var basisToWorld = GetBasisToWorldMatrix(shape.transform.localToWorldMatrix, center, eulerAngles, 1f);
var basisPriority = basisToWorld.HasShear() ? GetBasisAxisPriority(basisToWorld) : k_DefaultAxisPriority;
var bakeToShape = GetPrimitiveBakeToShapeMatrix(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(), ref center, ref eulerAngles, 1f, basisPriority);
var scale = math.cmax(bakeToShape.DecomposeScale());
var newRadius = radius / scale;
sphere.Radius = newRadius;
shape.SetSphere(sphere);
}
public static void SetBakedPlaneSize(this PhysicsShapeAuthoring shape, float2 size)
{
shape.GetPlaneProperties(out var center, out var planeSize, out EulerAngles orientation);
var prevSize = math.abs(planeSize);
size = math.abs(size);
if (math.abs(size[0] - prevSize[0]) < kMinimumChange) size[0] = prevSize[0];
if (math.abs(size[1] - prevSize[1]) < kMinimumChange) size[1] = prevSize[1];
planeSize = size;
shape.SetPlane(center, planeSize, orientation);
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/CapsuleGeometryAuthoring.cs
using System;
using Unity.Mathematics;
using UnityEngine;

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;

/// <summary> The radius of the capsule. </summary>


///
/// <value> The radius. </value>
public float Radius { get => m_Radius; set => m_Radius = value; }
[SerializeField]
float m_Radius;
public bool Equals(CapsuleGeometryAuthoring other)
{
return m_Height.Equals(other.m_Height)
&& m_Center.Equals(other.m_Center)
&& m_Radius.Equals(other.m_Radius)
&& m_OrientationEuler.Equals(other.m_OrientationEuler);
}
public override int GetHashCode()
{
return unchecked((int)math.hash(
new float3x3(
Center,
m_OrientationEuler.Value,
new float3((float)m_OrientationEuler.RotationOrder, m_Height, m_Radius)
)
));
}
}
public static class CapsuleGeometryAuthoringExtensions
{
/// <summary>
/// Construct a CapsuleGeometryAuthoring instance from a run-time CapsuleGeometry instance.
/// </summary>
public static CapsuleGeometryAuthoring ToAuthoring(this CapsuleGeometry input)
{
var orientationEuler = EulerAngles.Default;
orientationEuler.SetValue(quaternion.LookRotationSafe(input.Vertex1 - input.Vertex0, math.up()));
return new CapsuleGeometryAuthoring
{
Height = input.GetHeight(),
OrientationEuler = orientationEuler,
Center = input.GetCenter(),
Radius = input.Radius
};
}
/// <summary>
/// Construct a run-time CapsuleGeometry instance from a CapsuleGeometryAuthoring instance.
/// </summary>
public static CapsuleGeometry ToRuntime(this CapsuleGeometryAuthoring input)
{
var halfHeight = 0.5f * input.Height;
var halfDistance = halfHeight - input.Radius;
var axis = math.normalize(math.mul(input.Orientation, new float3 { z = 1f }));
var halfAxis = axis * halfDistance;
var vertex0 = input.Center + halfAxis;
var vertex1 = input.Center - halfAxis;
return new CapsuleGeometry
{
Vertex0 = vertex0,
Vertex1 = vertex1,
Radius = input.Radius
};
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/ConvexHullGenerationParametersExtensions.cs
using System;
using Unity.Collections;
using Unity.Mathematics;

namespace Unity.Physics.Authoring
{
public static class ConvexHullGenerationParametersExtensions
{
// recommended simplification tolerance is at least 1 centimeter
internal const float k_MinRecommendedSimplificationTolerance = 0.01f;

internal static void InitializeToRecommendedAuthoringValues(


ref this ConvexHullGenerationParameters generationParameters, NativeArray<float3> points
)
{
generationParameters = ConvexHullGenerationParameters.Default.ToAuthoring();

if (points.Length <= 1)
return;

var bounds = new Aabb { Min = points[0], Max = points[0] };


for (var i = 1; i < points.Length; ++i)
bounds.Include(points[i]);
generationParameters.SimplificationTolerance = math.max(
k_MinRecommendedSimplificationTolerance,
ConvexHullGenerationParameters.Default.SimplificationTolerance * math.cmax(bounds.Extents)
);
// TODO: initialize other properties based on input points?
}

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);
}

public static ConvexHullGenerationParameters ToAuthoring(this ConvexHullGenerationParameters generationParameters)


{
generationParameters.MinimumAngle = math.degrees(generationParameters.MinimumAngle);
return generationParameters;
}

public static ConvexHullGenerationParameters ToRunTime(this ConvexHullGenerationParameters generationParameters)


{
generationParameters.MinimumAngle = math.radians(generationParameters.MinimumAngle);
return generationParameters;
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/EulerAngles.cs
using System;
using Unity.Mathematics;
using UnityEngine;

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));

public static implicit operator quaternion(EulerAngles euler) =>


math.normalize(quaternion.Euler(math.radians(euler.Value), euler.RotationOrder));
public bool Equals(EulerAngles other) => Value.Equals(other.Value) && RotationOrder == other.RotationOrder;
public override bool Equals(object obj) => obj is EulerAngles other && Equals(other);
public override int GetHashCode() => unchecked((int)math.hash(new float4(Value, (float)RotationOrder)));
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/GetActiveChildrenScope.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityComponent = UnityEngine.Component;
namespace Unity.Physics.Authoring
{
public struct GetActiveChildrenScope<T> : IDisposable where T : UnityComponent
{
static readonly List<PhysicsShapeAuthoring> s_PhysicsShapes = new List<PhysicsShapeAuthoring>(8);

static bool s_BufferUsed;


static List<T> s_Buffer = new List<T>(8);

public List<T> Buffer => m_Disposed ? null : s_Buffer;


bool m_Disposed;
PhysicsShapeAuthoring m_Shape;
Transform m_Root;
GameObject m_PrimaryBody;
bool m_CheckIfComponentBelongsToShape;
public GetActiveChildrenScope(PhysicsShapeAuthoring shape, Transform root)
{
m_Disposed = false;
m_Shape = shape;
m_Root = root;
m_PrimaryBody = PhysicsShapeExtensions.GetPrimaryBody(root.gameObject);
m_CheckIfComponentBelongsToShape = root.transform.IsChildOf(shape.transform);
if (s_BufferUsed)
throw new InvalidOperationException($"Cannot nest two {GetType()}");
s_BufferUsed = true;
root.GetComponentsInChildren(true, s_Buffer);
}
public void Dispose()
{
if (m_Disposed)
return;
m_Disposed = true;
s_BufferUsed = false;
s_Buffer.Clear();
}

public bool IsChildActiveAndBelongsToShape(T child, bool filterOutInvalid = true)


{
var meshFilter = (UnityComponent)child as MeshFilter;
if (meshFilter != null)
{
if (meshFilter.sharedMesh == null)
return false;

var renderer = meshFilter.GetComponent<MeshRenderer>();


if (renderer == null || !renderer.enabled)
return false;
if (filterOutInvalid && !meshFilter.sharedMesh.IsValidForConversion(m_Shape.gameObject))
return false;
}

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);

// avoids drift in axes we're not actually changing


public const float kMinimumChange = HashableShapeInputs.k_DefaultLinearPrecision;

static readonly int[] k_NextAxis = { 1, 2, 0 };


static readonly int[] k_PrevAxis = { 2, 0, 1 };

// 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;

var imax = max == basisAxisLengths.x ? 0 : max == basisAxisLengths.y ? 1 : 2;


basisToWorld[k_NextAxis[imax]] = DeskewSecondaryAxis(basisToWorld[imax], basisToWorld[k_NextAxis[imax]]);
basisToWorld[k_PrevAxis[imax]] = DeskewSecondaryAxis(basisToWorld[imax], basisToWorld[k_PrevAxis[imax]]);

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];

return new int3(imax, imid, imin);


}

[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]]);

// recompute third axes from first two


var n2 = math.normalizesafe(
new float4(math.cross(localToBake[basisPriority[0]].xyz, localToBake[basisPriority[1]].xyz), 0f)
);
localToBake[basisPriority[2]] = n2 * math.dot(localToBake[basisPriority[2]], n2);
}
var bakeToShape = math.mul(math.inverse(shapeToWorld), localToBake);
// transform baked center/orientation (i.e. primitive basis) into shape space
orientation.SetValue(
quaternion.LookRotationSafe(bakeToShape[basisPriority[0]].xyz, bakeToShape[basisPriority[1]].xyz)
);
center = bakeToShape.c3.xyz;

return bakeToShape;
}

internal static CollisionFilter GetFilter(this PhysicsShapeAuthoring shape)


{
// TODO: determine optimal workflow for specifying group index
return new CollisionFilter
{
BelongsTo = shape.BelongsTo.Value,
CollidesWith = shape.CollidesWith.Value
};
}
internal static Material GetMaterial(this PhysicsShapeAuthoring shape)
{
// TODO: TBD how we will author editor content for other shape flags
return new Material
{
Friction = shape.Friction.Value,
FrictionCombinePolicy = shape.Friction.CombineMode,
Restitution = shape.Restitution.Value,
RestitutionCombinePolicy = shape.Restitution.CombineMode,
CollisionResponse = shape.CollisionResponse,
CustomTags = shape.CustomTags.Value
};
}
public static GameObject FindTopmostEnabledAncestor<T>(GameObject shape, List<T> buffer) where T : Component
{
// include inactive in case the supplied shape GameObject is a prefab that has not been instantiated
shape.GetComponentsInParent(true, buffer);
GameObject result = null;
for (var i = buffer.Count - 1; i >= 0; --i)
{
if (
(buffer[i] as UnityEngine.Collider)?.enabled ??
(buffer[i] as MonoBehaviour)?.enabled ?? true
)
{
result = buffer[i].gameObject;
break;
}
}
buffer.Clear();
return result;
}
public static GameObject GetPrimaryBody(this PhysicsShapeAuthoring shape) => GetPrimaryBody(shape.gameObject);
public static GameObject GetPrimaryBody(GameObject shape)
{
var pb = ColliderExtensions.FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
var rb = ColliderExtensions.FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
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
ColliderExtensions.FindTopmostStaticEnabledAncestor(shape, out GameObject 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;
}

public static BoxGeometry GetBakedBoxProperties(this PhysicsShapeAuthoring shape)


{
var box = shape.GetBoxProperties(out var orientation);
return box.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(), orientation);
}
internal static BoxGeometry BakeToBodySpace(
this BoxGeometry box, float4x4 localToWorld, float4x4 shapeToWorld, EulerAngles orientation
)
{
using (var geometry = new NativeArray<BoxGeometry>(1, Allocator.TempJob) { [0] = box })
{
var job = new BakeBoxJob
{
Box = geometry,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld,
orientation = orientation
};
job.Run();
return geometry[0];
}
}
public static void SetBakedBoxSize(this PhysicsShapeAuthoring shape, float3 size, float bevelRadius)
{
var box = shape.GetBoxProperties(out var orientation);
var center = box.Center;
var prevSize = math.abs(box.Size);
size = math.abs(size);

var bakeToShape = BakeBoxJobExtension.GetBakeToShape(shape, center, orientation);


var scale = bakeToShape.DecomposeScale();
size /= scale;

if (math.abs(size[0] - prevSize[0]) < kMinimumChange) size[0] = prevSize[0];


if (math.abs(size[1] - prevSize[1]) < kMinimumChange) size[1] = prevSize[1];
if (math.abs(size[2] - prevSize[2]) < kMinimumChange) size[2] = prevSize[2];
box.BevelRadius = bevelRadius;
box.Size = size;
shape.SetBox(box, orientation);
}

internal static CapsuleGeometryAuthoring GetBakedCapsuleProperties(this PhysicsShapeAuthoring shape)


{
var capsule = shape.GetCapsuleProperties();
return capsule.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix());
}

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();

var newRadius = radius / math.cmax(scale.xy);


if (math.abs(cylinder.Radius - newRadius) > kMinimumChange) cylinder.Radius = newRadius;
if (math.abs(cylinder.BevelRadius - bevelRadius) > kMinimumChange) cylinder.BevelRadius = bevelRadius;

var newHeight = math.max(0, height / scale.z);


if (math.abs(cylinder.Height - newHeight) > kMinimumChange) cylinder.Height = newHeight;
shape.SetCylinder(cylinder, orientation);
}

internal static SphereGeometry GetBakedSphereProperties(this PhysicsShapeAuthoring shape, out EulerAngles orientation)


{
var sphere = shape.GetSphereProperties(out orientation);
return sphere.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(), ref orientation);
}
internal static void GetBakedPlaneProperties(
this PhysicsShapeAuthoring shape, out float3 vertex0, out float3 vertex1, out float3 vertex2, out float3 vertex3
)
{
shape.GetPlaneProperties(out var center, out var size, out EulerAngles orientation);
BakeToBodySpace(
center, size, orientation, shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(),
out vertex0, out vertex1, out vertex2, out vertex3
);
}
public static void GetBakedConvexProperties(this PhysicsShapeAuthoring shape, NativeList<float3> pointCloud)
{
shape.GetConvexHullProperties(pointCloud, true, default, default, default, default);
shape.BakePoints(pointCloud.AsArray());
}
internal static Hash128 GetBakedMeshInputs(this PhysicsShapeAuthoring shape, HashSet<UnityEngine.Mesh> meshAssets = null)
{
using (var inputs = new NativeList<HashableShapeInputs>(8, Allocator.TempJob))
{
shape.GetMeshProperties(default, default, true, inputs, meshAssets);
using (var hash = new NativeArray<Hash128>(1, Allocator.TempJob))
using (var allSkinIndices = new NativeArray<int>(0, Allocator.TempJob))
using (var allBlendShapeWeights = new NativeArray<float>(0, Allocator.TempJob))
{
var job = new GetShapeInputsHashJob
{
Result = hash,
ForceUniqueIdentifier = (uint)(shape.ForceUnique ? shape.GetInstanceID() : 0),
Material = shape.GetMaterial(),
CollisionFilter = shape.GetFilter(),
BakeFromShape = shape.GetLocalToShapeMatrix(),
Inputs = inputs.AsArray(),
AllSkinIndices = allSkinIndices,
AllBlendShapeWeights = allBlendShapeWeights
};
job.Run();
return hash[0];
}
}
}
internal static Hash128 GetBakedConvexInputs(this PhysicsShapeAuthoring shape, HashSet<UnityEngine.Mesh> meshAssets)
{
using (var inputs = new NativeList<HashableShapeInputs>(8, Allocator.TempJob))
using (var allSkinIndices = new NativeList<int>(4096, Allocator.TempJob))
using (var allBlendShapeWeights = new NativeList<float>(64, Allocator.TempJob))
{
shape.GetConvexHullProperties(default, true, inputs, allSkinIndices, allBlendShapeWeights, meshAssets);
using (var hash = new NativeArray<Hash128>(1, Allocator.TempJob))
{
var job = new GetShapeInputsHashJob
{
Result = hash,
ForceUniqueIdentifier = (uint)(shape.ForceUnique ? shape.GetInstanceID() : 0),
GenerationParameters = shape.ConvexHullGenerationParameters,
Material = shape.GetMaterial(),
CollisionFilter = shape.GetFilter(),
BakeFromShape = shape.GetLocalToShapeMatrix(),
Inputs = inputs.AsArray(),
AllSkinIndices = allSkinIndices.AsArray(),
AllBlendShapeWeights = allBlendShapeWeights.AsArray()
};
job.Run();
return hash[0];
}
}
}
public static void GetBakedMeshProperties(
this PhysicsShapeAuthoring shape, NativeList<float3> vertices, NativeList<int3> triangles,
HashSet<UnityEngine.Mesh> meshAssets = null
)
{
shape.GetMeshProperties(vertices, triangles, true, default, meshAssets);
shape.BakePoints(vertices.AsArray());
}
const float k_HashFloatTolerance = 0.01f;
// used to hash convex hull generation properties in a way that is robust to imprecision
public static uint GetStableHash(
this ConvexHullGenerationParameters generationParameters,
ConvexHullGenerationParameters hashedParameters,
float tolerance = k_HashFloatTolerance
)
{
var differences = new float3(
generationParameters.BevelRadius - hashedParameters.BevelRadius,
generationParameters.MinimumAngle - hashedParameters.MinimumAngle,
generationParameters.SimplificationTolerance - hashedParameters.SimplificationTolerance
);
return math.cmax(math.abs(differences)) < tolerance
? unchecked((uint)hashedParameters.GetHashCode())
: unchecked((uint)generationParameters.GetHashCode());
}
// used to hash an array of points in a way that is robust to imprecision
public static unsafe uint GetStableHash(
this NativeList<float3> points, NativeArray<float3> hashedPoints, float tolerance = k_HashFloatTolerance
)
{
if (points.Length != hashedPoints.Length)
return math.hash(points.GetUnsafePtr(), UnsafeUtility.SizeOf<float3>() * points.Length);

for (int i = 0, count = points.Length; i < count; ++i)


{
if (math.cmax(math.abs(points[i] - hashedPoints[i])) > tolerance)
return math.hash(points.GetUnsafePtr(), UnsafeUtility.SizeOf<float3>() * points.Length);
}
return math.hash(hashedPoints.GetUnsafePtr(), UnsafeUtility.SizeOf<float3>() * hashedPoints.Length);
}
public static int GetMaxAxis(this float3 v)
{
var cmax = math.cmax(v);
return cmax == v.z ? 2 : cmax == v.y ? 1 : 0;
}
public static int GetDeviantAxis(this float3 v)
{
var deviation = math.abs(v - math.csum(v) / 3f);
return math.cmax(deviation) == deviation.z ? 2 : math.cmax(deviation) == deviation.y ? 1 : 0;
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/PropertyAttributes.cs
using UnityEngine;

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; }

public SoftRangeAttribute(float min, float max)


{
SliderMin = TextFieldMin = min;
SliderMax = TextFieldMax = max;
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/AssemblyInfo.cs
using System.Runtime.CompilerServices;

[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();

EditorUtilities.EditPivot(ballAndSocket.worldFromA, ballAndSocket.worldFromB, ballAndSocket.AutoSetConnected,


ref ballAndSocket.PositionLocal, ref ballAndSocket.PositionInConnectedEntity, ballAndSocket);
}
}
}
#endif
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/CustomPhysicsMaterialTagNamesEditor.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;

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();

public override void OnInspectorGUI()


{
LimitedHingeJoint limitedHinge = (LimitedHingeJoint)target;

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();
}
}

protected virtual void OnSceneGUI()


{
LimitedHingeJoint limitedHinge = (LimitedHingeJoint)target;

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;

public override void OnInspectorGUI()


{
serializedObject.Update();

EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_MotionType);
if (m_MotionType.intValue != (int)BodyMotionType.Static)
EditorGUILayout.PropertyField(m_Smoothing);

var dynamic = m_MotionType.intValue == (int)BodyMotionType.Dynamic;


if (dynamic)
EditorGUILayout.PropertyField(m_Mass, Content.MassLabel);
else
{
EditorGUI.BeginDisabledGroup(true);
var position = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
EditorGUI.BeginProperty(position, Content.MassLabel, m_Mass);
EditorGUI.FloatField(position, Content.MassLabel, float.PositiveInfinity);
EditorGUI.EndProperty();
EditorGUI.EndDisabledGroup();
}

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);
}

showAdvanced = EditorGUILayout.Foldout(showAdvanced, Content.AdvancedLabel);


if (showAdvanced)
{
++EditorGUI.indentLevel;
EditorGUILayout.PropertyField(m_WorldIndex);
if (m_MotionType.intValue != (int)BodyMotionType.Static)
{
EditorGUILayout.PropertyField(m_OverrideDefaultMassDistribution);
if (m_OverrideDefaultMassDistribution.boolValue)
{
++EditorGUI.indentLevel;
EditorGUILayout.PropertyField(m_CenterOfMass, Content.CenterOfMassLabel);

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];

static readonly string[] k_NonReadableGeometryWarning =


{
L10n.Tr($"{k_Singular} has a non-readable mesh in its hierarchy. Move it into a sub-scene or assign a custom mesh with Read/Write enabled in its import settings if it needs
L10n.Tr($"{k_Plural} has a non-readable mesh in its hierarchy. Move it into a sub-scene or assign a custom mesh with Read/Write enabled in its import settings if it needs t
};
public static string GetNonReadableGeometryWarning(int numTargets) =>
numTargets == 1 ? k_NonReadableGeometryWarning[0] : k_NonReadableGeometryWarning[1];
static readonly string[] k_MeshWithSkinnedPointsWarning =
{
L10n.Tr($"{k_Singular} is a mesh based on its render geometry, but its render geometry includes skinned points. These points will be excluded from the automatically generat
L10n.Tr($"{k_Plural} is a mesh based on its render geometry, but its render geometry includes skinned points. These points will be excluded from the automatically generated
};
public static string GetMeshWithSkinnedPointsWarning(int numTargets) =>
numTargets == 1 ? k_MeshWithSkinnedPointsWarning[0] : k_MeshWithSkinnedPointsWarning[1];
static readonly string[] k_StaticColliderStatusMessage =
{
L10n.Tr($"{k_Singular} will be considered static. Add a {ObjectNames.NicifyVariableName(typeof(PhysicsBodyAuthoring).Name)} component if you will move it at run-time."
L10n.Tr($"{k_Plural} will be considered static. Add a {ObjectNames.NicifyVariableName(typeof(PhysicsBodyAuthoring).Name)} component if you will move them at run-time."
};
public static string GetStaticColliderStatusMessage(int numTargets) =>
numTargets == 1 ? k_StaticColliderStatusMessage[0] : k_StaticColliderStatusMessage[1];
public static readonly string BoxCapsuleSuggestion =
L10n.Tr($"Target {ShapeType.Box} has uniform size on its two short axes and a large convex radius. Consider using a {ShapeType.Capsule} instead.");
public static readonly string BoxPlaneSuggestion =
L10n.Tr($"Target {ShapeType.Box} is flat. Consider using a {ShapeType.Plane} instead.");
public static readonly string BoxSphereSuggestion =
L10n.Tr($"Target {ShapeType.Box} has uniform size and large convex radius. Consider using a {ShapeType.Sphere} instead.");
public static readonly string CapsuleSphereSuggestion =
L10n.Tr($"Target {ShapeType.Capsule}'s diameter is equal to its height. Consider using a {ShapeType.Sphere} instead.");
public static readonly string CylinderCapsuleSuggestion =
L10n.Tr($"Target {ShapeType.Cylinder} has a large convex radius. Consider using a {ShapeType.Capsule} instead.");
public static readonly string CylinderSphereSuggestion =
L10n.Tr($"Target {ShapeType.Cylinder} has a large convex radius and its diameter is equal to its height. Consider using a {ShapeType.Sphere} instead.");
public static readonly GUIStyle Button =
new GUIStyle(EditorStyles.miniButton) { padding = new RectOffset() };
public static readonly GUIStyle ButtonDropDown =
new GUIStyle(EditorStyles.popup) { alignment = TextAnchor.MiddleCenter };
}
#pragma warning disable 649
[AutoPopulate] SerializedProperty m_ShapeType;
[AutoPopulate] SerializedProperty m_PrimitiveCenter;
[AutoPopulate] SerializedProperty m_PrimitiveSize;
[AutoPopulate] SerializedProperty m_PrimitiveOrientation;
[AutoPopulate] SerializedProperty m_Capsule;
[AutoPopulate] SerializedProperty m_Cylinder;
[AutoPopulate] SerializedProperty m_CylinderSideCount;
[AutoPopulate] SerializedProperty m_SphereRadius;
[AutoPopulate] SerializedProperty m_ConvexHullGenerationParameters;
[AutoPopulate(PropertyPath = "m_ConvexHullGenerationParameters.m_BevelRadius")] SerializedProperty m_BevelRadius;
[AutoPopulate] SerializedProperty m_MinimumSkinnedVertexWeight;
[AutoPopulate] SerializedProperty m_CustomMesh;
[AutoPopulate] SerializedProperty m_Material;
[AutoPopulate] SerializedProperty m_ForceUnique;
#pragma warning restore 649
[Flags]
enum GeometryState
{
Okay = 0,
NoGeometry = 1 << 0,
NonReadableGeometry = 1 << 1,
MeshWithSkinnedPoints = 1 << 2
}
GeometryState m_GeometryState;
int m_NumImplicitStatic;
// keep track of when the user is dragging some control to prevent continually rebuilding preview geometry
[NonSerialized]
int m_DraggingControlID;
[NonSerialized]
FitToRenderMeshesDropDown m_DropDown;
protected override void OnEnable()
{
base.OnEnable();
HashUtility.Initialize();

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);
}
}

float m_BevelRadius = ConvexHullGenerationParameters.Default.BevelRadius;


bool m_IsDragging = false;

public float height


{
get => GetSize().z;
set
{
var size = GetSize();
size.z = math.max(0f, value);
SetSize(size);
}
}

public float radius


{
get
{
var size = (float3)GetSize();
var diameter = 0f;
// only consider size values on enabled axes
if (IsAxisEnabled(0)) diameter = math.max(diameter, math.abs(size.x));
else if (IsAxisEnabled(1)) diameter = math.max(diameter, math.abs(size.y));
return diameter * 0.5f;
}
set
{
var size = (float3)GetSize();
size.x = size.y = math.max(0f, value * 2.0f);
SetSize(size);
}
}

public int sideCount


{
get => m_SideCount;
set
{
if (value == m_SideCount)
return;

m_SideCount = value;

Array.Resize(ref m_TopPoints, m_SideCount * 2);


Array.Resize(ref m_BottomPoints, m_SideCount * 2);
Array.Resize(ref m_Corners, m_SideCount * 2);
}
}
int m_SideCount;

PhysicsBoundsHandleUtility.Corner[] m_Corners = Array.Empty<PhysicsBoundsHandleUtility.Corner>();


Vector3[] m_TopPoints = Array.Empty<Vector3>();
Vector3[] m_BottomPoints = Array.Empty<Vector3>();

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()


{
using (new Handles.DrawingScope(Handles.matrix))
{
var backfacedColor = PhysicsBoundsHandleUtility.GetStateColor(true);
var frontfacedColor = Handles.color;
bool isCameraInsideBox = false;

var radius = this.radius;


var bevelRadius = this.bevelRadius;

var halfHeight = new float3(0f, 0f, height * 0.5f);


var ctr = (float3)center;
var halfAngleStep = math.PI / m_SideCount;
var angleStep = 2f * halfAngleStep;
const float kHalfPI = (math.PI * 0.5f);
var bottom = ctr + halfHeight + new float3 { z = bevelRadius };
var top = ctr - halfHeight - new float3 { z = bevelRadius };
var tangent = new float3(1, 0, 0);
var binormal = new float3(0, 1, 0);
var topBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(top, -tangent, binormal, axes, isCameraInsideBox);
var bottomBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(bottom, tangent, binormal, axes, isCameraInsideBox);

var cameraCenter = float3.zero;


var cameraForward = new float3 { z = 1f };
if (Camera.current != null)
{
cameraCenter = Camera.current.transform.position;
cameraForward = Camera.current.transform.forward;
}

// 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;

var noSides = (radius - bevelRadius) < PhysicsBoundsHandleUtility.kDistanceEpsilon;


var up = new float3(0, 0, -1f);

var t = ((m_SideCount - 2) * angleStep);


var xyAngle0 = new float3(math.cos(t), math.sin(t), 0f);

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 offset0 = xyAngle0 * (radius - bevelRadius);


var offset1 = xyAngle1 * (radius - bevelRadius);
var offset2 = xyAngle2 * (radius - bevelRadius);
var top1 = ctr + offset1 - (halfHeight - new float3 { z = bevelRadius });
var bottom1 = ctr + offset1 + (halfHeight - new float3 { z = bevelRadius });
var top2 = ctr + offset2 - (halfHeight - new float3 { z = bevelRadius });
var bottom2 = ctr + offset2 + (halfHeight - new float3 { z = bevelRadius });

var startOffset = direction1 * bevelRadius;


if (bevelGreaterThanZero)
{
var upOffset = up * bevelRadius;
// top/bottom caps
if (bevelLessThanCylinderRadius)
{
Handles.color = topBackFaced ? backfacedColor : frontfacedColor;
Handles.DrawLine(top1 + upOffset, top2 + upOffset);

Handles.color = bottomBackFaced ? backfacedColor : frontfacedColor;


Handles.DrawLine(bottom1 - upOffset, bottom2 - upOffset);
}

var currSideMidPoint = ctr + ((top1 + bottom1 + top2 + bottom2) * 0.25f) + startOffset;


var currSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(currSideMidPoint, up, sideways2, axes, isCameraInsideBox);
Handles.color = currSideBackFaced ? backfacedColor : frontfacedColor;
if (!noSides)
{
// Square side of bevelled cylinder
Handles.DrawLine(top2 + startOffset, bottom2 + startOffset);
Handles.DrawLine(bottom2 + startOffset, bottom1 + startOffset);
Handles.DrawLine(bottom1 + startOffset, top1 + startOffset);
Handles.DrawLine(top1 + startOffset, top2 + startOffset);
}
else
{
// Square side of bevelled cylinder, when squashed to a single line
Handles.DrawLine(top2 + startOffset, bottom2 + startOffset);
}
}
else
{
var top0 = ctr + offset0 - (halfHeight - new float3 { z = bevelRadius });
var bottom0 = ctr + offset0 + (halfHeight - new float3 { z = bevelRadius });
var prevMidPoint = ctr + ((top0 + top1 + bottom0 + bottom1) * 0.25f) + startOffset;
var prevSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(prevMidPoint, up, sideways1, axes, isCameraInsideBox);

var currMidPoint = ctr + ((top1 + top2 + bottom1 + bottom2) * 0.25f) + startOffset;


var currSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(currMidPoint, up, sideways2, axes, isCameraInsideBox);
// Square side of bevelled cylinder
Handles.color = (currSideBackFaced && prevSideBackFaced) ? backfacedColor : frontfacedColor;
Handles.DrawLine(bottom1 + startOffset, top1 + startOffset);
Handles.color = (currSideBackFaced && topBackFaced) ? backfacedColor : frontfacedColor;
Handles.DrawLine(top1 + startOffset, top2 + startOffset);
Handles.color = (currSideBackFaced && bottomBackFaced) ? backfacedColor : frontfacedColor;
Handles.DrawLine(bottom2 + startOffset, bottom1 + startOffset);
}
if (bevelGreaterThanZero)
{
Handles.color = frontfacedColor;

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;

// Side horizon on vertical curved edge


if (m_Corners[up1].splitCount > 1 &&
m_Corners[dn1].splitCount > 1)
{
if ((m_Corners[up1].splitAxis[0].y || m_Corners[up1].splitAxis[1].y) &&
(m_Corners[dn1].splitAxis[0].y || m_Corners[dn1].splitAxis[1].y))
{
var point0 = m_Corners[up1].splitAxis[0].y ? m_Corners[up1].points[0] : m_Corners[up1].points[1];
var point1 = m_Corners[dn1].splitAxis[0].y ? m_Corners[dn1].points[0] : m_Corners[dn1].points[1];
Handles.DrawLine(point0, point1);
}
}
// Top horizon on horizontal curved edge
if (m_Corners[up0].splitCount > 1 &&
m_Corners[up1].splitCount > 1)
{
if ((m_Corners[up0].splitAxis[0].x || m_Corners[up0].splitAxis[1].x) &&
(m_Corners[up1].splitAxis[0].z || m_Corners[up1].splitAxis[1].z))
{
var point0 = m_Corners[up0].splitAxis[0].x ? m_Corners[up0].points[0] : m_Corners[up0].points[1];
var point1 = m_Corners[up1].splitAxis[0].z ? m_Corners[up1].points[0] : m_Corners[up1].points[1];
Handles.DrawLine(point0, point1);
}
}
// Bottom horizon on horizontal curved edge
if (m_Corners[dn0].splitCount > 1 &&
m_Corners[dn1].splitCount > 1)
{
if ((m_Corners[dn0].splitAxis[0].z || m_Corners[dn0].splitAxis[1].z) &&
(m_Corners[dn1].splitAxis[0].x || m_Corners[dn1].splitAxis[1].x))
{
var point0 = m_Corners[dn0].splitAxis[0].z ? m_Corners[dn0].points[0] : m_Corners[dn0].points[1];
var point1 = m_Corners[dn1].splitAxis[0].x ? m_Corners[dn1].points[0] : m_Corners[dn1].points[1];
Handles.DrawLine(point0, point1);
}
}
}
for (var i = 0; i < m_Corners.Length; ++i)
PhysicsBoundsHandleUtility.DrawCorner(m_Corners[i], new bool3(true, true, !noSides));
}
}
}
protected override Bounds OnHandleChanged(HandleDirection handle, Bounds boundsOnClick, Bounds newBounds)
{
const int k_DirectionX = 0;
const int k_DirectionY = 1;
const int k_DirectionZ = 2;
var changedAxis = k_DirectionX;
var otherRadiusAxis = k_DirectionY;
switch (handle)
{
case HandleDirection.NegativeY:
case HandleDirection.PositiveY:
changedAxis = k_DirectionY;
otherRadiusAxis = k_DirectionX;
break;
case HandleDirection.NegativeZ:
case HandleDirection.PositiveZ:
changedAxis = k_DirectionZ;
break;
}
var upperBound = newBounds.max;
var lowerBound = newBounds.min;
var convexDiameter = 2f * bevelRadius;
// ensure changed dimension cannot be made less than convex diameter
if (upperBound[changedAxis] - lowerBound[changedAxis] < convexDiameter)
{
switch (handle)
{
case HandleDirection.PositiveX:
case HandleDirection.PositiveY:
case HandleDirection.PositiveZ:
upperBound[changedAxis] = lowerBound[changedAxis] + convexDiameter;
break;
default:
lowerBound[changedAxis] = upperBound[changedAxis] - convexDiameter;
break;
}
}
// ensure radius changes uniformly
if (changedAxis != k_DirectionZ)
{
var rad = 0.5f * (upperBound[changedAxis] - lowerBound[changedAxis]);
lowerBound[otherRadiusAxis] = center[otherRadiusAxis] - rad;
upperBound[otherRadiusAxis] = center[otherRadiusAxis] + rad;
}
return new Bounds((upperBound + lowerBound) * 0.5f, upperBound - lowerBound);
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/PhysicsBoundsHandleUtility.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEngine;
using static UnityEditor.IMGUI.Controls.PrimitiveBoundsHandle;
namespace Unity.Physics.Editor
{
static class PhysicsBoundsHandleUtility
{
internal const float kBackfaceAlphaMultiplier = 0.2f;
const float kDegreeEpsilon = 0.001f;
public const float kDistanceEpsilon = 0.0001f;
public static bool IsBackfaced(float3 localPos, float3 localTangent, float3 localBinormal, Axes axes, bool isCameraInsideBox)
{
// if inside the box then ignore back facing alpha multiplier (otherwise all handles will look disabled)
if (isCameraInsideBox || axes != Axes.All)
return false;
// use tangent and binormal to calculate normal in case handle matrix is skewed
float3 worldTangent = math.normalize(Handles.matrix.MultiplyVector(localTangent));
float3 worldBinormal = math.normalize(Handles.matrix.MultiplyVector(localBinormal));
float3 worldDir = math.normalize(math.cross(worldTangent, worldBinormal));
// adjust color if handle is back facing
float cosV;
var currentCamera = Camera.current;
if (currentCamera != null && !currentCamera.orthographic)
{
float3 cameraPos = currentCamera.transform.position;
float3 worldPos = Handles.matrix.MultiplyPoint(localPos);
cosV = math.dot(math.normalize(cameraPos - worldPos), worldDir);
}
else
{
float3 cameraForward = currentCamera == null ? Vector3.forward : currentCamera.transform.forward;
cosV = math.dot(-cameraForward, worldDir);
}
return cosV < -0.0001f;
}
public static Color GetStateColor(bool isBackfaced)
{
float alphaMultiplier = isBackfaced ? kBackfaceAlphaMultiplier : 1f;
return Handles.color * new Color(1f, 1f, 1f, alphaMultiplier);
}
static void AdjustMidpointHandleColor(bool isBackfaced)
{
Handles.color = GetStateColor(isBackfaced);
}
static readonly Vector3[] s_FacePoints = new Vector3[8];
static readonly Vector3[] s_LinePoints = new Vector3[2];
static readonly int[] k_NextAxis = { 1, 2, 0 };
public static void DrawFace(float3 center, float3 size, float cornerRadius, int normalAxis, Axes axes, bool isCameraInsideBox)
{
// 0 = 0 1 2
// 1 = 1 2 0
// 2 = 2 0 1
int a = normalAxis;
int b = k_NextAxis[a];
int c = k_NextAxis[b];
cornerRadius = math.abs(cornerRadius);
size *= 0.5f;
var normal = new float3 { [a] = size[a] };
var ctr = center + normal;
size -= new float3(cornerRadius);
// check if our face is a point
if (math.abs(size[c]) < kDistanceEpsilon &&
math.abs(size[b]) < kDistanceEpsilon)
return;
Vector3[] points;
// check if our face is a line or not
if (math.abs(size[c]) >= kDistanceEpsilon &&
math.abs(size[b]) >= kDistanceEpsilon)
{
var i = 0;
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
points = s_FacePoints;
}
else if (math.abs(size[c]) >= kDistanceEpsilon)
{
var i = 0;
s_LinePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
s_LinePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
points = s_LinePoints;
}
else
{
var i = 0;
s_LinePoints[i++] = ctr + new float3 { [c] = size[c], [b] = size[b] };
s_LinePoints[i++] = ctr + new float3 { [c] = size[c], [b] = -size[b] };
points = s_LinePoints;
}
float3 tangent, biNormal;
if (size[c] > 0)
{
tangent = math.cross(normal, math.normalizesafe(new float3 { [c] = size[c] }));
biNormal = math.cross(normal, tangent);
}
else
{
tangent = math.cross(normal, math.normalizesafe(new float3 { [b] = size[b] }));
biNormal = math.cross(normal, tangent);
}
using (new Handles.DrawingScope(Handles.color))
{
AdjustMidpointHandleColor(IsBackfaced(ctr, tangent, biNormal, axes, isCameraInsideBox));
Handles.DrawLines(points);
}
}
public struct Corner
{
public float3 angle;
public float3x2 intersections;
public float3x2 points;
public float3x3 axes;
public float3x3 normals;
public bool3x2 splitAxis;
public int splitCount;
public float3 position;
public float radius;
public bool isBackFaced;
public float3 cameraForward;
}
public static void CalculateCornerHorizon(float3 cornerPosition, quaternion orientation, float3 cameraCenter, float3 cameraForward, bool cameraOrtho, float radius, out Corner
{
var axisx = new float3(1f, 0f, 0f);
var axisy = new float3(0f, 1f, 0f);
var axisz = new float3(0f, 0f, 1f);
// a vector pointing away from the center of the corner
var cornerNormal = math.normalize(math.mul(orientation, new float3(1f, 1f, 1f)));
var axes = math.mul(new float3x3(orientation), new float3x3(axisx, axisy, axisz));
CalculateCornerHorizon(cornerPosition,
axes,
cornerNormal,
cameraCenter, cameraForward, cameraOrtho,
radius, out corner);
}
public static void CalculateCornerHorizon(float3 cornerPosition, float3x3 axes, float3 cornerNormal, float3 cameraCenter, float3 cameraForward, bool cameraOrtho, float radius
{
var cameraToCenter = cornerPosition - cameraCenter; // vector from camera to center
var sqrRadius = radius * radius;
var sqrDistCameraToCenter = math.lengthsq(cameraToCenter);
var sqrOffset = (sqrRadius * sqrRadius / sqrDistCameraToCenter); // squared distance from actual center to drawn disc center
if (!cameraOrtho)
cameraForward = cameraToCenter;
var normals = new float3x3
{
c0 = math.normalize(math.cross(axes[1], axes[2])),
c1 = math.normalize(math.cross(axes[2], axes[0])),
c2 = math.normalize(math.cross(axes[0], axes[1]))
};
corner = new Corner
{
angle = new float3(
Vector3.Angle(axes[0], axes[1]),
Vector3.Angle(axes[1], axes[2]),
Vector3.Angle(axes[2], axes[0])
),
intersections = default,
points = default,
splitAxis = default,
axes = axes,
normals = normals,
position = cornerPosition,
radius = radius,
cameraForward = cameraForward,
isBackFaced = math.dot(cornerNormal, cameraForward) > 0,
splitCount = 0
};
if (math.abs(sqrDistCameraToCenter) <= sqrRadius)
return;
for (int n = 0, sign = -1; n < 2; n++, sign += 2)
{
for (int i = 0; i < 3; i++)
{
var axis1 = normals[i] * sign;
var axis2 = axes[(i + 1) % 3] * sign;
var axis3 = axes[(i + 2) % 3] * sign;
var Q = Vector3.Angle(cameraForward, axis1);
var f = math.tan(math.radians(90 - math.min(Q, 180 - Q)));
var g = sqrOffset + f * f * sqrOffset;
if (g >= sqrRadius)
continue;
var e = math.degrees(math.asin(math.sqrt(g) / radius));
var vectorToPointOnHorizon = Quaternion.AngleAxis(e, axis1) * math.normalize(math.cross(axis1, cameraForward));
vectorToPointOnHorizon = math.normalize(Vector3.ProjectOnPlane(vectorToPointOnHorizon, axis1));
var intersectionDirection = vectorToPointOnHorizon;
var angle1 = Vector3.SignedAngle(axis2, intersectionDirection, axis1);
var angle2 = Vector3.SignedAngle(axis3, intersectionDirection, axis1);
if (angle1 <= 0 || angle2 >= 0)
continue;
var point = corner.position + (float3)(intersectionDirection * radius);
if (corner.splitCount < 2)
{
corner.splitAxis[corner.splitCount][i] = true;
corner.intersections[corner.splitCount] = intersectionDirection;
corner.points[corner.splitCount] = point;
corner.splitCount++;
}
}
}
if (!math.any(corner.splitAxis[0]) &&
!math.any(corner.splitAxis[1]))
{
corner.splitCount = 0;
corner.splitAxis[0] = false;
corner.splitAxis[1] = false;
}
}
public static void DrawCorner(Corner corner, bool3 showAxis)
{
var color = Handles.color;
var axes = corner.axes;
var intersections = corner.intersections;
var normals = corner.normals;
var origin = corner.position;
var radius = corner.radius;
if (corner.splitCount <= 1)
{
AdjustMidpointHandleColor(corner.isBackFaced);
if (showAxis[0]) Handles.DrawWireArc(origin, normals[0], axes[1], corner.angle[1], radius);
if (showAxis[1]) Handles.DrawWireArc(origin, normals[1], axes[2], corner.angle[2], radius);
if (showAxis[2]) Handles.DrawWireArc(origin, normals[2], axes[0], corner.angle[0], radius);
}
else
{
var angleLength = Vector3.SignedAngle(Vector3.ProjectOnPlane(intersections[0], corner.cameraForward),
Vector3.ProjectOnPlane(intersections[1], corner.cameraForward), corner.cameraForward);
bool reversePolarity = angleLength < 0;
if (reversePolarity)
Handles.DrawWireArc(origin, corner.cameraForward, corner.points[1] - origin, -angleLength, radius);
else
Handles.DrawWireArc(origin, corner.cameraForward, corner.points[0] - origin, angleLength, radius);

var backfacedColor = GetStateColor(true);


var axesBackfaced = new bool3(math.length(intersections[0] - axes[0]) < kDistanceEpsilon || math.length(intersections[1] - axes[0]) < kDistanceEpsilon,
math.length(intersections[0] - axes[1]) < kDistanceEpsilon || math.length(intersections[1] - axes[1]) < kDistanceEpsilon,
math.length(intersections[0] - axes[2]) < kDistanceEpsilon || math.length(intersections[1] - axes[2]) < kDistanceEpsilon);
var color1 = reversePolarity ? color : backfacedColor;
var color2 = reversePolarity ? backfacedColor : color;
for (int A = 1, B = 2, C = 0; C < 3; A = B, B = C, C++)
{
if (corner.splitAxis[0][C] == corner.splitAxis[1][C])
{
if (!axesBackfaced[A]) { angleLength = Vector3.Angle(intersections[0], axes[A]); axesBackfaced[A] = (angleLength<kDegreeEpsilon || angleLength> corner.angle
if (!axesBackfaced[B]) { angleLength = Vector3.Angle(intersections[1], axes[A]); axesBackfaced[B] = (angleLength<kDegreeEpsilon || angleLength> corner.angle
}
else if (corner.splitAxis[0][C])
{
if (showAxis[C])
{
angleLength = Vector3.Angle(intersections[0], axes[A]);
Handles.color = color1; Handles.DrawWireArc(origin, normals[C], intersections[0], -angleLength, radius);
Handles.color = color2; Handles.DrawWireArc(origin, normals[C], intersections[0], corner.angle[A] - angleLength, radius);
}
axesBackfaced[A] = true;
}
else
//if (corner.splitAxis[1][C])
{
if (showAxis[C])
{
angleLength = Vector3.Angle(intersections[1], axes[A]);
Handles.color = color2; Handles.DrawWireArc(origin, normals[C], intersections[1], -angleLength, radius);
Handles.color = color1; Handles.DrawWireArc(origin, normals[C], intersections[1], corner.angle[A] - angleLength, radius);
}
axesBackfaced[B] = true;
}
}
// check for singularity
if (math.all(axesBackfaced))
axesBackfaced = corner.isBackFaced;
for (int A = 1, B = 2, C = 0; C < 3; A = B, B = C, C++)
{
if (!showAxis[C])
continue;
if (corner.splitAxis[0][C] == corner.splitAxis[1][C])
{
Handles.color = (axesBackfaced[B] && axesBackfaced[A]) ? color1 : color2;
Handles.DrawWireArc(origin, normals[C], axes[A], corner.angle[A], radius);
}
}
}
Handles.color = color;
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/PhysicsCapsuleBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class PhysicsCapsuleBoundsHandle : CapsuleBoundsHandle
{
static PhysicsBoundsHandleUtility.Corner[] s_Corners = new PhysicsBoundsHandleUtility.Corner[8];
protected override void DrawWireframe()
{
if (this.radius <= 0f)
{
base.DrawWireframe();
return;
}
var cameraPos = default(float3);
var cameraFwd = new float3 { z = 1f };
var cameraOrtho = true;
if (Camera.current != null)
{
cameraPos = Camera.current.transform.position;
cameraFwd = Camera.current.transform.forward;
cameraOrtho = Camera.current.orthographic;
}
var size = new float3(this.radius * 2f, this.radius * 2f, height);
var radius = this.radius;
var origin = (float3)this.center;
var bounds = new Bounds(this.center, size);
// 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;
var cameraCenter = (float3)invMatrix.MultiplyPoint(cameraPos);
var cameraForward = (float3)invMatrix.MultiplyVector(cameraFwd);
bool isCameraInsideBox = Camera.current != null
&& bounds.Contains(invMatrix.MultiplyPoint(cameraPos));
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(-1f, 1f, 1f), radius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, -1f, 1f), radius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 2, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, -1f), radius, 2, axes, isCameraInsideBox);
var corner = 0.5f * size - new float3(1f) * radius;
var axisx = new float3(1f, 0f, 0f);
var axisy = new float3(0f, 1f, 0f);
var axisz = new float3(0f, 0f, 1f);
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, -1f), quaternion.LookRotation(-axisz, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, 1f), quaternion.LookRotation(-axisx, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, 1f), quaternion.LookRotation(axisz, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, -1f), quaternion.LookRotation(axisx, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, -1f), quaternion.LookRotation(-axisx, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, 1f), quaternion.LookRotation(axisz, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, 1f), quaternion.LookRotation(axisx, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, -1f), quaternion.LookRotation(-axisz, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[0], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[3], new bool3(true, false, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[4], new bool3(true, false, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[7], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[1], new bool3(true, false, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[2], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[5], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[6], new bool3(true, false, 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/PhysicsSphereBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class PhysicsSphereBoundsHandle : SphereBoundsHandle
{
protected override void DrawWireframe()
{
bool x = IsAxisEnabled(Axes.X);
bool y = IsAxisEnabled(Axes.Y);
bool z = IsAxisEnabled(Axes.Z);

if (x && !y && !z)


Handles.DrawLine(Vector3.right * radius, Vector3.left * radius);
if (!x && y && !z)
Handles.DrawLine(Vector3.up * radius, Vector3.down * radius);
if (!x && !y && z)
Handles.DrawLine(Vector3.forward * radius, Vector3.back * radius);
const float kEpsilon = 0.000001F;
if (radius > 0)
{
var frontfacedColor = Handles.color;
var backfacedColor = Handles.color * new Color(1f, 1f, 1f, PhysicsBoundsHandleUtility.kBackfaceAlphaMultiplier);
var discVisible = new bool[]
{
y && z,
x && z,
x && y
};
var discOrientations = new float3[]
{
Vector3.right,
Vector3.up,
Vector3.forward
};
// 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;
var cameraCenter = Camera.current == null ? Vector3.zero : Camera.current.transform.position;
var cameraToCenter = center - invMatrix.MultiplyPoint(cameraCenter); // vector from camera to center
var sqrDistCameraToCenter = cameraToCenter.sqrMagnitude;
var sqrRadius = radius * radius; // squared radius
var isCameraOrthographic = Camera.current == null || Camera.current.orthographic;
var sqrOffset = isCameraOrthographic ? 0 : (sqrRadius * sqrRadius / sqrDistCameraToCenter); // squared distance from actual center to drawn disc center
var insideAmount = sqrOffset / sqrRadius;
if (insideAmount < 1)
{
if (math.abs(sqrDistCameraToCenter) >= kEpsilon)
{
using (new Handles.DrawingScope(frontfacedColor))
{
if (isCameraOrthographic)
{
var horizonRadius = radius;
var horizonCenter = center;
Handles.DrawWireDisc(horizonCenter, cameraToCenter, horizonRadius);
}
else
{
var horizonRadius = math.sqrt(sqrRadius - sqrOffset);
var horizonCenter = center - sqrRadius * cameraToCenter / sqrDistCameraToCenter;
Handles.DrawWireDisc(horizonCenter, cameraToCenter, horizonRadius);
}
}
var planeNormal = cameraToCenter.normalized;
for (int i = 0; i < 3; i++)
{
if (!discVisible[i])
continue;

var discOrientation = discOrientations[i];


var angleBetweenDiscAndNormal = math.acos(math.dot(discOrientation, planeNormal));
angleBetweenDiscAndNormal = (math.PI * 0.5f) - math.min(angleBetweenDiscAndNormal, math.PI - angleBetweenDiscAndNormal);
float f = math.tan(angleBetweenDiscAndNormal);
float g = math.sqrt(sqrOffset + f * f * sqrOffset) / radius;
if (g < 1)
{
var angleToHorizon = math.degrees(math.asin(g));
var discTangent = math.cross(discOrientation, planeNormal);
var vectorToPointOnHorizon = Quaternion.AngleAxis(angleToHorizon, discOrientation) * discTangent;
var horizonArcLength = (90 - angleToHorizon) * 2.0f;
using (new Handles.DrawingScope(frontfacedColor))
Handles.DrawWireArc(center, discOrientation, vectorToPointOnHorizon, horizonArcLength, radius);
using (new Handles.DrawingScope(backfacedColor))
Handles.DrawWireArc(center, discOrientation, vectorToPointOnHorizon, horizonArcLength - 360, radius);
}
else
{
using (new Handles.DrawingScope(backfacedColor))
Handles.DrawWireDisc(center, discOrientation, radius);
}
}
}
}
else
{
using (new Handles.DrawingScope(backfacedColor))
{
for (int i = 0; i < 3; i++)
{
var discOrientation = discOrientations[i];
Handles.DrawWireDisc(center, discOrientation, radius);
}
}
}
}
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/BaseDrawer.cs
using UnityEditor;
using UnityEngine;

namespace Unity.Physics.Editor
{
abstract class BaseDrawer : PropertyDrawer
{
protected abstract bool IsCompatible(SerializedProperty property);

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)


{
return IsCompatible(property)
? EditorGUI.GetPropertyHeight(property)
: EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (IsCompatible(property))
DoGUI(position, property, label);
else
EditorGUIControls.DisplayCompatibilityWarning(position, label, ObjectNames.NicifyVariableName(GetType().Name));
}

protected abstract void DoGUI(Rect position, SerializedProperty property, GUIContent label);


}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/EnumFlagsDrawer.cs
using System;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(EnumFlagsAttribute))]
class EnumFlagsDrawer : BaseDrawer
{
protected override bool IsCompatible(SerializedProperty property)
{
return property.propertyType == SerializedPropertyType.Enum;
}

protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)


{
EditorGUI.BeginProperty(position, label, property);
var value = property.longValue;
EditorGUI.BeginChangeCheck();
value = Convert.ToInt64(
EditorGUI.EnumFlagsField(position, label, (Enum)Enum.ToObject(fieldInfo.FieldType, value))
);
if (EditorGUI.EndChangeCheck())
property.longValue = value;

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;

protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)


{
EditorGUI.BeginProperty(position, label, property);
EditorGUI.PropertyField(
new Rect(position) { xMax = position.xMax - Styles.PopupWidth },
property.FindPropertyRelative("Value"),
label
);
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
EditorGUI.PropertyField(
new Rect(position) { xMin = position.xMax - Styles.PopupWidth + EditorGUIUtility.standardVerticalSpacing },
property.FindPropertyRelative("CombineMode"),
GUIContent.none
);
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/PhysicsMaterialPropertiesDrawer.cs
using System.Collections.Generic;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;

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)."
);
}

const string k_CollisionFilterGroupKey = "m_BelongsToCategories";


const string k_AdvancedGroupKey = "m_CustomMaterialTags";
Dictionary<string, SerializedObject> m_SerializedTemplates = new Dictionary<string, SerializedObject>();

SerializedProperty GetTemplateValueProperty(SerializedProperty property)


{
var key = property.propertyPath;
var template = property.FindPropertyRelative("m_Template").objectReferenceValue;
SerializedObject serializedTemplate;
if (
!m_SerializedTemplates.TryGetValue(key, out serializedTemplate)
|| serializedTemplate?.targetObject != template
)
m_SerializedTemplates[key] = serializedTemplate = template == null ? null : new SerializedObject(template);
serializedTemplate?.Update();
return serializedTemplate?.FindProperty("m_Value");
}

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");
}

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)


{
var templateValueProperty = GetTemplateValueProperty(property);
// m_CollisionResponse, collision filter foldout, advanced foldout
var height = 3f * EditorGUIUtility.singleLineHeight + 2f * EditorGUIUtility.standardVerticalSpacing;

// 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;
}

protected override bool IsCompatible(SerializedProperty property) => true;

static void DisplayOverridableProperty(


Rect position, GUIContent label, SerializedProperty toggle, SerializedProperty value, bool templateAssigned
)
{
if (templateAssigned)
{
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth -= 16f + EditorGUIUtility.standardVerticalSpacing;
var togglePosition = new Rect(position) { width = EditorGUIUtility.labelWidth + 16f + EditorGUIUtility.standardVerticalSpacing };
EditorGUI.PropertyField(togglePosition, toggle, label);
EditorGUIUtility.labelWidth = labelWidth;

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);
}
}

protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)


{
var template = property.FindPropertyRelative("m_Template");
var templateAssigned = template.objectReferenceValue != null;
var supportsTemplate = property.FindPropertyRelative("m_SupportsTemplate");
if (supportsTemplate.boolValue)
{
position.height = EditorGUI.GetPropertyHeight(template);
EditorGUI.PropertyField(position, template);

position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;


}

var templateValue = GetTemplateValueProperty(property);


FindToggleAndValueProperties(property, templateValue, "m_CollisionResponse", out var collisionResponseDropDown, out var collisionResponse);
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.CollisionResponseLabel, collisionResponseDropDown, collisionResponse, templateAssigned);
SerializedProperty toggle;

// Check if regular collider


CollisionResponsePolicy collisionResponseEnum = (CollisionResponsePolicy)collisionResponse.intValue;
if (collisionResponseEnum == CollisionResponsePolicy.Collide ||
collisionResponseEnum == CollisionResponsePolicy.CollideRaiseCollisionEvents)
{
FindToggleAndValueProperties(property, templateValue, "m_Friction", out toggle, out var friction);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.FrictionLabel, toggle, friction, templateAssigned);
FindToggleAndValueProperties(property, templateValue, "m_Restitution", out toggle, out var restitution);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.RestitutionLabel, toggle, restitution, templateAssigned);
}

// collision filter group


var collisionFilterGroup = property.FindPropertyRelative(k_CollisionFilterGroupKey);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
collisionFilterGroup.isExpanded =
EditorGUI.Foldout(position, collisionFilterGroup.isExpanded, Content.CollisionFilterGroupFoldout, true);
if (collisionFilterGroup.isExpanded)
{
++EditorGUI.indentLevel;

FindToggleAndValueProperties(property, templateValue, "m_BelongsToCategories", out toggle, out var belongsTo);


position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.BelongsToLabel, toggle, belongsTo, templateAssigned);
FindToggleAndValueProperties(property, templateValue, "m_CollidesWithCategories", out toggle, out var collidesWith);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.CollidesWithLabel, toggle, collidesWith, templateAssigned);
--EditorGUI.indentLevel;
}
// advanced group
var advancedGroup = property.FindPropertyRelative(k_AdvancedGroupKey);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
advancedGroup.isExpanded =
EditorGUI.Foldout(position, advancedGroup.isExpanded, Content.AdvancedGroupFoldout, true);
if (advancedGroup.isExpanded)
{
++EditorGUI.indentLevel;
FindToggleAndValueProperties(property, templateValue, "m_CustomMaterialTags", out toggle, out var customFlags);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.CustomFlagsLabel, toggle, customFlags, templateAssigned);

--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");

public static readonly string MultipleAssetsTooltip =


L10n.Tr("Multiple {0} assets found. UI will display labels defined in {1}.");

public static readonly GUIContent MultipleAssetsWarning =


new GUIContent { image = EditorGUIUtility.Load("console.warnicon") as Texture };
}
protected abstract int MaxNumCategories { get; }
protected abstract string DefaultCategoryName { get; }
internal string FirstChildPropertyPath { get; set; } // TODO: remove when all usages of bool[] are migrated

string DefaultFormatString => L10n.Tr($"(Undefined {DefaultCategoryName})");


string[] DefaultOptions =>
m_DefaultOptions ?? (
m_DefaultOptions =
Enumerable.Range(0, MaxNumCategories)
.Select(i => string.Format(DefaultFormatString, i))
.ToArray()
);
string[] m_DefaultOptions;

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];

m_Options[i] = $"{i}: {m_Options[i]}";


}
return m_Options;
}

string[] m_Options;

static string GetButtonLabel(int value, IReadOnlyList<string> optionNames)


{
switch (value)
{
case 0:
return Styles.NothingName;
case ~0:
return Styles.EverythingName;
default:
{
for (var i = 0; i < 32; i++)
{
if (value == 1 << i)
return optionNames[i];
}
break;
}
}
return Styles.MixedName;
}

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;
}

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)


{
if (m_NamesAssets?.Length > 1)
position.xMax -= EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;

EditorGUI.BeginProperty(position, label, property);

var controlPosition = EditorGUI.PrefixLabel(position, label);


var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
var showMixed = EditorGUI.showMixedValue;

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];

public static void SoftSlider(


Rect position, GUIContent label, SerializedProperty property,
float sliderMin, float sliderMax,
float textFieldMin, float textFieldMax
)
{
if (property.propertyType != SerializedPropertyType.Float)
{
DisplayCompatibilityWarning(position, label, property.propertyType.ToString());
}
else if (k_SoftSlider == null)
{
EditorGUI.BeginChangeCheck();
EditorGUI.PropertyField(position, property, label);
if (EditorGUI.EndChangeCheck())
property.floatValue = math.clamp(property.floatValue, textFieldMin, textFieldMax);
}
else
{
k_SoftSliderArgs[0] = position;
k_SoftSliderArgs[1] = label;
k_SoftSliderArgs[2] = property.floatValue;
k_SoftSliderArgs[3] = sliderMin;
k_SoftSliderArgs[4] = sliderMax;
k_SoftSliderArgs[5] = textFieldMin;
k_SoftSliderArgs[6] = textFieldMax;
EditorGUI.BeginProperty(position, label, property);
EditorGUI.BeginChangeCheck();
var result = k_SoftSlider.Invoke(null, k_SoftSliderArgs);
if (EditorGUI.EndChangeCheck())
property.floatValue = (float)result;
EditorGUI.EndProperty();
}
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/ManipulatorUtility.cs
using System;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Editor
{
enum MatrixState
{
UniformScale,
NonUniformScale,
ZeroScale,
NotValidTRS
}
static class ManipulatorUtility
{
public static MatrixState GetMatrixState(ref float4x4 localToWorld)
{
if (
localToWorld.c0.w != 0f
|| localToWorld.c1.w != 0f
|| localToWorld.c2.w != 0f
|| localToWorld.c3.w != 1f
)
return MatrixState.NotValidTRS;
var m = new float3x3(localToWorld.c0.xyz, localToWorld.c1.xyz, localToWorld.c2.xyz);
var lossyScale = new float3(math.length(m.c0.xyz), math.length(m.c1.xyz), math.length(m.c2.xyz));
if (math.determinant(m) < 0f)
lossyScale.x *= -1f;
if (math.lengthsq(lossyScale) == 0f)
return MatrixState.ZeroScale;
return math.abs(math.cmax(lossyScale)) - math.abs(math.cmin(lossyScale)) > 0.000001f
? MatrixState.NonUniformScale
: MatrixState.UniformScale;
}
}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/SceneViewUtility.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEngine;

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;
}

const string k_NotificationsPrefKey = "SceneView/Tools/Notifications";


const bool k_DefaultNotifications = true;
const string k_NotificationSpeedPrefKey = "SceneView/Tools/Notification Speed";
const float k_DefaultNotificationsSpeed = 20f;

const float k_NotificationDuration = 1f;


const float k_NotificationFadeInTime = 0.04f;
const float k_NotificationFadeOutTime = 0.2f;
static readonly AnimationCurve k_NotificationFadeCurve = new AnimationCurve
{
keys = new[]
{
new Keyframe { time = 0f, value = 0f, outTangent = 1f / k_NotificationFadeInTime },
new Keyframe { time = k_NotificationFadeInTime, value = 1f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_NotificationDuration - k_NotificationFadeOutTime, value = 1f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_NotificationDuration, value = 0f, inTangent = -1f / k_NotificationFadeOutTime }
},
postWrapMode = WrapMode.Clamp,
preWrapMode = WrapMode.Clamp
};
const float k_IndeterminateProgressCurveDuration = 2f;
static readonly AnimationCurve k_IndeterminateProgressCurveLeftMargin = new AnimationCurve
{
keys = new[]
{
new Keyframe { time = 0f, value = 0f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration / 2f, value = 0.25f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration, value = 1f, inTangent = 0f, outTangent = 0f }
},
postWrapMode = WrapMode.Loop,
preWrapMode = WrapMode.Loop
};
static readonly AnimationCurve k_IndeterminateProgressCurveRightMargin = new AnimationCurve
{
keys = new[]
{
new Keyframe { time = 0f, value = 1f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration / 2f, value = 0f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration, value = 0f, inTangent = 0f, outTangent = 0f }
},
postWrapMode = WrapMode.Loop,
preWrapMode = WrapMode.Loop
};

static string s_StatusMessage;


static DateTime s_StartTime;
static bool s_IsTemporary;
static Func<float> s_GetProgress;

public static void DisplayProgressNotification(string message, Func<float> getProgress) =>


// insert an extra line to make room for progress bar
DisplayNotificationInSceneView(getProgress == null ? message : $"{message}\n", false, getProgress);
public static void DisplayPersistentNotification(string message) =>
DisplayNotificationInSceneView(message, false, null);

public static void DisplayTemporaryNotification(string message) =>


DisplayNotificationInSceneView(message, true, null);

static void DisplayNotificationInSceneView(string message, bool temporary, Func<float> getProgress)


{
s_StatusMessage = message ?? string.Empty;
s_StartTime = DateTime.Now;
s_IsTemporary = temporary;
s_GetProgress = getProgress;
ClearNotificationInSceneView();
SceneView.duringSceneGui += ToolNotificationCallback;
SceneView.RepaintAll();
}

static void ToolNotificationCallback(SceneView obj)


{
if (Camera.current == null)
return;
var duration = math.max(s_StatusMessage.Length, 1)
/ EditorPrefs.GetFloat(k_NotificationSpeedPrefKey, k_DefaultNotificationsSpeed);
var t = (float)(DateTime.Now - s_StartTime).TotalSeconds;
if (
s_IsTemporary
&& (t >= duration || !EditorPrefs.GetBool(k_NotificationsPrefKey, k_DefaultNotifications))
)
{
ClearNotificationInSceneView();
}
else
{
Handles.BeginGUI();
var color = GUI.color;
var progress = s_GetProgress?.Invoke() ?? 0f;
GUI.color *=
new Color(1f, 1f, 1f, math.max(k_NotificationFadeCurve.Evaluate(math.abs(t) / duration), progress));
var rect = new Rect { size = Camera.current.pixelRect.size / EditorGUIUtility.pixelsPerPoint };
using (new GUILayout.AreaScope(rect))
using (new GUILayout.HorizontalScope())
{
GUILayout.FlexibleSpace();
using (new GUILayout.VerticalScope())
{
GUILayout.Space(rect.height * 0.75f);
GUILayout.FlexibleSpace();
var maxWidth = rect.width * 0.5f;
GUILayout.Box(s_StatusMessage, Styles.SceneViewStatusMessage, GUILayout.MaxWidth(maxWidth));
if (s_GetProgress != null)
{
rect = GUILayoutUtility.GetLastRect();
rect = Styles.SceneViewStatusMessage.padding.Remove(rect);
rect.y = rect.yMax - Styles.ProgressBarTrack.fixedHeight;
rect.height = Styles.ProgressBarTrack.fixedHeight;
var c = GUI.color;
GUI.color *= Color.black;
GUI.Box(rect, GUIContent.none, Styles.ProgressBarTrack);
GUI.color = c;
if (progress >= 0f && progress <= 1f)
{
rect.width *= progress;
}
else
{
var w = rect.width;
rect.xMin = rect.xMin + w * k_IndeterminateProgressCurveLeftMargin.Evaluate(t);
rect.xMax = rect.xMax - w * k_IndeterminateProgressCurveRightMargin.Evaluate(t);
}
GUI.Box(rect, GUIContent.none, Styles.ProgressBarIndicator);
}
GUILayout.FlexibleSpace();
}
GUILayout.FlexibleSpace();
}
GUI.color = color;
Handles.EndGUI();
}
SceneView.RepaintAll();
}

public static void ClearNotificationInSceneView() => SceneView.duringSceneGui -= ToolNotificationCallback;


}
}
StressTest/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/StatusMessageUtility.cs
using System.Collections.Generic;
using System.Linq;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
using UnityObject = UnityEngine.Object;
using LegacyRigidBody = UnityEngine.Rigidbody;

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;

var targetType = c.GetType();


// only bodies (both explicit and implicit static bodies) will emit a message
if (
targetType == typeof(PhysicsBodyAuthoring)
|| targetType == typeof(LegacyRigidBody)
|| c.GetComponent<PhysicsBodyAuthoring>() == null
&& c.GetComponent<LegacyRigidBody>() == null
)
++numChildTargets;
}
switch (numChildTargets)
{
case 0:
return MessageType.None;
case 1:
statusMessage =
L10n.Tr("Target will be un-parented during the conversion process in order to take part in physics simulation.");
return MessageType.Warning;
default:
statusMessage =
L10n.Tr("One or more targets will be un-parented during the conversion process in order to take part in physics simulation.");
return MessageType.Warning;
}
}
public static MessageType GetMatrixStatusMessage(
IReadOnlyList<MatrixState> matrixStates, out string statusMessage
)
{
statusMessage = string.Empty;
if (matrixStates.Contains(MatrixState.NotValidTRS))
{
statusMessage = L10n.Tr(
matrixStates.Count == 1
? "Target's local-to-world matrix is not a valid transformation."
: "One or more targets' local-to-world matrices are not valid transformations."
);
return MessageType.Error;
}
if (matrixStates.Contains(MatrixState.ZeroScale))
{
statusMessage =
L10n.Tr(matrixStates.Count == 1 ? "Target has zero scale." : "One or more targets has zero scale.");
return MessageType.Warning;
}

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;

public class AIControllerAuthoring : MonoBehaviour


{
public AIController AIController;

class Baker : Baker<AIControllerAuthoring>


{
public override void Bake(AIControllerAuthoring authoring)
{
AddComponent(GetEntity(TransformUsageFlags.None), authoring.AIController);
}
}
}
Tutorial/Assets/Scripts/AIControllerSystem.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<PhysicsWorldSingleton>().PhysicsWorld;
NativeList<DistanceHit> distanceHits = new NativeList<DistanceHit>(Allocator.TempJob);
foreach (var (characterControl, aiController, localTransform) in SystemAPI.Query<RefRW<ThirdPersonCharacterControl>, AIController, LocalTransform>())
{
// Clear our detected hits list between each use
distanceHits.Clear();
// Create a hit collector for the detection hits
AllHitsCollector<DistanceHit> hitsCollector = new AllHitsCollector<DistanceHit>(aiController.DetectionDistance, ref distanceHits);
// 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);
// Iterate on all detected hits to try to find a human-controlled character...
Entity selectedTarget = Entity.Null;
for (int i = 0; i < hitsCollector.NumHits; i++)
{
Entity hitEntity = distanceHits[i].Entity;

// 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;

public class CharacterFrictionSurfaceSystem : MonoBehaviour


{
// Start is called before the first frame update
void Start()
{

// Update is called once per frame


void Update()
{

}
}
Tutorial/Assets/Scripts/JumpPad.cs
using Unity.Entities;
using Unity.Mathematics;

public struct JumpPad : IComponentData


{
public float3 JumpForce;
}
Tutorial/Assets/Scripts/JumpPadAuthoring.cs
using UnityEngine;
using Unity.Entities;
using Unity.Mathematics;

public class JumpPadAuthoring : MonoBehaviour


{
public float3 JumpForce;

class Baker : Baker<JumpPadAuthoring>


{
public override void Bake(JumpPadAuthoring authoring)
{
AddComponent(GetEntity(TransformUsageFlags.None), new JumpPad { JumpForce = authoring.JumpForce });
}
}
}
Tutorial/Assets/Scripts/JumpPadSystem.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<JumpPad, DynamicBuffer<StatefulTriggerEvent>>().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;
// Unground the character
// (without this, the character would snap right back to the ground on the next frame)
characterBody.IsGrounded = false;
// Don't forget to write back to the component
SystemAPI.SetComponent(otherEntity, characterBody);
}
}
}
}
}
Tutorial/Assets/Scripts/MovingPlatform.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;
}
Tutorial/Assets/Scripts/MovingPlatformAuthoring.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(GetEntity(TransformUsageFlags.Dynamic), authoring.MovingPlatform);
}
}
}
Tutorial/Assets/Scripts/MovingPlatformSystem.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
{
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.TranslationSpe
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
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/AssemblyInfo.cs
using System.Runtime.CompilerServices;

[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;

public RigidTransform worldFromA => LocalBody == null


? RigidTransform.identity
: Math.DecomposeRigidBodyTransform(LocalBody.transform.localToWorldMatrix);

public RigidTransform worldFromB => ConnectedBody == null


? RigidTransform.identity
: Math.DecomposeRigidBodyTransform(ConnectedBody.transform.localToWorldMatrix);

public Entity EntityA { get; set; }

public Entity EntityB { get; set; }

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);

public override int GetHashCode() => Value;


}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/EditorInitialization.cs
#if UNITY_EDITOR
using System.Linq;
using UnityEditor;
namespace Unity.Physics.Authoring
{
[InitializeOnLoad]
class EditorInitialization
{
static readonly string k_CustomDefine = "UNITY_PHYSICS_CUSTOM";
static EditorInitialization()
{
var definesStr = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
var defines = definesStr.Split(';').ToList();
var found = defines.Find(define => define.Equals(k_CustomDefine));
if (found == null)
{
defines.Add(k_CustomDefine);
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup,
string.Join(";", defines.ToArray()));
}
}
}
}
#endif
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsCategoryNames.cs
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace Unity.Physics.Authoring
{
[CreateAssetMenu(menuName = "Unity Physics/Physics Category Names", fileName = "Physics Category Names", order = 507)]
public sealed class PhysicsCategoryNames : ScriptableObject, ITagNames
{
PhysicsCategoryNames() {}

IReadOnlyList<string> ITagNames.TagNames => CategoryNames;

public IReadOnlyList<string> CategoryNames => m_CategoryNames;


[SerializeField]
string[] m_CategoryNames = Enumerable.Range(0, 32).Select(i => string.Empty).ToArray();

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);

public override int GetHashCode() => unchecked((int)Value);


}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsMaterialProperties.cs
using System;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
interface IPhysicsMaterialProperties
{
CollisionResponsePolicy CollisionResponse { get; set; }
PhysicsMaterialCoefficient Friction { get; set; }

PhysicsMaterialCoefficient Restitution { get; set; }


PhysicsCategoryTags BelongsTo { get; set; }
PhysicsCategoryTags CollidesWith { get; set; }
// TODO: Enable Mass Factors?
// TODO: Surface Velocity?
CustomPhysicsMaterialTags CustomTags { get; set; }
}

interface IInheritPhysicsMaterialProperties : IPhysicsMaterialProperties


{
PhysicsMaterialTemplate Template { get; set; }
bool OverrideCollisionResponse { get; set; }
bool OverrideFriction { get; set; }
bool OverrideRestitution { get; set; }
bool OverrideBelongsTo { get; set; }
bool OverrideCollidesWith { get; set; }
bool OverrideCustomTags { get; set; }
}

[Serializable]
public struct PhysicsMaterialCoefficient
{
[SoftRange(0f, 1f, TextFieldMax = float.MaxValue)]
public float Value;
public Material.CombinePolicy CombineMode;
}

abstract class OverridableValue<T> where T : struct


{
public bool Override { get => m_Override; set => m_Override = value; }
[SerializeField]
bool m_Override;

public T Value
{
get => m_Value;
set
{
m_Value = value;
Override = true;
}
}
[SerializeField]
T m_Value;

public void OnValidate() => OnValidate(ref m_Value);


protected virtual void OnValidate(ref T 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 PhysicsMaterialTemplate Template


{
get => m_Template;
set => m_Template = m_SupportsTemplate ? value : null;
}
[SerializeField]
[Tooltip("Assign a template to use its values.")]
PhysicsMaterialTemplate m_Template;

static T Get<T>(OverridableValue<T> value, T? templateValue) where T : struct =>


value.Override || templateValue == null ? value.Value : templateValue.Value;

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 };

internal static void OnValidate(ref PhysicsMaterialProperties material, bool supportsTemplate)


{
material.UpgradeVersionIfNecessary();

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();
}

const int k_LatestVersion = 1;

[SerializeField]
int m_SerializedVersion = 0;

void ISerializationCallbackReceiver.OnBeforeSerialize() {}
void ISerializationCallbackReceiver.OnAfterDeserialize() => UpgradeVersionIfNecessary();

internal static bool s_SuppressUpgradeWarnings;

#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


}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsMaterialTemplate.cs
using UnityEngine;

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);

void Reset() => OnValidate();


void OnValidate() => PhysicsMaterialProperties.OnValidate(ref m_Value, false);
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/PhysicsRenderEntityAuthoring.cs
using UnityEngine;
using Unity.Entities;
using Unity.Physics.GraphicsIntegration;
namespace Unity.Physics.Authoring
{
[AddComponentMenu("Entities/Physics/Physics Render Entity")]
[DisallowMultipleComponent]
public sealed class PhysicsRenderEntityAuthoring : MonoBehaviour
{
[Tooltip("Specifies an Entity in a different branch of the hierarchy that holds the graphical representation of this PhysicsShape.")]
public GameObject RenderEntity;
}
internal class PhysicsRenderEntityBaker : Baker<PhysicsRenderEntityAuthoring>
{
public override void Bake(PhysicsRenderEntityAuthoring authoring)
{
var renderEntity = new PhysicsRenderEntity { Entity = GetEntity(authoring.RenderEntity, TransformUsageFlags.Dynamic) };
var entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, renderEntity);
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Bodies/PhysicsBodyAuthoring.cs
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
/// <summary> The physics body authoring. This class cannot be inherited. </summary>
#if UNITY_2021_2_OR_NEWER
[Icon(k_IconPath)]
#endif
[AddComponentMenu("Entities/Physics/Physics Body")]
[DisallowMultipleComponent]
public sealed class PhysicsBodyAuthoring : MonoBehaviour
{
const string k_IconPath = "Packages/com.unity.physics/Unity.Physics.Editor/Editor Default Resources/Icons/d_Rigidbody@64.png";

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;
}

static readonly int[] k_NextAxis = { 1, 2, 0 };


public ShapeType ShapeType => m_ShapeType;
[SerializeField]
ShapeType m_ShapeType = ShapeType.Box;
[SerializeField]
float3 m_PrimitiveCenter;
[SerializeField]
float3 m_PrimitiveSize = new float3(1f, 1f, 1f);
[SerializeField]
EulerAngles m_PrimitiveOrientation = EulerAngles.Default;

[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;
}

public PhysicsCategoryTags BelongsTo


{
get => m_Material.BelongsTo;
set => m_Material.BelongsTo = value;
}
public bool OverrideCollidesWith
{
get => m_Material.OverrideCollidesWith;
set => m_Material.OverrideCollidesWith = value;
}
public PhysicsCategoryTags CollidesWith
{
get => m_Material.CollidesWith;
set => m_Material.CollidesWith = value;
}
public bool OverrideCustomTags
{
get => m_Material.OverrideCustomTags;
set => m_Material.OverrideCustomTags = value;
}
public CustomPhysicsMaterialTags CustomTags { get => m_Material.CustomTags; set => m_Material.CustomTags = value; }
[SerializeField]
PhysicsMaterialProperties m_Material = new PhysicsMaterialProperties(true);

public BoxGeometry GetBoxProperties() => GetBoxProperties(out _);


internal BoxGeometry GetBoxProperties(out EulerAngles orientation)
{
orientation = m_PrimitiveOrientation;
return new BoxGeometry
{
Center = m_PrimitiveCenter,
Size = m_PrimitiveSize,
Orientation = m_PrimitiveOrientation,
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius
};
}
void GetCylindricalProperties(
CylindricalProperties props,
out float3 center, out float height, out float radius, out EulerAngles orientation,
bool rebuildOrientation
)
{
center = m_PrimitiveCenter;
var lookVector = math.mul(m_PrimitiveOrientation, new float3 { [props.Axis] = 1f });
// use previous axis so forward will prefer up
var upVector = math.mul(m_PrimitiveOrientation, new float3 { [k_NextAxis[k_NextAxis[props.Axis]]] = 1f });
orientation = m_PrimitiveOrientation;
if (rebuildOrientation && props.Axis != 2)
orientation.SetValue(quaternion.LookRotation(lookVector, upVector));
radius = props.Radius;
height = props.Height;
}
internal CapsuleGeometryAuthoring GetCapsuleProperties()
{
GetCylindricalProperties(
m_Capsule, out var center, out var height, out var radius, out var orientationEuler, m_ShapeType != ShapeType.Capsule
);
return new CapsuleGeometryAuthoring
{
OrientationEuler = orientationEuler,
Center = center,
Height = height,
Radius = radius
};
}
public CylinderGeometry GetCylinderProperties() => GetCylinderProperties(out _);
internal CylinderGeometry GetCylinderProperties(out EulerAngles orientation)
{
GetCylindricalProperties(
m_Cylinder, out var center, out var height, out var radius, out orientation, m_ShapeType != ShapeType.Cylinder
);
return new CylinderGeometry
{
Center = center,
Height = height,
Radius = radius,
Orientation = orientation,
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius,
SideCount = m_CylinderSideCount
};
}
public SphereGeometry GetSphereProperties(out quaternion orientation)
{
var result = GetSphereProperties(out EulerAngles euler);
orientation = euler;
return result;
}
internal SphereGeometry GetSphereProperties(out EulerAngles orientation)
{
orientation = m_PrimitiveOrientation;
return new SphereGeometry
{
Center = m_PrimitiveCenter,
Radius = m_SphereRadius
};
}
public void GetPlaneProperties(out float3 center, out float2 size, out quaternion orientation)
{
GetPlaneProperties(out center, out size, out EulerAngles euler);
orientation = euler;
}
internal void GetPlaneProperties(out float3 center, out float2 size, out EulerAngles orientation)
{
center = m_PrimitiveCenter;
orientation = m_PrimitiveOrientation;
if (m_ShapeType == ShapeType.Plane)
{
size = m_PrimitiveSize.xz;
return;
}
UpdateCapsuleAxis();
var look = m_Capsule.Axis;
var nextAx = k_NextAxis[look];
var prevAx = k_NextAxis[k_NextAxis[look]];
var ax2 = m_PrimitiveSize[nextAx] > m_PrimitiveSize[prevAx] ? nextAx : prevAx;
size = new float2(m_PrimitiveSize[ax2], m_PrimitiveSize[look]);
var up = k_NextAxis[ax2] == look ? k_NextAxis[look] : k_NextAxis[ax2];
var offset = quaternion.LookRotation(new float3 { [look] = 1f }, new float3 { [up] = 1f });
orientation.SetValue(math.mul(m_PrimitiveOrientation, offset));
}
static readonly HashSet<int> s_BoneIDs = new HashSet<int>();
static readonly HashSet<Transform> s_BonesInHierarchy = new HashSet<Transform>();
static readonly List<Vector3> s_Vertices = new List<Vector3>(65535);
static readonly List<int> s_Indices = new List<int>(65535);
static UnityMesh ReusableBakeMesh =>
s_ReusableBakeMesh ??
(s_ReusableBakeMesh = new UnityMesh { hideFlags = HideFlags.HideAndDontSave });
static UnityMesh s_ReusableBakeMesh;
public void GetConvexHullProperties(NativeList<float3> pointCloud) =>
GetConvexHullProperties(pointCloud, true, default, default, default, default);
internal void GetConvexHullProperties(
NativeList<float3> pointCloud, bool validate,
NativeList<HashableShapeInputs> inputs, NativeList<int> allSkinIndices, NativeList<float> allBlendShapeWeights,
HashSet<UnityMesh> meshAssets
)
{
if (pointCloud.IsCreated)
pointCloud.Clear();
if (inputs.IsCreated)
inputs.Clear();
if (allSkinIndices.IsCreated)
allSkinIndices.Clear();
if (allBlendShapeWeights.IsCreated)
allBlendShapeWeights.Clear();
meshAssets?.Clear();
if (m_CustomMesh != null)
{
if (validate && !m_CustomMesh.IsValidForConversion(gameObject))
return;
AppendMeshPropertiesToNativeBuffers(
transform.localToWorldMatrix, m_CustomMesh, pointCloud, default, validate, inputs, meshAssets
);
}
else
{
using (var scope = new GetActiveChildrenScope<MeshFilter>(this, transform))
{
foreach (var meshFilter in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(meshFilter, validate))
{
AppendMeshPropertiesToNativeBuffers(
meshFilter.transform.localToWorldMatrix, meshFilter.sharedMesh, pointCloud, default, validate, inputs, meshAssets
);
}
}
}

using (var skinnedPoints = new NativeList<float3>(8192, Allocator.Temp))


using (var skinnedInputs = new NativeList<HashableShapeInputs>(8, Allocator.Temp))
{
GetAllSkinnedPointsInHierarchyBelongingToShape(
this, skinnedPoints, validate, skinnedInputs, allSkinIndices, allBlendShapeWeights
);
if (pointCloud.IsCreated)
pointCloud.AddRange(skinnedPoints.AsArray());
if (inputs.IsCreated)
inputs.AddRange(skinnedInputs.AsArray());
}
}
}

internal static void GetAllSkinnedPointsInHierarchyBelongingToShape(


PhysicsShapeAuthoring shape, NativeList<float3> pointCloud, bool validate,
NativeList<HashableShapeInputs> inputs, NativeList<int> allIncludedIndices, NativeList<float> allBlendShapeWeights
)
{
if (pointCloud.IsCreated)
pointCloud.Clear();
if (inputs.IsCreated)
inputs.Clear();
// get all the transforms that belong to this shape
s_BonesInHierarchy.Clear();
using (var scope = new GetActiveChildrenScope<Transform>(shape, shape.transform))
{
foreach (var bone in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(bone))
s_BonesInHierarchy.Add(bone);
}
}
// find all skinned mesh renderers in which this shape's transform might be a bone
using (var scope = new GetActiveChildrenScope<SkinnedMeshRenderer>(shape, shape.transform.root))
{
foreach (var skin in scope.Buffer)
{
var mesh = skin.sharedMesh;
if (
!skin.enabled
|| mesh == null
|| validate && !mesh.IsValidForConversion(shape.gameObject)
|| !scope.IsChildActiveAndBelongsToShape(skin)
)
continue;
// get indices of this shape's transform hierarchy in skinned mesh's bone array
s_BoneIDs.Clear();
var bones = skin.bones;
for (int i = 0, count = bones.Length; i < count; ++i)
{
if (s_BonesInHierarchy.Contains(bones[i]))
s_BoneIDs.Add(i);
}
if (s_BoneIDs.Count == 0)
continue;
// sample the vertices
if (pointCloud.IsCreated)
{
skin.BakeMesh(ReusableBakeMesh);
ReusableBakeMesh.GetVertices(s_Vertices);
}
// add all vertices weighted to at least one bone in this shape's transform hierarchy
var bonesPerVertex = mesh.GetBonesPerVertex(); // Allocator.None
var weights = mesh.GetAllBoneWeights(); // Allocator.None
var vertexIndex = 0;
var weightsOffset = 0;
var shapeFromSkin = math.mul(shape.transform.worldToLocalMatrix, skin.transform.localToWorldMatrix);
var includedIndices = new NativeList<int>(mesh.vertexCount, Allocator.Temp);
foreach (var weightCount in bonesPerVertex)
{
var totalWeight = 0f;
for (var i = 0; i < weightCount; ++i)
{
var weight = weights[weightsOffset + i];
if (s_BoneIDs.Contains(weight.boneIndex))
totalWeight += weight.weight;
}
if (totalWeight > shape.m_MinimumSkinnedVertexWeight)
{
if (pointCloud.IsCreated)
pointCloud.Add(math.mul(shapeFromSkin, new float4(s_Vertices[vertexIndex], 1f)).xyz);
includedIndices.Add(vertexIndex);
}
weightsOffset += weightCount;
++vertexIndex;
}
if (!inputs.IsCreated || !allIncludedIndices.IsCreated || !allBlendShapeWeights.IsCreated)
continue;
var blendShapeWeights = new NativeArray<float>(mesh.blendShapeCount, Allocator.Temp);
for (var i = 0; i < blendShapeWeights.Length; ++i)
blendShapeWeights[i] = skin.GetBlendShapeWeight(i);
var data = HashableShapeInputs.FromSkinnedMesh(
mesh, shapeFromSkin, includedIndices.AsArray(), allIncludedIndices, blendShapeWeights, allBlendShapeWeights
);
inputs.Add(data);
}
}
s_BonesInHierarchy.Clear();
}
public void GetMeshProperties(NativeList<float3> vertices, NativeList<int3> triangles) =>
GetMeshProperties(vertices, triangles, true, default);
internal void GetMeshProperties(
NativeList<float3> vertices, NativeList<int3> triangles, bool validate, NativeList<HashableShapeInputs> inputs, HashSet<UnityMesh> meshAssets = null
)
{
if (vertices.IsCreated)
vertices.Clear();
if (triangles.IsCreated)
triangles.Clear();
if (inputs.IsCreated)
inputs.Clear();
meshAssets?.Clear();
if (m_CustomMesh != null)
{
if (validate && !m_CustomMesh.IsValidForConversion(gameObject))
return;
AppendMeshPropertiesToNativeBuffers(
transform.localToWorldMatrix, m_CustomMesh, vertices, triangles, validate, inputs, meshAssets
);
}
else
{
using (var scope = new GetActiveChildrenScope<MeshFilter>(this, transform))
{
foreach (var meshFilter in scope.Buffer)
{
if (scope.IsChildActiveAndBelongsToShape(meshFilter, validate))
{
AppendMeshPropertiesToNativeBuffers(
meshFilter.transform.localToWorldMatrix, meshFilter.sharedMesh, vertices, triangles, validate, inputs, meshAssets
);
}
}
}
}
}

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();

void Sync(ref CylindricalProperties props)


{
props.Height = m_PrimitiveSize[props.Axis];
props.Radius = 0.5f * math.max(
m_PrimitiveSize[k_NextAxis[props.Axis]],
m_PrimitiveSize[k_NextAxis[k_NextAxis[props.Axis]]]
);
}
void SyncCapsuleProperties()
{
if (m_ShapeType == ShapeType.Box || m_ShapeType == ShapeType.Plane)
UpdateCapsuleAxis();
else
m_Capsule.Axis = 2;
Sync(ref m_Capsule);
}
void SyncCylinderProperties()
{
if (m_ShapeType == ShapeType.Box || m_ShapeType == ShapeType.Plane)
UpdateCylinderAxis();
else
m_Cylinder.Axis = 2;
Sync(ref m_Cylinder);
}
void SyncSphereProperties()
{
m_SphereRadius = 0.5f * math.cmax(m_PrimitiveSize);
}
public void SetBox(BoxGeometry geometry)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(geometry.Orientation);
SetBox(geometry, euler);
}
internal void SetBox(BoxGeometry geometry, EulerAngles orientation)
{
m_ShapeType = ShapeType.Box;
m_PrimitiveCenter = geometry.Center;
m_PrimitiveSize = math.max(geometry.Size, new float3());
m_PrimitiveOrientation = orientation;
m_ConvexHullGenerationParameters.BevelRadius = geometry.BevelRadius;
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
internal void SetCapsule(CapsuleGeometryAuthoring geometry)
{
m_ShapeType = ShapeType.Capsule;
m_PrimitiveCenter = geometry.Center;
m_PrimitiveOrientation = geometry.OrientationEuler;
var radius = math.max(0f, geometry.Radius);
var height = math.max(0f, geometry.Height);
m_PrimitiveSize = new float3(radius * 2f, radius * 2f, height);
SyncCapsuleProperties();
SyncCylinderProperties();
SyncSphereProperties();
}
public void SetCylinder(CylinderGeometry geometry)
{
var euler = m_PrimitiveOrientation;
euler.SetValue(geometry.Orientation);
SetCylinder(geometry, euler);
}
internal void SetCylinder(CylinderGeometry geometry, EulerAngles orientation)
{
m_ShapeType = ShapeType.Cylinder;
m_PrimitiveCenter = geometry.Center;
m_PrimitiveOrientation = orientation;
geometry.Radius = math.max(0f, geometry.Radius);
geometry.Height = math.max(0f, geometry.Height);
m_PrimitiveSize = new float3(geometry.Radius * 2f, geometry.Radius * 2f, geometry.Height);

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;
}

bool GetMeshes(PhysicsShapeAuthoring shape, out List<UnityEngine.Mesh> meshes, out List<float4x4> childrenToShape)


{
meshes = new List<UnityMesh>();
childrenToShape = new List<float4x4>();
if (shape.CustomMesh != null)
{
meshes.Add(shape.CustomMesh);
childrenToShape.Add(float4x4.identity);
}
// Try to get all the meshes in the children
var meshFilters = GetComponentsInChildren<MeshFilter>();
foreach (var meshFilter in meshFilters)
{
if (meshFilter != null && meshFilter.sharedMesh != null)
{
var shapeAuthoring = GetComponent<PhysicsShapeAuthoring>(meshFilter);
if (shapeAuthoring != null && shapeAuthoring != shape)
{
// Skip this case, since it will be treated independently
continue;
}
meshes.Add(meshFilter.sharedMesh);
// Don't calculate the children to shape if not needed, to avoid approximation that could prevent collider to be shared
if (shape.transform.localToWorldMatrix.Equals(meshFilter.transform.localToWorldMatrix))
childrenToShape.Add(float4x4.identity);
else
{
var transform = math.mul(shape.transform.worldToLocalMatrix, meshFilter.transform.localToWorldMatrix);
childrenToShape.Add(transform);
}
DependsOn(meshes.Last());
}
}
return meshes.Count > 0;
}
UnityEngine.Mesh CombineMeshes(PhysicsShapeAuthoring shape, List<UnityEngine.Mesh> meshes, List<float4x4> childrenToShape)
{
var instances = new List<CombineInstance>();
var numVertices = 0;
for (var i = 0; i < meshes.Count; ++i)
{
var currentMesh = meshes[i];
var currentChildToShape = childrenToShape[i];
if (!currentMesh.IsValidForConversion(shape.gameObject))
{
throw new InvalidOperationException(
$"Mesh '{currentMesh}' assigned on {shape.name} is not readable. Ensure that you have enabled Read/Write on its import settings."
);
}
// Combine submeshes manually
numVertices += meshes[i].vertexCount;
var combinedSubmeshes = new UnityEngine.Mesh();
combinedSubmeshes.vertices = currentMesh.vertices;
var combinedIndices = new List<int>();
for (int indexSubMesh = 0; indexSubMesh < meshes[i].subMeshCount; ++indexSubMesh)
{
combinedIndices.AddRange(currentMesh.GetIndices(indexSubMesh));
}
combinedSubmeshes.SetIndices(combinedIndices, MeshTopology.Triangles, 0);
combinedSubmeshes.RecalculateNormals();
var instance = new CombineInstance
{
mesh = combinedSubmeshes,
transform = currentChildToShape,
};
instances.Add(instance);
}
var mesh = new UnityEngine.Mesh();
mesh.indexFormat = numVertices > UInt16.MaxValue ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
mesh.CombineMeshes(instances.ToArray());
mesh.RecalculateBounds();
return mesh;
}
private ShapeComputationDataBaking GenerateComputationData(PhysicsShapeAuthoring shape, ColliderInstanceBaking colliderInstance, Entity colliderEntity)
{
var res = new ShapeComputationDataBaking
{
Instance = colliderInstance,
Material = ProduceMaterial(shape),
CollisionFilter = ProduceCollisionFilter(shape),
ForceUniqueIdentifier = shape.ForceUnique ? (uint)shape.GetInstanceID() : 0u
};
var transform = shape.transform;
var localToWorld = transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
EulerAngles orientation;
res.ShapeType = shape.ShapeType;
switch (shape.ShapeType)
{
case ShapeType.Box:
{
res.BoxProperties = shape.GetBoxProperties(out orientation)
.BakeToBodySpace(localToWorld, shapeToWorld, orientation);
break;
}
case ShapeType.Capsule:
{
res.CapsuleProperties = shape.GetCapsuleProperties()
.BakeToBodySpace(localToWorld, shapeToWorld)
.ToRuntime();
break;
}
case ShapeType.Sphere:
{
res.SphereProperties = shape.GetSphereProperties(out orientation)
.BakeToBodySpace(localToWorld, shapeToWorld, ref orientation);
break;
}
case ShapeType.Cylinder:
{
res.CylinderProperties = shape.GetCylinderProperties(out orientation)
.BakeToBodySpace(localToWorld, shapeToWorld, orientation);
break;
}
case ShapeType.Plane:
{
shape.GetPlaneProperties(out var center, out var size, out orientation);
PhysicsShapeExtensions.BakeToBodySpace(
center, size, orientation, localToWorld, shapeToWorld,
out res.PlaneVertices.c0, out res.PlaneVertices.c1, out res.PlaneVertices.c2, out res.PlaneVertices.c3
);
break;
}
case ShapeType.ConvexHull:
{
res.ConvexHullProperties.Filter = res.CollisionFilter;
res.ConvexHullProperties.Material = res.Material;
res.ConvexHullProperties.GenerationParameters = shape.ConvexHullGenerationParameters.ToRunTime();
CreateMeshAuthoringData(shape, colliderEntity);
break;
}
case ShapeType.Mesh:
{
res.MeshProperties.Filter = res.CollisionFilter;
res.MeshProperties.Material = res.Material;
CreateMeshAuthoringData(shape, colliderEntity);
break;
}
}
return res;
}
private void CreateMeshAuthoringData(PhysicsShapeAuthoring shape, Entity colliderEntity)
{
if (GetMeshes(shape, out var meshes, out var childrenToShape))
{
// Combine all detected meshes into a single one
var mesh = CombineMeshes(shape, meshes, childrenToShape);
if (!mesh.IsValidForConversion(shape.gameObject))
{
throw new InvalidOperationException(
$"Mesh '{mesh}' assigned on {shape.name} is not readable. Ensure that you have enabled Read/Write on its import settings."
);
}
var bakeFromShape = shape.GetLocalToShapeMatrix();
var meshBakingData = new PhysicsMeshAuthoringData()
{
Convex = shape.ShapeType == ShapeType.ConvexHull,
Mesh = mesh,
BakeFromShape = bakeFromShape,
MeshBounds = mesh.bounds,
ChildToShape = float4x4.identity
};
AddComponent(colliderEntity, meshBakingData);
}
else
{
throw new InvalidOperationException(
$"No {nameof(PhysicsShapeAuthoring.CustomMesh)} or {nameof(MeshFilter.sharedMesh)} assigned on {shape.name}."
);
}
}
public override void Bake(PhysicsShapeAuthoring authoring)
{
var shapeBakingData = new PhysicsColliderAuthoringData();
// First pass
Profiler.BeginSample("Collect Inputs from Authoring Components");
if (ShouldConvertShape(authoring))
{
// We can have multiple Colliders of the same type on the same game object, so instead of adding the components to the baking entity
// we add the components to an additional entity. These new entities will be processed by the baking system
var colliderEntity = CreateAdditionalEntity(TransformUsageFlags.None, true);
shapeBakingData.ShapeComputationalData = GetInputDataFromAuthoringComponent(authoring, colliderEntity);
AddComponent(colliderEntity, shapeBakingData);
// The data will be filled in by the BaseShapeBakingSystem, but we add it here so it gets reverted from the entity if the collider component is deleted
AddComponent(colliderEntity, new PhysicsColliderBakedData()
{
BodyEntity = shapeBakingData.ShapeComputationalData.Instance.BodyEntity,
BodyFromShape = shapeBakingData.ShapeComputationalData.Instance.BodyFromShape,
ChildEntity = shapeBakingData.ShapeComputationalData.Instance.ChildEntity,
// It is a leaf if the Shape Entity equals Body Entity
IsLeafEntityBody = (shapeBakingData.ShapeComputationalData.Instance.ShapeEntity.Equals(shapeBakingData.ShapeComputationalData.Instance.BodyEntity))
});
}
Profiler.EndSample();
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Joints/BallAndSocketJoint.cs
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

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;

public float3 PositionLocal;


public float3 PositionInConnectedEntity;

public virtual void UpdateAuto()


{
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
PositionInConnectedEntity = math.transform(bFromA, PositionLocal);
}
}
}
public abstract class JointBaker<T> : Baker<T> where T : BaseJoint
{
protected PhysicsConstrainedBodyPair GetConstrainedBodyPair(BaseJoint authoring)
{
return new PhysicsConstrainedBodyPair(
GetEntity(TransformUsageFlags.Dynamic),
authoring.ConnectedBody == null ? Entity.Null : GetEntity(authoring.ConnectedBody, TransformUsageFlags.Dynamic),
authoring.EnableCollision
);
}
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 uint GetWorldIndex(Component c)


{
uint worldIndex = 0;
if (c)
{
var physicsBody = GetComponent<PhysicsBodyAuthoring>(c);
if (physicsBody != null)
{
worldIndex = physicsBody.WorldIndex;
}
}
return worldIndex;
}
public uint GetWorldIndexFromBaseJoint(BaseJoint authoring)
{
var physicsBody = GetComponent<PhysicsBodyAuthoring>(authoring);
uint worldIndex = physicsBody.WorldIndex;
if (authoring.ConnectedBody == null)
{
return worldIndex;
}
var connectedBody = GetComponent<PhysicsBodyAuthoring>(authoring.ConnectedBody);
if (connectedBody != null)
{
Assertions.Assert.AreEqual(worldIndex, connectedBody.WorldIndex);
}
return worldIndex;
}
public void CreateJointEntities(uint worldIndex, PhysicsConstrainedBodyPair constrainedBodyPair, NativeArray<PhysicsJoint> joints, NativeList<Entity> newJointEntities)
{
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;
var entity = GetEntity(TransformUsageFlags.Dynamic);
for (var i = 0; i < joints.Length; ++i)
{
var jointEntity = CreateAdditionalEntity(TransformUsageFlags.Dynamic);
AddSharedComponent(jointEntity, new PhysicsWorldIndex(worldIndex));
AddComponent(jointEntity, constrainedBodyPair);
AddComponent(jointEntity, joints[i]);
newJointEntities.Add(jointEntity);
if (GetComponent<ModifyJointLimitsAuthoring>() != null)
{
AddComponent(jointEntity, new JointEntityBaking()
{
Entity = entity
});
AddSharedComponentManaged(jointEntity, new ModifyJointLimits());
}
}
if (multipleJoints)
{
// set companion buffers for new joints
for (var i = 0; i < joints.Length; ++i)
{
var companions = AddBuffer<PhysicsJointCompanion>(newJointEntities[i]);
for (var j = 0; j < joints.Length; ++j)
{
if (i == j)
continue;
companions.Add(new PhysicsJointCompanion {JointEntity = newJointEntities[j]});
}
}
}
}
}
class BallAndSocketJointBaker : JointBaker<BallAndSocketJoint>
{
public override void Bake(BallAndSocketJoint authoring)
{
authoring.UpdateAuto();
var physicsJoint = PhysicsJoint.CreateBallAndSocket(authoring.PositionLocal, authoring.PositionInConnectedEntity);
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/BaseJoint.cs
using Unity.Mathematics;

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;

for (var i = 0; i < joints.Length; ++i)


{
var jointEntity = CreateAdditionalEntity(TransformUsageFlags.Dynamic);
AddSharedComponent(jointEntity, new PhysicsWorldIndex(worldIndex));
AddComponent(jointEntity, constrainedBodyPair);
AddComponent(jointEntity, joints[i]);
newJointEntities.Add(jointEntity);
}
if (multipleJoints)
{
// set companion buffers for new joints
for (var i = 0; i < joints.Length; ++i)
{
var companions = AddBuffer<PhysicsJointCompanion>(newJointEntities[i]);
for (var j = 0; j < joints.Length; ++j)
{
if (i == j)
continue;
companions.Add(new PhysicsJointCompanion {JointEntity = newJointEntities[j]});
}
}
}
}
protected PhysicsConstrainedBodyPair GetConstrainedBodyPair(LimitDOFJoint authoring)
{
return new PhysicsConstrainedBodyPair(
GetEntity(TransformUsageFlags.Dynamic),
authoring.ConnectedBody == null ? Entity.Null : GetEntity(authoring.ConnectedBody, TransformUsageFlags.Dynamic),
authoring.EnableCollision
);
}
public uint GetWorldIndex(Component c)
{
uint worldIndex = 0;
var physicsBody = GetComponent<PhysicsBodyAuthoring>(c);
if (physicsBody != null)
{
worldIndex = physicsBody.WorldIndex;
}
return worldIndex;
}
public override void Bake(LimitDOFJoint authoring)
{
if (!math.any(authoring.LockLinearAxes) && !math.any(authoring.LockAngularAxes))
return;
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
PhysicsJoint physicsJoint = authoring.CreateLimitDOFJoint(bFromA);
var worldIndex = GetWorldIndex(authoring);
CreateJointEntity(
worldIndex,
GetConstrainedBodyPair(authoring),
physicsJoint
);
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Joints/LimitedDistanceJoint.cs
using Unity.Entities;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class LimitedDistanceJoint : BallAndSocketJoint
{
public float MinDistance;
public float MaxDistance;
}
class LimitedDistanceJointBaker : JointBaker<LimitedDistanceJoint>
{
public override void Bake(LimitedDistanceJoint authoring)
{
authoring.UpdateAuto();
var physicsJoint = PhysicsJoint.CreateLimitedDistance(authoring.PositionLocal, authoring.PositionInConnectedEntity, new FloatRange(authoring.MinDistance, authoring.MaxDistance
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/LimitedHingeJoint.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class LimitedHingeJoint : FreeHingeJoint
{
// Editor only settings
[HideInInspector]
public bool EditLimits;

public float3 PerpendicularAxisLocal;


public float3 PerpendicularAxisInConnectedEntity;
public float MinAngle;
public float MaxAngle;

public override void UpdateAuto()


{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
HingeAxisInConnectedEntity = math.mul(bFromA.rot, HingeAxisLocal);
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
}
}
}

class LimitedHingeJointBaker : JointBaker<LimitedHingeJoint>


{
public override void Bake(LimitedHingeJoint authoring)
{
authoring.UpdateAuto();

var physicsJoint = PhysicsJoint.CreateLimitedHinge(


new BodyFrame
{
Axis = math.normalize(authoring.HingeAxisLocal),
PerpendicularAxis = math.normalize(authoring.PerpendicularAxisLocal),
Position = authoring.PositionLocal
},
new BodyFrame
{
Axis = math.normalize(authoring.HingeAxisInConnectedEntity),
PerpendicularAxis = math.normalize(authoring.PerpendicularAxisInConnectedEntity),
Position = authoring.PositionInConnectedEntity
},
math.radians(new FloatRange(authoring.MinAngle, authoring.MaxAngle))
);

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/ModifyJointLimitsAuthoring.cs
using System;
using System.Collections.Generic;
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 UnityEngine;
using LegacyJoint = UnityEngine.Joint;
using FloatRange = Unity.Physics.Math.FloatRange;

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);

public override int GetHashCode() =>


unchecked((AngularRangeScalar.GetHashCode() * 397) ^ LinearRangeScalar.GetHashCode());
}

// an authoring component to add to a GameObject with one or more Joint


public class ModifyJointLimitsAuthoring : MonoBehaviour
{
public ParticleSystem.MinMaxCurve AngularRangeScalar = new ParticleSystem.MinMaxCurve(
1f,
min: new AnimationCurve(
new Keyframe(0f, 0f, 0f, 0f),
new Keyframe(2f, -2f, 0f, 0f),
new Keyframe(4f, 0f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
},
max: new AnimationCurve(
new Keyframe(0f, 1f, 0f, 0f),
new Keyframe(2f, -1f, 0f, 0f),
new Keyframe(4f, 1f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
}
);
public ParticleSystem.MinMaxCurve LinearRangeScalar = new ParticleSystem.MinMaxCurve(
1f,
min: new AnimationCurve(
new Keyframe(0f, 1f, 0f, 0f),
new Keyframe(2f, 0.5f, 0f, 0f),
new Keyframe(4f, 1f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
},
max: new AnimationCurve(
new Keyframe(0f, 0.5f, 0f, 0f),
new Keyframe(2f, 0f, 0f, 0f),
new Keyframe(4f, 0.5f, 0f, 0f)
)
{
preWrapMode = WrapMode.Loop,
postWrapMode = WrapMode.Loop
}
);
}
[BakingType]
public class ModifyJointLimitsBakingData : IComponentData
{
public ParticleSystem.MinMaxCurve AngularRangeScalar;
public ParticleSystem.MinMaxCurve LinearRangeScalar;
}
class ModifyJointLimitsBaker : Baker<ModifyJointLimitsAuthoring>
{
public override void Bake(ModifyJointLimitsAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponentObject(entity, new ModifyJointLimitsBakingData
{
AngularRangeScalar = authoring.AngularRangeScalar,
LinearRangeScalar = authoring.LinearRangeScalar
});
}
}
// after joints have been converted, find the entities they produced and add ModifyJointLimits to them
[UpdateAfter(typeof(EndJointBakingSystem))]
[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
partial struct ModifyJointLimitsBakingSystem : ISystem
{
private EntityQuery _ModifyJointLimitsBakingDataQuery;
private EntityQuery _JointEntityBakingQuery;
public void OnCreate(ref SystemState state)
{
_ModifyJointLimitsBakingDataQuery = state.GetEntityQuery(new EntityQueryDesc
{
All = new[] {ComponentType.ReadOnly<ModifyJointLimitsBakingData>()},
Options = EntityQueryOptions.IncludeDisabledEntities | EntityQueryOptions.IncludePrefab
});
_JointEntityBakingQuery = state.GetEntityQuery(new EntityQueryDesc
{
All = new[] {ComponentType.ReadOnly<JointEntityBaking>()}
});

_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;

public override void UpdateAuto()


{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
AxisInConnectedEntity = math.mul(bFromA.rot, AxisLocal);
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
}
}
}

class PrismaticJointBaker : JointBaker<PrismaticJoint>


{
public override void Bake(PrismaticJoint authoring)
{
authoring.UpdateAuto();

var physicsJoint = PhysicsJoint.CreatePrismatic(


new BodyFrame
{
Axis = authoring.AxisLocal,
PerpendicularAxis = authoring.PerpendicularAxisLocal,
Position = authoring.PositionLocal
},
new BodyFrame
{
Axis = authoring.AxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
new FloatRange(authoring.MinDistanceOnAxis, authoring.MaxDistanceOnAxis)
);
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/RagdollJoint.cs
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using static Unity.Physics.Math;
namespace Unity.Physics.Authoring
{
public class RagdollJoint : BallAndSocketJoint
{
const int k_LatestVersion = 1;
// Editor only settings
[HideInInspector]
public bool EditAxes;
[HideInInspector]
public bool EditLimits;

[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;

public override void UpdateAuto()


{
base.UpdateAuto();
if (AutoSetConnected)
{
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
OrientationInConnectedEntity = math.mul(bFromA.rot, OrientationLocal);
}
{
OrientationLocal = math.normalize(OrientationLocal);
OrientationInConnectedEntity = math.normalize(OrientationInConnectedEntity);
}
}
}

class RigidJointBaker : JointBaker<RigidJoint>


{
public override void Bake(RigidJoint authoring)
{
authoring.UpdateAuto();

var physicsJoint = PhysicsJoint.CreateFixed(


new RigidTransform(authoring.OrientationLocal, authoring.PositionLocal),
new RigidTransform(authoring.OrientationInConnectedEntity, authoring.PositionInConnectedEntity)
);

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/Motors/AngularVelocityMotor.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

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

var joint = PhysicsJoint.CreateAngularVelocityMotor(


new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularLocal,
Position = authoring.PivotPosition
},
new BodyFrame
{
Axis = authoring.HingeAxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
math.radians(authoring.TargetSpeed),
authoring.MaxImpulseAppliedByMotor
);
joint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntity(worldIndex, constraintBodyPair, joint);
}
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Motors/LinearVelocityMotor.cs
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
public class LinearVelocityMotor : BaseJoint
{
[Tooltip("An offset from the center of the body with the motor (bodyA), 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 at this speed from the initial position of bodyA, along the Direction of Movement, in m/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 AxisInConnectedEntity;
private float3 PerpendicularAxisInConnectedEntity;
class LinearVelocityMotorBaker : JointBaker<LinearVelocityMotor>
{
public override void Bake(LinearVelocityMotor authoring)
{
float3 axisInB = math.normalize(authoring.DirectionOfMovement);

RigidTransform aFromB = math.mul(math.inverse(authoring.worldFromA), authoring.worldFromB);


float3 axisInA = math.mul(aFromB.rot, axisInB); //motor axis relative to bodyA
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.AnchorPosition); //position of motored body relative to Connected Entity in world space
authoring.AxisInConnectedEntity = axisInB; //motor axis in Connected Entity space

// Always calculate the perpendicular axes


Math.CalculatePerpendicularNormalized(axisInA, out var perpendicularAxisLocal, out _);
authoring.PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, perpendicularAxisLocal); //perp motor axis in Connected Entity space
var joint = PhysicsJoint.CreateLinearVelocityMotor(
new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularAxisLocal,
Position = authoring.AnchorPosition
},
new BodyFrame
{
Axis = authoring.AxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
authoring.TargetSpeed,
authoring.MaxImpulseAppliedByMotor
);
joint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntity(worldIndex, constraintBodyPair, joint);
}
}
}
}
Tutorial/Assets/Scripts/Custom Physics Authoring/Unity.Physics.Custom/Motors/PositionMotor.cs
using Unity.Mathematics;
using UnityEngine;
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;
class PositionMotorBaker : JointBaker<PositionMotor>
{
public override void Bake(PositionMotor authoring)
{
float3 axisInB = math.normalize(authoring.DirectionOfMovement);

RigidTransform aFromB = math.mul(math.inverse(authoring.worldFromA), authoring.worldFromB);


float3 axisInA = math.mul(aFromB.rot, axisInB); //motor axis relative to bodyA

RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);


authoring.PositionInConnectedEntity = math.transform(bFromA, authoring.AnchorPosition); //position of motored body relative to Connected Entity in world space
authoring.AxisInConnectedEntity = axisInB; //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
var joint = PhysicsJoint.CreatePositionMotor(
new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularLocal,
Position = authoring.AnchorPosition
},
new BodyFrame
{
Axis = authoring.AxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
authoring.TargetDistance,
authoring.MaxImpulseAppliedByMotor
);
joint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);

var constraintBodyPair = GetConstrainedBodyPair(authoring);


uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntity(worldIndex, constraintBodyPair, joint);
}
}
}
}
Tutorial/Assets/Scripts/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
var joint = PhysicsJoint.CreateRotationalMotor(
new BodyFrame
{
Axis = axisInA,
PerpendicularAxis = perpendicularLocal,
Position = authoring.PivotPosition
},
new BodyFrame
{
Axis = authoring.HingeAxisInConnectedEntity,
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
Position = authoring.PositionInConnectedEntity
},
math.radians(authoring.TargetAngle),
authoring.MaxImpulseAppliedByMotor
);
joint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
var constraintBodyPair = GetConstrainedBodyPair(authoring);
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
CreateJointEntity(worldIndex, constraintBodyPair, joint);
}
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/BakeGeometryJobs.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Authoring
{
static partial class PhysicsShapeExtensions
{
static void MakeZAxisPrimaryBasis(ref int3 basisPriority)
{
if (basisPriority[1] == 2)
basisPriority = basisPriority.yxz;
else if (basisPriority[2] == 2)
basisPriority = basisPriority.zxy;
}
#region Box
[BurstCompile]
internal struct BakeBoxJob : IJob
{
public NativeArray<BoxGeometry> Box;

// TODO: make members PascalCase after merging static query fixes


public float4x4 localToWorld;
public float4x4 shapeToWorld;
public EulerAngles orientation;
public static float4x4 GetBakeToShape(float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center,
ref EulerAngles orientation)
{
float4x4 bakeToShape;
float4x4 rotationMatrix = float4x4.identity;
var basisPriority = k_DefaultAxisPriority;
var sheared = localToWorld.HasShear();
if (localToWorld.HasNonUniformScale() || sheared)
{
if (sheared)
{
var transformScale = localToWorld.DecomposeScale();
var basisToWorld =
GetBasisToWorldMatrix(localToWorld, center, orientation, transformScale);
basisPriority = GetBasisAxisPriority(basisToWorld);
}
rotationMatrix = new float4x4(
new float4 { [basisPriority[2]] = 1 },
new float4 { [basisPriority[1]] = 1 },
new float4 { [basisPriority[0]] = 1 },
new float4 { [3] = 1 }
);
}
bakeToShape = GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center,
ref orientation, 1f, basisPriority);
bakeToShape = math.mul(bakeToShape, rotationMatrix);
return bakeToShape;
}
public void Execute()
{
var center = Box[0].Center;
var size = Box[0].Size;
var bevelRadius = Box[0].BevelRadius;
var bakeToShape = GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientation);
bakeToShape = math.mul(bakeToShape, float4x4.Scale(size));
var scale = bakeToShape.DecomposeScale();
size = scale;
Box[0] = new BoxGeometry
{
Center = center,
Orientation = orientation,
Size = size,
BevelRadius = math.clamp(bevelRadius, 0f, 0.5f * math.cmin(size))
};
}
}
#endregion
#region Capsule
[BurstCompile]
internal struct BakeCapsuleJob : IJob
{
public NativeArray<CapsuleGeometryAuthoring> Capsule;
// TODO: make members PascalCase after merging static query fixes
public float4x4 localToWorld;
public float4x4 shapeToWorld;
public static float4x4 GetBakeToShape(float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center,
ref EulerAngles orientation)
{
var basisPriority = k_DefaultAxisPriority;
var sheared = localToWorld.HasShear();
if (localToWorld.HasNonUniformScale() || sheared)
{
if (sheared)
{
var transformScale = localToWorld.DecomposeScale();
var basisToWorld = GetBasisToWorldMatrix(localToWorld, center, orientation, transformScale);
basisPriority = GetBasisAxisPriority(basisToWorld);
}
MakeZAxisPrimaryBasis(ref basisPriority);
}
return GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center, ref orientation, 1f,
basisPriority);
}
public void Execute()
{
var radius = Capsule[0].Radius;
var center = Capsule[0].Center;
var height = Capsule[0].Height;
var orientationEuler = Capsule[0].OrientationEuler;
var bakeToShape = GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientationEuler);
var scale = bakeToShape.DecomposeScale();
radius *= math.cmax(scale.xy);
height = math.max(0, height * scale.z);
Capsule[0] = new CapsuleGeometryAuthoring
{
OrientationEuler = orientationEuler,
Center = center,
Height = height,
Radius = radius
};
}
}
#endregion
#region Cylinder
[BurstCompile]
internal struct BakeCylinderJob : IJob
{
public NativeArray<CylinderGeometry> Cylinder;
// TODO: make members PascalCase after merging static query fixes
public float4x4 localToWorld;
public float4x4 shapeToWorld;
public EulerAngles orientation;
public static float4x4 GetBakeToShape(float4x4 localToWorld, float4x4 shapeToWorld, ref float3 center,
ref EulerAngles orientation)
{
var basisPriority = k_DefaultAxisPriority;
var sheared = localToWorld.HasShear();
if (localToWorld.HasNonUniformScale() || sheared)
{
if (sheared)
{
var transformScale = localToWorld.DecomposeScale();
var basisToWorld = GetBasisToWorldMatrix(localToWorld, center, orientation, transformScale);
basisPriority = GetBasisAxisPriority(basisToWorld);
}
MakeZAxisPrimaryBasis(ref basisPriority);
}
return GetPrimitiveBakeToShapeMatrix(localToWorld, shapeToWorld, ref center, ref orientation, 1f,
basisPriority);
}
public void Execute()
{
var center = Cylinder[0].Center;
var height = Cylinder[0].Height;
var radius = Cylinder[0].Radius;
var bevelRadius = Cylinder[0].BevelRadius;
var bakeToShape = GetBakeToShape(localToWorld, shapeToWorld, ref center, ref orientation);
var scale = bakeToShape.DecomposeScale();
height *= scale.z;
radius *= math.cmax(scale.xy);
Cylinder[0] = new CylinderGeometry
{
Center = center,
Orientation = orientation,
Height = height,
Radius = radius,
BevelRadius = math.min(bevelRadius, math.min(height * 0.5f, radius)),
SideCount = Cylinder[0].SideCount
};
}
}
internal static CylinderGeometry BakeToBodySpace(
this CylinderGeometry cylinder, float4x4 localToWorld, float4x4 shapeToWorld, EulerAngles orientation
)
{
using (var geometry = new NativeArray<CylinderGeometry>(1, Allocator.TempJob) { [0] = cylinder })
{
var job = new BakeCylinderJob
{
Cylinder = geometry,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld,
orientation = orientation
};
job.Run();
return geometry[0];
}
}
#endregion

#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;

public void Execute()


{
var v = Vertices[0];
GetPlanePoints(center, size, orientation, out v.c0, out v.c1, out v.c2, out v.c3);
var localToShape = math.mul(math.inverse(shapeToWorld), localToWorld);
v.c0 = math.mul(localToShape, new float4(v.c0, 1f)).xyz;
v.c1 = math.mul(localToShape, new float4(v.c1, 1f)).xyz;
v.c2 = math.mul(localToShape, new float4(v.c2, 1f)).xyz;
v.c3 = math.mul(localToShape, new float4(v.c3, 1f)).xyz;
Vertices[0] = v;
}
}

internal static void BakeToBodySpace(


float3 center, float2 size, EulerAngles orientation, float4x4 localToWorld, float4x4 shapeToWorld,
out float3 vertex0, out float3 vertex1, out float3 vertex2, out float3 vertex3
)
{
using (var geometry = new NativeArray<float3x4>(1, Allocator.TempJob))
{
var job = new BakePlaneJob
{
Vertices = geometry,
center = center,
size = size,
orientation = orientation,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld
};
job.Run();
vertex0 = geometry[0].c0;
vertex1 = geometry[0].c1;
vertex2 = geometry[0].c2;
vertex3 = geometry[0].c3;
}
}

internal static void GetPlanePoints(


float3 center, float2 size, EulerAngles orientation,
out float3 vertex0, out float3 vertex1, out float3 vertex2, out float3 vertex3
)
{
var sizeYUp = math.float3(size.x, 0, size.y);
vertex0 = center + math.mul(orientation, sizeYUp * math.float3(-0.5f, 0, 0.5f));
vertex1 = center + math.mul(orientation, sizeYUp * math.float3(0.5f, 0, 0.5f));
vertex2 = center + math.mul(orientation, sizeYUp * math.float3(0.5f, 0, -0.5f));
vertex3 = center + math.mul(orientation, sizeYUp * math.float3(-0.5f, 0, -0.5f));
}

#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;

public uint ForceUniqueIdentifier;


public ConvexHullGenerationParameters GenerationParameters;
public Material Material;
public CollisionFilter CollisionFilter;
public float4x4 BakeFromShape;

[ReadOnly] public NativeArray<HashableShapeInputs> Inputs;


[ReadOnly] public NativeArray<int> AllSkinIndices;
[ReadOnly] public NativeArray<float> AllBlendShapeWeights;

public void Execute()


{
Result[0] = HashableShapeInputs.GetHash128(
ForceUniqueIdentifier, GenerationParameters, Material, CollisionFilter, BakeFromShape,
Inputs, AllSkinIndices, AllBlendShapeWeights
);
}
}
#endregion

#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;

var bakeToShape = BakeCapsuleJobExtension.GetBakeToShape(shape, center, capsule.OrientationEuler);


var scale = bakeToShape.DecomposeScale();
var newRadius = radius / math.cmax(scale.xy);
if (math.abs(capsule.Radius - newRadius) > kMinimumChange)
capsule.Radius = newRadius;
height /= scale.z;
if (math.abs(math.length(capsule.Height - height)) > kMinimumChange)
capsule.Height = height;
shape.SetCapsule(capsule);
}
internal static CapsuleGeometryAuthoring BakeToBodySpace(
this CapsuleGeometryAuthoring capsule, float4x4 localToWorld, float4x4 shapeToWorld
)
{
using (var geometry = new NativeArray<CapsuleGeometryAuthoring>(1, Allocator.TempJob) { [0] = capsule })
{
var job = new BakeCapsuleJob
{
Capsule = geometry,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld
};
job.Run();
return geometry[0];
}
}
public static class BakeCylinderJobExtension
{
internal static float4x4 GetBakeToShape(PhysicsShapeAuthoring shape, float3 center, EulerAngles orientation)
{
var transform = shape.transform;
var localToWorld = (float4x4)transform.localToWorldMatrix;
var shapeToWorld = shape.GetShapeToWorldMatrix();
return BakeCylinderJob.GetBakeToShape(localToWorld, shapeToWorld, ref center,
ref orientation);
}
}
public static CylinderGeometry GetBakedCylinderProperties(this PhysicsShapeAuthoring shape)
{
var cylinder = shape.GetCylinderProperties(out var orientation);
return cylinder.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(),
orientation);
}
public static void SetBakedSphereRadius(this PhysicsShapeAuthoring shape, float radius)
{
var sphere = shape.GetSphereProperties(out EulerAngles eulerAngles);
var center = sphere.Center;
radius = math.abs(radius);
var basisToWorld = GetBasisToWorldMatrix(shape.transform.localToWorldMatrix, center, eulerAngles, 1f);
var basisPriority = basisToWorld.HasShear() ? GetBasisAxisPriority(basisToWorld) : k_DefaultAxisPriority;
var bakeToShape = GetPrimitiveBakeToShapeMatrix(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(), ref center, ref eulerAngles, 1f, basisPriority);
var scale = math.cmax(bakeToShape.DecomposeScale());
var newRadius = radius / scale;
sphere.Radius = newRadius;
shape.SetSphere(sphere);
}
public static void SetBakedPlaneSize(this PhysicsShapeAuthoring shape, float2 size)
{
shape.GetPlaneProperties(out var center, out var planeSize, out EulerAngles orientation);
var prevSize = math.abs(planeSize);
size = math.abs(size);
if (math.abs(size[0] - prevSize[0]) < kMinimumChange) size[0] = prevSize[0];
if (math.abs(size[1] - prevSize[1]) < kMinimumChange) size[1] = prevSize[1];
planeSize = size;
shape.SetPlane(center, planeSize, orientation);
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/CapsuleGeometryAuthoring.cs
using System;
using Unity.Mathematics;
using UnityEngine;

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;

/// <summary> The radius of the capsule. </summary>


///
/// <value> The radius. </value>
public float Radius { get => m_Radius; set => m_Radius = value; }
[SerializeField]
float m_Radius;
public bool Equals(CapsuleGeometryAuthoring other)
{
return m_Height.Equals(other.m_Height)
&& m_Center.Equals(other.m_Center)
&& m_Radius.Equals(other.m_Radius)
&& m_OrientationEuler.Equals(other.m_OrientationEuler);
}
public override int GetHashCode()
{
return unchecked((int)math.hash(
new float3x3(
Center,
m_OrientationEuler.Value,
new float3((float)m_OrientationEuler.RotationOrder, m_Height, m_Radius)
)
));
}
}
public static class CapsuleGeometryAuthoringExtensions
{
/// <summary>
/// Construct a CapsuleGeometryAuthoring instance from a run-time CapsuleGeometry instance.
/// </summary>
public static CapsuleGeometryAuthoring ToAuthoring(this CapsuleGeometry input)
{
var orientationEuler = EulerAngles.Default;
orientationEuler.SetValue(quaternion.LookRotationSafe(input.Vertex1 - input.Vertex0, math.up()));
return new CapsuleGeometryAuthoring
{
Height = input.GetHeight(),
OrientationEuler = orientationEuler,
Center = input.GetCenter(),
Radius = input.Radius
};
}
/// <summary>
/// Construct a run-time CapsuleGeometry instance from a CapsuleGeometryAuthoring instance.
/// </summary>
public static CapsuleGeometry ToRuntime(this CapsuleGeometryAuthoring input)
{
var halfHeight = 0.5f * input.Height;
var halfDistance = halfHeight - input.Radius;
var axis = math.normalize(math.mul(input.Orientation, new float3 { z = 1f }));
var halfAxis = axis * halfDistance;
var vertex0 = input.Center + halfAxis;
var vertex1 = input.Center - halfAxis;
return new CapsuleGeometry
{
Vertex0 = vertex0,
Vertex1 = vertex1,
Radius = input.Radius
};
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/ConvexHullGenerationParametersExtensions.cs
using System;
using Unity.Collections;
using Unity.Mathematics;

namespace Unity.Physics.Authoring
{
public static class ConvexHullGenerationParametersExtensions
{
// recommended simplification tolerance is at least 1 centimeter
internal const float k_MinRecommendedSimplificationTolerance = 0.01f;

internal static void InitializeToRecommendedAuthoringValues(


ref this ConvexHullGenerationParameters generationParameters, NativeArray<float3> points
)
{
generationParameters = ConvexHullGenerationParameters.Default.ToAuthoring();

if (points.Length <= 1)
return;

var bounds = new Aabb { Min = points[0], Max = points[0] };


for (var i = 1; i < points.Length; ++i)
bounds.Include(points[i]);
generationParameters.SimplificationTolerance = math.max(
k_MinRecommendedSimplificationTolerance,
ConvexHullGenerationParameters.Default.SimplificationTolerance * math.cmax(bounds.Extents)
);
// TODO: initialize other properties based on input points?
}

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);
}

public static ConvexHullGenerationParameters ToAuthoring(this ConvexHullGenerationParameters generationParameters)


{
generationParameters.MinimumAngle = math.degrees(generationParameters.MinimumAngle);
return generationParameters;
}

public static ConvexHullGenerationParameters ToRunTime(this ConvexHullGenerationParameters generationParameters)


{
generationParameters.MinimumAngle = math.radians(generationParameters.MinimumAngle);
return generationParameters;
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/EulerAngles.cs
using System;
using Unity.Mathematics;
using UnityEngine;

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));

public static implicit operator quaternion(EulerAngles euler) =>


math.normalize(quaternion.Euler(math.radians(euler.Value), euler.RotationOrder));
public bool Equals(EulerAngles other) => Value.Equals(other.Value) && RotationOrder == other.RotationOrder;
public override bool Equals(object obj) => obj is EulerAngles other && Equals(other);
public override int GetHashCode() => unchecked((int)math.hash(new float4(Value, (float)RotationOrder)));
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/GetActiveChildrenScope.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityComponent = UnityEngine.Component;
namespace Unity.Physics.Authoring
{
public struct GetActiveChildrenScope<T> : IDisposable where T : UnityComponent
{
static readonly List<PhysicsShapeAuthoring> s_PhysicsShapes = new List<PhysicsShapeAuthoring>(8);

static bool s_BufferUsed;


static List<T> s_Buffer = new List<T>(8);

public List<T> Buffer => m_Disposed ? null : s_Buffer;


bool m_Disposed;
PhysicsShapeAuthoring m_Shape;
Transform m_Root;
GameObject m_PrimaryBody;
bool m_CheckIfComponentBelongsToShape;
public GetActiveChildrenScope(PhysicsShapeAuthoring shape, Transform root)
{
m_Disposed = false;
m_Shape = shape;
m_Root = root;
m_PrimaryBody = PhysicsShapeExtensions.GetPrimaryBody(root.gameObject);
m_CheckIfComponentBelongsToShape = root.transform.IsChildOf(shape.transform);
if (s_BufferUsed)
throw new InvalidOperationException($"Cannot nest two {GetType()}");
s_BufferUsed = true;
root.GetComponentsInChildren(true, s_Buffer);
}
public void Dispose()
{
if (m_Disposed)
return;
m_Disposed = true;
s_BufferUsed = false;
s_Buffer.Clear();
}

public bool IsChildActiveAndBelongsToShape(T child, bool filterOutInvalid = true)


{
var meshFilter = (UnityComponent)child as MeshFilter;
if (meshFilter != null)
{
if (meshFilter.sharedMesh == null)
return false;

var renderer = meshFilter.GetComponent<MeshRenderer>();


if (renderer == null || !renderer.enabled)
return false;
if (filterOutInvalid && !meshFilter.sharedMesh.IsValidForConversion(m_Shape.gameObject))
return false;
}

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);

// avoids drift in axes we're not actually changing


public const float kMinimumChange = HashableShapeInputs.k_DefaultLinearPrecision;

static readonly int[] k_NextAxis = { 1, 2, 0 };


static readonly int[] k_PrevAxis = { 2, 0, 1 };

// 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;

var imax = max == basisAxisLengths.x ? 0 : max == basisAxisLengths.y ? 1 : 2;


basisToWorld[k_NextAxis[imax]] = DeskewSecondaryAxis(basisToWorld[imax], basisToWorld[k_NextAxis[imax]]);
basisToWorld[k_PrevAxis[imax]] = DeskewSecondaryAxis(basisToWorld[imax], basisToWorld[k_PrevAxis[imax]]);

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];

return new int3(imax, imid, imin);


}

[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]]);

// recompute third axes from first two


var n2 = math.normalizesafe(
new float4(math.cross(localToBake[basisPriority[0]].xyz, localToBake[basisPriority[1]].xyz), 0f)
);
localToBake[basisPriority[2]] = n2 * math.dot(localToBake[basisPriority[2]], n2);
}
var bakeToShape = math.mul(math.inverse(shapeToWorld), localToBake);
// transform baked center/orientation (i.e. primitive basis) into shape space
orientation.SetValue(
quaternion.LookRotationSafe(bakeToShape[basisPriority[0]].xyz, bakeToShape[basisPriority[1]].xyz)
);
center = bakeToShape.c3.xyz;

return bakeToShape;
}

internal static CollisionFilter GetFilter(this PhysicsShapeAuthoring shape)


{
// TODO: determine optimal workflow for specifying group index
return new CollisionFilter
{
BelongsTo = shape.BelongsTo.Value,
CollidesWith = shape.CollidesWith.Value
};
}
internal static Material GetMaterial(this PhysicsShapeAuthoring shape)
{
// TODO: TBD how we will author editor content for other shape flags
return new Material
{
Friction = shape.Friction.Value,
FrictionCombinePolicy = shape.Friction.CombineMode,
Restitution = shape.Restitution.Value,
RestitutionCombinePolicy = shape.Restitution.CombineMode,
CollisionResponse = shape.CollisionResponse,
CustomTags = shape.CustomTags.Value
};
}
public static GameObject FindTopmostEnabledAncestor<T>(GameObject shape, List<T> buffer) where T : Component
{
// include inactive in case the supplied shape GameObject is a prefab that has not been instantiated
shape.GetComponentsInParent(true, buffer);
GameObject result = null;
for (var i = buffer.Count - 1; i >= 0; --i)
{
if (
(buffer[i] as UnityEngine.Collider)?.enabled ??
(buffer[i] as MonoBehaviour)?.enabled ?? true
)
{
result = buffer[i].gameObject;
break;
}
}
buffer.Clear();
return result;
}
public static GameObject GetPrimaryBody(this PhysicsShapeAuthoring shape) => GetPrimaryBody(shape.gameObject);
public static GameObject GetPrimaryBody(GameObject shape)
{
var pb = ColliderExtensions.FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
var rb = ColliderExtensions.FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
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
ColliderExtensions.FindTopmostStaticEnabledAncestor(shape, out GameObject 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;
}

public static BoxGeometry GetBakedBoxProperties(this PhysicsShapeAuthoring shape)


{
var box = shape.GetBoxProperties(out var orientation);
return box.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(), orientation);
}
internal static BoxGeometry BakeToBodySpace(
this BoxGeometry box, float4x4 localToWorld, float4x4 shapeToWorld, EulerAngles orientation
)
{
using (var geometry = new NativeArray<BoxGeometry>(1, Allocator.TempJob) { [0] = box })
{
var job = new BakeBoxJob
{
Box = geometry,
localToWorld = localToWorld,
shapeToWorld = shapeToWorld,
orientation = orientation
};
job.Run();
return geometry[0];
}
}
public static void SetBakedBoxSize(this PhysicsShapeAuthoring shape, float3 size, float bevelRadius)
{
var box = shape.GetBoxProperties(out var orientation);
var center = box.Center;
var prevSize = math.abs(box.Size);
size = math.abs(size);

var bakeToShape = BakeBoxJobExtension.GetBakeToShape(shape, center, orientation);


var scale = bakeToShape.DecomposeScale();
size /= scale;

if (math.abs(size[0] - prevSize[0]) < kMinimumChange) size[0] = prevSize[0];


if (math.abs(size[1] - prevSize[1]) < kMinimumChange) size[1] = prevSize[1];
if (math.abs(size[2] - prevSize[2]) < kMinimumChange) size[2] = prevSize[2];
box.BevelRadius = bevelRadius;
box.Size = size;
shape.SetBox(box, orientation);
}

internal static CapsuleGeometryAuthoring GetBakedCapsuleProperties(this PhysicsShapeAuthoring shape)


{
var capsule = shape.GetCapsuleProperties();
return capsule.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix());
}

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();

var newRadius = radius / math.cmax(scale.xy);


if (math.abs(cylinder.Radius - newRadius) > kMinimumChange) cylinder.Radius = newRadius;
if (math.abs(cylinder.BevelRadius - bevelRadius) > kMinimumChange) cylinder.BevelRadius = bevelRadius;

var newHeight = math.max(0, height / scale.z);


if (math.abs(cylinder.Height - newHeight) > kMinimumChange) cylinder.Height = newHeight;
shape.SetCylinder(cylinder, orientation);
}

internal static SphereGeometry GetBakedSphereProperties(this PhysicsShapeAuthoring shape, out EulerAngles orientation)


{
var sphere = shape.GetSphereProperties(out orientation);
return sphere.BakeToBodySpace(shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(), ref orientation);
}
internal static void GetBakedPlaneProperties(
this PhysicsShapeAuthoring shape, out float3 vertex0, out float3 vertex1, out float3 vertex2, out float3 vertex3
)
{
shape.GetPlaneProperties(out var center, out var size, out EulerAngles orientation);
BakeToBodySpace(
center, size, orientation, shape.transform.localToWorldMatrix, shape.GetShapeToWorldMatrix(),
out vertex0, out vertex1, out vertex2, out vertex3
);
}
public static void GetBakedConvexProperties(this PhysicsShapeAuthoring shape, NativeList<float3> pointCloud)
{
shape.GetConvexHullProperties(pointCloud, true, default, default, default, default);
shape.BakePoints(pointCloud.AsArray());
}
internal static Hash128 GetBakedMeshInputs(this PhysicsShapeAuthoring shape, HashSet<UnityEngine.Mesh> meshAssets = null)
{
using (var inputs = new NativeList<HashableShapeInputs>(8, Allocator.TempJob))
{
shape.GetMeshProperties(default, default, true, inputs, meshAssets);
using (var hash = new NativeArray<Hash128>(1, Allocator.TempJob))
using (var allSkinIndices = new NativeArray<int>(0, Allocator.TempJob))
using (var allBlendShapeWeights = new NativeArray<float>(0, Allocator.TempJob))
{
var job = new GetShapeInputsHashJob
{
Result = hash,
ForceUniqueIdentifier = (uint)(shape.ForceUnique ? shape.GetInstanceID() : 0),
Material = shape.GetMaterial(),
CollisionFilter = shape.GetFilter(),
BakeFromShape = shape.GetLocalToShapeMatrix(),
Inputs = inputs.AsArray(),
AllSkinIndices = allSkinIndices,
AllBlendShapeWeights = allBlendShapeWeights
};
job.Run();
return hash[0];
}
}
}
internal static Hash128 GetBakedConvexInputs(this PhysicsShapeAuthoring shape, HashSet<UnityEngine.Mesh> meshAssets)
{
using (var inputs = new NativeList<HashableShapeInputs>(8, Allocator.TempJob))
using (var allSkinIndices = new NativeList<int>(4096, Allocator.TempJob))
using (var allBlendShapeWeights = new NativeList<float>(64, Allocator.TempJob))
{
shape.GetConvexHullProperties(default, true, inputs, allSkinIndices, allBlendShapeWeights, meshAssets);
using (var hash = new NativeArray<Hash128>(1, Allocator.TempJob))
{
var job = new GetShapeInputsHashJob
{
Result = hash,
ForceUniqueIdentifier = (uint)(shape.ForceUnique ? shape.GetInstanceID() : 0),
GenerationParameters = shape.ConvexHullGenerationParameters,
Material = shape.GetMaterial(),
CollisionFilter = shape.GetFilter(),
BakeFromShape = shape.GetLocalToShapeMatrix(),
Inputs = inputs.AsArray(),
AllSkinIndices = allSkinIndices.AsArray(),
AllBlendShapeWeights = allBlendShapeWeights.AsArray()
};
job.Run();
return hash[0];
}
}
}
public static void GetBakedMeshProperties(
this PhysicsShapeAuthoring shape, NativeList<float3> vertices, NativeList<int3> triangles,
HashSet<UnityEngine.Mesh> meshAssets = null
)
{
shape.GetMeshProperties(vertices, triangles, true, default, meshAssets);
shape.BakePoints(vertices.AsArray());
}
const float k_HashFloatTolerance = 0.01f;
// used to hash convex hull generation properties in a way that is robust to imprecision
public static uint GetStableHash(
this ConvexHullGenerationParameters generationParameters,
ConvexHullGenerationParameters hashedParameters,
float tolerance = k_HashFloatTolerance
)
{
var differences = new float3(
generationParameters.BevelRadius - hashedParameters.BevelRadius,
generationParameters.MinimumAngle - hashedParameters.MinimumAngle,
generationParameters.SimplificationTolerance - hashedParameters.SimplificationTolerance
);
return math.cmax(math.abs(differences)) < tolerance
? unchecked((uint)hashedParameters.GetHashCode())
: unchecked((uint)generationParameters.GetHashCode());
}
// used to hash an array of points in a way that is robust to imprecision
public static unsafe uint GetStableHash(
this NativeList<float3> points, NativeArray<float3> hashedPoints, float tolerance = k_HashFloatTolerance
)
{
if (points.Length != hashedPoints.Length)
return math.hash(points.GetUnsafePtr(), UnsafeUtility.SizeOf<float3>() * points.Length);

for (int i = 0, count = points.Length; i < count; ++i)


{
if (math.cmax(math.abs(points[i] - hashedPoints[i])) > tolerance)
return math.hash(points.GetUnsafePtr(), UnsafeUtility.SizeOf<float3>() * points.Length);
}
return math.hash(hashedPoints.GetUnsafePtr(), UnsafeUtility.SizeOf<float3>() * hashedPoints.Length);
}
public static int GetMaxAxis(this float3 v)
{
var cmax = math.cmax(v);
return cmax == v.z ? 2 : cmax == v.y ? 1 : 0;
}
public static int GetDeviantAxis(this float3 v)
{
var deviation = math.abs(v - math.csum(v) / 3f);
return math.cmax(deviation) == deviation.z ? 2 : math.cmax(deviation) == deviation.y ? 1 : 0;
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom/Utilities/PropertyAttributes.cs
using UnityEngine;

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; }

public SoftRangeAttribute(float min, float max)


{
SliderMin = TextFieldMin = min;
SliderMax = TextFieldMax = max;
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/AssemblyInfo.cs
using System.Runtime.CompilerServices;

[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();

EditorUtilities.EditPivot(ballAndSocket.worldFromA, ballAndSocket.worldFromB, ballAndSocket.AutoSetConnected,


ref ballAndSocket.PositionLocal, ref ballAndSocket.PositionInConnectedEntity, ballAndSocket);
}
}
}
#endif
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Editors/CustomPhysicsMaterialTagNamesEditor.cs
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;

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();

public override void OnInspectorGUI()


{
LimitedHingeJoint limitedHinge = (LimitedHingeJoint)target;

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();
}
}

protected virtual void OnSceneGUI()


{
LimitedHingeJoint limitedHinge = (LimitedHingeJoint)target;

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;

public override void OnInspectorGUI()


{
serializedObject.Update();

EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_MotionType);
if (m_MotionType.intValue != (int)BodyMotionType.Static)
EditorGUILayout.PropertyField(m_Smoothing);

var dynamic = m_MotionType.intValue == (int)BodyMotionType.Dynamic;


if (dynamic)
EditorGUILayout.PropertyField(m_Mass, Content.MassLabel);
else
{
EditorGUI.BeginDisabledGroup(true);
var position = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
EditorGUI.BeginProperty(position, Content.MassLabel, m_Mass);
EditorGUI.FloatField(position, Content.MassLabel, float.PositiveInfinity);
EditorGUI.EndProperty();
EditorGUI.EndDisabledGroup();
}

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);
}

showAdvanced = EditorGUILayout.Foldout(showAdvanced, Content.AdvancedLabel);


if (showAdvanced)
{
++EditorGUI.indentLevel;
EditorGUILayout.PropertyField(m_WorldIndex);
if (m_MotionType.intValue != (int)BodyMotionType.Static)
{
EditorGUILayout.PropertyField(m_OverrideDefaultMassDistribution);
if (m_OverrideDefaultMassDistribution.boolValue)
{
++EditorGUI.indentLevel;
EditorGUILayout.PropertyField(m_CenterOfMass, Content.CenterOfMassLabel);

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];

static readonly string[] k_NonReadableGeometryWarning =


{
L10n.Tr($"{k_Singular} has a non-readable mesh in its hierarchy. Move it into a sub-scene or assign a custom mesh with Read/Write enabled in its import settings if it needs
L10n.Tr($"{k_Plural} has a non-readable mesh in its hierarchy. Move it into a sub-scene or assign a custom mesh with Read/Write enabled in its import settings if it needs t
};
public static string GetNonReadableGeometryWarning(int numTargets) =>
numTargets == 1 ? k_NonReadableGeometryWarning[0] : k_NonReadableGeometryWarning[1];
static readonly string[] k_MeshWithSkinnedPointsWarning =
{
L10n.Tr($"{k_Singular} is a mesh based on its render geometry, but its render geometry includes skinned points. These points will be excluded from the automatically generat
L10n.Tr($"{k_Plural} is a mesh based on its render geometry, but its render geometry includes skinned points. These points will be excluded from the automatically generated
};
public static string GetMeshWithSkinnedPointsWarning(int numTargets) =>
numTargets == 1 ? k_MeshWithSkinnedPointsWarning[0] : k_MeshWithSkinnedPointsWarning[1];
static readonly string[] k_StaticColliderStatusMessage =
{
L10n.Tr($"{k_Singular} will be considered static. Add a {ObjectNames.NicifyVariableName(typeof(PhysicsBodyAuthoring).Name)} component if you will move it at run-time."
L10n.Tr($"{k_Plural} will be considered static. Add a {ObjectNames.NicifyVariableName(typeof(PhysicsBodyAuthoring).Name)} component if you will move them at run-time."
};
public static string GetStaticColliderStatusMessage(int numTargets) =>
numTargets == 1 ? k_StaticColliderStatusMessage[0] : k_StaticColliderStatusMessage[1];
public static readonly string BoxCapsuleSuggestion =
L10n.Tr($"Target {ShapeType.Box} has uniform size on its two short axes and a large convex radius. Consider using a {ShapeType.Capsule} instead.");
public static readonly string BoxPlaneSuggestion =
L10n.Tr($"Target {ShapeType.Box} is flat. Consider using a {ShapeType.Plane} instead.");
public static readonly string BoxSphereSuggestion =
L10n.Tr($"Target {ShapeType.Box} has uniform size and large convex radius. Consider using a {ShapeType.Sphere} instead.");
public static readonly string CapsuleSphereSuggestion =
L10n.Tr($"Target {ShapeType.Capsule}'s diameter is equal to its height. Consider using a {ShapeType.Sphere} instead.");
public static readonly string CylinderCapsuleSuggestion =
L10n.Tr($"Target {ShapeType.Cylinder} has a large convex radius. Consider using a {ShapeType.Capsule} instead.");
public static readonly string CylinderSphereSuggestion =
L10n.Tr($"Target {ShapeType.Cylinder} has a large convex radius and its diameter is equal to its height. Consider using a {ShapeType.Sphere} instead.");
public static readonly GUIStyle Button =
new GUIStyle(EditorStyles.miniButton) { padding = new RectOffset() };
public static readonly GUIStyle ButtonDropDown =
new GUIStyle(EditorStyles.popup) { alignment = TextAnchor.MiddleCenter };
}
#pragma warning disable 649
[AutoPopulate] SerializedProperty m_ShapeType;
[AutoPopulate] SerializedProperty m_PrimitiveCenter;
[AutoPopulate] SerializedProperty m_PrimitiveSize;
[AutoPopulate] SerializedProperty m_PrimitiveOrientation;
[AutoPopulate] SerializedProperty m_Capsule;
[AutoPopulate] SerializedProperty m_Cylinder;
[AutoPopulate] SerializedProperty m_CylinderSideCount;
[AutoPopulate] SerializedProperty m_SphereRadius;
[AutoPopulate] SerializedProperty m_ConvexHullGenerationParameters;
[AutoPopulate(PropertyPath = "m_ConvexHullGenerationParameters.m_BevelRadius")] SerializedProperty m_BevelRadius;
[AutoPopulate] SerializedProperty m_MinimumSkinnedVertexWeight;
[AutoPopulate] SerializedProperty m_CustomMesh;
[AutoPopulate] SerializedProperty m_Material;
[AutoPopulate] SerializedProperty m_ForceUnique;
#pragma warning restore 649
[Flags]
enum GeometryState
{
Okay = 0,
NoGeometry = 1 << 0,
NonReadableGeometry = 1 << 1,
MeshWithSkinnedPoints = 1 << 2
}
GeometryState m_GeometryState;
int m_NumImplicitStatic;
// keep track of when the user is dragging some control to prevent continually rebuilding preview geometry
[NonSerialized]
int m_DraggingControlID;
[NonSerialized]
FitToRenderMeshesDropDown m_DropDown;
protected override void OnEnable()
{
base.OnEnable();
HashUtility.Initialize();

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);
}
}

float m_BevelRadius = ConvexHullGenerationParameters.Default.BevelRadius;


bool m_IsDragging = false;

public float height


{
get => GetSize().z;
set
{
var size = GetSize();
size.z = math.max(0f, value);
SetSize(size);
}
}

public float radius


{
get
{
var size = (float3)GetSize();
var diameter = 0f;
// only consider size values on enabled axes
if (IsAxisEnabled(0)) diameter = math.max(diameter, math.abs(size.x));
else if (IsAxisEnabled(1)) diameter = math.max(diameter, math.abs(size.y));
return diameter * 0.5f;
}
set
{
var size = (float3)GetSize();
size.x = size.y = math.max(0f, value * 2.0f);
SetSize(size);
}
}

public int sideCount


{
get => m_SideCount;
set
{
if (value == m_SideCount)
return;

m_SideCount = value;

Array.Resize(ref m_TopPoints, m_SideCount * 2);


Array.Resize(ref m_BottomPoints, m_SideCount * 2);
Array.Resize(ref m_Corners, m_SideCount * 2);
}
}
int m_SideCount;

PhysicsBoundsHandleUtility.Corner[] m_Corners = Array.Empty<PhysicsBoundsHandleUtility.Corner>();


Vector3[] m_TopPoints = Array.Empty<Vector3>();
Vector3[] m_BottomPoints = Array.Empty<Vector3>();

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()


{
using (new Handles.DrawingScope(Handles.matrix))
{
var backfacedColor = PhysicsBoundsHandleUtility.GetStateColor(true);
var frontfacedColor = Handles.color;
bool isCameraInsideBox = false;

var radius = this.radius;


var bevelRadius = this.bevelRadius;

var halfHeight = new float3(0f, 0f, height * 0.5f);


var ctr = (float3)center;
var halfAngleStep = math.PI / m_SideCount;
var angleStep = 2f * halfAngleStep;
const float kHalfPI = (math.PI * 0.5f);
var bottom = ctr + halfHeight + new float3 { z = bevelRadius };
var top = ctr - halfHeight - new float3 { z = bevelRadius };
var tangent = new float3(1, 0, 0);
var binormal = new float3(0, 1, 0);
var topBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(top, -tangent, binormal, axes, isCameraInsideBox);
var bottomBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(bottom, tangent, binormal, axes, isCameraInsideBox);

var cameraCenter = float3.zero;


var cameraForward = new float3 { z = 1f };
if (Camera.current != null)
{
cameraCenter = Camera.current.transform.position;
cameraForward = Camera.current.transform.forward;
}

// 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;

var noSides = (radius - bevelRadius) < PhysicsBoundsHandleUtility.kDistanceEpsilon;


var up = new float3(0, 0, -1f);

var t = ((m_SideCount - 2) * angleStep);


var xyAngle0 = new float3(math.cos(t), math.sin(t), 0f);

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 offset0 = xyAngle0 * (radius - bevelRadius);


var offset1 = xyAngle1 * (radius - bevelRadius);
var offset2 = xyAngle2 * (radius - bevelRadius);
var top1 = ctr + offset1 - (halfHeight - new float3 { z = bevelRadius });
var bottom1 = ctr + offset1 + (halfHeight - new float3 { z = bevelRadius });
var top2 = ctr + offset2 - (halfHeight - new float3 { z = bevelRadius });
var bottom2 = ctr + offset2 + (halfHeight - new float3 { z = bevelRadius });

var startOffset = direction1 * bevelRadius;


if (bevelGreaterThanZero)
{
var upOffset = up * bevelRadius;
// top/bottom caps
if (bevelLessThanCylinderRadius)
{
Handles.color = topBackFaced ? backfacedColor : frontfacedColor;
Handles.DrawLine(top1 + upOffset, top2 + upOffset);

Handles.color = bottomBackFaced ? backfacedColor : frontfacedColor;


Handles.DrawLine(bottom1 - upOffset, bottom2 - upOffset);
}

var currSideMidPoint = ctr + ((top1 + bottom1 + top2 + bottom2) * 0.25f) + startOffset;


var currSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(currSideMidPoint, up, sideways2, axes, isCameraInsideBox);
Handles.color = currSideBackFaced ? backfacedColor : frontfacedColor;
if (!noSides)
{
// Square side of bevelled cylinder
Handles.DrawLine(top2 + startOffset, bottom2 + startOffset);
Handles.DrawLine(bottom2 + startOffset, bottom1 + startOffset);
Handles.DrawLine(bottom1 + startOffset, top1 + startOffset);
Handles.DrawLine(top1 + startOffset, top2 + startOffset);
}
else
{
// Square side of bevelled cylinder, when squashed to a single line
Handles.DrawLine(top2 + startOffset, bottom2 + startOffset);
}
}
else
{
var top0 = ctr + offset0 - (halfHeight - new float3 { z = bevelRadius });
var bottom0 = ctr + offset0 + (halfHeight - new float3 { z = bevelRadius });
var prevMidPoint = ctr + ((top0 + top1 + bottom0 + bottom1) * 0.25f) + startOffset;
var prevSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(prevMidPoint, up, sideways1, axes, isCameraInsideBox);

var currMidPoint = ctr + ((top1 + top2 + bottom1 + bottom2) * 0.25f) + startOffset;


var currSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(currMidPoint, up, sideways2, axes, isCameraInsideBox);
// Square side of bevelled cylinder
Handles.color = (currSideBackFaced && prevSideBackFaced) ? backfacedColor : frontfacedColor;
Handles.DrawLine(bottom1 + startOffset, top1 + startOffset);
Handles.color = (currSideBackFaced && topBackFaced) ? backfacedColor : frontfacedColor;
Handles.DrawLine(top1 + startOffset, top2 + startOffset);
Handles.color = (currSideBackFaced && bottomBackFaced) ? backfacedColor : frontfacedColor;
Handles.DrawLine(bottom2 + startOffset, bottom1 + startOffset);
}
if (bevelGreaterThanZero)
{
Handles.color = frontfacedColor;

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;

// Side horizon on vertical curved edge


if (m_Corners[up1].splitCount > 1 &&
m_Corners[dn1].splitCount > 1)
{
if ((m_Corners[up1].splitAxis[0].y || m_Corners[up1].splitAxis[1].y) &&
(m_Corners[dn1].splitAxis[0].y || m_Corners[dn1].splitAxis[1].y))
{
var point0 = m_Corners[up1].splitAxis[0].y ? m_Corners[up1].points[0] : m_Corners[up1].points[1];
var point1 = m_Corners[dn1].splitAxis[0].y ? m_Corners[dn1].points[0] : m_Corners[dn1].points[1];
Handles.DrawLine(point0, point1);
}
}
// Top horizon on horizontal curved edge
if (m_Corners[up0].splitCount > 1 &&
m_Corners[up1].splitCount > 1)
{
if ((m_Corners[up0].splitAxis[0].x || m_Corners[up0].splitAxis[1].x) &&
(m_Corners[up1].splitAxis[0].z || m_Corners[up1].splitAxis[1].z))
{
var point0 = m_Corners[up0].splitAxis[0].x ? m_Corners[up0].points[0] : m_Corners[up0].points[1];
var point1 = m_Corners[up1].splitAxis[0].z ? m_Corners[up1].points[0] : m_Corners[up1].points[1];
Handles.DrawLine(point0, point1);
}
}
// Bottom horizon on horizontal curved edge
if (m_Corners[dn0].splitCount > 1 &&
m_Corners[dn1].splitCount > 1)
{
if ((m_Corners[dn0].splitAxis[0].z || m_Corners[dn0].splitAxis[1].z) &&
(m_Corners[dn1].splitAxis[0].x || m_Corners[dn1].splitAxis[1].x))
{
var point0 = m_Corners[dn0].splitAxis[0].z ? m_Corners[dn0].points[0] : m_Corners[dn0].points[1];
var point1 = m_Corners[dn1].splitAxis[0].x ? m_Corners[dn1].points[0] : m_Corners[dn1].points[1];
Handles.DrawLine(point0, point1);
}
}
}
for (var i = 0; i < m_Corners.Length; ++i)
PhysicsBoundsHandleUtility.DrawCorner(m_Corners[i], new bool3(true, true, !noSides));
}
}
}
protected override Bounds OnHandleChanged(HandleDirection handle, Bounds boundsOnClick, Bounds newBounds)
{
const int k_DirectionX = 0;
const int k_DirectionY = 1;
const int k_DirectionZ = 2;
var changedAxis = k_DirectionX;
var otherRadiusAxis = k_DirectionY;
switch (handle)
{
case HandleDirection.NegativeY:
case HandleDirection.PositiveY:
changedAxis = k_DirectionY;
otherRadiusAxis = k_DirectionX;
break;
case HandleDirection.NegativeZ:
case HandleDirection.PositiveZ:
changedAxis = k_DirectionZ;
break;
}
var upperBound = newBounds.max;
var lowerBound = newBounds.min;
var convexDiameter = 2f * bevelRadius;
// ensure changed dimension cannot be made less than convex diameter
if (upperBound[changedAxis] - lowerBound[changedAxis] < convexDiameter)
{
switch (handle)
{
case HandleDirection.PositiveX:
case HandleDirection.PositiveY:
case HandleDirection.PositiveZ:
upperBound[changedAxis] = lowerBound[changedAxis] + convexDiameter;
break;
default:
lowerBound[changedAxis] = upperBound[changedAxis] - convexDiameter;
break;
}
}
// ensure radius changes uniformly
if (changedAxis != k_DirectionZ)
{
var rad = 0.5f * (upperBound[changedAxis] - lowerBound[changedAxis]);
lowerBound[otherRadiusAxis] = center[otherRadiusAxis] - rad;
upperBound[otherRadiusAxis] = center[otherRadiusAxis] + rad;
}
return new Bounds((upperBound + lowerBound) * 0.5f, upperBound - lowerBound);
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/PhysicsBoundsHandleUtility.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEngine;
using static UnityEditor.IMGUI.Controls.PrimitiveBoundsHandle;
namespace Unity.Physics.Editor
{
static class PhysicsBoundsHandleUtility
{
internal const float kBackfaceAlphaMultiplier = 0.2f;
const float kDegreeEpsilon = 0.001f;
public const float kDistanceEpsilon = 0.0001f;
public static bool IsBackfaced(float3 localPos, float3 localTangent, float3 localBinormal, Axes axes, bool isCameraInsideBox)
{
// if inside the box then ignore back facing alpha multiplier (otherwise all handles will look disabled)
if (isCameraInsideBox || axes != Axes.All)
return false;
// use tangent and binormal to calculate normal in case handle matrix is skewed
float3 worldTangent = math.normalize(Handles.matrix.MultiplyVector(localTangent));
float3 worldBinormal = math.normalize(Handles.matrix.MultiplyVector(localBinormal));
float3 worldDir = math.normalize(math.cross(worldTangent, worldBinormal));
// adjust color if handle is back facing
float cosV;
var currentCamera = Camera.current;
if (currentCamera != null && !currentCamera.orthographic)
{
float3 cameraPos = currentCamera.transform.position;
float3 worldPos = Handles.matrix.MultiplyPoint(localPos);
cosV = math.dot(math.normalize(cameraPos - worldPos), worldDir);
}
else
{
float3 cameraForward = currentCamera == null ? Vector3.forward : currentCamera.transform.forward;
cosV = math.dot(-cameraForward, worldDir);
}
return cosV < -0.0001f;
}
public static Color GetStateColor(bool isBackfaced)
{
float alphaMultiplier = isBackfaced ? kBackfaceAlphaMultiplier : 1f;
return Handles.color * new Color(1f, 1f, 1f, alphaMultiplier);
}
static void AdjustMidpointHandleColor(bool isBackfaced)
{
Handles.color = GetStateColor(isBackfaced);
}
static readonly Vector3[] s_FacePoints = new Vector3[8];
static readonly Vector3[] s_LinePoints = new Vector3[2];
static readonly int[] k_NextAxis = { 1, 2, 0 };
public static void DrawFace(float3 center, float3 size, float cornerRadius, int normalAxis, Axes axes, bool isCameraInsideBox)
{
// 0 = 0 1 2
// 1 = 1 2 0
// 2 = 2 0 1
int a = normalAxis;
int b = k_NextAxis[a];
int c = k_NextAxis[b];
cornerRadius = math.abs(cornerRadius);
size *= 0.5f;
var normal = new float3 { [a] = size[a] };
var ctr = center + normal;
size -= new float3(cornerRadius);
// check if our face is a point
if (math.abs(size[c]) < kDistanceEpsilon &&
math.abs(size[b]) < kDistanceEpsilon)
return;
Vector3[] points;
// check if our face is a line or not
if (math.abs(size[c]) >= kDistanceEpsilon &&
math.abs(size[b]) >= kDistanceEpsilon)
{
var i = 0;
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
points = s_FacePoints;
}
else if (math.abs(size[c]) >= kDistanceEpsilon)
{
var i = 0;
s_LinePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
s_LinePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
points = s_LinePoints;
}
else
{
var i = 0;
s_LinePoints[i++] = ctr + new float3 { [c] = size[c], [b] = size[b] };
s_LinePoints[i++] = ctr + new float3 { [c] = size[c], [b] = -size[b] };
points = s_LinePoints;
}
float3 tangent, biNormal;
if (size[c] > 0)
{
tangent = math.cross(normal, math.normalizesafe(new float3 { [c] = size[c] }));
biNormal = math.cross(normal, tangent);
}
else
{
tangent = math.cross(normal, math.normalizesafe(new float3 { [b] = size[b] }));
biNormal = math.cross(normal, tangent);
}
using (new Handles.DrawingScope(Handles.color))
{
AdjustMidpointHandleColor(IsBackfaced(ctr, tangent, biNormal, axes, isCameraInsideBox));
Handles.DrawLines(points);
}
}
public struct Corner
{
public float3 angle;
public float3x2 intersections;
public float3x2 points;
public float3x3 axes;
public float3x3 normals;
public bool3x2 splitAxis;
public int splitCount;
public float3 position;
public float radius;
public bool isBackFaced;
public float3 cameraForward;
}
public static void CalculateCornerHorizon(float3 cornerPosition, quaternion orientation, float3 cameraCenter, float3 cameraForward, bool cameraOrtho, float radius, out Corner
{
var axisx = new float3(1f, 0f, 0f);
var axisy = new float3(0f, 1f, 0f);
var axisz = new float3(0f, 0f, 1f);
// a vector pointing away from the center of the corner
var cornerNormal = math.normalize(math.mul(orientation, new float3(1f, 1f, 1f)));
var axes = math.mul(new float3x3(orientation), new float3x3(axisx, axisy, axisz));
CalculateCornerHorizon(cornerPosition,
axes,
cornerNormal,
cameraCenter, cameraForward, cameraOrtho,
radius, out corner);
}
public static void CalculateCornerHorizon(float3 cornerPosition, float3x3 axes, float3 cornerNormal, float3 cameraCenter, float3 cameraForward, bool cameraOrtho, float radius
{
var cameraToCenter = cornerPosition - cameraCenter; // vector from camera to center
var sqrRadius = radius * radius;
var sqrDistCameraToCenter = math.lengthsq(cameraToCenter);
var sqrOffset = (sqrRadius * sqrRadius / sqrDistCameraToCenter); // squared distance from actual center to drawn disc center
if (!cameraOrtho)
cameraForward = cameraToCenter;
var normals = new float3x3
{
c0 = math.normalize(math.cross(axes[1], axes[2])),
c1 = math.normalize(math.cross(axes[2], axes[0])),
c2 = math.normalize(math.cross(axes[0], axes[1]))
};
corner = new Corner
{
angle = new float3(
Vector3.Angle(axes[0], axes[1]),
Vector3.Angle(axes[1], axes[2]),
Vector3.Angle(axes[2], axes[0])
),
intersections = default,
points = default,
splitAxis = default,
axes = axes,
normals = normals,
position = cornerPosition,
radius = radius,
cameraForward = cameraForward,
isBackFaced = math.dot(cornerNormal, cameraForward) > 0,
splitCount = 0
};
if (math.abs(sqrDistCameraToCenter) <= sqrRadius)
return;
for (int n = 0, sign = -1; n < 2; n++, sign += 2)
{
for (int i = 0; i < 3; i++)
{
var axis1 = normals[i] * sign;
var axis2 = axes[(i + 1) % 3] * sign;
var axis3 = axes[(i + 2) % 3] * sign;
var Q = Vector3.Angle(cameraForward, axis1);
var f = math.tan(math.radians(90 - math.min(Q, 180 - Q)));
var g = sqrOffset + f * f * sqrOffset;
if (g >= sqrRadius)
continue;
var e = math.degrees(math.asin(math.sqrt(g) / radius));
var vectorToPointOnHorizon = Quaternion.AngleAxis(e, axis1) * math.normalize(math.cross(axis1, cameraForward));
vectorToPointOnHorizon = math.normalize(Vector3.ProjectOnPlane(vectorToPointOnHorizon, axis1));
var intersectionDirection = vectorToPointOnHorizon;
var angle1 = Vector3.SignedAngle(axis2, intersectionDirection, axis1);
var angle2 = Vector3.SignedAngle(axis3, intersectionDirection, axis1);
if (angle1 <= 0 || angle2 >= 0)
continue;
var point = corner.position + (float3)(intersectionDirection * radius);
if (corner.splitCount < 2)
{
corner.splitAxis[corner.splitCount][i] = true;
corner.intersections[corner.splitCount] = intersectionDirection;
corner.points[corner.splitCount] = point;
corner.splitCount++;
}
}
}
if (!math.any(corner.splitAxis[0]) &&
!math.any(corner.splitAxis[1]))
{
corner.splitCount = 0;
corner.splitAxis[0] = false;
corner.splitAxis[1] = false;
}
}
public static void DrawCorner(Corner corner, bool3 showAxis)
{
var color = Handles.color;
var axes = corner.axes;
var intersections = corner.intersections;
var normals = corner.normals;
var origin = corner.position;
var radius = corner.radius;
if (corner.splitCount <= 1)
{
AdjustMidpointHandleColor(corner.isBackFaced);
if (showAxis[0]) Handles.DrawWireArc(origin, normals[0], axes[1], corner.angle[1], radius);
if (showAxis[1]) Handles.DrawWireArc(origin, normals[1], axes[2], corner.angle[2], radius);
if (showAxis[2]) Handles.DrawWireArc(origin, normals[2], axes[0], corner.angle[0], radius);
}
else
{
var angleLength = Vector3.SignedAngle(Vector3.ProjectOnPlane(intersections[0], corner.cameraForward),
Vector3.ProjectOnPlane(intersections[1], corner.cameraForward), corner.cameraForward);
bool reversePolarity = angleLength < 0;
if (reversePolarity)
Handles.DrawWireArc(origin, corner.cameraForward, corner.points[1] - origin, -angleLength, radius);
else
Handles.DrawWireArc(origin, corner.cameraForward, corner.points[0] - origin, angleLength, radius);

var backfacedColor = GetStateColor(true);


var axesBackfaced = new bool3(math.length(intersections[0] - axes[0]) < kDistanceEpsilon || math.length(intersections[1] - axes[0]) < kDistanceEpsilon,
math.length(intersections[0] - axes[1]) < kDistanceEpsilon || math.length(intersections[1] - axes[1]) < kDistanceEpsilon,
math.length(intersections[0] - axes[2]) < kDistanceEpsilon || math.length(intersections[1] - axes[2]) < kDistanceEpsilon);
var color1 = reversePolarity ? color : backfacedColor;
var color2 = reversePolarity ? backfacedColor : color;
for (int A = 1, B = 2, C = 0; C < 3; A = B, B = C, C++)
{
if (corner.splitAxis[0][C] == corner.splitAxis[1][C])
{
if (!axesBackfaced[A]) { angleLength = Vector3.Angle(intersections[0], axes[A]); axesBackfaced[A] = (angleLength<kDegreeEpsilon || angleLength> corner.angle
if (!axesBackfaced[B]) { angleLength = Vector3.Angle(intersections[1], axes[A]); axesBackfaced[B] = (angleLength<kDegreeEpsilon || angleLength> corner.angle
}
else if (corner.splitAxis[0][C])
{
if (showAxis[C])
{
angleLength = Vector3.Angle(intersections[0], axes[A]);
Handles.color = color1; Handles.DrawWireArc(origin, normals[C], intersections[0], -angleLength, radius);
Handles.color = color2; Handles.DrawWireArc(origin, normals[C], intersections[0], corner.angle[A] - angleLength, radius);
}
axesBackfaced[A] = true;
}
else
//if (corner.splitAxis[1][C])
{
if (showAxis[C])
{
angleLength = Vector3.Angle(intersections[1], axes[A]);
Handles.color = color2; Handles.DrawWireArc(origin, normals[C], intersections[1], -angleLength, radius);
Handles.color = color1; Handles.DrawWireArc(origin, normals[C], intersections[1], corner.angle[A] - angleLength, radius);
}
axesBackfaced[B] = true;
}
}
// check for singularity
if (math.all(axesBackfaced))
axesBackfaced = corner.isBackFaced;
for (int A = 1, B = 2, C = 0; C < 3; A = B, B = C, C++)
{
if (!showAxis[C])
continue;
if (corner.splitAxis[0][C] == corner.splitAxis[1][C])
{
Handles.color = (axesBackfaced[B] && axesBackfaced[A]) ? color1 : color2;
Handles.DrawWireArc(origin, normals[C], axes[A], corner.angle[A], radius);
}
}
}
Handles.color = color;
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/EditorTools/PhysicsCapsuleBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class PhysicsCapsuleBoundsHandle : CapsuleBoundsHandle
{
static PhysicsBoundsHandleUtility.Corner[] s_Corners = new PhysicsBoundsHandleUtility.Corner[8];
protected override void DrawWireframe()
{
if (this.radius <= 0f)
{
base.DrawWireframe();
return;
}
var cameraPos = default(float3);
var cameraFwd = new float3 { z = 1f };
var cameraOrtho = true;
if (Camera.current != null)
{
cameraPos = Camera.current.transform.position;
cameraFwd = Camera.current.transform.forward;
cameraOrtho = Camera.current.orthographic;
}
var size = new float3(this.radius * 2f, this.radius * 2f, height);
var radius = this.radius;
var origin = (float3)this.center;
var bounds = new Bounds(this.center, size);
// 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;
var cameraCenter = (float3)invMatrix.MultiplyPoint(cameraPos);
var cameraForward = (float3)invMatrix.MultiplyVector(cameraFwd);
bool isCameraInsideBox = Camera.current != null
&& bounds.Contains(invMatrix.MultiplyPoint(cameraPos));
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(-1f, 1f, 1f), radius, 0, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, -1f, 1f), radius, 1, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 2, axes, isCameraInsideBox);
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, -1f), radius, 2, axes, isCameraInsideBox);
var corner = 0.5f * size - new float3(1f) * radius;
var axisx = new float3(1f, 0f, 0f);
var axisy = new float3(0f, 1f, 0f);
var axisz = new float3(0f, 0f, 1f);
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, -1f), quaternion.LookRotation(-axisz, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, 1f), quaternion.LookRotation(-axisx, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, 1f), quaternion.LookRotation(axisz, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, -1f), quaternion.LookRotation(axisx, axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, -1f), quaternion.LookRotation(-axisx, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, 1f), quaternion.LookRotation(axisz, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, 1f), quaternion.LookRotation(axisx, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, -1f), quaternion.LookRotation(-axisz, -axisy), cameraCenter, cameraForward, cameraOrtho
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[0], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[3], new bool3(true, false, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[4], new bool3(true, false, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[7], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[1], new bool3(true, false, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[2], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[5], new bool3(false, true, true));
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[6], new bool3(true, false, 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/PhysicsSphereBoundsHandle.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Unity.Physics.Editor
{
class PhysicsSphereBoundsHandle : SphereBoundsHandle
{
protected override void DrawWireframe()
{
bool x = IsAxisEnabled(Axes.X);
bool y = IsAxisEnabled(Axes.Y);
bool z = IsAxisEnabled(Axes.Z);

if (x && !y && !z)


Handles.DrawLine(Vector3.right * radius, Vector3.left * radius);
if (!x && y && !z)
Handles.DrawLine(Vector3.up * radius, Vector3.down * radius);
if (!x && !y && z)
Handles.DrawLine(Vector3.forward * radius, Vector3.back * radius);
const float kEpsilon = 0.000001F;
if (radius > 0)
{
var frontfacedColor = Handles.color;
var backfacedColor = Handles.color * new Color(1f, 1f, 1f, PhysicsBoundsHandleUtility.kBackfaceAlphaMultiplier);
var discVisible = new bool[]
{
y && z,
x && z,
x && y
};
var discOrientations = new float3[]
{
Vector3.right,
Vector3.up,
Vector3.forward
};
// 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;
var cameraCenter = Camera.current == null ? Vector3.zero : Camera.current.transform.position;
var cameraToCenter = center - invMatrix.MultiplyPoint(cameraCenter); // vector from camera to center
var sqrDistCameraToCenter = cameraToCenter.sqrMagnitude;
var sqrRadius = radius * radius; // squared radius
var isCameraOrthographic = Camera.current == null || Camera.current.orthographic;
var sqrOffset = isCameraOrthographic ? 0 : (sqrRadius * sqrRadius / sqrDistCameraToCenter); // squared distance from actual center to drawn disc center
var insideAmount = sqrOffset / sqrRadius;
if (insideAmount < 1)
{
if (math.abs(sqrDistCameraToCenter) >= kEpsilon)
{
using (new Handles.DrawingScope(frontfacedColor))
{
if (isCameraOrthographic)
{
var horizonRadius = radius;
var horizonCenter = center;
Handles.DrawWireDisc(horizonCenter, cameraToCenter, horizonRadius);
}
else
{
var horizonRadius = math.sqrt(sqrRadius - sqrOffset);
var horizonCenter = center - sqrRadius * cameraToCenter / sqrDistCameraToCenter;
Handles.DrawWireDisc(horizonCenter, cameraToCenter, horizonRadius);
}
}
var planeNormal = cameraToCenter.normalized;
for (int i = 0; i < 3; i++)
{
if (!discVisible[i])
continue;

var discOrientation = discOrientations[i];


var angleBetweenDiscAndNormal = math.acos(math.dot(discOrientation, planeNormal));
angleBetweenDiscAndNormal = (math.PI * 0.5f) - math.min(angleBetweenDiscAndNormal, math.PI - angleBetweenDiscAndNormal);
float f = math.tan(angleBetweenDiscAndNormal);
float g = math.sqrt(sqrOffset + f * f * sqrOffset) / radius;
if (g < 1)
{
var angleToHorizon = math.degrees(math.asin(g));
var discTangent = math.cross(discOrientation, planeNormal);
var vectorToPointOnHorizon = Quaternion.AngleAxis(angleToHorizon, discOrientation) * discTangent;
var horizonArcLength = (90 - angleToHorizon) * 2.0f;
using (new Handles.DrawingScope(frontfacedColor))
Handles.DrawWireArc(center, discOrientation, vectorToPointOnHorizon, horizonArcLength, radius);
using (new Handles.DrawingScope(backfacedColor))
Handles.DrawWireArc(center, discOrientation, vectorToPointOnHorizon, horizonArcLength - 360, radius);
}
else
{
using (new Handles.DrawingScope(backfacedColor))
Handles.DrawWireDisc(center, discOrientation, radius);
}
}
}
}
else
{
using (new Handles.DrawingScope(backfacedColor))
{
for (int i = 0; i < 3; i++)
{
var discOrientation = discOrientations[i];
Handles.DrawWireDisc(center, discOrientation, radius);
}
}
}
}
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/BaseDrawer.cs
using UnityEditor;
using UnityEngine;

namespace Unity.Physics.Editor
{
abstract class BaseDrawer : PropertyDrawer
{
protected abstract bool IsCompatible(SerializedProperty property);

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)


{
return IsCompatible(property)
? EditorGUI.GetPropertyHeight(property)
: EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (IsCompatible(property))
DoGUI(position, property, label);
else
EditorGUIControls.DisplayCompatibilityWarning(position, label, ObjectNames.NicifyVariableName(GetType().Name));
}

protected abstract void DoGUI(Rect position, SerializedProperty property, GUIContent label);


}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/EnumFlagsDrawer.cs
using System;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
namespace Unity.Physics.Editor
{
[CustomPropertyDrawer(typeof(EnumFlagsAttribute))]
class EnumFlagsDrawer : BaseDrawer
{
protected override bool IsCompatible(SerializedProperty property)
{
return property.propertyType == SerializedPropertyType.Enum;
}

protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)


{
EditorGUI.BeginProperty(position, label, property);
var value = property.longValue;
EditorGUI.BeginChangeCheck();
value = Convert.ToInt64(
EditorGUI.EnumFlagsField(position, label, (Enum)Enum.ToObject(fieldInfo.FieldType, value))
);
if (EditorGUI.EndChangeCheck())
property.longValue = value;

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;

protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)


{
EditorGUI.BeginProperty(position, label, property);
EditorGUI.PropertyField(
new Rect(position) { xMax = position.xMax - Styles.PopupWidth },
property.FindPropertyRelative("Value"),
label
);
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
EditorGUI.PropertyField(
new Rect(position) { xMin = position.xMax - Styles.PopupWidth + EditorGUIUtility.standardVerticalSpacing },
property.FindPropertyRelative("CombineMode"),
GUIContent.none
);
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/PropertyDrawers/PhysicsMaterialPropertiesDrawer.cs
using System.Collections.Generic;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;

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)."
);
}

const string k_CollisionFilterGroupKey = "m_BelongsToCategories";


const string k_AdvancedGroupKey = "m_CustomMaterialTags";
Dictionary<string, SerializedObject> m_SerializedTemplates = new Dictionary<string, SerializedObject>();

SerializedProperty GetTemplateValueProperty(SerializedProperty property)


{
var key = property.propertyPath;
var template = property.FindPropertyRelative("m_Template").objectReferenceValue;
SerializedObject serializedTemplate;
if (
!m_SerializedTemplates.TryGetValue(key, out serializedTemplate)
|| serializedTemplate?.targetObject != template
)
m_SerializedTemplates[key] = serializedTemplate = template == null ? null : new SerializedObject(template);
serializedTemplate?.Update();
return serializedTemplate?.FindProperty("m_Value");
}

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");
}

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)


{
var templateValueProperty = GetTemplateValueProperty(property);
// m_CollisionResponse, collision filter foldout, advanced foldout
var height = 3f * EditorGUIUtility.singleLineHeight + 2f * EditorGUIUtility.standardVerticalSpacing;

// 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;
}

protected override bool IsCompatible(SerializedProperty property) => true;

static void DisplayOverridableProperty(


Rect position, GUIContent label, SerializedProperty toggle, SerializedProperty value, bool templateAssigned
)
{
if (templateAssigned)
{
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth -= 16f + EditorGUIUtility.standardVerticalSpacing;
var togglePosition = new Rect(position) { width = EditorGUIUtility.labelWidth + 16f + EditorGUIUtility.standardVerticalSpacing };
EditorGUI.PropertyField(togglePosition, toggle, label);
EditorGUIUtility.labelWidth = labelWidth;

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);
}
}

protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)


{
var template = property.FindPropertyRelative("m_Template");
var templateAssigned = template.objectReferenceValue != null;
var supportsTemplate = property.FindPropertyRelative("m_SupportsTemplate");
if (supportsTemplate.boolValue)
{
position.height = EditorGUI.GetPropertyHeight(template);
EditorGUI.PropertyField(position, template);

position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;


}

var templateValue = GetTemplateValueProperty(property);


FindToggleAndValueProperties(property, templateValue, "m_CollisionResponse", out var collisionResponseDropDown, out var collisionResponse);
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.CollisionResponseLabel, collisionResponseDropDown, collisionResponse, templateAssigned);
SerializedProperty toggle;

// Check if regular collider


CollisionResponsePolicy collisionResponseEnum = (CollisionResponsePolicy)collisionResponse.intValue;
if (collisionResponseEnum == CollisionResponsePolicy.Collide ||
collisionResponseEnum == CollisionResponsePolicy.CollideRaiseCollisionEvents)
{
FindToggleAndValueProperties(property, templateValue, "m_Friction", out toggle, out var friction);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.FrictionLabel, toggle, friction, templateAssigned);
FindToggleAndValueProperties(property, templateValue, "m_Restitution", out toggle, out var restitution);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.RestitutionLabel, toggle, restitution, templateAssigned);
}

// collision filter group


var collisionFilterGroup = property.FindPropertyRelative(k_CollisionFilterGroupKey);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
collisionFilterGroup.isExpanded =
EditorGUI.Foldout(position, collisionFilterGroup.isExpanded, Content.CollisionFilterGroupFoldout, true);
if (collisionFilterGroup.isExpanded)
{
++EditorGUI.indentLevel;

FindToggleAndValueProperties(property, templateValue, "m_BelongsToCategories", out toggle, out var belongsTo);


position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.BelongsToLabel, toggle, belongsTo, templateAssigned);
FindToggleAndValueProperties(property, templateValue, "m_CollidesWithCategories", out toggle, out var collidesWith);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.CollidesWithLabel, toggle, collidesWith, templateAssigned);
--EditorGUI.indentLevel;
}
// advanced group
var advancedGroup = property.FindPropertyRelative(k_AdvancedGroupKey);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
advancedGroup.isExpanded =
EditorGUI.Foldout(position, advancedGroup.isExpanded, Content.AdvancedGroupFoldout, true);
if (advancedGroup.isExpanded)
{
++EditorGUI.indentLevel;
FindToggleAndValueProperties(property, templateValue, "m_CustomMaterialTags", out toggle, out var customFlags);
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
position.height = EditorGUIUtility.singleLineHeight;
DisplayOverridableProperty(position, Content.CustomFlagsLabel, toggle, customFlags, templateAssigned);

--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");

public static readonly string MultipleAssetsTooltip =


L10n.Tr("Multiple {0} assets found. UI will display labels defined in {1}.");

public static readonly GUIContent MultipleAssetsWarning =


new GUIContent { image = EditorGUIUtility.Load("console.warnicon") as Texture };
}
protected abstract int MaxNumCategories { get; }
protected abstract string DefaultCategoryName { get; }
internal string FirstChildPropertyPath { get; set; } // TODO: remove when all usages of bool[] are migrated

string DefaultFormatString => L10n.Tr($"(Undefined {DefaultCategoryName})");


string[] DefaultOptions =>
m_DefaultOptions ?? (
m_DefaultOptions =
Enumerable.Range(0, MaxNumCategories)
.Select(i => string.Format(DefaultFormatString, i))
.ToArray()
);
string[] m_DefaultOptions;

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];

m_Options[i] = $"{i}: {m_Options[i]}";


}
return m_Options;
}

string[] m_Options;

static string GetButtonLabel(int value, IReadOnlyList<string> optionNames)


{
switch (value)
{
case 0:
return Styles.NothingName;
case ~0:
return Styles.EverythingName;
default:
{
for (var i = 0; i < 32; i++)
{
if (value == 1 << i)
return optionNames[i];
}
break;
}
}
return Styles.MixedName;
}

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;
}

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)


{
if (m_NamesAssets?.Length > 1)
position.xMax -= EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;

EditorGUI.BeginProperty(position, label, property);

var controlPosition = EditorGUI.PrefixLabel(position, label);


var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
var showMixed = EditorGUI.showMixedValue;

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];

public static void SoftSlider(


Rect position, GUIContent label, SerializedProperty property,
float sliderMin, float sliderMax,
float textFieldMin, float textFieldMax
)
{
if (property.propertyType != SerializedPropertyType.Float)
{
DisplayCompatibilityWarning(position, label, property.propertyType.ToString());
}
else if (k_SoftSlider == null)
{
EditorGUI.BeginChangeCheck();
EditorGUI.PropertyField(position, property, label);
if (EditorGUI.EndChangeCheck())
property.floatValue = math.clamp(property.floatValue, textFieldMin, textFieldMax);
}
else
{
k_SoftSliderArgs[0] = position;
k_SoftSliderArgs[1] = label;
k_SoftSliderArgs[2] = property.floatValue;
k_SoftSliderArgs[3] = sliderMin;
k_SoftSliderArgs[4] = sliderMax;
k_SoftSliderArgs[5] = textFieldMin;
k_SoftSliderArgs[6] = textFieldMax;
EditorGUI.BeginProperty(position, label, property);
EditorGUI.BeginChangeCheck();
var result = k_SoftSlider.Invoke(null, k_SoftSliderArgs);
if (EditorGUI.EndChangeCheck())
property.floatValue = (float)result;
EditorGUI.EndProperty();
}
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/ManipulatorUtility.cs
using System;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Physics.Editor
{
enum MatrixState
{
UniformScale,
NonUniformScale,
ZeroScale,
NotValidTRS
}
static class ManipulatorUtility
{
public static MatrixState GetMatrixState(ref float4x4 localToWorld)
{
if (
localToWorld.c0.w != 0f
|| localToWorld.c1.w != 0f
|| localToWorld.c2.w != 0f
|| localToWorld.c3.w != 1f
)
return MatrixState.NotValidTRS;
var m = new float3x3(localToWorld.c0.xyz, localToWorld.c1.xyz, localToWorld.c2.xyz);
var lossyScale = new float3(math.length(m.c0.xyz), math.length(m.c1.xyz), math.length(m.c2.xyz));
if (math.determinant(m) < 0f)
lossyScale.x *= -1f;
if (math.lengthsq(lossyScale) == 0f)
return MatrixState.ZeroScale;
return math.abs(math.cmax(lossyScale)) - math.abs(math.cmin(lossyScale)) > 0.000001f
? MatrixState.NonUniformScale
: MatrixState.UniformScale;
}
}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/SceneViewUtility.cs
using System;
using Unity.Mathematics;
using UnityEditor;
using UnityEngine;

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;
}

const string k_NotificationsPrefKey = "SceneView/Tools/Notifications";


const bool k_DefaultNotifications = true;
const string k_NotificationSpeedPrefKey = "SceneView/Tools/Notification Speed";
const float k_DefaultNotificationsSpeed = 20f;

const float k_NotificationDuration = 1f;


const float k_NotificationFadeInTime = 0.04f;
const float k_NotificationFadeOutTime = 0.2f;
static readonly AnimationCurve k_NotificationFadeCurve = new AnimationCurve
{
keys = new[]
{
new Keyframe { time = 0f, value = 0f, outTangent = 1f / k_NotificationFadeInTime },
new Keyframe { time = k_NotificationFadeInTime, value = 1f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_NotificationDuration - k_NotificationFadeOutTime, value = 1f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_NotificationDuration, value = 0f, inTangent = -1f / k_NotificationFadeOutTime }
},
postWrapMode = WrapMode.Clamp,
preWrapMode = WrapMode.Clamp
};
const float k_IndeterminateProgressCurveDuration = 2f;
static readonly AnimationCurve k_IndeterminateProgressCurveLeftMargin = new AnimationCurve
{
keys = new[]
{
new Keyframe { time = 0f, value = 0f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration / 2f, value = 0.25f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration, value = 1f, inTangent = 0f, outTangent = 0f }
},
postWrapMode = WrapMode.Loop,
preWrapMode = WrapMode.Loop
};
static readonly AnimationCurve k_IndeterminateProgressCurveRightMargin = new AnimationCurve
{
keys = new[]
{
new Keyframe { time = 0f, value = 1f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration / 2f, value = 0f, inTangent = 0f, outTangent = 0f },
new Keyframe { time = k_IndeterminateProgressCurveDuration, value = 0f, inTangent = 0f, outTangent = 0f }
},
postWrapMode = WrapMode.Loop,
preWrapMode = WrapMode.Loop
};

static string s_StatusMessage;


static DateTime s_StartTime;
static bool s_IsTemporary;
static Func<float> s_GetProgress;

public static void DisplayProgressNotification(string message, Func<float> getProgress) =>


// insert an extra line to make room for progress bar
DisplayNotificationInSceneView(getProgress == null ? message : $"{message}\n", false, getProgress);
public static void DisplayPersistentNotification(string message) =>
DisplayNotificationInSceneView(message, false, null);

public static void DisplayTemporaryNotification(string message) =>


DisplayNotificationInSceneView(message, true, null);

static void DisplayNotificationInSceneView(string message, bool temporary, Func<float> getProgress)


{
s_StatusMessage = message ?? string.Empty;
s_StartTime = DateTime.Now;
s_IsTemporary = temporary;
s_GetProgress = getProgress;
ClearNotificationInSceneView();
SceneView.duringSceneGui += ToolNotificationCallback;
SceneView.RepaintAll();
}

static void ToolNotificationCallback(SceneView obj)


{
if (Camera.current == null)
return;
var duration = math.max(s_StatusMessage.Length, 1)
/ EditorPrefs.GetFloat(k_NotificationSpeedPrefKey, k_DefaultNotificationsSpeed);
var t = (float)(DateTime.Now - s_StartTime).TotalSeconds;
if (
s_IsTemporary
&& (t >= duration || !EditorPrefs.GetBool(k_NotificationsPrefKey, k_DefaultNotifications))
)
{
ClearNotificationInSceneView();
}
else
{
Handles.BeginGUI();
var color = GUI.color;
var progress = s_GetProgress?.Invoke() ?? 0f;
GUI.color *=
new Color(1f, 1f, 1f, math.max(k_NotificationFadeCurve.Evaluate(math.abs(t) / duration), progress));
var rect = new Rect { size = Camera.current.pixelRect.size / EditorGUIUtility.pixelsPerPoint };
using (new GUILayout.AreaScope(rect))
using (new GUILayout.HorizontalScope())
{
GUILayout.FlexibleSpace();
using (new GUILayout.VerticalScope())
{
GUILayout.Space(rect.height * 0.75f);
GUILayout.FlexibleSpace();
var maxWidth = rect.width * 0.5f;
GUILayout.Box(s_StatusMessage, Styles.SceneViewStatusMessage, GUILayout.MaxWidth(maxWidth));
if (s_GetProgress != null)
{
rect = GUILayoutUtility.GetLastRect();
rect = Styles.SceneViewStatusMessage.padding.Remove(rect);
rect.y = rect.yMax - Styles.ProgressBarTrack.fixedHeight;
rect.height = Styles.ProgressBarTrack.fixedHeight;
var c = GUI.color;
GUI.color *= Color.black;
GUI.Box(rect, GUIContent.none, Styles.ProgressBarTrack);
GUI.color = c;
if (progress >= 0f && progress <= 1f)
{
rect.width *= progress;
}
else
{
var w = rect.width;
rect.xMin = rect.xMin + w * k_IndeterminateProgressCurveLeftMargin.Evaluate(t);
rect.xMax = rect.xMax - w * k_IndeterminateProgressCurveRightMargin.Evaluate(t);
}
GUI.Box(rect, GUIContent.none, Styles.ProgressBarIndicator);
}
GUILayout.FlexibleSpace();
}
GUILayout.FlexibleSpace();
}
GUI.color = color;
Handles.EndGUI();
}
SceneView.RepaintAll();
}

public static void ClearNotificationInSceneView() => SceneView.duringSceneGui -= ToolNotificationCallback;


}
}
Tutorial/Assets/Scripts/Custom Physics
Authoring/Unity.Physics.Custom.Editor/Utilities/StatusMessageUtility.cs
using System.Collections.Generic;
using System.Linq;
using Unity.Physics.Authoring;
using UnityEditor;
using UnityEngine;
using UnityObject = UnityEngine.Object;
using LegacyRigidBody = UnityEngine.Rigidbody;

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;

var targetType = c.GetType();


// only bodies (both explicit and implicit static bodies) will emit a message
if (
targetType == typeof(PhysicsBodyAuthoring)
|| targetType == typeof(LegacyRigidBody)
|| c.GetComponent<PhysicsBodyAuthoring>() == null
&& c.GetComponent<LegacyRigidBody>() == null
)
++numChildTargets;
}
switch (numChildTargets)
{
case 0:
return MessageType.None;
case 1:
statusMessage =
L10n.Tr("Target will be un-parented during the conversion process in order to take part in physics simulation.");
return MessageType.Warning;
default:
statusMessage =
L10n.Tr("One or more targets will be un-parented during the conversion process in order to take part in physics simulation.");
return MessageType.Warning;
}
}
public static MessageType GetMatrixStatusMessage(
IReadOnlyList<MatrixState> matrixStates, out string statusMessage
)
{
statusMessage = string.Empty;
if (matrixStates.Contains(MatrixState.NotValidTRS))
{
statusMessage = L10n.Tr(
matrixStates.Count == 1
? "Target's local-to-world matrix is not a valid transformation."
: "One or more targets' local-to-world matrices are not valid transformations."
);
return MessageType.Error;
}
if (matrixStates.Contains(MatrixState.ZeroScale))
{
statusMessage =
L10n.Tr(matrixStates.Count == 1 ? "Target has zero scale." : "One or more targets has zero scale.");
return MessageType.Warning;
}

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;

// If 1, then it is a vertex collision


// If 2, then it is an edge collision
// If 3 or more, then it is a face collision
public int NumberOfContactPoints;

// Estimated impulse applied


public float EstimatedImpulse;
// Average contact point position
public float3 AverageContactPointPosition;

public Details(int numContactPoints, float estimatedImpulse, float3 averageContactPosition)


{
IsValid = (0 < numContactPoints); // Should we add a max check?
NumberOfContactPoints = numContactPoints;
EstimatedImpulse = estimatedImpulse;
AverageContactPointPosition = averageContactPosition;
}
}
// Returns the other entity in EntityPair, if provided with other one
public Entity GetOtherEntity(Entity entity)
{
Assert.IsTrue((entity == EntityA) || (entity == EntityB));
return entity == EntityA ? EntityB : EntityA;
}

// 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;
}

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 selfEntity = GetEntity(TransformUsageFlags.None);
if (authoring.CalculateDetails)
{
var dynamicBufferTag = new StatefulCollisionEventDetails
{
CalculateDetails = authoring.CalculateDetails
};
AddComponent(selfEntity, dynamicBufferTag);
}
AddBuffer<StatefulCollisionEvent>(selfEntity);
}
}
}
}
Tutorial/Assets/Scripts/PhysicsStatefulEvents/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;

public ComponentHandles(ref SystemState systemState)


{
EventExcludes = systemState.GetComponentLookup<DummyExcludeComponent>(true);
EventDetails = systemState.GetComponentLookup<StatefulCollisionEventDetails>(true);
EventBuffers = systemState.GetBufferLookup<StatefulCollisionEvent>(false);
}

public void Update(ref SystemState systemState)


{
EventExcludes.Update(ref systemState);
EventBuffers.Update(ref systemState);
EventDetails.Update(ref systemState);
}
}

[BurstCompile]
public void OnCreate(ref SystemState state)
{
m_StateFulEventBuffers = new StatefulSimulationEventBuffers<StatefulCollisionEvent>();
m_StateFulEventBuffers.AllocateBuffers();
state.RequireForUpdate<StatefulCollisionEvent>();

m_Handles = new ComponentHandles(ref state);


}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
m_StateFulEventBuffers.Dispose();
}
[BurstCompile]
public partial struct ClearCollisionEventDynamicBufferJob : IJobEntity
{
public void Execute(ref DynamicBuffer<StatefulCollisionEvent> eventBuffer) => eventBuffer.Clear();
}

[BurstCompile]
public void OnUpdate(ref SystemState state)
{
m_Handles.Update(ref state);

state.Dependency = new ClearCollisionEventDynamicBufferJob()


.ScheduleParallel(state.Dependency);

m_StateFulEventBuffers.SwapBuffers();

var currentEvents = m_StateFulEventBuffers.Current;


var previousEvents = m_StateFulEventBuffers.Previous;
state.Dependency = new StatefulEventCollectionJobs.
CollectCollisionEventsWithDetails
{
CollisionEvents = currentEvents,
PhysicsWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().PhysicsWorld,
EventDetails = m_Handles.EventDetails
}.Schedule(SystemAPI.GetSingleton<SimulationSingleton>(), state.Dependency);

state.Dependency = new StatefulEventCollectionJobs.


ConvertEventStreamToDynamicBufferJob<StatefulCollisionEvent, DummyExcludeComponent>
{
CurrentEvents = currentEvents,
PreviousEvents = previousEvents,
EventBuffers = m_Handles.EventBuffers,

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;

public void AllocateBuffers()


{
Previous = new NativeList<T>(Allocator.Persistent);
Current = new NativeList<T>(Allocator.Persistent);
}
public void Dispose()
{
if (Previous.IsCreated) Previous.Dispose();
if (Current.IsCreated) Current.Dispose();
}

public void SwapBuffers()


{
var tmp = Previous;
Previous = Current;
Current = tmp;
Current.Clear();
}
/// <summary>
/// Returns a sorted combined list of stateful events based on the Previous and Current event frames.
/// Note: ensure that the frame buffers are sorted by calling SortBuffers first.
/// </summary>
/// <param name="statefulEvents"></param>
/// <param name="sortCurrent">Specifies whether the Current events list needs to be sorted first.</param>
public void GetStatefulEvents(NativeList<T> statefulEvents, bool sortCurrent = true) => GetStatefulEvents(Previous, Current, statefulEvents, sortCurrent);

/// <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;

public void Execute(CollisionEvent collisionEvent)


{
var statefulCollisionEvent = new StatefulCollisionEvent(collisionEvent);

// Check if we should calculate the collision details


bool calculateDetails = ForceCalculateDetails;
if (!calculateDetails && EventDetails.HasComponent(collisionEvent.EntityA))
{
calculateDetails = EventDetails[collisionEvent.EntityA].CalculateDetails;
}
if (!calculateDetails && EventDetails.HasComponent(collisionEvent.EntityB))
{
calculateDetails = EventDetails[collisionEvent.EntityB].CalculateDetails;
}
if (calculateDetails)
{
var details = collisionEvent.CalculateDetails(ref PhysicsWorld);
statefulCollisionEvent.CollisionDetails = new StatefulCollisionEvent.Details(
details.EstimatedContactPointPositions.Length,
details.EstimatedImpulse,
details.AverageContactPointPosition);
}

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);

StatefulSimulationEventBuffers<T>.GetStatefulEvents(PreviousEvents, CurrentEvents, statefulEvents);

for (int i = 0; i < statefulEvents.Length; i++)


{
var statefulEvent = statefulEvents[i];

var addToEntityA = EventBuffers.HasBuffer(statefulEvent.EntityA) && (!UseExcludeComponent || !EventExcludeLookup.HasComponent(statefulEvent.EntityA));


var addToEntityB = EventBuffers.HasBuffer(statefulEvent.EntityB) && (!UseExcludeComponent || !EventExcludeLookup.HasComponent(statefulEvent.EntityA));

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 {}

public class StatefulTriggerEventBufferAuthoring : MonoBehaviour


{
}
class StatefulTriggerEventBufferAuthoringBaker : Baker<StatefulTriggerEventBufferAuthoring>
{
public override void Bake(StatefulTriggerEventBufferAuthoring authoring)
{
Entity selfEntity = GetEntity(TransformUsageFlags.None);
AddBuffer<StatefulTriggerEvent>(selfEntity);
}
}
}
Tutorial/Assets/Scripts/PhysicsStatefulEvents/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;

public ComponentHandles(ref SystemState systemState)


{
EventExcludes = systemState.GetComponentLookup<StatefulTriggerEventExclude>(true);
EventBuffers = systemState.GetBufferLookup<StatefulTriggerEvent>();
}

public void Update(ref SystemState systemState)


{
EventExcludes.Update(ref systemState);
EventBuffers.Update(ref systemState);
}
}

[BurstCompile]
public void OnCreate(ref SystemState state)
{
EntityQueryBuilder builder = new EntityQueryBuilder(Allocator.Temp)
.WithAllRW<StatefulTriggerEvent>()
.WithNone<StatefulTriggerEventExclude>();

m_StateFulEventBuffers = new StatefulSimulationEventBuffers<StatefulTriggerEvent>();


m_StateFulEventBuffers.AllocateBuffers();

m_TriggerEventQuery = state.GetEntityQuery(builder);
state.RequireForUpdate(m_TriggerEventQuery);

m_ComponentHandles = new ComponentHandles(ref state);


}

[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);

state.Dependency = new ClearTriggerEventDynamicBufferJob()


.ScheduleParallel(m_TriggerEventQuery, state.Dependency);

m_StateFulEventBuffers.SwapBuffers();

var currentEvents = m_StateFulEventBuffers.Current;


var previousEvents = m_StateFulEventBuffers.Previous;

state.Dependency = new StatefulEventCollectionJobs.CollectTriggerEvents


{
TriggerEvents = currentEvents
}.Schedule(SystemAPI.GetSingleton<SimulationSingleton>(), state.Dependency);

state.Dependency = new StatefulEventCollectionJobs


.ConvertEventStreamToDynamicBufferJob<StatefulTriggerEvent, StatefulTriggerEventExclude>
{
CurrentEvents = currentEvents,
PreviousEvents = previousEvents,
EventBuffers = m_ComponentHandles.EventBuffers,

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;

public void Set(uint tick)


{
_lastSetTick = tick;
_wasEverSet = 1;
}
public bool IsSet(uint tick)
{
if (_wasEverSet == 1)
{
return tick == _lastSetTick;
}
return false;
}
}
Tutorial/Assets/Scripts/StandardCharacter/Common/Scripts/FixedTickSystem.cs
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using Unity.Burst;
using Unity.Entities;
using UnityEngine;

[UpdateInGroup(typeof(FixedStepSimulationSystemGroup), OrderLast = true)]


[BurstCompile]
public partial struct FixedTickSystem : ISystem
{
public struct Singleton : IComponentData
{
public uint Tick;
}
public void OnCreate(ref SystemState state)
{
if (!SystemAPI.HasSingleton<Singleton>())
{
Entity singletonEntity = state.EntityManager.CreateEntity();
state.EntityManager.AddComponentData(singletonEntity, new Singleton());
}
}

public void OnDestroy(ref SystemState state)


{ }

[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);
}

public bool CanCollideWithHit(


ref ThirdPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
in BasicHit hit)
{
ThirdPersonCharacterComponent characterComponent = CharacterComponent.ValueRO;
// First, see if we'd have to ignore based on the default implementation
if (!PhysicsUtilities.IsCollidable(hit.Material))
{
return false;
}
// if not, check for the ignored tag
if (PhysicsUtilities.HasPhysicsTag(in baseContext.PhysicsWorld, hit.RigidBodyIndex, characterComponent.IgnoredPhysicsTags))
{
return false;
}
return true;
}
public bool IsGroundedOnHit(
ref ThirdPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
in BasicHit hit,
int groundingEvaluationType)
{
ThirdPersonCharacterComponent characterComponent = CharacterComponent.ValueRO;
return CharacterAspect.Default_IsGroundedOnHit(
in this,
ref context,
ref baseContext,
in hit,
in characterComponent.StepAndSlopeHandling,
groundingEvaluationType);
}
public void OnMovementHit(
ref ThirdPersonCharacterUpdateContext 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;
ThirdPersonCharacterComponent characterComponent = CharacterComponent.ValueRO;
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 ThirdPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
ref PhysicsMass characterMass,
ref PhysicsMass otherMass,
BasicHit hit)
{
}
public void ProjectVelocityOnHits(
ref ThirdPersonCharacterUpdateContext context,
ref KinematicCharacterUpdateContext baseContext,
ref float3 velocity,
ref bool characterIsGrounded,
ref BasicHit characterGroundHit,
in DynamicBuffer<KinematicVelocityProjectionHit> velocityProjectionHits,
float3 originalVelocityDirection)
{
ThirdPersonCharacterComponent characterComponent = CharacterComponent.ValueRO;
CharacterAspect.Default_ProjectVelocityOnHits(
ref velocity,
ref characterIsGrounded,
ref characterGroundHit,
in velocityProjectionHits,
originalVelocityDirection,
characterComponent.StepAndSlopeHandling.ConstrainVelocityToGroundPlane);
}
#endregion
}
Tutorial/Assets/Scripts/StandardCharacter/ThirdPerson/Scripts/ThirdPersonCharacterAuthoring.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 ThirdPersonCharacterAuthoring : MonoBehaviour
{
public AuthoringKinematicCharacterProperties CharacterProperties = AuthoringKinematicCharacterProperties.GetDefault();
public ThirdPersonCharacterComponent Character = ThirdPersonCharacterComponent.GetDefault();
public class Baker : Baker<ThirdPersonCharacterAuthoring>
{
public override void Bake(ThirdPersonCharacterAuthoring authoring)
{
KinematicCharacterUtilities.BakeCharacter(this, authoring, authoring.CharacterProperties);
Entity selfEntity = GetEntity(TransformUsageFlags.None);

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;

public class Baker : Baker<ThirdPersonPlayerAuthoring>


{
public override void Bake(ThirdPersonPlayerAuthoring authoring)
{
Entity selfEntity = GetEntity(TransformUsageFlags.None);
AddComponent(selfEntity, new ThirdPersonPlayer
{
ControlledCharacter = GetEntity(authoring.ControlledCharacter, TransformUsageFlags.None),
ControlledCamera = GetEntity(authoring.ControlledCamera, TransformUsageFlags.None),
});
AddComponent(selfEntity, new ThirdPersonPlayerInputs());
}
}
}
Tutorial/Assets/Scripts/StandardCharacter/ThirdPerson/Scripts/ThirdPersonPlayerSystems.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 ThirdPersonPlayerInputsSystem : SystemBase
{
protected override void OnCreate()
{
RequireForUpdate<FixedTickSystem.Singleton>();
RequireForUpdate(SystemAPI.QueryBuilder().WithAll<ThirdPersonPlayer, ThirdPersonPlayerInputs>().Build());
}

protected override void OnUpdate()


{
uint fixedTick = SystemAPI.GetSingleton<FixedTickSystem.Singleton>().Tick;

foreach (var (playerInputs, player) in SystemAPI.Query<RefRW<ThirdPersonPlayerInputs>, ThirdPersonPlayer>())


{
playerInputs.ValueRW.MoveInput = new float2();
playerInputs.ValueRW.MoveInput.y += Input.GetKey(KeyCode.W) ? 1f : 0f;
playerInputs.ValueRW.MoveInput.y += Input.GetKey(KeyCode.S) ? -1f : 0f;
playerInputs.ValueRW.MoveInput.x += Input.GetKey(KeyCode.D) ? 1f : 0f;
playerInputs.ValueRW.MoveInput.x += Input.GetKey(KeyCode.A) ? -1f : 0f;

playerInputs.ValueRW.CameraLookInput = new float2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));


playerInputs.ValueRW.CameraZoomInput = -Input.mouseScrollDelta.y;
// For button presses that need to be queried during fixed update, use the "FixedInputEvent" helper struct.
// This is part of a strategy for proper handling of button press events that are consumed during the fixed update group
if (Input.GetKeyDown(KeyCode.Space))
{
playerInputs.ValueRW.JumpPressed.Set(fixedTick);
}
playerInputs.ValueRW.SprintHeld = Input.GetKey(KeyCode.LeftShift);
}
}
}
/// <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 ThirdPersonPlayerVariableStepControlSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate(SystemAPI.QueryBuilder().WithAll<ThirdPersonPlayer, ThirdPersonPlayerInputs>().Build());
}
public void OnDestroy(ref SystemState state)
{ }
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
foreach (var (playerInputs, player) in SystemAPI.Query<ThirdPersonPlayerInputs, ThirdPersonPlayer>().WithAll<Simulate>())
{
if (SystemAPI.HasComponent<OrbitCameraControl>(player.ControlledCamera))
{
OrbitCameraControl cameraControl = SystemAPI.GetComponent<OrbitCameraControl>(player.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());
}

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<ThirdPersonPlayerInputs>, ThirdPersonPlayer>().WithAll<Simulate>())


{
if (SystemAPI.HasComponent<ThirdPersonCharacterControl>(player.ControlledCharacter))
{
ThirdPersonCharacterControl characterControl = SystemAPI.GetComponent<ThirdPersonCharacterControl>(player.ControlledCharacter);

float3 characterUp = MathUtilities.GetUpFromRotation(SystemAPI.GetComponent<LocalTransform>(player.ControlledCharacter).Rotation);


// 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;
}
float3 cameraForwardOnUpPlane = math.normalizesafe(MathUtilities.ProjectOnPlane(MathUtilities.GetForwardFromRotation(cameraRotation), characterUp));
float3 cameraRight = MathUtilities.GetRightFromRotation(cameraRotation);
// Move
characterControl.MoveVector = (playerInputs.ValueRW.MoveInput.y * cameraForwardOnUpPlane) + (playerInputs.ValueRW.MoveInput.x * cameraRight);
characterControl.MoveVector = MathUtilities.ClampToMaxLength(characterControl.MoveVector, 1f);

// 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;

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,

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;
}

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
{
DeltaTime = SystemAPI.Time.DeltaTime,
PhysicsWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().PhysicsWorld,
LocalToWorldLookup = SystemAPI.GetComponentLookup<LocalToWorld>(false),
CameraTargetLookup = SystemAPI.GetComponentLookup<CameraTarget>(true),
KinematicCharacterBodyLookup = SystemAPI.GetComponentLookup<KinematicCharacterBody>(true),
};
job.Schedule();
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct 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 transform,
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
{
transform.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 transform.Rotation, characterBody.RotationFromParent, DeltaTime, characterBody
orbitCamera.PlanarForward = math.normalizesafe(MathUtilities.ProjectOnPlane(MathUtilities.GetForwardFromRotation(transform.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
transform.Rotation = quaternion.LookRotationSafe(orbitCamera.PlanarForward, targetEntityLocalToWorld.Up);
transform.Rotation = math.mul(transform.Rotation, pitchRotation);
}
float3 cameraForward = MathUtilities.GetForwardFromRotation(transform.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
transform.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(transform.Rotation, transform.Position);
LocalToWorldLookup[entity] = cameraLocalToWorld;
}
}
}
}
_Documentation/samples.md
# Samples
Multiple sample projects using the character controller are available. These include: * Basic: A basic character playground full of various obstacles,
physics objects, etc... * StressTest: A project where you can experiment with the performance of characters using all kinds of settings. *
Platformer: A demonstration of a heavily-extended character controller with many different kinds of movement features. * OnlineFPS: A
demonstration of an online first-person shooter game using client-predicted character movement.
_Documentation/tutorial.md
Tutorial
In this tutorial, we will go through the entire process of creating a new character controller, step-by-step. This will cover a wide variety of features,
and will give you an overview of all the different ways this character controller can be customized.
The tutorial can be followed without downloading the final project, but the completed tutorial project can be found here
Note 1: Throughout this tutorial, the code samples will often have chunks of excluded code in order to show only the important parts that you need
to add or modify. The // (...) represents excluded code.
Note 2: For a more general DOTS tutorial, you may consult the DOTS Tutorial

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".

Building the project


When trying to play the game online, especially with multiple game instances on the same machine, it might be preferable to build the game rather
than playing in the editor. To do so, you can simply build the game normally using the File > Build Settings menu.
_Documentation/Samples/OnlineFPSSample/character-and-
camera.md
OnlineFPS Sample - Character and Camera
The character in this sample started out with the first-person standard character, and was modified to add netcode compatibility. You can find a
summary of the necessary changes in the Networking article in the documentation.
The Character and Player ghost prefabs are located under: Assets/Prefabs/Ghost.
The GhostVariants class defines ghost variants for KinematicCharacterBody, CharacterInterpolation, and LocalTransform for
character ghosts.

Character & camera rotation synchronization


In this sample game, we use a special strategy for synchronizing character & camera rotation. Due to the fact that we have a character that only
ever rotates around its Y axis, it would be a waste of bandwidth to synchronize the full rotation quaternion of the character. Instead, we can simply
synchronize the Y euler angle of our rotation, and reconstruct our character rotation on clients/server based on that angle. Similarly, for camera
rotation, we only synchronize a pitch angle instead of the full rotation quaternion. These ghost fields are defined in
FirstPersonCharacterComponent.CharacterYDegrees and FirstPersonCharacterComponent.ViewPitchDegrees.

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

Variable-rate camera movement in prediction


Instead of setting the FirstPersonPlayerCommands.LookInputDelta based on the raw input vector every frame in
FirstPersonPlayerInputsSystem, we "accumulate" it every frame as long as we haven't reached a new tick. We do this because: * Camera
look movement must be part of prediction * We want camera look movement to be processed at a variable update * The server always updates
at a fixed rate
In this situation, we have camera movement logic that must yield the exact same outcome on client & server (due to prediction), but this movement
logic is updated at different rates on client and server. "Accumulating" the look input delta for every tick is our strategy to tackle this situation. On
clients, when camera rotation updates at a variable rate in the prediction group, camera rotation will first be reset to the state it had at the beginning
of the current tick, and then we will rotate using all of the accumulated rotation input that happened on this tick so far. On the server, where camera
rotation always updates at a fixed rate, we will be using the total accumulated rotation input for the simulated tick. In both cases, the simulation will
result in the same rotation, even though they are updated at different rates.
_Documentation/Samples/OnlineFPSSample/weapons.md
OnlineFPS Sample - Weapons
Assigning a weapon to a character
ServerGameSystem handles spawning a weapon for each character that it spawns, and assigning it as the character's ActiveWeapon. This is
done in ServerGameSystem.HandleSpawnCharacter.
Then, ActiveWeaponSystem iterates on all ActiveWeapon components (on all characters), and handles doing the weapon setup whenever a
change in active weapon is detected. Here we handle parenting the weapon to the FPS character view transform, setting the weapon's shot raycast
point to our character view entity, etc... This is a trick most FPS games use to make sure the weapon shot raycasts start from the center of the
camera and land exactly in the middle of the screen.

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 shot VFX


Networked weapon shot VFX requires some special handling, because we have some weapons that have a very high firing rate, and we'd like to
avoid the bandwidth cost of sending data over network for each projectile that gets shot (whether it's projectile ghosts or RPCs).
Our solution is that every time the server detects a shot to fire on the owner-predicted weapons, it increments a
StandardRaycastWeapon.RemoteShotsCount, which is a ghost field that gets synchronized on all clients. Then, on all clients, a
StandardRaycastWeaponRemoteShotsJob job in StandardRaycastWeaponVisualsSystem creates shot VFX requests for each new shots
in StandardRaycastWeapon.RemoteShotsCount since the last time the job was run. Finally, a StandardRaycastWeaponShotVisualsJob
job in StandardRaycastWeaponVisualsSystem processes these shot VFX requests, and spawns the VFX prefabs for each one.
Each projectile type has its own system handling the specific VFX logic, such as: * LazerShotVisualsSystem * BulletShotVisualsSystem

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.

This state is transitioned to by pressing the roll input.


_Documentation/Samples/PlatformerSample/CharacterStates/rope-
swing.md
Platformer Sample - Rope Swing
The RopeSwingState allows the character to simulate hanging on a rope attached to a point. It does this by handling movement in a very similar manner
to typical air movement, but with the addition of calling RopeSwingState.ConstrainToRope at the end of the update. This function projects the
character velocity to simulate a rope constraint.
This state is transitioned to when the rope input is pressed, and a valid rope attachment point is in range. Rope points are detected with
RopeSwingState.DetectRopePoints.

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();

// Create a hit collector for the detection hits


AllHitsCollector<DistanceHit> hitsCollector = new AllHitsCollector<DistanceHit>(aiController.DetectionDistance, ref distanceHits);

// 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);

// Iterate on all detected hits to try to find a human-controlled character...


Entity selectedTarget = Entity.Null;
for (int i = 0; i < hitsCollector.NumHits; i++)
{
Entity hitEntity = distanceHits[i].Entity;

// 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)
{
// (...)

// Reset air jumps when grounded


characterComponent.CurrentAirJumps = 0;
}
else
{
// Move in air
// (...)
// Air Jumps
if (characterControl.Jump && characterComponent.CurrentAirJumps < characterComponent.MaxAirJumps)
{
CharacterControlUtilities.StandardJump(ref characterBody, characterBody.GroundingUp * characterComponent.JumpSpeed, true, characterBody.GroundingUp);
characterComponent.CurrentAirJumps++;
}
// Gravity
// (...)
}
}

} ```
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.

```cs // (...) using Unity.Physics.Authoring;


[Serializable] public struct ThirdPersonCharacterComponent : IComponentData { // (...)
public CustomPhysicsBodyTags IgnoredPhysicsTags;

// (...)

} ```
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;

// First, see if we'd have to ignore based on the default implementation


if (!PhysicsUtilities.IsCollidable(hit.Material))
{
return false;
}
// if not, check for the ignored tag
if (PhysicsUtilities.HasPhysicsTag(in baseContext.PhysicsWorld, hit.RigidBodyIndex, characterComponent.IgnoredPhysicsTags))
{
return false;
}

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;

// Unground the character


// (without this, the character would snap right back to the ground on the next frame)
characterBody.IsGrounded = false;
// Don't forget to write back to the component
SystemAPI.SetComponent(otherEntity, characterBody);
}
}
}
}

} ```
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.

See Step Handling for a more in-depth explanation

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

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy