Skip to content

Commit

Permalink
Add object animations
Browse files Browse the repository at this point in the history
  • Loading branch information
STREGA committed Nov 24, 2023
1 parent 3f7cab6 commit 8e0b6b8
Show file tree
Hide file tree
Showing 6 changed files with 1,011 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright © 2023 Dustin Collins (Strega's Gate)
* All Rights Reserved.
*
* http://stregasgate.com
*/

@MainActor public final class ObjectAnimation3DComponent: ResourceConstrainedComponent {
public var disabled: Bool = false
internal var deltaAccumulator: Float = 0
public var slowAnimationsPastDistance: Float = 20

public var completion: (()->())? = nil
public var removeWhenFinished: Bool = false

public var animationSet: [ObjectAnimation3D]? = nil {
didSet {
self.resourcesState = .pending
}
}

public var blendingDuration: Float? = nil
public var blendingAccumulator: Float = 0
public var blendingProgress: Float {
guard let duration = blendingDuration, duration > 0 else { return 1 }
return .maximum(0, .minimum(1, Float(blendingAccumulator) / Float(duration)))
}

func update(deltaTime: Float, objectScale: Size3) {
if let activeAnimation, activeAnimation.isReady {
activeAnimation.accumulatedTime += deltaTime * (objectScale.length / 3) * activeAnimation.scale
deltaAccumulator += deltaTime
blendingAccumulator += deltaTime
}
}

public var activeAnimation: ObjectAnimation3D? = nil {
didSet {
resourcesState = .pending
}
}

public enum PlaybackState {
case play
case pause
}

public var playbackState: PlaybackState = .play

public var animationProgress: Float {
get {
return activeAnimation?.progress ?? 1
}
set {
activeAnimation?.progress = newValue
}
}

public var animationIsFinished: Bool {
guard activeAnimation?.isReady == true else {return false}
return activeAnimation?.isFinished ?? true
}

public var animationRepeats: Bool {
get {
return activeAnimation?.repeats ?? false
}
set {
activeAnimation?.repeats = newValue
}
}

public var animationScale: Float {
get {
return activeAnimation?.scale ?? 1
}
set {
activeAnimation?.scale = newValue
}
}

public func isActiveAnimation(at index: Int) -> Bool {
guard let activeAnimation else { return false }
guard let animation = animationSet?[index] else { return false }
return activeAnimation == animation
}

public func setAnimation(at index: Int, scale: Float = 1, repeats: Bool = false) {
guard let animation = animationSet?[index] else { return }
self.activeAnimation = animation
}

public func replaceActiveAnimation(withAt index: Int, scale: Float? = nil, repeats: Bool? = nil) {
guard let animation = animationSet?[index] else { return }
self.activeAnimation = animation
if let scale = scale {
self.activeAnimation?.scale = scale
}
if let repeats = repeats {
self.activeAnimation?.repeats = repeats
}
}

public var resourcesState: ResourceState = .pending
public var resources: [any Resource] {
return animationSet ?? []
}

nonisolated public required init() {}
public static let componentID: ComponentID = ComponentID()
}

extension ObjectAnimation3DComponent {
public func reset(keepingAnimationTime keepAnimationTime: Bool = false) {
self.blendingAccumulator = 0
self.blendingDuration = nil
if keepAnimationTime == false, activeAnimation?.isReady == true {
activeAnimation?.accumulatedTime = 0
}
self.playbackState = .play
}
}

extension Entity {
@inlinable @inline(__always)
public var objectAnimation3DComponent: ObjectAnimation3DComponent {
return self[ObjectAnimation3DComponent.self]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright © 2023 Dustin Collins (Strega's Gate)
* All Rights Reserved.
*
* http://stregasgate.com
*/

public final class ObjectAnimation3DSystem: System {
var checkedIDs: Set<ObjectIdentifier> = []
func getFarAway(from entities: ContiguousArray<Entity>) -> Entity? {
func filter(_ entity: Entity) -> Bool {
if let objectAnimation = entity.component(ofType: ObjectAnimation3DComponent.self) {
return objectAnimation.disabled == false && objectAnimation.deltaAccumulator > 0
&& checkedIDs.contains(entity.id) == false
}
return false
}
if let entity = entities.first(where: { filter($0) }) {
checkedIDs.insert(entity.id)
return entity
}
checkedIDs.removeAll(keepingCapacity: true)
if let entity = entities.first(where: { filter($0) }) {
checkedIDs.insert(entity.id)
return entity
}
return nil
}

public override func update(game: Game, input: HID, withTimePassed deltaTime: Float) async {
func shouldAccumulate(entity: Entity) -> Bool {
guard
let cameraTransform = game.cameraEntity?.component(ofType: Transform3Component.self)
else {
return false
}
guard let transform = entity.component(ofType: Transform3Component.self) else {
return false
}
guard let objectAnimation = entity.component(ofType: ObjectAnimation3DComponent.self) else {
return false
}
return cameraTransform.position.distance(from: transform.position)
> objectAnimation.slowAnimationsPastDistance
}
func updateAnimation(for entity: Entity) {
if let component = entity.component(ofType: ObjectAnimation3DComponent.self),
component.disabled == false {
if let animation = component.activeAnimation, animation.isReady {
component.update(
deltaTime: deltaTime + component.deltaAccumulator,
objectScale: entity.component(ofType: Transform3Component.self)?.scale ?? .one
)
if component.playbackState != .pause {
animation.applyAnimation(
atTime: animation.accumulatedTime,
repeating: animation.repeats,
interpolateProgress: component.blendingProgress,
to: &entity.transform3
)
component.deltaAccumulator = 0

if animation.isFinished {
component.completion?()
component.completion = nil
}
}
}
}
}

let slowEntity = getFarAway(from: game.entities)
if let entity = slowEntity {
updateAnimation(for: entity)
}
for entity in game.entities {
guard entity != slowEntity else { continue }
if let component = entity.component(ofType: ObjectAnimation3DComponent.self),
component.disabled == false
{
if shouldAccumulate(entity: entity) {
component.deltaAccumulator += deltaTime
} else {
updateAnimation(for: entity)
}
}
}
}

public override class var phase: System.Phase { .simulation }
public override class func sortOrder() -> SystemSortOrder? { .objectAnimation3DSystem }
}
1 change: 1 addition & 0 deletions Sources/GateEngine/ECS/Base/SortOrder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ extension SystemSortOrder {
public static let collision3DSystem: Self = 3_200

public static let rigSystem: Self = 4_100
public static let objectAnimation3DSystem: Self = 4_100
}

extension RenderingSystemSortOrder {
Expand Down
Loading

0 comments on commit 8e0b6b8

Please sign in to comment.