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


游戏截图:

00

01

02


核心代码:


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

           

猜你喜欢

转载自blog.csdn.net/qq_44934536/article/details/89465615