character lookAt problem

Hi, my 1st post (be gentle :slight_smile: )

I’ve worked as an apps developer most of my adult life (SQL, VB, and many other now-ancient languages), but am a total newbie to 3d game programming. Stumbled across panda3d 2-3 weeks ago and since I was familiar with python, gave it a try.

I must say it’s a fantastic engine and I’ve already gone further than I
thought I ever could in making the game of my dreams (an RTS-RPG thingie i thought up yrs ago). probably doing it all wrong right now, but I’m learning.

Anyway, I’m having trouble getting one specific lookAt statement to work.
I’m sure it’s just some understanding problem on my part.

I’ve set up a simple scene graph:

  • I have one player-controlled character (roaming Ralph-type controls)

  • I have one computer-controlled foe and I’ve setup a Task that looks for
    the closest player character (my code allows for multiple player characters so it 1st figures out the closest player character) and then moves the foe towards that character.

This code (shown below) is called by the task and works perfectly (even uses variable character speeds which I’ve saved in a SPD tag for each character in case you’re wondering what the extra bits of code are for)

def charMove(char, endPos=(0,0,0), anim="run"):
	char.isBusy = True
	char.loop(anim)
	elapsed = max(globalClock.getDt(), 1.1)	# 1.1 to make foes match player speeds
	time = getDistance(char.getPos(), endPos) / (char.SPD * SPD_MULTIPLIER * elapsed)
	part1 = Func(char.lookAt, endPos)
	part2 = char.posInterval(time, endPos)
	part3 = Func(charIdle,char)
	mySequence = Sequence(part1, part2, part3)
	mySequence.start()

My problem is the lookAt in the line that starts with “part1” doesn’t seem to work right, so my foe is running facing the wrong direction.
I’ve tried setting him to “look at” the endPos (which is where the player
character is) and also tried looking at the player’s character.

Can someone point out what I’m doing wrong? All i want is for the foe model to turn and face the point it’s moving to (which should be endPos)

BTW - using the same model for both player character and ai foe in this test.

Thanks for any help.

What does happen? Does your foe consistently turn his backside towards the direction he’s running (for instance)? lookAt() rotates the model so that its y axis is pointing in the indicated direction, but if your model isn’t set up so that it’s looking forward down the y axis, then lookAt() will rotate whatever part of the model is looking down the y axis to the indicated direction.

If that’s the case, you can either fix the model, or you can create an intervening node, parent your model to the new node (and the new node to render), and then rotate your model 180 degrees or whatever to correct the y axis facing. Then you can move around the new node instead.

David

Hi David,
Thanks for the reply. I will check with the 3d artist on the model. Hopefully that’s it :slight_smile:

OK, this is very strange - the artist sent me a new mesh turned 180 on the y axis. I tried it with the script and i get the same result.
(character chasing is running with his backside towards the direction he’s running).

I know it’s a new model since the texture is different, so i can tell that changed. Will ask him to doublechk that he turned it around, but is there anything else that could be causing this in the code?

It seemed like pretty straightforward code.
Thx for any advice.

Um, if the artist turned the model around, but it’s still facing the same direction, either the artist didn’t turn it around successfully, or something in your code is wiping out the original transform on the node.

But if you’re loading the model via loader.loadModel(), or via the Actor() constructor, and only working on the top node, that node won’t have any transforms from within the model (those will be deeper below). So there’s no danger of your code wiping out any original transforms unless you go digging within the model for a sub-node.

Try running pview orig.egg, and pview new.egg. You should be looking at the character’s backside. If you’re looking at his face, he’s facing backwards. And if both orig.egg and new.egg are facing the same way, the artist didn’t actually change his direction.

David

Thx for the very quick reply!

Yep, the model was facing forward in pview. I contacted the artist who realized what he’d done wrong and sent me a new one. When i look at this one in pview - i see his back. So all should be good.

But when i run the code, same problem. :<

I cleared the modelcache folder just in case. No dice.

I tried using roaming ralph instead of my model in case there was something weird about it. He has the same problem. Aha!

But then I looked ralph in pview and he’s facing forward, so I guess I would assume he would have the problem.

Not sure what to try next. Should I try with another model that should work (can you suggest one to use). I can post a test program will all the code in case it might help.

Did a small test program:

import direct.directbase.DirectStart
from direct.showbase.ShowBaseGlobal import *
from direct.interval.IntervalGlobal import *
from direct.gui.DirectGui import *
from direct.showbase import DirectObject
from direct.actor import Actor
from direct.task import Task
from pandac.PandaModules import *
from direct.showbase.PythonUtil import * 
import random, sys, os, math, glob

GAMEDIR_WIN = os.path.abspath(sys.path[0])
GAMEDIR = Filename.fromOsSpecific(GAMEDIR_WIN).getFullpath()
SPD_MULTIPLIER = 20

ANIMS = {
'idle':'art/characters/_anims/_idle',
'run':'art/characters/_anims/_run',
}

map = loader.loadModel(GAMEDIR + "/art/maps/world/world")
map.reparentTo(render)

char1 = Actor.Actor(GAMEDIR + "/art/characters/guy/guy", ANIMS)
char1.setPos(0,0,0)
char1.reparentTo(render)

char2 = Actor.Actor(GAMEDIR + "/art/characters/guy/guy", ANIMS)
char2.setPos(100,100,0)
char2.reparentTo(render)

char2.lookAt(char1)

run()

and this works! char2 is facing char1.
The difference between this and the first code i posted is that I used char1 as a parameter and not the x,y,z position.

I tried changing this line in my main program (hard-coded char1 for now), but it doesn’t work! ???

So something in the code?

Here’s a bigger test with movement

# Load the PANDA libraries
import direct.directbase.DirectStart
from direct.showbase.ShowBaseGlobal import *
from direct.interval.IntervalGlobal import *
from direct.gui.DirectGui import *
from direct.showbase import DirectObject
from direct.actor import Actor
from direct.task import Task
from pandac.PandaModules import *
from direct.showbase.PythonUtil import * 
import random, sys, os, math, glob

GAMEDIR_WIN = os.path.abspath(sys.path[0])
GAMEDIR = Filename.fromOsSpecific(GAMEDIR_WIN).getFullpath()
SPD_MULTIPLIER = 20

ANIMS = {
'idle':'art/characters/_anims/_idle',
'run':'art/characters/_anims/_run',
}

map = loader.loadModel(GAMEDIR + "/art/maps/world/world")
map.reparentTo(render)

char1 = Actor.Actor(GAMEDIR + "/art/characters/guy/guy", ANIMS)

char1.setPos(0,0,0)
char1.SPD = 10
char1.isMoving = False
char1.isAlive = True
char1.reparentTo(render)

char2 = Actor.Actor(GAMEDIR + "/art/characters/guy/guy", ANIMS)
char2.setPos(600,600,0)
char2.SPD = 6
char2.isBusy = False
char2.reparentTo(render)

class World(DirectObject.DirectObject):
	def __init__(self):

	########################################
	# Keyboard Controls
	########################################
		self.isViewMode = False

		self.keyMap = {"left":0, "right":0, "forward":0}
        	self.accept("escape", sys.exit,[0])

		self.accept("w", self.setKey, ["forward",1])
		self.accept("w-up", self.setKey, ["forward",0])
		self.accept("a", self.setKey, ["left",1])
		self.accept("a-up", self.setKey, ["left",0])
		self.accept("d", self.setKey, ["right",1])
		self.accept("d-up", self.setKey, ["right",0])

		self.accept("c", self.cameraView)
		self.accept("x", base.screenshot)
		self.accept("z", self.cameraInfo)

	########################################
	# Camera Controls
	########################################
		base.disableMouse() 
		base.camera.setPos((133, -132, 67))
		base.camera.reparentTo(char1)
		base.camera.setCompass(render)
		base.camera.lookAt(char1)

		self.camDist = 200
		self.mx,self.my = 0,0 
		self.dragging = False 
		self.target = Vec3() 
		self.turnCameraAroundPoint(0,0,self.target, self.camDist) 
		self.accept("mouse3",self.startDrag) 
		self.accept("mouse3-up",self.stopDrag) 
		self.accept("wheel_up",lambda : self.adjustCamDist(0.9)) 
		self.accept("wheel_down",lambda : self.adjustCamDist(1.1)) 

		taskMgr.add(self.taskCamera,"cameraTask") 
		taskMgr.add(self.taskMove,"moveTask")
		taskMgr.add(self.taskAI,"aiTask")

	def setKey(self, key, value):
		self.keyMap[key] = value

	def cameraInfo(self):
		print "Camera POS: ", base.camera.getPos()
		print "Camera RTN: ", base.camera.getHpr()
		print "Camera DIS: ", self.camDist

	def cameraView(self):
		if self.isViewMode == False:
			base.camera.setCompass(char1)
			self.isViewMode = True
		else:
			base.camera.setCompass(render)
			self.isViewMode = False

	def turnCameraAroundPoint(self,tx,ty,p,dist): 
		newCamHpr=Vec3() 
		camHpr=base.camera.getHpr() 
		newCamHpr.setX(camHpr.getX()+tx) 
		newCamHpr.setY(camHpr.getY()-ty) 
		newCamHpr.setZ(camHpr.getZ()) 
		base.camera.setHpr(newCamHpr) 
		angleradiansX = newCamHpr.getX() * (math.pi / 180.0) 
		angleradiansY = newCamHpr.getY() * (math.pi / 180.0) 
		base.camera.setPos( dist*math.sin(angleradiansX)*math.cos(angleradiansY)+p.getX(), 
		-dist*math.cos(angleradiansX)*math.cos(angleradiansY)+p.getY(), 
		-dist*math.sin(angleradiansY)+p.getZ() ) 
		base.camera.lookAt(p.getX(),p.getY(),p.getZ() ) 

	def startDrag(self): 
		self.dragging = True 

	def stopDrag(self): 
		self.dragging = False 
	
	def adjustCamDist(self,aspect): 
		self.camDist = self.camDist*aspect 
		self.turnCameraAroundPoint(0,0,self.target,self.camDist) 

	########################################
	# Camera TASK
	########################################
	def taskCamera(self,task): 
		if base.mouseWatcherNode.hasMouse(): 
			mpos = base.mouseWatcherNode.getMouse() 
			if self.dragging: 
				self.turnCameraAroundPoint((self.mx-mpos.getX())*100,(self.my-mpos.getY())*100,self.target,self.camDist) 
			else: 
				moveY = False 
				moveX = False 
				if self.my>0.8: 
					angleradiansX = base.camera.getH() * (math.pi / 180.0) 
					aspect = (1-self.my-0.2)*10
					moveY = True 
				if self.my<-0.8: 
					angleradiansX = base.camera.getH() * (math.pi / 180.0)+math.pi 
					aspect = (1+self.my-0.2)*10
					moveY = True 
				if self.mx>0.8: 
					angleradiansX2 = base.camera.getH() * (math.pi / 180.0)+math.pi*0.5 
					aspect2 = (1-self.mx-0.2)*10
					moveX = True 
				if self.mx<-0.8: 
					angleradiansX2 = base.camera.getH() * (math.pi / 180.0)-math.pi*0.5 
					aspect2 = (1+self.mx-0.2)*10
					moveX = True 
				if moveY: 
					self.target.setX(self.target.getX() + math.sin(angleradiansX)*aspect) 
					self.target.setY(self.target.getY() - math.cos(angleradiansX)*aspect) 
					self.turnCameraAroundPoint(0,0,self.target,self.camDist) 
				if moveX: 
					self.target.setX( self.target.getX() - math.sin(angleradiansX2)*aspect2 ) 
					self.target.setY( self.target.getY() + math.cos(angleradiansX2)*aspect2 ) 
					self.turnCameraAroundPoint( 0,0,self.target,self.camDist ) 
			self.mx = mpos.getX() 
			self.my = mpos.getY() 
		return Task.cont

	########################################
	# Movement TASK
	########################################
	def taskMove(self, task):		
		elapsed = globalClock.getDt()

		anim_move = "run"
		anim_idle = "idle"
		speed = char1.SPD * SPD_MULTIPLIER

		# move/rotate char
		if (self.keyMap["left"]!=0):
			char1.setH(char1.getH() + elapsed*300)
		if (self.keyMap["right"]!=0):
			char1.setH(char1.getH() - elapsed*300)
		if (self.keyMap["forward"]!=0):
			char1.setY(char1, - (elapsed * speed))

		# handle anim
		if (self.keyMap["forward"]!=0) or (self.keyMap["left"]!=0) or (self.keyMap["right"]!=0):
			if char1.isMoving == False:
				char1.loop(anim_move)
				char1.isMoving = True
		else:
			if char1.isMoving:
				char1.stop()
				char1.loop(anim_idle)
				char1.isMoving = False
		return Task.cont

	########################################
	# AI TASK
	########################################
	def taskAI(self,task):
		if char2.isBusy == False:
			charAI(char2, [char1,])

		return Task.cont

###########################################################################
# CHARAI
# Handles AI characters (movement, fighting)
###########################################################################
def charAI(char, targets):
	target = getNearestTgtChar(char, targets)
	distance = getDistance(char.getPos(), target.getPos())

	if distance > 50:
		charMove(char, target.getPos())
	else:
		charIdle(char)

########################################################################################################################
# CHARMOVE
# Tells a character to move towards a position, param for special motion anim, run is default
########################################################################################################################
def charMove(char, endPos=(0,0,0), anim="run"):
	char.isBusy = True
	char.loop(anim)
	elapsed = max(globalClock.getDt(), 1.1)			# at least 1.1 to make foes match player speeds
	time = getDistance(char.getPos(), endPos) / (char.SPD * SPD_MULTIPLIER * elapsed)	# Time = Distance / Speed
	part1 = Func(char.lookAt, char1)
	part2 = char.posInterval(time, endPos)
	part3 = Func(charIdle, char)
	mySequence = Sequence(part1, part2, part3)
	mySequence.start()

###########################################################################
# GETNEARESTTGTCHAR
# Returns closest target char to specified char
###########################################################################
def getNearestTgtChar(char, targets):
	minDistance = pow(10,6)		# a very big number!!
	nearestTarget = ''

	for target in targets:
		if target.isAlive:
			distance = getDistance(char.getPos(), target.getPos() )
			if distance < minDistance:
				minDistance = distance
				nearestTarget = target
	return nearestTarget

########################################################################################################################
# GETDISTANCE
# Calcs distance between two x,y,z coords
########################################################################################################################
def getDistance(pos1=(0,0,0), pos2=(10,10,10)):
	vector = pos1 - pos2
	distance = abs(math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]))
	return distance

########################################################################################################################
# CHARIDLE
# Sets char's isBusy flag back to False
########################################################################################################################
def charIdle(char):
	char.loop("idle")
	char.isBusy = False

w = World()
run()

camera and player movement is basically roaming ralph, my code starts at AITASK.

This line in CHARMOVE:
part1 = Func(char.lookAt, char1)
is what I can’t get to work

(though it worked in the little program above)

Your program is basically correct. It is the model problem, I believe.

A quick fix can be:

   time = getDistance(char.getPos(), endPos) / (char.SPD * SPD_MULTIPLIER * elapsed)   # Time = Distance / Speed
   #part1 = Func(char.lookAt, char1)

   char.lookAt(char1)
   char.setH(char.getH()+180)

   part2 = char.posInterval(time, endPos)
   part3 = Func(charIdle, char)
   #mySequence = Sequence(part1, part2, part3)
   mySequence = Sequence(part2, part3)
   mySequence.start()

Thx that code did the trick.
I’ll keep looking at the model too to see why the code is needed.

Hi, Im the idio…um, artist who had the model facing the wrong way.
Thanks for the help guys, and sorry bout the time-wasting mistake at my end.
I don’t know whether I/we should make all the models face the other way now, or continue as is and use the above code to spin them the right way in game.
Probably best to switch round at source I think. Spinning the models is no big thing, but spinning all the animations I’ve done might be some work…

I should also say, great game engine, thanks for it.

You can turn the model in the code, or using special command line utility that comes with Panda, called egg-trans (located in the “bin” folder of Panda). It can scale, move and rotate models. Call it from the command line with -h parameter to print out the complete help information.
EDIT: Sorry, for animated models you should use egg-optchar.exe utility.

Thank you, that’s very helpful!