Skip to content

Releases: STREGAsGate/GateEngine

v0.2.1

01 Jul 17:39
Compare
Choose a tag to compare

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
Screenshot_2023-10-18_at_9 15 51_PM

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

04 Oct 22:49
Compare
Choose a tag to compare

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

01 Oct 16:14
Compare
Choose a tag to compare

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

27 Sep 07:41
Compare
Choose a tag to compare

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:

  • 2D_Pong: A virtual table tenis clone.
  • 2D_JRPG: Uses Sprite, TileMap, Scripting, and StateMachine.

0.0.8

19 Jul 04:05
c62610f
Compare
Choose a tag to compare
0.0.8 Pre-release
Pre-release
  • XInput gamepads now supported on windows
  • Improved gamepad support on macOS and Linux
  • GameState objects are now available for quick and easy state persistance
  • Full FileSystem access is now available on game.platform.fileSystem

0.0.7

20 Jun 22:18
ecad7f0
Compare
Choose a tag to compare
0.0.7 Pre-release
Pre-release

--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

05 Jun 20:23
Compare
Choose a tag to compare
0.0.6 Pre-release
Pre-release

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

01 Jun 19:26
Compare
Choose a tag to compare
0.0.5 Pre-release
Pre-release

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 and Transform2Component are now a class type
  • GameMath is now builtin to the GateEngine package
  • System.sortOrder() uses a new type instead of an Int?
    • Includes convenience functions .after(Collision3DSystem.self)
  • Lot of bug fixes and improvements

0.0.4

21 May 04:55
Compare
Choose a tag to compare
0.0.4 Pre-release
Pre-release

Added 2D and 3D collision

0.0.3

19 May 17:21
Compare
Choose a tag to compare
0.0.3 Pre-release
Pre-release
Update Package.swift