class PandaController(object): DEFAULT_FULLSCREEN = False DEFAULT_WIDTH = 800 DEFAULT_HEIGHT = 600 DEFAULT_FPS = 60 DEFAULT_FRAME_METER = False DEFAULT_MUSIC_VOLUME = .3 DEFAULT_SOUND_VOLUME = .1 DEFAULT_MAX_DELTA = 1. / 20. DEFAULT_SHADERS = True def __init__(self): super(PandaController, self).__init__() self._timer = Timer() self._timer.max_delta = self.DEFAULT_MAX_DELTA self._tasks = task.TaskGroup() self._tasks.add(self._panda_task) self._music = None self._mouse_task = None self._relative_mouse = False @property def timer(self): return self._timer @property def tasks(self): return self._tasks def start(self, title): cfg = GlobalConf().child('panda') self.set_defaults(cfg) self.base = ShowBase() self.base.disableMouse() self.audio = self.base.sfxManagerList[0] self.audio3d = Audio3DManager(self.audio, camera) self.audio3d.setListenerVelocityAuto() self.audio3d.setDropOffFactor(0.1) # HACK self.create_properties(title) self.update_properties(cfg) self.listen_conf(cfg) loadPrcFileData("", "interpolate-frames 1") loadPrcFileData("", "support-threads #f") path = getModelPath() path.prependPath('./data') self.base.enableParticles() def loop(self): self._timer.reset() self._timer.loop(self._loop_fn) def _loop_fn(self, timer): task_count = 1 # _panda_task if self._relative_mouse: task_count += 1 # _mouse_task if self._tasks.count > task_count: return self._tasks.update(timer) return False def set_defaults(self, cfg): cfg.child('fps').default(self.DEFAULT_FPS) cfg.child('width').default(self.DEFAULT_WIDTH) cfg.child('height').default(self.DEFAULT_HEIGHT) cfg.child('fullscreen').default(self.DEFAULT_FULLSCREEN) cfg.child('frame-meter').default(self.DEFAULT_FRAME_METER) cfg.child('music-volume').default(self.DEFAULT_MUSIC_VOLUME) cfg.child('sound-volume').default(self.DEFAULT_SOUND_VOLUME) def listen_conf(self, cfg): cfg.on_conf_nudge += self.update_properties cfg.child('fps').on_conf_change += self.update_fps cfg.child('frame-meter').on_conf_change += self.update_frame_meter cfg.child('music-volume').on_conf_change += self.update_music_volume cfg.child('sound-volume').on_conf_change += self.update_sound_volume self.audio.setVolume(cfg.child('sound-volume').value) def create_properties(self, title): self._prop = WindowProperties() self._prop.setTitle(title) def relative_mouse(self): if not self._relative_mouse: self._prop.setCursorHidden(True) self._prop.setMouseMode(WindowProperties.MRelative) self.base.win.requestProperties(self._prop) self._mouse_task = self._tasks.add(MouseTask()) self._mouse_task.on_mouse_move += lambda x, y: \ messenger.send ('mouse-move', [(x, y)]) self._relative_mouse = True def absolute_mouse(self): if self._relative_mouse: self._relative_mouse = False if self._mouse_task: self._mouse_task.kill() self._prop.setCursorHidden(False) self._prop.setMouseMode(WindowProperties.MAbsolute) self.base.win.requestProperties(self._prop) def has_shaders(self): return self.base.win.getGsg().getSupportsBasicShaders() == 0 def update_properties(self, cfg): self._prop.setSize(cfg.child('width').value, cfg.child('height').value) self._prop.setFullscreen(cfg.child('fullscreen').value) self.base.win.requestProperties(self._prop) self._timer.fps = cfg.child('fps').value self.base.setFrameRateMeter(cfg.child('frame-meter').value) def update_frame_meter(self, cfg): self.base.setFrameRateMeter(cfg.value) def update_fps(self, cfg): self._timer.fps = cfg.value def update_music_volume(self, cfg): if self._music: self._music.setVolume(cfg.value) def update_sound_volume(self, cfg): if self.audio: self.audio.setVolume(cfg.value) def _panda_task(self, timer): taskMgr.step() return task.running def set_background_color(self, *color): base.setBackgroundColor(*color) def loop_music(self, file): if self._music: self._music.setLoop(False) self.tasks.add( task.sequence( task.linear(self._music.setVolume, self._music.getVolume(), 0.0))) volume = GlobalConf().path('panda.music-volume').value self._music = loader.loadSfx(file) self._music.setLoop(True) self.tasks.add( task.sequence( task.linear(self._music.setVolume, 0.0, volume, init=True))) self._music.play()
class PandaController (object): DEFAULT_FULLSCREEN = False DEFAULT_WIDTH = 800 DEFAULT_HEIGHT = 600 DEFAULT_FPS = 60 DEFAULT_FRAME_METER = False DEFAULT_MUSIC_VOLUME = .3 DEFAULT_SOUND_VOLUME = .1 DEFAULT_MAX_DELTA = 1. / 20. DEFAULT_SHADERS = True def __init__ (self): super (PandaController, self).__init__ () self._timer = Timer () self._timer.max_delta = self.DEFAULT_MAX_DELTA self._tasks = task.TaskGroup () self._tasks.add (self._panda_task) self._music = None self._mouse_task = None self._relative_mouse = False @property def timer (self): return self._timer @property def tasks (self): return self._tasks def start (self, title): cfg = GlobalConf ().child ('panda') self.set_defaults (cfg) self.base = ShowBase () self.base.disableMouse () self.audio = self.base.sfxManagerList [0] self.audio3d = Audio3DManager (self.audio, camera) self.audio3d.setListenerVelocityAuto () self.audio3d.setDropOffFactor (0.1) # HACK self.create_properties (title) self.update_properties (cfg) self.listen_conf (cfg) loadPrcFileData ("", "interpolate-frames 1") loadPrcFileData ("", "support-threads #f") path = getModelPath () path.prependPath ('./data') self.base.enableParticles () def loop (self): self._timer.reset () self._timer.loop (self._loop_fn) def _loop_fn (self, timer): task_count = 1 # _panda_task if self._relative_mouse: task_count += 1 # _mouse_task if self._tasks.count > task_count: return self._tasks.update (timer) return False def set_defaults (self, cfg): cfg.child ('fps').default (self.DEFAULT_FPS) cfg.child ('width').default (self.DEFAULT_WIDTH) cfg.child ('height').default (self.DEFAULT_HEIGHT) cfg.child ('fullscreen').default (self.DEFAULT_FULLSCREEN) cfg.child ('frame-meter').default (self.DEFAULT_FRAME_METER) cfg.child ('music-volume').default (self.DEFAULT_MUSIC_VOLUME) cfg.child ('sound-volume').default (self.DEFAULT_SOUND_VOLUME) def listen_conf (self, cfg): cfg.on_conf_nudge += self.update_properties cfg.child ('fps').on_conf_change += self.update_fps cfg.child ('frame-meter').on_conf_change += self.update_frame_meter cfg.child ('music-volume').on_conf_change += self.update_music_volume cfg.child ('sound-volume').on_conf_change += self.update_sound_volume self.audio.setVolume (cfg.child ('sound-volume').value) def create_properties (self, title): self._prop = WindowProperties () self._prop.setTitle (title) def relative_mouse (self): if not self._relative_mouse: self._prop.setCursorHidden (True) self._prop.setMouseMode (WindowProperties.MRelative) self.base.win.requestProperties (self._prop) self._mouse_task = self._tasks.add (MouseTask ()) self._mouse_task.on_mouse_move += lambda x, y: \ messenger.send ('mouse-move', [(x, y)]) self._relative_mouse = True def absolute_mouse (self): if self._relative_mouse: self._relative_mouse = False if self._mouse_task: self._mouse_task.kill () self._prop.setCursorHidden (False) self._prop.setMouseMode (WindowProperties.MAbsolute) self.base.win.requestProperties (self._prop) def has_shaders (self): return self.base.win.getGsg().getSupportsBasicShaders() == 0 def update_properties (self, cfg): self._prop.setSize (cfg.child ('width').value, cfg.child ('height').value) self._prop.setFullscreen (cfg.child ('fullscreen').value) self.base.win.requestProperties (self._prop) self._timer.fps = cfg.child ('fps').value self.base.setFrameRateMeter (cfg.child ('frame-meter').value) def update_frame_meter (self, cfg): self.base.setFrameRateMeter (cfg.value) def update_fps (self, cfg): self._timer.fps = cfg.value def update_music_volume (self, cfg): if self._music: self._music.setVolume (cfg.value) def update_sound_volume (self, cfg): if self.audio: self.audio.setVolume (cfg.value) def _panda_task (self, timer): taskMgr.step () return task.running def set_background_color (self, *color): base.setBackgroundColor (*color) def loop_music (self, file): if self._music: self._music.setLoop (False) self.tasks.add (task.sequence ( task.linear (self._music.setVolume, self._music.getVolume (), 0.0))) volume = GlobalConf ().path ('panda.music-volume').value self._music = loader.loadSfx (file) self._music.setLoop (True) self.tasks.add (task.sequence ( task.linear (self._music.setVolume, 0.0, volume, init = True))) self._music.play ()
class Fountain(object): def __init__(self): self.base = ShowBase() self.thrust = 0.5 self.wind = 0.2 self.UP = Vec3(0, 0, 1) # might as well just make this a variable # set up camera self.base.disableMouse() self.base.camera.setPos(20, -20, 5) self.base.camera.lookAt(0, 0, 5) # Set up the collision traverser. If we bind it to base.cTrav, then Panda will handle # management of this traverser (for example, by calling traverse() automatically for us once per frame) self.base.cTrav = CollisionTraverser() # Now let's set up some collision bits for our masks self.ground_bit = 1 self.ball_bit = 2 self.base.setBackgroundColor(0.64, 0, 0) # First, we build a card to represent the ground cm = CardMaker('ground-card') cm.setFrame(-60, 60, -60, 60) card = self.base.render.attachNewNode(cm.generate()) card.lookAt(0, 0, -1) # align upright #tex = loader.loadTexture('maps/envir-ground.jpg') tex = loader.loadTexture('models/textures/rock12.bmp') card.setTexture(tex) # Then we build a collisionNode which has a plane solid which will be the ground's collision # representation groundColNode = card.attachNewNode(CollisionNode('ground-cnode')) groundColPlane = CollisionPlane(Plane(Vec3(0, -1, 0), Point3(0, 0, 0))) groundColNode.node().addSolid(groundColPlane) # Now, set the ground to the ground mask groundColNode.setCollideMask(BitMask32().bit(self.ground_bit)) # Why aren't we adding a collider? There is no need to tell the collision traverser about this # collisionNode, as it will automatically be an Into object during traversal. # enable forces self.base.enableParticles() node = NodePath("PhysicsNode") node.reparentTo(self.base.render) # may want to have force dependent on mass eventually, # but at the moment assume all balls same weight self.force_mag = 200 # gravity gravity_fn = ForceNode('world-forces') gravity_fnp = self.base.render.attachNewNode(gravity_fn) gravity_force = LinearVectorForce(0.0, 0.0, -9.81) gravity_fn.addForce(gravity_force) self.base.physicsMgr.addLinearForce(gravity_force) # wind wind_fn = ForceNode('world-forces') wind_fnp = self.base.render.attachNewNode(wind_fn) wind_force = LinearVectorForce(1.0, 0.5, 0.0) wind_fn.addForce(wind_force) self.base.physicsMgr.addLinearForce(wind_force) # spurt out of fountain, bounce self.spurt = ForceNode("spurt") spurt_np = self.base.render.attachNewNode(self.spurt) # create a list for our ball actors, not sure if I need this, but seems likely self.ball_actors = [] # make a teapot teapot = loader.loadModel('teapot.egg') tex = loader.loadTexture('maps/color-grid.rgb') #teapot.setTexGen(TextureStage.getDefault(), TexGenAttrib.MWorldPosition) teapot.setTexture(tex) teapot.reparentTo(self.base.render) teapot.setPos(-5, 10, 10) # create the first ball: #ball = self.create_a_ball() #self.enliven_ball(ball) smiley = loader.loadModel('smiley.egg') lerper = NodePath('lerper') smiley.setTexProjector(TextureStage.getDefault(), NodePath(), lerper) smiley.reparentTo(self.base.render) smiley.setPos(5, 10, 10) i = lerper.posInterval(5, VBase3(0, 1, 0)) i.loop() # Tell the messenger system we're listening for smiley-into-ground messages and invoke our callback self.base.accept('ball_cnode-into-ground-cnode', self.ground_callback) ball_fountain = taskMgr.doMethodLater(.5, self.spurt_balls, 'tickTask') # tasks #self.frameTask = self.base.taskMgr.add(self.frame_loop, "frame_loop") #self.frameTask.last = 0 def frame_loop(self, task): # Make more balls! dt = task.time - task.last task.last = task.time # self.move_ball(dt) return task.cont # This task increments itself so that the delay between task executions # gradually increases over time. If you do not change task.delayTime # the task will simply repeat itself every 2 seconds def spurt_balls(self, task): print "Delay:", task.delayTime print "Frame:", task.frame # task.delayTime += 1 # for i in range(5): ball = self.create_a_ball() self.enliven_ball(ball) return task.again def move_ball(self, dt): pass #x, y, z = self.ball.getPos() #print x, y, z #print self.actor_node.getPhysicsObject().getPosition() # rotate ball #prevRot = LRotationf(self.ball.getQuat()) #axis = self.UP.cross(self.ballV) #newRot = LRotationf(axis, 45, 5 * dt) #self.ball.setQuat(prevRot + newRot) #print x, y, z #z += dt * self.thrust #x += dt * self.wind #y += dt * self.wind #self.ball.setPos(x, y, z) def remove_force(self, actor, force, task): print('remove force', force) actor.getPhysical(0).removeLinearForce(force) self.spurt.removeForce(force) return task.done def apply_spurt(self, actor_node, force=None): print 'spurt' print('force', force) #print actor_node # really want this to remember its previous state with that # particular ball, and reduce the force from then by some amount. if force is None: # force = LinearVectorForce(0.0, 0.0, self.force_mag) force = LinearVectorForce(0.0, 0.0, self.force_mag) self.spurt.addForce(force) print('new', force) #self.force_mag -= 15 else: force.getAmplitude() # apply the spurt actor_node.getPhysical(0).addLinearForce(force) print('added force', self.spurt.getForce(0).getVector(actor_node.getPhysicsObject())[2]) #print force, actor_node #for child in self.base.render.getChildren(): # print child # set a task that will clear this force a short moment later self.base.taskMgr.doMethodLater(0.01, self.remove_force, 'removeForceTask', extraArgs=[actor_node, force], appendTask=True) print 'set do method later' def ground_callback(self, entry): '''This is our ground collision message handler. It is called whenever a collision message is triggered''' print 'callback' # Get our parent actor_node ball_actor_node = entry.getFromNodePath().getParent().node() # Why do we call getParent? Because we are passed the CollisionNode during the event and the # ActorNode is one level up from there. Our node graph looks like so: # - ActorNode # + ModelNode # + CollisionNode # add bounce! self.apply_spurt(ball_actor_node) def create_a_ball(self): print 'new ball' ball = self.base.loader.loadModel('smiley') texture = self.base.loader.loadTexture('models/textures/rock12.bmp') #ball.setTexture(texture) #tex = loader.loadTexture('maps/noise.rgb') ball.setPos(0, 0, 0) ball.reparentTo(self.base.render) ball.setTexture(texture, 1) return ball def enliven_ball(self, ball): # create an actor node for the balls ball_actor = self.base.render.attachNewNode(ActorNode("ball_actor_node")) # choose a random color #ball_actor.setColor(random.random(), random.random(), random.random(), random.random()) self.create_ball_coll(ball_actor) # apply forces to it self.base.physicsMgr.attachPhysicalNode(ball_actor.node()) #spurt_force = LinearVectorForce(0.0, 0.0, self.force_mag) #spurt_force.setMassDependent(True) #self.spurt.addForce(spurt_force) #self.spurt.addForce() self.apply_spurt(ball_actor.node()) ball.reparentTo(ball_actor) self.ball_actors.append(ball_actor) def create_ball_coll(self, ball_actor): # Build a collisionNode for this smiley which is a sphere of the same diameter as the model ball_coll_node = ball_actor.attachNewNode(CollisionNode('ball_cnode')) ball_coll_sphere = CollisionSphere(0, 0, 0, 1) ball_coll_node.node().addSolid(ball_coll_sphere) # Watch for collisions with our brothers, so we'll push out of each other ball_coll_node.node().setIntoCollideMask(BitMask32().bit(self.ball_bit)) # we're only interested in colliding with the ground and other smileys cMask = BitMask32() cMask.setBit(self.ground_bit) cMask.setBit(self.ball_bit) ball_coll_node.node().setFromCollideMask(cMask) # Now, to keep the spheres out of the ground plane and each other, let's attach a physics handler to them ball_handler = PhysicsCollisionHandler() # Set the physics handler to manipulate the smiley actor's transform. ball_handler.addCollider(ball_coll_node, ball_actor) # This call adds the physics handler to the traverser list # (not related to last call to addCollider!) self.base.cTrav.addCollider(ball_coll_node, ball_handler) # Now, let's set the collision handler so that it will also do a CollisionHandlerEvent callback # But...wait? Aren't we using a PhysicsCollisionHandler? # The reason why we can get away with this is that all CollisionHandlerXs are inherited from # CollisionHandlerEvent, # so all the pattern-matching event handling works, too ball_handler.addInPattern('%fn-into-%in') return ball_coll_node
from pandac.PandaModules import * loadPrcFile('/c/Users/Brian/Documents/panda3d/Panda3D-CI/etc/Config.prc') loadPrcFileData('', 'default-model-extension .egg') from direct.showbase.ShowBase import ShowBase base = ShowBase() from direct.distributed.ClientRepository import ClientRepository from lib.coginvasion.suit import Suit base.enableParticles() from lib.coginvasion.suit.CogStation import CogStation from lib.coginvasion.dna.DNAParser import * import __builtin__ class game: process = 'client' __builtin__.game = game() from lib.coginvasion.toon import ParticleLoader, Toon from lib.coginvasion.hood.DistributedGagShop import DistributedGagShop from direct.gui.DirectGui import * from direct.interval.IntervalGlobal import * from lib.coginvasion.globals import CIGlobals base.cr = ClientRepository(['astron/direct.dc']) base.cr.isShowingPlayerIds = False base.cTrav = CollisionTraverser() vfs = VirtualFileSystem.getGlobalPtr() vfs.mount(Filename("phase_0.mf"), ".", VirtualFileSystem.MFReadOnly) vfs.mount(Filename("phase_3.mf"), ".", VirtualFileSystem.MFReadOnly)
class Fountain(object): def __init__(self): self.base = ShowBase() self.thrust = 0.5 self.wind = 0.2 self.UP = Vec3(0, 0, 1) # might as well just make this a variable # set up camera self.base.disableMouse() self.base.camera.setPos(20, -20, 5) self.base.camera.lookAt(0, 0, 5) # Set up the collision traverser. If we bind it to base.cTrav, then Panda will handle # management of this traverser (for example, by calling traverse() automatically for us once per frame) self.base.cTrav = CollisionTraverser() # Now let's set up some collision bits for our masks self.ground_bit = 1 self.ball_bit = 2 self.base.setBackgroundColor(0.64, 0, 0) # First, we build a card to represent the ground cm = CardMaker('ground-card') cm.setFrame(-60, 60, -60, 60) card = self.base.render.attachNewNode(cm.generate()) card.lookAt(0, 0, -1) # align upright #tex = loader.loadTexture('maps/envir-ground.jpg') tex = loader.loadTexture('models/textures/rock12.bmp') card.setTexture(tex) # Then we build a collisionNode which has a plane solid which will be the ground's collision # representation groundColNode = card.attachNewNode(CollisionNode('ground-cnode')) groundColPlane = CollisionPlane(Plane(Vec3(0, -1, 0), Point3(0, 0, 0))) groundColNode.node().addSolid(groundColPlane) # Now, set the ground to the ground mask groundColNode.setCollideMask(BitMask32().bit(self.ground_bit)) # Why aren't we adding a collider? There is no need to tell the collision traverser about this # collisionNode, as it will automatically be an Into object during traversal. # enable forces self.base.enableParticles() node = NodePath("PhysicsNode") node.reparentTo(self.base.render) # may want to have force dependent on mass eventually, # but at the moment assume all balls same weight self.force_mag = 200 # gravity gravity_fn = ForceNode('world-forces') gravity_fnp = self.base.render.attachNewNode(gravity_fn) gravity_force = LinearVectorForce(0.0, 0.0, -9.81) gravity_fn.addForce(gravity_force) self.base.physicsMgr.addLinearForce(gravity_force) # wind wind_fn = ForceNode('world-forces') wind_fnp = self.base.render.attachNewNode(wind_fn) wind_force = LinearVectorForce(1.0, 0.5, 0.0) wind_fn.addForce(wind_force) self.base.physicsMgr.addLinearForce(wind_force) # spurt out of fountain, bounce self.spurt = ForceNode("spurt") spurt_np = self.base.render.attachNewNode(self.spurt) # create a list for our ball actors, not sure if I need this, but seems likely self.ball_actors = [] # make a teapot teapot = loader.loadModel('teapot.egg') tex = loader.loadTexture('maps/color-grid.rgb') #teapot.setTexGen(TextureStage.getDefault(), TexGenAttrib.MWorldPosition) teapot.setTexture(tex) teapot.reparentTo(self.base.render) teapot.setPos(-5, 10, 10) # create the first ball: #ball = self.create_a_ball() #self.enliven_ball(ball) smiley = loader.loadModel('smiley.egg') lerper = NodePath('lerper') smiley.setTexProjector(TextureStage.getDefault(), NodePath(), lerper) smiley.reparentTo(self.base.render) smiley.setPos(5, 10, 10) i = lerper.posInterval(5, VBase3(0, 1, 0)) i.loop() # Tell the messenger system we're listening for smiley-into-ground messages and invoke our callback self.base.accept('ball_cnode-into-ground-cnode', self.ground_callback) ball_fountain = taskMgr.doMethodLater(.5, self.spurt_balls, 'tickTask') # tasks #self.frameTask = self.base.taskMgr.add(self.frame_loop, "frame_loop") #self.frameTask.last = 0 def frame_loop(self, task): # Make more balls! dt = task.time - task.last task.last = task.time # self.move_ball(dt) return task.cont # This task increments itself so that the delay between task executions # gradually increases over time. If you do not change task.delayTime # the task will simply repeat itself every 2 seconds def spurt_balls(self, task): print "Delay:", task.delayTime print "Frame:", task.frame # task.delayTime += 1 # for i in range(5): ball = self.create_a_ball() self.enliven_ball(ball) return task.again def move_ball(self, dt): pass #x, y, z = self.ball.getPos() #print x, y, z #print self.actor_node.getPhysicsObject().getPosition() # rotate ball #prevRot = LRotationf(self.ball.getQuat()) #axis = self.UP.cross(self.ballV) #newRot = LRotationf(axis, 45, 5 * dt) #self.ball.setQuat(prevRot + newRot) #print x, y, z #z += dt * self.thrust #x += dt * self.wind #y += dt * self.wind #self.ball.setPos(x, y, z) def remove_force(self, actor, force, task): print('remove force', force) actor.getPhysical(0).removeLinearForce(force) self.spurt.removeForce(force) return task.done def apply_spurt(self, actor_node, force=None): print 'spurt' print('force', force) #print actor_node # really want this to remember its previous state with that # particular ball, and reduce the force from then by some amount. if force is None: # force = LinearVectorForce(0.0, 0.0, self.force_mag) force = LinearVectorForce(0.0, 0.0, self.force_mag) self.spurt.addForce(force) print('new', force) #self.force_mag -= 15 else: force.getAmplitude() # apply the spurt actor_node.getPhysical(0).addLinearForce(force) print( 'added force', self.spurt.getForce(0).getVector(actor_node.getPhysicsObject())[2]) #print force, actor_node #for child in self.base.render.getChildren(): # print child # set a task that will clear this force a short moment later self.base.taskMgr.doMethodLater(0.01, self.remove_force, 'removeForceTask', extraArgs=[actor_node, force], appendTask=True) print 'set do method later' def ground_callback(self, entry): '''This is our ground collision message handler. It is called whenever a collision message is triggered''' print 'callback' # Get our parent actor_node ball_actor_node = entry.getFromNodePath().getParent().node() # Why do we call getParent? Because we are passed the CollisionNode during the event and the # ActorNode is one level up from there. Our node graph looks like so: # - ActorNode # + ModelNode # + CollisionNode # add bounce! self.apply_spurt(ball_actor_node) def create_a_ball(self): print 'new ball' ball = self.base.loader.loadModel('smiley') texture = self.base.loader.loadTexture('models/textures/rock12.bmp') #ball.setTexture(texture) #tex = loader.loadTexture('maps/noise.rgb') ball.setPos(0, 0, 0) ball.reparentTo(self.base.render) ball.setTexture(texture, 1) return ball def enliven_ball(self, ball): # create an actor node for the balls ball_actor = self.base.render.attachNewNode( ActorNode("ball_actor_node")) # choose a random color #ball_actor.setColor(random.random(), random.random(), random.random(), random.random()) self.create_ball_coll(ball_actor) # apply forces to it self.base.physicsMgr.attachPhysicalNode(ball_actor.node()) #spurt_force = LinearVectorForce(0.0, 0.0, self.force_mag) #spurt_force.setMassDependent(True) #self.spurt.addForce(spurt_force) #self.spurt.addForce() self.apply_spurt(ball_actor.node()) ball.reparentTo(ball_actor) self.ball_actors.append(ball_actor) def create_ball_coll(self, ball_actor): # Build a collisionNode for this smiley which is a sphere of the same diameter as the model ball_coll_node = ball_actor.attachNewNode(CollisionNode('ball_cnode')) ball_coll_sphere = CollisionSphere(0, 0, 0, 1) ball_coll_node.node().addSolid(ball_coll_sphere) # Watch for collisions with our brothers, so we'll push out of each other ball_coll_node.node().setIntoCollideMask(BitMask32().bit( self.ball_bit)) # we're only interested in colliding with the ground and other smileys cMask = BitMask32() cMask.setBit(self.ground_bit) cMask.setBit(self.ball_bit) ball_coll_node.node().setFromCollideMask(cMask) # Now, to keep the spheres out of the ground plane and each other, let's attach a physics handler to them ball_handler = PhysicsCollisionHandler() # Set the physics handler to manipulate the smiley actor's transform. ball_handler.addCollider(ball_coll_node, ball_actor) # This call adds the physics handler to the traverser list # (not related to last call to addCollider!) self.base.cTrav.addCollider(ball_coll_node, ball_handler) # Now, let's set the collision handler so that it will also do a CollisionHandlerEvent callback # But...wait? Aren't we using a PhysicsCollisionHandler? # The reason why we can get away with this is that all CollisionHandlerXs are inherited from # CollisionHandlerEvent, # so all the pattern-matching event handling works, too ball_handler.addInPattern('%fn-into-%in') return ball_coll_node