Java 2D 单人游戏
创立基于 Title 的地图
在 2D 游戏中,地图是整体构造,或者咱们叫做游戏地图(game map),通常是几个屏幕的宽度示意。有些游戏地图是屏幕的 20 倍;甚至是 100 位以上,次要特点是跨屏幕之后,让地图滚动显示,这种类型的游戏又叫做 2D 平台游戏(2D platform game)。所以平台游戏是指玩家从一个平台跑到另外一平台,在其中须要跑、跳等动作,除此之外,还要避开敌人,以及采血、加膂力等动作。本章咱们介绍怎么创立根本的地图、地图文件、碰撞侦测、加膂力、简略的敌人,以及生成背景的视差滚动成果等。
如果在游戏中如果巨幅图片,这种策略不是最好的解决方案,因为这会产生大量的内存耗费,可能会导致不装载图片。另外,巨幅图片不能阐明玩家哪个地图能够应用,哪些地图不能够应用。解决这个问题的个别策略是应用基于 title 的图片。tile-base 地图是把地图分解决成表格,每个单元格蕴含一小块的图片,或者没有图片。如下图示:
基于 tile 的地力点有点像应用预制块来创立游戏,不是同的就是这些块的色彩不,并且能够无限度应用色彩。Tile 地力的蕴含的援用属于表格的每个单元格 (cell) 所有,这样,咱们只须要一些小图片就能够实现整个 tile 的画面显示,并且咱们能够依据游戏的需要无限度创立背景画面,而不放心内存的束缚问题。大多数游戏都应用 16 或者 32 位的图片来示意单元格,咱们这里应用是 64 位的图片,如下图所示:
以上就是基于 tile 的地图,它有九个块成果能够很容易决定哪些是“solid”局部,哪些是”empty”的地图,这样,你能够晓得哪局部地图玩家能够跳,哪局部玩家能够穿墙。上面咱们首先实现这个 Tile 地图。
TileMap 类
package com.funfree.arklis.engine.tilegame;
import java.awt.Image;
import java.util.LinkedList;
import java.util.Iterator;
import com.funfree.arklis.graphic.*;
/**
性能:书写一个类用来示意 Tile 地图
备注:该类蕴含的数据用来实现地图、包含小怪。每个 tile 援用一个图片。当然这些图片会被
应用屡次。*/
public class TileMap{private Image[][] tiles; // 示意地图
private LinkedList sprites; // 示意游戏中的小怪
private Sprite player; // 示意玩家
/**
初始化成员变量时指定地图的宽和高
*/
public TileMap(int width, int height){tiles = new Image[width][height];
sprites = new LinkedList();}
/**
返回地图的宽度
*/
public int getWidth(){return tiles.length;}
/**
获取地图的高度
*/
public int getHeight(){return tiles[0].length;
}
/**
依据指定的坐标来获取相应的 tile。如果返回 null 值,那么示意地图越界了。*/
public Image getTile(int x, int y){if(x < 0 || x >= getWidth() ||
y < 0 || y >= getHeight()){
// 那么回返 null 值
return null;
}else{
// 否则返回 tile
return tiles[x][y];
}
}
/**
依据指定的坐标和指定的图片来更换 tile
*/
public void setTile(int x, int y, Image tile){tiles[x][y] = tile;
}
/**
返回玩家角色
*/
public Sprite getPlayer(){return player;}
/**
指定玩家角色
*/
public void setPlayer(Sprite player){this.player = player;}
/**
增加小怪地图中
*/
public void addSprite(Sprite sprite){sprites.add(sprite);
}
/**
删除该地图中的小怪
*/
public void removeSprite(Sprite sprite){sprites.remove(sprite);
}
/**
迭代出该地图中所有的小怪,除了玩家自身之外。*/
public Iterator getSprites(){return sprites.iterator();
}
}
除了地图,TileMap 还蕴含在游戏中的小怪,小怪能够地图中任何地位,并且没有边界的限度。如下图:
装载 Title 的地图
上面咱们须要有一个中央来保留该地图,而后在失当的时候理论创立该地图。Tile 地图游戏总是有多个级别的地图,该示例也不例外。如果咱们能够很轻松的形式来创立多个地图,那么玩家能够在实现一个地图之后,而后开始下一个地图的游戏情节。咱们创立地图时呼叫 TileMap 的 addTile()办法和 addSprite()办法,该办法的灵活性十分好,然而,这样编辑地图的级别比拟艰难,并且代码自身也不是很优雅。所以,大多数的 tile 游戏有本人的地图编辑器来创立地图。这个地图编辑器是可视化增加 tile 和小怪到游戏中,这样做的形式是十分简捷的形式。个别把地图保留到中介地图文件中,而这个文件是能够让游戏解析的。这样,咱们可定义一个基于文本地图文件,这样咱们能够编辑地图,因为 tile 是被定义在一个表格中的,所以文本文件中的每个字符能够示意一个 tile 或者是一个小怪 / 玩家,如下图:
其中”#”示意正文,而其它的示意 tile 的 row。该地图是固定的,所以能够咱们可让地图变量,并且可能增加更多的 line 或者让 line 更长。那么解析地图的步骤有三步:
- 读取每一行,疏忽正文行,而后把每行放到一个汇合中
- 创立一个 TileMap 对象,TileMap 的宽度就是汇合中最长元素的长度值,而高度就是汇合中的 line 的数量
- 解析每一 line 中的每个字符,依据该字符增加相应的 tile 或者 sprite 到地图中去。
实现这个工作是 ResourceManager 类。须要留神的是:增加 sprite 到 TileMap 中去时,开始,咱们须要创立不同的 Sprite 对象,这样,咱们可依据这些“主”怪来克隆小怪;第二,每个 sprite 不须要尺寸与 tile 的尺寸一样,所以,咱们须要保障每个 sprite 在 tile 中的地方,这些事件都在 addSprite()办法实现。本章以前的 Sprite 的地位雷同的屏幕,然而在本章示例中,sprite 的地位是雷同到 tile 地图。咱们应用 TileMapRender 的静态方法 titlesToPixels()来转换 tile 地位到地图的地位。该函数乘以 tile 的数值,
int pixelSize = numTiles * TITLE_SIZE;
以上公式能够让 sprite 挪动到地图上的任意一个地位,并且不须要调整 tile 的边界。也就是说,咱们有一个灵便的形式来创立地图和解析它们,以及创立一个 TileMap 对象。在示例中,所有的地图都在 map 文件夹中 (map1.txt 和 map2.txt) 等等。如果咱们须要下一个地图,只须要让代码去寻找下一个地图文件即可;如果没有找到,代码回装载第一个地图。也就是说,咱们不须要新地图,只须要在这个目录中删除地图文件即可,也不须要通知游戏有多少个地图存在。
ResourceManager 类
package com.funfree.arklis.engine.tilegame;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.io.*;
import java.util.ArrayList;
import javax.swing.ImageIcon;
import com.funfree.arklis.graphic.*;
import com.funfree.arklis.engine.tilegame.sprites.*;
/**
性能:书写一个 ResourceManager 类用来装载和治理 tile 图片和“主”怪。游戏中的
小怪是从“主”怪克隆而来。备注:该类有第四章 GameCore 所有性能。*/
public class ResourceManager {
private ArrayList tiles; // 保留文字地图的汇合
private int currentMap; // 以后地图
private GraphicsConfiguration gc; // 显示卡
// 用来被克隆的主怪
private Sprite playerSprite;
private Sprite musicSprite;
private Sprite coinSprite;
private Sprite goalSprite;
private Sprite grubSprite;
private Sprite flySprite;
/**
应用指定的显卡来创立资源管理器对象
*/
public ResourceManager(GraphicsConfiguration gc) {
this.gc = gc;
loadTileImages();
loadCreatureSprites();
loadPowerUpSprites();}
/**
从 images 目录获取图片
*/
public Image loadImage(String name) {
String filename = "images/" + name;
return new ImageIcon(filename).getImage();}
// 获取 Mirror 图片
public Image getMirrorImage(Image image) {return getScaledImage(image, -1, 1);
}
// 获取反转后的图片
public Image getFlippedImage(Image image) {return getScaledImage(image, 1, -1);
}
/**
实现图片的转换性能
*/
private Image getScaledImage(Image image, float x, float y) {
// 设置一个图片转换对象
AffineTransform transform = new AffineTransform();
transform.scale(x, y);
transform.translate((x-1) * image.getWidth(null) / 2,
(y-1) * image.getHeight(null) / 2);
// 创立通明的图片(不同半透明)Image newImage = gc.createCompatibleImage(image.getWidth(null),
image.getHeight(null),
Transparency.BITMASK);
// 绘制通明图片
Graphics2D g = (Graphics2D)newImage.getGraphics();
g.drawImage(image, transform, null);
g.dispose();
return newImage;
}
/**
从 maps 目录中装载下一下地图
*/
public TileMap loadNextMap() {
TileMap map = null;
while (map == null) {
currentMap++;
try {
map = loadMap("maps/map" + currentMap + ".txt");
}
catch (IOException ex) {if (currentMap == 1) {
// 无装载的地图,返回 null 值!
return null;
}
currentMap = 0;
map = null;
}
}
return map;
}
/**
从新装载 maps 目录下的地图文本
*/
public TileMap reloadMap() {
try {
return loadMap("maps/map" + currentMap + ".txt");
}
catch (IOException ex) {ex.printStackTrace();
return null;
}
}
/**
实现装载地图的外围办法
*/
private TileMap loadMap(String filename)throws IOException{ArrayList lines = new ArrayList();
int width = 0;
int height = 0;
// 读取文本文件中的每一行内容到汇合中保留
BufferedReader reader = new BufferedReader(new FileReader(filename));
for(;;) {String line = reader.readLine();
// 没有内容可读取了
if (line == null) {reader.close();
break;
}
// 增加每一行记录,除了正文
if (!line.startsWith("#")) {lines.add(line);
width = Math.max(width, line.length());
}
}
// 解析每一行,以便创立 TileEngine 对象
height = lines.size();
TileMap newMap = new TileMap(width, height);
for (int y = 0; y < height; y++) {
// 获取汇合中的字符串对象
String line = (String)lines.get(y);
// 把每个字符中的字符取出来
for (int x = 0; x < line.length(); x++) {char ch = line.charAt(x);
// 查看字符是否为 A,B,C 等字符
int tile = ch - 'A';
// 如果是字符 A
if (tile >= 0 && tile < tiles.size()) {
// 那么依据 tile 值来创立地图元素 -- 这里地图实现的外围办法
newMap.setTile(x, y, (Image)tiles.get(tile));
}
// 如果字符是示意小怪的,比方 0, ! 或者 *, 那么别离增加主怪到汇合中
else if (ch == 'o') {addSprite(newMap, coinSprite, x, y);
}else if (ch == '!') {addSprite(newMap, musicSprite, x, y);
}else if (ch == '*') {addSprite(newMap, goalSprite, x, y);
}else if (ch == '1') {addSprite(newMap, grubSprite, x, y);
}else if (ch == '2') {addSprite(newMap, flySprite, x, y);
}
}
}
// 增加玩家到地图中去
Sprite player = (Sprite)playerSprite.clone();
player.setX(TileMapRenderer.tilesToPixels(3));
player.setY(0);
newMap.setPlayer(player);
// 返回新的 tile 地图对象
return newMap;
}
/**
增加小怪到地图中去,并且是指定是的地位。*/
private void addSprite(TileMap map,Sprite hostSprite, int tileX, int tileY){if (hostSprite != null) {
// 从“主”怪克隆小怪
Sprite sprite = (Sprite)hostSprite.clone();
// 把小怪置中
sprite.setX(TileMapRenderer.tilesToPixels(tileX) +
(TileMapRenderer.tilesToPixels(1) -
sprite.getWidth()) / 2);
// 在底部调试该小怪
sprite.setY(TileMapRenderer.tilesToPixels(tileY + 1) -
sprite.getHeight());
// 增加该小怪到地图中去
map.addSprite(sprite);
}
}
// -----------------------------------------------------------
// 实现装载小怪和图片的代码
// -----------------------------------------------------------
public void loadTileImages() {
// 保留查找 A,B,C 等字符,这样能够十分不便的删除 images 目录下的 tiles
tiles = new ArrayList();
char ch = 'A';
while (true) {
String name = "tile_" + ch + ".png";
File file = new File("images/" + name);
if (!file.exists()) {break;}
tiles.add(loadImage(name));
ch++;
}
}
public void loadCreatureSprites() {
// 申明一个图片至多保留 4 个图片的数组
Image[][] images = new Image[4][];
// 装载右边朝向的图片
images[0] = new Image[] {
// 装载玩家图片
loadImage("player1.png"),
loadImage("player2.png"),
loadImage("player3.png"),
// 装载苍蝇图片
loadImage("fly1.png"),
loadImage("fly2.png"),
loadImage("fly3.png"),
// 装载蠕虫图片
loadImage("grub1.png"),
loadImage("grub2.png"),
};
images[1] = new Image[images[0].length];
images[2] = new Image[images[0].length];
images[3] = new Image[images[0].length];
for (int i = 0; i < images[0].length; i++) {
// 装载右朝向的图片
images[1][i] = getMirrorImage(images[0][i]);
// 装载左朝向“死亡”图片
images[2][i] = getFlippedImage(images[0][i]);
// 装载右朝向“死亡”图片
images[3][i] = getFlippedImage(images[1][i]);
}
// 创立 creature 动画对象
Animation[] playerAnim = new Animation[4];
Animation[] flyAnim = new Animation[4];
Animation[] grubAnim = new Animation[4];
for (int i = 0; i < 4; i++) {playerAnim[i] = createPlayerAnim(images[i][0], images[i][1], images[i][2]);
flyAnim[i] = createFlyAnim(images[i][3], images[i][4], images[i][5]);
grubAnim[i] = createGrubAnim(images[i][6], images[i][7]);
}
// 创立 creature 小怪(包含玩家)playerSprite = new Player(playerAnim[0], playerAnim[1],
playerAnim[2], playerAnim[3]);
flySprite = new Fly(flyAnim[0], flyAnim[1],
flyAnim[2], flyAnim[3]);
grubSprite = new Grub(grubAnim[0], grubAnim[1],
grubAnim[2], grubAnim[3]);
}
// 依据指定的图片来创立玩家动画对象
private Animation createPlayerAnim(Image player1,Image player2, Image player3){Animation anim = new Animation();
anim.addFrame(player1, 250);
anim.addFrame(player2, 150);
anim.addFrame(player1, 150);
anim.addFrame(player2, 150);
anim.addFrame(player3, 200);
anim.addFrame(player2, 150);
return anim;
}
// 依据指定的图片来创立苍蝇动画对象
private Animation createFlyAnim(Image img1, Image img2,Image img3){Animation anim = new Animation();
anim.addFrame(img1, 50);
anim.addFrame(img2, 50);
anim.addFrame(img3, 50);
anim.addFrame(img2, 50);
return anim;
}
// 依据指定的图片来创立蠕虫动画对象
private Animation createGrubAnim(Image img1, Image img2) {Animation anim = new Animation();
anim.addFrame(img1, 250);
anim.addFrame(img2, 250);
return anim;
}
private void loadPowerUpSprites() {
// 创立“心”怪用来加膂力
Animation anim = new Animation();
anim.addFrame(loadImage("heart1.png"), 150);
anim.addFrame(loadImage("heart2.png"), 150);
anim.addFrame(loadImage("heart3.png"), 150);
anim.addFrame(loadImage("heart2.png"), 150);
goalSprite = new PowerUp.Goal(anim);
// 创立 "星" 怪,用来加分
anim = new Animation();
anim.addFrame(loadImage("star1.png"), 100);
anim.addFrame(loadImage("star2.png"), 100);
anim.addFrame(loadImage("star3.png"), 100);
anim.addFrame(loadImage("star4.png"), 100);
coinSprite = new PowerUp.Star(anim);
// 创立“音乐”怪用来减速玩家
anim = new Animation();
anim.addFrame(loadImage("music1.png"), 150);
anim.addFrame(loadImage("music2.png"), 150);
anim.addFrame(loadImage("music3.png"), 150);
anim.addFrame(loadImage("music2.png"), 150);
musicSprite = new PowerUp.Music(anim);
}
}
图片起源:http://www.cungun.com/ 游戏