Releases: STREGAsGate/GateEngine
v0.2.1
This release adds support for Swift 5.10
What's New?
New Blending Modes
Two new blending modes are available for DrawCommand.
.add
Will do an addition of the color to the render target
.subtract
Will do a subtraction of the color from the render target
Object Animations
A new cached resource is available: ObjectAnimation3D
This resource stores Transform3 keyframes loaded from a file.
In combination with ObjectAnimation3DComponent
and ObjectAnimation3DSystem
you can now load and play object animations from files.
The animation will cause the objects Transform3Component to be updated, moving the whole object.
ObjectAnimation3DComponent
works similarly to Rig3DComponent
.
It can hold multiple animations and you can switch between them as desired.
entity.insert(ObjectAnimation3DComponent.self) { component in
component.animationSet = [
ObjectAnimation3D(
path: "Assets/MyAnimation.glb",
options: .named("AnimationTrack")
)
]
component.setAnimation(at: 0)
}
Billboarding
BillboardSystem
and BillboardComponent
are now available.
Adding BillboardComponent to a 3D entity will cause the entity to always face toward the camera.
SpriteComponent Animation Queue
SpriteComponent now uses a queue for animations.
By using queueAnimation(_)
along with the various playbackState options you can now easily create animations that seamlessly transition to one another.
Sprites in Scenes
You can now insert Sprite into a Scene, placing them in the 3D world.
Resource Loading Meta
You can now check to see which resources are still loading.
You can use this to find bugs and performance issues, as well as create a loading bar using the count against your known count.
print(game.resourceManager.currentlyLoading)
["Assets/Stages/test01/diffuse.png", "Assets/Characters/Protagonist/Protagonist.glb", "Assets/Characters/Protagonist/Protagonist.glb", "Assets/Characters/Protagonist/Protagonist.glb", "Assets/Characters/Protagonist/Protagonist.glb", "Assets/Characters/Protagonist/Protagonist.glb", "Assets/Characters/Protagonist/Protagonist.glb", "Assets/Characters/Protagonist/Protagonist.glb", "Assets/UI/HUD.png"]
Shaders
Branch
Branch allows you to conditionally return one of two provided values.
fsh.output.color = myCompareValue.branch(
success: Vec4(color.rgb, 0),
failure: Vec4(color.rgb, 1)
)
fsh.output.color = fsh.branch(
if: myCompareValue,
success: Vec4(color.rgb, 0),
failure: Vec4(color.rgb, 1)
)
Switch
Switch allows you to conditionally return one of many provided values.
fsh.output.color = myEnumValue.switch([
.case(0, result: Vec4(.red),
.case(1, result: Vec4(.green),
.case(2, result: Vec4(.blue),
])
Discard
Discard allows you to insert a fragment discard inline anywhere you can pass a value.
The operation will cancel the entire fragment resulting in no color, depth, or stencil writes.
let color: Vec4 = ...
fsh.output.color = color.discard(if: myBoolValue)
Texture Size
Returns the size of the texture.
let size = fsh.channel(0).texture.size
DrawCommand
DrawCommand is the API utilized by Scene and Canvas. Every insert(...)
call on those containers will ultimately create a DrawCommand.
You can now create your own 1DrawCommand and add them directly to Scene and Canvas.
This means if Canvas or Scene can't do what you want or the available implementation is too inefficient, you can make your own.
DrawCommand also gives access to standard renderer functionality in flags, like winding direction, cull modes, depth test modes, etc...
let command = DrawCommand(
resource: .geometry(.rectOriginTopLeft),
// An instance will be drawn for each transform
transforms: [someTransform],
material: someMaterial,
flags: .default
)
canvas.insert(command)
Resource Cache Change
The .whileReferenced
cache hint will now destroy resources as soon as their reference count reaches zero.
Previously they would all be garbage collected every 1 minute.
If you experience a change in your project you can change the affected resource to work the old way by changing the cache hint.
let myResource = Geometry(path: "MyModel.obj")
myResource.cacheHint = .until(minutes: 1)
More resources have been migrated to the standard cache system.
Skeleton
Contains animatable joint information for a skinned character.
let skeleton = Skeleton(path: "MyCharacter.gltf")
SkeletalAnimation
Contains the transforms for joints that together form an animation for a skeleton
let animation = SkeletalAnimation(path: "MyCharacter.gltf", options: .named("Running"))
Together they eliminate the need to use async functions to load skinned characters.
entity.insert(Rig3DComponent.self) { component in
component.skeleton = Skeleton(path: "Cat.glb")
component.animationSet = [SkeletalAnimation(path: "Cat.glb")]
component.activeAnimation = Rig3DAnimation(component.animationSet![0], repeats: true)
}
ResourceConstrainedComponent
An alternative Component protocol is available.
This protocol gives components the ability to declare their Resources.
component(ofType:_) will now return nil if any resource it uses is not .ready
.
if let component = entity.component(ofType: Rig3DComponent.self) {
// Skeleton and all SkeletalAnimations are ready
}else{
// A resource was not ready
}
This will eliminate boilerplate since these components can't be used without their resources.
Note:
For now, using the subscript entity[Rig3DComponent.self]
will still return the component no matter the state.
PerformanceRenderingSystem
The PerformanceRenderingSystem will now show some additional information.
- Count of all resources currently loading
- Count of Skeletons in cache
- Count of SkeletalAnimations in cache
- Count of TileSets in cache
- Count of TileMaps in cache
Mutable Geometry Variants
MutableGeometry has been available for a while, and is useful for changing geometry dynamically.
New variants are now available as MutablePoints
and MutableLines
.
Entity Cleanup
System subclasses can now override a function that is called when an entity is removed from the game.
This gives you an opportunity to preform System specific cleanup.
override func gameDidRemove(entity: Entity, game: Game, input: HID) async {
// Do entity cleanup
}
Immediate Geometry
GateEngine currently caches all geometry, a process that requires at least 1 game tick to complete.
So the geometry would not draw until the next frame.
This works great for file loaded geometry.
However sometimes you just want to draw a few primitives directly to a RenderTarget immediately.
You can now create a Geometry object from RawGeometry and have it block until its state is .ready
.
For simple geometry there would be no significant delay.
// geometry.state is instantly .ready and it will
// be drawable during the frame it was created in
let geometry = Geometry(rawGeometry, immediate: true)
This method is helpful for generating content once, such as building a UI element off screen and then rendering it as a texture every frame.
Quality Of Life (macOS)
The main window will now be restored to the display that it was on when it was last open.
This means if you have multiple displays you will no longer need to drag the game back to display 2 every launch.
This change will only effect Debug builds.
Bug Fixes
Metal Renderer
- Fixed an issue where geometry could be drawn with incorrect layout and shaders
Skinning
- Fixed a bug in the included skinning vertex shader
macOS/iOS/tvOS
- Fixed an issue where playing spatial sounds before starting music would cause a crash.
Linux/HTML5
- Fixed an issue where textures in channel(1) or greater would use the texture in channel(0).
- Fixed an issue where renderTarget textures used in Sprites would draw upside down.
- Fixed an issue where UVs could snap to an incorrect pixel on low res textures.
General
- Fixed an issue where a previously modified cacheHint would revert back to default when creating a new reference to the same resource.
- Cache log output now shows additional details on the specifics of the cached resource.
[GateEngine] Removing cache (unused for 5 min), Texture: Assets/Weapons/Handgun1_Black_Diffuse.png, MipMapping: none
[GateEngine] Removing cache (unused for 5 min), Geometry: Assets/Weapons/Handgun1.glb, Named: Handgun1
[GateEngine] Removing cache (unused for 5 min), Geometry: Assets/Weapons/Handgun1.glb, Named: Handgun1_MuzzleFlash1
0.1.2
Minor Changes
This update mostly exists to fix an issues using Swift Package Manager with tagged version.
Delay
Similar to the existing deferred() { } function, within a system you can now use a new delay function:
delay(duration: 0.5) {
// do stuff in half a second
}
Metal Graphics Enhancements
- Decreased memory usage when loading geometry.
Bugs
- Fixed an issue where custom uniform values on fragment shaders would crash with Metal and DirectX.
0.1.1
Performance Improvements
This update includes several changes and optimization for performance.
Component storage has been completely rebuilt.
Lookup and retrieval APIs are now substantially faster.
let myComponent = entity[MyComponent.self]
//
let myComponent = entity.component(ofType: MyComponent.self)
//
let hasMyComponent = entity.hasComponent(ofType: MyComponent.self)
Standardized DeltaTime
deltaTime is now a multiple of 0.004166666667 which will reduce floating point rounding errors.
This change is a step toward making the simulation deterministic.
Because of this change highPrecisionDeltaTime has been removed.
PerformanceRenderingSystem
The PerformanceRenderingSystem
is a RenderingSystem you can add to your game to visualize performance.
The values displayed have been completely overhauled.
- "Frame Time": The duration in milliseconds from simulation start to renderer, but not round trip.
- "Total Systems Time": The total System, PlatformSystem, and RenderingSystem duration in milliseconds.
- "Rendering Systems": The weight in percent of RenderingSystems.
- "Systems": The weight in percent of System and PlatformSystem.
- All systems are now weighted by percent of the "Total Systems Time".
You can use the PerformanceRenderingSystem by simply adding it to your game:
game.insertSystem(PerformanceRenderingSystem.self)
Bug Fixes & Other
- Removed excessive inlining which caused some slowdowns
- Fixed an issue where OpenGL platforms would fail render some geometry.
0.1.0
Major 2D Update
This update includes many new and refined elements directed at 2D games!
TileMap
New TileMap and TileSet importers are available for the open source Tiled app's JSON formats.
Completely overhauled TileMap, TileSet, and TileMapComponent. TileMap and TileSet are now resources and can be loaded like a Texture or Geometry:
let tileSet = TileSet(path: "TileSet.tsj")
let tileMap = TileMap(path: "TileMap.tmj")
// Convenince init for TileMapComponent
let entity = Entity(components: [
TileMapComponent(
tileSetPath: "Resources/TileSet.tsj",
tileMapPath: "Resources/TileMap.tmj"
),
])
You can now modify tiles in a TileMapComponent
in real time:
let tileMapCoordinate = TileMap.Layer.Coordinate(column: 10, row: 15)
let newTile = TileMap.Tile(id: tileSetTileID, options: [])
entity[TileMapComponent.self].layers[0].setTile(tile, at: tileMapCoordinate)
TileMapComponent
now supports animations:
entity[TileMapComponent.self].layers[0].animations.append(
TileMapComponent.Layer.TileAnimation(
coordinate: TileMap.Layer.Coordinate(column: 7, row: 12),
frames: [
TileMap.Tile(id: 5, options: []),
TileMap.Tile(id: 6, options: []),
],
duration: 1.5
)
)
StateMachine
A StateMachineComponent and StateMachineSystem are now available.
You can find an in-depth example on StateMachine usage at GateEngineDemos/JRPG.
Scripting
Gravity is now available as a scripting language in GateEngine.
Using Gravity in Swift:
let gravity = Gravity()
// Compile the script
try gravity.compile(scriptURL)
// Run the main function of the gravity script
let result = try gravity.runMain()
// print the result returned from `func main()`
print("Result:", result)
// Get a var by name and print it's value
print(gravity.getVar("myGravityScriptGloabalVar")!)
// Run an existing `func` in the gravity script
try gravity.runFunc("myGravityScriptFunction")
You can find an in-depth example on gravity scripting usage at GateEngineDemos/JRPG.
Keyboard
Keyboard buttons no longer return an optional and the button returned will always represent the key requested.
Previously if you requested a ButtonState for a searchable key, like .shift(.anyVariation) which represents the left or right shift buttons, the button would return either the left shift, right shift, or nil depending on if any were pressed.
Now the button will always be what you requested and will check each variation only when isPressed is checked.
This is a simple intuitive change and you probably expected this to be happening all along, but now it really is happening 😅
// previously
if input.keyboard.button(.shift(.anyVariation))?.isPressed == true { }
// New
if input.keyboard.button(.shift(.anyVariation)).isPressed { }
System
Errors thrown and in resource states are now an instance of GateEngineError
.
This will allow more precise error handling as seen in this example:
if case let .failed(error) = resource.state {
switch error {
case let .failedToLoad(reason):
// Load a different resource
// or change the game to function without this one
default:
fatalError("Unhandled error \(error).")
}
}
GateEngine Demos
New demos are available in the GateEngineDemos repository:
0.0.8
0.0.7
--System--
System subclasses now support async/await**
func setup(game: Game, input: HID) async {}
func shouldUpdate(game: Game, input: HID, withTimePassed deltaTime: Float) async -> Bool {}
func update(game: Game, input: HID, withTimePassed deltaTime: Float) async {}
System and RenderingSystem now have a highPrecisionDeltaTime property.
//System
override func update(game: Game, input: HID, withTimePassed deltaTime: Float) async {
let timeInterval = self.highPrecisionDeltaTime
}
//RenderingSystem
override func render(game: Game, window: Window, withTimePassed deltaTime: Float) {
let timeInterval = self.highPrecisionDeltaTime
}
You can use this value when accumulating durations such as with timers.
--GameDelegate--
On iOS and iPadOS you can create a window for an AirPlay screen, allowing you to draw your game on both the device and a TV or Mac display.
func screenBecomeAvailable(game: Game) throws -> Window? {
return try game.windowManager.createWindow(identifier: "external display window")
}
On iPadOS you can now create multiple windows, and users can also request windows. This allows GateEngine to be used for document based apps if you want.
func userRequestedWindow(game: Game) throws -> Window? {
return try game.windowManager.createWindow(identifier: "userwindow")
}
--Input--
You can check which input method was used last. This is helpful for updating UI to display the correct prompts for a user to presss.
if input.recentInputMethod == .mouseKeyboard {
text.string = "Press Spacebar!"
}else if input.recentInputMethod == .gamepad {
text.string = "Press \(gamePads.any.button.confirmButton)!"
}
Mouse Buttons now allow you to check for repetition based gestures that respect the users preference for multi-click duration.
if input.mouse.button(.primary).isPressed(ifDifferent: &inputRecipts, andGesture: .doubleClick) {
// Double Clicked!
}
You can also check the count yourself for gamplay style combo stuff
let numClicks = input.mouse.button(.primary).pressCount
You can now track scroll input
if input.mouse.scroller(.horizontal).didScroll(ifDifferent: &inputRecipts) {
// Scrolled along x
}
--Other--
- On macOS window size, position, and full screen state are now remembered for every window and restored on launch.
- On HTML5 fixed an issue where mouse lock failed to behave as expected.
0.0.6
New Keyboard Layout Translators
If you develop with a non-qwerty layout you can now express buttons in your layout
This is helpful for collaboration with international coworkers
// Default is qwerty
input.keyboard.button("w").isPressed
// New Translators
input.keyboard.button(.qwerty("w")).isPressed
input.keyboard.button(.qwertz("w")).isPressed
input.keyboard.button(.azerty("z")).isPressed
CharacterStream
Capture intended keyboard inputs in real time. When capturing, the stream will build a string out of user inputs for end user reading.
The string property will automatically be modified like a text editor, including backspace, delete, and arrow keys.
No need to attempt to parse keyboard inputs.
// Create a stream and capture keyboard input
let stream = CharacterStream()
stream.startCapture()
// Add the stream to a renderable text object
text.string = stream.string
Any Keys
You can ask for a keyboard button state that matches any kind of that key
let any1Pressed = keyboard.button(.number(1, .anyVariation)).isPressed
let topRow1Pressed = keyboard.button(.number(1, .standard)).isPressed
let numberPad1Pressed = keyboard.button(.number(1, .numberPad)).isPressed
Keyboard Audit
Windows, macOS, Linux, iOS, tvOS, and HTML5 are now standardized for keyboard input with full size keyboards.
Platform API
Game.platform now represents the current platform with a checkable type.
Platforms now have access to resource checking and loading APIs
func locateResource(from path: String) async -> String?
func loadResource(from path: String) async throws -> Data
These are a prerequisite for the upcoming custom file loaders feature.
Linux
Linux rendering is now functional. Linux has many more features needed but is making rapid progress.
0.0.5
Major Changes
- Mouse Lock, Hide, DeltaPosition
- Windows 10 HiDPI
- Windows 10 Alt+Enter Fullscreen Shortcut
- HTML5 Pre-Launch User Gesture Screen
- iOS/tvOS Keyboard Input
- iOS Mouse Input
- new
game.defer{/*run this later*/}
closure to move a code block to the end of the simulation Transform3Component
andTransform2Component
are now a class type- GameMath is now builtin to the GateEngine package
System.sortOrder()
uses a new type instead of anInt?
- Includes convenience functions
.after(Collision3DSystem.self)
- Includes convenience functions
- Lot of bug fixes and improvements