LibGDX游戏引擎-10-游戏地图(TiledMap)

要做游戏地图,在libgdx中我们使用到的工具是TiledMap Editor,官方网址是:链接

Tiled 地图编辑器是一种用于通用目的的编辑器。特点是比较好用,风格类似mini版的photoshop。它可以用于制作多种类型的游戏引擎需要,而且支持使用插件读写map、增加用于引擎的map格式。
地图编辑 :

使用图层、图块以及对象层的方式进行地图的编辑。以“.tmx”格式保存。


地图:

所谓的地图,特别是使用 Tiledmap 制作的地图,其实就是一张背景图片,

加上tiledmap地图编辑起来的一些图块所构成,这也是许多安卓和苹果应用所使用的技巧,

这样不仅可以节省游戏资源同时可以方便代码的实现。

地图图块:

负责做一些碰撞检测等的而背景中的蓝天、白云这些其实就是一张JPG的图片,就是为了是游戏更加好看、美观。


1.TiledMap软件的使用

TiledMap编辑器中,可以分为3个部分,第一个是图层、第二个是图块、第三个是对象层

(1) 图层介绍

游戏中一般要使用许多个图层,根据功能的不同,这里我将图层分为2种,
一种是作为碰撞检测的图层(即检测图层),另一种是作为美观装饰的图层(即装饰图层)。

检测图层:
一个作为碰撞检测的图层,就是负责游戏中不能穿过的图层。一般常作为地面、砖墙、障碍物等方面,游戏中必须使用到的。

装饰图层:
一个作为美观装饰的图层,就是负责装饰的,在地图中不作为碰撞检测,仅仅起到美观的作用,
可有可无,但是为了游戏体验,一般还是加入一个美观的图层,也可以被背景图片替代。

对象层:
一个作为创建对象而是用的图层,该层和其他2种不同,他是负责创建对象而是用的。
例如,超级玛丽中玛丽在地图的哪个点出生,金币在哪个点出生,怪物的设置点说的通俗点就是游戏中可以动的元素


层的高低

  • 对象层
  • 装饰层
  • 障碍层(全透明)
  • 背景层(地板层)

地形

瓦片地图中有许多方式来实现瓦片与瓦片之间地形的过渡,

但Tiled里的地形工具支持对地形的四角完好定义的瓦片图素过渡,这也是最常见的方式。

这个图块有4个不同地形种类。传统的编辑地图方式意味着你不得不小心地连接正确的过渡,来避免把接缝边缘玩坏掉。

但是我们现在会从图块中定义地形信息,地形工具会自动识别和放置地形间正确的过渡。

方法:

1 在图块上点击小按钮“编辑地形信息”

2 出现对话框,并允许你标记属于各地形种类的瓦片衔接处的拐角。

我们现在开始。先添加4种地形种类。最快的方法是,在最准确代表某一地形的瓦片上右键点击,

选择“增加地形类型”。这将自动设置这瓦片作为此地形的图片。

3 起一个合适的名字。然后在图块上标记这个地形应当有的所有拐角。

4如果中途涂错了,可以撤销或用清除工具擦掉错误的拐角。如此对4种地形每个都进行定义。

最终你标记完所有每个特定地形的所有瓦片部件。

5 关闭对话框,可以开始试着使用地形工具啦。使用地形工具编辑

6 切换到地形窗口。你会见到4种地形。点击沙地开始画,你会立即发现并没有什么特别的事情发生。
这是因为此时还没有其他瓦片在地图上,所以地形工具不知道如何提供帮助(因为我们图块种没有与“空”地形的过渡)
既然这样,那么此时我们最好先把整个地图用沙地填满。先暂时切回“图块”窗口,选择沙地瓦片然后使用“填充”工具。

7 切回地形窗口,来画一些地形。现在你可以看到效果啦!

在绘图的时候按住Command键,会减小绘图区域(默认是九宫格,减小后绘制4格)

到最接近光标位置的一个角落,可用来进行细微调整工作。

8 你会发现,当在“尘土”地形里面画鹅卵石的时候,因为没有“尘土”地形与“鹅卵石”地形的直接过渡,地形工具在两者之间插入了沙地地形过渡。很棒。

9 写在最后

现在你应该对地形工具在你自己项目中的运用有了很好的想法。这里还有几件事需要铭记在心:

  • 目前这个工具需要所有地形种类的各个瓦片在同一个图块里。你可以有许多带地形的图块,但该工具不能自动跨图块提供地形的过渡。这意味着通常你需要把几个图块合并到一张图里。

  • 目前通过Tiled的“添加外部图块”加入的图块包含的地形信息是不可编辑的。如需编辑,可以先导入图块,编辑后再重新导出。

  • 地形工具同样适用于等角地图(不能用于交错等角地图),但是,“编辑地形信息”对话框目前对其瓦片不能正确渲染覆盖指示。需要脑补:左上角会应用到上,右上角应用到右,依此类推。有疑问可参考example目录下的“isometric_grass_and_water.tmx”文件。

  • 该工具会处理任意数量的地形种类,且每个瓦片拐角都可以有不同种类的地形。针对此工具不能处理的过渡,仍然还有其他方法来解决。还有,同时编辑多图层是不可行的。要寻求关于自动放置瓦片的更加灵活且却更复杂的方法,请查阅“Automapping”。

Tiled在OpenGameArt.org上维护着一些瓦片地图图块,它们包含过渡且与此工具兼容。

以上内容英文原始资料来自《Using the Terrain tool》


地图使用 :

步骤简述:

  • 1 读取地图
  • 2 设置地图渲染
  • 3 新建相机
  • 4 设置相机
  • 5 图层处理
  • 5.1 遍历图层
  • 5.2 遍历图层对象
  • 5.3 读取图层对象属性
  • 5.4 设置障碍标识
  • 6 设置舞台
  • 6.1 新建演员
  • 6.2 添加监听
  • 6.3 添加音乐
  • 7 渲染
  • 7.1 清屏
  • 7.2 舞台渲染
  • 7.2.1 相机更新
  • 7.2.2 地图渲染关联相机
  • 7.2.3 地图渲染
  • 7.2.4 舞台行动
  • 7.2.5 舞台绘制
  • 7.2.6 其他画板绘制
  • 7.3 舞台监听重置
  • 8销毁资源
     

 

地图渲染 OrthogonalTiledMapRenderer 类
 

OrthogonalTiledMapRenderer renderer = new OrthogonalTiledMapRenderer(map,1/20f);
map 是 map = new TmxMapLoader().load(“map/map1.tmx”)

1/20f 是焦距

 

renderer.setView(camera)

绘制指定图层。传的是数组,例如绘制0,2图层:

int[] layers = {0,2};
render(layers);

render(OrthographicCamera cam)方法:渲染是传入相机。

相机 OrthographicCamera 类

OrthographicCamera camera = new OrthographicCamera();
camera.setToOrtho(false, 40, 24);
camera.update();
renderer.setView(camera);
renderer.render();
//相机监听
CameraInputController cameraController = new CameraInputController(camera);
Gdx.input.setInputProcessor(cameraController);

 

例子代码:

package com.qsuron11.barriers;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Sound;
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.maps.MapLayer;
import com.badlogic.gdx.maps.MapLayers;
import com.badlogic.gdx.maps.MapObject;
import com.badlogic.gdx.maps.MapObjects;
import com.badlogic.gdx.maps.objects.RectangleMapObject;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.maps.tiled.TmxMapLoader;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.qsuron11.barriers.Person;

public class MyDemo extends Gdx implements ApplicationListener {
public static final int WIDTH = 800;
//窗口宽度
public static final int HEIGHT = 480;
//窗口高度
public static final int MAP_PIC = 20;
//瓷砖大小
public static final int MAP_WIDTH = 40;
//瓷砖横向个数
public static final int MAP_HEIGHT = 24;
//瓷砖纵向个数

private static boolean GameIsRunning;
//主游戏界面是否运行
private static boolean ShopIsRunning;
//商店界面是否运行
private static boolean SuccessIsRunning;
//购买成功界面是否运行
private static int[][] barriers;
//障碍标识(24行40列)

private TiledMap map;
//地图
private BitmapFont font;
//标题字体
private SpriteBatch batch;
//画板
private OrthographicCamera camera;
//相机
private OrthogonalTiledMapRenderer renderer;
//图层渲染
private float p_x , p_y;
//人物坐标

private Image image_openStage_background,
//开始游戏背景
image_openStage_btn_newGame,
//新游戏按钮
image_openStage_btn_shop,
//商店按钮
image_shopStage_background,
//商店背景
image_shopStage_btn_heart,
//商店商品hreat
image_shopStage_btn_coin,
//商店商品coin
image_shopStage_btn_close,
//商店关闭按钮
image_successStage_success;
//购买成功
private Stage stage,
//游戏舞台
openStage, //开始舞台
shopStage, //商店舞台
successStage; //购买成功舞台
private Sound sound_openStage_btn_newGame;
//新游戏按钮声音
private Music music_openStage_background,
//开始游戏音乐
music_stage_background;
//主游戏音乐

static{
barriers = new int[MAP_HEIGHT][MAP_WIDTH];
GameIsRunning = false;
ShopIsRunning = false;
SuccessIsRunning = false;
}

@Override
public void create() {

batch = new SpriteBatch();
stage = new Stage(WIDTH, HEIGHT, false, batch);
openStage = new Stage(WIDTH, HEIGHT, false, batch);
shopStage = new Stage(WIDTH, HEIGHT, false, batch);
successStage = new Stage(WIDTH, HEIGHT, false, batch);

//地图
map = new TmxMapLoader().load(“map/map1.tmx”);
renderer = new OrthogonalTiledMapRenderer(map,1f/MAP_PIC);

//相机
camera = new OrthographicCamera();
camera.setToOrtho(false, MAP_WIDTH, MAP_HEIGHT);

//图层处理
this.setLayer();

//设置舞台
this.setStage();

//设置监听
this.setListener();

//设置音乐
this.setSound();
}

//判断是否能够移动
public static boolean passEnable(float x, float y){
//+0.5使四舍五入
int col = MathUtils.round(y/MAP_PIC);
int row = MathUtils.round(x/MAP_PIC);
boolean flag = barriers[col][row] == 0;
System.out.println(“往↑第” + col + “行,往→第” + row + “列 –> “ + flag);
return flag;
}

//判断是否能够下掉
public static boolean downEnable(float x, float y) {
//+0.5使四舍五入
boolean flag = barriers[(int) ((y/MAP_PIC-0.1f))][(int) (x/MAP_PIC+0.5f)] == 0;
//System.out.println(“往↑第” + ((y/MAP_PIC-0.1f)) + “行,往→第” + (x/MAP_PIC+0.5f) + “列 –> “ + flag);
return flag;
}

//图层处理
private void setLayer(){
MapLayers layers = map.getLayers();

for(int i=0;i<layers.getCount();i++){
System.out.println(“第” + i + “层:” + layers.get(i).getName());
}

for(MapLayer layer : layers){//遍历图层

if(layer.getName().equals(“object”)){//找到对象层
MapObjects objs = layer.getObjects();
for(MapObject obj : objs){//遍历图层内容
if(obj.getName().equals(“person”)){//找到人物图块
RectangleMapObject rmobj = (RectangleMapObject) obj;
p_x = rmobj.getRectangle().x;
p_y = rmobj.getRectangle().y;
break;
}
}
}

if(layer.getName().equals(“barriers”)){//找到障碍层
if(layer instanceof TiledMapTileLayer){//类型转换判断
TiledMapTileLayer tlayer = (TiledMapTileLayer) layer;
for(int x=0;x<MAP_HEIGHT;x++){
for(int y=0;y<MAP_WIDTH;y++){
if(tlayer.getCell(y, x) != null){
barriers[x][y] = 1;
}
}
}
}
}
}

for(int x=0;x<MAP_HEIGHT;x++){
for(int y=0;y<MAP_WIDTH;y++){
System.out.print(barriers[x][y]+” “);
}
System.out.println(“”);
}
}

//设置舞台
private void setStage(){
//开始游戏舞台
Texture texture_open = new Texture(Gdx.files.internal(“data/open.png”));
Texture texture_button = new Texture(Gdx.files.internal(“data/btn_newgame.png”));
Texture texture_button2 = new Texture(Gdx.files.internal(“data/btn_shop.png”));
image_openStage_background = new Image(new TextureRegion(texture_open,798,480));
image_openStage_background.setPosition(0,0);
image_openStage_btn_newGame = new Image(texture_button);
image_openStage_btn_newGame.setPosition(80,380);
image_openStage_btn_shop = new Image(texture_button2);
image_openStage_btn_shop.setPosition(80,80);
openStage.addActor(image_openStage_background);
openStage.addActor(image_openStage_btn_newGame);
openStage.addActor(image_openStage_btn_shop);

//商店舞台
Texture texture_shop = new Texture(Gdx.files.internal(“data/shop.png”));
image_shopStage_background = new Image(new TextureRegion(texture_shop,0,85,512,350));
image_shopStage_btn_heart = new Image(new TextureRegion(texture_shop,0,0,102,85));
image_shopStage_btn_coin = new Image(new TextureRegion(texture_shop,102,0,102,85));
image_shopStage_btn_close = new Image(new TextureRegion(texture_shop,300,10,50,40));
image_shopStage_background.setPosition(0, 0);
image_shopStage_background.setSize(480,320);
image_shopStage_btn_heart.setPosition(190,50);
image_shopStage_btn_coin.setPosition(50,50);
image_shopStage_btn_close.setPosition(400,275);
shopStage.addActor(image_shopStage_background);
shopStage.addActor(image_shopStage_btn_heart);
shopStage.addActor(image_shopStage_btn_coin);
shopStage.addActor(image_shopStage_btn_close);

//购买舞台
image_successStage_success = new Image(new TextureRegion(texture_shop,512,0,255,255));
image_successStage_success.setPosition(100,100);
successStage.addActor(image_successStage_success);

//游戏舞台
//背景
//image_stage_background = new Image(new Texture(Gdx.files.internal(“data/background.jpg”)));
//标题
font = new BitmapFont(Gdx.files.internal(“font/title.fnt”),
Gdx.files.internal(“font/title.png”),false);
font.setColor(Color.BLACK);
Gdx.input.setInputProcessor(stage);
Person p = new Person(p_x,p_y);
stage.addActor(p);
stage.addActor(p.btn_L);
stage.addActor(p.btn_R);
}

//设置声音
private void setSound() {
sound_openStage_btn_newGame = Gdx.audio.newSound(Gdx.files.internal(“music/openStage_btn_newGame.ogg”));
music_openStage_background = Gdx.audio.newMusic(Gdx.files.internal(“music/stage.ogg”));
music_stage_background = Gdx.audio.newMusic(Gdx.files.internal(“music/openStage.ogg”));
music_openStage_background.setLooping(true);
music_stage_background.setLooping(true);
//播放主页面音乐
music_openStage_background.play();
}

//设置监听
private void setListener() {
image_openStage_btn_newGame.addListener(new InputListener(){
@Override
public boolean touchDown(InputEvent event, float x, float y,
int pointer, int button) {
GameIsRunning = true;
music_openStage_background.stop();
music_stage_background.play();
return true;
}
});
image_openStage_btn_shop.addListener(new InputListener(){
@Override
public boolean touchDown(InputEvent event, float x, float y,
int pointer, int button) {
ShopIsRunning = true;
return true;
}
});
image_shopStage_btn_heart.addListener(new InputListener() {
@Override
public boolean touchDown(InputEvent event, float x, float y,
int pointer, int button) {
SuccessIsRunning = true;
sound_openStage_btn_newGame.play(1);
return true;
}
});
image_shopStage_btn_coin.addListener(new InputListener() {
@Override
public boolean touchDown(InputEvent event, float x, float y,
int pointer, int button) {
SuccessIsRunning = true;
sound_openStage_btn_newGame.play();
return true;
}
});
image_shopStage_btn_close.addListener(new InputListener(){
@Override
public boolean touchDown(InputEvent event, float x, float y,
int pointer, int button) {
ShopIsRunning = false;
return true;
}
});
image_successStage_success.addListener(new InputListener(){
@Override
public boolean touchDown(InputEvent event, float x, float y,
int pointer, int button) {
SuccessIsRunning = false;
return true;
}
});
}

//重置舞台监听
private void updateStage(){
if(GameIsRunning){
Gdx.input.setInputProcessor(stage);
}else{
Gdx.input.setInputProcessor(openStage);
if(ShopIsRunning){
Gdx.input.setInputProcessor(shopStage);
if(SuccessIsRunning){
Gdx.input.setInputProcessor(successStage);
}
}
}
}

//重置舞台绘制
private void stageRender(){
if(GameIsRunning){
camera.update();
renderer.setView(camera);
renderer.render();
stage.act();
stage.draw();
batch.begin();
font.draw(batch,”小树LibGDX游戏引擎开发”, 240, 450);//普通绘制
batch.end();
}else{
openStage.act();
openStage.draw();
if(ShopIsRunning){
shopStage.act();
shopStage.draw();
if(SuccessIsRunning){
successStage.act();
successStage.draw();
}
}
}
}

@Override
public void render() {

Gdx.gl.glClearColor(0,0,0, 0);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
this.stageRender();
this.updateStage();
}

@Override
public void dispose() {
batch.dispose();
stage.dispose();
openStage.dispose();
shopStage.dispose();
successStage.dispose();
map.dispose();
}

@Override
public void resize(int width, int height) {
}

@Override
public void pause() {
}

@Override
public void resume() {
}

}
第二个类

package com.qsuron11.barriers;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.ui.ImageButton;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;

public class Person extends Actor {
public static final int MAP_PIC = MyDemo.MAP_PIC;
public float x;
public float y;
public float statetime;

//按钮
ImageButton btn_L;
ImageButton btn_R;

//人物当前状态(枚举类)
STATE state;

//对应的动画
Animation animation_left;
Animation animation_right;
Animation animation_idle;

//实时动画
TextureRegion currentFrame;

public Person(float x, float y) {
super();
this.x = x;
this.y = y;
this.statetime = 0;
this.show();
state = STATE.I;
}

public void show() {

//分割动画
Texture animation_file = new Texture(Gdx.files.internal(“data/animation2-2.png”));
TextureRegion[][] animation_split_right = TextureRegion.split(animation_file,42,51);
TextureRegion[][] animation_split_left = TextureRegion.split(animation_file,42,51);
Texture animation_file2 = new Texture(Gdx.files.internal(“data/idle.png”));
TextureRegion[][] animation_split_idle = TextureRegion.split(animation_file2,42,51);

//实例化动画图片组
TextureRegion[] textureRegion_right = new TextureRegion[30];
TextureRegion[] textureRegion_left = new TextureRegion[30];
TextureRegion[] textureRegion_idle = new TextureRegion[1];

//向右动画图片组
int i = 0;
for(TextureRegion[] animation_split_right_split:animation_split_right){
for(TextureRegion animation_pic:animation_split_right_split){
textureRegion_right[i] = animation_pic;
i++;
}
}

//向左动画图片组
i = 0;
for(TextureRegion[] animation_split_left_split:animation_split_left){
for(TextureRegion animation_pic:animation_split_left_split){
textureRegion_left[i] = animation_pic;
textureRegion_left[i].flip(true,false);
i++;
}
}

//原地动画图片组
textureRegion_idle[0] = animation_split_idle[0][0];

//合成动画
animation_right = new Animation(0.05f,textureRegion_right);
animation_right.setPlayMode(Animation.LOOP);
animation_left = new Animation(0.05f,textureRegion_left);
animation_left.setPlayMode(Animation.LOOP);
animation_idle = new Animation(1f,textureRegion_idle);
animation_idle.setPlayMode(Animation.LOOP);

//按钮图片读取
Texture button_file = new Texture(Gdx.files.internal(“data/button.png”));
TextureRegion[][] button_split = TextureRegion.split(button_file,100,100);

//按钮实例化
btn_R = new ImageButton(
new TextureRegionDrawable(button_split[0][0]),
new TextureRegionDrawable(button_split[0][1]));
btn_L = new ImageButton(
new TextureRegionDrawable(button_split[0][2]),
new TextureRegionDrawable(button_split[0][3]));

//按钮位置
btn_R.setPosition(150,20);
btn_L.setPosition(20,20);

//按钮监听
btn_R.addListener(new InputListener(){
@Override
public boolean touchDown(InputEvent event, float x, float y,
int pointer, int button) {
state = STATE.R;
return true;
}
@Override
public void touchUp(InputEvent event, float x, float y,
int pointer, int button) {
state = STATE.I;
super.touchUp(event, x, y, pointer, button);
}
});

btn_L.addListener(new InputListener(){
@Override
public boolean touchDown(InputEvent event, float x, float y,
int pointer, int button) {
state = STATE.L;
return true;
}
@Override
public void touchUp(InputEvent event, float x, float y,
int pointer, int button) {
state = STATE.I;
super.touchUp(event, x, y, pointer, button);
}
});
}

//状态控制与动画选择
public void check() {
if(this.state==STATE.L){
currentFrame = animation_left.getKeyFrame(statetime,true);
}else if(this.state==STATE.R){
currentFrame = animation_right.getKeyFrame(statetime,true);
}else if(this.state==STATE.I){
currentFrame = animation_idle.getKeyFrame(statetime,true);
}
}

//人物移动
public void update(){
//验证间隔一个瓷砖(x,y是左端点,因此偏移多半块瓷砖)
if(state==STATE.L &&
MyDemo.passEnable(this.x - MAP_PIC0.5f, this.y)){
this.x -= 2f;
}else if(state==STATE.R &&
MyDemo.passEnable(this.x + MAP_PIC
1.5f, this.y)){
this.x += 2f;
}
if(MyDemo.downEnable(this.x, this.y)){
this.y -= 2f;
}
}

@Override
public void act(float delta) {
statetime += delta;
this.update();
this.check();
super.act(delta);
}

@Override
public void draw(SpriteBatch batch, float parentAlpha) {
batch.draw(currentFrame, x, y);
}

enum STATE {
L,R,I;
};

}