Skip to content

Instantly share code, notes, and snippets.

@shreve
Last active April 21, 2022 18:05
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shreve/4d8c3e2fe6a96c314f28b394d0082ac0 to your computer and use it in GitHub Desktop.
Save shreve/4d8c3e2fe6a96c314f28b394d0082ac0 to your computer and use it in GitHub Desktop.
2D Rope Swinging in Unity
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;
}
}
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);
}
}
}
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;
}
}
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>
@shreve
Copy link
Author

shreve commented Oct 22, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment