Step 8 Python#

../../_images/step_06.png
step_08.py#
  1import random
  2from pyglet.math import Vec2
  3
  4import arcade
  5from arcade.experimental import Shadertoy
  6
  7# Do the math to figure out our screen dimensions
  8SCREEN_WIDTH = 800
  9SCREEN_HEIGHT = 600
 10SCREEN_TITLE = "Ray-casting Demo"
 11
 12SPRITE_SCALING = 0.25
 13
 14# How fast the camera pans to the player. 1.0 is instant.
 15CAMERA_SPEED = 0.1
 16
 17PLAYER_MOVEMENT_SPEED = 7
 18BOMB_COUNT = 70
 19PLAYING_FIELD_WIDTH = 1600
 20PLAYING_FIELD_HEIGHT = 1600
 21
 22
 23class MyGame(arcade.Window):
 24
 25    def __init__(self, width, height, title):
 26        super().__init__(width, height, title, resizable=True)
 27
 28        # The shader toy and 'channels' we'll be using
 29        self.shadertoy = None
 30        self.channel0 = None
 31        self.channel1 = None
 32        self.load_shader()
 33
 34        # Sprites and sprite lists
 35        self.player_sprite = None
 36        self.wall_list = arcade.SpriteList()
 37        self.player_list = arcade.SpriteList()
 38        self.bomb_list = arcade.SpriteList()
 39        self.physics_engine = None
 40
 41        # Create cameras used for scrolling
 42        self.camera_sprites = arcade.camera.Camera2D()
 43        self.camera_gui = arcade.camera.Camera2D()
 44
 45        self.generate_sprites()
 46
 47        # Our sample GUI text
 48        self.score_text = arcade.Text("Score: 0", 10, 10, arcade.color.WHITE, 24)
 49
 50        self.background_color = arcade.color.ARMY_GREEN
 51
 52    def load_shader(self):
 53        # Size of the window
 54        window_size = self.get_size()
 55
 56        # Create the shader toy, passing in a path for the shader source
 57        self.shadertoy = Shadertoy.create_from_file(window_size, "step_06.glsl")
 58
 59        # Create the channels 0 and 1 frame buffers.
 60        # Make the buffer the size of the window, with 4 channels (RGBA)
 61        self.channel0 = self.shadertoy.ctx.framebuffer(
 62            color_attachments=[self.shadertoy.ctx.texture(window_size, components=4)]
 63        )
 64        self.channel1 = self.shadertoy.ctx.framebuffer(
 65            color_attachments=[self.shadertoy.ctx.texture(window_size, components=4)]
 66        )
 67
 68        # Assign the frame buffers to the channels
 69        self.shadertoy.channel_0 = self.channel0.color_attachments[0]
 70        self.shadertoy.channel_1 = self.channel1.color_attachments[0]
 71
 72    def generate_sprites(self):
 73        # -- Set up several columns of walls
 74        for x in range(0, PLAYING_FIELD_WIDTH, 128):
 75            for y in range(0, PLAYING_FIELD_HEIGHT, int(128 * SPRITE_SCALING)):
 76                # Randomly skip a box so the player can find a way through
 77                if random.randrange(2) > 0:
 78                    wall = arcade.Sprite(":resources:images/tiles/boxCrate_double.png", SPRITE_SCALING)
 79                    wall.center_x = x
 80                    wall.center_y = y
 81                    self.wall_list.append(wall)
 82
 83        # -- Set some hidden bombs in the area
 84        for i in range(BOMB_COUNT):
 85            bomb = arcade.Sprite(":resources:images/tiles/bomb.png", 0.25)
 86            placed = False
 87            while not placed:
 88                bomb.center_x = random.randrange(PLAYING_FIELD_WIDTH)
 89                bomb.center_y = random.randrange(PLAYING_FIELD_HEIGHT)
 90                if not arcade.check_for_collision_with_list(bomb, self.wall_list):
 91                    placed = True
 92            self.bomb_list.append(bomb)
 93
 94        # Create the player
 95        self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_person/femalePerson_idle.png",
 96                                           scale=SPRITE_SCALING)
 97        self.player_sprite.center_x = 256
 98        self.player_sprite.center_y = 512
 99        self.player_list.append(self.player_sprite)
100
101        # Physics engine, so we don't run into walls
102        self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite, self.wall_list)
103
104        # Start centered on the player
105        self.scroll_to_player(1.0)
106
107    def on_draw(self):
108        # Use our scrolled camera
109        self.camera_sprites.use()
110
111        # Select the channel 0 frame buffer to draw on
112        self.channel0.use()
113        self.channel0.clear()
114        # Draw the walls
115        self.wall_list.draw()
116
117        self.channel1.use()
118        self.channel1.clear()
119        # Draw the bombs
120        self.bomb_list.draw()
121
122        # Select this window to draw on
123        self.use()
124        # Clear to background color
125        self.clear()
126
127        # Calculate the light position. We have to subtract the camera position
128        # from the player position to get screen-relative coordinates.
129        p = (self.player_sprite.position[0] - self.camera_sprites.left,
130             self.player_sprite.position[1] - self.camera_sprites.bottom)
131
132        # Set the uniform data
133        self.shadertoy.program['lightPosition'] = p
134        self.shadertoy.program['lightSize'] = 300
135
136        # Run the shader and render to the window
137        self.shadertoy.render()
138
139        # Draw the walls
140        self.wall_list.draw()
141
142        # Draw the player
143        self.player_list.draw()
144
145        # Switch to the un-scrolled camera to draw the GUI with
146        self.camera_gui.use()
147        # Draw our sample GUI text
148        self.score_text.draw()
149
150    def on_key_press(self, key, modifiers):
151        """Called whenever a key is pressed. """
152
153        if key == arcade.key.UP:
154            self.player_sprite.change_y = PLAYER_MOVEMENT_SPEED
155        elif key == arcade.key.DOWN:
156            self.player_sprite.change_y = -PLAYER_MOVEMENT_SPEED
157        elif key == arcade.key.LEFT:
158            self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
159        elif key == arcade.key.RIGHT:
160            self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
161
162    def on_key_release(self, key, modifiers):
163        """Called when the user releases a key. """
164
165        if key == arcade.key.UP or key == arcade.key.DOWN:
166            self.player_sprite.change_y = 0
167        elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
168            self.player_sprite.change_x = 0
169
170    def on_update(self, delta_time):
171        """ Movement and game logic """
172
173        # Call update on all sprites (The sprites don't do much in this
174        # example though.)
175        self.physics_engine.update()
176        # Scroll the screen to the player
177        self.scroll_to_player()
178
179    def scroll_to_player(self, speed=CAMERA_SPEED):
180        """
181        Scroll the window to the player.
182
183        if CAMERA_SPEED is 1, the camera will immediately move to the desired position.
184        Anything between 0 and 1 will have the camera move to the location with a smoother
185        pan.
186        """
187
188        position = (self.player_sprite.center_x, self.player_sprite.center_y)
189        self.camera_sprites.position = arcade.math.lerp_2d(self.camera_sprites.position, position, CAMERA_SPEED)
190
191    def on_resize(self, width: int, height: int):
192        super().on_resize(width, height)
193        self.camera_sprites.match_screen(and_projection=True)
194        self.camera_gui.match_screen(and_projection=True)
195        self.shadertoy.resize((width, height))
196
197
198if __name__ == "__main__":
199    MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
200    arcade.run()