UNPKG

mylingo3d

Version:

Lingo3D is a React/Vue 3d game development framework that ships with a complete visual editor

318 lines (271 loc) 10.5 kB
import { deg2Rad, endPoint, Point3d, rad2Deg } from "@lincode/math" import store, { Reactive } from "@lincode/reactivity" import { interpret } from "xstate" import { onBeforeRender } from "../../events/onBeforeRender" import { onRender } from "../../events/onRender" import { DUMMY_URL, YBOT_URL } from "../../globals" import IDummy, { dummyDefaults, dummySchema, StrideMode } from "../../interface/IDummy" import FoundManager from "../core/FoundManager" import AnimationManager from "../core/AnimatedObjectManager/AnimationManager" import Model from "../Model" import { euler, vector3 } from "../utils/reusables" import poseMachine from "./poseMachine" import fpsAlpha from "../utils/fpsAlpha" import { getCentripetal } from "../../states/useCentripetal" export const dummyTypeMap = new WeakMap<Dummy, "dummy" | "readyplayerme">() export default class Dummy extends Model implements IDummy { public static override componentName = "dummy" public static override defaults = dummyDefaults public static override schema = dummySchema private poseService = interpret(poseMachine) public constructor() { super() this.width = 20 this.depth = 20 this.scale = 1.7 const [setType, getType] = store< "mixamo" | "readyplayerme" | "other" | undefined >(undefined) const [setSpine, getSpine] = store<FoundManager | undefined>(undefined) this.createEffect(() => { const spineName = this.spineNameState.get() const src = (super.src = this.srcState.get()) setSpine(undefined) setType(undefined) dummyTypeMap.delete(this) const handle = this.loaded.then((loaded) => { setType("other") if (spineName) { setSpine(this.find(spineName, true)) if (spineName === "mixamorigSpine") { setType("mixamo") src === YBOT_URL && dummyTypeMap.set(this, "dummy") } else if ( spineName === "Spine" && (loaded.getObjectByName("Wolf3D_Body") || loaded.getObjectByName("Wolf3D_Avatar")) ) { setType("readyplayerme") dummyTypeMap.set(this, "readyplayerme") } return } if ( loaded.getObjectByName("Wolf3D_Body") || loaded.getObjectByName("Wolf3D_Avatar") ) { setSpine(this.find("Spine", true)) setType("readyplayerme") dummyTypeMap.set(this, "readyplayerme") return } const spine = this.find("mixamorigSpine", true) setSpine(spine) if (spine) { setType("mixamo") src === YBOT_URL && dummyTypeMap.set(this, "dummy") } }) return () => { handle.cancel() } }, [this.srcState.get, this.spineNameState.get]) const [setPose, getPose] = store("idle") this.createEffect(() => { const type = getType() if (!type) return const preset = this.presetState.get() const prefix = preset === "rifle" ? "rifle-" : "" const src = this.srcState.get() const parts = src.split("/") parts.pop() let url = parts.join("/") + "/" if (type === "readyplayerme") url = DUMMY_URL + "readyplayerme/" else if (src !== YBOT_URL) { super.animations = this.animationsState.get() this.animation = getPose() return () => { this.animation = undefined } } super.animations = { idle: url + prefix + "idle.fbx", running: url + prefix + "running.fbx", runningBackwards: url + prefix + "running-backwards.fbx", jumping: url + prefix + "falling.fbx", death: url + "death.fbx", ...this.animationsState.get() } this.animation = getPose() return () => { this.animation = undefined super.animations = {} } }, [ this.presetState.get, this.srcState.get, getType, this.animationsState.get ]) const { poseService } = this this.createEffect(() => { const pose = (this.animation = getPose()) if (pose !== "jumping") return if (getCentripetal()) { vector3.set(this.velocity.x, this.jumpHeight, this.velocity.z) vector3.applyMatrix4(this.outerObject3d.matrixWorld) Object.assign(this.velocity, vector3.multiplyScalar(0.2)) } else this.velocity.y = this.jumpHeight const handle = onBeforeRender(() => { this.velocity.y === 0 && poseService.send("JUMP_STOP") }) return () => { handle.cancel() } }, [getPose]) poseService .onTransition( (state) => state.changed && setPose(state.value as string) ) .start() this.then(() => poseService.stop()) this.createEffect(() => { const loadedItem = this.loadedGroup.children[0] if (!loadedItem) return const { strideForward, strideRight, strideMove } = this if (!strideForward && !strideRight) { poseService.send("RUN_STOP") return } let strideMode = this.strideModeState.get() if ( strideMode === "aim" && !("runningBackwards" in this.animations) ) strideMode = "free" const backwards = strideMode === "aim" ? strideForward > 0 : false const sf = backwards ? -strideForward : strideForward const sr = backwards ? strideRight : -strideRight const angle = 90 - Math.atan2(-sf, -sr) * rad2Deg const spine = getSpine() const spineQuaternion = spine?.outerObject3d.quaternion.clone() const loadedItemQuaternion = loadedItem.quaternion.clone() const handle = onRender(() => { poseService.send( backwards ? "RUN_BACKWARDS_START" : "RUN_START" ) const quaternionOld = loadedItem.quaternion.clone() let spinePoint: Point3d | undefined if (strideMode === "aim" && spine && spineQuaternion) { loadedItem.quaternion.copy(loadedItemQuaternion) spine.outerObject3d.quaternion.copy(spineQuaternion) spinePoint = spine.pointAt(1000) } loadedItem.quaternion.setFromEuler( euler.set(0, angle * deg2Rad, 0) ) const quaternionNew = loadedItem.quaternion.clone() loadedItem.quaternion .copy(quaternionOld) .slerp(quaternionNew, fpsAlpha(0.2)) spinePoint && spine?.lookAt(spinePoint) if (!strideMove) return const { x, y } = endPoint( 0, 0, angle + 90, Math.max(Math.abs(strideForward), Math.abs(strideRight)) ) this.moveForward(backwards ? y : -y) this.moveRight(backwards ? x : -x) }) return () => { if ( strideMode === "aim" && !this.strideForward && !this.strideRight ) loadedItem.quaternion.set(0, 0, 0, 0) handle.cancel() } }, [ this.animationsState.get, this.strideModeState.get, this.strideMoveState.get, this.strideForwardState.get, this.strideRightState.get, getSpine ]) } private spineNameState = new Reactive<string | undefined>(undefined) public get spineName() { return this.spineNameState.get() } public set spineName(val) { this.spineNameState.set(val) } public override get resize() { return super.resize } public override set resize(val) {} private srcState = new Reactive(YBOT_URL) public override get src() { return this.srcState.get() } public override set src(val) { this.srcState.set(val) } private animationsState = new Reactive({}) public override get animations(): Record<string, AnimationManager> { return super.animations } public override set animations( val: Record<string, string | AnimationManager> ) { this.animationsState.set(val) } private presetState = new Reactive<"default" | "rifle">("default") public get preset() { return this.presetState.get() } public set preset(val) { this.presetState.set(val) } private strideForwardState = new Reactive(0) public get strideForward() { return this.strideForwardState.get() } public set strideForward(val) { this.strideForwardState.set(val) } private strideRightState = new Reactive(0) public get strideRight() { return this.strideRightState.get() } public set strideRight(val) { this.strideRightState.set(val) } private strideMoveState = new Reactive(false) public get strideMove() { return this.strideMoveState.get() } public set strideMove(val) { this.strideMoveState.set(val) } private strideModeState = new Reactive<StrideMode>("aim") public get strideMode() { return this.strideModeState.get() } public set strideMode(val) { this.strideModeState.set(val) } private jumpHeight = 10 public jump(height = 10) { this.jumpHeight = height this.poseService.send("JUMP_START") } }