Files
EVEngine/examples/sprite-stack/main.nut
T
西风逍遥游 f7962f43fe feat(examples): developer-experience and AI-native showcase examples
- devlab: flagship --debug example (console/snapshot/AI panel/pause/hot
  reload/error slice), make devlab target with RUN_ARGS support
- ai-game: agent-drivable live game + agent_demo.py (pause/read/mutate/
  verify/snapshot/restore/screenshot loop)
- ai-editor: headless eve mcp editor host demo (JSON View + ViewModel MVVM)
- examples: fill all missing READMEs + config.nut, add examples/ index
- load.nut: add key_just_pressed/clampf/persist helpers; dedupe 19 examples
- rpg/cardgame: move JSON definitions to data files
- fix eve mcp crash: ModuleManager::requireAll() instantiates modules in
  the headless host (IEditorHost etc. were null)
- graphics: actionable screenshot error when readback was just enabled
- CI: smoke all examples (devlab with --debug) + DevLab hot-reload test

Fixes surfaced by the new CI smoke (examples had never been smoke-tested):
- scene_director kit: camera slot doubled as function + instance and was
  nulled by reset(); move the live Camera3D into cameraObj
- readTextFile used non-existent readn()/len(); use file.read()
  (rpg/cardgame/hex-levels)
- daynight used non-existent keyboard.wasPressed() -> key_just_pressed
- outline passed ints to float-only drawSolidRect; linear-structures
  slider passed an int value
- scene-ecs beginNode() requires (id, name); ai-stage guarded compilestring
- smoke_examples.sh: timeout -k 3 so SIGTERM-ignoring hosts cannot hang CI
2026-08-21 19:18:52 +08:00

139 lines
5.2 KiB
Plaintext

// Sprite stacking pseudo-3D demo.
//
// A 3D primitive is sliced into thin RGBA layers on the CPU, then rendered as
// a stack of alpha-blended slices inside the 3D forward pass. From the design
// view angle the stack reads as a solid 3D object; rotating the camera (or the
// stack yaw) reveals the classic pseudo-3D parallax.
//
// Controls:
// A / D rotate stack yaw
// W / S camera distance
// H toggle vertical (billboard) / horizontal (top-down) mode
// 1..4 primitive: cylinder / sphere / cone / box
// M toggle procedural primitive / rock.obj (model-file slicing)
// Q / E slice thickness
// R rebuild (reslice) the stack
if (!("stackYaw" in getroottable())) stackYaw <- 0.0;
if (!("camDist" in getroottable())) camDist <- 6.0;
if (!("stackMode" in getroottable())) stackMode <- "vertical";
if (!("stackKind" in getroottable())) stackKind <- "cylinder";
if (!("sliceCount" in getroottable())) sliceCount <- 20;
if (!("sliceThick" in getroottable())) sliceThick <- 0.0;
if (!("stack" in getroottable())) stack <- null;
if (!("batch" in getroottable())) batch <- null;
if (!("satellites" in getroottable())) satellites <- [];
if (!("rockLayers" in getroottable())) rockLayers <- null;
if (!("useModel" in getroottable())) useModel <- false;
if (!("prevKeys" in getroottable())) prevKeys <- {};
local tints = {
cylinder = [0.35, 0.78, 0.72],
sphere = [0.85, 0.52, 0.28],
cone = [0.55, 0.42, 0.30],
box = [0.42, 0.55, 0.85],
};
function pressed(k) {
return key_just_pressed(k);
}
function rebuildStack() {
local axis = stackMode == "vertical" ? "z" : "y";
if (useModel) {
local md = model3d.newModelDataFromFile("assets/rock.obj");
rockLayers = spritestack.sliceModel(md, 22, 128, 128, axis, 0.0);
} else {
rockLayers = null;
}
local layers = rockLayers != null
? rockLayers
: spritestack.slicePrimitive(stackKind, sliceCount, 128, 128, axis, 0.0);
stack = spritestack.newStack(gfx);
stack.setLayerCount(layers.len());
for (local i = 0; i < layers.len(); i++)
stack.setLayerImage(gfx, layers[i], i);
stack.setThickness(stackMode == "vertical" ? 0.12 : 0.17);
stack.setSize(2.4, 2.8);
stack.setPosition(0.0, 0.0, 0.0);
stack.setMode(stackMode);
local t = useModel ? [0.55, 0.48, 0.40] : tints[stackKind];
stack.setTint(t[0], t[1], t[2], 1.0);
stack.setShadowEnabled(true);
stack.setShadowOpacity(0.38);
stack.setShadowLight(-0.45, -1.0, -0.35);
stack.setShadowPlaneY(-1.53);
stack.setCastShadow(true); // real CSM shadow from the slice silhouettes
if (stackMode == "vertical") stack.setOutline(0.045, 0.02, 0.03, 0.04);
rebuildSatellites();
}
// A few small copies sharing the main stack's layer textures, drawn in a
// single batched draw call per (texture, tint) group.
function rebuildSatellites() {
if (batch == null) batch = spritestack.newBatch(gfx);
batch.clear();
satellites = [];
local offsets = [[2.6, 0.9], [-2.5, 1.0], [3.0, -1.2]];
foreach (off in offsets) {
local s = spritestack.newStack(gfx);
s.setLayerCount(stack.getLayerCount());
for (local i = 0; i < stack.getLayerCount(); i++)
s.setLayerTexture(stack.getLayerTexture(i), i);
s.setThickness(stack.getThickness());
s.setSize(0.85, 0.95);
s.setPosition(off[0], 0.0, off[1]);
s.setMode(stackMode);
s.setTint(0.58, 0.52, 0.46, 1.0);
batch.add(s);
satellites.append(s);
}
}
local cam = eve.Camera3D();
cam.setEye(0.0, 2.8, camDist);
cam.setTarget(0.0, 0.2, 0.0);
cam.setUp(0.0, 1.0, 0.0);
cam.setFov(45.0);
cam.setAmbient(0.32, 0.35, 0.38);
cam.setActive(true);
gfx.setDirectionalLight(-0.45, -1.0, -0.35, 1.25, 1.18, 1.05);
gfx.setBackgroundColor(0.075, 0.10, 0.12, 1.0);
// Flat ground disc so the stack's depth reads against the scene.
local ground = eve.Renderable3D();
ground.setMesh(gfx.newMeshCylinder(48, 1, true));
ground.setPosition(0.0, -1.55, 0.0);
ground.setScale(9.0, 0.18, 9.0);
ground.setTint(0.20, 0.30, 0.24, 1.0);
ground.setRoughness(0.9);
rebuildStack();
function eve_update(dt) {
if (pressed("h") || pressed("H")) {
stackMode = stackMode == "vertical" ? "horizontal" : "vertical";
rebuildStack();
}
if (pressed("r") || pressed("R")) rebuildStack();
if (pressed("1")) { stackKind = "cylinder"; rebuildStack(); }
if (pressed("2")) { stackKind = "sphere"; rebuildStack(); }
if (pressed("3")) { stackKind = "cone"; rebuildStack(); }
if (pressed("4")) { stackKind = "box"; rebuildStack(); }
if (pressed("m") || pressed("M")) { useModel = !useModel; rebuildStack(); }
if (keyboard.isDown("a") || keyboard.isDown("A")) stackYaw -= dt * 0.9;
if (keyboard.isDown("d") || keyboard.isDown("D")) stackYaw += dt * 0.9;
if (keyboard.isDown("w") || keyboard.isDown("W")) camDist = max(2.5, camDist - dt * 3.0);
if (keyboard.isDown("s") || keyboard.isDown("S")) camDist = min(14.0, camDist + dt * 3.0);
stack.setYaw(stackYaw);
cam.setEye(0.0, 2.8, camDist);
}
function eve_render() {
gfx.clear();
gfx.render3D();
// Draw the pseudo-3D stack into the open 3D pass (before present).
stack.render(gfx);
batch.render(gfx);
}