bouncy fpga game
www.youtube.com/watch?v=IiLWF3GbV7w
1-- ========================================================================
2-- Physics Engine
3-- Runs once per frame on vert_sync rising edge (~60Hz).
4-- Handles gravity, keyboard input, friction, bouncing off walls/floor/
5-- ceiling/obstacles, and squish animation.
6--
7-- Outputs character position and animated dimensions for the renderer.
8-- All velocity math is 10-bit 2's complement (bit 9 = sign).
9-- ========================================================================
10
11library IEEE;
12use IEEE.STD_LOGIC_1164.all;
13use IEEE.STD_LOGIC_ARITH.all;
14use IEEE.STD_LOGIC_UNSIGNED.all;
15
16entity physics_engine is
17 port(
18 vert_sync : in std_logic; -- frame clock (~60Hz)
19 key_w : in std_logic; -- from ps2_decoder
20 key_a : in std_logic;
21 key_s : in std_logic;
22 key_d : in std_logic;
23 char_x : out std_logic_vector(9 downto 0); -- character center X
24 char_y : out std_logic_vector(9 downto 0); -- character center Y
25 char_width : out std_logic_vector(9 downto 0); -- animated half-width
26 char_height : out std_logic_vector(9 downto 0) -- animated half-height
27 );
28end physics_engine;
29
30architecture behavior of physics_engine is
31
32 -- Character state
33 signal pos_x : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(100, 10);
34 signal pos_y : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(200, 10);
35 signal vel_x : std_logic_vector(9 downto 0) := (others => '0');
36 signal vel_y : std_logic_vector(9 downto 0) := (others => '0');
37
38 constant SIZE : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(7, 10);
39
40 -- Tuning constants
41 constant GRAVITY : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(1, 10);
42 constant IMPULSE : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(3, 10);
43 constant JUMP_FORCE : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(13, 10);
44 constant MAX_VEL_X : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(32, 10);
45
46 -- Screen bounds
47 constant GROUND : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(440, 10);
48 constant CEILING : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(16, 10);
49 constant LEFT_WALL : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(8, 10);
50 constant RIGHT_WALL: std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(631, 10);
51
52 -- Obstacle positions (must match renderer)
53 constant O1_L : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(60, 10);
54 constant O1_T : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(370, 10);
55 constant O1_R : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(180, 10);
56 constant O1_B : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(386, 10);
57
58 constant O2_L : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(250, 10);
59 constant O2_T : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(300, 10);
60 constant O2_R : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(390, 10);
61 constant O2_B : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(316, 10);
62
63 constant O3_L : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(440, 10);
64 constant O3_T : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(200, 10);
65 constant O3_R : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(580, 10);
66 constant O3_B : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(216, 10);
67
68 constant O4_L : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(140, 10);
69 constant O4_T : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(120, 10);
70 constant O4_R : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(200, 10);
71 constant O4_B : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(150, 10);
72
73 -- Animation state
74 signal squish : std_logic_vector(3 downto 0) := (others => '0');
75 signal squish_h : std_logic := '0'; -- '0'=vertical squish, '1'=horizontal
76 signal on_ground : std_logic := '0';
77 signal jump_pressed : std_logic := '0';
78
79begin
80
81 -- Output character position
82 char_x <= pos_x;
83 char_y <= pos_y;
84
85 -- Squish deforms the character: vertical hit = wider+shorter, wall hit = taller+narrower
86 char_width <= SIZE + ("000000" & squish) when squish_h = '0'
87 else SIZE - ("000000" & squish(3 downto 1));
88 char_height <= SIZE - ("000000" & squish(3 downto 1)) when squish_h = '0'
89 else SIZE + ("000000" & squish);
90
91 -- Main physics process: one tick per frame
92 physics : process
93 variable vx, vy : std_logic_vector(9 downto 0);
94 variable px, py : std_logic_vector(9 downto 0);
95 variable bounced : std_logic;
96 variable bounce_wall : std_logic;
97 variable bounce_speed : std_logic_vector(9 downto 0);
98 variable grounded : std_logic;
99 variable c_left, c_right, c_top, c_bot : std_logic_vector(9 downto 0);
100 variable overlap_x, overlap_y : std_logic_vector(9 downto 0);
101 begin
102 wait until vert_sync'event and vert_sync = '1';
103
104 vx := vel_x;
105 vy := vel_y;
106 bounced := '0';
107 bounce_wall := '0';
108 bounce_speed := (others => '0');
109 grounded := '0';
110
111 -- == INPUT ==
112
113 if key_w = '0' then
114 jump_pressed <= '0';
115 end if;
116
117 -- Jump on ground (single impulse)
118 if key_w = '1' and jump_pressed = '0' and on_ground = '1' then
119 vy := (others => '0');
120 vy := vy - JUMP_FORCE;
121 jump_pressed <= '1';
122 end if;
123
124 -- Bounce boost: holding W adds energy each ground contact
125 if key_w = '1' and jump_pressed = '1' and on_ground = '1' then
126 vy := vy - 4;
127 end if;
128
129 -- Slam down (air only)
130 if key_s = '1' and on_ground = '0' then
131 vy := vy + IMPULSE;
132 end if;
133
134 -- Horizontal: full on ground, reduced in air
135 if key_a = '1' then
136 if on_ground = '1' then vx := vx - IMPULSE;
137 else vx := vx - 2; end if;
138 end if;
139 if key_d = '1' then
140 if on_ground = '1' then vx := vx + IMPULSE;
141 else vx := vx + 2; end if;
142 end if;
143
144 -- == GRAVITY ==
145 vy := vy + GRAVITY;
146
147 -- == FRICTION: vel -= vel/4, min 1 ==
148 if vx(9) = '0' then
149 if vx > 0 then
150 if vx(9 downto 2) = "00000000" then vx := vx - 1;
151 else vx := vx - ("000" & vx(9 downto 3)); end if;
152 end if;
153 else
154 if vx /= "0000000000" then
155 if vx(9 downto 2) = "11111111" then vx := vx + 1;
156 else vx := vx - ("11" & vx(9 downto 2)); end if;
157 end if;
158 end if;
159
160 -- == CLAMP ==
161 if vx(9) = '0' and vx > MAX_VEL_X then vx := MAX_VEL_X; end if;
162 if vx(9) = '1' and vx < (not MAX_VEL_X) + 1 then vx := (not MAX_VEL_X) + 1; end if;
163 if vy(9) = '0' and vy > 63 then vy := CONV_STD_LOGIC_VECTOR(63, 10); end if;
164 if vy(9) = '1' and vy < CONV_STD_LOGIC_VECTOR(960, 10) then vy := CONV_STD_LOGIC_VECTOR(960, 10); end if;
165
166 -- == MOVE ==
167 px := pos_x + vx;
168 py := pos_y + vy;
169
170 -- == GROUND ==
171 if py >= GROUND then
172 py := GROUND;
173 bounced := '1'; grounded := '1';
174 bounce_speed := vy;
175 vy := (not vy) + 1;
176 if vy(9) = '1' then
177 vy := vy + ("000" & ((not vy(9 downto 3)) + 1));
178 end if;
179 if vy(9) = '1' and vy >= CONV_STD_LOGIC_VECTOR(1022, 10) then vy := (others => '0');
180 elsif vy(9) = '0' then vy := (others => '0'); end if;
181 end if;
182
183 -- == CEILING ==
184 if py(9) = '1' or py <= CEILING then
185 py := CEILING;
186 bounced := '1';
187 bounce_speed := (not vy) + 1;
188 vy := (not vy) + 1;
189 if vy(9) = '0' and vy > 1 then
190 vy := vy - ("000" & vy(9 downto 3));
191 end if;
192 end if;
193
194 -- == LEFT WALL ==
195 if px(9) = '1' or px <= LEFT_WALL then
196 px := LEFT_WALL;
197 bounced := '1'; bounce_wall := '1';
198 bounce_speed := (not vx) + 1;
199 vx := (not vx) + 1;
200 if vx(9) = '0' and vx > 1 then
201 vx := vx - ("000" & vx(9 downto 3));
202 end if;
203 end if;
204
205 -- == RIGHT WALL ==
206 if px >= RIGHT_WALL then
207 px := RIGHT_WALL;
208 bounced := '1'; bounce_wall := '1';
209 bounce_speed := vx;
210 vx := (not vx) + 1;
211 -- vx is now negative: reduce magnitude toward zero
212 if vx(9) = '1' then
213 vx := vx + ("000" & ((not vx(9 downto 3)) + 1));
214 end if;
215 -- Kill tiny bounces
216 if vx(9) = '1' and vx >= CONV_STD_LOGIC_VECTOR(1022, 10) then vx := (others => '0'); end if;
217 end if;
218
219 -- == OBSTACLE COLLISIONS ==
220 -- Pattern: AABB overlap -> resolve on shallower axis -> bounce
221
222 -- Obstacle 1
223 c_left := px - SIZE; c_right := px + SIZE;
224 c_top := py - SIZE; c_bot := py + SIZE;
225 if (c_right >= O1_L) and (c_left <= O1_R) and
226 (c_bot >= O1_T) and (c_top <= O1_B) then
227 if vy(9) = '0' then overlap_y := c_bot - O1_T;
228 else overlap_y := O1_B - c_top; end if;
229 if vx(9) = '0' then overlap_x := c_right - O1_L;
230 else overlap_x := O1_R - c_left; end if;
231 if overlap_y <= overlap_x then
232 if vy(9) = '0' then py := O1_T - SIZE; grounded := '1';
233 else py := O1_B + SIZE; end if;
234 bounced := '1';
235 vy := (not vy) + 1;
236 if vy(9) = '0' and vy > 1 then vy := vy - ("000" & vy(9 downto 3));
237 elsif vy(9) = '1' and vy < CONV_STD_LOGIC_VECTOR(1022, 10) then
238 vy := vy + ("000" & ((not vy(9 downto 3)) + 1)); end if;
239 if vy(9) = '0' and vy < 2 then vy := (others => '0'); end if;
240 if vy(9) = '1' and vy >= CONV_STD_LOGIC_VECTOR(1022, 10) then vy := (others => '0'); end if;
241 else
242 if vx(9) = '0' then px := O1_L - SIZE;
243 else px := O1_R + SIZE; end if;
244 bounced := '1'; bounce_wall := '1';
245 vx := (not vx) + 1;
246 if vx(9) = '0' and vx > 1 then vx := vx - ("000" & vx(9 downto 3));
247 elsif vx(9) = '1' and vx < CONV_STD_LOGIC_VECTOR(1022, 10) then
248 vx := vx + ("000" & ((not vx(9 downto 3)) + 1)); end if;
249 end if;
250 end if;
251
252 -- Obstacle 2
253 c_left := px - SIZE; c_right := px + SIZE;
254 c_top := py - SIZE; c_bot := py + SIZE;
255 if (c_right >= O2_L) and (c_left <= O2_R) and
256 (c_bot >= O2_T) and (c_top <= O2_B) then
257 if vy(9) = '0' then overlap_y := c_bot - O2_T;
258 else overlap_y := O2_B - c_top; end if;
259 if vx(9) = '0' then overlap_x := c_right - O2_L;
260 else overlap_x := O2_R - c_left; end if;
261 if overlap_y <= overlap_x then
262 if vy(9) = '0' then py := O2_T - SIZE; grounded := '1';
263 else py := O2_B + SIZE; end if;
264 bounced := '1';
265 vy := (not vy) + 1;
266 if vy(9) = '0' and vy > 1 then vy := vy - ("000" & vy(9 downto 3));
267 elsif vy(9) = '1' and vy < CONV_STD_LOGIC_VECTOR(1022, 10) then
268 vy := vy + ("000" & ((not vy(9 downto 3)) + 1)); end if;
269 if vy(9) = '0' and vy < 2 then vy := (others => '0'); end if;
270 if vy(9) = '1' and vy >= CONV_STD_LOGIC_VECTOR(1022, 10) then vy := (others => '0'); end if;
271 else
272 if vx(9) = '0' then px := O2_L - SIZE;
273 else px := O2_R + SIZE; end if;
274 bounced := '1'; bounce_wall := '1';
275 vx := (not vx) + 1;
276 if vx(9) = '0' and vx > 1 then vx := vx - ("000" & vx(9 downto 3));
277 elsif vx(9) = '1' and vx < CONV_STD_LOGIC_VECTOR(1022, 10) then
278 vx := vx + ("000" & ((not vx(9 downto 3)) + 1)); end if;
279 end if;
280 end if;
281
282 -- Obstacle 3
283 c_left := px - SIZE; c_right := px + SIZE;
284 c_top := py - SIZE; c_bot := py + SIZE;
285 if (c_right >= O3_L) and (c_left <= O3_R) and
286 (c_bot >= O3_T) and (c_top <= O3_B) then
287 if vy(9) = '0' then overlap_y := c_bot - O3_T;
288 else overlap_y := O3_B - c_top; end if;
289 if vx(9) = '0' then overlap_x := c_right - O3_L;
290 else overlap_x := O3_R - c_left; end if;
291 if overlap_y <= overlap_x then
292 if vy(9) = '0' then py := O3_T - SIZE; grounded := '1';
293 else py := O3_B + SIZE; end if;
294 bounced := '1';
295 vy := (not vy) + 1;
296 if vy(9) = '0' and vy > 1 then vy := vy - ("000" & vy(9 downto 3));
297 elsif vy(9) = '1' and vy < CONV_STD_LOGIC_VECTOR(1022, 10) then
298 vy := vy + ("000" & ((not vy(9 downto 3)) + 1)); end if;
299 if vy(9) = '0' and vy < 2 then vy := (others => '0'); end if;
300 if vy(9) = '1' and vy >= CONV_STD_LOGIC_VECTOR(1022, 10) then vy := (others => '0'); end if;
301 else
302 if vx(9) = '0' then px := O3_L - SIZE;
303 else px := O3_R + SIZE; end if;
304 bounced := '1'; bounce_wall := '1';
305 vx := (not vx) + 1;
306 if vx(9) = '0' and vx > 1 then vx := vx - ("000" & vx(9 downto 3));
307 elsif vx(9) = '1' and vx < CONV_STD_LOGIC_VECTOR(1022, 10) then
308 vx := vx + ("000" & ((not vx(9 downto 3)) + 1)); end if;
309 end if;
310 end if;
311
312 -- Obstacle 4
313 c_left := px - SIZE; c_right := px + SIZE;
314 c_top := py - SIZE; c_bot := py + SIZE;
315 if (c_right >= O4_L) and (c_left <= O4_R) and
316 (c_bot >= O4_T) and (c_top <= O4_B) then
317 if vy(9) = '0' then overlap_y := c_bot - O4_T;
318 else overlap_y := O4_B - c_top; end if;
319 if vx(9) = '0' then overlap_x := c_right - O4_L;
320 else overlap_x := O4_R - c_left; end if;
321 if overlap_y <= overlap_x then
322 if vy(9) = '0' then py := O4_T - SIZE; grounded := '1';
323 else py := O4_B + SIZE; end if;
324 bounced := '1';
325 vy := (not vy) + 1;
326 if vy(9) = '0' and vy > 1 then vy := vy - ("000" & vy(9 downto 3));
327 elsif vy(9) = '1' and vy < CONV_STD_LOGIC_VECTOR(1022, 10) then
328 vy := vy + ("000" & ((not vy(9 downto 3)) + 1)); end if;
329 if vy(9) = '0' and vy < 2 then vy := (others => '0'); end if;
330 if vy(9) = '1' and vy >= CONV_STD_LOGIC_VECTOR(1022, 10) then vy := (others => '0'); end if;
331 else
332 if vx(9) = '0' then px := O4_L - SIZE;
333 else px := O4_R + SIZE; end if;
334 bounced := '1'; bounce_wall := '1';
335 vx := (not vx) + 1;
336 if vx(9) = '0' and vx > 1 then vx := vx - ("000" & vx(9 downto 3));
337 elsif vx(9) = '1' and vx < CONV_STD_LOGIC_VECTOR(1022, 10) then
338 vx := vx + ("000" & ((not vx(9 downto 3)) + 1)); end if;
339 end if;
340 end if;
341
342 -- == COMMIT ==
343 vel_x <= vx;
344 vel_y <= vy;
345 pos_x <= px;
346 pos_y <= py;
347
348 -- Squish on meaningful impacts only
349 if bounced = '1' and bounce_speed > 3 then
350 if bounce_speed >= 8 then squish <= "1000";
351 else squish <= bounce_speed(3 downto 0); end if;
352 squish_h <= bounce_wall;
353 elsif squish > 0 then
354 squish <= squish - 1;
355 end if;
356
357 if grounded = '1' then on_ground <= '1';
358 elsif py < GROUND - 1 then on_ground <= '0'; end if;
359
360 end process physics;
361
362end behavior;