A simple "Hello PixelRoot32" demo showcasing cycling background colors with button input detection.
Displays "Hello PixelRoot32!" on screen with a cycling background color from the PR32 palette every second. Additionally, it displays which button is currently pressed (Up/Down/Left/Right/A/B), demonstrating the input system.
- VS Code with PlatformIO IDE extension
- Python 3.8+ (usually included with PlatformIO)
- SDL2 development libraries:
- Windows (MSYS2):
pacman -S mingw-w64-x86_64-SDL2 - Linux:
sudo apt-get install libsdl2-dev - macOS:
brew install sdl2
- Windows (MSYS2):
- ESP32 board (any variant)
- ST7735 display (128x138)
- USB cable for programming
git clone https://github.com/PixelRoot32-Game-Engine/pixelroot32-hello-world.git
cd pixelroot32-hello-worldpio run -e nativepio run -e esp32dev- PC: Use arrow keys + Space (A) + Enter (B)
- ESP32: Use the joystick/buttons on your hardware
pixelroot32-hello-world/
├── src/
│ ├── main.cpp # Entry point (includes platform headers)
│ ├── HelloWorldScene.h # Scene header
│ ├── HelloWorldScene.cpp # Scene implementation
│ └── platforms/
│ ├── native.h # PC/SDL2 platform entry
│ └── esp32_dev.h # ESP32 platform entry
├── platformio.ini # Build configuration
└── README.mdThis section explains how the project was built from scratch.
Create a new PlatformIO project or clone this repository.
Add to your platformio.ini:
[env:native]
platform = native
lib_deps =
gperez88/PixelRoot32-Game-Engine@^1.1.0
build_flags =
-std=gnu++17
-fno-exceptions
-DPLATFORM_NATIVE
-lSDL2Create src/HelloWorldScene.h:
#pragma once
#include <core/Scene.h>
#include <graphics/Renderer.h>
#include <graphics/Color.h>
#include <graphics/ui/UILabel.h>
#include <math/Vector2.h>
#include <memory>
namespace helloworld {
class HelloWorldScene : public pixelroot32::core::Scene {
public:
void init() override;
void update(unsigned long deltaTime) override;
void draw(pixelroot32::graphics::Renderer& renderer) override;
private:
std::unique_ptr<pixelroot32::graphics::ui::UILabel> textLabel;
std::unique_ptr<pixelroot32::graphics::ui::UILabel> buttonPressLabel;
pixelroot32::graphics::Color backgroundColor = pixelroot32::graphics::Color::Black;
pixelroot32::graphics::Color textColor = pixelroot32::graphics::Color::White;
int frameCounter = 0;
static constexpr int COLOR_CHANGE_INTERVAL = 60;
char buttonPressText[32];
void changeBackground();
void checkButtonPress();
};
} // namespace helloworldCreate src/HelloWorldScene.cpp:
#include "HelloWorldScene.h"
#include <core/Engine.h>
#include <core/Log.h>
namespace pr32 = pixelroot32;
extern pr32::core::Engine engine;
namespace helloworld {
using namespace pr32::core::logging;
static constexpr uint8_t PR32_PALETTE_SIZE = 16;
static constexpr uint16_t PR32_PALETTE_RGB565[PR32_PALETTE_SIZE] = {
0x0000, 0xFFFF, 0x1907, 0x025F, // Black, White, Navy, Blue
0x061F, 0x13C2, 0x3648, 0xA7F3, // Cyan, DarkGreen, Green, LightGreen
0xFEA0, 0xFCE3, 0xC3FF, 0xB884, // Yellow, Orange, LightRed, Red
0x6822, 0x7977, 0xCE79, 0x8C71 // DarkRed, Purple, Magenta, Gray
};
void HelloWorldScene::init() {
log("HelloWorldScene: initializing...");
pr32::graphics::setPalette(pr32::graphics::PaletteType::PR32);
int screenWidth = engine.getRenderer().getLogicalWidth();
int screenHeight = engine.getRenderer().getLogicalHeight();
textLabel = std::make_unique<pr32::graphics::ui::UILabel>(
"Hello PixelRoot32!",
pr32::math::Vector2(0, screenHeight / 2 - 10),
pr32::graphics::Color::White,
1
);
textLabel->centerX(screenWidth);
textLabel->setRenderLayer(2);
addEntity(textLabel.get());
buttonPressLabel = std::make_unique<pr32::graphics::ui::UILabel>(
"",
pr32::math::Vector2(0, screenHeight / 2 + 10),
pr32::graphics::Color::White,
1
);
buttonPressLabel->centerX(screenWidth);
buttonPressLabel->setRenderLayer(2);
addEntity(buttonPressLabel.get());
snprintf(buttonPressText, sizeof(buttonPressText), "Press: -");
changeBackground();
log(LogLevel::Info, "HelloWorldScene: initialized");
}
void HelloWorldScene::update(unsigned long deltaTime) {
Scene::update(deltaTime);
checkButtonPress();
frameCounter++;
if (frameCounter >= COLOR_CHANGE_INTERVAL) {
frameCounter = 0;
changeBackground();
}
}
void HelloWorldScene::draw(pixelroot32::graphics::Renderer& renderer) {
renderer.drawFilledRectangle(
0, 0,
renderer.getLogicalWidth(),
renderer.getLogicalHeight(),
backgroundColor
);
Scene::draw(renderer);
}
void HelloWorldScene::changeBackground() {
static uint8_t colorIndex = 0;
static const uint8_t fixedRange[] = {0, 2, 5, 7, 10, 12, 14};
colorIndex = (colorIndex + 1) % 7;
backgroundColor = static_cast<pr32::graphics::Color>(fixedRange[colorIndex]);
}
void HelloWorldScene::checkButtonPress() {
auto& input = engine.getInputManager();
if (input.isButtonPressed(0)) {
snprintf(buttonPressText, sizeof(buttonPressText), "Press: U");
} else if (input.isButtonPressed(1)) {
snprintf(buttonPressText, sizeof(buttonPressText), "Press: D");
} else if (input.isButtonPressed(2)) {
snprintf(buttonPressText, sizeof(buttonPressText), "Press: L");
} else if (input.isButtonPressed(3)) {
snprintf(buttonPressText, sizeof(buttonPressText), "Press: R");
} else if (input.isButtonPressed(4)) {
snprintf(buttonPressText, sizeof(buttonPressText), "Press: A");
} else if (input.isButtonPressed(5)) {
snprintf(buttonPressText, sizeof(buttonPressText), "Press: B");
}
buttonPressLabel->setText(buttonPressText);
}
} // namespace helloworldCreate src/platforms/native.h for PC:
#ifdef PLATFORM_NATIVE
#include <SDL2/SDL.h>
#include <drivers/native/SDL2_Drawer.h>
#include <drivers/native/SDL2_AudioBackend.h>
#include <core/Engine.h>
#include <platforms/EngineConfig.h>
#include "HelloWorldScene.h"
namespace pr32 = pixelroot32;
pr32::graphics::DisplayConfig config(
pr32::graphics::DisplayType::NONE,
DISPLAY_ROTATION,
PHYSICAL_DISPLAY_WIDTH,
PHYSICAL_DISPLAY_HEIGHT,
LOGICAL_WIDTH,
LOGICAL_HEIGHT,
X_OFF_SET,
Y_OFF_SET
);
pr32::input::InputConfig inputConfig(6, SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_SPACE, SDL_SCANCODE_RETURN);
pr32::core::Engine engine(config, inputConfig);
helloworld::HelloWorldScene helloScene;
int main(int argc, char* argv[]) {
(void)argc;
(void)argv;
engine.init();
engine.setScene(&helloScene);
engine.run();
return 0;
}
#endif // PLATFORM_NATIVECreate src/platforms/esp32_dev.h for ESP32:
#ifdef PLATFORM_ESP32DEV
#include <Arduino.h>
#include <drivers/esp32/TFT_eSPI_Drawer.h>
#include <core/Engine.h>
#include <platforms/EngineConfig.h>
#include "HelloWorldScene.h"
namespace pr32 = pixelroot32;
const int BTN_UP = 32;
const int BTN_DOWN = 27;
const int BTN_LEFT = 33;
const int BTN_RIGHT = 14;
const int BTN_A = 13;
const int BTN_B = 12;
pr32::graphics::DisplayConfig config(
pr32::graphics::DisplayType::ST7789,
DISPLAY_ROTATION,
PHYSICAL_DISPLAY_WIDTH,
PHYSICAL_DISPLAY_HEIGHT,
LOGICAL_WIDTH,
LOGICAL_HEIGHT,
X_OFF_SET,
Y_OFF_SET
);
pr32::input::InputConfig inputConfig(6, BTN_UP, BTN_DOWN, BTN_LEFT, BTN_RIGHT, BTN_A, BTN_B);
pr32::core::Engine engine(config, inputConfig);
helloworld::HelloWorldScene helloScene;
void setup() {
engine.init();
engine.setScene(&helloScene);
}
void loop() {
engine.run();
}
#endifCreate src/main.cpp:
#ifdef PLATFORM_NATIVE
#include "platforms/native.h"
#else
#include "platforms/esp32_dev.h"
#endif[platformio]
default_envs = native
[env]
build_unflags = -std=gnu++11
build_flags =
-std=gnu++17
-fno-exceptions
-fno-rtti
-DPIXELROOT32_ENABLE_UI_SYSTEM=1
[env:native]
platform = native
build_src_filter =
+<*>
-<main.cpp>
build_flags =
${env.build_flags}
-DPLATFORM_NATIVE
-DPIXELROOT32_DEBUG_MODE
-lSDL2
-IC:/msys64/mingw64/include/SDL2
-LC:/msys64/mingw64/lib
-Wl,-subsystem,windows
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
build_src_filter =
+<*>
-<main.cpp>
build_flags =
${env.build_flags}
-DPLATFORM_ESP32DEV
-DPIXELROOT32_DEBUG_MODE
-DST7789_DRIVER
-DTFT_WIDTH=240
-DTFT_HEIGHT=240
-DTFT_MOSI=23
-DTFT_SCLK=18
-DTFT_DC=2
-DTFT_RST=4
-DTFT_CS=-1
-DSPI_FREQUENCY=40000000
lib_deps =
gperez88/PixelRoot32-Game-Engine@^1.1.0
bodmer/TFT_eSPI@^2.5.43- Scene code lives in
helloworldnamespace - Engine types use alias
pr32 = pixelroot32for cleaner code - Platform-specific code in
src/platforms/
PixelRoot32 uses a scene-based architecture. A scene is a container for game objects (entities). Your game can switch between scenes (menu, gameplay, game over, etc.).
init()- Called once when the scene starts. Use for setup.update(deltaTime)- Called every frame. Use for game logic.draw(renderer)- Called every frame. Use for rendering.
The demo shows how to read button input:
void HelloWorldScene::checkButtonPress() {
auto& input = engine.getInputManager();
if (input.isButtonPressed(0)) {
// Button 0 is Up
} else if (input.isButtonPressed(4)) {
// Button 4 is A
}
// ... etc
}Button mapping:
| Index | PC (SDL2) | ESP32 (GPIO) |
|---|---|---|
| 0 | Up Arrow | GPIO 32 |
| 1 | Down Arrow | GPIO 27 |
| 2 | Left Arrow | GPIO 33 |
| 3 | Right Arrow | GPIO 14 |
| 4 | Space | GPIO 13 (A) |
| 5 | Enter | GPIO 12 (B) |
The engine doesn't have a random function, so we use a fixed cycling array:
static const uint8_t fixedRange[] = {0, 2, 5, 7, 10, 12, 14};
colorIndex = (colorIndex + 1) % 7;This cycles through 7 distinct colors sequentially, creating a nice variety without needing random.
| Index | Color | RGB565 |
|---|---|---|
| 0 | Black | 0x0000 |
| 1 | White | 0xFFFF |
| 2 | Navy | 0x1907 |
| 3 | Blue | 0x025F |
| 4 | Cyan | 0x061F |
| 5 | DarkGreen | 0x13C2 |
| 6 | Green | 0x3648 |
| 7 | LightGreen | 0xA7F3 |
| 8 | Yellow | 0xFEA0 |
| 9 | Orange | 0xFCE3 |
| 10 | LightRed | 0xC3FF |
| 11 | Red | 0xB884 |
| 12 | DarkRed | 0x6822 |
| 13 | Purple | 0x7977 |
| 14 | Magenta | 0xCE79 |
| 15 | Gray | 0x8C71 |
SDL2 not found:
- Windows: Install via MSYS2:
pacman -S mingw-w64-x86_64-SDL2 - Linux:
sudo apt-get install libsdl2-dev
Compilation errors:
- Make sure you have C++17:
-std=gnu++17 - Disable exceptions:
-fno-exceptions
Display shows nothing:
- Check your wiring matches the pin configuration
- Try reducing SPI frequency:
-DSPI_FREQUENCY=20000000 - Verify your display type (ST7789 vs ST7735)
Upload fails:
- Press the BOOT button during upload
- Check the COM port in platformio.ini
- Check the PixelRoot32 Game Samples for more complex examples
- Read the official documentation
- Join the community on Discord
MIT License - See LICENSE for details.