本文紧跟上文:android游戏开发框架libgdx的使用(十三)——TiledMap中的角色和角色移动

上文说到绘制了Map,然后我们的主角也可以四处活动了,但是仍有一些不完善的地方。

1.地图的边界没有控制。Camera的位置其实是viewport的位置,不是屏幕边界,所以如果直接按照上文的做法做的话主角走到屏幕边缘的时候就有问题了。

 

2.没有障碍,主角的行动没有约束。

现在先来解决第一个问题。

解决方案很简单,我们时刻注意viewport的位置,根据viewport计算Screen的边界,让其不超过地图。

代码如下:

private void CameraMove(Vector3 vector3, Actor mainActor) { 
        Vector3 viewport = stage.getCamera().position.cpy(); 
        viewport = viewport.add(vector3); 
        Vector3 zbound = new Vector3(width / 2, height / 2, 0).add(viewport); 
        if (zbound.x > maxCamPosition.x || zbound.y > maxCamPosition.y) { 
            return; 
        } 
        Vector3 fbound = new Vector3(-width / 2, -height / 2, 0).add(viewport); 
        if (fbound.x < 0 || fbound.y < 0) { 
            return; 
        } 
        stage.getCamera().position.add(vector3); 
        for (Actor actor : stage.getActors()) { 
            actor.x += vector3.x; 
            actor.y += vector3.y; 
        } 
    }

运行一下,恩,感觉还行。但是又有一个问题出现了…当地图达到边界时地图不能滚动了,但是主角应该还是可以前进的。

 

处理方法我采用的是将Camera和主角分开处理,还是判断一下,主角如果移动后不超出屏幕,就继续移动。

Vector3 viewport = stage.getCamera().position.cpy(); 
        viewport = viewport.add(vector3); 
        Vector3 zbound = new Vector3(width / 2, height / 2, 0).add(viewport); 
        if (zbound.x > maxCamPosition.x || zbound.y > maxCamPosition.y) { 
            isCameraMove = false; 
        } 
        Vector3 fbound = new Vector3(-width / 2, -height / 2, 0).add(viewport); 
        if (fbound.x < 0 || fbound.y < 0) { 
            isCameraMove = false; 
        }

        Vector3 v3 = new Vector3(mainActor.x, mainActor.y, 0); 
        stage.getCamera().project(v3); 
        Vector3 a = v3.cpy().add(vector3); 
        if (a.x > width || a.y > height) { 
            isActorMove = false; 
        } 
        if (a.x < 0 || a.y < 0) { 
            isActorMove = false; 
        }

        if (isCameraMove) { 
            stage.getCamera().position.add(vector3); 
            for (Actor actor : stage.getActors()) { 
                if (!actor.equals(player)) { 
                    actor.x += vector3.x; 
                    actor.y += vector3.y; 
                } 
            } 
        } 
        if (isActorMove) { 
            player.x += vector3.x; 
            player.y += vector3.y; 
        }

 

第一个问题基本解决,为什么说是基本解决?因为主角和Camera的位置可能会变动。造成主角在屏幕一角行走的问题。我一般是判断主角位置,当位于中心区域时在让二者联动。

现在来解决第二个问题,障碍物的问题。

重新编辑我们的TMX文件。添加一个新图块:

大小还是32*32.

 

然后新建一个图层,将地图中不能穿越的部分用红色的方块填充。

 

编辑红色块的属性,添加一个Pass-False键值(这个值是随意的,只要你能懂就行)

 

最后隐藏该图层,保存文件。(我在Editor里面确实隐藏了这个层,但是libgdx还是绘制了,只有自己处理一下了)

map = TiledLoader.createMap(mapHandle);

        for (int i = 0; i < map.layers.size(); i++) { 
            if ("NoPass".equals(map.layers.get(i).name)) { 
                nopassLayer = map.layers.get(i); 
                map.layers.remove(i); 
                break; 
            } 
        }

map实例化以后,先寻找我们的障碍层,将其从map中移除。为了方便调试,暂时没有移除它。

在主角每次移动时我们就检查主角移动后的位置是在哪块里面,这块是不是不能通过的,如果不能就不移动,否则就移动。

先遍历所有tiles,看一下pass=false的是多少ID的那块。

  int nopassId = 0; 
        TileSet set = map.tileSets.get(map.tileSets.size() - 1); 
        int masSize = set.firstgid + layer.tiles.length; 
        for (int i = 0; i < masSize; i++) { 
            if ("False".equals(map.getTileProperty(i, "Pass"))) { 
                nopassId = i; 
                Gdx.app.log("Find!", i + " "); 
                break; 
            } 
        }

然后推算移动后会处于哪块中,哪一块是不是不能通过的。

 int xid = MathUtils.ceilPositive(pos.x / map.tileWidth); 
        int yid = MathUtils.ceilPositive(pos.y / map.tileWidth); 
        if (layer.tiles[layer.tiles.length - yid][xid - 1] == nopassId) { 
            return true; 
        } else { 
            return false; 
        }

在移动时先判断一下,如果会达到不能到达的块就直接退出。

  Vector2 pos = new Vector2(mainActor.x, mainActor.y); 
        if (CheckMoveable(map, nopassLayer, vector3, pos)) { 
            return; 
        }

完整代码:(代码有点乱…)

package com.cnblogs.htynkn.game;

import javax.swing.text.ZoneView; 
import javax.swing.text.html.MinimalHTMLWriter;

import com.badlogic.gdx.ApplicationListener; 
import com.badlogic.gdx.Gdx; 
import com.badlogic.gdx.InputMultiplexer; 
import com.badlogic.gdx.InputProcessor; 
import com.badlogic.gdx.files.FileHandle; 
import com.badlogic.gdx.graphics.Color; 
import com.badlogic.gdx.graphics.GL10; 
import com.badlogic.gdx.graphics.OrthographicCamera; 
import com.badlogic.gdx.graphics.Texture; 
import com.badlogic.gdx.graphics.g2d.BitmapFont; 
import com.badlogic.gdx.graphics.g2d.SpriteBatch; 
import com.badlogic.gdx.graphics.g2d.TextureRegion; 
import com.badlogic.gdx.graphics.g2d.tiled.TileAtlas; 
import com.badlogic.gdx.graphics.g2d.tiled.TileMapRenderer; 
import com.badlogic.gdx.graphics.g2d.tiled.TileSet; 
import com.badlogic.gdx.graphics.g2d.tiled.TiledLayer; 
import com.badlogic.gdx.graphics.g2d.tiled.TiledLoader; 
import com.badlogic.gdx.graphics.g2d.tiled.TiledMap; 
import com.badlogic.gdx.graphics.g2d.tiled.TiledObject; 
import com.badlogic.gdx.graphics.g2d.tiled.TiledObjectGroup; 
import com.badlogic.gdx.graphics.glutils.ShaderProgram; 
import com.badlogic.gdx.math.MathUtils; 
import com.badlogic.gdx.math.Vector2; 
import com.badlogic.gdx.math.Vector3; 
import com.badlogic.gdx.scenes.scene2d.Actor; 
import com.badlogic.gdx.scenes.scene2d.Stage; 
import com.badlogic.gdx.scenes.scene2d.ui.Image; 
import com.badlogic.gdx.scenes.scene2d.ui.Label; 
import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle;

public class JavaGame implements ApplicationListener, InputProcessor {

    Stage stage; 
    float width; 
    float height; 
    private TiledMap map; 
    private TileAtlas atlas; 
    private TileMapRenderer tileMapRenderer; 
    Image player; 
    Vector3 camDirection = new Vector3(1, 1, 0); 
    Vector2 maxCamPosition = new Vector2(0, 0); 
    Vector3 moveVector = new Vector3(0, 0, 0); 
    boolean isPress; 
    TiledLayer nopassLayer;

    // Image image;

    @Override 
    public void create() { 
        final String path = "map/"; 
        final String mapname = "tilemap"; 
        FileHandle mapHandle = Gdx.files.internal(path + mapname + ".tmx"); 
        map = TiledLoader.createMap(mapHandle);

        for (int i = 0; i < map.layers.size(); i++) { 
            if ("NoPass".equals(map.layers.get(i).name)) { 
                nopassLayer = map.layers.get(i); 
                // map.layers.remove(i); 
                break; 
            } 
        }

        atlas = new TileAtlas(map, new FileHandle("map/")); 
        tileMapRenderer = new TileMapRenderer(map, atlas, 10, 10); 
        maxCamPosition.set(tileMapRenderer.getMapWidthUnits(), tileMapRenderer 
                .getMapHeightUnits());

        width = Gdx.graphics.getWidth(); 
        height = Gdx.graphics.getHeight(); 
        stage = new Stage(width, height, true); 
        Label label = new Label("FPS:", new LabelStyle(new BitmapFont(Gdx.files 
                .internal("font/blue.fnt"), 
                Gdx.files.internal("font/blue.png"), false), Color.WHITE), 
                "fpsLabel"); 
        label.y = height - label.getPrefHeight(); 
        label.x = 0; 
        stage.addActor(label);

        for (TiledObjectGroup group : map.objectGroups) { 
            for (TiledObject object : group.objects) { 
                if ("play1".equals(object.name)) { 
                    player = new Image(new TextureRegion(new Texture(Gdx.files 
                            .internal("map/player.png")), 0, 0, 27, 40)); 
                    player.x = object.x; 
                    player.y = tileMapRenderer.getMapHeightUnits() - object.y; // map是左上角,Stage是左下角 
                    stage.addActor(player); 
                } 
            } 
        }

        InputMultiplexer inputMultiplexer = new InputMultiplexer(); 
        inputMultiplexer.addProcessor(this); 
        inputMultiplexer.addProcessor(stage); 
        Gdx.input.setInputProcessor(inputMultiplexer); 
    }

    @Override 
    public void dispose() { 
        // TODO Auto-generated method stub

    }

    @Override 
    public void pause() { 
        // TODO Auto-generated method stub

    }

    @Override 
    public void render() { 
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); 
        OrthographicCamera c = (OrthographicCamera) stage.getCamera(); 
        if (isPress) { 
            CameraMove(moveVector, player); 
        } 
        ((Label) stage.findActor("fpsLabel")).setText("FPS: " 
                + Gdx.graphics.getFramesPerSecond()); 
        stage.act(Gdx.graphics.getDeltaTime()); 
        tileMapRenderer.render(c); 
        stage.draw(); 
    }

    private void CameraMove(Vector3 vector3, Actor mainActor) { 
        boolean isCameraMove = true; 
        boolean isActorMove = true;

        Vector2 pos = new Vector2(mainActor.x, mainActor.y); 
        if (CheckMoveable(map, nopassLayer, vector3, pos)) { 
            return; 
        } 
         if (CheckMoveable(map, nopassLayer, vector3, pos.cpy().add( 
         mainActor.width, 0))) { 
         return; 
         } 
         if (CheckMoveable(map, nopassLayer, vector3, pos.cpy().add( 
         mainActor.width, mainActor.height))) { 
         return; 
         } 
         if (CheckMoveable(map, nopassLayer, vector3, pos.cpy().add(0, 
         mainActor.height))) { 
         return; 
         }

        Vector3 viewport = stage.getCamera().position.cpy(); 
        viewport = viewport.add(vector3); 
        Vector3 zbound = new Vector3(width / 2, height / 2, 0).add(viewport); 
        if (zbound.x > maxCamPosition.x || zbound.y > maxCamPosition.y) { 
            isCameraMove = false; 
        } 
        Vector3 fbound = new Vector3(-width / 2, -height / 2, 0).add(viewport); 
        if (fbound.x < 0 || fbound.y < 0) { 
            isCameraMove = false; 
        }

        Vector3 v3 = new Vector3(mainActor.x, mainActor.y, 0); 
        stage.getCamera().project(v3); 
        Vector3 a = v3.cpy().add(vector3); 
        if (a.x > width || a.y > height) { 
            isActorMove = false; 
        } 
        if (a.x < 0 || a.y < 0) { 
            isActorMove = false; 
        }

        if (isCameraMove) { 
            stage.getCamera().position.add(vector3); 
            for (Actor actor : stage.getActors()) { 
                if (!actor.equals(player)) { 
                    actor.x += vector3.x; 
                    actor.y += vector3.y; 
                } 
            } 
        } 
        if (isActorMove) { 
            player.x += vector3.x; 
            player.y += vector3.y; 
        } 
    }

    private boolean CheckMoveable(TiledMap map, TiledLayer layer, 
            Vector3 vector3, Vector2 playpos) { 
        Vector3 pos = new Vector3(playpos.x, playpos.y, 0).add(vector3); 
        int nopassId = 0; 
        TileSet set = map.tileSets.get(map.tileSets.size() - 1); 
        int masSize = set.firstgid + layer.tiles.length; 
        for (int i = 0; i < masSize; i++) { 
            if ("False".equals(map.getTileProperty(i, "Pass"))) { 
                nopassId = i; 
                Gdx.app.log("Find!", i + " "); 
                break; 
            } 
        } 
        int xid = MathUtils.ceilPositive(pos.x / map.tileWidth); 
        int yid = MathUtils.ceilPositive(pos.y / map.tileWidth); 
        if (layer.tiles[layer.tiles.length - yid][xid - 1] == nopassId) { 
            return true; 
        } else { 
            return false; 
        } 
    }

    @Override 
    public void resize(int width, int height) { 
        // TODO Auto-generated method stub

    }

    @Override 
    public void resume() { 
        // TODO Auto-generated method stub

    }

    @Override 
    public boolean keyDown(int keycode) {

        return false; 
    }

    @Override 
    public boolean keyTyped(char character) { 
        // TODO Auto-generated method stub 
        return false; 
    }

    @Override 
    public boolean keyUp(int keycode) { 
        // TODO Auto-generated method stub 
        return false; 
    }

    @Override 
    public boolean scrolled(int amount) { 
        // TODO Auto-generated method stub 
        return false; 
    }

    private void ChangeDirect(int typeId) { 
        switch (typeId) { 
        case 1: 
            moveVector.set(0, 1, 0); 
            Gdx.app.log("方向变动", "向上"); 
            break; 
        case 2: 
            moveVector.set(0, -1, 0); 
            Gdx.app.log("方向变动", "向下"); 
            break; 
        case 3: 
            moveVector.set(-1, 0, 0); 
            Gdx.app.log("方向变动", "向左"); 
            break; 
        case 4: 
            moveVector.set(1, 0, 0); 
            Gdx.app.log("方向变动", "向右"); 
            break; 
        } 
    }

    @Override 
    public boolean touchDown(int x, int y, int pointer, int button) { 
        Vector3 tmp = new Vector3(x, y, 0); 
        stage.getCamera().unproject(tmp); 
        float newx = tmp.x - player.x; 
        float newy = tmp.y - player.y; 
        if (newx > 0 && newy > 0) { 
            if (newx > newy) { 
                ChangeDirect(4); 
            } else { 
                ChangeDirect(1); 
            } 
        } else if (newx > 0 && newy < 0) { 
            if (newx > -newy) { 
                ChangeDirect(4); 
            } else { 
                ChangeDirect(2); 
            } 
        } else if (newx < 0 && newy > 0) { 
            if (-newx > newy) { 
                ChangeDirect(3); 
            } else { 
                ChangeDirect(1); 
            } 
        } else { 
            if (-newx > -newy) { 
                ChangeDirect(3); 
            } else { 
                ChangeDirect(2); 
            } 
        } 
        isPress = true; 
        return false; 
    }

    @Override 
    public boolean touchDragged(int x, int y, int pointer) { 
        // TODO Auto-generated method stub 
        return false; 
    }

    @Override 
    public boolean touchMoved(int x, int y) { 
        // TODO Auto-generated method stub 
        return false; 
    }

    @Override 
    public boolean touchUp(int x, int y, int pointer, int button) { 
        isPress = false; 
        Gdx.app.log("Info", "touchUp: x:" + x + " y: " + y + " pointer: " 
                + pointer + " button: " + button); 
        return false; 
    } 
}

最终效果:

 

写在最后:

1.调试好了就要在绘制前把障碍层删除。

2.注意各种坐标转化。

3.如果需要变化地图,直接操作Layer里面的那个二维数组。

 

本文用的检测方法只是一种可行方案而已,也可以直接看角色占的块数。

本文参考了:http://geekanddad.wordpress.com/2010/06/22/enemies-and-combat-how-to-make-a-tile-based-game-with-cocos2d-part-3/

 

[related_posts limit="3"]
[box]本文对原文有部分修改,原文出处:
作者:黄云坤
出处:http://www.cnblogs.com/htynkn/[/box]