BetterCharacterControl Slopes

I saw in the Character tutorial at https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:walking_character
It mentions the settings available for CharacterControl

I have a terrain which has raised and lowered areas where it seems my character should be able to walk over but seems to get stuck, I am using BetterCharacterControl

I noticed in the CharacterControl tutorial it has a field

setMaxSlope(1.5f) How steep the slopes and steps are that the character can climb without considering them an obstacle. Higher obstacles need to be jumped. Vertical height in world units.

Is there an equivalent way of getting the same functionality from BetterCharacterControl?

Thanks :slight_smile:

Hi there,

I’m also trying to use the BetterCharacterControl (moving from the regular CharacterControl now that the nightly builds are accessible)

Do you know how to get the physics location of the BetterCharacterControl?

I’m trying to attach my camera to it.

I just use node.getWorldTranslation() to update a local position value in my entity system for those controls
This has the advantage of working for all physics control types so you dont need to tailor for different types

you should be able to attach the camera to the node in the usual way?

Just noticed that there is an identical query on StackOverflow for this as well as multiple forum posts. Seems like no answer or fix for this in over a year? so we should basically not use this charactercontrol if the terrain isn’t flat it seems yay ;(

http://hub.jmonkeyengine.org/forum/topic/setmaxslope-for-bettercharactercontrol/

http://stackoverflow.com/questions/16782375/how-to-improve-character-control-for-my-3d-game/16988342#16988342

Somebody posted some code to extend the BetterCharacterControl in the forum but it basically made the whole character “kinetic”. Generally I agree, by now somebody could have easily posted some improvements. Oh well.

I wanted this functionality and tried it for the ninja with the quake level. I could extend the BetterCharacterControl but I did not know what to do in the update method. I would like it to work but I still don’t know exactly how. The BetterCharacterControl is better since it has built in collision detection. But I didn’t solve how to walk slopes or stairs. You can see how my BetterCharacterControl is looking and that collision detection is working.
[video]http://www.youtube.com/watch?v=aLEaNT8xvS0[/video]

I’ve been experimenting trying to solve this but no luck yet will post if I find something that works

So far I’ve tried storing the last position and if the current position = last position and we have a movement vector then assume were stuck and try incrementing the height of the movement vector

Unfortunately this seemed to trigger constantly and didn’t actually affect the height anyway

I think i might have something screwy with my variables because the logic seems sound

Looks something like this atm

[java]if(Utils.Utils.Vector3fEquals(p.getPosition(), node.getWorldTranslation()) && m.hasMovement())
{
BetterCharacterControl bcc = node.getControl(BetterCharacterControl.class);
if(bcc != null)
{
m.extraY += 0.01;
System.out.println(“Movement stagnating incrementing height to “+m.GetMovement()+” m.extraY”+m.extraY);
}

            }[/java]
1 Like

Hi guys, I see questions like this a lot. I wrote a method for the BetterCharacterControl class that tests the angle of the slope the Character is trying to walk into for my own project i’m working on. I’m not sure about changing the allowed stepHeight of the character but this method seems to work well for terrains and slopes.

[java]
protected void checkSlope() {

    if(walkDirection.length()>0)
    {
    List<PhysicsRayTestResult> results = space.rayTest(rigidBody.getPhysicsLocation(),
            rigidBody.getPhysicsLocation().add(walkDirection.normalize().mult(getFinalRadius())));
    
     for (PhysicsRayTestResult physicsRayTestResult : results)
     {
            float angle = physicsRayTestResult.getHitNormalLocal().normalize().angleBetween(physicsRayTestResult.getHitNormalLocal().setY(0).normalize());
         
            //System.out.println(Math.abs(angle*FastMath.RAD_TO_DEG-90));
            if(Math.abs(angle*FastMath.RAD_TO_DEG-90)>maxSlope)
            {
            tooSteep = true;
            return;
            }
     }
      tooSteep = false;
    }

[/java]

Where tooSteep is a boolean that is flagged if the character is trying to walk into a slope that is higher than the float maxSlope in degrees.
Hopefully this will be useful to someone.

2 Likes

Thanks. The tooSteep does change from false to true when my character tries to walk stairs, but I still don’t know how to manipulate the character to actually be able to walk the stairs / slope when the variable tooSteep = true. I used your code like this and the flag does change when the char walks a slope or stairs.

[java]public class GameCharControl extends BetterCharacterControl {
protected Vector3f lastlocation = new Vector3f();
boolean tooSteep = false;
float maxSlope = 5;

public GameCharControl(float x, float y, float z) {
	super(x, y, z);
}

@Override
public void update(float tpf) {
	super.update(tpf);
	checkSlope();
	System.out.println("tooSteep " + tooSteep);
	//System.out.println("lastlocation " + lastlocation);

	if (location.equals(lastlocation)) {
		//System.out.println("update2");
		this.setHeightPercent(1);
	}
	rigidBody.getPhysicsLocation(location);
	applyPhysicsTransform(location, rotation);
	lastlocation = location;
}

protected void checkSlope() {

    if(walkDirection.length() >0)
    {
    List<PhysicsRayTestResult> results = space.rayTest(rigidBody.getPhysicsLocation(),
            rigidBody.getPhysicsLocation().add(walkDirection.normalize().mult(getFinalRadius())));
    
     for (PhysicsRayTestResult physicsRayTestResult : results)
     {
            float angle = physicsRayTestResult.getHitNormalLocal().normalize().angleBetween(physicsRayTestResult.getHitNormalLocal().setY(0).normalize());
         
            //System.out.println(Math.abs(angle*FastMath.RAD_TO_DEG-90));
            if(Math.abs(angle*FastMath.RAD_TO_DEG-90)>maxSlope)
            {
            tooSteep = true;
            return;
            }
     }
      tooSteep = false;
    }

}
}[/java]

Can you tell me what change to make so that the character actually will walk the stairs / slope when tooSteep = true ?

@vvishmaster said: Hi guys, I see questions like this a lot. I wrote a method for the BetterCharacterControl class that tests the angle of the slope the Character is trying to walk into for my own project i'm working on. I'm not sure about changing the allowed stepHeight of the character but this method seems to work well for terrains and slopes.

[java]
protected void checkSlope() {

    if(walkDirection.length()>0)
    {
    List<PhysicsRayTestResult> results = space.rayTest(rigidBody.getPhysicsLocation(),
            rigidBody.getPhysicsLocation().add(walkDirection.normalize().mult(getFinalRadius())));
    
     for (PhysicsRayTestResult physicsRayTestResult : results)
     {
            float angle = physicsRayTestResult.getHitNormalLocal().normalize().angleBetween(physicsRayTestResult.getHitNormalLocal().setY(0).normalize());
         
            //System.out.println(Math.abs(angle*FastMath.RAD_TO_DEG-90));
            if(Math.abs(angle*FastMath.RAD_TO_DEG-90)>maxSlope)
            {
            tooSteep = true;
            return;
            }
     }
      tooSteep = false;
    }

[/java]

Where tooSteep is a boolean that is flagged if the character is trying to walk into a slope that is higher than the float maxSlope in degrees.
Hopefully this will be useful to someone.

1 Like

@niklasro that method i posted is supposed to be used for limiting the characters movements up slopes such as steep mountains in the terrain. I gave some thought to navigating steps and large vertical slopes and i have something you can try i have not tested it.

[java]
checkSlope();
checkWalkableStep();
if(tooSteep&&isWalkableStep)
{
rigidBody.setLinearVelocity(rigidBody.getLinearVelocity().add(0, 3, 0));
}[/java]

[java] private void checkWalkableStep()
{
if(walkDirection.length()>0)
{
if(tooSteep)
{
List<PhysicsRayTestResult> results = space.rayTest(rigidBody.getPhysicsLocation().add(0,maxStepHeight,0),
rigidBody.getPhysicsLocation().add(0,maxStepHeight,0).add(walkDirection.normalize().mult(getFinalRadius())));

            for (PhysicsRayTestResult physicsRayTestResult : results)
            {
                isWalkableStep = false;
                return;
            }
        
            isWalkableStep = true;
            return;
        }
    }
    
    isWalkableStep = false;
}[/java] 

Basically if the character encounters a vertical slope that is too steep then check if the step is lower than the maxStepHeight and if it is add an upward component to the character velocity.
Like i said i haven’t tested this code but if you play around with the values you may be able to make it work this way :slight_smile:

1 Like

Thanks, that kind of works but not always. I don’t know if I initiated my character correct but it did it this way.

[java]
private void createNinja() {
ninjaNode = (Node) assetManager
.loadModel(“Models/Ninja/Ninja.mesh.xml”);
ninjaNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
ninjaNode.setLocalScale(0.06f);
ninjaNode.setLocalTranslation(new Vector3f(55, 3.3f, -60));
ninjaControl = new GameCharControl(1.6f, 4f, 5f);// (2, 4, 0.5f);
// radius (meters), height (meters),
// gravity (mass)
ninjaControl.setJumpForce(new Vector3f(0, 100, 0));
ninjaNode.addControl(ninjaControl);
rootNode.attachChild(ninjaNode);
bulletAppState.getPhysicsSpace().add(ninjaControl);
getPhysicsSpace().add(ninjaControl);
animationControl = ninjaNode.getControl(AnimControl.class);
animationChannel = animationControl.createChannel();
}[/java]

Then the ninja could walk stairs and slopes if it used the CharacterControl but I switched to BetterCharacterControl and I don’t want to go back so I’m trying to changes and it works but only sometimes. I use the ninja model and the q3 level and the variables tooSteep and isWalkableStep change to correct values when debugged but not always, for example if the ninja walks over a step to the side it becomes difficult to walk steps and it slides too much walking downward. The code for the subclassed BetterCharacterControl that I tried is

[java]public class GameCharControl extends BetterCharacterControl {

private static Vector3f[] upAxisDirection = new Vector3f[] {
		new Vector3f(1.0f, 0.0f, 0.0f), new Vector3f(0.0f, 1.0f, 0.0f),
		new Vector3f(0.0f, 0.0f, 1.0f), };

boolean tooSteep = false;
boolean isWalkableStep = false;

float maxSlope = 5;
float maxStepHeight = 2;

protected float maxSlopeRadians; // Slope angle that is set (used for
									// returning the exact value)
protected float maxSlopeCosine; // Cosine equivalent of m_maxSlopeRadians
								// (calculated once when set, for
								// optimization)

// protected float gravity;

public float getMaxSlope() {
	return maxSlope;
}

// public void setMaxSlope(float maxSlope) {
// this.maxSlope = maxSlope;
// }

public GameCharControl(float x, float y, float z) {
	super(x, y, z);
}

public void setMaxSlope(float slopeRadians) {
	maxSlopeRadians = slopeRadians;
	maxSlopeCosine = (float) Math.cos((float) slopeRadians);
}

@Override
public void update(float tpf) {
	super.update(tpf);
	checkSlope();
	checkWalkableStep();
	System.out.println("tooSteep " + tooSteep + "isWalkableStep "
			+ isWalkableStep);		
	if (tooSteep &amp;&amp; isWalkableStep) {
		System.out.println("changing ");
		 rigidBody.setLinearVelocity(rigidBody.getLinearVelocity().add(0,
		 9500, 0));
	}

		}

protected void checkSlope() {
	if (this.getWalkDirection().length() &gt; 0) {

		List&lt;PhysicsRayTestResult&gt; results = space.rayTest(
				rigidBody.getPhysicsLocation(),
				rigidBody.getPhysicsLocation().add(
						walkDirection.normalize().mult(getFinalRadius())));
		for (PhysicsRayTestResult physicsRayTestResult : results) {
			float angle = physicsRayTestResult
					.getHitNormalLocal()
					.normalize()
					.angleBetween(
							physicsRayTestResult.getHitNormalLocal()
									.setY(0).normalize());
			System.out.println("angle " + angle);
			if (Math.abs(angle * FastMath.RAD_TO_DEG - 90) &gt; maxSlope) {
				tooSteep = true;
				return;
			}
		}
		tooSteep = false;
	}

}

private void checkWalkableStep() {
	if (walkDirection.length() &gt; 0) {
		if (tooSteep) {
			float maxStepHeight = 100;
			List&lt;PhysicsRayTestResult&gt; results = space
					.rayTest(
							rigidBody.getPhysicsLocation().add(0,
									maxStepHeight, 0),
							rigidBody
									.getPhysicsLocation()
									.add(0, maxStepHeight, 0)
									.add(walkDirection.normalize().mult(
											getFinalRadius())));

			for (PhysicsRayTestResult physicsRayTestResult : results) {
				isWalkableStep = false;
				return;
			}

			isWalkableStep = true;
			return;
		}
	}

	isWalkableStep = false;
}

}[/java]
I’d like to improve it with a maxSlope that works in all cases but it’s too difficult.

1 Like

@niklasro i changed a few things and it does seem to work in most cases now. You will have to edit all the values to your needs though.

Heres a link to the testProject i used for testing: http://rapidshare.com/share/10956F5CD0E7BFCB287E4EFAA64B23A8

and here’s the class. You might have to neaten it up a bit:

[java]package mygame;

import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.PhysicsRayTestResult;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.objects.PhysicsRigidBody;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import java.util.List;

public class GameCharControl extends BetterCharacterControl{

boolean tooSteep = false;
boolean isWalkableStep = false;
boolean helpingUpStep = false;

float maxSlope = 85;
float maxStepHeight = 2f;


public GameCharControl(float radius, int height, int mass) 
{
    this.radius = radius;
    this.height = height;
    this.mass = mass;
    rigidBody = new PhysicsRigidBody(getShape(), mass);
    //jumpForce = new Vector3f(0, mass * 5, 0);
    rigidBody.setAngularFactor(0);
}


@Override
public void prePhysicsTick(PhysicsSpace space, float tpf) {
super.prePhysicsTick(space, tpf);

checkSlope();
checkWalkableStep();

 System.out.println("TooSteep = "+tooSteep+", isWalkableStep = "+isWalkableStep);
 if(tooSteep &amp;&amp; isWalkableStep)
 {
    rigidBody.setLinearVelocity(rigidBody.getLinearVelocity().add(0, 5, 0));
    helpingUpStep = true;
    return;
 }
 
 if(helpingUpStep)
 {
     helpingUpStep = false;
     rigidBody.setLinearVelocity(rigidBody.getLinearVelocity().setY(0));
 }

}


protected void checkSlope() {
    if (this.getWalkDirection().length() &gt; 0) {

        List&lt;PhysicsRayTestResult&gt; results = space.rayTest(
                rigidBody.getPhysicsLocation().add(0, 0.01f, 0),
                rigidBody.getPhysicsLocation().add(
                        walkDirection.setY(0).normalize().mult(getFinalRadius())).add(0, 0.01f, 0));
        for (PhysicsRayTestResult physicsRayTestResult : results) {
            float angle = physicsRayTestResult
                    .getHitNormalLocal()
                    .normalize()
                    .angleBetween(
                            physicsRayTestResult.getHitNormalLocal()
                                    .setY(0).normalize());
            
            System.out.println(Math.abs(angle * FastMath.RAD_TO_DEG - 90));
           
            if (Math.abs(angle * FastMath.RAD_TO_DEG - 90) &gt; maxSlope &amp;&amp; !physicsRayTestResult.getCollisionObject().equals(rigidBody))
            {
                tooSteep = true;
                return;
            }
        }
        
    }
    tooSteep = false;

}

private void checkWalkableStep() {
    if (walkDirection.length() &gt; 0) {
        if (tooSteep) {
           
            List&lt;PhysicsRayTestResult&gt; results = space
                    .rayTest(
                            rigidBody.getPhysicsLocation().add(0,
                                    maxStepHeight, 0),
                            rigidBody
                                    .getPhysicsLocation()
                                    .add(0, maxStepHeight, 0)
                                    .add(walkDirection.normalize().mult(
                                            getFinalRadius())));

            for (PhysicsRayTestResult physicsRayTestResult : results) {
                isWalkableStep = false;
                return;
            }

            isWalkableStep = true;
            return;
        }
    }

    isWalkableStep = false;
}

public Vector3f getPos()
{
return rigidBody.getPhysicsLocation().add(0, 0.01f, 0);
}

public Vector3f getDir()
{
return rigidBody.getPhysicsLocation().add(walkDirection.setY(0).normalize().mult(getFinalRadius())).add(0, 0.01f, 0);
}

}[/java]

2 Likes

Good work guys, I’ll keep this thread bookmarked for the next iteration of the Control.

2 Likes

@vvishmaster Why are the second and third parameters of your constructor ints instead of floats, and why don’t you call the super constructor?

sorry to necro-post but is there a working copy of this lying around the forum seems to have corrupted it.

edit I just googled and I see that I can find the meanings of the corruption still if there is a clean copy available…

edit fixed