UNPKG

prendy

Version:

Make games with prerendered backdrops using babylonjs and repond

216 lines (187 loc) 8.84 kB
import { Engine, RawTexture2DArray, Texture } from "@babylonjs/core"; import { forEach } from "chootils/dist/loops"; import { getRefs, getState, makeEffects, onNextTick, runEffect, setState } from "repond"; import { CustomVideoTexture } from "../../helpers/babylonjs/CustomVideoTexture"; import { focusSlateOnFocusedDoll, slateSize } from "../../helpers/babylonjs/slate"; import { point3dToVector3 } from "../../helpers/babylonjs/vectors"; import { setDollPosition, setDollRotation } from "../../helpers/prendyHelpers/dolls"; import { updateTexturesForNowCamera } from "../../helpers/prendyUtils/cameraChange"; import { getCharDollStuff } from "../../helpers/prendyUtils/characters"; import { setGlobalState } from "../../helpers/prendyUtils/global"; import { getSpotPosition, getSpotRotation } from "../../helpers/prendyUtils/spots"; import { meta } from "../../meta"; import { AnyCameraName, DollName, PlaceName } from "../../types"; import { unloadBackdropTexturesForPlace } from "../../helpers/babylonjs/usePlace/utils"; import { getNowBackdropFrameInfo } from "../../helpers/prendyUtils/backdrops"; const cachedTextures = {} as Record<string, Texture>; function setPlayerPositionForNewPlace() { const { placeInfoByName } = meta.assets!; const { nowPlaceName, playerCharacter } = getState().global.main; const { dollName } = getCharDollStuff(playerCharacter); const placeInfo = placeInfoByName[nowPlaceName]; const { spotNames } = placeInfo; const { goalSpotNameAtNewPlace, goalPositionAtNewPlace, goalRotationAtNewPlace } = getState().dolls[dollName as string]; let newPosition = goalPositionAtNewPlace ? point3dToVector3(goalPositionAtNewPlace) : undefined; let newRotation = goalRotationAtNewPlace ? point3dToVector3(goalRotationAtNewPlace) : undefined; if (!newPosition || goalSpotNameAtNewPlace) { const newSpotName = goalSpotNameAtNewPlace ?? spotNames[0]; newPosition = getSpotPosition(nowPlaceName, newSpotName); newRotation = getSpotRotation(nowPlaceName, newSpotName); } if (newPosition) setDollPosition(dollName as DollName, newPosition); if (newRotation) setDollRotation(dollName as DollName, newRotation); setState({ dolls: { [dollName as string]: { goalSpotNameAtNewPlace: null } } }); // setDollToSpot({ doll: dollName as DollName, place: nowPlaceName, spot: newSpotName }); } function whenAllVideosLoadedForPlace() { const globalRefs = getRefs().global.main; const { nowPlaceName, nowCamName, nowSegmentName } = getState().global.main; const placesRefs = getRefs().places; const camRef = placesRefs[nowPlaceName].camsRefs[nowCamName]; const { nowTextureIndex } = getNowBackdropFrameInfo(nowCamName); globalRefs.backdropTex = camRef.backdropTexturesBySegment[nowSegmentName][nowTextureIndex ?? 0].color; globalRefs.backdropTexDepth = camRef.backdropTexturesBySegment[nowSegmentName][nowTextureIndex ?? 0].depth; } export const globalChangePlaceEffects = makeEffects(({ itemEffect }) => ({ whenGoalPlaceNameChanges: itemEffect({ run({ newValue: goalPlaceName, itemState: globalState }) { // Remove goalPlaceName if it's the same as nowPlaceName const isNowPlace = goalPlaceName === globalState.nowPlaceName; if (isNowPlace) setState({ global: { main: { goalPlaceName: null } } }); if (goalPlaceName === null || globalState.loadingOverlayFullyShowing || isNowPlace) return; setState({ global: { main: { loadingOverlayToggled: true } } }); }, check: { type: "global", prop: "goalPlaceName" }, step: "loadNewPlace", }), whenSegmentNameChanges: itemEffect({ run({ newValue: goalSegmentName, itemState: globalState }) { // Remove goalSegmentName if it's the same as nowSegmentName const isNowSegment = goalSegmentName === globalState.nowSegmentName; if (isNowSegment) setState({ global: { main: { goalSegmentName: null } } }); }, check: { type: "global", prop: "goalSegmentName" }, step: "loadNewPlace", }), whenGoalCamNameChanges: itemEffect({ run({ newValue: goalCamName, itemState: globalState }) { // Remove goalCamName if it's the same as nowCamName const isNowSegment = goalCamName === globalState.nowCamName; if (isNowSegment) setState({ global: { main: { goalCamName: null } } }); }, check: { type: "global", prop: "goalCamName" }, step: "loadNewPlace", }), whenOverlayFadedOut: itemEffect({ run({ itemState }) { if (itemState.goalPlaceName) setState({ global: { main: { readyToSwapPlace: true } } }); }, check: { type: "global", prop: "loadingOverlayFullyShowing", becomes: true }, step: "loadNewPlace", }), whenOverlayToggledOff: itemEffect({ run() { setGlobalState({ loadingOverlayFullyShowing: false }); }, check: { type: "global", prop: "loadingOverlayToggled", becomes: false }, step: "loadNewPlace", }), whenReadyToSwapPlace: itemEffect({ run({ itemState: globalState }) { const { placeInfoByName } = meta.assets!; // Run on the start of the next repond tick, so all the steps effects can run again onNextTick(() => { const { nowPlaceName, goalPlaceName } = globalState; if (!goalPlaceName) return; const cameraNames = placeInfoByName[nowPlaceName].cameraNames as AnyCameraName[]; const placeRefs = getRefs().places[nowPlaceName]; const globalRefs = getRefs().global.main; setState({ sliceVids: { [nowPlaceName]: { wantToUnload: true } } }); forEach(cameraNames, (camName) => { const camRef = placeRefs.camsRefs[camName]; camRef.probeTexture?.dispose(); camRef.probeTexture = null; }); globalRefs.backdropPostProcess.onApply = null; unloadBackdropTexturesForPlace(nowPlaceName); setGlobalState({ nowPlaceName: goalPlaceName, isLoadingBetweenPlaces: true, newPlaceVideosLoaded: false, newPlaceProbesLoaded: false, newPlaceModelLoaded: false, goalPlaceName: null, readyToSwapPlace: false, }); const { nowCamName, goalCamWhenNextPlaceLoads } = getState().global.main; setState({ global: { main: { nowCamName: goalCamWhenNextPlaceLoads ?? nowCamName } } }); }); }, check: { type: "global", prop: "readyToSwapPlace", becomes: true }, atStepEnd: true, step: "loadNewPlace", }), whenEverythingsLoaded: itemEffect({ run({ itemState: globalState }) { const { prendyOptions } = meta.assets!; const { nowPlaceName, newPlaceVideosLoaded, newPlaceProbesLoaded, modelNamesLoaded, goalSegmentWhenGoalPlaceLoads, } = globalState; const { goalCamWhenNextPlaceLoads } = getState().global.main; const wantedModelsForPlace = prendyOptions.modelNamesByPlace[nowPlaceName].sort(); const loadedModelNames = modelNamesLoaded.sort(); let allModelsAreLoaded = true; forEach(wantedModelsForPlace, (loopedCharacterName) => { if (!loadedModelNames.includes(loopedCharacterName)) allModelsAreLoaded = false; }); if (newPlaceVideosLoaded) { if (goalSegmentWhenGoalPlaceLoads) { setGlobalState({ goalSegmentWhenGoalPlaceLoads: null, goalSegmentName: goalSegmentWhenGoalPlaceLoads, }); } if (goalCamWhenNextPlaceLoads) { setState({ global: { main: { goalCamWhenNextPlaceLoads: null, goalCamName: goalCamWhenNextPlaceLoads, }, }, }); } onNextTick(() => { if (newPlaceVideosLoaded && newPlaceProbesLoaded && allModelsAreLoaded) { setPlayerPositionForNewPlace(); // onNextTick because sometimes the character position was starting incorrect // (maybe because the place-load story-rules werent reacting because it was the wrong flow) setGlobalState({ isLoadingBetweenPlaces: false }); onNextTick(() => { const { nowCamName } = getState().global.main; // whenAllVideosLoadedForPlace(); updateTexturesForNowCamera(nowCamName, true); focusSlateOnFocusedDoll(); // focus on the player // Start fading in the scene setState({ global: { main: { loadingOverlayToggled: false, loadingOverlayFullyShowing: false } } }); onNextTick(() => { runEffect("globalGeneral", "whenGameTimeSpeedChanges"); }); }); } }); } }, check: { type: "global", prop: ["newPlaceVideosLoaded", "newPlaceProbesLoaded", "modelNamesLoaded"], }, atStepEnd: true, step: "loadNewPlace", }), }));