Skip to content

Working with raylib

headshot54325-maker edited this page Jul 19, 2026 · 1 revision

#include "raylib.h" #include #include

const int SCREEN_WIDTH = 640; const int SCREEN_HEIGHT = 480; const int VIRTUAL_WIDTH = 320; // Retro pixel feel const int VIRTUAL_HEIGHT = 240;

enum GameState { EXPLORATION, BATTLE, MENU };

struct Button { Rectangle rect; std::string text; bool pressed = false; };

int main() { SetConfigFlags(FLAG_WINDOW_RESIZABLE); InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Undertale Fangame - Prototype"); SetTargetFPS(60);

// Camera for pixel perfect scaling
RenderTexture2D target = LoadRenderTexture(VIRTUAL_WIDTH, VIRTUAL_HEIGHT);

GameState state = EXPLORATION;

// Player Soul
Vector2 playerPos = {160, 160};
float speed = 2.0f;
Color soulColor = RED;

// Boss
Vector2 bossPos = {200, 60};
float bossHP = 100;

// Bullets
std::vector<Vector2> bullets;
float bulletTimer = 0;

// Virtual Controls
Button dpadUp    = {{20, 140, 40, 40}, "^"};
Button dpadDown  = {{20, 200, 40, 40}, "v"};
Button dpadLeft  = {{5, 170, 40, 40}, "<"};
Button dpadRight = {{55, 170, 40, 40}, ">"};

Button btnZ = {{VIRTUAL_WIDTH - 110, VIRTUAL_HEIGHT - 60, 45, 45}, "Z"};
Button btnX = {{VIRTUAL_WIDTH - 60, VIRTUAL_HEIGHT - 60, 45, 45}, "X"};
Button btnC = {{VIRTUAL_WIDTH - 160, VIRTUAL_HEIGHT - 60, 45, 45}, "C"};

bool inBattle = false;

while (!WindowShouldClose()) {
    // Update touch input
    Vector2 touchPos = GetTouchPosition(0);
    bool touchActive = (GetTouchPointCount() > 0);

    // Movement
    Vector2 dir = {0, 0};
    if (IsKeyDown(KEY_UP)    || (touchActive && CheckCollisionPointRec(touchPos, dpadUp.rect)))    dir.y -= 1;
    if (IsKeyDown(KEY_DOWN)  || (touchActive && CheckCollisionPointRec(touchPos, dpadDown.rect)))  dir.y += 1;
    if (IsKeyDown(KEY_LEFT)  || (touchActive && CheckCollisionPointRec(touchPos, dpadLeft.rect)))  dir.x -= 1;
    if (IsKeyDown(KEY_RIGHT) || (touchActive && CheckCollisionPointRec(touchPos, dpadRight.rect))) dir.x += 1;

    if (dir.x != 0 || dir.y != 0) {
        dir = Vector2Normalize(dir);
        playerPos.x += dir.x * speed;
        playerPos.y += dir.y * speed;
    }

    // Button presses
    bool pressZ = IsKeyPressed(KEY_Z) || (touchActive && IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && 
                  CheckCollisionPointRec(touchPos, btnZ.rect));
    bool pressX = IsKeyPressed(KEY_X) ||**✅ Got it.** Here's a **C++ raylib Undertale-style fangame prototype** with:
  • Pixel art aesthetic
  • Virtual D-Pad + on-screen Z / X / C buttons (mobile touch friendly)
  • Exploration mode
  • Boss fight (simple bullet hell with one basic boss)

1. First, install raylib

Follow the official raylib setup for your platform:
https://github.com/raysan5/raylib/wiki/Working-with-raylib

For Android, use the Android template from raylib.


2. Main Code (main.cpp)

#include "raylib.h"
#include <vector>
#include <string>

const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int VIRTUAL_WIDTH = 320;   // Retro pixel feel
const int VIRTUAL_HEIGHT = 240;

enum GameState { EXPLORATION, BATTLE, MENU };

struct Button {
    Rectangle rect;
    std::string text;
    bool pressed = false;
};

int main() {
    SetConfigFlags(FLAG_WINDOW_RESIZABLE);
    InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Undertale Fangame - Prototype");
    SetTargetFPS(60);

    // Camera for pixel perfect scaling
    RenderTexture2D target = LoadRenderTexture(VIRTUAL_WIDTH, VIRTUAL_HEIGHT);

    GameState state = EXPLORATION;

    // Player Soul
    Vector2 playerPos = {160, 160};
    float speed = 2.0f;
    Color soulColor = RED;

    // Boss
    Vector2 bossPos = {200, 60};
    float bossHP = 100;

    // Bullets
    std::vector<Vector2> bullets;
    float bulletTimer = 0;

    // Virtual Controls
    Button dpadUp    = {{20, 140, 40, 40}, "^"};
    Button dpadDown  = {{20, 200, 40, 40}, "v"};
    Button dpadLeft  = {{5, 170, 40, 40}, "<"};
    Button dpadRight = {{55, 170, 40, 40}, ">"};

    Button btnZ = {{VIRTUAL_WIDTH - 110, VIRTUAL_HEIGHT - 60, 45, 45}, "Z"};
    Button btnX = {{VIRTUAL_WIDTH - 60, VIRTUAL_HEIGHT - 60, 45, 45}, "X"};
    Button btnC = {{VIRTUAL_WIDTH - 160, VIRTUAL_HEIGHT - 60, 45, 45}, "C"};

    bool inBattle = false;

    while (!WindowShouldClose()) {
        // Update touch input
        Vector2 touchPos = GetTouchPosition(0);
        bool touchActive = (GetTouchPointCount() > 0);

        // Movement
        Vector2 dir = {0, 0};
        if (IsKeyDown(KEY_UP)    || (touchActive && CheckCollisionPointRec(touchPos, dpadUp.rect)))    dir.y -= 1;
        if (IsKeyDown(KEY_DOWN)  || (touchActive && CheckCollisionPointRec(touchPos, dpadDown.rect)))  dir.y += 1;
        if (IsKeyDown(KEY_LEFT)  || (touchActive && CheckCollisionPointRec(touchPos, dpadLeft.rect)))  dir.x -= 1;
        if (IsKeyDown(KEY_RIGHT) || (touchActive && CheckCollisionPointRec(touchPos, dpadRight.rect))) dir.x += 1;

        if (dir.x != 0 || dir.y != 0) {
            dir = Vector2Normalize(dir);
            playerPos.x += dir.x * speed;
            playerPos.y += dir.y * speed;
        }

        // Button presses
        bool pressZ = IsKeyPressed(KEY_Z) || (touchActive && IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && 
                      CheckCollisionPointRec(touchPos, btnZ.rect));
        bool pressX = IsKeyPressed(KEY_X) || (touchActive && CheckCollisionPointRec(touchPos, btnX.rect));
        bool pressC = IsKeyPressed(KEY_C) || (touchActive && CheckCollisionPointRec(touchPos, btnC.rect));

        if (pressC) state = (state == MENU) ? EXPLORATION : MENU;

        // Simple Battle Trigger (press Z near center)
        if (pressZ && !inBattle && state == EXPLORATION) {
            if (Vector2Distance(playerPos, (Vector2){160, 100}) < 60) {
                state = BATTLE;
                inBattle = true;
                playerPos = {100, 180}; // Move to battle position
            }
        }

        // Battle Logic
        if (state == BATTLE) {
            soulColor = YELLOW; // Fight mode soul
            bulletTimer += GetFrameTime();

            // Simple bullet pattern
            if (bulletTimer > 0.3f) {
                bullets.push_back({bossPos.x + GetRandomValue(-40,40), bossPos.y + 30});
                bulletTimer = 0;
            }

            // Update bullets
            for (auto& b : bullets) b.y += 3;

            // Collision with player
            for (size_t i = 0; i < bullets.size(); i++) {
                if (CheckCollisionCircles(playerPos, 8, bullets[i], 6)) {
                    // Hit!
                    bullets.erase(bullets.begin() + i);
                    break;
                }
            }
        } else {
            soulColor = RED;
        }

        // Drawing
        BeginTextureMode(target);
            ClearBackground(BLACK);
            
            if (state == EXPLORATION) {
                DrawRectangle(40, 40, 240, 160, DARKGRAY); // Floor
                DrawText("A mysterious room...", 50, 50, 10, WHITE);
                DrawCircleV(playerPos, 10, soulColor); // Soul
                DrawCircleV({160, 100}, 15, PURPLE);   // NPC / Boss trigger
            }
            else if (state == BATTLE) {
                DrawText("BOSS FIGHT", 100, 20, 20, RED);
                DrawCircleV(bossPos, 25, PURPLE);
                DrawCircleV(playerPos, 10, soulColor);

                for (auto& b : bullets) {
                    DrawCircleV(b, 6, WHITE);
                }

                DrawText(TextFormat("Boss HP: %.0f", bossHP), 20, 40, 10, WHITE);
            }
            else if (state == MENU) {
                DrawRectangle(60, 60, 200, 120, DARKBLUE);
                DrawText("MENU\n\nHP: 20/20\n\nPress C to close", 80, 80, 12, WHITE);
            }

        EndTextureMode();

        // Render with pixel scaling
        BeginDrawing();
            ClearBackground(BLACK);
            DrawTexturePro(target.texture, 
                {0, 0, (float)target.texture.width, -(float)target.texture.height},
                {0, 0, (float)GetScreenWidth(), (float)GetScreenHeight()},
                {0,0}, 0, WHITE);

            // Draw virtual controls
            DrawRectangleRec(dpadUp.rect,    Fade(LIGHTGRAY, 0.6f));
            DrawRectangleRec(dpadDown.rect,  Fade(LIGHTGRAY, 0.6f));
            DrawRectangleRec(dpadLeft.rect,  Fade(LIGHTGRAY, 0.6f));
            DrawRectangleRec(dpadRight.rect, Fade(LIGHTGRAY, 0.6f));
            DrawText(dpadUp.text.c_str(),    dpadUp.rect.x + 12, dpadUp.rect.y + 8, 20, BLACK);
            DrawText(dpadDown.text.c_str(),  dpadDown.rect.x + 12, dpadDown.rect.y + 8, 20, BLACK);
            DrawText(dpadLeft.text.c_str(),  dpadLeft.rect.x + 12, dpadLeft.rect.y + 8, 20, BLACK);
            DrawText(dpadRight.text.c_str(), dpadRight.rect.x + 12, dpadRight.rect.y + 8, 20, BLACK);

            DrawRectangleRec(btnZ.rect, BLUE);   DrawText("Z", btnZ.rect.x + 15, btnZ.rect.y + 12, 20, WHITE);
            DrawRectangleRec(btnX.rect, RED);    DrawText("X", btnX.rect.x + 15, btnX.rect.y + 12, 20, WHITE);
            DrawRectangleRec(btnC.rect, GREEN);  DrawText("C", btnC.rect.x + 15, btnC.rect.y + 12, 20, WHITE);

        EndDrawing();
    }

    UnloadRenderTexture(target);
    CloseWindow();
    return 0;
}

Clone this wiki locally