Skip to content

Commit a52c003

Browse files
committed
Add examples and installer for Obsidian Engine
1 parent 0352edd commit a52c003

20 files changed

Lines changed: 1547 additions & 72 deletions

‎.github/workflows/ci.yml‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ jobs:
6262
- name: Validate documentation annotations
6363
run: lua5.4 tools/docgen.lua --check
6464

65+
# install.lua fetches manifest.txt to know which source files to pull.
66+
# Generated rather than hand-maintained: a file added to src/ but missing
67+
# here would produce a source install that is silently incomplete.
68+
- name: Generate source manifest
69+
run: find src -name '*.lua' | sort > manifest.txt
70+
6571
- name: Build release bundles
6672
run: |
6773
mkdir -p "$RUNNER_TEMP/obsidian-bundle"
@@ -103,14 +109,14 @@ jobs:
103109
--exec "local ok = shell.run('smoketest.lua'); os.shutdown(ok and 0 or 1)"
104110
done
105111
106-
- name: Update committed bundles
112+
- name: Update committed bundles and manifest
107113
if: github.event_name == 'push'
108114
run: |
109115
mkdir -p bundle
110116
cp "$RUNNER_TEMP"/obsidian-bundle/*.lua bundle/
111-
git add bundle
117+
git add bundle manifest.txt
112118
if git diff --cached --quiet; then
113-
echo "bundles already up to date"
119+
echo "bundles and manifest already up to date"
114120
exit 0
115121
fi
116122
git config user.name "github-actions[bot]"

‎.github/workflows/docs.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ jobs:
5353
mkdir -p site/docs/.vitepress/theme
5454
mkdir -p site/docs/api
5555
mkdir -p site/docs/guides
56+
mkdir -p site/docs/public
5657
rsync --archive --delete \
5758
"$RUNNER_TEMP/obsidian-api/" site/docs/api/
5859
rsync --archive --delete \
@@ -62,6 +63,8 @@ jobs:
6263
cp source/.github/theme/custom.css site/docs/.vitepress/theme/custom.css
6364
cp source/.github/docs-index.md site/docs/index.md
6465
cp source/.github/docs-home.md site/docs/home.md
66+
# Served from the site root, so the install command stays short.
67+
cp source/install.lua site/docs/public/install.lua
6568
cp source/.github/site/package.json site/package.json
6669
cp source/.github/site/package-lock.json site/package-lock.json
6770
cp source/.github/site/.gitignore site/.gitignore

‎README.md‎

Lines changed: 140 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,148 @@
11
# Obsidian Engine
22

3-
Obsidian is a high-performance 2D game engine designed specifically for **CC: Tweaked**. It features a robust Entity Component System (ECS), an optimized rendering pipeline with double-buffering, and a flexible particle system.
3+
Obsidian is a 2D game engine for **CC: Tweaked**. It pairs an Entity Component
4+
System with a retained renderer, so game logic stays decoupled from data while
5+
drawing is reduced to the cells that actually changed.
6+
7+
**[API reference and guides](https://pyroxenium.github.io/Obsidian/)**
48

59
## Features
610

7-
* **High-Performance Rendering:** Uses double-buffering and `term.blit` optimization to ensure smooth frame rates.
8-
* **ECS Architecture:** Decoupled logic and data for scalable game development.
9-
* **Asset Management:** Automatic caching and pre-processing of sprites (`.osf`), UI layouts (`.oui`), and emitters (`.ope`).
10-
* **Particle System:** Fully integrated ECS-based particles with support for gravity, drag, and color/character interpolation.
11-
* **Static/Dynamic Layering:** Optimized handling of static backgrounds versus dynamic entities.
12-
13-
## Project Structure
14-
15-
* `src/core/`: The heart of the engine.
16-
* `loader.lua`: Handles asset loading and optimization.
17-
* `particles.lua`: ECS systems for particle effects.
18-
* `buffer.lua`: Low-level rendering logic (Double-Buffering).
19-
* `assets/`: Suggested directory for your `.osf` and `.ope` files.
20-
21-
## Rendering Pipeline
22-
The engine follows a strict 7-phase pipeline:
23-
1. Clear Phase
24-
2. Static Pass
25-
3. Query Pass (ECS)
26-
4. Sort Pass (Z-Indexing)
27-
5. Entity Pass
28-
6. UI Pass
29-
7. Present Phase (Sync to Hardware)
11+
* **ECS architecture:** entities, components and systems, backed by a spatial
12+
grid for rect queries, triggers, raycasting and line-of-sight checks.
13+
* **Retained renderer:** z-ordered layers with per-channel transparency, nested
14+
translation and clipping contexts, and dirty-region tracking so only changed
15+
rows reach the terminal.
16+
* **Colour beyond 16 slots:** register logical RGB colours; a sticky palette
17+
mapper assigns them to hardware slots per frame and approximates the excess.
18+
* **Subpixels:** a 2x3 virtual canvas compiled into CC mosaic characters.
19+
* **FLIMG images:** a binary format that keeps RGB and subpixels intact instead
20+
of baking them into terminal cells.
21+
* **Scenes and tilemaps:** multi-layer maps with collision, slopes and one-way
22+
platforms, plus static, dynamic and foreground render passes.
23+
* **Gameplay systems:** physics, particles, tweens, timers, pathfinding, AI
24+
behaviour trees, audio, save/load and a document store.
25+
* **Multiplayer:** a rednet wrapper with heartbeats, and a high-level server
26+
with rooms, middleware and an optional console.
27+
* **Developer friendly:** LuaLS annotations throughout, generated API docs, and
28+
source, minified and compressed release bundles.
29+
30+
## Installation
31+
32+
Fetch the installer and run it:
33+
34+
```bash
35+
wget https://pyroxenium.github.io/Obsidian/install.lua
36+
```
37+
38+
Run without arguments it opens a graphical installer; pass a variant to skip
39+
straight to the download:
40+
41+
```bash
42+
install minified
43+
```
44+
45+
| Variant | Result | Notes |
46+
| --- | --- | --- |
47+
| `source` | `obsidian/` folder | Editable original sources |
48+
| `bundled` | `obsidian.lua` | Readable single file |
49+
| `minified` | `obsidian.lua` | Smaller, instant startup (default) |
50+
| `compressed` | `obsidian.lua` | Smallest download, decompresses on load |
51+
52+
Whichever you choose, the engine is loaded by name:
53+
54+
```lua
55+
local Engine = require("obsidian")
56+
```
57+
58+
Both layouts answer that call: a folder containing `init.lua`, or a single
59+
`obsidian.lua` next to your program.
60+
61+
## Quick start
62+
63+
```lua
64+
local Engine = require("obsidian")
65+
66+
local scene = Engine.scene.new()
67+
scene.name = "Hello"
68+
69+
local player = scene:spawn()
70+
scene:attach(player, "pos", Engine.math.vec2(10, 5))
71+
72+
scene.onDraw = function()
73+
Engine.buffer:drawText(2, 2, "Hello Obsidian", "0", "f")
74+
end
75+
76+
Engine.setScene(scene)
77+
Engine.start()
78+
```
79+
80+
See [guides/engine_quickstart.md](guides/engine_quickstart.md) for a longer
81+
walkthrough and [guides/ecs_guide.md](guides/ecs_guide.md) for the ECS model.
82+
83+
## Project structure
84+
85+
* `src/` — the engine. Installed as `obsidian`.
86+
* `init.lua` — entry point and module loader.
87+
* `engine.lua` — subsystem wiring, main loop, scene transitions.
88+
* `core/` — one file per subsystem (`buffer`, `scene`, `ecs`, `ui/`, …).
89+
* `flimg.lua` — the FLIMG image codec, shared with Basalt.
90+
* `guides/` — prose documentation.
91+
* `examples/` — runnable sample programs.
92+
* `tools/` — development tooling, not shipped with the engine.
93+
* `bundle/` — generated single-file releases.
94+
* `install.lua` — the web installer.
95+
* `manifest.txt` — source file list the installer reads; regenerated by CI.
96+
97+
## Asset formats
98+
99+
| Extension | Contents | Loaded with |
100+
| --- | --- | --- |
101+
| `.obs` | Multi-frame sprite: character, foreground and background layers | `Engine.loader.loadSprite` |
102+
| `.oui` | UI layout definitions | `Engine.loader.loadUI` / `scene:loadUI` |
103+
| `.pe` | Particle emitter configuration | `Engine.loader.loadEmitter` |
104+
| `.flimg` | Binary image with RGB colours and subpixels | `Engine.loader.loadImage` |
105+
106+
`.obs`, `.oui` and `.pe` are serialised Lua tables; see
107+
[filetypes.md](filetypes.md) for their structure and validation rules, and
108+
[src/FLIMG.md](src/FLIMG.md) for the FLIMG binary layout.
109+
110+
## Rendering pipeline
111+
112+
Each frame runs a fixed sequence:
113+
114+
1. Clear
115+
2. Static pass (tilemap and static elements, cached between frames)
116+
3. Query pass (ECS)
117+
4. Sort pass (z-index)
118+
5. Entity pass
119+
6. UI pass
120+
7. Present (compose layers, blit changed rows)
121+
122+
Static content is redrawn only when the camera moves or the scene is marked
123+
dirty; otherwise the rows entities touched last frame are restored from cache.
124+
125+
## Tooling
126+
127+
Run from the repository root. All of these work under plain Lua as well as
128+
inside CraftOS-PC.
129+
130+
```bash
131+
lua tools/docgen.lua --check
132+
```
133+
134+
```bash
135+
lua tools/bundle-host.lua --compress
136+
```
137+
138+
* `docgen.lua` — generates the API reference from the source annotations.
139+
Without arguments it writes to `docs/`; `--check` validates only.
140+
* `bundle.lua` / `bundle-host.lua` — build a single-file release. Default is
141+
minified; `--no-minify` keeps readable source, `--compress` adds compression.
142+
* `smoketest.lua` — loads the engine and verifies every subsystem came up. Run
143+
it from a computer that has Obsidian installed as `obsidian`.
144+
* `minify.lua` — the Lua minifier used by the bundler.
30145

31146
## License
147+
32148
This project is licensed under the MIT License.

‎examples/basic.lua‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
-- A small Obsidian starter project.
2+
-- Put obsidian.lua next to this file, then run it in CraftOS.
3+
4+
local Engine = require("obsidian")
5+
local game = Engine.scene.new()
6+
local player
7+
8+
game.name = "My first Obsidian game"
9+
10+
local function sprite(character, foreground, background)
11+
return {
12+
width = 1,
13+
height = 1,
14+
[1] = {
15+
{ { character } },
16+
{ { foreground or "0" } },
17+
{ { background or "f" } },
18+
},
19+
}
20+
end
21+
22+
game.onLoad = function()
23+
local width, height = Engine.buffer:getSize()
24+
25+
player = game:spawn()
26+
game:attach(player, "pos", Engine.math.vec2(
27+
math.floor(width / 2),
28+
math.floor(height / 2)
29+
))
30+
game:attach(player, "sprite", sprite("@", "9", "f"))
31+
32+
game.ui:text("title", 2, 2, {
33+
text = "My first Obsidian game",
34+
fore = "a",
35+
back = "f",
36+
z = 100,
37+
})
38+
game.ui:text("help", 2, height, {
39+
text = "Arrow keys / WASD to move - Q to quit",
40+
fore = "8",
41+
back = "f",
42+
z = 100,
43+
})
44+
end
45+
46+
game.onEvent = function(event)
47+
if event[1] ~= "key" then return end
48+
49+
local pos = game:get(player, "pos")
50+
local key = event[2]
51+
52+
if key == keys.left or key == keys.a then
53+
pos.x = pos.x - 1
54+
elseif key == keys.right or key == keys.d then
55+
pos.x = pos.x + 1
56+
elseif key == keys.up or key == keys.w then
57+
pos.y = pos.y - 1
58+
elseif key == keys.down or key == keys.s then
59+
pos.y = pos.y + 1
60+
elseif key == keys.q then
61+
Engine.stop()
62+
end
63+
end
64+
65+
Engine.setScene(game)
66+
Engine.start()

0 commit comments

Comments
 (0)