My keyboard controls! Almost done! Just two minor issues.

I’m almost there! I’ve almost finished my ‘keyboard controls’ script. So far, it’s working beautifully :smiley: except for two very minor issues that I haven’t been able to solve:

First, when I run the script, the camera is positioned in front of the player (looking at him front on) and I’d like it to start with the camera positioned behind the player. However, changing the camera’s and the actor’s Heading values doesn’t seem to have any effect.

Second, the player walks along just fine, he changes direction and even walks backward, but when I release a key and he stops moving, he stops on whatever frame of animation he was on (stopping mid-stride if you like) which looks very unnatural. I’d prefer it if he stopped on the first frame of his animation (standing) and then resumed his animation from that frame.

Anyway, here is my code (I’m very proud of this. A few weeks ago I didn’t even know what a variable was :smiley:):

# KeyboardControls.py
# Controls are W - forward, S - backward, A - turn left, D - turn right. 
# The left and right arrows rotate the camera. The Up and Down arrows zoom 
# the camera in and out. 

import direct.directbase.DirectStart # Start Panda
from pandac.PandaModules import* # Import the Panda Modules
from direct.showbase.DirectObject import DirectObject # To listen for Events
from direct.task import Task # To use Tasks
from direct.actor import Actor # To use animated Actors 
from direct.interval.IntervalGlobal import * # To use Intervals
import math # To use math (angles, degrees..etc)
import sys 

class Controls(DirectObject):
    #Constructor
    def __init__(self): 
       base.disableMouse() # Disable default camera.
       self.speed = .05 # Controls speed of camera rotation and zoom.
       self.loadModels()
       self.setupAnimations()
       # Setup key controls
       self.accept("escape", sys.exit)
       self.accept("arrow_left", self.cameraTurn,[-1])
       self.accept("arrow_right", self.cameraTurn,[1])
       self.accept("arrow_up", self.cameraZoom,[-1])
       self.accept("arrow_down", self.cameraZoom,[1])
       self.acceptOnce("w", self.forward)
       self.acceptOnce("s", self.backward)
       self.acceptOnce("a", self.turn,[-1])
       self.acceptOnce("d", self.turn,[1])
       self.accept("w-up",self.stopForward)
       self.accept("s-up",self.stopBackward)
       self.accept("a-up",self.stopTurn)
       self.accept("d-up",self.stopTurn)
       # end __init__

    def loadModels(self):
        # Load the player and its animations
        self.player = Actor.Actor("MODELS/ralph",{"walk":"MODELS/ralph-walk"})
        self.player.reparentTo(render) # Make it display/render on the screen.
        self.player.setScale(.005)
        self.player.setPos(0, 0, 0) # Position it at the center of the world.
        # Load an environment
        self.environ = loader.loadModel("MODELS/env")
        self.environ.reparentTo(render)
        self.environ.setPos(0, 0, 0)
        #Create a camera dummy node
        self.camera_dummy_node = render.attachNewNode("camera_dummy_node")
        #Position the camera dummy node
        self.camera_dummy_node.setPos( 0, 0, 0)
        # Attach the camera dummy node to the player.
        self.camera_dummy_node.reparentTo(self.player)
        # Attach the camera to the dummy node.
        camera.reparentTo(self.camera_dummy_node)
        # Position the camera
        camera.setPos(0, -30, 10) # X = left & right, Y = zoom, Z = Up & down.
        camera.setHpr(0, -10, 0) # Heading, pitch, roll.
        camera.lookAt(self.player) # Make the camera follow the player.
        # end loadModels
    
    # Define the CameraTurn function.
    def cameraTurn(self,dir):
        self.camTurn = LerpHprInterval(self.camera_dummy_node, self.speed, Point3(self.camera_dummy_node.getH()-(10*dir), 0, 0))
        self.camTurn.start()
        # end cameraTurn
    
    # Define the cameraZoom function.
    def cameraZoom(self,dir):
        self.camZoom = LerpPosInterval(camera, self.speed, Point3(camera.getX(), camera.getY()-(2*dir), camera.getZ()))
        self.camZoom.start()
        # end cameraZoom
    
    # Setup the player's animations
    def setupAnimations(self):
        self.playerWalk = self.player.actorInterval("walk")
        self.playerBack = self.player.actorInterval("walk")
        self.playerTurn = self.player.actorInterval("walk")
        # end setupAnimations
        
    # Define the forward function
    def forward(self):
        taskMgr.add(self.forwardTask,"forwardTask")
        self.playerWalk.loop() # Play the actor's animations
        # end forward
    
    # Define the forwardTask    
    def forwardTask(self,task):
        speed = 0.05 # controls how far the actor moves
        dt = globalClock.getDt()
        dist = speed*dt
        angle = self.player.getH()*math.pi/180
        dx = dist*math.sin(angle)
        dy = dist*-math.cos(angle)
        self.player.setPos(Vec3(self.player.getX()+dx,self.player.getY()+dy,0))
        return Task.cont
    # end forwardTask
    
    # Define the stopForward function
    def stopForward(self):
        taskMgr.remove("forwardTask")
        self.playerWalk.pause()
        self.acceptOnce("w",self.forward)
        #end stopForward
    
    # Define the backward function    
    def backward(self):
        taskMgr.add(self.backwardTask,"backwardTask")
        self.playerBack.loop()
        # end backward
    
    # Define the backwardTask    
    def backwardTask(self,task):
        speed = 0.05 # controls how far the actor moves
        dt = globalClock.getDt()
        dist = speed*dt
        angle = self.player.getH()*math.pi/180
        dx = dist*math.sin(angle)
        dy = dist*-math.cos(angle)
        self.player.setPos(Vec3(self.player.getX()-dx,self.player.getY()-dy,0))
        return Task.cont
    # end backwardTask
    
    # Define the stopBackward function
    def stopBackward(self):
        taskMgr.remove("backwardTask")
        self.playerBack.pause()
        self.acceptOnce("s",self.backward)
        #end stopBackward
    
    # Define the turn function    
    def turn(self,dir):
        taskMgr.add(self.turnTask, "turnTask",extraArgs =[dir])
        self.playerTurn.loop()
        self.ignore("a")
        self.ignore("d")
        #end turn
    
    # Define the turnTask 
    def turnTask(self,dir):
        speed = 50.0
        dt = globalClock.getDt()
        angle = dir*speed*dt
        self.player.setH(self.player.getH()-angle)
        return Task.cont
    # end turnTask
    
    # Define the stopTurn function
    def stopTurn(self):
        taskMgr.remove("turnTask")
        self.playerTurn.pause()
        self.acceptOnce("a",self.turn,[-1])
        self.acceptOnce("d",self.turn,[1])
        #end stopTurn
        
# end class Controls

c = Controls()

run() 

If anybody knows how to solve these last two issues, I’ll be over the moon. Because this was the first real programming challenge that I set for myself.

So I’m feeling very pleased with myself :laughing:. Mind you, I couldn’t have done it without all your help. So thankyou all very, very much.

Yay! I’ve managed to solve the first issue on my own (starting with the camera behind the actor) it finally dawned on me that I should change the camera_dummy_node’s heading, and not the actor’s or camera’s itself :unamused:.

So I’ve added this line to my code, and it now works nicely :smiley::


self.camera_dummy_node.setHpr(180, 0, 0)

However, I’m still struggling with the second issue, how to make the actor stop and start on his first frame of animation.

I’ve taken another look at the manual, and it says this:

So I tried adding this to my code (which doesn’t work, of course):

# Define the setupAnimations function
    def setupAnimations(self):
        self.playerWalk = self.player.actorInterval("walk")
        self.playerBack = self.player.actorInterval("walk")
        self.playerTurn = self.player.actorInterval("walk")
        # end setupIntervals
        
    # Define the forward function
    def forward(self):
        taskMgr.add(self.forwardTask,"forwardTask")
        self.player.pose("walk", 0)
        self.playerWalk.loop("walk", fromFrame = 0, toFrame = 36)
        # end forward

If anybody knows the right solution to this, please help me out here. Also, how do you find out how many frames of animation an animation actually has? I’m just using the value that was in the manual (which I’m sure isn’t the right number).

Cheers

Hmmm… looks like you want to have each of the animations looping using a finite state machine. I am not good at describing it, maybe you want to read yourself. :slight_smile:

http://panda3d.org/manual/index.php/Finite_State_Machines

Regards, Bigfoot29

You can query the number of frames your animation has:


self.player.getNumFrames("walk")

Note that if the answer is, say, 24, then the frame range of the animation is frames 0 through 23.

You code seems correct to make the actor start looping at frame 0 (although if you wanted to start from some frame other than 0–the first frame in your range–you should include the parameter restart=0 to your loop() call, as shown in the manual).

To make an actor stop looping at a particular frame is harder, because you have to first answer the question “what do I want to happen when the actor is at frame, say, 12 when I decide to change animations?” Do you want the actor to continue playing out the rest of his cycle until he reaches frame 0 again, or do you just want him to pop to frame 0? If you just want him to pop, it’s easy:

self.player.stop()
self.player.pose("walk", 0)

However, if you want him to play out his animation, it gets a lot harder because now you have to do nothing for a while until the actor reaches the critical frame, and then you have to remember that you wanted to stop (or change to a different animation). This is where an FSM would come in handy, because it helps you keep track of this sort of state. However, using an FSM won’t make it simple; it’s still a hard programming problem. People have written books about it.

David

That’s wonderful David! Yep, that’s just what I was trying to achieve, I just wanted him to ‘pop’ to a particular frame of animation when he stopped (I’m trying to keep it simple for now. I’m nowhere near ready to tackle FSM’s just yet :unamused:).

I also used your trick to discover how many frames of animation he has, thanks very much for that.

If anybody is interested, the Ralph model’s ‘walk’ animation has 25 frames of animation :smiley:. To actually get it, I just added this little function to my code (which simply prints the number of animation frames to the command prompt when you press the F key):

self.accept("f",self.printFrames)

def printFrames(self):
        self.frames = self.player.getNumFrames("walk")
        print self.frames 

To make the player stop on a particular frame of animation, I did as David suggested, and added ‘self.player.pose’ to the stopForward function, like so:

# Define the stopForward function
    def stopForward(self):
        taskMgr.remove("forwardTask")
        self.playerWalk.pause()
        self.player.pose("walk",17)
        self.acceptOnce("w",self.forward)
        #end stopForward

Frame 17 isn’t exactly the standing pose (it’s reasonably close though) I just need to play around with it some more until I find which frame represents it.

Well, that’s it! It’s finished! I’ve conquered my first challenge :smiley:, I guess it’s now time to move on to the next one.

HUGE thanks to everybody who helped me with this, I couldn’t have done it without you. You folks are terrific.

Thankyou all.