create conversationPage
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
NekoVari 2025-05-06 17:08:03 +07:00
parent 8de6e9d771
commit 2336262c57
7 changed files with 92 additions and 76 deletions

View file

@ -8,8 +8,8 @@
type: conversation
name: Porsche
text: ”Hi! I havent seen you around before so you must be new. Im Porsche. Why dont you sit with me and my friends? We'll tell you about the school!”
sprite: ""
background: ""
sprite: "_Porsche_normal.zip"
background: "bg-hallway.png"
goTo: 3
- id: 3

9
src/css/conversation.css Normal file
View file

@ -0,0 +1,9 @@
.spriteBox {
height: 50vh;
width: 30vw;
}
@media (max-width: 768px) {
.spriteBox {
width: 75vw;
}
}

16
src/css/nameBox.css Normal file
View file

@ -0,0 +1,16 @@
.nameBox {
display: flex;
justify-content: center;
align-items: center;
width: 20vw;
min-width: 100px;
background-color: #8391b8;
color: white;
border-style: solid;
border-color: #8391b8;
border-width: 5px;
border-radius: -10px;
position: absolute;
transform: translateY(-40px);
z-index: 2;
}

View file

@ -9,13 +9,5 @@
border-color: #8391b8;
border-width: 5px;
border-radius: 30px;
}
@media (max-width: 768px) {
.textBox.title {
margin-top: 50vh;
}
.textBox.title.small {
margin-top: 50vh;
}
padding: 50px;
}

View file

@ -1,22 +1,19 @@
import React, { useEffect, useState, useRef } from 'react';
import JSZip from 'jszip';
const Animation = ({ src = "M_Porsche_cross_arm.zip", animationWidth = "500px" }) => {
const Animation = ({ src = "M_Porsche_cross_arm.zip", h = '100%', w = '100%' }) => {
const [images, setImages] = useState([]);
const [loading, setLoading] = useState(true);
const frameRate = 1000 / 24; // Original frame rate (24 FPS)
const frameRate = 1000 / 24; // 24 FPS
const canvasRef = useRef(null);
const animationRef = useRef(null);
const currentFrameRef = useRef(0);
const imageElementsRef = useRef([]);
const urlSource = `${import.meta.env.VITE_ASSETS_URL}/${src}`;
// Load images from the ZIP file
useEffect(() => {
const loadImages = async () => {
const zip = new JSZip();
// console.log(import.meta.env.VITE_ASSETS_URL);
try {
const response = await fetch(urlSource);
if (!response.ok) {
@ -42,13 +39,12 @@ const Animation = ({ src = "M_Porsche_cross_arm.zip", animationWidth = "500px" }
});
const imgElements = await Promise.all(imgPromises);
if (imgElements.length === 0) {
console.error('No images found in the ZIP file.');
}
imageElementsRef.current = imgElements; // Store preloaded images
setImages(imgElements); // Set images state
imageElementsRef.current = imgElements;
setImages(imgElements);
} catch (error) {
console.error('Error loading images:', error);
} finally {
@ -59,7 +55,6 @@ const Animation = ({ src = "M_Porsche_cross_arm.zip", animationWidth = "500px" }
loadImages();
}, [urlSource]);
// WebGL setup and animation loop
useEffect(() => {
if (images.length > 0) {
const canvas = canvasRef.current;
@ -69,17 +64,21 @@ const Animation = ({ src = "M_Porsche_cross_arm.zip", animationWidth = "500px" }
return;
}
// Set up WebGL context (basic)
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
const rect = canvas.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
gl.viewport(0, 0, canvas.width, canvas.height);
// Enable blending
canvas.style.width = `${rect.width}px`;
canvas.style.height = `${rect.height}px`;
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
// gl.blendFunc(gl.SRC_COLOR, gl.DST_COLOR);
// Shader program
const vertexShaderSource = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
@ -103,7 +102,7 @@ const Animation = ({ src = "M_Porsche_cross_arm.zip", animationWidth = "500px" }
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('ERROR compiling shader', gl.getShaderInfoLog(shader));
console.error('Shader compile error:', gl.getShaderInfoLog(shader));
}
return shader;
};
@ -116,18 +115,17 @@ const Animation = ({ src = "M_Porsche_cross_arm.zip", animationWidth = "500px" }
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.error('ERROR linking program', gl.getProgramInfoLog(shaderProgram));
console.error('Program link error:', gl.getProgramInfoLog(shaderProgram));
}
gl.useProgram(shaderProgram);
// Create buffer and set up attributes
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const vertices = new Float32Array([
-1.0, -1.0, // Bottom-left
1.0, -1.0, // Bottom-right
-1.0, 1.0, // Top-left
1.0, 1.0 // Top-right
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
1.0, 1.0,
]);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
@ -135,33 +133,13 @@ const Animation = ({ src = "M_Porsche_cross_arm.zip", animationWidth = "500px" }
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionLocation);
// Create texture and load image
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
const loadTexture = (image) => {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
};
loadTexture(imageElementsRef.current[0]); // Initially load the first image
// Texture coordinates
const texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
const texCoords = new Float32Array([
0.0, 1.0, // Bottom-left (now corresponds to top-left of the image)
1.0, 1.0, // Bottom-right (now corresponds to top-right of the image)
0.0, 0.0, // Top-left (now corresponds to bottom-left of the image)
1.0, 0.0 // Top-right (now corresponds to bottom-right of the image)
0.0, 1.0,
1.0, 1.0,
0.0, 0.0,
1.0, 0.0,
]);
gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);
@ -169,9 +147,28 @@ const Animation = ({ src = "M_Porsche_cross_arm.zip", animationWidth = "500px" }
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(texCoordLocation);
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
// Optionally use NEAREST for sharper image:
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
const uTextureLocation = gl.getUniformLocation(shaderProgram, "u_texture");
const loadTexture = (image) => {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
};
loadTexture(imageElementsRef.current[0]);
let lastFrameTime = 0;
const animate = (timestamp) => {
if (lastFrameTime === 0) lastFrameTime = timestamp;
@ -202,18 +199,12 @@ const Animation = ({ src = "M_Porsche_cross_arm.zip", animationWidth = "500px" }
return <div>Loading...</div>;
} else {
return (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<canvas
ref={canvasRef}
width={'1920'}
height={'1080'} // Maintain aspect ratio (example: 16:9)
style={{ maxWidth: animationWidth, height: 'auto' }}
/>
</div>
<canvas
ref={canvasRef}
style={{ height: h, width: w }}
/>
);
}
};
export default Animation;

View file

@ -2,29 +2,36 @@
import { useEffect, useState } from 'react';
import '../css/global.css';
import '../css/textBox.css';
import '../css/nameBox.css';
import '../css/conversation.css'
import Animation from './components/animation.jsx';
function ConversationPage({data , onClicked}) {
const backgroundSRC = `${import.meta.env.VITE_ASSETS_URL}/${data.background}`
return (
<div
onClick={onClicked}
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
width: '100vw',
height: '100svh',
width: '100svw',
flexDirection: 'column',
backgroundImage: `url(${backgroundSRC})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
>
<div className='nameBox title'>
{data.name}
<div className='spriteBox'>
<Animation src={`F${data.sprite}`}/>
</div>
<div className='textBox title' style={{marginTop: '50vh', overflow: 'scroll',}}>
<div style={{width:'60vw'}}>
<div className='nameBox title'>
{data.name}
</div>
</div>
<div className='textBox title' style={{overflow: 'scroll',}}>
{data.text}
</div>
</div>

View file

@ -4,6 +4,7 @@ import { useNavigate, useParams } from 'react-router-dom';
import '../css/global.css';
import { fetchYamlData } from './components/fetchYamlData';
import StoryPage from './storyPage';
import ConversationPage from './conversationPage.jsx';
import LoadingScene from './components/loadingScene.jsx';
function VitualNovelHandler() {
@ -58,7 +59,7 @@ function VitualNovelHandler() {
case "story":
return <StoryPage data={currentStepData} onClicked={handleNextStep}/>;
case "conversation":
return <div onClick={handleNextStep}>conversation</div>;
return <ConversationPage data={currentStepData} onClicked={handleNextStep}/>;
case "option":
return <div onClick={handleNextStep}>option</div>;
default:
@ -67,7 +68,7 @@ function VitualNovelHandler() {
};
return (
<div>
<div style={{ width: "100svw", height: "100svh" }}>
{renderComponent(currentStepData.type)}
</div>
);