4

Since I changed from the helper class CharacterControl to the new BetterCharacterControl I notice some improvements such as pushing other characters is working but my main character has started sliding over steps and can't climb higher steps.

enter image description here

I must jump the step above which is not the right way of playing, it should be just walking over. The old helper class CharacterControl had a default way of not sliding, just walking over steps and I think it can be corrected by altering the code where I create the main character.

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 BetterCharacterControl(2, 4, 0.5f);
    ninjaControl.setJumpForce(new Vector3f(6, 6, 6));

    ninjaNode.addControl(ninjaControl);
    rootNode.attachChild(ninjaNode);
    bulletAppState.getPhysicsSpace().add(ninjaControl);
    getPhysicsSpace().add(ninjaControl);
    animationControl = ninjaNode.getControl(AnimControl.class);
    animationChannel = animationControl.createChannel();
}

The complete code is

package adventure;

import com.jme3.system.AppSettings;
import java.io.File;

import com.jme3.renderer.queue.RenderQueue;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.AnimEventListener;
import com.jme3.animation.LoopMode;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.BlenderKey;
import com.jme3.asset.plugins.HttpZipLocator;
import com.jme3.asset.plugins.ZipLocator;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.input.ChaseCamera;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.MaterialList;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.BloomFilter;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.ogre.OgreMeshKey;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.input.MouseInput;

public class PyramidLevel extends SimpleApplication implements ActionListener,
        AnimEventListener {
    private Node gameLevel;
    private static boolean useHttp = false;
    private BulletAppState bulletAppState;
    private AnimChannel channel;
    private AnimControl control;
    // character
    private BetterCharacterControl goblinControl; 
    private BetterCharacterControl ninjaControl;
    private Node ninjaNode;
    boolean rotate = false;
    private Vector3f walkDirection = new Vector3f(0, 0, 0);
    private Vector3f viewDirection = new Vector3f(1, 0, 0);
    private boolean leftStrafe = false, rightStrafe = false, forward = false,
            backward = false, leftRotate = false, rightRotate = false;
    private Node goblinNode;
    Spatial goblin;
    RigidBodyControl terrainPhysicsNode;

    // animation
    AnimChannel animationChannel;
    AnimChannel shootingChannel;
    AnimControl animationControl;
    float airTime = 0;
    // camera
    private boolean left = false, right = false, up = false, down = false,
            attack = false;

    ChaseCamera chaseCam;
    private boolean walkMode = true;
    FilterPostProcessor fpp;
    private Spatial sceneModel;

    private RigidBodyControl landscape;

    public static void main(String[] args) {

        File file = new File("quake3level.zip");
        if (!file.exists()) {
            useHttp = true;
        }
        PyramidLevel app = new PyramidLevel();
        AppSettings settings = new AppSettings(true);
        settings.setTitle("Dungeon World");
        settings.setSettingsDialogImage("Interface/splash.png");
        app.setSettings(settings);
        app.start();
    }

    @Override
    public void simpleInitApp() {
        this.setDisplayStatView(false);
        bulletAppState = new BulletAppState();
        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
        stateManager.attach(bulletAppState);
        bulletAppState.setDebugEnabled(false);
        setupKeys();
        DirectionalLight dl = new DirectionalLight();
        dl.setColor(ColorRGBA.White.clone().multLocal(2));
        dl.setDirection(new Vector3f(-1, -1, -1).normalize());
        rootNode.addLight(dl);
        AmbientLight am = new AmbientLight();
        am.setColor(ColorRGBA.White.mult(2));
        rootNode.addLight(am);

        if (useHttp) {
            assetManager
                    .registerLocator(
                            "http://jmonkeyengine.googlecode.com/files/quake3level.zip",
                            HttpZipLocator.class);
        } else {
            assetManager.registerLocator("quake3level.zip", ZipLocator.class);
        }

        // create the geometry and attach it
        MaterialList matList = (MaterialList) assetManager
                .loadAsset("Scene.material");
        OgreMeshKey key = new OgreMeshKey("main.meshxml", matList);
        gameLevel = (Node) assetManager.loadAsset(key);
        gameLevel.setLocalScale(0.1f);
        gameLevel.addControl(new RigidBodyControl(0));
        getPhysicsSpace().addAll(gameLevel);
        rootNode.attachChild(gameLevel);
        getPhysicsSpace().addAll(gameLevel);
        createCharacters();
        setupAnimationController();
        setupChaseCamera();
        setupFilter();
    }

    private void setupFilter() {
        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
        BloomFilter bloom = new BloomFilter(BloomFilter.GlowMode.Objects);
        fpp.addFilter(bloom);
        viewPort.addProcessor(fpp);
    }

    private PhysicsSpace getPhysicsSpace() {
        return bulletAppState.getPhysicsSpace();
    }

    private void setupKeys() {
        inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T));
        inputManager.addListener(this, "wireframe");
        inputManager.addMapping("CharLeft", new KeyTrigger(KeyInput.KEY_A));
        inputManager.addMapping("CharRight", new KeyTrigger(KeyInput.KEY_D));
        inputManager.addMapping("CharUp", new KeyTrigger(KeyInput.KEY_W));
        inputManager.addMapping("CharDown", new KeyTrigger(KeyInput.KEY_S));
        inputManager
                .addMapping("CharSpace", new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addMapping("CharShoot", new MouseButtonTrigger(
                MouseInput.BUTTON_LEFT));
        inputManager.addListener(this, "CharLeft");
        inputManager.addListener(this, "CharRight");
        inputManager.addListener(this, "CharUp");
        inputManager.addListener(this, "CharDown");
        inputManager.addListener(this, "CharSpace");
        inputManager.addListener(this, "CharShoot");
    }

    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 BetterCharacterControl(2, 4, 0.5f);
        ninjaControl.setJumpForce(new Vector3f(6, 6, 6));

        ninjaNode.addControl(ninjaControl);
        rootNode.attachChild(ninjaNode);
        bulletAppState.getPhysicsSpace().add(ninjaControl);
        getPhysicsSpace().add(ninjaControl);
        animationControl = ninjaNode.getControl(AnimControl.class);
        animationChannel = animationControl.createChannel();
    }

    private void createGoblin() {
        goblinNode = (Node) assetManager
                .loadModel("objects/goblin.j3o");
        goblinNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
        goblinNode.setLocalScale(4f);
        goblinNode.setLocalTranslation(new Vector3f(51.5f, 3.3f, -60));
        goblinControl = new BetterCharacterControl(2, 4, 0.5f);
        goblinControl.setJumpForce(new Vector3f(6, 6, 6));

        goblinNode.addControl(goblinControl);
        rootNode.attachChild(goblinNode);
        bulletAppState.getPhysicsSpace().add(goblinControl);
        getPhysicsSpace().add(goblinControl);
        animationControl = goblinNode.getControl(AnimControl.class);
        animationChannel = animationControl.createChannel();
    }

    private void createCharacters() {
        CapsuleCollisionShape capsule = new CapsuleCollisionShape(0.05f, 0.05f);
        createNinja();
        ninjaControl.setViewDirection(new Vector3f(0, 0, 1));
        //getPhysicsSpace().add(ninjaControl);
        createGoblin();
        BlenderKey blenderKey = new BlenderKey("Models/Oto/Oto.mesh.xml");
        Spatial man = (Spatial) assetManager.loadModel(blenderKey);
        man.setLocalTranslation(new Vector3f(69, 15, -60));
        man.setShadowMode(ShadowMode.CastAndReceive);
        rootNode.attachChild(man);
        //goblin = assetManager.loadModel("objects/goblin.j3o");
        //goblin.scale(4f, 4f, 4f);

        //goblinControl = new BetterCharacterControl(2,3,0.5f);
        //goblin.addControl(goblinControl);

        //goblinControl.setPhysicsLocation(new Vector3f(60, 3.5f, -60));
        //goblin.setLocalTranslation(new Vector3f(150,70.5f, -5));
        //control = goblin.getControl(AnimControl.class);
        //control.addListener(this);
        //channel = control.createChannel();

        // for (String anim : control.getAnimationNames())
        // System.out.println("goblin can:"+anim);
        //channel.setAnim("walk");
        //goblin.setLocalTranslation(new Vector3f(51.5f, 3, -55));

        //rootNode.attachChild(goblin);
        //getPhysicsSpace().add(goblinControl);
        Spatial monster = assetManager
                .loadModel("objects/creatures/monster/monster.packed.j3o");

        Spatial monster2 = assetManager.loadModel("Models/Jaime/Jaime.j3o");
        monster2.scale(5f, 5f, 5f);
        monster.scale(2f, 2f, 2f);
        monster.setLocalTranslation(new Vector3f(53, 3, -55));
        monster2.setLocalTranslation(new Vector3f(48, 3, -55));

        rootNode.attachChild(monster2);
        rootNode.attachChild(monster);

    }

    private void setupChaseCamera() {
        flyCam.setEnabled(false);
        chaseCam = new ChaseCamera(cam, ninjaNode, inputManager);
        chaseCam.setDefaultDistance(37);

    }

    private void setupAnimationController() {
        animationControl = ninjaNode.getControl(AnimControl.class);
        animationControl.addListener(this);
        animationChannel = animationControl.createChannel();

    }

    @Override
    public void simpleUpdate(float tpf) {
        //goblinControl.setWalkDirection(goblin.getLocalRotation()
            //  .mult(Vector3f.UNIT_Z).multLocal(0.4f));
        Vector3f camDir = cam.getDirection().clone().multLocal(8f);
        Vector3f camLeft = cam.getLeft().clone().multLocal(8f);
        camDir.y = 0;
        camLeft.y = 0;
        walkDirection.set(0, 0, 0);
        if (left) {
            walkDirection.addLocal(camLeft);
        }
        if (right) {
            walkDirection.addLocal(camLeft.negate());
        }
        if (up) {
            walkDirection.addLocal(camDir);
        }
        if (down) {
            walkDirection.addLocal(camDir.negate());
        }
        // if (attack) {
        // animationChannel.setAnim("Attack1");
        // animationChannel.setLoopMode(LoopMode.DontLoop);
        // }
        if (!ninjaControl.isOnGround()) {
            airTime = airTime + tpf;
        } else {
            airTime = 0;
        }
        if (walkDirection.length() == 0) {
            if (!"Idle1".equals(animationChannel.getAnimationName())) {
                animationChannel.setAnim("Idle1", 1f);
            }
        } else {
            ninjaControl.setViewDirection(walkDirection.negate());
            if (airTime > .3f) {
                if (!"stand".equals(animationChannel.getAnimationName())) {
                    animationChannel.setAnim("Idle1");
                }
            } else if (!"Walk".equals(animationChannel.getAnimationName())) {
                animationChannel.setAnim("Walk", 1f);
            }
        }
        ninjaControl.setWalkDirection(walkDirection);
    }

    /*
     * Ninja can: Walk Ninja can: Kick Ninja can: JumpNoHeight Ninja can: Jump
     * Ninja can: Spin Ninja can: Attack1 Ninja can: Idle1 Ninja can: Attack3
     * Ninja can: Idle2 Ninja can: Attack2 Ninja can: Idle3 Ninja can: Stealth
     * Ninja can: Death2 Ninja can: Death1 Ninja can: HighJump Ninja can:
     * SideKick Ninja can: Backflip Ninja can: Block Ninja can: Climb Ninja can:
     * Crouch
     */

    public void onAction(String binding, boolean value, float tpf) {

        if (binding.equals("CharLeft")) {
            if (value) {
                left = true;
            } else {
                left = false;
            }
        } else if (binding.equals("CharRight")) {
            if (value) {
                right = true;
            } else {
                right = false;
            }
        } else if (binding.equals("CharUp")) {
            if (value) {
                up = true;
            } else {
                up = false;
            }
        } else if (binding.equals("CharDown")) {
            if (value) {
                down = true;
            } else {
                down = false;
            }
        } else if (binding.equals("CharSpace")) {
            // character.jump();
            ninjaControl.jump();
        } else if (binding.equals("CharShoot") && value) {
            // bulletControl();
            Vector3f origin = cam.getWorldCoordinates(
                    inputManager.getCursorPosition(), 0.0f);
            Vector3f direction = cam.getWorldCoordinates(
                    inputManager.getCursorPosition(), 0.0f);
            // direction.subtractLocal(origin).normalizeLocal();
            // character.setWalkDirection(location);
            System.out.println("origin" + origin);
            System.out.println("direction" + direction);
            // character.setViewDirection(direction);
            animationChannel.setAnim("Attack3");
            animationChannel.setLoopMode(LoopMode.DontLoop);
        }
    }

    public void onAnimCycleDone(AnimControl control, AnimChannel channel,
            String animName) {
        if (channel == shootingChannel) {
            channel.setAnim("Idle1");
        }
    }

    public void onAnimChange(AnimControl control, AnimChannel channel,
            String animName) {
    }

    public Node getGameLevel() {
        return gameLevel;
    }

    public void setGameLevel(Node gameLevel) {
        this.gameLevel = gameLevel;
    }

    public static boolean isUseHttp() {
        return useHttp;
    }

    public static void setUseHttp(boolean useHttp) {
        PyramidLevel.useHttp = useHttp;
    }

    public BulletAppState getBulletAppState() {
        return bulletAppState;
    }

    public void setBulletAppState(BulletAppState bulletAppState) {
        this.bulletAppState = bulletAppState;
    }

    public AnimChannel getChannel() {
        return channel;
    }

    public void setChannel(AnimChannel channel) {
        this.channel = channel;
    }

    public AnimControl getControl() {
        return control;
    }

    public void setControl(AnimControl control) {
        this.control = control;
    }

    public BetterCharacterControl getGoblincharacter() {
        return goblinControl;
    }

    public void setGoblincharacter(BetterCharacterControl goblincharacter) {
        this.goblinControl = goblincharacter;
    }

    public BetterCharacterControl getCharacterControl() {
        return ninjaControl;
    }

    public void setCharacterControl(BetterCharacterControl characterControl) {
        this.ninjaControl = characterControl;
    }

    public Node getCharacterNode() {
        return ninjaNode;
    }

    public void setCharacterNode(Node characterNode) {
        this.ninjaNode = characterNode;
    }

    public boolean isRotate() {
        return rotate;
    }

    public void setRotate(boolean rotate) {
        this.rotate = rotate;
    }

    public Vector3f getWalkDirection() {
        return walkDirection;
    }

    public void setWalkDirection(Vector3f walkDirection) {
        this.walkDirection = walkDirection;
    }

    public Vector3f getViewDirection() {
        return viewDirection;
    }

    public void setViewDirection(Vector3f viewDirection) {
        this.viewDirection = viewDirection;
    }

    public boolean isLeftStrafe() {
        return leftStrafe;
    }

    public void setLeftStrafe(boolean leftStrafe) {
        this.leftStrafe = leftStrafe;
    }

    public boolean isRightStrafe() {
        return rightStrafe;
    }

    public void setRightStrafe(boolean rightStrafe) {
        this.rightStrafe = rightStrafe;
    }

    public boolean isForward() {
        return forward;
    }

    public void setForward(boolean forward) {
        this.forward = forward;
    }

    public boolean isBackward() {
        return backward;
    }

    public void setBackward(boolean backward) {
        this.backward = backward;
    }

    public boolean isLeftRotate() {
        return leftRotate;
    }

    public void setLeftRotate(boolean leftRotate) {
        this.leftRotate = leftRotate;
    }

    public boolean isRightRotate() {
        return rightRotate;
    }

    public void setRightRotate(boolean rightRotate) {
        this.rightRotate = rightRotate;
    }

    public Node getModel() {
        return goblinNode;
    }

    public void setModel(Node model) {
        this.goblinNode = model;
    }

    public Spatial getGoblin() {
        return goblin;
    }

    public void setGoblin(Spatial goblin) {
        this.goblin = goblin;
    }

    public RigidBodyControl getTerrainPhysicsNode() {
        return terrainPhysicsNode;
    }

    public void setTerrainPhysicsNode(RigidBodyControl terrainPhysicsNode) {
        this.terrainPhysicsNode = terrainPhysicsNode;
    }

    public AnimChannel getAnimationChannel() {
        return animationChannel;
    }

    public void setAnimationChannel(AnimChannel animationChannel) {
        this.animationChannel = animationChannel;
    }

    public AnimChannel getShootingChannel() {
        return shootingChannel;
    }

    public void setShootingChannel(AnimChannel shootingChannel) {
        this.shootingChannel = shootingChannel;
    }

    public AnimControl getAnimationControl() {
        return animationControl;
    }

    public void setAnimationControl(AnimControl animationControl) {
        this.animationControl = animationControl;
    }

    public float getAirTime() {
        return airTime;
    }

    public void setAirTime(float airTime) {
        this.airTime = airTime;
    }

    public boolean isLeft() {
        return left;
    }

    public void setLeft(boolean left) {
        this.left = left;
    }

    public boolean isRight() {
        return right;
    }

    public void setRight(boolean right) {
        this.right = right;
    }

    public boolean isUp() {
        return up;
    }

    public void setUp(boolean up) {
        this.up = up;
    }

    public boolean isDown() {
        return down;
    }

    public void setDown(boolean down) {
        this.down = down;
    }

    public boolean isAttack() {
        return attack;
    }

    public void setAttack(boolean attack) {
        this.attack = attack;
    }

    public ChaseCamera getChaseCam() {
        return chaseCam;
    }

    public void setChaseCam(ChaseCamera chaseCam) {
        this.chaseCam = chaseCam;
    }

    public boolean isWalkMode() {
        return walkMode;
    }

    public void setWalkMode(boolean walkMode) {
        this.walkMode = walkMode;
    }

    public FilterPostProcessor getFpp() {
        return fpp;
    }

    public void setFpp(FilterPostProcessor fpp) {
        this.fpp = fpp;
    }

    public Spatial getSceneModel() {
        return sceneModel;
    }

    public void setSceneModel(Spatial sceneModel) {
        this.sceneModel = sceneModel;
    }

    public RigidBodyControl getLandscape() {
        return landscape;
    }

    public void setLandscape(RigidBodyControl landscape) {
        this.landscape = landscape;
    }

}

You can download a demo of my game but how do I improve the walking?

Update

My followup at the jmonkeyforum also had 0 replies.

Niklas Rosencrantz
  • 25,640
  • 75
  • 229
  • 424

1 Answers1

5

To do this you change the value of the max slope:

setMaxSlope()

From the jmonkey site:

"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."

Reading this I believe it works in a way similar to, when moving 1 unit in the world what is the maximum change in height a character can experience. If you set this to the size of the steps (or a larger by <1 for safety) you'r character should be able to walk up steps

Sources: http://hub.jmonkeyengine.org/wiki/doku.php/jme3:advanced:walking_character#charactercontrol Developing for Jmonkey before

user1646196
  • 589
  • 6
  • 28
  • 1
    Thank you for the answer! Yes, you are completely right that I want to use `setMaxSlope()` but it seems not available in the `BetterCharacterControl` upgrade which breaks my functionality. Can you please advice me what to do, it used to work better with the old version however now with the `BetterCharacterControl` it works when characters push each other. Please help, I've been stuck at this for weeks after trying to upgrade to `BetterCharacterControl` for months and finally succeeding now noticing that it breaks to maxslope functionality. – Niklas Rosencrantz Jun 07 '13 at 16:50
  • It's this class: http://hub.jmonkeyengine.org/wiki/doku.php/jme3:advanced:walking_character?s[]=bettercharactercontrol – Niklas Rosencrantz Jun 07 '13 at 16:56
  • 1
    Hmmmm, you could try adding an upwards force when walking, not enough to counteract gravity but enough to maybe give it a push up steps? Let me look over the documentation then I'll get back to you if I find anything. – user1646196 Jun 07 '13 at 21:55
  • 1
    Change height percent might be the way to go, check in update if the character has moved from last time then if it hasn't raise the height percent. It might slow down moving up stairs I can't quite tell. Looking at JMonkey it seems most people are still using normal characterControl, it might be better to take what you like of betterCharacterControl and extend characterControl to add that functionality? EDIT: Just saw guy behind JME3 say march of this year it wasn't yet stable, maybe leave it until it matures? – user1646196 Jun 07 '13 at 22:03
  • Yes, it's true that it is new but it should work and deprecating a class should be compatible with the code that uses the old class. In this case charactercontrol worked well, the bettercharactercontrol should not remove methods that were good and useful. – Niklas Rosencrantz Jun 13 '13 at 04:32
  • 1
    I agree but the man at JME does have his reasons: http://hub.jmonkeyengine.org/forum/topic/bettercharactercontrol-in-the-works/ – user1646196 Jun 13 '13 at 07:02