Hey guys,
Can anyone help me with some component code that Iβm working on to animate some hand drawn clouds. The clouds are meant to slide back and forth with a few sub elements rotating and bobbing while they do.
I keep getting a white screen and the following error when I try to preview:
Unhandled promise rejection: Error: Unknown type: [object Object]
at (anonymous) runtime.js
at Vd runtime.js
at (anonymous) runtime.js
at jd runtime.js
at (anonymous) runtime.js
at (anonymous) runtime.js
at (anonymous) runtime.js
at (anonymous) runtime.js
Hereβs the component code:
// cloud-animator.ts
import * as ecs from '@8thwall/ecs'
// Studio injects all of the component APIs onto `ecs.components` at runtime.
// Locally we cast it to any so webpack only has to resolve "@8thwall/ecs".
const {
Position,
Hierarchy,
Name,
Visible,
RotateAnimation,
} = (ecs as any).components
/** Per-entity animation state */
type CloudAnimState = {
baseX: number
baseY: number
baseZ: number
rotating: bigint[] // IDs of children that rotate
slides: bigint[] // IDs of slide-frame children
topChild: bigint | null // ID of the child bobbing up/down
time: number // accumulated seconds
frameTime: number // seconds since last slide swap
currentSlide: number // index into `slides`
}
ecs.registerComponent<CloudAnimState>({
name: 'cloud-animator',
// βββ THIS SCHEMA MUST BE A LITERAL OBJECT βββ
schema: {
rotSpeed: ecs.f32, // deg/sec
bobHeight: ecs.f32, // world units up/down
bobDuration: ecs.f32, // secs per bob cycle
slideDistance: ecs.f32, // world units back-and-forth
slideDuration: ecs.f32, // secs per full slide cycle
slideInterval: ecs.f32, // secs between frame swaps
reverse: ecs.boolean, // invert slide direction?
},
// βββ AND THESE DEFAULTS MUST ALSO BE A LITERAL OBJECT βββ
schemaDefaults: {
rotSpeed: 30,
bobHeight: 0.2,
bobDuration: 2,
slideDistance: 1.5,
slideDuration: 4,
slideInterval: 0.15,
reverse: false,
},
// one hidden slot for runtime state
data: {
_state: {} as CloudAnimState,
},
// Called once, when component is added to an entity
add: (world, component) => {
const eid = Number(component.eid) // cast once
const cfg = component.schema
// record starting transform
const {x, y, z} = Position.get(world, eid)
component.data._state = {
baseX: x,
baseY: y,
baseZ: z,
rotating: [],
slides: [],
topChild: null,
time: 0,
frameTime: 0,
currentSlide: 0,
}
// bucket children by name
const hier = Hierarchy.get(world, eid)
const kids = Array.isArray(hier.children) ? hier.children : []
for (const kid of kids) {
const nm = Name.get(world, kid).value.toLowerCase()
if (nm.includes('top')) {
component.data._state.topChild = kid
} else if (nm.includes('rot')) {
component.data._state.rotating.push(kid)
} else if (nm.includes('slide') || nm.includes('frame')) {
component.data._state.slides.push(kid)
}
}
// hide all but the first slide
component.data._state.slides.forEach((cid, i) => {
Visible.getMutable(world, cid).value = i === 0
})
},
// Runs every frame
tick: (world, component, dt) => {
const eid = Number(component.eid)
const cfg = component.schema
const s = component.data._state
// dt is a bigint in ms β seconds
const sec = Number(dt) * 0.001
s.time += sec
s.frameTime += sec
// β SLIDE BACK & FORTH β
const alpha = Math.sin((s.time / cfg.slideDuration) * Math.PI * 2) * 0.5 + 0.5
const dir = cfg.reverse ? -1 : 1
const nx = s.baseX + (alpha - 0.5) * cfg.slideDistance * dir
Position.set(world, eid, {x: nx, y: s.baseY, z: s.baseZ})
// β BOB THE TOP CHILD β
if (s.topChild !== null) {
const bob = Math.sin((s.time / cfg.bobDuration) * Math.PI * 2) * cfg.bobHeight
Position.set(world, s.topChild, {
x: s.baseX,
y: s.baseY + bob,
z: s.baseZ,
})
}
// β ROTATE CHILDREN β
for (const cid of s.rotating) {
RotateAnimation.set(world, cid, {
loop: true,
fromX: 0,
fromY: 0,
fromZ: 0,
toX: 0,
toY: 360,
toZ: 0,
shortestPath: false,
duration: 1000 / cfg.rotSpeed, // ms per deg/sec
})
}
// β CYCLE SLIDES β
if (s.slides.length > 0 && s.frameTime >= cfg.slideInterval) {
Visible.getMutable(world, s.slides[s.currentSlide]).value = false
s.currentSlide = (s.currentSlide + 1) % s.slides.length
Visible.getMutable(world, s.slides[s.currentSlide]).value = true
s.frameTime = 0
}
},
// Called when component is removed (no-op here)
remove: () => {},
})