So, I’ve been around the panda block a bit, and after doing a lot of reading, and bashing my head against the wall I have a stable framework for a single-player FPS. I had hoped to make it network-able but networking is far beyond the scope of my python understanding. So, I thought I would share what I had with the community, for new player benefit, and for mine, should anyone have constructive criticism.
I know, it’s not pretty, I won’t pretend to be an artist, but, it’s a start and I hope something I can build off.
Here is the svn, should anyone care to snatch it that way.
dc-fps.googlecode.com/svn/trunk/
And for those who would like to just see the code:
## -*- coding: utf-8 -*-
from direct.directbase import DirectStart
from direct.showbase.DirectObject import DirectObject
from direct.gui.OnscreenImage import OnscreenImage
from math import sqrt
import sys, time
from pandac.PandaModules import *
from direct.gui.DirectGui import *
class Main(DirectObject):
def __init__(self):
self.FLOORMASK=BitMask32.bit(0)
self.WALLMASK=BitMask32.bit(1)
self.accept("escape", sys.exit)
#Make some light to see
self.initLights()
#Create the world
self.colCount = 0
self.world = loader.loadModel("./models/World.egg")
self.world.reparentTo(render)
self.world.setPos(0,0,0)
self.world.setScale(2)
#Ground Collision
cGround = self.world.find("**/ground_col")
cGround.node().setIntoCollideMask(self.FLOORMASK)
#Wall collision
cWall = self.world.find("**/wall_col")
cWall.node().setIntoCollideMask(self.WALLMASK)
#Set up the collision handlers
self.initCollision()
#Create the player model
self.player = Player(self.FLOORMASK, self.WALLMASK)
#Muck the mouse countrols and stuff
props = WindowProperties()
props.setCursorHidden(True)
base.win.requestProperties(props)
base.disableMouse()
self.player.player.setH(base.camera.getH())
base.camera.reparentTo(self.player.player)
base.camera.setPos(0,0,0)
self.player.player.setPos(0,0,10)
def initCollision(self):
"""Setup the collision pushers and traverser"""
#Generic traverser
base.cTrav = CollisionTraverser('Collision Traverser')
base.cTrav.setRespectPrevTransform(True)
#Pusher Handler for walls
base.cPush = PhysicsCollisionHandler()
#Physics
base.enableParticles()
#Gravity
self.gravityFN=ForceNode('world-forces')
self.gravityFNP=render.attachNewNode(self.gravityFN)
self.gravityForce=LinearVectorForce(0,0,-9.81)
self.gravityFN.addForce(self.gravityForce)
#Attach it to the global physics manager.
base.physicsMgr.addLinearForce(self.gravityForce)
def initLights(self): #Sets up some default lighting
ambientLight = AmbientLight( "ambientLight" )
ambientLight.setColor( Vec4(.4, .4, .35, 1) )
directionalLight = DirectionalLight( "directionalLight" )
directionalLight.setDirection( Vec3( 0, 8, -2.5 ) )
directionalLight.setColor( Vec4( 0.9, 0.8, 0.9, 1 ) )
render.setLight(render.attachNewNode( directionalLight ) )
render.setLight(render.attachNewNode( ambientLight ) )
class Player(DirectObject):
def __init__(self, floorMask, wallMask):
"""A Class for player specific controls"""
self.FLOORMASK= floorMask
self.WALLMASK= wallMask
self.MouseSen = 0.1
self.initPlayer()
self.bullets = {}
#imageObject = OnscreenImage(image = 'textures/DCL-Logo.bmp')
#imageObject.setPos((-.82,0,.75))
#imageObject.setScale((.5, 1,.25))
self.initUI()
self.frame = None
def initUI(self):
"""Setup the players UI"""
self.objText = OnscreenText(text = '', pos = (0,0,0), scale = 0.07)
image = OnscreenImage(image = 'textures/crosshairs.png')
image.setPos((0,0,0))
image.setScale((0.08,1,0.08))
image.setTransparency(TransparencyAttrib.MAlpha)
def initPlayer(self):
"""Setup the player collision and camera setup"""
#Player Node
self.player = NodePath(ActorNode("Player"))
self.player.reparentTo(render)
#Player Movement
self.vel = Vec3(0.0,0.0,0.0)
self.strafespeed = 9.0
self.forwardspeed = 11.0
self.backspeed = 7.0
#Player variables
self.mouse = True
#Add player controls
self.keyMap = {"forward":0,"backward":0,"strafeleft":0,"straferight":0}
self.accept("w",self.setKey, ["forward",1])
self.accept("s",self.setKey, ["backward",1])
self.accept("a",self.setKey, ["strafeleft",1])
self.accept("d",self.setKey, ["straferight",1])
self.accept("mouse1",self.mouse1down)
self.accept("mouse2",self.mouse2down)
self.accept("mouse2-up",self.mouse2up)
self.accept("w-up",self.setKey, ["forward",0])
self.accept("s-up",self.setKey, ["backward",0])
self.accept("a-up",self.setKey, ["strafeleft",0])
self.accept("d-up",self.setKey, ["straferight",0])
#Collision handler for adding objects and the like
self.pTrav = CollisionTraverser("Picker Traverser")
self.pQueue = CollisionHandlerQueue()
self.accept('bullet',self.removeBullet)
base.cPush.addInPattern('%fn')
#Picker collision
pickerNode = CollisionNode('mouseRay')
pickerNP = base.camera.attachNewNode(pickerNode)
pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
pickerNode.setIntoCollideMask(BitMask32().allOff())
self.pickerRay = CollisionRay()
pickerNode.addSolid(self.pickerRay)
self.pTrav.addCollider(pickerNP, self.pQueue)
#Player Collision Sphere
self.cNode = CollisionNode("PlayerCollision")
#To make the person tall, but not wide we use three collisionspheres
self.cNode.addSolid(CollisionSphere(0,0,-1,1))
self.cNode.addSolid(CollisionSphere(0,0,-3,1))
self.cNode.addSolid(CollisionSphere(0,0,-5,1))
self.cNodePath = self.player.attachNewNode(self.cNode)
self.cNodePath.node().setFromCollideMask(self.FLOORMASK|self.WALLMASK)
self.cNodePath.node().setIntoCollideMask(BitMask32.allOff())
base.cPush.addCollider(self.cNodePath, self.player)
base.cTrav.addCollider(self.cNodePath, base.cPush)
#Attach it to the global physics manager.
base.physicsMgr.attachPhysicalNode(self.player.node())
taskMgr.add(self.updatePlayer, "updatePlayer")
taskMgr.add(self.updateBullet, "Update Bullet")
def setKey(self, key, value):
self.keyMap[key] = value
def updatePlayer(self, task):
"""Big task that updates the players position every tick"""
elapsed = globalClock.getDt()
self.objText['text'] = ''
if self.mouse:
if base.mouseWatcherNode.hasMouse():
# get the current location. it's in the range (-1 to 1)
md = base.win.getPointer(0)
x = md.getX()
y = md.getY()
# rotate the camera based on the mouse coordinates
if base.win.movePointer(0, base.win.getXSize()/2, base.win.getYSize()/2):
base.camera.setP((base.camera.getP() - (y-base.win.getYSize()/2)*self.MouseSen) % 360)
self.player.setH((self.player.getH() - (x-base.win.getXSize()/2)*self.MouseSen) % 360)
mpos = base.mouseWatcherNode.getMouse()
self.pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())
self.pTrav.traverse(render)
# Screen selection used to identify what the player is looking at
if self.pQueue.getNumEntries() > 0:
self.pQueue.sortEntries()
entry = self.pQueue.getEntry(0)
eName = entry.getIntoNodePath().getName()
ePos = entry.getSurfacePoint(render)
self.newpos = ePos
distVec = ePos - self.player.getPos()
if distVec.length() < 12.0:
self.objText['text'] = eName
x = 0.0
y = 0.0
#Move the player around
if (self.keyMap["strafeleft"]!=0):
x = -self.strafespeed
if (self.keyMap["straferight"]!=0):
x = self.strafespeed
if (self.keyMap["forward"]!=0):
y = self.forwardspeed
if (self.keyMap["backward"]!=0):
y = -self.backspeed
self.vel = Vec3(x,y,0)
self.vel *= elapsed
self.player.setFluidPos(self.player, self.vel)
return task.cont
def mouse1down(self):
"""Fire a bullet, which at the moment is just a collision sphere"""
fwd = base.camera.getQuat(render).getForward()
b = Bullet(base.camera.getPos(render), fwd, 75.)
self.bullets[b.cNodePath]=b
def mouse2down(self):
"""Non-Toggle zoom"""
base.camLens.setFov(30)
def mouse2up(self):
"""Zoom back out"""
base.camLens.setFov(40)
def updateBullet(self, task):
"""Find all of the bullets in the game and update their position
Would like to use a lerp interval, but don't know how with velocity
instead of an end position"""
elapsed = globalClock.getDt()
for b in self.bullets.itervalues():
p = b.bullet.getPos()
v = b.vel
b.bullet.setFluidPos(p+v*elapsed)
return task.cont
def removeBullet(self, entry):
"""Event that is raised when a bullet collides, remove it from the scene
and the current list of bullets"""
fnode = entry.getFromNodePath()
try:
b = self.bullets.pop(fnode)
b.bullet.removeNode()
except KeyError:
pass