如何改善我的 3D 游戏的角色控制?

Posted

技术标签:

【中文标题】如何改善我的 3D 游戏的角色控制?【英文标题】:How to improve character control for my 3D game? 【发布时间】:2013-05-22 20:28:40 【问题描述】:

自从我从帮助类 CharacterControl 更改为新的 BetterCharacterControl 后,我注意到一些改进,例如推动其他角色正在工作,但我的主角已经开始滑过台阶,无法​​爬更高的台阶。

我必须跳过上面的步骤,这不是正确的游戏方式,应该只是走过去。旧的辅助类 CharacterControl 有一个默认的不滑动方式,只是跨步,我认为可以通过更改我创建主角的代码来纠正它。

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();

完整的代码是

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;
    


您可以下载我的游戏的demo,但我如何改善行走?

更新

我在 jmonkeyforum 的followup 也有 0 条回复。

【问题讨论】:

【参考方案1】:

为此,您需要更改最大斜率的值:

setMaxSlope()

来自 jmonkey 网站:

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

阅读本文,我相信它的工作方式类似于在世界上移动 1 个单位时,角色可以经历的最大高度变化是多少。如果您将其设置为台阶的大小(或为安全起见大于

来源: http://hub.jmonkeyengine.org/wiki/doku.php/jme3:advanced:walking_character#charactercontrol 之前为 Jmonkey 开发

【讨论】:

感谢您的回答!是的,我想使用setMaxSlope() 是完全正确的,但它似乎在BetterCharacterControl 升级中不可用,这会破坏我的功能。你能告诉我该怎么做吗,它曾经在旧版本中工作得更好,但现在使用BetterCharacterControl 它在角色相互推动时工作。请帮忙,在尝试升级到 BetterCharacterControl 几个月后,我一直被困在这个问题上,现在终于成功了,注意到它破坏了 maxslope 功能。 就是这个类:hub.jmonkeyengine.org/wiki/doku.php/… 嗯,你可以试着在走路时增加一个向上的力量,这不足以抵消重力但足以让它向上推?让我看一下文档,如果有什么我会回复你的。 改变高度百分比可能是要走的路,如果角色从上次移动然后没有提高高度百分比,请检查更新。它可能会减慢上楼梯的速度,我不太清楚。看看 JMonkey,似乎大多数人仍在使用普通的 characterControl,最好采用你喜欢的 betterCharacterControl 并扩展 characterControl 以添加该功能?编辑:刚刚看到 JME3 背后的人说今年 3 月它还不稳定,也许等它成熟后再放吧? 我同意,但 JME 的人确实有他的理由:hub.jmonkeyengine.org/forum/topic/…

以上是关于如何改善我的 3D 游戏的角色控制?的主要内容,如果未能解决你的问题,请参考以下文章

[Unity3D] 多人游戏中镜头固定角度、随角色移动的实现方式

Scanvenger游戏制作笔记Unity3D控制角色吃食物

Unity3D实战RPG黑暗之光Scene3:创建游戏运行场景及角色控制

游戏内角色模型推荐的 3D 模型类型,手动加载?

如何实现横版游戏中角色的移动控制

如何实现横版游戏中角色的跳跃控制