Maze Game Tutorial: Custom Images & Collision Detection
Welcome to the maze game tutorial! This tutorial will teach you how to create and customize maze games with the Kywy engine. We'll cover:
- How maze collision detection works with bit-packed images
- Creating custom maze layouts
- Adding your own sprites and images
- Understanding the game structure
Ask questions, share your creations, and discuss with others on the discord:
What You'll Learn
By the end of this tutorial, you'll understand: - How to create maze games with pixel-perfect collision detection - How to convert images into Kywy-compatible sprite data - How to customize game sprites and maze layouts - How the bit-packed collision system works
The Maze Game Structure
Let's start by examining the maze game code structure:
// Game constants - these control the game behavior
const int PLAYER_SIZE = 8; // 8x8 pixel player sprite
const int MOVE_SPEED = 8; // Move 8 pixels per frame (1 tile)
// Start and finish positions
const int START_X = 0; // Top-left corner
const int START_Y = 0;
const int FINISH_X = KYWY_DISPLAY_WIDTH - PLAYER_SIZE; // Bottom-right
const int FINISH_Y = KYWY_DISPLAY_HEIGHT - PLAYER_SIZE;
Understanding Bit-Packed Collision Detection
The maze game uses a special collision detection system that works with bit-packed images. Here's how it works:
How Images Are Stored
Kywy images are stored as arrays of bytes where each bit represents one pixel:
// Example: 8 pixels stored in 1 byte
// Bit: 7 6 5 4 3 2 1 0
// Pixel: 0 1 0 1 0 1 0 1 = 0x55 in hex
uint8_t imageData[8] = {0x55, /* ... more bytes ... */};
- 0 bits = walls (black pixels, can't walk through)
- 1 bits = open spaces (white pixels, can walk through)
The Collision Detection Function
bool isWall(int x, int y, int width = 1, int height = 1) {
// Check all pixels in the rectangular area
for (int checkY = y; checkY < y + height; checkY++) {
for (int checkX = x; checkX < x + width; checkX++) {
// Calculate which byte contains this pixel
int bytesPerRow = MAZE_EXAMPLE_WIDTH / 8;
int byteIndex = checkY * bytesPerRow + (checkX / 8);
// Get the bit position within the byte
int bitPosition = checkX % 8;
uint8_t mask = 1 << (7 - bitPosition);
// Check if this bit is 0 (wall) or 1 (open)
if ((maze_example_data[byteIndex] & mask) == 0) {
return true; // Found a wall!
}
}
}
return false; // No walls in this area
}
Creating Custom Maze Images
Step 1: Design Your Maze
Create a simple black and white image (144×168 pixels) where: - Black pixels (#000000) = walls - White pixels (#FFFFFF) = open paths
Step 2: Convert to Kywy Format
Use the Kywy Drawing Editor or online tools to convert your image:
- Go to the Kywy Drawing Editor
- Upload your maze image using the import function on the top right
- Adjust settings to your liking
- Edit your image
- On the right side under export edit the name of your file and click export.
Step 3: Replace the Maze Data
Replace the maze_example.hpp file with your custom maze:
Step 4: Update the Game Code
Update the maze game to use your custom data:
#include "my_maze.hpp" // Your custom maze
// Update the draw call
engine.display.drawBitmap(0, 0, MY_MAZE_WIDTH, MY_MAZE_HEIGHT, my_maze_data); //replace my_maze with your maze name
// Update collision detection
int bytesPerRow = MY_MAZE_WIDTH / 8;
make sure change the name of your maze to what you named it!
Customizing Sprites
Player Sprite
The player uses an 8×8 sprite stored as 64 bytes (8 bytes per row):
Each byte represents 8 pixels in a row. To create custom sprites:
- Design an 8×8 pixel image
- Convert each row to a byte value
- Replace the
playerSpritearray
Start and Finish Flags
The game includes start and finish flag sprites:
#include "start_flag.hpp"
#include "finish_flag.hpp"
// Draw flags
engine.display.drawBitmap(START_X, START_Y, START_FLAG_WIDTH, START_FLAG_HEIGHT, start_flag_data);
engine.display.drawBitmap(FINISH_X, FINISH_Y, FINISH_FLAG_WIDTH, FINISH_FLAG_HEIGHT, finish_flag_data);
Create custom flags using the same process as maze images.
Game Logic Breakdown
Input Handling
The game samples input at the start of each frame for consistency:
void loop() {
// Sample input at frame start
bool leftPressed = engine.input.dPadLeftPressed;
bool rightPressed = engine.input.dPadRightPressed;
bool upPressed = engine.input.dPadUpPressed;
bool downPressed = engine.input.dPadDownPressed;
// Use these values throughout the frame
// ... game logic ...
}
Movement and Collision
int oldX = playerX;
int oldY = playerY;
// Try to move
if (leftPressed) playerX -= MOVE_SPEED;
// Check collision with entire player area
if (isWall(playerX, playerY, PLAYER_SIZE, PLAYER_SIZE)) {
// Hit a wall - revert position
playerX = oldX;
playerY = oldY;
}
Win Condition
if (reachedGoal(playerX, playerY)) {
// Show win screen
engine.display.drawText(35, 75, "YOU WIN!");
// Wait for button press to restart
while (!engine.input.buttonRightPressed) {
delay(10);
}
// Reset game
playerX = START_X;
playerY = START_Y;
}
Tips for Custom Mazes
Design Principles
- Clear Paths: Ensure there's always a path from start to finish
- Visual Clarity: Use high contrast between walls and paths
- Size Constraints: Keep within 144×168 pixel display limits
- Performance: More complex mazes = more collision checks
Common Issues
- Wrong Dimensions: Ensure your image is exactly 144×168
- Color Issues: Only use pure black (#000000) and white (#FFFFFF)
- Bit Order: The conversion tool handles bit ordering automatically
Testing Your Maze
- Compile and Upload: Test on actual hardware
- Walk Through: Manually test all paths
- Edge Cases: Check corners and tight spaces
- Performance: Ensure smooth movement
Advanced Customizations
Variable Player Size
const int PLAYER_SIZE = 16; // 16x16 player
const int MOVE_SPEED = 4; // Slower movement for larger sprite
Multiple Mazes
const uint8_t* currentMaze = maze1_data;
// Switch mazes based on level
if (level == 2) currentMaze = maze2_data;
Animated Sprites
int frame = (millis() / 200) % 4; // 4 frames, 200ms each
engine.display.drawBitmap(x, y, 8, 8, playerFrames[frame]);
Next Steps
Now that you understand how the maze game works:
- Create your own maze image
- Convert it using the Kywy tools
- Customize the sprites and gameplay
- Test on your Kywy device
- Share your creations on Discord!
The maze game demonstrates advanced concepts like bit manipulation, collision detection, and image processing - skills that apply to many types of games!
Troubleshooting
Common Issues
"Maze doesn't load" - Check image dimensions (must be 144×168) - Ensure pure black/white colors only
"Player clips through walls" - Verify bit ordering in your image data - Check collision detection coordinates
"Game runs slowly" - Reduce maze complexity - Optimize collision detection loops
"Sprites don't appear" - Check sprite dimensions match draw calls - Verify header file includes
Remember: The Kywy community is here to help! Join our Discord for support and to share your maze game creations.