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 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 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 __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 __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)
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()
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
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 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()