newgamefolder/volume1/player.py
from panda3d.core import Vec3
from direct.task import Task
from pandac.PandaModules import VBase3
class AlliedFlanker():
def __init__(self,loader,scenegraph,taskMgr):
self.loader = loader
self.render = scenegraph
self.taskMgr = taskMgr
self.maxspeed = 200.0
self.speed = 0
self.startPos = Vec3(200,200,100)
self.startHpr = Vec3(225,0,0)
self.player = self.loader.loadModel("volume1/models/alliedflanker")
self.player.setScale(.2,.2,.2)
self.player.reparentTo(self.render)
self.reset()
self.calculate()
# load the explosion ring
self.explosionModel = self.loader.loadModel('volume1/models/explosion')
self.explosionModel.reparentTo(self.render)
self.explosionModel.setScale(0.0)
self.explosionModel.setLightOff()
# only one explosion at a time:
self.exploding = False
self.maxdistance = 400 # default in case below never called
def setMaxHeight(self,distance):
""" Maximum flying altitude """
self.maxdistance = distance
def reset(self):
""" Back to start position, orientation, speed """
self.player.show()
self.player.setPos(self.startPos)
self.player.setHpr(self.startHpr)
self.speed = self.maxspeed/5
def calculate(self):
""" Should be called every frame.
It calculates how the player should move (position and orientation) """
self.factor = globalClock.getDt()
self.scalefactor = self.factor *self.speed
self.climbfactor = self.scalefactor * 0.3
self.bankfactor = self.scalefactor
self.speedfactor = self.scalefactor * 2.9
self.gravityfactor = ((self.maxspeed-self.speed)/100.0)*1.35
# note the collision enhancements
def climb(self):
if (self.speed > 0):
# faster you go, quicker you climb
self.player.setFluidZ(self.player.getZ()+self.climbfactor)
self.player.setR(self.player.getR()+(0.5*self.climbfactor))
# quickest return: (avoids uncoil/unwind)
if (self.player.getR() >= 180):
self.player.setR(-180)
def dive(self):
if (self.speed > 0):
self.player.setFluidZ(self.player.getZ()-self.climbfactor)
self.player.setR(self.player.getR()-(0.5*self.climbfactor))
# quickest return:
if (self.player.getR() <= -180):
self.player.setR(180)
def unwindVertical(self):
""" Used to return the aircraft to its default orientation, it would
look odd after a climb/fall if the plane stayed pointed up/down! """
if (self.player.getR() > 0):
self.player.setR(self.player.getR()-(self.climbfactor+0.1))
if (self.player.getR() < 0):
self.player.setR(0) # avoid jitter
elif (self.player.getR() < 0):
self.player.setR(self.player.getR()+(self.climbfactor+0.1))
if (self.player.getR() > 0):
self.player.setR(0)
def bankLeft(self):
if (self.speed > 0):
self.player.setH(self.player.getH()+self.bankfactor)
self.player.setP(self.player.getP()+self.bankfactor)
# quickest return:
if (self.player.getP() >= 180):
self.player.setP(-180)
def bankRight(self):
if (self.speed > 0):
self.player.setH(self.player.getH()-self.bankfactor)
self.player.setP(self.player.getP()-self.bankfactor)
if (self.player.getP() <= -180):
self.player.setP(180)
def unwindHorizontal(self):
""" Used to return the aircraft to its default orientation,
it would look odd after banking if the plane stayed tilted! """
if (self.player.getP() > 0):
self.player.setP(self.player.getP()-(self.bankfactor+0.1))
if (self.player.getP() < 0):
self.player.setP(0)
elif (self.player.getP() < 0):
self.player.setP(self.player.getP()+(self.bankfactor+0.1))
if (self.player.getP() > 0):
self.player.setP(0)
def move(self,boundingBox):
# move forwards - our X/Y is inverted, see the issue
valid = True
if self.exploding == False:
self.player.setFluidX(self.player,-self.speedfactor)
valid = self.__inBounds(boundingBox)
self.player.setFluidZ(self.player,-self.gravityfactor)
return valid
def __inBounds(self,boundingBox):
if (self.player.getZ() > self.maxdistance):
self.player.setZ(self.maxdistance)
elif (self.player.getZ() < 0):
self.player.setZ(0)
# and now the X/Y world boundaries:
valid = True
if (self.player.getX() < 0):
self.player.setX(0)
valid = False
elif (self.player.getX() > boundingBox):
self.player.setX(boundingBox)
valid = False
if (self.player.getY() < 0):
self.player.setY(0)
valid = False
elif (self.player.getY() > boundingBox):
self.player.setY(boundingBox)
valid = False
return valid
def accelerate(self):
self.speed += 1
if (self.speed > self.maxspeed):
self.speed = self.maxspeed
def brake(self):
self.speed -= 1
if (self.speed < 0.0):
self.speed = 0.0
def die(self):
if (self.exploding == False):
self.player.setZ(self.player.getZ()+10)
self.exploding = True
self.explosionModel.setPosHpr(Vec3(self.player.getX(),self.player.getY(), \
self.player.getZ()),Vec3( self.player.getH(),0,0))
self.player.hide()
self.taskMgr.add(self.__expandExplosion,'expandExplosion')
def __expandExplosion(self,Task):
# expand the explosion ring each frame until a certain size
if self.explosionModel.getScale( ) < VBase3( 60.0, 60.0, 60.0 ):
scale = self.explosionModel.getScale()
scale = scale + VBase3( self.factor*40, self.factor*40, self.factor*40 )
self.explosionModel.setScale(scale)
return Task.cont
else:
self.explosionModel.setScale(0)
self.exploding = False
self.reset()
# and it stops the task
def __speedAsPercentage(self):
# needed for camera trick
return (self.speed/self.maxspeed)
def attach(self,node):
return self.player.attachNewNode(node)
# See Issue 5 video for what this does:
def lookAtMe(self,camera):
percent = (self.__speedAsPercentage())*2
camera.setPos(self.player, 9+(20*percent), 0, 0)
# compensate for model problem (see Issue 3)
camera.setH(self.player.getH()+90)
camera.setP(self.player.getR())
camera.setR(self.player.getP())
# final adjustments
camera.setZ(self.player,3)
camera.setY(self.player,1.5)
Essentially, everything player related (including the model) is in the new class above. There are some changes to watch out for though. First, notice the use of ‘setFluid’ methods in place of ‘set’. These methods are useful when dealing with fast moving objects. You may have noticed the occasional collision blip in Issue 4. This happens when an object is moving so fast and so far frame by frame that it skips over the collision point. The player might be slightly above the terrain while, in the next frame, slightly below. Thus missing the collision point. Instead of simply moving an object (as setPos/X/Y/Z do), setFluidPos/X/Y/Z methods ‘slide’ it from its old position to its new, guaranteeing no collisions are missed. There is another piece of the puzzle to make this work which we will discuss when we reach game.py below.
We have also doubled the maximum speed the player can fly at – for no reason other than for fun! Notice also that we have comments in the code by using triple-quotes (“”) instead of the usual hash (#). Triple quote comments can run over multiple lines whereas hash comments cannot. They also help with automatic code documentation – something for a future Issue but note it is what we used to create the design diagram earlier in this Issue.
Lastly, the ‘lookAtMe’ method sets a camera correctly with regards to the player. It’s a replacement to our old ‘updateCamera’ (essentially) but we have added a bit more magic in the camera trickery. The camera and player will both now roll during play and our new relative positioning gives some nice effects. The video below shows the new camera setup in action.