把草稿删了,没想到也把发表的文章给删了,只好重新写了。不得不吐槽一下CSDN的博客系统。
简介
在我们开发游戏过程中,我们需要设置不同的关卡,如果我们直接使用使用图片来加载游戏,这将会使我们的游戏安装包本身变得非常臃肿。不过好在Libgdx给我们提供了瓦片地图,我们可以直接使用编辑器来编辑地图,然后使用Libgdx提供的API解析加载,而且地图图片还可以在不同地图上重复使用,节约了游戏安装包的空间。
瓦片地图介绍
地图编辑器 Tiled: http://www.mapeditor.org/download.html
TMX文件介绍:
注意:在这个文件中( image source=”tileset.png” trans=”5e81a2” width=”692” height=”692”/) image source默认是地图编辑器的绝对路径,只需要用笔记本修改为相对路径,然后吧对应的图片文件放到同一个目录下面就OK
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.0" orientation="orthogonal" width="30" height="12" tilewidth="21" tileheight="21">
<tileset firstgid="1" name="tileset" tilewidth="21" tileheight="21" spacing="2" margin="2">
<image source="tileset.png" trans="5e81a2" width="692" height="692"/>
</tileset>
<tileset firstgid="901" name="backgrounds" tilewidth="21" tileheight="21">
<image source="backgrounds.png" trans="5e81a2" width="231" height="189"/>
</tileset>
<layer name="background" width="30" height="12">
<data encoding="base64">
加密的数据,太长,省略了
</data>
</layer>
<layer name="terrain" width="30" height="12">
<data encoding="base64">
加密的数据,太长,省略了
</data>
</layer>
<layer name="foreground" width="30" height="12">
<data encoding="base64">
加密的数据,太长,省略了
</data>
</layer>
<objectgroup name="objects" width="30" height="12">
<object name="player" x="63" y="126" width="21" height="21"/>
<object name="item.chest" x="147" y="63" width="21" height="21"/>
<object name="item.coin" x="252" y="21" width="21" height="21"/>
<object name="item.key" x="567" y="126" width="21" height="21"/>
<object name="trigger.exit" x="609" y="0" width="21" height="252"/>
<object name="item.coin" x="273" y="21" width="21" height="21"/>
<object name="item.coin" x="357" y="21" width="21" height="21"/>
<object name="item.coin" x="378" y="21" width="21" height="21"/>
<object name="trigger.exit" x="420" y="0" width="21" height="252"/>
</objectgroup>
<objectgroup name="physics" width="30" height="12">
<object x="0" y="168">
<polyline points="0,0 63,0 63,-21 105,-21 105,84 0,84 0,0"/>
</object>
<object x="378" y="189">
<polyline points="0,-21 -21,0 -21,63 -105,63 -105,0 -126,-21"/>
</object>
<object x="252" y="168">
<polyline points="0,0 21,0 21,-21 42,-21 42,-42 84,-42 84,-21 105,-21 105,0 126,0"/>
</object>
<object x="420" y="252">
<polyline points="0,0 0,-63 21,-63 21,-84 42,-84 42,-126 63,-126 63,-105 84,-105 84,0 0,0"/>
</object>
<object x="546" y="252">
<polyline points="0,0 0,-42 21,-42 21,-105 42,-105 42,-126 84,-126 84,0 0,0"/>
</object>
</objectgroup>
</map>
Libgdx 相关API介绍
1.com.badlogic.gdx.maps.Map implements Disposable
Map代表了我们用地图编辑器编辑完之后的TMX文件,实际上是其子类TiledMap来具体实现。主要包含1. MapProperties
TMX文件的各种属性。2. MapLayers
,Map layers是有序的并且是可索引的,可通过index来访问, MapLayer包含MapObject
对象,可以通过方法来获取访问,Libgdx中有不同的MapObject可供使用,比如CircleMapObject, RectangleMapObject
。
方法、属性 | 描述 |
---|---|
layers : MapLayers | 地图中所包含的图层 |
properties : MapProperties | 地图中所包含的对象 |
getLayers() : MapLayers | 获取地图中所包含的图层 |
getProperties() : MapProperties | 获取地图中的对象 |
2.com.badlogic.gdx.maps.tiled.TiledMap extends Map
TiledMap是Libgdx中真正承载TMX地图的类,代表了tiled map,增加了tiles 和 tiledsets
3.com.badlogic.gdx.maps.tiled.TiledMapTile : interface
代表了TiledMap中每个网格(瓦片),留意其方法就可以了
方法 | 描述 |
---|---|
getId() : int | 瓦片的ID |
getTextureRegion() : TextureRegion | 瓦片使用的TextureRegion |
setTextureRegion(TextureRegion textureRegion) | 设置瓦片的纹理 |
getOffsetX() : float | 瓦片相对于x轴的位置 |
getProperties() : MapProperties | 单个瓦片的属性 |
4.com.badlogic.gdx.maps.tiled.TiledMapTileSet implements Iterable<TiledMapTile
>
TiledMapTile的实例,通常用来组成TiledMapLayer
方法、属性 | 描述 |
---|---|
name : String | 瓦片的name |
tiles : IntMap<TiledMapTile > |
瓦片 |
getTile (int id) : TiledMapTile | 获取瓦片实例 |
iterator () : Iterator<TiledMapTile > |
便利所有瓦片 |
removeTile (int id) | 移除指定瓦片 |
size () | 瓦片的数量 |
5.com.badlogic.gdx.maps.tiled.TiledMapTileSets implements Iterable<TiledMapTileSet
>
其实看类名就知道是TiledMapTileSet的集合类,主要是提供工具帮助访问处理TiledMapTileSet。不做过多解释,可看源码。
6.com.badlogic.gdx.maps.MapLayer
MapLayer就是我们在地图编辑器中创建的Layer(普通Layer和ObjectLayer)对应,包含了Layer对应的object和properties
// 下面列出的就是类中常用的属性,方法就是对属性的读写
private String name = ""; // 图层的名字
private float opacity = 1.0f; // 透明度
private boolean visible = true; // 是否可见
private MapObjects objects = new MapObjects(); // 包含的对象
private MapProperties properties = new MapProperties();// 包含的属性
7.com.badlogic.gdx.maps.tiled.TiledMapTileLayer extends MapLayer
TiledMap的Layer,具体的实现类
// Layer的高度和宽度
private int width;
private int height;
// 瓦片的高度和宽度
private float tileWidth;
private float tileHeight;
// 内部类,里面包含了private TiledMapTile tile
private Cell[][] cells;
8.com.badlogic.gdx.maps.MapLayers implements Iterable<MapLayer
>
可被遍历的MapLayer合集,方便访问操作MapLayer,主要是TMX文件也是很多MapLayer的集合。
方法、属性 | 描述 |
---|---|
get (int index) : MapLayer | 根据索引获取MapLayer |
get (String name) : MapLayer | 根据名字返回找到的第一个匹配MapLayer |
getIndex (String name) : int | 根据名字返回查找到的第一个图片的位置 |
getCount () : int | 获取TiledMap中Layer的数量 |
remove (int index) | 移除指定TiledMapLayer |
iterator () : Iterator<MapLayer > |
迭代访问 |
9.com.badlogic.gdx.maps.MapObject
TiledMap里面包含的对象的基本属性,比如: name, opacity, color
private String name = "";
private float opacity = 1.0f;
private boolean visible = true;
private MapProperties properties = new MapProperties();
private Color color = Color.WHITE.cpy();
10.com.badlogic.gdx.maps.MapObjects implements Iterable<MapObject
>
MapObject的集合,不做过多解释,可自己查询源码。
11.com.badlogic.gdx.maps.MapProperties
可索引的(indexed)string值,代表了Map中元素的属性,可以被递归访问,修改,和添加属性。
方法、属性 | 描述 |
---|---|
get (int index) : MapLayer | 根据索引获取MapLayer |
get (String name) : MapLayer | 根据名字返回找到的第一个匹配MapLayer |
getIndex (String name) : int | 根据名字返回查找到的第一个图片的位置 |
getCount () : int | 获取TiledMap中Layer的数量 |
remove (int index) | 移除指定TiledMapLayer |
iterator () : Iterator<MapLayer > |
迭代访问 |
12.com.badlogic.gdx.maps.tiled.TmxMapLoader
地图加载去,使用方法简单,可参考后面的源码
13.com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer
渲染地图, 使用方法简单,可参考后面源码
代码用例展示
1.TiledMapSample这个用例只是简单的展示加载和渲染地图,以及操作照相机,来展示地图的不同部分。
public class TiledMapSample extends ApplicationAdapter {
private static final float VIRTUAL_WIDTH = 384.0f;
private static final float VIRTUAL_HEIGHT = 216.0f;
private static final float CAMERA_SPEED = 100.0f;
private OrthographicCamera camera;
private Viewport viewport;
private TiledMap map;
private TmxMapLoader loader;
private OrthogonalTiledMapRenderer renderer;
private Vector2 direction;
@Override
public void create() {
camera = new OrthographicCamera();
viewport = new FitViewport(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, camera);
loader = new TmxMapLoader();
map = loader.load("p/platformer.tmx");
renderer = new OrthogonalTiledMapRenderer(map);
direction = new Vector2();
}
@Override
public void dispose() {
map.dispose();
renderer.dispose();
}
@Override
public void render() {
Gdx.gl.glClearColor(0.8f, 0.8f, 0.8f, 1.0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
updateCamera();
renderer.setView(camera);
renderer.render();
}
@Override
public void resize(int width, int height) {
viewport.update(width, height);
}
private void updateCamera() {
direction.set(0.0f, 0.0f);
int mouseX = Gdx.input.getX();
int mouseY = Gdx.input.getY();
int width = Gdx.graphics.getWidth();
int height = Gdx.graphics.getHeight();
if (Gdx.input.isKeyPressed(Keys.LEFT) || (Gdx.input.isTouched() && mouseX < width * 0.25f)) {
direction.x = -1;
}
else if (Gdx.input.isKeyPressed(Keys.RIGHT) || (Gdx.input.isTouched() && mouseX > width * 0.75f)) {
direction.x = 1;
}
if (Gdx.input.isKeyPressed(Keys.UP) || (Gdx.input.isTouched() && mouseY < height * 0.25f)) {
direction.y = 1;
}
else if (Gdx.input.isKeyPressed(Keys.DOWN) || (Gdx.input.isTouched() && mouseY > height * 0.75f)) {
direction.y = -1;
}
direction.nor().scl(CAMERA_SPEED * Gdx.graphics.getDeltaTime());;
camera.position.x += direction.x;
camera.position.y += direction.y;
TiledMapTileLayer layer = (TiledMapTileLayer)map.getLayers().get(0);
float cameraMinX = viewport.getWorldWidth() * 0.5f;
float cameraMinY = viewport.getWorldHeight() * 0.5f;
float cameraMaxX = layer.getWidth() * layer.getTileWidth() - cameraMinX;
float cameraMaxY = layer.getHeight() * layer.getTileHeight() - cameraMinY;
camera.position.x = MathUtils.clamp(camera.position.x, cameraMinX, cameraMaxX);
camera.position.y= MathUtils.clamp(camera.position.y, cameraMinY, cameraMaxY);
camera.update();
}
}
2. TiledMapObjectsSample示例不仅加载渲染地图也解析了里面包含的对象
public class TiledMapObjectsSample extends ApplicationAdapter {
private static final float SCALE = 0.2916f;
private static final int VIRTUAL_WIDTH = (int) (1280 * SCALE);
private static final int VIRTUAL_HEIGHT = (int) (720 * SCALE);
private static final float CAMERA_SPEED = 100.0f;
private OrthographicCamera camera;
private Viewport viewport;
private SpriteBatch batch;
private TiledMap map;
private TmxMapLoader loader;
TiledMapTileLayer layer;
private OrthogonalTiledMapRenderer renderer;
private Vector2 direction;
private Array<Sprite> enemies;
private Array<Sprite> items;
private Array<Sprite> triggers;
private Sprite player;
private TextureAtlas atlas;
@Override
public void create() {
camera = new OrthographicCamera();
viewport = new FitViewport(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, camera);
batch = new SpriteBatch();
loader = new TmxMapLoader();
map = loader.load("p/tiled-objects.tmx");
renderer = new OrthogonalTiledMapRenderer(map, batch);
atlas = new TextureAtlas(Gdx.files.internal("p/sprites.atlas"));
direction = new Vector2();
processMapMetadata();
}
@Override
public void dispose() {
map.dispose();
renderer.dispose();
atlas.dispose();
batch.dispose();
}
@Override
public void render() {
Gdx.gl.glClearColor(0.8f, 0.8f, 0.8f, 1.0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
updateCamera();
renderer.setView(camera);
renderer.render();
batch.begin();
for (Sprite enemy : enemies) {
enemy.draw(batch);
}
for (Sprite item : items) {
item.draw(batch);
}
for (Sprite trigger : triggers) {
trigger.draw(batch);
}
player.draw(batch);
batch.end();
}
@Override
public void resize(int width, int height) {
viewport.update(width, height);
}
private void updateCamera() {
direction.set(0.0f, 0.0f);
int mouseX = Gdx.input.getX();
int mouseY = Gdx.input.getY();
int width = Gdx.graphics.getWidth();
int height = Gdx.graphics.getHeight();
if (Gdx.input.isKeyPressed(Keys.LEFT) || (Gdx.input.isTouched() && mouseX < width * 0.25f)) {
direction.x = -1;
} else if (Gdx.input.isKeyPressed(Keys.RIGHT) || (Gdx.input.isTouched() && mouseX > width * 0.75f)) {
direction.x = 1;
}
if (Gdx.input.isKeyPressed(Keys.UP) || (Gdx.input.isTouched() && mouseY < height * 0.25f)) {
direction.y = 1;
} else if (Gdx.input.isKeyPressed(Keys.DOWN) || (Gdx.input.isTouched() && mouseY > height * 0.75f)) {
direction.y = -1;
}
direction.nor().scl(CAMERA_SPEED).scl(Gdx.graphics.getDeltaTime());
;
camera.position.x += direction.x;
camera.position.y += direction.y;
// 获取Map编辑器里面最底层的Layer,同时也是tml文件里面最上面的一层layer
float cameraMinX = viewport.getWorldWidth() * 0.5f;
float cameraMinY = viewport.getWorldHeight() * 0.5f;
float cameraMaxX = layer.getWidth() * layer.getTileWidth() - cameraMinX;
float cameraMaxY = layer.getHeight() * layer.getTileHeight() - cameraMinY;
camera.position.x = MathUtils.clamp(camera.position.x, cameraMinX, cameraMaxX);
camera.position.y = MathUtils.clamp(camera.position.y, cameraMinY, cameraMaxY);
camera.update();
}
private void processMapMetadata() {
// Load entities
System.out.println("Searching for game entities...\n");
enemies = new Array<Sprite>();
items = new Array<Sprite>();
triggers = new Array<Sprite>();
layer = (TiledMapTileLayer) map.getLayers().get(0);
MapObjects objects = map.getLayers().get("objects").getObjects();
System.out.println("width=" + layer.getWidth() +" tileWidth" + layer.getTileWidth());
for (MapObject object : objects) {
String name = object.getName();;
String[] parts = name.split("[.]");
RectangleMapObject rectangleObject = (RectangleMapObject) object;
Rectangle rectangle = rectangleObject.getRectangle();
System.out.println("Object found");
System.out.println("- name: " + name);
System.out.println("- position: (" + rectangle.x + ", " + rectangle.y + ")");
System.out.println("- size: (" + rectangle.width + ", " + rectangle.height + ")");
if (name.equals("enemy")) {
Sprite enemy = new Sprite(atlas.findRegion("enemy"));
enemy.setPosition(rectangle.x, rectangle.y);
enemies.add(enemy);
} else if (name.equals("player")) {
player = new Sprite(atlas.findRegion("player"));
player.setPosition(rectangle.x, rectangle.y);
} else if (parts.length > 1 && parts[0].equals("item")) {
Sprite item = new Sprite(atlas.findRegion(parts[1]));
item.setPosition(rectangle.x, rectangle.y);
items.add(item);
} else if (parts.length > 0 && parts[0].equals("trigger")) {
Sprite trigger = new Sprite(atlas.findRegion("pixel"));
trigger.setColor(1.0f, 1.0f, 1.0f, 0.5f);
trigger.setScale(rectangle.width, rectangle.height);
trigger.setPosition(rectangle.x - rectangle.width * 0.5f, rectangle.y + rectangle.height * 0.5f);
triggers.add(trigger);
}
}
}
}