- Home /
Climb Wall character controller
Hey guys, i have this simple script i found for my charater and would like to know how can i make it climb a wall up down sideways on controller collision. Something like function
OnControllerColliderHit (hit : ControllerColliderHit) {
if(hit.transform.tag == "wall1" && Input.GetAxis("Vertical") > .1) {
transform.LookAt(Vector3(hit.transform.position.x, transform.position.y, hit.transform.position.z));
do stuff change gravity, vector3..
And then when enters a trigger the controls reset to the initial state
Any help would be appreciated since im new to programming and really need this to finish my project
var animation1: String = "idle"; var animation2: String = "walk"; var animation3: String = "run"; var animation4: String = "rotateAround";
var walk : float = 1.0; var run : float = 4.0; private var walkSpeed : float = 1.0; private var gravity = 100.0; private var moveDirection : Vector3 = Vector3.zero; private var charController : CharacterController;
function Start() { charController = GetComponent(CharacterController); animation.wrapMode = WrapMode.Loop; }
function Update () { if(charController.isGrounded == true) { if(Input.GetAxis("Vertical") > .1) { if(Input.GetButton("Fire1")) { animation.CrossFade(animation3); walkSpeed = run; } else { animation[animation2].speed = 1; animation.CrossFade(animation2); walkSpeed = walk; } } else if(Input.GetAxis("Vertical") < -.1) { animation[animation2].speed = -1; animation.CrossFade(animation2); walkSpeed = walk; } else { // Plays Idle animation.CrossFade(animation1); }
// Create an animation cycle for when the character is turning on the spot
if(Input.GetAxis("Horizontal") > .1 && !Input.GetAxis("Vertical"))
{
animation[animation4].speed = 1;
animation.CrossFade(animation4);
}
// Nota esta a inverter animacao fica melhor uma nova animacao
if(Input.GetAxis("Horizontal") < -.1 && !Input.GetAxis("Vertical"))
{
animation[animation4].speed = -1;
animation.CrossFade(animation4);
}
transform.eulerAngles.y += Input.GetAxis("Horizontal");
// Calculate the movement direction (forward motion)
moveDirection = Vector3(0,0, Input.GetAxis("Vertical"));
moveDirection = transform.TransformDirection(moveDirection);
}
moveDirection.y -= gravity * Time.deltaTime;
charController.Move(moveDirection * (Time.deltaTime * walkSpeed));
}
Answer by Mickydtron · Dec 20, 2010 at 09:24 PM
What you could do is give the climbable walls or areas a box collider which set as a trigger. Then you could have on your character
var climbing = false;
OnTriggerEnter(){
climbing = true;
//whatever other changes you want
}
OnTriggerExit(){
climbing = false;
}
Then in your Update() function, you could have an if statement to check if we are climbing, and if we are, move in one way, and if not, then move as you already have it moving. Probably the only changes we want to what you have are to ignore gravity (remove the line moveDirection.y -= gravity * Time.deltaTime;
) and to map forward movement (Input.GetAxis("Vertical")
) to upward movement:
moveDirection = Vector3(0, Input.GetAxis("Vertical"), 0);
It's not perfect by a long shot, but as a quick and dirty way of climbing a wall, it should be sufficient.
EDIT:
Ok, here is the full script that I came up with, based on your original script. It is commented heavily, which I used to explain the whys of what I did, so that if you need to change it you can understand what each part does.
This script requires a kinematic rigidbody, but it will automatically attach one to your gameObject, so don't worry about that. It also requires a box collider set to act as a trigger that is just slightly in front of the walls which are climbable. The trigger needs to have the tag set to "climbableWall" and it is very important. Without it, this won't be any different from what you have now.
I also cleaned a few things up which were unrelated to climbing, but I commented all of them, and tried to explain my reasoning.
So without further rambling, here it is:
//require the CharacterController and a Rigidbody (for trigger) @script RequireComponent(CharacterController) @script RequireComponent(Rigidbody)
//not how I would have done the animation assignment, but perfectly functional var animation1: String = "idle"; var animation2: String = "walk"; var animation3: String = "run"; var animation4: String = "rotateAround";
//renamed walkSpeed to moveSpeed for clarity //also added our rigidbody var which is required to trigger OnTriggerEnter events var walk : float = 1.0; var run : float = 4.0; private var moveSpeed : float = 1.0; private var gravity = 100.0; private var moveDirection : Vector3 = Vector3.zero; private var charController : CharacterController; private var body : Rigidbody; private var climbing : boolean;
function Start() { //assign our variables which link to other components, and set animation mode charController = GetComponent(CharacterController); body = GetComponent(Rigidbody); body.isKinematic=true; animation.wrapMode = WrapMode.Loop; }
function Update () { //We call Input.GetAxis a lot, so let's just call them once, and store the answer var h : float = Input.GetAxis("Horizontal"); var v : float = Input.GetAxis("Vertical");
//reset our moveDirection variable each time, just to be safe
moveDirection = Vector3.zero;
if(climbing)
{
//we are climbing, so we use different movement rules
//we shall use much of the same structure that is laid out below, because it is a fundamentally good structure
//this is not really optimized, and there is probably a more compact way of accomplishing the same goal, but this is easy to follow.
//always use the walk speed
moveSpeed = walk;
if(v > .1)
{
//pushing forward
moveDirection.y = v * moveSpeed;
//in addition to moving updwards, let's push forwards
//if we are still on the wall, the wall will push back and this will not matter
//if we reach the top of the wall, this will allow us to move to standing on top of it, instead of getting stuck hovering over the trigger
moveDirection.z = v * moveSpeed;
//perhaps also a climbing animation?
}
else if(v < -.1)
{
//pushing backwards
//this is actually the case when we care if we are grounded
if(charController.isGrounded)//shorthand for == true, it's a boolean itself, after all
{
//we are already at the bottom of the wall, and should move backwards, away from the wall
moveDirection.z = v * moveSpeed;
}
else
{
//we are not at the bottom, and should move downwards
moveDirection.y = v * moveSpeed;
}
}
//now we do the same thing (more or less) with the horizontal axis, so that we can move sideways.
if(h > .1)
{
//we are pushing to the right nontrivially
moveDirection.x = h * moveSpeed;
}
else if(h < -.1)
{
//we are pushing left nontrivially
moveDirection.x = h * moveSpeed;
//we have these two separate instead of just comparing Mathf.Abs(h) > .1 in case you want to change the animation for right vs left
//if you don't add anything else, you might as well replace this if else with a single if(Mathf.Abs(h) > .1){ moveDirection.x = h * moveSpeed}
}
//it is actually possible to get here without triggering any of the above movement conditions.
//But if that is the case, then we are on a climbable wall and should not do anything
//if you do want to do something if we aren't moving, like play a wall idle animation, then let's use:
//if(moveDirection == Vector3.zero)
//{
//
//}
}
else
{
//we are not climbing, so see if we are on the ground or in the air
if(charController.isGrounded == true)
{
//if we are grounded, we process input
if(v > .1)
{
//if we are pushing forward in a nontrivial manner
if(Input.GetButton("Fire1"))
{
//if we are pressing the Fire1 button, which is apparently the run button
animation.CrossFade(animation3);
moveSpeed = run;
}
else
{
//we are not pressing the run button, so just walk (forward)
animation[animation2].speed = 1;
animation.CrossFade(animation2);
moveSpeed = walk;
}
}
else if(v < -.1)
{
//we are not pushing forward, so look if we are pushing backwards in a nontrivial manner
//we don't care about the run button, just walk
animation[animation2].speed = -1;
animation.CrossFade(animation2);
moveSpeed = walk;
}
else
{
// Plays Idle
//we were not pushing significantly in either direction
animation.CrossFade(animation1);
}
// Create an animation cycle for when the character is turning on the spot
//I restructured this collection of if statements. There wasn't anything wrong with how you had it, but changes I have made necessitated a change
//and I think it's prettier this way.
if(Mathf.Abs(v) < .1)
{
//if we are inside of our deadzone:
if(h > .1)
{
animation[animation4].speed = 1;
animation.CrossFade(animation4);
}
// Nota esta a inverter animacao fica melhor uma nova animacao
//the above comment means nothing to me :)
if(h < -.1)
{
animation[animation4].speed = -1;
animation.CrossFade(animation4);
}
}
//transform.eulerAngles.y += Input.GetAxis("Horizontal");
//I took out the above line because, from the script reference:
//Don't increment them, as it will fail when the angle exceeds 360 degrees. Use Transform.Rotate instead.
transform.Rotate(0, h ,0);
//we rotate around the y axis, but with less failure when we cross from 360 to 0
// Calculate the movement direction (forward motion)
//Let's apply the movement scalar here instead of after gravity, so that gravity is not effected by it.
moveDirection = Vector3(0,0, v * moveSpeed);
moveDirection = transform.TransformDirection(moveDirection);
//now on to apply gravity (obey gravity, it's the law)
}
//we just exited the if(grounded) loop, so if we are not grounded, then all we do is apply gravity
//and then apply the motion to the controller
//gravity is here because we only want to apply it if we are not climbing,
//but we don't care if we are grounded or not
moveDirection.y -= gravity * Time.deltaTime;
}
//we have just left the if(climbing) else structure, so all possible conditions will execute this next line, as it should be
charController.Move(moveDirection * Time.deltaTime);
}
function OnTriggerEnter(other : Collider) { //check to see what trigger we just set off. It is possible that we want to use other triggers than just our wall climbing one //Triggers are useful, after all. //we obviously need to match the tag on the climbable wall triggers to what we have here. if (other.tag == "climbableWall") { //ok, so we hit our climbable wall trigger climbing = true; //we should face the wall directly, to make sideways movement work properly. //this finds the point on the trigger that is closest to us and looks at it. transform.LookAt(other.ClosestPointOnBounds(transform.position)); //we will then level it out, leaving only the y rotation. the x shouldn't be necessary, but it doesn't hurt. transform.rotation.eulerAngles.z = 0; transform.rotation.eulerAngles.x = 0; } }
function OnTriggerExit(other : Collider) { //again, check which trigger we just left if(other.tag == "climbableWall") { climbing = false; } }
Hi $$anonymous$$ickydtron, just saw the script now thank you so much for the time and effort put in to this. It works exactly the way i wanted it. Again thank you so much for the help.
Answer by Jason B · Dec 22, 2010 at 07:16 PM
One thing that may solve your problem is looking into hit normals.
I recently applied some code to my own game that tracked the hit normals of my charactercontroller's collisions, and realized that by simply tracking the hit normals you can easily detect when you're pushing against a wall, which will eliminate the need to create specially tagged wall colliders and allow the character to climb up any valid, perfectly vertical (or whatever angle you want) wall. If you link the hit normals to a GUIText, you can easily debug them and learn what values to track.
I have no code for you to work off of, but I thought I would offer you the theory and see if you can do anything with it.
Once the hit normals detect you're pressing against a wall, you could then have your character mount the wall (and be in perhaps a "mounted" state). And one thought I had from there is that while "mounted" you can cast rays from 4 points above/below/left/right of the character aimed at the wall you're on to constantly check when there's no longer a climbable surface around you, and restricting movement in the corresponding fashion.
Sorry if that makes no sense, I tried to explain it the best I could. And also remember that this is primarily for a "climb anything" system that purely reacts to geometry, similar to something like Assassin's Creed, and may not be what you were after.
That would actually be a good approach, but I think it might be overkill here. Although this technique is getting added to my mental library of useful things.
Cool. Well even if I can't help the original guy, I'm glad to have given you a new addition to your library.
Answer by Paul 11 · Dec 22, 2010 at 11:39 AM
Thank you for the help. Removing the gravity line won't do the trick seems that it doesn't ignore it just by removing this line so i made a separate script and thought of using the gravity to climb up. The character goes up but pressing the opposite vertical axis he goes on the z axis and not the y. Also pressing the horizontal makes him rotate around because of the transform.eulerAngles.y. Also i had to remove the if(charController.isGrounded == true) witch is not good either. So using the gravity won't do the trick. Can you please make a sample code im really new to scripting
OnControllerColliderHit (hit : ControllerColliderHit) { if(hit.transform.tag == "ClimbWall" && Input.GetAxis("Vertical") > .1) { climbingWall = true; transform.LookAt(Vector3(hit.transform.position.x, transform.position.y, hit.transform.position.z)); transform.eulerAngles.y += hit.transform.position.y; someScript.moveDirection = Vector3(0,Input.GetAxis("Vertical"),0); someScript.gravity = -5; someScript.moveDirection.y = someScript.gravity * Time.deltaTime; } else if (hit.transform.tag == "ClimbWall" && Input.GetAxis("Vertical") < -.1) { someScript.gravity = 5; } else { someScript.gravity = 0; } }
function OnTriggerEnter (other : Collider) {
someScript.moveDirection = Vector3(0,0, Input.GetAxis("Vertical"));
someScript.gravity = 10;
someScript.moveDirection.y -= someScript.gravity * Time.deltaTime;
}
So the behavior you describe here is what you want it to do? I can't quite follow your train of thought. As far as gravity goes, do you have a rigidbody attached to your character? and if so, do you have the Is$$anonymous$$inematic property set to true? because if you have a non-kinematic rigidbody, then the physics engine would continue to apply gravity to it. I would recommend a kinematic rigidbody. So the desired behavior is: run into a ClimbWall, press forward to go up, press backwards to move backwards (away from wall), and if we press nothing, do not move at all. correct?
The player has a character controller only. I want the player to when collide with that wall the controllers change like you suggested and by pressig forward he goes up the wall, backwards he goes down the wall (y axis only), pressing right/left he goes left/right on the wall. And like you said if we press nothing he stops.
Are you committed to using OnControllerColliderHit? I really think that putting a trigger collider in front of our climbable walls would be a better solution. If you're ok with using triggers ins$$anonymous$$d of OnControllerColliderHit, then I can write up a script that does your movement for both ground and wall climbing. If you're really sure you want to use the controller collider hit, then I can try to do that, but I'm less confident that I could make it work. I am not a professional codemonkey, so I can't work all the miracles that some other people can.
I'm sorry for wasting your time with this. If you could make it with triggers then i would really appreciated. It doesn't need to be perfect.
It's not a waste of time. I'm a Computer Science student, so I do this sort of thing to help myself learn. I get to see interesting problems and feel good about helping people.