Skip to content

Latest commit

 

History

History
244 lines (184 loc) · 9.16 KB

File metadata and controls

244 lines (184 loc) · 9.16 KB

FR-Ocean Engine

A minimal, focused 2D game engine. C++17 core, Lua 5.4 for game logic, SDL2 for rendering, Box2D for physics. Cross-platform via CMake, no external toolchain, no heavyweight editor. You write components in Lua, choose a resource pack, and ship.

C++17 Lua 5.4 macOS · Linux · Windows License MIT

FR-Ocean title screen

Actual screenshots, nothing touched up. Everything you see is produced by the engine under ./build/bin/game_engine.


Quickstart

git clone https://github.com/jack-chaudier/fr-ocean-engine.git
cd fr-ocean-engine
make play          # configure, build, launch the platformer
make launcher      # menu for every example game

That's it. Prerequisites: a C++17 compiler and CMake ≥ 3.16. SDL2 is vendored on macOS and Windows; Linux uses system packages (libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev). Sprite PNGs are committed, so no Python packages are needed to play the samples.

No make? The raw commands are:

cmake --preset release
cmake --build --preset release
./build/bin/game_engine --resources resources.platformer/

Gameplay

Tidebreaker arcade sample Level 1
Tidebreaker. Generated reef art, dashing submarine, pearls, drifting hazards, HUD, particles, win/loss loop. Level 1 tutorial. Long ground, three coins, one stompable enemy, a two-step stair to a higher coin, flag on the right.
Level 2 Feature demo
Level 2 challenge. Start pad, staggered moving platforms, spike floor, enemy, coins, and flag. Feature demo. ~80 lines of Lua exercising every engine subsystem: particles, tweens, timers, events, physics, input, text.
Sample Run Shows
Platformer make play Full gameplay loop: title → level1 → level2 → victory. Variable jump, coyote time, stomp enemies, coin pickups, screen shake, fade-to-black transitions, persistent score.
Tidebreaker make tidebreaker Compact arcade loop: generated reef backdrop, dashing submarine, collectibles, hazards, particles, score, win/loss states.
Feature demo make demo Minimal showcase: particle bursts, scheduled timers, tweens, events, a bouncing physics ball, input-reactive particles.

Launcher

python3 scripts/run_game.py --list
python3 scripts/run_game.py --menu
python3 scripts/run_game.py tidebreaker
python3 scripts/run_game.py --resources /path/to/custom/resources
python3 scripts/run_game.py tidebreaker --scene smoke --self-check 120 --screenshot /tmp/tidebreaker.png

The launcher builds the engine by default, accepts any resources.<game>/ style pack, and forwards common engine flags such as --scene, --debug, --self-check, and --screenshot.

Controls (platformer)

Key Action
A / / D / Move
Space / W / Jump (hold for higher, ~100 ms coyote time and jump buffering)
P Pause (toggles Time.SetTimeScale)
R Restart the current level
F1 Toggle Box2D collider debug overlay
Esc Quit

Controls (Tidebreaker)

Key Action
W / A / S / D or arrow keys Move
Space Dash, start, restart
Esc Quit

Architecture

FR-Ocean Engine architecture

Per-frame pipeline

Frame pipeline

Every frame: input events → time tick → schedulers (tween, timer, animation, particles, camera) → scene lifecycle (OnStartOnUpdate → physics step → OnLateUpdate → queued destroys) → render (clear → world sprites → particles → UI rects → text → debug pixels → present).

See ARCHITECTURE.md for the subsystem walk-through and API_REFERENCE.md for every Lua binding. For a higher-level system map and improvement notes, see ENGINE_OVERVIEW.md.


Writing a component

Every game object is an actor with components. A component is a Lua table that defines lifecycle methods. The coin, for example:

-- resources.platformer/component_types/Coin.lua
Coin = { value = 10, collected = false, bob = 0 }

function Coin:OnStart()
    self.rb = self.actor:GetComponent("Rigidbody")
end

function Coin:OnUpdate()
    self.bob = self.bob + Time.GetDeltaTime() * 3
    local p = self.rb:GetPosition()
    Image.DrawEx("coin", p.x, p.y + math.sin(self.bob) * 0.12,
                 0, 0.7, 0.7, 0.5, 0.5, 255, 215, 40, 255, 2)
end

function Coin:Collect()
    if self.collected then return end
    self.collected = true
    Event.Emit("coin_collected", { value = self.value })
    Actor.Destroy(self.actor)
end

Then reference it from the scene JSON or actor template:

{ "name": "Coin", "template": "coin",
  "components": { "Rigidbody": { "x": -3, "y": 2.4 } } }

Lua API surface

Namespace What it lets you do
Actor Find, FindAll, Instantiate, Destroy
Input GetKey*, GetMouse*, GetMouseScrollDelta, HideCursor, ShowCursor
Image Draw, DrawEx, DrawUI, DrawUIEx, DrawPixel, DrawRect
Text Draw(str, x, y, font, size, r, g, b, a)
Audio Play, Halt, SetVolume
Camera SetPosition, SetZoom, Follow, Shake, SetBounds
Scene Load, LoadWithTransition("scene", "fade", dur), GetCurrent, DontDestroy
Physics Raycast, RaycastAll
Event Subscribe, SubscribeOnce, Emit, Unsubscribe, UnsubscribeAll
Timer After(dt, fn), Every(delay, interval, fn), Cancel, CancelAll
Tween To(obj, field, target, duration), Cancel, CancelAll
Particles Emit(x, y, count, ParticleConfig), GetActiveCount
Animation Define, Play, Stop, SetFrame, IsPlaying
Time GetDeltaTime, GetUnscaledDeltaTime, GetTotalTime, SetTimeScale, GetFrameCount
Application Quit, Sleep, OpenURL, GetFrame

Build

# macOS, Linux
cmake --preset release
cmake --build --preset release

# Windows (Visual Studio)
cmake --preset release -G "Visual Studio 17 2022"
cmake --build --preset release

The binary is build/bin/game_engine. Resources are copied to build/bin/resources/.

Regenerating sprites

All sample sprites are tracked, so a fresh clone runs out of the box. If you want to change deterministic generated sprites, edit resources.<game>/create_assets.py and rerun:

make assets

(Needs uv or pip install Pillow.) Tidebreaker's lagoon backdrop was generated with the image creator and committed as a normal PNG asset; its small gameplay sprites are regenerated by make assets.

Testing

make test

CTest smoke targets boot every sample headlessly with --self-check and fail on any [FATAL] or [ERROR] log line. Good CI shape.

CLI flags

--resources <path>     Override the resources directory
--scene <name>         Start in this scene instead of game.config's initial_scene
--self-check [N]       Run N frames (default 60) then exit 0
--screenshot <path>    Save the final frame as a PNG (implies --self-check)
--debug                Enable DEBUG-level logs
--version, --help

--screenshot is how docs/screenshots/*.png are produced — see the screenshots target in the Makefile.

Project layout

game_engine/            C++17 core (~7k LOC, 53 files)
resources.platformer/   Hero sample game — two-level platformer
resources.tidebreaker/  Polished arcade sample with generated reef art
resources.demo/         Minimal feature showcase
vendor/                 SDL2, Box2D, Lua 5.4, LuaBridge, GLM, rapidjson
scripts/run_game.py     Build + launch helper and menu
docs/                   Architecture SVGs · gameplay screenshots

Docs

License

MIT. See LICENSE.