Panda3D gamedev tutorial 4

In this tutorial, we will see how to work with simple physics simulations in Panda3D’s built in physics engine. This simulations will be applied in our avatar movement, and ability to jump. Also we will explore briefly, Panda3D’s Interval features.

This tutorial aims to show how to use in a simple way, a physics engine. Just in case you are interested in learn about the use of ODE in Panda, check out this tutorial: http://paulobarbeiro.com.br/blog/?p=77&lang=en, it might be helpful.

Bad news guys! A good amount of all our logic will be lost! Most of all the logic related to the avatar’s movement, described in previous tutorials, will no longer work with physics simulation. I hope that can teach you something: Do not feed emotional feeling to your code! 🙂 And think well if you want to use physics or not, because the code will not be the same!

The work in this tutorial will be a bit long. And to make it easy to understand, we will talk a bit about physics engines, and how to implement them, afterwards, we will implement our code.

Physics Engines ——————-

Physics engines are softwares dedicated to perform physics calculus, usually Newtonian physics, to many different applications, and games is one of them.

These game engines, has one single work: calculate physics. That’s all! Physics engines, do not render screens, do not capture user inputs, do not synthesize audio. They just perform calculus, based on physic formulas.

Each engine has a certain number of formulas, it’s up to you choose the engine based on the features you will need.

So, how they work? how to use them on games?

Till now, in our tutorials, we had change our avatar’s coordinates values in order to simulate it’s movements. To move the avatar towards the right side, we have incremented it’s x position. And this is an absolutely acceptable approach, as far as we do not concern to realistic movements.

Any one can see that our avatar, falls at a constant speed. Which is not a realistic fall. In our universe, gravity speeds up the fall. In order to achieve more realistic results, implement physics simulation is crucial.

So, what change when some physics engine is implemented? We do not touch the object coordinates! When some physics engine is been used, we think about movements as a result of forces been applied to the objects! That means: to an object move towards right, a force, pointing to right, must be applied to this object. And all the jobs is upon the engine. The object movement, will be a result of physics calculus, performed by the engine. Our job will be select, and calibrate the forces that will be applied.

Think in movement as a result of applied forces isn’t hard. But it demands a little knowledge, of how to describe this forces mathematically: Vectors. Everybody knows that! We all studied this subject at the school, is just a matter to remember! This link might help a little bit.

There are a huge number of content about vectors, vector operations, forces, vector forces, on internet, just google it!

Now, we have seen a bit of concepts in physics engines usage. Let’s see, conceptually, how to apply this concepts in our game.

Till now, our avatar moves in three directions: left, right, down. So, to make our avatar move in this directions, by forces, we will have:
– down: gravity force;
– right: a push force towards right;
– left: a push force towards left.

In case we want a jump, a push force towards up, will be needed. See the graph bellow:

grafo05

The graph above, shows the forces we will use, and their vector representation. For example, the gravity: Vec3(0,0,-9.81).
Those vector values, are XYZ “coordinates”. I gravity case: x=0, y=0, z=-9.81; that means, gravity pushed down, with 9.81 intensity (in Newtons).

In this tutorial, all forces are pointing to simple directions. See bellow:
– pushRight: Vec3(10,0,0): points to rightwards (x positive), intensity 10
– pushLeft: Vec3(-10,0,0): points leftwards (x negative), intensity 10
– pushUp: Vec3(0,0,20): point upwards (z positive), intensity 20

In our code, we will implement the basic Panda3D’s built in engine. During this process, we will see, how to implement the basic elements, and logic solution for problems.

Let’s start the first step. In Panda-tut-00.py file, we won’t change it much. In this main file, we just import and initialize the PhysicsCollisionHandler, which will be responsible for collision detections.

[code lang=”python”]
from pandac.PandaModules import loadPrcFileData
loadPrcFileData(”,’show-frame-rate-meter 1′)

import direct.directbase.DirectStart
from pandac.PandaModules import *

from direct.task.Task import Task
from direct.showbase.DirectObject import DirectObject

from avatar import Avatar

#game class
class Game(DirectObject):
def __init__(self):
#init collider system
traverser = CollisionTraverser()
base.cTrav = traverser
base.cTrav.setRespectPrevTransform(True)

base.pusher = CollisionHandlerPusher()
base.pusher.addInPattern(“%fn-into-%in”)
base.pusher.addOutPattern(“%fn-out-%in”)

base.physics = PhysicsCollisionHandler()
base.physics.addInPattern(“%fn-into-%in”)
base.physics.addOutPattern(“%fn-out-%in”)
base.enableParticles()

#load platform model
self.plataforma = loader.loadModel(“../assets/eggs/plataformaBase”)
self.plataforma.reparentTo(render)
self.plataforma.setPos(0,10,0)
#load background sky
self.wall = loader.loadModel(‘../assets/eggs/papelParede’)
self.wall.reparentTo(render)
self.wall.setScale(1)
self.wall.setPos(-5, 30, -2)
#avatar
avatar = Avatar()
avatar.setCollision(traverser)

game = Game()
run()
[/code]

Pay attention that in this code, we just add the lines 24, 25, 26, and 27. The PhysicsCollisionHandler works just like the other handlers, the mayor difference is that it requires the particle system activation (line 27). The CollisionHandlerPusher, will not work here, but I left those line, for you to compare handler pusher with handler physics.

Now in Avatar class, there ar lots of changes! The code is presented below, and than the explanations.

avatar class code
[code lang=”python”]
from direct.showbase.DirectObject import DirectObject
from direct.actor.Actor import Actor
from direct.task import Task
from pandac.PandaModules import *
from direct.interval.IntervalGlobal import *

#modulo da classe Avatar
class Avatar(DirectObject):
def __init__(self):
self.persona = render.attachNewNode(‘persona’)

self.personaActorNode = ActorNode(‘personaActorNode’)
self.personaActorNode.getPhysicsObject().setMass(35)
self.personaActorNP = self.persona.attachNewNode(self.personaActorNode)
base.physicsMgr.attachPhysicalNode(self.personaActorNode)

self.personaActor = Actor(‘../assets/eggs/personagem.egg’,
{‘idle’:’../assets/eggs/personagem-parado’,
‘run’ :’../assets/eggs/personagem-correr’,
‘jump’:’../assets/eggs/personagem-pular’}
)
self.personaActor.setScale(1)
self.personaActor.setPos(0,0,5)
self.personaActor.reparentTo(self.personaActorNP)

self.persona.setPos(0,10,10)
self.persona.setScale(.15)
self.persona.setH(90)

self.state = { ‘left’ :False,
‘right’ :False,
‘ground’:False,
‘canJump’ :False
}

taskMgr.add(self.movement, “Avatar Movement”)

#timer for jump
self.startCallforJump = 0
self.timer = LerpFunc(self.performJump,
fromData = 0,
toData =1,
duration = 0.25,
blendType=’easeIn’,
extraArgs=[],
name=None)

#capture keyboard events
self.accept(‘arrow_left’, self.changeState, [‘left’,True])
self.accept(‘arrow_left-up’, self.changeState, [‘left’,False])
self.accept(‘arrow_right’, self.changeState, [‘right’,True])
self.accept(‘arrow_right-up’, self.changeState, [‘right’,False])
self.accept(‘space’, self.changeState,[‘jump’,True])
self.accept(‘space-up’, self.changeState,[‘jump’,False])
#capture collision events
self.accept(‘bola0CN-into-plataforma’, self.changeState, [‘ground’,True ])
self.accept(‘bola0CN-out-plataforma’, self.changeState , [‘ground’,False])

#Persona physics forces
self.pushUp=LinearVectorForce(0,0,20)
self.pushUpFN = ForceNode(‘pushup-force’)
self.pushUpFNP = render.attachNewNode(self.pushUpFN)
self.pushUpFN.addForce(self.pushUp)

self.pushLeft=LinearVectorForce(-10,0,0)
self.pushLeftFN = ForceNode(‘pushleft-force’)
self.pushLeftFNP = render.attachNewNode(self.pushLeftFN)
self.pushLeftFN.addForce(self.pushLeft)

self.pushRight=LinearVectorForce(10,0,0)
self.pushRightFN = ForceNode(‘pushright-force’)
self.pushRightFNP = render.attachNewNode(self.pushRightFN)
self.pushRightFN.addForce(self.pushRight)

self.gravityFN=ForceNode(‘world-forces’)
self.gravityFNP=render.attachNewNode(self.gravityFN)
self.gravityForce=LinearVectorForce(0,0,-9.81) #gravity acceleration
self.gravityFN.addForce(self.gravityForce)

base.physicsMgr.addLinearForce(self.gravityForce)

def setCollision(self, trav):
#colision nodes and solids
self.ball0 = CollisionSphere(0,0,1.5,1.5)
self.ball0NP = self.personaActorNP.attachNewNode(CollisionNode(‘bola0CN’))
self.ball0NP.node().addSolid(self.ball0)
self.ball0NP.show()

base.physics.addCollider(self.ball0NP, self.personaActorNP)
base.cTrav.addCollider(self.ball0NP, base.physics)

def movement(self, task):
#print self.personaActorNode.getPhysical(0).getLinearForces()
if(self.state[‘left’] == True):
if(len(self.personaActorNode.getPhysical(0).getLinearForces()) == 0):
self.personaActorNode.getPhysical(0).addLinearForce(self.pushLeft)
else:
self.personaActorNode.getPhysical(0).removeLinearForce(self.pushLeft)

if(self.state[‘right’] == True):
if(len(self.personaActorNode.getPhysical(0).getLinearForces()) == 0):
self.personaActorNode.getPhysical(0).addLinearForce(self.pushRight)
else:
self.personaActorNode.getPhysical(0).removeLinearForce(self.pushRight)

return Task.cont

def changeState(self, key, value, col=None):
if(self.state[‘canJump’] == True and key == ‘jump’):
if(value==True):
self.startCallForJump = globalClock.getRealTime()
if(value == False):
preparationTime = globalClock.getRealTime() – self.startCallForJump
if (preparationTime > 0.4): preparationTime = 0.4
elif(preparationTime 0):
self.personaActorNode.getPhysical(0).removeLinearForce(self.pushUp)
[/code]

Lets start in function __init__(self):

The first change here is to add an ActorNode to our avatar’s nodes structure. The ActoNode class is a special node class to be used in physics system. Do not confuse ActorNode with Actor class, which is used to play animation in rigged models.
First we create an ActoNode (line 5), than it’s mass is set; The ActorNode is attached to persona node, and the nodepath resulted is attached to physics handler.
Pay attention to another important detail, in line 17: the personaActor node (our model node), is attached to the ActorNode. This link will make the model move according physics simulations.

In lines 39 and 40, there is one variable and one function, that are gonna be used for avatar’s jump. More about it soon.
In lines 53 and 54, we capture the user input in space bar.

In lines 60 till 80, the forces to be applied in our avatar are set. In line 71, one of the forces is been applied, the gravity.

The forces are created as a vector force, the class LinearVectorForce, that contains the force direction and intensity. Also a ForceNode, attached to render, is needed.

The setCollision function:
Not much changes here. Note that now, collisionNodes are linked to PhysicsCollisionNode.

The movement function:
Here we have the logic that moves the avatar to left or right. Basically, the avatar state changes with user input, than this function applies the forces according this state changes. The methods addLinearForce() and removeLinearForce(), are pretty much self-explanatory, are they?

The changeState function:
In this function there is a special logic to avatar’s jump.
The avatar will perform the jump, when user releases the space bar, and the power of the jump, will vary depending on the time the user held the bar pressed.
When space bar is pressed the function evaluates if the avatar can jump (canJump = if the avatar is on the platform). Than the function evaluates if the key were pressed or released; If the key was pressed, the function stores the moment when the key was pressed; If the key was released, the function calculates the time it was pressed, than this value divides a value of 1000, than the result of such division is applied to the pushUp force, and finally a timer, that applies and removes, is started.

The performJump function:
This is a LerpFunction declared in line 40. This function is a sort of timer, that is used to apply the force, and in the next moment, remove it. A human jump, just like any other terrestrials animals, are resulting of one strong force application in one single moment. That is the reason the pushUp force must be removed so soon.

Conclusion??
Well, a simple conclusion?? You don’t need to use physics simulation if the resulting movement is not pleasing you! Physics is just another option! But, get yourself used to them. As more you know them, and better you apply the forces, better the results will look!

Leave a Reply

Your email address will not be published. Required fields are marked *