Austin Morlan 1 year ago
parent
commit
90a5c8443c
Signed by: Austin Morlan <mail@austinmorlan.com> GPG Key ID: FD6B27654AF5E348
5 changed files with 582 additions and 0 deletions
  1. 12
    0
      CMakeLists.txt
  2. BIN
      DejaVuSansMono.ttf
  3. 570
    0
      Main.cpp
  4. BIN
      PaddleHit.wav
  5. BIN
      WallHit.wav

+ 12
- 0
CMakeLists.txt View File

@@ -0,0 +1,12 @@
1
+cmake_minimum_required(VERSION 3.14)
2
+project(pong)
3
+
4
+set(CMAKE_CXX_STANDARD 17)
5
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+set(CMAKE_CXX_EXTENSIONS OFF)
7
+
8
+find_package(SDL2 REQUIRED)
9
+
10
+add_executable(pong Main.cpp)
11
+
12
+target_link_libraries(pong PRIVATE SDL2::SDL2 SDL2_ttf SDL2_mixer)

BIN
DejaVuSansMono.ttf View File


+ 570
- 0
Main.cpp View File

@@ -0,0 +1,570 @@
1
+#include <chrono>
2
+#include <SDL2/SDL.h>
3
+#include <SDL2/SDL_mixer.h>
4
+#include <SDL2/SDL_ttf.h>
5
+#include <string>
6
+
7
+
8
+const int WINDOW_WIDTH = 1280;
9
+const int WINDOW_HEIGHT = 720;
10
+const float PADDLE_SPEED = 1.0f;
11
+const int PADDLE_WIDTH = 10;
12
+const int PADDLE_HEIGHT = 100;
13
+const float BALL_SPEED = 1.0f;
14
+const int BALL_WIDTH = 15;
15
+const int BALL_HEIGHT = 15;
16
+
17
+
18
+enum Buttons
19
+{
20
+	PaddleOneUp = 0,
21
+	PaddleOneDown,
22
+	PaddleTwoUp,
23
+	PaddleTwoDown,
24
+};
25
+
26
+
27
+enum class CollisionType
28
+{
29
+	None,
30
+	Top,
31
+	Middle,
32
+	Bottom,
33
+	Left,
34
+	Right
35
+};
36
+
37
+
38
+struct Contact
39
+{
40
+	CollisionType type;
41
+	float penetration;
42
+};
43
+
44
+
45
+class Vec2
46
+{
47
+public:
48
+	Vec2()
49
+		: x(0.0f), y(0.0f)
50
+	{}
51
+
52
+	Vec2(float x, float y)
53
+		: x(x), y(y)
54
+	{}
55
+
56
+	Vec2 operator+(Vec2 const& rhs)
57
+	{
58
+		return Vec2(x + rhs.x, y + rhs.y);
59
+	}
60
+
61
+	Vec2& operator+=(Vec2 const& rhs)
62
+	{
63
+		x += rhs.x;
64
+		y += rhs.y;
65
+
66
+		return *this;
67
+	}
68
+
69
+	Vec2 operator*(float rhs)
70
+	{
71
+		return Vec2(x * rhs, y * rhs);
72
+	}
73
+
74
+	float x, y;
75
+};
76
+
77
+class Paddle
78
+{
79
+public:
80
+	Paddle(Vec2 position, Vec2 velocity)
81
+		: position(position), velocity(velocity)
82
+	{
83
+		rect.x = static_cast<int>(position.x);
84
+		rect.y = static_cast<int>(position.y);
85
+		rect.w = PADDLE_WIDTH;
86
+		rect.h = PADDLE_HEIGHT;
87
+	}
88
+
89
+	void Update(float dt)
90
+	{
91
+		position += velocity * dt;
92
+
93
+		if (position.y < 0)
94
+		{
95
+			// Restrict to top of the screen
96
+			position.y = 0;
97
+		}
98
+		else if (position.y > (WINDOW_HEIGHT - PADDLE_HEIGHT))
99
+		{
100
+			// Restrict to bottom of the screen
101
+			position.y = WINDOW_HEIGHT - PADDLE_HEIGHT;
102
+		}
103
+	}
104
+
105
+	void Draw(SDL_Renderer* renderer)
106
+	{
107
+		rect.y = static_cast<int>(position.y);
108
+
109
+		SDL_RenderFillRect(renderer, &rect);
110
+	}
111
+
112
+	Vec2 position;
113
+	Vec2 velocity;
114
+	SDL_Rect rect{};
115
+};
116
+
117
+
118
+class Ball
119
+{
120
+public:
121
+	Ball(Vec2 position, Vec2 velocity)
122
+		: position(position), velocity(velocity)
123
+	{
124
+		rect.x = static_cast<int>(position.x);
125
+		rect.y = static_cast<int>(position.y);
126
+		rect.w = BALL_WIDTH;
127
+		rect.h = BALL_HEIGHT;
128
+	}
129
+
130
+	void Update(float dt)
131
+	{
132
+		position += velocity * dt;
133
+	}
134
+
135
+	void Draw(SDL_Renderer* renderer)
136
+	{
137
+		rect.x = static_cast<int>(position.x);
138
+		rect.y = static_cast<int>(position.y);
139
+
140
+		SDL_RenderFillRect(renderer, &rect);
141
+	}
142
+
143
+	void CollideWithPaddle(Contact const& contact)
144
+	{
145
+		position.x += contact.penetration;
146
+		velocity.x = -velocity.x;
147
+
148
+		if (contact.type == CollisionType::Top)
149
+		{
150
+			velocity.y = -.75f * BALL_SPEED;
151
+		}
152
+		else if (contact.type == CollisionType::Bottom)
153
+		{
154
+			velocity.y = 0.75f * BALL_SPEED;
155
+		}
156
+	}
157
+
158
+	void CollideWithWall(Contact const& contact)
159
+	{
160
+		if ((contact.type == CollisionType::Top)
161
+		    || (contact.type == CollisionType::Bottom))
162
+		{
163
+			position.y += contact.penetration;
164
+			velocity.y = -velocity.y;
165
+		}
166
+		else if (contact.type == CollisionType::Left)
167
+		{
168
+			position.x = WINDOW_WIDTH / 2.0f;
169
+			position.y = WINDOW_HEIGHT / 2.0f;
170
+			velocity.x = BALL_SPEED;
171
+			velocity.y = 0.75f * BALL_SPEED;
172
+		}
173
+		else if (contact.type == CollisionType::Right)
174
+		{
175
+			position.x = WINDOW_WIDTH / 2.0f;
176
+			position.y = WINDOW_HEIGHT / 2.0f;
177
+			velocity.x = -BALL_SPEED;
178
+			velocity.y = 0.75f * BALL_SPEED;
179
+		}
180
+	}
181
+
182
+	Vec2 position;
183
+	Vec2 velocity;
184
+	SDL_Rect rect{};
185
+};
186
+
187
+
188
+class PlayerScore
189
+{
190
+public:
191
+	PlayerScore(Vec2 position, SDL_Renderer* renderer, TTF_Font* font)
192
+		: renderer(renderer), font(font)
193
+	{
194
+		surface = TTF_RenderText_Solid(font, "0", {0xFF, 0xFF, 0xFF, 0xFF});
195
+		texture = SDL_CreateTextureFromSurface(renderer, surface);
196
+
197
+		int width, height;
198
+		SDL_QueryTexture(texture, nullptr, nullptr, &width, &height);
199
+
200
+		rect.x = static_cast<int>(position.x);
201
+		rect.y = static_cast<int>(position.y);
202
+		rect.w = width;
203
+		rect.h = height;
204
+	}
205
+
206
+	~PlayerScore()
207
+	{
208
+		SDL_FreeSurface(surface);
209
+		SDL_DestroyTexture(texture);
210
+	}
211
+
212
+	void SetScore(int score)
213
+	{
214
+		SDL_FreeSurface(surface);
215
+		SDL_DestroyTexture(texture);
216
+
217
+		surface = TTF_RenderText_Solid(font, std::to_string(score).c_str(), {0xFF, 0xFF, 0xFF, 0xFF});
218
+		texture = SDL_CreateTextureFromSurface(renderer, surface);
219
+
220
+		int width, height;
221
+		SDL_QueryTexture(texture, nullptr, nullptr, &width, &height);
222
+		rect.w = width;
223
+		rect.h = height;
224
+	}
225
+
226
+	void Draw()
227
+	{
228
+		SDL_RenderCopy(renderer, texture, nullptr, &rect);
229
+	}
230
+
231
+	SDL_Renderer* renderer;
232
+	TTF_Font* font;
233
+	SDL_Surface* surface{};
234
+	SDL_Texture* texture{};
235
+	SDL_Rect rect{};
236
+};
237
+
238
+Contact CheckPaddleCollision(Ball const& ball, Paddle const& paddle)
239
+{
240
+	float ballLeft = ball.position.x;
241
+	float ballRight = ball.position.x + BALL_WIDTH;
242
+	float ballTop = ball.position.y;
243
+	float ballBottom = ball.position.y + BALL_HEIGHT;
244
+
245
+	float paddleLeft = paddle.position.x;
246
+	float paddleRight = paddle.position.x + PADDLE_WIDTH;
247
+	float paddleTop = paddle.position.y;
248
+	float paddleBottom = paddle.position.y + PADDLE_HEIGHT;
249
+
250
+	Contact contact{};
251
+
252
+	if (ballLeft >= paddleRight)
253
+	{
254
+		return contact;
255
+	}
256
+
257
+	if (ballRight <= paddleLeft)
258
+	{
259
+		return contact;
260
+	}
261
+
262
+	if (ballTop >= paddleBottom)
263
+	{
264
+		return contact;
265
+	}
266
+
267
+	if (ballBottom <= paddleTop)
268
+	{
269
+		return contact;
270
+	}
271
+
272
+	float paddleRangeUpper = paddleBottom - (2.0f * PADDLE_HEIGHT / 3.0f);
273
+	float paddleRangeMiddle = paddleBottom - (PADDLE_HEIGHT / 3.0f);
274
+
275
+	if (ball.velocity.x < 0)
276
+	{
277
+		// Left paddle
278
+		contact.penetration = paddleRight - ballLeft;
279
+	}
280
+	else if (ball.velocity.x > 0)
281
+	{
282
+		// Right paddle
283
+		contact.penetration = paddleLeft - ballRight;
284
+	}
285
+
286
+	if ((ballBottom > paddleTop)
287
+	    && (ballBottom < paddleRangeUpper))
288
+	{
289
+		contact.type = CollisionType::Top;
290
+	}
291
+	else if ((ballBottom > paddleRangeUpper)
292
+	         && (ballBottom < paddleRangeMiddle))
293
+	{
294
+		contact.type = CollisionType::Middle;
295
+	}
296
+	else
297
+	{
298
+		contact.type = CollisionType::Bottom;
299
+	}
300
+
301
+	return contact;
302
+}
303
+
304
+
305
+Contact CheckWallCollision(Ball const& ball)
306
+{
307
+	float ballLeft = ball.position.x;
308
+	float ballRight = ball.position.x + BALL_WIDTH;
309
+	float ballTop = ball.position.y;
310
+	float ballBottom = ball.position.y + BALL_HEIGHT;
311
+
312
+	Contact contact{};
313
+
314
+	if (ballLeft < 0.0f)
315
+	{
316
+		contact.type = CollisionType::Left;
317
+	}
318
+	else if (ballRight > WINDOW_WIDTH)
319
+	{
320
+		contact.type = CollisionType::Right;
321
+	}
322
+	else if (ballTop < 0.0f)
323
+	{
324
+		contact.type = CollisionType::Top;
325
+		contact.penetration = -ballTop;
326
+	}
327
+	else if (ballBottom > WINDOW_HEIGHT)
328
+	{
329
+		contact.type = CollisionType::Bottom;
330
+		contact.penetration = WINDOW_HEIGHT - ballBottom;
331
+	}
332
+
333
+	return contact;
334
+}
335
+
336
+
337
+int main()
338
+{
339
+	// Initialize SDL components
340
+	SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
341
+	TTF_Init();
342
+	Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);
343
+
344
+	SDL_Window* window = SDL_CreateWindow("Pong", 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN);
345
+	SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
346
+
347
+
348
+	// Initialize the font
349
+	TTF_Font* scoreFont = TTF_OpenFont("DejaVuSansMono.ttf", 40);
350
+
351
+
352
+	// Initialize sound effects
353
+	Mix_Chunk* wallHitSound = Mix_LoadWAV("WallHit.wav");
354
+	Mix_Chunk* paddleHitSound = Mix_LoadWAV("PaddleHit.wav");
355
+
356
+	// Game logic
357
+	{
358
+		// Create the player score text fields
359
+		PlayerScore playerOneScoreText(Vec2(WINDOW_WIDTH / 4, 20), renderer, scoreFont);
360
+
361
+		PlayerScore playerTwoScoreText(Vec2(3 * WINDOW_WIDTH / 4, 20), renderer, scoreFont);
362
+
363
+
364
+		// Create the ball
365
+		Ball ball(
366
+			Vec2(WINDOW_WIDTH / 2.0f, WINDOW_HEIGHT / 2.0f),
367
+			Vec2(BALL_SPEED, 0.0f));
368
+
369
+
370
+		// Create the paddles
371
+		Paddle paddleOne(
372
+			Vec2(50.0f, WINDOW_HEIGHT / 2.0f),
373
+			Vec2(0.0f, 0.0f));
374
+
375
+		Paddle paddleTwo(
376
+			Vec2(WINDOW_WIDTH - 50.0f, WINDOW_HEIGHT / 2.0f),
377
+			Vec2(0.0f, 0.0f));
378
+
379
+
380
+		int playerOneScore = 0;
381
+		int playerTwoScore = 0;
382
+
383
+		bool running = true;
384
+		bool buttons[4] = {};
385
+
386
+		float dt = 0.0f;
387
+
388
+		while (running)
389
+		{
390
+			auto startTime = std::chrono::high_resolution_clock::now();
391
+
392
+			SDL_Event event;
393
+			while (SDL_PollEvent(&event))
394
+			{
395
+				if (event.type == SDL_QUIT)
396
+				{
397
+					running = false;
398
+				}
399
+				else if (event.type == SDL_KEYDOWN)
400
+				{
401
+					if (event.key.keysym.sym == SDLK_ESCAPE)
402
+					{
403
+						running = false;
404
+					}
405
+					else if (event.key.keysym.sym == SDLK_w)
406
+					{
407
+						buttons[Buttons::PaddleOneUp] = true;
408
+					}
409
+					else if (event.key.keysym.sym == SDLK_s)
410
+					{
411
+						buttons[Buttons::PaddleOneDown] = true;
412
+					}
413
+					else if (event.key.keysym.sym == SDLK_UP)
414
+					{
415
+						buttons[Buttons::PaddleTwoUp] = true;
416
+					}
417
+					else if (event.key.keysym.sym == SDLK_DOWN)
418
+					{
419
+						buttons[Buttons::PaddleTwoDown] = true;
420
+					}
421
+				}
422
+				else if (event.type == SDL_KEYUP)
423
+				{
424
+					if (event.key.keysym.sym == SDLK_w)
425
+					{
426
+						buttons[Buttons::PaddleOneUp] = false;
427
+					}
428
+					else if (event.key.keysym.sym == SDLK_s)
429
+					{
430
+						buttons[Buttons::PaddleOneDown] = false;
431
+					}
432
+					else if (event.key.keysym.sym == SDLK_UP)
433
+					{
434
+						buttons[Buttons::PaddleTwoUp] = false;
435
+					}
436
+					else if (event.key.keysym.sym == SDLK_DOWN)
437
+					{
438
+						buttons[Buttons::PaddleTwoDown] = false;
439
+					}
440
+				}
441
+			}
442
+
443
+
444
+			if (buttons[Buttons::PaddleOneUp])
445
+			{
446
+				paddleOne.velocity.y = -PADDLE_SPEED;
447
+			}
448
+			else if (buttons[Buttons::PaddleOneDown])
449
+			{
450
+				paddleOne.velocity.y = PADDLE_SPEED;
451
+			}
452
+			else
453
+			{
454
+				paddleOne.velocity.y = 0.0f;
455
+			}
456
+
457
+			if (buttons[Buttons::PaddleTwoUp])
458
+			{
459
+				paddleTwo.velocity.y = -PADDLE_SPEED;
460
+			}
461
+			else if (buttons[Buttons::PaddleTwoDown])
462
+			{
463
+				paddleTwo.velocity.y = PADDLE_SPEED;
464
+			}
465
+			else
466
+			{
467
+				paddleTwo.velocity.y = 0.0f;
468
+			}
469
+
470
+
471
+			// Update the paddle positions
472
+			paddleOne.Update(dt);
473
+			paddleTwo.Update(dt);
474
+
475
+
476
+			// Update the ball position
477
+			ball.Update(dt);
478
+
479
+
480
+			// Check collisions
481
+			if (Contact contact = CheckPaddleCollision(ball, paddleOne);
482
+				contact.type != CollisionType::None)
483
+			{
484
+				ball.CollideWithPaddle(contact);
485
+
486
+				Mix_PlayChannel(-1, paddleHitSound, 0);
487
+			}
488
+			else if (contact = CheckPaddleCollision(ball, paddleTwo);
489
+				contact.type != CollisionType::None)
490
+			{
491
+				ball.CollideWithPaddle(contact);
492
+
493
+				Mix_PlayChannel(-1, paddleHitSound, 0);
494
+			}
495
+			else if (contact = CheckWallCollision(ball);
496
+				contact.type != CollisionType::None)
497
+			{
498
+				ball.CollideWithWall(contact);
499
+
500
+				if (contact.type == CollisionType::Left)
501
+				{
502
+					++playerTwoScore;
503
+
504
+					playerTwoScoreText.SetScore(playerTwoScore);
505
+				}
506
+				else if (contact.type == CollisionType::Right)
507
+				{
508
+					++playerOneScore;
509
+
510
+					playerOneScoreText.SetScore(playerOneScore);
511
+				}
512
+				else
513
+				{
514
+					Mix_PlayChannel(-1, wallHitSound, 0);
515
+				}
516
+			}
517
+
518
+
519
+			// Clear the window to black
520
+			SDL_SetRenderDrawColor(renderer, 0x0, 0x0, 0x0, 0xFF);
521
+			SDL_RenderClear(renderer);
522
+
523
+			// Set the draw color to be white
524
+			SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
525
+
526
+
527
+			// Draw the net
528
+			for (int y = 0; y < WINDOW_HEIGHT; ++y)
529
+			{
530
+				if (y % 5)
531
+				{
532
+					SDL_RenderDrawPoint(renderer, WINDOW_WIDTH / 2, y);
533
+				}
534
+			}
535
+
536
+
537
+			// Draw the ball
538
+			ball.Draw(renderer);
539
+
540
+			// Draw the paddles
541
+			paddleOne.Draw(renderer);
542
+			paddleTwo.Draw(renderer);
543
+
544
+			// Display the scores
545
+			playerOneScoreText.Draw();
546
+			playerTwoScoreText.Draw();
547
+
548
+
549
+			// Present the backbuffer
550
+			SDL_RenderPresent(renderer);
551
+
552
+
553
+			// Calculate frame time
554
+			auto stopTime = std::chrono::high_resolution_clock::now();
555
+			dt = std::chrono::duration<float, std::chrono::milliseconds::period>(stopTime - startTime).count();
556
+		}
557
+	}
558
+
559
+	Mix_FreeChunk(wallHitSound);
560
+	Mix_FreeChunk(paddleHitSound);
561
+	SDL_DestroyRenderer(renderer);
562
+	SDL_DestroyWindow(window);
563
+	TTF_CloseFont(scoreFont);
564
+	Mix_Quit();
565
+	TTF_Quit();
566
+	SDL_Quit();
567
+
568
+	return 0;
569
+}
570
+

BIN
PaddleHit.wav View File


BIN
WallHit.wav View File


Loading…
Cancel
Save