More video optimizations. Font rendering and GUI optimizations. Logo updates. Manual updates. Lots of stuff!
This commit is contained in:
parent
25aa909c3d
commit
67e2a1ddcd
138 changed files with 7339 additions and 2534 deletions
477
CHANGELOG
477
CHANGELOG
|
|
@ -9,6 +9,53 @@ SINGE 3.00
|
|||
API Changes
|
||||
-----------
|
||||
|
||||
- controls.cfg, settings.cfg and gamecontrollerdb.txt are read game first.
|
||||
The four places are the same, but the file beside the game's script --
|
||||
what its author shipped -- is now read first, then the machine's own in
|
||||
the directory Singe was started in, then the one above the data directory,
|
||||
then the game's data directory, and a later file still wins. Before, the
|
||||
game's file came last and beat the machine's, so a handheld could not keep
|
||||
a game from putting its test mode on a face button. A machine file only
|
||||
replaces what it names: INPUT_TEST_MODE = { SCANCODE.F2 } takes test mode
|
||||
off whatever button a game gave it.
|
||||
|
||||
- gamecontrollerdb.txt now beats SDL_GAMECONTROLLERCONFIG. Systems that set
|
||||
that variable for their built-in pad -- ArkOS does, with a mapping for the
|
||||
R36S that leaves Select and Start out -- used to win over any file, so no
|
||||
game ever saw those buttons. The files the first game finds are added to
|
||||
the variable before SDL starts, where a later line for a pad replaces an
|
||||
earlier one. With --program, a pad button its mapping leaves out is traced
|
||||
with its raw number, so a gamecontrollerdb.txt line can name it.
|
||||
|
||||
- A controls.cfg input can be a chord: { GAMEPAD_0.BUTTON_BACK,
|
||||
GAMEPAD_0.BUTTON_START } as one entry of a mapping fires when the buttons
|
||||
are held together. Each button keeps its own meaning as well.
|
||||
|
||||
- fontPrint keeps the strings it renders, keyed by font, quality, colours and
|
||||
text, so printing the same text every frame costs about what drawing a
|
||||
sprite of it does. Rendering text with fontToSprite for performance is
|
||||
deprecated; it still works, and is still the way to get text that can be
|
||||
measured, scaled or turned. Strings not printed for a second are dropped,
|
||||
and a font's go when it is unloaded.
|
||||
|
||||
- GUIs are redrawn only when their picture changes. When a frame's document
|
||||
draws exactly what the last frame drew and nothing new was made in between,
|
||||
the texture already holds the picture and is kept; changes through RML,
|
||||
RCSS, the RmlUi Lua interface and the gui* calls are all seen.
|
||||
guiRefresh(gui) forces a redraw, as a way out should a change ever be
|
||||
missed; a game should not need it. guiGetStats reports zeros on a frame
|
||||
where nothing was redrawn.
|
||||
|
||||
- videoDraw has the GPU draw a video that is not scaled or turned by
|
||||
videoScale or videoRotate, when it lands on the overlay where nothing has
|
||||
been drawn since an overlayClear to a transparent colour: under the
|
||||
overlay, where it looks the same as the frame copied in and costs next to
|
||||
nothing. Such a frame is scaled to the screen the way the overlay is,
|
||||
rather than nearest-neighbour into the overlay first. Loaded videos now
|
||||
decode the way the disc does, including zero-copy, and are converted to
|
||||
RGB on the CPU only when a CPU draw needs the pixels, so draw a video
|
||||
before anything that goes over it rather than after.
|
||||
|
||||
- Singe can update itself. Once a day the menu asks the master service
|
||||
whether a newer release exists for this machine's platform, and says so in
|
||||
a line under the game's details; nothing is fetched until a person opens
|
||||
|
|
@ -89,7 +136,7 @@ API Changes
|
|||
virtual clock like everything else, each frame lasting the time it is
|
||||
worth, and the script runs every step. A bare os.time() answers
|
||||
with the virtual clock too, so a game that seeds its generator from
|
||||
the wall clock when a game starts (the one-player Space Pirates)
|
||||
the wall clock when a game starts (a one-player game does)
|
||||
repeats; os.time with a date table is Lua's own.
|
||||
|
||||
- fontSelect given no font (nil, or an index that was never loaded)
|
||||
|
|
@ -612,7 +659,7 @@ API Changes
|
|||
packed games can be modded by dropping files beside them and files a
|
||||
game writes land under data/<game>/files. Inside a database names
|
||||
are matched without regard to case and a leading own-directory prefix
|
||||
("DLe/Cfg/x") is ignored. singe --unpack GAME.game DIRECTORY restores
|
||||
("MyGame/Cfg/x") is ignored. singe --unpack GAME.game DIRECTORY restores
|
||||
the files and singe --patch GAME.game SOURCE replaces files from a
|
||||
directory or a patch database in one transaction. --entry=N picks
|
||||
which games.dat entry a .game launch runs. Loose games are
|
||||
|
|
@ -733,7 +780,7 @@ API Changes
|
|||
that layout; its saves now belong under <base>/<name>/Game/ and must be
|
||||
moved by hand. Archives whose script sits at the root are unaffected.
|
||||
|
||||
- Packed games: a path naming the .game file itself (Games/DLe.game,
|
||||
- Packed games: a path naming the .game file itself (Games/MyGame.game,
|
||||
with or without a trailing separator) addresses the database root, as
|
||||
"." does from inside: lfs.dir lists it and lfs.attributes reports a
|
||||
directory, while io.open on it returns nil and lfs.mkdir and lfs.rmdir
|
||||
|
|
@ -1032,6 +1079,82 @@ API Changes
|
|||
New Features
|
||||
------------
|
||||
|
||||
- An OpenGL ES 3.1 backend for the 3D scene and the GUI. SDL_GPU has
|
||||
Vulkan, Direct3D 12 and Metal and no OpenGL of any kind, which left the
|
||||
cheap Linux handhelds and the Raspberry Pi with 2D only: their Mali and
|
||||
VideoCore parts have a mature GLES driver and either no Vulkan or an
|
||||
immature one. The subset of SDL_GPU the scene and the GUI use is now
|
||||
behind one table of function pointers with two implementations, so both
|
||||
files stay the single description of what Singe draws, and the backend is
|
||||
chosen at run time -- one aarch64 binary serves a board with Vulkan and one
|
||||
without. The ES shaders come from the same HLSL through SPIRV-Cross, and
|
||||
the GL entry points are loaded through SDL rather than linked, so a machine
|
||||
with no GLES driver simply keeps its 2D renderer. The Raspberry Pi 4
|
||||
remains the minimum for 3D: the Pi 3 has only OpenGL ES 2.0, which cannot
|
||||
express the scene's skinning or morph targets.
|
||||
|
||||
- Singe runs on the R36S under ArkOS, and on the Rockchip handhelds like it.
|
||||
On KMSDRM the OpenGL ES context is asked for before the window is made and
|
||||
SDL's GLES renderer is chosen first, where SDL used to ask EGL for desktop
|
||||
OpenGL and fail to make a window. The scene's instance data goes through
|
||||
texture buffers, since Mali has no storage buffers in vertex shaders;
|
||||
uniforms are written without waiting for the GPU, uploads orphan their
|
||||
buffers, and targets are invalidated when a frame is done with them, which
|
||||
is what a tiling GPU needs to keep from copying.
|
||||
|
||||
- Compiled GLES shader programs are kept in the Singe folder of the data
|
||||
directory and loaded from there next time, and the programs a scene will
|
||||
need are built at the start rather than on the frame that first uses them,
|
||||
so the first seconds of a game no longer stutter on a small GPU.
|
||||
|
||||
- --scenequality low draws the 3D scene at half resolution, without
|
||||
antialiasing or bloom, for small GPUs.
|
||||
|
||||
- --zerocopy (on by default) hands hardware decoded frames to the GPU as
|
||||
dma-bufs instead of copying them: Rockchip's MPP on the handhelds, VAAPI on
|
||||
desktops, imported on the device the picture is drawn on. Where the GPU or
|
||||
the decoder cannot, frames are copied as before. Rockchip's decoder now
|
||||
hands out cached buffers, so frames that are copied are read far faster.
|
||||
|
||||
- --quitchord=BUTTONS names gamepad buttons that quit when held together,
|
||||
whatever the game maps and even when it has turned its quit key off: a game
|
||||
quits back to the menu it came from, and the menu quits Singe. A press of
|
||||
one of the chord's buttons waits a quarter of a second for the rest before
|
||||
it does what it normally does, so Start does not launch a game from the
|
||||
menu on the way to quitting it.
|
||||
|
||||
- SIGTERM and SIGINT end all of Singe: the running game shuts down as usual,
|
||||
and the menu it came from does not come back. A second signal while that
|
||||
is under way ends the process at once.
|
||||
|
||||
- --program traces where each frame's time went, every five seconds: per
|
||||
loop, the disc, the script, the scene, the GUIs, the overlay upload and the
|
||||
present, with the script split into its Lua and the engine calls it made,
|
||||
costliest first, and a line for each video's decoding. --perfsync makes
|
||||
each stage wait for the GPU, so its time shows up in the stage that caused
|
||||
it rather than all in present. The manual explains the lines.
|
||||
|
||||
- A crash prints a backtrace, on Linux and macOS, to the console and to
|
||||
trace.txt, so a report can say where it happened and trace.txt alone is a
|
||||
whole report.
|
||||
|
||||
- A resolution larger than the display is fitted to it instead of refused.
|
||||
|
||||
- Videos loaded with videoLoad build their frame index in the background, so
|
||||
loading one no longer holds the frame. The audio queue is longer, which
|
||||
stops the breakup a slow machine suffered while a video decoded.
|
||||
|
||||
- A damaged packet in a video is skipped instead of ending the game; a stream
|
||||
that keeps failing still does.
|
||||
|
||||
- Printing and clearing the overlay cost far less. overlayClear refills only
|
||||
what was drawn since the last clear, when the colour is the same, and the
|
||||
overlay is sent to the GPU only where it changed. On the R36S, Dragon's
|
||||
Lair Enhanced went from 31-38 frames a second to the display's full rate.
|
||||
|
||||
- The menu no longer needs its background video as the disc; Menu.sh passes
|
||||
its own arguments on to Singe.
|
||||
|
||||
- Forge can judge a shot by the picture rather than by a hitbox. lightSensor
|
||||
is a behaviour of the framework now, the way a home laserdisc gun works: a
|
||||
spot on the picture flashes on the frames a target is shown, and a shot is
|
||||
|
|
@ -1104,8 +1227,8 @@ New Features
|
|||
stop, from an entity or a list of them in turn, or at a point; and
|
||||
soundDone says when a clip playSound or a sound behaviour started has
|
||||
ended. Ports of the test library began (FORGE.md section 14): the
|
||||
five ActionMax games are Forge descriptions written by
|
||||
util/forgePortActionMax.py, and play the same as the emulator under
|
||||
five light gun games of the VHS era are Forge descriptions written by
|
||||
a porting script, and play the same as the emulator under
|
||||
the same inputs (testScripts/ports/). On the way: a lightSensor
|
||||
behaviour, a pointer behaviour, fonts and anchors on the text look,
|
||||
sprites drawn in copies, discPlay and discPause, pointerX and pointerY
|
||||
|
|
@ -1116,17 +1239,14 @@ New Features
|
|||
conditions, actions, layers, or events to the manifest for that game
|
||||
alone; the editor loads it when the game opens and forgets it when
|
||||
the game closes, the compiler checks against it, and the built game
|
||||
carries it into a release. The ActionMax light sensor lives in one
|
||||
now, beside those five games, and not in Forge. The Hypseus library's
|
||||
eleven Karis Framework 3.32b titles (Arcade Xperience 1 to 3, Astroboy,
|
||||
Danmachi, Freddy, Starship Troopers, Sugar Rush, Survival, Esh's
|
||||
Aurunmilla, and Badlands Lite) are ported by the same converter, which
|
||||
carries it into a release. Their light sensor lives in one now,
|
||||
beside those five games, and not in Forge. The Hypseus library's
|
||||
eleven Karis Framework 3.32b titles are ported by the same converter, which
|
||||
finds the framework where a game keeps it and notes its version; the
|
||||
rest of that library is surveyed in FORGE.md 14.9. A branch on the
|
||||
branching track can want its move mashed (mash = 6) or held (hold =
|
||||
20). The seven Kimmy Script Engine titles (Brain Dead 13, Future Boy
|
||||
Conan, Mazinga Z, Fuma Conspiracy, Sonic 1996, Sintel, Ninja Hayate
|
||||
1080) are ported by the same converter and a kimmy behaviour that
|
||||
20). The seven Kimmy Script Engine titles are ported by the same
|
||||
converter and a kimmy behaviour that
|
||||
plays that engine's loop, and play as their originals do (FORGE.md
|
||||
14.11).
|
||||
util/forgePorts.py
|
||||
|
|
@ -1270,12 +1390,13 @@ New Features
|
|||
- The menu's intro is drawn, not played, and runs straight into the menu.
|
||||
A charge goes off, the Singe dragon and name fly out of it toward the
|
||||
camera with the dragon's wings beating, the tagline fades in, the dragon
|
||||
rears and breathes flame -- and then the grid the menu sits on comes up
|
||||
out of the dark beneath the logo while the smoke is still clearing, the
|
||||
logo lifts away, and the menu takes the screen. The dragon settles
|
||||
centred on the sun rather than above it, with the name below crossing the
|
||||
horizon, so the sun is a disc behind the animal instead of a rim around
|
||||
the pair of them. It is one scene from the
|
||||
rears and roars with its mouth open, then rears again and breathes flame
|
||||
-- and then the grid the menu sits on comes up out of the dark beneath
|
||||
the logo while the smoke is still clearing, the logo lifts away, and the
|
||||
menu takes the screen. The dragon faces left and settles centred on the
|
||||
sun rather than above it, with the name below crossing the horizon, so the
|
||||
sun is a disc behind the animal instead of a rim around the pair of
|
||||
them. It is one scene from the
|
||||
first frame to the last: there is no cut and nothing loads at the join,
|
||||
and the menu takes input when the logo has gone rather than when a video
|
||||
frame number passes. Returning from a game starts the same timeline past
|
||||
|
|
@ -1394,7 +1515,9 @@ New Features
|
|||
the top of the neck; before, every part's node sat at the model's origin
|
||||
and turning a wing swung it around the middle of the animal. The model
|
||||
looks identical -- no part moves by so much as a thousandth -- but it can
|
||||
now be animated without rigging it again in script each time.
|
||||
now be animated without rigging it again in script each time. The lower
|
||||
jaw is a part of its own, hinged at the back of the mouth, so the mouth
|
||||
can open; the printable variants keep it in the head.
|
||||
|
||||
- The manual's 3D Scenes, Physics and Particles chapters are rewritten as
|
||||
sections in a learning order with worked examples, every reference
|
||||
|
|
@ -1482,28 +1605,6 @@ New Features
|
|||
Fixes
|
||||
-----
|
||||
|
||||
- Two things on the engine update's Windows path. Windows cannot replace a
|
||||
running image, so the update starts the new Singe beside this one and lets
|
||||
this one end; but with a console -- the default -- this one then sat in
|
||||
getchar() waiting for a key, so two engines were alive at once and the old
|
||||
one held the console the new one was writing to. The key wait now happens
|
||||
only when nothing took over, and still happens when the start failed, since
|
||||
that message is worth reading. And setting the running binary aside as
|
||||
old-<name> ignored whether the rename worked. It usually does: renaming a
|
||||
running executable is allowed on Windows as it is everywhere, as long as the
|
||||
new name is on the same volume, and only deleting one is refused. It can
|
||||
still be beaten by a folder the user cannot write or by a scanner holding the
|
||||
file, and that was silent. The tool now says so, on screen and in the trace,
|
||||
and carries on: the launcher runs the newest Singe in the folder, so the old
|
||||
binary left in place is harmless.
|
||||
|
||||
- The menu's grid, horizon and sun were drawn black and unlit through the
|
||||
intro, which kept the lights off them but not the blast's glow behind
|
||||
them: a black line across a flash is a line, and the whole grid showed
|
||||
through the explosion as its outline. They are not drawn at all until
|
||||
the grid comes up out of the dark. The recording of the backdrop is
|
||||
re-made with it.
|
||||
|
||||
- A PNG with 16-bit samples and no alpha channel (crate.png in the test
|
||||
scripts is one) drew with coloured fringes: SDL_image put libpng's
|
||||
big-endian 48-bit pixels into a surface the machine reads
|
||||
|
|
@ -1512,7 +1613,7 @@ Fixes
|
|||
|
||||
- musicPlay answers the handle it was given, as Hypseus's does. Its
|
||||
games keep that (sndHandle = musicPlay(music)) to stop the piece, and
|
||||
three of them (Freddy, Starship Troopers, Sugar Rush) died on the map
|
||||
three of them died on the map
|
||||
screen with "musicStop: Argument 1 must be a number" because Singe's
|
||||
answered nothing. musicStop takes a nil handle as none.
|
||||
|
||||
|
|
@ -1525,48 +1626,12 @@ Fixes
|
|||
gun dead with only "No mice detected" to explain it. Only --manymouse
|
||||
needs those devices now, and the message names the group.
|
||||
|
||||
- A game whose art is missing shows that it is missing, rather than taking
|
||||
the engine down with it. An empty or wrong decorator path could open
|
||||
something that is not a file, report a nonsense length, and throw
|
||||
std::bad_alloc out of the GUI's texture loader -- a typo in a games.dat
|
||||
ended the program. The loader now refuses an impossible size, and both
|
||||
menu renderers fall back to Singe/missing.png, drawn at build time, so a
|
||||
bad path reads as "MISSING art" instead of a hole in the page.
|
||||
|
||||
- GUIs and 2D particles no longer blink. guiDraw and emitterDraw are
|
||||
documented as lasting one frame, meaning one onOverlayUpdate -- but their
|
||||
queues were emptied after every rendered frame, and the script's turn is
|
||||
throttled to FRAME_TICK_MS while the display refreshes as often as it
|
||||
can. With a 3D scene enabled it refreshes continuously, so the GUIs and
|
||||
the 2D particles were drawn only on the frames the script happened to run
|
||||
on: in the menu, a game list flickering over a grid that did not. The
|
||||
queues are emptied when the script gets its turn instead.
|
||||
|
||||
- The menu shows packed games properly. A .game is a database the engine
|
||||
presents through the VFS, so lfs.attributes reports it as a directory,
|
||||
not a file -- and the menu tested the mode before the extension, so every
|
||||
container went down the loose-directory branch and its games.dat was
|
||||
loaded with no container recorded. Every CABINET, MARQUEE and ATTRACT
|
||||
path in it then named a file that exists only inside the database: a
|
||||
library of .game files showed no art at all and died with "Unable to
|
||||
open" on the first attract video. The extension is tested first now.
|
||||
Games packed with earlier releases are correct and do not need repacking.
|
||||
|
||||
- A lone .m2v played with --framefile now finds the .ogg beside it, as a
|
||||
framefile's segments always have. A Daphne era elementary stream
|
||||
carries no audio of its own, so lair.m2v played silently unless it was
|
||||
carries no audio of its own, so such a .m2v played silently unless it was
|
||||
listed in a framefile or --altaudio was given. Any other container
|
||||
keeps using the audio inside it.
|
||||
|
||||
- A single-frame image saved as a 1-bit PNG (many overlay assets from
|
||||
the 2.x era) loads again; spriteLoad reported "Blit combination not
|
||||
supported" on it. Images with 16 bits per channel or floating point
|
||||
pixels (16-bit PNGs among them) load as sprites, animation frames and
|
||||
particle textures; they ended the program.
|
||||
|
||||
- lfs.dir(".") and other names for the root work inside a packed game;
|
||||
they were refused as if the directory did not exist.
|
||||
|
||||
- --sindengun and --scalefactor are listed in --help and the manual;
|
||||
they were hidden because mouse positions ignored the Sinden border and
|
||||
the overscan shrink. Positions now map into the picture actually
|
||||
|
|
@ -1635,96 +1700,15 @@ Fixes
|
|||
copy differs from the running build. Upgrading the binary used to keep
|
||||
the old Framework.singe and Menu.singe forever.
|
||||
|
||||
- Lua reads a packed game where it lies. Its file handle is a C FILE * and
|
||||
a database row is not a file, so anything a script opened by name inside
|
||||
a .game was written out to data/<game>/cache first and the copy opened
|
||||
instead -- a second copy of the game on disk, made a file at a time.
|
||||
Everything else in the engine had long since stopped needing that: the
|
||||
sprite loader, the decoders and the GUI's file interface all read a row
|
||||
as a stream. Only Lua could not, because the io library is not ours.
|
||||
It is patched now rather than worked around: io.open, io.lines and
|
||||
io.input hand back a handle of Singe's own for a name that lives only in
|
||||
the database, and it reads through the same stream everything else uses.
|
||||
It answers to every method and format Lua's does, io.type calls it a
|
||||
file, io.read and io.close take it, and it closes itself on collection or
|
||||
on leaving a <close> variable's scope; writing to it is an error, which
|
||||
is what a packed asset is. testScripts/packedIo holds all of that to
|
||||
Lua's own behaviour. With the last reader of the cache gone, the cache
|
||||
itself is gone: vfsFilePath answers with a path only when there is a
|
||||
real file, and a packed name being read is asked for as a stream.
|
||||
|
||||
- The menu no longer unpacks a library's artwork to look at it. It asked
|
||||
whether each picture was there by opening it, and io.open has to hand Lua
|
||||
a file the C library can read, so every cabinet and marquee in a packed
|
||||
library was written out to data/<game>/cache first -- none of which was
|
||||
ever opened that way: spriteLoad and the document's texture loader both
|
||||
read straight out of the database. The question is asked of the index
|
||||
now, through lfs.attributes, which opens nothing. The cache remains for
|
||||
what it is for: a game's own script opening its own files by name.
|
||||
|
||||
- A packed game writes to one data directory. Everything that named a file
|
||||
inside a .game by a path that passes through it -- a script opening one
|
||||
of its own videos, which is how the menu loads an attract clip -- got a
|
||||
data directory named after the container including the extension, while
|
||||
the container itself, and everything else the same game wrote, used the
|
||||
name without it. A library of packed games therefore grew two data
|
||||
directories each: the video indexes in one and the saves, the overlay
|
||||
and the cache in the other. Any component naming a database is reduced
|
||||
to its stem now, so both land in the same place. An installation that
|
||||
has both may delete the ".game" one; what was in it is rebuilt.
|
||||
|
||||
- Both menus scroll their details. When a games.dat entry has more to say
|
||||
than fits, the text walks down by itself a line at a time, rests, and
|
||||
walks back up to the start rather than jumping there: up and down belong
|
||||
to the library, and a cabinet has no key to spare for scrolling a
|
||||
paragraph. The document renderer's description box had a scroll bar the
|
||||
style sheet gave it and nothing to drive it with; the overlay renderer
|
||||
clipped and left it at that. Page up and page down override the walk for
|
||||
whoever does have a keyboard, and it carries on from where they leave it.
|
||||
Both menus say so along the bottom.
|
||||
|
||||
- The overlay menu -- the one a machine with no GPU gets -- shows what a
|
||||
games.dat says about a game. Its details had the attract video's column
|
||||
and whatever height was left under it, which at 720x480 was four lines,
|
||||
so the description used most of them and every fact about the game fell
|
||||
off the bottom. They run the full width now, under the artwork, with the
|
||||
short fields grouped onto shared lines the way the document renderer
|
||||
groups them and the description last rather than first. Cabinet and
|
||||
marquee artwork is scaled to fit its region as well: spriteDraw blits at
|
||||
the picture's own size, so art larger than the space allowed for it used
|
||||
to be drawn straight over whatever was beside it.
|
||||
|
||||
- The overlay menu says which key does what, along the bottom, as the
|
||||
document menu always has. There was no way to learn that up and down
|
||||
move one game and left and right move a page except to be told.
|
||||
|
||||
- Left and right move a third of the library, or a screenful, whichever is
|
||||
smaller. A page in a library of nine games was ten, so every press
|
||||
landed on the first game or the last and nothing between them could be
|
||||
reached that way at all.
|
||||
|
||||
- The menu's Start button is gone. Nothing but a mouse could ever reach it
|
||||
-- the document takes no keyboard focus, and a cabinet has no pointer --
|
||||
and the line beside it already says which button starts a game. Clicking
|
||||
a game's row still starts it.
|
||||
|
||||
- The menu background video carries a keyframe every half second, and the
|
||||
first frame of its looping section is one of them, so the menu's jumps
|
||||
into and around it are instant. It has no audio track and is compressed
|
||||
harder than the clips it replaced -- flat colour with hard edges is easy
|
||||
to encode -- so what was 26 MB of source clips and 1.3 MB inside the
|
||||
binary is now 0.9 MB of picture and 1.1 MB of sound.
|
||||
|
||||
- Framefiles whose first line is a relative directory produced garbage
|
||||
video paths (the directory string was freed before use); a lone "." now
|
||||
means the framefile's own directory.
|
||||
|
||||
- A crash prints a backtrace to the console (Linux and macOS) so a report
|
||||
can say where it happened; run with --program and send trace.txt too.
|
||||
|
||||
- Mouse positions are delivered in the video's coordinates again (SDL3
|
||||
stopped scaling them for us), so crosshairs follow the pointer.
|
||||
|
||||
- os.clock() in scripts now returns wall time rather than processor time.
|
||||
Nearly every existing game times its input debounces and prompts with
|
||||
it; once the GPU decodes the video the engine mostly sleeps, and those
|
||||
|
|
@ -1736,10 +1720,6 @@ Fixes
|
|||
press in the script that follows. Confirming Exit in a game with
|
||||
button 1 used to relaunch it from the menu.
|
||||
|
||||
- Audio ran late by the device queue depth on most Linux audio servers:
|
||||
the clock assumed one mixer buffer of latency where PulseAudio and
|
||||
PipeWire hold several. See the audio sync entry above.
|
||||
|
||||
- Windows builds now carry the icon and version resource, with the version
|
||||
number encoded correctly.
|
||||
|
||||
|
|
@ -1772,167 +1752,23 @@ Fixes
|
|||
discGetFrame reports, so a skip in any segment after the first lands
|
||||
where it should.
|
||||
|
||||
- Every collision and trigger event of a frame reaches onCollision and
|
||||
onTrigger; only the first 64 used to, the rest were dropped.
|
||||
onTrigger fires once per body and trigger pair even when a mesh or
|
||||
compound shape touches in several places. playerJump is granted
|
||||
whenever the player is on the ground.
|
||||
|
||||
- playerSetEnabled(node, false) removes the player's body from the
|
||||
world and reports it leaving every trigger it stood in; enabling puts
|
||||
it back. navPath ends a partial path at the last reachable point.
|
||||
navLoad rejects data that is not a Detour mesh, or a file shorter
|
||||
than the mesh its header describes ("the navigation mesh file is
|
||||
truncated or damaged"). The world holds at most 4096 bodies; bodyNew
|
||||
raises an error when it is full.
|
||||
|
||||
- A body, player, ragdoll, soft body or vehicle whose node was deleted
|
||||
is released at the start of the next frame, disabled, inactive, static
|
||||
or paused or not; a disabled player or an inactive ragdoll on a deleted
|
||||
node kept its slot (16 players, 8 ragdolls) forever. playerExists,
|
||||
softExists, ragdollExists, vehicleExists and every setter answer false
|
||||
for a node handle that was deleted and reused instead of driving the
|
||||
previous owner's record. bodySetTrigger(node, false) no longer
|
||||
swallows the leave of a trigger the node is standing in.
|
||||
physicsSetDebug's mask is cleared by a reload.
|
||||
|
||||
- Sprite nodes are no longer culled early at the edge of the view, and
|
||||
shadow fitting sees their real size. Soft particles fade correctly in
|
||||
a scene with no meshes instead of against stale depth. Point-light
|
||||
shadows update when a caster's mesh is rewritten (soft bodies), given
|
||||
morph targets, or swapped with nodeSetMesh at an unchanged transform;
|
||||
the six cached faces could freeze in the old shape. Cascaded sun
|
||||
shadows appear in render-to-texture views (viewNew) instead of
|
||||
vanishing; a view takes the finest cascade that covers each point.
|
||||
Debug lines no longer pile up while the scene layer is enabled but has
|
||||
no target.
|
||||
|
||||
- modelLoad refuses a skin with more than 128 joints or a skinned mesh
|
||||
naming a joint numbered 128 or more (the shader used to read past the
|
||||
matrices); a joint beyond the skins that use the mesh is traced and the
|
||||
load goes on, so a mesh shared by skins of different sizes loads. A
|
||||
texture that cannot be decoded, or a missing KTX2 image, is traced and
|
||||
the load goes on without it instead of leaving modelLastError set.
|
||||
KHR_materials_emissive_strength is applied. Sparse accessors load,
|
||||
animation keyframes included, so Blender shape keys are no longer
|
||||
silently zero; a sampler that cannot be read is traced and skipped,
|
||||
and a rotation channel with a malformed output no longer produces NaN
|
||||
transforms. Alpha MASK materials, textures on TEXCOORD_1 and up, and
|
||||
KHR_texture_transform are traced as unsupported.
|
||||
|
||||
- After a script reload, or any stall longer than a tenth of a second,
|
||||
model animations advance by at most a tenth of a second instead of the
|
||||
whole gap; a non-looping clip used to be finished before its first
|
||||
frame. A 3D emitter whose node is deleted is removed even when the
|
||||
handle is reused by a later nodeNew; it used to jump to the new node.
|
||||
|
||||
- Video index files are named <name>-<hash>.index, the hash from the
|
||||
video's full name, so two videos with one base name in a game stop
|
||||
rebuilding each other's index. Old <name>.index files are ignored and
|
||||
can be deleted.
|
||||
|
||||
- The packer reports every forbidden file in a directory rather than
|
||||
stopping at the first. --unpack refuses a database whose stored names
|
||||
would land outside the target directory. Launching a .game database
|
||||
from the command line no longer crashes.
|
||||
|
||||
- --stretch hands the script mouse positions in the game's coordinates
|
||||
at any window size. --scalefactor with --sindengun shrinks the picture
|
||||
inside the Sinden border instead of being cancelled by it. A negative
|
||||
--audio track number is ignored. With no --datadir the controls.cfg
|
||||
beside the script is read once, not twice.
|
||||
|
||||
- A reload (F5, --reload, singeReload) ignores keys and buttons held
|
||||
across it until they are released, resets the effect channels and their
|
||||
pending onSoundCompleted queue, the held switches and the 2D particle
|
||||
textures, restores the effects volume, mouse mode, mouse enabled state
|
||||
and mouse capture, parks the disc on frame 1 paused, and no longer
|
||||
calls into the closed Lua state when it happens while paused, so the
|
||||
new script starts exactly as a fresh launch does. Under --reload a
|
||||
script that fails to compile stays watched, so saving the fix triggers
|
||||
the reload.
|
||||
|
||||
- Quitting with a stick past its dead zone no longer crashes; the
|
||||
release reaches onInputReleased after onShutdown, before the state
|
||||
closes. Plugging in a gamepad no longer resets the positions
|
||||
mouseGetPosition reports, and a controls.cfg mapping that lists the
|
||||
same code twice fires its switch once.
|
||||
|
||||
- materialSetVideo(material) on a framefile disc keeps showing the disc
|
||||
after a segment change.
|
||||
|
||||
- In a packed game the controls.cfg at the root of the data directory
|
||||
loads again, io.open(".") returns nil with a message instead of a
|
||||
handle whose first read fails, and a directory listing where a loose
|
||||
file and a packed one differ only in case shows the loose spelling.
|
||||
|
||||
- Error messages: an invalid view, navigation mesh or navigation agent
|
||||
handle reports "No <thing> N." like every other handle; navAgentNew,
|
||||
vehicleSetGears and viewNew state their limits from the engine's
|
||||
constants; fontLoad and soundLoad of a missing file say "Unable to open
|
||||
NAME" instead of the library's "invalid parameter"; navDraw reports its
|
||||
argument count, and running out of memory in it is an error rather than
|
||||
a silent blank; scriptExecute and scriptPush that cannot create the new
|
||||
data directory abort the script with a message instead of exiting.
|
||||
materialNew, sceneGetSize and sceneGetStats no longer reject extra
|
||||
arguments.
|
||||
|
||||
- A review of the week's work (the io patch, the scheduler, Forge, the
|
||||
menu, the GLES backend, saves and the GUI) fixed what it found:
|
||||
|
||||
Lua's io on a packed asset: f:lines and io.lines with more than one
|
||||
format handed back only the last value read a step; a later format
|
||||
that comes up empty now returns what came before it with nil in its
|
||||
place, as Lua does, and only an empty first read ends the loop.
|
||||
read("a") reads in blocks rather than a byte at a time, read(0) is the
|
||||
end-of-file test it is in Lua, and a NUL byte is no longer taken for
|
||||
part of a number. testScripts/packedIo covers all of it.
|
||||
|
||||
Timer and tween handles carry a generation: a handle kept past its
|
||||
timer's end no longer cancels, or answers for, whatever timer was
|
||||
given the same slot afterwards.
|
||||
|
||||
Forge: a rule's keyHeld condition never fired from a real keyboard
|
||||
(it compared a key name against the scancodes the engine delivers);
|
||||
an entity whose look kind is unknown is drawn as a box with a message
|
||||
instead of ending the game; and the compiler loads the runtime from
|
||||
Forge/, where it lives, rather than a Singe/Author.singe that was never
|
||||
shipped. The games.dat Forge writes no longer carries a DATA key the
|
||||
engine does not read, and the manual's example loses it too.
|
||||
|
||||
The menu: a games.dat entry with no ATTRACT clip no longer stops the
|
||||
engine on either renderer, quitting with no games listed no longer
|
||||
fails in onShutdown, and a directory entry lfs cannot describe is
|
||||
skipped rather than indexed.
|
||||
|
||||
The GLES backend freed nothing when its framebuffer cache filled and
|
||||
was reset; the framebuffer objects go now. A save is written with a
|
||||
single atomic rename everywhere but Windows, so the old save stays
|
||||
whole until the new one is in place. A GUI texture decoded with a
|
||||
padded pitch is packed row by row instead of uploaded with its padding
|
||||
read as pixels. The menu's backdrop stops rewriting its grid colours
|
||||
every frame once the grid is lit.
|
||||
|
||||
- navAgentNew placed an agent from the node's world position as of the
|
||||
last drawn frame, so an agent made before the first frame -- or in a
|
||||
game that never draws the scene, which is what a 2D room walking a
|
||||
navigation mesh is -- started at the origin, off the mesh, and never
|
||||
moved. The transforms are refreshed first now, as navAddNode already
|
||||
did.
|
||||
|
||||
Net.singe verified a connection against Linux's CA list by path, so
|
||||
without a pin every HTTPS request failed on Windows, macOS and the
|
||||
handhelds. The engine now extracts Mozilla's CA list as
|
||||
Singe/cacerts.pem with the other support files and Net.singe uses it
|
||||
wherever the platform's own list is missing. A chunked response --
|
||||
what an HTTP/1.1 server sends when it does not know the length -- is
|
||||
unwrapped as it arrives rather than handed to the caller with the chunk
|
||||
sizes still in it. And a mistyped value in a table given to scriptPush
|
||||
or scriptExecute is reported like any other bad API argument -- the
|
||||
script line and the call named -- rather than as a bare exit message.
|
||||
|
||||
|
||||
|
||||
SINGE 2.10
|
||||
==========
|
||||
|
||||
|
|
@ -1942,27 +1778,6 @@ SINGE 2.10
|
|||
New Features
|
||||
------------
|
||||
|
||||
- Texture samplers now walk the whole mipmap chain. SDL hands max_lod
|
||||
straight to Vulkan's maxLod and Direct3D 12's MaxLOD, and the scene
|
||||
and GUI left it at the zero a memset gives, which clamps every lookup
|
||||
to mip level 0. Mipmapping was therefore off on every backend:
|
||||
distant surfaces aliased, the 8x anisotropy did far less than it
|
||||
looked, and the sky cube could not pick a level by roughness.
|
||||
|
||||
- An OpenGL ES 3.1 backend for the 3D scene and the GUI. SDL_GPU has
|
||||
Vulkan, Direct3D 12 and Metal and no OpenGL of any kind, which left the
|
||||
cheap Linux handhelds and the Raspberry Pi with 2D only: their Mali and
|
||||
VideoCore parts have a mature GLES driver and either no Vulkan or an
|
||||
immature one. The subset of SDL_GPU the scene and the GUI use is now
|
||||
behind one table of function pointers with two implementations, so both
|
||||
files stay the single description of what Singe draws, and the backend is
|
||||
chosen at run time -- one aarch64 binary serves a board with Vulkan and one
|
||||
without. The ES shaders come from the same HLSL through SPIRV-Cross, and
|
||||
the GL entry points are loaded through SDL rather than linked, so a machine
|
||||
with no GLES driver simply keeps its 2D renderer. The Raspberry Pi 4
|
||||
remains the minimum for 3D: the Pi 3 has only OpenGL ES 2.0, which cannot
|
||||
express the scene's skinning or morph targets.
|
||||
|
||||
- spriteDraw() now has two more forms. In addition to being able to draw
|
||||
regular sprites and stretched sprites it can now draw both using the
|
||||
sprite's center as the anchor instead of the upper left. This is highly
|
||||
|
|
@ -2197,5 +2012,5 @@ Notes
|
|||
-----
|
||||
|
||||
- Original add-on for Daphne.
|
||||
- Shipped with the ActionMax "emulator" and games.
|
||||
- Shipped with an "emulator" for a VHS game system, and its games.
|
||||
|
||||
|
|
|
|||
|
|
@ -172,6 +172,10 @@ function(singeEmbedLua path prefix)
|
|||
set(EMBEDDED_HEADERS ${EMBEDDED_HEADERS} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# assets/ holds only what is embedded in Singe. Sources those files are made from (the traced
|
||||
# artwork, OBJ and STL models, the sound samples) and anything else not embedded live in media/.
|
||||
# Forge, which ships on its own, lives in forge/.
|
||||
|
||||
# Images
|
||||
singeEmbedImage(font)
|
||||
# The icon is drawn large; the window gets a 128 pixel copy and the Windows .ico the usual sizes,
|
||||
|
|
@ -226,7 +230,7 @@ singeEmbed(${CMAKE_SOURCE_DIR}/assets/settings.cfg ${GENERATED_DIR}/settings_cfg
|
|||
singeEmbed(${CMAKE_SOURCE_DIR}/assets/Menu.singe ${GENERATED_DIR}/Menu_singe.h "")
|
||||
singeEmbed(${CMAKE_SOURCE_DIR}/assets/Tools.singe ${GENERATED_DIR}/Tools_singe.h "")
|
||||
|
||||
# Forge, the authoring tool, is built from assets/Forge but is NOT embedded: it is distributed on
|
||||
# Forge, the authoring tool, is built from forge but is NOT embedded: it is distributed on
|
||||
# its own, not with the engine. Nothing of it -- not the editor, not the compiler, not the runtime
|
||||
# a game it builds loads -- belongs inside Singe.
|
||||
singeEmbed(${CMAKE_SOURCE_DIR}/assets/Net.singe ${GENERATED_DIR}/Net_singe.h "")
|
||||
|
|
@ -326,13 +330,13 @@ if(LUA_INTERPRETER)
|
|||
add_custom_command(
|
||||
OUTPUT ${CMAKE_SOURCE_DIR}/docs/ForgeVocabulary.adoc
|
||||
COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR} ${LUA_INTERPRETER} util/forgeVocabulary.lua > ${CMAKE_SOURCE_DIR}/docs/ForgeVocabulary.adoc
|
||||
DEPENDS assets/Forge/Author.singe util/forgeVocabulary.lua
|
||||
DEPENDS forge/Author.singe util/forgeVocabulary.lua
|
||||
COMMENT "Writing the Forge vocabulary"
|
||||
)
|
||||
endif()
|
||||
# The book embeds the tutorials' pictures and includes their shipped descriptions, so any of
|
||||
# those changing renders it again; the globs are re-run on every build.
|
||||
file(GLOB_RECURSE FORGE_BOOK_PARTS CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/docs/images/tutorials/* ${CMAKE_SOURCE_DIR}/assets/Forge/tutorials/*.game)
|
||||
file(GLOB_RECURSE FORGE_BOOK_PARTS CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/docs/images/tutorials/* ${CMAKE_SOURCE_DIR}/forge/tutorials/*.game)
|
||||
add_custom_command(
|
||||
OUTPUT ${MANUAL_DIR}/Forge.pdf
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${MANUAL_DIR}
|
||||
|
|
@ -840,15 +844,23 @@ if(NOT SINGE_SHADERCROSS)
|
|||
message(FATAL_ERROR "SDL_shadercross is needed to compile the scene shaders; build through the superbuild (cmake --preset ...) or set SINGE_SHADERCROSS.")
|
||||
endif()
|
||||
endif()
|
||||
# SPIRV-Cross turns the same SPIR-V into GLSL ES for the GLES backend (PLAN.md section 56). Unlike
|
||||
# shadercross this one is not fatal when missing: the header then carries null ES strings, the GLES
|
||||
# backend declines to start, and every other platform builds exactly as before.
|
||||
# SPIRV-Cross turns the same SPIR-V into GLSL ES for the GLES backend (PLAN.md section 56). The
|
||||
# superbuild builds it beside shadercross and hands it over, so there it is fatal when missing: a
|
||||
# warning let every release binary ship with null ES strings and no GLES backend, unnoticed.
|
||||
# Configuring this project alone it is only a warning; the header then carries null ES strings,
|
||||
# the GLES backend declines to start, and every other platform builds exactly as before.
|
||||
if(SINGE_FROM_SUPERBUILD)
|
||||
if((NOT SINGE_SPIRV_CROSS) OR (NOT EXISTS ${SINGE_SPIRV_CROSS}))
|
||||
message(FATAL_ERROR "The superbuild did not supply spirv-cross (SINGE_SPIRV_CROSS='${SINGE_SPIRV_CROSS}'); without it there are no OpenGL ES shaders.")
|
||||
endif()
|
||||
else()
|
||||
if(NOT SINGE_SPIRV_CROSS)
|
||||
find_program(SINGE_SPIRV_CROSS spirv-cross)
|
||||
endif()
|
||||
if(NOT SINGE_SPIRV_CROSS)
|
||||
message(WARNING "spirv-cross was not found; this build will have no OpenGL ES shaders and so no GLES backend.")
|
||||
endif()
|
||||
endif()
|
||||
set(shaderHeader ${CMAKE_BINARY_DIR}/generated/shaders/sceneShaders.h)
|
||||
add_custom_command(
|
||||
OUTPUT ${shaderHeader}
|
||||
|
|
|
|||
12
INSTALL
12
INSTALL
|
|
@ -69,12 +69,12 @@ libibus-1.0-dev libxkbcommon-dev libx11-dev libxext-dev libxfixes-dev
|
|||
libxi-dev libxcursor-dev libxrandr-dev libxss-dev libxtst-dev
|
||||
libwayland-dev wayland-protocols libdecor-0-dev.
|
||||
|
||||
The artwork, the font and the menu video in assets/ are stored with Git
|
||||
LFS. Clone with git-lfs present, or those files arrive as small text
|
||||
pointers and the build embeds the pointers instead of the assets; a
|
||||
checkout already made without it is repaired with "git lfs install &&
|
||||
git lfs pull". build-all.sh stops with an error rather than build a
|
||||
binary around them.
|
||||
The artwork, the font, the models, and the menu video in assets/ and
|
||||
media/ are stored with Git LFS. Clone with git-lfs present, or those
|
||||
files arrive as small text pointers and the build embeds the pointers
|
||||
instead of the assets; a checkout already made without it is repaired
|
||||
with "git lfs install && git lfs pull". build-all.sh stops with an
|
||||
error rather than build a binary around them.
|
||||
|
||||
The Linux release build and the Windows build use zig as the compiler:
|
||||
the build fetches a pinned zig release (version and SHA-256 in
|
||||
|
|
|
|||
2
LICENSES
2
LICENSES
|
|
@ -74,7 +74,7 @@ Sounds
|
|||
|
||||
The menu's intro (assets/menuIntro.flac, written by util/makeMenuSound.py) is built on three
|
||||
recordings from SoundBible, each under the Creative Commons Attribution 3.0 licence
|
||||
(https://creativecommons.org/licenses/by/3.0/) and kept as FLAC under assets/samples:
|
||||
(https://creativecommons.org/licenses/by/3.0/) and kept as FLAC under media/samples:
|
||||
|
||||
Explosion Ultra Bass Mark DiAngelo CC BY 3.0 https://soundbible.com/1807-Explosion-Ultra-Bass.html
|
||||
Large Fireball Mike Koenig CC BY 3.0 https://soundbible.com/1348-Large-Fireball.html
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@
|
|||
-- The menu's backdrop: the intro and the grid the menu sits on, as one scene.
|
||||
--
|
||||
-- A charge goes off, the Singe dragon and name fly out of it toward the camera, the tagline fades
|
||||
-- in underneath, the dragon rears and breathes flame -- and then the grid comes up out of the dark
|
||||
-- in underneath, the dragon rears and roars, then rears again and breathes flame -- and then the
|
||||
-- grid comes up out of the dark
|
||||
-- beneath the logo while the smoke is still clearing, the logo lifts away, and what is left is the
|
||||
-- backdrop the menu sits on. One scene from the first frame to the last: no cut, and nothing
|
||||
-- loads at the join.
|
||||
|
|
@ -107,16 +108,27 @@ local LOGO_Y = 0.15 -- Where the logo settles. It is the dragon that
|
|||
-- worth re-measuring if either model changes.
|
||||
|
||||
-- The dragon carries no animation, but util/dragonModel.py builds it out of named parts -- head,
|
||||
-- neck, wingL, wingR and the rest -- and every one of them is its own node. They all sit at the
|
||||
-- jaw, neck, wingL, wingR and the rest -- and every one of them is its own node. They all sit at the
|
||||
-- model's origin though, so turning a wing node would swing the wing round the dragon's middle
|
||||
-- rather than its shoulder. The joints are baked into the model now; coordinates below are the
|
||||
-- model's own: it faces +X, +Y is up, and the wings reach out along +/-Z.
|
||||
local MOUTH = { 3.55, 5.60, 0.00 } -- Where the flame leaves, in model coordinates.
|
||||
local HEAD_AT = { 0.62, 5.69, 0.00 } -- The head's baked joint; see assets/DragonPivots.json.
|
||||
local MOUTH = { 2.88, 5.46, 0.00 } -- Where the flame leaves, in model coordinates: in the gap
|
||||
-- between the snout and the jaw once the jaw has dropped, back
|
||||
-- from the nose so the fire comes out of the mouth, not the nose.
|
||||
local HEAD_AT = { 0.62, 5.69, 0.00 } -- The head's baked joint; see media/DragonPivots.json.
|
||||
local FLAP_MAX = 34.0 -- Degrees a wing rises and falls.
|
||||
local FLAP_FAST = 13.0 -- Radians a second while it is flying out of the blast.
|
||||
local FLAP_SLOW = 3.4 -- and once it has settled.
|
||||
local REAR_BACK = 3.05 -- Seconds: the head starts to rear.
|
||||
local ROAR_BACK = 2.20 -- Seconds: the head starts to rear for the roar, which builds in
|
||||
-- media/samples/dragonFireBreathAndRoar.flac from here,
|
||||
local ROAR_TOP = 2.50 -- is fully back as the roar reaches its loudest,
|
||||
local ROAR_HOLD = 2.85 -- held there until the roar falls away,
|
||||
-- and down again by REAR_BACK, so the rear for the fire reads as a
|
||||
-- second one rather than as the roar carrying on.
|
||||
local ROAR_ANGLE = 24.0 -- Degrees the head tips back to roar.
|
||||
local JAW_OPEN = -30.0 -- Degrees the lower jaw drops, for the roar and for the fire.
|
||||
local NECK_SHARE = 0.35 -- How much of the head's pitch the neck takes as well.
|
||||
local REAR_BACK = 3.05 -- Seconds: the head starts to rear for the fire.
|
||||
local REAR_TOP = 3.40 -- fully back.
|
||||
local REAR_STRIKE = 3.58 -- and thrown forward.
|
||||
local BREATH_START = 3.46
|
||||
|
|
@ -124,7 +136,7 @@ local BREATH_END = 4.35
|
|||
local REAR_DONE = 4.90 -- Back to where it started.
|
||||
local REAR_ANGLE = 34.0 -- Degrees the head tips back.
|
||||
local STRIKE_ANGLE = -14.0 -- and forward through the strike.
|
||||
local DRAGON_YAW = -32.0 -- The dragon alone is turned toward the camera, so the wingbeat is
|
||||
local DRAGON_YAW = -148.0 -- The dragon faces left, turned toward the camera so the wingbeat is
|
||||
-- seen three quarters on rather than edge on; the name stays square
|
||||
-- to the camera because it has to be read.
|
||||
local TAGLINE = "SINGE Is Not a Game Emulator"
|
||||
|
|
@ -157,7 +169,7 @@ sceneSetTonemap(TONEMAP_ACES)
|
|||
|
||||
|
||||
-- The dragon's parts carry their own joints now: util/objToGlb.py bakes them from
|
||||
-- assets/DragonPivots.json, so wingL turns at the shoulder and head at the top of the neck without
|
||||
-- media/DragonPivots.json, so wingL turns at the shoulder and head at the top of the neck without
|
||||
-- anything being rigged here. This used to build them at run time from bounding boxes, which
|
||||
-- worked but left every future use of the model to re-derive the same thing.
|
||||
local function joint(root, name, parent)
|
||||
|
|
@ -281,11 +293,14 @@ nodeSetScale(dragon, 0.9)
|
|||
nodeSetScale(logo, 0.5)
|
||||
nodeSetVisible(logo, false)
|
||||
|
||||
-- The rig. The head hangs off the neck so that rearing the neck carries the head with it.
|
||||
-- The rig. The head hangs off the neck so that rearing the neck carries the head with it, and
|
||||
-- the jaw off the head for the same reason. Tip first: joint measures the parent where it sits,
|
||||
-- so the jaw has to be hung on the head while the head is still at its own baked joint rather than
|
||||
-- already moved to be relative to the neck.
|
||||
local wingLeft = joint(dragon, "wingL")
|
||||
local wingRite = joint(dragon, "wingR")
|
||||
local neck = joint(dragon, "neck")
|
||||
-- The head hangs off the neck so that rearing the neck carries the head with it.
|
||||
local jaw = joint(dragon, "jaw", nodeFind("head", dragon))
|
||||
local head = joint(dragon, "head", neck)
|
||||
|
||||
-- The flame, on a node at the dragon's mouth so it follows the head round.
|
||||
|
|
@ -534,10 +549,15 @@ function backdropFrame()
|
|||
nodeSetRotation(wingLeft, -flap, 0, 0)
|
||||
nodeSetRotation(wingRite, flap, 0, 0)
|
||||
|
||||
-- The head rears back, snaps forward, and settles; the flame goes with the strike.
|
||||
-- The head rears to roar and comes down; then it rears again, snaps forward, and settles,
|
||||
-- and the flame goes with the strike.
|
||||
local pitch = 0
|
||||
|
||||
if t < REAR_TOP then
|
||||
if t < ROAR_TOP then
|
||||
pitch = ROAR_ANGLE * easeOut(span(t, ROAR_BACK, ROAR_TOP))
|
||||
elseif t < REAR_BACK then
|
||||
pitch = ROAR_ANGLE * (1 - span(t, ROAR_HOLD, REAR_BACK))
|
||||
elseif t < REAR_TOP then
|
||||
pitch = REAR_ANGLE * span(t, REAR_BACK, REAR_TOP)
|
||||
elseif t < REAR_STRIKE then
|
||||
local k = span(t, REAR_TOP, REAR_STRIKE)
|
||||
|
|
@ -547,11 +567,21 @@ function backdropFrame()
|
|||
pitch = STRIKE_ANGLE * (1 - easeOut(span(t, REAR_STRIKE, REAR_DONE)))
|
||||
end
|
||||
nodeSetRotation(head, 0, 0, pitch)
|
||||
nodeSetRotation(neck, 0, 0, pitch * 0.35)
|
||||
nodeSetRotation(neck, 0, 0, pitch * NECK_SHARE)
|
||||
|
||||
-- The mouth opens as the head goes back for the roar and shuts as it comes down, then
|
||||
-- opens again for the fire and stays open until the flame is spent.
|
||||
local roarJaw = span(t, ROAR_BACK, ROAR_TOP) * (1 - span(t, ROAR_HOLD, REAR_BACK))
|
||||
local fireJaw = span(t, REAR_BACK, BREATH_START) * (1 - span(t, BREATH_END, REAR_DONE))
|
||||
local jawPitch = JAW_OPEN * math.max(roarJaw, fireJaw)
|
||||
|
||||
nodeSetRotation(jaw, 0, 0, jawPitch)
|
||||
|
||||
-- The flame leaves the mouth along the dragon's own forward, which the whole logo has been
|
||||
-- turning; a 3D emitter takes its direction in world axes, so it is turned here by hand.
|
||||
local aim = math.rad(pitch)
|
||||
-- The head rides on the neck, so its pitch in the world is its own plus the neck's, and the
|
||||
-- flame follows the middle of the open mouth, half the jaw's drop below the head's line.
|
||||
local aim = math.rad(pitch * (1 + NECK_SHARE) + jawPitch / 2)
|
||||
local spin = math.rad(yaw + DRAGON_YAW)
|
||||
|
||||
emitterSetDirection(breath, math.cos(aim) * math.cos(spin), math.sin(aim), -math.cos(aim) * math.sin(spin))
|
||||
|
|
|
|||
BIN
assets/DragonModel.glb
(Stored with Git LFS)
BIN
assets/DragonModel.glb
(Stored with Git LFS)
Binary file not shown.
|
|
@ -486,6 +486,9 @@ if GAME_COUNT == 0 then
|
|||
else
|
||||
overlaySetResolution(discGetWidth(), discGetHeight())
|
||||
|
||||
-- No disc: the menu is laid out on the canvas, 720x480 unless --canvas says otherwise, and the
|
||||
-- renderer that needs Singe/menuBackground.mkv loads it itself.
|
||||
--
|
||||
-- Three frames of Singe/menuBackground.mkv: where the menu may take the screen, where the
|
||||
-- grid section it loops begins, and its last frame. The first two are not the same moment --
|
||||
-- the menu comes up while the music is still fading out behind it, and only then does the
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ local hintFont = nil -- Smaller: the hints are a footnote, and at the
|
|||
-- of them ran off the end of a 720 pixel overlay.
|
||||
local titleFont = nil -- Larger: a tool's title on its bar.
|
||||
local introDone = false
|
||||
local backdrop = nil -- Singe/menuBackground.mkv, played here rather than as the menu's disc
|
||||
local videoX, videoY, videoW, videoH = 0, 0, 0, 0
|
||||
local marqueeX, marqueeY, marqueeW, marqueeH = 0, 0, 0, 0
|
||||
local textX, textY, textW, textH = 0, 0, 0, 0
|
||||
|
|
@ -316,41 +317,52 @@ MENU_RENDER.finish = function()
|
|||
end
|
||||
end
|
||||
font, hintFont, titleFont = nil, nil, nil
|
||||
if backdrop then
|
||||
videoUnload(backdrop)
|
||||
backdrop = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- The backdrop is the disc: the engine intro, then the grid section played over and over. A
|
||||
-- The backdrop is a recording: the engine intro, then the grid section played over and over. A
|
||||
-- machine with no GPU device has nothing to draw a grid with, so this renderer plays the one in
|
||||
-- Singe/menuBackground.mkv; Singe/MenuDocument.singe draws its own instead.
|
||||
-- Singe/menuBackground.mkv; Singe/MenuDocument.singe draws its own instead. It is loaded here
|
||||
-- rather than handed to the menu as its disc, so the renderer that draws its own never opens it.
|
||||
MENU_RENDER.backdropBegin = function(showIntro)
|
||||
introDone = not showIntro
|
||||
discPlay()
|
||||
backdrop = videoLoad("Singe/menuBackground.mkv")
|
||||
videoPlay(backdrop)
|
||||
if not showIntro then
|
||||
discSkipToFrame(DISC_GRID_START)
|
||||
videoSeek(backdrop, DISC_GRID_START)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
MENU_RENDER.backdrop = function()
|
||||
-- Loop the grid. A machine too slow to show every disc frame can step over the last one, after
|
||||
-- which the engine starts the disc again from the top; once the intro has played, that is a loop.
|
||||
if discGetFrame() >= DISC_LAST_FRAME or (introDone and discGetFrame() < DISC_GRID_START) then
|
||||
discSkipToFrame(DISC_GRID_START)
|
||||
-- Loop the grid. A machine too slow to show every frame can step over the last one, after which
|
||||
-- the player starts the video again from the top; once the intro has played, that is a loop.
|
||||
local frame = videoGetFrame(backdrop)
|
||||
|
||||
if frame >= DISC_LAST_FRAME or (introDone and frame < DISC_GRID_START) then
|
||||
videoSeek(backdrop, DISC_GRID_START)
|
||||
end
|
||||
if discGetFrame() >= DISC_GRID_START then
|
||||
if frame >= DISC_GRID_START then
|
||||
introDone = true
|
||||
end
|
||||
|
||||
-- Transparent, so the disc behind the overlay is what shows.
|
||||
-- The recording fills the overlay, and everything else is drawn over it. Cleared see-through
|
||||
-- rather than black, which looks the same under a picture that covers everything, so the engine
|
||||
-- can have the GPU draw the recording instead of copying every frame of it into the overlay.
|
||||
colorBackground(0, 0, 0, 0)
|
||||
overlayClear()
|
||||
videoDraw(backdrop, 0, 0, overlayGetWidth() - 1, overlayGetHeight() - 1)
|
||||
end
|
||||
|
||||
|
||||
MENU_RENDER.backdropReady = function()
|
||||
-- The menu comes up where the drawn backdrop's logo leaves, which is before the picture starts
|
||||
-- repeating: the frames between the two are the grid running on while the music fades out.
|
||||
return discGetFrame() >= DISC_MENU_FRAME
|
||||
return videoGetFrame(backdrop) >= DISC_MENU_FRAME
|
||||
end
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -786,11 +786,17 @@ TOOLS[#TOOLS + 1] = {
|
|||
discResults = {}
|
||||
discPending = nil
|
||||
discWorst = 0
|
||||
-- The menu runs without a disc of its own, so there is only something to time when it was
|
||||
-- started with one (-v or a framefile on its command line).
|
||||
if discGetFrameCount() == 0 then
|
||||
discShow("There is no disc to test: start the menu with -v to time one.")
|
||||
else
|
||||
discShow("Button 1 runs the test.")
|
||||
end
|
||||
end,
|
||||
|
||||
input = function(what)
|
||||
if what == SWITCH_BUTTON1 and not discPending then
|
||||
if what == SWITCH_BUTTON1 and not discPending and (discGetFrameCount() > 0) then
|
||||
discResults = {}
|
||||
discWorst = 0
|
||||
discPending = { step = 0, started = 0 }
|
||||
|
|
|
|||
|
|
@ -44,7 +44,9 @@ DEAD_ZONES = {
|
|||
TRIGGER_THRESHOLD = 0
|
||||
|
||||
-- One table per switch, in the order the engine numbers them (SWITCH_UP = 0 ...).
|
||||
-- Each entry is a { name, value } pair from the SCANCODE, GAMEPAD_N, or MOUSE_N tables.
|
||||
-- Each entry is a { name, value } pair from the SCANCODE, GAMEPAD_N, or MOUSE_N tables, or a
|
||||
-- chord: a list of two to four of them held together, such as
|
||||
-- { GAMEPAD_0.BUTTON_BACK, GAMEPAD_0.BUTTON_START }. Each keeps its own meaning as well.
|
||||
INPUT_UP = { SCANCODE.UP, SCANCODE.KP_8, GAMEPAD_0.AXIS_LEFT_Y_U, GAMEPAD_0.AXIS_RIGHT_Y_U, GAMEPAD_0.DPAD_UP }
|
||||
INPUT_LEFT = { SCANCODE.LEFT, SCANCODE.KP_4, GAMEPAD_0.AXIS_LEFT_X_L, GAMEPAD_0.AXIS_RIGHT_X_L, GAMEPAD_0.DPAD_LEFT }
|
||||
INPUT_DOWN = { SCANCODE.DOWN, SCANCODE.KP_2, GAMEPAD_0.AXIS_LEFT_Y_D, GAMEPAD_0.AXIS_RIGHT_Y_D, GAMEPAD_0.DPAD_DOWN }
|
||||
|
|
|
|||
BIN
assets/menuBackground.mkv
(Stored with Git LFS)
BIN
assets/menuBackground.mkv
(Stored with Git LFS)
Binary file not shown.
|
|
@ -28,10 +28,10 @@
|
|||
-- from the same four places it reads controls.cfg, in this order, and a later
|
||||
-- one wins key by key:
|
||||
--
|
||||
-- 1. the directory Singe was started in
|
||||
-- 2. the directory above the game's data directory
|
||||
-- 3. the game's data directory
|
||||
-- 4. the game's own script directory
|
||||
-- 1. the game's own script directory: what the game ships
|
||||
-- 2. the directory Singe was started in: the machine's own
|
||||
-- 3. the directory above the game's data directory
|
||||
-- 4. the game's data directory: yours, for that one game
|
||||
--
|
||||
-- Every key is the long name of a command line option without the leading
|
||||
-- dashes, so there is no second vocabulary to learn, and the command line
|
||||
|
|
@ -57,6 +57,7 @@
|
|||
-- fullscreen_window = false -- borderless window covering the desktop
|
||||
-- nologos = false -- skip the splash screens
|
||||
-- canvas = "720x480" -- world size for a game with no disc
|
||||
-- scenequality = "high" -- 3D detail: high, or low for a small GPU (half resolution, no antialiasing, no bloom)
|
||||
|
||||
|
||||
-- Where the picture sits -------------------------------------------------
|
||||
|
|
@ -114,6 +115,7 @@
|
|||
|
||||
-- idleexit = 0 -- quit after this many seconds with no input; 0 never does
|
||||
-- no_update_check = false -- never look for a newer Singe from the menu (Engine Update still works by hand)
|
||||
-- quitchord = "back+start" -- first gamepad buttons held together that quit whatever a game maps
|
||||
-- fvalue = 0 -- one number handed to the game, which reads it with getFValue()
|
||||
|
||||
|
||||
|
|
@ -121,9 +123,11 @@
|
|||
|
||||
-- reload = false -- rerun the game when one of its loose script files changes
|
||||
-- program = false -- trace engine activity to trace.txt
|
||||
-- perfsync = false -- with program, show the GPU's time in each stage of the perf lines; slows everything
|
||||
-- trace = false -- trace every Lua call to trace.txt
|
||||
-- showcalculated = false -- print the frame ranges of every framefile segment
|
||||
-- softwarevideo = false -- decode video in software even when the machine can do it in hardware
|
||||
-- zerocopy = true -- hand hardware decoded frames to the GPU without copying them, where it can
|
||||
-- deinterlace = "auto" -- what an interlaced picture gets: off, auto or on. A laserdisc held
|
||||
-- interlaced fields, and a rip that kept them combs on a progressive
|
||||
-- display; auto touches only the frames the file marks interlaced
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ function buildAll() {
|
|||
}
|
||||
|
||||
|
||||
# assets/ holds the artwork, the font and the menu video in Git LFS. A clone made without git-lfs
|
||||
# assets/ and media/ hold the artwork, the font, the models, and the menu video in Git LFS. A clone made without git-lfs
|
||||
# leaves pointer stubs -- a hundred and thirty bytes of text where a video should be -- and the
|
||||
# resource generators embed them without complaining, so the failure only shows up at run time as a
|
||||
# menu with no background. One sentinel is enough to tell the two apart.
|
||||
|
|
@ -213,7 +213,7 @@ sudo apt-get install -y "${G_HOSTPACKAGES[@]}"
|
|||
|
||||
|
||||
if ! checkoutHydrated; then
|
||||
echo "error: assets/ holds git-lfs pointer files, not the real artwork, font and video."
|
||||
echo "error: assets/ and media/ hold git-lfs pointer files, not the real artwork, font and video."
|
||||
echo " The build would embed those stubs and produce a broken binary."
|
||||
echo " Fix the checkout with: git lfs install && git lfs pull"
|
||||
exit 1
|
||||
|
|
|
|||
|
|
@ -450,7 +450,7 @@ ExternalProject_Add(singe
|
|||
SOURCE_DIR ${CMAKE_SOURCE_DIR}
|
||||
BINARY_DIR ${singeBinary}
|
||||
DEPENDS zlib zstd SDL3 SDL3_image SDL3_mixer SDL3_ttf openssl ffmpeg Jolt recastnavigation RmlUi shadercross
|
||||
CONFIGURE_COMMAND ${SB_ENV} ${CMAKE_COMMAND} -S ${CMAKE_SOURCE_DIR} -B ${singeBinary} ${SB_CMAKE_ARGS} -DSINGE_SHADERCROSS=${SINGE_SHADERCROSS} -DSINGE_SUPERBUILD=OFF -DKANGAROO_OS=${KANGAROO_OS} -DKANGAROO_ARCH=${KANGAROO_ARCH} -DSINGE_TREE=${SINGE_TREE}
|
||||
CONFIGURE_COMMAND ${SB_ENV} ${CMAKE_COMMAND} -S ${CMAKE_SOURCE_DIR} -B ${singeBinary} ${SB_CMAKE_ARGS} -DSINGE_SHADERCROSS=${SINGE_SHADERCROSS} -DSINGE_SPIRV_CROSS=${SINGE_SPIRV_CROSS} -DSINGE_FROM_SUPERBUILD=ON -DSINGE_SUPERBUILD=OFF -DKANGAROO_OS=${KANGAROO_OS} -DKANGAROO_ARCH=${KANGAROO_ARCH} -DSINGE_TREE=${SINGE_TREE}
|
||||
BUILD_COMMAND ${SB_ENV} ${CMAKE_COMMAND} --build ${singeBinary} --parallel ${SB_JOBS}
|
||||
INSTALL_COMMAND ${CMAKE_COMMAND} -E copy ${singeBinary}/${singeBinaryBase}${CMAKE_EXECUTABLE_SUFFIX} ${CMAKE_SOURCE_DIR}/.builddir/${singeBinaryName}
|
||||
BUILD_ALWAYS ON
|
||||
|
|
@ -458,15 +458,17 @@ ExternalProject_Add(singe
|
|||
|
||||
# Forge, packed into .builddir beside the binary so it can be copied out and tested. It is NOT
|
||||
# part of the engine and nothing of it is embedded; this only puts the distributable where the
|
||||
# binaries already are. Only a build that produces a runnable binary can pack it, since packing is
|
||||
# the engine's own --pack, so cross builds skip it and the host build does the work. The manual,
|
||||
# binaries already are. Only a build that produces a binary this machine can run can pack it, since
|
||||
# packing is the engine's own --pack: the architecture has to match as well as the system, or the
|
||||
# shell is handed an ARM executable and answers "Syntax error". Every other build skips it and the
|
||||
# host build does the work. The manual,
|
||||
# rendered by the engine's own build into .builddir, goes in beside the scripts: Forge copies it
|
||||
# out to its data directory on its first run.
|
||||
if((KANGAROO_OS STREQUAL "linux") AND (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux"))
|
||||
# Everything under assets/Forge -- the scripts, the kit, the tutorials -- and re-globbed on
|
||||
if((KANGAROO_OS STREQUAL "linux") AND (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") AND (KANGAROO_ARCH STREQUAL CMAKE_HOST_SYSTEM_PROCESSOR))
|
||||
# Everything under forge/ -- the scripts, the kit, the tutorials -- and re-globbed on
|
||||
# every build, so a file added or changed anywhere in it repacks Forge.game without anyone
|
||||
# having to remember.
|
||||
file(GLOB_RECURSE forgeSources CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/assets/Forge/*)
|
||||
file(GLOB_RECURSE forgeSources CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/forge/*)
|
||||
set(forgePack ${CMAKE_SOURCE_DIR}/.builddir/Forge.game)
|
||||
set(forgeManual ${CMAKE_SOURCE_DIR}/.builddir/Forge.pdf)
|
||||
set(forgeStage ${SB_PREFIX}/forgepack)
|
||||
|
|
@ -474,7 +476,7 @@ if((KANGAROO_OS STREQUAL "linux") AND (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux"))
|
|||
OUTPUT ${forgePack}
|
||||
# Packing refuses to overwrite, and a stale pack is worse than none.
|
||||
COMMAND ${CMAKE_COMMAND} -E rm -rf ${forgePack} ${forgeStage}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/Forge ${forgeStage}/Forge
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/forge ${forgeStage}/Forge
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${forgeManual} ${forgeStage}/Forge/Forge.pdf
|
||||
# The samples that need no art beyond what the engine ships, so the chooser has something to
|
||||
# open on a fresh install. They come from the test set, which is the one copy of them.
|
||||
|
|
|
|||
|
|
@ -25,7 +25,9 @@ set(entries ${ENTRIES})
|
|||
set(formats SPIRV DXIL MSL)
|
||||
|
||||
# GLSL ES 3.10 for the GLES backend. Three switches matter and each is load bearing:
|
||||
# --es --version 310 ES 3.1, the floor the scene needs; its storage buffers are ES 3.1 only.
|
||||
# --es --version 310 ES 3.1, the floor the scene needs. The vertex stage's storage buffers become texture
|
||||
# buffers in this form (SINGE_ESSL below), since ES lets a vertex shader have no
|
||||
# storage blocks and Mali drivers give it none; renderGles.c adjusts the extension.
|
||||
# --combined-samplers-inherit-bindings
|
||||
# HLSL's separate Texture2D and SamplerState have no ES equivalent, so SPIRV-Cross folds
|
||||
# each pair into one sampler and keeps the HLSL t-register as the binding, which is the
|
||||
|
|
@ -71,12 +73,18 @@ foreach(entry IN LISTS entries)
|
|||
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," bytes "${hex}")
|
||||
string(APPEND header "static const unsigned char _${name}${format}[] = {\n\t${bytes}\n};\n\n")
|
||||
endforeach()
|
||||
# The ES form, cross compiled from the SPIR-V above. Emitted as a C string rather than bytes because that is what
|
||||
# glShaderSource takes; the escaping is only quotes and backslashes, neither of which GLSL ES has a use for.
|
||||
# The ES form, cross compiled from its own SPIR-V: the same source with SINGE_ESSL defined, which reads the
|
||||
# vertex stage's storage buffers as texture buffers. Emitted as a C string rather than bytes because that is
|
||||
# what glShaderSource takes; the escaping is only quotes and backslashes, neither of which GLSL ES has a use for.
|
||||
set(esslSymbol "NULL")
|
||||
if(SPIRVCROSS)
|
||||
set(esslSpirv ${work}/${name}.essl.spirv)
|
||||
execute_process(COMMAND ${SHADERCROSS} ${SOURCE} -s HLSL -d SPIRV -t ${stage} -e ${name} -I ${includeDir} -DSINGE_ESSL -o ${esslSpirv} RESULT_VARIABLE code OUTPUT_VARIABLE out ERROR_VARIABLE err)
|
||||
if(NOT code EQUAL 0)
|
||||
message(FATAL_ERROR "Shader ${name} (SPIR-V for GLSL ES) failed to compile:\n${out}\n${err}")
|
||||
endif()
|
||||
set(essl ${work}/${name}.essl)
|
||||
execute_process(COMMAND ${SPIRVCROSS} ${esslFlags} ${work}/${name}.spirv --output ${essl} RESULT_VARIABLE code OUTPUT_VARIABLE out ERROR_VARIABLE err)
|
||||
execute_process(COMMAND ${SPIRVCROSS} ${esslFlags} ${esslSpirv} --output ${essl} RESULT_VARIABLE code OUTPUT_VARIABLE out ERROR_VARIABLE err)
|
||||
if(NOT code EQUAL 0)
|
||||
message(FATAL_ERROR "Shader ${name} (GLSL ES) failed to cross compile:\n${out}\n${err}")
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ in the chooser is this file.
|
|||
|
||||
[source,lua]
|
||||
----
|
||||
include::../assets/Forge/tutorials/01-tenMinutes.forge[]
|
||||
include::../forge/tutorials/01-tenMinutes.forge[]
|
||||
----
|
||||
|
||||
=== Tutorial 2: A Platformer
|
||||
|
|
@ -231,7 +231,7 @@ is the three types, their entities, the three rules, and the second room.
|
|||
|
||||
[source,lua]
|
||||
----
|
||||
include::../assets/Forge/tutorials/02-platformer.forge[]
|
||||
include::../forge/tutorials/02-platformer.forge[]
|
||||
----
|
||||
|
||||
=== Tutorial 3: Sprites and Sound
|
||||
|
|
@ -311,7 +311,7 @@ states are in <<_instances_vars_and_states>> and
|
|||
|
||||
[source,lua]
|
||||
----
|
||||
include::../assets/Forge/tutorials/03-sprites.forge[]
|
||||
include::../forge/tutorials/03-sprites.forge[]
|
||||
----
|
||||
|
||||
=== Tutorial 4: A Shoot-Em-Up
|
||||
|
|
@ -412,7 +412,7 @@ runs once per instance of a type, with that instance as `self`, and a
|
|||
|
||||
[source,lua]
|
||||
----
|
||||
include::../assets/Forge/tutorials/04-shmup.forge[]
|
||||
include::../forge/tutorials/04-shmup.forge[]
|
||||
----
|
||||
|
||||
=== Tutorial 5: A Quick-Time Event
|
||||
|
|
@ -475,7 +475,7 @@ image::tutorials/05-rules.png[The rules, 600]
|
|||
**What happened.** A `disc` layer plays a video underneath everything, and
|
||||
the rules read its *frame*: `discBetween` is true while the disc is inside
|
||||
a window, `frameReached` fires as it passes a frame, and `discTo` sends it
|
||||
elsewhere -- the whole of a Dragon's Lair is windows and jumps. `once`
|
||||
elsewhere -- the whole of a branching laserdisc game is windows and jumps. `once`
|
||||
keeps a rule from firing every frame the window is open, and `keyPressed`
|
||||
is the frame a key goes down, where `keyHeld` is every frame it is down.
|
||||
`setVar` and `test` on a var of your own are how a rule remembers what
|
||||
|
|
@ -493,14 +493,14 @@ released game, `games.dat` names the video, as it does for any Singe game.
|
|||
|
||||
[source,lua]
|
||||
----
|
||||
include::../assets/Forge/tutorials/05-qte.forge[]
|
||||
include::../forge/tutorials/05-qte.forge[]
|
||||
----
|
||||
|
||||
=== Tutorial 6: A Light Gun Game
|
||||
|
||||
image::tutorials/06-playing.png[Draw, 600]
|
||||
|
||||
**What you will make.** Mad Dog McCree in miniature: the clip plays, a
|
||||
**What you will make.** A light gun shooting gallery in miniature: the clip plays, a
|
||||
target rises, and the player shoots it with the mouse or a light gun. The
|
||||
hand that reaches in is not to be shot. Ammo runs out and is reloaded by a
|
||||
shot off the picture.
|
||||
|
|
@ -579,7 +579,7 @@ with a light gun needs -- calibration, two guns, the recoil -- is in
|
|||
|
||||
[source,lua]
|
||||
----
|
||||
include::../assets/Forge/tutorials/06-gun.forge[]
|
||||
include::../forge/tutorials/06-gun.forge[]
|
||||
----
|
||||
|
||||
=== Tutorial 7: A Point-and-Click Adventure
|
||||
|
|
@ -706,7 +706,7 @@ off. All of it is in
|
|||
|
||||
[source,lua]
|
||||
----
|
||||
include::../assets/Forge/tutorials/07-adventure.forge[]
|
||||
include::../forge/tutorials/07-adventure.forge[]
|
||||
----
|
||||
|
||||
=== Tutorial 8: Into 3D
|
||||
|
|
@ -801,7 +801,7 @@ viewport in <<_the_3d_viewport>>.
|
|||
|
||||
[source,lua]
|
||||
----
|
||||
include::../assets/Forge/tutorials/08-scene.forge[]
|
||||
include::../forge/tutorials/08-scene.forge[]
|
||||
----
|
||||
|
||||
=== Tutorial 9: A Rail Shooter
|
||||
|
|
@ -920,7 +920,7 @@ in <<_games_in_the_scene>>; painted rooms in
|
|||
|
||||
[source,lua]
|
||||
----
|
||||
include::../assets/Forge/tutorials/09-rail.forge[]
|
||||
include::../forge/tutorials/09-rail.forge[]
|
||||
----
|
||||
|
||||
=== Tutorial 10: Releasing Your Game
|
||||
|
|
@ -1003,7 +1003,7 @@ is what a cabinet's coin door shows. Releasing is in
|
|||
|
||||
[source,lua]
|
||||
----
|
||||
include::../assets/Forge/tutorials/10-release.forge[]
|
||||
include::../forge/tutorials/10-release.forge[]
|
||||
----
|
||||
|
||||
=== Recipes
|
||||
|
|
@ -1065,7 +1065,7 @@ ones.
|
|||
|
||||
*The board and the bezel* (`fmv.forge`). `bezel` round any game, `credit`
|
||||
on the coin switch, `restart` on start with credits, and `submitScore` at
|
||||
the end; `branching` for a Dragon's Lair of windows, moves, and jumps.
|
||||
the end; `branching` for a branching game of windows, moves, and jumps.
|
||||
|
||||
*Anything else.* The `lua` action runs a line of Singe Lua in a rule, with
|
||||
`self`, `other`, and `event` in scope, and the engine's whole API with them:
|
||||
|
|
@ -1309,7 +1309,7 @@ row of bullets or of hearts; the `pointer` behaviour keeps an instance on
|
|||
the player's pointer, a crosshair, and hides it when the engine says a real
|
||||
gun wants none drawn; `discPlay` and `discPause` let the disc go and hold
|
||||
it, a still behind a title; `pointerX(1)` and `pointerY(1)` in an
|
||||
expression say where the pointer is. The ActionMax way of judging a shot
|
||||
expression say where the pointer is. The light sensor way of judging a shot
|
||||
over video -- the picture under the gun agreeing with a sensor spot across
|
||||
two frames -- is a `lightSensor` behaviour those five games carry in a
|
||||
vocabulary of their own (see <<own-vocabulary>>), not one of Forge's.
|
||||
|
|
@ -1323,7 +1323,7 @@ game's script. The game's script and its `Script/addons.singe` run inside
|
|||
the behaviour, so a game keeps its own scoring and level order. The
|
||||
seventeen games of the test library and eleven of the Hypseus library are
|
||||
ported this way and play as their originals do (FORGE.md section 14), and
|
||||
so is LINEA, the one game on the MazescaterFramework -- that framework
|
||||
so is the one game on the MazescaterFramework -- that framework
|
||||
with compound moves (sequences of two or three inputs, circles from any
|
||||
start, mashes of a direction or a button, a direction with the second or
|
||||
third button), which the behaviour judges from tables of its own once the
|
||||
|
|
@ -1343,17 +1343,16 @@ where the game carries one, a map between levels -- with the copies'
|
|||
drift (the later generation's level order, game type, and state numbers)
|
||||
gated by what the game's script declares, and what a title does without
|
||||
declaring it named in a `tweaks` table the converter writes. The
|
||||
`timegal` behaviour plays the same author's Time Gal remake, a compact
|
||||
`timegal` behaviour plays one of the same author's remakes, a compact
|
||||
loop of its own (checkpoints, a time-stop choice of three, a tally by
|
||||
deaths, a life per quota, a level order by tiers), over the game's own
|
||||
files in the same way; and the `sdq` behaviour plays Super Don Quixote,
|
||||
files in the same way; and the `sdq` behaviour plays one game that is
|
||||
the one hybrid of map-mode data under the Karis framework's judging,
|
||||
with its difficulty select, level clear, and trophies.
|
||||
|
||||
*A hand-written game.* A game that was a program of its own rather than
|
||||
a framework's tables -- the American Laser Games titles, Mad Dog McCree
|
||||
first among them and the typing edition of Mad Dog II the ninth, and
|
||||
Platoon, a prototype in their shape -- is ported as a vocabulary of its own beside
|
||||
a framework's tables -- a set of light gun titles, a typing edition of one
|
||||
of them, and a prototype in their shape -- is ported as a vocabulary of its own beside
|
||||
its description (see
|
||||
<<own-vocabulary>>): a behaviour that is the game's loop rewritten in
|
||||
Forge's terms, its screens and each level's flow, over the game's own
|
||||
|
|
@ -1476,7 +1475,7 @@ examples; scenes 66 and 67 play them.
|
|||
|
||||
=== Arcade Plumbing: Branching Video, Lives, Credits, the Board
|
||||
|
||||
A `branches` track is Dragon's Lair: each branch is a window of disc frames,
|
||||
A `branches` track is a branching laserdisc game: each branch is a window of disc frames,
|
||||
the `move` (a key) or `switch` it wants, and the frames the disc goes to on
|
||||
`success` and on `fail`; a type with `{ kind = "branching", track = "moves" }`
|
||||
watches it and raises `branchOpen` (with `event.move`), `branchTaken`, and
|
||||
|
|
@ -1592,7 +1591,7 @@ sets `instance.hearsAll` and reads them in `on`.
|
|||
|
||||
A word that turns out to be general belongs in the framework rather than
|
||||
beside one game. `lightSensor`, which judges a shot by the picture under the
|
||||
gun, began as the five ActionMax games' own vocabulary and is now one of the
|
||||
gun, began as five light gun games' own vocabulary and is now one of the
|
||||
behaviours above, with `pictureAt` beside it as a condition any rule can ask;
|
||||
those five name no vocabulary at all any more. The test scene's `bob` is the
|
||||
working example instead, and the rule to apply is the same one: a vocabulary
|
||||
|
|
@ -1856,7 +1855,7 @@ drive it.
|
|||
| `K` | a key at the cursor; in a 3D room, a rail point where the camera stands
|
||||
| `J`, `L`, `Y`, `H`, `I`, `K` | in a 3D room: orbit, tilt, and zoom the editor camera
|
||||
| `U`, `R` | undo and redo, forty steps deep; a drag or a typed form is one step, and a change by hand ends the redo history
|
||||
| `S`, `B`, `P`, `X` | save, build, play, release
|
||||
| `S`, `B`, `P`, `X`, `Shift+P` | save, build, play, release, publish
|
||||
|===
|
||||
|
||||
From a script the same work is `forgeEntityAdd`, `forgeEntityDuplicate`,
|
||||
|
|
@ -1886,7 +1885,7 @@ for every named file there first, so the release runs wherever it is put.
|
|||
`--pack` turns that directory into a `.game` like any other, and it runs on a
|
||||
machine that has never had Forge on it.
|
||||
|
||||
`P` goes one step further: it exports, packs, and sends the package to the
|
||||
`Shift+P` goes one step further: it exports, packs, and sends the package to the
|
||||
master service for review, in one press. It is not in the catalogue until it
|
||||
has been reviewed, and Forge says which version number it was given. Press
|
||||
again after a change and a later version goes up on its own, without taking an
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Generated by util/forgeVocabulary.lua from assets/Forge/Author.singe. Do not edit.
|
||||
// Generated by util/forgeVocabulary.lua from forge/Author.singe. Do not edit.
|
||||
|
||||
=== Layers
|
||||
|
||||
|
|
@ -107,7 +107,7 @@
|
|||
| `bounce` (number), `buoyancy` (number), `friction` (number), `mass` (number), `shape` (box, sphere)
|
||||
|
||||
| `branching`
|
||||
| Dragon's Lair: a track of branches, each a disc frame window, the move it wants, and where the disc goes on success and on failure. A branch with mash wants the move that many times inside the window; one with hold wants it held that many frames. Raises branchOpen, branchTaken, and branchMissed.
|
||||
| A branching laserdisc game: a track of branches, each a disc frame window, the move it wants, and where the disc goes on success and on failure. A branch with mash wants the move that many times inside the window; one with hold wants it held that many frames. Raises branchOpen, branchTaken, and branchMissed.
|
||||
| `track` (track)
|
||||
|
||||
| `camera`
|
||||
|
|
@ -191,7 +191,7 @@
|
|||
| --
|
||||
|
||||
| `sdq`
|
||||
| Plays Super Don Quixote: map-mode levels, scenes, death clips, order, and map from the game's own files, judged as the Karis 3.31c framework judges, with its difficulty select, level clear, finish, and trophies. Written by util/forgePortKaris.lua from the game's script.
|
||||
| Plays one hybrid map-mode game: map-mode levels, scenes, death clips, order, and map from the game's own files, judged as the Karis 3.31c framework judges, with its difficulty select, level clear, finish, and trophies. Written by util/forgePortKaris.lua from the game's script.
|
||||
| --
|
||||
|
||||
| `seek`
|
||||
|
|
@ -231,7 +231,7 @@
|
|||
| `force` (number), `turn` (number)
|
||||
|
||||
| `timegal`
|
||||
| Plays RDG2010's Time Gal (Singe Edition): levels of moves from a difficulty array (mirrored by a random draw), checkpoints, a time-stop choice of three, a tally by deaths, a life per quota, and a level order by tiers, at random, or in sequence. The game's own files are loaded and its data and order functions run as they are; the loop is this. Written by util/forgePortKaris.lua from the game's script.
|
||||
| Plays one of RDG2010's remakes: levels of moves from a difficulty array (mirrored by a random draw), checkpoints, a time-stop choice of three, a tally by deaths, a life per quota, and a level order by tiers, at random, or in sequence. The game's own files are loaded and its data and order functions run as they are; the loop is this. Written by util/forgePortKaris.lua from the game's script.
|
||||
| --
|
||||
|
||||
| `timer`
|
||||
|
|
|
|||
603
docs/Manual.adoc
603
docs/Manual.adoc
|
|
@ -24,22 +24,33 @@ own game are made available through a simple application programming interface
|
|||
Singe provides numerous features to the game developer. Some of the more
|
||||
interesting ones are:
|
||||
|
||||
* 3D scenes with models, animation, lighting, and shadows
|
||||
* Physics, including joints, ragdolls, soft bodies, and vehicles
|
||||
* Menus, option screens, and HUDs built like web pages
|
||||
* Hardware video decoding, even on small machines
|
||||
* Online high scores, and a catalogue of games to download and update
|
||||
* Games packed into a single file
|
||||
* Object-oriented programming language
|
||||
* Animated sprites
|
||||
* TrueType font support
|
||||
* 32-bit color space with transparency
|
||||
* Multi-channel, overlapping, stereo sound
|
||||
* Analog and digital input device support
|
||||
* Multi-channel, overlapping, stereo sound, and MIDI
|
||||
* Analog and digital input device support, including light guns
|
||||
* Wide support for video and audio formats
|
||||
|
||||
Singe runs on Windows, macOS, and Linux, including the Raspberry Pi and
|
||||
handhelds like the R36S.
|
||||
|
||||
For players, Singe allows many unemulated and new games to be enjoyed using any
|
||||
desired front end or via the included menu system.
|
||||
|
||||
Since the original release of Singe back in 2006, several revisions have been
|
||||
released, both officially and unofficially. In 2020, Singe 2.00 was released.
|
||||
This was a total rewrite from the ground up, adding numerous features while
|
||||
staying compatible with existing 1.xx games. As of the writing of this manual,
|
||||
the latest release is {revnumber}.
|
||||
staying compatible with existing 1.xx games. Singe 3.00 adds 3D, physics,
|
||||
user interfaces, and much more, while still running the games that came
|
||||
before it. As of the writing of this manual, the latest release is
|
||||
{revnumber}.
|
||||
|
||||
== Installation and Upgrading
|
||||
|
||||
|
|
@ -74,7 +85,7 @@ files. Always refresh the `Singe/` directory when upgrading.
|
|||
Do not touch the `Singe/` folder. Files in this folder belong to Singe and may
|
||||
be updated or deleted at any time.
|
||||
|
||||
== Usage
|
||||
== Playing Games
|
||||
|
||||
=== Installing Games
|
||||
|
||||
|
|
@ -127,28 +138,29 @@ Singe/ Support files extracted by the engine
|
|||
Manual.pdf This manual
|
||||
COPYING The GNU General Public License, version 3
|
||||
LICENSES Third-party notices and asset credits
|
||||
ActionMax/ One game
|
||||
Collection/ A directory of games
|
||||
games.dat Menu entries for the games in this directory
|
||||
38AmbushAlley.singe A script
|
||||
frame_38AmbushAlley.txt Its framefile (or a video with the same base name)
|
||||
AsteroidPatrol.singe A script
|
||||
frame_AsteroidPatrol.txt Its framefile (or a video with the same base name)
|
||||
sprite_*.png, sound_*.wav, font_*.ttf
|
||||
DLe.game A game packed into one file (see Single-File Games)
|
||||
MyGame.game A game packed into one file (see Single-File Games)
|
||||
data/
|
||||
machine.cfg This machine's audio delay and picture geometry
|
||||
ActionMax/ Indexes, trace.txt, screenshots, saves for that game
|
||||
Collection/ Indexes, trace.txt, screenshots, saves for those games
|
||||
----
|
||||
|
||||
Where Singe looks for a script's files, and where a script may write, is a
|
||||
matter for whoever writes the script; the Singe Reference covers both under
|
||||
Packaging Your Game.
|
||||
|
||||
|
||||
[#bundledmenu]
|
||||
=== The Bundled Menu
|
||||
|
||||
`Menu.sh` (or `Menu.bat`) starts `Singe/Menu.singe`, the bundled menu: an
|
||||
RmlUi document, `Singe/Menu.rml` styled by `Singe/menu.rcss`, drawn over the
|
||||
engine's own disc (see xref:Reference.adoc#gui[GUI]). It reads the `games.dat` of every game
|
||||
RmlUi document, `Singe/Menu.rml` styled by `Singe/menu.rcss`, drawn over a
|
||||
live 3D backdrop (see xref:Reference.adoc#gui[GUI]). The menu runs without a
|
||||
disc; a machine with no GPU plays `Singe/menuBackground.mkv`, a recording of
|
||||
the same backdrop, behind a simpler page drawn on the overlay instead. It reads the `games.dat` of every game
|
||||
directory and `.game` file beside it and lists every game it finds, sorted by
|
||||
title, down the left of the screen. Beside the list are the selected game's
|
||||
cabinet art, its marquee and its attract clip, with the year, genre,
|
||||
|
|
@ -160,9 +172,10 @@ a page at a time; both wrap from either end of the list to the other. Start
|
|||
or any action button launches the selected game, and the key mapped to
|
||||
`INPUT_SERVICE` (the `9` key by default) opens the service tools (see
|
||||
<<servicetools,The Service Tools>>). With a mouse, a
|
||||
click on a row selects that game and a second click on the same row, or a
|
||||
click on the Start button in the footer, launches it; the mouse wheel
|
||||
scrolls a description too long for its panel. The menu keeps its selection
|
||||
click on a row selects that game and a second click on the same row launches
|
||||
it; the mouse wheel scrolls a description too long for its panel. If you set
|
||||
`--quitchord`, holding those buttons quits a game back to the menu, and quits
|
||||
the menu back to your system (see <<handhelds,Handhelds and Small Machines>>). The menu keeps its selection
|
||||
in `menu.dat` in its data directory, so it comes back to the same game when
|
||||
one quits or when Singe is next started. That file is a Lua fragment the menu
|
||||
reads at startup and rewrites at shutdown: `GAME_SELECTED` is the game it was
|
||||
|
|
@ -332,15 +345,60 @@ Turns the developer's overlay on and off. It stays on into the game, in the
|
|||
top left corner. See xref:Reference.adoc#statsenable[statsEnable].
|
||||
|
||||
[[controls]]
|
||||
=== Customizing the Controls
|
||||
== Controls
|
||||
|
||||
By default, Singe maps controllers as if they were Xbox gamepads. If your
|
||||
controller is not recognized or you wish to change the default mappings as they
|
||||
appear to Singe, you can use the free SDL2 Gamepad Tool from
|
||||
http://generalarcade.com/gamepadtool/.
|
||||
|
||||
[[controlscfg]]
|
||||
=== controls.cfg
|
||||
|
||||
In addition to configuring your controller, you can remap all the input options
|
||||
for Singe as a whole or for any individual game by using `controls.cfg` files.
|
||||
When determining how inputs are mapped, Singe reads configuration settings in
|
||||
the following order, and a later file replaces an earlier one input by input
|
||||
(see <<configorder,Where Configuration Files Are Read From>>):
|
||||
|
||||
. The defaults from inside Singe.
|
||||
. `controls.cfg` from the game's main script directory: what the game's author shipped.
|
||||
. `controls.cfg` from the directory Singe was started in: the machine's own (the `Singe` directory holds a `controls.cfg.example` template you can copy).
|
||||
. `controls.cfg` from the directory above the game's data directory.
|
||||
. `controls.cfg` from the game's data directory: yours, for that one game.
|
||||
|
||||
You do not have to specify every option in a custom `controls.cfg` file. Feel
|
||||
free to just provide the entries you want changed.
|
||||
|
||||
After installing Singe, you can find a sample input configuration file at
|
||||
`<InstallDir>/Singe/controls.cfg.example`. To see the available configuration
|
||||
options available to be used in this file, read through
|
||||
`<InstallDir>/Singe/Framework.singe`.
|
||||
|
||||
`--keymapfile` names one file to read instead of making that search, which is
|
||||
useful to a front end that wants no doubt about which mappings a game got.
|
||||
|
||||
Each `INPUT_` entry is a list, and any one thing in it works the switch. A list
|
||||
inside that list is a chord: two to four keys or buttons held together, which
|
||||
works the switch when the last of them goes down and lets go when any of them
|
||||
comes up. A handheld with no Escape key quits on Select and Start together
|
||||
this way:
|
||||
|
||||
[source,lua]
|
||||
----
|
||||
INPUT_QUIT = { SCANCODE.ESCAPE, { GAMEPAD_0.BUTTON_BACK, GAMEPAD_0.BUTTON_START } }
|
||||
----
|
||||
|
||||
Each key or button in a chord keeps its own meaning too, so pressing Select and
|
||||
Start to quit this way also drops a coin and presses Start on the way out.
|
||||
|
||||
If you want a way to quit that always works, use `--quitchord` instead. When
|
||||
you press one of its buttons, Singe waits a quarter of a second for the rest
|
||||
before doing anything else, so quitting does not drop a coin or press Start. A
|
||||
game can turn its own quit key off, but it cannot turn off `--quitchord`.
|
||||
|
||||
[[devices]]
|
||||
==== Every Device Is a Gamepad
|
||||
=== Every Device Is a Gamepad
|
||||
|
||||
Singe has one family of controller, `GAMEPAD_0` through `GAMEPAD_3`, and every
|
||||
device it opens is in it. SDL calls a device a gamepad only when it has a
|
||||
|
|
@ -369,24 +427,34 @@ have it used instead.
|
|||
not recognise is not opened at all, exactly as in earlier releases.
|
||||
|
||||
[[gamecontrollerdb]]
|
||||
==== gamecontrollerdb.txt
|
||||
=== gamecontrollerdb.txt
|
||||
|
||||
SDL ships with a large database of controller mappings, and the community keeps
|
||||
a larger one -- `gamecontrollerdb.txt`, from
|
||||
https://github.com/mdqinc/SDL_GameControllerDB. Singe looks for that file in
|
||||
the same four places it looks for `controls.cfg`:
|
||||
the same four places it looks for `controls.cfg`, in the same order (see
|
||||
<<configorder,Where Configuration Files Are Read From>>):
|
||||
|
||||
. the game's main script directory;
|
||||
. the directory Singe was started in;
|
||||
. the directory above the game's data directory;
|
||||
. the game's data directory;
|
||||
. the game's main script directory.
|
||||
. the game's data directory.
|
||||
|
||||
Every file found is loaded, in that order, so one beside a game wins over a
|
||||
machine-wide one, and both win over SDL's own database. Only a device that
|
||||
Every file found is loaded, in that order, so the machine's file wins over one
|
||||
a game ships, a file in the game's data directory wins over both, and all of
|
||||
them win over SDL's own database. Only a device that
|
||||
none of them names gets the mapping Singe writes for it, so the order of
|
||||
precedence is: SDL's built-in database, then your `gamecontrollerdb.txt`, then
|
||||
the synthetic mapping. `--program` reports how many mappings each file added.
|
||||
|
||||
The files also beat `SDL_GAMECONTROLLERCONFIG`, the environment variable SDL
|
||||
takes mappings from, which some handheld systems set for their built-in pad.
|
||||
ArkOS's mapping for the R36S, for instance, leaves Select and Start out, so no
|
||||
game sees them; a line in `gamecontrollerdb.txt` for the same pad puts them
|
||||
back. SDL reads the variable once, when Singe starts, so this holds for the
|
||||
files the first game finds. A game started later from the menu still reads its
|
||||
own, but a pad the variable names keeps the variable's mapping.
|
||||
|
||||
A line of that file looks like this, and is exactly what the trace prints for a
|
||||
device Singe mapped itself:
|
||||
|
||||
|
|
@ -394,30 +462,9 @@ device Singe mapped itself:
|
|||
ff00d87a475300004900000000007603,Arcade Encoder,platform:Linux,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftx:a0,lefty:a1,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
|
||||
----
|
||||
|
||||
In addition to configuring your controller, you can remap all the input options
|
||||
for Singe as a whole or for any individual game by using `controls.cfg` files.
|
||||
When determining how inputs are mapped, Singe reads configuration settings in
|
||||
the following order:
|
||||
|
||||
. The defaults from inside Singe.
|
||||
. `controls.cfg` from the directory Singe was started in (the `Singe` directory holds a `controls.cfg.example` template you can copy).
|
||||
. `controls.cfg` from the directory above the game's data directory.
|
||||
. `controls.cfg` from the game's data directory.
|
||||
. `controls.cfg` from the game's main script directory.
|
||||
|
||||
You do not have to specify every option in a custom `controls.cfg` file. Feel
|
||||
free to just provide the entries you want changed.
|
||||
|
||||
After installing Singe, you can find a sample input configuration file at
|
||||
`<InstallDir>/Singe/controls.cfg.example`. To see the available configuration
|
||||
options available to be used in this file, read through
|
||||
`<InstallDir>/Singe/Framework.singe`.
|
||||
|
||||
`--keymapfile` names one file to read instead of making that search, which is
|
||||
useful to a front end that wants no doubt about which mappings a game got.
|
||||
|
||||
[[deadzones]]
|
||||
==== Dead Zones and Triggers
|
||||
=== Dead Zones and Triggers
|
||||
|
||||
`DEAD_ZONE` in `controls.cfg` is how far an axis must move before it counts as
|
||||
a pressed switch, in the raw units SDL reports: `32767` at full deflection, so
|
||||
|
|
@ -455,7 +502,7 @@ directly: a per cent is `32767 / 100` raw units, so Singe's `DEAD_ZONE` of
|
|||
`controls.cfg`; `DEAD_ZONES` wins over both, being the most specific.
|
||||
|
||||
[[joymouse]]
|
||||
==== Driving the Mouse with a Stick
|
||||
=== Driving the Mouse with a Stick
|
||||
|
||||
A light gun game on a cabinet with a stick and no mouse is unplayable unless
|
||||
the stick can move the cursor. `--joymouse` makes it do so: the **left stick of
|
||||
|
|
@ -475,7 +522,7 @@ and reads it with `joyMouseIsEnabled`. A reload puts it back where the command
|
|||
line left it.
|
||||
|
||||
[[mousedevices]]
|
||||
==== Mice, Guns, and Who Chooses
|
||||
=== Mice, Guns, and Who Chooses
|
||||
|
||||
`mouseSetMode(MOUSE_MANY)` is a game's decision to tell up to four mice apart,
|
||||
which a two gun cabinet needs and which a game that never makes the call cannot
|
||||
|
|
@ -501,7 +548,7 @@ name. `controls.cfg` leaves it bound to nothing; bind a key to it as well if
|
|||
you want to see what a game does without pulling a cable.
|
||||
|
||||
[[hotkeys]]
|
||||
==== Hotkeys
|
||||
=== Hotkeys
|
||||
|
||||
`Alt-Enter` switches between full screen and a window while the game runs, as
|
||||
it does in Hypseus. The game never sees the key. A window that started in
|
||||
|
|
@ -517,6 +564,11 @@ The other switches a user can press -- quit, pause, screenshot, the console and
|
|||
the mouse capture toggle -- are ordinary `controls.cfg` mappings and are
|
||||
described with the `INPUT_*` names in the sample file.
|
||||
|
||||
== Settings and Options
|
||||
|
||||
Almost everything you can put on the command line can also go in a settings
|
||||
file. Set things up once and every game uses them.
|
||||
|
||||
[[settings]]
|
||||
=== The Settings File
|
||||
|
||||
|
|
@ -525,15 +577,15 @@ Everything on the command line can also be written down once, in a
|
|||
same dozen options for every game. It is Lua, like `controls.cfg`, and Singe
|
||||
looks for it in exactly the same four places, in this order:
|
||||
|
||||
. `settings.cfg` from the game's main script directory.
|
||||
. `settings.cfg` from the directory Singe was started in (the `Singe` directory holds a `settings.cfg.example` template you can copy).
|
||||
. `settings.cfg` from the directory above the game's data directory.
|
||||
. `settings.cfg` from the game's data directory.
|
||||
. `settings.cfg` from the game's main script directory.
|
||||
|
||||
Each place is read once, and a later file wins key by key, so a machine-wide
|
||||
file can set the display and the volumes while a file beside one game turns off
|
||||
its crosshair. You do not have to write every key in a file; write the ones you
|
||||
want changed.
|
||||
file can set the display and the volumes while a file in one game's data
|
||||
directory turns off that game's crosshair. You do not have to write every key
|
||||
in a file; write the ones you want changed.
|
||||
|
||||
Every key is the long name of a command line option with the leading dashes
|
||||
removed, so there is no second vocabulary to learn:
|
||||
|
|
@ -558,7 +610,32 @@ One option, `--deterministic`, takes a value or leaves it out, and its key does
|
|||
the same: `deterministic = true` uses the default step, `deterministic = 20`
|
||||
names one, and `deterministic = false` leaves the option off.
|
||||
|
||||
==== What Wins
|
||||
[[configorder]]
|
||||
=== Where Configuration Files Are Read From
|
||||
|
||||
`controls.cfg`, `settings.cfg` and `gamecontrollerdb.txt` are all looked for in
|
||||
the same four places, in the same order, and a file read later wins over one
|
||||
read earlier:
|
||||
|
||||
. *The game's main script directory,* which for a packed game is inside its
|
||||
`.game` file. This is what the game's author shipped, so it is read first:
|
||||
it sets the game's defaults and nothing more.
|
||||
. *The directory Singe was started in.* The machine's own files, such as a
|
||||
handheld's button layout, go here, and they win over what a game ships. A
|
||||
file only replaces what it names, so to take a game's binding off a button,
|
||||
name that input: `INPUT_TEST_MODE = { SCANCODE.F2 }` in the machine's
|
||||
`controls.cfg` leaves test mode on the keyboard only, whatever button a game
|
||||
put it on.
|
||||
. *The directory above the game's data directory,* for files shared by every
|
||||
game using that data directory.
|
||||
. *The game's data directory.* Your own files for one game, which win over
|
||||
everything else.
|
||||
|
||||
Without `--datadir` the data directory can be the game's own directory, in
|
||||
which case the file there is read once, as the game's. `--keymapfile` replaces
|
||||
the whole search for `controls.cfg` with one named file.
|
||||
|
||||
=== What Wins
|
||||
|
||||
. The built in default.
|
||||
. The settings file.
|
||||
|
|
@ -601,8 +678,8 @@ Singe [OPTIONS] gameName
|
|||
----
|
||||
|
||||
The game name is the only required argument. It may be a `.singe` script, a
|
||||
directory containing a script of the same name (`ActionMax` finds
|
||||
`ActionMax/ActionMax.singe`), or a packed game (`DLe.game` runs the first
|
||||
directory containing a script of the same name (`MyGame` finds
|
||||
`MyGame/MyGame.singe`), or a packed game (`MyGame.game` runs the first
|
||||
entry of the `games.dat` inside it, `--entry=N` another). A script or directory
|
||||
launched this way also runs with its `games.dat` entry when one names it (see
|
||||
xref:Reference.adoc#gamesdat[games.dat]), so a game plays the same from the command line as from
|
||||
|
|
@ -610,79 +687,148 @@ the menu. For a laserdisc game (`--disc`) with no
|
|||
`--framefile`, Singe looks for a video next to the script with the same base
|
||||
name and any extension FFmpeg can demux, then for a `.txt` framefile.
|
||||
|
||||
The options are grouped by what they do.
|
||||
|
||||
==== The Game and Its Files
|
||||
|
||||
Which game runs, where it and its data live, and what it is played on.
|
||||
|
||||
[cols="1,2",options="header"]
|
||||
|===
|
||||
| Option | Purpose
|
||||
| `-A`, `--audiodelay=MS` | Compensate for audio that is heard `MS` milliseconds later than the engine can measure (negative when it is heard early), `-1000` to `1000`. See xref:Reference.adoc#audiosync[Audio Sync].
|
||||
| `-a`, `--aspect=N:D` | Force the aspect ratio used to pick a window size (`4:3`, `16:9`, `16:10`).
|
||||
| `-B`, `--bezel=FILENAME` | Draw cabinet artwork around the picture. The file is looked for in the `bezels` folder of the game directory (a packed game may carry its own) and then in the `bezels` folder of the data directory; `--bezeldir` names a different folder. Any format SDL_image reads will do, and transparency is kept. A sidecar beside it says where the picture goes inside it; with none, the picture keeps the whole window and `--shiftx`, `--shifty` and `--scalefactor` place it. Artwork that cannot be found ends the program. See <<bezels,Bezels>>.
|
||||
| `-b`, `--scalefactor=PERCENT` | Shrink the picture about its centre to `PERCENT` (`25` to `100`) of the window for displays that lose their edges to overscan. Mouse and light gun positions follow the shrunken picture. A script may change it while the game runs with `vldpSetScale`. Combines with `--shiftx`, `--shifty`, `--rotate` and `--sindengun`; see <<videorect,The Video Rectangle>>.
|
||||
| `-c`, `--showcalculated` | Print the frame ranges of every segment of a framefile, for debugging.
|
||||
| `-C`, `--canvas=WxH` | World size for a game without a disc, default 720x480. Ignored when there is a disc.
|
||||
| `-D`, `--disc` | Play a laserdisc video: the one named by `--framefile`, or the video found next to the script. Implied by `--framefile`. Without it a video next to the script is reported and ignored.
|
||||
| `-d`, `--datadir=PATHNAME` | Directory for everything Singe writes: video indexes, `trace.txt`, screenshots, saves, the menu's `menu.dat`. A subdirectory named for the game's directory is created inside it (for a packed game, one named for the database and then for the script's directory inside it). Defaults to `data` in the game directory. Singe 2.x wrote beside the game instead; game directories may be read only, so nothing is written there any more.
|
||||
| `-D`, `--disc` | Play a laserdisc video: the one named by `--framefile`, or the video found next to the script. Implied by `--framefile`. Without it a video next to the script is reported and ignored.
|
||||
| `-E`, `--entry=N` | Run the Nth entry of the `games.dat` inside a `.game` file (default 1). See xref:Reference.adoc#singlefile[Single-File Games].
|
||||
| `-e`, `--volume_nonvldp=PERCENT` | Sound effect and extra video volume, `0` to `100`.
|
||||
| `-F`, `--bezelflip` | Draw the bezel artwork in front of the picture instead of behind it, which is what Hypseus does with its own bezels; the artwork's transparency is then the hole the game shows through. Without it the artwork is behind everything and the picture covers its cutout. A script chooses whether the overlay and the GUIs stay above the artwork with `setOverlayOnTop`. See <<bezels,Bezels>>.
|
||||
| `-f`, `--fullscreen` | Exclusive full screen at the desktop resolution.
|
||||
| `-G`, `--gamedir=PATHNAME` | The directory holding the games, the packed `.game` files and the `Singe` support folder, when the executable lives somewhere else. It becomes the working directory, so the game name, a relative `--datadir` and the packer's names all count from it; absolute paths work as they are. Defaults to the current directory.
|
||||
| `-g`, `--sindengun='PARAMS'` | Draw the border a Sinden light gun tracks and map mouse and light gun positions into the picture inside it: the black ring, when one is given, runs along the edge of the video, the white ring sits inside it, and the game is drawn in the middle. `PARAMS` is one quoted list of numbers: `WW` (white border width in video pixels); `WW WB` (white width, then a black border outside it); `RW GW BW WW` (a custom border color and width); `RW GW BW WW WB` (the same plus a black width); or `RW GW BW WW RB GB BB WB` (custom colors and widths for both). A border that leaves no room for the game (twice the white and black widths together reaching the smaller side of the video), or a negative width, ends the program with a message. The rings are drawn around whatever `--shiftx`, `--shifty` and `--scalefactor` left, so a border stays a border however the picture is placed; see <<videorect,The Video Rectangle>>.
|
||||
| `-H`, `--softwarevideo` | Decode video in software even when the platform offers a hardware decoder (VA-API or VDPAU on Linux, the V4L2 decoder on the Pi, D3D11VA on Windows, VideoToolbox on macOS). Use it to rule the hardware path in or out when a video misbehaves; the program trace says which decoder is in use.
|
||||
| `-h`, `--help` | Show the option summary and exit.
|
||||
| `-I`, `--bezeldir=PATHNAME` | The folder holding `--bezel`'s artwork, in place of `bezels`. Relative to the game directory, or absolute. Only that folder is searched. See <<bezels,Bezels>>.
|
||||
| `-k`, `--nologos` | Skip the splash screens.
|
||||
| `-l`, `--volume_vldp=PERCENT` | Laserdisc volume, `0` to `100`.
|
||||
| `-m`, `--nomouse` | Disable mouse and lightgun input entirely.
|
||||
| `-n`, `--nocrosshair` | Ask the game not to draw its own crosshair. Exposed to scripts as `singeWantsCrosshairs()`.
|
||||
| `-o`, `--audio=TRACK` | Default audio track (zero based) for videos with several, such as multi-language releases.
|
||||
| `-P`, `--pack=DIRECTORY` | Pack the game in `DIRECTORY` into the database named after the options, then exit. See xref:Reference.adoc#singlefile[Single-File Games].
|
||||
| `-p`, `--program` | Trace engine activity to the console and to `trace.txt` in the data directory. The file opens with a header describing the machine; see xref:Reference.adoc#traceheader[The Trace Header].
|
||||
| `-S`, `--sindenedge=WHERE` | Where the Sinden border sits: `video` draws it around the picture, `window` at the edge of the screen. Without the option a bezel decides: artwork around the picture puts the border at the window's edge, because the gun's camera sees the whole screen, and with no artwork it goes around the picture as it always has. At the window's edge the two rings are drawn as rings, last of all, so the artwork between them and the picture stays visible. See <<bezels,Bezels>>.
|
||||
| `-s`, `--nosound` | Mute all audio.
|
||||
| `-T`, `--patch=GAME.game` | Replace files in the packed game from the directory or patch database named after the options, then exit.
|
||||
| `-t`, `--trace` | Trace every Lua API call, with the script line that made it, to the console and to `trace.txt`. The file opens with a header describing the machine; see xref:Reference.adoc#traceheader[The Trace Header].
|
||||
| `-r`, `--rotate=DEGREES` | Turn the whole presentation clockwise by `0`, `90`, `180` or `270` degrees, for a cabinet whose panel is mounted on its side. The video, the overlay, the GUIs, the 3D scene and the particles all turn together, a quarter turn swaps the shape of the picture so a portrait window is filled, and mouse and light gun positions are turned back so a click still lands where it is drawn. Only the four right angles are accepted. A script may change it with `vldpSetRotate`.
|
||||
| `-R`, `--reload` | Watch the game's loose script files and run the game again from scratch when one changes; `F5` does the same on demand. A script that fails to compile stays watched, so saving the fix reloads it. For working on a game; see Reloading While You Work.
|
||||
| `-U`, `--unpack=GAME.game` | Write the packed game's files into the directory named after the options, then exit. A database whose stored names would land outside that directory is refused.
|
||||
| `-u`, `--stretch` | Stretch the video to fill the window instead of keeping its aspect ratio.
|
||||
| `-v`, `--framefile=FILENAME` | Video file or framefile to use instead of the one found next to the script.
|
||||
| `-G`, `--gamedir=PATHNAME` | The directory holding the games, the packed `.game` files and the `Singe` support folder, when the executable lives somewhere else. It becomes the working directory, so the game name, a relative `--datadir` and the packer's names all count from it; absolute paths work as they are. Defaults to the current directory.
|
||||
| `-c`, `--showcalculated` | Print the frame ranges of every segment of a framefile, for debugging.
|
||||
|===
|
||||
|
||||
==== Display
|
||||
|
||||
The window and how the picture fills it.
|
||||
|
||||
[cols="1,2",options="header"]
|
||||
|===
|
||||
| Option | Purpose
|
||||
| `-a`, `--aspect=N:D` | Force the aspect ratio used to pick a window size (`4:3`, `16:9`, `16:10`).
|
||||
| `-f`, `--fullscreen` | Exclusive full screen at the desktop resolution.
|
||||
| `-w`, `--fullscreen_window` | Borderless window covering the desktop.
|
||||
| `-X`, `--shiftx=PERCENT` | Move the picture horizontally inside the room `--scalefactor` left it, `-100` (hard against the left) to `100` (hard against the right), `0` centred. As in Hypseus, the shift is a percentage of that room and nothing more, so at `--scalefactor=100` there is no room and the shift does nothing: reduce the scale first. See <<videorect,The Video Rectangle>>.
|
||||
| `-x`, `--xresolution=VALUE` | Window width. The height is taken from the matching entry of the built in resolution table when omitted.
|
||||
| `-Y`, `--shifty=PERCENT` | Move the picture vertically inside the room `--scalefactor` left it, `-100` (hard against the top) to `100` (hard against the bottom), `0` centred. It does nothing at full scale, for the same reason. See <<videorect,The Video Rectangle>>.
|
||||
| `-y`, `--yresolution=VALUE` | Window height.
|
||||
| `-z`, `--noconsole` | Print nothing (and open no console window on Windows).
|
||||
| `--absolutes_only` | Keep only the mice that report an absolute position, which is what a real light gun does and an ordinary mouse does not. ManyMouse cannot be asked what a device is, so a device counts as absolute once it has reported an absolute position and not before; see <<mousedevices,Mice, Guns, and Who Chooses>>. Hypseus writes it `-absolutes-only`; the name here uses an underscore because a settings file key has to be a Lua name. Default: off.
|
||||
| `--altaudio=SUFFIX` | Play `<base><SUFFIX>.ogg` beside the disc's video instead of the audio inside it, for a release whose other languages ship as separate files: `--altaudio=-es` next to `lair.m2v` plays `lair-es.ogg`. Every segment of a framefile is switched together. A file that is not there leaves the game's own audio playing and prints a warning. The `AUDIO_SUFFIX` key in `games.dat` does the same for one game, and a script changes it while running with `discAudioSuffix`. Default: none.
|
||||
| `--apiversion` | Print one machine readable line describing this build to standard output and exit, for front ends. See <<apiversion,The Version Line>>. Nothing else is printed.
|
||||
| `--deinterlace=MODE` | What an interlaced picture gets: `auto` (the default) deinterlaces only the frames the file marks interlaced, `on` deinterlaces every frame for a file whose flags are wrong, and `off` never touches the picture. A laserdisc held interlaced fields and a rip that kept them combs on a progressive display; a rip that was deinterlaced when it was made needs nothing here. One picture comes out for each one that goes in, so frame numbers never move. Default: `auto`.
|
||||
| `--deterministic[=MS]` | For testing only: ignore real time and run the whole engine on a virtual clock that moves `MS` milliseconds every frame, so the same frame number always means the same moment. The disc follows the virtual clock too, its frames lasting the time they are worth, and the random generators are seeded from the same number, so a run repeats to the pixel. The value is optional and is both the step and the seed, `1` to `1000`. Default when given without one: `15`, the frame time the engine's own rate implies. Audio and pacing are meaningless in this mode; see xref:Reference.adoc#deterministic[Deterministic Test Mode]. Default: off.
|
||||
| `--fvalue=NUMBER` | One number handed from the launcher to the game, which reads it with `getFValue()`. Singe does nothing with it. `0` to `100000`, kept to three decimals as Hypseus keeps it. Default: `0`.
|
||||
| `--gamepad_reorder=DIGITS` | Which physical pad fills which gamepad slot, as enumeration positions counting from `0`, one for each slot in turn: `--gamepad_reorder=10` makes the second pad found player one and the first player two. Written as bare digits (`3210`) or separated by commas or spaces, as Hypseus writes it; a repeated position is refused. Positions not named fill the slots that are left, in the order SDL found them, and a position with no pad behind it leaves its slot empty. Default: SDL's own order.
|
||||
| `--haptic=STEP` | The strongest rumble step a game may use, `0` to `4`. `0` turns rumble off altogether, and a lower number quietens a game that asks for more: `controllerDoRumble` never rumbles harder than this. Default: `4`, which is every step a game asks for.
|
||||
| `--idleexit=SECONDS` | Quit after `SECONDS` with no input of any kind -- no key, no button, no pad axis, no mouse. For an attract cabinet that should hand the screen back. `0` never quits. `0` to `86400`. Default: `0`.
|
||||
| `--joymouse=BOOLEAN` | Let the left stick of gamepad slot `0` drive the mouse cursor, for a light gun game on a cabinet with no mouse. `--joymouse=false` is Hypseus's `-nojoymouse`, and a script sets it with `joyMouseEnable()`. See <<joymouse,Driving the Mouse With a Stick>>. Default: `false`, where Hypseus's default is on -- the shipped `controls.cfg` already binds that stick to the four directions.
|
||||
| `--js_range=SPEED` | How fast that stick drives it, `1` to `20`, in video pixels a frame at full deflection. Default: `5`, as in Hypseus.
|
||||
| `--keymapfile=FILENAME` | Read the control mappings from this file instead of searching the four places for `controls.cfg`, so a front end can be certain which mappings a game got. The built in defaults still apply underneath it, so a partial file still works, and a file that is not there ends the program. Default: the four place search, described in <<controls,Customizing the Controls>>.
|
||||
| `--linearscale=BOOLEAN` | Smooth the overlay as the window scales it (`true`) or take the nearest pixel (`false`, which keeps pixel art crisp). Takes `true`, `false`, `yes`, `no`, `on`, `off`, `1` or `0`. A script changes it while running with `setOverlayLinearScale`. Default: `true`, which is what every Singe release has drawn; Hypseus's own default is the opposite.
|
||||
| `--manymouse` | Tell up to four mice apart whatever the game asks for, which a two gun cabinet needs from a game that never calls `mouseSetMode(MOUSE_MANY)` itself. The user's choice outranks the game's: `mouseSetMode(MOUSE_SINGLE)` no longer switches it off. Default: off, and the game decides.
|
||||
| `--mapjoysticks=BOOLEAN` | Write a gamepad mapping for a device SDL does not recognise, so an arcade encoder, a spinner, a wheel or a flight stick is opened as `GAMEPAD_0` to `GAMEPAD_3` like anything else. `false` leaves such a device unopened, as earlier releases did. See <<devices,Every Device Is a Gamepad>>. Default: `true`.
|
||||
| `--monochrome` | Start with the disc picture in grey. The overlay, the GUIs and the 3D scene keep their colour; a script does the same with `vldpSetMonochrome`, and undoes it. Default: off.
|
||||
| `--nogamepad` | Ignore every gamepad, the counterpart of `--nomouse`. None is opened and no pad event reaches the game, so a stuck arcade encoder cannot press anything. Default: off.
|
||||
| `--no_update_check` | Never ask the service whether a newer Singe exists. The menu otherwise looks once a day and shows what it found under the game's details; the Engine Update tool still works by hand. Default: off, so the daily look happens.
|
||||
| `--refresh` | Set the `Singe` support folder aside as `old-Singe-<version>`, named for the version that wrote it, and write a fresh one. For upgrading: a file an older release shipped and this one does not is left in the folder that was set aside rather than sitting in the live one. Nothing is deleted, and a second run sets its own aside as `old-Singe-<version>-1`. The Engine Update tool passes this to the Singe it restarts into.
|
||||
| `-k`, `--nologos` | Skip the splash screens.
|
||||
| `-r`, `--rotate=DEGREES` | Turn the whole presentation clockwise by `0`, `90`, `180` or `270` degrees, for a cabinet whose panel is mounted on its side. The video, the overlay, the GUIs, the 3D scene and the particles all turn together, a quarter turn swaps the shape of the picture so a portrait window is filled, and mouse and light gun positions are turned back so a click still lands where it is drawn. Only the four right angles are accepted. A script may change it with `vldpSetRotate`.
|
||||
| `--screen=N` | Open the window on display `N`, counting from `1` as Hypseus counts them. A number larger than the number of displays lists the displays that are there and exits. Default: the primary display.
|
||||
| `-u`, `--stretch` | Stretch the video to fill the window instead of keeping its aspect ratio.
|
||||
| `-x`, `--xresolution=VALUE` | Window width. The height is taken from the matching entry of the built in resolution table when omitted. A size larger than the display -- asked for here or by a game's `games.dat` -- is shrunk to fit it, keeping its shape, and the trace says so.
|
||||
| `-y`, `--yresolution=VALUE` | Window height.
|
||||
|===
|
||||
|
||||
==== Picture Placement, Bezels and Guns
|
||||
|
||||
For a cabinet: where the picture sits, the artwork around it, and the border a Sinden gun tracks. See <<cabinet,Setting Up a Cabinet>>.
|
||||
|
||||
[cols="1,2",options="header"]
|
||||
|===
|
||||
| Option | Purpose
|
||||
| `-B`, `--bezel=FILENAME` | Draw cabinet artwork around the picture. The file is looked for in the `bezels` folder of the game directory (a packed game may carry its own) and then in the `bezels` folder of the data directory; `--bezeldir` names a different folder. Any format SDL_image reads will do, and transparency is kept. A sidecar beside it says where the picture goes inside it; with none, the picture keeps the whole window and `--shiftx`, `--shifty` and `--scalefactor` place it. Artwork that cannot be found ends the program. See <<bezels,Bezels>>.
|
||||
| `-I`, `--bezeldir=PATHNAME` | The folder holding `--bezel`'s artwork, in place of `bezels`. Relative to the game directory, or absolute. Only that folder is searched. See <<bezels,Bezels>>.
|
||||
| `-F`, `--bezelflip` | Draw the bezel artwork in front of the picture instead of behind it, which is what Hypseus does with its own bezels; the artwork's transparency is then the hole the game shows through. Without it the artwork is behind everything and the picture covers its cutout. A script chooses whether the overlay and the GUIs stay above the artwork with `setOverlayOnTop`. See <<bezels,Bezels>>.
|
||||
| `-b`, `--scalefactor=PERCENT` | Shrink the picture about its centre to `PERCENT` (`25` to `100`) of the window for displays that lose their edges to overscan. Mouse and light gun positions follow the shrunken picture. A script may change it while the game runs with `vldpSetScale`. Combines with `--shiftx`, `--shifty`, `--rotate` and `--sindengun`; see <<videorect,The Video Rectangle>>.
|
||||
| `-X`, `--shiftx=PERCENT` | Move the picture horizontally inside the room `--scalefactor` left it, `-100` (hard against the left) to `100` (hard against the right), `0` centred. As in Hypseus, the shift is a percentage of that room and nothing more, so at `--scalefactor=100` there is no room and the shift does nothing: reduce the scale first. See <<videorect,The Video Rectangle>>.
|
||||
| `-Y`, `--shifty=PERCENT` | Move the picture vertically inside the room `--scalefactor` left it, `-100` (hard against the top) to `100` (hard against the bottom), `0` centred. It does nothing at full scale, for the same reason. See <<videorect,The Video Rectangle>>.
|
||||
| `-S`, `--sindenedge=WHERE` | Where the Sinden border sits: `video` draws it around the picture, `window` at the edge of the screen. Without the option a bezel decides: artwork around the picture puts the border at the window's edge, because the gun's camera sees the whole screen, and with no artwork it goes around the picture as it always has. At the window's edge the two rings are drawn as rings, last of all, so the artwork between them and the picture stays visible. See <<bezels,Bezels>>.
|
||||
| `-g`, `--sindengun='PARAMS'` | Draw the border a Sinden light gun tracks and map mouse and light gun positions into the picture inside it: the black ring, when one is given, runs along the edge of the video, the white ring sits inside it, and the game is drawn in the middle. `PARAMS` is one quoted list of numbers: `WW` (white border width in video pixels); `WW WB` (white width, then a black border outside it); `RW GW BW WW` (a custom border color and width); `RW GW BW WW WB` (the same plus a black width); or `RW GW BW WW RB GB BB WB` (custom colors and widths for both). A border that leaves no room for the game (twice the white and black widths together reaching the smaller side of the video), or a negative width, ends the program with a message. The rings are drawn around whatever `--shiftx`, `--shifty` and `--scalefactor` left, so a border stays a border however the picture is placed; see <<videorect,The Video Rectangle>>.
|
||||
|===
|
||||
|
||||
==== Sound
|
||||
|
||||
[cols="1,2",options="header"]
|
||||
|===
|
||||
| Option | Purpose
|
||||
| `--altaudio=SUFFIX` | Play `<base><SUFFIX>.ogg` beside the disc's video instead of the audio inside it, for a release whose other languages ship as separate files: `--altaudio=-es` next to `game.m2v` plays `game-es.ogg`. Every segment of a framefile is switched together. A file that is not there leaves the game's own audio playing and prints a warning. The `AUDIO_SUFFIX` key in `games.dat` does the same for one game, and a script changes it while running with `discAudioSuffix`. Default: none.
|
||||
| `-o`, `--audio=TRACK` | Default audio track (zero based) for videos with several, such as multi-language releases.
|
||||
| `-A`, `--audiodelay=MS` | Compensate for audio that is heard `MS` milliseconds later than the engine can measure (negative when it is heard early), `-1000` to `1000`. See xref:Reference.adoc#audiosync[Audio Sync].
|
||||
| `-s`, `--nosound` | Mute all audio.
|
||||
| `--soundfont=FILE` | The SoundFont (`.sf2`) MIDI files are synthesised with. Singe ships none, because a good one is tens of megabytes for a format almost no game uses. Without this option it looks for `Singe/soundfont.sf2` -- so a packed game can carry its own -- and then in the places a distribution installs one. The `SoundFont:` line of the trace header says which was used, or that none was found. See xref:Reference.adoc#midi[MIDI]. Default: the search.
|
||||
| `--startsilent` | Start muted and stay muted until the first input of any kind, then play at the configured volumes. For an attract cabinet in a quiet room. `--nosound` outranks it. Default: off.
|
||||
| `-e`, `--volume_nonvldp=PERCENT` | Sound effect and extra video volume, `0` to `100`.
|
||||
| `-l`, `--volume_vldp=PERCENT` | Laserdisc volume, `0` to `100`.
|
||||
|===
|
||||
|
||||
==== Input
|
||||
|
||||
See <<controls,Controls>>.
|
||||
|
||||
[cols="1,2",options="header"]
|
||||
|===
|
||||
| Option | Purpose
|
||||
| `--absolutes_only` | Keep only the mice that report an absolute position, which is what a real light gun does and an ordinary mouse does not. ManyMouse cannot be asked what a device is, so a device counts as absolute once it has reported an absolute position and not before; see <<mousedevices,Mice, Guns, and Who Chooses>>. Hypseus writes it `-absolutes-only`; the name here uses an underscore because a settings file key has to be a Lua name. Default: off.
|
||||
| `--gamepad_reorder=DIGITS` | Which physical pad fills which gamepad slot, as enumeration positions counting from `0`, one for each slot in turn: `--gamepad_reorder=10` makes the second pad found player one and the first player two. Written as bare digits (`3210`) or separated by commas or spaces, as Hypseus writes it; a repeated position is refused. Positions not named fill the slots that are left, in the order SDL found them, and a position with no pad behind it leaves its slot empty. Default: SDL's own order.
|
||||
| `--haptic=STEP` | The strongest rumble step a game may use, `0` to `4`. `0` turns rumble off altogether, and a lower number quietens a game that asks for more: `controllerDoRumble` never rumbles harder than this. Default: `4`, which is every step a game asks for.
|
||||
| `--joymouse=BOOLEAN` | Let the left stick of gamepad slot `0` drive the mouse cursor, for a light gun game on a cabinet with no mouse. `--joymouse=false` is Hypseus's `-nojoymouse`, and a script sets it with `joyMouseEnable()`. See <<joymouse,Driving the Mouse With a Stick>>. Default: `false`, where Hypseus's default is on -- the shipped `controls.cfg` already binds that stick to the four directions.
|
||||
| `--js_range=SPEED` | How fast that stick drives it, `1` to `20`, in video pixels a frame at full deflection. Default: `5`, as in Hypseus.
|
||||
| `--keymapfile=FILENAME` | Read the control mappings from this file instead of searching the four places for `controls.cfg`, so a front end can be certain which mappings a game got. The built in defaults still apply underneath it, so a partial file still works, and a file that is not there ends the program. Default: the four place search, described in <<controls,Controls>>.
|
||||
| `--manymouse` | Tell up to four mice apart whatever the game asks for, which a two gun cabinet needs from a game that never calls `mouseSetMode(MOUSE_MANY)` itself. The user's choice outranks the game's: `mouseSetMode(MOUSE_SINGLE)` no longer switches it off. Default: off, and the game decides.
|
||||
| `--mapjoysticks=BOOLEAN` | Write a gamepad mapping for a device SDL does not recognise, so an arcade encoder, a spinner, a wheel or a flight stick is opened as `GAMEPAD_0` to `GAMEPAD_3` like anything else. `false` leaves such a device unopened, as earlier releases did. See <<devices,Every Device Is a Gamepad>>. Default: `true`.
|
||||
| `-n`, `--nocrosshair` | Ask the game not to draw its own crosshair. Exposed to scripts as `singeWantsCrosshairs()`.
|
||||
| `--nogamepad` | Ignore every gamepad, the counterpart of `--nomouse`. None is opened and no pad event reaches the game, so a stuck arcade encoder cannot press anything. Default: off.
|
||||
| `-m`, `--nomouse` | Disable mouse and lightgun input entirely.
|
||||
| `--quitchord=BUTTONS` | Buttons on the first gamepad that quit when held together, whatever the game maps and even when it has turned its quit key off: the way out on a handheld with no keyboard. A game quits back to the menu it was started from, and the menu quits Singe. Two to four of SDL's gamepad button names joined by `+`, such as `back+start` (Select and Start); the names are `a`, `b`, `x`, `y`, `back`, `guide`, `start`, `leftstick`, `rightstick`, `leftshoulder`, `rightshoulder`, `dpup`, `dpdown`, `dpleft` and `dpright`, as the pad's mapping gives them, and `--program` traces the name of every button pressed. A press of one of the chord's buttons waits a quarter of a second for the rest before it does what it normally does, so Start does not launch a game from the menu on the way to quitting it. Belongs in the machine's `settings.cfg`. Default: none.
|
||||
| `--trigger_threshold=PERCENT` | How far an analogue trigger must travel before it counts as a button, as a per cent of full travel -- Hypseus's unit, whose own default is `99.5`. `0` leaves the triggers on `DEAD_ZONE`, which is what Singe has always given them. Wins over `TRIGGER_THRESHOLD` in `controls.cfg`; `DEAD_ZONES` wins over both. `0` to `100`; see <<deadzones,Dead Zones and Triggers>>. Default: `0`.
|
||||
| `--xratio=FACTOR` | The horizontal scale a light gun game reads with `ratioGetX()` to stretch its own gun coordinates for a display whose shape does not match the video. Singe does nothing with it; the game does the arithmetic, exactly as in Hypseus. `0` to `100`, kept to two decimals as Hypseus keeps it. Default: `0`, which every game that reads it treats as `1`.
|
||||
| `--yratio=FACTOR` | The same for `ratioGetY()`. Default: `0`.
|
||||
|===
|
||||
|
||||
==== Video Decoding and Performance
|
||||
|
||||
Mostly for small machines; see <<handhelds,Handhelds and Small Machines>>.
|
||||
|
||||
[cols="1,2",options="header"]
|
||||
|===
|
||||
| Option | Purpose
|
||||
| `--deinterlace=MODE` | What an interlaced picture gets: `auto` (the default) deinterlaces only the frames the file marks interlaced, `on` deinterlaces every frame for a file whose flags are wrong, and `off` never touches the picture. A laserdisc held interlaced fields and a rip that kept them combs on a progressive display; a rip that was deinterlaced when it was made needs nothing here. One picture comes out for each one that goes in, so frame numbers never move. Default: `auto`.
|
||||
| `--monochrome` | Start with the disc picture in grey. The overlay, the GUIs and the 3D scene keep their colour; a script does the same with `vldpSetMonochrome`, and undoes it. Default: off.
|
||||
| `--scenequality=LEVEL` | How much the 3D scene draws: `high`, everything a game asks for, or `low` for a small GPU -- a handheld or an early Raspberry Pi -- which draws the scene at half resolution each way and leaves off antialiasing and bloom whatever the game asks. The 3D picture is softer and bright things have no glow, and it runs much faster where the GPU was the limit; the disc, the overlay and the GUIs are untouched. Belongs in the machine's `settings.cfg` rather than on a game's command line, since it describes the machine. Default: `high`.
|
||||
| `-H`, `--softwarevideo` | Decode video in software even when the platform offers a hardware decoder (VA-API or VDPAU on Linux, the V4L2 decoder on the Pi, D3D11VA on Windows, VideoToolbox on macOS). Use it to rule the hardware path in or out when a video misbehaves; the program trace says which decoder is in use.
|
||||
| `--zerocopy=BOOLEAN` | Hand hardware decoded video frames straight to the GPU instead of copying them through the CPU: Rockchip's MPP decoder and VA-API on Linux, drawn with OpenGL ES. It saves most of the cost of full screen video on a small machine. Singe falls back to copying by itself when the driver will not take a frame, or when a game reads or changes the picture's pixels, and the program trace says which it is doing; `false` makes it copy from the start, to rule this path in or out. Default: `true`.
|
||||
|===
|
||||
|
||||
==== Cabinets and Front Ends
|
||||
|
||||
[cols="1,2",options="header"]
|
||||
|===
|
||||
| Option | Purpose
|
||||
| `--apiversion` | Print one machine readable line describing this build to standard output and exit, for front ends. See <<apiversion,The Version Line>>. Nothing else is printed.
|
||||
| `--fvalue=NUMBER` | One number handed from the launcher to the game, which reads it with `getFValue()`. Singe does nothing with it. `0` to `100000`, kept to three decimals as Hypseus keeps it. Default: `0`.
|
||||
| `-h`, `--help` | Show the option summary and exit.
|
||||
| `--idleexit=SECONDS` | Quit after `SECONDS` with no input of any kind -- no key, no button, no pad axis, no mouse. For an attract cabinet that should hand the screen back. `0` never quits. `0` to `86400`. Default: `0`.
|
||||
| `--no_update_check` | Never ask the service whether a newer Singe exists. The menu otherwise looks once a day and shows what it found under the game's details; the Engine Update tool still works by hand. Default: off, so the daily look happens.
|
||||
| `-z`, `--noconsole` | Print nothing (and open no console window on Windows).
|
||||
| `--refresh` | Set the `Singe` support folder aside as `old-Singe-<version>`, named for the version that wrote it, and write a fresh one. For upgrading: a file an older release shipped and this one does not is left in the folder that was set aside rather than sitting in the live one. Nothing is deleted, and a second run sets its own aside as `old-Singe-<version>-1`. The Engine Update tool passes this to the Singe it restarts into.
|
||||
|===
|
||||
|
||||
==== Making and Testing Games
|
||||
|
||||
See <<troubleshooting,When Something Goes Wrong>> for the traces.
|
||||
|
||||
[cols="1,2",options="header"]
|
||||
|===
|
||||
| Option | Purpose
|
||||
| `--deterministic[=MS]` | For testing only: ignore real time and run the whole engine on a virtual clock that moves `MS` milliseconds every frame, so the same frame number always means the same moment. The disc follows the virtual clock too, its frames lasting the time they are worth, and the random generators are seeded from the same number, so a run repeats to the pixel. The value is optional and is both the step and the seed, `1` to `1000`. Default when given without one: `15`, the frame time the engine's own rate implies. Audio and pacing are meaningless in this mode; see xref:Reference.adoc#deterministic[Deterministic Test Mode]. Default: off.
|
||||
| `-P`, `--pack=DIRECTORY` | Pack the game in `DIRECTORY` into the database named after the options, then exit. See xref:Reference.adoc#singlefile[Single-File Games].
|
||||
| `-T`, `--patch=GAME.game` | Replace files in the packed game from the directory or patch database named after the options, then exit.
|
||||
| `--perfsync` | With `--program`, wait for the GPU to finish each stage of a frame before timing the next, so the perf lines say which stage the GPU's time belongs to rather than leaving it all in `present`. Everything runs slower while it is on, so it is for measuring, not for playing; see <<perflines,Reading the Perf Lines>>. Default: off.
|
||||
| `-p`, `--program` | Trace engine activity to the console and to `trace.txt` in the data directory. The file opens with a header describing the machine; see xref:Reference.adoc#traceheader[The Trace Header]. Every five seconds it also says where the frame's time went; see <<perflines,Reading the Perf Lines>>.
|
||||
| `-R`, `--reload` | Watch the game's loose script files and run the game again from scratch when one changes; `F5` does the same on demand. A script that fails to compile stays watched, so saving the fix reloads it. For working on a game; see Reloading While You Work.
|
||||
| `-t`, `--trace` | Trace every Lua API call, with the script line that made it, to the console and to `trace.txt`. The file opens with a header describing the machine; see xref:Reference.adoc#traceheader[The Trace Header].
|
||||
| `-U`, `--unpack=GAME.game` | Write the packed game's files into the directory named after the options, then exit. A database whose stored names would land outside that directory is refused.
|
||||
|===
|
||||
|
||||
Options may be given as `-x 640`, `-x640`, or `--xresolution=640`. Numeric
|
||||
values are validated; a bad value prints the usage text and exits. The options
|
||||
at the end of the table have no short letter; the alphabet ran out, and a letter
|
||||
picked at random would help nobody.
|
||||
values are validated; a bad value prints the usage text and exits. Newer options
|
||||
have no short letter; the alphabet ran out, and a letter picked at random would
|
||||
help nobody.
|
||||
|
||||
Anything here except the options that name the game, name a directory, or must
|
||||
act before a file can be found may also be set in a settings file, so a cabinet
|
||||
|
|
@ -690,7 +836,7 @@ does not need a launcher script for every game. See <<settings,The Settings
|
|||
File>>.
|
||||
|
||||
[[apiversion]]
|
||||
==== The Version Line
|
||||
=== The Version Line
|
||||
|
||||
`--apiversion` prints one line and exits with status `0`:
|
||||
|
||||
|
|
@ -711,6 +857,16 @@ This is a contract for front ends and will not change shape:
|
|||
* Later releases may add fields. A reader must ignore keys it does not know
|
||||
rather than treating them as an error.
|
||||
|
||||
[[cabinet]]
|
||||
== Setting Up a Cabinet
|
||||
|
||||
Arcade cabinets need a few things a desktop does not: moving the picture away
|
||||
from edges the monitor hides, turning it for a sideways screen, artwork around
|
||||
it, a border for a Sinden light gun, and a scoreboard. You only need this
|
||||
chapter if you are building a cabinet. Set these once in the cabinet's
|
||||
`settings.cfg`, or use the Display tool in the menu (see
|
||||
<<servicetools,The Service Tools>>) to adjust the picture while you look at it.
|
||||
|
||||
[[videorect]]
|
||||
=== The Video Rectangle
|
||||
|
||||
|
|
@ -872,6 +1028,192 @@ xref:Reference.adoc#guidrawscreen[`guiDrawScreen`].
|
|||
Hypseus's `scoreBezelEnable` takes a second argument choosing among its five
|
||||
scoreboard backends. Singe has one, so the argument is accepted and ignored.
|
||||
|
||||
[[handhelds]]
|
||||
== Handhelds and Small Machines
|
||||
|
||||
Singe runs on the Raspberry Pi and on inexpensive ARM handhelds like the R36S.
|
||||
The same games work there as on a desktop, but a small machine needs a little
|
||||
help to run them well.
|
||||
|
||||
=== What a Small Machine Needs
|
||||
|
||||
Sprites, text, sound, and video work everywhere. The 3D scene and the menu's
|
||||
fancy look need a graphics chip with Vulkan or OpenGL ES 3.1.
|
||||
|
||||
* A Raspberry Pi 4 or later has both, so everything works. A Pi 3 only has
|
||||
OpenGL ES 2.0, which is not enough for 3D. It still runs 2D games.
|
||||
* Most handhelds, the R36S included, have a Mali graphics chip with OpenGL ES
|
||||
3.1, so 3D works there too.
|
||||
* On a machine without either, the menu uses a simpler look that does not need
|
||||
them.
|
||||
|
||||
The first time you run a 3D game, Singe builds its shaders, which can take a
|
||||
few seconds on a small machine. They are saved in the `Singe` folder inside the
|
||||
data directory, so this only happens once.
|
||||
|
||||
=== Settings for a Small Machine
|
||||
|
||||
Put these in the `settings.cfg` beside the Singe binary so every game uses
|
||||
them:
|
||||
|
||||
`scenequality = "low"`::
|
||||
Draws the 3D scene at half size, without antialiasing or bloom. 3D looks a
|
||||
little softer, but runs much faster. Video, sprites, and menus are not
|
||||
affected.
|
||||
|
||||
`zerocopy = true`::
|
||||
This is already the default. Video goes from the hardware decoder straight to
|
||||
the graphics chip without the CPU copying it. This is what lets a handheld play
|
||||
laserdisc video at full speed. Only turn it off if a video misbehaves and you
|
||||
want to rule it out.
|
||||
|
||||
`quitchord = "back+start"`::
|
||||
Handhelds do not have an Escape key. Holding Select and Start quits the game
|
||||
back to the menu, and quits the menu back to your system, no matter how the
|
||||
game is set up.
|
||||
|
||||
=== The R36S and ArkOS
|
||||
|
||||
The R36S and similar handhelds running ArkOS use the Linux `aarch64` build of
|
||||
Singe. Put the Singe binary, its `Singe` folder, `Menu.sh`, and your games in
|
||||
one folder, such as `/roms/singe`. Run `Menu.sh` once so Singe can create its
|
||||
support files.
|
||||
|
||||
To start Singe from the Ports menu, add a script like the one below to
|
||||
`/roms/ports`. These handhelds are slow to speed up their processor and
|
||||
graphics chip, so the script sets them to full speed while Singe runs and puts
|
||||
them back when it exits.
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
#!/bin/bash
|
||||
# /roms/ports/Singe.sh: Singe in the Ports menu.
|
||||
SINGE_DIR=/roms/singe
|
||||
|
||||
[ "$(id -u)" -eq 0 ] && ESUDO="" || ESUDO="sudo"
|
||||
GOVERNORS=()
|
||||
for g in /sys/devices/system/cpu/cpufreq/policy*/scaling_governor /sys/class/devfreq/*/governor; do
|
||||
[ -f "$g" ] || continue
|
||||
GOVERNORS+=("$g:$(cat "$g")")
|
||||
echo performance | $ESUDO tee "$g" > /dev/null 2>&1
|
||||
done
|
||||
restore() {
|
||||
for entry in "${GOVERNORS[@]}"; do
|
||||
echo "${entry##*:}" | $ESUDO tee "${entry%:*}" > /dev/null 2>&1
|
||||
done
|
||||
}
|
||||
trap restore EXIT
|
||||
|
||||
cd "$SINGE_DIR" || exit 1
|
||||
./Menu.sh "$@"
|
||||
----
|
||||
|
||||
ArkOS does not tell games about the Select and Start buttons, so the quit chord
|
||||
will not work until you add this `gamecontrollerdb.txt` beside the Singe binary
|
||||
(see <<gamecontrollerdb,gamecontrollerdb.txt>>):
|
||||
|
||||
----
|
||||
1900bb3e4b4800000011000000010000,GO-Super Gamepad,a:b0,b:b1,x:b2,y:b3,back:b12,start:b13,leftshoulder:b4,rightshoulder:b5,dpup:b8,dpdown:b9,dpleft:b10,dpright:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Linux,
|
||||
----
|
||||
|
||||
If a button does not do what you expect, run Singe once with `--program` and
|
||||
press it. The trace shows the name of each button you press, or its number if
|
||||
the mapping above does not include it.
|
||||
|
||||
Finally, add a `controls.cfg` beside the Singe binary. This one puts coin and
|
||||
start on Select and Start, and keeps a game's test and service menus off the
|
||||
face buttons, where some games put them.
|
||||
|
||||
[source,lua]
|
||||
----
|
||||
INPUT_1P_COIN = { SCANCODE.MAIN_5, SCANCODE.C, GAMEPAD_0.BUTTON_BACK }
|
||||
INPUT_1P_START = { SCANCODE.MAIN_1, GAMEPAD_0.BUTTON_START }
|
||||
INPUT_PAUSE = { SCANCODE.P }
|
||||
INPUT_SCREENSHOT = { SCANCODE.F12, SCANCODE.F11 }
|
||||
INPUT_SERVICE = { SCANCODE.MAIN_9 }
|
||||
INPUT_TEST_MODE = { SCANCODE.F2 }
|
||||
----
|
||||
|
||||
This file wins over the one a game includes (see
|
||||
<<configorder,Where Configuration Files Are Read From>>). Pause and screenshot
|
||||
are moved to the keyboard because Singe normally puts them on Start and
|
||||
Select.
|
||||
|
||||
[[troubleshooting]]
|
||||
== When Something Goes Wrong
|
||||
|
||||
=== The Trace
|
||||
|
||||
Run Singe with `--program` (or `-p`) to see what it is doing. Everything is
|
||||
printed to the console and saved to `trace.txt` in the game's data directory.
|
||||
If you report a problem, include `trace.txt` from a run with `--program`.
|
||||
`--trace` (or `-t`) also logs every Lua call a game makes, which is mostly
|
||||
useful when writing a game.
|
||||
|
||||
If Singe crashes on Linux or macOS, it writes where it crashed to the console
|
||||
and to `trace.txt`.
|
||||
|
||||
`trace.txt` only has Singe's own messages. Messages from the sound system, the
|
||||
video decoder, and the graphics driver only go to the console. When those might
|
||||
be the problem, save the console too. On Linux:
|
||||
|
||||
----
|
||||
./Menu.sh --program &> singe.log
|
||||
----
|
||||
|
||||
[[perflines]]
|
||||
=== Reading the Perf Lines
|
||||
|
||||
With `--program`, every five seconds the trace says where the time went, as
|
||||
averages per trip round the frame loop:
|
||||
|
||||
----
|
||||
Perf: 58.9 frames/s shown, 353 loops; per loop 13.1 ms (worst 82.3): disc 1.1, script 0.1, scene 0.0, gui 0.4, overlay 0.0, compose 0.0, present 11.4, other 0.1
|
||||
Perf script: per loop 0.1 ms of Lua and 0.0 in engine calls it made; 0.0 in engine calls altogether: overlayClear 0.01 (0.8 calls), discGetFrame 0.00 (0.8 calls)
|
||||
Video 3 perf: 23.8 frames/s decoded; per frame 0.5 ms, of which read back 0.0, convert 0.0
|
||||
----
|
||||
|
||||
* `disc` -- advancing the laserdisc video and putting its frame on the GPU.
|
||||
* `script` -- the game's `onOverlayUpdate`, which the `Perf script` line splits
|
||||
into the Lua itself and the engine calls it made, costliest calls first.
|
||||
* `scene` -- drawing the 3D scene.
|
||||
* `gui` -- laying out and drawing the GUI documents.
|
||||
* `overlay` -- sending what changed on the overlay to the GPU.
|
||||
* `compose` -- only with `--perfsync`: the GPU drawing the picture, the
|
||||
overlay and the GUIs together.
|
||||
* `present` -- showing the frame, which includes waiting for the screen's next
|
||||
refresh.
|
||||
* `other` -- everything else: input, sound, physics and the rest.
|
||||
|
||||
A video's own line says how fast it decodes and how much of each frame went on
|
||||
copying it back from the hardware decoder and converting its colours; zero for
|
||||
both means its frames go to the GPU without being copied.
|
||||
|
||||
The GPU works behind the CPU, so without `--perfsync` its time lands wherever
|
||||
the CPU next has to wait for it, which is usually `present`. A large `present`
|
||||
at the display's full rate is only the wait for the refresh; a large `present`
|
||||
below it means the GPU is busy. `--perfsync` makes each stage wait for the GPU
|
||||
before the next starts, so `scene`, `gui`, `overlay` and `compose` include
|
||||
their GPU time and `present` is left with the wait for the refresh. The frame
|
||||
rate drops while it is on, because the CPU and the GPU no longer work at the
|
||||
same time; read the stages against each other, not against the frame rate.
|
||||
|
||||
=== A Button Does Nothing
|
||||
|
||||
Open the Input Test tool in the menu (see <<servicetools,The Service Tools>>)
|
||||
and press the button. If the tool sees it, the game is ignoring it, so check the
|
||||
game's `controls.cfg`. If the tool does not see it, check your own
|
||||
`controls.cfg`, or your controller's mapping in `gamecontrollerdb.txt`.
|
||||
Running with `--program` shows every button as you press it.
|
||||
|
||||
=== Sound and Picture Are Out of Sync
|
||||
|
||||
Some televisions, receivers, and wireless speakers delay the sound. Use the
|
||||
Audio Delay tool in the menu to measure the delay. Singe remembers it for every
|
||||
game. See xref:Reference.adoc#audiosync[Audio Sync] for details.
|
||||
|
||||
For choppy or stuttering sound, see the Frequently Asked Questions.
|
||||
|
||||
== Frequently Asked Questions
|
||||
|
||||
*Why is it named Singe?*
|
||||
|
|
@ -880,17 +1222,18 @@ Singe is the name of the dragon in Dragon's Lair. As Singe (the program) began
|
|||
as an add-on to Daphne (the princess in Dragon's Lair) the Dragon's Lair theme
|
||||
was kept.
|
||||
|
||||
*What is the difference between Daphne, Singe, Hypseus, and Singe 2.xx?*
|
||||
*What is the difference between Daphne, Singe, Hypseus, and Singe 3.xx?*
|
||||
|
||||
Daphne is an actual laserdisc game emulator that can run a handful of classic
|
||||
laser games. Daphne/Singe (Singe 1.xx) is the original version of Singe that
|
||||
was an add-on for the Daphne emulator -- originally shipped as a game DLL for
|
||||
Daphne, then later combined into a single binary with the Daphne emulation
|
||||
features removed. Hypseus-Singe is an enhanced fork of Daphne with Singe 1.xx
|
||||
and some Singe 2.xx support. Finally, Singe 2.xx is an all-new, built-from-
|
||||
scratch upgrade of the original Singe. In short, if you want accuracy, use
|
||||
Daphne or Hypseus if they support emulating the desired game. If it is a Singe
|
||||
game, use Singe 2.xx, or try Hypseus.
|
||||
and some Singe 2.xx support. Singe 2.xx is an all-new, built-from-scratch
|
||||
upgrade of the original Singe, and Singe 3.xx builds on it with 3D, physics,
|
||||
and much more. In short, if you want accuracy, use Daphne or Hypseus if they
|
||||
support emulating the desired game. If it is a Singe game, use Singe, or try
|
||||
Hypseus.
|
||||
|
||||
*Why should we not call it an "emulator"?*
|
||||
|
||||
|
|
@ -900,10 +1243,18 @@ perspective, it is business as usual. Singe, on the other hand, requires that
|
|||
every game be re-implemented (ported) to run on Singe. The end result may be
|
||||
very similar, but how it works is entirely different.
|
||||
|
||||
*Can I run Singe 1.xx games on Singe 2.xx?*
|
||||
*Can I run Singe 1.xx and 2.xx games on Singe 3.xx?*
|
||||
|
||||
Yes, probably. But you likely will not want to. Almost everything from Singe
|
||||
1.xx has been converted and enhanced for Singe 2.xx.
|
||||
Yes, probably. Almost everything from Singe 1.xx has been converted and
|
||||
enhanced for newer versions of Singe, and most Singe 2.xx games run as they
|
||||
are. Games that use the older sprite functions may need a small change; see
|
||||
xref:Reference.adoc#migrating[Migrating from Singe 2.10].
|
||||
|
||||
*Do I need a laserdisc game to use Singe?*
|
||||
|
||||
No. Games do not need a disc at all. They can use sprites, video, 3D, and
|
||||
anything else Singe offers. See xref:Reference.adoc#withoutadisc[Games Without
|
||||
a Disc].
|
||||
|
||||
*Why does the game I just installed not show in the menu?*
|
||||
|
||||
|
|
@ -911,9 +1262,11 @@ Whoever packaged the game failed to include a proper `games.dat` file.
|
|||
|
||||
*Why is HD video slow?*
|
||||
|
||||
Due to the way Singe accesses video files to provide frame seeking, it is
|
||||
unable to offload video decoding to the video card. High definition video
|
||||
requires a lot of CPU.
|
||||
It should not be any more. Singe now uses your video card to decode video when
|
||||
your system supports it, even on the Raspberry Pi and many handhelds. If HD
|
||||
video is still slow, your system may not have hardware video decoding, and
|
||||
then high definition video requires a lot of CPU. Running with `--program`
|
||||
shows which decoder Singe is using.
|
||||
|
||||
*Why does my audio stutter on Windows?*
|
||||
|
||||
|
|
@ -935,7 +1288,7 @@ do not allow you to use stereo, you can use Virtual Audio Cable to fix this:
|
|||
. You should have sound again. Load up Singe and your audio will be working
|
||||
and lag-free.
|
||||
|
||||
*Does 3D work on a Raspberry Pi?*
|
||||
*Does 3D work on a Raspberry Pi or a handheld?*
|
||||
|
||||
On a Raspberry Pi 4 or later, yes. They have both a Vulkan driver and OpenGL
|
||||
ES 3.1, and Singe uses whichever it finds. The Pi 3 has only OpenGL ES 2.0,
|
||||
|
|
@ -943,7 +1296,7 @@ which cannot express the scene's skinning or morph targets, so it runs 2D games
|
|||
(2D physics included) exactly as before and refuses the first 3D call. The Pi 4
|
||||
is the minimum for any game that uses the 3D Scenes chapter. The same applies to
|
||||
the cheap handhelds: a Mali-G31 or G610 has the ES 3.1 the scene needs, whether
|
||||
or not it has Vulkan.
|
||||
or not it has Vulkan. See <<handhelds,Handhelds and Small Machines>>.
|
||||
|
||||
*Why does my audio stutter on the Raspberry Pi?*
|
||||
|
||||
|
|
|
|||
|
|
@ -187,11 +187,11 @@ installing the game is copying the file next to the executable. The menu
|
|||
lists every `.game` file it finds beside the game directories.
|
||||
|
||||
----
|
||||
Singe --pack DLe DLe.game Pack the DLe directory into DLe.game
|
||||
Singe DLe.game Run the first games.dat entry in it
|
||||
Singe --entry=2 DLe.game Run its second entry
|
||||
Singe --unpack DLe.game DLe Write the files back out
|
||||
Singe --patch DLe.game fixes Replace files from a directory (or a patch database)
|
||||
Singe --pack MyGame MyGame.game Pack the MyGame directory into MyGame.game
|
||||
Singe MyGame.game Run the first games.dat entry in it
|
||||
Singe --entry=2 MyGame.game Run its second entry
|
||||
Singe --unpack MyGame.game MyGame Write the files back out
|
||||
Singe --patch MyGame.game fixes Replace files from a directory (or a patch database)
|
||||
----
|
||||
|
||||
Nothing in a game has to change to be packed. Every name a script uses,
|
||||
|
|
@ -203,7 +203,7 @@ exactly as before. For a packed game a name is tried in three places, in
|
|||
this order, and the first hit wins:
|
||||
|
||||
. A loose directory named like the database without its extension
|
||||
(`DLe/` beside `DLe.game`). This is how you keep editing a packed game,
|
||||
(`MyGame/` beside `MyGame.game`). This is how you keep editing a packed game,
|
||||
and how a player mods one: files dropped there beat the packed copies.
|
||||
. The game's data directory, under `files/`. Everything a game writes
|
||||
with `io.output` or `io.open(name, "w")` lands here, so a saved
|
||||
|
|
@ -212,9 +212,9 @@ this order, and the first hit wins:
|
|||
|
||||
Inside a database, names are matched without regard to case, with either
|
||||
path separator, and relative to the game root. A leading component equal to
|
||||
the game's own directory name is ignored, so `DLe/Cfg/game.cfg`,
|
||||
the game's own directory name is ignored, so `MyGame/Cfg/game.cfg`,
|
||||
`Cfg/game.cfg`, and `DIR .. "Cfg/game.cfg"` all find the same file. The
|
||||
name of the database itself (`DLe.game`, with or without a trailing
|
||||
name of the database itself (`MyGame.game`, with or without a trailing
|
||||
separator) is the game root, as `.` is from inside. Names
|
||||
starting with `Singe/` or with the data directory stay on the filesystem;
|
||||
`Singe/Framework.singe` and `singeGetDataPath()` work as always, and such a
|
||||
|
|
@ -268,7 +268,7 @@ patched game, so a patch cannot leave a dangling path behind either.
|
|||
|
||||
`--patch` takes either a directory laid out like the game, whose files
|
||||
replace the packed ones, or a `.patch` file made by `--pack` from such a
|
||||
directory (`Singe --pack fixes DLe-1.1.patch`; a patch needs no
|
||||
directory (`Singe --pack fixes MyGame-1.1.patch`; a patch needs no
|
||||
`games.dat`). Files the patch does not name are left alone. Either way the
|
||||
change is one transaction: the game is intact if it is interrupted. A patch
|
||||
may also carry a `removed(path)` table naming files to delete. A `.patch`
|
||||
|
|
@ -292,15 +292,15 @@ gain. Nothing else in Singe needs this; a game is only ever written by the
|
|||
packer.
|
||||
|
||||
A packed game's data directory is named for the database and then for the
|
||||
script's directory inside it: `DLe/` for a `DLe.game` whose script sits at
|
||||
its root under the data directory (`data/DLe/` by default), which is where the loose
|
||||
`DLe/DLe.singe` has it, so saves and settings are shared between a loose
|
||||
script's directory inside it: `MyGame/` for a `MyGame.game` whose script sits at
|
||||
its root under the data directory (`data/MyGame/` by default), which is where the loose
|
||||
`MyGame/MyGame.singe` has it, so saves and settings are shared between a loose
|
||||
install and a packed one; a script packed in a subdirectory
|
||||
(`Game/Main.singe`) gets `DLe/Game/`. The video index goes there too.
|
||||
(`Game/Main.singe`) gets `MyGame/Game/`. The video index goes there too.
|
||||
|
||||
NOTE: Earlier 3.00 builds gave a packed game whose script sat in a
|
||||
subdirectory the data directory `Game/` under the base, which every archive
|
||||
with that layout shared. Such a game's saves now belong under `DLe/Game/`;
|
||||
with that layout shared. Such a game's saves now belong under `MyGame/Game/`;
|
||||
move them by hand.
|
||||
When Singe runs a game from a database the menu passes `CONTAINER` in the
|
||||
`games.dat` entry it launches; the field is set by the menu, never by hand.
|
||||
|
|
@ -556,26 +556,26 @@ of your game. An example containing multiple games:
|
|||
----
|
||||
GAMES = {
|
||||
{
|
||||
TITLE = ".38 Ambush Alley",
|
||||
SCRIPT = "ActionMax/38AmbushAlley.singe",
|
||||
VIDEO = "ActionMax/frame_38AmbushAlley.txt",
|
||||
TITLE = "Asteroid Patrol",
|
||||
SCRIPT = "Collection/AsteroidPatrol.singe",
|
||||
VIDEO = "Collection/frame_AsteroidPatrol.txt",
|
||||
STRETCH = false,
|
||||
NO_MOUSE = false,
|
||||
RESOLUTION_X = 720,
|
||||
RESOLUTION_Y = 480,
|
||||
SINDEN_GUN = "",
|
||||
GAME_ID = "6f1e7b62-0a4e-4d9c-9b2f-1c7a5e3d8a10",
|
||||
CABINET = "ActionMax/cabinet_38AmbushAlley.png",
|
||||
MARQUEE = "ActionMax/marquee_ActionMax.png",
|
||||
ATTRACT = "ActionMax/video_38AmbushAlley.mkv",
|
||||
CABINET = "Collection/cabinet_AsteroidPatrol.png",
|
||||
MARQUEE = "Collection/marquee_Collection.png",
|
||||
ATTRACT = "Collection/video_AsteroidPatrol.mkv",
|
||||
ATTRACT_START = 3000,
|
||||
ATTRACT_END = 3500,
|
||||
YEAR = 1987,
|
||||
PLATFORM = "ActionMax",
|
||||
DEVELOPER = "Sourcing International, Ltd.",
|
||||
PUBLISHER = "Worlds of Wonder, Inc.",
|
||||
YEAR = 2026,
|
||||
PLATFORM = "Laserdisc",
|
||||
DEVELOPER = "Your Studio",
|
||||
PUBLISHER = "Your Studio",
|
||||
GENRE = "Shooter",
|
||||
DESCRIPTION = "Get your target practice in with real police officers then hit the streets.",
|
||||
DESCRIPTION = "Keep the asteroids away from the station for as long as you can.",
|
||||
CREATOR = "Scott Duensing",
|
||||
SOURCE = "http://kangaroopunch.com"
|
||||
},
|
||||
|
|
@ -601,7 +601,7 @@ A script launched directly, by name or by its directory, gets the same
|
|||
treatment: Singe looks for a `games.dat` in the script's directory and up to
|
||||
three directories above it, and if an entry's `SCRIPT` names the launched
|
||||
script (entry paths count from the directory above the `games.dat`, so
|
||||
`ActionMax/games.dat` names `ActionMax/38AmbushAlley.singe`), that entry's
|
||||
`Collection/games.dat` names `Collection/AsteroidPatrol.singe`), that entry's
|
||||
keys apply. The one difference from a menu launch is precedence: an option
|
||||
typed on the command line (`--framefile`, `--disc`, `--stretch`, `--nomouse`,
|
||||
`--xresolution`, `--yresolution`, `--sindengun`, `--audio`, `--audiodelay`,
|
||||
|
|
@ -639,8 +639,9 @@ document written in RML (an HTML-like markup) and styled by RCSS (a CSS-like
|
|||
style sheet), laid out and drawn by RmlUi, with text wrapping, flexbox,
|
||||
nine-slice panels from your own artwork, transitions, animations, form
|
||||
controls and a focus model a gamepad can drive. A *GUI* is one RmlUi
|
||||
context of a fixed size rendered into its own texture every frame, and that
|
||||
texture goes wherever a texture can: composited over the overlay with
|
||||
context of a fixed size rendered into its own texture, redrawn whenever its
|
||||
picture changes (see <<guirefresh,guiRefresh>>), and that texture goes
|
||||
wherever a texture can: composited over the overlay with
|
||||
`guiDraw`, or shown on a material in the 3D scene with `materialSetGui`, so
|
||||
the same options page works flat on the screen and on the monitor of a
|
||||
modeled arcade cabinet. A GUI needs the GPU device the 3D scene uses, so it
|
||||
|
|
@ -684,9 +685,9 @@ under `Singe/`, and absolute paths.
|
|||
|
||||
The same rule read the other way lets a document that lives under `Singe/`
|
||||
reach a game's files: a relative name resolves from the document's own
|
||||
directory, so from `Singe/Menu.rml` the name `../ActionMax/cabinet.png` is
|
||||
`ActionMax/cabinet.png` in the game directory, and a name under
|
||||
`../DLe.game/` is a file inside that packed game. The bundled menu does
|
||||
directory, so from `Singe/Menu.rml` the name `../Collection/cabinet.png` is
|
||||
`Collection/cabinet.png` in the game directory, and a name under
|
||||
`../MyGame.game/` is a file inside that packed game. The bundled menu does
|
||||
exactly this for the cabinet and marquee art of the selected game, setting
|
||||
an image decorator on the art panel from Lua so the picture is fitted
|
||||
inside it whatever its size:
|
||||
|
|
@ -952,11 +953,13 @@ the script makes them again when it runs; nothing from the `rmlui` object
|
|||
model survives, so do not keep those objects in globals a reload would
|
||||
reuse.
|
||||
|
||||
Every GUI updates and renders into its texture every frame whether or not
|
||||
it is drawn anywhere, so a document keeps running its animations and
|
||||
scripts off screen, and a GUI that is finished with should be deleted
|
||||
rather than merely hidden. The cost is the size of the texture and the
|
||||
complexity of the document: on a desktop a full screen page is cheap, on a
|
||||
Every GUI updates every frame whether or not it is drawn anywhere, so a
|
||||
document keeps running its animations and scripts off screen, and a GUI
|
||||
that is finished with should be deleted rather than merely hidden. It is
|
||||
redrawn into its texture only when its picture changes, so a page or a HUD
|
||||
that stands still costs the GPU almost nothing (see
|
||||
<<guirefresh,guiRefresh>>). The cost of a frame that does change is the
|
||||
size of the texture and the complexity of the document: on a desktop a full screen page is cheap, on a
|
||||
Raspberry Pi keep a GUI to the overlay's size or smaller and prefer one
|
||||
GUI with several documents to several GUIs. Each font face costs an atlas
|
||||
per size it is used at, so a page that uses one face at three sizes pays
|
||||
|
|
@ -964,6 +967,31 @@ for three atlases; a dozen sizes is a dozen. Sixteen GUIs may exist at
|
|||
once, each with thirty-two documents, and sixteen `guiDraw` calls are
|
||||
honored per frame.
|
||||
|
||||
What a document can show, and what it cannot:
|
||||
|
||||
* *Pictures come from files.* Images, sprite sheets and image decorators
|
||||
load PNG and the other formats Singe reads, from disk or from a packed
|
||||
game. A document cannot show a Singe sprite, a video, the 3D scene or
|
||||
anything drawn while the game runs, and a script cannot hand it pixels.
|
||||
The other way round works: a GUI's texture goes onto a 3D material with
|
||||
`materialSetGui`.
|
||||
* *Decorators:* `image`, `text`, `tiled-horizontal`, `tiled-vertical`,
|
||||
`tiled-box`, `ninepatch`, the two-colour `gradient` (and its
|
||||
`horizontal-gradient` and `vertical-gradient` names), and `linear-gradient`,
|
||||
`radial-gradient` and `conic-gradient` with their `repeating-` forms, with at
|
||||
most sixteen colour stops. The custom `shader` decorator is refused.
|
||||
* *Filters:* `opacity`, `blur`, `drop-shadow`, `brightness`, `contrast`,
|
||||
`invert`, `grayscale`, `sepia`, `hue-rotate` and `saturate`. Any other
|
||||
name is ignored with a warning in the trace.
|
||||
* *Layers:* filters, `opacity` below 1, mask images and box shadows each
|
||||
draw through a layer the size of the GUI, and at most eight stack at
|
||||
once; one past that draws without its layer, and so without its effect.
|
||||
* *Images* larger than 64 MB are refused.
|
||||
* *RML and RCSS* are RmlUi's, close to HTML and CSS but not the same: there
|
||||
is flexbox and no CSS grid, and a document is scripted in Lua, through
|
||||
`guiSetHandler` and the `rmlui` object, rather than in JavaScript. RmlUi's
|
||||
own documentation is the authority on what the languages hold.
|
||||
|
||||
==== A Worked Example
|
||||
|
||||
An options page over the disc: a panel from the shipped theme with a name
|
||||
|
|
@ -2336,6 +2364,7 @@ manual: `docs/Forge.adoc` in the source tree, built to `Forge.html` and
|
|||
`Forge.pdf` beside this one.
|
||||
|
||||
|
||||
[[migrating]]
|
||||
=== Migrating from Singe 2.10
|
||||
|
||||
Singe 3.00 moved the sprite handle to the first argument of `spriteDraw`,
|
||||
|
|
@ -2393,6 +2422,11 @@ changed shape, but a few behaviors did:
|
|||
is frozen until the key is pressed again, and `SWITCH_PAUSE` is delivered
|
||||
to the script only when the key has been disabled. It acts on the key
|
||||
press rather than the release. See <<pausing,Pausing>>.
|
||||
* `fontPrint` keeps the strings it renders, and `overlayClear` refills only
|
||||
what was drawn since the last clear, so a HUD reprinted every frame is
|
||||
cheap. Rendering text with `fontToSprite` for performance is deprecated;
|
||||
it still works, and is still the way to get text you can measure, scale,
|
||||
or turn. See <<fonttosprite,fontToSprite>>.
|
||||
|
||||
=== Engine Constants
|
||||
|
||||
|
|
@ -2496,8 +2530,8 @@ of audio arriving early.
|
|||
|
||||
The bundled menu has a calibration screen for that downstream delay. Press the
|
||||
key mapped to `INPUT_SERVICE` (the `9` key by default) on the game list and
|
||||
choose Audio Delay from the service tools (see <<servicetools,The Service
|
||||
Tools>>). The screen is a page of the menu's document: it covers the menu in
|
||||
choose Audio Delay from the service tools (see xref:Manual.adoc#servicetools[The Service
|
||||
Tools]). The screen is a page of the menu's document: it covers the menu in
|
||||
black, shows the delay being tried and the device queue the engine measured,
|
||||
plays a click once a second and turns white for one frame at each flash.
|
||||
Adjust with left and right (10 ms) and up and down (1 ms) until the flash and
|
||||
|
|
@ -2664,19 +2698,6 @@ morph target weights are all driven. See the Animation part of
|
|||
<<scenes3d,3D Scenes>>.
|
||||
|
||||
[#animationgettime]
|
||||
==== allowSocketCall
|
||||
|
||||
[source,text]
|
||||
----
|
||||
allowed = allowSocketCall()
|
||||
----
|
||||
|
||||
Hypseus extension, always `false` in Singe. Hypseus answers whether the user let it send data to a stats server, which its `-noserversend` turns off; the sending itself is commented out there. Singe sends nothing anywhere, so nothing is permitted and the answer never changes. It exists so a game that asks does not stop on a missing function.
|
||||
|
||||
*Returns:* a boolean, always `false`.
|
||||
|
||||
*Since:* 3.00
|
||||
|
||||
==== animationGetTime
|
||||
|
||||
[source,text]
|
||||
|
|
@ -3806,6 +3827,7 @@ met but *where the mover ends up* -- sliding along a wall rather than stopping
|
|||
dead in it -- `require("bump")` and let it do that; it is bundled for exactly
|
||||
that reason.
|
||||
|
||||
[#collidecircles]
|
||||
==== collideCircles
|
||||
|
||||
[source,text]
|
||||
|
|
@ -3825,6 +3847,7 @@ Whether two circles overlap. Cheaper than it looks: nothing takes a square root
|
|||
*Since:* 3.00.
|
||||
*See also:* <<colliderectcircle,collideRectCircle>>
|
||||
|
||||
[#collidepointcircle]
|
||||
==== collidePointCircle
|
||||
|
||||
[source,text]
|
||||
|
|
@ -3843,6 +3866,7 @@ Whether a point falls within a circle. The edge counts as inside.
|
|||
|
||||
*Since:* 3.00.
|
||||
|
||||
[#collidepointpolygon]
|
||||
==== collidePointPolygon
|
||||
|
||||
[source,text]
|
||||
|
|
@ -3877,6 +3901,7 @@ function onInputPressed(key)
|
|||
end
|
||||
----
|
||||
|
||||
[#collidepointrect]
|
||||
==== collidePointRect
|
||||
|
||||
[source,text]
|
||||
|
|
@ -3907,6 +3932,7 @@ if collidePointRect(x, y, enemyX, enemyY, spriteGetWidth(enemy), spriteGetHeight
|
|||
end
|
||||
----
|
||||
|
||||
[#colliderectcircle]
|
||||
==== collideRectCircle
|
||||
|
||||
[source,text]
|
||||
|
|
@ -3925,6 +3951,7 @@ Whether a rectangle and a circle overlap, by finding the rectangle's nearest poi
|
|||
|
||||
*Since:* 3.00.
|
||||
|
||||
[#colliderects]
|
||||
==== collideRects
|
||||
|
||||
[source,text]
|
||||
|
|
@ -3944,6 +3971,7 @@ Whether two rectangles overlap. The one you will reach for most.
|
|||
*Since:* 3.00.
|
||||
*See also:* <<collidepointrect,collidePointRect>>
|
||||
|
||||
[#collidesegments]
|
||||
==== collideSegments
|
||||
|
||||
[source,text]
|
||||
|
|
@ -4285,7 +4313,7 @@ for slot = 0, 3 do
|
|||
end
|
||||
----
|
||||
|
||||
[#controllerhowmany]
|
||||
[#controllergetpadding]
|
||||
==== controllerGetPadding
|
||||
|
||||
[source,text]
|
||||
|
|
@ -4300,6 +4328,7 @@ Hypseus extension, always `0` in Singe. Hypseus adds this offset to a switch cod
|
|||
*Since:* 3.00
|
||||
*See also:* <<controllersetpadding,controllerSetPadding>>
|
||||
|
||||
[#controllerhowmany]
|
||||
==== controllerHowMany
|
||||
|
||||
[source,text]
|
||||
|
|
@ -4307,7 +4336,7 @@ Hypseus extension, always `0` in Singe. Hypseus adds this offset to a switch cod
|
|||
count = controllerHowMany()
|
||||
----
|
||||
|
||||
Hypseus extension. How many gamepads are open, of the four slots Singe has. Since every joystick Singe can open becomes a gamepad (see <<everydeviceisagamepad,Every Device Is a Gamepad>>), an arcade encoder or a spinner is counted here as well.
|
||||
Hypseus extension. How many gamepads are open, of the four slots Singe has. Since every joystick Singe can open becomes a gamepad (see xref:Manual.adoc#devices[Every Device Is a Gamepad]), an arcade encoder or a spinner is counted here as well.
|
||||
|
||||
The count changes as devices are plugged in and out, so read it when you need it rather than once at startup. It says nothing about which slots are filled: a device may leave slot 0 empty while slot 1 is in use, so <<controllerisvalid,`controllerIsValid`>> is still the way to ask about a particular slot.
|
||||
|
||||
|
|
@ -4357,12 +4386,7 @@ function drawAttract()
|
|||
end
|
||||
----
|
||||
|
||||
[#debug]
|
||||
=== Debug
|
||||
|
||||
Two calls for seeing what a script is doing: `debugPrint` writes text, and `lineDraw` draws a line over the 3D scene for one frame (`physicsSetDebug` and `navDraw` draw through the same lines). Printed output goes to the console Singe was started from, so it is visible during development and harmless in a cabinet, where `--noconsole` silences it. When Singe runs with `--trace`, every API call including this one is also written with its arguments to `trace.txt` in the data directory, which is the way to get a permanent record. Command line options are listed in the chapter on running Singe; the ZeroBrane Studio integration in `zbstudio/` offers a debugger when printing is not enough.
|
||||
|
||||
[[traceheader]]
|
||||
[#controllersetpadding]
|
||||
==== controllerSetPadding
|
||||
|
||||
[source,text]
|
||||
|
|
@ -4377,6 +4401,12 @@ Hypseus extension, accepted and ignored. See <<controllergetpadding,controllerGe
|
|||
*Since:* 3.00
|
||||
*See also:* <<controllergetpadding,controllerGetPadding>>
|
||||
|
||||
[#debug]
|
||||
=== Debug
|
||||
|
||||
Two calls for seeing what a script is doing: `debugPrint` writes text, and `lineDraw` draws a line over the 3D scene for one frame (`physicsSetDebug` and `navDraw` draw through the same lines). Printed output goes to the console Singe was started from, so it is visible during development and harmless in a cabinet, where `--noconsole` silences it. When Singe runs with `--trace`, every API call including this one is also written with its arguments to `trace.txt` in the data directory, which is the way to get a permanent record. Command line options are listed in the chapter on running Singe; the ZeroBrane Studio integration in `zbstudio/` offers a debugger when printing is not enough.
|
||||
|
||||
[[traceheader]]
|
||||
==== The Trace Header
|
||||
|
||||
Whichever of `--program` and `--trace` opened it, `trace.txt` begins with a
|
||||
|
|
@ -4386,14 +4416,14 @@ whole into a bug report:
|
|||
----
|
||||
----------------------------------------------------------------
|
||||
Singe: v3.00
|
||||
Command: ./Singe -w -p -d data --screen 2 ActionMax
|
||||
Command: ./Singe -w -p -d data --screen 2 MyGame
|
||||
OS: Linux 6.8.0-124-generic (x86_64)
|
||||
CPU: Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz, 8 cores, 20177 MB
|
||||
Renderer: gpu, GPU driver vulkan
|
||||
Decoder: hardware vaapi or vdpau where the codec allows, software otherwise
|
||||
SDL: built 3.4.16, linked 3.4.16
|
||||
Settings: haptic=0 screen=2 volume_vldp=80
|
||||
Game: ActionMax/ActionMax.singe
|
||||
Game: MyGame/MyGame.singe
|
||||
----------------------------------------------------------------
|
||||
----
|
||||
|
||||
|
|
@ -4479,19 +4509,6 @@ end
|
|||
The *disc* is the main laserdisc video, the one Singe was originally built to emulate. A game has at most one disc, opened at startup from the `VIDEO` named in `games.dat` or from `--disc` / `--framefile` on the command line; a game that names none runs without a disc (see <<withoutadisc,Games Without a Disc>>). All `disc*` functions operate on this single implicit disc and take no handle. Frame numbers are zero based positions in the video file (or in the combined framefile) and are the time reference for every branch decision in a laserdisc game. Without a disc the query functions return `0`, `discGetState` reports `DISC_EJECTED`, and the transport functions trace and do nothing. Extra clips layered on top of the disc are the job of the <<video,Video>> family.
|
||||
|
||||
[#discaudio]
|
||||
==== drawTransparent
|
||||
|
||||
[source,text]
|
||||
----
|
||||
drawTransparent(boolean)
|
||||
----
|
||||
|
||||
Hypseus extension, accepted and ignored. It decides whether Hypseus sets colour key 0 on the sprites loaded after it, which is how that engine carries transparency. Singe carries transparency in the alpha channel, as <<spriteresetcolorkey,`spriteResetColorKey`>> already describes, so there is no key to turn on or off.
|
||||
|
||||
*Returns:* nothing.
|
||||
|
||||
*Since:* 3.00
|
||||
|
||||
==== discAudio
|
||||
|
||||
[source,text]
|
||||
|
|
@ -6091,7 +6108,7 @@ titleFont = fontLoad("Singe/FreeSansBold.ttf", 32)
|
|||
hudFont = fontLoad("Singe/FreeSansBold.ttf", 18)
|
||||
|
||||
fontSelect(titleFont)
|
||||
titleSprite = fontToSprite("SPACE ACE")
|
||||
titleSprite = fontToSprite("GAME OVER")
|
||||
fontSelect(hudFont)
|
||||
----
|
||||
|
||||
|
|
@ -6103,7 +6120,7 @@ fontSelect(hudFont)
|
|||
fontPrint(x, y, text)
|
||||
----
|
||||
|
||||
Renders a string with the selected font, the current quality, and the current foreground color (plus the background color for `FONT_QUALITY_SHADED`), then blits it onto the overlay with its top-left corner at `(x, y)`. Call it from `onOverlayUpdate`. A new text image is rendered and discarded on every call, which is fine for a few lines but expensive for text that never changes; `fontToSprite` renders once and draws many times. With no font selected the script ends with an error.
|
||||
Renders a string with the selected font, the current quality, and the current foreground color (plus the background color for `FONT_QUALITY_SHADED`), then blits it onto the overlay with its top-left corner at `(x, y)`. Call it from `onOverlayUpdate`, every frame if need be: the engine keeps each string it renders, keyed by the font, quality, colors, and text, and a later call asking for the same one only blits it. Strings not printed for about a second are forgotten, and a font's strings go when it is unloaded, so text that changes costs no more than it always did and text that does not is nearly free. A whole page of high scores reprinted every frame is fine. With no font selected the script ends with an error.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
|
|
@ -6150,7 +6167,7 @@ Selects how `fontPrint` and `fontToSprite` rasterize text from now on. The setti
|
|||
----
|
||||
-- Blended text over the video, but a shaded label box for the debug readout.
|
||||
fontQuality(FONT_QUALITY_BLENDED)
|
||||
titleSprite = fontToSprite("DRAGON'S LAIR")
|
||||
titleSprite = fontToSprite("HIGH SCORES")
|
||||
|
||||
colorBackground(0, 0, 0, 255)
|
||||
fontQuality(FONT_QUALITY_SHADED)
|
||||
|
|
@ -6178,7 +6195,7 @@ Makes a loaded font the one used by `fontPrint` and `fontToSprite`. The selectio
|
|||
function onOverlayUpdate()
|
||||
overlayClear()
|
||||
fontSelect(titleFont)
|
||||
fontPrint(TITLE_X, 40, "DRAGON'S LAIR")
|
||||
fontPrint(TITLE_X, 40, "HIGH SCORES")
|
||||
fontSelect(hudFont)
|
||||
fontPrint(8, 8, "Score: " .. score)
|
||||
return OVERLAY_UPDATED
|
||||
|
|
@ -6193,7 +6210,9 @@ end
|
|||
result = fontToSprite(text)
|
||||
----
|
||||
|
||||
Renders a string with the selected font, quality, and colors into a new sprite and returns its handle. The sprite is independent of the font from then on, so it survives `fontUnload`, and it works with every `sprite*` call: draw it, measure it, scale or rotate it, and free it with `spriteUnload`. Render once and draw many times for labels and headings; for a string that changes, render a new sprite and unload the old one. With no font selected the script ends with an error.
|
||||
Renders a string with the selected font, quality, and colors into a new sprite and returns its handle. The sprite is independent of the font from then on, so it survives `fontUnload`, and it works with every `sprite*` call: draw it, measure it, scale or rotate it, and free it with `spriteUnload`. For a string that changes, render a new sprite and unload the old one. With no font selected the script ends with an error.
|
||||
|
||||
NOTE: Rendering text to a sprite for performance is deprecated. `fontPrint` now keeps what it renders, so printing the same string every frame costs about what drawing a sprite made from it does, without the sprite to manage. Use `fontToSprite` when the text has to be a sprite: to scale it, rotate it, flip it, or measure it with `spriteGetWidth` and `spriteGetHeight`.
|
||||
|
||||
*Returns:* integer sprite handle.
|
||||
|
||||
|
|
@ -6203,18 +6222,15 @@ Renders a string with the selected font, quality, and colors into a new sprite a
|
|||
.Example
|
||||
[source,lua]
|
||||
----
|
||||
-- Pre-render the game description, wrapping lines to the panel width.
|
||||
descriptionLines = {}
|
||||
for _, line in ipairs(wrapText(GAME_LIST[selected].DESCRIPTION, TEXT_W)) do
|
||||
table.insert(descriptionLines, fontToSprite(line))
|
||||
end
|
||||
-- A title that spins and grows in: text that has to be a sprite, to be turned and scaled.
|
||||
titleSprite = fontToSprite("GAME OVER")
|
||||
titleFrames = 0
|
||||
|
||||
function onOverlayUpdate()
|
||||
local y = TEXT_Y
|
||||
for _, line in ipairs(descriptionLines) do
|
||||
spriteDraw(line, TEXT_X, y)
|
||||
y = y + spriteGetHeight(line) + 1
|
||||
end
|
||||
titleFrames = titleFrames + 1
|
||||
spriteRotate(titleSprite, (titleFrames * 12) % 360)
|
||||
spriteScale(titleSprite, math.min(titleFrames / 30, 1))
|
||||
spriteDraw(titleSprite, overlayGetWidth() / 2, overlayGetHeight() / 2, true)
|
||||
return OVERLAY_UPDATED
|
||||
end
|
||||
----
|
||||
|
|
@ -6368,20 +6384,6 @@ end
|
|||
----
|
||||
|
||||
[#guigetheight]
|
||||
==== getUserString
|
||||
|
||||
[source,text]
|
||||
----
|
||||
id = getUserString()
|
||||
----
|
||||
|
||||
Hypseus extension, always an empty string. Despite the name it is not a string the user set: Hypseus answers the machine install ID it sends with network statistics, taken from the Windows `MachineGuid` or the Linux machine ID. Singe sends nothing anywhere and will not hand a game a fingerprint of the machine it is running on.
|
||||
|
||||
*Returns:* a string, always empty.
|
||||
|
||||
*Since:* 3.00
|
||||
*See also:* <<allowsocketcall,allowSocketCall>>
|
||||
|
||||
==== guiGetHeight
|
||||
|
||||
[source,text]
|
||||
|
|
@ -6413,7 +6415,7 @@ guiDraw(dialog, x, y)
|
|||
stats = guiGetStats()
|
||||
----
|
||||
|
||||
What rendering every GUI cost in the last completed frame, for tuning documents that use filters, masks and shadows (each of which draws through a layer the size of the GUI). The counters cover all GUIs together.
|
||||
What rendering every GUI cost in the last completed frame, for tuning documents that use filters, masks and shadows (each of which draws through a layer the size of the GUI). The counters cover all GUIs together. They are all zero on a frame where no GUI's picture changed, since nothing was redrawn (see <<guirefresh,guiRefresh>>).
|
||||
|
||||
*Returns:* A table with the fields `drawCalls` (indexed draws, including mask writes, composites and filter passes), `layerPushes` (layers entered for filters, mask images and box shadows), `filterPasses` (full-size passes and blits inside filter chains; a blur costs several), `maskWrites` (draws into the stencil clip mask), `layerBytes` (bytes of layer textures currently allocated; each is the size of its GUI at four bytes a pixel) and `passes` (render passes begun; every layer switch begins one).
|
||||
|
||||
|
|
@ -6590,6 +6592,30 @@ function onOverlayUpdate()
|
|||
end
|
||||
----
|
||||
|
||||
[#guirefresh]
|
||||
==== guiRefresh
|
||||
|
||||
[source,text]
|
||||
----
|
||||
guiRefresh(gui)
|
||||
----
|
||||
|
||||
Draws the GUI again on the next frame even if nothing about it seems to have changed. A GUI is only redrawn when its picture changes: when a frame's document draws exactly what the last frame drew, and nothing new was made in between, the texture already holds that picture and is kept, which saves the GPU most of a GUI's cost on the frames a HUD or a menu stands still. Changes made through RML, RCSS, the RmlUi Lua interface or the `gui*` calls are all seen. This call is the way out should one ever be missed -- a GUI that stops updating when it should -- and a game should not need it; if it does, that is worth reporting. A GUI that does not exist raises an error.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<guidraw,guiDraw>>, <<guigetstats,guiGetStats>>
|
||||
|
||||
.Example
|
||||
[source,lua]
|
||||
----
|
||||
-- A workaround while a missed change is reported: redraw the HUD every frame.
|
||||
function onOverlayUpdate()
|
||||
guiRefresh(hud)
|
||||
guiDraw(hud)
|
||||
return OVERLAY_UPDATED
|
||||
end
|
||||
----
|
||||
|
||||
[#guisethandler]
|
||||
==== guiSetHandler
|
||||
|
||||
|
|
@ -6887,8 +6913,8 @@ end
|
|||
|
||||
Every controller Singe opens is a gamepad and is read through the `controller*`
|
||||
calls; a device SDL does not recognise reaches them through a mapping Singe
|
||||
writes for it, so there is nothing else to call. See <<devices,Every Device Is
|
||||
a Gamepad>>. The two calls here are about letting a stick stand in for a mouse.
|
||||
writes for it, so there is nothing else to call. See xref:Manual.adoc#devices[Every Device Is
|
||||
a Gamepad]. The two calls here are about letting a stick stand in for a mouse.
|
||||
|
||||
[#joymouseenable]
|
||||
==== joyMouseEnable
|
||||
|
|
@ -8360,6 +8386,7 @@ interfaces and any synthesiser or other program registering one. macOS uses
|
|||
CoreMIDI and Windows the multimedia MIDI calls, where output device `0` is
|
||||
usually the synthesiser the system itself provides.
|
||||
|
||||
[#midicloseinput]
|
||||
==== midiCloseInput
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8378,6 +8405,7 @@ Stops listening to the input port. <<onmidimessage,`onMidiMessage`>> stops firin
|
|||
*Since:* 3.00.
|
||||
*See also:* <<midiopeninput,midiOpenInput>>
|
||||
|
||||
[#midicloseoutput]
|
||||
==== midiCloseOutput
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8396,6 +8424,7 @@ Releases the output port. The sending calls answer `false` from now on. Closing
|
|||
*Since:* 3.00.
|
||||
*See also:* <<midiopenoutput,midiOpenOutput>>
|
||||
|
||||
[#midicontrolchange]
|
||||
==== midiControlChange
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8423,6 +8452,7 @@ Moves a controller on the open output port: volume is controller `7`, pan is `10
|
|||
midiControlChange(1, 7, 40)
|
||||
----
|
||||
|
||||
[#midiinputcount]
|
||||
==== midiInputCount
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8441,6 +8471,7 @@ How many MIDI input ports this machine has. `0` when there are none, or when the
|
|||
*Since:* 3.00.
|
||||
*See also:* <<midiinputname,midiInputName>>
|
||||
|
||||
[#midiinputname]
|
||||
==== midiInputName
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8467,6 +8498,7 @@ for i = 0, midiInputCount() - 1 do
|
|||
end
|
||||
----
|
||||
|
||||
[#midiisinputopen]
|
||||
==== midiIsInputOpen
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8485,6 +8517,7 @@ Whether an input port is open.
|
|||
*Since:* 3.00.
|
||||
*See also:* <<midiopeninput,midiOpenInput>>
|
||||
|
||||
[#midiisoutputopen]
|
||||
==== midiIsOutputOpen
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8503,6 +8536,7 @@ Whether an output port is open. Worth asking before a burst of messages, since e
|
|||
*Since:* 3.00.
|
||||
*See also:* <<midiopenoutput,midiOpenOutput>>
|
||||
|
||||
[#midinoteoff]
|
||||
==== midiNoteOff
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8522,6 +8556,7 @@ Releases a note on the open output port. A note left on sounds until something s
|
|||
*Since:* 3.00.
|
||||
*See also:* <<midinoteon,midiNoteOn>>
|
||||
|
||||
[#midinoteon]
|
||||
==== midiNoteOn
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8550,6 +8585,7 @@ midiNoteOn(10, 49, 110)
|
|||
midiNoteOff(10, 49)
|
||||
----
|
||||
|
||||
[#midiopeninput]
|
||||
==== midiOpenInput
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8576,6 +8612,7 @@ if midiInputCount() > 0 then
|
|||
end
|
||||
----
|
||||
|
||||
[#midiopenoutput]
|
||||
==== midiOpenOutput
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8594,6 +8631,7 @@ Takes one output port for the sending calls. Whichever output port was open is c
|
|||
*Since:* 3.00.
|
||||
*See also:* <<midicloseoutput,midiCloseOutput>>
|
||||
|
||||
[#midioutputcount]
|
||||
==== midiOutputCount
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8612,6 +8650,7 @@ How many MIDI output ports this machine has.
|
|||
*Since:* 3.00.
|
||||
*See also:* <<midioutputname,midiOutputName>>
|
||||
|
||||
[#midioutputname]
|
||||
==== midiOutputName
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8630,6 +8669,7 @@ The name of an output port.
|
|||
*Since:* 3.00.
|
||||
*See also:* <<midioutputcount,midiOutputCount>>
|
||||
|
||||
[#midipitchbend]
|
||||
==== midiPitchBend
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8648,6 +8688,7 @@ Bends a channel. `8192` is the middle, meaning no bend; how far `0` and `16383`
|
|||
|
||||
*Since:* 3.00.
|
||||
|
||||
[#midiprogramchange]
|
||||
==== midiProgramChange
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8666,6 +8707,7 @@ Chooses the sound a channel plays. The numbers are the receiving device's; on a
|
|||
|
||||
*Since:* 3.00.
|
||||
|
||||
[#midirescan]
|
||||
==== midiRescan
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8684,6 +8726,7 @@ Looks for ports again, for a device plugged in while the game is running. An ope
|
|||
*Since:* 3.00.
|
||||
*See also:* <<midiinputcount,midiInputCount>>, <<midioutputcount,midiOutputCount>>
|
||||
|
||||
[#midisend]
|
||||
==== midiSend
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8927,7 +8970,7 @@ for device = 0, mouseHowMany() - 1 do
|
|||
end
|
||||
----
|
||||
|
||||
[#mousegetposition]
|
||||
[#mousegetpadding]
|
||||
==== mouseGetPadding
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8942,6 +8985,7 @@ Hypseus extension, always `0`, for the reason <<controllergetpadding,`controller
|
|||
*Since:* 3.00
|
||||
*See also:* <<controllergetpadding,controllerGetPadding>>
|
||||
|
||||
[#mousegetposition]
|
||||
==== mouseGetPosition
|
||||
|
||||
[source,text]
|
||||
|
|
@ -11034,7 +11078,7 @@ end
|
|||
overlayClear()
|
||||
----
|
||||
|
||||
Fills the whole overlay with the current background color. The background starts as transparent black, so with no `colorBackground` call this erases the overlay back to invisible and the video shows through. A partly opaque background dims the video; a fully opaque one hides it. Most games call it first thing in `onOverlayUpdate`. Any arguments are ignored.
|
||||
Fills the whole overlay with the current background color. When the color is the same as the last clear's, only what was drawn since is filled again, so clearing an overlay that holds a few lines of text costs next to nothing whatever its size. The background starts as transparent black, so with no `colorBackground` call this erases the overlay back to invisible and the video shows through. A partly opaque background dims the video; a fully opaque one hides it. Most games call it first thing in `onOverlayUpdate`. Any arguments are ignored.
|
||||
|
||||
*Since:* 1.x
|
||||
*See also:* <<colorbackground,colorBackground>>, <<onoverlayupdate,onOverlayUpdate>>
|
||||
|
|
@ -11329,7 +11373,7 @@ MARQUEE_X = overlayGetWidth() - MARGIN_X - MARQUEE_W
|
|||
CABINET_H = overlayGetHeight() - MARGIN_Y - CABINET_Y
|
||||
----
|
||||
|
||||
[#setoverlaylinearscale]
|
||||
[#setoverlayfullalpha]
|
||||
==== setOverlayFullAlpha
|
||||
|
||||
[source,text]
|
||||
|
|
@ -11343,6 +11387,7 @@ Hypseus extension, accepted and ignored. Hypseus blits its overlay with the alph
|
|||
|
||||
*Since:* 3.00
|
||||
|
||||
[#setoverlaylinearscale]
|
||||
==== setOverlayLinearScale
|
||||
|
||||
[source,text]
|
||||
|
|
@ -12407,6 +12452,7 @@ Whole numbers come back as whole numbers. JSON has only one kind of number and
|
|||
Lua has two, so a score saved as `100` would otherwise return as `100.0` and
|
||||
print with a decimal point.
|
||||
|
||||
[#saveclear]
|
||||
==== saveClear
|
||||
|
||||
[source,text]
|
||||
|
|
@ -12424,6 +12470,7 @@ Forgets everything and writes the empty save out. For a service menu's "reset hi
|
|||
|
||||
*Since:* 3.00.
|
||||
|
||||
[#savedelete]
|
||||
==== saveDelete
|
||||
|
||||
[source,text]
|
||||
|
|
@ -12441,6 +12488,7 @@ Removes one key. Deleting a key that was never there is not an error.
|
|||
|
||||
*Since:* 3.00.
|
||||
|
||||
[#saveflush]
|
||||
==== saveFlush
|
||||
|
||||
[source,text]
|
||||
|
|
@ -12459,6 +12507,7 @@ Writes the save out now rather than at the end of the frame. Use it after someth
|
|||
*Since:* 3.00.
|
||||
*See also:* <<saveset,saveSet>>
|
||||
|
||||
[#saveget]
|
||||
==== saveGet
|
||||
|
||||
[source,text]
|
||||
|
|
@ -12493,6 +12542,7 @@ saveSet("highScores", best)
|
|||
saveFlush()
|
||||
----
|
||||
|
||||
[#savegetall]
|
||||
==== saveGetAll
|
||||
|
||||
[source,text]
|
||||
|
|
@ -12518,6 +12568,7 @@ A copy of the whole save, for reading: dumping it while you are debugging, or wa
|
|||
debugPrint(require("inspect")(saveGetAll()))
|
||||
----
|
||||
|
||||
[#saveset]
|
||||
==== saveSet
|
||||
|
||||
[source,text]
|
||||
|
|
@ -12537,6 +12588,7 @@ Keeps a value until the game is uninstalled. The write happens at the end of the
|
|||
*Since:* 3.00.
|
||||
*See also:* <<saveget,saveGet>>, <<saveflush,saveFlush>>
|
||||
|
||||
[#savesetall]
|
||||
==== saveSetAll
|
||||
|
||||
[source,text]
|
||||
|
|
@ -13164,8 +13216,8 @@ Replaces the running script with the one the table describes. The current script
|
|||
function onInputPressed(what)
|
||||
if what == SWITCH_START1 then
|
||||
scriptExecute({
|
||||
SCRIPT = DIR .. "../Lair/Lair.singe",
|
||||
VIDEO = DIR .. "../Lair/Lair.mp4",
|
||||
SCRIPT = DIR .. "../MyGame/MyGame.singe",
|
||||
VIDEO = DIR .. "../MyGame/MyGame.mp4",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
@ -13205,6 +13257,35 @@ end
|
|||
|
||||
Functions in the `singe*` namespace control the engine itself: the window, screenshots, the pause system, quitting and reloading, the audio delay used for lip sync, and a few paths and flags that come from the command line and `games.dat`. Nothing here takes a handle. Pause behavior is described in <<pausing,Pausing>> and the audio delay values in <<audiosync,Audio Sync>>.
|
||||
|
||||
[#allowsocketcall]
|
||||
==== allowSocketCall
|
||||
|
||||
[source,text]
|
||||
----
|
||||
allowed = allowSocketCall()
|
||||
----
|
||||
|
||||
Hypseus extension, always `false` in Singe. Hypseus answers whether the user let it send data to a stats server, which its `-noserversend` turns off; the sending itself is commented out there. Singe sends nothing anywhere, so nothing is permitted and the answer never changes. It exists so a game that asks does not stop on a missing function.
|
||||
|
||||
*Returns:* a boolean, always `false`.
|
||||
|
||||
*Since:* 3.00
|
||||
|
||||
[#getuserstring]
|
||||
==== getUserString
|
||||
|
||||
[source,text]
|
||||
----
|
||||
id = getUserString()
|
||||
----
|
||||
|
||||
Hypseus extension, always an empty string. Despite the name it is not a string the user set: Hypseus answers the machine install ID it sends with network statistics, taken from the Windows `MachineGuid` or the Linux machine ID. Singe sends nothing anywhere and will not hand a game a fingerprint of the machine it is running on.
|
||||
|
||||
*Returns:* a string, always empty.
|
||||
|
||||
*Since:* 3.00
|
||||
*See also:* <<allowsocketcall,allowSocketCall>>
|
||||
|
||||
[#singedisablepausekey]
|
||||
==== singeDisablePauseKey
|
||||
|
||||
|
|
@ -13917,7 +13998,7 @@ Sets the title of the Singe window. Call it once at startup with the game's disp
|
|||
.Example
|
||||
[source,lua]
|
||||
----
|
||||
singeSetGameName("Space Ace")
|
||||
singeSetGameName("Asteroid Patrol")
|
||||
keyboardSetMode(MODE_NORMAL)
|
||||
hudFont = fontLoad(DIR .. "fonts/FreeSansBold.ttf", 18)
|
||||
----
|
||||
|
|
@ -14843,7 +14924,21 @@ Sprites are 2D bitmaps loaded from disk with `spriteLoad`, or rendered from text
|
|||
|
||||
*Sprite sheets.* Hypseus Singe extends the family with sprite sheets: `spriteLoadFrames` slices a single row strip of equal frames into an animation that is not playing, and `spriteDrawFrame` draws one chosen frame (1-based) without touching playback. A sheet is otherwise an ordinary animation, so `spriteSetFrame` (0-based), `spritePlay`, `spriteDraw`, `nodeSetSprite` and `emitterSetTexture` all accept it, and `spriteFrameWidth` and `spriteFrameHeight` report the unscaled size of one frame. The entries marked "Hypseus extension" exist so games written for Hypseus Singe run unchanged.
|
||||
|
||||
[#spritedraw]
|
||||
[#drawtransparent]
|
||||
==== drawTransparent
|
||||
|
||||
[source,text]
|
||||
----
|
||||
drawTransparent(boolean)
|
||||
----
|
||||
|
||||
Hypseus extension, accepted and ignored. It decides whether Hypseus sets colour key 0 on the sprites loaded after it, which is how that engine carries transparency. Singe carries transparency in the alpha channel, as <<spriteresetcolorkey,`spriteResetColorKey`>> already describes, so there is no key to turn on or off.
|
||||
|
||||
*Returns:* nothing.
|
||||
|
||||
*Since:* 3.00
|
||||
|
||||
[#spriteblend]
|
||||
==== spriteBlend
|
||||
|
||||
[source,text]
|
||||
|
|
@ -14857,6 +14952,7 @@ Hypseus extension, accepted and ignored. It is a workaround for that engine: Hyp
|
|||
|
||||
*Since:* 3.00
|
||||
|
||||
[#spritedraw]
|
||||
==== spriteDraw
|
||||
|
||||
[source,text]
|
||||
|
|
@ -15770,7 +15866,7 @@ if srtLoad("subs/" .. language .. ".srt") then
|
|||
end
|
||||
----
|
||||
|
||||
[#srtposition]
|
||||
[#srtloadtrack]
|
||||
==== srtLoadTrack
|
||||
|
||||
[source,text]
|
||||
|
|
@ -15805,6 +15901,7 @@ end
|
|||
srtEnable(true)
|
||||
----
|
||||
|
||||
[#srtposition]
|
||||
==== srtPosition
|
||||
|
||||
[source,text]
|
||||
|
|
@ -15988,6 +16085,7 @@ function onInputPressed(key)
|
|||
end
|
||||
----
|
||||
|
||||
[#statsisenabled]
|
||||
==== statsIsEnabled
|
||||
|
||||
[source,text]
|
||||
|
|
@ -16066,6 +16164,7 @@ the game catches up rather than firing the twenty times it missed.
|
|||
Everything is forgotten when the script reloads, so a timer from the last run
|
||||
never fires into the next one.
|
||||
|
||||
[#timerafter]
|
||||
==== timerAfter
|
||||
|
||||
[source,text]
|
||||
|
|
@ -16095,6 +16194,7 @@ timerAfter(1500, function()
|
|||
end)
|
||||
----
|
||||
|
||||
[#timercancel]
|
||||
==== timerCancel
|
||||
|
||||
[source,text]
|
||||
|
|
@ -16112,6 +16212,7 @@ Stops a timer. Cancelling one that has already fired is not an error, so nothing
|
|||
|
||||
*Since:* 3.00.
|
||||
|
||||
[#timerevery]
|
||||
==== timerEvery
|
||||
|
||||
[source,text]
|
||||
|
|
@ -16146,6 +16247,7 @@ clock = timerEvery(1000, function()
|
|||
end)
|
||||
----
|
||||
|
||||
[#timerisactive]
|
||||
==== timerIsActive
|
||||
|
||||
[source,text]
|
||||
|
|
@ -16181,6 +16283,7 @@ to sharp; `EASE_BACK_*` overshoots and comes back; `EASE_ELASTIC_*` springs;
|
|||
`EASE_BOUNCE_*` drops and bounces. Each comes in `_IN` (slow at the start),
|
||||
`_OUT` (slow at the end) and `_IN_OUT` (both).
|
||||
|
||||
[#tweencancel]
|
||||
==== tweenCancel
|
||||
|
||||
[source,text]
|
||||
|
|
@ -16198,6 +16301,7 @@ Stops a tween where it is. Neither function is called again, and the value stays
|
|||
|
||||
*Since:* 3.00.
|
||||
|
||||
[#tweenisactive]
|
||||
==== tweenIsActive
|
||||
|
||||
[source,text]
|
||||
|
|
@ -16215,6 +16319,7 @@ Whether the tween is still running.
|
|||
|
||||
*Since:* 3.00.
|
||||
|
||||
[#tweenvalue]
|
||||
==== tweenValue
|
||||
|
||||
[source,text]
|
||||
|
|
@ -16956,6 +17061,8 @@ videoDraw(id, x, y, centered)
|
|||
|
||||
Advances a video to the frame its clock calls for and draws that frame on the overlay. The clock follows the video's audio when it has any and wall time otherwise, so a call may advance zero, one or several frames; a video that reaches its end loops back to frame `0`. The five argument form stretches the frame into the rectangle from `(x, y)` to `(x2, y2)` inclusive, always with nearest-neighbor sampling and ignoring `videoScale`, `videoRotate` and `videoQuality`. The four argument form draws the frame at its native size transformed by `videoScale` and `videoRotate`, anchored at its top left corner or, when `centered` is `true`, at its center; the transformed frame is rebuilt only when the frame or the transform changes, and rotated corners are transparent. If the decoder has not produced a frame yet nothing is drawn.
|
||||
|
||||
A frame that is not scaled or turned by `videoScale` or `videoRotate`, drawn on the overlay where nothing has been drawn since an `overlayClear` to a transparent color -- a background video drawn first, say -- is drawn by the GPU instead, under the overlay, and costs next to nothing; anything drawn afterwards still goes over it. It is then scaled to the screen the way the overlay is, rather than nearest-neighbor into the overlay first. Everywhere else the frame is copied into the overlay on the CPU, which on a small machine costs several milliseconds a frame, so draw a video before anything that goes over it rather than after.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* `id` -- video handle from `videoLoad`.
|
||||
|
|
@ -18239,7 +18346,7 @@ function onKeyPressed(keysym, scancode)
|
|||
end
|
||||
----
|
||||
|
||||
[#onmousemoved]
|
||||
[#onmidimessage]
|
||||
==== onMidiMessage
|
||||
|
||||
[source,text]
|
||||
|
|
@ -18286,6 +18393,7 @@ function onMidiMessage(status, data1, data2, bytes)
|
|||
end
|
||||
----
|
||||
|
||||
[#onmousemoved]
|
||||
==== onMouseMoved
|
||||
|
||||
[source,text]
|
||||
|
|
@ -18558,8 +18666,10 @@ real engine calls under <<overlay,Overlay>>; `mainBezelLoaded` and the seven
|
|||
`scoreBezel` calls are real engine calls under <<bezelref,Bezel>>; and
|
||||
`getFValue`, `ratioGetX` and `ratioGetY` are real engine calls under
|
||||
<<commandlineref,Command Line>>; `joyMouseEnable` is a real engine call
|
||||
under <<joystick,Joystick>>; and the whole `music` family is now a set of real
|
||||
engine calls under <<musicref,Music>>. The six `spriteAnim` names below are
|
||||
under <<joystick,Joystick>>; `allowSocketCall` and `getUserString` are real
|
||||
engine calls under <<singe,Singe>>, and `drawTransparent` under <<sprite,Sprite>>;
|
||||
and the whole `music` family is now a set of real engine calls under
|
||||
<<musicref,Music>>. The six `spriteAnim` names below are
|
||||
plain aliases set after the legacy sprite wrapping, so they take whichever
|
||||
argument order the rest of the sprite family is taking: Hypseus's own (handle
|
||||
last) under `LEGACY_SPRITE_ARGS`, and Singe 3.00's (handle first) without it.
|
||||
|
|
|
|||
|
|
@ -183,6 +183,14 @@ white, this is why.
|
|||
|
||||
=== The Trap: fontPrint Is Not Free
|
||||
|
||||
NOTE: Rendering text to a sprite for performance is deprecated. Singe 3.00's
|
||||
`fontPrint` keeps every string it renders and only copies it again while the
|
||||
string, font, quality, and colours stay the same, so printing a title every
|
||||
frame now costs about what drawing a sprite of it does. The trap below is the
|
||||
one earlier versions set, and the pattern still works; new games can just
|
||||
`fontPrint`, and keep `fontToSprite` for text that has to be a sprite -- to be
|
||||
measured, scaled, or turned.
|
||||
|
||||
Here is the thing the reference says plainly and everyone reads past.
|
||||
|
||||
`fontPrint` does not draw a picture it made earlier. Every single call
|
||||
|
|
@ -475,9 +483,10 @@ thing, several hundred lines away from the `fontUnload` that caused it.
|
|||
* Colour is not an argument. `colorForeground` is a setting; set it, then draw.
|
||||
* `fontQuality` picks how letters are rasterized, and `FONT_QUALITY_BLENDED`
|
||||
is the usual answer.
|
||||
* `fontPrint` re-renders the string on every single call.
|
||||
* `fontToSprite` renders once into a sprite, which `spriteDraw` then copies
|
||||
cheaply and `spriteGetWidth` measures.
|
||||
* `fontPrint` keeps what it renders, so printing the same text every frame is
|
||||
cheap.
|
||||
* `fontToSprite` renders into a sprite, which `spriteGetWidth` measures and the
|
||||
`sprite*` calls can scale and turn. Using it only for speed is deprecated.
|
||||
* Measuring is how you centre or right align text, because nothing else can
|
||||
tell you how wide a string will be.
|
||||
* A sprite's colour is fixed when it is made, not when it is drawn.
|
||||
|
|
|
|||
|
|
@ -5255,41 +5255,80 @@ end
|
|||
|
||||
-- ----- Timers, sounds, clips ----------------------------------------------------------------
|
||||
|
||||
-- The framework's two countdowns, the timer and the joystick delay, arithmetic and all: each sums
|
||||
-- os.clock() deltas as (sum + now) - last and compares the sum. One that subtracted two times
|
||||
-- instead lands a hair either side of the limit on the frame it is due, which is a frame late for
|
||||
-- the rest of the game (FORGE.md 14.19). A paused game's countdown holds, as the framework's does.
|
||||
-- keys names the countdown's three fields on q.
|
||||
local TIMER_KEYS = { limit = "timerLimit", secs = "timerSecs", last = "timerLast" }
|
||||
local JOY_KEYS = { limit = "joyLimit", secs = "joySecs", last = "joyLast" }
|
||||
|
||||
|
||||
local function countdownStart(q, keys, seconds)
|
||||
q[keys.limit] = seconds
|
||||
q[keys.secs] = 0
|
||||
q[keys.last] = os.clock()
|
||||
end
|
||||
|
||||
|
||||
local function countdownDue(q, keys)
|
||||
if q[keys.limit] == nil then
|
||||
return true
|
||||
end
|
||||
local now = os.clock()
|
||||
|
||||
if q.bPause then
|
||||
q[keys.last] = now
|
||||
return false
|
||||
end
|
||||
if now ~= q[keys.last] then
|
||||
q[keys.secs] = q[keys.secs] + now - q[keys.last]
|
||||
q[keys.last] = now
|
||||
end
|
||||
if q[keys.secs] >= q[keys.limit] then
|
||||
q[keys.limit] = nil
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function Q.timerON(q, seconds)
|
||||
q.timerLimit = seconds
|
||||
q.timerFrom = authorTime()
|
||||
countdownStart(q, TIMER_KEYS, seconds)
|
||||
end
|
||||
|
||||
|
||||
function Q.timerDue(q)
|
||||
if q.timerLimit == nil then
|
||||
return true
|
||||
end
|
||||
if authorTime() - q.timerFrom >= q.timerLimit then
|
||||
q.timerLimit = nil
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
return countdownDue(q, TIMER_KEYS)
|
||||
end
|
||||
|
||||
|
||||
function Q.joyDelayON(q, seconds)
|
||||
q.joyLimit = seconds
|
||||
q.joyFrom = authorTime()
|
||||
countdownStart(q, JOY_KEYS, seconds)
|
||||
end
|
||||
|
||||
|
||||
function Q.joyDelayDue(q)
|
||||
if q.joyLimit == nil then
|
||||
return true
|
||||
end
|
||||
if authorTime() - q.joyFrom >= q.joyLimit then
|
||||
q.joyLimit = nil
|
||||
return true
|
||||
return countdownDue(q, JOY_KEYS)
|
||||
end
|
||||
|
||||
return false
|
||||
|
||||
-- The framework's setLang: the disc's audio in the language the dips name, or the default when
|
||||
-- that file is missing. readConfig calls it only when the saved config has a dip_Lang line, which
|
||||
-- is exactly when the description's snapshot carries one, so the loops call it on that condition.
|
||||
-- Beyond the language, the call itself holds the disc for a frame, and the traces see it.
|
||||
function Q.setLang(q)
|
||||
if (q.LangOpt == nil) or (q.snapshot.dips == nil) or (q.snapshot.dips.dip_Lang == nil) then
|
||||
return
|
||||
end
|
||||
if q.dip_Lang + 1 > #q.LangOpt then
|
||||
q.dip_Lang = 0
|
||||
end
|
||||
if not discAudioSuffix(q.LangOpt[q.dip_Lang + 1][2]) then
|
||||
debugPrint(q.LangOpt[q.dip_Lang + 1][1] .. " audio file unavailable, using default instead.")
|
||||
discAudioSuffix("")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -5311,13 +5350,19 @@ function Q.sound(q, name)
|
|||
end
|
||||
|
||||
|
||||
-- The framework's 3.32b, the Hypseus library's copy, where it parts from 3.31c.
|
||||
function Q.karis332(q)
|
||||
return q.snapshot.framework == "3.32b"
|
||||
end
|
||||
|
||||
|
||||
-- The secret level's combination on the attract screens: the framework's 3.32b asks for
|
||||
-- BUTTON4 and UP, the earlier versions for BUTTON2, BUTTON3, UP, and RIGHT.
|
||||
function Q.secretPressed(q)
|
||||
if not q.AllowSecret then
|
||||
return false
|
||||
end
|
||||
if q.snapshot.framework == "3.32b" then
|
||||
if Q.karis332(q) then
|
||||
return q.p1BUTTON4 and q.p1UP
|
||||
end
|
||||
|
||||
|
|
@ -7205,7 +7250,9 @@ function Q.doGameOver(q)
|
|||
if q.bGOAlt then
|
||||
Q.setupClip(q, q.offsetGameOverAlt, q.offsetGameOverAltend)
|
||||
q.bGOAlt = false
|
||||
elseif q.currentFrame == q.offsetContinueend then
|
||||
elseif (not Q.karis332(q)) and (q.currentFrame == q.offsetContinueend) then
|
||||
-- 3.31c plays on from the continue clip when the game over follows it on the disc;
|
||||
-- 3.32b always seeks.
|
||||
q.iFrameEnd = q.offsetGameOverend
|
||||
else
|
||||
Q.setupClip(q, q.offsetGameOver, q.offsetGameOverend)
|
||||
|
|
@ -7384,7 +7431,7 @@ function Q.loadSprite(q, name, frames)
|
|||
if probe ~= nil then
|
||||
probe:close()
|
||||
if frames then
|
||||
handle = spriteLoadFrames(path, frames)
|
||||
handle = spriteLoadFrames(frames, path)
|
||||
else
|
||||
handle = spriteLoad(path)
|
||||
end
|
||||
|
|
@ -7606,7 +7653,6 @@ function Q.shim(q)
|
|||
videoSetVolume = function() end,
|
||||
videoDraw = function() end,
|
||||
overlayClear = function() end,
|
||||
colorForeground = function() end,
|
||||
autoSave = function() end,
|
||||
writeSave = function() end,
|
||||
loadSave = function() end,
|
||||
|
|
@ -7614,7 +7660,13 @@ function Q.shim(q)
|
|||
cfgWritePath = function(name) return singeGetDataPath() .. "Cfg/" .. name end,
|
||||
singeGetScriptPath = function() return q.MYDIR .. "/" .. (q.snapshot.script or "game.singe") end,
|
||||
dofile = function(name)
|
||||
-- A map-mode game's files are its own data and helpers: loaded into the shim.
|
||||
-- A map-mode game's files are its own data and helpers: loaded into the shim. The
|
||||
-- engine's own Framework.singe is not: the shim stands in for it, and loading it here
|
||||
-- would wrap the shim's sprite calls a second time for a game that asks for the old
|
||||
-- argument order, turning its y into a sprite handle.
|
||||
if (type(name) == "string") and name:lower():match("^singe[/\\]framework%.singe$") then
|
||||
return
|
||||
end
|
||||
if q.loadsFiles then
|
||||
Q.runScript(q, Q.gameFile(q, name))
|
||||
end
|
||||
|
|
@ -7804,6 +7856,12 @@ AUTHOR.behaviours.qte = {
|
|||
step = function(instance)
|
||||
local q = instance.q
|
||||
|
||||
Q.dropDraws(q)
|
||||
|
||||
-- What the game's own Lua queued to draw last frame: this loop has no look that draws it
|
||||
-- (FORGE.md 15.4), and a queue nothing empties grows by every call, every frame.
|
||||
Q.dropDraws(q)
|
||||
|
||||
q.currentFrame = discGetFrame()
|
||||
if q.gameflow == "vldp" then
|
||||
-- The framework's start: the title clip once through, a frame of init, the attract loop.
|
||||
|
|
@ -7822,6 +7880,7 @@ AUTHOR.behaviours.qte = {
|
|||
if q.gameflow == "init" then
|
||||
q.gameflow = "running"
|
||||
q.lvlState = QTE.lvlSetup
|
||||
Q.setLang(q) -- initJob's readConfig
|
||||
return
|
||||
end
|
||||
if q.currentLevel == QTE.levelIntro then
|
||||
|
|
@ -10587,6 +10646,8 @@ AUTHOR.behaviours.kimmy = {
|
|||
step = function(instance)
|
||||
local q = instance.q
|
||||
|
||||
Q.dropDraws(q)
|
||||
|
||||
q.currentFrame = discGetFrame()
|
||||
if q.gameflow == "vldp" then
|
||||
if q.lvlState == QTE.lvlSetup then
|
||||
|
|
@ -10607,6 +10668,7 @@ AUTHOR.behaviours.kimmy = {
|
|||
q.lvlState = QTE.lvlSetup
|
||||
q.iCoins, q.iScore, q.iScoreTemp, q.iBonus, q.iScene = 0, 0, 0, 0, 0
|
||||
q.bShowCredits = true
|
||||
Q.setLang(q) -- initJob's readConfig
|
||||
return
|
||||
end
|
||||
local level = q.currentLevel
|
||||
|
|
@ -11733,6 +11795,8 @@ AUTHOR.behaviours.rdg = {
|
|||
step = function(instance)
|
||||
local q = instance.q
|
||||
|
||||
Q.dropDraws(q)
|
||||
|
||||
q.currentFrame = discGetFrame()
|
||||
if q.gameflow == "vldp" then
|
||||
-- The ancestor's start: the title clip with the inputs paused, then the init.
|
||||
|
|
@ -12532,6 +12596,8 @@ AUTHOR.behaviours.timegal = {
|
|||
step = function(instance)
|
||||
local q = instance.q
|
||||
|
||||
Q.dropDraws(q)
|
||||
|
||||
q.currentFrame = discGetFrame()
|
||||
if q.gameflow == "vldp" then
|
||||
-- The ancestor's start: the title clip, then the init.
|
||||
|
|
@ -13664,6 +13730,8 @@ AUTHOR.behaviours.sdq = {
|
|||
step = function(instance)
|
||||
local q = instance.q
|
||||
|
||||
Q.dropDraws(q)
|
||||
|
||||
q.currentFrame = discGetFrame()
|
||||
if q.gameflow == "vldp" then
|
||||
if q.lvlState == q.lvlSetup then
|
||||
|
|
@ -4162,8 +4162,6 @@ function forgeKey(keysym, scancode)
|
|||
forgeBuild(singeGetDataPath() .. "preview.singe")
|
||||
elseif scancode == SCANCODE.X.value then
|
||||
forgeExport(forgeReleaseFolder())
|
||||
elseif scancode == SCANCODE.P.value then
|
||||
forgePublish()
|
||||
elseif scancode == SCANCODE.U.value then
|
||||
forgeUndo()
|
||||
elseif scancode == SCANCODE.R.value then
|
||||
|
|
@ -5094,7 +5092,12 @@ if not FORGE_LIBRARY then
|
|||
elseif (scancode == SCANCODE.ESCAPE.value) and (FORGE.editing == nil) and (FORGE.picker == nil) then
|
||||
editorEscape()
|
||||
elseif (scancode == SCANCODE.P.value) and (FORGE.editing == nil) and (FORGE.picker == nil) then
|
||||
-- P plays; Shift+P publishes, one step past X's export.
|
||||
if (keyboardGetModifiers() & MODIFIER.SHIFT.value) ~= 0 then
|
||||
forgePublish()
|
||||
else
|
||||
forgePlay()
|
||||
end
|
||||
else
|
||||
forgeKey(keysym, scancode)
|
||||
end
|
||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
File diff suppressed because it is too large
Load diff
|
|
@ -10,6 +10,7 @@
|
|||
],
|
||||
|
||||
"head": ["min", "mid", "mid"],
|
||||
"jaw": ["min", "max", "mid"],
|
||||
"neck": ["mid", "min", "mid"],
|
||||
"tail": ["max", "mid", "mid"],
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
BIN
media/music/eliza_music-neons-in-napoli-539381.mp3
(Stored with Git LFS)
Normal file
BIN
media/music/eliza_music-neons-in-napoli-539381.mp3
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
media/music/miiira-vintage-funky-groove-90s-dance-club-beat-533491.mp3
(Stored with Git LFS)
Normal file
BIN
media/music/miiira-vintage-funky-groove-90s-dance-club-beat-533491.mp3
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
media/music/poorartistt-game-is-on-video-game-music-no-copyright-426130.mp3
(Stored with Git LFS)
Normal file
BIN
media/music/poorartistt-game-is-on-video-game-music-no-copyright-426130.mp3
(Stored with Git LFS)
Normal file
Binary file not shown.
45
src/gui.cpp
45
src/gui.cpp
|
|
@ -66,6 +66,9 @@ typedef struct GuiContextS {
|
|||
int32_t height;
|
||||
bool inputEnabled;
|
||||
bool used;
|
||||
bool mustDraw; // Draw next frame whatever the signature says: new, or guiInvalidate
|
||||
uint64_t signature; // The last frame drawn, as GuiRenderT signs it
|
||||
uint64_t generation; // What the render interface had made by then
|
||||
char name[GUI_NAME_MAX];
|
||||
} GuiContextT;
|
||||
|
||||
|
|
@ -470,24 +473,23 @@ static void _releaseContext(GuiContextT *context) {
|
|||
}
|
||||
|
||||
|
||||
// Clears the context's texture to transparent black, then lets RmlUi draw the context over it.
|
||||
// Lets RmlUi draw the context into its texture, from transparent black. RmlUi describes the whole
|
||||
// picture every frame, but a HUD or a menu is the same picture most of them: when this frame's
|
||||
// drawing signs the same as the last one drawn and nothing new was made since, the texture already
|
||||
// holds it and the GPU is spared the work. Anything that changes the picture behind RmlUi's back
|
||||
// calls guiInvalidate.
|
||||
static void _renderContext(GuiContextT *context, SDL_GPUCommandBuffer *commands) {
|
||||
SDL_GPUColorTargetInfo target = {};
|
||||
SDL_GPURenderPass *pass = nullptr;
|
||||
bool draw = false;
|
||||
|
||||
target.texture = context->target;
|
||||
target.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
target.store_op = SDL_GPU_STOREOP_STORE;
|
||||
target.clear_color = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
pass = rgpuBeginRenderPass(commands, &target, 1, nullptr);
|
||||
if (pass == nullptr) {
|
||||
utilTrace("Gui: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
rgpuEndRenderPass(pass);
|
||||
_render->BeginFrame(commands, context->target, context->stencil, (uint32_t)context->width, (uint32_t)context->height);
|
||||
context->context->Render();
|
||||
_render->EndFrame();
|
||||
draw = context->mustDraw || (_render->GetSignature() != context->signature) || (_render->GetGeneration() != context->generation);
|
||||
if (draw) {
|
||||
context->signature = _render->GetSignature();
|
||||
context->generation = _render->GetGeneration();
|
||||
context->mustDraw = false;
|
||||
}
|
||||
_render->EndFrame(draw);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -697,6 +699,20 @@ bool guiInit(SDL_GPUDevice *device, SDL_Renderer *renderer, SDL_Window *window)
|
|||
}
|
||||
|
||||
|
||||
// The GUI draws next frame even if RmlUi describes the same picture: for anything that changes what
|
||||
// it shows without RmlUi knowing. False for a GUI that does not exist.
|
||||
bool guiInvalidate(int32_t gui) {
|
||||
GuiContextT *context = _contextOf(gui);
|
||||
|
||||
if (context == nullptr) {
|
||||
return false;
|
||||
}
|
||||
context->mustDraw = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool guiKey(int32_t gui, SDL_Keycode key, SDL_Keymod modifiers, bool down) {
|
||||
GuiContextT *context = _inputContextOf(gui);
|
||||
Rml::Input::KeyIdentifier identifier = _keyOf(key);
|
||||
|
|
@ -870,6 +886,7 @@ int32_t guiNew(int32_t width, int32_t height) {
|
|||
context->width = width;
|
||||
context->height = height;
|
||||
context->inputEnabled = false;
|
||||
context->mustDraw = true;
|
||||
info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
info.format = _targetFormat;
|
||||
info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ bool guiDelete(int32_t gui);
|
|||
int32_t guiGetHeight(int32_t gui);
|
||||
void guiGetStats(GuiStatsT *stats); // The last completed frame's rendering counters
|
||||
int32_t guiGetWidth(int32_t gui);
|
||||
bool guiInvalidate(int32_t gui); // Draw next frame even when RmlUi describes the same picture
|
||||
int32_t guiNew(int32_t width, int32_t height); // GUI_NO_HANDLE when full or without a GPU
|
||||
SDL_GPUTexture *guiTexture(int32_t gui); // For materials; NULL when invalid
|
||||
SDL_Texture *guiTexture2D(int32_t gui); // The same as the 2D renderer sees it (premultiplied alpha)
|
||||
|
|
|
|||
|
|
@ -93,6 +93,8 @@ extern "C" {
|
|||
#define MAX_LAYERS 8 // Layers stacked over a context's target at once
|
||||
#define FILTER_BUFFERS 2 // The ping-pong pair a filter chain runs through
|
||||
#define BLUR_WEIGHTS 4 // The centre tap and the three beyond it each side (BLUR_TAPS = 7, in gui.hlsl)
|
||||
#define FNV_OFFSET 14695981039346656037ULL // FNV-1a, which the frame's signature is
|
||||
#define FNV_PRIME 1099511628211ULL
|
||||
#define BLUR_MAX_LEVELS 10 // Halvings before a blur, at most
|
||||
#define BLUR_MAX_SIGMA 3.0f // The widest sigma one pass of BLUR_TAPS taps handles
|
||||
#define BLUR_MIN_SIGMA 0.1f // Below this the blur is the identity
|
||||
|
|
@ -104,6 +106,23 @@ extern "C" {
|
|||
#define LUMA_BLUE 0.0722f
|
||||
#define TWO_PI_SQRT 2.5066283f
|
||||
|
||||
// What each drawing command adds to the frame's signature ahead of its values, so two different
|
||||
// commands with the same values never sign alike.
|
||||
enum GuiCommandE : uint8_t {
|
||||
COMMAND_CLIP_MASK,
|
||||
COMMAND_COMPOSITE,
|
||||
COMMAND_GEOMETRY,
|
||||
COMMAND_POP_LAYER,
|
||||
COMMAND_PUSH_LAYER,
|
||||
COMMAND_SAVE_MASK,
|
||||
COMMAND_SAVE_TEXTURE,
|
||||
COMMAND_SCISSOR,
|
||||
COMMAND_SCISSOR_ENABLE,
|
||||
COMMAND_SHADER,
|
||||
COMMAND_TO_CLIP_MASK,
|
||||
COMMAND_TRANSFORM
|
||||
};
|
||||
|
||||
|
||||
// The fragment shaders, as the pipeline table names them.
|
||||
typedef enum GuiFragmentE {
|
||||
|
|
@ -464,6 +483,8 @@ void GuiRenderT::BeginFrame(SDL_GPUCommandBuffer *frameCommands, SDL_GPUTexture
|
|||
recordDepth = 0;
|
||||
refusedDepth = 0;
|
||||
recordScissor = { 0, 0, (int)width, (int)height };
|
||||
signature = FNV_OFFSET;
|
||||
signatureBytes = 0;
|
||||
proj = Rml::Matrix4f::ProjectOrtho(0.0f, (float)width, (float)height, 0.0f, ORTHO_NEAR, ORTHO_FAR);
|
||||
if (screenQuad == nullptr) {
|
||||
screenQuad = uploadGeometry(_screenQuadVertices, sizeof(_screenQuadVertices), _screenQuadIndices, sizeof(_screenQuadIndices));
|
||||
|
|
@ -486,6 +507,7 @@ Rml::CompiledFilterHandle GuiRenderT::CompileFilter(const Rml::String &name, con
|
|||
Rml::Vector3f mix;
|
||||
Rml::Vector3f green;
|
||||
Rml::Vector3f blue;
|
||||
generation++;
|
||||
|
||||
filter->type = GUI_FILTER_OPACITY;
|
||||
filter->factor = 1.0f;
|
||||
|
|
@ -560,6 +582,7 @@ Rml::CompiledFilterHandle GuiRenderT::CompileFilter(const Rml::String &name, con
|
|||
|
||||
Rml::CompiledGeometryHandle GuiRenderT::CompileGeometry(Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices) {
|
||||
GeometryT *geometry = uploadGeometry(vertices.data(), (uint32_t)(vertices.size() * sizeof(Rml::Vertex)), indices.data(), (uint32_t)(indices.size() * sizeof(int)));
|
||||
generation++;
|
||||
|
||||
return reinterpret_cast<Rml::CompiledGeometryHandle>(geometry);
|
||||
}
|
||||
|
|
@ -578,6 +601,7 @@ Rml::CompiledShaderHandle GuiRenderT::CompileShader(const Rml::String &name, con
|
|||
float angle = 0.0f;
|
||||
int32_t kind = GUI_GRADIENT_LINEAR;
|
||||
int32_t i = 0;
|
||||
generation++;
|
||||
|
||||
if (name == "linear-gradient") {
|
||||
kind = GUI_GRADIENT_LINEAR;
|
||||
|
|
@ -628,11 +652,20 @@ Rml::CompiledShaderHandle GuiRenderT::CompileShader(const Rml::String &name, con
|
|||
|
||||
|
||||
void GuiRenderT::CompositeLayers(Rml::LayerHandle source, Rml::LayerHandle destination, Rml::BlendMode blendMode, Rml::Span<const Rml::CompiledFilterHandle> filters) {
|
||||
sign(COMMAND_COMPOSITE);
|
||||
sign(source);
|
||||
sign(destination);
|
||||
sign(blendMode);
|
||||
for (Rml::CompiledFilterHandle filter : filters) {
|
||||
sign(filter);
|
||||
}
|
||||
commands.push_back(Rml::MakeUnique<CompositeLayersCommandT>(source, destination, blendMode, filters));
|
||||
}
|
||||
|
||||
|
||||
void GuiRenderT::EnableClipMask(bool enable) {
|
||||
sign(COMMAND_CLIP_MASK);
|
||||
sign(enable);
|
||||
commands.push_back(Rml::MakeUnique<EnableClipMaskCommandT>(enable));
|
||||
}
|
||||
|
||||
|
|
@ -641,14 +674,27 @@ void GuiRenderT::EnableScissorRegion(bool enable) {
|
|||
if (!enable) {
|
||||
recordScissor = { 0, 0, (int)targetWidth, (int)targetHeight };
|
||||
}
|
||||
sign(COMMAND_SCISSOR_ENABLE);
|
||||
sign(enable);
|
||||
commands.push_back(Rml::MakeUnique<EnableScissorRegionCommandT>(enable));
|
||||
}
|
||||
|
||||
|
||||
void GuiRenderT::EndFrame() {
|
||||
void GuiRenderT::EndFrame(bool draw) {
|
||||
// Drawing starts from a cleared target, in a pass of its own that also clears the stencil.
|
||||
if (draw) {
|
||||
target = baseTarget;
|
||||
targetClear = true;
|
||||
if (beginRenderPass()) {
|
||||
rgpuEndRenderPass(renderPass);
|
||||
renderPass = nullptr;
|
||||
}
|
||||
}
|
||||
for (Rml::UniquePtr<CommandT> &command : commands) {
|
||||
if (draw || command->Releases()) {
|
||||
command->Run(*this);
|
||||
}
|
||||
}
|
||||
commands.clear();
|
||||
if (copyPass != nullptr) {
|
||||
rgpuEndCopyPass(copyPass);
|
||||
|
|
@ -678,6 +724,7 @@ Rml::TextureHandle GuiRenderT::GenerateTexture(Rml::Span<const Rml::byte> source
|
|||
SDL_GPUCopyPass *pass = nullptr;
|
||||
uint32_t size = (uint32_t)(sourceDimensions.x * sourceDimensions.y * BYTES_PER_PIXEL);
|
||||
void *destination = nullptr;
|
||||
generation++;
|
||||
|
||||
transferInfo.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||
transferInfo.size = size;
|
||||
|
|
@ -745,6 +792,18 @@ void GuiRenderT::GetStats(GuiStatsT *outStats) const {
|
|||
}
|
||||
|
||||
|
||||
uint64_t GuiRenderT::GetGeneration() const {
|
||||
return generation;
|
||||
}
|
||||
|
||||
|
||||
// The byte count goes in too, so a frame that is the last one's with something left off the end
|
||||
// still signs differently.
|
||||
uint64_t GuiRenderT::GetSignature() const {
|
||||
return signature ^ (signatureBytes * FNV_PRIME);
|
||||
}
|
||||
|
||||
|
||||
GuiRenderT::GuiRenderT(SDL_GPUDevice *gpuDevice, SDL_Window *gpuWindow) {
|
||||
SDL_GPUSamplerCreateInfo info = {};
|
||||
size_t i = 0;
|
||||
|
|
@ -828,6 +887,7 @@ Rml::TextureHandle GuiRenderT::LoadTexture(Rml::Vector2i &textureDimensions, con
|
|||
size_t dot = source.rfind('.');
|
||||
Rml::String extension = (dot == Rml::String::npos) ? Rml::String() : source.substr(dot + 1);
|
||||
Rml::TextureHandle handle = 0;
|
||||
generation++;
|
||||
|
||||
if (!file) {
|
||||
return 0;
|
||||
|
|
@ -897,6 +957,7 @@ void GuiRenderT::PopLayer() {
|
|||
if (recordDepth > 0) {
|
||||
recordDepth--;
|
||||
}
|
||||
sign(COMMAND_POP_LAYER);
|
||||
commands.push_back(Rml::MakeUnique<PopLayerCommandT>());
|
||||
}
|
||||
|
||||
|
|
@ -913,6 +974,7 @@ Rml::LayerHandle GuiRenderT::PushLayer() {
|
|||
return (Rml::LayerHandle)recordDepth;
|
||||
}
|
||||
recordDepth++;
|
||||
sign(COMMAND_PUSH_LAYER);
|
||||
commands.push_back(Rml::MakeUnique<PushLayerCommandT>());
|
||||
return (Rml::LayerHandle)recordDepth;
|
||||
}
|
||||
|
|
@ -963,6 +1025,10 @@ void GuiRenderT::ReleaseTexture(Rml::TextureHandle texture) {
|
|||
|
||||
|
||||
void GuiRenderT::RenderGeometry(Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation, Rml::TextureHandle texture) {
|
||||
sign(COMMAND_GEOMETRY);
|
||||
sign(geometry);
|
||||
sign(translation);
|
||||
sign(texture);
|
||||
commands.push_back(Rml::MakeUnique<RenderGeometryCommandT>(geometry, translation, texture));
|
||||
}
|
||||
|
||||
|
|
@ -973,11 +1039,19 @@ void GuiRenderT::RenderShader(Rml::CompiledShaderHandle shader, Rml::CompiledGeo
|
|||
if ((shader == 0) || (geometry == 0)) {
|
||||
return;
|
||||
}
|
||||
sign(COMMAND_SHADER);
|
||||
sign(shader);
|
||||
sign(geometry);
|
||||
sign(translation);
|
||||
commands.push_back(Rml::MakeUnique<RenderShaderCommandT>(reinterpret_cast<ShaderT *>(shader), geometry, translation));
|
||||
}
|
||||
|
||||
|
||||
void GuiRenderT::RenderToClipMask(Rml::ClipMaskOperation operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation) {
|
||||
sign(COMMAND_TO_CLIP_MASK);
|
||||
sign(operation);
|
||||
sign(geometry);
|
||||
sign(translation);
|
||||
commands.push_back(Rml::MakeUnique<RenderToClipMaskCommandT>(operation, geometry, translation));
|
||||
}
|
||||
|
||||
|
|
@ -997,6 +1071,9 @@ Rml::CompiledFilterHandle GuiRenderT::SaveLayerAsMaskImage() {
|
|||
filter->matrix = Rml::Matrix4f::Identity();
|
||||
filter->mask = nullptr;
|
||||
memset(filter->colour, 0, sizeof(filter->colour));
|
||||
generation++;
|
||||
sign(COMMAND_SAVE_MASK);
|
||||
sign(filter);
|
||||
commands.push_back(Rml::MakeUnique<SaveLayerAsMaskImageCommandT>(filter));
|
||||
return reinterpret_cast<Rml::CompiledFilterHandle>(filter);
|
||||
}
|
||||
|
|
@ -1024,6 +1101,10 @@ Rml::TextureHandle GuiRenderT::SaveLayerAsTexture() {
|
|||
Rml::Log::Message(Rml::Log::LT_ERROR, "failed to create layer texture: %s", SDL_GetError());
|
||||
return 0;
|
||||
}
|
||||
generation++;
|
||||
sign(COMMAND_SAVE_TEXTURE);
|
||||
sign(texture);
|
||||
sign(region);
|
||||
commands.push_back(Rml::MakeUnique<SaveLayerAsTextureCommandT>(texture, region));
|
||||
return reinterpret_cast<Rml::TextureHandle>(texture);
|
||||
}
|
||||
|
|
@ -1034,11 +1115,17 @@ void GuiRenderT::SetScissorRegion(Rml::Rectanglei region) {
|
|||
recordScissor.y = region.Top();
|
||||
recordScissor.w = region.Width();
|
||||
recordScissor.h = region.Height();
|
||||
sign(COMMAND_SCISSOR);
|
||||
sign(region);
|
||||
commands.push_back(Rml::MakeUnique<SetScissorRegionCommandT>(region));
|
||||
}
|
||||
|
||||
|
||||
void GuiRenderT::SetTransform(const Rml::Matrix4f *newTransform) {
|
||||
sign(COMMAND_TRANSFORM);
|
||||
if (newTransform != nullptr) {
|
||||
sign(*newTransform);
|
||||
}
|
||||
commands.push_back(Rml::MakeUnique<SetTransformCommandT>(newTransform));
|
||||
}
|
||||
|
||||
|
|
@ -1555,6 +1642,18 @@ void GuiRenderT::setTarget(SDL_GPUTexture *texture, bool clear) {
|
|||
}
|
||||
|
||||
|
||||
// Adds bytes to the frame's signature (FNV-1a).
|
||||
void GuiRenderT::sign(const void *bytes, size_t count) {
|
||||
const uint8_t *in = (const uint8_t *)bytes;
|
||||
size_t x = 0;
|
||||
|
||||
for (x = 0; x < count; x++) {
|
||||
signature = (signature ^ in[x]) * FNV_PRIME;
|
||||
}
|
||||
signatureBytes += count;
|
||||
}
|
||||
|
||||
|
||||
// Vertices and indices into pooled buffers through the frame's copy pass.
|
||||
GuiRenderT::GeometryT *GuiRenderT::uploadGeometry(const void *vertexData, uint32_t vertexSize, const void *indexData, uint32_t indexSize) {
|
||||
SDL_GPUTransferBufferLocation location = {};
|
||||
|
|
@ -1587,6 +1686,9 @@ GuiRenderT::GeometryT *GuiRenderT::uploadGeometry(const void *vertexData, uint32
|
|||
delete geometry;
|
||||
return nullptr;
|
||||
}
|
||||
// Cycled: a pooled buffer came back from geometry that is gone, so nothing in it is wanted, while
|
||||
// the last frame's draws may still be reading it. Writing over it in place makes a tiler wait for
|
||||
// them or copy the buffer first; cycling hands the upload fresh storage instead.
|
||||
memcpy(vertexStage, vertexData, vertexSize);
|
||||
memcpy(indexStage, indexData, indexSize);
|
||||
rgpuUnmapTransferBuffer(device, geometry->vertices->transfer);
|
||||
|
|
@ -1594,11 +1696,11 @@ GuiRenderT::GeometryT *GuiRenderT::uploadGeometry(const void *vertexData, uint32
|
|||
location.transfer_buffer = geometry->vertices->transfer;
|
||||
region.buffer = geometry->vertices->buffer;
|
||||
region.size = vertexSize;
|
||||
rgpuUploadToBuffer(copyPass, &location, ®ion, false);
|
||||
rgpuUploadToBuffer(copyPass, &location, ®ion, true);
|
||||
location.transfer_buffer = geometry->indices->transfer;
|
||||
region.buffer = geometry->indices->buffer;
|
||||
region.size = indexSize;
|
||||
rgpuUploadToBuffer(copyPass, &location, ®ion, false);
|
||||
rgpuUploadToBuffer(copyPass, &location, ®ion, true);
|
||||
geometry->indexCount = (int32_t)(indexSize / sizeof(int32_t));
|
||||
geometry->vertices->inUse = true;
|
||||
geometry->indices->inUse = true;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue