add choice mechanic
This commit is contained in:
@@ -11,6 +11,7 @@ interface VNPreviewProps {
|
||||
onNext: () => void;
|
||||
statements: Statement[];
|
||||
spriteBank: Record<string, Sprite[]>;
|
||||
onChoiceSelect?: (jumpLabel: string) => void;
|
||||
}
|
||||
|
||||
export const VNPreview: React.FC<VNPreviewProps> = ({
|
||||
@@ -21,15 +22,20 @@ export const VNPreview: React.FC<VNPreviewProps> = ({
|
||||
onNext,
|
||||
statements,
|
||||
spriteBank,
|
||||
onChoiceSelect,
|
||||
}) => {
|
||||
// Track state of all characters on scene at the current line
|
||||
// Track state of all characters on scene and the current label
|
||||
const activeSprites: Record<
|
||||
string,
|
||||
{ emotion: string; pos: SpritePosition }
|
||||
> = {};
|
||||
let currentLabel: string | null = null;
|
||||
|
||||
statements.forEach((s) => {
|
||||
if (s.lineNumber <= (currentStatement?.lineNumber || 999999)) {
|
||||
if (s.type === "label") {
|
||||
currentLabel = s.label || null;
|
||||
}
|
||||
if (s.type === "sprite" && s.character) {
|
||||
activeSprites[s.character] = {
|
||||
emotion: s.emotion || "default",
|
||||
@@ -46,7 +52,25 @@ export const VNPreview: React.FC<VNPreviewProps> = ({
|
||||
};
|
||||
|
||||
const renderDialogueBox = () => {
|
||||
if (!currentStatement || currentStatement.type !== "say") return null;
|
||||
if (!currentStatement) return null;
|
||||
|
||||
if (currentStatement.type === "choice") {
|
||||
return (
|
||||
<div className="flex flex-col gap-3 items-center">
|
||||
{currentStatement.choices?.map((choice, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => onChoiceSelect?.(choice.jumpLabel)}
|
||||
className="w-full max-w-lg bg-black/80 hover:bg-cyan-900/80 border border-cyan-500/50 hover:border-cyan-400 p-4 rounded-md text-cyan-100 font-bold transition-all transform hover:scale-[1.02] active:scale-[0.98] shadow-lg backdrop-blur-md"
|
||||
>
|
||||
{choice.text}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (currentStatement.type !== "say") return null;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
@@ -64,7 +88,7 @@ export const VNPreview: React.FC<VNPreviewProps> = ({
|
||||
)}
|
||||
|
||||
<div className="bg-black/60 border border-white/10 p-6 pt-8 rounded-lg rounded-tl-none backdrop-blur-md shadow-2xl min-h-[140px]">
|
||||
<div className="text-xl leading-relaxed text-gray-100 italic font-serif">
|
||||
<div className="text-xl leading-relaxed text-gray-100 font-serif">
|
||||
{[
|
||||
...(currentStatement.segments || []),
|
||||
...(currentStatement.continuations || []),
|
||||
@@ -87,7 +111,7 @@ export const VNPreview: React.FC<VNPreviewProps> = ({
|
||||
{seg.modifiers.wait && <Icons.Wait />}
|
||||
{seg.modifiers.click && <Icons.Click />}
|
||||
{seg.modifiers.clickLock && <Icons.Lock />}
|
||||
{seg.text}{" "}
|
||||
{seg.text}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
@@ -99,10 +123,15 @@ export const VNPreview: React.FC<VNPreviewProps> = ({
|
||||
return (
|
||||
<div className="flex-1 bg-[#0b0e14] p-8 flex flex-col items-center justify-center relative">
|
||||
<div className="w-full max-w-4xl aspect-video bg-gradient-to-b from-gray-900 to-black rounded-lg overflow-hidden border border-gray-800 shadow-2xl relative group">
|
||||
<div className="absolute top-4 left-6 z-20 flex gap-2">
|
||||
<div className="absolute top-4 left-6 z-50 flex gap-2 items-center">
|
||||
<span className="text-[10px] tracking-[0.2em] uppercase text-gray-500 font-bold bg-black/40 px-3 py-1 rounded backdrop-blur border border-white/5">
|
||||
PREVIEW | {currentStatement?.character || "NARRATOR"}
|
||||
</span>
|
||||
{currentLabel && (
|
||||
<span className="text-[9px] tracking-widest uppercase text-cyan-400 font-bold bg-cyan-950/40 px-3 py-1 rounded backdrop-blur border border-cyan-500/20">
|
||||
SCENE: {currentLabel}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-0 pointer-events-none">
|
||||
@@ -112,7 +141,7 @@ export const VNPreview: React.FC<VNPreviewProps> = ({
|
||||
const isSmallPos = data.pos === "pos7" || data.pos === "pos8";
|
||||
const baseScale = isSmallPos ? 0.8 : 1.0;
|
||||
const finalScale = isSpeaking ? baseScale * 1.05 : baseScale;
|
||||
const translateY = isSpeaking ? "1rem" : "2rem";
|
||||
const translateY = isSpeaking ? "0rem" : "0.5rem";
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -122,7 +151,7 @@ export const VNPreview: React.FC<VNPreviewProps> = ({
|
||||
zIndex: isSpeaking ? 20 : 10,
|
||||
transform: `translateX(-50%) translateY(${translateY}) scale(${finalScale})`,
|
||||
}}
|
||||
className="absolute bottom-0 h-[85%] transition-all duration-700"
|
||||
className="absolute bottom-0 h-[95%] transition-all duration-700"
|
||||
>
|
||||
{url ? (
|
||||
<img
|
||||
@@ -173,7 +202,7 @@ export const VNPreview: React.FC<VNPreviewProps> = ({
|
||||
</div>
|
||||
<div className="absolute inset-y-0 right-2 flex items-center z-40">
|
||||
<button
|
||||
disabled={currentSayIndex >= totalPreviewSteps - 1}
|
||||
disabled={currentSayIndex >= totalPreviewSteps - 1 || currentStatement?.type === 'choice'}
|
||||
onClick={onNext}
|
||||
className="p-2 text-white/30 hover:text-cyan-400 transition-colors disabled:opacity-0"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user