UNPKG

prendy

Version:

Make games with prerendered backdrops using babylonjs and repond

208 lines (180 loc) 7.59 kB
import { Point3D } from "chootils/dist/points3d"; import delay from "delay"; import { getState, onNextTick, setState } from "repond"; import { meta } from "../../meta"; import { AnyCameraName, AnySegmentName, CameraNameByPlace, CameraNameFromPlace, CharacterName, PlaceName, SegmentNameByPlace, SpotNameByPlace, WallNameByPlace, } from "../../types"; import { get2DAngleFromCharacterToSpot, getCharDollStuff } from "../prendyUtils/characters"; import { setGlobalState } from "../prendyUtils/global"; import { doWhenNowCamChanges, doWhenNowSegmentChanges, getSegmentFromSegmentRules } from "../prendyUtils/scene"; type ToPlaceOption<T_PlaceName extends PlaceName> = { toPlace: T_PlaceName; toSpot?: SpotNameByPlace[T_PlaceName]; toPositon?: Point3D; // NOTE might be able to make this auto if the first spot is inside a cam collider? toCam?: CameraNameByPlace[T_PlaceName]; toSegment?: SegmentNameByPlace[T_PlaceName]; // could use nicer type like SegmentNameFromCamAndPlace, or a new SegmentNameFromPlace? }; async function changeSegmentAtLoop< T_Place extends PlaceName, T_Segment extends AnySegmentName // NOTE & might mes with the tye here >(_place: T_Place, newSegmentName: T_Segment) { // NOTE WARNING This will probably break if goalSegmentNameAtLoop changes from somewhere else!!! // to fix: could listen to changes to goalSegmentNameAtLoop // might be fixed now that doWhenNowSegmentChanges listens to any change, instead of waiting for the expected segment name // FIXME this can not work? (the async resolve part) return new Promise<void>((resolve, _reject) => { onNextTick(() => { setGlobalState((state) => { const { goalSegmentNameAtLoop } = state; if (goalSegmentNameAtLoop) { // TEMP resolve straight away if there's already a goalSegmentNameAtLoop console.error("there was already a goalSegmentNameAtLoop when running changeSegmentAtLoopAsync"); resolve(); return {}; } doWhenNowSegmentChanges(newSegmentName, () => resolve()); return { goalSegmentNameAtLoop: newSegmentName }; }); }); }); } async function changeCameraAtLoop< // WARNING This might mess up if the place changes while the cam change was waiting T_Place extends PlaceName, T_Cam extends CameraNameFromPlace<T_Place> & AnyCameraName // NOTE new & type >(_place: T_Place, newCamName: T_Cam) { return new Promise<void>((resolve, _reject) => { setState((state) => { const { goalCamNameAtLoop } = state.global.main; if (goalCamNameAtLoop) { // TEMP resolve straight away if there's already a goalCamNameAtLoop console.error("there was already a goalSegmentNameAtLoop when running changeSegmentAtLoopAsync"); resolve(); return {}; } doWhenNowCamChanges(newCamName, () => resolve()); return { // AnyCameraName needed if there's only 1 place global: { main: { goalCamNameAtLoop: newCamName } }, }; }); }); } export function lookAtSpot<T_Place extends PlaceName>( place: T_Place, spot: SpotNameByPlace[T_Place], character?: CharacterName ) { const { playerCharacter } = getState().global.main; const editedCharacter = character ?? (playerCharacter as CharacterName); const charDollStuff = getCharDollStuff(editedCharacter); const { dollName } = charDollStuff; const angle = get2DAngleFromCharacterToSpot(editedCharacter, place, spot); setState({ dolls: { [dollName]: { rotationYGoal: angle } } }); } export function hideWallIf<T_Place extends PlaceName, T_Wall extends WallNameByPlace[T_Place]>( placeName: T_Place, wallName: T_Wall, isDisabled: boolean ) { // NOTE could update to set properties in a loop to avoid spreading setState((state) => ({ places: { [placeName]: { toggledWalls: { ...(state.places[placeName].toggledWalls as any), [wallName]: !isDisabled } }, }, })); } export async function showStoryView(isVisible: boolean = true) { const GUESSED_FADE_TIME = 1000; // note could listen to something like isFullyFaded and return here, but maybe a set time is okay setGlobalState({ storyOverlayToggled: !isVisible }); await delay(GUESSED_FADE_TIME); } export function setSegment<T_Place extends PlaceName, T_Segment extends SegmentNameByPlace[T_Place]>( _placeName: T_Place, segmentName: T_Segment // whenToRun: "now" | "at loop" = "at loop" ) { return new Promise<void>((resolve, _reject) => { // always sets segment at loop sicne it probably shouldn't change halfway through // if (whenToRun === "now") { // const { nowSegmentName } = getState().global.main; // if (nowSegmentName === segmentName) { // console.warn("already on that segment"); // resolve(); // return; // } // setGlobalState({ goalSegmentName: segmentName }, () => resolve()); // } else if (whenToRun === "at loop") { changeSegmentAtLoop(_placeName, segmentName as any).finally(() => resolve()); // } }); } export function setCamera< T_Place extends PlaceName, T_Cam extends CameraNameFromPlace<T_Place> & AnyCameraName // NOTE & Type >(_placeName: T_Place, cameraName: T_Cam, whenToRun: "now" | "at loop" = "now") { return new Promise<void>((resolve, _reject) => { if (whenToRun === "now") { const { nowPlaceName } = getState().global.main; const { nowCamName } = getState().global.main; // already on that camera if (nowCamName === cameraName) { resolve(); return; } // AnyCameraName needed if there's only 1 place setState({ global: { main: { goalCamName: cameraName } } }, () => resolve()); } else if (whenToRun === "at loop") { changeCameraAtLoop(_placeName, cameraName as any).finally(() => resolve()); } }); } export function goToNewPlace<T_PlaceName extends PlaceName>( toOption: ToPlaceOption<T_PlaceName>, charNameParam?: CharacterName ) { const charName = charNameParam || meta.assets!.characterNames[0]; const { placeInfoByName } = meta.assets!; // NOTE could include waitForPlaceFullyLoaded here so it can be awaited let { toSpot, toPlace, toPositon, toCam, toSegment } = toOption; const { dollName } = getCharDollStuff(charName) ?? {}; if (!dollName) return; onNextTick(() => { setState((state) => { const newPlaceDefaultCamName = placeInfoByName[toPlace].cameraNames[0]; const nowSegmentName = state.global.main.nowSegmentName; const placeInfo = placeInfoByName[toPlace]; // toSpot = toSpot ?? (placeInfo.spotNames[0] as SpotNameByPlace[T_PlaceName]); toSpot = toSpot; // ?? (placeInfo.spotNames[0] as SpotNameByPlace[T_PlaceName]); toPositon = toPositon; toCam = toCam ?? (placeInfo.cameraNames[0] as NonNullable<typeof toCam>); // types as a cam for the chosen place toSegment = toSegment ?? (placeInfo.segmentNames[0] as SegmentNameByPlace[T_PlaceName]); const foundRuleSegmentName = getSegmentFromSegmentRules(toPlace, toCam); if (foundRuleSegmentName) toSegment = foundRuleSegmentName; return { global: { main: { goalPlaceName: toPlace, goalSegmentWhenGoalPlaceLoads: toSegment || nowSegmentName, goalCamWhenNextPlaceLoads: toCam || newPlaceDefaultCamName, }, }, dolls: { [dollName]: { goalPositionAtNewPlace: toPositon, goalSpotNameAtNewPlace: toSpot } }, }; }); }); } // ideas // disable/enable camCubes ? // start/stop characterFollowinglayer ? // }