iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 8
0
自我挑戰組

30天遊戲原型開發系列 第 9

Day8 中階LPC學習筆記 ch1-7

//趁今天一次把進度衝完,大綱:driver運作機制、進階資料類型、預編譯指令、字串處理、深入繼承、除錯教學
//之後研究方向: 區域撰寫、戰鬥、心跳、互動

中階LPC

ch1 簡介
主要障礙:mudlib不合,建議往上找大神改善
注意:不教shadow、不教系統安全和OOP

ch2 LPMud driver
2.1 簡短回顧

  1. 物件載入時,create() or reset()
  2. 物件到了固定周期,reset()
  3. 活物件遇到其他物件,init(), add_action()
  4. 外部函數 eFun

2.2 週期cycle
driver週期內執行工作

  1. 接受連線
  2. 解析LPC物件
  3. 接受使用者輸入命令
  4. 呼叫所有物件的heart_beat()
  5. 執行延遲呼叫(call out)

2.3 使用者命令
命令表: 活物件名稱、給予命令者物件、命令對應函數
輸入命令的人=>給予命令者(通常是玩家this_player())

改變環境時,物件會清空環境中的其他物件列表和環境中的可用命令,然後呼叫新環境的init()
//沒有示意圖,光看文字說明不太懂,需要搭配實際程式碼
//控制權轉移部分,直接看程式碼比較快,作者…

"north" 在 room 的 set_exits()中定義
"tell" 在 player 的 cmd_hook()中處理對應關係

命令執行過程:
north
"north" 轉換為 room_obj->do_move(0)
this_player()->move_player()
move_object()

tell descartes
"descartes"作為參數傳給tell()

2.4 set_heart_beat(), call_out()
set_heart_beat(1) 加入心跳物件列表中
set_heart_beat(0) 從心跳物件列表移除

心跳發生場景:治療、怪物、戰鬥

玩家的heart_beat()

  • 變老
  • 治癒自己
  • 尋找周圍攻擊、被攻擊的人或物件,找到後自動攻擊
  • 其他需要每秒發生的事情

注意:大量的心跳物件是MUD效能低落的原因

call_out()算是在遊戲中實作的簡單排程函數
只是名字取不好,取 call_after 或 call_later 會感覺好一點

remove_call_out()能夠移除已經加入排程的函數,但如果出現同名的排程函數,程式就會出問題

如果要弄成循環呼叫的話,在被呼叫的函數內加call_out()形成遞迴
//幹嘛不直接實作一個週期呼叫的列表就好= =

心跳和延遲指令都能完成目標,但對系統的消耗不太一樣
經驗法則:十秒以上用延遲,十秒以下用心跳

ch3 複雜資料類型
3.1 簡單資料類型 int, string, object, void
object是奇怪的特例 //工三小?

3.2 NULL在認知上造成的困擾
NULL和0常常是等價的關係,但實際上它們是不該混用的
//其他程式語言也存在一樣的問題,不要混用就好了

3.3 陣列 Array
//當時的C應該沒有string這個型別
//陣列採用({})分隔,跟原本陣列的[]區隔

3.4 使用陣列
member_array(x,ary) 回傳 x 在 ary 中出現的位置

arr = ({ str1 }) + ({ str2 }); //糟糕例子
arr = ({ str1, str2 }) //好例子

注意:單一元素加入陣列會出現類別錯誤,需要將元素轉換成「單一元素的陣列」才行

3.5 映射 Mapping
說明一些array不好發揮的場景,這時候透過 mapping 就比較好處理
//這種資料類別現在已經很廣泛了,在當時的C還是很先進的語言實作
//作者又舉了很糟糕的例子,強烈建議不要使用Array的加減法運算子,非常容易造成誤解

([ "gold":10, "silver":20 ])
概念上跟今天json的object是相反的,採用([])作區隔

map_delete(money, type); //MudOS
money = m_delete(money, type); //LPMud 3.* 衍生版本
m_delete(money, type); //LPMud 3.* 衍生版本

由此可見driver並沒有統一標準

map = ([]);
map = allocate_mapping(10) //map也有大小限制
map = ([ "gold": 20, "silver": 15 ]);

關於array和mapping常見錯誤:

  1. Indexing on illegal type.
  2. Illegal index.
  3. Bad argument 1 to (+ += - -=)

原因:
未正確初始化 => 1.3.
索引不存在 => 2.
單一元素對陣列操錯 => 3.

//以現今的語言標準實作的話,痛苦指數應該會少很多,不用在像以前那樣痛苦了

ch4 預編譯LPC
作者再次強調LPC沒有編譯過程,預編譯是一種假象,它只是在LPC解析前會做的動作而已
實際語法跟C的編譯器巨集差不多
#define #undefine #include #ifdef #ifndef #if #elseif #else #endif #pragma

提醒: 預編譯指令只有單行,換行記得在結尾加\。跟C一樣

ch5 字串處理
字串是經過driver抽象化處理的簡單資料型態
//一切都是輕薄的假象
strlen(),回傳字串長度
'a' 是字元,"a" 是字串,兩者不同

範圍運算子 (range operator),算是弱化版的python切片
語法範例:str[0..1]

LPC沒有char類別,最基本的類別就是string

sscanf(str, format, arg...) 從 str 中按照 format 解析資料,將結果存到 arg 中
%*s,*代表丟棄運算子,解析但不採用
//題外話:範例中的參數沒有用&,代表這些arg都是採用指標的方式運作的
sscanf() 回傳值代表成功解析的變數數量

其他推薦: explode() implode() replace_string() sprintf()

ch6 再談繼承機制

6.2 複製和繼承
所有遊戲物件都會有一個主本(master copy),一般是拿來複製用,玩家不會跟它發生互動
概念有點像本金利息,主本放著,讓它去生利息(遊戲物件)

慣例是寫區域用繼承,寫怪物用複製。

ob = new("/std/monster");
檢查主本是否存在?
存在,複製它
不存在,建立主本,再複製它

如果是繼承過的怪物,就需要同時檢查"/std/monster"和"/wizards/descartes/my_monster"
作者建議使用標準怪物物件的原因應該是為了節省記憶體
因為系統運作安全的關係,開發者不可能直接修改原始內容,所有的東西都是複製品
只有在大量使用複製的情況下,繼承才會發揮功效。個人經驗:數量超過3就可以使用繼承了

OOP這時候應該還在萌芽階段,而後發展成的class-based和prototype-based兩條路線
class要求所有的物件都要先經過定義;prototype的運作很像目前LPMUD的樣子,建立物件之後再衍生出去

6.3 繼承
C基本物件
A是繼承C的基本活物件
B是繼承C的基本武器
感覺C有很大可能變成「超級物件」,影響系統效能

6.4 標籤
用來修飾函數、變數在繼承時候的可見度
public 公有
private 私有,能夠防止其他物件取用,但防不了call_other()
static 靜態
nomask 不可覆蓋,防止繼承物件重寫原始函式

注意:標籤支援度在某些driver存在差異

靜態標籤只防止外部的外界呼叫. 另外, this_object()->foo() 就算有靜態標籤, 也視為內部呼叫.
一個靜態變數無法經由save_object() 外部函式儲存, 也無法由 restore_object() 還原
//有點難懂,算了…

這部分的內容跟mudlib開發有比較大的關係,撰寫區域通常不用注意這些細節

一個不可遮蓋函式無法經由繼承或投影 (shadow) 僭越之. 投影是一種反向繼承,將在高級 LPC 課本中詳細介紹
//高級 LPC 斷更,殘念
//僭越(override),目前都是翻重寫,年代感很重的文章…

ch7 除錯
常見的錯誤:

  • 編譯時錯誤 compile time error
    語法錯誤、沒有宣告的函式或變數
  • 執行時錯誤 run time error
    不正確的值、未支援的資料運算
  • 故障程式碼 malfunctioning code
    未按照預期運作的程式碼,通常是誤解函式運作造成

不確定目前有沒有改善開發/測試環境?
畢竟LPC上面全部都是動態的東西,遇到錯誤只能從log判斷,但不知道能不能把當時記憶體狀態dump下來?

執行錯誤通常跟NULL指標有關
最後一種錯誤只能從頭下去找,一步一步地追蹤

以現在來說,除錯技術可以把整個程式運行變成逐步執行,也能插入中斷點
也有區分層級的logger把資訊詳細記錄下來,但這些要從driver改善才行

異常處理:

  • 使用asset語句,直接報錯死掉
  • 無視,繼續往下走(後面遲早會死)
  • 嘗試判斷原因,修正之

上一篇
Day8 基礎LPC學習筆記 ch3-8
下一篇
Day9 mudlib研究: 區域
系列文
30天遊戲原型開發31

尚未有邦友留言

立即登入留言