voxel_game 0.3.0-dev
voxel_game: ^0.3.0-dev copied to clipboard
A voxel sandbox in a few lines: declare blocks, world generation, player and mobs, get a playable world on flutter_scene. The kit over voxel_engine, voxel_scene and sound_recipes.
Changelog #
0.3.0-dev #
GameWorld.updaterebuilds the chunk regions the streaming changed within the view's frame budget, nearest the focus first (VoxelChunkView.rebuild), so a column of chunks entering or leaving the window is drawn over a few frames instead of in one long one;GameWorld.isIdlealso waits for the regions still to draw.- Breaking: the HUD is built once, not every frame.
HudBuilderis called whenVoxelGameWidgetbuilds (the game starts, a screen opens or closes), and the HUD sits behind aRepaintBoundary, so the scene's repaint every frame no longer repaints it. A piece that shows the game's state watches it through the newHudSelector, which checks a value on every tick of the newVoxelGame.framesand rebuilds only when it changes; a custom HUD that read the game in itsbuildmust move those reads into selectors.DefaultHudis such a tree: while nothing it shows changes, a frame builds none of it. RigInstance.placecomposes the root's pose into its matrix at once and allocates nothing, asRigPart.applynow does; every creature is placed every step.- A networked game sends the block edits of a step together, as one
editsmessage at the step's end, no longer asetmessage per cell as each was made: an explosion's ~120 cells cost the host 1.7 ms with 4 peers and 3.3 with 8 (M2 Pro), a message and a write each per peer, against 37 and 50 µs as one. The session now ticks last inVoxelGame.step, after the game's systems andonTick, so every edit of a step leaves in it. The wire changed: a host and a client of different versions cannot talk. - The mining crack is one draw: its sticks were a node and a geometry each, up to 60 draws
at its last stage, and each stage is now one
BoxMesh(voxel_scene) holding its sticks and every earlier stage's, on one node.FirstPersonView.crackBoxes(stage)lists them.
0.2.0-dev #
- Frames between two steps are drawn between them. Above 60 fps half the frames ran
no step, and everything drawn moved in the steps, so the view and every body stood
still one frame and moved a whole step the next: a 60 Hz world on a 120 Hz display.
Now
VoxelGame.framedraws every bodyalphaof the way between its last two steps' poses (NodeBody.drawNode, the steps keeping the pose before theirs withbeginStep), the camera's eye and the first-person hand follow the drawn player (PlayerEntity.drawnEye), and the view bob and the third-person pull-out move byVoxelGame.drawnTime, the game time a frame shows. A frame is drawn a step behind the simulation, 16.7 ms. The step is unchanged: 60 Hz, nothing paused. - The look is drained once a frame, by
VoxelGame.frame, before the steps, and no longer by the step: the view turns at the display's rate, and the steps aim with the newest yaw.PlayerEntity.lookapplies it (dropped while dead or not yet placed). The buttons are still read by the step alone. A game that callsstepitself, withoutframe, turns the player withlook. - A rig's facing is its owner's:
RigInstance.placeno longer turns the rig's root, and the mob, the player and a remote player turn their node byrig.yawthroughsyncNode, so a body turns as smoothly as it moves. The limbs are still posed a step at a time. The outline around an aimed creature follows its drawn box, once a frame (PlayerEntity.drawOutline). - Drops and projectiles set their pose through
syncNode(a drop's bob and spin, a projectile's heading) instead of writing their node, so they are drawn between steps too; a respawn or a placement snaps (syncNode(snap: true)) instead of gliding there. FrameStatsat 120 Hz, the Mac,dpr2.0:view judder1.0 → 0.0 atorbit:6andmobs:6, 1.0 → 0.05 atfly:6(its first four frames, before the flight starts), and no other frame shows the view of the frame before.InputMap.turn(yaw, pitch): a steady turn from code, radians a second, taken bytakeLookover itsdtas the right stick's is. A bot, a cutscene or the benchmark turns the view the way a held stick does.FrameStatsmeasures how evenly the view moves, which no frame timing can: a frame presented on time may show the view the one before showed.addView(eye, forward), whichViewCameracalls once a frame, keeps how far the view moved (the angle its forward turned, plus its eye's travel overFrameStats.viewDepth, 10 m), andaddFrametakes the frame'sseconds.FrameReport.viewJudderis the root mean square of each frame's speed against the mean, minus one (0 for a view that moves by the time that passed, about 1 for one that moves every other frame) andstillFramescounts the frames that moved less than a quarter of the mean; the JSON has them underview. At 120 Hz the kit today reads about 1 and half its frames: the view moves in the 60 Hz steps.
0.1.2-dev #
- The creatures of a species share their meshes:
RigModel.of(rig, halfWidth, height)builds a look at a size once (its parts' voxels asRigShapes, their rest poses, the fit), and eachRigInstancehangs only its own posable nodes on it. flutter_scene then draws a part once a pass, instanced over every creature that has it, instead of once a creature.Rigis equal by its values, so two species declared with the same look share one model; a humanoid's two legs and two arms, and a quadruped's four legs, share one shape. With 40 creatures (mobs:6) their meshes go from 260 to 8, and on the M2 Pro the shadow pass, which draws every creature again in every cascade each frame, from 2.4 ms to 0.45 a frame. gamepads: ^0.1.10, the first version withNormalizedGamepadState. The constraint allowed0.1.1-dev2, which lacks it, so the lowest resolution did not compile and pana took 20 pub points.- Formatted by
dart formatat the 120 columns the code is written at (formatter: page_width: 120inanalysis_options.yaml); pana took 10 pub points for formatting. - A step runs at most
Mob.searchesPerStep(3) A* searches; a mob whose plan is due when they are spent plans on a later step (VoxelGame.searchesLeft). The random phase each mob starts with was lost at its first plan, so hunters that saw the player in one step replanned together every 0.6 s, as every hunter does when the player moves 1.5 m: up to 18 searches in one step. With 40 creatures (mobs:6, M2 Pro) a step's p99 falls from 4.5 ms to 1.15, with as many searches a second. - Fixed: a
Wanderer whose last walk was blocked dropped every later goal at once and stood still.Mob.pathBlockedbelonged to the last plan, which is made after the behaviours run, soWanderread the verdict on the previous goal.walkTonow clears it for a goal more thanMob.replanDistance(1.5 m, the distance that already triggered a replan) from the planned one, until that goal is planned. - A walking mob replans its path every
Mob.replanEvery(0.6 s), sooner (never beforeMob.replanSoonest, 0.2 s) only when its goal moved 1.5 m, from a random phase per mob; it used to replan every step once its path ran out, which an unreachable goal makes happen every step.Mob.pathsPlannedcounts its searches. With 40 creatures (mobs:6, M2 Pro) a step costs 0.27 ms instead of 7.8 at the median, and the frame rate goes from 84 to 93 fps. FrameReport.stepMs: a fixed step's own cost (each tick's simulation time over the steps it ran), in the JSON asstepMs.simMsgrows with the steps a slow frame banks, so on a device that is behind it readsFixedStepLoop's cap, not the simulation.GameWorld.isLoadedandgroundHeightread the chunk throughChunkStreamer.chunkAtXZ. With the engine's faster block queries, 40 creatures (mobs:6, M2 Pro at 120 Hz) run at 91 fps instead of 50, and the simulation's p99 falls from 66 ms to 11.
0.1.1-dev #
- Requires Flutter 3.47.1, the first with a runner setting that turns Flutter GPU on for
Windows and Linux. The README has the setting for every platform, and why the web is
not one (worker isolates, TCP sockets and save files need
dart:isolateanddart:io). The example's Windows and Linux runners turn it on. FrameStats(VoxelGame.stats): what the frames cost. Anfpsreadout, and betweenstartRecordingandstopRecordingevery sample: Flutter's presented frames (interval, UI build, raster), the simulation (VoxelGame.frame), the scene's encoding and how long after it the GPU finished each scene frame (gpuLatencyMs: a latency, queue included, not the GPU's cost).FrameReportsummarises them (percentiles, hitches). The scene is aMeasuredScene, which times its own encoding and the GPU's completion.VoxelGameSpec.copyWith;GameWorld.chunksBuiltandGameWorld.facesEmitted.GraphicsSpec(VoxelGameSpec.graphics):renderScale,maxPixelRatio,antiAliasingand aShadowSpec(cascades, resolution, distance, the sun's step).GraphicsSpec.desktopis the look as it was;GraphicsSpec.phonedraws at most 1.5 pixels a point, with FXAA and two 1024² cascades over 48 m, and is what a spec withoutgraphicsgets on iOS and Android.- The camera's far plane ends where the fog is full (
VoxelGame.viewDistance, plus a chunk) instead of 800 m, so the loaded chunks past the fog are culled, not drawn.
0.1.0-dev #
First version.
VoxelGameSpec+runVoxelGame: a playable voxel game from one declaration.- Mining, placing, an inventory and crafting screen, a HUD, first and third person.
- Keyboard and mouse, gamepad and touch controls.
InputMapreads a finger as a gesture, not as a mouse button: one that lifts where it landed presses the primary or the secondary button (touchTapPrimary, the game's own "is a creature under the crosshair" bit), one that stays put formineDelayholds the primary one until it lifts, and one that travels pasttapSlopis the look and presses nothing.touchMove,setTouchHeldandtouchDigitare the on-screen controls' half, read through the samedown/justPressed/axis(..., touch:)/digitPressed()a key or a pad goes through.VoxelGameWidgetforwardsonPointerCancel, so a pointer the system takes away is dropped instead of left down.- A one-shot press waits for the step that reads it. A frame that runs no simulation step
no longer drains them, which above 60 fps threw away about half of every player's taps
and clicks; and
VoxelGame.stepis now the single reader of the bag and pause buttons, so one press can no longer close a screen in one place and open another somewhere else. - Mobs from
MobSpec: rigs, gaits, drops, spawn rules and a brain of goals. Goal/GoalSelector: the goal system, usable with any creature class.- Day and night sky, sounds, circuits (
SignalSpec), liquids. - Save slots, and hosting or joining a multiplayer world.
- Hooks:
onBlockBroken,onBlockPlaced,onMobKilled,onTick,GameSystem. - The package description and the dartdoc say what the kit does, not which game it was measured against: "a voxel sandbox in a few lines". The library's header names the three packages it sits on as they are called today.