using System; using System.Collections.Generic; using KinematicCharacterController; using Twokinds.Assets._Game.Scripts.Movement; using Twokinds.Assets._Game.Scripts.MovementKine.Actions; using Twokinds.Assets._Game.Scripts.MovementKine.Stances; using Unity.Entities; using UnityEngine; using UnityEngine.Events; namespace Twokinds.Assets._Game.Scripts.MovementKine { public class KineMovement : BaseCharacterController { public Transform MovementTransform { get { return transform; } } public Vector3 MovementInput { get { return _movementInput; } } public bool Walk { get { return _walk; } } //order by priority. Top has say, second overlays with top mask public List Stance { get; private set; } public Vector3[] LastGoodGround = new Vector3[3]; public event UnityAction OnTriggerAction; public event UnityAction OnEndAction; public int ComboMax { get; private set; } public AttackType AttackType { get; private set; } public bool TwoHands { get; private set; } public float MoveSpeed { get { if (Stance.Count < 1) { return _moveSpeed; } var speed = Stance[0].MovementSpeed; for (int cnt = 1; cnt < Stance.Count; cnt++) { if (Stance[cnt].MovementSpeed < speed) { speed = Stance[cnt].MovementSpeed; } } return _moveSpeed * speed; } } public Vector3 LocalVelocity { get { return _localVelocity; } } public float ReverseAdjust { get { if (Stance.Count < 1) { return .33f; } var speed = Stance[0].ReverseSpeed; for (int cnt = 1; cnt < Stance.Count; cnt++) { if (Stance[cnt].ReverseSpeed < speed) { speed = Stance[cnt].ReverseSpeed; } } return speed; } } public BaseActionKine Action { get { return _action; } } public Animator Animator; public Transform CameraTarget; [SerializeField] private DefaultStance _defaultStance = DefaultStance.Standard; private Type _defaultStanceType; private int _jumpCount = 1; private BaseActionKine _action; [Header("Movement")] [SerializeField] float _moveSpeed = 10; float _stableMovementSharpness = 15; [SerializeField] private bool _walk; [SerializeField] private Vector3 _desiredRotation; [SerializeField] private Vector3 _rotationInput; [SerializeField] private Vector3 _movementInput; private Vector3 _localVelocity; public List IgnoredColliders = new List(); [Header("Air Movement")] public float MaxAirMoveSpeed = 10f; public float AirAccelerationSpeed = 5f; public float Drag = 0.1f; public Vector3 Gravity = new Vector3(0, -30f, 0); [Header("Network Settings")] public bool Networked; private float _cameraUpdateFollowEnd = 0; void Awake() { Stance = new List(); SetMaxCombo(3); if (CameraTarget == null) { CameraTarget = new GameObject("Camera Target").transform; CameraTarget.parent = transform; CameraTarget.localPosition = Vector3.up; CameraTarget.localRotation = Quaternion.identity; } } // Use this for initialization void Start () { BaseStanceKine defStance; if (_defaultStance == DefaultStance.Horse) { //defStance = new HorseStanceKine(this); defStance = new HorseStance(this); _defaultStanceType = defStance.GetType(); } else { //standard defStance = new StandardStanceKine(this); } _defaultStanceType = defStance.GetType(); Debug.Log(_defaultStanceType.ToString()); SetStance(defStance); } // Update is called once per frame void Update () { if (Motor.GroundingStatus.IsStableOnGround && (Action == null || Action.PreventJumpReset == false)) { if (_jumpCount != 1) { Debug.Log("Resetting jump counter"); } _jumpCount = 1; } else if (Motor.GroundingStatus.IsStableOnGround == false && Motor.BaseVelocity.y < -.15f && StanceActive() == false) { //fall animation SetStance(new FallStance(this)); } if (_action != null) { _action.Update(); return; } for (int cnt = 0; cnt < Stance.Count; cnt++) { Stance[cnt].Update(); if (!Stance[cnt].AllowSub) { break; } } } public bool StanceActive() where T : BaseStanceKine { for (int cnt = 0; cnt < Stance.Count; cnt++) { if (Stance[cnt] is T) { return true; } } return false; } public void SetStance(BaseStanceKine stance) { Stance.Add(stance); Stance.Sort((s1, s2) => s1.Priority.CompareTo(s2.Priority)); } /// /// /// /// /// /// True if Active, fals if not public bool ToggleStance(T stance = null) where T : BaseStanceKine { if (typeof(T) == _defaultStanceType) return true; for (int cnt = 0; cnt < Stance.Count; cnt++) { if (Stance[cnt] is T) { Stance.RemoveAt(cnt); return false; } } if (stance == null) { return false; } SetStance(stance); return true; } public override void UpdateRotation(ref Quaternion currentRotation, float deltaTime) { if (Time.time < _cameraUpdateFollowEnd) { if (_action != null && _action.LockRotation) { _desiredRotation = currentRotation.eulerAngles; } float orientationSharpness = 10; var rot = Quaternion.Euler(_desiredRotation) * Vector3.forward; // Smoothly interpolate from current to target look direction Vector3 smoothedLookInputDirection = Vector3 .Slerp(Motor.CharacterForward, rot, 1 - Mathf.Exp(-orientationSharpness * deltaTime)).normalized; // Set the current rotation (which will be used by the KinematicCharacterMotor) currentRotation = Quaternion.LookRotation(smoothedLookInputDirection, Motor.CharacterUp); } } public override void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime) { Vector3 targetMovementVelocity = Vector3.zero; // Ground movement if (Motor.GroundingStatus.IsStableOnGround) { SaveGroundPoint(Motor.transform.position); var movementInput = _movementInput; if (_action != null && _action.LockMovement) { movementInput = Vector3.zero; } if (Stance[0].LimitStrafe) { movementInput.x = 0; } if (_walk) { movementInput /= 3f; //movementInput.x = Mathf.Clamp(movementInput.x, -.3f, .3f); //movementInput.y = Mathf.Clamp(movementInput.y, -.3f, .3f); //movementInput.z = Mathf.Clamp(movementInput.z, -.3f, .3f); } //move in direction of desired rotation movementInput = Quaternion.Euler(_desiredRotation) * movementInput; Vector3 effectiveGroundNormal = Motor.GroundingStatus.GroundNormal; if (currentVelocity.sqrMagnitude > 0f && Motor.GroundingStatus.SnappingPrevented) { // Take the normal from where we're coming from Vector3 groundPointToCharacter = Motor.TransientPosition - Motor.GroundingStatus.GroundPoint; if (Vector3.Dot(currentVelocity, groundPointToCharacter) >= 0f) { effectiveGroundNormal = Motor.GroundingStatus.OuterGroundNormal; } else { effectiveGroundNormal = Motor.GroundingStatus.InnerGroundNormal; } } // Reorient velocity on slope currentVelocity = Motor.GetDirectionTangentToSurface(currentVelocity, effectiveGroundNormal) * currentVelocity.magnitude; // Calculate target velocity Vector3 inputRight = Vector3.Cross(movementInput, Motor.CharacterUp); //Debug.Log(inputRight); Vector3 reorientedInput = Vector3.Cross(effectiveGroundNormal, inputRight).normalized * movementInput.magnitude; targetMovementVelocity = reorientedInput * MoveSpeed; var localVel = Motor.Transform.InverseTransformDirection(targetMovementVelocity); localVel.z = Mathf.Clamp(localVel.z, - _moveSpeed * ReverseAdjust, MoveSpeed); _localVelocity = localVel; targetMovementVelocity = Motor.Transform.TransformDirection(localVel); // Smooth movement Velocity currentVelocity = Vector3.Lerp(currentVelocity, targetMovementVelocity, 1 - Mathf.Exp(-_stableMovementSharpness * deltaTime)); } // Air movement else { //move in direction of desired rotation var movementInput = Quaternion.Euler(_desiredRotation) * _movementInput; if (_action != null && _action.LockMovement) { movementInput = Vector3.zero; } // Add move input if (movementInput.sqrMagnitude > 0f) { targetMovementVelocity = movementInput * MaxAirMoveSpeed; // Prevent climbing on un-stable slopes with air movement if (Motor.GroundingStatus.FoundAnyGround) { Vector3 perpenticularObstructionNormal = Vector3.Cross(Vector3.Cross(Motor.CharacterUp, Motor.GroundingStatus.GroundNormal), Motor.CharacterUp).normalized; targetMovementVelocity = Vector3.ProjectOnPlane(targetMovementVelocity, perpenticularObstructionNormal); } _localVelocity = targetMovementVelocity; Vector3 velocityDiff = Vector3.ProjectOnPlane(targetMovementVelocity - currentVelocity, Gravity); currentVelocity += velocityDiff * AirAccelerationSpeed * deltaTime; } // Gravity currentVelocity += Gravity * deltaTime; if (currentVelocity.y < 0) { if (!Physics.Raycast(new Ray(transform.position, Vector3.down), 10000, ~(1 << 12))) { //stop falling currentVelocity.y = 0; } } // Drag currentVelocity *= (1f / (1f + (Drag * deltaTime))); } if (_action != null) { _action.Trigger(ref currentVelocity); } } private void SaveGroundPoint(Vector3 position) { if(Mathf.Abs((LastGoodGround[0] - position).sqrMagnitude) < 2) return; //maybe use a queue in the future //skip the last for (int cnt = LastGoodGround.Length - 2; cnt >= 0; cnt--) { LastGoodGround[cnt + 1] = LastGoodGround[cnt]; } LastGoodGround[0] = position; } public override void BeforeCharacterUpdate(float deltaTime) { } public override void PostGroundingUpdate(float deltaTime) { } public override void AfterCharacterUpdate(float deltaTime) { } public override bool IsColliderValidForCollisions(Collider coll) { if (IgnoredColliders.Count == 0) { return true; } if (IgnoredColliders.Contains(coll)) { return false; } return true; } public override void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport) { } public override void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport) { } public override void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport) { } public void SetInput(Vector3 input, Vector3 rotationInput, Vector3 desiredRotation) { rotationInput.x = 0; rotationInput.z = 0; desiredRotation.x = 0; desiredRotation.z = 0; _desiredRotation = desiredRotation; _rotationInput = rotationInput; //input = Quaternion.Euler(_desiredRotation) * input; Vector3 moveInputVector = Vector3.ClampMagnitude(new Vector3(input.x, 0f, input.z), 1f); _movementInput = moveInputVector; if (_rotationInput.magnitude > 0) { _cameraUpdateFollowEnd = Time.time + 0.25f; } } public void ToggleCrouch(bool? crouch = null) { if (!crouch.HasValue) { ToggleStance(new SneakStance(this)); return; } if (crouch.Value) { ToggleStance(new SneakStance(this)); } ToggleStance(null); } public void TriggerJump() { if (_jumpCount < 1) return; if (_action != null) { if (!_action.Interruptable) return; _action.Interrupt(); } _action = new JumpAction(this); _jumpCount--; } public void SetAction(BaseActionKine action) { if (_action != null) { _action.Interrupt(); } _action = action; SetupActionEvents(); if (_action == null) { //_motion.ChangeAnimation(_motion.Animation); } } void SetupActionEvents() { if(_action == null) return; _action.OnTriggerAction += OnTriggerAction; _action.OnEndAction += OnEndAction; } void RemoveActionEvents() { if (_action == null) return; _action.OnTriggerAction -= OnTriggerAction; _action.OnEndAction -= OnEndAction; } public void ToggleWalk(bool walk) { _walk = walk; } public void RemoveAction(BaseActionKine action) { if(action == null) return; if (action.Equals(_action)) { _action.Reset(); } Debug.Log($"Removed Action: {action.GetType()}"); RemoveActionEvents(); _action = null; } public void SetMaxCombo(int comboMax) { ComboMax = comboMax; } public void WeaponAttackType(AttackType attackType, bool twoHands) { AttackType = attackType; TwoHands = twoHands; } } }