别 踩 白 块 儿 别 踩 白 块 儿 是 目 前 非 常 火 的 一 款 游 戏, 游 戏 非 常 简 单 刺 激 关 于 具 体 怎 么 火 法 怎 么 玩 我 就 不 多 说 了, 相 信 看 到 本 文 的 朋 友 们 都 非 常 地 清 楚 什 么 游 戏 火, 我 们 都 想 知 道 自 己 能 不 能 也 弄 一 个 玩 玩, 我 也 花 了 点 时 间 弄 了 一 个, 游 戏 代 码 将 会 开 源, 利 人 利 己, 大 家 一 起 提 高, 希 望 各 位 多 多 支 持 下 面 介 绍 如 何 用 OGEngine 游 戏 引 擎 完 成 别 踩 白 块 儿 游 戏 的 经 典 模 式 一 最 终 实 现 的 部 分 效 果 截 图 1 刚 开 始 时, 最 下 面 有 一 栏 为 黄 色, 紧 接 着 上 面 每 一 行 都 是 有 一 个 黑 色 块, 其 余 为 白 色 块, 准 备 冲 啊... 2 游 戏 中, 在 游 戏 过 程 中 都 有 一 个 计 时 器, 记 录 当 前 所 进 行 的 时 间 冲 啊... 注 意 别 点 到 了 白 块 儿
3 玩 家 如 果 误 点 了 白 色 块, 会 出 现 游 戏 失 败 界 面, 如 果 之 前 有 创 立 过 最 佳 成 绩, 则 会 显 示 出 来, 记 录 的 是 完 成 任 务 所 用 的 时 间, 时 间 越 短 说 明 你 越 厉 害 点 击 重 来 即 会 重 新 开 始 4 游 戏 进 行 到 最 后 会 出 现 绿 色 栏, 游 戏 胜 利 界 面, 会 显 示 本 次 所 用 时 间, 如 果 刷 新 了 记 录
则 标 记 新 纪 录 字 样, 如 果 没 有 刷 新 记 录 则 还 会 显 示 历 史 最 佳 记 录 大 伙 努 力 挑 战 新 记 录 吧!
二 创 建 Block 类 游 戏 中 要 用 到 很 多 方 块, 所 以 我 们 单 独 创 建 一 个 方 块 类 Block Block 是 一 个 个 白 块 或 者 黑 块, 单 个 块 元 素, 这 里 我 们 的 Block 继 承 矩 形 Rectangle, 每 个 Block 用 矩 形 来 展 示 1 声 明 一 些 所 需 要 的 成 员 变 量 public class Block extends Rectangle { // 游 戏 场 景 private GameScene mgamescene; // 此 block 的 颜 色 类 型, 白 色 还 是 黑 色? private int colortype; // block 所 在 的 行 private int row; // block 所 在 的 列 private int column; // ======================get&set======================== public int getrow() { return row; public void setrow(int row) { this.row = row; public int getcolumn() { return column; public int getcolortype() { return colortype; // =====================================================
2 构 造 器 1, 初 始 化 blocks 时 用 到, 当 为 最 底 部 一 行 时 设 为 黄 色 * 构 造 器 1, 初 始 化 blocks 时 用 到 * @param pgamescene 游 戏 场 景 * @param row block 所 在 的 行 * @param column block 所 在 的 列 * @param pwidth block 的 宽 * @param pheight block 的 高 * @param blackindex 用 来 确 定 是 否 是 黑 块, 如 果 blackindex == column 时 设 为 黑 块 * @param pvertexbufferobjectmanager public Block(GameScene pgamescene, int row, int column, float pwidth, float pheight, int blackindex, VertexBufferObjectManager pvertexbufferobjectmanager) { super(column * pwidth, (3 - row) * pheight, pwidth - 1, pheight - 1, pvertexbufferobjectmanager); this.mgamescene = pgamescene; this.row = row; this.column = column; if (row == 0) { // 第 一 行 设 置 为 黄 块 this.setcolor(color.yellow); else { // 初 始 化 block 的 颜 色 数 据, 是 白 块 还 是 黑 块? initblockdata(column, blackindex); // 设 置 可 以 相 应 触 碰 事 件 this.setignoretouch(false);
3 构 造 器 2, 新 增 blocks 时 用 到 * 构 造 器 2, 新 增 blocks 时 用 到 * @param pgamescene 游 戏 场 景 * @param column block 所 在 的 列 * @param pwidth block 的 宽 * @param pheight block 的 高 * @param blackindex 来 确 定 是 否 是 黑 块, 如 果 blackindex == column 时 设 为 黑 块 * @param pvertexbufferobjectmanager public Block(GameScene pgamescene, int column, float pwidth, float pheight, int blackindex, VertexBufferObjectManager pvertexbufferobjectmanager) { super(column * pwidth, 0, pwidth - 1, pheight - 1, pvertexbufferobjectmanager); this.mgamescene = pgamescene; this.column = column; // 初 始 化 block 的 颜 色 数 据, 是 白 块 还 是 黑 块? initblockdata(column, blackindex); // 设 置 可 以 相 应 触 碰 事 件 this.setignoretouch(false); 4 实 现 函 数 initblockdata 初 始 化 block 的 颜 色 数 据, 是 白 块 还 是 黑 块 * 初 始 化 block 的 颜 色 数 据, 是 白 块 还 是 黑 块? * @param column * @param blackindex private void initblockdata(int column, int blackindex) { if (blackindex == column) { // 设 置 为 黑 块 this.setcolor(color.black); this.colortype = ConstantUtil.COLOR_BLACK; else { // 设 置 为 白 块 this.setcolor(color.white); this.colortype = ConstantUtil.COLOR_WHITE;
5 每 个 block 实 现 触 碰 监 听, 当 按 下 时, 调 起 在 GameScene 中 实 现 的 touchblock 方 法 记 得 设 置 可 以 相 应 触 碰 事 件 setignoretouch(false); @Override public boolean onareatouched(touchevent pscenetouchevent, float ptoucharealocalx, float ptoucharealocaly) { // 触 碰 事 件 监 听 if (pscenetouchevent.isactiondown()) { // 点 击 到 Block 时 进 行 的 逻 辑 处 理 mgamescene.touchblock(this); return true; 三 创 建 GameScene 类 GameScene 类 是 游 戏 场 景 类, 是 本 游 戏 的 主 要 部 分, 主 要 实 现 游 戏 界 面 及 相 关 逻 辑 1 先 看 看 其 成 员 变 量 public class GameScene extends Scene { 块 的 宽 * private float blockwidth = 0; 块 的 高 * private float blockhight = 0; 用 于 装 当 前 所 有 生 成 但 未 被 删 除 的 block * private List<Block[]> blocklist; 当 前 生 成 的 块 的 行 数 * private int lineslength = 5; 游 戏 状 态 * private int gamestatus = ConstantUtil.GAME_START; 移 动 步 数 * private int movenum = 0; 成 功 界 面 * private SuccGroup msuccgroup; 失 败 界 面 * private FailGroup mfailgroup; 计 时 管 理 类 * private TimerTool mtimertool; 显 示 计 时 的 Text * private Text timertext; 游 戏 提 示 语 * private AnimatedSprite game_tip; // 用 于 Z 索 引 排 序 private static final int pzindex_middle = 2; private static final int pzindex_top = 3;
2 计 算 每 个 block 块 的 宽 高 // 镜 头 里 显 示 的 是 4*4 的 块, 所 以 用 镜 头 宽 的 四 分 之 一 作 为 块 宽 blockwidth = this.getcamerawidth() / 4; blockhight = this.getcameraheight() / 4; 3 初 始 blocks, 先 创 建 4*5 表 格, 使 用 时 候 再 一 行 行 增 加 * 初 始 化 blocks private void initblocks() { Random mrandom = new Random(); int blackindex = 0; // 初 始 blocks, 先 创 建 4*5 表 格, 使 用 时 候 再 一 行 行 增 加 for (int row = 0; row < lineslength; row++) {// 行 // 一 行 blocks Block[] rowbolcks = new Block[4]; // 随 机 一 个 黑 块 所 在 位 置 blackindex = mrandom.nextint(4); for (int column = 0; column < 4; column++) {// 列 rowbolcks[column] = new Block(this, row, column, blockwidth, blockhight, blackindex, getvertexbufferobjectmanager()); this.attachchild(rowbolcks[column]); blocklist.add(rowbolcks);
游 戏 交 互 实 现 1 前 面 已 经 介 绍 在 Block 类 实 现 了 每 个 block 的 触 碰 监 听,block 实 现 触 碰 监 听, 当 按 下 时, 调 起 在 GameScene 中 实 现 的 touchblock 方 法 下 面 来 看 改 方 法 的 实 现 * 点 击 到 Block 时 进 行 的 逻 辑 处 理 * * @param pblock * 所 点 击 的 block public void touchblock(block pblock) { if (gamestatus == ConstantUtil.GAME_START) { if (pblock.getrow() == movenum + 2) {// 表 示 是 在 底 部 往 上 数 的 倒 数 第 三 行 // 判 断 是 不 是 点 击 了 该 点 击 的 黑 块 的 上 一 格, 如 果 是, 我 们 也 判 定 这 是 正 确 点 击 了, 做 出 相 应 移 动 upblocktouch(pblock); else if (pblock.getrow() == movenum + 1) {// 表 示 是 在 底 部 往 上 数 的 倒 数 第 二 行 if (pblock.getcolortype() == ConstantUtil.COLOR_BLACK) { if (lineslength == movenum + 2) { // 游 戏 胜 利 gamesucc(pblock); else { // 整 体 blocks 下 移 movedown(pblock, 1); else if (pblock.getcolortype() == ConstantUtil.COLOR_WHITE) { // 误 点 了 白 块, 游 戏 失 败 gamefail(); // 失 败 时 pblock 的 一 个 闪 红 效 果 LoopEntityModifier loop = failaction(); // 播 放 效 果 pblock.registerentitymodifier(loop);
上 面 的 代 码 已 经 有 所 注 释, 这 里 再 简 单 解 析 一 下 1 理 论 上 可 以 点 击 的 一 行 为 倒 数 第 二 行, 如 果 点 击 到 该 行 的 黑 块, 则 所 有 block 向 下 移 动 一 格, 如 果 点 击 到 该 行 的 白 块, 则 游 戏 失 败, 弹 出 失 败 界 面 2 为 了 体 验 上 的 优 化, 如 果 点 击 到 倒 数 第 二 行 黑 块 所 在 列 正 对 上 的 一 个 block( 不 管 是 黑 块 还 是 白 块 ) 也 视 为 正 确 点 击 了 黑 块 因 为 在 快 速 点 击 黑 块 的 同 时 整 体 的 block 也 迅 速 向 下 移 动, 很 容 易 点 击 到 上 一 格, 此 时 我 们 认 为 这 也 是 正 确 的 点 击, 这 样 才 能 保 证 快 速 点 击 的 连 贯 性 2 判 断 是 不 是 点 击 了 该 点 击 的 黑 块 的 上 一 格, 如 果 是, 我 们 也 判 定 这 是 正 确 点 击 了, 做 出 相 应 移 动 * 判 断 是 不 是 点 击 了 该 点 击 的 黑 块 的 上 一 格, 如 果 是, 我 们 也 判 定 这 是 正 确 点 击 了, 做 出 相 应 移 动 * * @param pblock * 所 被 点 击 的 块 private void upblocktouch(block pblock) { int touchcolumn = pblock.getcolumn(); for (Block[] blocks : blocklist) { for (Block block : blocks) { if (block.getrow() == movenum + 1 && block.getcolortype() == ConstantUtil.COLOR_BLACK) { if (block.getcolumn() == touchcolumn) { // 整 体 blocks 下 移 movedown(block, 1); return;
3 方 块 下 移 等 相 关 逻 辑 movedown 方 法 的 实 现 // 正 确 点 击 该 点 击 的 黑 块, 或 者 上 一 行 的 块, 整 体 向 下 移 动 创 建 新 的 一 样 块, 改 变 黑 块 private void movedown(block pblock, int stepnum) { if(movenum == 0){ // 开 始 计 时 mtimertool.start(); // 隐 藏 提 示 文 字 game_tip.setvisible(false); if (movenum < ConstantUtil.LINES_LEN) { // 创 建 添 加 新 的 一 行 createnewrowbolcks(); else if (movenum == ConstantUtil.LINES_LEN) { // 开 始 显 示 绿 色 胜 利 界 面, 即 将 胜 利, 但 还 没 有 胜 利 showsuccgroup(); // 被 点 击 的 黑 块 变 灰 pblock.setcolor(0.63f, 0.63f, 0.63f); pblock.registerentitymodifier(new ScaleModifier(0.1f, 0.5f, 1.0f)); // 移 动 步 数 加 1 movenum++; // 需 要 移 动 的 距 离 float movedistance = this.getcameraheight() - blockhight * stepnum - pblock.gety(); for (Block[] rowblocks : blocklist) { for (Block block : rowblocks) { // 遍 历, 所 有 block 向 下 移 动 指 定 距 离 movetoy(block, movedistance); // 快 到 胜 利 出 现 绿 色 胜 利 界 面 时, 胜 利 界 面 跟 着 移 动 if (msuccgroup.isvisible()) { movetoy(msuccgroup, movedistance); 1moveNum 用 来 记 录 移 动 了 多 少 步, 第 一 次 开 始 下 移 时, 我 们 开 始 计 时 以 及 隐 藏 下 边 的 提 示 文 字 2 如 果 还 没 有 达 到 预 设 的 总 行 数 就 创 建 添 加 新 的 一 行 blocks, 如 果 到 了 则 开 始 显 示 绿 色 胜 利 界 面 3 被 点 击 的 黑 块 变 灰 色, 加 点 小 小 的 动 作 效 果 4 计 算 出 需 要 向 下 移 动 的 距 离, 遍 历 所 有 blocks, 向 下 移 动 指 定 距 离 5 快 到 胜 利 出 现 绿 色 胜 利 界 面 时, 胜 利 界 面 跟 着 移 动
4 创 建 添 加 新 的 一 行 createnewrowbolcks() 方 法 的 实 现 // 创 建 添 加 新 的 一 行 private void createnewrowbolcks() { // 超 出 屏 幕 没 用 的 部 分 移 除 掉 if (blocklist.size() > 5) { Block[] rowbolcks = blocklist.get(0); for (Block block : rowbolcks) { block.detachself(); blocklist.remove(0); // 目 前 顶 部 的 一 行 块 的 Y 坐 标 float topblocksy = blocklist.get(blocklist.size() - 1)[0].getY(); // 一 行 块 Block[] rowbolcks = new Block[4]; int blackindex = new Random().nextInt(4); for (int column = 0; column < 4; column++) { // 新 创 建 Block rowbolcks[column] = new Block(this, column, blockwidth, blockhight, blackindex, getvertexbufferobjectmanager()); // 设 置 Y 坐 标 rowbolcks[column].setbottompositiony(topblocksy - 1); // 设 置 已 经 是 第 几 行 rowbolcks[column].setrow(lineslength); this.attachchild(rowbolcks[column]); blocklist.add(rowbolcks); // 按 Z 索 引 排 序 一 下 上 下 层 顺 序 this.sortchildren(); // 当 前 生 成 的 块 的 行 数 增 加 lineslength++; 1 先 把 超 出 屏 幕 没 用 的 部 分 移 除 掉, 把 新 增 的 一 行 blocks 的 位 置 设 在 最 顶 端, 设 置 已 经 是 第 几 行
5 游 戏 成 功 或 者 游 戏 失 败 * 最 后 一 个 移 动, 游 戏 胜 利 * * @param pblock private void gamesucc(block pblock) { gamestatus = ConstantUtil.GAME_OVER; // 最 后 一 个 移 动, 游 戏 胜 利 movedown(pblock, 0); // 停 止 计 时 mtimertool.stop(); // 显 示 游 戏 胜 利 画 面 msuccgroup.showitems(true); // 隐 藏 显 示 时 间 的 Text timertext.setvisible(false); * 点 击 错 误 后 弹 出 红 色 的 失 败 界 面, 游 戏 失 败 private void gamefail() { gamestatus = ConstantUtil.GAME_OVER; // 游 戏 失 败, 停 止 计 时 mtimertool.stop(); // 隐 藏 显 示 时 间 的 Text timertext.setvisible(false);
计 时 控 制 类 TimerTool 的 实 现 我 们 知 道 游 戏 一 开 始 就 会 有 时 间 的 计 时, 完 成 指 定 任 务 后, 时 间 越 短 越 厉 害, 玩 家 通 过 不 断 刷 新 记 录 来 提 高 对 游 戏 的 成 就 感, 所 以 计 时 显 示 历 史 最 佳 记 录 必 不 可 少 下 面 我 们 就 来 看 看 如 何 实 现 计 时 我 们 使 用 OGEngine 引 擎 中 的 TimerHandler 来 实 现 计 时, 我 们 先 来 了 解 一 下 TimerHandler 从 源 码 我 们 发 现 它 实 现 IUpdateHandler 接 口 public class TimerHandler implements IUpdateHandler{ 再 来 看 一 下 它 的 构 造 器 * @param ptimerseconds 经 过 多 少 秒 后 执 行 ptimercallback 中 的 回 调 * @param pautoreset 是 否 循 环 执 行 * @param ptimercallback 回 调 函 数 public TimerHandler(final float ptimerseconds, final boolean pautoreset, final ITimerCallback ptimercallback) { if(ptimerseconds <= 0){ throw new IllegalStateException("pTimerSeconds must be > 0!"); this.mtimerseconds = ptimerseconds; this.mautoreset = pautoreset; this.mtimercallback = ptimercallback; 下 面 来 看 TimerTool 的 具 体 实 现 1 所 定 义 的 成 员 变 量, 构 造 器 实 现 public class TimerTool { private GameScene mgamescene; private TimerHandler mtimerhandler; 当 前 经 过 的 总 毫 秒 数 * private long millispass = 0; 是 否 计 时 中 * private boolean iscountdowning = false; 多 少 毫 秒 累 加 一 次 计 时 * private static long stepmillis = 83; public TimerTool(GameScene pgamescene) { this.mgamescene = pgamescene; inittimerhandler(); 2 初 始 化 TimerHandler
// 初 始 化 TimerHandler, 设 置 为 每 隔 stepmillis * 0.001f 秒 循 环 回 调 ontimepassed 方 法 private void inittimerhandler() { mtimerhandler = new TimerHandler(stepMillis * 0.001f, true, new ITimerCallback() { @Override public void ontimepassed(timerhandler ptimerhandler) { // 累 加 所 经 过 的 时 间, 并 在 界 面 上 更 新 显 示 当 前 时 间 addmillispass(); ); 3 相 关 时 间 的 逻 辑 实 现 // 重 置 时 间 public void resettimer() { millispass = 0; iscountdowning = false; // 累 加 所 经 过 的 时 间, 并 在 界 面 上 更 新 显 示 当 前 时 间 private void addmillispass() { millispass += stepmillis; mgamescene.gettimertext().settext(millistotimer(millispass)); // 获 取 当 前 经 过 的 总 毫 秒 数 public long getmillispass() { return millispass; // 把 毫 秒 转 化 为 一 种 分 秒 毫 秒 的 文 本 显 示 模 式 字 符 串 public String millistotimer(long pmillispass) { String timer = ""; long min = pmillispass / 60000; long sec = (pmillispass - min * 60000) / 1000; String secstr = sec < 10? "0" + sec : String.valueOf(sec); long millisec = pmillispass % 1000; if (min > 0) { timer += min + ":"; timer += secstr + "." + millisec + "\""; return timer; 4 开 始 计 时 和 结 束 计 时
// 注 册 mtimerhandler 开 始 计 时 public void start() { if (!iscountdowning) { iscountdowning = true; mgamescene.registerupdatehandler(mtimerhandler); // 反 注 册 mtimerhandler 停 止 计 时 public void stop() { mgamescene.unregisterupdatehandler(mtimerhandler); 在 正 确 点 击 第 一 块 黑 块 的 时 候 开 始 计 时, 游 戏 失 败 或 者 游 戏 完 成 时 停 止 计 时, 保 存 好 记 录 的 时 间, 在 胜 利 界 面 或 失 败 界 面 根 据 实 际 情 况 实 现 所 用 时 间 跟 最 佳 记 录 的 显 示 本 游 戏 就 介 绍 到 这 里, 更 多 细 节 请 看 游 戏 源 码