本系列文章用以描述如何建構遊戲人工智能這一課題,它是我建立的開源項目《遊戲人工智能示範》( http://code.google.com/p/aidemo)的配套文檔,随着項目的推進而逐漸完善。
項目QQ群:5657582 遊戲人工智能示範(1)追逐 網易廣州 賴勇浩( http://blog.csdn.net/lanphaday ) 邏輯 FPS 接觸過遊戲程式設計的朋友都知道在每一幀重新整理圖形界面的時候執行一次邏輯代碼顯然是行不通的。比如在一個《俄羅斯方塊》遊戲中,程式設定掉落中的每10幀下落一個像素點的話,那每在配置好的機器上方塊可能瞬間就已經掉到最低端,玩家根本反應不過來。這時我們可以引入邏輯FPS,即設定1秒内運作邏輯的次數,如30次。這時隻要機器能夠一秒執行30幀邏輯,那方塊肯定掉落了3個像素,進而同步了不同的機器。如果一台很老舊的機器,不能達到 30 FPS呢?那就要使用另一套解決方案了,暫時我們還用不着,就先不讨論這個。 在 aidemo 中, 邏輯 FPS 設定為 30,這在目前主流機器上運作已有的 aidemo 是肯定可以達到的。邏輯 FPS 實作在 fps.py 檔案中,如下: class FixFPS(object): def __init__(self, fps): self.fps = fps self.span = 1.0 / fps self.bgn = time() def GetFps(self): return self.fps def NeedUpdate(self): end = time() span = end - self.bgn if span > self.span: self.fps = int(1.0 // span) self.bgn = end return True return False FixFPS.NeedUpdate() 方法用在這裡: class MyApp(GameApp): def Logic(self): if not logic_fps.NeedUpdate(): return game.Logic() 這樣就能保證遊戲邏輯的執行頻率了。 dc_defend 如果我們用 WIN32 或者 MFC 開發過 GDI 或 GDI+ 程式,我們一定對 SetObject()、GetOjbect()之類的函數非常熟悉。為了不“污染” DC ,我們需要在配置 DC 時儲存 DC 原有的設定,然後在使用完 DC 後再恢複過來。使用 wxWidgets 的時候也是如此,使用 wxPython 也不例外,不過借助 Python 的 decorator 文法,我們可以有效地提升程式設計體驗:1)不用再寫那麼多 Set/Get 方法;2)提升執行效率。利用 decorator 編寫的 dc_defend: def dc_defend(getters, setters): def func_decorator(func): def defender(self, obj, *a, **k): values = [getter(obj) for getter in getters] ret = func(self, obj, *a, **k) for setter, arg in zip(setters, values): setter(obj, arg) return ret return defender return func_decorator 很簡單的帶參數的 decorator 實作,對 decorator 不了解的朋友可以去看一下 Python Manual,裡面有詳解。dc_defend,顧名思義就是“保護DC”的了,它的用法示例: @dc_defend( / (wx.BufferedPaintDC.GetTextForeground,), / (wx.BufferedPaintDC.SetTextForeground,)) def DrawFps(self, dc): dc.SetTextForeground(wx.RED) dc.DrawText('FPS: %d/%d'%(self.fps.GetFps(), logic_fps.GetFps()), 10, 10) 因為在 DrawFps() 函數裡調用了DC.SetTextForeground() 方法來設定字型顔色,是以在 dc_defend 的參數裡傳入 Get/Set 字型顔色的函數,由 dc_defend 自動在調用 DrawFps() 之前儲存字型顔色的原有配置,并在調用之後恢複。 引入 Game類 在 demo_1 中,我們要實作一個垂直俯視角度的遊戲,遊戲很簡單:隻有一個不停地追逐滑鼠的 Ball(怪物的抽象)。demo 雖小,但已經有一個實體(圓球)要管理,還要處理滑鼠輸入(以獲得圓球追逐的目标),要繪制實體,還要執行實體的邏輯。這麼多功能,如果不獨立出來,就很難保證正交的設計了。是以有必要引入Game類: class Game(object): def __init__(self): # … 在這裡初始化遊戲,如執行個體化 Ball def Draw(self, dc): # … 在這裡繪制 Ball def Logic(self): # … 在這裡執行 Ball 的邏輯(趨近滑鼠) def OnMotion(self, evt): # … 在這裡告訴 Ball 滑鼠位置已經改變 具體的Game 類實作請參見 demo_1 目錄下的 game.py 檔案。 顯而易見的,App 需要知道 Game 的執行個體以在執行遊戲邏輯的時候調用 Game.Logic() 以執行遊戲邏輯,而 Frame 也需要知道 Game 的執行個體以委托 Game.Draw() 繪制遊戲界面。因為整個遊戲是如此簡單,是以 Game 的執行個體完全可以定義為全局變量,demo.py 的内容大體如下: game = Game() # 全局的 Game 執行個體 class MyFrame(GameFrame): def OnMotion(self, evt): game.OnMotion(evt) # 傳遞滑鼠消息 def DoPaint(self, dc): game.Draw(dc) # 繪制遊戲 class MyApp(GameApp): def Logic(self): if not logic_fps.NeedUpdate(): return game.Logic() # 執行遊戲邏輯 實體與動體 按面向對象的程式設計思想,每一件事物都有一個對應的類。aidemo 的實作是相當面向對象的,是以 aidemo 中在遊戲中可見與不可見事物都是一個實體(Entity),而部分在遊戲中會發生空間位置移動的稱為動體(Motile)。所有的實體都有擁有遊戲中唯一ID。關于實體和動體的實作,請見 entity.py 檔案。 追逐 要實作追逐,有三個要點: 1) 需要一個動體來執行追逐的動作 2) 這個動體能夠判斷是否已經追上目标 3) 這個動體必須能夠覺察目标的位置變化 class Ball(Motile): def __init__(self, *args, **kw): super(Ball, self).__init__(*args, **kw) self.SetBoxes(((collision.InCircle, (12,)),)) def Logic(self): self.MoveToTarget() def MoveToTarget(self): if self.IsArrivedTarget(): return # 計算自己與目标之間的距離 dist = self.target - self.pos # 歸一化 dist.Normalize() # 向目标前進 self.pos += vector2d.Vector2D( / dist.x * self.velocity.x, / dist.y * self.velocity.y) 上圖是“不停地追逐滑鼠的Ball”的主要實作代碼。其中 MoveToTarget() 對應着三個要點中的第一點。 IsArrivedTarget() 是父類 Motile 實作的方法,用以判斷是否已經追上目标(第二點): class Motile(Entity): def IsArrivedTarget(self): for box, args in self.boxes: if box(self.pos, self.target, *args): return True return False 在IsArrivedTarget()是引用到的 self.boxes 來自 Ball.__init__() 的這一行調用: self.SetBoxes(((collision.InCircle, (12,)),)) 可見所謂的 boxes (包圍盒)其實是一個回調函數及其部分參數(以後會用函數式程式設計理念來重構的 J)。 現在要完成的是第三個要點:這個動體必須能夠覺察目标的位置變化。這個任務交給 Game 來完成,請參見上兩節“引入 Game 類”裡的 Game 示意代碼。實作代碼如下: class Game(object): def OnMotion(self, evt): self.ball.SetTarget( / Vector2D(float(evt.m_x), float(evt.m_y))) 完成這些代碼,我們的 demo 就就成了。 截圖
預告 在遊戲中,我們可以看到無數怪物、NPC甚至小蟲小魚不停地按照一定的路線循環運動,這就是有着 PathFollow 能力的智能體,接下來,讓我們來實作它們。敬請期待。