prendy
Version:
Make games with prerendered backdrops using babylonjs and repond
141 lines (140 loc) • 6.49 kB
JavaScript
import delay from "delay";
import { getState, onNextTick, setState } from "repond";
import { meta } from "../../meta";
import { get2DAngleFromCharacterToSpot, getCharDollStuff } from "../prendyUtils/characters";
import { setGlobalState } from "../prendyUtils/global";
import { doWhenNowCamChanges, doWhenNowSegmentChanges, getSegmentFromSegmentRules } from "../prendyUtils/scene";
async function changeSegmentAtLoop(_place, newSegmentName) {
// 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((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(_place, newCamName) {
return new Promise((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(place, spot, character) {
const { playerCharacter } = getState().global.main;
const editedCharacter = character ?? playerCharacter;
const charDollStuff = getCharDollStuff(editedCharacter);
const { dollName } = charDollStuff;
const angle = get2DAngleFromCharacterToSpot(editedCharacter, place, spot);
setState({ dolls: { [dollName]: { rotationYGoal: angle } } });
}
export function hideWallIf(placeName, wallName, isDisabled) {
// NOTE could update to set properties in a loop to avoid spreading
setState((state) => ({
places: {
[placeName]: { toggledWalls: { ...state.places[placeName].toggledWalls, [wallName]: !isDisabled } },
},
}));
}
export async function showStoryView(isVisible = 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(_placeName, segmentName
// whenToRun: "now" | "at loop" = "at loop"
) {
return new Promise((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).finally(() => resolve());
// }
});
}
export function setCamera(_placeName, cameraName, whenToRun = "now") {
return new Promise((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).finally(() => resolve());
}
});
}
export function goToNewPlace(toOption, charNameParam) {
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]; // types as a cam for the chosen place
toSegment = toSegment ?? placeInfo.segmentNames[0];
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 ?
// }