//********************************** Banshee Engine (www.banshee3d.com) **************************************************// //**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************// using System; using System.Runtime.InteropServices; namespace BansheeEngine { /** @addtogroup Physics * @{ */ /// /// Special physics controller meant to be used for game characters. Uses the "slide-and-collide" physics instead of /// of the standard physics model to handle various issues with manually moving kinematic objects.Uses a capsule to /// represent the character's bounds. /// public sealed class CharacterController : ManagedComponent { internal NativeCharacterController native; [SerializeField] internal SerializableData serializableData = new SerializableData(); /// /// Triggered when the controller hits a collider. /// public event Action OnColliderHit; /// /// Triggered when the controller hits another character controller. /// public event Action OnControllerHit; /// /// Position of the bottom of the controller. Position takes contact offset into account. Changing this value will /// teleport the character to the location. Use for movement that includes physics. /// public Vector3 FootPosition { get { if (native != null) return native.FootPosition; return Vector3.Zero; } set { if (native != null) { native.FootPosition = value; UpdatePositionFromController(); } } } /// /// Radius of the controller capsule. /// public float Radius { get { return serializableData.radius; } set { serializableData.radius = value; if(native != null) UpdateDimensions(); } } /// /// Height between the centers of the two spheres of the controller capsule. /// public float Height { get { return serializableData.height; } set { serializableData.height = value; if(native != null) UpdateDimensions(); } } /// /// Up direction of capsule. Determines capsule orientation. /// public Vector3 Up { get { return serializableData.up; } set { serializableData.up = value; if (native != null) native.Up = value; } } /// /// Controls what happens when character encounters a height higher than its step offset. /// public CharacterClimbingMode ClimbingMode { get { return serializableData.climbingMode; } set { serializableData.climbingMode = value; if (native != null) native.ClimbingMode = value; } } /// /// Controls what happens when character encounters a slope higher than its slope offset. /// public CharacterNonWalkableMode NonWalkableMode { get { return serializableData.nonWalkableMode; } set { serializableData.nonWalkableMode = value; if (native != null) native.NonWalkableMode = value; } } /// /// Represents minimum distance that the character will move during a call to . This is used to /// stop the recursive motion algorithm when the remaining distance is too small. /// public float MinMoveDistance { get { return serializableData.minMoveDistance; } set { serializableData.minMoveDistance = value; if (native != null) native.MinMoveDistance = value; } } /// /// Contact offset specifies a skin around the object within which contacts will be generated. It should be a small /// positive non-zero value. /// public float ContactOffset { get { return serializableData.contactOffset; } set { serializableData.contactOffset = value; if (native != null) native.ContactOffset = value; } } /// /// Controls which obstacles will the character be able to automatically step over without being stopped. This is /// the height of the maximum obstacle that will be stepped over (with exceptions, ). /// public float StepOffset { get { return serializableData.stepOffset; } set { serializableData.stepOffset = value; if (native != null) native.StepOffset = value; } } /// /// Controls which slopes should the character consider too steep and won't be able to move over. /// for more information. /// public Radian SlopeLimit { get { return serializableData.slopeLimit; } set { serializableData.slopeLimit = value; if (native != null) native.SlopeLimit = value; } } /// /// Determines what can the controller collide with. Objects that are allowed to collide with this object must have /// the same bits set in their own layers. /// public ulong Layer { get { return serializableData.layer; } set { serializableData.layer = value; if (native != null) native.Layer = value; } } /// /// Moves the controller in the specified direction by the specified amount, while interacting with surrounding /// geometry.Returns flags signaling where collision occurred after the movement. /// /// Does not account for gravity, you must apply it manually. /// /// Position to move the controller to, in world space. public CharacterCollisionFlag Move(Vector3 position) { if (native == null) return 0; CharacterCollisionFlag output = native.Move(position); UpdatePositionFromController(); return output; } /// /// Triggered when the controller hits a collider. /// /// Data about the collision. internal void DoOnColliderHit(ControllerColliderCollision data) { if (OnColliderHit != null) OnColliderHit(data); } /// /// Triggered when the controller hits another character controller. /// /// Data about the collision. internal void DoOnControllerHit(ControllerControllerCollision data) { if (OnControllerHit != null) OnControllerHit(data); } private void OnInitialize() { NotifyFlags = TransformChangedFlags.Transform; } private void OnEnable() { RestoreNative(); } private void OnDisable() { DestroyNative(); } private void OnDestroy() { DestroyNative(); } private void OnTransformChanged(TransformChangedFlags flags) { if (!SceneObject.Active || native == null) return; native.Position = SceneObject.Position; } /// /// Updates the position by copying it from the controller to the component's scene object. /// private void UpdatePositionFromController() { NotifyFlags = 0; SceneObject.Position = native.Position; NotifyFlags = TransformChangedFlags.Transform; } /// /// Updates the dimensions of the controller by taking account scale of the parent scene object. /// private void UpdateDimensions() { Vector3 scale = SceneObject.Scale; float height = serializableData.height * MathEx.Abs(scale.y); float radius = serializableData.radius * MathEx.Abs(MathEx.Max(scale.x, scale.z)); native.Height = height; native.Radius = radius; } /// /// Restores the internal character controller representation and initializes it with data stored by the component. /// private void RestoreNative() { ScriptCharacterControllerData initData = new ScriptCharacterControllerData(); initData.position = SceneObject.Position; initData.contactOffset = serializableData.contactOffset; initData.stepOffset = serializableData.stepOffset; initData.slopeLimit = serializableData.slopeLimit; initData.minMoveDistance = serializableData.minMoveDistance; initData.height = serializableData.height; initData.radius = serializableData.radius; initData.up = serializableData.up; initData.climbingMode = serializableData.climbingMode; initData.nonWalkableMode = serializableData.nonWalkableMode; native = new NativeCharacterController(initData); native.Component = this; native.Layer = serializableData.layer; UpdateDimensions(); } /// /// Destroys the internal character controller representation. /// private void DestroyNative() { if (native != null) { native.Destroy(); native = null; } } /// /// Holds all data the character controller component needs to persist through serialization. /// [SerializeObject] internal class SerializableData { public float contactOffset = 0.1f; public float stepOffset = 0.5f; public Radian slopeLimit = new Degree(45.0f); public float minMoveDistance = 0.0f; public float height = 1.0f; public float radius = 0.25f; public Vector3 up = Vector3.YAxis; public CharacterClimbingMode climbingMode = CharacterClimbingMode.Normal; public CharacterNonWalkableMode nonWalkableMode = CharacterNonWalkableMode.Prevent; public ulong layer = 1; } } /// /// Controls climbing behaviour for a capsule character controller. Normally the character controller will not /// automatically climb when heights are greater than the assigned step offset.However due to the shape of the capsule /// it might automatically climb over slightly larger heights than assigned step offsets. /// public enum CharacterClimbingMode { /// /// Normal behaviour. Capsule character controller will be able to auto-step even above the step offset. /// Normal, /// /// The system will attempt to limit auto-step to the provided step offset and no higher. /// Constrained } /// /// Controls behaviour when a character controller reaches a slope thats larger than its slope offset. /// public enum CharacterNonWalkableMode { /// /// Character will be prevented from going further, but will be allowed to move laterally. /// Prevent, /// /// Character will be prevented from going further, but also slide down the slope. /// PreventAndSlide } /// /// Reports in which directions is the character colliding with other objects. /// public enum CharacterCollisionFlag { /// /// Character is colliding with its sides. /// Sides = 0x1, /// /// Character is colliding with the ceiling. /// Up = 0x2, /// /// Character is colliding with the ground. /// Down = 0x4 } /// /// Used for passing CharacterController initialization data between native and managed code. /// [StructLayout(LayoutKind.Sequential)] internal struct ScriptCharacterControllerData // Note: Must match C++ struct CHAR_CONTROLLER_DESC { public Vector3 position; public float contactOffset; public float stepOffset; public Radian slopeLimit; public float minMoveDistance; public float height; public float radius; public Vector3 up; public CharacterClimbingMode climbingMode; public CharacterNonWalkableMode nonWalkableMode; } /// /// Used for passing ControllerCollision data between native and managed code. /// internal struct ScriptControllerCollision // Note: Must match C++ struct ScriptControllerCollision { public Vector3 position; public Vector3 normal; public Vector3 motionDir; public float motionAmount; public NativeCollider collider; public int triangleIndex; public NativeCharacterController controller; } /// /// Contains data about a collision of a character controller and another object. /// public class ControllerCollision { /// /// Contact position. /// public Vector3 position; /// /// Contact normal. /// public Vector3 normal; /// /// Direction of motion after the hit. /// public Vector3 motionDir; /// /// Magnitude of motion after the hit. /// public float motionAmount; }; /// /// Contains data about a collision of a character controller and a collider. /// public class ControllerColliderCollision : ControllerCollision { /// /// Collider that was touched. /// public Collider collider; /// /// Touched triangle index for mesh colliders. /// public int triangleIndex; }; /// /// Contains data about a collision between two character controllers. /// public class ControllerControllerCollision : ControllerCollision { /// /// Controller that was touched. /// public CharacterController controller; }; /** @} */ }