前回ステップ3まで実行したので、ステップ4以降を実行していきます。
ステップ4:ボールを転がす
迷路の上を転がるボールを作成します。
ball.egg.pzを使用します。
必要なモジュールのインポートと変数の初期値設定を行います。
# 衝突判定
from panda3d.core import CollisionTraverser, CollisionNode
from panda3d.core import CollisionHandlerQueue, CollisionRay
# ボールの速度計算
from panda3d.core import LVector3, LRotationf
ACCEL = 70
MAX_SPEED = 5
MAX_SPEED_SQ = MAX_SPEED ** 2
ボールオブジェクトを設定します。
# ボールオブジェクトの設定
self.ballRoot = render.attachNewNode("ballRoot")
self.ball = loader.loadModel("models/ball")
self.ball.reparentTo(self.ballRoot)
# ボールの衝突検出用のマスク
self.ballSphere = self.ball.find("**/ball")
self.ballSphere.node().setFromCollideMask(BitMask32.bit(0))
self.ballSphere.node().setIntoCollideMask(BitMask32.allOff())
# ボールが床と衝突したときの位置を知る光線
self.ballGroundRay = CollisionRay()
self.ballGroundRay.setOrigin(0, 0, 10)
self.ballGroundRay.setDirection(0, 0, -1)
# 光線の衝突検出用のマスク
self.ballGroundCol = CollisionNode('groundRay')
self.ballGroundCol.addSolid(self.ballGroundRay)
self.ballGroundCol.setFromCollideMask(BitMask32.bit(1))
self.ballGroundCol.setIntoCollideMask(BitMask32.allOff())
self.ballGroundColNp = self.ballRoot.attachNewNode(self.ballGroundCol)
# 衝突ハンドラーにボールと光線を追加
self.cTrav = CollisionTraverser()
self.cHandler = CollisionHandlerQueue()
self.cTrav.addCollider(self.ballSphere, self.cHandler)
self.cTrav.addCollider(self.ballGroundColNp, self.cHandler)
startメソッドの中でボールの初期値を設定します。
def start(self):
# ボールの初期位置の設定
startPos = self.maze.find("**/start").getPos()
self.ballRoot.setPos(startPos)
self.ballV = LVector3(0, 0, 0)
self.accelV = LVector3(0, 0, 0)
ボールが衝突したときの処理の分岐、ボールの速度や向きを設定します。
# ボールが衝突したときの処理の分岐
for i in range(self.cHandler.getNumEntries()):
entry = self.cHandler.getEntry(i)
name = entry.getIntoNode().getName()
if name == "wall_collide":
self.wallCollideHandler(entry)
elif name == "ground_collide":
self.groundCollideHandler(entry)
elif name == "loseTrigger":
self.loseGame(entry)
elif name =="goalCol":
self.winGame(entry)
# ボールの速度や向きの計算
self.ballV += self.accelV * dt * ACCEL
if self.ballV.lengthSquared() > MAX_SPEED_SQ:
self.ballV.normalize()
self.ballV *= MAX_SPEED
self.ballRoot.setPos(self.ballRoot.getPos() + (self.ballV * dt))
prevRot = LRotationf(self.ball.getQuat())
axis = LVector3.up().cross(self.ballV)
newRot = LRotationf(axis, 45.5 * dt * self.ballV.length())
self.ball.setQuat(prevRot * newRot)
groundCollideHandlerメソッドと wallCollideHandlerメソッドを定義します。
それぞれ、ボールが床にぶつかっているときの速度や向きを計算します。
ボールが壁にぶつかっているときの速度や向きを計算します。
# groundCollideHandler関数の定義
def groundCollideHandler(self, colEntry):
newZ = colEntry.getSurfacePoint(render).getZ()
self.ballRoot.setZ(newZ + .4)
norm = colEntry.getSurfaceNormal(render)
accelSide = norm.cross(LVector3.up())
self.accelV = norm.cross(accelSide)
# wallCollideHandler関数の定義
def wallCollideHandler(self, colEntry):
norm = colEntry.getSurfaceNormal(render) * -1 # 壁の法線
curSpeed = self.ballV.length() # 現在の速度
inVec = self.ballV / curSpeed # 進行方向
velAngle = norm.dot(inVec) # 角度
hitDir = colEntry.getSurfacePoint(render) - self.ballRoot.getPos()
hitDir.normalize()
hitAngle = norm.dot(hitDir)
if velAngle > 0 and hitAngle > .995:
reflectVec = (norm * norm.dot(inVec * -1) * 2) + inVec
self.ballV = reflectVec * (curSpeed * (((1 - velAngle) * .5) + .5))
disp = (colEntry.getSurfacePoint(render) -
colEntry.getInteriorPoint(render))
newPos = self.ballRoot.getPos() + disp
self.ballRoot.setPos(newPos)
実行するとボールをマウスで操作するウインドウが表示されます。
穴に落ちると、loseGameメソッドを定義していないので、穴に落ちるとエラーでウインドウが落ちます。
ステップ5:穴に落ちたら開始地点に戻る
ボールが穴に落ちるとスタート地点に戻るようにプログラムを追加します。
ステップ5に必要なモジュールのインストール
# インターバル
from direct.interval.MetaInterval import Sequence, Parallel
from direct.interval.LerpInterval import LerpFunc
from direct.interval.FunctionInterval import Func, Wait
loseGameの定義
# loseGame関数の定義
def loseGame(self, entry):
toPos = entry.getInteriorPoint(render)
taskMgr.remove('rollTask')
# ボールを穴に落とした後最初の位置に移動させて再度スタート
Sequence(
Parallel(
LerpFunc(self.ballRoot.setX, fromData=self.ballRoot.getX(),
toData=toPos.getX(), duration=.1),
LerpFunc(self.ballRoot.setY, fromData=self.ballRoot.getY(),
toData=toPos.getY(), duration=.1),
LerpFunc(self.ballRoot.setZ, fromData=self.ballRoot.getZ(),
toData=self.ballRoot.getZ() - .9, duration=.2)),
Wait(1),
Func(self.start)).start()
ステップ6:ゴールしたらテキストを表示する
ゴールしたら実行するwinGameメソッドを定義します。
# ゴールの衝突判定
from panda3d.core import CollisionBox,Point3
# winGame関数の定義
def winGame(self, entry):
self.title.setText("ゴール!!")
toPos = entry.getInteriorPoint(render)
self.ballRoot.hide()
taskMgr.remove('rollTask')
ボールがゴールすると、右下にゴールと表示されます。
ステップ7:ボールに光を反射させる
最後にボールに光を追加します。
# ライトと素材
from panda3d.core import AmbientLight, DirectionalLight
from panda3d.core import Material
# ライトの設定
ambientLight = AmbientLight("ambientLight")
directionalLight = DirectionalLight("directionalLight")
directionalLight.setDirection(LVector3(0, 0, -1))
self.ballRoot.setLight(render.attachNewNode(ambientLight))
self.ballRoot.setLight(render.attachNewNode(directionalLight))
# 光沢のある素材の設定
m = Material()
m.setShininess(96)
self.ball.setMaterial(m, 1)
ボールに光が入りました。
光る前のボールはこんな感じでした。