我最近将手游《奥林劈图》上线到苹果商店,了却了一桩三年的心愿,心情也由之前的燥动不安回归平静。现在我真的有时间和一颗平常心去拥抱机器学习和数据挖掘了。
几年前自己刚开始学习cocos2dx的时候,脑子经常冒出各种各样的奇怪的游戏创意,害怕下一分钟可能会忘却,就习惯了把它们记录在有道笔记上。至今翻看那些笔记,可以零星的回忆起这个游戏创作和开发的轨迹和新路历程。
[2016年4月19日]
任务目标设计(旧)
添加3根木棒,获得:
2个L2 (该设计太抽象)
1个Z1 (该设计太抽象)
(其它图形数量不限)
除去3根棍棍儿,获得:
3个O4 (该设计太抽象)
用给定的棍棍儿, 将图形分为4个大小和形状相同的图形。
放置所有积木到灰色图形内,即可过关。
任务id:
任务目标:添加%d根木棒,获得:
* %d个%s
* %d个%s
(其它形状数量不限)
任务验证函数
任务完成状态提示:打勾
TaskPopup和TaskStatus的UI根据StageTask的参数配置生成。
复杂困难的任务分步让玩家完成。
[2016年4月20日]
StageLayer (经典模式)
|----背景层
|----StatusBar
|----scrollview
|----stageN (sp1.csb, sp2.csb, sp3.csb e.g.)
|----task (type=0, pieces=3,award:{wall=3,coin=100})
|----mapboard (W=100, H=100)
|----dimension (size = 700x1100 e.g.)
|----fields
|----walls
|----blocks
|----TaskPopup (跟据stageN的配置信息生成)
|----ResultPopup (奖励根据stageN的配置信息生成)
|----HintPopup (createNode by stageN.csb)
任务目标:
通过添加墙,将图形分成若干个子图形,然后移动子图形填充到下方的阴影图形中。
任务完成:
您消耗了6个墙完成本次任务,目前世界最好成绩为使用5个墙。
您的过关奖励为: 墙: 6 金币:100
[返回] [确定]
过关奖励:
恭喜你获得: 墙: 6 金币:100
(此处与服务器通信,wall +6, coin +100, usewall 6, 如果好于预设墙数发送:墙的布局数据)
墙布局: {stage=sp5, num=6, pos: [{x:1,y:2,d=1},{x:2,y:3,d=0}]}
[2016年4月22日]
[难]:
面积大
融合度高
规定劈开的图形数目,大小或形状
规定使用围墙数目
[易]:
面积小
离散
原图形面积大于目标图形面积 (原图形允许有剩余)
基本上,可添加围墙数目决定答案的难度
[2016年4月23日]
-
经典模式 (暗黑夜空)
-
PVP模式设计,每天10:00 (或整点)发布一个新副本,30分钟内完成,墙数少者获胜。墙数相同时间早者获胜。前10名有奖品。
bool g2Grid::hasPart()
bool g2Grid::getField()
g2Body -> [covered grids]集合 ->遍历
Body::isLanded()
g2ShapeModel, g2ShapeClip ==> g2Shape (嵌套结构)
bool g2ShapeClip::isFieldLaid()
{
int xnum = xNum();
int ynum = yNum();
for (int i = 0; i < xnum; i++)
{
for (int j = 0; j < ynum; j++)
{
auto pt = m_origin + GridPoint(i, j);
auto grid = getGrid(pt);
if (!grid || !grid->getField())
return false;
}
}
return true;
}
[2016年4月25日]
-
环状图形允许
-
CShape : g2Mapboard
{
std::<CShape*> m_children;
}
Sharesdk集成
Sqlite加密
主界面设计
关卡选择界面
关卡背景
iOS内购集成
[2016年4月26日]
一个Zone由关卡选择器和若干关卡组成。
关卡选择器中包含若干Button,每个Button是一个关卡的入口。
StagePicker
|--------Button1 {version:1, source:“xxx.csb”}
|--------Button2 {version:2, source:“xxx.csb”}
|--------Button3 {version:2, source:“xxx.csb”}
StageLayer1 {wall:3, award:{coin:100,wall:4}}
StageLayer2 {wall:3, award:{coin:100,wall:4}}
StageLayer3 {wall:3, award:{coin:100,wall:4}}
[2016年4月26日]
服务器与客户端同步设计
通用格式:
send: req={op:xxx, opdata}&cct=yyy
resp: ret=0&rsp={op:xxx, respdata, cmd:{jsondata}}&cct=yyy
定时请求server端的scounter
通信协议:
- 第三方鉴权成功后,开始登陆游戏服务器
send: msg={op:login, account:{uid3:124234135,type:QQ,name:气昏头}}&cct=0
resp: ret=0&rsp={accountuid: xxx, roleuid:AABB112233}}&cct=0
- 选择一个从服务器返回的角色
send: msg={op:selectrole, roleuid:AABB112233}&cct=xxx
resp: ret=0&rsp={roleuid:AABB112233,cct=xxx,sct:yyy}&cct=xxx
- 请求慢同步 when ccounter (client) < ccounter(server) (服务器数据覆盖客户端)
send: msg={op:slowsync, roleuid:AABB112233}&cct=0
resp: ret=0&rsp={roledata:{…},sct:xxx}&cct=0
角色用户数据:
{
role:{uid=xxx,name=yyy,coin=500,wall=12,zone1Prog=7,accountUid=zzz,ccounter=11,scounter=9},
zone1:{s1:{…},s2:{…},s3:{…},s4:{…},s5:{…},s6:{…},s7:{…}}
}
- 合并本地帐号 (when ccounter=0 on server side) (客户端数据覆盖服务器)
send: msg={op:merge, roledata:{…}}&cct=xxx
resp: ret=0&rsp={roleuid:AABB112233,cct=xxx,sct:yyy,cmd:{op:slowsync}&cct=xxx
- 花费金币500购买当前关卡hint
send: msg={op:buyhint, roleuid:AABB112233, cost:{coin:500}}&cct=xxx
resp: ret=0&&rsp={op:buyhint, coin: 1500, sct:yyy}&cct=xxx
- 使用5个围墙条,通过关卡 (zone=zone1 sn=57)
send: msg={op:pass, roleuid:AABB112233, stage:{zone:zone1,group:0,sn: s27}, cost:{wall:5}, award:{coin:100, wall:6}, misc:{time=300}}&cct=xxx
resp: ret=0&rsp={op:pass, coin: 1500, wall: 11, sct:yyy}&cct=xxx
- 请求获得服务器的新事项
send: msg={op:getupdate,roleuid:AABB112233,sct:25}&cct=xxx (获取服务器中scounter为25的更新事项)
resp: ret=0&rsp={jsondata},sct:xxx}&cct=xxx
说明:
- ccounter_on_client >= ccounter_on_server
99.99%的时间ccounter_on_client应该大于counter_on_server, 如果相等意味最后一次操作的返回数据未到达客户端。
- 当scounter_on_client <= scounter_on_server, 服务器有更新的事件需要客户端获得。
设计目标:
-
没有连接服务器的条件下,游戏可以正常运行。当连接服务器后,客户端实现与服务器的数据同步。
-
多客户端交替玩游戏不会有严重问题。
-
客户端数据永远与服务器端相同或更新(离线玩),当客户端数据旧的时候需要与服务器同步。
-
增量数据同步:
json queue:
0 {type:“login”, data: }
1 {type:“stage”, data:{id=sp2, usedwall=8, layout:[], wall=5,coin=100}
2 {type:“useitem”, data: }
- 全数据同步
用户数据包括:
-
过关数(例如:通过23关,第24关解锁但未过)
-
通过的关卡的信息: 使用的墙数,时间
-
账户拥有的墙数和金币数量。
-
墙布局在离线状态丢弃。
同步锚: 通关序号(如23),counter0(每次数据通讯标识), counter1(每次数据通讯标识)
[2016年4月27日]
宠物系统
某一关通过后,赠送玩家一种图形:
恭喜你获得宠物: 马蹄型 (图)
又某一关通过后,赠送玩家一种图形:
恭喜你获得宠物: 直角型 (图)
玩家携带一个宠物参战,每一关如果劈出宠物图形,则奖励增加xx%。
通关后宠物获得经验,当经验满足升级条件,可以手动升级,升级需要花费一定的金钱。
宠物级别越高,如果劈出与宠物相同图形,通关获得的金钱奖励和围墙奖励越多。
您确定更换参战的宠物吗? 更换后,原宠物的连胜次数被清除。 连胜越多,通关后获得的经验越多。
[2016年4月29日]
提示设计 (HintPopup)
HintPopup (经典模式)
|----背景层
|----scrollview
|----stageN (sp1.csb, sp2.csb, sp3.csb e.g.)
|----task (type=0, pieces=3,award:{wall=3,coin=100})
|----mapboard (W=100, H=100)
|----dimension (size = 700x1100 e.g.)
|----fields
|----walls
|----blocks
|----hints (visible)
花钱买过提示,会记录在案,以后可以自由查看。
[2016年5月6日]
过关检查
触发事件: block moved
检查:
1) dstShape检查
每一个方格都已被block覆盖。
- srcShape检查
劈分的每一个clip,都已经生成block,每一个block都至少覆盖一个目标方格。
3) Limit检查
调用limit::isAllOk(),没一个检查道具均检查通过。
[2016年5月9日]
数据库设计
服务器:
account
[uid] [uid3] [type]
role
[uid] [name] [coin] [wall] [zone1Prog] [accountUid]
zone1_by_role
[roleUid] [groupId] [s1] [s2] … [s40]
字段s(n): json format e.g. {w:5, playtime:300, version:1}
zone1_by_stage (主要用于游戏统计)
[uid] [version] [source] [playerNum] [playTimes] [wallNum] [spentTime] [hintTimes] [minWallNum] [maxWallNum] [bestSolution] [bestRoleUid] [bestCreateTime]
s1 1 split1 57 6 241 8 4 9 {jsondata}
客户端: (Logined Account -> Active Role)
account
[uid] [uid3] [type]
role
[uid] [name] [coin] [wall] [Zone1Prog] [accountUid] [ccounter] [scounter]
zone1
[roleUid] [groupId] [s1] [s2] … [s40]
字段s(n): json format e.g. {w:5, playtime:300, version:1}
opmsg
[_id] [msg] [md5] [roleUid]
[2016年5月9日]
用户数据管理
class Role 是所有用户数据入口类
Role -> Udb -> Local Database
Role::init 加载表role和zone1中的数据。
提供对本地用户数据的getter和setter
setCoin() getCoin()
setWall() getWall()
setCCounter() getCCounter()
setSCounter() getSCounter()
pushOpMsg() popOpMsg()
Udb严禁Remote访问,保证数据在客户端和服务器的一致性和可维护性。
[2016年5月12日]
帐号登录及角色初始化过程
-
帐号和角色必须在服务器创建。 (本地角色除外)
-
xxxManager is not also to manager xxx but also to give life to xxx.
-
使用帐号数据的前提与联网无关,而是:本地数据库中的帐号和角色数据完整。所以需要先检查这两个。
-
帐号表以服务器为准,角色表需要比较ccounter.
-
只有用户选择“游客模式”才会启用本地角色
-
登录失败或者选择角色失败,仍然使用帐号角色,前提是地数据库完整。
bool Role::checkUserDataIntegrity()
AccountManager::onAuthenticated() -> (uid3, type)
{
find(uid3, type) -> (accountUid); //首先本地搜索帐号:
find(accountUid) -> (roleUid) //搜索角色
(–>onAccountSelected)
AccountManager::login() //如果本地无此帐号或角色,启动登录
(-->onAccountSelected)
}
AccountManager::onAccountSelected(accountUid, roleUid)
{
account = Account::create();
Account::init() //读数据库初始化account
RoleManager::selectRole(roleUid)
}
RoleManager::selectRole(roleUid)
{
RoleManager::find(accountUid) -> (roleUid) //首先本地搜索角色:
(–>onSelected)
//没有找到, 查询服务器
(-->onSelected)
}
RoleManager::onSelected(roleUid)
{
role = Role::create();
//读数据库初始化role
}