UNPKG

prendy

Version:

Make games with prerendered backdrops using babylonjs and repond

257 lines (243 loc) 8.74 kB
import { DepthRenderer, Effect, PostProcess, RawTexture2DArray, RenderTargetTexture, Scene, SolidParticleSystem, Texture, } from "@babylonjs/core"; import { Point3D } from "chootils/dist/points3d"; import { mover2dRefs, mover2dState, moverRefs, moverState } from "repond-movers"; import { MyTypes } from "../../declarations"; import { CustomVideoTexture } from "../../helpers/babylonjs/CustomVideoTexture"; import { InRangeForDoll } from "../../helpers/prendyUtils/dolls"; import { makeAutomaticMusicStartRefs, makeAutomaticSoundStartRefs } from "./globalStoreUtils"; import { AnyCameraName, AnySegmentName, CharacterName, DollName, ModelName, PickupName, PlaceInfoByName, PlaceName, } from "../../types"; // save it to global export type PrendySaveState = { global: { nowCamName: string; nowPlaceName: string; nowSegmentName: string; heldPickups: string[]; storyOverlayToggled: boolean; alarmTextIsVisible: boolean; alarmText: string; aSpeechBubbleIsShowing: boolean; aConvoIsHappening: boolean; }; dolls: Record< string, { position: Point3D; rotationY: number; isVisible: boolean; // collisionsEnabled: boolean; // setting isVisible also affects collisions toggledMeshes: Record<string, boolean>; inRange: Record<string, InRangeForDoll>; nowAnimation: string; // animWeightsGoal: Record<string, number>; // maybe not needed, since loading reacts to animation name } >; places: Record< string, { toggledWalls: Record<string, boolean>; } >; characters: Record< string, { hasLeftFirstTrigger: boolean; } >; player: { animationNames: { walking: string; idle: string; }; }; // (scraps) walkSpeed (move to state?) miniBubbles: Record< string, { isVisible: boolean; text: string; // forCharacter } >; speechBubbles: Record< string, { isVisible: boolean; goalText: string; // text? // visibleLetterAmount? nowVideoName: string; font: string; stylesBySpecialText: { [key: string]: { color: string; fontSize: string; fontWeight: string; fontStyle: string; textDecoration: string; }; }; forCharacter: string; typingFinished: boolean; } >; // (whole state) storyState: Record<any, any>; }; export const timeStatePath = ["global", "main", "elapsedGameTime"] as const; export default function global<T_MyTypes extends MyTypes = MyTypes>(prendyAssets: T_MyTypes["Assets"]) { const { musicNames, soundNames, placeInfoByName, prendyOptions } = prendyAssets; type MaybeSegmentName = null | AnySegmentName; type MaybeCam = null | AnyCameraName; type SegmentNameFromCameraAndPlace< T_Place extends keyof PlaceInfoByName, T_Cam extends keyof PlaceInfoByName[T_Place]["segmentNamesByCamera"] > = keyof PlaceInfoByName[T_Place]["segmentNamesByCamera"][T_Cam]; type CameraNameFromPlace<T_Place extends keyof PlaceInfoByName> = keyof PlaceInfoByName[T_Place]["segmentNamesByCamera"]; type CamSegmentRulesOptionsUntyped = Partial<{ [P_PlaceName in PlaceName]: Partial<{ [P_CamName in CameraNameFromPlace<P_PlaceName>]: ( usefulStuff: Record<any, any> // usefulStoryStuff, but before the types for global state exist ) => SegmentNameFromCameraAndPlace<P_PlaceName, P_CamName>; }>; }>; const placeName = prendyOptions.place; // State const getDefaultState = () => ({ // place nowPlaceName: placeName as PlaceName, goalPlaceName: null as null | PlaceName, readyToSwapPlace: false, isLoadingBetweenPlaces: true, loadingOverlayToggled: true, loadingOverlayFullyShowing: true, // cameras goalCamWhenNextPlaceLoads: null as MaybeCam, goalCamNameAtLoop: null as MaybeCam, goalCamName: null as MaybeCam, // NOTE always set goalCamName? and never nowCamName? to prepare everything first? nowCamName: ((prendyOptions.place === placeName ? prendyOptions.camera : "") || ((placeInfoByName as any)?.[placeName as any]?.cameraNames?.[0] as unknown as AnyCameraName)) ?? ("testItemCamName" as AnyCameraName), // if state() is called with a random itemId // segments and slice video nowSegmentName: prendyOptions.segment as AnySegmentName, goalSegmentName: null as MaybeSegmentName, goalSegmentNameAtLoop: null as MaybeSegmentName, goalSegmentWhenGoalPlaceLoads: null as MaybeSegmentName, // changing places modelNamesLoaded: [] as ModelName[], newPlaceModelLoaded: false, newPlaceVideosLoaded: false, newPlaceProbesLoaded: false, // // player playerCharacter: prendyOptions.playerCharacter as CharacterName, // TODO Move to players ? gravityValue: 5, playerMovingPaused: false, // to be able to prevent moving while theres a cutscene for example focusedDoll: prendyAssets.characterOptions[prendyOptions.playerCharacter].doll ?? ("walker" as DollName), focusedDollIsInView: false, // // slate ...mover2dState("slatePos"), ...moverState("slateZoom", { value: prendyOptions.zoomLevels.default, valueGoal: prendyOptions.zoomLevels.default, // springStopSpeed: 0.001, // NOTE not used in mover yet }), // (like scale) slatePosMoveConfigName: "default", // todo move to mover2dState() isOnVerticalScreen: false, zoomMultiplier: 1, // for vertical screens, zoom out a bit // // interacting timeScreenResized: Date.now(), interactButtonPressTime: 0, // story heldPickups: prendyOptions.heldPickups as PickupName[], storyOverlayToggled: false, // so the screen can fade out without affecting loading a new place alarmTextIsVisible: false, // TODO rename to title text? alarmText: "⚠ wobble detected ⚠", // TODO rename to title text? // // meta aSpeechBubbleIsShowing: false, aConvoIsHappening: false, // // backdrop times backdropTime: 0, // this is how far into the backdrop animation it is, it gets reset when reaching the end of a segment, and updated in the frame ticks backdropFrame: 0, // this is the current frame of the backdrop animation, based on the time and frame rate // frameTick: 0, timeMode: "game" as "game" | "pause" | "miniGame", elapsedGameTime: 0, elapsedPauseTime: 0, elapsedMiniGameTime: 0, // when not in the pause menu or the main game isGamePaused: false, gameTimeSpeed: prendyOptions.gameTimeSpeed, gameIsInBackground: false, // debugMessage: "", // latestSave: null as null | PrendySaveState, latestLoadTime: 0, // so things can be initialed after loading state, like isVisible }); // Refs const getDefaultRefs = () => ({ scene: null as null | Scene, backdropTex: null as null | Texture, backdropTexDepth: null as null | Texture, depthRenderer: null as DepthRenderer | null, depthRenderTarget: null as null | RenderTargetTexture, backdropPostProcess: null as null | PostProcess, backdropPostProcessEffect: null as null | Effect, fxaaPostProcess: null as null | PostProcess, // Video shader stuff stretchVideoSize: { x: 1, y: 1 }, stretchVideoGoalSize: { x: 1, y: 1 }, stretchSceneSize: { x: 1, y: 1 }, // ...mover2dRefs("slatePos", { mass: 41.5, stiffness: 50, damping: 10, friction: 0.35, stopSpeed: 0.003 }), ...moverRefs("slateZoom", { mass: 41.5, stiffness: 25, damping: 10, friction: 0.35 }), // NOTE stopSpeed not on 1dMover // sounds: makeAutomaticSoundStartRefs(soundNames), music: makeAutomaticMusicStartRefs(musicNames), musicEffects: { lowPass: null as null | BiquadFilterNode, compress: null as null | DynamicsCompressorNode, extraGain: null as null | GainNode, }, // solidParticleSystems: {} as Record<string, SolidParticleSystem>, // // Temporary timerSpeed: 1, // aConvoIsHappening_timeout: null as null | ReturnType<typeof setTimeout>, // camSegmentRulesOptions: null as null | CamSegmentRulesOptionsUntyped, // NOTE if using the typed version, might need to define it in consts to remove cyclic dependancy // onPickupButtonClick: null as null | ((pickupName: PickupName) => void), // what to do when pressing the pickup button onPickupButtonClick: null as null | ((pickupName: any) => void), // what to do when pressing the pickup button }); // const startStates: InitialItemsState<typeof state> = { const startStates = { main: getDefaultState(), }; return { startStates, getDefaultState, getDefaultRefs }; }