`
xpenxpen
  • 浏览: 724971 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Java版赤色要塞源码分析

阅读更多
1.框架与环境搭建
1.1 本游戏使用了以下框架
slick2d 
lwjgl

首先去这两个网站分别将他们下载下来,(注意目前slick2d暂不支持lwjgl 3,所以需要下载lwjgl 2)然后建一个eclipse工程。
游戏源码(java,图片声音地图数据)可在我之前一篇博文中下载,本文文末代码只含有我加了中文注释的java代码。

1.2 lib目录下加入如下jar包
ibxm.jar
jinput.jar
jnlp.jar
jogg-0.0.7.jar
jorbis-0.0.15.jar
lwjgl.jar
slick.jar

1.3 native目录下加入如下本地库(本文以windows7 32位为例,其他类似)
jinput-dx8.dll
jinput-raw.dll
lwjgl.dll
OpenAL32.dll

1.4 关联native库
如图,展开lwjgl.jar,填上native库位置。


1.5 解决编译错误
删除ApplicationGameContainer.java,ScalableGameContainer.java
(注:slick2d最后一次更新是2013年,貌似N久不维护了,有些API已经过期了,导致编译不过)
Main.java修改main函数,将ApplicationGameContainer替换为AppGameContainer
至此编译通过,环境搭建完毕。


2. 运行
要分析源码最好的方法就是先——玩游戏,熟悉一下功能。
游戏的功能:
原汁原味复刻FC版赤色要塞,但是画面分辨率提高了
2D卷轴
2种难度供选择
可以continue
玩家8方向移动
秘籍模式(输入上上下下左右左右ZX变30条命,且武器升级)
OGG音乐播放
加载百分比显示
多张图合成到一张图提高加载效率
瓷砖,地图,地形,方向,触发地图(敌人)


3.主类分析
3.1 Main
public class Main extends BasicGame {
  public static final int DISPLAY_WIDTH = 1024;
  public static final int DISPLAY_HEIGHT = 960;
  public static final int SMALL_WIDTH = 512;
  public static final int SMALL_HEIGHT = 480;
  public IMode mode;
  public void init(GameContainer gc) throws SlickException {
    //......
    try {
      //载入进度条,字体,类
      loadProgressBar();
      loadFont();
      loadClasses();
    } catch(Throwable t) {
      Log.error("Loading error", t);
    }
        
    //接受按键
    input = new HumanInput(buttonMapping, gc);
    //秘籍
    konamiCode = new KonamiCode(this);
    startPlayer();
    resetNextFrameTime();
    //进入loading模式
    requestMode(Modes.LOADING, gc);
  }
  public void update(GameContainer gc, int delta) throws SlickException {
    //......
    int count = 0;  
    while(nextFrameTime <= Sys.getTime()) {
      //全屏切换
      fullScreenToggleCheck(gc);
      //接收按键
      input.snap();
      //模式模型更新
      mode.update(gc);
      nextFrameTime += (int)((Sys.getTimerResolution() * 0.01f) + 0.5f);
      if (++count == 8) {
        resetNextFrameTime();
        break;
      }
    }
  }
  public void render(GameContainer gc, Graphics g) throws SlickException {
    //模式渲染
    mode.render(gc, g);
    //......
  }
  public static void main(String[] args) throws SlickException {
    java.awt.Toolkit.getDefaultToolkit();
    Main main = new Main();
    AppGameContainer appGameContainer = new AppGameContainer(
        new ScalableGame(main, DISPLAY_WIDTH, DISPLAY_HEIGHT, true),
        SMALL_WIDTH, SMALL_HEIGHT, false);
    try {
      appGameContainer.setIcon("icons/32x32.png");
    } catch(Throwable t) {
      Log.error("Icon error", t);
    }
    appGameContainer.start();
  }  
}


由于用的slick2d框架,所以继承BasicGame,然后实现3个方法(init,update,render)即可。可看到,一般2d游戏框架都是这样的,程序员只需实现3个方法,init负责资源加载,update负责更新模型,render负责绘图。剩下的通用功能框架会负责考虑,如双缓冲绘图,FPS,跳帧。
其中update和render又转而交给IMode来处理

3.2 IMode

public interface IMode {
  public void init(Main main, GameContainer gc) throws SlickException;
  public void update(GameContainer gc) throws SlickException;
  public void render(GameContainer gc, Graphics g) throws SlickException;
}


老规矩,IMode的抽象化是关键,可以避免冗长的if else分支判断现在处于哪种模式,使得扩展更简单,达到方便切换模式的作用。
Main的init函数最后进入了LoadingMode

4. 资源加载
4.1 LoadingMode
该模式的使用可以不必等所有资源加载完毕,一开始就直接显示窗口,并提示用户加载进度。
如图


主要就是调用main.loadNext()

  public float loadNext() throws Throwable {
    
    switch(loadIndex) {
      //加载声音
      case 0:
        bossIntro = new Music("music/boss_intro.ogg", Song.STREAMING);
        break;
      //37 加载精灵
      case 37:
        loadSprites();
        break;
      //38 大图片
      case 38:
        loadLargeImages();
        break;
      //39 精灵大小,貌似是用作碰撞检测
      case 39:
        loadSizes();
        break;
      //40 关卡
      case 40:
        loadStages(stages);
        break;
      case 41:
        //全部加载完进入介绍模式
        requestMode(Modes.INTRO, gc);
        //requestMode(Modes.SUNSET, gc);
    	//setMode(new SunsetMode2(), gc);
    	//setMode(new StageViewMode(), gc);
        break;
    }
    
    return ++loadIndex / 42f;
  }


4.2 XMLPackedSheet使用
看一下如何加载精灵。用了slick2d的一个加载大图片的功能XMLPackedSheet
  private void loadSprites() throws Throwable {
    
    XMLPackedSheet pack1 = new XMLPackedSheet(
        "images/sprites-1.png", "images/sprites-1.xml");
  }


看一下png和xml是什么样地


xml里面存储了某个小图片的位置
<sheet>
	<sprite name="explosion-2.png" x="1" y="1" width="128" height="124" />
	<sprite name="gray-jeep-1.png" x="131" y="1" width="96" height="96" />
</sheet>


这样的好处就是多张图合成到一张图可以提高加载效率。
其实,制作这种图有工具可以使用,如slick2d自带的packulike(下载来的slick.zip的tool目录下),或者imagepacker

4.3 大图绘制
加载如下
  private void loadLargeImages() throws Throwable {
    sunset = loadExtraLargeImage("sunset", "large-0", "large-1");
    map = loadLargeImage("map", "large-1");
    jeepYeah = loadExtraLargeImage("jeep-yeah", "large-2", "large-3");
  }


如结尾日落图片,数据在sunset.dat里,tile则在large-0.png,large-1.png里


现在将Main里面的loadNext函数最后改为setMode(new SunsetMode2(), gc);

看下如何显示结尾日落图片

public class SunsetMode2 implements IMode {
  
  public Main main;
    
  @Override
  public void init(Main main, GameContainer gc) throws SlickException {
    this.main = main;
  }

  @Override
  public void update(GameContainer gc) throws SlickException {
  }

  @Override
  public void render(GameContainer gc, Graphics g) throws SlickException {
    main.sunset.draw(0, 0);
    //main.map.draw(0, 0);
    //main.jeepYeah.draw(0, 0);
  }
}


最后拼合效果如下图


4.4 地图加载

  private void loadStage(int index, Stage stage) throws Throwable {
    //加载瓷砖,地图,地形,方向,触发地图(敌人)
    loadTiles(index, stage);
    loadMaps(index, stage);
    loadTypes(index, stage);
    loadDirections(index, stage);
    loadTriggerMap(stage.mapHeight, triggerSizes, index, stage);
  }


瓷砖,即tile,用过地图编辑器的都很熟悉了,地图由许多tile拼接而成。
地图,记录了每一格究竟用哪个tile
地形,每一格是固体,还是水。。。?
方向我没研究。
触发地图(敌人)就是敌人登场表,记录敌人在哪一个。

4.5 一个查看地图的小程序

现在将Main里面的loadNext函数最后改为setMode(new StageViewMode(), gc);
然后运行StageViewMode,注意程序会将地图以截屏的方式保存到d盘根目录。d:/jackal{$x}{$y}.png, x和y是递增的数字。注意执行前备份d盘根目录的同名png文件,以防文件被覆盖造成惨剧。

public class StageViewMode implements IMode {
  
  Image copy;
  GameMode gameMode;
  int xTile;
  int yTile;
  
  public Main main;
  public GameContainer gc;  
  public IInput input;
    
  @Override
  public void init(Main main, GameContainer gc) throws SlickException {
    this.main = main;
    this.gc = gc;
    this.input = main.input;
    
    gameMode = new GameMode();
    gameMode.setStage(0, main.stages[0], false);
    System.out.println("tilemap width=" +gameMode.tileMap.length+",height=" +gameMode.tileMap[0].length);
  }

  @Override
  public void update(GameContainer gc) throws SlickException {
  }

  @Override
  public void render(GameContainer gc, Graphics g) throws SlickException {
	for (int y = 29; y >= 0; y--) {
		for (int x = 31; x >= 0; x--) {
			main.draw(gameMode.tiles[gameMode.tileMap[y + yTile*30][x + xTile*32]],
					(x << 5) - 0, (y << 5) - 0);
			
			int type = gameMode.typesMap[y + yTile*30][x + xTile*32];
			
			Color originalColor = g.getColor();
			g.setColor(Color.red);
			g.drawRect(x << 5, y << 5, 32, 32);
			if (type==GameMode.TYPE_SOLID) {
				Color transparentColor = new Color(255, 0, 0, 0.5f);
				g.setColor(transparentColor);
				g.fillRect(x << 5, y << 5, 32, 32);
			} else if (type==GameMode.TYPE_SHIELD) {
				Color transparentColor = new Color(0, 255, 0, 0.5f);
				g.setColor(transparentColor);
				g.fillRect(x << 5, y << 5, 32, 32);
			} else if (type==GameMode.TYPE_WATER) {
				Color transparentColor = new Color(0, 255, 255, 0.5f);
				g.setColor(transparentColor);
				g.fillRect(x << 5, y << 5, 32, 32);
			}
			g.setColor(originalColor);
			
			
			//System.out.println("triggerMap.length->"+gameMode.triggerMap.length);
			int[][] triggers = gameMode.triggerMap[y + yTile*30];
	        for(int i = triggers.length - 1; i >= 0; i--) {
	            int[] trigger = triggers[i];
	            //System.out.println("trigger->"+trigger[0]);
	            int displatX = trigger[1];
	            boolean displayTrigger =false;
	            if (trigger[1] >= Main.DISPLAY_WIDTH && xTile == 1) {
	            	displatX = trigger[1]- Main.DISPLAY_WIDTH;
	            	displayTrigger= true;
	            } else if (trigger[1] < Main.DISPLAY_WIDTH && xTile == 0) {
	            	displatX = trigger[1];
	            	displayTrigger= true;
	            }
	            if (displayTrigger) {
	            	
		            if (trigger[0]==Triggers.SOLDIER_WALKER || trigger[0]==Triggers.SOLDIER_STATIONARY) {
		            	main.draw((main.enemySoldiers)[1][1], 
		            			displatX, y <<5); 
		            } else if (trigger[0]==Triggers.GREEN_BOAT) {
		            	main.draw((main.greenBoats)[0], 
		            			displatX, y <<5); 
			        }
	            }
	        }
		}
	}
	copy = new Image(Main.SMALL_WIDTH, Main.SMALL_HEIGHT);
	g.copyArea(copy, 0, 0);
	ImageOut.write(copy, "d:/jackal" + yTile + xTile + ".png");
	
	if (xTile == 1) {
		xTile = 0;
		yTile++;
	} else {
		xTile++;
	}
	if (yTile > 11) {
		System.exit(0);
	}
  }
}



下图是地图的一隅


游戏地图绘制大致就是以下流程:
先绘制底图,只是图片,没有碰撞检测
然后记录障碍物(固体,水。。。),控制玩家是否可以通过。
再加上敌人

下图是第一关整个地图,用的 Picture Merge Genius工具合成的。


本来想做个地图编辑器的,但是比较懒,没时间做了。


5. GameMode主程序

5.1 触发机关(敌人登场)
processTriggers函数判断镜头Y坐标是否达到敌人位置,若达到,则敌人出现(位置已由之前的loadTriggerMap载入)

敌人就不分析了,跟之前本博客分析的STG飞机游戏差不多。敌人都继承自一个共同的Enemy。Java语言就是这样,通过一层层抽象,实现了复杂的功能。

5.2 groupmap
游戏中有门,被炸开后会出现通路。还有房子被炸开后出现破房子,这些必须要改Stage.tileMap才行,是通过Stage.groupMap这个变量实现的。


6.结尾
java很好很强大,降低了游戏制作的门槛,得以圆我们以前做游戏的梦。不过通过分析一个游戏我们也发现了,游戏要做好,程序、图片、声音、关卡设计一个都不能少。程序决定了游戏的可玩性高不高,所以还是很重要的。

  • 大小: 17 KB
  • 大小: 31.8 KB
  • 大小: 25.3 KB
  • 大小: 8.7 KB
  • 大小: 23.1 KB
  • 大小: 26.7 KB
  • 大小: 733.3 KB
分享到:
评论

相关推荐

    Java版赤色要塞

    【标题】"Java版赤色要塞" 指的是一项使用Java编程语言重新实现经典游戏"赤色要塞"的项目。这个项目可能是为了学习和实践Java编程、游戏开发或者重温经典游戏的乐趣。在Java中开发游戏可以利用Java的跨平台特性,...

    赤色要塞.rar经典FC游戏

    《赤色要塞》是一款经典的8位FC(Family Computer,即任天堂红白机)游戏,深受玩家喜爱。这款游戏在1980年代末期推出,由日本的KONAMI公司开发,以其独特的游戏机制、丰富的关卡设计和紧张刺激的战斗体验闻名。 ...

    FC赤色要塞NES文件

    FC赤色要塞NES文件

    mrp游戏 赤色要塞

    本人收藏的MRP游戏,不需要联网更新。。。用废卡或把短信模式调为空号即可免费玩。。(论坛上有此类设置的介绍,可百度)

    绿色兵团,经典小游戏!

    赤色要塞:运行VirtuaNes,选择“文件-〉打开”,选中目录rom里面的“赤色要塞.nes文件”,确定。选择“编辑-〉电影-〉重放”,选中目录rom里面的“赤色要塞.vmv文件”,确定,然后就可以开始欣赏了。 绿色兵团:...

    JAVA微信开发之AirKiss 简单demo

    主要实现通过JAVA 传入授权参数到 js-sdk 调出微信一键配置界面,可参考我的博客https://blog.csdn.net/maizang52/article/details/79851960, 初始界面没有优化,介意者请勿下载。

    java根据PDF模板自动生成PDF文件

    用途:根据PDF模板生成PDF文件,将数据库查询的数据插入到模板指定未知,然后生成新的PDF文件 原理: 代码说明:exprotPDF_Main 为主文件。...支持各种java调用数据库数据,生成PDF文件。多个版本供参考。

    mysql_connector_java_5.1.44

    JDBC是Java平台的标准API,由Java SE(标准版)提供,用于与各种类型的数据库进行通信。它定义了一组接口和类,使得开发者无需关心底层数据库的具体细节,就能在Java应用程序中实现数据存取功能。MySQL Connector/J...

    500款红白机游戏大合集

    红白机游戏合集包含了众多经典游戏,如《魂斗罗》系列、《93超级魂》、《沙罗曼蛇》系列、《超级马里奥兄弟》、《赤色要塞》、《双截龙》、《飞龙之拳3》、《坦克大战》、《松鼠大作战》、《淘金者...

    FC nes游戏机模拟器 visual c++源代码.zip

    在压缩包中,除了源代码文件外,还包含了一些经典的NES游戏ROM文件,如"沙漠漫蛇.NES"、"魂斗罗.NES"、"赤色要塞.nes"、"绿色兵团.NES"、"超级玛莉.nes"、"马戏团.nes"。这些文件实际上是NES游戏的二进制数据,...

    C#自制IP地址控件(源码+Demo)

    源码分析可以帮助我们理解控件的具体实现细节,如属性的getter和setter方法是如何工作的,以及如何在控件内部处理输入验证。通过阅读源码,开发者可以学习到自定义控件设计的最佳实践,以及如何在C#中有效利用属性...

    自己做的FC/NES模拟器0.0版本

    压缩包内的文件名称列表包括了一些FC/NES游戏的ROM文件,例如“绿色兵团.NES”、“沙漠漫蛇.NES”、“魂斗罗.NES”、“赤色要塞.nes”、“超级玛莉.nes”、“马戏团.nes”。这些都是FC/NES平台上的知名游戏,这些ROM...

    学院团委辅导赤色社团工作汇报沟通资料.docx

    学院团委辅导赤色社团工作汇报沟通资料.docx

    分布式学习从基础到深入

    包含了四本书:《大型分布式网站架构设计与实践》、《大型网站系统与JAVA中间件实践》、《分布式Java应用基础与实践》、《核心原理与案例分析》。可为有java开发基础的开发人员提供从基础到深入的学习分布式技术

    word合并,多个word合并成一个

    将多个word路径下的word合并成一个word 第一个路径合并后在第一个位置,最后一个路径合并后在最后

    通达信公式指标涨停攻击 源码.doc

    2. 赤色生命线(MA(CLOSE,56)):慢速移动平均线,代表长期趋势。 交易策略: - 当事情线从下方向上穿越生命线,这通常被视为买入信号,因为这表明短期趋势可能正在转强。 - 同时,当DIFF(MACD的快线)上穿DEA...

    jdk api 1.8中文文档

    这个中文版文档使得中国开发者能更方便地理解和应用Java 1.8的新特性,提升开发效率。 在Java 1.8中,最显著的改变之一是Lambda表达式。这是一种简洁的匿名函数表示方式,可以用于简化那些只需要一次性的、短小的...

    根据word模板导出word、PDF文档

    根据word模板导出word、PDF文档,功能全,内容多,介绍详细。主要实现根据word模板及模板中的坐标($[标明.字段名])的形式进行查询出所对应的数据,然后分别生成PDF 和 WORD功能。 本功能是将各个模板的数据均查询...

Global site tag (gtag.js) - Google Analytics