Java游戏编程不完全详解-5

Java 2D单人游戏

创建基于Title的地图

在2D游戏中,地图是整体结构,或者我们叫做游戏地图(game map),通常是几个屏幕的宽度表示。有些游戏地图是屏幕的20倍;甚至是100位以上,主要特点是跨屏幕之后,让地图滚动显示,这种类型的游戏又叫做2D平台游戏(2D platform game)。所以平台游戏是指玩家从一个平台跑到另外一平台,在其中需要跑、跳等动作,除此之外,还要避开敌人,以及采血、加体力等动作。本章我们介绍怎样创建基本的地图、地图文件、碰撞侦测、加体力、简单的敌人,以及生成背景的视差滚动效果等。

如果在游戏中如果巨幅图片,这种策略不是最好的解决方案,因为这会产生大量的内存消耗,可能会导致不装载图片。另外,巨幅图片不能说明玩家哪个地图可以使用,哪些地图不可以使用。解决这个问题的一般策略是使用基于title的图片。tile-base地图是把地图分解决成表格,每个单元格包含一小块的图片,或者没有图片。如下图示:

Java游戏编程不完全详解-5_第1张图片

基于tile的地力点有点像使用预制块来创建游戏,不是同的就是这些块的颜色不,并且可以无限制使用颜色。Tile地力的包含的引用属于表格的每个单元格(cell)所有,这样,我们只需要一些小图片就可以实现整个tile的画面显示,并且我们可以根据游戏的需求无限制创建背景画面,而不担心内存的约束问题。大多数游戏都使用16或者32位的图片来表示单元格,我们这里使用是64位的图片,如下图所示:

Java游戏编程不完全详解-5_第2张图片

以上就是基于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还包含在游戏中的小怪,小怪可以地图中任何位置,并且没有边界的限制。如下图:

Java游戏编程不完全详解-5_第3张图片

装载Title的地图

下面我们需要有一个地方来保存该地图,然后在恰当的时候实际创建该地图。Tile地图游戏总是有多个级别的地图,该示例也不例外。如果我们可以很轻松的方式来创建多个地图,那么玩家可以在完成一个地图之后,然后开始下一个地图的游戏情节。我们创建地图时呼叫TileMap的addTile()方法和addSprite()方法,该方法的灵活性非常好,但是,这样编辑地图的级别比较困难,并且代码本身也不是很优雅。所以,大多数的tile游戏有自己的地图编辑器来创建地图。这个地图编辑器是可视化添加tile和小怪到游戏中,这样做的方式是非常简捷的方式。一般把地图保存到中介地图文件中,而这个文件是可以让游戏解析的。这样,我们可定义一个基于文本地图文件,这样我们可以编辑地图,因为tile是被定义在一个表格中的,所以文本文件中的每个字符可以表示一个tile或者是一个小怪/玩家,如下图:

Java游戏编程不完全详解-5_第4张图片

其中”#”表示注释,而其它的表示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/ 游戏

你可能感兴趣的:(java游戏开发)