Jkeeper 发布的文章

最近在做新手引導系統, 遇到了些問題, 先說說新手引導的設計吧

一、新手引導UI

預設作法如下, 去看了下以前的做法似乎有些問題需要解決, 以前用 NGUI 做這塊, 需要將目標 UI 顯示層級提上來, 當時我的作法是把目標 Button parent 換成教學 UI Panel, 這樣就能保證要點擊目標一定會在最上層
33163-uzqhom9nj2.png

現在項目使用 UGUI, UGUI 顯示層級是看 UI 順序的, 不然就是修改 Sorting Layer 或 Order in Layer

Sorting Layer

Sorting Layer 可以想像是一個大類, 在這個列表下渲染是按照這個清單順序的, 譬如

A物件

  • Sorting layer: UI
  • Order in Layer: 10

B物件

  • Sorting layer: Fx
  • Order in Layer: 1

就算你設定比較高的 Order in Layer, A物件也不可能比B物件層級高, 這是因為在 Sorting Layer 就已經決定誰高誰低了
94201-1cfhiru1usk.png

Order in Layer

在同一個 Sorting Layer 下你可以藉由 Order in Layer 決定先後, 值越大層級越高
47776-rd0m2cznn3b.png

在 Sprite Renderer 可以設定這兩個值
43936-9hz0ff5lsvg.png

Spine 用的 Mesh Renderer 雖然沒有, 但其實都是繼承 render, 可以直接調用修改
34462-jjsbc5n9jg.png

mSpine_Teacher.GetComponent<MeshRenderer>().sortingLayerID = SortingLayer.NameToID("Spine");
mSpine_Teacher.GetComponent<MeshRenderer>().sortingOrder = 1;

最後呈現出來的效果是這樣
27106-b8ado9haxra.png

服務端跑起來發現出現了很多警告, 警告內容是註冊消息 OpCode = 0, 警告如下

消息opcode为0: C2S_BomberGame_QueryFrameMessage

接著從錯誤的地方順著流程查看

List<Type> types = Game.EventSystem.GetTypes(typeof(MessageAttribute));
foreach (Type type in types)
{
    object[] attrs = type.GetCustomAttributes(typeof(MessageAttribute), false);
    if (attrs.Length == 0){
         continue;
    }
    MessageAttribute messageAttribute = attrs[0] as MessageAttribute;
    if (messageAttribute == null){
        continue;
    this.typeMessages.Add(messageAttribute.Opcode, Activator.CreateInstance(type));
    this.opcodeTypes.Add(messageAttribute.Opcode, type);
}

public ushort GetOpcode(Type type)
{
    return this.opcodeTypes.GetKeyByValue(type);
}

發現 opcodeTypes 跑到一半就crash了, 這個框架一直有個致命的問題就是流程上有exception部會跳出提示, 有時候就需要下來下斷點查看....有空真的必須好好看看這個問題怎麼解決

發現 Activator.CreateInstance(type) 執行到 MarketInfo 就掛了, 出現這個錯誤

'Activator.CreateInstance(type)' threw an exception of type 'System.MissingMethodException' object {System.MissingMethodException}

後來查看 MarketInfo.cs 發現了 constructor 沒有寫, 補上去就好了, 就這段

public MarketInfo() { }

56888-xind84hxker.png

在開發好友系統時, 做了個功能叫做推薦名單, 也就是將最近對戰的玩家記錄到數據表上, 再推給客戶端, 但在獲取數據時發現必須過濾掉已經添加的清單, 索性在表格上添加了一個字段來紀錄是否有效
45883-4ngtcpyj76m.png

後來發現Chloe處理這段很麻煩, 沒查到官方有 replace 的功能, 所以自己用了 procedure 來處理, 有點久沒用研究了下 Chole 怎麼調用

官方文檔說明
https://github.com/shuxinqin/Chloe/wiki/Oracle-procedure
42712-uac01obubl8.png

兩個寫法不一樣, dbContext.SqlQuery 是直接獲取 Procedure 返回數據的, 就譬如 procedure 最後面寫了 select * from user limit 1; 可以透過這個方式獲取返回的 user 資料, 而 ExecuteNonQuery("Proc_GetPersonName", CommandType.StoredProcedure, id, outputName) 則是透過 Procedure 的 out 獲取數據

我們在更新 recent 表格同時也要獲取 recent 數據, 作法如果

DbParam iUserId = new DbParam("@iUserId", userId);
DbParam iRUserId = new DbParam("@iRUserId", recentUserId);
DbParam iState = new DbParam("@iState", 1);
dbContext.SqlQuery<Tbl_Recent>("proc_update_recent", CommandType.StoredProcedure, iUserId, iRUserId, iState);

這是一個沒有 return 的 procedure 調用方式, Procedure 如下

CREATE DEFINER=`root`@`127.0.0.1` PROCEDURE `proc_update_recent`(
    IN `iUserId` int,
    IN `iRUserId` int, 
    IN `iState` int)
BEGIN
    DECLARE ts long;
    set ts = (UNIX_TIMESTAMP() * 1000) + (MICROSECOND(NOW(3)) / 1000);

    REPLACE into `recent`(`UserId` ,`RUserId`, `State`)
        value
    (iUserId, iRUserId, iState);
END

另外得益於使用 Procedure , 在發送邀請功能上需要判斷很多情況, 如果在 c# 處理邏輯就會很繁瑣, 後來使用 Procedure 解決, 如下

CREATE DEFINER=`root`@`127.0.0.1` PROCEDURE `proc_check_send_friendApply`(
    IN `iApplyUserId` BIGINT,
    IN `iApplyUserName` VARCHAR(64),
    IN `iApplyUserHead` VARCHAR(128),
    IN `iToUserId` BIGINT, OUT `oData` INT)
MAINLOOP:BEGIN

    # declare timestamp
    DECLARE ts BIGINT(20) default 0;
    DECLARE isMyFriend TINYINT(4) default 0;
    DECLARE findRecent TINYINT(4) default 0;
    DECLARE findFriend TINYINT(4) default 0;
    DECLARE findApply TINYINT(4) default 0;


    set ts = (UNIX_TIMESTAMP() * 1000) + (MICROSECOND(NOW(3)) / 1000);

    #已經是我的好友, 退出流程
    select count(*) into isMyFriend from `friend` where `UserId`=iApplyUserId and `FUserId`=iToUserId;
    if isMyFriend != 0 then
        LEAVE MAINLOOP;
    end if;

    #if 如果Recent裡面有數據, 就改掉 state = 1
    select count(*) into findRecent from `recent` where `UserId`=iApplyUserId and `RUserId`=iToUserId;
    if findRecent != 0 then
        REPLACE into `recent`(`UserId` ,`RUserId`,`State`) value (iApplyUserId, iToUserId, 1);
    end IF;

    #if 對方已經是好友就直接加回去, 
    select count(*) into findFriend from `friend` where `UserId`=iToUserId and `FUserId`=iApplyUserId;
    if findFriend != 0 then
        REPLACE into `friend`(`UserId` ,`FUserId`,`CreateTime`, `UpdateTime`) value (iApplyUserId, iToUserId, ts, ts);
        set oData = 2;
        LEAVE MAINLOOP;
    end if;

    #如果沒有邀請過就發邀請
    select count(*) into findApply from `friendapply` where `ApplyUserId`=iApplyUserId and `ToUserId`=iToUserId;
    if findApply = 0 then
        REPLACE into `friendapply`(`ApplyUserId` ,`ApplyUserName`, `ApplyUserHead`, `ToUserId`, `State`, `CreateTime`, `UpdateTime`)
        value
        (iApplyUserId, iApplyUserName, iApplyUserHead, iToUserId, 0, ts, 0);

        select * from `friendapply` where `ApplyUserId`=iApplyUserId and `ToUserId`=iToUserId limit 1;
        set oData = 1;
    end if;
END

很久以前在做第一版的麻將時發現那位服務端非常喜歡用存儲過程, 可能是因為服務端 nodejs 且沒有做數據持久化的緣故, 很多數據無法獲取必須重新拉數據, 這樣就會造成數據庫壓力很大, 最早接手開發RPG遊戲時必然是必須做數據持久化的, 設計到服務端乘載人數跟架構, 不做數據持久化管理壓力會壓在Mysql上, 後來做博奕遊戲就很少做這塊了, 輕便為主

AssetStudioGUI 可以用來查看 Unity 包體內容, 可以多看看其他優質產品的資源內容怎麼做的

首先開啟 AssetStudioGUI
56533-xssdnwijam8.png

將 unity 的包給解壓, 用來測試的是之前上傳 Google Play 的 RummyGo aab包
81185-kb3z490tbzb.png

AssetStudioGUI 點擊 文件->加載目錄, 選擇解壓目錄
85830-fao0kd2llnj.png

切換到資源列表, 並點類型排序
96427-dr0n1ygrhel.png

可以直接查看包體內容
42052-yy9rtnr5hs.png

打包app執行轟炸超人發現遊戲開局就crash了....
43263-pqdof30pwk9.png
95688-6lpou38yl0i.png

在pc上複查發現這個錯誤
04236-o2zub6agc9j.png

最後發現是這邊出錯, int[,]在查詢數據時 address 不知道為什麼出錯了

for (int j = 0; j < map.MapConfig.ChunkH; j++){
    strm += "[";
    for (int i = 0; i < map.MapConfig.ChunkW; i++){
                //這邊出錯
        strm += matrixData[j, i] + ",";
    }
    strm += "],";
}
strm += "]";

詳細錯誤如下

IL_00af: call System.Int32& System.Int32[0...,0...]::Address(System.Int32,System.Int32)
at ETHotfix.AIPlayer.calcNonPath(System.Int32 depth) (at G:/OlgCase/bbm/source/Unity/Assets/Hotfix/GameGather/Bomber/AI/AIPlayer.cs:366)
at ETHotfix.AIPlayer.calcPath() (at G:/OlgCase/bbm/source/Unity/Assets/Hotfix/GameGather/Bomber/AI/AIPlayer.cs:200)
at ETHotfix.AIPlayer.V_Think() (at G:/OlgCase/bbm/source/Unity/Assets/Hotfix/GameGather/Bomber/AI/AIPlayer.cs:86)
at ETHotfix.AIPlayer.think() (at G:/OlgCase/bbm/source/Unity/Assets/Hotfix/GameGather/Bomber/AI/AIPlayer.cs:79)
at ETHotfix.AILogic.UpdateLogic() (at G:/OlgCase/bbm/source/Unity/Assets/Hotfix/GameGather/Bomber/AI/AILogic.cs:292)
at ETHotfix.BaseEntity.updateAILogicAct() (at G:/OlgCase/bbm/source/Unity/Assets/Hotfix/GameGather/Bomber/Entity/BaseEntity.cs:1581)

代碼修改, 把查詢給int再使用就好了

for (int j = 0; j < map.MapConfig.ChunkH; j++){
    strm += "[";
    for (int i = 0; i < map.MapConfig.ChunkW; i++){
        num = matrixData[j, i];
        strm += num + ",";
    }           
    strm += "],";

以後有空再來查這個問題