After a few beers last night and mucking about with models I remembered that someone asked about clicking on a grid or map and having an object walk to that point. So here is some code I knocked out. Be warned its very rough and has a rotation bug (:oops:) which I am sure is easy to fix but its only an example. I was deeply under the affluence of incahol at the time
Obviously if your dealing with objects on a grid, then you would need some path finding like A*. Check out gamedev for a nice tut on this.
The board.py is taken from the chess tutorial and needs the square.egg file in your models directory to work.
Have fun!
boardWalker.py
from math import sqrt
from direct.interval.IntervalGlobal import *
from direct.actor import Actor
class BoardWalker(DirectObject):
def __init__(self, modelPath, walkingModelPath, pos, hpr, scale,
rotationOffset = 0, movementSpeed = 0.5):
self.actor = Actor.Actor(modelPath, {'walk':walkingModelPath})
self.actor.setScale(scale, scale, scale)
self.actor.reparentTo(render)
self.actor.setPos(pos)
self.actor.setHpr(hpr[0], hpr[1], hpr[2])
self.rotationOffset = rotationOffset
self.accept('actor-stopped', self.__stopWalkAnim)
self.actorMovement = None
self.movementSpeed = movementSpeed
def moveTo(self, position):
if self.actorMovement != None:
self.actorMovement.pause()
self.__stopWalkAnim()
# This is the "start" position
sPos = self.actor.getPos()
# Calculate the new hpr
# Create a tmp NodePath and set it to the start position
# Then make it "lookAt" the position we want to be at.
# It will then create the hpr that we can turn to.
a = NodePath('tmp')
a.setPos(sPos)
a.lookAt(position)
newHpr = a.getHpr()
# Create a turn animation from current hpr to the calculated new hpr.
actorTurn = self.actor.hprInterval(1,
Point3(newHpr[0] + self.rotationOffset,
newHpr[1],
newHpr[2]),
startHpr = self.actor.getHpr())
# Calculate the distance between the start and finish positions.
# This is then used to calculate the duration it should take to
# travel to the new coordinate base on self.movementSpeed
if sPos[0] > position[0]:
distanceX = sPos[0] - position[0]
else:
distanceX = position[0] - sPos[0]
if sPos[1] > position[1]:
distanceY = sPos[1] - position[1]
else:
distanceY = position[1] - sPos[1]
distance = sqrt((distanceX * distanceX) + (distanceY * distanceY))
# Create a movement animation using the 2 positions
actorMove = self.actor.posInterval( (distance / self.movementSpeed), position, startPos = sPos)
# But the animations into a sequnce and create an event that will be
# called when its finished.
self.actorMovement = Sequence(actorTurn, actorMove, name = 'actorMove')
self.actorMovement.setDoneEvent('actor-stopped')
# startFrame, stopFrame, playRate, loop
#self.actorMovement.setupPlay(-1, -1, 5, 0)
# Start the walking animation, then start the turn+move anims
self.actor.loop('walk')
self.actorMovement.start()
def __stopWalkAnim(self):
# This is called when the turn+move anim has finished.
# We can then stop the walk anim.
self.actor.stop('walk')
self.actorMovement = None
board.py
import direct.directbase.DirectStart
from pandac.PandaModules import *
from direct.interval.IntervalGlobal import *
from direct.gui.DirectGui import *
from direct.actor import Actor
from boardWalker import *
#First we define some contants for the colors
BLACK = Vec4(0,0,0,1)
WHITE = Vec4(1,1,1,1)
HIGHLIGHT = Vec4(0,1,1,1)
PIECEBLACK = Vec4(.15, .15, .15, 1)
#This is derived from the mathmatical of a plane, solved for a given point
def PointAtZ(z, point, vec):
return point + vec * ((z-point.getZ()) / vec.getZ())
#A handy little function for getting the proper position for a given square
def SquarePos(i):
return Point3((i%8) - 3.5, int(i/8) - 3.5, 0)
#Helper function for determining wheter a square should be white or black
#The modulo operations (%) generate the every-other pattern of a chess-board
def SquareColor(i):
if (i + ((i/8)%2))%2: return BLACK
else: return WHITE
class Board(DirectObject):
def __init__(self):
self.accept('escape', sys.exit) #Escape quits
base.disableMouse() #Disble mouse camera control
camera.setPosHpr(0, -13.75, 6, 0, -25, 0) #Set the camera
self.setupLights() #Setup default lighting
#Since we are using collision detection to do picking, we set it up like
#any other collision detection system with a traverser and a handler
self.picker = CollisionTraverser() #Make a traverser
self.pq = CollisionHandlerQueue() #Make a handler
#Make a collision node for our picker ray
self.pickerNode = CollisionNode('mouseRay')
#Attach that node to the camera since the ray will need to be positioned
#relative to it
self.pickerNP = camera.attachNewNode(self.pickerNode)
#Everything to be picked will use bit 1. This way if we were doing other
#collision we could seperate it
self.pickerNode.setFromCollideMask(BitMask32.bit(1))
self.pickerRay = CollisionRay() #Make our ray
self.pickerNode.addSolid(self.pickerRay) #Add it to the collision node
#Register the ray as something that can cause collisions
self.picker.addCollider(self.pickerNode, self.pq)
#self.picker.showCollisions(render)
#Now we create the chess board and its pieces
#We will attach all of the squares to their own root. This way we can do the
#collision pass just on the sqaures and save the time of checking the rest
#of the scene
self.squareRoot = render.attachNewNode("squareRoot")
#For each square
self.squares = [None for i in range(64)]
for i in range(64):
#Load, parent, color, and position the model (a single square polygon)
self.squares[i] = loader.loadModelCopy("models/square")
self.squares[i].reparentTo(self.squareRoot)
self.squares[i].setPos(SquarePos(i))
self.squares[i].setColor(SquareColor(i))
#Set the model itself to be collideable with the ray. If this model was
#any more complex than a single polygon, you should set up a collision
#sphere around it instead. But for single polygons this works fine.
self.squares[i].find("**/polygon").node().setIntoCollideMask(
BitMask32.bit(1))
#Set a tag on the square's node so we can look up what square this is
#later during the collision pass
self.squares[i].find("**/polygon").node().setTag('square', str(i))
#We will use this variable as a pointer to whatever piece is currently
#in this square
self.squares[i].piece = None
# Load in the actor
self.boardWalker = BoardWalker('models/panda-model',
'models/panda-walk4',
SquarePos(1),
[0, 0, 0],
0.001,
180)
#This will represent the index of the currently highlited square
self.hiSq = False
#This wil represent the index of the square where currently dragged piece
#was grabbed from
self.dragging = False
#Start the task that handles the picking
self.mouseTask = taskMgr.add(self.mouseTask, 'mouseTask')
self.accept('mouse1', self.selectSquare) #left-click grabs a piece
def mouseTask(self, task):
#This task deals with the highlighting and dragging based on the mouse
#First, clear the current highlight
if self.hiSq is not False:
self.squares[self.hiSq].setColor(SquareColor(self.hiSq))
self.hiSq = False
#Check to see if we can access the mouse. We need it to do anything else
if base.mouseWatcherNode.hasMouse():
#get the mouse position
mpos = base.mouseWatcherNode.getMouse()
#Set the position of the ray based on the mouse position
self.pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())
#Do the actual collision pass (Do it only on the squares for
#efficiency purposes)
self.picker.traverse(self.squareRoot)
if self.pq.getNumEntries() > 0:
#if we have hit something, sort the hits so that the closest
#is first, and highlight that node
self.pq.sortEntries()
i = int(self.pq.getEntry(0).getIntoNode().getTag('square'))
#Set the highlight on the picked square
self.squares[i].setColor(HIGHLIGHT)
self.hiSq = i
return Task.cont
def selectSquare(self):
if self.hiSq != None:
self.boardWalker.moveTo(SquarePos(self.hiSq))
def setupLights(self): #This function sets up some default lighting
lAttrib = LightAttrib.makeAllOff()
ambientLight = AmbientLight( "ambientLight" )
ambientLight.setColor( Vec4(.8, .8, .8, 1) )
lAttrib = lAttrib.addLight( ambientLight )
directionalLight = DirectionalLight( "directionalLight" )
directionalLight.setDirection( Vec3( 0, 45, -45 ) )
directionalLight.setColor( Vec4( 0.2, 0.2, 0.2, 1 ) )
lAttrib = lAttrib.addLight( directionalLight )
render.attachNewNode( directionalLight.upcastToPandaNode() )
render.attachNewNode( ambientLight.upcastToPandaNode() )
render.node().setAttrib( lAttrib )
#Do the main initialization and start 3D rendering
w = Board()
run()