Hello, is there a way that the own camera in a 3d game doesn’t goes through file elements (glb files) like a street, terrain etc. I can walk through walls and everything in my 3d game and want to fix that.
I didn’t find a soulution for my problem on the internet so far.
Thanks for your help.
Hi, what you are looking for is called collission detection. It’s described in this part of the manual Collision Detection — Panda3D Manual
By default no object will collide and be kept out of other objects. For that you need to define special collision shapes on your models (streets, terrain, walls, etc) as well as on your camera. For cameras, it’s common to use either rays, spheres or a combination of them dependent on what you try to achieve. In addition you also need to have a handler that controls what happens when a collision occurs. In your case it should reposition the camera to keep out of other models, something like the CollisionHandlerPusher does automatically.
here is my code(sorry it’s a bit chaotical):
(where should I set the collision into the programm?)
from math import pi, sin, cos
from direct.showbase.ShowBase import ShowBase
from panda3d.core import loadPrcFile
from panda3d.core import DirectionalLight, AmbientLight
from panda3d.core import TransparencyAttrib
from panda3d.core import WindowProperties
from panda3d.core import CollisionTraverser, CollisionNode, CollisionBox, CollisionRay, CollisionHandlerQueue
from direct.gui.OnscreenImage import OnscreenImage
from direct.task import Task
from direct.actor.Actor import Actor
from direct.interval.IntervalGlobal import *
from panda3d.core import Point3
from pandac.PandaModules import *
from direct.gui.OnscreenText import OnscreenText
import sys
loadPrcFile (‘settings.prc’)
def degToRad(degrees):
return degrees * (pi / 180)
class MyGame (ShowBase):
def init(self):
ShowBase.init (self)
self.selectedBlockType='grass'
self.loadModels ()
self.createCollisions()
self.generateTerrain ()
self.setupCamera ()
self.setupSkybox ()
self.captureMouse ()
self.setupControls ()
taskMgr.add (self.update, 'update')
def update(self, task):
dt=globalClock.getDt ()
playerMoveSpeed=2000
x_movement=0
y_movement=0
z_movement=0
if self.keyMap['forward']:
x_movement-=dt * playerMoveSpeed * sin (degToRad (camera.getH ()))
y_movement+=dt * playerMoveSpeed * cos (degToRad (camera.getH ()))
if self.keyMap['backward']:
x_movement+=dt * playerMoveSpeed * sin (degToRad (camera.getH ()))
y_movement-=dt * playerMoveSpeed * cos (degToRad (camera.getH ()))
if self.keyMap['left']:
x_movement-=dt * playerMoveSpeed * cos (degToRad (camera.getH ()))
y_movement-=dt * playerMoveSpeed * sin (degToRad (camera.getH ()))
if self.keyMap['right']:
x_movement+=dt * playerMoveSpeed * cos (degToRad (camera.getH ()))
y_movement+=dt * playerMoveSpeed * sin (degToRad (camera.getH ()))
if self.keyMap['up']:
z_movement+=dt * playerMoveSpeed
if self.keyMap['down']:
z_movement-=dt * playerMoveSpeed
camera.setPos (
camera.getX () + x_movement,
camera.getY () + y_movement,
camera.getZ () + z_movement,
)
if self.cameraSwingActivated:
md=self.win.getPointer (0)
mouseX=md.getX ()
mouseY=md.getY ()
mouseChangeX=mouseX - self.lastMouseX
mouseChangeY=mouseY - self.lastMouseY
self.cameraSwingFactor=10
currentH=self.camera.getH ()
currentP=self.camera.getP ()
self.camera.setHpr (
currentH - mouseChangeX * dt * self.cameraSwingFactor,
min (90, max (-90, currentP - mouseChangeY * dt * self.cameraSwingFactor)),
0
)
self.lastMouseX=mouseX
self.lastMouseY=mouseY
return task.cont
def setupControls(self):
self.keyMap={
"forward": False,
"backward": False,
"left": False,
"right": False,
"up": False,
"down": False,
}
self.accept ('escape', self.releaseMouse)
self.accept ('mouse1', self.handleLeftClick)
self.accept ('mouse3', self.handleRightClick)
self.accept ('w', self.updateKeyMap, ['forward', True])
self.accept ('w-up', self.updateKeyMap, ['forward', False])
self.accept ('a', self.updateKeyMap, ['left', True])
self.accept ('a-up', self.updateKeyMap, ['left', False])
self.accept ('s', self.updateKeyMap, ['backward', True])
self.accept ('s-up', self.updateKeyMap, ['backward', False])
self.accept ('d', self.updateKeyMap, ['right', True])
self.accept ('d-up', self.updateKeyMap, ['right', False])
self.accept ('space', self.updateKeyMap, ['up', True])
self.accept ('space-up', self.updateKeyMap, ['up', False])
self.accept ('lshift', self.updateKeyMap, ['down', True])
self.accept ('lshift-up', self.updateKeyMap, ['down', False])
self.accept ('1', self.setSelectedBlockType, ['grass'])
self.accept ('2', self.setSelectedBlockType, ['dirt'])
self.accept ('3', self.setSelectedBlockType, ['sand'])
self.accept ('4', self.setSelectedBlockType, ['stone'])
def setSelectedBlockType(self, type):
self.selectedBlockType=type
def handleRightClick(self):
self.captureMouse ()
# arrow
self.disableMouse ()
# Load the environment model.
self.scene=self.loader.loadModel ("shuriken.glb")
# Reparent the model to render.
self.scene.reparentTo (self.camera)
# Apply scale and position transforms on the model.
self.scene.setScale (1.5, 1.5, 1.5)
self.scene.setPos (0, 0, 0)
self.scene.setHpr (100, 100, 100)
# Add the spinCameraTask procedure to the task manager.
# self.taskMgr.add (self.spinCameraTask, "SpinCameraTask")
# Load and transform the panda actor.
self.pandaActor1=Actor ("shuriken.glb")
self.pandaActor1.setScale (8, 8, 8)
self.pandaActor1.setPos (0, 0, 0)
self.pandaActor1.setHpr (90, 90, 90)
self.pandaActor1.reparentTo (self.camera)
# Loop its animation.
self.pandaActor1.loop ("shuriken.glb")
# Create the four lerp intervals needed for the panda to
# walk back and forth.
posInterval1=self.pandaActor1.posInterval (1,
Point3 (0, 280, 0),
startPos=Point3 (0, 0, 0))
posInterval2=self.pandaActor1.posInterval (1,
Point3 (0, 280, 0),
startPos=Point3 (0, 0, 0))
hprInterval1=self.pandaActor1.hprInterval (1,
Point3 (100, 100, 10),
startHpr=Point3 (0, 0, 0))
hprInterval2=self.pandaActor1.hprInterval (1,
Point3 (10, 10, 10),
startHpr=Point3 (0, 0, 0))
# Create and play the sequence that coordinates the intervals.
self.pandaPace=Parallel (posInterval1, hprInterval1,
posInterval2, hprInterval2,
name="pandaPace")
self.pandaPace=Parallel (posInterval1, hprInterval1,
posInterval2, hprInterval2,
name="pandaPace")
self.pandaPace.loop ()
self.pandaActor1=Actor ("shuriken.glb")
self.pandaActor1.setScale (100, 100, 100)
self.pandaActor1.setPos (-390, -150, -605)
self.pandaActor1.setHpr (0, 90, 90)
self.pandaActor1.reparentTo (self.camera)
# Loop its animation.
self.pandaActor1.loop ("shuriken.glb")
# Create the four lerp intervals needed for the panda to
# walk back and forth.
posInterval1=self.pandaActor1.posInterval (10,
Point3 (0, -90, 0),
startPos=Point3 (0, 39, 0))
posInterval2=self.pandaActor1.posInterval (10,
Point3 (0, 90, 0),
startPos=Point3 (0, -39, 0))
hprInterval1=self.pandaActor1.hprInterval (3,
Point3 (180, 100, 100),
startHpr=Point3 (0, 0, 0))
hprInterval2=self.pandaActor1.hprInterval (3,
Point3 (100, 100, 100),
startHpr=Point3 (180, 0, 100))
# Create and play the sequence that coordinates the intervals.
self.pandaPace=Parallel (posInterval1, hprInterval1,
posInterval2, hprInterval2,
name="pandaPace")
self.pandaPace=Parallel (posInterval1, hprInterval1,
posInterval2, hprInterval2,
name="pandaPace")
def handleLeftClick(self):
self.captureMouse ()
# arrow
self.disableMouse ()
# Load the environment model.
self.scene=self.loader.loadModel ("crossbowb.glb")
# Reparent the model to render.
self.scene.reparentTo (self.camera)
# Apply scale and position transforms on the model.
self.scene.setScale (140, 140, 140)
self.scene.setPos (-20, 79, 12)
self.scene.setHpr (-56, 500, -39)
self.pandaActor1=Actor ("arrow.glb")
self.pandaActor1.setScale (1.5, 1.5, 1.5)
self.pandaActor1.setPos (-390, -250, -65)
self.pandaActor1.setHpr (-37, -170, -270)
self.pandaActor1.reparentTo (self.camera)
# Loop its animation.
self.pandaActor1.loop ("arrow.glb")
# Create the four lerp intervals needed for the panda to
# walk back and forth.
posInterval1=self.pandaActor1.posInterval (1,
Point3 (0, 90, 0),
startPos=Point3 (0, 0, 0))
posInterval2=self.pandaActor1.posInterval (1,
Point3 (0, 90, 0),
startPos=Point3 (0, 0, 0))
hprInterval1=self.pandaActor1.hprInterval (3,
Point3 (90, 0, 0),
startHpr=Point3 (0, 0, 0))
hprInterval2=self.pandaActor1.hprInterval (3,
Point3 (0, 0, 0),
startHpr=Point3 (90, 0, 0))
# Create and play the sequence that coordinates the intervals.
self.pandaPace=Parallel (posInterval1, hprInterval1,
posInterval2, hprInterval2,
name="pandaPace")
self.pandaPace=Parallel (posInterval1, hprInterval1,
posInterval2, hprInterval2,
name="pandaPace")
self.pandaPace.loop ()
self.pandaActor1=Actor ("arrow.glb")
self.pandaActor1.setScale (1.5, 1.5, 1.5)
self.pandaActor1.setPos (-390, -150, -605)
self.pandaActor1.setHpr (-117, -17, -27)
self.pandaActor1.reparentTo (self.camera)
# Loop its animation.
self.pandaActor1.loop ("arrow.glb")
# Create the four lerp intervals needed for the panda to
# walk back and forth.
posInterval1=self.pandaActor1.posInterval (15,
Point3 (0, -90, 0),
startPos=Point3 (0, 39, 0))
posInterval2=self.pandaActor1.posInterval (15,
Point3 (0, 90, 0),
startPos=Point3 (0, -39, 0))
hprInterval1=self.pandaActor1.hprInterval (3,
Point3 (180, 0, 0),
startHpr=Point3 (0, 0, 0))
hprInterval2=self.pandaActor1.hprInterval (3,
Point3 (0, 0, 0),
startHpr=Point3 (180, 0, 0))
# Create and play the sequence that coordinates the intervals.
self.pandaPace=Parallel (posInterval1, hprInterval1,
posInterval2, hprInterval2,
name="pandaPace")
self.pandaPace=Parallel (posInterval1, hprInterval1,
posInterval2, hprInterval2,
name="pandaPace")
def removeBlock(self):
if self.rayQueue.getNumEntries () > 0:
self.rayQueue.sortEntries ()
rayHit=self.rayQueue.getEntry (0)
hitNodePath=rayHit.getIntoNodePath ()
hitObject=hitNodePath.getPythonTag ('owner')
distanceFromPlayer=hitObject.getDistance (self.camera)
if distanceFromPlayer < 12:
hitNodePath.clearPythonTag ('owner')
hitObject.removeNode ()
def placeBlock(self):
if self.rayQueue.getNumEntries () > 0:
self.rayQueue.sortEntries ()
rayHit=self.rayQueue.getEntry (0)
hitNodePath=rayHit.getIntoNodePath ()
normal=rayHit.getSurfaceNormal (hitNodePath)
hitObject=hitNodePath.getPythonTag ('owner')
distanceFromPlayer=hitObject.getDistance (self.camera)
if distanceFromPlayer < 14:
hitBlockPos=hitObject.getPos ()
newBlockPos=hitBlockPos + normal * 2
self.createNewBlock (newBlockPos.x, newBlockPos.y, newBlockPos.z, self.selectedBlockType)
def updateKeyMap(self, key, value):
self.keyMap[key]=value
def captureMouse(self):
self.cameraSwingActivated=True
md=self.win.getPointer (0)
self.lastMouseX=md.getX ()
self.lastMouseY=md.getY ()
properties=WindowProperties ()
properties.setCursorHidden (True)
properties.setMouseMode (WindowProperties.M_relative)
self.win.requestProperties (properties)
def releaseMouse(self):
self.cameraSwingActivated=False
properties=WindowProperties ()
properties.setCursorHidden (False)
properties.setMouseMode (WindowProperties.M_absolute)
self.win.requestProperties (properties)
def setupCamera(self):
self.disableMouse ()
self.camera.setPos (0, 0, 3)
self.camLens.setFov (90)
self.camera.reparentTo(self.render)#
crosshairs=OnscreenImage (
image='crosshairs.png',
pos=(0, 0, 0),
scale=0.05,
)
crosshairs.setTransparency (TransparencyAttrib.MAlpha)
self.cTrav=CollisionTraverser ()
ray=CollisionRay ()
ray.setFromLens (self.camNode, (0, 0))
rayNode=CollisionNode ('line-of-sight')
rayNode.addSolid (ray)
rayNodePath=self.camera.attachNewNode (rayNode)
self.rayQueue=CollisionHandlerQueue ()
self.cTrav.addCollider (rayNodePath, self.rayQueue)
def createCollisions(self):
ray = CollisionRay()
ray.setOrigin(0,0,-.2)
ray.setDirection(0,0,-1)
cn= CollisionNode('playerRay')
cn.addSolid(ray)
cn.setFromCollideMask(BitMask32.bit(0))
cn.setIntoCollideMask(BitMask32.allOff())
def setupSkybox(self):
skybox=loader.loadModel ("skybox.egg")
skybox.setScale (500)
skybox.setBin ('background', 1)
skybox.setDepthWrite (0)
skybox.setLightOff ()
skybox.reparentTo (render)
def generateTerrain(self):
for z in range (1):
for y in range (2):
for x in range (2):
self.createNewBlock (
x * 2 - 20,
y * 2 - 20,
-z * 2,
'stone' if z == 0 else 'stone'
)
def createNewBlock(self, x, y, z, type):
newBlockNode=render.attachNewNode ('new-block-placeholder')
newBlockNode.setPos (x, y, z)
if type == 'grass':
self.grassBlock.instanceTo (newBlockNode)
elif type == 'dirt':
self.dirtBlock.instanceTo (newBlockNode)
elif type == 'sand':
self.sandBlock.instanceTo (newBlockNode)
elif type == 'stone':
self.stoneBlock.instanceTo (newBlockNode)
elif type == 'sword':
self.sword.instanceto (newBlockNode)
blockSolid=CollisionBox ((-1, -1, -1), (1, 1, 1))
blockNode=CollisionNode ('block-collision-node')
blockNode.addSolid (blockSolid)
collider=newBlockNode.attachNewNode (blockNode)
collider.setPythonTag ('owner', newBlockNode)
def physics_update(Task):
dt=globalClock.get_dt ()
self.world.do_physics (dt)
return Task.cont
def loadModels(self):
self.grassBlock=loader.loadModel ('grass-block (2).glb')
self.dirtBlock=loader.loadModel ('dirt-block.glb')
self.stoneBlock=loader.loadModel ('stone-block.glb')
self.sandBlock=loader.loadModel ('sand-block (1).glb')
#garage
self.rock_boulder_1=loader.loadModel ("parking_garage_free_download.glb")
self.rock_boulder_1.setPos(0,0,3)
self.rock_boulder_1.setScale(500,500,500)
self.rock_boulder_1.setHpr(0,90,90)
self.rock_boulder_1.reparentTo (render)
self.rock_col=self.rock_boulder_1.find("**/rock_boulder_1_collide")
def spinCameraTask(self, task):
angleDegrees=task.time * 6.0
angleRadians=angleDegrees * (pi / 180.0)
self.camera.setPos (20 * sin (angleRadians), -20 * cos (angleRadians), 3)
self.camera.setHpr (angleDegrees, 0, 0)
return Task.cont
def setupLights(self):
mainLight=DirectionalLight ('main light')
mainLightNodePath=render.attachNewNode (mainLight)
mainLightNodePath.setHpr (30, -60, 0)
render.setLight (mainLightNodePath)
ambientLight=AmbientLight ('ambient light')
ambientLight.setColor ((0.3, 0.3, 0.3, 1))
ambientLightNodePath=render.attachNewNode (ambientLight)
render.setLight (ambientLightNodePath)
game=MyGame ()
game.run ()
It looks like you’re going for an first person style camera, so I’d recommend adding a collision sphere to it in your setupCamera
function, similar to how you already add the ray.
Then, the most simple way for now is to add a CollisionHandlerPusher
just like you did with the ray queue handler. Add your sphere to the new handler and you should be done.
An example of how to do that for the base camera is already available in the documentation for the CollisionHandlerPusher. So you should be able to copy-paste most of that, you just don’t need to pass the driveInterface since you’re using your own logic to controll the camera.
#garage
self.smiley=loader.loadModel ("parking_garage_free_download.glb")
self.smiley.setPos(0,0,3)
self.smiley.setScale(500,500,500)
self.smiley.setHpr(0,90,90)
self.smiley.reparentTo (self.render)
self.fromObject = self.smiley.attachNewNode(CollisionNode('colNode'))
self.fromObject.node().addSolid(CollisionSphere(0,0,0,1))
self.pusher = CollisionHandlerPusher()
self.pusher.addCollider((self.fromObject, self.smiley))
self.traverser.addCollider(fromObject, pusher)
self.smiley.setPos(x,y,z)
I’ve tried this code for the CollisionHandlerPusher but it doesn’t work.
That’s the message what the computer say’s it’s wrong. I don’t know what’s the meaning of this message in detail only that I should add 1 or 2 more arguments to it.
self.pusher.addCollider((self.fromObject, self.smiley))
TypeError: add_collider() takes 3 or 4 arguments (2 given)
Thanks for your help.
You surrounded the parameters of addCollider with braces making then a tuple instead of two individual values. Just remove one pair so it’s
self.pusher.addCollider(self.fromObject, self.smiley)
Then it should work.
Thank you. This part of the code is now working.
So I’ ll try the rest of my programm.