- Home /
A question about procedural ledge grabbing.
I'm making a procedural ledge grabbing script. This is what happens so far:
1st the code checks, if there is a wall nearby and if the character is in the air. Then it SphereCasts from 10m above the character down and checks if the character is close enough to the top of the surface to grab it.
Edit:
(should I move this to the forums, since it looks like this is more a discussion type question, instead of the regular Q->A type?)
I've fixed a huge chunk of the code, but in some cases it still doesn't work. These are the movements that work:
Diagonal movement and transitions to different angles.
Curved movement:
And the movements that kinda work:
Circular movement.... This is a though one. It works, but since the cylinder's base isn't a perfect circle, it's kinda jittery. I'd need to make a code just for smoothing out transitions between vertices.
And the thoughest one - cutting 90 degree edges. It sometimes works, sometimes doesn't, it's a problem with the player's forward axis (I think, anyways) not being properly aligned when it disconnects from the old ledge and connects to the new one, that's perpendicular to the old one.
Updated code. It does compile, but I didn't test it, because it's a stripped down version of the real lady, which I had to strip down, because it contains a lot of other stuff that'd just distract you from the problem (just joking, the real reason is because I don't wnat you to have my super algorithms that calculate and proove the real mass of photons! :evil:). DrawSphere is a custom method, that draws debug spheres (hurrdurr) :
#pragma strict
public var isGrabbingALedge : boolean = false;
private var lastLedgeDirection : Vector3;
private var isGrounded : boolean = false;
function Start () {
var prim : GameObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
sphereMesh = new prim.GetComponent.<MeshFilter>().sharedMesh;
Destroy(prim);
sphereMeshMaterial = new Material(Shader.Find("Diffuse"));
}
function Update () {
var hitInfo : RaycastHit;
isGrounded = Physics.SphereCast(collider.bounds.center,0.4,-transform.up,hitInfo,0.6);
var move = Vector3.zero;
move.z = Input.GetAxisRaw("Vertical");
move.x = Input.GetAxisRaw("Horizontal");
move = move.normalized * (isGrounded ? 1 : .3);
rigidbody.drag = isGrounded ? 2 : .5;
DetectLedge();
if(!isGrabbingALedge) {
rigidbody.velocity = move * 10;
if(Physics.CapsuleCast(transform.position,transform.position-transform.up,0.5,transform.TransformDirection(move),hitInfo,0.55))
move = Vector3.zero;
}
else {
rigidbody.velocity = lastLedgeDirection * -move.x * 10;
transform.localEulerAngles.y = Mathf.LerpAngle(transform.localEulerAngles.y,(Quaternion.LookRotation(Vector3.Cross(transform.up, lastLedgeDirection))).eulerAngles.y,Time.deltaTime*5);
}
if(Input.GetKeyDown(KeyCode.Space)
&& (isGrounded || isGrabbingALedge)) {
if(isGrabbingALedge) {
isGrabbingALedge = false;
ledgeDetectPause = 0.5;
}
rigidbody.velocity.y = 0;
rigidbody.AddForce(Vector3.up*10,ForceMode.Impulse);
}
}
public var ledgeDetectPause : float = 0;
function DetectLedge() {
if(ledgeDetectPause > 0) {
ledgeDetectPause -= Time.deltaTime;
return;
} else ledgeDetectPause = 0;
if(!isGrounded) {
var l = ~((1 << (LayerMask.NameToLayer("Player") ) | (1 << LayerMask.NameToLayer("Physics"))));
var colliders : Collider[] = Physics.OverlapSphere(transform.position,1, l);
if(colliders.Length > 0) {
DrawSphere(transform.position, 1, Quaternion.identity, Color.green);
var hit : RaycastHit;
Physics.Linecast(transform.position, colliders[0].transform.position,hit,l);
var point : Vector3 = hit.point;
if(Physics.SphereCast(point + transform.up * 10, 0.5, -transform.up, hit, 10, l)) {
var normal : Vector3 = hit.normal;
point = hit.point;
if(Physics.Raycast(transform.position, transform.forward, hit, 0.75, l)) {
DrawSphere(hit.point, 1, Quaternion.identity, Color.magenta);
if((normal.y <= 0.2 || normal.y >= 0.8)) {
var direction : Vector3 = Vector3.Cross(-hit.normal, normal);
DrawSphere(point, 1, Quaternion.identity, Color.green);
DrawSphere(point - direction, 1, Quaternion.identity, Color(0,0.5,1));
DrawSphere(point + direction, 1, Quaternion.identity, Color(0,0.5,1));
if(((point - transform.position).sqrMagnitude < 1 && rigidbody.velocity.y <= 0 && !isGrabbingALedge)) {
isGrabbingALedge = true;
lastLedgeDirection = direction;
var localPoint : Vector3 = transform.InverseTransformPoint(point);
localPoint.z -= collider.bounds.extents.z;
localPoint.y = 0;
localPoint.x = 0;
transform.position = transform.TransformPoint(localPoint);
rigidbody.velocity = Vector3.zero;
}
if(isGrabbingALedge && direction != lastLedgeDirection) {
lastLedgeDirection = direction;
}
}
else {
DrawSphere(point, 1, Quaternion.identity, Color.red);
isGrabbingALedge = false;
}
}
else
isGrabbingALedge = false;
}
else
isGrabbingALedge = false;
}
else
DrawSphere(transform.position, 1, Quaternion.identity, Color.red);
}
}
private var sphereMesh : Mesh;
private var sphereMeshMaterial : Material;
function DrawSphere (position : Vector3, size : float, rotation : Quaternion, color : Color) {
//if(!Application.isEditor) return;
var mat : Material = new Material(sphereMeshMaterial);
mat.color = color;
var mes : Mesh = Instantiate(sphereMesh);
var verts = mes.vertices;
for(var i = 0; i < verts.Length; i++)
verts[i] *= size;
mes.vertices = verts;
Graphics.DrawMesh(mes, position, rotation, mat, LayerMask.NameToLayer("Default"), Camera.main);
}
function FixedUpdate () {
if(isGrabbingALedge) // Don't apply gravity
return;
else //Apply gravity
rigidbody.AddForce(-Vector3.up * 9.81, ForceMode.Acceleration);
}
OLD PROBLEMS
Now the problems. To detect where the left and right hands are going to be, I do a Cross product of the player transform's forward direction and the normal, but this doesn't really work, because if the player rotates to the left or right, the spheres go off the edge.
Next problem is detecting if the SphereCast hit an edge or a surface. And the last problem is with SphereCast itself, because the normals don't always return just what I'd want and what I'd need for it properly function is CylinderCast, which doesn't exist.
And just to be clear, this isn't a write-code-for-me question, I'd just like to discuss different ways of procedural ledge grabbing and find a nice solution. But if you feel like writing up some code I won't be mad ;).
-- David
Oh c'mon, 50 views and not even a comment? That's rude! :P
We are awestruck by your skills!
Should the player be able to rotate while holding a ledge? If not, when grabbing the ledge, subtly rotate the player so they are perpendicular to the ledge they are grabbing. If so, you may need to have the hand object separate to the player object, keep the hand rotated perpendicular to the ledge.
That's all I've got, sorry. This is some excellent work, well done so far, upvote for nice question =]
Thanks for the positive comment, much appreciated! The hands are gonna be positioned to where the blue spheres are trough I$$anonymous$$, so the animations don't look awkward when grabbing diagonal surfaces. I've also updated my question, since the problems stated in it are already fixed, but as it always has and will be, new bugs were sent to new places where one'd never think they'd ever be and so the new problems were added.
Thanks, means a lot ;). I'll probably just move this to forums, because starting a discussion here failed.
Your answer
Follow this Question
Related Questions
Finding a point that is "visible" to two objects 2 Answers
Using Spherecast to identify objects? 1 Answer
SphereCast misses but RayCast and OverlapSphere don't 1 Answer
Car AI help 0 Answers
SphereCast and CheckSphere failing to catch collisions. 1 Answer