Collision Pusher First Person View

Hi, I’m a noob at programming and panda3d so bear with me.
I’m trying to create a first-person game where the player can swing a sword around. The problem I am encountering is that I can’t figure out how to handle the collision with the wall.
Right now I have an object called flyBot that represents the player. Both the camera and the sword are parented to it. I detect the collision between the walls and flyBot using CollisionHandlerPusher, but it still seems to go through the walls.
The function appears to be working as intended since when I am going sideways, it doesn’t go through the wall, but when I go forward or backward, weird things happen. Any suggestions on how to deal with this problem? I have tried using the sword as the parent, but then when the sword swings, the camera will also swing around.

Here’s my code for reference.

# This function is in my world class
    def wallCollisions(self):
        # Pusher so that the player cannot move past the walls
        self.pusher = CollisionHandlerPusher()
        self.cTrav.addCollider(self.flyBot.flyBotCollider, self.pusher)
        self.pusher.addCollider(self.flyBot.flyBotCollider, self.flyBot.flyBot, base.drive.node())

class FlyBot(DirectObject):
    def __init__(self):
        self.flyBot = base.loader.loadModel('Version_1/flyBot')
        self.flyBot.setScale(1, 1, 1)
        self.flyBot.setPos(0, 0, 4)
        self.flyBot.setHpr(0, 0, 0)
        self.flyBot.reparentTo(render)
        
        self.flyBotCollider = self.flyBot.find('**/flyBotCollider')
        
        self.playerCollide = CollisionSphere(0, 0, 0, 2)
        self.playerNode = CollisionNode('player')
        self.playerNode.addSolid(self.playerCollide)
        self.playerNode.setFromCollideMask(CollideMask.bit(0))
        self.playerNode.setIntoCollideMask(CollideMask.allOff())
        self.playerCollider = self.flyBot.attachNewNode(self.playerNode)
        # self.flyBotHandler = CollisionHandlerQueue()
        self.playerCollider.show()

Any help will be great appreciated. Thank you all in advance!

Looking at your code, I note that when you call “addCollider”, you seem to be passing into it the “flyBotCollider” object, which comes from within your model. However, when you construct your “flyBot”, you seem to also create a CollisionSphere–but you don’t seem to pass this object to the pusher or to “base.cTrav”.

Do you want to use both colliders? If so, what do you intend for them each to do?

And if you do want to use the “flyBotCollider” object that’s loaded with your model, then, presuming that it’s a polygonal mesh, note that Panda doesn’t allow polygonal collision objects to be used as “from” objects, only “into” objects, if I’m not much mistaken.

(See this page for more information, specifically the description of “CollisionPolygon” and, at the end, the table showing which collision-shapes may collide with which others.)

Lastly, I note that you’ve passed in “base.drive” when calling “addCollider”–are you actually using Panda’s “drive mode”?

I intend to use the one that is loaded with the model and it made it a sphere in the egg file. My problem is that the camera is fixed with my flybot but somehow it sees through the wall although it works fine if I move horizontally.
To be honest, I’m not sure what the drive mode is and can’t seem to find a good source that explains it - I just modified my code based on the examples. I am moving the character with wsad keys.

Thank you for your help!

Just to check: Do you mean that it’s a model of a sphere, composed of polygons, or that you’ve specified a CollisionSphere in the egg file? If the former, then I think that it’ll still be a polygonal object, and thus likely to not work as you seem to intend. If the latter, then fair enough!

Given that you intend to use that object, why are you creating a second sphere?

Ah, wait–do you mean that the camera sees through the wall, but that you can’t move through it, or that you can actually move through the wall?

If you can only see through the wall, then the problem is likely the “near” setting of your camera–this page gives some information on the topic, I believe.

I think that it’s Panda’s default camera controller–but I very much stand to be corrected.

More to the point, if you’re not intentionally using it, then I suggest removing the “base.drive.node()” parameter from your call to “addCollider” (thus leaving both calls to “addCollider” with only two parameters). It’s not required, I believe.

Also, just to check: If you don’t intend to use Panda’s default camera-control, are you calling “base.disableMouse()”, likely near the start of your program, in order to disable that default camera-control? Don’t worry, despite the name it doesn’t disable your mouse–instead, it disables the mouse-based camera-controller that Panda otherwise automatically employs. This default camera-controller can interfere with your own camera-control code, so unless you actually want to use it, I highly recommend disabling it.

Yes, I meant that I specified a CollisionSphere in the egg file, although I have removed that one so I am just using the one I created with code right now as I realized I don’t need it.

That seems to be the problem. I’ll look into the page!

I did call base.disableMouse() and removed the base.drive.node(). Thank you so much!

I have encountered a new problem. If I call fireballHitPlayer, collisions with walls no longer work. I think this is due to having the same collisiontraverser so it overwrites? I read the manual and it said I would need to call the self-made collisiontraverser. How would I do that?

Thank you!

# home
import os, sys, inspect, thread, time
sys.path.insert(0, "/Users/Hoshizora/Desktop/112_TP/LeapSDK/lib")

import Leap
from Leap import CircleGesture, KeyTapGesture, ScreenTapGesture, SwipeGesture
from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from direct.showbase.DirectObject import DirectObject
from direct.gui.DirectGui import DirectFrame, DirectLabel, DirectButton
from direct.gui.OnscreenText import OnscreenText
from panda3d.core import *
from direct.task.Task import Task
from panda3d.core import CollisionHandlerQueue, CollisionHandlerEvent, CollisionHandlerPusher
from panda3d.core import CollisionNode, CollideMask, CollisionTraverser
from panda3d.core import CollisionRay
from direct.fsm.FSM import FSM

import player
import environment
import enemy
import leapmotion
import menu

class GameRun(FSM):
    def __init__(self):
        FSM.__init__(self, 'GameRun')
        self.request('Menu')
    
    def enterMenu(self):
        self.accept("Menu-Start", self.request, ["Game"])
        self.accept("Menu-Quit", self.quit)
        self.menu.show()
    
    def exitMenu(self):
        self.ignore("Menu-Start")
        self.ignore("Menu-Quit")
        self.menu.hide()




class Game(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        # import leap motion
        self.leap = leapmotion.LeapMotion()
        # disable mouse control
        base.disableMouse()

        # Load the map.
        self.environ = self.loader.loadModel("models/backgroundCollide2")
        # Reparent the model to render.
        self.environ.reparentTo(self.render)
        # Apply scale and position transforms on the model.
        myTexture = loader.loadTexture("models/backgroundtexture.jpg")
        self.environ.setTexGen(TextureStage.getDefault(), TexGenAttrib.MWorldPosition)
        self.environ.setTexture(myTexture)
        self.environ.setScale(1, 1, 1)
        self.environ.setPos(0, 0, 0)
        self.environ.reparentTo(render)

        # import flybot, enemy, and player
        self.flyBot = FlyBot()
        self.player = player.Player(self.flyBot)
        # self.numbEnemies = 0
        self.enemies = [enemy.Enemy(2)]
        
        
        # display player stats
        self.display = self.player.displayStats()
        
        
        # run collision settings
        self.cTrav = CollisionTraverser()
        # Set up collision for environment
        self.backgroundCollide = CollisionRay()
        self.backgroundNode = CollisionNode('backgroundCollider')
        self.backgroundNode.addSolid(self.backgroundCollide)
        self.backgroundNode.setFromCollideMask(CollideMask.bit(0))
        self.backgroundNode.setIntoCollideMask(CollideMask.allOff())
        self.backgroundCollider = self.environ.attachNewNode(self.backgroundNode)
        self.environHandler = CollisionHandlerQueue()

        # call all functions that deal with collisions
        # taskMgr.add(self.swordHitEnemy, 'killEnemy')
        self.wallCollisions()
        # taskMgr.add(self.fireballHitPlayer, 'fireballHits')
        # show collision-debugging purposes
        self.cTrav.showCollisions(render)
        
        
        # run quitGame
        self.quitGame()
        
        # Set up initial camera position
        base.camera.setPos(0, 0, 0)
        base.camera.setHpr(0, 0, 0)
        base.camera.reparentTo(self.flyBot.flyBot)
        # import first person view from player file
        self.cameraControl = player.cameraControl(self.flyBot)
        
        # import leap motion control
        # self.leapControl = player.swordControl(self.leap, self.player)
        # taskMgr.add(self.leapControl.swingsword, 'swingsword')
        
        # spawn new enemies
        # taskMgr.doMethodLater(10, self.spawnEnemies, 'createNewEnemy')
        
        # test enemy movements
        # taskMgr.add(self.test, 'test')
        
        # Game AI
        AI = GameAI(self.enemies, self.flyBot)
        # taskMgr.add(AI.checkGameState, 'checkGameState')
        # taskMgr.add(AI.makeDecision, 'enemyStateDetermination')
    
        taskMgr.add(self.update, 'update')
    
    def quitGame(self):
        textObject = OnscreenText(text = 'ESC to exit game', pos=(-0.25, -0.05), scale = 0.05, parent = base.a2dTopRight)
        # The key esc will exit the game
        self.accept('escape', sys.exit)
    
    def spawnEnemies(self, task):
        # self.numbEnemies += 1
        if len(self.enemies) < 10:
            # default state is stay
            newEnemy = enemy.Enemy(3)
            self.enemies.append(newEnemy)
        task.delayTime += 1
        return task.again
    

    def test(self, task):
        for enemy in self.enemies:
            enemy.lookAtPlayer(self.flyBot)
            enemy.move(self.flyBot)
        return task.cont
    
    def update(self, task):
        print(self.flyBot.flyBot.getX(), self.flyBot.flyBot.getY())
        return task.cont
    
    # Collision functions, called in init
    '''
    fix bug!!! --> player moves when this function is called?? don't know why
    '''
    def wallCollisions(self):
        # Pusher so that the player cannot move past the walls
        self.pusher = CollisionHandlerPusher()
        base.cTrav.addCollider(self.flyBot.playerCollider, self.pusher)
        self.pusher.addCollider(self.flyBot.playerCollider, self.flyBot.flyBot)
    
    '''
    fix bug!!! kills all enemy
    '''
    def swordHitEnemy(self, task):
        # When the sword collides with enemy, enemy dies
        for enemy in self.enemies:
            playerKill = CollisionHandlerEvent()
            self.cTrav.addCollider(enemy.collider, playerKill)
            playerKill.addInPattern('%fn-into-%in')
    
        # perform the task
            kill = DirectObject()
            kill.accept('enemy-into-swordCollider', enemy.killEnemy)
            if not enemy.isAlive:
                self.enemies.remove(enemy)
        return task.cont
    
    '''
    fix bug!!! doesn't do damage with multiple enemies?? don't know why
    '''
    def fireballHitPlayer(self, task):
        for enemy in self.enemies:
            if enemy.fireballFired.isLive:
                cTrav = CollisionTraverser()
                # When fireball collides with player, player health reduces
                playerDamage = CollisionHandlerEvent()
                playerDamage.addInPattern('%fn-into-%in')
                
                cTrav.addCollider(self.flyBot.playerCollider, playerDamage)
                
                # perform the tasks
                damagePlayer = DirectObject()
                damagePlayer.accept('player-into-FireballCollider', self.receiveDamage)
                
                self.fireballRemove = DirectObject()
                self.fireballRemove.accept('player-into-FireballCollider', enemy.fireballFired.removeFireball(task))
                
                return task.done

    # reduce health in player's stats and removes fireball
    def receiveDamage(self, task):
        # remove the display screen
        self.player.displayText.removeNode()
        # create new display screen with new stats
        self.player.health -= 5
        self.player.displayStats()


# allow camera and player to move independently
class FlyBot(DirectObject):
    def __init__(self):
        self.flyBot = base.loader.loadModel('models/flyBot')
        self.flyBot.setScale(1, 1, 1)
        self.flyBot.setPos(0, 0, 4)
        self.flyBot.setHpr(0, 0, 0)
        self.flyBot.reparentTo(render)
        self.moved = False
                
        self.playerCollide = CollisionSphere(0, 0, 0, 2)
        self.playerNode = CollisionNode('player')
        self.playerNode.addSolid(self.playerCollide)
        self.playerNode.setFromCollideMask(CollideMask.bit(0))
        self.playerNode.setIntoCollideMask(CollideMask.allOff())
        self.playerCollider = self.flyBot.attachNewNode(self.playerNode)
        # self.flyBotHandler = CollisionHandlerQueue()
        # self.playerCollider.show()

# Game AI
class GameAI(DirectObject):
    def __init__(self, enemies, flyBot):
        self.enemies = enemies
        self.flyBot = flyBot
        self.state = ['moveToPlayer', 'moveToChest', 'stay']
    
    # check the state of the game
    def checkGameState(self, task):
        self.movingToPlayer = 0
        self.movingToChest = 0
        self.stay = 0
        self.around = 0
        for enemy in self.enemies:
            # count how many enemeis are in each state
            if enemy.state == 0:
                self.movingToPlayer += 1
            elif enemy.state == 2:
                self.movingToChest += 1
            elif enemy.state == 2:
                self.stay += 1
            # check if the enemy is around the player
            if self.aroundPlayer(self.flyBot, enemy):
                self.around += 1
        # print 'state:', (self.movingToPlayer, self.stay, self.around, len(self.enemies))
        return task.cont
    
    '''
    add chest part when it goes to that
    '''
    # determine state of the enemy
    def makeDecision(self, task):
        for enemy in self.enemies:
            # print enemy, enemy.state
            # if enemy is around the player, move to the player
            if self.aroundPlayer(self.flyBot, enemy):
                enemy.state = 0
            # if less than three enemies are moving to player and less than two around player and is not moving, set the enemy state to moving to player
            elif self.movingToPlayer < 3 and self.around < 2 and enemy.state == 2:
                enemy.state = 0
            # or else, stop the enemy from moving
            else:
                enemy.state = 3
            # print enemy, enemy.state
        return task.cont

    # check if enemy is within 10 unit radius of the player
    def aroundPlayer(self, flyBot, enemy):
        player = flyBot.flyBot
        enemy = enemy
        if enemy.isAlive:
            pX = player.getX()
            pY = player.getY()
            eX = enemy.enemyModel.getX()
            eY = enemy.enemyModel.getY()
            
            deltaX = eX - pX
            deltaY = eY - pY
            
            return ((deltaX)**2 + (deltaY)**2)**0.5 <= 100




app = Game()
app.run()```

I’m glad that you seem to be making progress. :slight_smile:

As to your new problem, hmm…

First of all, what are you trying to do in this method? The name seems to suggest that it’s intended to be called when a fireball-object collides with the player–but the actual contents seem to suggest that it’s intended to set up collisions for fireballs.

That said, indeed, you seem to be setting up a new traverser for each fireball, not to mention a new collision-handler, and new DirectObjects–all of which seems very superfluous.

At this point, perhaps it might be simpler for me to refer you to a tutorial that I made. It shows and explains the creation of a full (if simple) game in Panda3D–including collision-handling. If you’re interested, you should find it at the following link:


(The links in the table of contents may or may not work properly, but those at the top of each page and the “next” links in each lesson should work. I recommend starting from the beginning anyway, so that you get the fundamentals.)