-
-
Save IneonInoodle/1809ceb54d7ffce7a49bddb47b34a04d to your computer and use it in GitHub Desktop.
GunkGrapple
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System.Collections; | |
using MoreMountains.Tools; | |
using System; | |
namespace MoreMountains.CorgiEngine | |
{ | |
/// <summary> | |
/// Add this class to a character and it'll be able to grapple | |
/// </summary> | |
[AddComponentMenu("Corgi Engine/Character/Abilities/CharacterGrapple")] | |
public class CharacterGrapple : CharacterAbility | |
{ | |
/// This method is only used to display a helpbox text at the beginning of the ability's inspector | |
public override string HelpBoxText() { return "This component handles grappling"; } | |
/// the possible jump restrictions | |
public enum GrappleBehavior | |
{ | |
CanGrappleAnywhere, | |
CanGrappleOnSticky, | |
CanGrappleOnStickyWhenGrounded, | |
CantGrapple | |
} | |
private enum GrappleLineState | |
{ | |
starting, | |
grappleing, | |
ending, | |
none | |
} | |
public GameObject GrappleHookImagePrefab; | |
protected GameObject _currentGrappleHookImageObject; | |
// will be more likley to find grapple in direction | |
public bool PreferGrapplePointByFacingDirection = true; | |
public float PreferGrapplePointByFacingDirectionOffset = 2f; | |
public bool DontAllowGrappleDown = true; | |
public LayerMask GrappleStickyLayerMask; | |
public LayerMask GrappleStickyRaycastLayerMask; | |
[Header("Grapple Behaviour")] | |
/// the farthest we can grapple | |
public float MaxGrappleDistance = 15; | |
/// the shortest the grapple rope can be | |
public float MinGrappleDistance = 0.5f; | |
// the speed at which you can shorten or lengthen your grappling hook | |
public float GrappleReelSpeed = 4f; | |
public float GrappleControlSlownessSpeed = 4f; | |
/// basic rules for grapples : where can the player grapple ? | |
public GrappleBehavior GrappleRestrictions = GrappleBehavior.CanGrappleOnSticky; | |
/// a timeframe during which, after pressing the grapple button the grapple hook will hit the wall and trigger the swing | |
public float GrappleInitTimeWindow = 0.1f; | |
public float HorizontalInput; | |
public float VerticalInput; | |
//made a bool to check in Update if we can grapple to determine the indicator and not doublecheck that frame. | |
private bool canGrapple = false; | |
/// whether or not the grapple happened this frame | |
public bool GrappleHappenedThisFrame { get; set; } | |
public bool isThrowingGrapple = false; | |
public bool isGrappling = false; | |
protected Vector2 _currentGrappleAnchorPoint; | |
protected Collider2D[] _availableAnchorPoints; | |
protected float _jumpButtonPressTime = 0; | |
protected bool _jumpButtonPressed = false; | |
protected bool _jumpButtonReleased = false; | |
protected bool _doubleJumping = false; | |
protected CharacterWalljump _characterWallJump = null; | |
protected CharacterCrouch _characterCrouch = null; | |
protected CharacterButtonActivation _characterButtonActivation = null; | |
protected CharacterLadder _characterLadder = null; | |
protected int _initialNumberOfJumps; | |
protected float _lastTimeGrounded = 0f; | |
private float _grappleHookInitTimer; | |
public Transform AnchorPoint; | |
public Transform PlayerPoint; | |
public Vector2 AnchorPos; | |
public Vector2 Pos; | |
public Vector2 LastPos; | |
protected CharacterJump _characterJump; | |
public float Distance { get { return Vector2.Distance(AnchorPos, Pos); } } | |
public float OriginalDistance; | |
public float Gravity = 0.04f; | |
public float Friction = 0.99f; | |
//GrappleLine Stuff | |
private LineRenderer line; | |
private GrappleLineState gls = GrappleLineState.ending; | |
public float lineDrawSpeed = 1f; | |
private float lineCounter = 0f; | |
private float lineDistance; | |
public float lineWidth = 1f; | |
public float linePointDistance = 0.3f; | |
private GameObject GrappleableGO | |
{ | |
get { return _grappleableGO; } | |
set | |
{ | |
if (_grappleableGO != null) | |
_grappleableGO.transform.parent.GetChild(4).gameObject.SetActive(false); | |
_grappleableGO = value; | |
if(_grappleableGO != null) | |
_grappleableGO.transform.parent.GetChild(4).gameObject.SetActive(true); | |
} | |
} | |
private GameObject _grappleableGO; | |
private CharacterShellRun _shell; | |
private bool lastFrameGrounded = false; | |
private bool justStartedGrapple = false; | |
private bool lastFrameGrapple = false; | |
public float momentumSpeed = 3f; | |
private CharacterHorizontalMovement _charHor; | |
/// Evaluates the grapple restrictions | |
public bool GrappleAuthorized | |
{ | |
get | |
{ | |
if (_movement.CurrentState == CharacterStates.MovementStates.SwimmingIdle) | |
{ | |
return false; | |
} | |
if ((GrappleRestrictions == GrappleBehavior.CanGrappleAnywhere)) | |
return true; | |
if ((GrappleRestrictions == GrappleBehavior.CanGrappleOnStickyWhenGrounded)) | |
{ | |
if (_controller.State.IsGrounded) | |
{ | |
return true; | |
} | |
} | |
if (GrappleRestrictions == GrappleBehavior.CanGrappleOnSticky) | |
{ | |
return true; | |
} | |
return false; | |
} | |
} | |
#region AbilityCalls | |
protected override void Initialization() | |
{ | |
_characterJump = GetComponent<CharacterJump>(); | |
base.Initialization(); | |
_shell = GetComponent<CharacterShellRun>(); | |
_charHor = GetComponent<CharacterHorizontalMovement>(); | |
GetLineRenderer(); | |
} | |
protected override void HandleInput() | |
{ | |
//must do this here so it gets calculated before it checks for the Input. | |
if (gls == GrappleLineState.none) | |
{ | |
canGrapple = EvaluateGrappleConditions(); | |
} | |
else { canGrapple = false; } | |
if (!canGrapple && gls == GrappleLineState.none) GrappleableGO = null; | |
if (_inputManager.GrappleButton.State.CurrentState == MMInput.ButtonStates.ButtonDown) | |
{ | |
if (!isGrappling) | |
{ | |
GrappleStart(); | |
} | |
else | |
{ | |
GrappleStop(); | |
} | |
} | |
//threshold for y input if on controller. | |
float PrimaryAbs2 = Mathf.Abs(_character.LinkedInputManager.PrimaryMovement.y); | |
VerticalInput = (PrimaryAbs2 > 0.9f) ? Mathf.Sign(_character.LinkedInputManager.PrimaryMovement.y) : 0f; | |
//threshold for x input if on controller. | |
float PrimaryAbs = Mathf.Abs(_character.LinkedInputManager.PrimaryMovement.x); | |
HorizontalInput = (PrimaryAbs > 0.3f) ? Mathf.Sign(_character.LinkedInputManager.PrimaryMovement.x) : 0f; | |
} | |
/// <summary> | |
/// Every frame we perform a number of checks related to jump | |
/// </summary> | |
public override void ProcessAbility() | |
{ | |
base.ProcessAbility(); | |
if (!AbilityPermitted) { return; } | |
//We stop if we leave ground or enter ground from the air or if we are in shellmode | |
if (!lastFrameGrounded && _controller.State.IsGrounded | |
|| lastFrameGrounded && !justStartedGrapple && !_controller.State.IsGrounded | |
|| _shell.isInShell) GrappleStop(); | |
if (isGrappling && !isThrowingGrapple) | |
{ | |
// keeps character from walking longer than max grapple length when on ground | |
//or if we would otherwise go into a wall (2. line | |
CanAdjustGrapple(); | |
} | |
//save last pos so our momentum is carried over, also locks us when max length on ground | |
if (!isGrappling || _controller.State.IsGrounded) | |
{ | |
//LastPos = this.transform.position; | |
} | |
PrepareGrappleVariables(); | |
HandleVerticalInput(); | |
HandleGrappleLine(); | |
} | |
private bool CanAdjustGrapple() | |
{ | |
if (_controller.State.IsGrounded && Vector2.Distance(_currentGrappleAnchorPoint, this.transform.position) > MaxGrappleDistance) | |
{ | |
this.transform.position = LastPos; | |
return false; | |
} | |
if (_controller.State.IsCollidingLeft && transform.position.x - LastPos.x < 0 | |
|| _controller.State.IsCollidingRight && transform.position.x - LastPos.x > 0) | |
{ | |
this.transform.position = new Vector2(LastPos.x, LastPos.y); | |
return true; | |
} | |
return true; | |
} | |
public virtual void GrappleStart() | |
{ | |
if (!canGrapple) | |
{ | |
return; | |
} | |
_character.MovementState.ChangeState(CharacterStates.MovementStates.Idle); | |
_currentGrappleHookImageObject = Instantiate(GrappleHookImagePrefab, AnchorPoint); | |
AnchorPos = new Vector2(_currentGrappleAnchorPoint.x, _currentGrappleAnchorPoint.y); | |
Pos = new Vector2(this.transform.position.x, this.transform.position.y); | |
LastPos = Pos - _controller.Speed/30; | |
//LastPos = Pos - _controller.Speed/80; | |
OriginalDistance = Vector2.Distance(AnchorPos, Pos); | |
//Debug.Log("GrappleStart:" + OriginalDistance + " A:" + AnchorPos + " P:" + Pos); | |
//we wanna get dragged towards our grapplepoint if we grapple while on ground | |
if (_controller.State.IsGrounded) | |
{ | |
Vector2 moveTo = (AnchorPos - Pos).normalized * OriginalDistance * 12f; //the float is the determening how far you get dragged in. should be atleast 50f. | |
_controller.AddForce(moveTo); | |
justStartedGrapple = true; | |
} | |
//this starts the grappleing. gls goes for the visual, isGrappling will activate the physics. | |
isThrowingGrapple = true; | |
isGrappling = true; | |
gls = GrappleLineState.starting; | |
_shell.ShellStop(true); //going out of shell | |
_grappleHookInitTimer = GrappleInitTimeWindow; | |
if (_movement.CurrentState == CharacterStates.MovementStates.LadderClimbing) | |
{ | |
//_characterLadder.GetOffTheLadder(); | |
} | |
_controller.ResetColliderSize(); | |
if (_movement.CurrentState != CharacterStates.MovementStates.ShellRunning) | |
_movement.ChangeState(CharacterStates.MovementStates.Grappling); | |
// we start our sounds | |
PlayAbilityStartSfx(); | |
// we reset our current condition and gravity | |
_condition.ChangeState(CharacterStates.CharacterConditions.Normal); | |
_controller.CollisionsOn(); | |
} | |
/// <summary> | |
/// Causes the character to stop running. | |
/// </summary> | |
public virtual void GrappleStop() | |
{ | |
if (!isGrappling) return; | |
// model rotates during swing so we set it back | |
_character.CharacterModel.transform.eulerAngles = new Vector3(0,0,0); | |
// destroy eyeball image | |
if (_currentGrappleHookImageObject != null) | |
Destroy(_currentGrappleHookImageObject); | |
// return physics to normal | |
_currentGrappleAnchorPoint = Vector2.zero; | |
AnchorPos = Vector2.zero; | |
//_controller.State.IsCollidingBelow = false; | |
//_controller.State.IsFalling = true; | |
isThrowingGrapple = false; | |
_controller.GravityActive(true); | |
isGrappling = false; | |
gls = GrappleLineState.ending; | |
_movement.ChangeState(CharacterStates.MovementStates.Idle); | |
StopSfx(); | |
} | |
/// <summary> | |
/// Stops all run sounds | |
/// </summary> | |
protected virtual void StopSfx() | |
{ | |
StopAbilityUsedSfx(); | |
PlayAbilityStopSfx(); | |
} | |
public override void LateProcessAbility() | |
{ | |
base.LateProcessAbility(); | |
lastFrameGrounded = _controller.State.IsGrounded; | |
} | |
/// <summary> | |
/// Adds required animator parameters to the animator parameters list if they exist | |
/// </summary> | |
protected override void InitializeAnimatorParameters() | |
{ | |
RegisterAnimatorParameter("Grappling", AnimatorControllerParameterType.Bool); | |
} | |
/// <summary> | |
/// At the end of each cycle, sends Jumping states to the Character's animator | |
/// </summary> | |
public override void UpdateAnimator() | |
{ | |
MMAnimator.UpdateAnimatorBool(_animator, "Grappling", isGrappling || isThrowingGrapple); | |
} | |
/// <summary> | |
/// Resets parameters in anticipation for the Character's respawn. | |
/// </summary> | |
public override void ResetAbility() | |
{ | |
base.ResetAbility(); | |
} | |
#endregion | |
#region privateMethods | |
private void GetLineRenderer() | |
{ | |
if (GetComponentInChildren<LineRenderer>() == null) | |
{ | |
if(this.tag == "Player") | |
Debug.LogError("Missing LineRenderer Component in the player Prefab!!!"); | |
} | |
else | |
{ | |
line = GetComponentInChildren<LineRenderer>(); | |
} | |
} | |
private void PrepareGrappleVariables() | |
{ | |
if (isThrowingGrapple) | |
{ | |
_grappleHookInitTimer -= Time.deltaTime; | |
if (_grappleHookInitTimer < 0) | |
{ | |
isThrowingGrapple = false; | |
gls = GrappleLineState.starting; | |
_characterJump.ResetNumberOfJumps(); | |
_controller.GravityActive(false); | |
} | |
} | |
} | |
private void HandleVerticalInput() | |
{ | |
if (isGrappling && !isThrowingGrapple) | |
{ | |
if (!_controller.State.IsGrounded) | |
{ | |
if (VerticalInput != 0) | |
{ | |
OriginalDistance -= VerticalInput * Time.deltaTime * GrappleReelSpeed; | |
OriginalDistance = Mathf.Clamp(OriginalDistance, MinGrappleDistance, MaxGrappleDistance); | |
// reel in character | |
} | |
} | |
} | |
} | |
private void HandleGrappleLine() | |
{ | |
line.SetPosition(0, transform.position); | |
switch (gls) | |
{ | |
case GrappleLineState.starting: | |
lastFrameGrapple = true; | |
BuildUpLine(); | |
break; | |
case GrappleLineState.grappleing: | |
justStartedGrapple = false; | |
HandleLineCurve(); | |
break; | |
case GrappleLineState.ending: | |
lineCounter = 0; | |
line.positionCount = 1; | |
gls = GrappleLineState.none; | |
lastFrameGrapple = false; | |
break; | |
case GrappleLineState.none: | |
break; | |
} | |
} | |
private void BuildUpLine() | |
{ | |
lineDistance = Vector3.Distance(transform.position, _currentGrappleAnchorPoint); | |
line.positionCount = 2; | |
if (lineCounter < lineDistance) | |
{ | |
lineCounter += .1f / lineDrawSpeed; | |
float x = Mathf.Lerp(0, lineDistance, lineCounter); | |
Vector3 pointA = transform.position; | |
Vector3 pointB = _currentGrappleAnchorPoint; | |
Vector3 pointAlongLine = x * Vector3.Normalize(pointB - pointA) + pointA; | |
line.SetPosition(1, pointAlongLine); | |
_currentGrappleHookImageObject.transform.position = pointAlongLine; | |
Vector3 targ = pointAlongLine; | |
targ.z = 0f; | |
Vector3 objectPos = transform.position; | |
targ.x = targ.x - objectPos.x; | |
targ.y = targ.y - objectPos.y; | |
float angle = Mathf.Atan2(targ.y, targ.x) * Mathf.Rad2Deg; | |
_currentGrappleHookImageObject.transform.rotation = Quaternion.Euler(new Vector3(0, 0, angle - 90f)); | |
_character.CharacterModel.transform.rotation = _currentGrappleHookImageObject.transform.rotation; | |
_currentGrappleHookImageObject.SetActive(true); | |
} | |
else gls = GrappleLineState.grappleing; | |
} | |
private void HandleLineCurve() | |
{ | |
//lets us know how many points we have to place on our renderer. | |
int pointsOnLine = Mathf.RoundToInt(Mathf.Floor(lineDistance / linePointDistance)); | |
Vector2 startPoint = transform.position; | |
Vector2 goalPoint = _currentGrappleAnchorPoint; | |
Vector3 targetVector = (goalPoint - startPoint); | |
Vector3 partOfTarget = targetVector / pointsOnLine; | |
line.positionCount = pointsOnLine + 2; //+2 because starting at 0 and last one is our target. | |
//because im Rounding pointsOnLine down it means space between very last and 2. last point is different to the others, but shouldnt matter. | |
//first Position is always our transform. | |
for (int i = 1; i <= pointsOnLine;i++) | |
{ | |
line.SetPosition(i, (partOfTarget * i) + new Vector3(startPoint.x, startPoint.y, 0f)); | |
} | |
//making sure the End of our line is always at our goalPoint. | |
line.SetPosition(pointsOnLine + 1, goalPoint); | |
Vector3 targ = goalPoint; | |
targ.z = 0f; | |
Vector3 objectPos = transform.position; | |
targ.x = targ.x - objectPos.x; | |
targ.y = targ.y - objectPos.y; | |
float angle = Mathf.Atan2(targ.y, targ.x) * Mathf.Rad2Deg; | |
_currentGrappleHookImageObject.transform.rotation = Quaternion.Euler(new Vector3(0, 0, angle - 90f)); | |
_character.CharacterModel.transform.rotation = Quaternion.Euler(new Vector3(0, 0, angle - 90f)); | |
//Look at doesnt work in 2d idk why | |
//GrappleHookImage.transform.LookAt(this.transform); | |
} | |
private void ResetLineCurve() | |
{ | |
line.positionCount = 0; | |
} | |
// returns true if found at least one; | |
public virtual bool FindGrapplingPoints() | |
{ | |
_availableAnchorPoints = Physics2D.OverlapCircleAll(this.transform.position, MaxGrappleDistance, GrappleStickyLayerMask); | |
if (_availableAnchorPoints.Length != 0) | |
{ | |
// get closest grapple point? get one closest to direction press on left stick? TODO | |
// raycast back to anchor point to see if rope should bend as well | |
if (gls == GrappleLineState.none) _currentGrappleAnchorPoint = getClosestGrapplePoint(); | |
if (!_currentGrappleAnchorPoint.Equals(Vector2.zero)) | |
return true; | |
else return false; | |
} | |
else | |
return false; | |
} | |
public Vector2 getClosestGrapplePoint() | |
{ | |
Vector2 output = Vector2.zero; | |
Vector2 charPos = this.transform.position; | |
Transform outputObject = null; | |
if (PreferGrapplePointByFacingDirection) | |
{ | |
if (_character.IsFacingRight) | |
{ | |
charPos.x += PreferGrapplePointByFacingDirectionOffset; | |
} | |
else | |
{ | |
charPos.x -= PreferGrapplePointByFacingDirectionOffset; | |
} | |
} | |
foreach (Collider2D c2D in _availableAnchorPoints) | |
{ | |
if (charPos.y < c2D.transform.position.y || !DontAllowGrappleDown) | |
{ | |
if (output.Equals(Vector2.zero)) | |
{ | |
RaycastHit2D hit = Physics2D.Raycast(this.transform.position, c2D.transform.position - this.transform.position, MaxGrappleDistance, GrappleStickyRaycastLayerMask); | |
if (hit.collider != null) | |
{ | |
if (hit.collider.gameObject.layer == 24) | |
{ | |
output = c2D.transform.position; | |
outputObject = c2D.transform; | |
} | |
} | |
} else if (Vector2.Distance(c2D.transform.position, charPos) < Vector2.Distance(output, charPos)) | |
{ | |
RaycastHit2D hit = Physics2D.Raycast(this.transform.position,c2D.transform.position - this.transform.position, MaxGrappleDistance, GrappleStickyRaycastLayerMask); | |
if (hit.collider != null) | |
{ | |
if (hit.collider.gameObject.layer == 24) | |
{ | |
output = c2D.transform.position; | |
outputObject = c2D.transform; | |
} | |
} | |
} | |
} | |
} | |
ActivateIndicator(outputObject); | |
return output; | |
} | |
private void ActivateIndicator(Transform go) | |
{ | |
if (go == null) return; | |
GrappleableGO = go.gameObject; | |
} | |
protected virtual void FixedUpdate() | |
{ | |
GrapplePhysics(); | |
} | |
public Vector2 GetGrapplePos() { return Pos; } | |
public void GrapplePhysics() | |
{ | |
MovePoint(); | |
MoveStick(); | |
Pos.x += HorizontalInput/ GrappleControlSlownessSpeed * Time.fixedDeltaTime; | |
//return LastPos - Pos; | |
} | |
void MoveStick() | |
{ | |
float Difference = OriginalDistance - Distance; | |
float Percent = Difference / Distance; | |
float offsetX = (AnchorPos.x - Pos.x) * Percent; | |
float offsetY = (AnchorPos.y - Pos.y) * Percent; | |
Pos.x -= offsetX; | |
Pos.y -= offsetY; | |
} | |
void MovePoint() | |
{ | |
Vector2 Velocity = (Pos - LastPos) * Friction; | |
LastPos = Pos; | |
Pos += Velocity; | |
//Add Gravity | |
Pos.y -= Gravity; | |
} | |
void MovePlayerToPoint() | |
{ | |
PlayerPoint.position = Pos; | |
} | |
/// <summary> | |
/// Evaluates the jump conditions to determine whether or not a jump can occur | |
/// </summary> | |
/// <returns><c>true</c>, if jump conditions was evaluated, <c>false</c> otherwise.</returns> | |
protected virtual bool EvaluateGrappleConditions() | |
{ | |
if (!AbilityPermitted // if the ability is not permitted | |
|| !GrappleAuthorized // if jumps are not authorized right now | |
|| (!FindGrapplingPoints()) // we there are grappling points | |
|| ((_condition.CurrentState != CharacterStates.CharacterConditions.Normal) // or if we're not in the normal stance | |
&& (_condition.CurrentState != CharacterStates.CharacterConditions.ControlledMovement))) | |
{ | |
return false; | |
} | |
return true; | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment