class Jumping(CollisionSystem): ''' Make the character jump. Components used :func:`wecs.core.and_filter` 'character' | :class:`wecs.panda3d.character.CharacterController` | :class:`wecs.panda3d.character.JumpingMovement` | :class:`wecs.panda3d.character.FallingMovement` | :class:`wecs.panda3d.model.Scene` | :class:`wecs.panda3d.model.Clock` | :class:`wecs.panda3d.model.Model` ''' entity_filters = { 'character': and_filter([ Proxy('scene_node'), Proxy('character_node'), Clock, CharacterController, JumpingMovement, FallingMovement, ]), } proxies = { 'character_node': ProxyType(Model, 'node'), 'scene_node': ProxyType(Model, 'parent'), } 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 StartBallMotion(System): """ StartBallMotion ensures that the game restarts after the start key is pressed. """ entity_filters = { 'ball': and_filter([ Proxy('model'), Resting, Ball, ]), } start_key = KeyboardButton.space() def update(self, entities_by_filter): """ Check whether the resting ball should be started? Note that restarting the ball's movement means removing the Entity's Resting Component, and adding it's Movement component with the desired direction. """ start_key_is_pressed = base.mouseWatcherNode.is_button_down( StartBallMotion.start_key) if start_key_is_pressed: for entity in set(entities_by_filter['ball']): del entity[Resting] entity[Movement] = Movement( direction=Vec3(-0.1 * randrange(5, 10), 0, 0))
class BallTouchesBoundary(System): """ BallTouchesBoundary ensures the balls is bounced of the top and bottom of the court. """ entity_filters = { 'ball': and_filter([ Proxy('model'), Movement, Ball, ]), } def update(self, entities_by_filter): for entity in entities_by_filter['ball']: model_proxy = self.proxies['model'] model_node = model_proxy.field(entity) movement = entity[Movement] # The ball's size is assumed to be 0.1, and if it moved over # the upper or lower boundary (1 / -1), we reflect it. z = model_node.get_z() if z > 0.9: model_node.set_z(0.9 - (z - 0.9)) movement.direction.z = -movement.direction.z if z < -0.9: model_node.set_z(-0.9 - (z + 0.9)) movement.direction.z = -movement.direction.z
class ErectCharacter(System): entity_filters = { 'character': [ Proxy('model_node'), CharacterController, ], } proxies = {'model_node': ProxyType(Model, 'node')} def update(self, entities_by_filter): for entity in entities_by_filter['character']: character = entity[CharacterController] model_node = self.proxies['model_node'].field(entity) up = character.gravity * -1 roll = math.atan(character.gravity.x / character.gravity.z) / (2.0 * math.pi) * 360.0 model_node.set_r(model_node, roll) # FIXME: We now shoud recalculate gravity by also rolling the vector. pitch = math.atan(character.gravity.y / character.gravity.z) / (2.0 * math.pi) * 360.0 model_node.set_p(model_node, -pitch) character.gravity = Vec3(0, 0, -character.gravity.length())
class GivePaddlesMoveCommands(System): entity_filters = { 'paddle': and_filter([ Proxy('model'), Movement, Paddle, ]), } def update(self, entities_by_filter): for entity in entities_by_filter['paddle']: paddle = entity[Paddle] movement = entity[Movement] # What keys does the player use? if paddle.player == Players.LEFT: up_key = KeyboardButton.ascii_key(b'w') down_key = KeyboardButton.ascii_key(b's') elif paddle.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.direction.z = delta
class MoveObject(System): """ :class:MoveObject update the position of the Entity's :class:Model according to it's movement direction. """ entity_filters = { 'movable': and_filter([ Proxy('model'), Movement, ]), } def update(self, entities_by_filter): """ On update, iterate all 'movable' entities. For each: - Get its position - Get its movement(direction) - Get its model - finally, update its model according to position and direction Note the position is update by the direction multiplied by dt, which is the deltaTime since the previous update, as the update function is called several times per second. :param entities_by_filter: """ for entity in entities_by_filter['movable']: movement = entity[Movement] model_proxy = self.proxies['model'] model = entity[model_proxy.component_type] movement = movement.direction * globalClock.dt model.node.set_pos(model.node, movement)
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([ Proxy('scene_node'), Proxy('character_node'), Clock, CharacterController, BumpingMovement, ]), } proxies = { 'character_node': ProxyType(Model, 'node'), 'scene_node': ProxyType(Model, 'parent'), } 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_proxy = self.proxies['scene_node'] scene = entity[scene_proxy.component_type] scene_node = scene_proxy.field(entity) 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 AnimateCharacter(System): entity_filters = { 'animated_character': and_filter([Proxy('actor'), Animation, CharacterController]) } proxies = {'actor': ProxyType(Actor, 'node')} def update(self, entities_by_filter): for entity in entities_by_filter['animated_character']: controller = entity[CharacterController] animation = entity[Animation] actor_proxy = self.proxies['actor'] actor = actor_proxy.field(entity) if FallingMovement in entity: grounded = entity[FallingMovement].ground_contact else: grounded = False initial = "idle" if not grounded: if controller.translation.z > 0.1: initial = "jump" elif controller.translation.z < -0.1: initial = "fall" elif controller.crouches: initial = "crouch" animation.to_play = [initial, "walk_forward", "run_forward"] # TODO: bad constant, 1.4? Should be fixed in animation # when the right value is found in lab. forward_speed = abs(controller.translation.y * 1.4) idle = max(0, (1 - forward_speed)) walk = 1 - abs(forward_speed - 0.5) * 2 run = max(0, forward_speed * 2 - 1) blends = [idle, walk, run] # sideways movement # TODO: same here, another constant. Fix in animation after lab. strafe_speed = (controller.translation.x * 1.4) if not strafe_speed == 0: blends.append(abs(strafe_speed)) if strafe_speed > 0: animation.to_play.append("walk_right") elif strafe_speed < 0: animation.to_play.append("walk_left") animation.framerate = (0.5 + (forward_speed + abs(strafe_speed))) # If walking backwards simply play the animation in reverse # TODO: Only do this when there's no animations for walking backwards. if controller.translation.y < 0: animation.framerate = -animation.framerate if controller.translation.z < -0.2: animation.framerate *= 0.2 animation.blends = blends
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([ Proxy('character_node'), Clock, AutomaticTurningMovement, TurningBackToCameraMovement, CharacterController, or_filter([ WalkingMovement, FloatingMovement, ]), Camera, ObjectCentricCameraMode, ]) } proxies = {'character_node': ProxyType(Model, 'node')} def update(self, entities_by_filter): for entity in entities_by_filter['character']: character = entity[CharacterController] camera = entity[Camera] center = entity[ObjectCentricCameraMode] turning = entity[TurningBackToCameraMovement] autoturning = entity[AutomaticTurningMovement] model_node = self.proxies['character_node'].field(entity) dt = entity[Clock].game_time autoturning.direction = model_node.get_relative_vector( camera.pivot, Vec3(0, 1, 0), ) if character.move.xy.length() >= turning.threshold * dt: autoturning.alignment = turning.view_axis_alignment else: autoturning.alignment = 0.0
class Animate(System): entity_filters = { 'animation': and_filter([ Proxy('actor'), Animation, ]) } proxies = {'actor': ProxyType(Actor, 'node')} def update(self, entities_by_filter): for entity in entities_by_filter['animation']: animation = entity[Animation] actor_proxy = self.proxies['actor'] actor = actor_proxy.field(entity) 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 name not 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 BareTypeProxy(System): entity_filters = { 'test': Proxy('proxy'), } proxies = { 'proxy': ProxyType(TestComponent, 'foo'), } def update(self, entity_by_filters): for entity in entity_by_filters['test']: proxy = self.proxies['proxy'] # test = entity[proxy.component_type] token = proxy.field(entity) global token_out token_out = token
class ReorientInputBasedOnCamera(System): """ By default, player input is relative to the character. If it is viewed from a third person perspective, it is usually preferable to rotate them so that they align with the camera instead. """ entity_filters = { 'reorient': and_filter([ Camera, ObjectCentricCameraMode, CameraReorientedInput, Proxy('model'), CharacterController, ]), } proxies = { 'model': ProxyType(Model, 'node'), } def enter_filter_reorient(self, entity): camera = entity[Camera] reorienter = entity[CameraReorientedInput] reorienter.node.reparent_to(camera.camera) def exit_filter_reorient(self, entity): reorienter = entity[CameraReorientedInput] reorienter.node.detach_node() def update(self, entities_by_filter): for entity in entities_by_filter['reorient']: model_proxy = self.proxies['model'] model = entity[model_proxy.component_type] model_node = model_proxy.field(entity) character = entity[CharacterController] camera = entity[Camera] reorienter = entity[CameraReorientedInput] reorienter.node.set_p(model_node, 0) character.translation = model_node.get_relative_vector( reorienter.node, character.translation, )
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 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 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)
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 AdjustGravity(System): entity_filters = { 'character': [ Proxy('model_node'), CharacterController, ], } proxies = {'model_node': ProxyType(Model, 'node')} def update(self, entities_by_filter): for entity in entities_by_filter['character']: character = entity[CharacterController] model_node = self.proxies['model_node'].field(entity) gravity_node = base.render attractor = model_node.get_pos(gravity_node) attractor.y = 0.0 attractor.normalize() attractor *= 9.81 local_gravity = model_node.get_relative_vector( gravity_node, attractor, ) character.gravity = local_gravity
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([ Proxy('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_proxy = self.proxies['model'] paddle = entity[Paddle] model_proxy.field(entity).set_scale(paddle.size)
class ProxyingNullSystem(NullSystem): entity_filters = { "null": Proxy('null_proxy'), }
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([ Proxy('character_node'), # Not used, but required for a complete character Clock, CharacterController, ]), 'input': and_filter([ CharacterController, Input, ]), } proxies = {'character_node': ProxyType(Model, 'node')} 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] if context['direction'] is not None: character.move.x = context['direction'].x character.move.y = context['direction'].y else: character.move = Vec2(0, 0) if context['rotation'] is not None: character.heading = -context['rotation'].x character.pitch = context['rotation'].y else: character.heading = 0 character.pitch = 0 # 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 = False character.sprints = False character.crouches = False if 'jump' in context: character.jumps = context['jump'] if 'sprint' in context: character.sprints = context['sprint'] if 'crouch' in context: character.crouches = context['crouch'] for entity in entities_by_filter['character']: controller = entity[CharacterController] 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)
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([ Proxy('character_node'), Clock, CharacterController, InertialMovement, ]), } proxies = {'character_node': ProxyType(Model, 'node')} def enter_filter_character(self, entity): movement = entity[InertialMovement] model_proxy = self.proxies['character_node'] model = entity[model_proxy.component_type] model_node = model_proxy.field(entity) 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_proxy = self.proxies['character_node'] model = entity[model_proxy.component_type] model_node = model_proxy.field(entity) 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. And if it is between those, it will # interpolate accordingly. 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 if inertia.delta_inputs: delta_v = wanted_speed_vector else: 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 else: capped_delta_v = delta_v character.translation = (last_speed_vector + capped_delta_v) * dt if inertia.ignore_z: character.translation.z = old_z
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([ Proxy('scene_node'), Proxy('character_node'), Clock, CharacterController, FallingMovement, ]), } proxies = { 'character_node': ProxyType(Model, 'node'), 'scene_node': ProxyType(Model, 'parent'), } 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): character = entity[CharacterController] model_proxy = self.proxies['character_node'] model = entity[model_proxy.component_type] model_node = model_proxy.field(entity) scene_proxy = self.proxies['scene_node'] scene = entity[scene_proxy.component_type] scene_node = scene_proxy.field(entity) clock = entity[Clock] controller = entity[CharacterController] falling_movement = entity[FallingMovement] # Adjust inertia by gravity frame_gravity = character.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(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: # FIXME: We're assuming a sphere here. # We only use the lower half of the sphere, no equator. if contact.get_surface_normal(lifter).get_z() > 0.0: contact_point = contact.get_surface_point(lifter) contact_point -= center # In solid's space xy = contact_point.xy expected_z = -sqrt(radius ** 2 - xy.length() ** 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 movement controller.translation += frame_falling
class BallTouchesPaddleLine(System): """ BallTouchesPaddleLine takes care what happens when the ball reaches the line of one of the paddles: Either the paddle is in reach, and the ball reflects off it, or the other player has scored, and the game is reset to its starting state. """ entity_filters = { 'ball': and_filter([ Proxy('model'), Movement, Ball, ]), 'paddles': and_filter([ Proxy('model'), Paddle, ]), } def update(self, entities_by_filter): """ The Update goes over all the relevant Entities (which should be only the ball) and check whether it reached each paddle's line. A ball always has a Position component so it has so we can check it's x position. Same goes for the paddles. If x is touching the paddle's line, we check whether the ball hit the paddle or not. If it hit the paddle it would bounce back. If it misses the paddle, it would print "SCORE" and stop the balls movement. Note that to stop the ball's movement the update deletes the Movement component from the ball's Entity. It also adds the Resting component to ensure that the StartBallMotion System will return the ball to a moving state when the game restarts. """ paddles = {p[Paddle].player: p for p in entities_by_filter['paddles']} for entity in set(entities_by_filter['ball']): model_proxy = self.proxies['model'] ball_node = model_proxy.field(entity) movement = entity[Movement] # Whose line are we behind, if any? ball_x = ball_node.get_x() if ball_x < -1: player = Players.LEFT elif ball_x > 1: player = Players.RIGHT else: continue ball_z = ball_node.get_z() paddle = paddles[player] paddle_node = model_proxy.field(paddle) paddle_paddle = paddle[Paddle] paddle_z = paddle_node.get_z() paddle_size = paddle_paddle.size if abs(paddle_z - ball_z) > paddle_size: # The paddle is too far away, a point is scored. print("SCORE!") del entity[Movement] entity[Resting] = Resting() ball_node.set_pos(0, 0, 0) else: # Reverse left-right direction movement.direction.x *= -1 # Adjust up-down speed based on where the paddle was hit dist_to_center = paddle_z - ball_z normalized_dist = dist_to_center / paddle_size speed = abs(movement.direction.x) movement.direction.z -= normalized_dist * speed
class AutomaticallyTurnTowardsDirection(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([ Proxy('character_node'), Clock, AutomaticTurningMovement, CharacterController, or_filter([ WalkingMovement, FloatingMovement, ]), ]) } proxies = {'character_node': ProxyType(Model, 'node')} def update(self, entities_by_filter): for entity in entities_by_filter['character']: character = entity[CharacterController] turning = entity[AutomaticTurningMovement] model_node = self.proxies['character_node'].field(entity) if WalkingMovement in entity: movement = entity[WalkingMovement] else: movement = entity[FloatingMovement] dt = entity[Clock].game_time if turning.direction.xy.length() > 0.0: # How much would he have to adjust heading to face # towards the given vector? direc = Vec2(turning.direction.xy) angle = Vec2(0, 1).signed_angle_deg(direc) # How far can we turn this frame? Clamp to that. max_angle = movement.turning_speed if abs(angle) > max_angle: angle = copysign(max_angle, angle) # How much of that do we *want* to turn? angle *= turning.alignment # So let's turn, and clamp, in case we're already turning. old_rotation = character.rotation.x character.rotation.x += angle character.rotation.x = min( character.rotation.x, max_angle, ) character.rotation.x = max( character.rotation.x, -max_angle, ) # Since the camera rotates with the character, we need # to counteract that as well. delta_rotation = character.rotation.x - old_rotation turning.angle = delta_rotation # FIXME: This needs to be its own system camera = entity[Camera] camera.pivot.set_h(camera.pivot.get_h() - delta_rotation)
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([ Proxy('character_node'), Clock, TurningBackToCameraMovement, CharacterController, or_filter([ WalkingMovement, FloatingMovement, ]), Camera, ObjectCentricCameraMode, ]) } proxies = {'character_node': ProxyType(Model, 'node')} def update(self, entities_by_filter): for entity in entities_by_filter['character']: character = entity[CharacterController] 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 PrepareCameras(System): """ Add a `Camera` to an entity with the `'model`' proxy to attach a camera to its node. Add `MountedCameraMode` or `ObjectCentricCameraMode` to specify how the camera should place itself. """ entity_filters = { 'camera': and_filter([ Camera, Proxy('model'), ]), 'mount': and_filter([ Camera, MountedCameraMode, ]), 'mount_actor': and_filter([ Camera, MountedCameraMode, Actor, ]), 'center': and_filter([ Camera, ObjectCentricCameraMode, ]), } proxies = { 'model': ProxyType(Model, 'node'), } def enter_filter_camera(self, entity): model_proxy = self.proxies['model'] model = entity[model_proxy.component_type] camera = entity[Camera] camera.camera.reparent_to(camera.pivot) camera.pivot.reparent_to(model_proxy.field(entity)) camera.camera.node().get_lens().set_fov(camera.fov) def exit_filter_camera(self, entity): camera = entity[Camera] camera.pivot.detach_node() camera.camera.detach_node() def enter_filter_mount(self, entity): camera = entity[Camera] camera.pivot.set_pos(0, 0, 0) camera.pivot.set_hpr(0, 0, 0) camera.camera.set_pos(0, 0, 0) camera.camera.set_hpr(0, 0, 0) def enter_filter_mount_actor(self, entity): camera = entity[Camera] node = entity[Actor].node joint = node.expose_joint(None, 'modelRoot', 'camera') if joint: camera.pivot.set_pos((0, 0, 0)) camera.pivot.reparent_to(joint) def exit_filter_mount(self, entity): model_proxy = self.proxies['model'] model = entity[model_proxy.component_type] camera = entity[Camera] def update(self, entities_by_filter): for entity in entities_by_filter['center']: center = entity[ObjectCentricCameraMode] center.heading = 0 center.pitch = 0 center.zoom = 0
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