手遊《奧林劈圖》的開發日記(一)

峻峰飛陽發表於2019-04-03

我最近將手遊《奧林劈圖》上線到蘋果商店,了卻了一樁三年的心願,心情也由之前的燥動不安迴歸平靜。現在我真的有時間和一顆平常心去擁抱機器學習和資料探勘了。

幾年前自己剛開始學習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日]

1. 經典模式 (暗黑夜空)

2. 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日]

1. 環狀圖形允許

2. 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

通訊協議:

1. 第三方鑑權成功後,開始登陸游戲伺服器

send: msg={op:login, account:{uid3:124234135,type:QQ,name:氣昏頭}}&cct=0

resp: ret=0&rsp={accountuid: xxx, roleuid:AABB112233}}&cct=0

2. 選擇一個從伺服器返回的角色

send: msg={op:selectrole, roleuid:AABB112233}&cct=xxx

resp: ret=0&rsp={roleuid:AABB112233,cct=xxx,sct:yyy}&cct=xxx

3. 請求慢同步 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:{..}}

}

 

4. 合併本地帳號 (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

 

5. 花費金幣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

 

6. 使用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

 

7. 請求獲得伺服器的新事項

send: msg={op:getupdate,roleuid:AABB112233,sct:25}&cct=xxx (獲取伺服器中scounter為25的更新事項)

resp: ret=0&rsp={jsondata},sct:xxx}&cct=xxx

 

說明:

1. ccounter_on_client >= ccounter_on_server

99.99%的時間ccounter_on_client應該大於counter_on_server, 如果相等意味最後一次操作的返回資料未到達客戶端。

 

2. 當scounter_on_client <= scounter_on_server, 伺服器有更新的事件需要客戶端獲得。

 

設計目標:

1. 沒有連線伺服器的條件下,遊戲可以正常執行。當連線伺服器後,客戶端實現與伺服器的資料同步。

2. 多客戶端交替玩遊戲不會有嚴重問題。

3. 客戶端資料永遠與伺服器端相同或更新(離線玩),當客戶端資料舊的時候需要與伺服器同步。

 

1. 增量資料同步:

json queue:

0 {type:"login", data: }

1 {type:"stage", data:{id=sp2, usedwall=8, layout:[], wall=5,coin=100}

2 {type:"useitem", data: }

 

1. 全資料同步

使用者資料包括:

1. 過關數(例如:通過23關,第24關解鎖但未過)

2. 通過的關卡的資訊: 使用的牆數,時間

3. 賬戶擁有的牆數和金幣數量。

4. 牆佈局在離線狀態丟棄。

 

同步錨: 通關序號(如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覆蓋。

2) 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日]

 

帳號登入及角色初始化過程

1. 帳號和角色必須在伺服器建立。 (本地角色除外)

2. xxxManager is not also to manager xxx but also to give life to xxx.

3. 使用帳號資料的前提與聯網無關,而是:本地資料庫中的帳號和角色資料完整。所以需要先檢查這兩個。

4. 帳號表以伺服器為準,角色表需要比較ccounter.

5. 只有使用者選擇“遊客模式”才會啟用本地角色

6. 登入失敗或者選擇角色失敗,仍然使用帳號角色,前提是地資料庫完整。

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
}

 

 

 

 

 

相關文章