I have worked a lot the last days to get ground collisions working. But it appears to be to too slow…
I copied the Roaming-Ralph-method, but with more ralphs. I’ve tried CollisionHandlerFloor before with the same results.
With the Roaming-Ralph-Map, which has an own collide mesh*, I have a framerate between 10 an 18 with 8 Ralphs. With a huge map with very simple geometry, but no collision mesh, the framerate is between 0 an 1. In Roaming Ralph, my framerate is between 30 an 45, depending on the current collisions.
- (but i can’t find any tag inside the egg file, as mentioned in the sample, so maybe it checks against visible geometry)
I am using listings for storing the eight Ralphs, and I have a lot of ‘for player in list’ statements, which aren’t very fast, but on the huge map, the framerate is nearly zero, so that isn’t the point here.
You can test the following code inside the Roaming-Ralph-Sample-Folder. The Skydome is from here: http://www.mygamefast.com/volume1/issue3/5/, if you want to use it.
I would be very glad, if anyone can show me a smart, fast way for doing ground collisions with uneven terrain, which maybe contains overhanging cliffs and caves, for a lot of Actors (maybe 50 or 100).
Here is the whole code, but at the moment only the parts with ‘setupCollision’ and ‘setPlayersZ’ are important.
# Catch Me!
# -*- encoding: utf-8 -*-
from direct.showbase.ShowBase import ShowBase
from direct.showbase.PythonUtil import pdir # for debugging
from direct.gui.OnscreenText import OnscreenText
from direct.actor.Actor import Actor
from direct.task import Task
from panda3d.core import Point3, Vec3, Vec4, BitMask32
from panda3d.core import PandaNode, NodePath, Camera, TextNode
from panda3d.core import AmbientLight, DirectionalLight, Fog
from panda3d.core import CollisionTraverser, CollisionNode
from panda3d.core import CollisionHandlerQueue, CollisionRay
from panda3d.ai import AIWorld, AICharacter, AIBehaviors
from pandac.PandaModules import CompassEffect
from random import randrange, choice
## Global Variables ##
# color for the catchers
CATCHCOLOR = Vec4(1, .2, .2, 1)
# used by catchers: Ignore targets, that aren't nearer than this, compared to
# the current target
IGNORE_NEAR = 5
# time in sec. between each check for nearest target
CATCHER_CHECK_TIME = 3.0
# length of the Field of View
CAM_MAXVIEW = 1000
# angle of the Field of View, default is 40
CAM_DEGREE = 60
BMASK_OFF = BitMask32.allOff()
BMASK_GROUND = BitMask32.bit(0)
## helper functions ##
# function to put instructions on the screen.
def addInstructions(line, message):
pos = 1 - (line * .05)
return OnscreenText(text=message, style=1, fg=(1,1,1,1),
pos=(-1.3, pos), align=TextNode.ALeft, scale=.05)
# function to put title on the screen.
def addTitle(text):
return OnscreenText(text=text, style=1, fg=(1,1,1,1),
pos=(1.3,-0.95), align=TextNode.ARight, scale=.07)
## main World class ##
class World(ShowBase):
def __init__ (self):
"""
Setup the World.
"""
ShowBase.__init__(self)
self.keyMap = {} # will be filled later
base.setFrameRateMeter(True) # show framerate
addTitle('Catch Me!')
addInstructions(1, "Just Watch!")
self.actors = {'players':8, 'catchers':1, 'catched':0}
self.world = render.attachNewNode('The World') # the main Node
# calling all setups
self.setupAmbiente()
self.setupCamera()
self.setupMeshes()
self.setupCollisions()
#self.setupKeyMap()
self.setupAi()
self.setupDebug(True)
self.setupUpdates()
def setupAmbiente(self):
"""
Game atmospheric, nothing important here.
"""
# Fog to hide the distance limit
color = (0.73,0.71,0.85)
distance_fog = Fog("distance fog")
distance_fog.setColor(*color)
distance_fog.setLinearRange(200, CAM_MAXVIEW - (CAM_MAXVIEW // 16))
self.world.setFog(distance_fog)
base.setBackgroundColor(*color)
'''
# dome shaped skydome
sky_np = NodePath('Skydome')
sky_np.node().setIntoCollideMask(BMASK_OFF)
skydome = loader.loadModel('models/sky')
skydome.setEffect(CompassEffect.make(self.world))
skydome.setScale(CAM_MAXVIEW + CAM_MAXVIEW // 20)
skydome.setZ(- CAM_MAXVIEW // 16)
skydome.reparentTo(sky_np)
sky_np.reparentTo(self.camera) # NOT to render
'''
# lighting
ambientLight = AmbientLight("ambientLight")
ambientLight.setColor(Vec4(.15, .15, .15, 1))
directionalLight = DirectionalLight("directionalLight")
directionalLight.setDirection(Vec3(0,-10,-10))
directionalLight.setColor(Vec4(.85, .85, .8, 1))
directionalLight.setSpecularColor(Vec4(.2, .2, .2, 1))
self.world.setLight(self.world.attachNewNode(ambientLight))
self.world.setLight(self.world.attachNewNode(directionalLight))
def setupCamera(self):
"""
Adjust the main camera. Currently standart mouse movement.
"""
base.camera.reparentTo(self.world)
self.camLens.setFar(CAM_MAXVIEW)
self.camLens.setFov(CAM_DEGREE)
def setupMeshes(self):
"""
Load and position environment & players (based on self.actors).
Create:
self.catcher_np_list: NodePaths of all catchers, used for moving & AI
self.hunted_np_list: same for the targets
self.catcher_act_list: only for animations!
Else scaling would cause a Bug
self.hunted_act_list: same for the targets
self.players_list: NodePaths of all players
self.players_pos_list: last valid position of each player
"""
# environment
self.envir = loader.loadModel("models/world")
#self.envir.setSz(.6)
self.envir.reparentTo(self.world)
#self.envir.find('**/start_point').getPos()
#self.envir.place()
# standart player
self.player_args = ("models/ralph",
{"run":"models/ralph-run", "walk":"models/ralph-walk"})
# load & pos players
self.catcher_np_list, self.catcher_act_list = \
self._initActors(self.actors["catchers"], 3, CATCHCOLOR)
self.hunted_np_list, self.hunted_act_list = \
self._initActors(self.actors["players"] - self.actors["catchers"])
self.players_list = self.hunted_np_list[:] + self.catcher_np_list
self.players_pos_list = \
[player.getPos() for player in self.players_list]
def _initActors(self, number, area=10,
color=None, scale=0.12, actor_args=None):
"""
Return a tuple of two listings: Actor NodePath and the Actors.
There are randomly positioned in radiant of area and are childs
of self.world. actor_args is a tuple of the arguments that Actor()
needs for creation.
"""
if actor_args is None: actor_args = self.player_args
area = round(area)
np_list = []
actor_list = []
for i in range(number):
np_list.append(NodePath('Player NodePath'))
actor_list.append(Actor(*actor_args))
actor_list[i].setScale(scale)
actor_list[i].setCollideMask(BMASK_OFF)
position = Vec3(i*2 + randrange(-area, area+1),
i*2 + randrange(-area, area+1), 10)
np_list[i].setPos(position)
np_list[i].reparentTo(self.world)
actor_list[i].reparentTo(np_list[i])
if color is not None: actor_list[i].setColor(color)
return (np_list, actor_list)
def setupCollisions(self):
"""
Using self.players_list.
Create:
self.ground_traverser: currently the main Traverser
self.col_np_players_list: ground collision NodePaths for all players
self.col_ground_queues: list of Handlers for ground collisions
"""
# create Traverser
self.ground_traverser = CollisionTraverser()
# create CollisionNodes and Handlers
self.col_np_players_list, self.col_ground_queues = \
self._initGroundCols(self.players_list, BMASK_GROUND, BMASK_OFF)
# add the Handlers to the Traverser
for col_np, handler in zip(self.col_np_players_list,
self.col_ground_queues):
self.ground_traverser.addCollider(col_np, handler)
def _initGroundCols(self, nodePath_list, from_mask, into_mask):
"""
Return a tuple of two listings: a list of all CollisionNodePaths
and a list of the CollsionHandlerQueues.
"""
np_list, handler_list = [], []
for player in nodePath_list:
col_node = CollisionNode('ColNode')
col_node.addSolid(CollisionRay(0, 0, 2, 0, 0, -1))
col_node.setFromCollideMask(from_mask)
col_node.setIntoCollideMask(into_mask)
np_list.append(player.attachNewNode(col_node))
handler_queue = CollisionHandlerQueue()
handler_list.append(handler_queue)
return (np_list, handler_list)
def setupAi(self):
"""
Currently very basic and not compatible with the ground collisions. But
it moves the players for a short time, until there are all stuck.
Create:
self.ai_world_catch: This contains all AI-Characters
"""
# main AI World
self.ai_world_catch = AIWorld(self.world)
# AI Characters of hunted / targets and their empty Behaviors
self.ai_chars_hunted, self.ai_hunted_behaviors = \
self._initAiActors(self.hunted_np_list, 'hunted', speed=8)
# AI Characters of catchers and their empty Behaviors
self.ai_chars_catchers, self.ai_catchers_behaviors = \
self._initAiActors(self.catcher_np_list, 'catcher', speed=18)
# targets evade catchers
for i in range(len(self.hunted_np_list)):
for j in range(len(self.catcher_np_list)):
self.ai_hunted_behaviors[i].evade(self.catcher_np_list[j],
15, 30, .8)
self.ai_hunted_behaviors[i].wander(5, 0, 50, .2)
self.hunted_act_list[i].loop('run')
# catchers pursue their nearest target
for i in range(len(self.catcher_act_list)):
target = choice(self.hunted_np_list)
self.ai_catchers_behaviors[i].pursue(target, .8)
self.ai_catchers_behaviors[i].wander(2, 0, 50, .2)
self.catcher_act_list[i].loop('run')
def _initAiActors(self, char_list, name, mass=60, movt=.05, speed=12):
"""
Return a tuple of two listings. First the AICharacters, second the
AIBehaviors.
"""
ai_list, behav_list = [], []
number = len (char_list)
for i in range(number):
ai_list.append(AICharacter(name,
char_list[i], mass, movt, speed))
self.ai_world_catch.addAiChar(ai_list[i])
behav_list.append(ai_list[i].getAiBehaviors())
#behav_list[i].initPathFind("models/navmesh.csv") # no navmesh yet
return (ai_list, behav_list)
def setupDebug(self, debug_mode=False):
if not debug_mode: return
# show all collisions
self.ground_traverser.showCollisions(render)
for col_np in self.col_np_players_list:
col_np.show()
## Task section ##
def setupUpdates(self):
"""
Setup all Tasks that are known at startup.
"""
taskMgr.add(self.playersUpdate, 'Players Moves')
taskMgr.doMethodLater(CATCHER_CHECK_TIME, self.checkCatcherTarget,
'check nearest Target')
taskMgr.add(self.setPlayersZ, 'earthing')
def playersUpdate(self, task):
"""
Saves the current player positions as valid, then moves all
AI-Characters.
"""
self.players_pos_list = \
[player.getPos() for player in self.players_list]
self.ai_world_catch.update()
return Task.cont
def checkCatcherTarget(self, task):
"""
Check for all catchers: is the current target the nearest target?
Consider IGNORE_NEAR.
Currently a placeholder with random effect.
"""
counter = 0
for catcher in self.catcher_np_list:
current_distance = randrange(1,21) # placeholder
target, new_distance = self.getNearestTarget(catcher)
# need to change the target?
if current_distance + IGNORE_NEAR < new_distance:
self.ai_catchers_behaviors[counter].removeAi('pursue')
self.ai_catchers_behaviors[counter].pursue(target, .8)
counter += 1
return Task.again
def getNearestTarget(self, catcher):
"""
Return a tuple with the nearest target and its distance.
Currently a placeholder with random effect.
"""
target = choice(self.hunted_act_list) # placeholder
new_distance = randrange(6,26) # placeholder
return target, new_distance
def setPlayersZ(self, task):
"""
Update the Z-coordinate for all players. Very slow, need to improved.
"""
self.ground_traverser.traverse(self.world)
for ground_handler, player, player_pos in \
zip(self.col_ground_queues, self.players_list, self.players_pos_list):
entries = []
for i in range(ground_handler.getNumEntries()):
entries.append(ground_handler.getEntry(i))
entries.sort(lambda x,y: cmp(y.getSurfacePoint(render).getZ(),
x.getSurfacePoint(render).getZ()))
if (len(entries)>0) and (entries[0].getIntoNode().getName() == \
"terrain"):
player.setZ(entries[0].getSurfacePoint(render).getZ())
else:
player.setPos(player_pos)
return Task.cont
def setupKeyMap(self):
pass
world = World()
world.run()