转载自:http://www.qiushurong.cn/2014/03/23/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》
————————————————————————————-
地图使用 :
————————————————————————————-
步骤简述:
地图渲染 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_PIC*0.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; }; }