Releases: PurpleKingdomGames/indigo
v0.17.0
This release brings Indigo up to Scala 3.4.1 and Scala.js to 1.16.0. You will need to upgrade your projects to at least these version numbers.
As always, a big thank you to all those who contributed to this release in any capacity.
Give it a try, and please report any issues!
Notable changes
Many of these changes represent breaking changes to Indigo's APIs.
Physics iterative solver
Indigo's physics solution continues to be crude and naive, but now includes a simple iterative solver. This provides more accurate results by solving for up to a configurable maximum number of attempts per frame, or until a stable solution is found.
SubSystems have read-only access to the game model
Previously, sub-systems where kept strictly separate from the main game. They were mini-games unto themselves, and could only communicate via events. This worked well, but could be a little cumbersome.
The SubSystem
definition now includes a reference
method, that you must implement:
type ReferenceData
def reference(model: Model): ReferenceData = ???
This allows you to extract information from your game's model that the sub-system would like access to. Access is provided in the SubSystemFrameContext
under a new reference
field.
This means that you no longer need to tediously pass model data to sub-systems via events, should the sub-system's logic require state information from the game in order to do it's job.
Layer improvements
Prior to this release, a SceneUpdateFragment
held a Batch
(a list) of layers, and those layers could be given keys (identifiers) to help you manage the order - particularly when merging scene fragments.
That approach has served us well until now ...but what if you want to dynamically create N layers and have them positioned in a known place in the tree, without interfering with something else? You could try and wrangle a pool of keys, but a much simpler solution is to allow layers to be nested.
Solving that problem creates another, because keys suddenly become difficult to manage. The solution is to move the keys out of the layer, like this:
Original:
Layer(key, ...)
Now:
LayerEntry(key, Layer(...))
Which for convenience, you can construct like this:
SceneUpdateFragment(
key1 -> layer1,
key2 -> layer2
)
So far, all we've done is rearrange things a bit, but what about the nesting? Layer
is now an ADT formed of Layer.Content
which is the equivalent of the old Layer format (all the old constructors assume a content layer, to make migration/upgrading easier), and Layer.Stack
, allowing you to create a tree of layers and assign it to a single key. Stacks are flattened during the rendering process, so they are a purely organisational device.
Finally, Layer
s (that is, content layers: Layer.Content
) have gotten a little cleverer, and can now hold CloneBlank
s - which could previously only be done by SceneUpdateFragment
s. This makes sense, as layers are all about organising elements to be rendered, and some of them will need clone blanks.
Plugin: Font generator
Another exciting improvement (prompted by @mprevel): The plugin generators have a new addition! You can now generate font sheets and FontInfo
instances from font files! This makes it much, much easier to work with font's and Text
instances. No doubt, there is further room for improvement, but it's a great step forward.
Example:
val options: FontOptions =
FontOptions("my font", 16, CharSet.Alphanumeric)
.withColor(RGB.Green)
.withMaxCharactersPerLine(16)
.noAntiAliasing
val files =
IndigoGenerators("com.example.test")
.embedFont("MyFont", sourceFontTTF, options, targetDir / Generators.OutputDirName / "images")
.toSourcePaths(targetDir)
Generates this font sheet:
And this FontInfo
code:
package com.example.test
import indigo.*
// DO NOT EDIT: Generated by Indigo.
object MyFont {
val fontKey: FontKey = FontKey("my font")
val fontInfo: FontInfo =
FontInfo(
fontKey,
151,
68,
FontChar(" ", 129, 51, 9, 17)
).isCaseSensitive
.addChars(
Batch(
FontChar("a", 0, 0, 9, 17),
FontChar("b", 9, 0, 9, 17),
FontChar("c", 18, 0, 9, 17),
FontChar("d", 27, 0, 9, 17),
FontChar("e", 36, 0, 9, 17),
FontChar("f", 45, 0, 9, 17),
FontChar("g", 54, 0, 9, 17),
...
FontChar("9", 120, 51, 9, 17),
FontChar(" ", 129, 51, 9, 17)
)
)
}
Custom HTML templates
Finally, a long requested improvement (ok, mostly by @hobnob 😄): You can now provide your own HTML templates for your games, rather than being stuck with the defaults we generate. This mechanism isn't clever, we assume you know what you're doing!
To use this feature, you can modify your build's IndigoOptions
as follows:
Default
// Set up your game options with the default template, meaning Indigo will generate the template as normal.
val indigoOptions =
IndigoOptions.defaults
.withAssets(indigoAssets)
.useDefaultTemplate // You can set this explicitly, but it is the default.
Custom
// Set up your game options with a custom template
val indigoOptions =
IndigoOptions.defaults
.withAssets(indigoAssets)
.useCustomTemplate(
IndigoTemplate.Inputs(os.pwd / "test-custom-template"), // A directory containing you template files
IndigoTemplate.Outputs(
os.rel / "custom-assets-directory", // The name of the assets directory in your output directory
os.rel / "custom-scripts-directory" // The name of the assets directory in your output directory
)
)
Other Improvements
*.eot
added to list of supported font typesCamera.topLeft
can take a Size param- New options to get a Camera's bounds/frustum
SceneEvent
First
andLast
events addedSceneEvent
LoopNext
andLoopPrevious
events added
Additional bug fixes
ConfigGen
takes mixed case hex values (#688)- SceneFinder: Jump to index is clamped
Further generated notes:
What's Changed
- Mouse button filter was incorrect by @hobnob in #682
- Ensures fonts are auto-generated correctly by @hobnob in #686
- Physics iterations by @davesmith00000 in #694
- Updates the Mouse Wheel to allow for more than 1 axis by @hobnob in #696
- Improving camera frustum methods by @davesmith00000 in #701
- Fixed #698: mod operator for Point-like types by @davesmith00000 in #702
- Update sbt to 1.9.9 by @davesmith00000 in #700
- Fixed #707: Xbox One Controller not working on FF/Chrome by @bilki in #708
- Fixed #706: Expand/contract BoundingBox/Rectangle by Size/Vec2 by @davesmith00000 in #717
- Fixed #705: Circles and Rectangles agree on overlap by @davesmith00000 in #721
- Fixed #713: SubSystemFrame/SceneContext have toFrameContext method by @davesmith00000 in #719
- Scene event improvements by @davesmith00000 in #718
- Fixed #715: SubSystems have read-only access to the model by @davesmith00000 in #716
- Upgrade Scala.js to 1.16.0 by @davesmith00000 in #723
- Upgrade to Scala 3.4.1 by @davesmith00000 in #724
- Issue #714: Nested layers by @davesmith00000 in #727
- Fixed #709: Random basic data types by @davesmith00000 in #728
- Fixed #695: Allow custom HTML templates by @davesmith00000 in #729
- Issue #726: Adding a Font generator to the plugin by @davesmith00000 in #731
- Fixed #732: Content Layers support clone blanks by @davesmith00000 in #734
Full Changelog: v0.16.0...v0.17.0
0.16.0
Great to have a couple of contributors on this release, thank you for your help @JPonte and @mprevel, much appreciated!
As usual, this release has been driven by using Indigo, finding problems, and fixing them.
Noteworthy changes
Scala.js 1.15.0
All newly release Purple Kingdom Games libs are now on Scala.js 1.15.0, and Indigo is no exception.
Electron Executable option is now working properly
This was a tiny fix that is worth it's weight in gold. By default, when you use the indigoRun
command to run your game via Electron, your build will install the latest version via NPM and then run. This works ok, and gives you the latest and greatest Electron version with almost no effort on your part.
The only problem is that NPM will try to check the install is up to date on every single run, slowing down development.
What you can now do instead, is install Electron by some other means, e.g. yarn add --dev electron
, and then add the following line to your build's IndigoOptions
:
.useElectronExecutable("npx electron")
This does the local install manually just once, and then just reuses it with no installation checks, resulting in much faster game start times.
Polygon shader fixed!
During the original port to Ultraviolet, a mistake was made in the polygon shader translation from GLSL to Scala 3. This has now been corrected by @JPonte.
Tyrian / Indigo bridge
The Tyrian / Indigo bridge is a module that allows Tyrian web apps to communicate with embedded Indigo games.
The release of Tyrian 0.9.0 removed the tyrian/indigo bridge module in order the correct the library dependency order.
This module is now published with Indigo, and so follows Indigo version scheme, i.e. it has jumped from 0.8.0
to 0.16.0
.
Improved PathFinder
This is the first release of a new A* PathFinder
by @mprevel. It is both more flexible and more correct than previous attempts!
Please note that the old, suspect SearchGrid
implementation has now been marked as deprecated, and will likely be removed soon.
Improved Physics Scene Scaling
Indigo includes a rudimentary physics engine that is squarely aimed at pixel art games and nothing more. It uses discrete rather than continuous simulation, and so suffers from issues like tunnelling (to be addressed in future updates), but nonetheless is good fun and quite helpful.
Previous versions did not scale well as the simulation had no 'broad phase' to cull unnecessary collision comparisons. This has now been fixed, allowing for much larger simulations.
Adding this new phase has required some changes to the API:
- Worlds are now bounded, i.e. you will need to provide a
BoundingBox
covering the simulation area. - You must also supply a
SimulationSettings
instance as part of the world set up, that gives clues about the size and density of the simulation.
Physics: Transient colliders are static
Another change is that transient colliders (colliders that only exist in this frame and are not officially part of the world instance, typically user controller) are now treated as static in all cases, and you no longer need to set them as such.
Physics: Collider Terminal Velocity
You can now set a terminal velocity on your colliders. This gives you much better control over how your game's play, and helps reduce the possibility of tunnelling.
Bresenham's line algorithm
Bresenham's line algorithm is a useful tool for working out a line across a grid with no overlapping squares. Super handy for drawing pixel art lines or lines of sight across a grid.
There is now an implementation of the algorithm in the Indigo extras library, that was ported from the Roguelike-Starterkit:
indigoextras.utils.Bresenham.line(from, to)
QuadTree overhaul
QuadTree's are data structures used to store spatial data so that it can be quickly queried. QuadTree's are now the underlying data structure used to allow the physics engine to scale.
There has been a "QuadTree" implementation bundled with Indigo in the extras package for many years now. It was based on work done in Indigo's core that handles the texture atlases. Unfortunately, borne out of that very specific use case, it wasn't a true QuadTree, and could only handle data associated with vertices, and then only one entry per leaf, leading to some interesting design choices. What it ended up being used for, in a number of places, was actually not a query-able tree at all, but a sort of sparse data look up for grids.
The QuadTree implementation has now been totally re-worked to meet the physics use case, and can now support data stored against all of the standard spatial types, i.e. Point, Rectangle, Circle, BoundingBox, BoundingCircle, LineSegment, and Vertex, or any custom type with a SpatialOps
instance. The implementation is also much more robust, and can store many entries at the leaf level. How many entries and how deep the tree should go is specified by you.
All this change has resulted in a substantial API rethink and the behaviour is now significantly different. QuadTree's are also now part of the main Indigo library, not the extras package.
SparseGrid
The use case that the old QuadTree's was fulfilling as a sparse data grid was a real need, and since QuadTree's can no longer meet that requirement, a new data type has been made, cunningly named: SparseGrid
!
SparseGrid
is a straight-up grid data structure, that provides a nice API for managing the data in that grid.
Minor bug fixes and improvements
- BoundingBox.resizeBy now takes
Double
s instead ofInt
s - Fixed #658: Large box
overlap
of small circle check - Vector2/3/4 all now have a clamp operation that takes the same type, i.e. you can clamp a Vector2 with another Vector2.
Generated notes below
What's Changed
- minor improvements by @mprevel in #645
- Fix Polygon shader by @JPonte in #654
- Update sbt to 1.9.8 by @davesmith00000 in #655
- Issue #656: Move Tyrian Bridge to Indigo repo by @davesmith00000 in #657
- Fixed #658: Large box overlap small circle check by @davesmith00000 in #664
- Add pathfinder by @mprevel in #648
- Physics engine: Improved scaling performance by @davesmith00000 in #666
- Fixed #671: Bresenham's line algorithm port by @davesmith00000 in #673
- Fixed #672: Added SparseGrid datatype by @davesmith00000 in #674
- Fixed #647: Entry points use defs not vals by @davesmith00000 in #675
- Physics: Terminal Velocity by @davesmith00000 in #679
New Contributors
Full Changelog: v0.15.2...v0.16.0
0.15.2
Many small fixes...
Bug fixes
lastOption
now works correctly onBatch
whether the underlying structure was aBatch.Combine
.- Support multiple newlines in
Text
(and thereforeInputField
) instances. - Plugin: Use of period (
.
) in a cell does not throw a number exception. - Plugin: Negative
Int
s andDouble
s are correctly recognised. - Plugin: Generators can handle trailing quotes.
Camera.LookAt
behaves correctly with different levels / combinations or global + layer magnification.Camera.LookAt
does no produce gaps betweenCloneTiles
due to rounding errors.
General Improvements
Text
can now have it's letter space and line height set.Rectangle.toCircle
deprecated - Disambiguation of whether the circle is inside or outside the rectangle. UsetoIncircle
ortoCircumcircle
instead.Rectangle
andBoundingBox
now haveresizeBy
methods for relative sizing.- Canvas name is now simply the parent ID plus a
-canvas
suffix, to make CSS work easier by removing the square brackets. - Friendly mouse event extractors - the mouse event extractors now only extracts the common case of the position field (e.g.
case MouseEvent.onClick(position) => ???
) instead of all 8-10 fields, and all other properties need to be accessed from a reference to the event. LineSegment
from
andto
aliases are provided forstart
andend
.SceneUpdateFragment
now has awithLayers
method to replace the existing layers with another set.SceneUpdateFragment
now has amapLayers
method, for example if you want to set the magnification on all layers.- Added an alias to
QuickCache
undermutable.QuickCache
.QuickCache
is a handy type if you need to avoid recalculating values, but it's been hidden under themutable
package as a warning to tread carefully. Batch
: Addedlast
operation.Batch
: Addeddistint
anddistinctBy
operations.Batch
: AddedpadTo
operation.
Plugin Improvements
IndigoOptions
now supports antialiasing flag, defaults to false.- Generators:
embedText
now supports apresent
function simply of typeString => String
that allows you to transform any arbitrary file contents into Scala code, to be included as a source file in your game. - Generators: Trailing delimiters are supported.
- Generators: Empty cells are seen as
null
values (the wordnull
is assumed to be a string!). - Generators: Columns containing
null
values are typed as optional.
Full Changelog: v0.15.1...v0.15.2
0.15.1
Bug fix release
Indigo 0.15.0 included the biggest changes to the Indigo plugins, more or less since they were created, and with that... a couple of bugs, too.
Issue with fullLinkJS in sbt
At the point when Scala.js switched naming from fullOpt
to fullLinkJS
and fastOptJS
to fastLinkJS
, the output files and paths were changed, and work was done to support both the old and new arrangements in sbt and Mill. Unfortunately an issue has crept in with fullLinkJS
's output file not be found correctly. This has now been resolved.
Mill Indigo Plugin Generators were inadequate...
Before any release, Indigo goes through quite a lot of testing, but not enough on this occasion. After the release of 0.15.0, two issues quickly became apparent with how the new generators worked with Mill, specifically.
mill clean <module>
was not removing generated classes. This wasn't noticed because during plugin development, a lot of "hard cleaning" (i.e. removing the out directory) happens to be sure of what has been loaded, and in that scenario,mill clean
has no work to do.- Working with generators on multi-module builds was terrible, because unlike the sbt version, all the generated files were being written to a common shared output folder, instead of a folder that was part of each module's build.
The fix for both was to make generators adhere to Mill's T.dest
, which is obvious in hindsight... oh well...
Reformulating the plugin's a little to make this work properly now requires the following changes (taken from real examples):
Mill
Before:
val indigoGenerators: IndigoGenerators =
IndigoGenerators
.mill("snake.generated")
.listAssets("Assets", indigoOptions.assets)
.generateConfig("SnakeConfig", indigoOptions)
After:
val indigoGenerators: IndigoGenerators =
IndigoGenerators("snake.generated")
.listAssets("Assets", indigoOptions.assets)
.generateConfig("SnakeConfig", indigoOptions)
sbt
Before:
IndigoGenerators
.sbt((Compile / sourceManaged).value, "pirate.generated")
.listAssets("Assets", pirateOptions.assets)
.generateConfig("Config", pirateOptions)
.embedAseprite("CaptainAnim", baseDirectory.value / "assets" / "captain" / "Captain Clown Nose Data.json")
.toSourceFiles
.toSet
After:
IndigoGenerators("pirate.generated")
.listAssets("Assets", pirateOptions.assets)
.generateConfig("Config", pirateOptions)
.embedAseprite("CaptainAnim", baseDirectory.value / "assets" / "captain" / "Captain Clown Nose Data.json")
.toSourceFiles((Compile / sourceManaged).value)
.toSet
0.15.0
Indigo 0.15.0
There are quite a few changes an improvements in this release, the two big ones to note (as they are breaking changes) are:
- New game resizing behaviour
- Indigo plugin overhaul.
Read on for more details.
Warning! New game resizing behaviour!
An excellent piece of work has been done that allows you to set the resizing behaviour of your game in your game config, and which has enabled us to deprecate the old (and rather suspect) debounce script.
You can now set resizing your game's resizing policy, like this:
// Resizes to fill the available space
GameConfig.default.withResizePolicy(ResizePolicy.Resize)
// Stays at a fixed size
GameConfig.default.withResizePolicy(ResizePolicy.NoResize)
// Resizes to fill the available space, but retains the original aspect ratio
GameConfig.default.withResizePolicy(ResizePolicy.ResizePreserveAspect)
When building or running an Indigo game via the plugin, the new behaviour will be dealt with automatically.
However, if you have an existing web-page, or you are embedding your game manually, then you'll need to do some additional set up to avoid the game trying to aggressively fill an ever expanding screen!
There are two ways to handle the new behaviour:
- Change your
GameConfig
by calling the.noResize
method, which is the same as.withResizePolicy(ResizePolicy.Resize)
. - Copy (and adjust) the following CSS (included by default by the plugin):
#indigo-container {
display: flex;
align-items: center;
justify-content: center;
padding:0px;
margin:0px;
width: 100vw;
height: 100vh;
}
New Features
Focus events
New events have been added to let you know when the application or canvas has gained or lost focus.
ApplicationGainedFocus
- The application is in focusApplicationLostFocus
- The application has lost focusCanvasGainedFocus
- The game canvas is in focusCanvasLostFocus
- The game canvas has lost focus
Online / Offline network events
New events have been added to indicate whether your game is on/offline. Beware that this is not a complete test, since your machine will be considered online, if your local network is available.
Online
Offline
Mouse and Pointer events now have a lot more detail
The main example of this is MouseEvent.Click
which used to take a simple argument "position", but now has all of the following, inherited from the browser.:
MouseEvent.Click(position, buttons, isAltKeyDown, isCtrlKeyDown, isMetaKeyDown, isShiftKeyDown, movementPosition, button)
Indigo plugin overhaul
This release includes an unusually large amount of work on Indigo's Mill / sbt plugins. The motivation behind the work stems from the question: "Does indigo need an editor?"
Indigo is a code-first engine for people who like making games by writing code. However, writing code alone can be inconvenient, boring, and slow when there is a lot of manual wiring or repeat tasks to perform, and this is where a game editor typically comes in. Your game is ultimately code, but you get a load of tools to speed up the dev process.
Is it possible to make the plugin do more? To reduce the delta between having and not having a full editor by providing more tools in the plugin?
One thing that the Scala community has become somewhat allergic to is a framework. Frameworks however, provide convenience and speed at the cost of having to conform to how they work ...but that sort of sounds like what we want.
Indigo can now be thought of as a library that can optionally be upgraded to a framework:
- Indigo itself is (pretty much) a library - you can combine it with any other web targeted Scala.js project or framework.
- The Indigo plugin - Now adds more functionality and takes Indigo into framework territory for game building speed and convenience.
There is a lot more to do in this space, but this release contains two specific changes:
IndigoOptions
- A simple builder object that replaces all the old sbt / mill settings with a standard interface and expanded options. (When you upgrade an existing project to Indigo 0.15.0, you'll likely get a build error here.)IndigoGenerators
- A set of code generators that help perform/automate a variety of tasks.
Indigo Options
The experience of setting up an Indigo game in Mill and sbt has now been unified with the new IndigoOptions
type, which provides a fluent API for IDE support, examples below:
Mill
The MillIndigo
trait requires the presence of an indigoOptions
instance.
val indigoOptions: IndigoOptions =
IndigoOptions.defaults
.withTitle("My Game")
.withWindowWidth(720)
.withWindowHeight(516)
.withBackgroundColor("black") // defaults to 'white'
.excludeAssets {
case p if p.startsWith(os.RelPath("data")) => true
case _ => false
}
sbt
It is recommended that you do NOT inline your game options, because you will probably want to reference them in you generators later.
import indigoplugin.IndigoOptions
// In sbt, you must use a lazy val.
lazy val myGameOptions: IndigoOptions =
IndigoOptions.defaults
.withTitle("My Game")
.withWindowSize(1280, 720)
.withBackgroundColor("#21293f") // You can use valid CSS colors, like 'black', hex values, or rgb() / rgba()
.excludeAssetPaths {
case p if p.endsWith(".json") => true
case p if p.startsWith("shaders") => true
}
lazy val mygame =
project
.enablePlugins(ScalaJSPlugin, SbtIndigo)
.settings(
name := "mygame",
indigoOptions := myGameOptions
)
Asset paths and filtering
For the most part, the new IndigoOptions
type is simply a re-organisation of the old settings, however, assets paths have changed.
You can set the path to your asset directory using withAssetDirectory
, as follows:
IndigoOptions.defaults
.withAssetDirectoy(os.RelPath.rel / "assets")
The argument is a relative path and can be expressed with a String
, os-lib / Mill RelPath
(recommended, even in sbt), or File
. The path is now relative to the project directory, not the module.
You can also use (as above) excludeAssets
or includeAssets
to filter your assets (these methods use os.Path
s, if you'd prefer a String path, use excludeAssetPaths
and includeAssetPaths
instead). For example you could exclude a whole directory of in-development assets / files, but include one specific file from that folder.
The rules are, in order:
- If there is a specific include rule that covers the file, include it.
- If there is a specific exclude rule that covers the file, exclude it.
- Otherwise, include it.
This behaviour is important when used in conjunction with the asset listing generator (see below).
Indigo Generators
The purpose of these generators is to automate some of the less glamorous work, or fiddly tasks. Much of this work was originally prototyped and validated in the Roguelike demo.
The generators are once again based on a fluent API that you can explore with the assistance of your IDE, however here are simple Mill / sbt examples and a list of the generators available:
Mill
val indigoGenerators: IndigoGenerators =
IndigoGenerators
.mill("com.mygame") // Full qualified package for generated code to live under.
.listAssets("Assets", indigoOptions.assets) // Generate asset information
.generateConfig("MyConfig", indigoOptions) // Generate Config sync'd to build settings.
sbt
Please refer to sbt documentation for ways to tell sbt how to cache these values, if desired.
Compile / sourceGenerators += Def.task {
IndigoGenerators
.sbt((Compile / sourceManaged).value, "com.mygame")
.listAssets("Assets", pirateOptions.assets)
.generateConfig("Config", myGameOptions)
.toSourceFiles
}
Generator list
There are variations of most of the generators to allow you to supply paths as either
String
,File
, oros.Path
/os.RelPath
(as appropriate).
Wiring generators:
listAssets
- takes in yourIndigoOptions
assets configuration and generates a module that contains details of all of your assets, respecting asset filter rules, ready to be loaded in your game. Names that would generate duplicates / collisions will be reported (and will also fail compilation).generateConfig
- takes in youIndigoOptions
settings and generates a game config instance that conforms toGameConfig.default
with your build details embedded, namely the background colour and viewport size.
Data embedding helpers:
embedText
- Read any text file and embeds it into code.embedGLSLShaders
- Reads a pair of GLSL shader files (vertex and fragment), and embeds them as an Indigo shader, optionally validates the code (requires local install of glslValidator).embedAseprite
- Reads an Aseprite json export (array type), and converts it directly into anAseprite
instance and adds it to your source code. This means you do not need to load the separate JSON at runtime. Handy for loading screens.
Data generators:
Data generators generate data! The intended use case here is for when you have a table of data that you'd like to edit conveniently, say, a table of item stats in a spreadsheet that you export to CSV, and then put into your game. Clearly you could load these as a text file, but that means (at runtime) loading the data, parsing it, validating it, and all look ups being subject to things like null checks. The data generators by pass all of that by embedding your data as compile time values...
0.15.0-RC3
Release Candidate 3
This release is an unexpected addition to the previous RC versions. All the core functionality can be considered stable. A full release version will be cut as soon as the documentation site is overhauled (in progress).
Hope you like it, and please report any bugs and issues you come across!
Details of Changes
Axis-Aligned Physics Support
Indigo now includes a basic physics engine. The engine is what is known as a rigid body axis-aligned physics engine. This is as simple as physics gets! Axis aligned means that there is no support for rotation, but for the kinds of game Indigo is intended for, this is still good enough for a wide range of use cases.
(The framerate in this gif isn't great, but the simulation runs smoothly at 60 fps.)
Pong!
There is a playable game of Pong! (Single player) made with Indigo's new physics capabilities here:
The Cursed Pirate
There will also be a new version of the cursed pirate demo where all the hand rolled collision work has been replaced by the physics engine.
Limitations and issues, bullets and paper walls
This is a limited Physics model, and it will have problems both in terms of physical accuracy (even within the scope of what we model) and in terms of initial feature set. You are welcome and encouraged to help by playing with it, reporting issues, and helping with code / maths / physics if you can.
One example of a known limitation of the current model, is that it suffers from what is sometimes called the 'bullet through paper' problem.
Simply put: If a projectile is moving fast enough and an object in it's flight path is small / thin enough, then on frame A the projectile's calculated position is on one side of the object it should collide with, and on the next frame B it's calculated position is on the other side of the object in it's way. The result is that the two objects never collide in the engine and carry on unobstructed, even though they clearly would collide in the real world.
You can see this problem for yourself if you go and play the pong demo until the ball is going really, really fast! (It doesn't take long provided you can keep up.)
The question is: Is this problem... a problem? Well, probably not for the most part. It's all about relative scale and speed. Of course work will be done to try and fix it, but for the kinds of games Indigo is designed for, the expectation is that this probably won't come up too often, and can be coped with by the game developer with a little defensive coding. Time will tell if that turns out to be the case or not!
There are no docs! How do I use it? (Doc's coming soon-ish...)
All of the physics primitives are available with the following import:
import indigo.physics.*
As always, a lot of work has gone into the API to let the types and IDE discoverable methods guide you, however, here are a few pointers to get you started.
Everything starts with a world, that you keep in your Model
:
World.empty[String]
Notice the String
type there? That is the type of your collider 'tags', and can be any type you like, an enum is a good idea. The tags are used to help you access and manipulate the scene. For example the world has methods like findByTag
.
Initially, your world is a lot like outer space. No general forces or resistances. Let's add some gravity and wind resistance:
World.empty[String]
.addForces(Vector2(0, 1000))
.withResistance(Resistance(0.01))
What are these magic numbers? Well here you need to use some discretion. The physics engine does not understand the scale of your game - the scale can be anything! In this case we're adding a force that acts on all elements at a rate of 1000 'units' per second, meaning, at full speed an item will be moving 1000 'thingys' downwards. How do we decide what the units are/mean? Well in this case we have a screen that is 600 pixels high, so we're saying that terminal velocity (maximum speed by falling) allows you to cover just under 2 times the height of the screen in 1 second. Again, this scale is arbitrary, you will need to think about what makes sense for your game.
Now we need a bouncy ball and a platform for it to land on. Our screen is 800x600 in size, so positions are based on that:
World.empty[String]
.addForces(Vector2(0, 1000))
.withResistance(Resistance(0.01))
.withColliders(
Collider("ball", BoundingCircle(400.0, 80.0, 25.0)).withRestitution(Restitution(0.8)),
Collider("platform", BoundingBox(10.0d, 550.0, 780.0, 20.0)).makeStatic
)
Physics engines use things called 'colliders' to represent physical things in the world that can ...collide! Colliders can be Collider.Circle
or Collider.Box
types. We have two colliders:
- The first collider is our ball, it's positioned near the top of the screen, centered horizontally. The ball has been given a 'restitution' value of
0.8
. Restitution is how much energy is retained when it bounces, so0
would be no bounce at all, and1
would be a perfect elastic bounce. - The second is our platform, notice that the platform has been made 'static'. This has a few effects internally, but all you need to know right now is that it won't move no matter what happens.
This simulation is entirely self contained, but you can interact with it using the various world 'find', 'modify' and 'remove' methods. You can also add transient colliders, which are colliders that don't exist in the world but are added deliberately on update / presentation for some reason. A good example of this is the paddles in the Pong demo, which are added every frame because they are directly controlled by the player (Tip: Make things like this static!). They need to be in the world for the ball to interact with, but otherwise aren't an on going part of the simulation.
Ok, so we have a simulation set up, how do we use it?
- First, we place it in our model during the
initialModel
call. - On each
FrameTick
of theupdateModel
function, we need to update it using:world.update(context.delta)
(delta
is the time delta, or the time that has elapsed since the last frame. All calculations are frame rate independent.) - We need to decide how to present our world...
Presentation comes in a few flavours, which you can mix and match to suit your needs. You can:
- Call
present
on the world and render aBatch[SceneNode]
based on what is in the simulation. Present can also do a partial presentation based on the filter arguments. - Look up the colliders or map over them yourself and render the bits you care about. Perhaps your simulation is abstract and you just need information rather than it being a direct 1-1 collider to renderable relationship.
- Render things in your model directly, that only affect the sim as transient colliders (like the paddles in Pong).
In our case, let's just render the world (as in the gif at the top) and see what's going on*:
def present(world: World[String]): Outcome[SceneUpdateFragment] =
Outcome(
SceneUpdateFragment(
world.present {
case Collider.Circle(_, bounds, _, _, _, _, _, _, _) =>
Shape.Circle(
bounds.position.toPoint,
bounds.radius.toInt,
Fill.Color(RGBA.White.withAlpha(0.2)),
Stroke(1, RGBA.White)
)
case Collider.Box(_, bounds, _, _, _, _, _, _, _) =>
Shape.Box(
bounds.toRectangle,
Fill.Color(RGBA.White.withAlpha(0.2)),
Stroke(1, RGBA.White)
)
}
)
)
(* Rendering colliders on top of your game can also be handy for visual debugging.)
Wrapping up
...and that's about it for now. There are other options to explore, for example you can emit events on collision, but hopefully there's enough information here to get you started.
Explore the features. Raise issues and bugs. See what you can make, and don't forget to show us your creations on Discord!
Other minor changes
- Construct
Shape.Circle
with aCircle
Batch.sorted
added
Bug fixes
- BoundingBox lineIntersect doesn't fail at 0,0
What's Changed
- Fixed #538: Shape.Circle apply with Circle by @davesmith00000 in #543
- Fixed #540: Added Batch.sorted by @davesmith00000 in #544
- Fixed #539: BoundingBox lineIntersect fails at 0,0 by @davesmith00000 in #545
- Reflection data by @davesmith00000 in #546
- Fixes a minor typo in the documentation by @hobnob in #549
- Update sbt to 1.9.1 by @davesmith00000 in #548
- Axis-Aligned Physics by @davesmith00000 in #551
- Issue/561/world present improvements 2 by @davesmith00000 in #563
- Piratical physics by @davesmith00000 in #568
- Mill upgrade by @hobnob in #567
- Update sbt to 1.9.2 by @davesmith00000 in #572
**Full Change...
0.15.0-RC2
Release Candidate 2
Since the previous release candidate, we've been putting Indigo through its paces and all seems stable and ok.
However we're not quite ready for a full release, a few improvements have been added and there may be others in the works. Nonetheless, you can use the RC versions with a high degree of confidence.
Hope you like it, and please report any bugs and issues you come across!
Details of Changes
Scala 3.3.0
Since the last RC version, Scala 3.3.0 has been released, and Indigo 0.15.0-RC2 is now up to date with that, as well as the freshly minted Ultraviolet 0.1.2 (which has also been updated).
Note: You will need to upgrade your Scala version.
For the most part, everything works exactly as it always did, the main motivation was just to get onto the LTS Scala 3 version.
The only slight wrinkle is a change in how Ultraviolet uses external inline functions, which is detailed here:
https://github.com/PurpleKingdomGames/ultraviolet#using-ultraviolet-with-scala-330
Geometry Changes & Improvements
Most the other changes in this release are to do with the various geometry primitives.
New Circle datatype
A new Circle
type has been added to complement Rectangle
. This was proposed by someone who was trying to re-create the classic game 'Pong' and needed to represent a ball. How this has been overlooked until now is anyone's guess.
All under one roof
The geometry
package has been promoted out of the indigo-extras
library into indigo
proper. You're getting geometry primitives whether you like it or not!
Where previously you needed to import types like BoundingBox
from indigoextras.geometry
, they are now first class citizens under import indigo.*
.
Better interop with other data types
Before the geometry package was promoted, the geom types had a difficult relationship with the main data types. For example, you could say Vertex(1, 0).toPoint
, but because of the dependency relationship, you could not say Point(1, 0).toVertex
.
These sorts of minor irritations have now been fixed.
BoundingCircle (and Circle) improvements
BoundingCircle
is relatively new and hasn't had the same amount of love as BoundingBox
, but improvements have now been made to bring it up to scratch:
- More resizing options, including
expand
andcontract
. - Conversation methods for interop with other types.
- Ability to check if a
BoundingBox
overlaps aBoundingCircle
. - Create a
BoundingCircle
from three vertices where all three lie on the circles circumference. - Added a new 'approximately equals' operator:
~==
.
Other minor changes
Radians
now has atoDegrees
methodVertex
now has simple arithmatic operators that work withVector2
Shape.Circle
usesCircle
under the hood.
What's Changed
- Circle Datatype by @davesmith00000 in #511
- Promote the geometry package from extras to main package by @davesmith00000 in #527
- Update sbt to 1.8.3 by @davesmith00000 in #509
- Create a
BoundingCircle
from 3 vertices by @davesmith00000 in #530 - Upgrade to Scala 3.3.0 by @davesmith00000 in #532
- Update sbt to 1.9.0 by @davesmith00000 in #533
- Fixed #521: BoundingCircle overlaps BoundingBox by @davesmith00000 in #534
Full Changelog: v0.15.0-RC1...v0.15.0-RC2
0.15.0-RC1
Release Candidate
This release is expected to become a full release of Indigo 0.15.0, as long as no serious issues are discovered in the next few months.
It's been over six months since the last release of Indigo, but that is a reflection of the magnitude of the changes the engine has undergone. The aim of all this work is eventually to break Indigo away from being reliant on Scala.js and WebGL, so that it can move onto new platforms like WebGPU, and one day Scala Native / JVM. There is much more work to do, however.
If you're excited about that work, please consider contributing to or sponsoring the project.
New / Noteworthy stuff!
Ultraviolet Support
This is the bulk of the change in this release. Indigo no longer has any GLSL code in it, all of its shaders have been rewritten in Scala 3 using Ultraviolet.
Ultraviolet is an early and slightly rough around the edges DSL + macro that converts Scala 3 into shader code, and at the moment that code is GLSL flavoured. However in the future, the aim is to expand the output formats to support other shader dialects allowing Indigo shaders to work on other platforms. There is more work to do here, but it really does work!
From an end user perspective, if you aren't writing custom shaders then you shouldn't notice any difference. If you have written custom GLSL shaders, these will still work, you just need to be aware of one change, which is that two of the standard function hooks must now take an argument and return a value.
These functions:
void vertex() {}
void fragment() {}
Must now be:
vec4 vertex(vec4 v){ // where v is the vertex as calculated so far...
return v;
}
vec4 fragment(vec4 c){ // where c is the colour of the fragment as calculated so far...
return c;
}
The other standard hooks of composite
, prepare
, and light
are unchanged.
You can use Ultraviolet to write shaders for Indigo in two ways:
- As generated source code in a
Source
shader, which just takes a string. - Using the new
UltravioletShader
shader type.
Full documentation will be provided at some point in the near future (hopefully), but at the moment the Indigo website is undergoing a rebuild. In the meantime, please refer to the Ultraviolet repo, examples that are being produced, and come and ask questions on our Discord server.
Pointer support
Huge thanks to @grzegorz-bielski for adding support for pointer events, allowing Indigo games to response to touch inputs on things like phones and tablets. This is also an overhaul of mouse input events. The work is a combination of a new set of Pointer events, and Pointer
state in the InputState
object in the FrameContext
.
Frame Rate Limiter
The framerate limiter has never worked. At various times it was thought to work but didn't. In a nutshell this is because the browser chooses when a frame will be rendered, and any attempt to massage or throttle that time is an uphill battle.
However! The frame rate limiter (err... once again?) now works! And this is very important, so much so that a previous change to disable it by default has been reverted and new games are limited to 60fps by default.
What is behind this move?
- Improved electron performance.
- VSync.
If you're using Electron, then in an Indigo game's build you can tell Electron to disable it's frame rate limiter. In older version of Electron this does not present a problem, but in newer versions the performance has improved significantly, and you can now get frame rates in excess of 1000 fps! (On a simple game doing very little, with up to date graphics drivers, and so on and so on...)
Unfortunately at these high frame rates strange things begin to happen, so as a recommended default, it's been capped to 60 fps. You can still turn it off if you so wish.
The other reason is vsync. VSync is when your graphics card updates your game at the same rate as your monitor. For a pixel art game of the kind Indigo was designed for, 60 fps is ...lets just say it's ample, but if your game is running in a browser and the monitor has a 144 refresh rate, then your game will try and run at 144 fps! More than double our recommended cap of 60. Bear in mind that this doesn't just update your rendering, but also all your model processing and so on, which is needless work.
So again, the default has now been capped at a sensible 60 fps.
You can modify the framerate limit or disable it as you wish via your GameConfig
. Existing games are recommended to enable it at some specific frame rate rather than just leaving it to peoples machines to decide.
Improvements!
General Shader Improvements
- Custom/Blend UBO offsets have been reviewed and we've been able to make more room for custom data UBO's, meaning you can now send more data to your shaders.
- The binding of the
VERTEX
variable now happens in a different place, so that in general you need only manipulate UV's in vertex shaders without needing to worry about how that translates onto texture atlases. (There is more work to do here to make this stuff easier...) - The
ShaderData
APIs such asUniformBlockName
and the various data types have seen a range of improvements, and there are more helpful conversion extension methods now under theindigo.syntax.*
andindigo.syntax.shaders.*
imports.
BlankEntity
A new super simple BlankEntity
primitive has been added. This is for all you shader writers out there. BlankEntity
is a way to describe a space on the screen with nothing in it, that you can then draw to using a shader.
This was made for the IndigoShader
game type described below, but in fact has more general purpose uses.
IndigoShader
game type
Along side the existing IndigoSandbox
, IndigoDemo
, and IndigoGame
game types, we now have IndigoShader
. Again this is aimed at people who just want to practice writing shaders for fun. You can see a few examples of it in use in this repo.
Essentially this is a new game type with a pared back API that only renders a single shader that fills the window, somewhat like the brilliant ShaderToy.
Other bug fixes and improvements
Here are some of the general improvements in this release. These have been made to smooth out development and were generally discovered while working on a new demo.
- The confusing
sceneTime
has been replaced withsceneStartTime
andsceneRunning
. - AudioPlayer now throws when an asset is missing, to avoid confusion as to why your audio isn't playing.
- Transparent backgrounds really are transparent now!
- Added
withSize
andwithPosition
toRectangle
. - Improved and expanded local storage events.
- Add Dice.rollRange helper
- Indigo's sbt and Mill plugins now support fast/fullLinkJS
- Added
Outcome.fromOption
and syntax improvements - You can now multiply
Point
/Size
/Rectangle
byDouble
s - You can construct
SceneUpdateFragment
s fromBatch
/Option
of Layers - Bug fix: Volume of a playing track can now be changed! 🤦
- Improved asset loading
- Ability to query timeline animations for their length
- Added
Timeline.atOrElse
, timelines return an optional value, and this helper allows you to quickly ensure you always get something back. - Added
Timeline.atOrLast
, when a timeline ends, instead of vanishing, the last frame continues to be rendered.
What's Changed
- Convert Indigo Shaders from GLSL to Ultraviolet by @davesmith00000 in #444
- Fixed #422: Misleading sceneTime has been replaced by @davesmith00000 in #426
- Improve uniform syntax and API experience by @davesmith00000 in #427
- Scalafmt checks on build by @davesmith00000 in #464
- IndigoShader entry point (Issue #462) by @davesmith00000 in #468
- Pointer events support by @grzegorz-bielski in #471
- Fixed #461: Best effort to match target framerate by @davesmith00000 in #474
- Fixed #451: Transparent backgrounds are transparent by @davesmith00000 in #478
- Fixing game loop rebuild race condition by @davesmith00000 in #496
- WIP: Timeline Animation Improvements by @davesmith00000 in #500
- Fixed #501: Volume of playing track can be changed by @davesmith00000 in #502
- Add basic Pointers state by @grzegorz-bielski in #503
New Contributors
- @grzegorz-bielski made their first contribution in #471
Full Changelog: v0.14.0...v0.15.0-RC1
0.14.0
Notable changes:
New Build Target Versions:
- Scala.js 1.11.0
- Scala 3.2.0
- Mill 0.10.7
Timeline animations!
This is a brand new and frankly very exciting feature! You can now describe timeline-style animations which can happen consecutively and concurrently (just like you can in animation tools). Here is an example of a timeline that animates two sequential stages of movement for a graphic, and also affects it's appearance throughout the whole animation via the modifier
:
val tl: Seconds => Timeline[Graphic[_]] = delay =>
timeline(
layer(
startAfter(delay),
animate(5.seconds) {
easeInOut >>> lerp(Point(0), Point(100)) >>> move(_)
},
animate(3.seconds) {
easeInOut >>> lerp(Point(100), Point(100, 0)) >>> move(_)
}
),
layer(
startAfter(delay),
animate(8.seconds, modifier)
)
)
This is great for animating things like title screens, but we've also added 'scrubbing' support to the existing Clip
and Sprite
types so that you can control keyframe animations (such as those exported form Aseprite) using this method.
The DSL is available via import indigo.syntax.animation.*
.
Timelines do not really know what they are animating, they are just a way to produce a value of a certain type over time. Thus they can return renderable nodes, Outcome
s, or perhaps even model values if you get creative!
Electron installs do not re-do local module installs
This seemed like a small improvement at the time but has changed the way I work.
Global installs of Electron (particularly if you're a Nix user...) display extreme variation in terms of performance and capabilities across different platforms. However using a global install was convenient because it was nice and fast compared to doing indigoplugin.ElectronInstall.Version("^18.0.0")
or indigoplugin.ElectronInstall.Latest
in your sbt or Mill build. This was mostly down to npm reinstalling artefacts on every build, but this has now been fixed.
You are now advised to prefer a Version
or Latest
over Global
.
Batch improvements and bug fixes
Batch
is a cornerstone data type for Indigo that was introduced in the previous release. It was introduced for performance reasons with an expectation that the usability would need some work. Batch
is still a bit rough around the edges, but it's getting better. Here are a few of the latest improvements:
- Better tail operations + uncons
- Better mkString
- Catch only NonFatal errors during Batch.equals
- Added flatten to Batch
- Better pattern matching (still needs work!)
- Improved constructors
Scene's now have their on context type
All the standard functions supply a FrameContext
which provides a suite of useful tools and bits of information, but Scene
s now have their own variation. SceneContext
does everything that FrameContext
does, but also provides the running sceneTime
so that you can start animations from when you entered the scene, and the current scene name.
Other Improvements
- Add advanced config parameter to disable browser context menu (#385)
- Add ability to customize html background color
- Replaced
SceneAudio.None
withOption
types (#403) - Outcome can sequence
List
,Batch
, and their non-empty their variations (other sequencing syntax options also added) - Improve the JS launch method so you can now supply an element (#407 & Fixed #400)
- Many new
SignalFunction
helper methods Vector2, 3, 4
andVertex
now have ceil and floor methods- Syntax:
1.second
Sprite
's supportScrubTo
animation actionSceneUpdateFragment
's will accept an optional nodeLayer
's will accept an optional nodeClip
s can be exported toGraphic
s.Clip
s can be fixed withtoFrame
Clip
s support scrubbingClip
s expose frame info and clip length- Enabled/Disable lighting at the material level
Shape
enhancements, modifier methods
Bug fixes
- Fixed #390:
closestPointOnLine
works in all directions (Line Segment) - UBO hashing fixed
- UBO data is only set if it has changed
- Reset
lastFrameAdvance
after aAnimationAction.jumpTo
in order to start animation in the correct place - Fixed #419:
Signal
easing functions useSignalFunction
versions - Fixed #420:
Polygon
s with stroke 1 render better
What's Changed
- Move Lens to indigo.shared.utils package (#381) by @zikolach in #383
- Fixed #390: closestPointOnLine works in all directions by @davesmith00000 in #392
- Working on Batch improvements by @davesmith00000 in #389
- Zikolach feature/301/context menu config by @davesmith00000 in #393
- Add ability to customize html background color by @zikolach in #386
- Fixed #379: Do no reinstall local electron by @davesmith00000 in #395
- Do not re-set UBOs whose data has not changed by @davesmith00000 in #394
- Bugfix aseprite json by @solarized-fox in #398
- example mill template in docs is incomplete by @solarized-fox in #402
- Reset lastFrameAdvance after an AnimationAction.jumpTo by @solarized-fox in #399
- simplification: replace SceneAudio.None with Option types by @solarized-fox in #403
- Sequencing things by @davesmith00000 in #405
- Improve launch method by @davesmith00000 in #407
- Upgrades 9/9/2022 by @davesmith00000 in #406
- Timeline Animations by @davesmith00000 in #416
- Scenes use
SceneContext
in place ofFrameContext
by @davesmith00000 in #413
New Contributors
- @solarized-fox made their first contribution in #398
Full Changelog: v0.13.0...v0.14.0
0.13.0
Notable changes:
New Build Target Versions:
- Scala.js 1.10.0
- Scala 3.1.2
- Mill 0.10.4
New Batch
type (Breaking change)
Since the time Indigo was first created, all scenes have been described as List
s of things. The great thing about using List
is that it is immutable and very friendly to use when compared to something like Array
.
Unfortunately, List
's friendly syntax comes with some fairly punishing performance penalties. Since game loops run very frequently, it is quite easy to get yourself into trouble with Indigo, just by doing some fairly ordinary list-like things. Under the hood, Indigo runs almost entirely on js.Array
s which give us better performance, but until now we had to convert all those user defined lists into js.Array
s on every frame.
The nature of how List
s were primarily utilised by Indigo’s API’s was for construction rather than traversal. So what we need is a new type that is super fast to construct, very low friction to convert to js.Array
, and is at least no worse (performance-wise) than using a list in every other regard.
For this we have a new type called Batch
, and this has replaced List
and other collection types almost everywhere.
Batch
is very similar to Chain
from Cats! The main difference is just that it’s backed by js.Array
and named differently to avoid confusion. Batch
is not particularly clever. It is immutable and borrows from Chain
to speed up construction, while delegating to js.Array
for pretty much everything else.
Batch
has a lot of the usual list-like operations, and can be empty (Batch.empty
) or populated as expected, e.g. Batch(1, 2, 3)
. To aid construction there are a number of convertors like Batch.fromList(myList)
, and you can also import indigo.syntax.*
to allow myList.toBatch
. Of course, you should avoid doing conversions like that in performance sensitive areas of code.
Option to use non-global Electron
You can now supply a flag to the plugin instructing it as to how it should source the Electron version you’d like to use. By default, this preserves the existing behaviour of assuming a global install (here in sbt syntax, but works in Mill too).
electronInstall := indigoplugin.ElectronInstall.Global
Other options include: Latest
, Version("^18.0.0")
, and PathToExecutable("/bin/../electron")
.
What's Changed
- Small rectangles now overlap correctly by @hobnob in #349
- Override Key.hashCode to be compatible with equals. by @tsnee in #348
- Add utility for generic sprite sheet format. by @tsnee in #350
- Upgrade to Scala.js 1.10.0 by @davesmith00000 in #365
- Fixed #359: Option to use non-global Electron by @davesmith00000 in #367
- Replace List with a new type focused on construction speed by @davesmith00000 in #368
- Improving Mutant Performance by @davesmith00000 in #370
- Fixed #351: Ability to filter quadtree results by @davesmith00000 in #372
- Issue 353 texture channel tiling bug by @davesmith00000 in #374
New Contributors
Full Changelog: v0.12.1...v0.13.0