class NullSystem(System): entity_filters = { "null": and_filter([NullComponent]) } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.entries = [] self.exits = [] self.updates = [] def enter_filters(self, filters, entity): self.entries.append((filters, entity)) def exit_filters(self, filters, entity): self.exits.append((filters, entity)) def update(self, entities_by_filter): self.updates.append(entities_by_filter)
class Animate(System): entity_filters = { 'animation': and_filter([ Animation, Model, ]) } def update(self, entities_by_filter): for entity in entities_by_filter['animation']: animation = entity[Animation] actor = entity[Model].node if not animation.playing == animation.to_play: if len(animation.to_play) > 0: actor.enableBlend() else: actor.disableBlend() # TODO: Don't stop and swap different animations instantly # but ease in (and bounce?) between them. #Stop animations not in to_play. for name in animation.playing: if not name in animation.to_play: actor.stop(name) actor.setControlEffect(name, 0) #Play newly added animations. for n, name in enumerate(animation.to_play): if name not in animation.playing: actor.loop(name) animation.playing = animation.to_play # Set blends each frame for b, blend in enumerate(animation.blends): if b < len(animation.playing): name = animation.playing[b] actor.setControlEffect(name, blend / len(animation.playing)) # Set framerate each frame for name in animation.playing: actor.setPlayRate(animation.framerate, name)
class Cursoring(System): entity_filters = { 'cursor': and_filter([CursorMovement, CharacterController, Model]), } def update(self, entities_by_filter): for entity in entities_by_filter['cursor']: cursor = entity[CursorMovement] model = entity[Model] char = entity[CharacterController] char.translation *= cursor.move_speed char.rotation *= cursor.rot_speed char.rotation[1] = 0 if cursor.snapping: if char.move.x == 0 and char.move.y == 0 and char.move.z == 0: if char.heading == 0: np = model.node np.set_pos(snap_vector(np.get_pos(), cursor.move_snap)) np.set_hpr(snap_vector(np.get_hpr(), cursor.rot_snap))
class Frictioning(System): ''' Applies a slow-down to the character. ''' entity_filters = { 'character': and_filter([ Clock, CharacterController, FrictionalMovement, ]), } def update(self, entities_by_filter): for entity in entities_by_filter['character']: dt = entity[Clock].game_time character = entity[CharacterController] friction = entity[FrictionalMovement] character.translation *= 0.5**(dt / friction.half_life)
class StartBallMotion(System): entity_filters = { 'ball': and_filter([ Model, Scene, Position, Resting, Ball, ]), } def update(self, entities_by_filter): # Should resting balls be started? start_key = KeyboardButton.space() start_balls = base.mouseWatcherNode.is_button_down(start_key) if start_balls: for entity in set(entities_by_filter['ball']): del entity[Resting] entity[Movement] = Movement(value=Vec3(-1, 0, 0))
class FaceMovement(System): entity_filters = { 'character': and_filter([ Proxy('geometry_node'), Clock, CharacterController, FacingMovement, ]), } proxies = {'geometry_node': ProxyType(Geometry, 'node')} def update(self, entities_by_filter): for entity in entities_by_filter['character']: geometry_proxy = self.proxies['geometry_node'] geometry = entity[geometry_proxy.component_type] geometry_node = geometry_proxy.field(entity) controller = entity[CharacterController] x, y, z = controller.last_translation_speed geometry_node.look_at(x, y, 0)
class Jumping(CollisionSystem): entity_filters = { 'character': and_filter([ CharacterController, JumpingMovement, FallingMovement, Scene, Clock, Model, ]), } def update(self, entities_by_filter): for entity in entities_by_filter['character']: controller = entity[CharacterController] falling_movement = entity[FallingMovement] jumping_movement = entity[JumpingMovement] if controller.jumps and falling_movement.ground_contact: falling_movement.inertia += jumping_movement.impulse
class Floating(System): ''' Components used :func:`wecs.core.and_filter` 'character' | :class:`wecs.panda3d.character.CharacterController` | :class:`wecs.panda3d.character.FloatingMovement` ''' entity_filters = { 'character': and_filter([ CharacterController, FloatingMovement, ]), } def update(self, entities_by_filter): for entity in entities_by_filter['character']: character = entity[CharacterController] floating = entity[FloatingMovement] character.translation *= floating.speed character.rotation *= floating.turning_speed
class DetermineTimestep(System): entity_filters = { 'clock': and_filter([Clock]), } def update(self, entities_by_filter): clocks_by_parent = defaultdict(set) for entity in entities_by_filter['clock']: clocks_by_parent[entity[Clock].parent].add(entity) updated_parents = set() for entity in clocks_by_parent[None]: clock = entity[Clock] dt = clock.clock() # Wall time: The last frame's physical duration clock.wall_time = dt # Frame time: Wall time, capped to a maximum max_timestep = clock.max_timestep if dt > max_timestep: dt = max_timestep clock.frame_time = dt # FIXME: Provided for legacy purposes clock.timestep = dt # Game time: Time-dilated frame time clock.game_time = clock.frame_time * clock.scaling_factor # ...and to start the loop... updated_parents.add(entity._uid) while updated_parents: next_parents = set() for parent in updated_parents: if parent in clocks_by_parent: for entity in clocks_by_parent[parent]: child_clock = entity[Clock] parent_clock = entity.world[parent][Clock] child_clock.wall_time = parent_clock.wall_time # FIXME: Rip out timestep child_clock.timestep = parent_clock.frame_time child_clock.frame_time = parent_clock.frame_time child_clock.game_time = parent_clock.game_time * child_clock.scaling_factor next_parents.add(entity._uid) updated_parents = next_parents
class ExecuteMovement(System): ''' Transcribe the final intended movement to the model, making it an actual movement. Components used :func:`wecs.core.and_filter` 'character' | :class:`wecs.panda3d.character.CharacterController` | :class:`wecs.panda3d.model.Model` | :class:`wecs.panda3d.model.Clock` ''' entity_filters = { 'character': and_filter([ Proxy('character_node'), Clock, CharacterController, ]), } proxies = {'character_node': ProxyType(Model, 'node')} def update(self, entities_by_filter): for entity in entities_by_filter['character']: model_proxy = self.proxies['character_node'] model = entity[model_proxy.component_type] character = entity[CharacterController] dt = entity[Clock].game_time # Translation: Simple self-relative movement for now. model_node = model_proxy.field(entity) model_node.set_pos(model_node, character.translation) character.last_translation_speed = character.translation / dt # Rotation if character.clamp_pitch: # Adjust intended pitch until it won't move you over a pole. preclamp_pitch = model_node.get_p() + character.rotation.y clamped_pitch = max(min(preclamp_pitch, 89.9), -89.9) character.rotation.y += clamped_pitch - preclamp_pitch model_node.set_hpr(model_node, character.rotation) character.last_rotation_speed = character.rotation / dt
class CastRejuvenationSpell(CanCastRejuvenationMixin, System): entity_filters = { 'cast_spell': and_filter([Mana, CastingRejuvenationSpell, Age, Alive]), } spell_class = RejuvenationSpell def update(self, filtered_entities): super().update(filtered_entities) def can_cast(self, entity): return super().can_cast(entity) def cast(self, entity): mana = entity.get_component(Mana) spell = entity.get_component(self.spell_class) age = entity.get_component(Age) mana.mana -= spell.mana_cost age.age -= spell.time_restored if age.age < 0: age.age = 0 print("REJUVENATION SPELL CAST!")
class ResizePaddles(System): """ ResizePaddles ensures that the paddle's size stays updated. The idea is that other systems may influence the size by changing the paddle's Component state. ResizePaddles will make the actual change to the Model. """ entity_filters = { 'paddle': and_filter([ Model, Paddle, ]), } def update(self, entities_by_filter): """ Update the paddle size by setting the scale of the paddle's Model. """ for entity in entities_by_filter['paddle']: model = entity[Model] paddle = entity[Paddle] model.node.set_scale(paddle.size)
class PaddleTouchesBoundary(System): entity_filters = { 'paddles': and_filter([ Proxy('model'), Paddle, ]), } def update(self, entities_by_filter): for entity in set(entities_by_filter['paddles']): model = entity[Model] position = entity[Position] paddle = entity[Paddle] z = position.value.z size = paddle.size if z + size > 1: position.value.z = 1 - size elif (z - size) < -1: position.value.z = -1 + size model.node.set_z(position.value.z)
def test_compound_filter_3(world, entity): sub_filter_1 = and_filter([ComponentA]) sub_filter_2 = or_filter([ComponentB, ComponentC]) f = or_filter([sub_filter_1, sub_filter_2]) assert not f(entity) entity.add_component(ComponentA()) world.flush_component_updates() assert f(entity) entity.remove_component(ComponentA) entity.add_component(ComponentB()) world.flush_component_updates() assert f(entity) entity.remove_component(ComponentB) entity.add_component(ComponentC()) world.flush_component_updates() assert f(entity) entity.remove_component(ComponentC) world.flush_component_updates()
class WalkSpeedLimiting(System): ''' Applies a slow-down to the character. ''' entity_filters = { 'character': and_filter([ Clock, CharacterController, WalkingMovement, ]), } def update(self, entities_by_filter): for entity in entities_by_filter['character']: dt = entity[Clock].game_time character = entity[CharacterController] walking = entity[WalkingMovement] v_length = character.translation.length() if v_length > walking.speed * dt: character.translation.normalize() character.translation *= walking.speed * dt
class Bumping(CollisionSystem): ''' Stop the character from moving through solid geometry. Components used :func:`wecs.core.and_filter` 'character' | :class:`wecs.panda3d.model.Scene` | :class:`wecs.panda3d.character.CharacterController` | :class:`wecs.panda3d.character.BumpingMovement` | :class:`wecs.panda3d.model.Clock` | :class:`wecs.panda3d.model.Model` ''' entity_filters = { 'character': and_filter([ Scene, CharacterController, BumpingMovement, Clock, Model, ]), } def enter_filter_character(self, entity): movement = entity[BumpingMovement] self.init_sensors(entity, movement) bumper = movement.solids['bumper'] node = bumper['node'] movement.queue.add_collider(node, node) def update(self, entities_by_filter): for entity in entities_by_filter['character']: scene = entity[Scene] character = entity[CharacterController] movement = entity[BumpingMovement] bumper = movement.solids['bumper'] node = bumper['node'] node.set_pos(character.translation) movement.traverser.traverse(scene.node) character.translation = node.get_pos()
class PaddleTouchesBoundary(System): entity_filters = { 'paddles': and_filter([ Model, Scene, Position, Paddle, ]), } def update(self, entities_by_filter): for entity in set(entities_by_filter['paddles']): pos = entity.get_component(Position).value size = entity.get_component(Paddle).size if pos.z + size > 1: pos.z = 1 - size np = entity.get_component(Model).node np.set_z(pos.z) if (pos.z - size) < -1: pos.z = -1 + size np = entity.get_component(Model).node np.set_z(pos.z)
class DirectlyIndicateDirection(System): entity_filters = { 'character': and_filter([ Proxy('model'), AutomaticTurningMovement, TwinStickMovement, CharacterController, Camera, ObjectCentricCameraMode, ]) } proxies = {'model': ProxyType(Model, 'node')} input_context = 'character_direction' def update(self, entities_by_filter): for entity in entities_by_filter['character']: input = entity[Input] if self.input_context in input.contexts: context = base.device_listener.read_context(self.input_context) self.process_input(context, entity) def process_input(self, context, entity): model_proxy = self.proxies['model'] model_node = model_proxy.field(entity) camera = entity[Camera] turning = entity[AutomaticTurningMovement] if context['direction'] is not None: turning.direction = model_node.get_relative_vector( camera.pivot, Vec3( context['direction'].x, context['direction'].y, 0, ), ) else: turning.direction = Vec3(0, 1, 0)
class BallTouchesBoundary(System): entity_filters = { 'ball': and_filter([ Model, Scene, Position, Movement, Ball, ]), } def update(self, entities_by_filter): for entity in entities_by_filter['ball']: np = entity.get_component(Model).node z = np.get_z() if z > 0.9: np.set_z(0.9 - (z - 0.9)) movement = entity.get_component(Movement).value movement.z = -movement.z if z < -0.9: np.set_z(-0.9 - (z + 0.9)) movement = entity.get_component(Movement).value movement.z = -movement.z
class AnimationPlayer(System): entity_filters = { 'character': and_filter([Character]), } def change_state(self, char, old_state, new_state): old_state_def = char.states.get(old_state, {}) new_state_def = char.states[new_state] #char._actor.stop() if old_state is not None: char._state_paths[old_state].stash() transition = char.transitions.get((old_state, new_state)) if transition: char._actor.unstash() for part, anim in transition.items(): Sequence( char._actor.actor_interval(anim, partName=part, playRate=char.play_rates[new_state]), Func(lambda: char._actor.stash()), Func(lambda: char._state_paths[new_state].unstash()), ).start() else: char._actor.stash() char._state_paths[new_state].unstash() def update(self, entities_by_filter): if base.paused: return for entity in entities_by_filter['character']: char = entity[Character] if char._state != char.state: self.change_state(char, char._state, char.state) char._state = char.state
class GiveTankMoveCommands(System): entity_filters = { 'tank': and_filter([ Model, Scene, Position, Movement, Tank, ]), } def enter_filter_model(self, entity): geometry = entity[Geometry] print(geometry) def update(self, entities_by_filter): for entity in entities_by_filter['tank']: tank = entity[Tank] movement = entity[Movement] # What keys does the player use? if tank.player == Players.LEFT: up_key = KeyboardButton.ascii_key(b'w') down_key = KeyboardButton.ascii_key(b's') elif tank.player == Players.RIGHT: up_key = KeyboardButton.up() down_key = KeyboardButton.down() # Read player input delta = 0 if base.mouseWatcherNode.is_button_down(up_key): delta += 1 if base.mouseWatcherNode.is_button_down(down_key): delta -= 1 # Store movement movement.value.z = delta
class Walking(System): entity_filters = { 'character': and_filter([ CharacterController, WalkingMovement, ]), } def update(self, entities_by_filter): for entity in entities_by_filter['character']: character = entity[CharacterController] walking = entity[WalkingMovement] speed = walking.speed if character.sprints and SprintingMovement in entity: speed = entity[SprintingMovement].speed if character.crouches and CrouchingMovement in entity: speed = entity[CrouchingMovement].speed if character.move.y < 0: speed *= walking.backwards_multiplier character.translation *= speed character.rotation *= walking.turning_speed character.rotation.y = 0 # No pitch adjustment while walking
class CollideCamerasWithTerrain(System): entity_filters = { 'camera': and_filter([ Camera, ObjectCentricCameraMode, CollisionZoom, ]) } def init_entity(self, filter_name, entity): camera = entity[Camera] center = entity[ObjectCentricCameraMode] zoom = entity[CollisionZoom] w = zoom.body_width / 2 segs = ((0, 0, w), (0, 0, -w), (w, 0, 0), (-w, 0, 0)) for seg in segs: segment = CollisionSegment(seg,(0, -center.distance, 0)) zoom.collision.add_solid(segment) zoom.collision.set_into_collide_mask(0) cn = camera.camera.parent.attach_new_node(zoom.collision) zoom.traverser.add_collider(cn, zoom.queue) def update(self, entities_by_filter): for entity in entities_by_filter['camera']: camera = entity[Camera] center = entity[ObjectCentricCameraMode] zoom = entity[CollisionZoom] zoom.traverser.traverse(render) entries = list(zoom.queue.entries) if len(entries) > 0: hit_pos = entries[0].get_surface_point(camera.camera.parent) camera.camera.set_pos(hit_pos) else: camera.camera.set_pos(0, -center.distance, 0)
class TankTouchesBoundary(System): entity_filters = { 'tanks': and_filter([ Model, Scene, Position, Tank, ]), } def update(self, entities_by_filter): for entity in set(entities_by_filter['tanks']): model = entity[Model] position = entity[Position] tank = entity[Tank] z = position.value.z size = tank.size if z + size > 1: position.value.z = 1 - size elif (z - size) < -1: position.value.z = -1 + size model.node.set_z(position.value.z)
class CastLichdomSpell(CanCastLichdomMixin, System): entity_filters = { 'cast_spell': and_filter([ Mana, CastingLichdomSpell, Alive, ]), } spell_class = LichdomSpell def update(self, filtered_entities): super().update(filtered_entities) def cast(self, entity): mana = entity.get_component(Mana) spell = entity.get_component(self.spell_class) if entity.has_component(LichdomSpellEffect): mana.mana -= int(spell.mana_cost / 2) print("SPELL FIZZLES, already under its effect.") else: mana.mana -= spell.mana_cost entity.add_component(LichdomSpellEffect()) print("LICHDOM SPELL CAST!")
class UpdateStamina(System): entity_filters = { 'character': and_filter([ CharacterController, Stamina, Clock, ]), } def update(self, entities_by_filter): for entity in entities_by_filter['character']: character = entity[CharacterController] stamina = entity[Stamina] dt = entity[Clock].timestep av = abs(character.move.x) + abs(character.move.y) drain = 0 if character.move.x or character.move.y: drain = stamina.move_drain * av if character.sprints and SprintingMovement in entity: if stamina.current > stamina.sprint_drain * av: drain = stamina.sprint_drain * av else: character.sprints = False elif character.crouches and CrouchingMovement in entity: if stamina.current > stamina.crouch_drain * av: drain = stamina.crouch_drain * av else: character.crouches = False if character.jumps and JumpingMovement in entity: if stamina.current > stamina.jump_drain * av: drain += stamina.jump_drain else: character.jumps = False stamina.current -= drain * dt stamina.current += stamina.recovery * dt stamina.current = clamp(stamina.current, 0, stamina.maximum)
class Falling(CollisionSystem): ''' Stop the character from falling through solid geometry. Components used :func:`wecs.core.and_filter` 'character' | :class:`wecs.panda3d.character.CharacterController` | :class:`wecs.panda3d.character.FallingMovement` | :class:`wecs.panda3d.model.Clock` | :class:`wecs.panda3d.model.Model` ''' entity_filters = { 'character': and_filter([ CharacterController, FallingMovement, Scene, Clock, Model, ]), } def enter_filter_character(self, entity): self.init_sensors(entity, entity[FallingMovement]) def update(self, entities_by_filter): for entity in entities_by_filter['character']: # Adjust the falling inertia by gravity, and position the # lifter collider. self.predict_falling(entity) # Find collisions with the ground. self.run_sensors(entity, entity[FallingMovement]) # Adjust the character's intended translation so that his # falling is stoppedby the ground. self.fall_and_land(entity) def predict_falling(self, entity): model = entity[Model] scene = entity[Scene] clock = entity[Clock] controller = entity[CharacterController] falling_movement = entity[FallingMovement] # Adjust inertia by gravity falling_movement.local_gravity = model.node.get_relative_vector( scene.node, falling_movement.gravity, ) frame_gravity = falling_movement.local_gravity * clock.game_time falling_movement.inertia += frame_gravity # Adjust lifter collider by inertia frame_inertia = falling_movement.inertia * clock.game_time lifter = falling_movement.solids['lifter'] node = lifter['node'] node.set_pos(lifter['center'] + controller.translation + frame_inertia) def fall_and_land(self, entity): falling_movement = entity[FallingMovement] clock = entity[Clock] controller = entity[CharacterController] falling_movement.ground_contact = False frame_falling = falling_movement.inertia * clock.game_time if len(falling_movement.contacts) > 0: lifter = falling_movement.solids['lifter']['node'] center = falling_movement.solids['lifter']['center'] radius = falling_movement.solids['lifter']['radius'] height_corrections = [] for contact in falling_movement.contacts: if contact.get_surface_normal(lifter).get_z() > 0.0: contact_point = contact.get_surface_point(lifter) - center x = contact_point.get_x() y = contact_point.get_y() # x**2 + y**2 + z**2 = radius**2 # z**2 = radius**2 - (x**2 + y**2) expected_z = -sqrt(radius**2 - (x**2 + y**2)) actual_z = contact_point.get_z() height_corrections.append(actual_z - expected_z) if height_corrections: frame_falling += Vec3(0, 0, max(height_corrections)) falling_movement.inertia = Vec3(0, 0, 0) falling_movement.ground_contact = True # Now we know how falling / lifting influences the character move controller.translation += frame_falling
class Inertiing(System): ''' Accelerate character, as opposed to an instantanious velocity. Components used :func:`wecs.core.and_filter` 'character' | :class:`wecs.panda3d.character.CharacterController` | :class:`wecs.panda3d.character.InertialMovement` | :class:`wecs.panda3d.model.Model` | :class:`wecs.model.clock` ''' entity_filters = { 'character': and_filter([ CharacterController, InertialMovement, Model, Clock, ]), } def enter_filter_character(self, entity): movement = entity[InertialMovement] model = entity[Model] movement.node.reparent_to(model.node) movement.node.set_hpr(0, 0, 0) def exit_filter_character(self, entity): # detach InertialMovement.node import pdb pdb.set_trace() def update(self, entities_by_filter): for entity in entities_by_filter['character']: dt = entity[Clock].game_time model = entity[Model] character = entity[CharacterController] inertia = entity[InertialMovement] # Usually you want to apply inertia only to x and y, and # ignore z, so we cache it. old_z = character.translation.z # We use inertia.node to represent "last frame's" model # orientation, scaled for how much inertia you'd like to # keep model-relative. Wow, what a sentence... # When you run forward and turn around, where should inertia # carry you? Physically, towards your new backward # direction. The opposite end of the scale of realism is # that your inertia vector turns around with you, and keeps # carrying you towards your new forward. # So if inertia.rotated_inertia = 1.0, inertia.node will # be aligned with the model, and thus the inertia vector # turns with you. If inertia.rotated_inertia = 0.0, # inertia.node will extrapolate the model's past rotation, # and the inertia vector will thus be kept still relative to # the surroundings. inertia.node.set_hpr( -character.last_rotation_speed * dt * (1 - inertia.rotated_inertia), ) last_speed_vector = model.node.get_relative_vector( inertia.node, character.last_translation_speed, ) # Now we calculate the wanted speed difference, and scale it # within gameplay limits. wanted_speed_vector = character.translation / dt delta_v = wanted_speed_vector - last_speed_vector max_delta_v = inertia.acceleration * dt if delta_v.length() > max_delta_v: capped_delta_v = delta_v / delta_v.length() * max_delta_v character.translation = (last_speed_vector + capped_delta_v) * dt if inertia.ignore_z: character.translation.z = old_z
class TurningBackToCamera(System): ''' Turns character away from the camera. Components used :func:`wecs.core.and_filter` 'character' | :class:`wecs.panda3d.character.TurningBackToCameraMovement` | :class:`wecs.panda3d.character.CharacterController` | :class:`wecs.panda3d.model.Model` | :class:`wecs.panda3d.camera.ThirdPersonCamera` | :class:`wecs.panda3d.camera.TurntableCamera` | :class:`wecs.panda3d.model.Clock` ''' entity_filters = { 'character': and_filter([ TurningBackToCameraMovement, CharacterController, Model, Camera, ObjectCentricCameraMode, Clock, or_filter([ WalkingMovement, FloatingMovement, ]), ]) } def update(self, entities_by_filter): for entity in entities_by_filter['character']: character = entity[CharacterController] model = entity[Model] camera = entity[Camera] center = entity[ObjectCentricCameraMode] turning = entity[TurningBackToCameraMovement] if WalkingMovement in entity: movement = entity[WalkingMovement] else: movement = entity[FloatingMovement] dt = entity[Clock].game_time if character.move.x**2 + character.move.y**2 > (turning.threshold * dt)**2: # What's the angle to turn? target_angle = camera.pivot.get_h() % 360 if target_angle > 180.0: target_angle = target_angle - 360.0 # How far can we turn this frame? Clamp to that. max_angle = movement.turning_speed * dt if abs(target_angle) > max_angle: target_angle *= max_angle / abs(target_angle) # How much of that do we *want* to turn? target_angle *= turning.view_axis_alignment # So let's turn, and clamp, in case we're already turning. old_rotation = character.rotation.x character.rotation.x += target_angle character.rotation.x = min(character.rotation.x, movement.turning_speed * dt) character.rotation.x = max(character.rotation.x, -movement.turning_speed * dt) # Since the camera rotates with the character, we need # to counteract that as well. delta_rotation = character.rotation.x - old_rotation camera.pivot.set_h(camera.pivot.get_h() - delta_rotation)
class UpdateCharacter(System): ''' Convert input to character movement. Components used :func:`wecs.core.and_filter` 'character' | :class:`wecs.panda3d.character.CharacterController` | :class:`wecs.panda3d.model.Clock` | :class:`wecs.panda3d.mode.Model` ''' entity_filters = { 'character': and_filter([ CharacterController, Clock, Model, ]), 'input': and_filter([ CharacterController, Input, ]), } input_context = 'character_movement' def update(self, entities_by_filter): for entity in entities_by_filter['input']: input = entity[Input] if self.input_context in input.contexts: context = base.device_listener.read_context(self.input_context) character = entity[CharacterController] character.move.x = context['direction'].x character.move.y = context['direction'].y character.heading = -context['rotation'].x character.pitch = context['rotation'].y # Special movement modes. # By default, you run ("sprint"), unless you press e, in # which case you walk. You can crouch by pressing q; this # overrides walking and running. Jump by pressing space. # This logic is implemented by the Walking system. Here, # only intention is signalled. character.jumps = context['jump'] character.sprints = context['sprint'] character.crouches = context['crouch'] for entity in entities_by_filter['character']: controller = entity[CharacterController] model = entity[Model] dt = entity[Clock].game_time # Rotation controller.rotation = Vec3( controller.heading * dt, controller.pitch * dt, 0, ) # Translation # Controllers gamepad etc.) fill a whole rectangle of input # space, but characters are limited to a circle. If you're # strafing diagonally, you still don't get sqrt(2) speed. xy_dist = sqrt(controller.move.x**2 + controller.move.y**2) xy_scaling = 1.0 if xy_dist > 1: xy_scaling = 1.0 / xy_dist x = controller.move.x * xy_scaling y = controller.move.y * xy_scaling z = controller.move.z * xy_scaling controller.translation = Vec3(x * dt, y * dt, z * dt)