Moving the Paddles - Keyboard Input - Demo 04¶
Purpose¶
Add movement to the paddles using keyboard input.
How to Execute¶
On Linux or on MacOS, in a shell, type “python src/demo04/demo.py”. On Windows, in a command prompt, type “python src\demo04\demo.py”.
Move the Paddles using the Keyboard¶
Keyboard Input |
Action |
---|---|
w |
Move Left Paddle Up |
s |
Move Left Paddle Down |
k |
Move Right Paddle Down |
i |
Move Right Paddle Up |
Paddles which don’t move are quite boring. Let’s make them move up or down by getting keyboard input.
And while we are at it, let’s go ahead and create data structures for a Vertex, and for the collection of vertices that make up a Paddle.
Code¶
Data Structures¶
Here we use dataclasses, which automatically creates on the class a constructor, accessor methods, and pretty-printer. This saves a lot of boiler plate code.
104@dataclass
105class Vertex:
106 x: float
107 y: float
108
109
114@dataclass
115class Paddle:
116 vertices: list[Vertex]
117 r: float
118 g: float
119 b: float
120
121
Although Python is a dynamically-typed language, we can add type information as helpful hints to the reader, and for use with static type-checking tools for Python, such as mypy.
125paddle1 = Paddle(
126 vertices=[
127 Vertex(x=-1.0, y=-0.3),
128 Vertex(x=-0.8, y=-0.3),
129 Vertex(x=-0.8, y=0.3),
130 Vertex(x=-1.0, y=0.3),
131 ],
132 r=0.578123,
133 g=0.0,
134 b=1.0,
135)
136
137paddle2 = Paddle(
138 vertices=[Vertex(0.8, -0.3), Vertex(1.0, -0.3), Vertex(1.0, 0.3), Vertex(0.8, 0.3)],
139 r=1.0,
140 g=1.0,
141 b=0.0,
142)
Create two instances of a Paddle.
I make heavy use of keyword arguments in Python.
Notice that I am nesting the constructors. I could have instead have written the construction of paddle1 like this:
x = -1.0
y = -0.3
vertex_one = Vertex(x, y)
x = -0.8
y = -0.3
vertex_two = Vertex(x, y)
x = -0.8
y = 0.3
vertex_three = Vertex(x, y)
x = -1.0
y = 0.3
vertex_four = Vertex(x, y)
vertex_list = list(vertex_one, vertex_two, vertex_three, vertex_four)
r = 0.57
g = 0.0
b = 1.0
paddle1 = Paddle(vertex_list, r, g, b)
But then I would have many local variables, some of whose values change frequently over time, and most of which are single use variables. By nesting the constructors as the author has done above, the author minimizes those issues at the expense of requiring a degree on non-linear reading of the code, which gets easy with practice.
Query User Input and Use It To Animate¶
147def handle_movement_of_paddles() -> None:
148 global paddle1, paddle2
149 if glfw.get_key(window, glfw.KEY_S) == glfw.PRESS:
150 for v in paddle1.vertices:
151 v.y -= 0.1
152 if glfw.get_key(window, glfw.KEY_W) == glfw.PRESS:
153 for v in paddle1.vertices:
154 v.y += 0.1
155 if glfw.get_key(window, glfw.KEY_K) == glfw.PRESS:
156 for v in paddle2.vertices:
157 v.y -= 0.1
158 if glfw.get_key(window, glfw.KEY_I) == glfw.PRESS:
159 for v in paddle2.vertices:
160 v.y += 0.1
161
162
If the user presses ‘s’ this frame, subtract 0.1 from the y component of each of the vertices in the paddle. If the key continues to be held down over time, this value will continue to decrease.
If the user presses ‘w’ this frame, add 0.1 more to the y component of each of the vertices in the paddle
If the user presses ‘k’ this frame, subtract .1.
If the user presses ‘i’ this frame, add .1 more.
when writing to global variables within a nested scope, you need to declare their scope as global at the top of the nested scope. (technically it is not a global variable, it is local to the current python module, but the point remains)
The Event Loop¶
Monitors can have variable frame-rates, and in order to ensure that movement is consistent across different monitors, we choose to only flush the screen at 60 hertz (frames per second).
166TARGET_FRAMERATE: int = 60
167
168time_at_beginning_of_previous_frame: float = glfw.get_time()
172while not glfw.window_should_close(window):
173 while (
174 glfw.get_time() < time_at_beginning_of_previous_frame + 1.0 / TARGET_FRAMERATE
175 ):
176 pass
177
178 time_at_beginning_of_previous_frame = glfw.get_time()
182 glfw.poll_events()
183
184 width, height = glfw.get_framebuffer_size(window)
185 glViewport(0, 0, width, height)
186 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
190 draw_in_square_viewport()
194 handle_movement_of_paddles()
We’re still near the beginning of the event loop, and we haven’t drawn the paddles yet. So we call the function to query the user input, which will also modify the vertices’ values if there was input.
198 glColor3f(paddle1.r, paddle1.g, paddle1.b)
199
200 glBegin(GL_QUADS)
201 for vertex in paddle1.vertices:
202 glVertex2f(vertex.x, vertex.y)
203 glEnd()
While rendering, we now loop over the vertices of the paddle. The paddles may be displaced from their original position that was hard-coded, as the callback may have updated the values based off of the user input.
When glVertex is now called, we are not directly passing numbers into it, but instead we are getting the numbers from the data structures of Paddle and its associated vertices.
207 glColor3f(paddle2.r, paddle2.g, paddle2.b)
208
209 glBegin(GL_QUADS)
210 for vertex in paddle2.vertices:
211 glVertex2f(vertex.x, vertex.y)
212 glEnd()
216 glfw.swap_buffers(window)