Trouble rotating actor

I have created a basic movable player and a camera. Player moves with “W” “A” “S” and “D”. When I try to rotate the player model in order to face the direction that he’s walking, the player model moves away from the camera.

But if you comment out

self.publicNode.setH(coord)

at the line marked “#PROBLEM” in demo.py, the player moves fine. Any help would be appreciated.

demo.py:

import direct.directbase.DirectStart
from direct.actor import Actor
from direct.task import Task

from pandac.PandaModules import *

# my modules
import keyEvents
			
class playableChar():
	def __init__(self, child):
		# Direction constants
		self.UP = 1
		self.DOWN = 2
		self.LEFT = 3
		self.RIGHT = 4
		
		# Actor or Model that this object will handle
		self.__child = child
		
		# Handle animation
		try:
			self.__animName = self.__child.getCurrentAnim()
			self.__animCtl = self.__child.getAnimControl(self.__animName)
		except AttributeError:
			self.__animCtl = None
		
		# internal dummy node
		self.__dummyNode = render.attachNewNode('dummyNode') 
		
		# public dummy node
		self.publicNode = self.__dummyNode.attachNewNode('publicNode')

		self.direction = self.DOWN
		self.__child.reparentTo(self.publicNode)
		
	def move(self, toMove):			
		moveTable = {
			"move-left" 	: {'pos':(-0.5, 0.0, 0.0),	'h':-90, 	'dir':self.LEFT},
			"move-right"	: {'pos':(0.5, 0.0, 0.0), 	'h':90, 	'dir':self.RIGHT},
			"move-up"		: {'pos':(0.0, -0.5, 0.0), 	'h':180, 	'dir':self.UP},
			"move-down"		: {'pos':(0.0, 0.5, 0.0), 	'h':0, 		'dir':self.DOWN}
			}
		
		# Rotate model, if he is not facing the correct direction
		if self.direction != moveTable[toMove]['dir']:
			self.direction = moveTable[toMove]['dir']
			coord = moveTable[toMove]['h']
			self.publicNode.setH(coord)  # PROBLEM
			
		coords = moveTable[toMove]['pos']
		self.publicNode.setPos(self.publicNode, coords[0], coords[1], coords[2])
			
	def acceptCam(self, camera):
		""" 'Accept' a playerCam() object as a sort of 'child node' """
		self.__camera = camera
		
	def getCam(self):
		return self.__camera
		
	def disableCam(self):
		self.__camera = None
		
	def playAnimation(self):
		if self.__animCtl != None:
			self.__animCtl.loop(self.__animName)
			
	def pauseAnimation(self):
		if self.__animCtl != None:
			self.__animCtl.stop()
		
class playerCam():
	def __init__(self, player, camera=base.camera):
		self.camera = camera
		self.__player = player
		self.enabled = False
		
	def enable(self):
		if self.enabled == False:
			self.camera.setHpr(0, -16, 0)
			self.camera.setPos(0, -25, 12)
			base.disableMouse()
			self.camera.reparentTo(self.__player.publicNode)
			self.enabled = True
			
	def disable(self):
		if self.enabled == True:
			self.camera.reparentTo(render)
			self.camera.setHpr(0, -16, 0)
			self.camera.setPos(0, -25, 12)	
			base.enableMouse()
			self.enabled = False
			
	def doParent(self):		
		# "Attach" the camera to the player object
		self.__player.acceptCam(self)
	
	def noParent(self):
		self.__player.disableCam()

# load the base panda
pandaObj = Actor.Actor("models/panda-model", {"animate" : "models/panda-walk4"})
pandaObj.setScale(0.005)
pandaObj.setPos(0, 0, 0)
pandaObj.loop("animate")

# key response handler
kh = keyEvents.keyHandler()

# moveable player
player = playableChar(pandaObj)
playerAnim = False
playerWalking = False

# camera
cam = playerCam(player)
cam.noParent()
cam.disable()

def gameLoop(task):
	
	global playerAnim, playerWalking
	
	playerWalking = False
	
	for key, pressed in kh.keyStates.iteritems():
		if pressed == True:
			cmd = kh.controls[key]
			player.move(cmd)
			playerWalking = True
			
	if playerWalking == True:
		if playerAnim == False:
			playerAnim = True
			player.playAnimation()
	else:
		if playerAnim == True:
			playerAnim = False
			player.pauseAnimation()
						
	return Task.cont

taskMgr.add(gameLoop, "gameLoop")
run()

keyEvents.py

from direct.showbase.DirectObject import DirectObject
import sys

class keyHandler(DirectObject):
	def __init__(self):
	
		self.accept("escape", sys.exit)
		
		self.controls = {
			"a"	: "move-left",
			"w"	: "move-up",
			"d"	: "move-right",
			"s"	: "move-down"
			}
			
		# at start, no key is active
		self.keyStates = {}
		for key in self.controls:
			self.keyStates[key] = 0
				
		# go through keys and 'accept' them
		for key, action in self.controls.items():
			self.accept(key, self.chKeyState, [key, True])
			self.accept(key+"-up", self.chKeyState, [key, False])
			
	def chKeyState(self, key, pressed):
		print("%s is %s" % (key, (pressed==True and "pressed" or "released"))) 
		self.keyStates[key] = pressed

Hello,

Your problem lies in the combination of these two lines:

moveTable = {
         "move-left"    : {'pos':(-0.5, 0.0, 0.0),   'h':-90,    'dir':self.LEFT},
         "move-right"   : {'pos':(0.5, 0.0, 0.0),    'h':90,    'dir':self.RIGHT},
         "move-up"      : {'pos':(0.0, -0.5, 0.0),    'h':180,    'dir':self.UP},
         "move-down"      : {'pos':(0.0, 0.5, 0.0),    'h':0,       'dir':self.DOWN}
         }
      
  

and

self.publicNode.setPos(self.publicNode, coords[0], coords[1], coords[2])

The problem is that when you want to move say left, your ‘pos’ argument gives a negative x component (which would be correct from the global standpoint) but you change your position with a relative assignment. If you want your character to rotate first, then walk in his relative forward direction, change all of your pos’s to (0,5,0). When you disabled the rotation, he suddenly moved correctly because when you wanted him to walk left he would sidestep left.

Hope that helps!

PS: I like the use of the self.controls dictionary in your keyEvents.py to act as an intermediary between the event and the passed function - I will be shamelessly stealing it to make an easy user preferences system (unless you have strong objections). That said, nested dictionaries in move and the combo of public/private nodes seems rather complicated for the task at hand?

thanks!