I finally got my character to have some interaction with the environment, thought I’d share it with others. The below code is just the most relevant portions. Some notes, I modeled a spinning platform in Blender as a cylinder. Likewise, I made a saw blade. I changed the names from the default name (cylinder) to sawBlade and spinningPlatform which is important for collision detection. I made a grid as the floor and renamed it Floor. I edited their egg files to include sawBlade {keep polyset descend}. I also made a cube which I used as the player. I did not add a collision node to this. If you add a collision node, the collision ray will hit it. But now that I think about it I guess I could get away with this by changing
firsthit = self.rayToGroundHandler.getEntry(0)
to
firsthit = self.rayToGroundHandler.getEntry(1)
Its set up so that when the player hits the sawBlade their position just gets reset to zero but this could be easily changed to have a decrease in health or play an animation or whatever. When the player steps on the spinning platform, they stick to it (like standing on a merry go round). But they can still walk off.
This technique can be easily extended. If I create a box in Blender and flatten it and rename it lava… or if I make a box and rename it water and set its alpha low, it can be used to start a swim animation. This reminds me of triggers in some level editors. Another box could be made and named LandMine which would start a projectile interval, throwing the player into the air. Or the box could be named trap, causing a trap door in the floor to open or a rock to fall from the sky. Or the walls could move in to crush the player. A platform elevator can be created. A teleporter.
This code also allowed my player to go up steps. The height limit of he step can be contolled by lowering the z value in the code below:
self.toGroundRay.setOrigin(0,0,10)
To prevent the character from going certain places I’ve inserted :
if self.name != "noGoZone":
into my character control code:
def forwardT (self,task):
if self.name != "noGoZone":
radians = self.player.getH()*math.pi/180.0
self.player.setX(self.Icube.getX()-1*math.sin(radians))
self.player.setY(self.Icube.getY()+1*math.cos(radians))
return Task.cont
Here is the core code:
class World (DirectObject):
def __init__(self):
...
#set up my floor
self.floor = loader.loadModel('floor')
self.floor.reparentTo(render)
self.floor.setPos(0,0,0)
self.floor.setScale(2)
#set up a player
self.player = loader.loadModel('box')
self.player.reparentTo(render)
self.player.setPos(0,0,0)
self.saw = loader.loadModel('saw3')
self.saw.reparentTo(render)
self.saw.setPos(20,0,0)
self.spinner = loader.loadModel('spinner')
self.spinner.reparentTo(render)
self.spinner.setPos(-20,0,0)
....
self.makeGroundRayF()
taskMgr.add(self.stickT, "stickT")
taskMgr.add(self.rotateT, "rotateT")
def rotateT(self,task):
self.saw.setH(self.saw.getH()+1)
self.spinner.setH(self.spinner.getH()+1)
return Task.cont
def makeGroundRayF(self):
self.cTrav = CollisionTraverser()
self.toGroundRay = CollisionRay()
self.toGroundRay.setOrigin(0,0,10)
self.toGroundRay.setDirection(0,0,-1)
self.rayNode = CollisionNode('RayNode')
self.rayNode.addSolid(self.toGroundRay)
self.rayNode.setFromCollideMask(BitMask32.bit(0))
self.rayNode.setIntoCollideMask(BitMask32.allOff())
self.rayNodePath = self.Icube.attachNewNode(self.rayNode)
self.rayToGroundHandler = CollisionHandlerQueue()
self.cTrav.addCollider(self.rayNodePath, self.rayToGroundHandler)
self.rayNodePath.show()
self.cTrav.showCollisions(render)
def stickT(self, task):
self.cTrav.traverse(render)
self.rayToGroundHandler.sortEntries()
if self.rayToGroundHandler.getNumEntries() > 0:
firsthit = self.rayToGroundHandler.getEntry(0)
self.name = firsthit.getIntoNode().getName()
if self.name == "sawBlade": self.player.setPos(0,0,0)
elif self.name == "spinningPlatform":
self.player.wrtReparentTo(self.spinner)
self.player.setZ(firsthit.getSurfacePoint(render).getZ())
elif self.name == "Floor":
self.player.wrtReparentTo(render)
self.player.setZ(firsthit.getSurfacePoint(render).getZ())
return Task.cont