Java版推箱子(搬箱子)游戏开发入门示例及源码

推(搬)箱子,又名Sokoban,仓库番等,是一款堪称古玩级的电脑游戏。

提起它,笔者相信没什么人会感觉到陌生,更没什么生物会连听都没听说过。它的发展历史之久远,甚至超越了俄罗斯方块(1988年电脑游戏化)。

这款游戏最初起源于日本,是个很难争辩的事实(我知道有人反对,但笔者确实找不到什么有力的反对证据)。他由日本人(哎……)今川宏行在1981年创立游戏规则,并于1982年经日本软件公司Thinking Rabbit正式发布。比较遗憾的是,早期的推箱子并没有PC版,笔者在网络上搜索到的老版游戏也大多为90年以前的Mac OS下程式。

但说起真正令推箱子风靡于PC机的,却该感谢我们的*同胞李果兆先生。是他在1994年开发的仓库世家,才真正令推箱子游戏在世界各地大受推崇;仔细说来,推箱子这款小游戏之所以能有今时今日的声望与地位,固然有今川宏行的开创之功,但若说到贡献最大,承前启后的,则非中国*的李果兆先生莫属。

推箱子游戏的规则非常简单,就是用尽量少的推动或移动把所有箱子都推到目标点上。箱子只能推动而不能拉动;一次只能推动一个箱子。然而,尽管它的规则是很简单的,但对于不同难度的关卡,所需要的脑力却是截然不同的,有些关卡可能会花费您几个小时、几天甚至几个月的时间,也正是这种简单性和复杂性的结合,最终令推箱子类游戏风靡全球!

本回笔者在Blog中提供的,就是一款Java版推箱子游戏的简单实现。

笔者设定[上、下、左、右]为方向控制 ,[S]键为后退到上一步操作,[ESC]为重新开始当前关卡,点击键盘上对应关卡的数字键可以直接选关,需要注意的是笔者以HP限制了角色的移动次数,HP归0则挑战失败。

目前版本仅提供有5关,有需要者可参考同类游戏自行扩充,游戏源码在jar内。

下载地址1(由于google code维护,需要等此文发表的隔天才能开放下载):http://code.google.com/p/loon-simple/downloads/list

下载地址2:http://download.csdn.net/source/1397545


游戏截图:

Java版推箱子(搬箱子)游戏开发入门示例及源码

Java版推箱子(搬箱子)游戏开发入门示例及源码

Java版推箱子(搬箱子)游戏开发入门示例及源码


核心代码:


Sokoban.java

package org.loon.game.simple.sokoban.control; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Image; import java.awt.event.KeyEvent; import org.loon.game.simple.sokoban.GraphicsUtils; import org.loon.game.simple.sokoban.LSystem; import org.loon.game.simple.sokoban.SimpleControl; /** * Copyright 2008 - 2009 * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * @project loonframework * @author chenpeng * @email <a title="" href="http://hi.baidu.com/ceponline" mce_href="http://hi.baidu.com/ceponline" target="_blank">ceponline</a>@yahoo.com.cn * @version 0.1 */ public class Sokoban extends SimpleControl { /** * */ private static final long serialVersionUID = 1L; private Image backImage = GraphicsUtils.loadImage("image/back1.jpg"); private Image screenImage; // 墙壁图 private Image floorImage[]; // 角色精灵 private RpgSprite sprite; // 窗体显示图 private Graphics screen; private String message = "按下 [Enter] 开始进行游戏"; private String m_stageName[] = { "关卡1", "关卡2", "关卡3", "关卡4", "关卡5", "关卡6", "关卡7" }; private int CS = 32; private int maxX, maxY, comps; private int stageNo; private boolean complete[]; private boolean boxMove[][]; private int hp[]; private int stageStates[][][]; private int ons[][]; private int role[]; private int rolex[]; private int roley[]; private int mapx[]; private int mapy[]; private int sleep = 0; private boolean succeed = false; private Stage stage; public Sokoban(Stage stage) { this.stage = stage; this.floorImage = new Image[4]; this.sprite = new RpgSprite("image/role1.gif"); this.maxX = 16; this.maxY = 13; this.comps = 0; this.complete = new boolean[stage.getMaxStageNo()]; this.boxMove = new boolean[stage.getMaxStageNo()][1500]; this.ons = new int[stage.getMaxStageNo()][1500]; this.hp = new int[stage.getMaxStageNo()]; this.stageStates = new int[stage.getMaxStageNo()][maxY][maxX]; this.role = new int[stage.getMaxStageNo()]; this.rolex = new int[stage.getMaxStageNo()]; this.roley = new int[stage.getMaxStageNo()]; this.mapx = new int[stage.getMaxStageNo()]; this.mapy = new int[stage.getMaxStageNo()]; for (int j = 0; j < 4; j++) { floorImage[j] = GraphicsUtils.loadImage("image/back" + (j + 1) + ".gif"); } this.screenImage = GraphicsUtils.createImage(CS * maxX + CS, CS * (maxY + 1) + 32, true); this.screen = screenImage.getGraphics(); for (stageNo = 0; stageNo < stage.getMaxStageNo(); stageNo++) { this.setupStage(); } this.stageNo = -1; // 开场界面 this.openDraw(); } /** * 刷新关卡 * */ public synchronized void reset(int stageNo) { this.stageNo = stageNo; this.stage.getMode()[stageNo] = 1; this.message = null; // 设定关卡 this.setupStage(); // 重绘背景 this.background(); // 重绘游戏屏幕 this.drawScreen(); } /** * 绘制屏幕背景 * */ public synchronized void background() { screen.drawImage(backImage, 0, 0, null); screen.setColor(Color.black); screen.fillRect(0, LSystem.HEIGHT - 40, LSystem.WIDTH, 40); } /** * 绘制屏幕图像 * */ public synchronized void drawScreen() { if (stageNo >= stage.getMaxStageNo()) { stageNo = 0; } for (int i = 0; i < mapy[stageNo]; i++) { for (int j = 0; j < mapx[stageNo];) { switch (stageStates[stageNo][i][j]) { case 2: case 3: case 4: case 5: screen.drawImage(floorImage[1], moveX(j), moveY(i), null); default: j++; break; } } } for (int n = 0; n < mapy[stageNo]; n++) { for (int l = 0; l < mapx[stageNo]; l++) switch (stageStates[stageNo][n][l]) { case 1: screen.drawImage(floorImage[0], moveX(l), moveY(n), null); screen.setColor(new Color(32, 0, 0)); screen.drawLine(moveX(l), moveY(n + 1), moveX(l) + CS - 1, moveY(n + 1)); if (l == 0 || l > 0 && stageStates[stageNo][n][l - 1] != 1) { screen.setColor(new Color(160, 128, 96)); screen.drawLine(moveX(l), moveY(n) + 1, moveX(l), moveY(n) + CS - 2); } if (l == mapx[stageNo] - 1 || l < mapx[stageNo] - 1 && stageStates[stageNo][n][l + 1] != 1) { screen.setColor(new Color(72, 64, 64)); screen.drawLine(moveX(l) + CS - 1, moveY(n), moveX(l) + CS - 1, moveY(n) + CS - 2); } break; case 2: case 3: case 4: switch (stageStates[stageNo][n][l]) { default: break; case 2: case 3: screen.drawImage(floorImage[3], moveX(l), moveY(n), null); break; case 4: break; } if (stageStates[stageNo][n][l] != 3) screen.drawImage(floorImage[2], moveX(l), moveY(n), null); break; default: break; } } // System.out.println(role[stageNo]); screen.drawImage(sprite.getOnlyMove(role[stageNo]), moveX(rolex[stageNo]), moveY(roley[stageNo]), null); if (stageStates[stageNo][roley[stageNo]][rolex[stageNo]] == 4) { screen.drawImage(floorImage[2], moveX(rolex[stageNo]), moveY(roley[stageNo]), null); } setupDisplay(); } /** * 定位到实际的X座标 * * @param i * @return */ public int moveX(int i) { return (CS * ((maxX - mapx[stageNo]) + 1)) / 2 + CS * i; } /** * 定位到实际的Y座标 * * @param i * @return */ public int moveY(int i) { return (CS * ((maxY - mapy[stageNo]) + 1)) / 2 + CS * i; } /** * 执行操作 * */ public synchronized void execute() { boolean flag = true; for (int i = 0; i < mapy[stageNo]; i++) { for (int j = 0; j < mapx[stageNo]; j++) { if (stageStates[stageNo][i][j] == 4) { flag = false; } } } if (flag) { stage.getMode()[stageNo] = 3; complete[stageNo] = true; } else if (hp[stageNo] == 0) { stage.getMode()[stageNo] = 2; } comps = 0; for (int n = 0; n < stage.getMaxStageNo(); n++) { if (complete[n]) { comps++; } } } /** * 键盘事件处理 */ public void keyPressed(KeyEvent e) { if (e.getKeyCode() == 10 && stageNo == -1) { // 开始关卡1 reset(0); } else if (stageNo < 0) { return; } // 选关(默认为最大支持7关,更多请自行设定) if (e.getKeyCode() == 97 || e.getKeyCode() == 49) { stageNo = 0; } else if (e.getKeyCode() == 98 || e.getKeyCode() == 50) { stageNo = 1; } else if (e.getKeyCode() == 99 || e.getKeyCode() == 51) { stageNo = 2; } else if (e.getKeyCode() == 100 || e.getKeyCode() == 52) { stageNo = 3; } else if (e.getKeyCode() == 101 || e.getKeyCode() == 53) { stageNo = 4; } else if (e.getKeyCode() == 102 || e.getKeyCode() == 54) { stageNo = 5; } else if (e.getKeyCode() == 103 || e.getKeyCode() == 55) { stageNo = 6; // ESC,重新开始 } else if (e.getKeyCode() == 27) { reset(stageNo); } else if (stage.getMode()[stageNo] == 1) { // 退步 if (e.getKeyCode() == 83) { nextStep(-1); } // 移动角色 else if (e.getKeyCode() == 40) { nextStep(0); } else if (e.getKeyCode() == 37) { nextStep(1); } else if (e.getKeyCode() == 39) { nextStep(2); } else if (e.getKeyCode() == 38) { nextStep(3); } else { return; } } else { return; } // 绘制背景 background(); // 绘制游戏画面 drawScreen(); } /** * 切换动作 * * @param i */ public synchronized void nextStep(final int i) { boxMove[stageNo][hp[stageNo] - 1] = false; switch (i) { case 0: if (stageStates[stageNo][roley[stageNo] + 1][rolex[stageNo]] < 2) { return; } if (stageStates[stageNo][roley[stageNo] + 1][rolex[stageNo]] < 4) { if (stageStates[stageNo][roley[stageNo] + 2][rolex[stageNo]] < 4) { return; } stageStates[stageNo][roley[stageNo] + 1][rolex[stageNo]] += 2; stageStates[stageNo][roley[stageNo] + 2][rolex[stageNo]] -= 2; boxMove[stageNo][hp[stageNo] - 1] = true; } roley[stageNo]++; break; case 1: if (stageStates[stageNo][roley[stageNo]][rolex[stageNo] - 1] < 2) { return; } if (stageStates[stageNo][roley[stageNo]][rolex[stageNo] - 1] < 4) { if (stageStates[stageNo][roley[stageNo]][rolex[stageNo] - 2] < 4) { return; } stageStates[stageNo][roley[stageNo]][rolex[stageNo] - 1] += 2; stageStates[stageNo][roley[stageNo]][rolex[stageNo] - 2] -= 2; boxMove[stageNo][hp[stageNo] - 1] = true; } rolex[stageNo]--; break; case 2: if (stageStates[stageNo][roley[stageNo]][rolex[stageNo] + 1] < 2) { return; } if (stageStates[stageNo][roley[stageNo]][rolex[stageNo] + 1] < 4) { if (stageStates[stageNo][roley[stageNo]][rolex[stageNo] + 2] < 4) { return; } stageStates[stageNo][roley[stageNo]][rolex[stageNo] + 1] += 2; stageStates[stageNo][roley[stageNo]][rolex[stageNo] + 2] -= 2; boxMove[stageNo][hp[stageNo] - 1] = true; } rolex[stageNo]++; break; case 3: if (stageStates[stageNo][roley[stageNo] - 1][rolex[stageNo]] < 2) { return; } if (stageStates[stageNo][roley[stageNo] - 1][rolex[stageNo]] < 4) { if (stageStates[stageNo][roley[stageNo] - 2][rolex[stageNo]] < 4) { return; } stageStates[stageNo][roley[stageNo] - 1][rolex[stageNo]] += 2; stageStates[stageNo][roley[stageNo] - 2][rolex[stageNo]] -= 2; boxMove[stageNo][hp[stageNo] - 1] = true; } roley[stageNo]--; break; default: if (hp[stageNo] == stage.getMaxHp()[stageNo]) { return; } switch (ons[stageNo][hp[stageNo]]) { case 0: if (boxMove[stageNo][hp[stageNo]]) { stageStates[stageNo][roley[stageNo] + 1][rolex[stageNo]] += 2; stageStates[stageNo][roley[stageNo]][rolex[stageNo]] -= 2; } roley[stageNo]--; break; case 1: if (boxMove[stageNo][hp[stageNo]]) { stageStates[stageNo][roley[stageNo]][rolex[stageNo] - 1] += 2; stageStates[stageNo][roley[stageNo]][rolex[stageNo]] -= 2; } rolex[stageNo]++; break; case 2: if (boxMove[stageNo][hp[stageNo]]) { stageStates[stageNo][roley[stageNo]][rolex[stageNo] + 1] += 2; stageStates[stageNo][roley[stageNo]][rolex[stageNo]] -= 2; } rolex[stageNo]--; break; default: if (boxMove[stageNo][hp[stageNo]]) { stageStates[stageNo][roley[stageNo] - 1][rolex[stageNo]] += 2; stageStates[stageNo][roley[stageNo]][rolex[stageNo]] -= 2; } roley[stageNo]++; break; } break; } if (i != -1) { hp[stageNo]--; ons[stageNo][hp[stageNo]] = i; } role[stageNo] = ons[stageNo][hp[stageNo]]; if (i == -1) { hp[stageNo]++; } execute(); } /** * 开场画面 * */ public synchronized void openDraw() { background(); for (int i = 0; i < 5; i++) { for (int j = 0; j < maxX + 1; j++) { screen.drawImage(floorImage[0], CS * j, (CS * i + CS / 2 * maxY) - CS * 2, null); } } GraphicsUtils.setAntialias(screen, true); String mes = "怪蜀黍传说 - 勇者推魔王"; screen.setFont(new Font("华文新魏", 1, 35)); GraphicsUtils.drawStyleString(screen, mes, (CS * maxX - screen .getFontMetrics().stringWidth(mes)) / 2 + 13, (CS * maxY) / 2 + 25, Color.black, Color.orange); mes = "Java版搬箱子游戏开发入门示例 - 0.1.0"; screen.setFont(new Font("华文新魏", 0, 30)); GraphicsUtils.drawStyleString(screen, mes, (CS * maxX - screen .getFontMetrics().stringWidth(mes)) / 2 + 13, (CS * (maxY + 2)) / 2 - 55, Color.black, Color.white); GraphicsUtils.setAntialias(screen, false); setupDisplay(); } /** * 绘图接口实现 */ public synchronized void draw(Graphics g) { g.drawImage(screenImage, 0, 0, null); if (succeed) { if (sleep == 100) { // 进入下一关 reset(++stageNo); succeed = false; sleep = 0; } sleep++; } } /** * 设置基本数据 * */ public void setupStage() { for (int i = 0; i < maxY; i++) { for (int j = 0; j < maxX; j++) { stageStates[stageNo][i][j] = 0; } } mapx[stageNo] = stage.getConf()[stageNo][0]; mapy[stageNo] = stage.getConf()[stageNo][1]; rolex[stageNo] = stage.getConf()[stageNo][2]; roley[stageNo] = stage.getConf()[stageNo][3]; for (int n = 0; n < mapy[stageNo]; n++) { for (int l = 0; l < mapx[stageNo]; l++) { stageStates[stageNo][n][l] = stage.getMapData()[stageNo][n][l]; } } hp[stageNo] = stage.getMaxHp()[stageNo]; role[stageNo] = 0; for (int i1 = 0; i1 < stage.getMaxHp()[stageNo]; i1++) { ons[stageNo][i1] = 0; } for (int j1 = 0; j1 < stage.getMaxHp()[stageNo]; j1++) { boxMove[stageNo][j1] = false; } } /** * 设置显示 * */ public void setupDisplay() { if (message == null) { screen.setFont(new Font(LSystem.FONT, 0, 12)); int size = 0; for (int j = 0; j < stage.getMaxStageNo(); j++) { if (complete[j]) { screen.setColor(Color.gray); } else { screen.setColor(Color.white); } if (j == stageNo) { if (complete[j]) { screen.setColor(Color.magenta); } else { screen.setColor(Color.cyan); } } screen.drawString(m_stageName[j], size = (10 + (screen .getFontMetrics().stringWidth("关卡0") + 20) * j), LSystem.HEIGHT - 50); } screen.setColor(Color.white); if (stageNo == -1) { // 初始画面,无数据 } else { // 显示状态 if (hp[stageNo] == 0) { screen.setColor(Color.red); } else if (hp[stageNo] <= stage.getMaxHp()[stageNo] / 4) { screen.setColor(Color.orange); } else if (hp[stageNo] <= stage.getMaxHp()[stageNo] / 2) { screen.setColor(Color.yellow); } screen.drawString("主角 HP " + hp[stageNo] + "/" + stage.getMaxHp()[stageNo], size + 50, LSystem.HEIGHT - 50); switch (stage.getMode()[stageNo]) { // 生命耗尽 case 2: screen.setFont(new Font(LSystem.FONT, 1, 30)); screen.setColor(Color.red); GraphicsUtils.setAntialias(screen, true); screen.drawString("The End!", (CS * maxX - screen.getFontMetrics().stringWidth( "The End!")) / 2 + 14, ((CS * (maxY + 1) + screen.getFontMetrics() .getAscent()) - screen.getFontMetrics() .getDescent()) / 2); GraphicsUtils.setAntialias(screen, false); break; // 胜利 case 3: screen.setFont(new Font(LSystem.FONT, 2, 40)); screen.setColor(Color.darkGray); GraphicsUtils.drawStyleString(screen, "Good job!", (CS * maxX - screen.getFontMetrics().stringWidth( "Good job!")) / 2 + 14, ((CS * (maxY + 1) + screen .getFontMetrics().getAscent()) - screen .getFontMetrics().getDescent()) / 2, Color.black, Color.white); // 胜利标记 succeed = true; break; } } } // 存在信息时 else { screen.setFont(new Font(LSystem.FONT, 0, 12)); int n = screen.getFontMetrics().stringWidth(message) + 16; int l = screen.getFontMetrics().getAscent() + screen.getFontMetrics().getDescent() + 8; int i1 = (CS * (maxX + 1) - n) / 2; int j1 = (CS * maxY + 1) - l / 2; int k1 = i1 + 8; int l1 = CS * maxY + 1 + (screen.getFontMetrics().getAscent() - screen .getFontMetrics().getDescent()) / 2; screen.setColor(Color.black); screen.fillRect(i1, j1, n, l); screen.setColor(Color.white); screen.drawString(message, k1, l1); } } }


启动类:


Main.java

package org.loon.game.simple.sokoban.main; import org.loon.game.simple.sokoban.GameCursor; import org.loon.game.simple.sokoban.GameFrame; import org.loon.game.simple.sokoban.control.Sokoban; import org.loon.game.simple.sokoban.control.Stage; import org.loon.game.simple.sokoban.control.Stage1; /** * Copyright 2008 - 2009 * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * @project loonframework * @author chenpeng * @email <a title="" href="http://hi.baidu.com/ceponline" mce_href="http://hi.baidu.com/ceponline" target="_blank">ceponline</a>@yahoo.com.cn * @version 0.1 */ public class Main { public static void main(String[] args) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { GameFrame frame = new GameFrame("Java版搬箱子游戏入门示例及源码-0.1.0", 540, 480); frame.setCursor(GameCursor.getCursor("image/cursor.png")); // 获得搬箱子关卡接口 Stage stage = new Stage1(); // 设定游戏控制器 frame.getGame().setControl(new Sokoban(stage)); // 游戏全屏 // frame.updateFullScreen(); // 是否显示fps frame.setFPS(true); // 允许的最大刷新率 frame.setMaxFrames(60); frame.mainLoop(); frame.showFrame(); } }); } }

下载地址1(由于google code维护,需要等此文发表的隔天才能开放下载):http://code.google.com/p/loon-simple/downloads/list

下载地址2:http://download.csdn.net/source/1397545