Skip to content

Instantly share code, notes, and snippets.

@IneonInoodle
Created September 26, 2019 08:12
Show Gist options
  • Save IneonInoodle/1809ceb54d7ffce7a49bddb47b34a04d to your computer and use it in GitHub Desktop.
Save IneonInoodle/1809ceb54d7ffce7a49bddb47b34a04d to your computer and use it in GitHub Desktop.
GunkGrapple
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