Skip to content

PixelRoot32-Game-Engine/pixelroot32-hello-world

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PixelRoot32 Hello World

A simple "Hello PixelRoot32" demo showcasing cycling background colors with button input detection.

Demo

What This Project Does

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.

Prerequisites

Required Software

  • VS Code with PlatformIO IDE extension
  • Python 3.8+ (usually included with PlatformIO)

For Native (PC) Development

  • SDL2 development libraries:
    • Windows (MSYS2): pacman -S mingw-w64-x86_64-SDL2
    • Linux: sudo apt-get install libsdl2-dev
    • macOS: brew install sdl2

For ESP32 Development

  • ESP32 board (any variant)
  • ST7735 display (128x138)
  • USB cable for programming

Quick Start

1. Clone the Repository

git clone https://github.com/PixelRoot32-Game-Engine/pixelroot32-hello-world.git
cd pixelroot32-hello-world

2. Build for Native (PC)

pio run -e native

3. Build for ESP32

pio run -e esp32dev

4. Run and Test

  • PC: Use arrow keys + Space (A) + Enter (B)
  • ESP32: Use the joystick/buttons on your hardware

Project Structure

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.md

Tutorial: Step by Step

This section explains how the project was built from scratch.

Step 1: Create the Project

Create a new PlatformIO project or clone this repository.

Step 2: Install PixelRoot32 Engine

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
    -lSDL2

Step 3: Create the Scene Header

Create 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 helloworld

Step 4: Implement the Scene

Create 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 helloworld

Step 5: Create the Platform Entry Points

Create 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_NATIVE

Create 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();
}

#endif

Step 6: Create the Main Entry Point

Create src/main.cpp:

#ifdef PLATFORM_NATIVE
#include "platforms/native.h"
#else
#include "platforms/esp32_dev.h"
#endif

Step 7: Configure platformio.ini

[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

Understanding the Code

Namespace Organization

  • Scene code lives in helloworld namespace
  • Engine types use alias pr32 = pixelroot32 for cleaner code
  • Platform-specific code in src/platforms/

Scene System

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.).

Lifecycle Methods

  • 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.

Input System

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)

Color Cycling (No Random Needed)

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.

PR32 Color Palette

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

Troubleshooting

Native Build Issues

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

ESP32 Build Issues

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

Next Steps

License

MIT License - See LICENSE for details.

About

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.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages