加载中…
个人资料
水龙一谭
水龙一谭
  • 博客等级:
  • 博客积分:0
  • 博客访问:73,469
  • 关注人气:21
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
相关博文
推荐博文
谁看过这篇博文
加载中…
正文 字体大小:

初学quick改制 coinflip 游戏笔记

(2015-11-14 20:46:38)
标签:

游戏

quick

lua

分类: 写代码
初学quick改制 coinflip 游戏笔记
lymslive / 403708621@qq.com /2015-11

(文末附源码链接及其他参考教程)

一、前言

虽然入行做的是游戏策划,不想做程序猿,但还是想自己学会并掌握一种游戏引擎,能自己做些想做的游戏,哪怕是独立小游戏。

近来在公司参与的项目都是用 unity 3d 做的。我也曾了解过这个引擎或编辑吧,基础也不难。但我终究会没深入潜进去。想想初衷吧,u3d对美术倚重大,做商业项目自然卖相好。但我个人想“玩”好u3d,难度还是有更多方面的。所以决定还是回去学用 coco2d-x 吧。想当年学几何数学,立体几何也是更头痛点,平面几何还好。

于是找到 cocos 的官网。通过其相关论坛、教程了解到 cocos2d-x 版本多,且更新快的,但文档却似不怎么规范,这不算好事。几经调查权衡,还是决定使用 quick cocos2d-lua 版本。我此前学会了 lua,比较认同 lua 语言的理念。所以就用它吧。

在 cocos 商店搜索下载到 quick3.5 最新版,也是 final 最终版,因为据说官网不再维护 quick 了,只整合 cocos2d-lua 。有点坑,却暂也无他法了。

读了些其他同学写的笔记、入门教程之类,后来猛然发现要动手研究下 quick 自带的 sample 才合适,quick 框架的源码也是有中文注释的,有必要时查看也甚有收获。

先大致浏览了一下各示例工程,发觉翻金币(coinflip)这个例子挺有意思的,在结合其他教程关于该示例的解读,基本搞通这个示例工程后,决定再动手整改这个例程,想要最终将其改成一个大致可玩的益智小游戏吧。经过一周时日,自己的想法添加完成得七七八八了,所以回顾记录一下此中过程,以作学习备录。

二、coinflip 小游戏整改过程

0. 原例概况

quick 安装目录下 sample/coinflip 目录包含完整代码 src 与资源 res。新建空白工程后,将它们拷至新工程目录下,即可用 cocos2d-x 自带的模拟器运行查看效果了。quick3.3 之前是一个叫 player3.exe 的东东,quick3.5 被整合没 player.exe 了,就用另外一个 simulator.exe 的,位置藏得有点深。

quick 3.5 要学会用命令创建新工程: cocos new PROJECT -l lua
编译用 cocos compile 命令。当然要配置一些环境,就按其他网上教程来,不再多记。

这个示例游戏,大概有以下几点:

1) 首页做了两个比较有动感的汽泡按钮,BubbleButton.lua
2) 关卡选择界面,写了个滑动页面控件,PageControl.lua(实际有5个相关的.lua)
3) 游戏关卡实现游戏逻辑,PlayLevelScene.lua
4) 配置了了100个关卡,data/Levels.lua,在示例中这项100个关卡可随意玩
5) 首页左边的按钮 MoreGame 留空,没实现任何东西

我边学边看边玩了下,觉得有些地方玩得不舒心,就开始按自己的想法修改一些东西了。但是本人美术战五渣,所以只涉及代码修改,没有涉及增加其他美术资源。只找了个拆分合图 plist 的工具,看它原有图片资源作些调用调整。

1. 去掉底部广告

AdBar.lua 示范我们如何在游戏中添加一个恒定的广告条,该文件只有几行,挺简单的:


    local sprite = display.newSprite()
    sprite:align(display.BOTTOM_CENTER, display.cx, display.bottom)
    return sprite
end
return AdBar


然后在其他场景类 **Scene.lua 中创建一个AdBar实例就可以了。我不想要这个广告条,就如上面注释掉 display.newSprite("#AdBar.pn") 那行,替换为创建一个空精灵。这简单省事,不必到每个场景类文件中删去相关代码。

2. 记忆关卡选择界面所在页数

在原例中从游戏关卡PlayLevelScene中返回关卡选择界面ChooseLevelScene时,始终返回第一页,想选玩后面关卡时,必须再滑动翻上几页。因此我想增加记忆功能,返回时自动滑到能看到刚才所玩关卡的那页。


-- 额外构造参数 levelIndex 用于定位页面
function ChooseLevelScene:ctor(levelIndex)
    -- 省略.....
    -- 滚动到指定页
    levelIndex = levelIndex or GameData.currentLevel
    local pageIndex = self.levelsList:getPageIndex(levelIndex)
    self.levelsList:scrollToCell(pageIndex)
    -- 省略.....
end


为此,在 ChooseLevelScene 的构造函数中添加一个参数,用以表示第几关。然后调用控件的 scrollToCell 方法。其他相关修改还有:

1) 从关卡号获得所属性页面数的方法,LevelsList:getPageIndex()
2)  MyApp:enterChooseLevelScene(levelIndex),也要求能接收关卡号参数
3) PlayLevelScene 的构造函数接收的参数也统一修改为关卡号,响应 Back 按钮时又传回关卡号

3. 记录玩家进度 GameState

大凡常规游戏,关卡不会是一下全开的,是要玩家去推关过关才逐步解锁的。于是需要保存玩家的游戏进度,涉及读写本地数据了。可以用引擎提供的 GameState,在 MyApp.lua 中添加:

-- 保存用户数据,当前关卡进度
GameState = require("framework.cc.utils.GameState")

当然需要更多配合代码,请查看 GameState 的用法,注意 GameState 与 GameData 的区别,我曾经踩坑不小心写错,找 bug 半天。我使用是未加密保存数据,就是想自己打开 json 格式的保存数据查看是否预期的。

在 GameData 中保存一个 currentLevel ,表示玩家新锁的最应该玩的关卡。于是在从首页进入关卡选择界面中也应该根据该值滚动到相应页面。同时这个值也将所有关卡分成了前后两段,包括自己这个点就是三种状态了,因此我从源图片资源中找出三种贴图,分别创建不同的关卡按钮。

.png"
        if levelIndex < GameData.currentLevel then
              png = "#CompletedLevelIcon.png"
        elseif levelIndex == GameData.currentLevel then
             png = "#UnlockedLevelIcon.png"
        else
             png = "#LockedLevelIcon.png"
        end
        local icon = display.newSprite(png, x, y)


4. 成功后进入下一关

原例中玩过一关后,只能通过返回到选关界面,才能继续玩另一关。正式游戏是应该让玩家成功过关后继续玩下一关才爽的。

修改 PlayLevelScene:onLevelCompleted() 函数,为成功后弹出的对话框图片添加点击响应事件,点击后进入下一关。同时为了方便点击,让动画将对话框移动中央。还要记得在此更新玩家进度信息。

5. 添加更多的功能按钮

想要添加更多功能,至少还得添加一些按钮与用户交互吧。可我懒得画按钮,更怕找的或画的按钮与原风格不搭,所以就想把文字标签做成可点击的功能。我查了一下教程,quick2 把这种UI称作 menu (菜单),老早的东西了,我以前没用过就不管了。quick3则集成为 UIPushButton 了。

我参考原例中游戏关卡中既有的“Level: xx”信息,添加更多得标签文本,同时查看下框架中关于 UIPushButton 与 UIButton 的接口,为重复利用,就新加了一个 TextButton 类


-- 文本按钮
local TextButton = class("TextButton", cc.ui.UIPushButton)

function TextButton:ctor(str)
    TextButton.super.ctor(self)
    local label = cc.ui.UILabel.new({
    UILabelType = 1,
    font  = "UIFont.fnt",
    text  = str,
    align = cc.ui.TEXT_ALIGN_LEFT,
    })
    self:setButtonLabel(label)
end

function TextButton:setString(str)
    self:setButtonLabelString(str)
end

return TextButton


UIFont.fnt是原例游戏中使用的字体,只有这一种位图字体,为书写简化,就直接写进构造函数中了。我觉得这个字体还不错,干脆把原来的 Back 按钮也换成了文本按钮,统一风格。

在游戏关卡界面 PlayLevelScene.lua 左下角是原来的“Level:xx”标签,右下角换了“Back”。左上角增加“Flips:xx”统计翻动次数,右上角增加“Retry”,重来的意思,玩乱了的话,可以重新回到关卡初始局面。

为了文本标签辨识度高些,将场景背景图的透明度稍微调低了点。

三、更多扩展

1. 自定义关卡,编辑保存关卡功能

原例游戏中左边的主按钮“MoreGame”,是空空如也。我想添加些什么好呢。自定义关卡!我想让玩家除了只能玩预设的关卡外,还能自己编辑、保存关卡,然后拿给别人玩或自己玩。

首先分析下,这个小游戏的核心玩法,点击将硬币翻面,自己以及相邻十字形的都会翻面,目标是将所有翻成金色朝上。很显然,翻面的过程是可逆的。所以如果一开始给出的棋盘,所有者硬币金色朝上,然后将其他部分硬币有意地翻到银面,那就成了一个关卡,而且这样生成的关卡布局肯定是有解的。

按此思路,就做出了自定义关卡功能,主要实现于新增的四个场景类文件。

1) MoreGamesScene
原有的空场景。参考选关场景 ChooseLevelScene 列出所有自定义关卡,也是玩自定义关卡的入口。但我未重用它的滑动页面,而且是只做了一页。一是为了实现简单,二是觉得玩家自定义关卡没必要保存那么多,自娱自乐做出的关卡能当时看到即可。

一页最多保存 16 个,一开始是空的按钮,有创建了关卡才标上数字并可点击。如果超过16个,则按队列方式插入,先进先出。最近创建的关卡,放在16位,把1位挤出抛弃。注意,仍用 GameSate 保存用户自己关卡数据。

2) CreateLevelScene
创建关卡的场景,本质上与游戏关卡差不多,只是一开始全是金色朝向,并且不会判断过关条件,可以一直翻来覆去,设计成想要的局面。除了统计步数的纯文本标签外,添加了许多功能性文本按钮。

中上 “Random” 文本按钮响应随机翻一次;
右上 "Resume“恢复全金色朝向;
右下 “Back” 返回
左下 “Export” 保存棋盘而已并返回自定义的选关界面(MoreGamesScene)
中下 “Set” 进入另一个设置棋盘模板的场景(SetCreateLevelScene)

3) SetCreateLevelScene
设置棋盘模板的意思是,指定棋盘大小,最小2×2,最大6×6。再小了就意思,而最大行列数受屏幕大小限制。该游戏按 640×960 竖屏设计,每个硬币底盘格子大小就 100,所以最多6格,预设的那 100 关,好像多数为5格。

这里的交互思路是,先呈现最大的6×6格方阵,固定左下角(1,1),玩家点击另一格表示右上角,由这两个对角便限定了棋盘的矩形大小。这两个对角上放金币表示,其他范围内的放银币表示,棋盘外的格子则另找了一个禁用图标放上。随着玩家点击不同的右上角,方阵内所有元素动态更新。

除了棋盘大小,在预设的100关中还发现有“空洞”的元素,因此这里也要实现编辑空洞的功能。交互逻辑是这样的。点击中下方的“Hole”文本按钮,临时进入空洞编辑模式,在此模式下,棋盘上的所有金币银向移走,露出底板,若是空洞的位置则显示另一个表示空洞的图标;玩家此时点击棋盘内一格,标记该格为空洞,或取消原来的空洞标记。点一次 Hole 编辑一个空洞,随即恢复正常模式,即显示硬币,但空洞处不显示空洞。

在这个场景中,由于我一时对 quick 与 cocos2d-lua 的 API 还不是太熟,故而用了个比较笨的办法,在棋盘格上创建了三层精灵类对象。下层是底板,中层是硬币或范围外标志,上层是空洞标记,但正常模式下隐藏。切换到空洞编辑模式时,切换空洞与硬币的可见属性(setVisible)。

中层的硬币可调用原来的 Coin 类,同时可利用其翻面动画。但整个棋盘的逻辑功能与原来的 Board 类相差甚远,干脆重写了个 BoardSet 类。

下方是三个文本按钮,OK, Hole, Back。点 OK 时保存设置到 GameData。

4) PlayCustomeScene
正常规则下玩自定义关卡的场景,可以继承自 PlayLevelScene。只重写部分逻辑。左下角的标签改为“Custome: xx”,而非“Level: xx”;成功后点击弹出的对话框返回选关界面,而非下一关。

2 玩法提示功能

这个游戏虽然简单,但仍不保证能让所有人一看就懂怎么玩。所以还是想添加一些玩法提示。我想了个方案,在原来广告条的底部,用于显示提示文字信息,并且根据场景切换的上下文动态更新提示文本。

首先需要个配置文件,保存提示文本。在 data 目录下新增 HelpTips.lua,每条记录类似如下结构:

tipLine = {
    [1] = {
    id = "welcome",
    en = "Welcome to Coin Flip, tap START to have fun",
    zh = "翻金币欢迎您,点击 START 开始游戏吧",
    tag = "mainsc",
    },
    [2] = {
    id = "quick",
    en = "Many Thanks to Quick-Cocos2d-x 3.5",
    zh = "本游戏用 Quick-Cocos2d-x 3.5 开发",
    tag = "mainsc",
    },
....
}

tag 标签用于将提示文本分组,在每个场景中要显示的提示信息是一组而不是一条,在一组中循环显示。

文本条用 UILabel 创建,采用系统小字。放在另一个 Node 中,定义为 Footbar 类,然后其他场景类添加 Footbar 的实例,传入不同的分组 tag 以创建不同的 Footbar。

可用直接用 Node:shedule() 方法,它其实是创建一个循环动作,用于循环提示文本。间隔时间长一点,同时也支持用户主动点击切换下一条提示文本。

在主界面,增加按钮,可切换提示文本的语言,中文或英文。配置文件须保存为 utf-8,否则在手机上中文可能显示乱码。

ps. 提示文本的字体颜色目前调的可能不太理解,俺也一时找不到哪种颜色放在这背景上会好看些。

3. 其他优化

增加安卓机返回键功能,等同于各个界面上的“Back”按钮。我直接在各个场景类中添加代码了:

    -- 监听手机硬件返回按键
    display.newLayer()
    :addTo(self, -10)
    :setKeypadEnabled(true)
    :addNodeEventListener(cc.KEYPAD_EVENT, function (event)
        if event.key == "back" then self:onBack() end
    end)

注意用 addTo 或 addChild 加入场景时,最好将 zorder 设置为负值,防止该层在上面屏蔽其他响应触屏事件。

四、TODO

如何用程序算法智能寻找关卡的解法?以及判断用未知方法预设的关卡是否有解?

如果用暴力搜索的笨办法,随着棋盘格数的增大,时间复杂度与空间复杂度迅速增大。比如 6*6 =36 格的棋盘,就总共有 2^36 种不同状态。这是多大的数呢? 2^10 字节是 1Kb, 2^20是1Mb,2^30是1Gb,你的内存有多大呢。难怪说电脑不会下围棋了。

另一个有关发布的问题,平时开发写改代码都只用 vim,用 simulator 或 quick 的player3 查看效果足够快捷。配好环境也能编译出 debug 版本在自己的安卓机上玩。但想编个 release 版本时遇到 keystore 的问题,暂未解决呢。

五、其他参考教程

Quick-Cocos2d-x v3.2中示例Coinfilp解析
http://www.cocos.com/doc/tutorial/lists?id=97

Quick-Cocos2d-x数据存储之GameState
http://www.cocos.com/doc/tutorial/show?id=2758

硬件按键事件
http://www.cocos.com/doc/tutorial/show?id=1467

六、项目源码分享
src.zip           http://pan.baidu.com/s/1eQwgzMM
coinflip.apk  http://pan.baidu.com/s/1kTETjW7

0

阅读 评论 收藏 禁止转载 喜欢 打印举报/Report
  • 评论加载中,请稍候...
发评论

    发评论

    以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

      

    新浪BLOG意见反馈留言板 电话:4000520066 提示音后按1键(按当地市话标准计费) 欢迎批评指正

    新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

    新浪公司 版权所有