using System.Collections; using System.Collections.Generic; using UnityEngine; namespace BNG { public class GrappleShot : GrabbableEvents { [Header("Range")] public float MaxRange = 100f; [Header("CharacterController Grapple Settings")] [Tooltip("(CharacterController Player Only) How much movement speed to apply to the CharacterController to move towards the grapple")] public float GrappleReelForce = 0.5f; [Tooltip("currentGrappleDistance must be greater than this to reel in")] public float MinReelDistance = 0.25f; [Header("Rigidbody Grapple Settings")] [Tooltip("(Rigidbody Player Only) How much force to apply to the player to move towards the grapple")] public float GrappleForce = 3f; [Tooltip("(Rigidbody Player Only) Type of ForceMode to use to move the player towards the grapple point. ForceMode. ForceMode.Acceleration will let you preserve momentum and Swing Around. Use lower number for Acceleration (ex : 3). ForceMode.Velocity will immediately alter your player's velocity, resulting in a smooth but linear movement. Use higher numbers (ex : 200).")] public ForceMode GrappleForceMode = ForceMode.Acceleration; [Header("Raycast Layers")] public LayerMask GrappleLayers; [Header("Component definitions")] public Transform MuzzleTransform; public Transform HitTargetPrefab; public LineRenderer GrappleLine; public LineRenderer HelperLine; public AudioClip GrappleShotSound; // Are we currently grappling towards an object bool grappling = false; // Were we grappling last frame bool wasGrappling = false; CharacterController characterController; SmoothLocomotion smoothLocomotion; PlayerGravity playerGravity; PlayerClimbing playerClimbing; Rigidbody playerRigid; AudioSource audioSource; // How far away the grapple is in meters [Header("Shown for Debug :")] public float currentGrappleDistance = 0; bool validTargetFound = false;// Is there something valid to grapple on to bool isDynamic = false; // Can we reel the object in Rigidbody grappleTargetRigid; // Object we're grappling Collider grappleTargetCollider; // Collider we're grappling Transform grappleTargetParent; // Store original parent bool requireRelease = false; // Require release to be pressed before continuing bool climbing = false; // Have we transitioned to climbing? // We will use the climbable as our means of moving and pulling the character around public Climbable ClimbHelper; // Start is called before the first frame update void Start() { GameObject player = GameObject.FindGameObjectWithTag("Player"); if (player) { characterController = player.GetComponentInChildren(); smoothLocomotion = player.GetComponentInChildren(); playerGravity = player.GetComponentInChildren(); playerClimbing = player.GetComponentInChildren(); playerRigid = player.GetComponent(); } else { Debug.Log("No player object found."); } audioSource = GetComponent(); } private void LateUpdate() { // Draw Lines in LateUpdate // Draw Helper Line if (!grappling && grab.BeingHeld && !requireRelease) { drawGrappleHelper(); } else { hideGrappleHelper(); } // Draw Grappling Line if (grappling && validTargetFound && grab.BeingHeld) { drawGrappleLine(); } else { hideGrappleLine(); } } // Try to shoot or grapple public override void OnTrigger(float triggerValue) { updateGrappleDistance(); // Fire gun if possible if (triggerValue >= 0.25f) { if (grappling) { reelInGrapple(triggerValue); } else { shootGrapple(); } } else { grappling = false; requireRelease = false; } // User was grappling previous frame, but not now if(!grappling && wasGrappling) { onReleaseGrapple(); } base.OnTrigger(triggerValue); } void updateGrappleDistance() { // Update Distance if (grappling) { currentGrappleDistance = Vector3.Distance(MuzzleTransform.position, HitTargetPrefab.position); } else { currentGrappleDistance = 0; } } public override void OnGrab(Grabber grabber) { base.OnGrab(grabber); } public override void OnRelease() { onReleaseGrapple(); base.OnRelease(); } // Called when grappling previous frame, but not this one void onReleaseGrapple() { // Reset gravity back to normal changeGravity(true); if(grappleTargetRigid && isDynamic) { grappleTargetRigid.useGravity = true; grappleTargetRigid.isKinematic = false; grappleTargetRigid.transform.parent = grappleTargetParent; // More reliable method of resetting parent : if(grappleTargetRigid.GetComponent()) { grappleTargetRigid.GetComponent().ResetParent(); } } // Reset Climbing ClimbHelper.transform.localPosition = Vector3.zero; playerClimbing.RemoveClimber(thisGrabber); climbing = false; grappling = false; validTargetFound = false; isDynamic = false; wasGrappling = false; } // Draw area where Grapple will land void drawGrappleHelper() { if (HitTargetPrefab) { RaycastHit hit; if (Physics.Raycast(MuzzleTransform.position, MuzzleTransform.forward, out hit, MaxRange, GrappleLayers, QueryTriggerInteraction.Ignore)) { // Ignore other grapple shots if (hit.transform.name.StartsWith("Grapple")) { hideGrappleHelper(); validTargetFound = false; isDynamic = false; return; } showGrappleHelper(hit.point, Quaternion.FromToRotation(Vector3.forward, hit.normal)); grappleTargetRigid = hit.collider.GetComponent(); grappleTargetCollider = hit.collider; isDynamic = grappleTargetRigid != null && !grappleTargetRigid.isKinematic && grappleTargetRigid.useGravity; // Parent the helper to the object we hit so it will be moved with it // Only allow uniform scale so it doesn't warp bool isUniformVector = hit.collider.transform.localScale.x == hit.collider.transform.localScale.y; if (isUniformVector || isDynamic) { HitTargetPrefab.parent = null; HitTargetPrefab.localScale = Vector3.one; HitTargetPrefab.transform.parent = hit.collider.transform; } else { HitTargetPrefab.parent = null; HitTargetPrefab.localScale = Vector3.one; } validTargetFound = true; if(isDynamic) { grappleTargetParent = grappleTargetRigid.transform.parent; } else { grappleTargetParent = null; } } else { hideGrappleHelper(); validTargetFound = false; isDynamic = false; } } } void drawGrappleLine() { GrappleLine.gameObject.SetActive(true); GrappleLine.SetPosition(0, MuzzleTransform.position); GrappleLine.SetPosition(1, HitTargetPrefab.position); } void hideGrappleLine() { if (GrappleLine && GrappleLine.gameObject.activeSelf) { GrappleLine.gameObject.SetActive(false); } } void showGrappleHelper(Vector3 pos, Quaternion rot) { HitTargetPrefab.gameObject.SetActive(true); HitTargetPrefab.position = pos; HitTargetPrefab.rotation = rot; HitTargetPrefab.localScale = Vector3.one; if (HelperLine) { HelperLine.gameObject.SetActive(true); HelperLine.SetPosition(0, MuzzleTransform.position); HelperLine.SetPosition(1, pos); } } void hideGrappleHelper() { if(HitTargetPrefab && HitTargetPrefab.gameObject.activeSelf) { HitTargetPrefab.gameObject.SetActive(false); } if (HelperLine && HelperLine.gameObject.activeSelf) { HelperLine.gameObject.SetActive(false); } } void reelInGrapple(float triggerValue) { // Has the collider been destroyed or disabled? if(validTargetFound && grappleTargetCollider != null && !grappleTargetCollider.enabled) { dropGrapple(); return; } if(validTargetFound && currentGrappleDistance > MinReelDistance) { // Move object towards our hand if(isDynamic) { grappleTargetRigid.isKinematic = false; grappleTargetRigid.transform.parent = grappleTargetParent; grappleTargetRigid.useGravity = false; grappleTargetRigid.AddForce((MuzzleTransform.position - grappleTargetRigid.transform.position) * 0.1f, ForceMode.VelocityChange); //r.MovePosition(MuzzleTransform.position * Time.deltaTime); } // Move character towards Hit location else { Vector3 moveDirection = (HitTargetPrefab.position - MuzzleTransform.position) * GrappleReelForce; // Turn off gravity before we move changeGravity(false); // Use smooth loco method if available if(smoothLocomotion) { if (smoothLocomotion.ControllerType == PlayerControllerType.CharacterController) { smoothLocomotion.MoveCharacter(moveDirection * Time.deltaTime * triggerValue); } else if (smoothLocomotion.ControllerType == PlayerControllerType.Rigidbody) { if(GrappleForceMode == ForceMode.VelocityChange) { playerRigid.velocity = Vector3.MoveTowards(playerRigid.velocity, (moveDirection * GrappleForce) * Time.fixedDeltaTime, 1f); } else { playerRigid.AddForce(moveDirection * GrappleForce, GrappleForceMode); } } } // Fall back to character controller else if(characterController) { characterController.Move(moveDirection * Time.deltaTime * triggerValue); } } } else if(validTargetFound && currentGrappleDistance <= MinReelDistance) { if (isDynamic) { //grappleTargetRigid.useGravity = true; grappleTargetRigid.velocity = Vector3.zero; grappleTargetRigid.isKinematic = true; grappleTargetRigid.transform.parent = transform; } if(!climbing && !isDynamic) { // Add climbable / grabber ClimbHelper.transform.localPosition = Vector3.zero; playerClimbing.AddClimber(ClimbHelper, thisGrabber); climbing = true; } } } // Shoot the valid out if valid target void shootGrapple() { if(validTargetFound) { // Play Grapple Sound if(GrappleShotSound && audioSource) { audioSource.clip = GrappleShotSound; audioSource.pitch = Time.timeScale; audioSource.Play(); } grappling = true; wasGrappling = true; requireRelease = true; } } void dropGrapple() { grappling = false; validTargetFound = false; isDynamic = false; wasGrappling = false; } void changeGravity(bool gravityOn) { if(playerGravity) { playerGravity.ToggleGravity(gravityOn); } } } }