fun: dialog++
This commit is contained in:
parent
84b1c49527
commit
4a0b42fbe5
|
@ -77,7 +77,7 @@ export default async function createHomestead(id) {
|
|||
interacting: 1,
|
||||
interactScript: `
|
||||
subject.Interlocutor.dialogue({
|
||||
body: 'Hey what is up :)',
|
||||
body: '<strong>Hey</strong><rate frequency={1}> </rate><rate frequency={0}><shake>what</shake></rate> <em>is</em> <wave frequency={0.5}>uu<rainbow frequency={2}>uu<blink>uu</blink><fade frequency={5}>uu<shake magnitude={4}>uu</shake>uuu</fade></rainbow>uup</wave>? ^_^',
|
||||
origin: subject.Position.toJSON(),
|
||||
position: {x: subject.Position.x, y: subject.Position.y - 32},
|
||||
})
|
||||
|
|
56
app/dialogue.js
Normal file
56
app/dialogue.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
import mdx from 'remark-mdx';
|
||||
import parse from 'remark-parse';
|
||||
import {unified} from 'unified';
|
||||
import {visitParents as visit} from 'unist-util-visit-parents';
|
||||
|
||||
const parser = unified().use(parse).use(mdx);
|
||||
|
||||
function computeParams(ancestors) {
|
||||
const params = {};
|
||||
for (let i = ancestors.length - 1; i >= 0; --i) {
|
||||
const {dialogue} = ancestors[i];
|
||||
if (dialogue) {
|
||||
if (!(dialogue.name in params)) {
|
||||
params[dialogue.name] = dialogue.params;
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
export function parseLetters(source) {
|
||||
const tree = parser.parse(source);
|
||||
tree.dialogue = {
|
||||
name: 'rate',
|
||||
params: {frequency: 0.05, length: 0},
|
||||
};
|
||||
const letters = [];
|
||||
visit(tree, (node, ancestors) => {
|
||||
switch (node.type) {
|
||||
case 'mdxJsxFlowElement':
|
||||
case 'mdxJsxTextElement': {
|
||||
node.dialogue = {name: node.name, params: {length: 0}};
|
||||
for (const {name, value: {value}} of node.attributes) {
|
||||
node.dialogue.params[name] = value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'text': {
|
||||
const params = computeParams(ancestors);
|
||||
const split = node.value.split('');
|
||||
for (let i = 0; i < split.length; ++i) {
|
||||
const indices = {};
|
||||
for (const name in params) {
|
||||
indices[name] = i + params[name].length;
|
||||
}
|
||||
letters.push({character: split[i], indices, params});
|
||||
}
|
||||
for (const name in params) {
|
||||
params[name].length += split.length;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
return letters;
|
||||
}
|
|
@ -2,6 +2,7 @@ import {useEffect, useRef, useState} from 'react';
|
|||
|
||||
import {RESOLUTION} from '@/constants.js';
|
||||
import {useDomScale} from '@/context/dom-scale.js';
|
||||
import {TAU} from '@/util/math.js';
|
||||
|
||||
import styles from './dialogue.module.css';
|
||||
|
||||
|
@ -10,21 +11,60 @@ const CARET_SIZE = 12;
|
|||
export default function Dialogue({
|
||||
camera,
|
||||
dialogue,
|
||||
onClose,
|
||||
scale,
|
||||
}) {
|
||||
const domScale = useDomScale();
|
||||
const ref = useRef();
|
||||
const [dimensions, setDimensions] = useState({h: 0, w: 0});
|
||||
const [caret, setCaret] = useState(0);
|
||||
const [radians, setRadians] = useState(0);
|
||||
useEffect(() => {
|
||||
const {ttl = 5} = dialogue;
|
||||
if (!ttl) {
|
||||
setRadians(0);
|
||||
let handle;
|
||||
let last;
|
||||
const spin = (ts) => {
|
||||
if ('undefined' === typeof last) {
|
||||
last = ts;
|
||||
}
|
||||
const elapsed = (ts - last) / 1000;
|
||||
last = ts;
|
||||
setRadians((radians) => radians + (elapsed * TAU));
|
||||
handle = requestAnimationFrame(spin);
|
||||
};
|
||||
handle = requestAnimationFrame(spin);
|
||||
return () => {
|
||||
cancelAnimationFrame(handle);
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const {params} = dialogue.letters[caret];
|
||||
let handle;
|
||||
if (caret >= dialogue.letters.length - 1) {
|
||||
const {linger = 5} = dialogue;
|
||||
if (!linger) {
|
||||
dialogue.onClose();
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
onClose();
|
||||
}, ttl * 1000);
|
||||
}, [dialogue, onClose]);
|
||||
handle = setTimeout(() => {
|
||||
dialogue.onClose();
|
||||
}, linger * 1000);
|
||||
}
|
||||
else {
|
||||
let jump = caret;
|
||||
while (0 === dialogue.letters[jump].params.rate.frequency && jump < dialogue.letters.length - 1) {
|
||||
jump += 1;
|
||||
}
|
||||
setCaret(jump);
|
||||
if (jump < dialogue.letters.length - 1) {
|
||||
handle = setTimeout(() => {
|
||||
setCaret(caret + 1);
|
||||
}, params.rate.frequency * 1000)
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
clearTimeout(handle);
|
||||
}
|
||||
}, [caret, dialogue]);
|
||||
useEffect(() => {
|
||||
let handle;
|
||||
function track() {
|
||||
|
@ -134,7 +174,62 @@ export default function Dialogue({
|
|||
>
|
||||
<polygon points="0 0, 24 0, 12 24" />
|
||||
</svg>
|
||||
{dialogue.body}
|
||||
{
|
||||
dialogue.letters
|
||||
.map(({character, indices, params}, i) => {
|
||||
let color = 'inherit';
|
||||
let fade = 0;
|
||||
let fontStyle = 'normal';
|
||||
let fontWeight = 'normal';
|
||||
let left = 0;
|
||||
let opacity = 1;
|
||||
let top = 0;
|
||||
if (params.blink) {
|
||||
const {frequency = 1} = params.blink;
|
||||
opacity = (radians * (2 / frequency) % TAU) > (TAU / 2) ? opacity : 0;
|
||||
}
|
||||
if (params.fade) {
|
||||
const {frequency = 1} = params.fade;
|
||||
fade = frequency;
|
||||
}
|
||||
if (params.wave) {
|
||||
const {frequency = 1, magnitude = 3} = params.wave;
|
||||
top += magnitude * Math.cos((radians * (1 / frequency)) + TAU * indices.wave / params.wave.length);
|
||||
}
|
||||
if (params.rainbow) {
|
||||
const {frequency = 1} = params.rainbow;
|
||||
color = `hsl(${(radians * (1 / frequency)) + TAU * indices.rainbow / params.rainbow.length}rad 100 50)`;
|
||||
}
|
||||
if (params.shake) {
|
||||
const {magnitude = 1} = params.shake;
|
||||
left += (Math.random() * magnitude * 2) - magnitude;
|
||||
top += (Math.random() * magnitude * 2) - magnitude;
|
||||
}
|
||||
if (params.em) {
|
||||
fontStyle = 'italic';
|
||||
}
|
||||
if (params.strong) {
|
||||
fontWeight = 'bold';
|
||||
}
|
||||
return (
|
||||
<span
|
||||
className={styles.letter}
|
||||
key={i}
|
||||
style={{
|
||||
color,
|
||||
fontStyle,
|
||||
fontWeight,
|
||||
left: `${left}px`,
|
||||
opacity: i <= caret ? opacity : 0,
|
||||
top: `${top}px`,
|
||||
transition: `opacity ${fade}s`,
|
||||
}}
|
||||
>
|
||||
{character}
|
||||
</span>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,3 +5,8 @@
|
|||
left: 50%;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.letter {
|
||||
opacity: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
@ -10,9 +10,6 @@ export default function Dialogues({camera, dialogues, scale}) {
|
|||
camera={camera}
|
||||
dialogue={dialogues[key]}
|
||||
key={key}
|
||||
onClose={() => {
|
||||
delete dialogues[key];
|
||||
}}
|
||||
scale={scale}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import {useState} from 'react';
|
||||
|
||||
import {useEcs, useEcsTick} from '@/context/ecs.js';
|
||||
import {parseLetters} from '@/dialogue.js';
|
||||
|
||||
import Entity from './entity.jsx';
|
||||
|
||||
|
@ -23,8 +25,18 @@ export default function Entities({camera, scale}) {
|
|||
updatedEntities[id] = ecs.get(id);
|
||||
const {dialogue} = update.Interlocutor || {};
|
||||
if (dialogue) {
|
||||
const {dialogues} = updatedEntities[id].Interlocutor;
|
||||
for (const key in dialogue) {
|
||||
updatedEntities[id].Interlocutor.dialogues[key] = dialogue[key];
|
||||
updatedEntities[id] = ecs.rebuild(id);
|
||||
dialogues[key] = dialogue[key];
|
||||
dialogues[key].letters = parseLetters(dialogues[key].body);
|
||||
dialogues[key].onClose = () => {
|
||||
delete dialogues[key];
|
||||
setEntities((entities) => ({
|
||||
...entities,
|
||||
[id]: ecs.rebuild(id),
|
||||
}));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
3283
package-lock.json
generated
3283
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -37,7 +37,11 @@
|
|||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-tabs": "^6.0.2",
|
||||
"remark-mdx": "^3.0.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"simplex-noise": "^4.0.1",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit-parents": "^6.0.1",
|
||||
"ws": "^8.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
Loading…
Reference in New Issue
Block a user