Center for Analysis and Design of Intelligent Agents

Site Tools

public:t-vien-10-3:lab_5_materials

Differences

This shows you the differences between two versions of the page.

public:t-vien-10-3:lab_5_materials [2010/10/14 02:20] (current)
hannes created
Line 1: Line 1:
+====== Lab 5 - Actors and State Machines ======
+
+==== Before You Start ====
+
+
+
+
+
+
+==== Interacting with a moving actor  ====
+
+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.\\
+{{:public:t-vien-09-1:characterstates.png|}}
+
+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.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
+
+        # 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.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
+
+        """
+        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)
+
+# Create a world
+w = World()
+# Start main loop
+run()
+</code>
+  - **Adding a Moving Character:**\\ Create a **Character** class derived from **FSM.FSM** (Finite State Machine).  In the constructor, you first need to call the constructor of the parent like this:<code python>
+FSM.FSM.__init__(self,"Character")
+</code> 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 **[[http://www.ru.is/kennarar/hannes/useful/PandaManual/Loading_Actors_and_Animations.1.html|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 [[http://www.ru.is/kennarar/hannes/useful/PandaManual/Lerp_Intervals.1.html|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!
+  - **Starting a State Machine:**\\ Create a state for pacing such that when he enters the state he paces around, but stops when he leaves the state.  You do that by adding the methods **enterPacing(self)** and **exitPacing(self)**.  In the former, you loop the **Walk** animation like before, but instead of calling **loop** on your movement interval, call **resume**.  This is so that he can continue moving from where he left off when he was interrupted.  In the latter, you'll have to stop the walking animation, but instead of calling **stop**, which leaves him frozen at the first frame of the animation, you should start a new idle motion loop (fromFrame=3083, toFrame=3166).  Create another state (with both enter and exit methods) called **Noticing** and for now, just print out a message on the screen.  Test your state machine by for example accepting two different keystrokes, one which enters the **Pacing** state by calling **self.request('Pacing')** from its handler and another that enters the **Noticing** state with **self.request('Noticing')**.
+  - **Making the Character Look:**\\ Now add to the actor the ability to face you when his **self.is_looking** member variable is set to **True**.  Simply do this by creating a task called **look** and inside it call **lookAt(base.camera)** on the nodepath of the mover, but only if **self.is_looking** is **True**.  Now set the **self.is_looking** variable to False when you enter the Pacing state and True when you enter the Noticing state.  Test that this works (try approaching him from different directions).  One problem that you may notice is that your character faces the wrong way once he resumes his pacing after having looked at you.  You can fix this with a member variable that stores the last facing angle of the pacing state when leaving it and sets it again when re-entering it.  See if you can get it fixed that way.
+  - **Triggering Transitions with Collisions:**\\ Let's have him change automatically from Pacing to Noticing when you approach him.  You need to create two **Collision Spheres**.  One under a new **CollisionNode** you create under your **base.camera** node, which you call **'avatar'** and another under a new **CollisionNode** you create under your actor node, which you call **'sensor'**.  To refresh your memory, here is how you add a collision sphere to a new collision node:<code python>
+<colnodepath> = <modelnodepath>.attachNewNode(CollisionNode(<name>))
+</code> 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:<code python>
+</code> 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: <code python>
+base.cTrav = CollisionTraverser('world traverser')