香港曾道人六宵:戰魂小筑

討論群:309800774 知乎關注://zhihu.com/people/sunicdavy 開源項目:https://github.com/davyxu

   :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
  252 隨筆 :: 0 文章 :: 506 評論 :: 0 Trackbacks

2018年11月30日 #

近期在游戲demo中試驗ECS, 深入研究Unity官方的ECS框架和第三方Entitas框架, 分享下使用ECS的心得。


Unity在2018版中加入了ECS系統, 但處于小白鼠階段。默認不是Unity的一部分, 需要手動下載代碼并導入Packages(新特性)。官方提供海豚例子, 但除此之外例子和資料非常少。所以完全無法,也不敢在demo中貿然引入這種系統,所以放棄官方ECS系統。


第三方的Entitas(https://github.com/sschmid/Entitas-CSharp)ECS框架從2015年就開始在各地演講中介紹。整體框架基于代碼生成, 能解決一部分的代碼爆炸問題, 而且性能也要好一些。例子,介紹非常豐富,例子雖然基于不同版本的Entitas,特性支持和最新版差不太多, 只是寫法有細微差異, 對于理解來說無礙。


經過1~2天的改造, 終于將demo從傳統Unity寫法改造為ECS標準寫法,新增了46個文件, 而傳統邏輯一共只有16個文件,大概對比下ECS的特點和差異。


Entitas的ECS系統

1. 本來在一個對象中添加一個類字段的過程,ECS需要添加一個類代表Component,并且代碼生成。

這個字段一般用于描述對象的資源,處理顯示的GameObject, 表示對象的類型等。


2. 本來一個對象的業務邏輯處理過程直接用方法解決的, ECS需要新加一個System,而操作對象需要使用Filter或Group查詢獲得。


3. 一系列的操作, 需要拆分為多個System和Component拆分處理。如果System順序不對, 會造成一些詭異的bug。


4. Component不僅僅是Model承載體, 也可以是參數的數據結構。參數Component通過Entity傳遞到System處理。 例如: 通過ECS創建一個方塊的過程,使用CreateTileComponent,包含創建Tile的位置, 創建Entity并添加CreateTileComponent, 在CreateTileSystem中處理就創建了Tile,處理完成時, 需要將傳入的Entity.Destroy掉。


6. Entity上修改Component的過程, 會觸發事件。修改的過程需要使用RelaceXXX,XXX表示組件名。組件可以頻繁修改, 不用擔心添加和刪除組件過程的性能, Entitas底層處理性能只相當于指針賦值的性能。


ECS像什么?

1. ECS中的System類似觸發器系統(Event-Condition-Action),其中,Event對應Entitas的GetTrigger+Collector,表示觸發事件。Condition對應Filter表示在事件來源對象中找到需要的對象。 Action對應Execute,表示實際的操作。

2. ECS中的Component類似不用lua擴展的Redis或者不用存儲過程的MySQL, 純粹純數據, 而不能對數據有任何封裝操作。沒有lua和存儲過程支持的db寫起來還是比較費勁的,但ECS就是那么的純。


3. ECS中的Entity很尷尬,因為Component是按類別連續存儲的以保證性能。 邏輯又需要Entity組合成邏輯需要的復合對象。 兩邊都要照顧,所以這種設計就讓代碼量巨增,可讀性下降。



ECS企圖用一套框架滅掉設計模式

1. 單件(Singleton)在Entitas用Unique標簽標記Component, 在Context中就是唯一的, 其實也就是Singleton。


2. ECS干掉了傳統的工廠模式,底層統一對對象(Entity)和屬性(Component)統一管理。需要按Component中的值找回Entity時, 可以使用EntityIndex。


3. Entity攜帶不同的組件時,整個創建和銷毀過程被記錄并恢復,其實就是Command模式


ECS適合做UI框架(類似MVVM,MVC,MVP)么?

ECS不是專用的UI框架,但是可以對不同系統和數據間解耦。傳統代碼中數據修改后的Callback,ECS也可以用Listener做, 但Listener因為能保存數據, 就需要用Component保存。 所以你需要面對的是,一個Button,響應創建一個參數用的Component和System,還要為數據改變寫一套ListenComponent和Listener處理的System,酸爽吧?


Minecraft適合ECS來做么?

可以,性能應該能提升不少,但是代碼會更繁瑣,特別像Java這種啰嗦語言配上ECS這種啰嗦框架,估計代碼量翻翻還是很輕松的。MC屬于特殊類型的游戲,適合特殊領域特別優化,也就是專門為方塊做出特別的設計來做優化。ECS屬于通用框架,即便性能OK,但是代碼未必能有良好的可讀性。


體量小的游戲適合用ECS來做么?

可以,但不建議。特別是只有幾個人維護的工程,貿然上ECS系統,會讓系統變的極為復雜。當然你會說,如果開發到后期,傳統開發模式會導致代碼會亂,ECS會好些吧。掌握ECS也不是一天兩天的事情,不熟悉ECS的程序員設計出來的系統獲得的優勢可能還不如用傳統設計方法好呢。

架構解決的是人的問題, 人都有問題,用什么框架都沒辦法。


到底什么項目適合用ECS?

1. 大量的小個體不斷的生成和銷毀以及顯示,例如: 攻城戰中,要體現每個角色的移動,戰斗。

2. 多于5個人編寫核心戰斗邏輯?;ハ嘈骱湍?榍蟹?,需要一個大家都能信服的框架,ECS可以選擇。


P.S.

不要造ECS的輪子!

很多同學看了ECS基本原理,在沒有深入使用過任何ECS系統時馬上操刀造輪子。ECS系統確實看起來簡單。實際造下來你會發現,性能非常糟糕以及不知道一些邏輯如何用ECS來解決。


總結:

1. ECS確實為性能而生,沒有并發加持性能的ECS都是耍流氓,要快就要快到極致。

2. Unity中,ECS并發能擴展CPU的利用率,但是GPU的性能依然還是DrawCall優化那一套,別期望ECS會顛覆Unity,性能也不會快到飛起,關鍵還是要看具體的項目和人。

3. ECS是萬能框架,但不全能。傳統架構和設計思想也不是一無是處,熟啥用啥,怎么快怎么來!

無恥的廣告鏈接,請各位支持

《Go語言從入門到進階實戰(視頻教學版)》(徐波)【摘要 書評 試讀】- 京東圖書

posted @ 2018-11-30 18:01 戰魂小筑 閱讀(800) | 評論 (0)編輯 收藏

2018年8月29日 #

為了編寫基于cellnet的新一代游戲服務器框架,最近深入研究微服務,ServiceMesh等概念。研究過程中對Web和游戲兩種服務器架構設計有一些心得,編寫并記錄下來。(下文中,Game表示游戲服務器,Web表示Web服務器) ``
狀態緩存
所謂狀態緩存,就是在內存而非專業數據緩存服務器(如redis)中保存和處理邏輯數據,手動編寫此過程較為繁瑣但是效率較高,但隨著狀態邏輯復雜性和并發、擴容問題提出,狀態同步會變得越來越復雜。
Game:
強交互性的服務器類型需要在服務器做緩存,邏輯編寫也較為容易,無需處理事務并發問題。例如:組隊,匹配,戰斗邏輯。服務器不能隨意重啟。
弱交互性的服務器類型可配合redis做成無狀態服務器,例如:養成,技能升級,領取物品等。服務器隨時支持重啟。
游戲服務器為了提高性能,早期所有服務器都是使用狀態緩存寫法編寫,特別是MMORPG這類強交互的游戲服務器尤為嚴重。
Web:
均為無狀態服務器,弱交互。使用事務方式處理并發邏輯,例如:交易,下單等。
推送,單獨發送
這里提到的所謂推送,單獨發送是與RPC區別的通訊方法。RPC要求請求必須有回應。而推送單獨發送則更像是通知和廣播,無需目的方返回任何消息。
Game:
找到服務器的Session,直接Send
通過中轉服務器,或稱為中心服務器進行注冊/廣播
客戶端的model數據需要更新時,服務器會主動推送消息。
游戲服務器沒有嚴格的RPC設計需求,推送和單獨發送較Web服務器更多。而且游戲服務器多使用長連接,所以主動推送也比Web服務器來的方便一些。
Web:
將推送做成專有的服務,并做排隊和并發處理。
可用性
聽說過游戲停服更新,支付寶服務器在刷二維碼時停服了可一定被罵慘吧。Web對服務器高可用性要求很高,游戲雖然也注重服務器穩定性和可用性,但是由于版本迭代更新頻繁,停服更新反而能獲得玩家接受。
Game:
游戲對可用性要求不高。
游戲大版本更新時需要停服更新。支持熱更新技術的服務器(例如Erlang,Skynet)僅使用熱更新修復bug,很少直接更新新版本。
不是所有的游戲服務器支持動態添加服務器。
Web:
極高的可用性,服務不允許停服更新,使用藍綠及灰度方式更新服務器。
隨時可以橫向擴展服務器,提高服務器容量和承載。
連接及傳輸
均使用TCP傳輸協議,游戲服務器注重性能,自有協議及二進制協議使用較多。
Web注重兼容和接口友好,使用JSON格式較多。
Game:
使用長連接,需要從邏輯層維護連接狀態及處理服務器不在線情況
使用自有封包格式,大部分使用protobuf或二進制流格式。
Web:
微服務大部分使用短連接,grpc支持http2長連接
使用json編碼方便調試和版本兼容。
流量限制
人數多了,任何服務器都扛不住,流量限制和登入限制能有效?;し衿魑榷?。
Game:
單服有人數限制,可以通過GM后臺設置擋墻,超過無法進入
Web:
限流器中間件,可以精確到服務控制流量
斷流,防止雪崩
Game:
游戲沒有,也不需要這種概念,游戲請求不會突然升高,即便有,也通過GM后臺人為控制
Web:
斷流器中間件
服務發現
如何找到服務器地址。
服務有變化時,通過Watch系統通知訂閱者更新本地緩存
服務器沒有變化時,使用本地緩存找到服務地址
Game:
游戲服務器互相依賴復用只在很小的范圍內,因此無需在不同語言不同進程服務間獲得地址,大部分在配置文件中填寫各服務的IP及地址即可互相訪問。
早期游戲自己編寫服務器狀態及地址發現服務。
有用redis做服務發現
Web:
使用服務發現系統,分布式部署。無需依賴配置文件
網關需求
Game:
網關處理客戶端上下線通知,心跳,維持連接,轉發,廣播上下行封包
Web:
根據請求地址路由,無上下線概念,無心跳。廣播通過消息推送系統完成
由于筆者從事游戲行業,對Web服務器概念在逐漸熟悉中,若有錯誤和不足請各位大佬指出。
本人新書《Go語言從入門到進階實戰》,生動的語言,例子帶有各種彩蛋,輕松了解Go語言特性,更有cellnet框架剖析解密
https://search.jd.com/Search?keyword=go%E8%AF%AD%E8%A8%80%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E8%BF%9B%E9%98%B6%E5%AE%9E%E6%88%98&enc=utf-8&suggest=1.def.0.V02&wq=Go%E8%AF%AD%E8%A8%80%E4%BB%8E&pvid=145d55a92cab4b07b71326f8beb1700b
posted @ 2018-08-29 11:16 戰魂小筑 閱讀(1321) | 評論 (0)編輯 收藏

2017年12月19日 #

嵌套git庫的管理
使用git作為源代碼管理時,經常需要在一個git代碼庫中從外網獲取新的git庫,也就是在git庫下嵌套另外一個git庫。而同時維護兩個git庫的過程就變的非常復雜。
submodule的弊端
常見的做法是使用git 提供的submodule功能。但submodule的管理嵌套git庫的結果往往不是我們期望的結果。假設有一個git庫叫project,在project的某個子目錄下還包含一個叫3rd的目錄,是另外一個git庫。
D:.
└─project
└─3rd
mytext.txt
假設甲和乙都取了project的代碼,由于時間差異,甲通過submodule取到V1版,而乙取到了V2版本。甲乙同時在不同的3rd庫下進行開發,勢必會造成不同的運行結果。也許你認為可以在獲取submodule時指定版本,但這個獲取過程很難控制。
一般說來,第三方庫應由主程序進行更新及維護,一般情況下,項目沒有特殊需求時,不會隨便更新第三方庫到最新版本,因此submodule更新嵌套的git庫并不是理想的解決方案。
嵌套git庫的修改可見性
前面的例子中,project git庫下的3rd的git庫中如果有文件發生修改,此時在project目錄下,使用sourceTree等git管理工具無法識別3rd下的文件修改。
也就是說,無法將3rd下的修改提交到project的git庫中。
終極解決方案
此時,將3rd目錄下的.git目錄暫時移出project和3rd目錄,此時,在project目錄用sourceTree查看時,將可以看到3rd下的文件修改,提交修改到project庫中。再將剛移出去的3rd的.git目錄移回3rd目錄下,在3rd目錄下使用git status,將可看到3rd目錄的修改,提交時,會將修改提交到3rd目錄。
posted @ 2017-12-19 22:04 戰魂小筑 閱讀(1489) | 評論 (0)編輯 收藏

2017年11月27日 #

越來越多的人選擇用Mac或者Linux環境進行跨平臺項目開發。但是仍然有大部分人習慣于在Windows環境下進行開發,畢竟Windows在各方面使用還是較為方便,特別像文件版本管理(Git,SVN等)

在跨平臺下開發游戲或軟件,就需要有一套方便的自動化工具。Windows下需要使用批處理,雖然有PowerShell加持,但這東西學了也不靠譜,只有一個平臺能用。大家還是習慣Linux Shell。連Mac平臺都可以用Shell,Windows上要做自動化腳本就顯得非常尷尬。

我曾經在項目中使用go編寫了一套將配置轉為批處理和Linux Shell的工具。使用過程較為復雜,但是能跨平臺進行表格導出和協議編譯等工作。

但是,這個工具還是需要對不同的平臺編寫多套模板進行代碼生成,非常繁瑣。如果有一套跨平臺的Shell,編寫一次就可以跨平臺運行那該多好。

查閱資料后,一共有幾個方案:

  1. 使用Python作為自動化工具 這個方案其實就是使用python把批處理和Shell干的事情用代碼來解決。但前提是要重新學習Python,也需要一部分熟悉簡單的Python語法,人為學習成本較高,也比較費事。

  2. 自己編寫一套獨立的自動化工具 這個方案需要長時間的適配過程,差什么指令補什么指令,對項目進度有一定干擾。

  3. 自己編寫Linux Shell的解釋器 這個就更難了,要做到100%兼容,基本不可能。

  1. 使用Cygwin和Mingw 需要一個微型運行時進行Linux Shell的解釋,msys大概是18M左右,可行性較高。

在研究Cygwin和Mingw如何整合的過程中,我誤操作點擊了一個sh后綴的Linux Shell,這是我希望讓Mingw運行的Shell。結果呢,sh后綴的文件居然能在Windows下運行。我馬上編寫了一系列的例子,發現幾乎完全兼容常用的Shell指令。 經過研究,我發現Windows下能運行sh文件是由Git自帶的msys2提供的。MSYS2 (Minimal SYStem 2, //www.msys2.org/) 是一個MSYS的獨立改寫版本,主要用于 shell 命令行開發環境。同時它也是一個在Cygwin (POSIX 兼容性層) 和 MinGW-w64(從"MinGW-生成")基礎上產生的,追求更好的互操作性的 Windows 軟件。

那就是說,只要安裝了Git(https://git-scm.com/),那么就可以在Windows下直接運行Linux Shell,只需要將文件后綴命名為sh即可。

問過周邊友人是否知道這一功能,都說知道,只是沒有廣播而已,害我研究很久……

posted @ 2017-11-27 15:15 戰魂小筑 閱讀(3086) | 評論 (3)編輯 收藏

2017年7月6日 #

本文主要研究游戲服務器帶狀態的熱更新需求 http的無狀態熱更新需求已經有成熟方案, 故不在本文描述范圍

基本概念

  • Golang的熱更新采用什么機制?

    使用go1.8提供的plugin包機制實現

  • plugin包本身設計的目的是熱更新么?

    plugin包其實只是支持將代碼分別編譯為多個動態庫,動態加載后運行 并不能完全支持類似C/C++的動態庫方式處理代碼

  • 帶狀態的進程熱更新的基本概念及范圍是什么?

    數據部分(model)不更新, 只更新邏輯部分(函數)

  • 表格和配置更新算熱更新么?

    算, 但不是本文描述范圍

  • 熱更新能在windows上使用么?

    不支持

代碼及結構

  • 我能將原來一個exe的代碼編譯為so提供給plugin使用么?

    可以, 但是必須仍然保留main包作為插件入口, 并在main包內添加提供給plugin調用入口.

  • 如果動態庫中沒有main包, 編譯出的so能用么?

    不能, 包必須包含main, 否則輸出的是.a的文件, plugin包加載會報錯

  • 動態庫中, 非main包的的代碼修改能做熱更新么?

    不能!(崩潰了吧, 我提了一個issue: https://github.com/golang/go/issues/20554)

    如果確實做了修改, 底層會報錯: plugin was built with a different version of package

    解決方法: 修改plugin包底層實現并重新編譯 打開runtime/plugin.go, 注釋以下代碼 for _, pkghash := range md.pkghashes { if pkghash.linktimehash != *pkghash.runtimehash { return "", nil, pkghash.modulename } } 執行/usr/local/go/run.bash 重編譯+測試

  • 代碼中哪些可以被更新? 方法可以被更新么? 閉包呢?

    只能更新擁有靜態地址的結構.例如: 包級別函數(類似于靜態函數)

    例如: svc_0.so里有一個Foo函數, svc_1.so修改了Foo函數實現, 熱更新可實現

    閉包=函數+捕獲變量, 實際上是一個動態結構, 沒有靜態地址, 無法被更新

    各種包級別變量, 結構體變量, 結構體方法, 局部變量均不能被熱更新, 但是變量值不會被影響

    新增結構可以被運行

  • 使用結構體方法調用了包級別函數, 包級別函數能被更新么?

    可以, 雖然方法不能被更新, 但方法被調用的包級別函數的地址是固定的, 所以可以被熱更新

  • init包初始化函數能用么? 能被熱更新么?

    官方這樣描述:

    When a plugin is first opened, the init functions of all packages not already part of the program are called. The main function is not run. A plugin is only initialized once, and cannot be closed

    插件第一次被打開時, 其關聯的, 沒有成為程序的一部分的所有的包的init函數將被調用. 插件的main函數不會被調用. 插件只會被初始化一次, 不能被關閉

    因此, 需要手動將init函數改成自己的函數, 統一在so的main包里調用

編譯

  • 如何編譯獲得plugin包支持的動態庫

    SVCNAME=$1 SVCVER=$2 TIMESTAMP=`date '+%Y%m%d_%H%M%S'` go build -v -buildmode=plugin --ldflags="-pluginpath=${SVCNAME}_${TIMESTAMP}" -o ${SVCNAME}_${SVCVER}.so ${SVCNAME}

    -buildmode=plugin是重要參數

    --ldflags里的-pluginpath的作用是: 每次編譯的內部識別路徑都是不同的, 避免重復加載的警告

    參考: https://github.com/golang/go/issues/19004

posted @ 2017-07-06 12:47 戰魂小筑 閱讀(5808) | 評論 (0)編輯 收藏

2017年4月20日 #

使用Visual Studio Code調試Golang工程

關鍵字

  • 最簡單的調試攻略
  • 多項目調試, 適用個人開發和項目開發
  • 無需修改系統環境變量

準備VSCode

在官網下載最新版的VSCode:

https://code.visualstudio.com/

安裝Golang插件

  • 打開擴展面板

    VSCode->查看->擴展

  • 找到Go插件 在搜索框里輸入Go, 找到第二行寫有 Rich Go language support for Visual Studio Code的插件, 點擊安裝

    注意不是排名最高的

  • 重啟編輯器

配置啟動項

  • 打開調試面板

    VSCode->查看->調試

  • 添加調試目標

    在"沒有調試"的下拉框中點擊"添加配置.."

  • 添加目標調試配置

    例子:

    {
        "version": "0.2.0",
        "configurations": [
            {
                "name": "Launch",
                "type": "go",
                "request": "launch",
                "mode": "debug",
                "remotePath": "",
                "port": 2345,
                "host": "127.0.0.1",
                "program": "${fileDirname}",
                "env": {
                    "GOPATH":"D:/Develop/vscodegolang"
                },
                "args": [],
                "showLog": true
            }
        ]
    }

其中: "port", "host"都是go插件自動生成的

"env"為設置環境變量, 設置為你的工程目錄就可以(包含bin, src的文件夾)

準備調試插件

此時找到main.go按F5, 會報錯提示:

Failded to continue:"Cannot find Delve debugger. Install from https://github.com/derekparker/delve & ensure it is in your "GOPATH/bin" or "PATH"

我們使用go命令行編譯調試器

go get github.com/derekparker/delve/cmd/dlv

將dlv調試器放在GOPATH(工程目錄)的bin目錄下

開始調試

選中要調試的main.go, 點擊F5, 既可以開始調試

調試快捷鍵和Visual Studio系一致

  • F9 切換斷點
  • F10 Step over
  • F11 Step in
  • Shift+F11 Step out

注意點

  • 某些結構體成員無法直接顯示時, 可以直接選中變量名, 添加到監視, 或者右鍵點擊: "調試:求值"

多項目調試

在launch.json中可以添加多組調試入口, 通過調試面板中選中對應的配置開啟不同目標的調試

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "client",
            "type": "go",
            "request": "launch",
            "mode": "debug",
            "remotePath": "",
            "port": 2345,
            "host": "127.0.0.1",
            "program": "${fileDirname}",
            "env": {
                "GOPATH":"D:/Develop/vscodegolang"
            },
            "args": [],
            "showLog": true
        },

        {
            "name": "server",
            "type": "go",
            "request": "launch",
            "mode": "debug",
            "remotePath": "",
            "port": 2345,
            "host": "127.0.0.1",
            "program": "${workspaceRoot}/src/server",
            "env": {
                "GOPATH":"D:/Develop/vscodegolang"
            },
            "args": [],
            "showLog": true
        }
    ]
}

"program"中的"${fileDirname}"是以當前選中文件作為啟動點

更建議使用"program"的"${workspaceRoot}", 以包名作為啟動點的方式進行配置

參考鏈接

https://code.visualstudio.com/Docs/editor/debugging

posted @ 2017-04-20 12:52 戰魂小筑 閱讀(7139) | 評論 (0)編輯 收藏

2017年4月8日 #

相位技術

相位技術大規模出現在魔獸世界WLK版本, 現在應用已經廣泛應用在各種MMORPG游戲中. 下面對相位技術的做法進行簡單歸納匯總

表現分類

副本相位

早期副本的出現, 避免搶怪問題. 所以, 副本其實本身就是一種相位技術. 只不過實現時, 我們一般會將小隊和怪物直接預分配在獨立的一個副本實例中(所以副本原文也是實例的意思)

分線相位

相位技術還沒有正式命名時, 同一個場景, 玩家進到不同的分線看到的玩家不一樣, 也是屬于相位的一種. 當然, 如果是組隊玩家, 服務器默認會分配所有隊伍玩家在同一個線(位面)

真相位

副本相位和分線相位其實都是靜態相位, 一旦進入, 中途不會有切換或者混合查看的過程. 真相位可以在一個場景中,動態切換相位, 相位內和相位外所以不同

我們常見的真相位表現為: 相位中的角色+玩家+隊伍成員

在護送任務時, 還會在上面所見角色中疊加世界中的所有角色, 也就是說, 你和隊伍成員可以看到的角色, 其他人看不到, 其他人也看不到你和你的隊伍成員


為了清晰的簡單的描述, 我為相位創建如下概念與名詞

相位客體

表現為除玩家外的角色(怪物,交互物體與相位可見場景)

私有客體

這是最常見的一種相位內角色

  • 持有變量

    取PhasingID時,為PhasingTargetID

  • 生成規則

    當玩家開啟相位后, 在玩家相位內生成的角色為私有客體.

    此時, 將 PhasingTargetID設置為相位生成者的實例ID

  • 刪除規則

    如果玩家退出相位, 私有客體會存在一段時間或按照需求刪除

公共客體

一般指提前放置在場景中, 世界內不可見, 但是能被同相位玩家可見,且同相位玩家都可以互相可見 比如: 只要接了同一個任務的玩家, 都可以看到的NPC

  • 持有變量

    取PhasingID時,為PublicPhasingID

  • 生成規則

    通過場景編輯器, 放置角色時, 設置其可被觀察到的任務ID

    角色被加載后, 將任務ID設置到StaticPhasingID

  • 刪除規則

    場景刪除, 角色刪除

相位主體

包含玩家與同隊伍玩家

  • 開啟相位后, 可見私有客體+公有客體

  • 隊長視為相位主體, 單人時, 自己為隊長

  • 隊伍其他成員共享隊長的私有相位客體

  • 隊伍其他成員根據自己的PublicPhasingID匹配目標對象的PublicPhasingID時可互相可見

  • 持有變量

相位開啟時, 取PhasingID時, 為角色實例ID

相位關閉時, 取PhasingID時, 為0

PublicPhasingID

可見規則

當兩個角色的PhasingID相等時, 主體與私有客體互相可見

當兩個角色的PublicPhasingID相等時, 主體與公有客體互相可見

可以通過開關設置, 是否在可見的相位客體基礎上, 疊加世界角色(護送任務)

約束

  • 玩家同時只能看見1個相位
posted @ 2017-04-08 14:41 戰魂小筑 閱讀(1642) | 評論 (0)編輯 收藏

2016年12月1日 #

Golang中沒有設計構造函數. 取而代之的, 設計Golang的大師希望你用普通函數去實現構造的任務. 
一直只是覺得這只是體現Golang這門新語言的精簡設計之道, 直到自己實現編譯器后才發現構造函數的設計本身是值得商榷的

我們先看下構造函數的規則

構造函數調用規則

構造參數量: 0表示沒有構造函數, 1表示有構造函數0個參數

本類構造父類構造處理方法
00不處理
10調本類ctor
01調父類ctor
11調本類ctor, 本類ctor調父類ctor
21調本類ctor, 本類ctor調父類ctor
12報錯, 手動調父類ctor
22報錯, 手動調父類ctor

普通函數重載規則

實際只用考慮最典型的一種行為: 實例化子類, 轉為父類調用方法, 這個時候

如果方法是override, 調用的是子類

如果方法是virutal或者不指明, 調用的是父類

整個重載過程, 子類絕對不會隱式調用父類的行為

需要構造函數么?

構造函數的優點

  • 本身屬于一種特殊的成員函數
  • 編譯器幫你自動傳導調用父級

構造函數的缺點

  • 隱式的調用規則
  • 雖然屬于成員函數, 但是與其他成員函數調用規則完全不同, 需要特殊記憶
  • 帶參數的構造函數, 在父類參數多于子類時, 需要引用復雜語法來實現父級構造調用

其實我們對初始化函數的需求只有1條: 自定義

所以, 可以理解Golang不加入構造函數的設計是正確的 
即: 簡單, 清晰, 有規律

posted @ 2016-12-01 10:45 戰魂小筑 閱讀(2408) | 評論 (0)編輯 收藏

2016年11月2日 #

append, map, len不是關鍵字

他們其實還是類庫功能, 都在buildin包里的, 系統默認給你做了個

  1. import(
  2. . "buildin"
  3. )

將buildin的包內容都映射到全局而已, 其實你也可以用自己的包這么做

打印的另一種寫法

想跟腳本一樣調試打印數據么?

  1. println("hello world")

無需包含任何包, 因為它在buildin包里

iota不是黑科技

這是在buildin包里的定義

  1. // iota is a predeclared identifier representing the untyped integer ordinal
  2. // number of the current const specification in a (usually parenthesized)
  3. // const declaration. It is zero-indexed.
  4. const iota = 0 // Untyped int.

其實go是有泛型概念的

想想map和數組的定義 
只是泛型沒有開放給用戶用而已(只許XX放火,不許XX點燈)

map是支持多個key的, 而且很方便

還在為多個key轉id的復雜算法而頭疼么?

  1. type myKey struct{
  2. number int
  3. str string
  4. }
  5. func main(){
  6. t := map[ myKey] int {
  7. myKey{ 2, "world"}: 1,
  8. }
  9. fmt.Println(t[myKey{2, "world"}])
  10. }
  11. 輸出: 1

枚舉是可以轉成string的

默認定義一個枚舉

  1. type MyConst int
  2. const (
  3. MyConst_A MyConst = iota
  4. MyConst_B MyConst = iota
  5. )
  6. func main(){
  7. fmt.Println(MyConst_A)
  8. }

輸出: 0 
如果我們想自動化輸出MyConst_A字符串時 
就需要使用golang的一個工具鏈:golang.org/x/tools/cmd/stringer 
將其下載, 編譯成可執行工具后, 對代碼進行生成 
生成的代碼會多出一個xx_string.go 
里面就是枚舉的String()string 函數

臨時轉換一個接口并調用的方法

  1. type love struct{
  2. }
  3. func (self*love)foo(){
  4. fmt.Println("love")
  5. }
  6. func main(){
  7. var chaos interface{} = new(love)
  8. chaos.(interface{
  9. foo()
  10. }).foo()
  11. }

Golang的receiver實際上就是this的變種實現

  1. func( self*MyStruct) foo( p int ){
  2. }

寫不慣receiver的寫法? 如果這樣改下呢?

  1. func foo( self *MyStruct, p int ){
  2. }

所以為什么說Golang還是一個C語言嘛

關于內存分配…

  • new 傳入Type類型, 返回*Type類型
  • make 可以在分配數組時設置預分配大小, new不可以
  • make 能分配數組,map, 但不能分配結構體和原始類型
  • new 能分配數組, map, 結構體和原始類型等的所有類型
  • &Type等效于new
  • 切片不需要分配內存(make,new), 直接聲明就可以了…

Golang的反射無法通過一個類型名, 創建其實例

C#有Assembly概念, 可以在一個Assembly里搜索, 創建實例

Golang是靜態類型語言, 如果需要, 只能注冊你需要創建的結構體, 然后將注冊好的map用于創建

Golang可以替換Python來進行復雜的工具流程處理

如果你需要跨平臺的工具流程處理, 對Python不熟悉, 可以使用

  1. go run yourcode.go 參數1 參數2

方式來進行工具處理 
覺得慢, 可以編譯成可執行文件

這樣做的好處: 如果有些類庫本身就是go寫的, Python想使用是很麻煩的, 而Golang來寫則輕而易舉

例子: 通過go的protobuf庫, 對一些文件進行處理

Golang可以自動持有方法的接收者實例

  1. type myType struct{
  2. }
  3. func (self*myType) foo( p int){
  4. fmt.Println("hello", p )
  5. }
  6. func main(){
  7. var callback func( int )
  8. ins := new(myType)
  9. callback = ins.foo
  10. callback( 100 )
  11. }

做過lua的C++代碼綁定的童鞋都清楚: lua只支持外部靜態或者全局函數調用 
如果要進行C++類成員函數調用時, 要自己處理this和成員函數 
這種技巧因為早起編譯器的虛表不同平臺實現細節不統一需要專門處理 
后面跨平臺虛表統一后, 類成員函數的調用寫法也是很惡心復雜的 
但是Golang的小白式用法, 直接吊打C++, 甚至C#復雜的delegate

LiteIDE篇: 多開秘籍

  • 找到 菜單->查看->選項->通用->存儲->存儲設置到本地ini文件

  • 關閉LiteIDE

  • 復制LiteIDE整個目錄, 命名文件夾為你的工程名

  • 每個工程所在的LiteIDE的配置將是獨立的, 不會互相干擾

LiteIDE篇: 測試程序也是可以調試的

別以為程序一定要是main開始的才可以調試

Golang的測試程序雖然都是一個個Test開頭的函數,但執行go test時, 還是有main入口

在LiteIDE中, 可以在 調試->開始調試測試程序里進行測試程序調試

LiteIDE篇: 在Windows上可以輸出linux可執行文件

go的工具鏈默認支持交叉編譯 
在LiteIDE中, 可以通過切換輸出平臺, 輸出不同平臺的可執行文件

posted @ 2016-11-02 11:09 戰魂小筑 閱讀(3526) | 評論 (1)編輯 收藏

2016年9月3日 #

Google官方為Golang的調試例子默認使用了gdb

然而, 使用gdb調試go程序會遇到goroutine的各類問題, 因為gdb不懂go

因此, 這里使用delve黑科技來進行Golang的程序調試

純命令行調試方法在網上很容易搜索到, 本文主要以LiteIDE來進行程序調試

關閉編譯器優化

正常go build/install出的go程序是完全優化過的, 強行使用調試器掛接調試時, 某些local變量/lamda表達式捕獲的變量會直接進入寄存器, 無法使用調試器查看

刪掉所有的pkg, 為build或install參數加入關閉編譯器優化的參數 -gcflags "-N -l"

例如:

  1. go install -gcflags "-N -l" svc\gamesvc

delve調試器安裝方法

LiteIDE自帶了gdb, 但是沒有delve調試器, 需要自行安裝, 命令如下

  1. go get github.com/derekparker/delve/cmd/dlv

delve調試器會被放到你的GOPATH/bin下

LiteIDE中的delve調試器配置

選擇調試器

在LiteIDE菜單中選擇 調試->debugger/delve

delve環境變量設置

這個時候, LiteIDE依然找不到delve, 因為它不在環境變量PATH中, 這里無需修改環境變量, 只需要LiteIDE的環境配置

在LiteIDE菜單中選擇 查看->編輯當前環境, 在彈出的文檔中修改

  1. PATH=c:\mingw32\bin;%GOROOT%\bin;%PATH%;c:\your\path\to\delve

去掉PATH前的注釋#, 在%PATH%添加分號, 然后和你到delve調試器的路徑

開始調試

選擇你的工程, 點擊F5, 進入調試模式

調試器顯示變量值

LiteIDE使用delve調試時, 無法在 變量 監視等窗口中正確捕捉delve調試返回數據(因為格式太復雜了…)

沒關系, 我們使用命令行配合顯示即可

LiteIDE控制臺或調試輸出窗口在delve調試時, 實際上是一個標準命令行 
命令如下

  • p 變量名可以查看變量值

  • locals查看局部變量

  • ls可查看當前文件

  • stack查看棧

  • help可以查看各種幫助

調試外部程序

如果你的程序是外部程序, 或者使用go install安裝到GOPATH/bin目錄的程序, 那么使用delve調試器啟動程序時, 可能會碰到啟動路徑錯誤的問題

使用LiteIDE菜單 調試->調試其他應用程序… 填入你要調試程序的路徑以及工作目錄, 可以解決這個問題

posted @ 2016-09-03 18:12 戰魂小筑 閱讀(4547) | 評論 (0)編輯 收藏

僅列出標題  曾道人内部中特图
幸运飞艇6码图 逍遥森林舞会下载 北京pk10历史走势图 万人炸金花手机下载 必赢计划软件安卓版 pk10视频开奖直播 网上打彩票pk10赚水钱 重庆时时彩2期必中计划 一分彩免费计划软件哪个好 三公棋牌软件 至尊牌九作弊器下载 3d复式胆拖投注金额计算表 极速pk10软件 赛车pk10的开奖记录98 重庆欢乐生肖开奖结果 时时包赢公式0369