Roaming-Ralph演示一個在場景(地形)中行走的角色,以第三視角觀看。我嘗試將原本的範例程式改寫得更容易理解。這裡傾向表達清楚,並沒有對效能優化多做調整。原始檔可由此下載。
操作方式:方向鍵左右表示左右轉,方向鍵上表示前進。
此範例將會用到:
- 起始panda3d
- 接收鍵盤按鍵事件
- 3d模型的位置移動與面向設定
- 碰撞偵測的設定
- 碰裝物件的擷取
- 攝影機位置與面向設定
在這個範例中,共有四種物件。
1. 控制器 -- 接收鍵盤事件的的控制器。
2. 角色 -- 根據鍵盤命令移動的角色。
3. 場景 -- 場景提供角色與地面的碰撞偵測,讓角色能走在地面上,並且不能進入障礙物如樹,石頭中。
4. 攝影機 -- 跟著角色移動的攝影機。
最後用一個世界物件,將上述四種物件裝起來。
因此我將檔案分成--
main.py -- 執行起始。
_define.py -- 一些定義,主要是控制器的命令定義。
World.py -- 世界類別。
Controller.py -- 控制器類別。
Avatar.py -- 角色類別。
Environment.py -- 場景類別。
CameraController.py -- 攝影機類別。
以下簡單描述各檔案的內容。
世界類別
main.py檔案很單純的僅是產生一個世界類別,然後開始panda3d的main loop。
import direct.directbase.DirectStart from World import World w = World() run()
世界類別,World.py。先載入角色與場景的3d模型(函數_model_load中,呼叫_avatar_model_get與 _environment_model_get函數),並賦予給角色物件與場景物件。設定好控制器(_controller),角色(_avatar),場景(_environment),攝影機(_camera_ctrl)等物件後。將每個frame更新的函數(_main_loop)掛上 taskMgr。
每個frame的更新函數(_main_loop)會依序呼叫角色的更新函數,場景的更新函數,最後是攝影機更新函數。
角色更新函數主要是從控制器物件獲取目前命令狀態,並依此做角色的3d模型位置的y值與面向更新。
場景更新函數主要是讓角色能站在地面上,依據碰撞偵測的反應更新角色模型位置的z值。
最後,攝影機取得角色的位置,並依此更新攝影機的位置與面向。
角色更新與場景更新函數可這麼看:角色更新函數依據輸入命令與自身狀態,給出理論的角色行為。場景更新函數依據角色與場景間的關係,修正理論的角色行為,得到實際上的角色行為。如此範例中,角色更新函數依據輸入命令前進,但場景更新函數因為角色將會進入障礙物而不讓角色前進,修正角色的位置為改變前的位置。
from direct.actor.Actor import Actor
from Controller import Controller
from Avatar import Avatar
from Environment import Environment
from CameraController import CameraController
def _environment_model_get():
environment = loader.loadModel("models/world")
environment.reparentTo(render)
environment.setPos(0,0,0)
return environment
def _avatar_model_get():
avatar = Actor("models/ralph",
{"run":"models/ralph-run",
"walk":"models/ralph-walk"})
avatar.reparentTo(render)
avatar.setScale(.2)
return avatar
class World:
def __init__(self):
self._model_load()
self._controller = Controller()
self._avatar = Avatar(self._avatar_model, self._controller)
self._environment = Environment(self._environment_model, self._avatar)
self._camera_ctrl = CameraController(self._avatar)
self._main_loop_start()
def _model_load(self):
self._environment_model = _environment_model_get()
self._avatar_model = _avatar_model_get()
avatar_start_pos = self._environment_model.find("**/start_point").getPos()
self._avatar_model.setPos(avatar_start_pos)
def _main_loop_start(self):
taskMgr.add(self._main_loop,"Main Loop")
def _main_loop(self, task):
self._avatar.update()
self._environment.update()
self._camera_ctrl.update()
return task.cont
控制器類別
控制器類別,Controller.py。內含目前命令狀態(key_state)。在接收鍵盤事件後,更新目前命令狀態值(函數setKey)。在這個範例中僅有三種命令--左轉,右轉,與前進。
鍵盤事件僅包含按鍵按下與按鍵放開,例如"arrow_left"表示左箭頭按鍵按下的事件,"arrow_left-up"表示左箭頭按鍵放開的事件。因此,按鍵持續按著的這個動作就由key_state保存,在按下後設定對應的key_state命令狀態為1,與放開後設定對應的key_state命令狀態為0。
from direct.showbase.DirectObject import DirectObject
from _define import _LEFT, _RIGHT, _FORWARD
class Controller(DirectObject):
def __init__(self):
self.key_state = {_LEFT:0, _RIGHT:0, _FORWARD:0}
self._keyborad_event_setup()
def setKey(self, key, value):
self.key_state[key] = value
def _keyborad_event_setup(self):
self.accept("arrow_left", self.setKey, [_LEFT,1])
self.accept("arrow_right", self.setKey, [_RIGHT,1])
self.accept("arrow_up", self.setKey, [_FORWARD,1])
self.accept("arrow_left-up", self.setKey, [_LEFT,0])
self.accept("arrow_right-up", self.setKey, [_RIGHT,0])
self.accept("arrow_up-up", self.setKey, [_FORWARD,0])
def state_get(self):
ret = self.key_state
return ret
角色類別
角色類別,Avatar.py。在每個frame時,從控制器取得目前命令狀態(函數_cmd_get),藉此更新角色3d模型的y值與面向(函數_pos_update),並更新角色3d模型的動畫狀態(函數_animation_update)。函數pos_recover用來恢復角色3d模型改變前的位置,於之後的場景類別中使用。
from _define import _LEFT, _RIGHT, _FORWARD
class Avatar:
def __init__(self, model, controller):
self.model = model
self._isMoving = False
self._cmd_get = controller.state_get
def update(self):
cmd_state = self._cmd_get()
cmd_forward = cmd_state[_FORWARD]
cmd_left = cmd_state[_LEFT] - cmd_state[_RIGHT]
self._pos_update(cmd_forward, cmd_left)
self._animation_update(cmd_forward, cmd_left)
def _pos_update(self, cmd_forward, cmd_left):
self._old_pos = self.model.getPos()
if cmd_forward:
self.model.setY(self.model, -25 * globalClock.getDt())
if cmd_left:
self.model.setH(self.model.getH() + (cmd_left * 300 * globalClock.getDt()))
def _animation_update(self, cmd_forward, cmd_left):
if (cmd_forward or cmd_left):
if (not self._isMoving):
self.model.loop("run")
self._isMoving = True
else:
if self._isMoving:
self.model.stop()
self.model.pose("walk",5)
self._isMoving = False
def pos_recover(self):
self.model.setPos(self._old_pos)
場景類別
場景類別,Environment.py。設定角色的碰撞(函數_collision_setup),於每個frame更新時檢查碰撞事件(函數 _collision_object_list_get),並藉此更新角色3d模型位置的z值(函數_collision_handle),讓角色能隨著地形起伏改變高度,達到"站在地上"的視覺效果。當角色3d模型"進入"場景中的石頭或樹木等障礙物時,恢復到角色移動前的位置(角色類別的函數 pos_recover),如此角色就不會穿越障礙物了。
from panda3d.core import CollisionTraverser,CollisionNode
from panda3d.core import CollisionHandlerQueue,CollisionRay
from panda3d.core import BitMask32
class Environment:
def __init__(self, model, avatar):
self.model = model
self._avatar = avatar
self._avatar_model = avatar.model
base.win.setClearColor((0,0,0,1))
self._collision_setup()
def _collision_setup(self):
self.cTrav = CollisionTraverser()
self._avatar_GroundRay = CollisionRay()
self._avatar_GroundRay.setOrigin(0,0,1000)
self._avatar_GroundRay.setDirection(0,0,-1)
self._avatar_GroundCol = CollisionNode('_avatar_Ray')
self._avatar_GroundCol.addSolid(self._avatar_GroundRay)
self._avatar_GroundCol.setFromCollideMask(BitMask32.bit(0))
self._avatar_GroundCol.setIntoCollideMask(BitMask32.allOff())
self._avatar_GroundColNp = self._avatar_model.attachNewNode(self._avatar_GroundCol)
self._avatar_GroundHandler = CollisionHandlerQueue()
self.cTrav.addCollider(self._avatar_GroundColNp, self._avatar_GroundHandler)
def update(self):
entries = self._collision_object_list_get()
self._collision_handle(entries)
def _collision_object_list_get(self):
self.cTrav.traverse(render)
entries = [self._avatar_GroundHandler.getEntry(i)
for i in range(self._avatar_GroundHandler.getNumEntries())]
entries.sort(key = lambda x: x.getSurfacePoint(render).getZ())
return entries
def _collision_handle(self, obj_list):
if (len(obj_list)>0) and (obj_list[0].getIntoNode().getName() == "terrain"):
self._avatar_model.setZ(obj_list[0].getSurfacePoint(render).getZ())
else:
self._avatar.pos_recover()
攝影機類別
攝影機類別,CameraController.py。這裡只是很簡單的設定攝影機的位置與面向,讓角色能以第三人視角表現。
class CameraController:
def __init__(self, avatar):
self._avatar_model = avatar.model
base.disableMouse()
def update(self):
base.camera.setPos(self._avatar_model.getX(),
self._avatar_model.getY()+10,
self._avatar_model.getZ()+2)
base.camera.lookAt(self._avatar_model)
沒有留言:
張貼留言