makepad/apps/arcade/resources/template.splash
andodeki 435135ed50 feat(arcade): port makepad game libs + arcade from makepad-internal onto the fork
Reintroduce apps/arcade and the ten game crates that were ripped out of
the fork (assets, audio, blocks, coedit, gen, net, pkg, render, script,
session), from makepad-internal/dev. Reconcile against the fork's layout:

- makepad-game-sim/math come from the fork's existing libs/sim + sim/math;
  the copied internal libs/game/{sim,math} are dropped and the kept game
  crates plus arcade point at ../../sim and ../../sim/math.
- arcade AI is rewritten off the removed makepad-ai agent API onto the
  makepad-ai-hub headless ChatProvider worker (example/cad pattern):
  AiWorker{send,cancel,poll}, ai_worker_loop driving
  ClaudeApiChatProvider(ClaudeCli), AiWorkerEvent availability/delta/
  done/error mapped into the chat feed and authoring land_edit, with
  main.rs send_message/cancel_request/event-drain rebuilt around it.
- the game-script sandbox is adapted to the fork's jailed splash storage:
  every isolate gets mod.fs = splash_storage jail; splaes splash_storage
  set_root_for_heap is now public so the ScriptHost (or Splash) can grant
  a per-game jail root. ENT font: build.rs/dispatch.rs entities fill new
  fork sim Entity/Part fields via ..Default::default(); world_raycast now
  borrows mutably and returns the material id.

cargo check -p makepad-arcade and the game + arcade test suites pass.
2026-09-05 05:49:12 +03:00

151 lines
8.6 KiB
Text

// Makepad Arcade — the starting world.
//
// This is a TEMPLATE, not a demo: it is what a new world begins as, and it is
// meant to be edited. Everything here is a `game.*` verb, so the AI editing
// this file can change any of it — which is the whole point. Nothing about
// this world lives in Rust.
//
// It is deliberately readable top to bottom: sky, ground, town, cars, a
// player, something to climb. If you are adding to it, add a section.
// ---------------------------------------------------------------- sky & sun
game.sky({})
// A FIXED sun, given as a direction rather than a time of day. 38 degrees of
// elevation casts a shadow about 1.3x its caster's height: long enough to show
// what shape threw it and which way the ground slopes, short enough that the
// village does not disappear into its own shade. A moving sun re-bakes the
// world's shadows continuously and nothing on screen ever settles.
game.sun({dir: vec3(0.55, 0.62, 0.56)})
// ------------------------------------------------------------------ terrain
// `feature` is how far apart the hills are, in world units. `rim` grows the
// relief toward the edge of the map: a playable basin with a scenic horizon,
// which is the answer to terrain being either boring or unbuildable. The
// plaza keeps the town's ground genuinely flat — a road is one long box and a
// house has square feet, and neither can follow a slope.
game.terrain({
size: 260,
cells: 129,
smooth: true,
seed: 1466247766,
amp: 5,
feature: 90,
flatten: 1.25,
rim: 5,
rim_start: 0.34,
plaza: {r: 66, ramp: 26, h: 0},
color: #5a8f46
})
// -------------------------------------------------------------------- roads
// A crossroads, not a corridor. A junction is the smallest thing that makes a
// place feel like it has somewhere else to be. `collide: false` makes these
// surfaces rather than kerbs the car has to climb.
game.box({pos: vec3(0, 0.02, 0), size: vec3(116, 0.1, 14), color: #4d4d52, tag: "road", collide: false})
game.box({pos: vec3(0, 0.02, 0), size: vec3(14, 0.1, 104), color: #4d4d52, tag: "road", collide: false})
game.box({pos: vec3(-20, 0.02, 11), size: vec3(8, 0.1, 22), color: #52525a, tag: "road", collide: false})
// ------------------------------------------------------------------- houses
// `find_model` returns DISTINCT models. Ask for several and place a different
// one at each lot — five identical houses read as wallpaper however good the
// model is. Four, not more: this kit runs out of houses and starts returning
// lot pieces, one of which is a swimming pool.
let houses = game.find_model("suburban house building", {count: 4, spread: "variants"})
game.model(houses[0], {pos: vec3(-30, 0, -10), yaw: 0, scale: 4.2, tag: "house"})
game.model(houses[1], {pos: vec3(-19, 0, -10), yaw: 0, scale: 4.5, tag: "house"})
game.model(houses[2], {pos: vec3(-10, 0, -10), yaw: 0, scale: 4.2, tag: "house"})
game.model(houses[3], {pos: vec3(0.5, 0, -10), yaw: 0, scale: 4.5, tag: "house"})
game.model(houses[0], {pos: vec3(10, 0, -10), yaw: 0, scale: 4.2, tag: "house"})
game.model(houses[1], {pos: vec3(19, 0, -10), yaw: 0, scale: 4.5, tag: "house"})
game.model(houses[2], {pos: vec3(30, 0, -10), yaw: 0, scale: 4.2, tag: "house"})
// North side, set well back: the third-person camera sits about 9 units behind
// you, so a row any closer is in the shot rather than behind it.
game.model(houses[3], {pos: vec3(-25, 0, 26), yaw: 3.14159, scale: 4.0, tag: "house"})
game.model(houses[0], {pos: vec3(-14.5, 0, 26), yaw: 3.14159, scale: 4.4, tag: "house"})
game.model(houses[1], {pos: vec3(14.5, 0, 26), yaw: 3.14159, scale: 4.0, tag: "house"})
game.model(houses[2], {pos: vec3(25, 0, 26), yaw: 3.14159, scale: 4.4, tag: "house"})
// ------------------------------------------------------------------ scenery
let trees = game.find_model("tree pine", {count: 4, spread: "kinds"})
game.model(trees[0], {pos: vec3(-34, 0, -26), scale: 7, tag: "tree"})
game.model(trees[1], {pos: vec3(-24, 0, -30), scale: 6, tag: "tree"})
game.model(trees[2], {pos: vec3(12, 0, -28), scale: 7, tag: "tree"})
game.model(trees[3], {pos: vec3(28, 0, -24), scale: 6, tag: "tree"})
game.model(trees[0], {pos: vec3(38, 0, 8), scale: 7, tag: "tree"})
// --------------------------------------------------------------------- cars
// `spread: "kinds"` gives one model per FAMILY — one of each type. "variants"
// would give six of the same kind, which is right for a terrace of houses and
// wrong for traffic.
//
// Every one of these is a real vehicle: walk up to any of them and the engine
// offers to let you in. Cars need no `game.interactable` — the prompt is
// derived from the car itself.
let cars = game.find_model("ambulance delivery taxi police sedan hatchback", {count: 6, spread: "kinds"})
game.car({pos: vec3(-6, 1.2, 1.6), model: cars[0], player: true})
game.car({pos: vec3(-24, 1.2, 5.2), model: cars[1]})
game.car({pos: vec3(-13, 1.2, 5.2), model: cars[2]})
game.car({pos: vec3(7.5, 1.2, 5.2), model: cars[3]})
game.car({pos: vec3(22, 1.2, 5.2), model: cars[4]})
game.car({pos: vec3(16.5, 1.2, -4.5), model: cars[5], rot_y: 3.14159})
// ------------------------------------------------------------------- people
// A bearded hero on the full hero rig. `player_character` is the whole
// third-person binding — walker, follow camera, and getting in and out of
// cars — with crafted defaults you never have to name.
let heroes = game.find_model("barbarian", {count: 1, rigged: true})
let me = game.player_character({pos: vec3(-6, 2, 9.5), model: heroes[0]})
game.label(me, "You")
// Townsfolk who wander their own patch.
let folk = game.find_model("character person", {count: 4, spread: "kinds", rigged: true})
let a = game.character({pos: vec3(-14, 2, 8), model: folk[0], tag: "villager"})
game.wander(a, {home: vec3(-14, 0, 8), range: 14, speed: 2.0})
let b = game.character({pos: vec3(4, 2, 10), model: folk[1], tag: "villager"})
game.wander(b, {home: vec3(4, 0, 10), range: 14, speed: 2.2})
let c = game.character({pos: vec3(18, 2, 6), model: folk[2], tag: "villager"})
game.wander(c, {home: vec3(18, 0, 6), range: 12, speed: 1.9})
let d = game.character({pos: vec3(-26, 2, 2), model: folk[3], tag: "villager"})
game.wander(d, {home: vec3(-26, 0, 2), range: 12, speed: 2.1})
// -------------------------------------------------------------------- climb
// A tower you spiral around rather than a line of pads: the thing you are
// climbing stays in view, so your progress is legible from the ground.
//
// Three rules make a course feel fair rather than fiddly — every jump is
// survivable from a standstill, the whole route is visible from the bottom,
// and falling costs height rather than a life.
game.box({pos: vec3(-36, 8, 34), size: vec3(6, 16, 6), color: #857569, tag: "tower"})
// Nine ledges, easing outward as they rise so the last steps are the boldest.
game.box({pos: vec3(-30.1, 1.6, 36.5), size: vec3(4.2, 0.7, 4.2), color: #99855c, tag: "ledge"})
game.box({pos: vec3(-31.3, 3.35, 40.4), size: vec3(4.2, 0.7, 4.2), color: #99855c, tag: "ledge"})
game.box({pos: vec3(-35.6, 5.1, 42.3), size: vec3(4.2, 0.7, 4.2), color: #99855c, tag: "ledge"})
game.box({pos: vec3(-40.4, 6.85, 41.1), size: vec3(4.2, 0.7, 4.2), color: #99855c, tag: "ledge"})
game.box({pos: vec3(-43.6, 8.6, 37.2), size: vec3(4.2, 0.7, 4.2), color: #99855c, tag: "ledge"})
game.box({pos: vec3(-44.0, 10.35, 32.1), size: vec3(4.2, 0.7, 4.2), color: #99855c, tag: "ledge"})
game.box({pos: vec3(-41.4, 12.1, 27.6), size: vec3(4.2, 0.7, 4.2), color: #99855c, tag: "ledge"})
game.box({pos: vec3(-36.6, 13.85, 25.4), size: vec3(4.2, 0.7, 4.2), color: #99855c, tag: "ledge"})
game.box({pos: vec3(-31.4, 15.6, 26.5), size: vec3(4.2, 0.7, 4.2), color: #99855c, tag: "ledge"})
// Two gaps are bridged by moving platforms instead — the timing beats.
let plat_x = game.box({pos: vec3(-25, 6.5, 32), size: vec3(4.6, 0.7, 4.6), color: #5999cc, tag: "lift", body: "kinematic"})
let plat_y = game.box({pos: vec3(-39, 11, 45), size: vec3(4.6, 0.7, 4.6), color: #6bb385, tag: "lift", body: "kinematic"})
// The summit: wide, still, and obviously the end.
game.box({pos: vec3(-36, 16.8, 34), size: vec3(8, 1, 8), color: #d9b34d, tag: "goal"})
// A ramp at the base, so the climb starts from the ground rather than from a
// jump you have to already know is there.
game.box({pos: vec3(-28, 1, 25), size: vec3(6, 2, 10), color: #b89a6b, tag: "ramp", shape: "wedge"})
// The lifts. Different periods, so they are never the same crossing twice.
game.on_tick(|dt| {
let t = game.time()
game.set_vel(plat_x, vec3(cos(t * 0.55) * 4.2, 0, 0))
game.set_vel(plat_y, vec3(0, cos(t * 0.42) * 2.6, 0))
})
game.text("hint", "WASD move · mouse look · space jump", {anchor: "top_left"})