def __init__(self, parent: NodePath): super().__init__() self.parent = parent self.mouse_np = p3d.camera.attach_new_node(PandaNode('mouse')) self.mouse_np.set_y(p3d.lens.get_near()) picker_node = CollisionNode('mouse_ray') picker_np = p3d.camera.attach_new_node(picker_node) self._picker_ray = CollisionRay() picker_node.add_solid(self._picker_ray) self._collision_handler = CollisionHandlerQueue() self._traverser = CollisionTraverser('mouse_traverser') self._traverser.add_collider(picker_np, self._collision_handler) self.actor = Actor( resource_filename('tsim', 'data/models/cursor'), {'spin': resource_filename('tsim', 'data/models/cursor-spin')}) self.actor.loop('spin') self.actor.reparent_to(parent) self.actor.set_pos(0.0, 0.0, 0.0) self.actor.set_shader_off() self._position = Point(0.0, 0.0) self.last_position = self._position self.moved = False self.pointed_at = None self._tool: Optional[Tool] = None self._register_events()
def __init__(self, pos, radius, dest): """Setup this portal.""" #Setup model self.model = loader.load_model("./data/models/scenery/portal/portal") self.model.set_pos(*pos) #Y is up in the map data self.model.set_scale(radius, radius, radius) try: texture = loader.load_texture( os.path.join("./data/maps", dest, "portal.png")) self.model.set_texture(texture, 1) except IOError: Logger.warning("No portal texture for map '{}'.".format(dest)) self.model.reparent_to(base.world_mgr.scenery_np) #Setup collision detection cnode = CollisionNode("portal") cnode.add_solid(CollisionSphere(0, 0, 0, 1)) self.collider = self.model.attach_new_node(cnode) self.collider.set_python_tag("object", self) base.cTrav.add_collider(self.collider, base.portal_handler) #Store desination self.dest = dest
def __init__(self, map, color, node): self.map = map self.color = color self.node = node col = CollisionNode(node.name) col.add_solid(CollisionSphere((0, 0, 0), 1)) col.set_collide_mask(base.itemmask) self.col_node = self.map.collisions.attach_new_node(col) self.col_node.set_pos(node.get_pos()) self.col_node.set_z(node, 1)
def setup_ray(node, traverser, bitmask, point_a=(0, 0, 1), point_b=(0, 0, 0)): ray = CollisionSegment(point_a, point_b) col = CollisionNode(node.getName() + "-ray") col.add_solid(ray) col.set_from_collide_mask(bitmask) col.set_into_collide_mask(CollideMask.all_off()) col_node = node.attach_new_node(col) handler = CollisionHandlerQueue() traverser.add_collider(col_node, handler) return {"collider": col, "ray": ray, "handler": handler, "node": col_node}
def __initCollisions(self, name): self.notify.debug("Initializing collision sphere...") ss = CollisionSphere(0, 0, 0, 5) ss.setTangible(0) snode = CollisionNode(name) snode.add_solid(ss) snode.set_collide_mask(CIGlobals.WallBitmask) self.snp = self.attach_new_node(snode) self.snp.setZ(3) self.acceptOnce("enter" + self.snp.node().getName(), self.__handleEnterCollisionSphere)
def __init__(self): """Setup this camera manager.""" Logger.info("Initializing camera manager...") self.reset() #Camera Controls #=============== #F1 - first person view #F2 - chase cam view #F3 - free cam view #W - move forward #S - move backward #A - move left #D - move right #R - rise #F - fall #Up - look up #Down - look down #Left - turn left #Right - turn right base.accept("f1", self.change_mode, [CAM_MODE_FIRST_PERSON]) base.accept("f2", self.change_mode, [CAM_MODE_CHASE]) base.accept("f3", self.change_mode, [CAM_MODE_FREE]) base.accept("w", self.set_move_vec_y, [self.speed]) base.accept("w-up", self.set_move_vec_y, [0]) base.accept("s", self.set_move_vec_y, [-self.speed]) base.accept("s-up", self.set_move_vec_y, [0]) base.accept("a", self.set_move_vec_x, [-self.speed]) base.accept("a-up", self.set_move_vec_x, [0]) base.accept("d", self.set_move_vec_x, [self.speed]) base.accept("d-up", self.set_move_vec_x, [0]) base.accept("r", self.set_move_vec_z, [self.speed]) base.accept("r-up", self.set_move_vec_z, [0]) base.accept("f", self.set_move_vec_z, [-self.speed]) base.accept("f-up", self.set_move_vec_z, [0]) base.accept("arrow_up", self.set_rot_vec_p, [self.speed / 10]) base.accept("arrow_up-up", self.set_rot_vec_p, [0]) base.accept("arrow_down", self.set_rot_vec_p, [-self.speed / 10]) base.accept("arrow_down-up", self.set_rot_vec_p, [0]) base.accept("arrow_left", self.set_rot_vec_h, [self.speed / 10]) base.accept("arrow_left-up", self.set_rot_vec_h, [0]) base.accept("arrow_right", self.set_rot_vec_h, [-self.speed / 10]) base.accept("arrow_right-up", self.set_rot_vec_h, [0]) #Setup collision detection cnode = CollisionNode("camera") cnode.add_solid(CollisionSphere(0, 0, 0, 10)) self.collider = base.camera.attach_new_node(cnode) base.cTrav.add_collider(self.collider, base.portal_handler) #Start camera manager task base.task_mgr.add(self.run_logic) Logger.info("Camera manager initialized.")
def enter_filter_mouseoverable(self, entity): model_proxy = self.proxies['model'] model_node = model_proxy.field(entity) mouseoverable = entity[MouseOverable] into_node = CollisionNode('wecs_mouseoverable') into_node.add_solid(mouseoverable.solid) into_node.set_from_collide_mask(0x0) into_node.set_into_collide_mask(mouseoverable.mask) into_node_path = model_node.attach_new_node(into_node) into_node_path.set_python_tag('wecs_mouseoverable', entity._uid)
def pieCollisions(self): pss = CollisionSphere(0, 0, 0, 1) psnode = CollisionNode('projectilePieSensor' + str(id(self))) psnode.add_solid(pss) self.psnp = self.pie.attach_new_node(psnode) self.psnp.set_collide_mask(BitMask32(0)) self.psnp.node().set_from_collide_mask(CIGlobals.WallBitmask | CIGlobals.FloorBitmask) event = CollisionHandlerEvent() event.set_in_pattern("%fn-into") event.set_out_pattern("%fn-out") base.cTrav.add_collider(self.psnp, event)
def __initCollisions(self, name): self.notify.debug('Initializing collision sphere...') numSlots = len(self.circles) ss = CollisionSphere(0, 0, 0, self.numPlayers2SphereRadius[numSlots]) ss.setTangible(0) snode = CollisionNode(name) snode.add_solid(ss) snode.set_collide_mask(CIGlobals.WallBitmask) self.snp = self.attach_new_node(snode) self.snp.setZ(3) self.snp.setY(self.numPlayers2SphereY[numSlots]) self.snp.setSx(self.numPlayers2SphereSx[numSlots]) self.snp.setSy(self.numPlayers2SphereSy[numSlots]) self.acceptOnce('enter' + self.snp.node().getName(), self.__handleEnterCollisionSphere)
def setup_shadow_ray(self, shadow_node, mat): ray = CollisionRay(0.0, 0.0, CollisionHandlerRayStart, 0.0, 0.0, -1.0) ray_node = CollisionNode('ray_node') ray_node.add_solid(ray) self.ray_np = shadow_node.attach_new_node(ray_node) self.ray_np.node().set_from_collide_mask(CIGlobals.FloorBitmask) self.ray_np.node().set_into_collide_mask(BitMask32.allOff()) floor_offset = 0.025 lifter = CollisionHandlerFloor() lifter.set_offset(floor_offset) lifter.set_reach(4.0) lifter.add_collider(self.ray_np, shadow_node) if not mat: base.cTrav.add_collider(self.ray_np, lifter)
def ray(self, name, bitmask, point_a=(0, 0, 1), point_b=(0, 0, 0)): shape = CollisionSegment(point_a, point_b) col = CollisionNode(self.node.getName() + "-ray-" + name) col.add_solid(shape) col.set_from_collide_mask(bitmask) col.set_into_collide_mask(CollideMask.all_off()) col_node = self.node.attach_new_node(col) handler = CollisionHandlerQueue() self.traverser.add_collider(col_node, handler) return { "collider": col, "shape": shape, "handler": handler, "node": col_node }
def sphere(self, name, bitmask, pos=(0, 0, 1), radius=0.2): col = CollisionNode(self.node.getName() + "-sphere-" + name) shape = CollisionSphere(pos, radius) col.add_solid(shape) col.set_from_collide_mask(bitmask) col.set_into_collide_mask(CollideMask.allOff()) col_node = self.node.attachNewNode(col) handler = CollisionHandlerPusher() handler.add_collider(col_node, self.node) self.traverser.add_collider(col_node, handler) return { "collider": col, "shape": shape, "handler": handler, "node": col_node }
def add_border_walls(self): '''Attaching invisible walls to map's borders, to avoid falling off map''' log.debug("Adding invisible walls to collide with on map's borders") wall_coordinates = [ ((self.map_size[0], 0, 0), (self.map_size[1], 0, 0)), ((-self.map_size[0], 0, 0), (-self.map_size[1], 0, 0)), ((0, self.map_size[2], 0), (0, self.map_size[3], 0)), ((0, -self.map_size[2], 0), (0, -self.map_size[3], 0)) ] for sizes in wall_coordinates: wall_node = CollisionNode("wall") #it looks like without adding node to pusher (we dont need that there), #masks wont work. Thus for now I wont use them, as defaults seem to work #wall_node.set_collide_mask(BitMask32(shared.WALLS_COLLISION_MASK)) wall_node.add_solid(CollisionPlane(Plane(*sizes))) wall = render.attach_new_node(wall_node)
def __init__(self, app, render, camera, mouseWatcher, pickKeyOn, pickKeyOff, collideMask, pickableTag="pickable"): self.render = render self.mouseWatcher = mouseWatcher.node() self.camera = camera self.camLens = camera.node().get_lens() self.collideMask = collideMask self.pickableTag = pickableTag self.taskMgr = app.task_mgr # setup event callback for picking body self.pickKeyOn = pickKeyOn self.pickKeyOff = pickKeyOff app.accept(self.pickKeyOn, self._pickBody, [self.pickKeyOn]) app.accept(self.pickKeyOff, self._pickBody, [self.pickKeyOff]) # collision data self.collideMask = collideMask self.cTrav = CollisionTraverser() self.collisionHandler = CollisionHandlerQueue() self.pickerRay = CollisionRay() pickerNode = CollisionNode("Utilities.pickerNode") node = NodePath("PhysicsNode") node.reparentTo(render) anp = node.attachNewNode(pickerNode) base.physicsMgr.attachPhysicalNode(pickerNode) pickerNode.add_solid(self.pickerRay) pickerNode.set_from_collide_mask(self.collideMask) pickerNode.set_into_collide_mask(BitMask32.all_off()) #pickerNode.node().getPhysicsObject().setMass(10) self.cTrav.add_collider(self.render.attach_new_node(pickerNode), self.collisionHandler) # service data self.pickedBody = None self.oldPickingDist = 0.0 self.deltaDist = 0.0 self.dragging = False self.updateTask = None
def make_collision(solid_from, solid_into): node_from = CollisionNode("from") node_from.add_solid(solid_from) node_into = CollisionNode("into") node_into.add_solid(solid_into) root = NodePath("root") trav = CollisionTraverser() queue = CollisionHandlerQueue() np_from = root.attach_new_node(node_from) np_into = root.attach_new_node(node_into) trav.add_collider(np_from, queue) trav.traverse(root) entry = None for e in queue.get_entries(): if e.get_into() == solid_into: entry = e return (entry, np_from, np_into)
def __init__(self, map, node): self.map = map self.node = node self.init_heading = self.node.get_h() pos = node.get_pos(render) node.set_pos(pos) if self.node.get_z() < 2: self.node.reparent_to(self.map.doors_node_f1) else: self.node.reparent_to(self.map.doors_node_f2) self.node.set_collide_mask(base.mapmask) self.open = False self.speed = 5 self.make_key() self.timer = 0 col = CollisionNode(node.name) col.add_solid(CollisionSphere((0, 0, 0), 1.5)) col.set_collide_mask(base.itemmask) col_node = self.map.collisions.attach_new_node(col) col_node.set_pos(node.get_pos(render)) col_node.set_y(node, 2) self.opening = False
def add_borders(self): """Attach invisible walls to map's borders, to avoid falling off map""" log.debug("Adding invisible walls to collide with on map's borders") wall_coordinates = [ ((self.map_size[0], 0, 0), (self.map_size[1], 0, 0)), ((-self.map_size[0], 0, 0), (-self.map_size[1], 0, 0)), ((0, self.map_size[2], 0), (0, self.map_size[3], 0)), ((0, -self.map_size[2], 0), (0, -self.map_size[3], 0)), ] for sizes in wall_coordinates: wall_node = CollisionNode(shared.game_data.border_category) # it looks like without adding node to pusher (we dont need that there), # masks wont work. Thus for now I wont use them, as defaults seem to work # wall_node.set_collide_mask(BitMask32(shared.WALLS_COLLISION_MASK)) wall_node.add_solid(CollisionPlane(Plane(*sizes))) wall = self.scene.attach_new_node(wall_node) # adding tag with wall's coordinations, so it will be possible to # push entities back if these collide with wall # because calling .get_pos() will return LPoint3f(0,0,0) wall_node.set_python_tag("position", sizes)
def __init__(self, world): self.world = world self.root = self.world.root.attach_new_node("player") self.root_target = self.world.root.attach_new_node("player_target") self.pivot = self.root.attach_new_node("player_pivot") base.camera.reparent_to(self.pivot) base.camera.set_z(1.7) base.cam.node().get_lens().set_fov(90) self.traverser = CollisionTraverser() self.ray = setup_ray( self.pivot, self.traverser, self.world.mask, # ray ends well below feet to register downward slopes (0, 0, 1), (0, 0, -1)) self.xyh_inertia = Vec3(0, 0, 0) h_acc = ConfigVariableDouble('mouse-accelleration', 0.1).get_value() self.xyh_acceleration = Vec3(0.8, 0.8, h_acc) self.friction = 0.15 self.torque = 0.5 self.last_up = Vec3(0, 0, 1) # Collider for portals csphere = CollisionSphere(0, 0, 1.25, 1.5) cnode = CollisionNode('player') cnode.add_solid(csphere) cnode.set_from_collide_mask(0x2) cnode.set_into_collide_mask(CollideMask.all_off()) self.collider = self.root.attach_new_node(cnode) self.event_handler = CollisionHandlerEvent() self.event_handler.add_in_pattern('into-%in') self.traverser.add_collider(self.collider, self.event_handler) self.collider.show() self.teleported = False base.input.set_mouse_relativity(True)
class MapPicker(): __name: Final[str] __base: Final[ShowBase] __data: Final[NDArray[(Any, Any, Any), np.uint8]] # collision data __ctrav: Final[CollisionTraverser] __cqueue: Final[CollisionHandlerQueue] __cn: Final[CollisionNode] __cnp: Final[NodePath] # picker data __pn: Final[CollisionNode] __pnp: Final[NodePath] __pray: Final[CollisionRay] # constants COLLIDE_MASK: Final[BitMask32] = BitMask32.bit(1) def __init__(self, services: Services, base: ShowBase, map_data: MapData, name: Optional[str] = None): self.__services = services self.__services.ev_manager.register_listener(self) self.__base = base self.__name = name if name is not None else (map_data.name + "_picker") self.__map = map_data self.__data = map_data.data # collision traverser & queue self.__ctrav = CollisionTraverser(self.name + '_ctrav') self.__cqueue = CollisionHandlerQueue() # collision boxes self.__cn = CollisionNode(self.name + '_cn') self.__cn.set_collide_mask(MapPicker.COLLIDE_MASK) self.__cnp = self.__map.root.attach_new_node(self.__cn) self.__ctrav.add_collider(self.__cnp, self.__cqueue) self.__points = [] z_offset = 1 if self.__map.dim == 3 else self.__map.depth for idx in np.ndindex(self.__data.shape): if bool(self.__data[idx] & MapData.TRAVERSABLE_MASK): p = Point(*idx) self.__points.append(p) idx = self.__cn.add_solid(CollisionBox(idx, Point3(p.x+1, p.y+1, p.z-z_offset))) assert idx == (len(self.__points) - 1) # mouse picker self.__pn = CollisionNode(self.name + '_pray') self.__pnp = self.__base.cam.attach_new_node(self.__pn) self.__pn.set_from_collide_mask(MapPicker.COLLIDE_MASK) self.__pray = CollisionRay() self.__pn.add_solid(self.__pray) self.__ctrav.add_collider(self.__pnp, self.__cqueue) # debug -> shows collision ray / impact point # self.__ctrav.show_collisions(self.__map.root) @property def name(self) -> str: return self.__name @property def pos(self): # check if we have access to the mouse if not self.__base.mouseWatcherNode.hasMouse(): return None # get the mouse position mpos = self.__base.mouseWatcherNode.get_mouse() # set the position of the ray based on the mouse position self.__pray.set_from_lens(self.__base.camNode, mpos.getX(), mpos.getY()) # find collisions self.__ctrav.traverse(self.__map.root) # if we have hit something sort the hits so that the closest is first if self.__cqueue.get_num_entries() == 0: return None self.__cqueue.sort_entries() # compute & return logical cube position x, y, z = self.__cqueue.get_entry(0).getSurfacePoint(self.__map.root) x, y, z = [max(math.floor(x), 0), max(math.floor(y), 0), max(math.ceil(z), 0)] if x == len(self.__data): x -= 1 if y == len(self.__data[x]): y -= 1 if z == len(self.__data[x][y]): z -= 1 return Point(x, y, z) def notify(self, event: Event) -> None: if isinstance(event, MapUpdateEvent): z_offset = 1 if self.__map.dim == 3 else self.__map.depth for p in event.updated_cells: if p.n_dim == 2: p = Point(*p, 0) if bool(self.__data[p.values] & MapData.TRAVERSABLE_MASK): self.__points.append(p) idx = self.__cn.add_solid(CollisionBox(p.values, Point3(p.x+1, p.y+1, p.z-z_offset))) assert idx == (len(self.__points) - 1) else: try: i = self.__points.index(p) except ValueError: continue self.__cn.remove_solid(i) self.__points.pop(i) def destroy(self) -> None: self.__cqueue.clearEntries() self.__ctrav.clear_colliders() self.__cnp.remove_node() self.__pnp.remove_node()
def __init__( self, name: str, category: str, collision_settings: CollisionSettings, scale: int = None, animated_parts: list = None, static_parts: list = None, ): self.name = name log.debug(f"Initializing {self.name} object") self.category = category # creating empty node to attach everything to. This way it will be easier # to attach other objects (like floating text and such), coz offset trickery # from animation wont affect other items attached to node (since its not # a parent anymore, but just another child) entity_node = PandaNode(name) # for now, self.node will be empty nodepath - we will reparent it to render # on spawn, to dont overflood it with reference entity instances self.node = NodePath(entity_node) # separate visuals node, to which parts from below will be attached vn = PandaNode(f"{name}_visuals") self.visuals = self.node.attach_new_node(vn) # this allows for rotating node around its h without making it invisible self.visuals.set_two_sided(True) # storage for entity parts (visuals) that will be attached to entity node. # static parts is used for stuff that is SpritesheetObject and doesnt have # switcher in it. Animated_parts reffers to thing that need to be changed # in case some related even occurs self.static_parts = static_parts or [] self.animated_parts = animated_parts or [] # setting character's collisions entity_collider = CollisionNode(self.category) # if no collision mask has been received - using defaults if collision_settings.mask is not None: mask = BitMask32(collision_settings.mask) entity_collider.set_from_collide_mask(mask) entity_collider.set_into_collide_mask(mask) entity_collider.add_solid( collision_settings.shape(*collision_settings.size)) self.collision = self.node.attach_new_node(entity_collider) if collision_settings.position: self.collision.set_pos(*collision_settings.position) self.direction = None # initializing visual parts added with self.add_part() if self.static_parts or self.animated_parts: for sp in self.static_parts: sp.instance.wrt_reparent_to(self.visuals) if sp.position: sp.instance.set_pos(sp.position) if sp.scale: sp.instance.set_scale(sp.scale) for ap in self.animated_parts: ap.instance.node.wrt_reparent_to(self.visuals) if ap.position: ap.instance.node.set_pos(ap.position) if ap.scale: ap.instance.node.set_scale(ap.scale) # this will rescale whole node with all parts attached to it # in case some part as already been rescaled above - it will be rescaled # again, which may cause issues if scale: self.node.set_scale(scale) self.change_direction("right") # death status, that may be usefull during cleanup self.dead = False # attaching python tags to node, so these will be accessible during # collision events and similar stuff self.node.set_python_tag("name", self.name) self.node.set_python_tag("category", self.category) # I thought to put ctrav there, but for whatever reason it glitched proj # to fly into left wall. So I moved it to Creature subclass # debug function to show collisions all time if shared.settings.show_collisions: self.collision.show()
def __init__(self, name, spritesheet=None, sprite_size=None, hitbox_size=None, collision_mask=None, position=None, animations_speed=None): log.debug(f"Initializing {name} object") if not animations_speed: animations_speed = DEFAULT_ANIMATIONS_SPEED self.animations_timer = animations_speed self.animations_speed = animations_speed if not sprite_size: sprite_size = DEFAULT_SPRITE_SIZE if not spritesheet: #I cant link assets above, coz their default value is None texture = base.assets.sprite[name] else: texture = base.assets.sprite[spritesheet] size_x, size_y = sprite_size log.debug(f"{name}'s size has been set to {size_x}x{size_y}") #the magic that allows textures to be mirrored. With that thing being #there, its possible to use values in range 1-2 to get versions of sprites #that will face the opposite direction, removing the requirement to draw #them with hands. Without thing thing being there, 0 and 1 will be threated #as same coordinates, coz "out of box" texture wrap mode is "repeat" texture.set_wrap_u(Texture.WM_mirror) texture.set_wrap_v(Texture.WM_mirror) sprite_data = spritesheet_cutter.cut_spritesheet(texture, sprite_size) horizontal_scale, vertical_scale = sprite_data['offset_steps'] offsets = sprite_data['offsets'] entity_frame = CardMaker(name) #setting frame's size. Say, for 32x32 sprite all of these need to be 16 entity_frame.set_frame(-(size_x / 2), (size_x / 2), -(size_y / 2), (size_y / 2)) entity_object = render.attach_new_node(entity_frame.generate()) entity_object.set_texture(texture) #okay, this does the magic #basically, to show the very first sprite of 2 in row, we set tex scale #to half (coz half is our normal char's size). If we will need to use it #with sprites other than first - then we also should adjust offset accordingly #entity_object.set_tex_offset(TextureStage.getDefault(), 0.5, 0) #entity_object.set_tex_scale(TextureStage.getDefault(), 0.5, 1) entity_object.set_tex_scale(TextureStage.getDefault(), horizontal_scale, vertical_scale) #now, to use the stuff from cut_spritesheet function. #lets say, we need to use second sprite from sheet. Just do: #entity_object.set_tex_offset(TextureStage.getDefault(), *offsets[1]) #but, by default, offset should be always set to 0. In case our object #has just one sprite. Or something like that default_sprite = 0 entity_object.set_tex_offset(TextureStage.getDefault(), *offsets[default_sprite]) #enable support for alpha channel. This is a float, e.g making it non-100% #will require values between 0 and 1 entity_object.set_transparency(1) #if no position has been received - wont set it up if position: entity_object.set_pos(*position) #setting character's collisions entity_collider = CollisionNode(name) #if no collision mask has been received - using defaults if collision_mask: entity_collider.set_from_collide_mask(BitMask32(collision_mask)) entity_collider.set_into_collide_mask(BitMask32(collision_mask)) #TODO: move this to be under character's legs #right now its centered on character's center if hitbox_size: self.hitbox_size = hitbox_size else: #coz its sphere and not oval - it doesnt matter if we use x or y #but, for sake of convenience - we are going for size_y self.hitbox_size = (size_y / 2) entity_collider.add_solid(CollisionSphere(0, 0, 0, self.hitbox_size)) entity_collision = entity_object.attach_new_node(entity_collider) #this will explode if its not, but I dont have a default right now if name in ANIMS: entity_anims = ANIMS[name] self.name = name self.object = entity_object self.collision = entity_collision self.sprites = offsets #setting this to None may cause crashes on few rare cases, but going #for "idle_right" wont work for projectiles... So I technically add it #there for anims updater, but its meant to be overwritten at 100% cases self.current_animation = None #this will always be 0, so regardless of consistency I will live it be self.current_frame = default_sprite self.animations = entity_anims #death status, that may be usefull during cleanup self.dead = False #attaching python tags to object node, so these will be accessible during #collision events and similar stuff self.object.set_python_tag("name", self.name) #I thought to put ctrav there, but for whatever reason it glitched projectile #to fly into left wall. So I moved it to Creature subclass #debug function to show collisions all time #self.collision.show() #do_method_later wont work there, coz its indeed based on frames base.task_mgr.add(self.update_anims, "update entity's animations")
class MouseOverOnEntity(System): entity_filters = { 'mouseoverable': [Proxy('model'), MouseOverable], 'mouseoverable_geometry': [Proxy('geometry'), MouseOverableGeometry], 'camera': [Camera, Input, MouseOveringCamera], } proxies = { 'model': ProxyType(Model, 'node'), 'geometry': ProxyType(Geometry, 'node'), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.traverser = CollisionTraverser() self.queue = CollisionHandlerQueue() self.picker_ray = CollisionRay() self.picker_node = CollisionNode('mouse ray') self.picker_node.add_solid(self.picker_ray) self.picker_node.set_from_collide_mask(MOUSEOVER_MASK) self.picker_node.set_into_collide_mask(0x0) self.picker_node_path = NodePath(self.picker_node) self.traverser.add_collider(self.picker_node_path, self.queue) def enter_filter_mouseoverable(self, entity): model_proxy = self.proxies['model'] model_node = model_proxy.field(entity) mouseoverable = entity[MouseOverable] into_node = CollisionNode('wecs_mouseoverable') into_node.add_solid(mouseoverable.solid) into_node.set_from_collide_mask(0x0) into_node.set_into_collide_mask(mouseoverable.mask) into_node_path = model_node.attach_new_node(into_node) into_node_path.set_python_tag('wecs_mouseoverable', entity._uid) def exit_filter_mouseoverable(self, entity): # FIXME: Undo all the other stuff that accumulated! entity[MouseOverable].solid.detach_node() def enter_filter_mouseoverable_geometry(self, entity): into_node = self.proxies['geometry'].field(entity) old_mask = into_node.get_collide_mask() new_mask = old_mask | entity[MouseOverableGeometry].mask into_node.set_collide_mask(new_mask) into_node.find('**/+GeomNode').set_python_tag('wecs_mouseoverable', entity._uid) def update(self, entities_by_filter): for entity in entities_by_filter['camera']: mouse_overing = entity[MouseOveringCamera] camera = entity[Camera] input = entity[Input] # Reset overed entity to None mouse_overing.entity = None mouse_overing.collision_entry = None requested = 'mouse_over' in input.contexts has_mouse = base.mouseWatcherNode.has_mouse() if requested and has_mouse: # Attach and align testing ray, and run collisions self.picker_node_path.reparent_to(camera.camera) mpos = base.mouseWatcherNode.get_mouse() self.picker_ray.set_from_lens( base.camNode, mpos.getX(), mpos.getY(), ) self.traverser.traverse(camera.camera.get_top()) # Remember reference to mouseovered entity, if any if self.queue.get_num_entries() > 0: self.queue.sort_entries() entry = self.queue.get_entry(0) picked_node = entry.get_into_node_path() picked_uid = picked_node.get_python_tag( 'wecs_mouseoverable') mouse_overing.entity = picked_uid mouse_overing.collision_entry = entry