1.控制器CorgiController
CorgiControllerState
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.CorgiEngine
{
/// <summary>
/// 您可以使用各种状态来检查角色在当前帧中是否正在做某事
/// </summary>
public class CorgiControllerState
{
/// 角色碰撞右
public bool IsCollidingRight { get; set; }
/// 角色碰撞左
public bool IsCollidingLeft { get; set; }
/// 这个角色是否与上面的东西发生了碰撞?
public bool IsCollidingAbove { get; set; }
/// 这个角色是否与下面的东西发生了碰撞?
public bool IsCollidingBelow { get; set; }
/// 这个角色和什么东西有冲突吗?
public bool HasCollisions { get { return IsCollidingRight || IsCollidingLeft || IsCollidingAbove || IsCollidingBelow; } }
/// 返回到左侧碰撞器的距离,如果不向左碰撞,则等于-1
public float DistanceToLeftCollider;
/// 返回到右侧碰撞器的距离,如果不是右侧碰撞,则等于-1
public float DistanceToRightCollider;
/// 返回水平方向上遇到的坡度角
public float LateralSlopeAngle { get; set; }
/// 返回角色在角度上移动的斜率
public float BelowSlopeAngle { get; set; }
/// 返回角色相对于Vector2.Up在角度上移动的斜率,从0到360
public float BelowSlopeAngleAbsolute { get; set; }
/// 如果坡度角适合行走,则返回true
public bool SlopeAngleOK { get; set; }
/// 如果角色站在移动的平台上,则返回true
public bool OnAMovingPlatform { get; set; }
/// 这个角色是脚踏实地的吗?
public bool IsGrounded { get { return IsCollidingBelow; } }
/// 这个角色现在倒下了吗?
public bool IsFalling { get; set; }
/// 这个角色现在跳跃了吗?
public bool IsJumping { get; set; }
public bool WasGroundedLastFrame { get; set; }
public bool WasTouchingTheCeilingLastFrame { get; set; }
public bool JustGotGrounded { get; set; }
public bool ColliderResized { get; set; }
/// 角色是否触及水平边界?
public bool TouchingLevelBounds { get; set; }
public virtual void Reset()
{
IsCollidingLeft = false;
IsCollidingRight = false;
IsCollidingAbove = false;
DistanceToLeftCollider = -1;
DistanceToRightCollider = -1;
SlopeAngleOK = false;
JustGotGrounded = false;
IsFalling = true;
LateralSlopeAngle = 0;
}
public override string ToString()
{
Debug.Log("CorgiControllerState-----ToString");
return string.Format("(controller: collidingRight:{0} collidingLeft:{1} collidingAbove:{2} collidingBelow:{3} lateralSlopeAngle:{4} belowSlopeAngle:{5} isGrounded: {6}",
IsCollidingRight,
IsCollidingLeft,
IsCollidingAbove,
IsCollidingBelow,
LateralSlopeAngle,
BelowSlopeAngle,
IsGrounded);
}
}
}
CorgiController
using MoreMountains.Tools;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
namespace MoreMountains.CorgiEngine
{
/// <summary>
/// 处理角色重力和碰撞的角色控制器。
///它需要一个Collider 2D和一个刚体才能运行。
/// </summary>
[RequireComponent(typeof(BoxCollider2D))]
[AddComponentMenu("Corgi Engine/Character/Core/Corgi Controller")]
public class CorgiController : MonoBehaviour
{
/// <summary>
/// Update
/// </summary>时调用,考虑到使用设备的硬件差别(不同性能机器上运行相同游戏程序时帧率是有差异的),所以按帧率调用可能会造成理想与现实的差距,我们应该尽量避免帧率对游戏操作本身的影响。
///FixedUpdate
/// <summary>
/// 是固定时间调用,比如默认是0.02s调用一次,我们在处理物理逻辑(比如给物体施加力让物体移动),我们能在不受帧率的影响下达到我们的预期。
/// </summary>
/// the possible modes this controller can update on
/// 这里让你自由选择,你每一帧的处理放在 Update里面还是FixedUpdate
public enum UpdateModes { Update, FixedUpdate }
/// 我们角色的各种状态
public CorgiControllerState State { get; protected set; }
[Header("默认参数")]
[Tooltip("初始参数")]
public CorgiControllerParameters DefaultParameters;
/// 当前参数
///A ?? B表示如果A为null则返回B,否则返回A
public CorgiControllerParameters Parameters
{
get
{
Debug.Log("CorgiController-----CorgiControllerParameters");
return _overrideParameters ?? DefaultParameters;
}
}
[Header("碰撞")]
[MMInformation("您需要定义该角色将考虑哪些层作为可步行平台/移动平台等。默认情况下,您希望平台、移动平台、单向平台、移动单向平台按此顺序排列。", MoreMountains.Tools.MMInformationAttribute.InformationType.Info, false)]
/// & 逻辑与运算 比如 2 & 1 = 0 , (10 & 01 = 00)
/// | 逻辑或运算 比如 2 | 1 = 3 , (10 | 01 = 11)
[Tooltip("平台所在的层掩码")]
public LayerMask PlatformMask = LayerManager.PlatformsLayerMask | LayerManager.PushablesLayerMask;
[Tooltip("移动平台所在的图层掩码")]
public LayerMask MovingPlatformMask = LayerManager.MovingPlatformsLayerMask;
[Tooltip("该层屏蔽了单向平台")]
public LayerMask OneWayPlatformMask = LayerManager.OneWayPlatformsLayerMask;
[Tooltip("该层遮挡了移动的单向平台")]
public LayerMask MovingOneWayPlatformMask = LayerManager.MovingOneWayPlatformsMask;
[Tooltip("该层掩盖了中等高度的单向平台")]
public LayerMask MidHeightOneWayPlatformMask = LayerManager.MidHeightOneWayPlatformsLayerMask;
[Tooltip("楼梯所在的图层遮罩")]
public LayerMask StairsMask = LayerManager.StairsLayerMask;
/// 光线可以投射的可能方向
public enum RaycastDirections { up, down, left, right };
/// 角色与单向或移动平台分离的可能方式
public enum DetachmentMethods { Layer, Object }
[Header("Safe Mode")]
[MMInformation("If you set SafeSetTransform to true, abilities that modify directly the character's position, like CharacterGrip, will perform a check to make sure there's enough room to do so." +
"This isn't enabled by default because this comes at a (small) performance cost and can usually be avoided by having a safe level design.", MoreMountains.Tools.MMInformationAttribute.InformationType.Info, false)]
/// whether or not to perform additional checks when setting the transform's position. Slightly more expensive in terms of performance, but also safer.
[Tooltip("whether or not to perform additional checks when setting the transform's position. Slightly more expensive in terms of performance, but also safer. ")]
public bool SafeSetTransform = false;
/// if this is true, this controller will set a number of physics settings automatically on init, to ensure they're correct
[Tooltip("if this is true, this controller will set a number of physics settings automatically on init, to ensure they're correct")]
public bool AutomaticallySetPhysicsSettings = false;
/// gives you the object the character is standing on
[Tooltip("gives you the object the character is standing on")]
[MMReadOnly]
public GameObject StandingOn;
/// the object the character was standing on last frame
public GameObject StandingOnLastFrame { get; protected set; }
/// gives you the collider the character is standing on
public Collider2D StandingOnCollider { get; protected set; }
/// the current speed of the character
public Vector2 Speed { get { return _speed; } }
/// the world speed of the character
public Vector2 WorldSpeed { get { return _worldSpeed; } }
/// the value of the forces applied at one point in time
public Vector2 ForcesApplied { get; protected set; }
/// the wall we're currently colliding with
public GameObject CurrentWallCollider { get; protected set; }
[Header("Raycasting")]
[MMInformation(
"Here you can define how many rays are cast horizontally and vertically. You'll want them as far as possible from each other, but close enough that no obstacle or enemy can fit between 2 rays.",
MoreMountains.Tools.MMInformationAttribute.InformationType.Info, false)]
/// whether this controller should run on Update or FixedUpdate
[Tooltip("whether this controller should run on Update or FixedUpdate")]
public UpdateModes UpdateMode = UpdateModes.Update;
/// the number of rays cast horizontally
[Tooltip("the number of rays cast horizontally")]
public int NumberOfHorizontalRays = 8;
/// the number of rays cast vertically
[Tooltip("the number of rays cast vertically")]
public int NumberOfVerticalRays = 8;
/// a small value added to all raycasts to accomodate for edge cases
[FormerlySerializedAs("RayOffset")]
[Tooltip("a small value added to all horizontal raycasts to accomodate for edge cases")]
public float RayOffsetHorizontal = 0.05f;
/// a small value added to all raycasts to accomodate for edge cases
[Tooltip("a small value added to all vertical raycasts to accomodate for edge cases")]
public float RayOffsetVertical = 0.05f;
/// by default, the length of the raycasts used to get back to normal size will be auto generated based on your character's normal/standing height, but here you can specify a different value
[Tooltip("by default, the length of the raycasts used to get back to normal size will be auto generated based on your character's normal/standing height, but here you can specify a different value")]
public float CrouchedRaycastLengthMultiplier = 1f;
/// if this is true, rays will be cast on both sides, otherwise only in the current movement's direction.
[Tooltip("if this is true, rays will be cast on both sides, otherwise only in the current movement's direction.")]
public bool CastRaysOnBothSides = false;
/// the maximum length of the ray used to detect the distance to the ground
[Tooltip("the maximum length of the ray used to detect the distance to the ground")]
public float DistanceToTheGroundRayMaximumLength = 100f;
/// if this is true, an extra boxcast will be performed to prevent going through a platform
[Tooltip("if this is true, an extra boxcast will be performed to prevent going through a platform")]
public bool PerformSafetyBoxcast = false;
/// a multiplier to apply to vertical downward raycasts while on a moving platform (longer == more stable movement on platforms)
[Tooltip("a multiplier to apply to vertical downward raycasts while on a moving platform (longer == more stable movement on platforms)")]
public float OnMovingPlatformRaycastLengthMultiplier = 2f;
/// a multiplier to apply to vertical downward raycasts while on a moving platform (longer == more stable movement on platforms)
[Tooltip("a multiplier to apply to vertical downward raycasts while on a moving platform (longer == more stable movement on platforms)")]
public float ObstacleHeightTolerance = 0.05f;
[Header("Stickiness")]
[MMInformation("Here you can define whether or not you want your character stick to slopes when walking down them, and how long the raycast handling that should be (0 means automatic length).", MoreMountains.Tools.MMInformationAttribute.InformationType.Info, false)]
/// If this is set to true, the character will stick to slopes when walking down them
[Tooltip("If this is set to true, the character will stick to slopes when walking down them")]
public bool StickToSlopes = false;
/// The length of the raycasts used to stick to downward slopes
[Tooltip("The length of the raycasts used to stick to downward slopes")]
public float StickyRaycastLength = 0f;
/// the movement's Y offset to evaluate for stickiness.
[Tooltip("the movement's Y offset to evaluate for stickiness. ")]
public float StickToSlopesOffsetY = 0.2f;
/// the time (in seconds) since the last time the character was grounded
[Tooltip("the time (in seconds) since the last time the character was grounded")]
[MMReadOnly]
public float TimeAirborne = 0f;
[Header("Safety")]
[MMInformation("Here you can authorize your controller to start rotated. This will change its gravity direction. It's safer to leave this safety on and use a CharacterGravity ability instead.", MoreMountains.Tools.MMInformationAttribute.InformationType.Info, false)]
/// if this is true, gravity ability settings will be automatically set. It's recommended to set that to true.
[Tooltip("if this is true, gravity ability settings will be automatically set. It's recommended to set that to true.")]
public bool AutomaticGravitySettings = true;
public Vector3 ColliderSize
{
get
{
Debug.Log("CorgiController-----ColliderSize");
return Vector3.Scale(transform.localScale, _boxCollider.size);
}
}
public Vector3 ColliderCenterPosition
{
get
{
Debug.Log("CorgiController-----ColliderCenterPosition");
return _boxCollider.bounds.center;
}
}
public virtual Vector3 ColliderBottomPosition
{
get
{
Debug.Log("CorgiController-----ColliderBottomPosition");
_colliderBottomCenterPosition.x = _boxCollider.bounds.center.x;
_colliderBottomCenterPosition.y = _boxCollider.bounds.min.y;
_colliderBottomCenterPosition.z = 0;
return _colliderBottomCenterPosition;
}
}
public bool IsGravityActive
{
get
{
Debug.Log("CorgiController-----IsGravityActive");
return _gravityActive;
}
}
public virtual float DeltaTime
{
get
{
Debug.Log("CorgiController-----DeltaTime");
return _update ? Time.deltaTime : Time.fixedDeltaTime;
}
}
/// <summary>
/// 摩擦
/// </summary>
public float Friction
{
get
{
Debug.Log("CorgiController-----Friction");
return _friction;
}
}
/// <summary>
/// Returns the character's bounds width
/// </summary>
public virtual float Width()
{
Debug.Log("CorgiController-----Width");
return _boundsWidth;
}
/// <summary>
/// Returns the character's bounds height
/// </summary>
public virtual float Height()
{
Debug.Log("CorgiController-----Height");
return _boundsHeight;
}
public virtual Vector2 Bounds
{
get
{
Debug.Log("CorgiController-----Bounds");
_bounds.x = _boundsWidth;
_bounds.y = _boundsHeight;
return _bounds;
}
}
public virtual Vector3 BoundsTopLeftCorner
{
get
{
Debug.Log("CorgiController-----BoundsTopLeftCorner");
return _boundsTopLeftCorner;
}
}
public virtual Vector3 BoundsBottomLeftCorner
{
get
{
Debug.Log("CorgiController-----BoundsBottomLeftCorner");
return _boundsBottomLeftCorner;
}
}
public virtual Vector3 BoundsTopRightCorner
{
get
{
Debug.Log("CorgiController-----BoundsTopRightCorner");
return _boundsTopRightCorner;
}
}
public virtual Vector3 BoundsBottomRightCorner
{
get
{
Debug.Log("CorgiController-----BoundsBottomRightCorner");
return _boundsBottomRightCorner;
}
}
public virtual Vector3 BoundsTop
{
get
{
Debug.Log("CorgiController-----BoundsTop");
return (_boundsTopLeftCorner + _boundsTopRightCorner) / 2;
}
}
public virtual Vector3 BoundsBottom
{
get
{
Debug.Log("CorgiController-----BoundsBottom");
return (_boundsBottomLeftCorner + _boundsBottomRightCorner) / 2;
}
}
public virtual Vector3 BoundsRight
{
get
{
Debug.Log("CorgiController-----BoundsRight");
return (_boundsTopRightCorner + _boundsBottomRightCorner) / 2;
}
}
public virtual Vector3 BoundsLeft
{
get
{
Debug.Log("CorgiController-----BoundsLeft");
return (_boundsTopLeftCorner + _boundsBottomLeftCorner) / 2;
}
}
public virtual Vector3 BoundsCenter
{
get
{
Debug.Log("CorgiController-----BoundsCenter");
return _boundsCenter;
}
}
public virtual float DistanceToTheGround
{
get
{
Debug.Log("CorgiController-----DistanceToTheGround");
return _distanceToTheGround;
}
}
public virtual Vector2 ExternalForce
{
get
{
Debug.Log("CorgiController-----ExternalForce");
return _externalForce;
}
}
// parameters override storage
protected CorgiControllerParameters _overrideParameters;
// private local references
protected Vector2 _speed;
protected float _friction = 0;
protected float _fallSlowFactor;
protected float _currentGravity = 0;
protected Vector2 _externalForce;
protected Vector2 _newPosition;
protected Transform _transform;
protected BoxCollider2D _boxCollider;
protected CharacterGravity _characterGravity;
protected LayerMask _platformMaskSave;
protected LayerMask _raysBelowLayerMaskPlatforms;
protected LayerMask _raysBelowLayerMaskPlatformsWithoutOneWay;
protected LayerMask _raysBelowLayerMaskPlatformsWithoutMidHeight;
protected int _savedBelowLayer;
protected MMPathMovement _movingPlatform = null;
protected float _movingPlatformCurrentGravity;
protected bool _gravityActive = true;
protected Collider2D _ignoredCollider = null;
protected bool _collisionsOnWithStairs = false;
protected const float _smallValue = 0.0001f;
protected const float _movingPlatformsGravity = -500;
protected Vector2 _originalColliderSize;
protected Vector2 _originalColliderOffset;
protected Vector2 _originalSizeRaycastOrigin;
protected Vector3 _crossBelowSlopeAngle;
protected RaycastHit2D[] _sideHitsStorage;
protected RaycastHit2D[] _belowHitsStorage;
protected RaycastHit2D[] _aboveHitsStorage;
protected RaycastHit2D _stickRaycastLeft;
protected RaycastHit2D _stickRaycastRight;
protected RaycastHit2D _stickRaycast;
protected RaycastHit2D _distanceToTheGroundRaycast;
protected float _movementDirection;
protected float _storedMovementDirection = 1;
protected const float _movementDirectionThreshold = 0.0001f;
protected Vector2 _horizontalRayCastFromBottom = Vector2.zero;
protected Vector2 _horizontalRayCastToTop = Vector2.zero;
protected Vector2 _verticalRayCastFromLeft = Vector2.zero;
protected Vector2 _verticalRayCastToRight = Vector2.zero;
protected Vector2 _aboveRayCastStart = Vector2.zero;
protected Vector2 _aboveRayCastEnd = Vector2.zero;
protected Vector2 _rayCastOrigin = Vector2.zero;
protected Vector3 _colliderBottomCenterPosition;
protected Vector3 _colliderLeftCenterPosition;
protected Vector3 _colliderRightCenterPosition;
protected Vector3 _colliderTopCenterPosition;
protected MMPathMovement _movingPlatformTest;
protected SurfaceModifier _frictionTest;
protected bool _update;
protected RaycastHit2D[] _raycastNonAlloc = new RaycastHit2D[0];
protected Vector2 _boundsTopLeftCorner;
protected Vector2 _boundsBottomLeftCorner;
protected Vector2 _boundsTopRightCorner;
protected Vector2 _boundsBottomRightCorner;
protected Vector2 _boundsCenter;
protected Vector2 _bounds;
protected float _boundsWidth;
protected float _boundsHeight;
protected float _distanceToTheGround;
protected Vector2 _worldSpeed;
protected List<RaycastHit2D> _contactList;
protected bool _shouldComputeNewSpeed = false;
/// <summary>
/// initialization
/// </summary>
protected virtual void Awake()
{
Initialization();
}
protected virtual void Initialization()
{
// we get the various components
_transform = transform;
_boxCollider = this.gameObject.GetComponent<BoxCollider2D>();
_originalColliderSize = _boxCollider.size;
_originalColliderOffset = _boxCollider.offset;
// we test the boxcollider's x offset. If it's not null we trigger a warning.
if ((_boxCollider.offset.x != 0) && (Parameters.DisplayWarnings))
{
Debug.LogWarning("The boxcollider for " + gameObject.name + " should have an x offset set to zero. Right now this may cause issues when you change direction close to a wall.");
}
// raycast list and state init
_contactList = new List<RaycastHit2D>();
State = new CorgiControllerState();
// we add the edge collider platform and moving platform masks to our initial platform mask so they can be walked on
_platformMaskSave = PlatformMask;
PlatformMask |= OneWayPlatformMask;
PlatformMask |= MovingPlatformMask;
PlatformMask |= MovingOneWayPlatformMask;
PlatformMask |= MidHeightOneWayPlatformMask;
_sideHitsStorage = new RaycastHit2D[NumberOfHorizontalRays];
_belowHitsStorage = new RaycastHit2D[NumberOfVerticalRays];
_aboveHitsStorage = new RaycastHit2D[NumberOfVerticalRays];
_update = (UpdateMode == UpdateModes.Update);
CurrentWallCollider = null;
State.Reset();
SetRaysParameters();
ApplyGravitySettings();
ApplyPhysicsSettings();
}
/// <summary>
/// Forces a number of physics settings, if needed
/// </summary>
protected virtual void ApplyPhysicsSettings()
{
if (AutomaticallySetPhysicsSettings)
{
Debug.Log("CorgiController-----ApplyPhysicsSettings");
}
}
/// <summary>
/// Forces rotation if we don't have a gravity ability
/// </summary>
protected virtual void ApplyGravitySettings()
{
if (AutomaticGravitySettings)
{
Debug.Log("CorgiController-----ApplyGravitySettings---1");
}
}
/// <summary>
/// Use this to add force to the character
/// </summary>
/// <param name="force">Force to add to the character.</param>
public virtual void AddForce(Vector2 force)
{
Debug.Log("CorgiController-----AddForce");
}
/// <summary>
/// use this to set the horizontal force applied to the character
/// </summary>
/// <param name="x">The x value of the velocity.</param>
public virtual void AddHorizontalForce(float x)
{
Debug.Log("CorgiController-----AddHorizontalForce");
}
/// <summary>
/// use this to set the vertical force applied to the character
/// </summary>
/// <param name="y">The y value of the velocity.</param>
public virtual void AddVerticalForce(float y)
{
Debug.Log("CorgiController-----AddVerticalForce");
}
/// <summary>
/// Use this to set the force applied to the character
/// </summary>
/// <param name="force">Force to apply to the character.</param>
public virtual void SetForce(Vector2 force)
{
Debug.Log("CorgiController-----SetForce");
}
/// <summary>
/// use this to set the horizontal force applied to the character
/// </summary>
/// <param name="x">The x value of the velocity.</param>
public virtual void SetHorizontalForce(float x)
{
Debug.Log("CorgiController-----SetHorizontalForce");
}
/// <summary>
/// use this to set the vertical force applied to the character
/// </summary>
/// <param name="y">The y value of the velocity.</param>
public virtual void SetVerticalForce(float y)
{
Debug.Log("CorgiController-----SetVerticalForce");
}
/// <summary>
/// On FixedUpdate we run our routine if needed
/// </summary>
protected virtual void FixedUpdate()
{
if (!_update)
{
EveryFrame();
}
}
/// <summary>
/// On Update we run our routine if needed
/// </summary>
protected virtual void Update()
{
if (_update)
{
EveryFrame();
}
}
/// <summary>
/// On late update we reset our airborne time
/// </summary>
protected virtual void LateUpdate()
{
Debug.Log("CorgiController-----LateUpdate");
}
/// <summary>
/// Every frame, we apply the gravity to our character, then check using raycasts if an object's been hit, and modify its new position
/// accordingly. When all the checks have been done, we apply that new position.
/// </summary>
protected virtual void EveryFrame()
{
Debug.Log("CorgiController-----EveryFrame");
}
protected virtual void FrameInitialization()
{
Debug.Log("CorgiController-----FrameInitialization");
}
/// <summary>
/// Called at the very last moment
/// </summary>
protected virtual void FrameExit()
{
Debug.Log("CorgiController-----FrameExit");
}
/// <summary>
/// Determines the current movement direction
/// </summary>
protected virtual void DetermineMovementDirection()
{
Debug.Log("CorgiController-----DetermineMovementDirection");
}
/// <summary>
/// Moves the transform to its new position, after having performed an optional safety boxcast
/// </summary>
protected virtual void MoveTransform()
{
Debug.Log("CorgiController-----MoveTransform");
}
/// <summary>
/// Applies gravity to the current speed
/// </summary>
protected virtual void ApplyGravity()
{
Debug.Log("CorgiController-----ApplyGravity");
}
/// <summary>
/// If the CorgiController is standing on a moving platform, we match its speed
/// </summary>
protected virtual void HandleMovingPlatforms()
{
Debug.Log("CorgiController-----HandleMovingPlatforms");
}
/// <summary>
/// Disconnects the CorgiController from its current moving platform.
/// </summary>
public virtual void DetachFromMovingPlatform()
{
Debug.Log("CorgiController-----DetachFromMovingPlatform");
}
/// <summary>
/// A public API to cast rays to any of the 4 cardinal directions using the built-in setup.
/// You can specify the length and color of your rays, and pass a storageArray as a ref parameter, and then analyse its contents to do whatever you want.
/// Note that in most situations (other than collision detection) this may be a bit overkill and maybe you should consider casting a single ray instead. It's up to you!
/// Will return true if any of the rays hit something, false otherwise
/// </summary>
/// <returns><c>true</c>, if one of the rays hit something, <c>false</c> otherwise.</returns>
/// <param name="direction">Direction.</param>
/// <param name="rayLength">Ray length.</param>
/// <param name="color">Color.</param>
/// <param name="storageArray">Storage array.</param>
public virtual bool CastRays(RaycastDirections direction, float rayLength, Color color, ref RaycastHit2D[] storageArray)
{
Debug.Log("CorgiController-----CastRays");
return false;
}
protected virtual void CastRaysToTheLeft()
{
Debug.Log("CorgiController-----CastRaysToTheLeft");
}
protected virtual void CastRaysToTheRight()
{
Debug.Log("CorgiController-----CastRaysToTheRight");
}
/// <summary>
/// Casts rays to the sides of the character, from its center axis.
/// If we hit a wall/slope, we check its angle and move or not according to it.
/// </summary>
protected virtual void CastRaysToTheSides(float raysDirection)
{
Debug.Log("CorgiController-----CastRaysToTheSides");
}
/// <summary>
/// Every frame, we cast a number of rays below our character to check for platform collisions
/// </summary>
protected virtual void CastRaysBelow()
{
Debug.Log("CorgiController-----CastRaysBelow");
}
/// <summary>
/// If we're in the air and moving up, we cast rays above the character's head to check for collisions
/// </summary>
protected virtual void CastRaysAbove()
{
Debug.Log("CorgiController-----CastRaysAbove");
}
/// <summary>
/// If we're going down a slope, sticks the character to the slope to avoid bounces
/// </summary>
protected virtual void StickToSlope()
{
Debug.Log("CorgiController-----StickToSlope");
}
/// <summary>
/// Computes the new speed of the character
/// </summary>
protected virtual void ComputeNewSpeed()
{
Debug.Log("CorgiController-----ComputeNewSpeed");
}
protected virtual void ClampSpeed()
{
Debug.Log("CorgiController-----ClampSpeed");
}
protected virtual void ClampExternalForce()
{
Debug.Log("CorgiController-----ClampExternalForce");
}
/// <summary>
/// Sets grounded state and collision states
/// </summary>
protected virtual void SetStates()
{
Debug.Log("CorgiController-----SetStates");
}
/// <summary>
/// Computes the distance to the ground
/// </summary>
protected virtual void ComputeDistanceToTheGround()
{
Debug.Log("CorgiController-----ComputeDistanceToTheGround");
}
/// <summary>
/// Creates a rectangle with the boxcollider's size for ease of use and draws debug lines along the different raycast origin axis
/// </summary>
public virtual void SetRaysParameters()
{
float top = _boxCollider.offset.y + (_boxCollider.size.y / 2f);
float bottom = _boxCollider.offset.y - (_boxCollider.size.y / 2f);
float left = _boxCollider.offset.x - (_boxCollider.size.x / 2f);
float right = _boxCollider.offset.x + (_boxCollider.size.x / 2f);
_boundsTopLeftCorner.x = left;
_boundsTopLeftCorner.y = top;
_boundsTopRightCorner.x = right;
_boundsTopRightCorner.y = top;
_boundsBottomLeftCorner.x = left;
_boundsBottomLeftCorner.y = bottom;
_boundsBottomRightCorner.x = right;
_boundsBottomRightCorner.y = bottom;
_boundsTopLeftCorner = transform.TransformPoint(_boundsTopLeftCorner);
_boundsTopRightCorner = transform.TransformPoint(_boundsTopRightCorner);
_boundsBottomLeftCorner = transform.TransformPoint(_boundsBottomLeftCorner);
_boundsBottomRightCorner = transform.TransformPoint(_boundsBottomRightCorner);
_boundsCenter = _boxCollider.bounds.center;
_boundsWidth = Vector2.Distance(_boundsBottomLeftCorner, _boundsBottomRightCorner);
_boundsHeight = Vector2.Distance(_boundsBottomLeftCorner, _boundsTopLeftCorner);
}
public virtual void SetIgnoreCollider(Collider2D newIgnoredCollider)
{
Debug.Log("CorgiController-----SetIgnoreCollider");
}
/// <summary>
/// Disables the collisions for the specified duration
/// </summary>
/// <param name="duration">the duration for which the collisions must be disabled</param>
public virtual IEnumerator DisableCollisions(float duration)
{
Debug.Log("CorgiController-----DisableCollisions");
return null;
}
/// <summary>
/// Resets the collision mask with the default settings
/// </summary>
public virtual void CollisionsOn()
{
Debug.Log("CorgiController-----CollisionsOn");
}
/// <summary>
/// Turns all collisions off
/// </summary>
public virtual void CollisionsOff()
{
Debug.Log("CorgiController-----CollisionsOff");
}
/// <summary>
/// Disables the collisions with one way platforms for the specified duration
/// </summary>
/// <param name="duration">the duration for which the collisions must be disabled</param>
public virtual IEnumerator DisableCollisionsWithOneWayPlatforms(float duration)
{
Debug.Log("CorgiController-----DisableCollisionsWithOneWayPlatforms");
return null;
}
/// <summary>
/// Disables the collisions with moving platforms for the specified duration
/// </summary>
/// <param name="duration">the duration for which the collisions must be disabled</param>
public virtual IEnumerator DisableCollisionsWithMovingPlatforms(float duration)
{
Debug.Log("CorgiController-----DisableCollisionsWithMovingPlatforms");
return null;
}
/// <summary>
/// Disables collisions only with the one way platform layers
/// </summary>
public virtual void CollisionsOffWithOneWayPlatformsLayer()
{
Debug.Log("CorgiController-----CollisionsOffWithOneWayPlatformsLayer");
}
/// <summary>
/// Disables collisions only with moving platform layers
/// </summary>
public virtual void CollisionsOffWithMovingPlatformsLayer()
{
Debug.Log("CorgiController-----CollisionsOffWithMovingPlatformsLayer");
}
/// <summary>
/// Enables collisions with the stairs layer
/// </summary>
public virtual void CollisionsOnWithStairs()
{
Debug.Log("CorgiController-----CollisionsOnWithStairs");
}
/// <summary>
/// Disables collisions with the stairs layer
/// </summary>
public virtual void CollisionsOffWithStairs()
{
Debug.Log("CorgiController-----CollisionsOffWithStairs");
}
/// <summary>
/// Resets all overridden parameters.
/// </summary>
public virtual void ResetParameters()
{
Debug.Log("CorgiController-----ResetParameters");
}
/// <summary>
/// Slows the character's fall by the specified factor.
/// </summary>
/// <param name="factor">Factor.</param>
public virtual void SlowFall(float factor)
{
Debug.Log("CorgiController-----SlowFall");
}
/// <summary>
/// Activates or desactivates the gravity for this character only.
/// </summary>
/// <param name="state">If set to <c>true</c>, activates the gravity. If set to <c>false</c>, turns it off.</param>
public virtual void GravityActive(bool state)
{
Debug.Log("CorgiController-----GravityActive");
}
/// <summary>
/// Resizes the collider to the new size set in parameters
/// </summary>
/// <param name="newSize">New size.</param>
public virtual void ResizeCollider(Vector2 newSize)
{
Debug.Log("CorgiController-----ResizeCollider");
}
/// <summary>
/// Returns the collider to its initial size
/// </summary>
public virtual void ResetColliderSize()
{
Debug.Log("CorgiController-----ResetColliderSize");
}
/// <summary>
/// Determines whether this instance can go back to original size.
/// </summary>
/// <returns><c>true</c> if this instance can go back to original size; otherwise, <c>false</c>.</returns>
public virtual bool CanGoBackToOriginalSize()
{
Debug.Log("CorgiController-----CanGoBackToOriginalSize");
return false;
}
/// <summary>
/// Moves the controller's transform to the desired position
/// </summary>
/// <param name="position"></param>
public virtual void SetTransformPosition(Vector3 position)
{
Debug.Log("CorgiController-----SetTransformPosition");
}
/// <summary>
/// Returns the closest "safe" point (not overlapping any platform) to the destination
/// </summary>
/// <param name="destination"></param>
/// <returns></returns>
public virtual Vector2 GetClosestSafePosition(Vector2 destination)
{
Debug.Log("CorgiController-----GetClosestSafePosition");
return Vector2.zero;
}
/// <summary>
/// Teleports the character to the ground
/// </summary>
public virtual void AnchorToGround()
{
Debug.Log("CorgiController-----AnchorToGround");
}
/// <summary>
/// triggered when the character's raycasts collide with something
/// </summary>
protected virtual void OnCorgiColliderHit()
{
Debug.Log("CorgiController-----OnCorgiColliderHit");
}
/// <summary>
/// triggered when the character enters a collider
/// </summary>
/// <param name="collider">the object we're colliding with.</param>
protected virtual void OnTriggerEnter2D(Collider2D collider)
{
Debug.Log("CorgiController-----OnTriggerEnter2D");
}
/// <summary>
/// triggered while the character stays inside another collider
/// </summary>
/// <param name="collider">the object we're colliding with.</param>
protected virtual void OnTriggerStay2D(Collider2D collider)
{
Debug.Log("CorgiController-----OnTriggerStay2D");
}
/// <summary>
/// triggered when the character exits a collider
/// </summary>
/// <param name="collider">the object we're colliding with.</param>
protected virtual void OnTriggerExit2D(Collider2D collider)
{
Debug.Log("CorgiController-----OnTriggerExit2D");
}
}
}
CorgiControllerParameters
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MoreMountains.Tools;
namespace MoreMountains.CorgiEngine
{
/// <summary>
/// Corgi控制器类的参数。
///这是定义坡度限制、重力和速度阻尼系数的地方
/// </summary>
[RequireComponent(typeof(Collider2D))]
[Serializable]
public class CorgiControllerParameters
{
[Header("重力")]
[Tooltip("始终垂直施加的力")]
public float Gravity = -30f;
[Tooltip("下降时应用于角色重力的倍数")]
public float FallMultiplier = 1f;
[Tooltip("上升时应用于角色重力的倍数")]
public float AscentMultiplier = 1f;
[Header("速度")]
[Tooltip("角色的最大速度,例如防止其在斜坡上移动过快")]
public Vector2 MaxVelocity = new Vector2(100f, 100f);
[Tooltip("地面速度系数")]
public float SpeedAccelerationOnGround = 20f;
[Tooltip("如果这是真的,在地面减速时将使用单独的减速值。如果为false,将使用地面速度加速")]
public bool UseSeparateDecelerationOnGround = false;
[Tooltip("速度修改器,在不应用输入时应用,以阻止角色在地面上移动")]
[MMCondition("UseSeparateDecelerationOnGround", true)]
public float SpeedDecelerationOnGround = 20f;
[Tooltip("空气中的速度系数")]
public float SpeedAccelerationInAir = 5f;
[Tooltip("如果这是真的,在空中减速时将使用单独的减速值。如果为false,将使用SpeedAccelerationInAir")]
public bool UseSeparateDecelerationInAir = false;
[Tooltip("速度调节器,在不应用输入时应用,以阻止角色在空中移动")]
[MMCondition("UseSeparateDecelerationInAir", true)]
public float SpeedDecelerationInAir = 5f;
[Tooltip("一般速度系数")]
public float SpeedFactor = 1f;
[Header("斜坡")]
[Tooltip("角色可以行走的最大角度(度)")]
[Range(0, 90)]
public float MaximumSlopeAngle = 30f;
[Tooltip("在斜坡上行走时应用的速度倍增器")]
public AnimationCurve SlopeAngleSpeedFactor = new AnimationCurve(new Keyframe(-90f, 1f), new Keyframe(0f, 1f), new Keyframe(90f, 1f));
[Header("物理2D相互作用[实验]")]
[Tooltip("如果设置为true,角色将把其力传递到水平碰撞的所有刚体上")]
public bool Physics2DInteraction = true;
[Tooltip("施加在角色遇到的物体上的力")]
public float Physics2DPushForce = 2.0f;
[Header("小控件")]
[Tooltip("如果设置为true,将绘制CorgiController使用的各种光线投射,以便在小控件处于活动状态时在场景视图中检测碰撞")]
public bool DrawRaycastsGizmos = true;
[Tooltip("如果这是真的,如果设置不正确,将显示警告")]
public bool DisplayWarnings = true;
}
}
MMConditionAttribute
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace MoreMountains.Tools
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Struct, Inherited = true)]
public class MMConditionAttribute : PropertyAttribute
{
public string ConditionBoolean = "";
public bool Hidden = false;
public MMConditionAttribute(string conditionBoolean)
{
Debug.Log("MMConditionAttribute---1");
this.ConditionBoolean = conditionBoolean;
this.Hidden = false;
}
public MMConditionAttribute(string conditionBoolean, bool hideInInspector)
{
this.ConditionBoolean = conditionBoolean;
this.Hidden = hideInInspector;
}
}
}
MMConditionAttributeDrawer
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace MoreMountains.Tools
{
[CustomPropertyDrawer(typeof(MMConditionAttribute))]
public class MMConditionAttributeDrawer : PropertyDrawer
{
#if UNITY_EDITOR
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
MMConditionAttribute conditionAttribute = (MMConditionAttribute)attribute;
bool enabled = GetConditionAttributeResult(conditionAttribute, property);
bool previouslyEnabled = GUI.enabled;
GUI.enabled = enabled;
if (!conditionAttribute.Hidden || enabled)
{
EditorGUI.PropertyField(position, property, label, true);
}
GUI.enabled = previouslyEnabled;
}
#endif
private bool GetConditionAttributeResult(MMConditionAttribute condHAtt, SerializedProperty property)
{
bool enabled = true;
string propertyPath = property.propertyPath;
string conditionPath = propertyPath.Replace(property.name, condHAtt.ConditionBoolean);
SerializedProperty sourcePropertyValue = property.serializedObject.FindProperty(conditionPath);
if (sourcePropertyValue != null)
{
enabled = sourcePropertyValue.boolValue;
}
else
{
Debug.LogWarning("No matching boolean found for ConditionAttribute in object: " + condHAtt.ConditionBoolean);
}
return enabled;
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
MMConditionAttribute conditionAttribute = (MMConditionAttribute)attribute;
bool enabled = GetConditionAttributeResult(conditionAttribute, property);
if (!conditionAttribute.Hidden || enabled)
{
return EditorGUI.GetPropertyHeight(property, label);
}
else
{
return -EditorGUIUtility.standardVerticalSpacing;
}
}
}
}
LayerManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.CorgiEngine
{
/// <summary>
/// 一个简单的静态类,用于跟踪层名称,为最常见的层和层掩码组合提供现成的层掩码
///当然,如果你碰巧更改了图层顺序或编号,你会想使用这个类。
/// </summary>
public static class LayerManager
{
private static int PlatformsLayer = 8;
private static int OneWayPlatformsLayer = 11;
private static int ProjectilesLayer = 12;
private static int PlayerLayer = 9;
private static int EnemiesLayer = 13;
private static int MovingPlatformsLayer = 18;
private static int PushablesLayer = 27;
private static int MovingObjectsLayer = 17;
private static int MovingOneWayPlatformsLayer = 20;
private static int StairsLayer = 28;
private static int MidHeightOneWayPlatformsLayer = 26;
//1<<8 左移8位 =========0x100000000
public static int PlatformsLayerMask = 1 << PlatformsLayer;
public static int OneWayPlatformsLayerMask = 1 << OneWayPlatformsLayer;
public static int ProjectilesLayerMask = 1 << ProjectilesLayer;
public static int PlayerLayerMask = 1 << PlayerLayer;
public static int EnemiesLayerMask = 1 << EnemiesLayer;
public static int MovingPlatformsLayerMask = 1 << MovingPlatformsLayer;
public static int PushablesLayerMask = 1 << PushablesLayer;
public static int MovingObjectsLayerMask = 1 << MovingObjectsLayer;
public static int MovingOneWayPlatformsMask = 1 << MovingOneWayPlatformsLayer;
public static int StairsLayerMask = 1 << StairsLayer;
public static int MidHeightOneWayPlatformsLayerMask = 1 << MidHeightOneWayPlatformsLayer;
//Obstacle:障碍
public static int ObstaclesLayerMask = LayerManager.PlatformsLayerMask | LayerManager.MovingPlatformsLayerMask | LayerManager.OneWayPlatformsLayerMask;
}
}
CharacterGravity
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MoreMountains.Tools;
namespace MoreMountains.CorgiEngine
{
/// <summary>
/// 将此类添加到角色中,它将能够具有与默认重力不同的重力,并且能够通过重力区和重力点进行更改
///动画师参数:无
/// </summary>
[MMHiddenProperties("AbilityStartFeedbacks", "AbilityStopFeedbacks")]
[AddComponentMenu("Corgi Engine/Character/Abilities/Character Gravity")]
public class CharacterGravity : CharacterAbility
{
}
}
CharacterAbility
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.CorgiEngine
{
/// <summary>
/// 一个旨在被重写的类,用于处理角色的能力。
/// </summary>
///
public class CharacterAbility : MonoBehaviour
{
}
}
MMPathMovement
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.Tools
{
/// <summary>
/// 将此组件添加到对象中,它将能够沿着从其检查器定义的路径移动。
/// </summary>
[AddComponentMenu("More Mountains/Tools/Movement/MMPathMovement")]
public class MMPathMovement : MonoBehaviour
{
}
}
SurfaceModifier
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SurfaceModifier : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
Character
using MoreMountains.Tools;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.CorgiEngine
{
[SelectionBase]
/// SelectionBase 标签加上后,我们在scene窗口点击选中角色时候,他们会默认最优先选中我们带有 Character
/// 组件的gameobject 不会从默认的最上层gameobject一层层往下找
/// (你在scene窗口点击你角色后方便一下子定位到这个组件的gameobject上面)
/// <summary>
/// 本课程将引导您角色的CorgiController组件。
///在这里,你将执行角色的所有游戏规则,如跳跃、冲刺、射击等。
///动画师参数:固定(bool)、xSpeed(浮点)、ySpeed(浮点数)、,
///碰撞左(bool),碰撞右(bool)
///随机:介于0和1之间的随机浮点数,每帧更新一次,可用于为状态条目转换添加方差,例如
///RandomConstant:一个随机整数(介于0和1000之间),在开始时生成,在该动画师的整个生命周期内保持不变,对于具有相同类型的不同角色很有用
///行为不同
/// </summary>
[AddComponentMenu("Corgi Engine/Character/Core/Character")]
public class Character : MonoBehaviour
{
/// the possible character types : player controller or AI (controlled by the computer)
public enum CharacterTypes { Player, AI }
/// the possible initial facing direction for your character
public enum FacingDirections { Left, Right }
/// the possible directions you can force your character to look at after its spawn
public enum SpawnFacingDirections { Default, Left, Right }
[MMInformation("The Character script is the mandatory basis for all Character abilities. Your character can either be a Non Player Character, controlled by an AI, or a Player character, controlled by the player. In this case, you'll need to specify a PlayerID, which must match the one specified in your InputManager. Usually 'Player1', 'Player2', etc.", MoreMountains.Tools.MMInformationAttribute.InformationType.Info, false)]
/// Is the character player-controlled or controlled by an AI ?
[Tooltip("Is the character player-controlled or controlled by an AI ?")]
public CharacterTypes CharacterType = CharacterTypes.AI;
/// Only used if the character is player-controlled. The PlayerID must match an input manager's PlayerID. It's also used to match Unity's input settings. So you'll be safe if you keep to Player1, Player2, Player3 or Player4
[Tooltip("Only used if the character is player-controlled. The PlayerID must match an input manager's PlayerID. It's also used to match Unity's input settings. So you'll be safe if you keep to Player1, Player2, Player3 or Player4")]
public string PlayerID = "";
/// the various states of the character
public CharacterStates CharacterState { get; protected set; }
[Header("Direction")]
[MMInformation("It's usually good practice to build all your characters facing right. If that's not the case of this character, select Left instead.", MoreMountains.Tools.MMInformationAttribute.InformationType.Info, false)]
/// true if the player is facing right
[Tooltip("true if the player is facing right")]
public FacingDirections InitialFacingDirection = FacingDirections.Right;
[MMInformation("Here you can force a direction the character should face when spawning. If set to default, it'll match your model's initial facing direction.", MoreMountains.Tools.MMInformationAttribute.InformationType.Info, false)]
/// the direction the character will face on spawn
[Tooltip("the direction the character will face on spawn")]
public SpawnFacingDirections DirectionOnSpawn = SpawnFacingDirections.Default;
public bool IsFacingRight { get; set; }
[Header("Animator")]
[MMInformation("The engine will try and find an animator for this character. If it's on the same gameobject it should have found it. If it's nested somewhere, you'll need to bind it below. You can also decide to get rid of it altogether, in that case, just uncheck 'use mecanim'.", MoreMountains.Tools.MMInformationAttribute.InformationType.Info, false)]
/// the character animator
[Tooltip("the character animator")]
public Animator CharacterAnimator;
/// Set this to false if you want to implement your own animation system
[Tooltip("Set this to false if you want to implement your own animation system")]
public bool UseDefaultMecanim = true;
/// If this is true, sanity checks will be performed to make sure animator parameters exist before updating them. Turning this to false will increase performance but will throw errors if you're trying to update non existing parameters. Make sure your animator has the required parameters.
[Tooltip("If this is true, sanity checks will be performed to make sure animator parameters exist before updating them. Turning this to false will increase performance but will throw errors if you're trying to update non existing parameters. Make sure your animator has the required parameters.")]
public bool PerformAnimatorSanityChecks = true;
/// if this is true, animator logs for the associated animator will be turned off to avoid potential spam
[Tooltip("if this is true, animator logs for the associated animator will be turned off to avoid potential spam")]
public bool DisableAnimatorLogs = false;
[Header("Model")]
[MMInformation("Leave this unbound if this is a regular, sprite-based character, and if the SpriteRenderer and the Character are on the same GameObject. If not, you'll want to parent the actual model to the Character object, and bind it below. See the 3D demo characters for an example of that. The idea behind that is that the model may move, flip, but the collider will remain unchanged.", MoreMountains.Tools.MMInformationAttribute.InformationType.Info, false)]
/// the 'model' (can be any gameobject) used to manipulate the character. Ideally it's separated (and nested) from the collider/corgi controller/abilities, to avoid messing with collisions.
[Tooltip("the 'model' (can be any gameobject) used to manipulate the character. Ideally it's separated (and nested) from the collider/corgi controller/abilities, to avoid messing with collisions.")]
public GameObject CharacterModel;
/// the object to use as the camera target for this character
[Tooltip("the object to use as the camera target for this character")]
public GameObject CameraTarget;
/// the speed at which the Camera Target moves
[Tooltip("the speed at which the Camera Target moves")]
public float CameraTargetSpeed = 5f;
[Header("Abilities")]
/// A list of gameobjects (usually nested under the Character) under which to search for additional abilities
[Tooltip("A list of gameobjects (usually nested under the Character) under which to search for additional abilities")]
public List<GameObject> AdditionalAbilityNodes;
[MMInformation("You can also decide if the character must automatically flip when going backwards or not. Additionnally, if you're not using sprites, you can define here how the character's model's localscale will be affected by flipping. By default it flips on the x axis, but you can change that to fit your model.", MoreMountains.Tools.MMInformationAttribute.InformationType.Info, false)]
/// whether we should flip the model's scale when the character changes direction or not
[Tooltip("whether we should flip the model's scale when the character changes direction or not")]
public bool FlipModelOnDirectionChange = true;
/// the FlipValue will be used to multiply the model's transform's localscale on flip. Usually it's -1,1,1, but feel free to change it to suit your model's specs
[MMCondition("FlipModelOnDirectionChange", true)]
[Tooltip("the FlipValue will be used to multiply the model's transform's localscale on flip. Usually it's -1,1,1, but feel free to change it to suit your model's specs")]
public Vector3 ModelFlipValue = new Vector3(-1, 1, 1);
/// whether we should rotate the model on direction change or not
[Tooltip("whether we should rotate the model on direction change or not")]
public bool RotateModelOnDirectionChange;
/// the rotation to apply to the model when it changes direction
[MMCondition("RotateModelOnDirectionChange", true)]
[Tooltip("the rotation to apply to the model when it changes direction")]
public Vector3 ModelRotationValue = new Vector3(0f, 180f, 0f);
/// the speed at which to rotate the model when changing direction, 0f means instant rotation
[MMCondition("RotateModelOnDirectionChange", true)]
[Tooltip("the speed at which to rotate the model when changing direction, 0f means instant rotation")]
public float ModelRotationSpeed = 0f;
[Header("Health")]
/// the Health script associated to this Character, will be grabbed automatically if left empty
[Tooltip("the Health script associated to this Character, will be grabbed automatically if left empty")]
public Health CharacterHealth;
[Header("Events")]
[MMInformation("Here you can define whether or not you want to have that character trigger events when changing state. See the MMTools' State Machine doc for more info.", MoreMountains.Tools.MMInformationAttribute.InformationType.Info, false)]
/// If this is true, the Character's state machine will emit events when entering/exiting a state
[Tooltip("If this is true, the Character's state machine will emit events when entering/exiting a state")]
public bool SendStateChangeEvents = true;
/// If this is true, a state machine processor component will be added and it'll emit events on updates (see state machine processor's doc for more details)
[Tooltip("If this is true, a state machine processor component will be added and it'll emit events on updates (see state machine processor's doc for more details)")]
public bool SendStateUpdateEvents = true;
[Header("Airborne")]
/// The distance after which the character is considered airborne
[Tooltip("The distance after which the character is considered airborne")]
public float AirborneDistance = 0.5f;
/// The time (in seconds) to consider the character airborne, used to reset Jumping state
[Tooltip("The time (in seconds) to consider the character airborne, used to reset Jumping state")]
public float AirborneMinimumTime = 0.1f;
/// Whether or not the character is airborne this frame
public bool Airborne
{
get
{
Debug.Log("Character-------Airborne");
return ((_controller.DistanceToTheGround > AirborneDistance) || (_controller.DistanceToTheGround == -1));
}
}
[Header("AI")]
/// The brain currently associated with this character, if it's an Advanced AI. By default the engine will pick the one on this object, but you can attach another one if you'd like
[Tooltip("The brain currently associated with this character, if it's an Advanced AI. By default the engine will pick the one on this object, but you can attach another one if you'd like")]
public AIBrain CharacterBrain;
// State Machines
/// the movement state machine
public MMStateMachine<CharacterStates.MovementStates> MovementState;
/// the condition state machine
public MMStateMachine<CharacterStates.CharacterConditions> ConditionState;
// associated camera
public CameraController SceneCamera { get; protected set; }
/// associated input manager
public InputManager LinkedInputManager { get; protected set; }
/// associated animator
public Animator _animator { get; protected set; }
/// a list of animator parameters to update
public HashSet<int> _animatorParameters { get; set; }
/// whether or not the character can flip this frame
public bool CanFlip { get; set; }
// animation parameters
protected const string _groundedAnimationParameterName = "Grounded";
protected const string _airborneAnimationParameterName = "Airborne";
protected const string _xSpeedAnimationParameterName = "xSpeed";
protected const string _ySpeedAnimationParameterName = "ySpeed";
protected const string _worldXSpeedAnimationParameterName = "WorldXSpeed";
protected const string _worldYSpeedAnimationParameterName = "WorldYSpeed";
protected const string _collidingLeftAnimationParameterName = "CollidingLeft";
protected const string _collidingRightAnimationParameterName = "CollidingRight";
protected const string _collidingBelowAnimationParameterName = "CollidingBelow";
protected const string _collidingAboveAnimationParameterName = "CollidingAbove";
protected const string _idleSpeedAnimationParameterName = "Idle";
protected const string _aliveAnimationParameterName = "Alive";
protected const string _facingRightAnimationParameterName = "FacingRight";
protected const string _randomAnimationParameterName = "Random";
protected const string _randomConstantAnimationParameterName = "RandomConstant";
protected const string _flipAnimationParameterName = "Flip";
protected int _groundedAnimationParameter;
protected int _airborneSpeedAnimationParameter;
protected int _xSpeedSpeedAnimationParameter;
protected int _ySpeedSpeedAnimationParameter;
protected int _worldXSpeedSpeedAnimationParameter;
protected int _worldYSpeedSpeedAnimationParameter;
protected int _collidingLeftAnimationParameter;
protected int _collidingRightAnimationParameter;
protected int _collidingBelowAnimationParameter;
protected int _collidingAboveAnimationParameter;
protected int _idleSpeedAnimationParameter;
protected int _aliveAnimationParameter;
protected int _facingRightAnimationParameter;
protected int _randomAnimationParameter;
protected int _randomConstantAnimationParameter;
protected int _flipAnimationParameter;
protected CorgiController _controller;
protected SpriteRenderer _spriteRenderer;
protected Color _initialColor;
protected CharacterAbility[] _characterAbilities;
protected float _originalGravity;
protected bool _spawnDirectionForced = false;
protected Vector3 _targetModelRotation;
protected DamageOnTouch _damageOnTouch;
protected Vector3 _cameraTargetInitialPosition;
protected Vector3 _cameraOffset = Vector3.zero;
protected bool _abilitiesCachedOnce = false;
protected float _animatorRandomNumber;
protected CharacterPersistence _characterPersistence;
//TODO:下面进行注释删除,测试用
/*
public enum FollowerCondition { Follow, Hide, Show }
public MMStateMachine<FollowerCondition> FollowerConditionState;
*/
/// <summary>
/// Initializes this instance of the character
/// </summary>
protected virtual void Awake()
{
Initialization();
}
/// <summary>
/// Gets and stores input manager, camera and components
/// </summary>
public virtual void Initialization()
{
// we initialize our state machines
MovementState = new MMStateMachine<CharacterStates.MovementStates>(this.gameObject, SendStateChangeEvents);
ConditionState = new MMStateMachine<CharacterStates.CharacterConditions>(this.gameObject, SendStateChangeEvents);
MovementState.ChangeState(CharacterStates.MovementStates.Idle);
//TODO:下面3行之后删除,测试用
/*
MovementState.ChangeState(CharacterStates.MovementStates.Jumping);
MovementState.ChangeState(CharacterStates.MovementStates.Flying);
ConditionState.ChangeState(CharacterStates.CharacterConditions.Dead);
FollowerConditionState = new MMStateMachine<FollowerCondition>(this.gameObject, false);
*/
Debug.Log("Character------Initialization");
}
/// <summary>
/// Gets the main camera and stores it
/// </summary>
public virtual void GetMainCamera()
{
Debug.Log("Character------GetMainCamera");
}
/// <summary>
/// Caches abilities if not already cached
/// </summary>
protected virtual void CacheAbilitiesAtInit()
{
Debug.Log("Character------CacheAbilitiesAtInit");
}
/// <summary>
/// Grabs abilities and caches them for further use
/// Make sure you call this if you add abilities at runtime
/// Ideally you'll want to avoid adding components at runtime, it's costly,
/// and it's best to activate/disable components instead.
/// But if you need to, call this method.
/// </summary>
public virtual void CacheAbilities()
{
Debug.Log("Character------CacheAbilities");
}
/// <summary>
/// A method to check whether a Character has a certain ability or not
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T FindAbility<T>() where T : CharacterAbility
{
Debug.Log("Character------CacheAbilities");
return null;
}
/// <summary>
/// Use this method to change the character animator at runtime
/// </summary>
/// <param name="newAnimator"></param>
public virtual void ChangeAnimator(Animator newAnimator)
{
Debug.Log("Character------ChangeAnimator");
}
/// <summary>
/// Binds an animator to this character
/// </summary>
public virtual void AssignAnimator()
{
Debug.Log("Character------AssignAnimator");
}
/// <summary>
/// Gets (if it exists) the InputManager matching the Character's Player ID
/// </summary>
public virtual void SetInputManager()
{
Debug.Log("Character------SetInputManager");
}
/// <summary>
/// Sets a new input manager for this Character and all its abilities
/// </summary>
/// <param name="inputManager"></param>
public virtual void SetInputManager(InputManager inputManager)
{
Debug.Log("Character------SetInputManager");
}
/// <summary>
/// Updates the linked input manager for all abilities
/// </summary>
protected virtual void UpdateInputManagersInAbilities()
{
Debug.Log("Character------UpdateInputManagersInAbilities");
}
/// <summary>
/// Resets the input for all abilities
/// </summary>
public virtual void ResetInput()
{
Debug.Log("Character------ResetInput");
}
/// <summary>
/// Sets the player ID
/// </summary>
/// <param name="newPlayerID">New player ID.</param>
public virtual void SetPlayerID(string newPlayerID)
{
Debug.Log("Character------SetPlayerID");
}
/// <summary>
/// This is called every frame.
/// </summary>
protected virtual void Update()
{
Debug.Log("Character------Update");
//TODO:下面3行之后删除,测试用
/*
FollowerConditionState.ChangeState(FollowerCondition.Hide);
*/
}
/// <summary>
/// We do this every frame. This is separate from Update for more flexibility.
/// </summary>
protected virtual void EveryFrame()
{
Debug.Log("Character------EveryFrame");
}
protected virtual void RotateModel()
{
Debug.Log("Character------RotateModel");
}
/// <summary>
/// Calls all registered abilities' Early Process methods
/// </summary>
protected virtual void EarlyProcessAbilities()
{
Debug.Log("Character------EarlyProcessAbilities");
}
/// <summary>
/// Calls all registered abilities' Process methods
/// </summary>
protected virtual void ProcessAbilities()
{
Debug.Log("Character------ProcessAbilities");
}
/// <summary>
/// Calls all registered abilities' Late Process methods
/// </summary>
protected virtual void LateProcessAbilities()
{
Debug.Log("Character------LateProcessAbilities");
}
/// <summary>
/// Initializes the animator parameters.
/// </summary>
protected virtual void InitializeAnimatorParameters()
{
Debug.Log("Character------InitializeAnimatorParameters");
}
/// <summary>
/// This is called at Update() and sets each of the animators parameters to their corresponding State values
/// </summary>
protected virtual void UpdateAnimators()
{
Debug.Log("Character------UpdateAnimators");
}
/// <summary>
/// Generates a random number to send to the animator
/// </summary>
protected virtual void UpdateAnimationRandomNumber()
{
Debug.Log("Character------UpdateAnimationRandomNumber");
}
/// <summary>
/// Handles the character status.
/// </summary>
protected virtual void HandleCharacterStatus()
{
// if the character is dead, we prevent it from moving horizontally
if (ConditionState.CurrentState == CharacterStates.CharacterConditions.Dead)
{
Debug.Log("Character------HandleCharacterStatus----1");
}
// if the character is frozen, we prevent it from moving
if (ConditionState.CurrentState == CharacterStates.CharacterConditions.Frozen)
{
Debug.Log("Character------HandleCharacterStatus----2");
}
}
/// <summary>
/// Freezes this character.
/// </summary>
public virtual void Freeze()
{
Debug.Log("Character------Freeze");
}
protected CharacterStates.CharacterConditions _conditionStateBeforeFreeze;
/// <summary>
/// Unfreezes this character
/// </summary>
public virtual void UnFreeze()
{
Debug.Log("Character------UnFreeze");
}
/// <summary>
/// Use this method to force the controller to recalculate the rays, especially useful when the size of the character has changed.
/// </summary>
public virtual void RecalculateRays()
{
Debug.Log("Character------RecalculateRays");
}
/// <summary>
/// Called to disable the player (at the end of a level for example.
/// It won't move and respond to input after this.
/// </summary>
public virtual void Disable()
{
Debug.Log("Character------Disable");
}
/// <summary>
/// Makes the player respawn at the location passed in parameters
/// </summary>
/// <param name="spawnPoint">The location of the respawn.</param>
public virtual void RespawnAt(Transform spawnPoint, FacingDirections facingDirection)
{
Debug.Log("Character------RespawnAt");
}
/// <summary>
/// Flips the character and its dependencies (jetpack for example) horizontally
/// </summary>
public virtual void Flip(bool IgnoreFlipOnDirectionChange = false)
{
Debug.Log("Character------Flip");
}
/// <summary>
/// Flips the model only, no impact on weapons or attachments
/// </summary>
public virtual void FlipModel()
{
Debug.Log("Character------FlipModel");
}
/// <summary>
/// Forces the character to face left or right on spawn (and respawn)
/// </summary>
protected virtual void ForceSpawnDirection()
{
Debug.Log("Character------ForceSpawnDirection");
}
/// <summary>
/// Forces the character to face right or left
/// </summary>
/// <param name="facingDirection">Facing direction.</param>
public virtual void Face(FacingDirections facingDirection)
{
Debug.Log("Character------facingDirection");
}
/// <summary>
/// Called every frame, makes the camera target move
/// </summary>
protected virtual void HandleCameraTarget()
{
Debug.Log("Character------HandleCameraTarget");
}
/// <summary>
/// Sets a new offset for the camera target
/// </summary>
/// <param name="offset"></param>
public virtual void SetCameraTargetOffset(Vector3 offset)
{
Debug.Log("Character------SetCameraTargetOffset");
}
/// <summary>
/// Called when the Character dies.
/// Calls every abilities' Reset() method, so you can restore settings to their original value if needed
/// </summary>
public virtual void Reset()
{
Debug.Log("Character------Reset");
}
/// <summary>
/// On revive, we force the spawn direction
/// </summary>
protected virtual void OnRevive()
{
Debug.Log("Character------OnRevive");
}
/// <summary>
/// On character death, disables the brain and any damage on touch area
/// </summary>
protected virtual void OnDeath()
{
Debug.Log("Character------OnDeath");
}
/// <summary>
/// OnEnable, we register our OnRevive event
/// </summary>
protected virtual void OnEnable()
{
if (CharacterHealth != null)
{
Debug.Log("Character------OnEnable----1");
}
//TODO:下面3行之后删除,测试用
/*
FollowerConditionState.OnStateChange += OnStateChange;
*/
}
/// <summary>
/// OnDisable, we unregister our OnRevive event
/// </summary>
protected virtual void OnDisable()
{
if (CharacterHealth != null)
{
Debug.Log("Character------OnDisable");
}
//TODO:下面3行之后删除,测试用
/*
FollowerConditionState.OnStateChange -= OnStateChange;
*/
}
//TODO:下面3行之后删除,测试用
/*
private void OnStateChange()
{
switch (FollowerConditionState.CurrentState)
{
case FollowerCondition.Follow:
Debug.Log("现在需要让跟随者跟我一起运动");
break;
case FollowerCondition.Hide:
Debug.Log("现在需要让跟随者隐藏");
FollowerConditionState.ChangeState(FollowerCondition.Follow);
break;
}
}
*/
}
}
CharacterStates
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.CorgiEngine
{
/// <summary>
/// 您可以使用各种状态来检查角色在当前帧中是否正在做某事
/// </summary>
public class CharacterStates
{
/// 可能的角色条件
public enum CharacterConditions
{
Normal,
ControlledMovement,
Frozen,
Paused,
Dead,
Stunned
}
/// 角色可能处于的运动状态。这些通常对应于他们自己的阶级,
///但这不是强制性的
public enum MovementStates
{
Null,
Idle,
Walking,
Falling,
Running,
Crouching,
Crawling,
Dashing,
LookingUp,
WallClinging,
Jetpacking,
Diving,
Gripping,
Dangling,
Jumping,
Pushing,
DoubleJumping,
WallJumping,
LadderClimbing,
SwimmingIdle,
Gliding,
Flying,
FollowingPath,
LedgeHanging,
LedgeClimbing,
Rolling
}
}
}
MMStateMachine
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.Tools
{
//IComparable 继承这个接口后可以自己重写CompareTo 来自定义排序
//IConvertible 继承这个接口后可以自己重写To***() 来自定义类型转换
//IFormattable 继承这个接口后可以自己重写ToString() 自定义字符串格式化
public struct MMStateChangeEvent<T> where T : struct, IComparable, IConvertible, IFormattable
{
public GameObject Target;
public MMStateMachine<T> TargetStateMachine;
public T NewState;
public T PreviousState;
public MMStateChangeEvent(MMStateMachine<T> stateMachine)
{
Target = stateMachine.Target;
TargetStateMachine = stateMachine;
NewState = stateMachine.CurrentState;
PreviousState = stateMachine.PreviousState;
}
}
public interface MMIStateMachine
{
bool TriggerEvents { get; set; }
}
/// <summary>
/// StateMachine管理器,设计时考虑了简单性(无论如何,状态机都可以很简单)。
///要使用它,你需要一个枚举。例如:公共枚举CharacterConditions{正常、受控移动、冻结、暂停、死亡}
///声明如下:公共状态机<CharacterConditions>条件状态机;
///按如下方式初始化:ConditionStateMachine=new StateMachine<CharacterConditions>();
///然后,在任何地方,您只需在需要时更新其状态,例如:ConditionStateMachine。改变状态(字符状态。死亡);
///状态机将为您存储其当前和以前的状态,可以随时访问,并且还可以选择在这些状态的进入/退出时触发事件。
/// </summary>
public class MMStateMachine<T> : MMIStateMachine where T : struct, IComparable, IConvertible, IFormattable
{
///如果将TriggerEvents设置为true,则状态机将在进入和退出状态时触发事件。
///此外,它还可以选择在FixedUpdate、LateUpdate上触发当前状态的事件,但也可以
///on Update(在EarlyUpdate、Update和EndOfUpdate中分开,在Update()时按此顺序触发
///要在任何类的Start()方法中(或您喜欢的任何地方)监听这些事件,请使用MMEventManager。开始监听(gameObject.GetInstanceID()。ToString()+“XXXEnter”,在XXXEnter上);
///其中XXX是您正在监听的状态的名称,OnXXXEnter是您在触发该事件时要调用的方法。
///MMEventManager。开始监听(gameObject.GetInstanceID()。ToString()+“CrouchingEarlyUpdate”、OnCrouchingEarlyUpdate);例如,将收听Crouching状态的Early Update事件,以及
///将触发OnCrouchingEarlyUpdate()方法。
public bool TriggerEvents {
get ; set ;
}
public GameObject Target;
public T CurrentState { get; protected set; }
/// 角色在进入当前状态之前的运动状态
public T PreviousState { get; protected set; }
public delegate void OnStateChangeDelegate();
/// an event you can listen to to listen locally to changes on that state machine
/// to listen to them, from any class :
/// void OnEnable()
/// {
/// yourReferenceToTheStateMachine.OnStateChange += OnStateChange;
/// }
/// void OnDisable()
/// {
/// yourReferenceToTheStateMachine.OnStateChange -= OnStateChange;
/// }
/// void OnStateChange()
/// {
/// // Do something
/// }
public OnStateChangeDelegate OnStateChange;
/// <summary>
/// 创建一个新的StateMachine,带有targetName(用于事件,通常使用GetInstanceID()),以及是否要在其中使用事件
/// </summary>
/// <param name="targetName">Target name.</param>
/// <param name="triggerEvents">If set to <c>true</c> trigger events.</param>
public MMStateMachine(GameObject target, bool triggerEvents)
{
this.Target = target;
this.TriggerEvents = triggerEvents;
}
/// <summary>
/// 将当前移动状态更改为参数中指定的状态,并在需要时触发退出和进入事件
/// </summary>
/// <param name="newState">New state.</param>
public virtual void ChangeState(T newState)
{
// 如果“新状态”是当前状态,我们什么都不做,退出
if (EqualityComparer<T>.Default.Equals(newState, CurrentState))
{
return;
}
// 我们存储之前的角色运动状态
PreviousState = CurrentState;
CurrentState = newState;
OnStateChange?.Invoke();
if (TriggerEvents)
{
MMEventManager.TriggerEvent(new MMStateChangeEvent<T>(this));
}
}
/// <summary>
/// 将字符返回到其当前状态之前的状态
/// </summary>
public virtual void RestorePreviousState()
{
// 我们恢复了以前的状态
CurrentState = PreviousState;
OnStateChange?.Invoke();
if (TriggerEvents)
{
MMEventManager.TriggerEvent(new MMStateChangeEvent<T>(this));
}
}
}
}
InputManager
using MoreMountains.Tools;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.CorgiEngine
{
/// <summary>
/// 这个持久的单例处理输入并向玩家发送命令。
///重要提示:此脚本的执行顺序必须为-100。
///您可以通过单击脚本的文件,然后单击脚本检查器右下角的“执行顺序”按钮来定义脚本的执行顺序。
///请参阅https://docs.unity3d.com/Manual/class-ScriptExecution.html更多详细信息
/// </summary>
[AddComponentMenu("Corgi Engine/Managers/Input Manager")]
public class InputManager : MMSingleton<InputManager>
{
}
}
using MoreMountains.Tools;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static MoreMountains.CorgiEngine.CharacterStates;
namespace MoreMountains.CorgiEngine
{
public class TestStateMachineEnemy1 : MonoBehaviour,
MMEventListener<MMStateChangeEvent<CharacterStates.MovementStates>>,
MMEventListener<MMStateChangeEvent<CharacterStates.CharacterConditions>>
{
public void OnMMEvent(MMStateChangeEvent<CharacterStates.MovementStates> movementStates)
{
switch (movementStates.NewState)
{
case CharacterStates.MovementStates.Jumping:
Debug.Log("TestStateMachineEnemy1: 玩家" + movementStates.Target.gameObject.name + "跳起来了,我应该进行防空技能");
break;
case CharacterStates.MovementStates.Flying:
Debug.Log("TestStateMachineEnemy1: 玩家" + movementStates.Target.gameObject.name + "正在飞行中,我要切换远程攻击");
break;
}
}
public void OnMMEvent(MMStateChangeEvent<CharacterStates.CharacterConditions> characterConditions)
{
switch (characterConditions.NewState)
{
case CharacterStates.CharacterConditions.Dead:
Debug.Log("TestStateMachineEnemy1: 玩家" + characterConditions.Target.gameObject.name + "死亡了,我摆出胜利姿势");
break;
case CharacterStates.CharacterConditions.Frozen:
Debug.Log("TestStateMachineEnemy1: 玩家" + characterConditions.Target.gameObject.name + "冻结中了,我乘机攻击他去");
break;
}
}
/// <summary>
/// On enable, we start listening for MMGameEvents. You may want to extend that to listen to other types of events.
/// </summary>
protected void OnEnable()
{
this.MMEventStartListening<MMStateChangeEvent<CharacterStates.MovementStates>>();
this.MMEventStartListening<MMStateChangeEvent<CharacterStates.CharacterConditions>>();
}
/// <summary>
/// On disable, we stop listening for MMGameEvents. You may want to extend that to stop listening to other types of events.
/// </summary>
protected void OnDisable()
{
this.MMEventStopListening<MMStateChangeEvent<CharacterStates.MovementStates>>();
this.MMEventStopListening<MMStateChangeEvent<CharacterStates.CharacterConditions>>();
}
}
}