Skip to content

Commit

Permalink
Feat tweens (#753)
Browse files Browse the repository at this point in the history
* feat add tweens component with sequence helper

* fix build and update proto

* fix tween helper system

* refactor tweenHelper custom component to use instead TweenSequence from PB

* add isCompleted and tweenChanged helpers

* create tween mode helper

* add tween loop

* add tests

* add rotate to old moving

* add tween system tests

* update snapshots

* update protocol

* update tests
  • Loading branch information
gonpombo8 authored Oct 10, 2023
1 parent 658c448 commit 9a3a718
Show file tree
Hide file tree
Showing 38 changed files with 1,043 additions and 171 deletions.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"bugs": "https://github.com/decentraland/js-sdk-toolchain/issues",
"dependencies": {
"@actions/core": "^1.10.0",
"@dcl/protocol": "1.0.0-6314457636.commit-a9a962a",
"@dcl/protocol": "1.0.0-6473373363.commit-d038938",
"@dcl/quickjs-emscripten": "^0.21.0-3680274614.commit-1808aa1",
"@dcl/ts-proto": "1.153.0",
"@types/fs-extra": "^9.0.12",
Expand Down
62 changes: 62 additions & 0 deletions packages/@dcl/ecs/src/components/extended/Tween.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { IEngine, LastWriteWinElementSetComponentDefinition } from '../../engine'
import { Tween, PBTween, Move, Rotate, Scale } from '../generated/index.gen'

/**
* @public
*/
export interface TweenHelper {
/**
* @returns a move mode tween
*/
Move: (move: Move) => PBTween['mode']
/**
* @returns a move mode tween
*/
Rotate: (rotate: Rotate) => PBTween['mode']
/**
* @returns a move mode tween
*/
Scale: (scale: Scale) => PBTween['mode']
}

/**
* @public
*/
export interface TweenComponentDefinitionExtended extends LastWriteWinElementSetComponentDefinition<PBTween> {
/**
* Texture helpers with constructor
*/
Mode: TweenHelper
}

const TweenHelper: TweenHelper = {
Move(move) {
return {
$case: 'move' as const,
move
}
},
Rotate(rotate) {
return {
$case: 'rotate',
rotate
}
},
Scale(scale) {
return {
$case: 'scale',
scale
}
}
}

export function defineTweenComponent(
engine: Pick<IEngine, 'defineComponentFromSchema'>
): TweenComponentDefinitionExtended {
const theComponent = Tween(engine)

return {
...theComponent,
Mode: TweenHelper
}
}
4 changes: 4 additions & 0 deletions packages/@dcl/ecs/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { AnimatorComponentDefinitionExtended, defineAnimatorComponent } from './
import { defineMaterialComponent, MaterialComponentDefinitionExtended } from './extended/Material'
import { defineMeshColliderComponent, MeshColliderComponentDefinitionExtended } from './extended/MeshCollider'
import { defineMeshRendererComponent, MeshRendererComponentDefinitionExtended } from './extended/MeshRenderer'
import { defineTweenComponent, TweenComponentDefinitionExtended } from './extended/Tween'
import { LwwComponentGetter, GSetComponentGetter } from './generated/index.gen'
import defineNameComponent, { NameType } from './manual/Name'
import defineSyncComponent, { ISyncComponentsType } from './manual/SyncComponents'
Expand Down Expand Up @@ -37,6 +38,9 @@ export const MeshRenderer: LwwComponentGetter<MeshRendererComponentDefinitionExt
export const MeshCollider: LwwComponentGetter<MeshColliderComponentDefinitionExtended> = (engine) =>
defineMeshColliderComponent(engine)

/* @__PURE__ */
export const Tween: LwwComponentGetter<TweenComponentDefinitionExtended> = (engine) => defineTweenComponent(engine)

/**
* @alpha
*/
Expand Down
1 change: 1 addition & 0 deletions packages/@dcl/ecs/src/components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export type { AnimatorComponentDefinitionExtended } from './extended/Animator'
export type { MeshRendererComponentDefinitionExtended } from './extended/MeshRenderer'
export type { MeshColliderComponentDefinitionExtended } from './extended/MeshCollider'
export type { TextureHelper, MaterialComponentDefinitionExtended } from './extended/Material'
export type { TweenHelper, TweenComponentDefinitionExtended } from './extended/Tween'
export type { TransformComponentExtended, TransformTypeWithOptionals } from './manual/Transform'
export type { NameComponent, NameType } from './manual/Name'
export type { ISyncComponents, ISyncComponentsType } from './manual/SyncComponents'
4 changes: 3 additions & 1 deletion packages/@dcl/ecs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import {
MeshRendererComponentDefinitionExtended,
TransformComponentExtended,
AnimatorComponentDefinitionExtended,
ISyncComponents
ISyncComponents,
TweenComponentDefinitionExtended
} from './components/types'
import { NameComponent } from './components/manual/Name'

Expand All @@ -36,6 +37,7 @@ export const Material: MaterialComponentDefinitionExtended = /* @__PURE__*/ comp
export const MeshRenderer: MeshRendererComponentDefinitionExtended = /* @__PURE__*/ components.MeshRenderer(engine)
export const MeshCollider: MeshColliderComponentDefinitionExtended = /* @__PURE__*/ components.MeshCollider(engine)
export const Name: NameComponent = components.Name(engine)
export const Tween: TweenComponentDefinitionExtended = /* @__PURE__*/ components.Tween(engine)
/**
* @alpha
* This is going to be used for sync components through a server.
Expand Down
9 changes: 8 additions & 1 deletion packages/@dcl/ecs/src/runtime/initialization/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { createPointerEventsSystem, PointerEventsSystem } from '../../systems/ev
import { createInputSystem, IInputSystem } from './../../engine/input'
import { createRaycastSystem, RaycastSystem } from '../../systems/raycast'
import { createVideoEventsSystem, VideoEventsSystem } from '../../systems/videoEvents'
import { TweenSystem, createTweenSystem } from '../../systems/tween'

/**
* @public
Expand Down Expand Up @@ -50,13 +51,19 @@ export const raycastSystem: RaycastSystem = /* @__PURE__ */ createRaycastSystem(
export { RaycastSystem }

/**
* @alpha
* @public
* Register callback functions to a particular entity on video events.
*/
export const videoEventsSystem: VideoEventsSystem = /* @__PURE__ */ createVideoEventsSystem(engine)
export { VideoEventsSystem }

/**
* @public
* Register callback functions to a particular entity on video events.
*/
export const tweenSystem: TweenSystem = createTweenSystem(engine)
export { TweenSystem }

/**
* @public
* Runs an async function
Expand Down
146 changes: 146 additions & 0 deletions packages/@dcl/ecs/src/systems/tween.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import * as components from '../components'
import { PBTween, TweenLoop, TweenStateStatus } from '../components'
import { Entity, IEngine } from '../engine'
import { ReadWriteByteBuffer } from '../serialization/ByteBuffer'
import { dataCompare } from './crdt/utils'

export type TweenSystem = {
tweenCompleted(entity: Entity): boolean
}

/**
* @internal
* @returns tween helper to be used on the scene
*/
export function createTweenSystem(engine: IEngine): TweenSystem {
const Tween = components.Tween(engine)
const TweenState = components.TweenState(engine)
const TweenSequence = components.TweenSequence(engine)

const cache = new Map<
Entity,
{
// Used to detect new tweens for the same entity
tween: Uint8Array
// Avoid updaing again the tween in the case we receieve a network tween from other client
frames: number
// Trigger the isCompleted only once per tween
completed: boolean
// Tween has changed on this frame
changed: boolean
}
>()

function isCompleted(entity: Entity) {
const tweenState = TweenState.getOrNull(entity)
const tween = Tween.getOrNull(entity)
const tweenCache = cache.get(entity)
if (!tweenState || !tween) return false
/* istanbul ignore next */
if (
// Renderer notified that the tween is completed
(tweenChanged(entity) || tweenState.state === TweenStateStatus.TS_COMPLETED) &&
// Avoid sending isCompleted multiple times
!tweenCache?.completed &&
// Amount of frames needed to consider a tween completed
(tweenCache?.frames ?? 0) > 2
) {
return true
}

return false
}

function tweenChanged(entity: Entity) {
const currentTween = Tween.getOrNull(entity)
const prevTween = cache.get(entity)?.tween

/* istanbul ignore next */
if ((currentTween && !prevTween) || (!currentTween && prevTween)) {
return true
}

const currentBuff = new ReadWriteByteBuffer()
Tween.schema.serialize(currentTween!, currentBuff)
const equal = dataCompare(currentBuff.toBinary(), prevTween)

return equal
}

const restartTweens: (() => void)[] = []
// Logic for sequence tweens
engine.addSystem(() => {
for (const restart of restartTweens) {
restart()
}
restartTweens.length = 0
for (const [entity, tween] of engine.getEntitiesWith(Tween)) {
if (tweenChanged(entity)) {
const buffer = new ReadWriteByteBuffer()
Tween.schema.serialize(tween, buffer)
cache.set(entity, {
tween: buffer.toBinary(),
frames: 0,
completed: false,
changed: true
})
continue
}
const tweenCache = cache.get(entity)!
tweenCache.frames += 1
tweenCache.changed = false
if (isCompleted(entity)) {
// Reset tween frames.
tweenCache.frames = 0
// set the tween completed to avoid calling this again for the same tween
tweenCache.completed = true

const tweenSequence = TweenSequence.getOrNull(entity)
if (!tweenSequence) continue
const { sequence } = tweenSequence

if (sequence && sequence.length) {
const [nextTweenSequence, ...otherTweens] = sequence
Tween.createOrReplace(entity, nextTweenSequence)
const mutableTweenHelper = TweenSequence.getMutable(entity)
mutableTweenHelper.sequence = otherTweens
if (tweenSequence.loop === TweenLoop.TL_RESTART) {
mutableTweenHelper.sequence.push(tween)
}
} else if (tweenSequence.loop === TweenLoop.TL_YOYO) {
Tween.createOrReplace(entity, backwardsTween(tween))
} else if (tweenSequence.loop === TweenLoop.TL_RESTART) {
Tween.deleteFrom(entity)
cache.delete(entity)

restartTweens.push(() => {
Tween.createOrReplace(entity, tween)
})
}
}
}
}, Number.NEGATIVE_INFINITY)

function backwardsTween(tween: PBTween): PBTween {
if (tween.mode?.$case === 'move' && tween.mode.move) {
return { ...tween, mode: { ...tween.mode, move: { start: tween.mode.move.end, end: tween.mode.move.start } } }
}
if (tween.mode?.$case === 'rotate' && tween.mode.rotate) {
return {
...tween,
mode: { ...tween.mode, rotate: { start: tween.mode.rotate.end, end: tween.mode.rotate.start } }
}
}
if (tween.mode?.$case === 'scale' && tween.mode.scale) {
return { ...tween, mode: { ...tween.mode, scale: { start: tween.mode.scale.end, end: tween.mode.scale.start } } }
}

/* istanbul ignore next */
throw new Error('Invalid tween')
}

return {
// This event is fired only once per tween
tweenCompleted: isCompleted
}
}
Loading

0 comments on commit 9a3a718

Please sign in to comment.