android游戏开发框架libgdx的使用(十四)—TiledMap中视角完善和障碍物处理

13 1月

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载自夜明的孤行灯

本文链接地址: https://www.huangyunkun.com/2012/01/13/libgdx_14/

本文使用的libgdx是0.92版本,和现在的最新版可能有一些不一样的地方。全文内容仅供参考。

本文紧跟上文:http://www.cnblogs.com/htynkn/archive/2012/01/13/libgdx_13.html

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

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

zuobiao

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

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

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

代码如下:

privatevoidCameraMove(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;
}
}

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

13-1

处理方法我采用的是将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;
}

13-2

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

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

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

metatiles

大小还是32*32.

13-3

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

tilemap

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

13-4

最后隐藏该图层,保存文件。(我在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) {
returntrue;
}else {
returnfalse;
}

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

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;

publicclass 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
publicvoidcreate() {
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
publicvoiddispose() {
// TODO Auto-generated method stub

}

@Override
publicvoidpause() {
// TODO Auto-generated method stub

}

@Override
publicvoidrender() {
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();
}

privatevoidCameraMove(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;
}
}

privatebooleanCheckMoveable(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) {
returntrue;
}else {
returnfalse;
}
}

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

}

@Override
publicvoidresume() {
// TODO Auto-generated method stub

}

@Override
publicbooleankeyDown(int keycode) {

returnfalse;
}

@Override
publicbooleankeyTyped(char character) {
// TODO Auto-generated method stub
returnfalse;
}

@Override
publicbooleankeyUp(int keycode) {
// TODO Auto-generated method stub
returnfalse;
}

@Override
publicbooleanscrolled(int amount) {
// TODO Auto-generated method stub
returnfalse;
}

privatevoidChangeDirect(int typeId) {
switch (typeId) {
case1:
moveVector.set(0,1,0);
Gdx.app.log("方向变动","向上");
break;
case2:
moveVector.set(0, -1,0);
Gdx.app.log("方向变动","向下");
break;
case3:
moveVector.set(-1,0,0);
Gdx.app.log("方向变动","向左");
break;
case4:
moveVector.set(1,0,0);
Gdx.app.log("方向变动","向右");
break;
}
}

@Override
publicbooleantouchDown(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);
}
}elseif (newx >0 && newy <0) {
if (newx > -newy) {
ChangeDirect(4);
}else {
ChangeDirect(2);
}
}elseif (newx <0 && newy >0) {
if (-newx > newy) {
ChangeDirect(3);
}else {
ChangeDirect(1);
}
}else {
if (-newx > -newy) {
ChangeDirect(3);
}else {
ChangeDirect(2);
}
}
isPress =true;
returnfalse;
}

@Override
publicbooleantouchDragged(int x,int y,int pointer) {
// TODO Auto-generated method stub
returnfalse;
}

@Override
publicbooleantouchMoved(int x,int y) {
// TODO Auto-generated method stub
returnfalse;
}

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

最终效果:

13-5

写在最后:

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

  • 注意各种坐标转化。

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

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

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

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载自夜明的孤行灯

本文链接地址: https://www.huangyunkun.com/2012/01/13/libgdx_14/

发表评论

您的电子邮箱地址不会被公开。