带帧缓冲的生活游戏#

《生活的游戏》截图
game_of_life_colors.py#
  1"""
  2Game of Life - Shader Version
  3
  4This version uses shaders which draws to textures to
  5run Conway's Game of Life with an added twist: colors.
  6
  7Press SPACE to reset the simulation with random data.
  8
  9It uses two textures: One to keep the old state and
 10a second to draw the new state into. These textures are
 11flipped around after every update.
 12
 13You can configure the cell and window size by changing
 14the constants at the top of the file after the imports.
 15
 16If Python and Arcade are installed, this example can be run from the command line with:
 17python -m arcade.examples.gl.game_of_life_colors
 18"""
 19import random
 20from array import array
 21
 22import arcade
 23from arcade import key
 24from arcade.gl import geometry
 25
 26
 27CELL_SIZE = 2  # Cell size in pixels
 28WINDOW_WIDTH = 512  # Width of the window
 29WINDOW_HEIGHT = 512  # Height of the window
 30FRAME_DELAY = 2  # The game will only update every 2nd frame
 31
 32
 33class GameOfLife(arcade.Window):
 34
 35    def __init__(self, width, height):
 36        super().__init__(width, height, "Game of Life - Shader Version")
 37        self.frame = 0
 38
 39        # Calculate how many cells we need to simulate at this pixel size
 40        self.texture_size = width // CELL_SIZE, height // CELL_SIZE
 41
 42        # Create two textures for the next and previous state (RGB textures)
 43        self.texture_1 = self.ctx.texture(
 44            self.texture_size,
 45            components=3,
 46            filter=(self.ctx.NEAREST, self.ctx.NEAREST),
 47        )
 48        self.texture_2 = self.ctx.texture(
 49            self.texture_size,
 50            components=3,
 51            filter=(self.ctx.NEAREST, self.ctx.NEAREST)
 52        )
 53        self.write_initial_state()
 54
 55        # Add the textures to framebuffers so we can render to them
 56        self.fbo_1 = self.ctx.framebuffer(color_attachments=[self.texture_1])
 57        self.fbo_2 = self.ctx.framebuffer(color_attachments=[self.texture_2])
 58
 59        # Fullscreen quad (using triangle strip)
 60        self.quad_fs = geometry.quad_2d_fs()
 61
 62        # Shader to draw the texture
 63        self.display_program = self.ctx.program(
 64            vertex_shader="""
 65            #version 330
 66
 67            in vec2 in_vert;
 68            in vec2 in_uv;
 69            out vec2 uv;
 70
 71            void main() {
 72                gl_Position = vec4(in_vert, 0.0, 1.0);
 73                uv = in_uv;
 74            }
 75            """,
 76            fragment_shader="""
 77            #version 330
 78
 79            uniform sampler2D texture0;
 80            out vec4 fragColor;
 81            in vec2 uv;
 82
 83            void main() {
 84                fragColor = texture(texture0, uv);
 85            }
 86            """,
 87        )
 88
 89        # Shader which calculates the next game state.
 90        # It uses the previous state as input (texture0) and
 91        # renders the next state into the second texture.
 92        self.life_program = self.ctx.program(
 93            vertex_shader="""
 94            #version 330
 95            in vec2 in_vert;
 96
 97            void main() {
 98                gl_Position = vec4(in_vert, 0.0, 1.0);
 99            }
100            """,
101            fragment_shader="""
102            #version 330
103
104            uniform sampler2D texture0;
105            out vec4 fragColor;
106
107            // Check if something is living in the cell
108            bool cell(vec4 fragment) {
109                return length(fragment.xyz) > 0.1;
110            }
111
112            void main() {
113                // Get the pixel position we are currently writing
114                ivec2 pos = ivec2(gl_FragCoord.xy);
115
116                // Grab neighbor fragments + current one
117                vec4 v1 = texelFetch(texture0, pos + ivec2(-1, -1), 0);
118                vec4 v2 = texelFetch(texture0, pos + ivec2( 0, -1), 0);
119                vec4 v3 = texelFetch(texture0, pos + ivec2( 1, -1), 0);
120
121                vec4 v4 = texelFetch(texture0, pos + ivec2(-1, 0), 0);
122                vec4 v5 = texelFetch(texture0, pos, 0);
123                vec4 v6 = texelFetch(texture0, pos + ivec2(1,  0), 0);
124
125                vec4 v7 = texelFetch(texture0, pos + ivec2(-1, 1), 0);
126                vec4 v8 = texelFetch(texture0, pos + ivec2( 0, 1), 0);
127                vec4 v9 = texelFetch(texture0, pos + ivec2( 1, 1), 0);
128
129                // Cell in current position is alive?
130                bool living = cell(v5);
131
132                // Count how many neighbors is alive
133                int neighbors = 0;
134                if (cell(v1)) neighbors++;
135                if (cell(v2)) neighbors++;
136                if (cell(v3)) neighbors++;
137                if (cell(v4)) neighbors++;
138                if (cell(v6)) neighbors++;
139                if (cell(v7)) neighbors++;
140                if (cell(v8)) neighbors++;
141                if (cell(v9)) neighbors++;
142
143                // Average color for all neighbors
144                vec4 sum = (v1 + v2 + v3 + v4 + v6 + v7 + v8 + v9) / float(neighbors);
145
146                if (living) {
147                    if (neighbors == 2 || neighbors == 3) {
148                        // The cell lives, but we write out the average color minus a small value
149                        fragColor = vec4(sum.rgb - vec3(1.0/255.0), 1.0);
150                    } else {
151                        // The cell dies when too few or too many neighbors
152                        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
153                    }
154                } else {
155                    if (neighbors == 3) {
156                        // A new cell was born
157                        fragColor = vec4(normalize(sum.rgb), 1.0);
158                    } else {
159                        // Still dead
160                        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
161                    }
162                }
163            }
164            """,
165        )
166
167    def gen_initial_data(self, num_values: int):
168        """Generate initial data.
169
170        We need to be careful about the initial game state. Carelessly
171        random numbers will make the simulation die in only a few frames.
172        Instead, we need to generate values which leave room for life
173        to exist.
174
175        The implementtation below is one of the slowest possible ways to
176        would generate the initial data, but it keeps things simple for
177        this example.
178        """
179        choices = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 64, 128, 192, 255]
180        for i in range(num_values):
181            yield random.choice(choices)
182
183    def write_initial_state(self):
184        """Write initial data to the source texture."""
185        size = self.texture_size
186        self.texture_1.write(array('B', self.gen_initial_data(size[0] * size[1] * 3)))
187
188    def on_draw(self):
189        self.clear()
190
191        # Should we do an update this frame?
192        if self.frame % FRAME_DELAY == 0:
193            # Calculate the next state
194            self.fbo_2.use()  # Render to texture 2
195            self.texture_1.use()  # Take texture 1 as input
196            self.quad_fs.render(self.life_program)  # Run the life program
197
198            # Draw result to screen
199            self.ctx.screen.use()  # Switch back to rendering to screen
200            self.texture_2.use()  # Take texture 2 as input
201            self.quad_fs.render(self.display_program)  # Display the texture
202
203            # Swap things around for the next frame
204            self.texture_1, self.texture_2 = self.texture_2, self.texture_1
205            self.fbo_1, self.fbo_2 = self.fbo_2, self.fbo_1
206        # Otherwise just draw the current texture
207        else:
208            # Draw the current texture to the screen
209            self.ctx.screen.use()  # Switch back to rendering to screen
210            self.texture_1.use()  # Take texture 2 as input
211            self.quad_fs.render(self.display_program)  # Display the texture
212
213    def on_update(self, delta_time: float):
214        # Track the number of frames
215        self.frame += 1
216
217    def on_key_press(self, symbol: int, modifiers: int):
218        if symbol == key.SPACE:
219            self.write_initial_state()
220
221
222if __name__ == "__main__":
223    game = GameOfLife(WINDOW_WIDTH, WINDOW_HEIGHT)
224    game.run()