-- Character controller: the Fox as a player walking a course of stairs, ramps, a wall, a crate to -- shove and a moving platform, driven by a scripted route so it runs headless; the camera follows. local font = fontLoad("Singe/FreeSansBold.ttf", 28) local frames = 0 fontSelect(font) discPlay() sceneEnable(true) sceneSetBackground(0, 0, 0, 0) sceneSetAmbient(70, 70, 80) local stone = materialNew() materialSetColor(stone, 120, 120, 130) materialSetRoughness(stone, 0.9) local wood = materialNew() materialSetColor(wood, 150, 100, 50) local steel = materialNew() materialSetColor(steel, 90, 110, 150) materialSetMetallic(steel, 0.6) local function block(x, y, z, w, h, d, material) local node = nodeNew() nodeSetMesh(node, meshBox(w, h, d), material) nodeSetPosition(node, x, y, z) bodyNew(node, BODY_STATIC, SHAPE_BOX, w, h, d) return node end -- The course runs along +X. Floor, three stairs, a gentle ramp, a steep ramp, a wall to slide along. block(8, -0.1, 0, 30, 0.2, 8, stone) block(2.0, 0.075, 0, 0.6, 0.15, 4, wood) block(2.6, 0.15, 0, 0.6, 0.30, 4, wood) block(3.2, 0.225, 0, 0.6, 0.45, 4, wood) local ramp = nodeNew() nodeSetMesh(ramp, meshBox(3, 0.2, 4), stone) nodeSetPosition(ramp, 6.2, 0.7, 0) nodeSetRotation(ramp, 0, 0, 25) bodyNew(ramp, BODY_STATIC, SHAPE_BOX, 3, 0.2, 4) block(9.0, 0.7, 0, 2.6, 2.6, 4, stone) -- The top landing behind the ramp local steep = nodeNew() nodeSetMesh(steep, meshBox(2, 0.2, 4), stone) nodeSetPosition(steep, 11.0, 2.85, 0) nodeSetRotation(steep, 0, 0, 60) bodyNew(steep, BODY_STATIC, SHAPE_BOX, 2, 0.2, 4) block(9.0, 2.4, -2.5, 4, 1.2, 0.2, steel) -- A wall along the landing's far edge -- A crate to shove and a platform that slides back and forth. local crate = nodeNew() nodeSetMesh(crate, meshBox(0.6, 0.6, 0.6), wood) nodeSetPosition(crate, 4.6, 0.3, 0.15) bodyNew(crate, BODY_DYNAMIC, SHAPE_BOX, 0.6, 0.6, 0.6) bodySetMass(crate, 5) local platform = nodeNew() nodeSetMesh(platform, meshBox(1.5, 0.2, 1.5), steel) nodeSetPosition(platform, 8.5, 2.1, 1.5) bodyNew(platform, BODY_KINEMATIC, SHAPE_BOX, 1.5, 0.2, 1.5) -- The player: a capsule with the Fox model hanging off the node, facing +X. local player = nodeNew() nodeSetPosition(player, 0, 0.05, 0) playerNew(player, 0.3, 1.0) playerSetStep(player, 0.35) local foxModel = modelLoad("testScripts/Models/Fox.glb") local fox = modelInstance(foxModel) nodeSetParent(fox, player) nodeSetScale(fox, 0.01) nodeSetRotation(fox, 0, 90, 0) animationPlay(fox, "Walk", true) local sun = lightNew(LIGHT_DIRECTIONAL) nodeSetPosition(sun, -3, 8, 5) nodeLookAt(sun, 6, 0, 0) lightSetIntensity(sun, 1.5) lightSetShadow(sun, true) local camera = nodeNew() cameraSet(camera) local jumped = false local landings = 0 function onCollision(a, b, x, y, z, speed) if a == player or b == player then landings = landings + 1 end end function onOverlayUpdate() local px, py, pz = nodeGetPosition(player) local t = frames / 60 frames = frames + 1 -- Route: walk east up the stairs and the ramp, veer to the wall, then onto the platform. local vz = 0 if px > 7.5 and px < 10 then vz = (t < 6) and -1.2 or 1.2 end playerMove(player, 3.0, vz) if px > 5.5 and not jumped and playerIsOnGround(player) then jumped = playerJump(player, 4.5) end nodeSetRotation(player, 0, math.deg(math.atan(vz, 3.0)) * -1, 0) -- The platform slides in Z. nodeSetPosition(platform, 8.5, 2.1, 1.5 + math.sin(t * 1.5) * 1.5) -- Camera behind and above the player. nodeSetPosition(camera, px - 4.5, py + 2.6, pz + 4.5) nodeLookAt(camera, px + 1, py + 0.6, pz) overlayClear() fontPrint(20, 20, string.format("Player, frame %d x %.1f y %.2f ground %s landings %d", frames, px, py, tostring(playerIsOnGround(player)), landings)) if frames % 30 == 0 then local fx, fy, fz = nodeGetWorldPosition(fox) local vx, vy, vz = playerGetVelocity(player) local cx, cy, cz = nodeGetPosition(crate) debugPrint(string.format("frame %d player %.2f %.2f %.2f v %.2f %.2f %.2f ground %s jumped %s crate %.2f %.2f landings %d", frames, px, py, pz, vx, vy, vz, tostring(playerIsOnGround(player)), tostring(jumped), cx, cz, landings)) end if frames == 40 or frames == 80 or frames == 120 or frames == 160 then singeScreenshot() end if frames == 180 then singeQuit() end end