|
|
@@ -1,5 +1,7 @@
|
|
|
//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
|
|
|
//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
|
|
|
+
|
|
|
+using System;
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
namespace BansheeEngine
|
|
|
@@ -11,10 +13,327 @@ namespace BansheeEngine
|
|
|
/// </summary>
|
|
|
public class CharacterController : Component
|
|
|
{
|
|
|
+ internal NativeCharacterController native;
|
|
|
+
|
|
|
[SerializeField]
|
|
|
internal SerializableData serializableData = new SerializableData();
|
|
|
|
|
|
- // TODO
|
|
|
+ /// <summary>
|
|
|
+ /// Triggered when the controller hits a collider.
|
|
|
+ /// </summary>
|
|
|
+ public event Action<ControllerColliderCollision> OnColliderHit;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Triggered when the controller hits another character controller.
|
|
|
+ /// </summary>
|
|
|
+ public event Action<ControllerControllerCollision> OnControllerHit;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Position of the bottom of the controller. Position takes contact offset into account. Changing this value will
|
|
|
+ /// teleport the character to the location. Use <see cref="Move"/> for movement that includes physics.
|
|
|
+ /// </summary>
|
|
|
+ public Vector3 FootPosition
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ if (native != null)
|
|
|
+ return native.FootPosition;
|
|
|
+
|
|
|
+ return Vector3.Zero;
|
|
|
+ }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (native != null)
|
|
|
+ {
|
|
|
+ native.FootPosition = value;
|
|
|
+ UpdatePositionFromController();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Radius of the controller capsule.
|
|
|
+ /// </summary>
|
|
|
+ public float Radius
|
|
|
+ {
|
|
|
+ get { return serializableData.radius; }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ serializableData.radius = value;
|
|
|
+
|
|
|
+ if(native != null)
|
|
|
+ UpdateDimensions();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Height between the centers of the two spheres of the controller capsule.
|
|
|
+ /// </summary>
|
|
|
+ public float Height
|
|
|
+ {
|
|
|
+ get { return serializableData.height; }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ serializableData.height = value;
|
|
|
+
|
|
|
+ if(native != null)
|
|
|
+ UpdateDimensions();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Up direction of capsule. Determines capsule orientation.
|
|
|
+ /// </summary>
|
|
|
+ public Vector3 Up
|
|
|
+ {
|
|
|
+ get { return serializableData.up; }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ serializableData.up = value;
|
|
|
+
|
|
|
+ if (native != null)
|
|
|
+ native.Up = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Controls what happens when character encounters a height higher than its step offset.
|
|
|
+ /// </summary>
|
|
|
+ public CharacterClimbingMode ClimbingMode
|
|
|
+ {
|
|
|
+ get { return serializableData.climbingMode; }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ serializableData.climbingMode = value;
|
|
|
+
|
|
|
+ if (native != null)
|
|
|
+ native.ClimbingMode = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Controls what happens when character encounters a slope higher than its slope offset.
|
|
|
+ /// </summary>
|
|
|
+ public CharacterNonWalkableMode NonWalkableMode
|
|
|
+ {
|
|
|
+ get { return serializableData.nonWalkableMode; }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ serializableData.nonWalkableMode = value;
|
|
|
+
|
|
|
+ if (native != null)
|
|
|
+ native.NonWalkableMode = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Represents minimum distance that the character will move during a call to <see cref="Move"/>. This is used to
|
|
|
+ /// stop the recursive motion algorithm when the remaining distance is too small.
|
|
|
+ /// </summary>
|
|
|
+ public float MinMoveDistance
|
|
|
+ {
|
|
|
+ get { return serializableData.minMoveDistance; }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ serializableData.minMoveDistance = value;
|
|
|
+
|
|
|
+ if (native != null)
|
|
|
+ native.MinMoveDistance = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Contact offset specifies a skin around the object within which contacts will be generated. It should be a small
|
|
|
+ /// positive non-zero value.
|
|
|
+ /// </summary>
|
|
|
+ public float ContactOffset
|
|
|
+ {
|
|
|
+ get { return serializableData.contactOffset; }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ serializableData.contactOffset = value;
|
|
|
+
|
|
|
+ if (native != null)
|
|
|
+ native.ContactOffset = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 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, <see cref="ClimbingMode"/>).
|
|
|
+ /// </summary>
|
|
|
+ public float StepOffset
|
|
|
+ {
|
|
|
+ get { return serializableData.stepOffset; }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ serializableData.stepOffset = value;
|
|
|
+
|
|
|
+ if (native != null)
|
|
|
+ native.StepOffset = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Controls which slopes should the character consider too steep and won't be able to move over.
|
|
|
+ /// <see cref="NonWalkableMode"/> for more information.
|
|
|
+ /// </summary>
|
|
|
+ public Radian SlopeLimit
|
|
|
+ {
|
|
|
+ get { return serializableData.slopeLimit; }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ serializableData.slopeLimit = value;
|
|
|
+
|
|
|
+ if (native != null)
|
|
|
+ native.SlopeLimit = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 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.
|
|
|
+ /// </summary>
|
|
|
+ public ulong Layer
|
|
|
+ {
|
|
|
+ get { return serializableData.layer; }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ serializableData.layer = value;
|
|
|
+
|
|
|
+ if (native != null)
|
|
|
+ native.Layer = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 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.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="position">Position to move the controller to, in world space.</param>
|
|
|
+ public void Move(Vector3 position)
|
|
|
+ {
|
|
|
+ if (native == null)
|
|
|
+ return;
|
|
|
+
|
|
|
+ native.Move(position);
|
|
|
+ UpdatePositionFromController();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Triggered when the controller hits a collider.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="data">Data about the collision.</param>
|
|
|
+ internal void DoOnColliderHit(ControllerColliderCollision data)
|
|
|
+ {
|
|
|
+ if (OnColliderHit != null)
|
|
|
+ OnColliderHit(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Triggered when the controller hits another character controller.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="data">Data about the collision.</param>
|
|
|
+ internal void DoOnControllerHit(ControllerControllerCollision data)
|
|
|
+ {
|
|
|
+ if (OnControllerHit != null)
|
|
|
+ OnControllerHit(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnInitialize()
|
|
|
+ {
|
|
|
+ NotifyFlags = TransformChangedFlags.Transform;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnReset()
|
|
|
+ {
|
|
|
+ RestoreNative();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnEnable()
|
|
|
+ {
|
|
|
+ if (native == null)
|
|
|
+ RestoreNative();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnDisable()
|
|
|
+ {
|
|
|
+ DestroyNative();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnDestroy()
|
|
|
+ {
|
|
|
+ DestroyNative();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnTransformChanged(TransformChangedFlags flags)
|
|
|
+ {
|
|
|
+ if (!SceneObject.Active || native == null)
|
|
|
+ return;
|
|
|
+
|
|
|
+ native.Position = SceneObject.Position;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Updates the position by copying it from the controller to the component's scene object.
|
|
|
+ /// </summary>
|
|
|
+ private void UpdatePositionFromController()
|
|
|
+ {
|
|
|
+ NotifyFlags = 0;
|
|
|
+ SceneObject.Position = native.Position;
|
|
|
+ NotifyFlags = TransformChangedFlags.Transform;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Updates the dimensions of the controller by taking account scale of the parent scene object.
|
|
|
+ /// </summary>
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Restores the internal character controller representation and initializes it with data stored by the component.
|
|
|
+ /// </summary>
|
|
|
+ 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();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Destroys the internal character controller representation.
|
|
|
+ /// </summary>
|
|
|
+ private void DestroyNative()
|
|
|
+ {
|
|
|
+ if (native != null)
|
|
|
+ {
|
|
|
+ native.Destroy();
|
|
|
+ native = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Holds all data the character controller component needs to persist through serialization.
|
|
|
@@ -22,7 +341,6 @@ namespace BansheeEngine
|
|
|
[SerializeObject]
|
|
|
internal class SerializableData
|
|
|
{
|
|
|
- public Vector3 position;
|
|
|
public float contactOffset = 0.1f;
|
|
|
public float stepOffset = 0.5f;
|
|
|
public Radian slopeLimit = new Degree(45.0f);
|
|
|
@@ -32,6 +350,7 @@ namespace BansheeEngine
|
|
|
public Vector3 up = Vector3.YAxis;
|
|
|
public CharacterClimbingMode climbingMode = CharacterClimbingMode.Normal;
|
|
|
public CharacterNonWalkableMode nonWalkableMode = CharacterNonWalkableMode.Prevent;
|
|
|
+ public ulong layer = 1;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -90,7 +409,7 @@ namespace BansheeEngine
|
|
|
/// Used for passing CharacterController initialization data between native and managed code.
|
|
|
/// </summary>
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
|
- internal struct ScriptCharacterControllerData
|
|
|
+ internal struct ScriptCharacterControllerData // Note: Must match C++ struct CHAR_CONTROLLER_DESC
|
|
|
{
|
|
|
public Vector3 position;
|
|
|
public float contactOffset;
|
|
|
@@ -113,10 +432,9 @@ namespace BansheeEngine
|
|
|
public Vector3 normal;
|
|
|
public Vector3 motionDir;
|
|
|
public float motionAmount;
|
|
|
- public Collider collider;
|
|
|
+ public NativeCollider collider;
|
|
|
public int triangleIndex;
|
|
|
- public CharacterController controller;
|
|
|
- public bool isControllerCollision;
|
|
|
+ public NativeCharacterController controller;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|