prendy
Version:
Make games with prerendered backdrops using babylonjs and repond
150 lines (149 loc) • 5.75 kB
JavaScript
import { forEach } from "chootils/dist/loops";
import { getRefs, makeEffects, setState } from "repond";
import { indexOf, length, substring, toArray } from "stringz";
import { getTypingDelayForLetter } from "../helpers/prendyUtils/speechBubbles";
let zIndexCounter = 100;
/*
Param Effects?
When characters position changes
bubble position to character
*/
export const speechBubbleEffects = makeEffects(({ itemEffect, effect }) => ({
whenGoalTextChanges: itemEffect({
run({ itemId, itemRefs, itemState }) {
const { goalText, stylesBySpecialText } = itemState;
setState({
speechBubbles: {
[itemId]: {
typingFinished: false,
visibleLetterAmount: 0,
_specialTextByLetterIndex: getSpecialTextByLetterIndex(goalText, stylesBySpecialText),
_goalTextWordLetterArrays: textToWordLetterArrays(goalText),
},
},
});
showNewLetter({ itemId, itemRefs, itemState });
},
check: { prop: "goalText", type: "speechBubbles" },
}),
whenVisibleTextChanges: itemEffect({
run({ itemRefs, itemState, itemId }) {
showNewLetter({ itemId, itemRefs, itemState });
},
check: { prop: "visibleLetterAmount", type: "speechBubbles" },
}),
whenTypingFinishedBecomesFalse: itemEffect({
run({ itemRefs, itemState, itemId }) {
showNewLetter({ itemId, itemRefs, itemState });
},
check: {
prop: "typingFinished",
type: "speechBubbles",
becomes: false,
},
}),
// The position changing based on camera and character position are inside the SpeechBubble component
whenAddedOrRemoved: effect({
run(diffInfo) {
// forEach(diffInfo.itemsAdded.speechBubbles, (itemId) => {
// potentially start param effects here
// });
forEach(diffInfo.itemsRemoved.speechBubbles, (itemId) => {
// potentially stop param effects here
const speechBubblesRefs = getRefs().speechBubbles;
const { currentTimeout } = speechBubblesRefs[itemId];
if (currentTimeout !== null)
clearTimeout(currentTimeout);
});
},
check: { addedOrRemoved: true, type: "speechBubbles" },
}),
whenBecameVisible: itemEffect({
run({ itemId }) {
setState({
speechBubbles: {
[itemId]: { isFullyHidden: false, zIndex: zIndexCounter },
},
});
zIndexCounter += 1;
},
check: { prop: "isVisible", type: "speechBubbles", becomes: true },
step: "positionUi",
}),
// whenShouldRemoveBecomesTrue: itemEffect({
// run({ itemId }) {
// // removeItem()
// removeItem({ id: itemId, type: "speechBubbles" });
// },
// check: { prop: "shouldRemove", type: "speechBubbles" },
// }),
}));
// utils
function textToWordLetterArrays(text) {
const words = text.split(/(\S+)/).filter((word) => word); // filter removes "" blank words
const wordLetterArrays = [];
words.forEach((word) => {
const lettersForWord = toArray(word).filter((letter) => letter);
wordLetterArrays.push(lettersForWord);
});
return wordLetterArrays;
}
function findIndexesOf(text, whatToFind) {
const foundIndexes = [];
let keepLooking = true;
let searchFromIndex = 0;
const whatToFindLength = length(whatToFind);
while (keepLooking) {
const foundIndex = indexOf(text, whatToFind, searchFromIndex);
const didFind = foundIndex > -1;
if (didFind) {
foundIndexes.push(foundIndex);
searchFromIndex = foundIndex + whatToFindLength;
}
else {
keepLooking = false;
}
}
return foundIndexes;
}
function getSpecialTextByLetterIndex(text, stylesBySpecialText) {
const specialTexts = Object.keys(stylesBySpecialText);
const specialTextByLetterIndex = {};
forEach(specialTexts, (specialText) => {
const specialTextLength = length(specialText);
const foundStartIndexes = findIndexesOf(text, specialText);
forEach(foundStartIndexes, (startIndex) => {
for (let index = startIndex; index < startIndex + specialTextLength; index++) {
specialTextByLetterIndex[index] = specialText;
}
});
});
return specialTextByLetterIndex;
}
function showNewLetter({ itemRefs, itemState, itemId, }) {
if (itemRefs.currentTimeout !== null)
clearTimeout(itemRefs.currentTimeout);
// If visible/goal Text length not the same
const { goalText, typingFinished, stylesBySpecialText, visibleLetterAmount } = itemState;
const goalLength = length(goalText);
let newVisibleLetterAmount = visibleLetterAmount;
let latestLetter = substring(goalText, Math.max(visibleLetterAmount, 0) - 1, Math.max(visibleLetterAmount, 1));
let newTypingFinished = typingFinished;
if (visibleLetterAmount < length(goalText)) {
newVisibleLetterAmount += 1;
}
if (newVisibleLetterAmount === length(goalText)) {
newTypingFinished = true;
}
const typingDelay = getTypingDelayForLetter(latestLetter, itemId);
itemRefs.currentTimeout = setTimeout(() => {
setState({
speechBubbles: {
[itemId]: {
visibleLetterAmount: newVisibleLetterAmount,
typingFinished: newTypingFinished,
},
},
});
}, typingDelay);
}