create conversationPage
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
8de6e9d771
commit
2336262c57
7 changed files with 92 additions and 76 deletions
|
@ -8,8 +8,8 @@
|
||||||
type: conversation
|
type: conversation
|
||||||
name: Porsche
|
name: Porsche
|
||||||
text: ”Hi! I haven’t seen you around before so you must be new. I’m Porsche. Why don’t you sit with me and my friends? We'll tell you about the school!”
|
text: ”Hi! I haven’t seen you around before so you must be new. I’m Porsche. Why don’t you sit with me and my friends? We'll tell you about the school!”
|
||||||
sprite: ""
|
sprite: "_Porsche_normal.zip"
|
||||||
background: ""
|
background: "bg-hallway.png"
|
||||||
goTo: 3
|
goTo: 3
|
||||||
|
|
||||||
- id: 3
|
- id: 3
|
||||||
|
|
9
src/css/conversation.css
Normal file
9
src/css/conversation.css
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
.spriteBox {
|
||||||
|
height: 50vh;
|
||||||
|
width: 30vw;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.spriteBox {
|
||||||
|
width: 75vw;
|
||||||
|
}
|
||||||
|
}
|
16
src/css/nameBox.css
Normal file
16
src/css/nameBox.css
Normal 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;
|
||||||
|
}
|
|
@ -9,13 +9,5 @@
|
||||||
border-color: #8391b8;
|
border-color: #8391b8;
|
||||||
border-width: 5px;
|
border-width: 5px;
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
|
padding: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.textBox.title {
|
|
||||||
margin-top: 50vh;
|
|
||||||
}
|
|
||||||
.textBox.title.small {
|
|
||||||
margin-top: 50vh;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +1,19 @@
|
||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
import JSZip from 'jszip';
|
import JSZip from 'jszip';
|
||||||
|
|
||||||
|
const Animation = ({ src = "M_Porsche_cross_arm.zip", h = '100%', w = '100%' }) => {
|
||||||
const Animation = ({ src = "M_Porsche_cross_arm.zip", animationWidth = "500px" }) => {
|
|
||||||
const [images, setImages] = useState([]);
|
const [images, setImages] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
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 canvasRef = useRef(null);
|
||||||
const animationRef = useRef(null);
|
const animationRef = useRef(null);
|
||||||
const currentFrameRef = useRef(0);
|
const currentFrameRef = useRef(0);
|
||||||
const imageElementsRef = useRef([]);
|
const imageElementsRef = useRef([]);
|
||||||
const urlSource = `${import.meta.env.VITE_ASSETS_URL}/${src}`;
|
const urlSource = `${import.meta.env.VITE_ASSETS_URL}/${src}`;
|
||||||
|
|
||||||
// Load images from the ZIP file
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadImages = async () => {
|
const loadImages = async () => {
|
||||||
const zip = new JSZip();
|
const zip = new JSZip();
|
||||||
// console.log(import.meta.env.VITE_ASSETS_URL);
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(urlSource);
|
const response = await fetch(urlSource);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
@ -42,24 +39,22 @@ const Animation = ({ src = "M_Porsche_cross_arm.zip", animationWidth = "500px" }
|
||||||
});
|
});
|
||||||
|
|
||||||
const imgElements = await Promise.all(imgPromises);
|
const imgElements = await Promise.all(imgPromises);
|
||||||
|
|
||||||
if (imgElements.length === 0) {
|
if (imgElements.length === 0) {
|
||||||
console.error('No images found in the ZIP file.');
|
console.error('No images found in the ZIP file.');
|
||||||
}
|
}
|
||||||
|
|
||||||
imageElementsRef.current = imgElements; // Store preloaded images
|
imageElementsRef.current = imgElements;
|
||||||
setImages(imgElements); // Set images state
|
setImages(imgElements);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading images:', error);
|
console.error('Error loading images:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadImages();
|
loadImages();
|
||||||
}, [urlSource]);
|
}, [urlSource]);
|
||||||
|
|
||||||
// WebGL setup and animation loop
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (images.length > 0) {
|
if (images.length > 0) {
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
|
@ -69,17 +64,21 @@ const Animation = ({ src = "M_Porsche_cross_arm.zip", animationWidth = "500px" }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up WebGL context (basic)
|
const rect = canvas.getBoundingClientRect();
|
||||||
gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
const dpr = window.devicePixelRatio || 1;
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
canvas.width = rect.width * dpr;
|
||||||
|
canvas.height = rect.height * dpr;
|
||||||
gl.viewport(0, 0, canvas.width, canvas.height);
|
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.enable(gl.BLEND);
|
||||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||||
// gl.blendFunc(gl.SRC_COLOR, gl.DST_COLOR);
|
|
||||||
|
|
||||||
// Shader program
|
|
||||||
const vertexShaderSource = `
|
const vertexShaderSource = `
|
||||||
attribute vec2 a_position;
|
attribute vec2 a_position;
|
||||||
attribute vec2 a_texCoord;
|
attribute vec2 a_texCoord;
|
||||||
|
@ -103,7 +102,7 @@ const Animation = ({ src = "M_Porsche_cross_arm.zip", animationWidth = "500px" }
|
||||||
gl.shaderSource(shader, source);
|
gl.shaderSource(shader, source);
|
||||||
gl.compileShader(shader);
|
gl.compileShader(shader);
|
||||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
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;
|
return shader;
|
||||||
};
|
};
|
||||||
|
@ -116,18 +115,17 @@ const Animation = ({ src = "M_Porsche_cross_arm.zip", animationWidth = "500px" }
|
||||||
gl.attachShader(shaderProgram, fragmentShader);
|
gl.attachShader(shaderProgram, fragmentShader);
|
||||||
gl.linkProgram(shaderProgram);
|
gl.linkProgram(shaderProgram);
|
||||||
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
|
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);
|
gl.useProgram(shaderProgram);
|
||||||
|
|
||||||
// Create buffer and set up attributes
|
|
||||||
const positionBuffer = gl.createBuffer();
|
const positionBuffer = gl.createBuffer();
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||||
const vertices = new Float32Array([
|
const vertices = new Float32Array([
|
||||||
-1.0, -1.0, // Bottom-left
|
-1.0, -1.0,
|
||||||
1.0, -1.0, // Bottom-right
|
1.0, -1.0,
|
||||||
-1.0, 1.0, // Top-left
|
-1.0, 1.0,
|
||||||
1.0, 1.0 // Top-right
|
1.0, 1.0,
|
||||||
]);
|
]);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
|
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.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
|
||||||
gl.enableVertexAttribArray(positionLocation);
|
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();
|
const texCoordBuffer = gl.createBuffer();
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
|
||||||
const texCoords = new Float32Array([
|
const texCoords = new Float32Array([
|
||||||
0.0, 1.0, // Bottom-left (now corresponds to top-left of the image)
|
0.0, 1.0,
|
||||||
1.0, 1.0, // Bottom-right (now corresponds to top-right of the image)
|
1.0, 1.0,
|
||||||
0.0, 0.0, // Top-left (now corresponds to bottom-left of the image)
|
0.0, 0.0,
|
||||||
1.0, 0.0 // Top-right (now corresponds to bottom-right of the image)
|
1.0, 0.0,
|
||||||
]);
|
]);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);
|
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.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
|
||||||
gl.enableVertexAttribArray(texCoordLocation);
|
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 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;
|
let lastFrameTime = 0;
|
||||||
|
|
||||||
const animate = (timestamp) => {
|
const animate = (timestamp) => {
|
||||||
if (lastFrameTime === 0) lastFrameTime = timestamp;
|
if (lastFrameTime === 0) lastFrameTime = timestamp;
|
||||||
|
|
||||||
|
@ -202,18 +199,12 @@ const Animation = ({ src = "M_Porsche_cross_arm.zip", animationWidth = "500px" }
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
<canvas
|
||||||
<canvas
|
ref={canvasRef}
|
||||||
ref={canvasRef}
|
style={{ height: h, width: w }}
|
||||||
width={'1920'}
|
/>
|
||||||
height={'1080'} // Maintain aspect ratio (example: 16:9)
|
|
||||||
style={{ maxWidth: animationWidth, height: 'auto' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Animation;
|
export default Animation;
|
||||||
|
|
||||||
|
|
|
@ -2,29 +2,36 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import '../css/global.css';
|
import '../css/global.css';
|
||||||
import '../css/textBox.css';
|
import '../css/textBox.css';
|
||||||
|
import '../css/nameBox.css';
|
||||||
|
import '../css/conversation.css'
|
||||||
|
import Animation from './components/animation.jsx';
|
||||||
|
|
||||||
function ConversationPage({data , onClicked}) {
|
function ConversationPage({data , onClicked}) {
|
||||||
const backgroundSRC = `${import.meta.env.VITE_ASSETS_URL}/${data.background}`
|
const backgroundSRC = `${import.meta.env.VITE_ASSETS_URL}/${data.background}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={onClicked}
|
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
height: '100vh',
|
height: '100svh',
|
||||||
width: '100vw',
|
width: '100svw',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
backgroundImage: `url(${backgroundSRC})`,
|
backgroundImage: `url(${backgroundSRC})`,
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'cover',
|
||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className='nameBox title'>
|
<div className='spriteBox'>
|
||||||
{data.name}
|
<Animation src={`F${data.sprite}`}/>
|
||||||
</div>
|
</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}
|
{data.text}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import '../css/global.css';
|
import '../css/global.css';
|
||||||
import { fetchYamlData } from './components/fetchYamlData';
|
import { fetchYamlData } from './components/fetchYamlData';
|
||||||
import StoryPage from './storyPage';
|
import StoryPage from './storyPage';
|
||||||
|
import ConversationPage from './conversationPage.jsx';
|
||||||
import LoadingScene from './components/loadingScene.jsx';
|
import LoadingScene from './components/loadingScene.jsx';
|
||||||
|
|
||||||
function VitualNovelHandler() {
|
function VitualNovelHandler() {
|
||||||
|
@ -58,7 +59,7 @@ function VitualNovelHandler() {
|
||||||
case "story":
|
case "story":
|
||||||
return <StoryPage data={currentStepData} onClicked={handleNextStep}/>;
|
return <StoryPage data={currentStepData} onClicked={handleNextStep}/>;
|
||||||
case "conversation":
|
case "conversation":
|
||||||
return <div onClick={handleNextStep}>conversation</div>;
|
return <ConversationPage data={currentStepData} onClicked={handleNextStep}/>;
|
||||||
case "option":
|
case "option":
|
||||||
return <div onClick={handleNextStep}>option</div>;
|
return <div onClick={handleNextStep}>option</div>;
|
||||||
default:
|
default:
|
||||||
|
@ -67,7 +68,7 @@ function VitualNovelHandler() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{ width: "100svw", height: "100svh" }}>
|
||||||
{renderComponent(currentStepData.type)}
|
{renderComponent(currentStepData.type)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue