In this exercise, you have to create a character that paces around while he is unaware of you. But as soon as you come within a certain range, he stops and looks at you. While the character is watching you, you can click on him to start a conversation with him. If you have entered a conversation, you can click on something else to stop talking. If at any point you leave the character, he goes back to his pacing around. These states and the various events that trigger transitions are shown in this figure.
import direct.directbase.DirectStart from direct.showbase.DirectObject import DirectObject from pandac.PandaModules import * from direct.actor.Actor import Actor from direct.fsm import FSM from direct.interval.IntervalGlobal import * from direct.task.Task import Task from direct.gui.OnscreenText import OnscreenText from picker import Picker # Note: This module is in the Lab 5 Asset file (put in same folder) # TODO: Add a Character class # TODO: Add a Conversation class # Movement keys key_map = (('s',['back',1]),('s-up',['back',0]),('w',['forward',1]),('w-up',['forward',0]), ('a',['left',1]),('a-up',['left',0]),('d',['right',1]),('d-up',['right',0])) class World(DirectObject): def __init__(self): # Set up movement keys self.keystate = {'back':0, 'forward':0, 'right':0, 'left':0} for key in key_map: self.accept(key[0],self.set_keystate,key[1]) self.lasttime = 0 taskMgr.add(self.move, "move") # Camera setup base.disableMouse() base.camera.setPos(0,-10, 2) base.camLens.setFov(60) base.camLens.setNear(0.01) base.setBackgroundColor(Vec3(0,0,0)) # Background environment setup palace = loader.loadModel("models/palacedoors.egg") palace.reparentTo(render) palace.setPos(0,0,0) palace.setScale(1.5) # Add a character into the world ## TODO: person = Character(render) # TODO: Add collision detection def set_keystate(self, key, state): """ Set a stored state (0,1) for a specified key ('back','forward','right','left')""" self.keystate[key] = state def move(self, task): """ Update camera position and/or rotation for all active key states. """ elapsed = task.time - self.lasttime if self.keystate['back'] is 1: base.camera.setPos(camera,0,-7*elapsed,0) if self.keystate['forward'] is 1: base.camera.setPos(camera,0,7*elapsed,0) if self.keystate['right'] is 1: base.camera.setH(camera.getH()-30*elapsed) if self.keystate['left'] is 1: base.camera.setH(camera.getH()+30*elapsed) self.lasttime = task.time return task.cont # Create a world w = World() # Start main loop run()
FSM.FSM.__init__(self,"Character")
Then, create a new node called “mover” that will become the parent of the actor. Place the mover at (3,5,0). This extra node is not always needed, but it makes it possible to redefine the “front” of the model by rotating it with respect to this new model base (think of the mover as a platform that the actor is standing on). Now you should load the Actor from the model “models/FelOrcWarriorAxe.egg” with the animation 'anims' : “models/FelOrcWarriorAxe.egg” and attach it to the mover. Define the front facing of the actor by rotating it 90 degrees (with respect to the mover). Notice that the animations are stored in exactly the same file as the model in this case. Furthermore, all the different motions that the Orc can perform are bundled together into a single animation here (that's just the way that this model/animation was constructed). In order to play a particular motion sequence from this animation, you always specify the fromFrame and toFrame. So, to start the Orcs walking animation, call loop('anims',fromFrame=166, toFrame=193) on your actor. The fromFrame and toFrame values of other motions can be found inside the text file “FelOrcWarriorAxe.animsez”. Finally create a new Sequence of Lerp Intervals that first changes mover heading to 90 (in about 0.8 seconds) and then moves it to position (-3,5,0) in about 3 seconds. Rotate the mover back and bring it back to the original position. Now loop this interval and watch your actor pace around!
<colnodepath> = <modelnodepath>.attachNewNode(CollisionNode(<name>)) <colnodepath>.node().addSolid(CollisionSphere(0,0,0,<radius>))
Use a radius of 5 for the avatar sphere and 2 for the sensor sphere. Now that you have the two collision spheres set up, you need to create a collision handler that sends out the types of messages we want. Inside your World constructor, create a new CollisionHandlerEvent instance and then define these two event patterns:
<collhandler>.addInPattern('%fn-into-%in') <collhandler>.addOutPattern('%fn-out-%in')
Then set up the default collision traverser (base.cTrav) to use your avatar collision node (that you created above) as the from object and associate events its collision to the handler. Here's how that could be done:
base.cTrav = CollisionTraverser('world traverser') base.cTrav.addCollider(self.avatar,<collhandler>)
Inside you Character class, you can now accept the messages 'avatar-into-sensor' and 'avatar-out-sensor' that represent when the user enters the sensor sphere around the character and when he/she leaves the sphere, respectively. Create handlers for these messages (as methods of Character), which you can call approach_handler and leave_handler. In the former, you should have the character enter the Noticing state and in the latter the Pacing state. Make sure this works (remember you can make collision solids visible by calling the show() method on their nodes).
<mouse_picker>=Picker(<youractornodepath>) <mouse_picker>.make_pickable(<youractornodepath>)
Now, whenever you click on your actor, an event with the name 'clicked_render/mover/Orc' occurs, which you can of course accept in your Character class. See if you can make your character respond to your clicking.
defaultFilter
) to create a new Conversation class that handles the branching dialog shown in this diagram: self.request(<input>)
method for advancing to the next state.