[SerializeField] public AirState airState;
[SerializeField] public CutsceneState cutsceneState;
[SerializeField] public BoxMoveState boxMoveState;
[SerializeField] public GroundMoveState groundMoveState;
[SerializeField] public SlopeSlideState slopeSlideState;
[SerializeField] public BeamMoveState beamMoveState;
[SerializeField] public ClimbingState climbingState;
[SerializeField] public IMovement currentState;
public InputManager input;
public CharacterController cc;
public PlayerAbilities abilities;
public bool addToMove = false;
public Vector3 addMoveVector;
public InteractPrompt interactPrompt;
private void Awake()
{
MyPlayer = this;
anim = GetComponent<Animator>();
FindReferences();
}
void Start()
{
CreatePlayerStates();
groundCastStart = ( new Vector3(0f, pushBox.radius - pushBox.bounds.extents.y, 0f) + pushBox.center );
groundCastRadius = pushBox.radius;
airState.climbCheck = GetComponent<ClimbCheck>();
currentState = groundMoveState;
currentState.Start();
}
private void FindReferences()
{
abilities = GetComponent<PlayerAbilities>();
cc = GetComponent<CharacterController>();
pushBox = GetComponent<CapsuleCollider>();
input = GetComponent<InputManager>();
}
private void CreatePlayerStates()
{
///////////////////////////////// Here we create the states. They are not monobehaviour and can therefor not be placed on an object.
airState = new AirState(this);
cutsceneState = new CutsceneState(this);
boxMoveState = new BoxMoveState(this);
groundMoveState = new GroundMoveState(this);
slopeSlideState = new SlopeSlideState(this);
beamMoveState = new BeamMoveState(this);
climbingState = new ClimbingState(this);
/////////////////////////////////
}
void Update()
{
grounded = GroundCheck();
Vector3 move = currentState.UpdateState(input.MovementInput(), cc.velocity); //Here we update the current state. Now in Update, it is called every frame
if(addToMove)
{
move += addMoveVector;
addMoveVector = Vector3.zero;
addToMove = false;
}
cc.Move(move * Time.deltaTime);
}
private bool GroundCheck()
{
return (Physics.SphereCast(transform.position + groundCastStart, groundCastRadius, Vector3.down, out groundHit, groundCastDistance, blockingLayers)); //Add a layerMask to this at some point.
}
public float GroundAngle()
{
return Vector3.Angle(Vector3.up, groundHit.normal);
}
public bool ReturnGrounded() //use to check if player is grounded (There's probably a better way to do this??)
{
return grounded;
}
public RaycastHit ReturnGroundHit()
{
return groundHit;
}
public IEnumerator SwitchState(IMovement state)
{
yield return new WaitForEndOfFrame();
currentState.ExitState();
currentState = state;
currentState.Start();
}
[SerializeField] public ClimbSettings settings;
private PlayerMovement p;
private CapsuleCollider col;
private GameObject currentClimbBox;
private Vector3 flatNormal;
private Vector3 capsEndPos;
private float surfaceAngle;
private float pointsOffset;
private float radius;
private bool onCooldown = false;
private RaycastHit wallhit, topHit;
public Vector3 rHand, lHand;
public Quaternion targetRot;
public Vector3 targetCenter;
public Vector3 climbUpTarget;
private void Start()
{
p = gameObject.GetComponent<PlayerMovement>();
col = GetComponent<CapsuleCollider>();
radius = col.radius;
pointsOffset = (col.height * 0.5f) - radius;
}
private Vector3 CapsulePoint(float offset, Vector3 center)
{
return (transform.up * offset + col.center + center);
}
public void TryClimb()
{
if (onCooldown == false)
{
if (TraceForward())
{
surfaceAngle = CheckForSurface();
if (surfaceAngle < settings.maxSurfaceAngle && LookForCorner())
{
if (CheckObstruction() == false)
{
targetRot = Quaternion.LookRotation(flatNormal * -1f, Vector3.up);
StartCoroutine(p.SwitchState(p.climbingState));
}
}
}
}
}
private bool TraceForward()
{
if (Physics.CapsuleCast(
CapsulePoint(pointsOffset, transform.position),
CapsulePoint(-pointsOffset, transform.position),
radius, transform.forward,
out wallhit,
settings.forwardReach,
settings.climbable))
{
Vector3 playerPos = transform.position;
capsEndPos = wallhit.point - transform.forward * radius * 1.1f;
playerPos.y = 0f;
capsEndPos.y = 0f;
float traceDist = Vector3.Distance(playerPos, capsEndPos);
if (Physics.CapsuleCast(CapsulePoint(pointsOffset, transform.position),
CapsulePoint(-pointsOffset, transform.position),
radius,
transform.forward,
traceDist,
settings.obstructing)
|| Vector3.Angle(wallhit.normal, Vector3.ProjectOnPlane(wallhit.normal, Vector3.up)) > 7.87f)
{
return false;
}
else
{
currentClimbBox = wallhit.transform.gameObject;
return true;
}
}
return false;
}
private float CheckForSurface()
{
flatNormal = Vector3.ProjectOnPlane(wallhit.normal, Vector3.up).normalized;
Vector3 capsCenter = wallhit.point - (flatNormal * (radius + 0.1f)); // 0.1f because we dont wanna be too close to the edge after climbing up.
capsCenter.y = transform.position.y + settings.upwardReach + col.bounds.extents.y * 0.51f;
float traceLength = capsCenter.y - transform.position.y;
if (Physics.CapsuleCast(CapsulePoint(pointsOffset, capsCenter), CapsulePoint(-pointsOffset, capsCenter), radius, Vector3.down, out topHit, traceLength, settings.climbable))
{
float angle = Vector3.Angle(topHit.normal, Vector3.up);
if (angle < settings.maxSurfaceAngle)
{
return angle;
}
}
return settings.maxSurfaceAngle + 1f;
}
private bool LookForCorner()
{
float hitAngle = 99f;
float yHeightOffset = 0f;
float yHeight = wallhit.point.y + 0.5f;
RaycastHit hit = new RaycastHit();
Vector3 startPos = wallhit.point + flatNormal * 0.5f;
Vector3 traceDir = Vector3.down - flatNormal;
while (hitAngle > settings.maxSurfaceAngle && yHeightOffset < settings.upwardReach + 1f)
{
startPos.y = yHeight + yHeightOffset;
if (Physics.Raycast(startPos, traceDir, out hit, 1.5f, settings.climbable))
{
hitAngle = Vector3.Angle(hit.normal, Vector3.up);
}
Debug.DrawLine(startPos, startPos + traceDir.normalized * 2f, Color.red);
yHeightOffset += 0.05f;
}
if (hitAngle < settings.maxSurfaceAngle && SetTargetPos(hit.point))
{
lHand = hit.point + transform.right * settings.handOffset;
rHand = hit.point - transform.right * settings.handOffset;
return true;
}
else
{
return false;
}
}
public IEnumerator ClimbCD(float time)
{
onCooldown = true;
yield return new WaitForSeconds(time);
onCooldown = false;
}
private bool SetTargetPos(Vector3 ledgePos)
{
targetCenter = ledgePos + flatNormal * radius * 1.01f + Vector3.down * (col.bounds.extents.y + 0.4f);
if (Physics.OverlapCapsule(CapsulePoint(pointsOffset, targetCenter), CapsulePoint(-pointsOffset, targetCenter), radius, settings.both).Length > 0)
{
targetCenter.y += 0.4f;
if (Physics.OverlapCapsule(CapsulePoint(pointsOffset, targetCenter), CapsulePoint(-pointsOffset, targetCenter), radius, settings.both).Length > 0)
{
for (int i = 0; i < Physics.OverlapCapsule(CapsulePoint(pointsOffset, targetCenter), CapsulePoint(-pointsOffset, targetCenter), radius, settings.both).Length; i++)
{
if (Physics.OverlapCapsule(CapsulePoint(pointsOffset, targetCenter), CapsulePoint(-pointsOffset, targetCenter), radius, settings.both)[i].gameObject != currentClimbBox)
{
return false;
}
}
}
}
return true;
}
private bool CheckObstruction()
{
Vector3 vec = wallhit.point;
vec.y = transform.position.y;
Vector3 target = vec + flatNormal * (radius * 0.5f + 0.001f);
Vector3 p1 = CapsulePoint(pointsOffset, target);
Vector3 p2 = CapsulePoint(-pointsOffset, target);
Vector3 dir = Vector3.up;
float dist = topHit.point.y + (col.height * 0.5f) + 0.2f - transform.position.y;
if (Physics.CapsuleCast(p1, p2, radius, dir, dist, settings.obstructing) == false)
{
p1.y += dist;
p2.y += dist;
Vector3 hStart = p1;
hStart.y = 0f;
Vector3 hEnd = topHit.point;
hEnd.y = 0f;
float dist2 = Vector3.Distance(hEnd, hStart);
if (Physics.CapsuleCast(p1, p2, radius, -flatNormal, dist2, settings.obstructing) == false)
{
climbUpTarget = topHit.point + new Vector3(0f, col.bounds.extents.y + 0.1f, 0f);
return false;
}
}
return true;
}
private bool inPosition = false;
private bool recieveInput = false;
private bool climb = false;
private float releaseCount;
private bool runFailSafe;
ClimbCheck climbCheck;
PlayerMovement p;
private Quaternion targetRot;
private Vector3 climbTargetPos;
private Vector3 targetPos;
private Vector3 rHand, lHand;
public ClimbingState(PlayerMovement player)
{
p = player;
climbCheck = p.gameObject.GetComponent<ClimbCheck>();
}
private IEnumerator FailSafe(bool climbingUp)
{
runFailSafe = false;
yield return new WaitForSeconds(2f);
runFailSafe = true;
if (inPosition == false && climbingUp == false)
{
p.StartCoroutine(p.SwitchState(p.airState));
}
}
public Vector3 UpdateState(Vector3 input, Vector3 vel)
{
if (inPosition == false)
{
return(LerpToTarget());
}
else
{
if (recieveInput)
{
float forwardInput = (Vector3.Project(input, p.gameObject.transform.forward).normalized == p.gameObject.transform.forward)
? Vector3.Project(input, p.gameObject.transform.forward).magnitude : -Vector3.Project(input, p.gameObject.transform.forward).magnitude;
if (forwardInput > -0.55f)
{
releaseCount += Time.deltaTime;
if (releaseCount < 0.2f)
{
p.StartCoroutine(climbCheck.ClimbCD(0.2f));
p.StartCoroutine(p.SwitchState(p.airState));
}
}
else
{
releaseCount = 0f;
}
if (p.input.Buttons() == "Jump")
{
p.anim.SetTrigger("climbUp");
FMODUnity.RuntimeManager.PlayOneShotAttached(climbCheck.settings.climbSound, p.gameObject);
climb = true;
recieveInput = false;
}
}
if (climb)
{
if(runFailSafe)
{
p.StartCoroutine(FailSafe(true));
}
return ClimbUp();
}
}
return Vector3.zero;
}
private Vector3 ClimbUp()
{
if ((climbTargetPos - p.gameObject.transform.position).magnitude < 0.1f)
{
p.StartCoroutine(p.SwitchState(p.airState));
return Vector3.zero;
}
else
{
if (p.gameObject.transform.position.y < climbTargetPos.y)
{
return Vector3.up * climbCheck.settings.climbSpeed;
}
else
{
Vector3 dir = (climbTargetPos - p.gameObject.transform.position).normalized;
return dir * climbCheck.settings.climbSpeed;
}
}
}
private Vector3 LerpToTarget()
{
if ((p.gameObject.transform.position - targetPos).magnitude < climbCheck.settings.entrySpeed * Time.deltaTime && p.transform.rotation == targetRot)
{
p.StartCoroutine(InputDelay());
inPosition = true;
return Vector3.zero;
}
p.transform.rotation = Quaternion.RotateTowards(p.gameObject.transform.rotation, targetRot, Time.deltaTime * 360f);
Vector3 dir = (targetPos - p.gameObject.transform.position).normalized;
return dir * climbCheck.settings.entrySpeed;
}
}