working 2D rope concept for sidscrolling games (for free)

Update: I don’t have any of the code anymore, I’m sorry, but this thread is all that’s left. I can’t PM you a working example anymore

Hi

Some time ago I was came across the “Rope Script” made by a user called Jake. However, his rope was all fine for 3D games, but way too unpredictable for 2D games. So I set out to make my own rope.

Instead of explaining the details and how amazing I am, just take a look:

Get your hands on a working Lerpz With Rope example [DEAD LINK]

So, how to make this? I will explain it step by step.

1. the rope
The rope consists of several segments connected through character joints. Only the topmost segment has a hinge joint instead.

So, how to build the rope? Unfortunately, I have no idea, how to automate this process, so you’ll have to do it by hand. If anyone can figure out how to make a script that does the work, be my guest.

Start off with one segment (e.g. a capsule).
Give it the following componnts: a collider (capsule recommended), rigidbody and character joint.
Set the joint’s properties for Axis to (1, 0, 0) and Swingg Axis to (0, 0, 1). I am not sure about this, but it works for me.

Do the same for the next segment, which is above the one before.

Go back to the fist segment and in the Character Joint component set “Connected Body” to the second segment.

Here is a screenshot for reference:

Do the same thing for all the other segments accordingly, always connecting it to the segment right below it.

When you are about to make the final, topmost segment, give it a hinge joint.
Set Axis to (1, 0, 0) as well, but leave “Connected Body” empty. This will connect the joint to the world, keeping it in place, and providing a reliable holder.

As a final note, I set the layer of all segments, including the final one, to a layer, that can only collide with the player. So the rope will not be interrupted by enemies, powerups, or even other ropes. Usualy this is desirable in a game, as you don’t want the rope to become unreachable thus making the player stuck.

2. restricting the rope to 2D
Even though the player is seeing the game as a 2D game, everything still works in 3D. So the rope might move along the z-Axis into the back- or foreground, an the player character will seem to walk through it.
To restrict the rope i used a simple script. Attach it to every segment. It might be bad for performance, so you might want to be able to deactivate the rope when the player is too far away. Figure out what suits your needs best.
Anyway, here is the script:

//prevents the object from moving and rotating in a way unfit for 2D

function FixedUpdate () {
	transform.localPosition.z = 0;
	transform.eulerAngles.x = 0;
	transform.eulerAngles.y = 0;
}

3. climbing
OK, here comes the cool part. You will need three scripts.
The first one goes onto the player. It contains all the controls related aspects. When you attach it, disable it, we won’t need it yet.

/*
STUFF TO DO:
-Clean up the secondary variables stuff
-see how the horizontal moving works out

*/

//make speed for climbing and slipping idepentent, just in case
var climbingSpeed = 3.0;
var slippingSpeed = 3.0;
var horizontalSpeed = 3.0;

//a nice little touch, disable for more control
var slipsDownABit = true;
var slipsUpABit = false;

//enable to be able to move horizontally
var canMoveHorizontally = false;
//enable to apply force on the carrier (overrides the above)
var canShakeCarrier = false;

//these will be used for switching between climbing and sliding
@System.NonSerialized
	var climbingSwitch = 0;
@System.NonSerialized
	var slippingSwitch = 0;
	
//this will keep track of how many segments we are climbing at the moment
var segmentHashtable: Hashtable;
//the next lower segment the player could hang onto
@System.NonSerialized
	var previousParent: GameObject;

//the horizontal offset for climbing the target (0 is recommended), will be set by object to climb
private var xOffset = 0.0;

@System.NonSerialized
	var directionToFace = 1;


function Awake(){
	segmentHashtable = new Hashtable();
}

function Update () {
	//first snap to the thing we climb
	transform.localPosition.x = xOffset;
	
	//cache raw and smoothed axis values
	var rawVerticalAxis = Input.GetAxisRaw("Vertical");
	var smoothVerticalAxis = Input.GetAxis("Vertical");
	var rawHorizontalAxis = Input.GetAxisRaw("Horizontal");
	var smoothHorizontalAxis = Input.GetAxis("Horizontal");
	
	//set the switches
	if (smoothVerticalAxis > 0){
		//climbing up
		climbingSwitch = 1;
		slippingSwitch = 0;
	} else if (smoothVerticalAxis < 0){
		//sliding down
		climbingSwitch = 0;
		slippingSwitch = 1;
	} else{
		//hanging still
		climbingSwitch = 0;
		slippingSwitch = 0;
	}
	
	//now move, but only if a button is pressed OR shortly after releasing down (or up) to simulate some minor sliding
	if (rawVerticalAxis != 0 || (smoothVerticalAxis < 0  slipsDownABit) || (smoothVerticalAxis > 0  slipsUpABit))
		transform.Translate(Vector3.up * Mathf.Pow(climbingSpeed *smoothVerticalAxis, climbingSwitch) *  Mathf.Pow(slippingSpeed * smoothVerticalAxis, slippingSwitch) * Time.deltaTime);
	//horizontal movement
	if(smoothHorizontalAxis != 0  canMoveHorizontally)
		transform.Translate(Vector3.right * horizontalSpeed * smoothHorizontalAxis * Time.deltaTime);
		
	//shake it!
	if(canShakeCarrier)
		transform.parent.gameObject.rigidbody.AddRelativeForce(Vector3.right * smoothHorizontalAxis * 10);
		
	if(Input.GetButtonDown("Jump"))
		transform.parent.gameObject.GetComponent(RopeSegmentBehaviour).JumpOff(rawHorizontalAxis, gameObject);	
	if(rawHorizontalAxis != 0)
		directionToFace = rawHorizontalAxis;
}

function LateUpdate(){
	//set the rotation to fit the parent, if parented
//	if(transform.parent)
//		transform.rotation = transform.parent.transform.rotation;
//	Debug.Log (GetSegments());
	
	/*
	if(directionToFace == 1){
		transform.localEulerAngles.y = 0;
	} else{
		transform.localEulerAngles.y = 180;
	}
	*/
}


function RegisterSegment(segment: GameObject){
	segmentHashtable.Add(segment.name, segment);
}

function UnregisterSegment(segment: GameObject){
	segmentHashtable.Remove(segment.name);
}


function SetXOffest(offset: float){
	xOffset = offset;
}

function SetUpController(newClimbingSpeed: float, newSlippingSpeed: float, newHorizontalSpeed: float, slipUp: boolean, slipDown: boolean, moveHorizontally: boolean, shake: boolean){
	//if a passed value is 0, then ignore it
	if (newClimbingSpeed != 0.0)
		climbingSpeed = newClimbingSpeed;
	if ( newSlippingSpeed != 0.0)
		slippingSpeed = newSlippingSpeed;
	if ( newHorizontalSpeed != 0.0)
		horizontalSpeed = newHorizontalSpeed;
	slipsDownABit = slipUp;
	slipsUpABit = slipDown;
	canMoveHorizontally = moveHorizontally;
	canShakeCarrier = shake;
	
	//override canMoveHorizontally if needed
	if(canShakeCarrier)
		canMoveHorizontally = false;
}

There are some things I don’t intend to use in my game, but I put them in, just in case I might want in some future game. I won’t go into detail, the code should have enough comments. There might be stuff to clean up, I haven’t had time to go into details yet. Again, if anyone has suggestions or improvements, feel free to contribute.

The second sript goes on every single segment and only works if the rope can only collide with the player. More speciffically, my player is carrying a game object, that does the rope- and enemy related collisions, while the player itself cannot collide with them. This approach gives me more control.

just make sure the collider is slightly bigger than the player. This thing never collides with walls.

Also, make sure to have all the segments’ colliders as triggers or else they will move the player#s collision sensor around.

So, attach this to every segment:

/*
--- script used to parent player or other objects to a rope during gameplay ---

Use this script in combination with a rope. Currently only works with triggers.
The player has to press Up, in order to stick
*/
var segmentNumber: int;

function OnTriggerEnter (collision: Collider){
	collision.gameObject.transform.parent.gameObject.SendMessage("OnEnterRope", gameObject, SendMessageOptions.DontRequireReceiver);
}

function OnTriggerExit (collision: Collider){
	collision.gameObject.transform.parent.gameObject.SendMessage("OnExitRope", gameObject, SendMessageOptions.DontRequireReceiver);
}

function JumpOff(theDirection: int, jumper: GameObject){
	//always have some direction, if nothing use the joint's one
	if(theDirection == 0)
		theDirection = Mathf.Sign(rigidbody.velocity.x);
	
	//cache the controller script
	var controllerScript = jumper.GetComponent(GroundController);
	
	//now set everything to be ready for the jump
	controllerScript.jump.isPrimitive = false;
	controllerScript.movement.momentum = rigidbody.velocity.x;
	//choose the speed
	if(Mathf.Abs(rigidbody.velocity.x) >= controllerScript.movement.runSpeed){
		controllerScript.movement.speed = controllerScript.movement.runSpeed * Mathf.Sign(rigidbody.velocity.x);
	} else if (Mathf.Abs(rigidbody.velocity.x) >= controllerScript.movement.walkSpeed){
		controllerScript.movement.speed = controllerScript.movement.walkSpeed * Mathf.Sign(rigidbody.velocity.x);
	} else{
		controllerScript.movement.speed = rigidbody.velocity.x;
	}
	controllerScript.jump.launchDirection = theDirection;
	
	//decouple
	jumper.transform.parent = null;
	jumper.GetComponent(ClimbingController).segmentHashtable.Clear();
	
	//switch controller scripts and the decoupling is done
	controllerScript.enabled = true;
	jumper.GetComponent(ClimbingController).enabled = false;
	
	//now JUMP!
	controllerScript.movement.verticalSpeed = controllerScript.CalculateJumpVerticalSpeed (controllerScript.jump.height);
	controllerScript.DidJump();
}

Again, cleanup still to be done. GroundController refers to the controller script I’m using for regular movement, so whenever “GroundController” is referenced, substitute these lines with whatever would be appropriate for you.

I use third script on the player that handles managing different control scripts:

/*
--- Character Controls Manager Script ---

This script is used to receive Messages from various triggers and objects
to enable controls the player to use powerups or switch control scripts

*/

//using variables to cache script components for performance reasons
@System.NonSerialized
	var walkingScript: GroundController;
@System.NonSerialized
	var climbingScript: ClimbingController;



//_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
//setting everything up
function Awake(){
	walkingScript = gameObject.GetComponent(GroundController);
	climbingScript = gameObject.GetComponent(ClimbingController);
}
//_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-



//_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
//here come the functions to call

function OnEnterRope(rope: GameObject){
	if((Input.GetAxisRaw("Vertical") != 1  climbingScript.enabled == false) || climbingScript.segmentHashtable.ContainsValue(rope))
		return;

	//add this segment to the hashtable keeping track
	climbingScript.RegisterSegment(rope);
	
//	Debug.Log (Time.time + rope.name + gameObject.name);
	
	//only parent to the topmost segment or if not parented to any segment
	if(transform.parent == null || transform.parent.position.y < rope.transform.position.y){
		climbingScript.SetUpController(0.0, 0.0, 0.0, true, false, false, true);
	
		if(transform.parent == null){
			//need something for refernece
			climbingScript.previousParent = rope;
		}else{
			//the previous parent, which will be replaced now
			climbingScript.previousParent = transform.parent.gameObject;	
		}
		transform.parent = rope.transform;
		
		climbingScript.SetXOffest(0.0);
		walkingScript.enabled = false;
		climbingScript.enabled = true;
	}
}

function OnExitRope(rope: GameObject){
	if (climbingScript.enabled == false)
		return;
	
	//remove this segment from the hashtable
	climbingScript.UnregisterSegment(rope);
	
//	Debug.Log (Time.time + gameObject.name + rope.name + "exited collision");

	//when exiting the segment the player is parented to (i.e. climbing down) we need another parent
	if(transform.parent == rope.transform){
		transform.parent = climbingScript.previousParent.transform;
		//some setup
		var tempValue: int = 0; //the lowest segment must have the number 1
		var nextParent: GameObject;
		// loop through all remaining segments to find the topmost one, this will be the next ready segment
		for(var segment: DictionaryEntry in climbingScript.segmentHashtable){
			if(segment.Value.GetComponent(RopeSegmentBehaviour).segmentNumber > tempValue  transform.parent != segment.Value.transform)
				nextParent = segment.Value;
			if(transform.parent != segment.Value.transform)
				tempValue = segment.Value.GetComponent(RopeSegmentBehaviour).segmentNumber;
		}
		if(nextParent){
			climbingScript.previousParent = nextParent;
		}else{
			climbingScript.previousParent = transform.parent.gameObject;
		}
	}
	
	//the player will be touching several sements at a time. We want to dismount the player
	//only if he exited the last trigger he was in
	if(climbingScript.segmentHashtable.Count == 0){
		//the following two make the player fall down straight
		walkingScript.jump.isPrimitive = true;
		walkingScript.movement.momentum = 0;
		//now unparent
		transform.parent = null;
		//and acivate the right controller
		walkingScript.enabled = true;
		climbingScript.enabled = false;
	}
}
//_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-



//_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
//various herlper functions

//my own Signum function to the rescue, because Mathf.Sign() sucks!
function Signum(x: int){
	var sgnm: int = 0;
	if (x != 0)
		sgnm = Mathf.Sign(x);
	return sgnm;
}

The explanations and codes above are provided for free and can be used and/or modified at will. They are free for any use, including commercial.
I do not intend to support this concept, mainly for time reasons. It is just concept and there sure is room for improvement and addition.

hello HiPhish

first off all a big thx for the post, and for sharing your work wit the community.
i’ve not really coding knowledge, and i have trying to rebuild the scene with your instructions. but i don’t bring it to work :(.

so now a ask, can someone please upload a working unitypackage, so i’am able to see what i do not correct.

cheers

michael

I might build a rope into the 2D gameplay tutorial provided by Unity. But it won’t be anytime soon, I just don’t have the time at the moment.

hello dear unity community

first of all one thing to clarify, i’m a “3D Artist” and not a “Dev”, but since i know unity i trying to get the logic of coding :face_with_spiral_eyes:.

i trying to upload the .unitypackage (9.3MB) mix between the “Unity 2D Gameplay Tutorial” and “HiPhish’s 2D Rope Climbing Controller”, but always it comes up with the file is to large :shock: .

:roll_eyes:

this is why i put it to: http://rapidshare.com/files/446634313/_2D_Rope_and_2D_Platformer.unitypackage

:sunglasses: sorry for that, and i am happy:smile: if someone will improve this version and upload it 4 the community.

cheers

michael

Careful how you play with these things… it can get dangerous, as I discovered!

Anyway, I hope the video is good for a laugh. On a more serious note, I’m working on ropes and other flexible things to use on iOS and other games. If I come up with anything useful beyond what’s here or the other rope thread I’ll share. Thanks for sharing the code!

Haha, nice one :slight_smile: You have to find a way to switch back to the old controls and turn the climbing script off, once you are no longer touching any rope segments.
Good luck with your project.

Hi everyone. Bad news.

I had just returned to developing after a long break and found the same bug happening as in rbeverly’s video. The rope did work in Unity 3.1, so it looks like one of the updates broke the rope.

The problem is, that when entering a rope segment, OnTriggerEnter keeps being called every frame or so. The player keeps track of how many segments he is touching, so if OnTriggerEnter is called all the time, the numer gets way too high. One should slide off the rope when the count equals 0, but this way the count will never be that low, so the player stays parented to the last segment he touches.

I already filed a bug report, but there is nothing else i can do at the moment. i will post here when the issue gets fixed.

Nice try Unity, but if you strike me down, I shall become more powerful than you could possibly imagine.

I worked around the issue by using a hashtable to keep track of the segments instead of just counting their amount. This also allowed me to finally get rid of some ugly behaviour when climbing down. The scripts above have been updated, however weird stuff can stillhappen if the player swings around wildly, but let’s just say that it’s an intended design decision which reflects the real-world scenario when someone was swinging too much on a real rope :wink: And if that doesn’t sound convincing, just disable the player from being able to swing the rope.

On an unrelated note, I won’t be able to make an example project for upload. Ripping out my code and replacing it with other stuff and still making it work would take too much time in between studies, job and actually working on my game. Sorry guys, but this thread is the best I can offer.

Hey HiPhish, congrats you did a great job on this. All in all, how did you do the ropes to swing?

Take a look into the climbing script in my second post. The important part is here:

//shake it!
	if(canShakeCarrier)
		transform.parent.gameObject.rigidbody.AddRelativeForce(Vector3.right * smoothHorizontalAxis * 10);

Fist of all, you have to check canShakeCarrier in the inspector (or have it be true on default). Then when you press left or right this line will apply some force to the segment the player is holding on to.

Hi HiPhish, thanks for your reply. Actually, i was referencing about the rope natural swing. I noticed the ropes has a constant movement independently from the user interaction with the ropes.

Thanks

Oh that, just have the whole rope at an angle when you assemble it in the editor. Once the level lads gravity will do the work (just like it would in real life)

Since all the segments are just triggers they won’t be stopped when the player just jumps through the rope without holding “Up”. This may not be physically accurate, but it feels more classic-game like.

Hi, I’m working wiht koki, and we our rope is slowing down, we set the dampings to 0, but after some time it stops, does your hope have the same behaviour?

No, it doesn’t. With no information on what you did I can’t help you either. Maybe Unity changed something in 3.4 or maybe there is friction between the joints. Have you set all the segments’ collider to be triggers? Try just the rope without any scripts.

OK, there is a working example for download right below the video in the first post. I put a rope into Unity’s 2D gameplay tutorial project. It’s very rough work, since my scripts were heavily modified and adapting my rope to the old stuff would have been too much work. Essentially the only thing that does not work properly is setting the PlatformerController script to reasonable values after jumping off the rope, so you will be stuck in mid-air. Since everyone will have their own tackle on the walking controls there was no real point in fixing this, as everyone would have to adapt it to their own code anyway.

Treat the example as a working proof-of-concept. Also, the scripts in the example are slightly improved, but nothing special, so it doesn’t really matter if you use the example or what’s written here.

Hello HiPhish,
thanks a lot for sharing this with us, there is lots of useful infos in the technique you used for faking ropes and also great techniques in your scripts for managing different gameplay behaviors on your character.

I am very new to Unity, but so far the experience has been pretty good. With the help of some files like the Lurpz tutorial files, I was able to quickly get things working. I was able to achieve the basis of the 2D platformer I wanted to do in only a few days. And the discovery of you rope example comes in at the perfect moment for continuing my learning.

I struggled a bit with the integration of the rope at first, but now I got the basis working. I am using the latest version of Unity and it seems to work pretty well except for a couple of details.

One thing that I found out in order to make it work is that the scripts seem to need a character setup a lot like Lurpz. I didnt have a seperate CollisionEmitter child object on mine, and also realised that I needed to lock the position and rotation (except z) axis on that CollisionEmitter in order to get it to work.
Maybe thats a common setup for everyone, not sure.

Thanks again!

Manu

.

Yeah, that’s how I did it. The Lerpz example is just something I put out so people can try out a working example instead of figuring it out themselves. The whole thing was much better integrated into my game, putting it into the Lerpz tutorial was just a quick effort. As a result there are a few issues, but they have to be ironed out specifically for the scripts you are using. The controls for Lerpz work, but they are not very good, thus I assumed everyone would have their own code, so there was no point investing time into something that won’t be of use to anyone. Except those who intend to ship their game with Lerpz’s controls, but you really shouldn’t do that.

Any chance this would work as a grappling hook?