Search Unity

Project involving wall-running and ledge grabbing

Discussion in 'Editor & General Support' started by MD_Reptile, Jan 31, 2012.

  1. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    ***See newest post below***

    I have been working towards developing a third person controller, with the helpful tutorials of 3d Buzz, and so far I have learned quite a bit and made it most of the way through the creating the control system for my project, butttt....

    I need to implement wall running and ledge grabbing. I know that as I make it to the later parts of the tutorials on 3d Buzz they show you one method to implement the ledge grabbing, however it uses triggers to assign where to do these ledge grabs at. While that is nice, and certainly a viable way to do it.... I would hate going through the creation of large, intricate levels, having to add triggers all over the place to grabs ledges, and plus, when (if) I can get wall running going, that means I would have to have either have separate triggers to control wall running and ledge grabbing, creating a huge mess of triggers everywhere, or I could forget about triggers, and use the idea which is bouncing around in my head for wall running (while still using triggers for climbing), which would be raycasting around the player to detect walls and such, and I think it could lead to all sorts of problems, like when a player hits the wall and starts running, then makes it into a trigger to climb a ledge, it would probably be buggy trying to jump between either wall running or using the trigger to climb.

    So unity community - help me figure this out - how does one go about making both wall running and ledge grabbing (in a fashion similar to say, assassins creed or prince of persia or overgrowth) without using triggers whatsoever? As you might notice in overgrowth, they have a system to use invisible objects up above the players head, that draws an invisible block down to the ledges in front of the player, and if its within reach, allows the player to climb, and also a sphere around the player which detects walls, and allows wall running if within the sphere, thus eliminating triggers. (see http://www.youtube.com/watch?v=GFu44oeLYPI if you dont know what im talking about)

    I have seen some users on this forum, with posts showing methods they tried, such as using a downward raycast from above the player, which detects a "climb-able" ledge, and raycasts around the player which detect "run-able" walls, but it seems that most of them had ran into problems getting that system finished, or atleast didnt share with the community....

    Now here is an image to give you an idea what im thinking:


    so how I see it in my head is do a raycast from above his head a couple feet detecting the ledge to be possible to climb, and if it is hitting a flat ledge there (maybe would require more than one ray to determine if its "flat") then have the player be able to climb if he hits jump....and as far as the rays from his body (yes i put that all in one image to get this across haha) they would hit the wall, and if the wall is flat and within range, he can hit jump and run up the wall as well, so in a situation where he has both a climbable ledge and a runnable wall in range, the climb would take control and disable the wall run until he lands at the top of the ledge, re-enabling the wall running ability, see.

    I need to know how crazy my idea is, and if there is another route which might be better (please dont say triggers...please? :p )
     
    Last edited: Feb 8, 2012
  2. ChaosWWW

    ChaosWWW

    Joined:
    Nov 25, 2009
    Posts:
    470
    I can't say too much on the topic, but I actually tried this before and used a method very similar to what you are proposing and it worked pretty well from what I recall. When holding the "grab" button a line was cast down and up, and if the line cast down was hit and the line cast up wasn't too small, it stopped the player's movement and allowed them to press forward to climb up, assuming the ray cast up was big enough for that. The player could still rotate while grabbing which was a problem. If I were to update that script, I'd have it so the player has to be perpendicular to the wall and disable player rotation while grabbing.

    But in other words, no, your idea isn't crazy. Try it and see if it works, because it worked for me, although I didn't test it out for very long.

    EDIT: I imagine wall running can't be too difficult. Say "if jumping and line cast to the left or right is equal to distance, do wall run for x time then disable wall run" pretty much.
     
    Last edited: Feb 1, 2012
  3. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    ***See newest post***
     
    Last edited: Feb 8, 2012
  4. dasbin

    dasbin

    Joined:
    Jan 14, 2012
    Posts:
    261
    Sounds like a good idea. Two potential options for detecting the ledge:
    1) Use two or more overhead raycasts, coming from the same point but shooting at different angles. The difference in length between the raycasts will determine if a ledgr is flat.

    2) Use a single raycast to detect the ledge. Make it short enough to only return a result when the player is close enough to the ledge. Make a "ledges" layer and assign all your grabbable ledges to it. Use LayerMask on your raycast to only hit Ledges. I think that's what I would do.
     
  5. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    thanks for the suggestions! Here is my current script which I have doing some of the things I need

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class TP_Raycast : MonoBehaviour
    5. {
    6.     public static TP_Raycast Instance;
    7.  
    8.     //Creates a ray and float for locking axis
    9.     private Ray ray;
    10.     private float posLock = 0f;
    11.  
    12.     //Creates a RaycastHit
    13.     private RaycastHit hit = new RaycastHit();
    14.  
    15.     //Get this GameObject's transform
    16.     private Transform playerTrans;
    17.  
    18.     void Awake()
    19.     {
    20.        //get this Transform
    21.        playerTrans = this.GetComponent<Transform>();
    22.     }
    23.  
    24.     // Update is called once per frame
    25.     void Update ()
    26.     {
    27.        if(!TP_Controller.CharacterController.isGrounded)
    28.        {
    29.          //Ray to the left of player
    30.          //recreate the ray every frame AND draw debug line for the ray
    31.          ray = new Ray(playerTrans.position, transform.TransformDirection(Vector3.left));
    32.          Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.left), Color.green);
    33.  
    34.          //Casts a ray to see if something has hit it
    35.          if(Physics.Raycast(ray.origin, ray.direction, out hit, 1))
    36.          {
    37.           //var to hold hit distance
    38.           var rayDist = hit.distance;
    39.           //Collision has happened, print the distance and name of the object into the console
    40.           Debug.Log(hit.collider.name);
    41.           Debug.Log(hit.distance);
    42.           if(rayDist > 0)
    43.           {
    44.               if(rayDist < 1)
    45.               {
    46.                  SnapToWalls();
    47.               }
    48.           }
    49.          }
    50.        }
    51.        else
    52.        {
    53.          //the ray isn't colliding with anything
    54.          Debug.Log("none");
    55.          //reset gravity
    56.          TP_Motor.Gravity = 21f;
    57.          TP_Motor.Instance.CanSnapAlign = true;
    58.          Debug.Log("CanSnapAlign is enabled");
    59.        }
    60.     }
    61.  
    62.     void SnapToWalls()
    63.     {
    64.        //Stop my TP_Motor from aligning the character to the camera rotation
    65.        TP_Motor.Instance.CanSnapAlign = false;
    66.        Debug.Log("CanSnapAlign is Disabled");
    67.        //change gravity
    68.        TP_Motor.Gravity = 11f;
    69.        //then have the player forced forward somewhat (even if they dont push forward - more so if they do)
    70.        TP_Controller.CharacterController.Move(transform.TransformDirection(Vector3.forward) * Time.deltaTime * 2);
    71.        //and output blah into the console
    72.        Debug.Log("BLAH");
    73.        transform.Rotate(transform.rotation.x, posLock, transform.rotation.z);
    74.     }
    75. }
    76.  
    so right now, it will do some of my work, which is to have the player move forward (vector3.forward) if he is hitting something with his ray. it also manages to lower the gravity while he is in the air. This is so far so good.

    okay imagine this:

    If the players ray is hitting wall, and he is off the ground, then take raycasthit.point location, and move the player 1 unit (locally to that wall or object) to the right of that point, lock players rotation to face forwards (forwards being with his left side facing wall so he appears to be running along it yaknow) and then go back to normal when he either hits space, or is grounded again.

    now make my paragraph into code :D haha




    so this image is my player doing what I want to happen with this script, just if the ray collides and he jumps, its aligning like he is moving, which is 90 degrees right of the surface the ray is hitting, How can I lock him into that position?

    EDIT: pretty much got this working the way I wanted, its been heavily changed since this code post, but I ended up getting him going the right way with the hit normals, and got rays for the right and the front now, at this point im moving on the integrated my character and such!
     
    Last edited: Feb 11, 2012
  6. Essential

    Essential

    Joined:
    Sep 8, 2011
    Posts:
    265
    How are you going with this approach, Reptile? I am currently using triggers on ledges, but you're right about it being a lot of work to implement them on every ledge. Have been considering something using ray casting.
     
  7. KoldGames

    KoldGames

    Joined:
    Jan 30, 2014
    Posts:
    6
    Hey! I know this thread is old, but I just wanted to add on a post about how I am handling wall climbing. Basically, I cast a ray from the player to whichever direction the player is facing, and if I get a hit, I do the following:

    1. Send a ray from the hit point and up with a distance of 3.5 to check if any other object is above the surface the player is trying to climb onto, and if so, return.

    2. I check the hit's normal, and if hit.normal.y is equal to 1 or -1, return.

    3. I get a mesh filter component from hit.transform and use hit.triangleindex to get the 3 vertices of the triangle the ray hit, use hit.transform.TransformPoint on all three, put all 3 vertices position.y into a float array, use Mathf.Max to find the biggest y component in the float array and output it to a float called maxY, create a float called heightDiff which equals maxY - transform.position.y + [height of Capsule Collider], and check to see if heightDiff is less than 3 (height limit) and if so, add heightDiff to transform.position.y and move the player toward the camera's forward vector.

    Code (csharp):
    1.  RaycastHit hit;
    2.  
    3.         if (Physics.Raycast(new Ray(Camera.main.transform.position, Camera.main.transform.forward), out hit, 1))
    4.         {
    5.             if (Physics.Raycast(new Ray(hit.point, Vector3.up), 3.5f))
    6.                 return;
    7.  
    8.             if (hit.normal.y == 1 || hit.normal.y == -1)
    9.                 return;
    10.  
    11.             Mesh mesh = (hit.transform.GetComponent("MeshFilter") as MeshFilter).mesh;
    12.  
    13.             Vector3[] vert = new Vector3[3];
    14.             float[] yAxes = new float[3];
    15.  
    16.             for (int i = 0; i < 3; ++i)
    17.             {
    18.                 vert[i] = mesh.vertices[mesh.triangles[hit.triangleIndex * 3 + i]];
    19.                 vert[i] = hit.transform.TransformPoint(vert[i]);
    20.                 yAxes[i] = vert[i].y;
    21.             }
    22.  
    23.             float maxY = Mathf.Max(yAxes);
    24.  
    25.             float xDiff = Camera.main.transform.forward.x;
    26.             float heightDiff = maxY - transform.position.y + player.DefaultHeight;
    27.             float zDiff = Camera.main.transform.forward.z;
    28.  
    29.             Debug.Log(heightDiff);
    30.  
    31.             if (heightDiff < WallHeightLimit)
    32.                 transform.position += new Vector3(xDiff, heightDiff, zDiff);
    33.         }
    Hope this helps! :)
     
  8. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    Thanks KoldGames, next time I am working on a project that calls for it I'll test it out! Been ages since I was working on this little demo I had going, but still it is good to know how others approached the problem!

    Essential I never really polished it fully and moved on to other projects. Maybe one day here I'll upload a new version of that demo just to polish the animations and mechanics better now that I am better at C# scripting
     
  9. yanuaris

    yanuaris

    Joined:
    Oct 16, 2015
    Posts:
    61
    I'm also trying to work on this system...but it seems like your wallrunning is pretty realistic.

    Mine is just, "run on walls until button is unpressed"