Last active
April 21, 2022 18:05
-
-
Save shreve/4d8c3e2fe6a96c314f28b394d0082ac0 to your computer and use it in GitHub Desktop.
2D Rope Swinging in Unity
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 System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
public class Reticle : MonoBehaviour { | |
public GameObject prefab; | |
GameObject sprite; | |
LineRenderer line; | |
Quaternion direction; | |
Vector2 offscreen = new Vector2(999f, 999f); | |
enum Mode { Controller, Mouse }; | |
Mode aimMode = Mode.Mouse; | |
void Start () { | |
direction = Quaternion.Euler(0, 0, 0); | |
sprite = Instantiate(prefab, offscreen, Quaternion.identity); | |
line = GetComponent<LineRenderer>(); | |
} | |
void Update () { | |
RenderReticle(); | |
float x = Input.GetAxis("RightHorizontal") * 10; | |
float y = Input.GetAxis("RightVertical") * 10; | |
float z; | |
if (x != 0 || y != 0) { | |
aimMode = Mode.Controller; | |
} | |
if (aimMode == Mode.Mouse) { | |
Vector2 mouse = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position; | |
z = Vector2.Angle(mouse, Vector2.up); | |
x = mouse.x; | |
if (x > 0) { | |
z = 360 - z; | |
} | |
direction = Quaternion.Euler(0f, 0f, z); | |
} else { | |
// If there is no input, don't do anything. | |
if (x == 0 && y == 0) { return; } | |
// Translate the x and y coords to an angle from up. | |
Vector3 vec = new Vector3(x, y, 0); | |
z = Vector2.Angle(Vector2.up, vec); | |
// I have no idea why this works, but it does. | |
if (x > 0) { | |
z += 180; | |
} else { | |
z = 180 - z; | |
} | |
direction = Quaternion.Slerp(direction, Quaternion.Euler(0f, 0f, z), 0.2f); | |
} | |
} | |
// If there is a target, render the reticle there. Otherwise, render it offscreen. | |
void RenderReticle() { | |
Vector3 target = CurrentTarget(); | |
line.positionCount = 2; | |
line.SetPosition(0, transform.position); | |
if (target.x == 0 && target.y == 0) { | |
sprite.transform.position = offscreen; | |
line.SetPosition(1, transform.position + direction * (Vector3.up * 50)); | |
} else { | |
sprite.transform.position = target; | |
line.SetPosition(1, target); | |
} | |
} | |
// Shoot a raycast toward direction and return the hit point | |
public Vector2 CurrentTarget() { | |
LayerMask mask = LayerMask.GetMask("Default"); | |
RaycastHit2D hit = Physics2D.CircleCast(transform.position + (Vector3.up * 1f), | |
1.0f, | |
direction * Vector3.up, | |
Mathf.Infinity, mask); | |
if (hit && hit.collider) { | |
if (hit.collider.gameObject.GetComponent<Snappable>()) { | |
return hit.collider.gameObject.transform.position; | |
} | |
} | |
return hit.point; | |
} | |
} |
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 System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
public class Rope : MonoBehaviour { | |
public GameObject source; | |
public Vector3 target; | |
public GameObject nodePrefab; | |
GameObject lastNode; | |
bool attached = true; | |
// Has the rope reached it's target? | |
bool inPlace = false; | |
// Space between nodes | |
float nodeSpace = 0.5f; | |
LineRenderer line; | |
List<GameObject> nodes = new List<GameObject>(); | |
void Start() { | |
nodes.Clear(); | |
nodes.Add(gameObject); | |
lastNode = gameObject; | |
line = GetComponent<LineRenderer>(); | |
} | |
// If our rope hits something on the way, attach to that instead | |
void OnCollisionStay2D(Collision2D other) { | |
Rigidbody2D rb = other.gameObject.GetComponent<Rigidbody2D>(); | |
if (!rb) rb = other.gameObject.GetComponentInParent<Rigidbody2D>(); | |
if (rb) { | |
HingeJoint2D joint = gameObject.AddComponent<HingeJoint2D>(); | |
joint.connectedBody = rb; | |
target = transform.position; | |
} | |
} | |
void Update() { | |
DrawRope(); | |
if (inPlace) return; | |
if (transform.position == target) { | |
inPlace = true; | |
// Once we've reached our target, add nodes lining up back to the character | |
while (Vector3.Distance(source.transform.position, lastNode.transform.position) > nodeSpace) { | |
CreateNode(); | |
} | |
// The connect the last node's hinge joint to whatever shot the web | |
lastNode.GetComponent<HingeJoint2D>().connectedBody = source.GetComponent<Rigidbody2D>(); | |
} else { | |
// Lerp toward the target | |
transform.position = Vector3.MoveTowards(transform.position, target, 1.5f); | |
} | |
} | |
// Use the LineRenderer to connect the dots | |
void DrawRope() { | |
line.positionCount = nodes.Count; | |
// If we're attached, we need to add one more line segment to the user | |
if (attached) { | |
line.positionCount++; | |
line.SetPosition(line.positionCount - 1, source.transform.position); | |
} | |
// Draw a line to each node's position | |
int i = 0; | |
for (; i < nodes.Count; i++) { | |
line.SetPosition(i, nodes[i].transform.position); | |
} | |
} | |
public void Detach() { | |
Destroy(lastNode.GetComponent<HingeJoint2D>()); | |
attached = false; | |
StartCoroutine(_destroy()); | |
} | |
void CreateNode() { | |
// Create a new node $nodeSpace distance toward the target | |
Vector3 direction = source.transform.position - lastNode.transform.position; | |
Vector3 position = (Vector3.Normalize(direction) * nodeSpace) + lastNode.transform.position; | |
GameObject newNode = Instantiate(nodePrefab, position, Quaternion.identity); | |
// Set it relative to rope | |
newNode.transform.SetParent(transform); | |
// Connect the lats node to this node | |
lastNode.GetComponent<HingeJoint2D>().connectedBody = newNode.GetComponent<Rigidbody2D>(); | |
// And add the new node to the list | |
lastNode = newNode; | |
nodes.Add(newNode); | |
} | |
// Destroy a node every 0.1s starting from the source and going to the target | |
IEnumerator _destroy() { | |
yield return new WaitForSeconds(1.0f); | |
for (int i = nodes.Count - 1; i >= 0; i--) { | |
Destroy(nodes[i]); | |
nodes.RemoveAt(i); | |
yield return new WaitForSeconds(0.1f); | |
} | |
} | |
} |
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 System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
public class RopeShooter : MonoBehaviour { | |
public GameObject ropePrefab; | |
GameObject user; | |
GameObject currentRope; | |
bool shooting = false; | |
void Start() { | |
user = transform.parent.gameObject; | |
} | |
void Update() { | |
bool activated = Mathf.Abs(Input.GetAxis("R2")) > 0 || | |
Input.GetMouseButton(0); | |
if (shooting == false && activated) { | |
Vector2 target = GetComponent<Reticle>().CurrentTarget(); | |
// If a Raycast2D doesn't hit anything, it returns point (0,0) | |
if (target.x == 0 && target.y == 0) { return; } | |
shooting = true; | |
ShootRope(target); | |
} | |
if (shooting == true && !activated) { | |
if (currentRope) { | |
currentRope.GetComponent<Rope>().Detach(); | |
} | |
shooting = false; | |
} | |
} | |
public void ShootRope(Vector3 target) { | |
GameObject rope = Instantiate(ropePrefab, transform.position, Quaternion.identity); | |
Rope ropec = rope.GetComponent<Rope>(); | |
ropec.target = target; | |
ropec.source = user; | |
currentRope = rope; | |
} | |
public bool Attached() { | |
return shooting; | |
} | |
} |
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
RopeShooter (Prefab) | |
<RopeShooter RopePrefab=Rope> | |
<Reticle Prefab=Reticle> | |
Rope (Prefab) | |
<Rope NodePrefab=RopeNode> | |
<CircleCollider2D> | |
<Rigidbody2D> | |
<HingeJoint2D> | |
<LineRenderer> | |
RopeNode (Prefab) | |
<CircleCollider2D> | |
<Rigidbody2D> | |
<HingeJoint2D> | |
Reticle (Prefab) | |
<SpriteRenderer> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Demo at http://www-personal.umich.edu/~shreve/unity/ManSpider/