带帧缓冲的生活游戏#

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