CS50 2D - Lecture 0 - Pong
Watch on YouTube →
Overview
CS50's Colton Ogden introduces 2D game development using Lua and the LÖVE framework, starting with a Pong implementation. The lecture covers core game development concepts like the game loop, coordinate systems, drawing shapes, handling input, collision detection, and state management, culminating in a functional Pong game with scoring and basic AI.
Key takeaways
- LÖVE framework and Lua scripting provide a powerful yet accessible environment for 2D game development.
- The game loop (input, update, render) is fundamental, with `love.update` and delta time crucial for consistent physics.
- Object-Oriented Programming (classes, objects, methods) significantly improves code organization and scalability for game entities.
- Collision detection (AABB) is essential for game interaction, enabling responses like bouncing and scoring.
- Game states (start, serve, play, done) manage the flow and logic of the game.
- Sound effects and visual aesthetics (pixel filtering, low resolution) greatly enhance player immersion and game polish.
Chapters
0:00
Introduction to CS50 2D and Colton Ogden's Background
- CS50 2D is an introduction to 2D game development.
- Colton Ogden shares his early programming journey, starting with video games.
- Initial disappointment with complex 3D game programming books led to learning C.
- The course aims to teach game programming through replications of famous games.
0:26
Course Overview: From Pong to Modern Classics
- The course will cover a 'smorgasbord' of games, from Pong to Pokemon.
- Pong, despite its simplicity, contains fundamental 2D game development principles.
- Key tools: Lua (language) and LÖVE (framework/runtime).
- Topics include shapes, text, delta time, velocity, states, OOP, and collision.
5:21
Setting Up the Development Environment: LÖVE and Lua
- LÖVE (Love2D) is a C++ framework for game development scripted in Lua.
- Lua is a lightweight, flexible scripting language, similar in syntax to JavaScript.
- LÖVE provides modules for graphics, input, audio, and math.
- Balatro, a popular game, is built using LÖVE.
8:04
Pong Fundamentals: Shapes, Collision, and States
- Pong features movable rectangles (paddles) and a square (ball).
- Collision detection is crucial for ball-paddle and ball-wall interactions.
- Scorekeeping, game states (win conditions), and sound effects are present.
- These fundamentals are transferable to other 2D games like Breakout.
9:53
Implementing the 'Day Zero Update': A Basic Window
- The first example, 'Pong Zero', focuses on drawing a window and text.
- LÖVE expects a main.lua file to bootstrap the program.
- VS Code extension 'LÖVE 2D Support' simplifies running code.
- The demo shows paddle movement (WASD/Arrows), enter to start, and ball serve.
15:06
Core Game Loop: Input, Update, and Render
- Games operate on a continuous loop: process input, update game state, render.
- This loop aims for 60 frames per second, creating an illusion of motion.
- LÖVE provides `love.load`, `love.update`, and `love.draw` functions for this loop.
- Sprites are images drawn to the screen; shapes are the focus for Pong.
20:06
2D Coordinate Systems and Basic LÖVE Functions
- 2D games use XY coordinate systems; in LÖVE, positive Y goes down.
- Pixels on the screen map to this Cartesian grid.
- Key functions: `love.graphics.printF` (draw text) and `love.window.setMode` (create window).
21:51
Creating the Initial Window and 'Hello Pong' Text
- A `main.lua` file is required to run LÖVE applications.
- Window dimensions set to 1280x720 (720p) with VSync enabled.
- `love.graphics.printF` displays 'Hello Pong' centered on the screen.
- The `end` keyword terminates code blocks like functions and if statements.
28:29
Understanding Lua's `end` vs. Parentheses for Function Calls
- `end` terminates blocks (functions, if statements) defining scope.
- Parentheses `()` are used for function invocations and passing arguments.
- Tables (key-value pairs) are Lua's versatile data structure.
- Order of key-value pairs in tables does not matter.
28:53
Implementing Retro Aesthetics: Low Resolution and Pixel Filtering
- Default text rendering is low-resolution; retro games used lower resolutions.
- A 'Push.lua' library simulates a virtual canvas for consistent retro visuals.
- Virtual dimensions set to 432x243 (16:9 aspect ratio).
- Point filtering (nearest neighbor) prevents blurry pixel art.
35:03
Handling Keyboard Input: Quitting with Escape
- `love.keypressed` is a callback function triggered when a key is pressed.
- `love.event.quit` programmatically closes the application.
- Setting `resizable = true` in `love.window.setMode` allows window resizing.
- `love.resize` callback handles window resizing events and updates Push.
38:20
Applying Pixel Filtering and String Concatenation
- `love.graphics.setDefaultFilter('nearest', 'nearest')` ensures sharp pixel art.
- String concatenation in Lua uses the `..` operator.
- Single and double quotes are interchangeable for strings; use consistency.
- The 'Push' library requires `push.setup` and `push.start`/`push.finish` around drawing calls.
44:14
Drawing Rectangles and Setting Colors
- `love.graphics.rectangle('fill', x, y, width, height)` draws filled rectangles.
- Default draw color is white; `love.graphics.clear(r, g, b, a)` sets background color.
- RGB values range from 0 to 1 (or 0 to 255 normalized) for color.
- New fonts are created with `love.graphics.newFont(path, size)` and set with `love.graphics.setFont`.
50:00
Implementing Paddles and Ball with Rectangles
- Paddles and the ball are drawn as rectangles using `love.graphics.rectangle`.
- Coordinates are based on virtual dimensions (432x243) for consistent scaling.
- Offsets are crucial for precise positioning (e.g., drawing from the top-left corner).
- Scores are rendered using `love.graphics.print` and converted to strings with `tostring`.
54:26
Introducing Delta Time and the `love.update` Function
- `love.update(dt)` is called every frame, `dt` is delta time (time since last frame).
- Delta time normalizes movement and physics across different frame rates.
- Paddle movement is implemented using `love.keyboard.isDown` and `dt`.
- Paddle speed is defined in pixels per second (e.g., 200).
1:03:55
Moving Paddles with Keyboard Input
- Paddle Y-coordinates are stored in variables (`player1Y`, `player2Y`).
- Movement logic multiplies speed by `dt` to ensure frame-rate independence.
- Conditional checks (`if love.keyboard.isDown`) handle W/S for player 1 and Up/Down for player 2.
- The `love.draw` function now uses these updated Y-coordinates.
1:08:33
Clamping Paddle Movement within Screen Bounds
- Paddles can move off-screen; clamping prevents this.
- Math.max(0, value) ensures the Y-coordinate doesn't go below 0.
- Math.min(limit, value) ensures the Y-coordinate doesn't exceed the screen height minus paddle height.
- These clamping functions are applied within `love.update`.
1:19:43
Ball Movement and Random Velocity Initialization
- Ball movement requires `ballX`, `ballY`, `ballDX` (delta X), and `ballDY` (delta Y) variables.
- `math.randomseed(os.time())` seeds the random number generator.
- `math.random(min, max)` generates a random number within a range.
- Initial ball velocity (`ballDX`, `ballDY`) is randomized for variability.
1:25:13
Implementing Game States: Start, Serve, Play
- Game states manage different phases (e.g., 'start', 'serve', 'play').
- A `gameState` variable controls logic execution within `love.update` and `love.draw`.
- `love.keypressed` handles transitions: Enter/Return changes state from 'start' to 'serve'.
- The ball only moves when `gameState` is 'play'.
1:30:43
Object-Oriented Programming: Classes for Paddle and Ball
- Classes (blueprints) encapsulate data (attributes) and behavior (methods).
- This reduces global variable clutter and improves code organization.
- A `Class.lua` helper library simplifies class creation.
- Objects (instances of classes) like `paddle` and `ball` are created using `Class()`.
1:35:49
Class Structure: Init, Update, Render Methods
- The `init` method (constructor) initializes object properties (position, size, velocity).
- The `update` method handles object logic (movement, clamping, state changes).
- The `render` method draws the object on screen.
- The colon syntax (`object:method()`) implicitly passes the object itself (`self`) to the method.
1:42:34
Adding FPS Counter and Setting Window Title
- `love.window.setTitle()` sets the window's title bar text.
- `love.timer.getFPS()` retrieves the current frames per second.
- FPS is displayed using `tostring` and `love.graphics.print`.
- The drawing color can be set using `love.graphics.setColor`.
1:47:03
Collision Detection: Axis-Aligned Bounding Boxes (AABB)
- AABB checks for overlap between two non-rotated rectangles.
- Collision occurs if no gap exists between the rectangles on any axis.
- The `collides(otherObject)` method is added to the `ball` class.
- It checks for collision with paddles and screen boundaries.
1:51:57
Implementing Ball-Paddle and Ball-Wall Collisions
- On paddle collision, `ballDX` is inverted and multiplied by 1.03 (increasing speed).
- The ball's position is adjusted (`ballX = player1.x + player1.width + 5`) to prevent sticking.
- Collisions with top/bottom walls invert `ballDY`.
- Scoring occurs when the ball passes the left (player 1 scores) or right (player 2 scores) edge.
1:56:49
Scoring and Game States: Serve, Play, Done
- Score variables (`player1Score`, `player2Score`) are incremented on scoring.
- When a player reaches 10 points, `gameState` becomes 'done'.
- Pressing Enter in the 'start' state transitions to 'serve'.
- Pressing Enter in the 'done' state resets scores and transitions to 'serve'.
2:01:51
Implementing the Serve State and Resetting Ball
- The 'serve' state pauses ball movement and displays serving messages.
- The player who was scored against becomes the server.
- Ball velocity (`ballDX`, `ballDY`) is reset randomly upon entering 'serve'.
- The `gameState` logic in `love.update` controls ball movement and collision checks.
2:05:54
Victory Condition and Game Reset Logic
- A 'done' state is triggered when a player reaches the target score (e.g., 10 points).
- In the 'done' state, a winner message is displayed.
- Pressing Enter in the 'done' state resets scores, re-seeds the ball, and transitions to 'serve'.
- The serving player alternates based on who was scored against.
2:09:10
Adding Sound Effects with LÖVE Audio
- LÖVE's audio module (`love.audio`) handles sound playback.
- `love.audio.newSource(filename, type)` loads sound files (WAV, MP3, OGG).
- Sounds are stored in a table (`sounds`) for easy access.
- Methods like `play()`, `stop()`, and `loop()` control playback.
2:12:33
Generating and Integrating Sound Effects
- Online tools like Chiptone and Bfxr can generate retro sound effects.
- Sounds are assigned to specific game events: paddle hits, wall hits, scoring.
- Paddle hits use a 'blip' sound, wall hits use 'boom', scoring uses 'hurt'.
- Sounds add immersion and provide feedback for player actions.
2:15:13
Handling Window Resizing with `love.resize` and Push
- The `resizable` window setting must be `true` for resizing.
- `love.resize(width, height)` is a callback function triggered on window resize.
- Push's `push.resize()` must be called within `love.resize` to update the virtual canvas.
- This ensures the retro aesthetic scales correctly with window size.
2:21:59
Assignment 1: Implementing Basic AI for Player 2
- The first assignment involves adding AI to control the right paddle (Player 2).
- Player 1 remains human-controlled (WASD/Arrows).
- The AI should move the paddle to track the ball's Y-position.
- The AI's difficulty can be adjusted by the implementer.
2:23:25
Next Lecture Preview: Flappy Bird and Images
- Lecture 1 will cover image rendering and sprite-based games.
- Flappy Bird will be the example game, introducing infinite scrolling.
- Concepts of 'games as illusions' and procedural generation (pipe spawning) will be explored.
- Mouse input will also be introduced.
Summary, takeaways, and chapters were generated by AI from the video's transcript and may contain errors. The video belongs to its creator, CS50.