Why CollisionSphere isn't following the player (camera object)?

I’m stuying the code here Lesson 6 | Panda3D Beginner’s Tutorial and the example code works nicely. The player with collision sphere is created and the sphere’s position updates as the player moves around.

When I used the code and adjusted to my use case sphere was created but it’s not attached
to the player which in my case is the camera object. I’m making FPS view.

from direct.showbase.ShowBase import ShowBase
from panda3d.core import (loadPrcFile, Point2, Point3, WindowProperties,
                        CollisionHandlerQueue, CollisionHandlerPusher,
                        CollisionNode, CollisionPolygon, CollisionTraverser,
                          CollisionSphere)
import sys
loadPrcFile('conf.prc')


class FloorPlan(ShowBase):

    def __init__(self):
        super().__init__()
        self.cam.setPos(0, 0, -5)
        #self.disableMouse()
        props = WindowProperties()
        props.setCursorHidden(True)
        props.setMouseMode(WindowProperties.M_relative)
        self.win.requestProperties(props)
        self.path = self.render.attach_new_node("player")
        base.cam.reparentTo(self.path)
        base.cam.setZ(1.85)
        base.camLens.set_fov(100, 100)
        self.setBackgroundColor(0, 0, 0, 1)
        self.house = self.loader.loadModel('./floorplan.glb')
        self.house.setPos(0, 0, 0)
        self.house.setH(self.house, 90)
        self.house.reparentTo(self.render)
        self.prev_mouse = Point2(0)
        self.accept('q', self.quit_program)
        self.cTrav = CollisionTraverser()
        self.pusher = CollisionHandlerPusher()

        cnodePath = self.camera.attachNewNode(CollisionNode('collisionNode'))
        cnodePath.node().addSolid(CollisionSphere(self.camera.getPos(), 4.0))
        queue = CollisionHandlerQueue()
        cnodePath.show()
        pusher = CollisionHandlerPusher()
        pusher.addCollider(cnodePath, self.camera)
        wallNode = CollisionNode('wall')
        wallNode.addSolid(CollisionPolygon(Point3(0, -10, 0), Point3(5, -5, 0),
                                           Point3(0, -5, 5), Point3(5, -5, 5)))

        self.cTrav.addCollider(cnodePath, queue)
        self.taskMgr.add(self.update_keys, 'update-keys')

    def update_keys(self, task):
        if base.mouseWatcherNode.has_mouse():
            mouse = base.mouseWatcherNode.get_mouse()
            button = getattr(base.mouseWatcherNode, 'is_raw_button_down', base.mouseWatcherNode.is_button_down)
            delta = mouse - self.prev_mouse
            self.prev_mouse = Point2(mouse)
            self.path.setH(self.path, -delta.x * 15)
            base.cam.set_p(base.cam, delta.y * 15)
            base.cam.set_p(max(-85, min(base.cam.get_p(), 85)))
            speed = (8 + int(button("lshift") * 4)) * base.clock.dt
            self.path.setPos(self.path,
                ((int(button("d") - int(button("a"))) * speed)),
                ((int(button("w") - int(button("s"))) * speed)), 0)

        return task.cont

    def quit_program(self):
        sys.exit(0)


game = FloorPlan()
game.run()

As you can see it’s pretty much the same code. Amended to fit my scene.
I admit I’m little frustrated when I see example code to learn from but it’s not working in my use case.

Your feedback is much appreciated.

Hmm… Well, one thing that I notice is that you’ve set the sphere’s centre-point to be the position of the camera. However, if the camera is not at (0, 0, 0) at that point, then the sphere will be offset from centre.

Still, it should move with the camera, even if offset…

So, do I take it correctly that the sphere is sitting still while you move the camera?

If so, could you please show me the code that handles movement?

And, just to check, you’re not changing the camera somewhere, are you?

Hello Thaumaturge,
I’ve updated my code. It’s now showing my complete code. It’s not a lot.

And yes, the sphere just sits in the centre of the scene instead of moving around with the camera.

Ah, I think that I’ve found the problem: you’re attaching the sphere to “self.camera”, but you’re moving “self.cam”.

(To explain: “self.cam” is a child-node of “self.camera”. As a result, the sphere is ending up as a “sibling” of “self.cam”, and thus when you move “self.cam” the sphere isn’t being moved with it.)

If you were to either attach the sphere to “self.cam” or move “self.camera” you should find that the sphere moves along with the player, I believe.

1 Like

I believe I got it working. At first the sphere wouldn’t show because of the angle of the view in relation to the sphere. But then I played with the Z position of the sphere and I could see it. So it is working. Lovely ! Thank you for your input !

1 Like

Hi, I was wondering if you could explain how come your collision tubes prevent the player from going through ? I’m using similar code but I’m using collision tubes but collision polygon to make walls impenetrable in my 3D scene. In my case my collisions are detected but my player is allowed to go through.

It’s hard to say exactly without looking at your collision -geometry and -code, but there are a few general points that might be involved in your issue:

  • CollisionPolygons can never be “from”-colliders
    • That is, they can only be “passive” colliders, having other objects collide “into” them
    • Or, put another way, calling “traverser.addCollider(someCollider, someCollisionHandler)” is more or less ineffectual when “someCollider” uses CollisionPolygons for its solid, I daresay.
  • CollisionPolygons may be less reliable than other solids
    • This is at least in part because, unlike such solids as CollisionCapsules and CollisionSpheres, they have no real volume–even arranged as a tube, they’re just thin walls around non-colliding emptiness. As a result, fast-moving objects may “skip past” them.
  • CollisionPolygons face a specific direction
    • That is, they have a single “front”, with one set of normals
    • This means that objects approaching from the “back” may end up popping to the front as a result of collisions, rather than being stopped.

If I may ask, why are you not using a CollisionCapsule? Is the difference of the two end-cap spheres really unworkable in your project…?

If a CollisionCapsule might be made to work in your situation, I suspect that you’ll find it far less troublesome overall…

Let me show you my code:

from direct.showbase.ShowBase import ShowBase
from panda3d.core import (loadPrcFile, Point2, Point3, WindowProperties,
                        CollisionHandlerPusher, CollisionNode, CollisionPolygon,
                        CollisionTraverser, CollisionSphere)
import sys
loadPrcFile('conf.prc')


class FloorPlan(ShowBase):

    def __init__(self):
        super().__init__()
        self.camera.setPos(0, 0, -5)
        self.wall1Detected = False
        # self.disableMouse()
        props = WindowProperties()
        props.setCursorHidden(True)
        props.setMouseMode(WindowProperties.M_relative)
        self.win.requestProperties(props)
        self.path = self.render.attach_new_node("player")
        base.camLens.set_fov(100, 100)
        base.camera.reparentTo(self.path)
        self.setBackgroundColor(0, 0, 0, 1)
        self.house = self.loader.loadModel('./floorplan.glb')
        self.house.setPos(0, 0, -2)
        self.house.setH(self.house, 90)
        self.house.reparentTo(self.render)
        self.prev_mouse = Point2(0)
        self.accept('q', self.quit_program)
        self.cTrav = CollisionTraverser()
        self.pusher = CollisionHandlerPusher()
        self.pusher.add_in_pattern('%fn-into-%in')
        cp = CollisionPolygon(Point3(-9, -1.0, 0),
                                    Point3(-8.5, -1.0, 2),
                                    Point3(-8.5, 4.6, 2),
                                    Point3(-8.5, 4.6, 0))
                                    # y, x, z
        wallNode = CollisionNode('wall1')
        wallNode.addSolid(cp)
        self.wall1Node = self.house.attachNewNode(wallNode)

        self.playerNode = self.camera.attachNewNode(CollisionNode('playerNode'))

        # THE COLLISION SPHERE IS SURROUNDING THE PLAYER (THE CAMERA OBJECT)
        # HOWEVER THE PLAYER IS IN SUCH ANGLE IN RELATION TO THE SPHERE
        # THAT HE CAN'T SEE THE SPHERE SURROUNDING HIM DESPITE .show() IS ENABLED.
        # LOWERING THE SPHERE ON Z AXIS WILL HELP YOU TO SEE IT.

        self.playerNode.node().addSolid(CollisionSphere(self.camera.getPos()[0],
                                                  self.camera.getPos() [1],
                                                  self.camera.getPos()[2]+4, .9))

        ### FOR DEBUGGING ###
        # self.playerNode.show()
        # self.wall1Node.show()
        # ----------------#

        self.accept('playerNode-into-wall1', self.switch_wall_1)
        self.pusher.addCollider(self.playerNode, self.camera)
        self.cTrav.addCollider(self.playerNode, self.pusher)
        self.playerNode.setPythonTag('owner', self)
        self.taskMgr.add(self.update_keys, 'update-keys')

    def switch_wall_1(self, entry):
        #print(self.playerNode.getPos())
        #print(entry)
        collider = entry.getFromNodePath()
        if collider.hasPythonTag('owner'):
            self.wall1Detected = True
            print('------------------------')
            print('COLLIDED')
            print(self.path.getPos()[0])
            print(self.path.getPos()[1])

    def update_keys(self, task):
        #self.switch_wall_1('###')
        if base.mouseWatcherNode.has_mouse():
            mouse = base.mouseWatcherNode.get_mouse()
            button = getattr(base.mouseWatcherNode, 'is_raw_button_down',
                             base.mouseWatcherNode.is_button_down)
            delta = mouse - self.prev_mouse
            self.prev_mouse = Point2(mouse)
            self.path.setH(self.path, -delta.x * 15)
            base.cam.set_p(base.cam, delta.y * 15)
            base.cam.set_p(max(-85, min(base.cam.get_p(), 85)))
            speed = (8 + int(button("lshift") * 4)) * base.clock.dt
            if not self.wall1Detected:
                self.path.setPos(self.path,
                                 ((int(button("d") - int(button("a"))) * speed)),
                                 ((int(button("w") - int(button("s"))) * speed)),
                                 0)
            else:
                if (self.path.getPos()[0] < -4.6 and self.path.getPos()[1] < -9.6) \
                    or (self.path.getPos()[0] > 1 and self.path.getPos()[1] < -9.6) \
                    or (self.path.getPos()[0] < -2.2 and self.path.getPos()[1] > -8.26) \
                    or (self.path.getPos()[0] < -2.2 and self.path.getPos()[1] > -8.26):
                    self.wall1Detected = False
                else:
                    self.path.setPos(self.path,
                                     ((int(button("d") - int(
                                         button("a"))) * speed)),
                                     ((int(button("#") - int(
                                         button("s"))) * speed)),
                                     0)
                    print(self.path.getPos())

        return task.cont

    def quit_program(self):
        sys.exit(0)


game = FloorPlan()
game.run()

The above code works. 1 wall is impenetrable but I don’t like the implementation. I don’t believe there isn’t more elegant way of doing it.

Thank you for your feedback.

Can capsules work on 3D walls ?

Ah, I see! As I read your earlier post, I thought that you were constructing a tube of CollisionPolygons, where in fact you’re just making polygonal walls.

Which is pretty much normal, I think.

What don’t you like about it?

I misunderstood your previous post: I thought that you were doing this for an “active” collider, such as a player-character.

To answer your question, capsules could theoretically be used for walls, but–save in unusual cases I daresay–I doubt that it’s the best way.

(Depending on your walls, you could perhaps use CollisionBoxes, however.)

Yeah I’ve thought of CollisionBoxes too. It sounds sensible since walls are pretty much boxes as well. I wanted second opinion so thank you.

1 Like

What I don’t like about my current code is that I’m using crazy if - else statement to determine whether or not player should be allowed to go forward. To press W key.

Oh wow, yeah, I see–I hadn’t even really looked at that bit. (I was looking at the construction of your collision geometry, I believe.)

So… What is the purpose of this code? What does it do that CollisionHandlerPusher doesn’t already do…?

If I comment out the crazy if - else statement player can get through the walls. I find it interesting that in your code you’re not handling this in any way which makes me think Panda is preventing your player from going through walls using traverser or pusher.

Ah, I think that I may see the problem there:

Looking at your code, you’re moving the player via a NodePath called “path”, which also contains the model that represents the player.

However, when you add your player-collider to the CollisionHandlerPusher, you’re passing to it the NodePath named “self.camera”, which is a child of “path”. Thus the CollisionHandlerPusher is presumably attempting to resolve collisions by pushing “self.camera” out of the things being collided with (in this case, the walls).

Now, this isn’t entirely unfair, because the player’s collision-object is itself a child of “self.camera”, and thus the collision-object should indeed be being moved out of the walls.

But conversely, doing so doesn’t actually move the “path” out of the walls, and as a result the player’s in-logic position and rendered model will freely continue on.

What I’d suggest, then, is that when you add your player-collider to your CollisionHandlerPusher, instead of passing it “self.camera”, you pass it “path”.

I’m travelling now so I’ll look into it later. What’s your nick on Panda’s discord ?
I really admire how you’re able to diagnose the problem so quickly. Is it experience with Panda that got you that far ?

I don’t have one: I’m not on the Discord, I’m afraid.

Thank you! I appreciate that! :slight_smile:

I suppose that it’s a mixture of things, including long experience with Panda, and with programming in general. (I’ve been doing this for a while now!)