Compare commits

...

5 commits

Author SHA1 Message Date
6b62ebe64f optimaize with webgl
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-01-05 07:05:36 +07:00
e4704c62f3 create introductionPage
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-12-30 01:19:31 +07:00
bdec78f7c6 create animation with zip of frame
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-12-17 17:36:53 +07:00
f9165a1c93 test sprite animate , series file , zip
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-12-17 00:04:55 +07:00
22ffb870e8 fix style namePage 2024-12-11 16:10:39 +07:00
40 changed files with 744 additions and 11 deletions

107
package-lock.json generated
View file

@ -8,6 +8,8 @@
"name": "fifty-shades-of-bully",
"version": "0.0.0",
"dependencies": {
"animejs": "^3.2.2",
"jszip": "^3.10.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^7.0.2"
@ -1373,6 +1375,12 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/animejs": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/animejs/-/animejs-3.2.2.tgz",
"integrity": "sha512-Ao95qWLpDPXXM+WrmwcKbl6uNlC5tjnowlaRYtuVDHHoygjtIPfDUoK9NthrlZsQSKjZXlmji2TrBUAVbiH0LQ==",
"license": "MIT"
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@ -1711,6 +1719,12 @@
"node": ">=18"
}
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -2645,6 +2659,12 @@
"node": ">= 4"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -2672,6 +2692,12 @@
"node": ">=0.8.19"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/internal-slot": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
@ -3155,6 +3181,18 @@
"node": ">=4.0"
}
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@ -3179,6 +3217,15 @@
"node": ">= 0.8.0"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@ -3431,6 +3478,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -3527,6 +3580,12 @@
"node": ">= 0.8.0"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@ -3631,6 +3690,27 @@
"react-dom": ">=18"
}
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/readable-stream/node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/reflect.getprototypeof": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
@ -3757,6 +3837,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/safe-regex-test": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz",
@ -3834,6 +3920,12 @@
"node": ">= 0.4"
}
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"license": "MIT"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@ -3886,6 +3978,15 @@
"node": ">=0.10.0"
}
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/string.prototype.matchall": {
"version": "4.0.11",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz",
@ -4168,6 +4269,12 @@
"punycode": "^2.1.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/vite": {
"version": "5.4.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",

View file

@ -4,12 +4,14 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "vite --host",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"animejs": "^3.2.2",
"jszip": "^3.10.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^7.0.2"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -9,7 +9,9 @@
border: none;
border-radius: 30px;
padding: 8px 52px 8px 52px;
/* padding: 8px 52px 8px 52px; */
height: 51px;
width: 249;
transition-duration: 0.2s;
display: flex;
align-items: center;

View file

@ -8,12 +8,13 @@
}
html * {
background-color: rgb(var(--black));
/* background-color: rgb(var(--black)); */
color: rgb(var(--white));
}
body {
margin: 0;
background-color: rgb(var(--black));
}
.title {
@ -22,6 +23,10 @@ body {
font-weight: 900;
text-align: center;
}
.title.medium{
font-size: 24pt;
}
.body {
font-family: "Source Sans 3";
font-size: 24pt;

View file

@ -0,0 +1,21 @@
.character {
width: 624px; /* Width of a single frame */
height: 814px; /* Height of a single frame */
background-color: wheat;
/* overflow: hidden; */
/* Ensure only one frame is visible */
}
.spriteAnimation {
width: 9984px;
animation: moveSprite 1s steps(16) infinite; /* 240 total frames */
}
@keyframes moveSprite {
form{
transform: translate3d(0,0,0)
}
to{
transform: translate3d(-100%,0,0);
}
}

View file

@ -1,11 +1,14 @@
import * as React from 'react';
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import './css/global.css'
import HomePage from './pages/homePage.jsx'
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import './css/global.css';
import HomePage from './pages/homePage.jsx';
import WarningPage from './pages/warningPage.jsx';
import NamePage from './pages/namePage.jsx';
import Animation from './pages/components/animation.jsx';
import IntroductionPage from './pages/introductionPage.jsx';
createRoot(document.getElementById('root')).render(
<StrictMode>
@ -13,8 +16,12 @@ createRoot(document.getElementById('root')).render(
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/warn" element={<WarningPage />} />
<Route path="/name" element={<NamePage />}/>
<Route path="/name" element={<NamePage />} />
<Route path="/sprite" element={<Animation />} />
<Route path="/introduction" element={<IntroductionPage />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
</StrictMode>,
)
);

View file

@ -0,0 +1,403 @@
//version 1
// import React, { useEffect, useState } from 'react';
// import JSZip from 'jszip';
// const Animation = ({src="src/assets/mainCharacters/M_Porsche_cross_arm.zip" ,animationWidth="500px"}) => {
// const [images, setImages] = useState([]);
// const [currentFrame, setCurrentFrame] = useState(0);
// const [loading, setLoading] = useState(true);
// const frameRate = 1000/24;
// useEffect(() => {
// const loadImages = async () => {
// const zip = new JSZip();
// try {
// const response = await fetch(src);
// if (!response.ok) {
// throw new Error('Network response was not ok');
// }
// const data = await response.arrayBuffer();
// const zipContent = await zip.loadAsync(data);
// const imgPromises = [];
// zipContent.forEach((relativePath, file) => {
// if (file.name.endsWith('.webp')) {
// imgPromises.push(
// file.async('base64').then(base64 => {
// return `data:image/webp;base64,${base64}`;
// })
// );
// }
// });
// const imgUrls = await Promise.all(imgPromises);
// if (imgUrls.length === 0) {
// console.error('No images found in the ZIP file.');
// }
// setImages(imgUrls);
// } catch (error) {
// console.error('Error loading images:', error);
// } finally {
// setLoading(false);
// }
// };
// loadImages();
// }, []);
// useEffect(() => {
// if (images.length > 0) {
// const interval = setInterval(() => {
// setCurrentFrame((prevFrame) => (prevFrame + 1) % images.length);
// }, frameRate);
// return () => clearInterval(interval);
// }
// }, [images]);
// if (loading) {
// return <div>Loading...</div>;
// } else {
// return (
// <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
// {images.length > 0 ? (
// <img
// src={images[currentFrame]}
// alt={`Animation frame ${currentFrame + 1}`}
// style={{ maxWidth: animationWidth, height: 'auto' }}
// />
// ) : (
// <div>No images to display.</div>
// )}
// </div>
// );
// }
// };
// export default Animation;
//version 2
// import React, { useEffect, useState, useRef } from 'react';
// import JSZip from 'jszip';
// const Animation = ({ src = "src/assets/mainCharacters/M_Porsche_cross_arm.zip", animationWidth = "500px" }) => {
// const [images, setImages] = useState([]);
// const [loading, setLoading] = useState(true);
// const frameRate = 1000 / 24; // Original frame rate (24 FPS)
// const canvasRef = useRef(null);
// const animationRef = useRef(null);
// const currentFrameRef = useRef(0);
// const imageElementsRef = useRef([]);
// useEffect(() => {
// const loadImages = async () => {
// const zip = new JSZip();
// try {
// const response = await fetch(src);
// if (!response.ok) {
// throw new Error('Network response was not ok');
// }
// const data = await response.arrayBuffer();
// const zipContent = await zip.loadAsync(data);
// const imgPromises = [];
// zipContent.forEach((relativePath, file) => {
// if (file.name.endsWith('.webp')) {
// imgPromises.push(
// file.async('base64').then(base64 => {
// const img = new Image();
// img.src = `data:image/webp;base64,${base64}`;
// return img; // Return the Image object
// })
// );
// }
// });
// 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
// } catch (error) {
// console.error('Error loading images:', error);
// } finally {
// setLoading(false);
// }
// };
// loadImages();
// }, [src]);
// useEffect(() => {
// if (images.length > 0) {
// const canvas = canvasRef.current;
// const ctx = canvas.getContext('2d');
// let lastFrameTime = 0;
// const animate = (timestamp) => {
// if (lastFrameTime === 0) lastFrameTime = timestamp;
// const elapsed = timestamp - lastFrameTime;
// if (elapsed > frameRate) {
// currentFrameRef.current = (currentFrameRef.current + 1) % images.length;
// lastFrameTime = timestamp;
// }
// // Clear the canvas
// ctx.clearRect(0, 0, canvas.width, canvas.height);
// // Draw the current frame
// ctx.drawImage(imageElementsRef.current[currentFrameRef.current], 0, 0, canvas.width, canvas.height);
// animationRef.current = requestAnimationFrame(animate);
// };
// animationRef.current = requestAnimationFrame(animate);
// return () => {
// if (animationRef.current) {
// cancelAnimationFrame(animationRef.current);
// }
// };
// }
// }, [images]);
// if (loading) {
// return <div>Loading...</div>;
// } else {
// return (
// <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
// <canvas
// ref={canvasRef}
// width={'500px'}
// height={'700'} // Maintain aspect ratio (example: 16:9)
// style={{ maxWidth: animationWidth, height: 'auto' }}
// />
// </div>
// );
// }
// };
// export default Animation;
import React, { useEffect, useState, useRef } from 'react';
import JSZip from 'jszip';
const Animation = ({ src = "src/assets/mainCharacters_pow2/M_porsche_cross_arm_power2.zip", animationWidth = "500px" }) => {
const [images, setImages] = useState([]);
const [loading, setLoading] = useState(true);
const frameRate = 1000 / 24; // Original frame rate (24 FPS)
const canvasRef = useRef(null);
const animationRef = useRef(null);
const currentFrameRef = useRef(0);
const imageElementsRef = useRef([]);
// Load images from the ZIP file
useEffect(() => {
const loadImages = async () => {
const zip = new JSZip();
try {
const response = await fetch(src);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.arrayBuffer();
const zipContent = await zip.loadAsync(data);
const imgPromises = [];
zipContent.forEach((relativePath, file) => {
if (file.name.endsWith('.webp')) {
imgPromises.push(
file.async('base64').then(base64 => {
const img = new Image();
img.src = `data:image/webp;base64,${base64}`;
return new Promise((resolve, reject) => {
img.onload = () => resolve(img);
img.onerror = reject;
});
})
);
}
});
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
} catch (error) {
console.error('Error loading images:', error);
} finally {
setLoading(false);
}
};
loadImages();
}, [src]);
// WebGL setup and animation loop
useEffect(() => {
if (images.length > 0) {
const canvas = canvasRef.current;
const gl = canvas.getContext('webgl');
if (!gl) {
console.error("WebGL is not supported.");
return;
}
// Set up WebGL context (basic)
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, canvas.width, canvas.height);
// Enable blending
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;
varying vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
}
`;
const fragmentShaderSource = `
precision mediump float;
uniform sampler2D u_texture;
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_texture, v_texCoord);
}
`;
const createShader = (source, type) => {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('ERROR compiling shader', gl.getShaderInfoLog(shader));
}
return shader;
};
const vertexShader = createShader(vertexShaderSource, gl.VERTEX_SHADER);
const fragmentShader = createShader(fragmentShaderSource, gl.FRAGMENT_SHADER);
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.error('ERROR linking program', 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
]);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const positionLocation = gl.getAttribLocation(shaderProgram, "a_position");
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)
]);
gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);
const texCoordLocation = gl.getAttribLocation(shaderProgram, "a_texCoord");
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(texCoordLocation);
const uTextureLocation = gl.getUniformLocation(shaderProgram, "u_texture");
let lastFrameTime = 0;
const animate = (timestamp) => {
if (lastFrameTime === 0) lastFrameTime = timestamp;
const elapsed = timestamp - lastFrameTime;
if (elapsed > frameRate) {
currentFrameRef.current = (currentFrameRef.current + 1) % images.length;
loadTexture(imageElementsRef.current[currentFrameRef.current]);
lastFrameTime = timestamp;
}
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
animationRef.current = requestAnimationFrame(animate);
};
animationRef.current = requestAnimationFrame(animate);
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}
}, [images]);
if (loading) {
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>
);
}
};
export default Animation;

View file

@ -0,0 +1,46 @@
import React, { useEffect, useRef, useState } from 'react';
import anime from 'animejs';
const startNumber = 1000;
const endNumber = 1239;
const imageCount = endNumber - startNumber + 1;
const images = [];
for (let i = startNumber; i <= endNumber; i++) {
const formattedNumber = String(i).padStart(5, '0');
images.push(import(`../../assets/characters/F_porche_akimbo_AME/Porsche${formattedNumber}.png`));
}
const CharacterAnimation = () => {
const totalFrames = imageCount;
const frameDuration = 20;
const [currentFrame, setCurrentFrame] = useState(0);
const [loadedImages, setLoadedImages] = useState([]);
const characterRef = useRef(null);
useEffect(() => {
Promise.all(images).then((resolvedImages) => {
setLoadedImages(resolvedImages.map(image => image.default));
});
}, []);
useEffect(() => {
const interval = setInterval(() => {
setCurrentFrame((prevFrame) => (prevFrame + 1) % totalFrames);
}, frameDuration);
return () => clearInterval(interval);
}, []);
return (
<img
ref={characterRef}
src={loadedImages[currentFrame]}
alt="Character Animation"
style={{ width: '200px', height: 'auto' }}
/>
);
};
export default CharacterAnimation;

View file

@ -0,0 +1,81 @@
import React, { useEffect, useState } from 'react';
import JSZip from 'jszip';
const TestAnimation = () => {
const [images, setImages] = useState([]);
const [currentFrame, setCurrentFrame] = useState(0);
const [loading, setLoading] = useState(true);
const frameRate = 60;
useEffect(() => {
const loadImages = async () => {
const zip = new JSZip();
try {
const response = await fetch('src/assets/mainCharacters/M_Porsche_cross_arm.zip');
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.arrayBuffer();
const zipContent = await zip.loadAsync(data);
const imgPromises = [];
zipContent.forEach((relativePath, file) => {
if (file.name.endsWith('.webp')) {
imgPromises.push(
file.async('base64').then(base64 => {
return `data:image/webp;base64,${base64}`;
})
);
}
});
const imgUrls = await Promise.all(imgPromises);
// console.log('Loaded images:', imgUrls);
if (imgUrls.length === 0) {
console.error('No images found in the ZIP file.');
}
setImages(imgUrls);
} catch (error) {
console.error('Error loading images:', error);
} finally {
setLoading(false);
}
};
loadImages();
}, []);
useEffect(() => {
if (images.length > 0) {
const interval = setInterval(() => {
setCurrentFrame((prevFrame) => (prevFrame + 1) % images.length);
}, frameRate);
return () => clearInterval(interval);
}
}, [images]);
if (loading) {
return <div>Loading...</div>;
} else {
return (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
{images.length > 0 ? (
<img
src={images[currentFrame]}
alt={`Animation frame ${currentFrame + 1}`}
style={{ maxWidth: '500px', height: 'auto' }}
/>
) : (
<div>No images to display.</div>
)}
</div>
);
}
};
export default TestAnimation;

View file

@ -0,0 +1,58 @@
import { useState } from 'react'
import '../css/global.css'
import BlackButton from './components/customButton'
import Animation from './components/animation.jsx';
function IntroductionPage() {
return(
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
maxWidth:"100vw",
flexDirection: 'column',
}}>
<div style={{position:"absolute"}}>
<Animation src='src/assets/introduction/6_Classroom.zip' animationWidth='100vw'/>
</div>
<div style={{position:"absolute"}}>
<Animation src='src/assets/introduction/5_M_Pie.zip' animationWidth='100vw'/>
</div>
<div style={{position:"absolute"}}>
<Animation src='src/assets/introduction/4_NPC+hallway.zip' animationWidth='100vw'/>
</div>
<div style={{position:"absolute"}}>
<Animation src='src/assets/introduction/3_M_Patt.zip' animationWidth='100vw'/>
</div>
<div style={{position:"absolute"}}>
<Animation src='src/assets/introduction/2_M_Porsche.zip' animationWidth='100vw'/>
</div>
<div style={{position:"absolute"}}>
<Animation src='src/assets/introduction/1_NPC+pillar.zip' animationWidth='100vw'/>
</div>
<div style={{
position: "fixed",
top: 0,
left: 0,
width: "100vw",
height: "100vh",
background: "rgba(var(--black), 0.5)",
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1 // Ensure this is above other elements
}}>
<label style={{color:"rgb(var(--white))", fontWeight:"bold"}}>
You are a new student who just <br />
transfered to this school not long ago. <br />
You don't know anyone yet, <br />
but eventually you meet...
</label>
</div>
</div>
)
}
export default IntroductionPage

View file

@ -29,7 +29,7 @@ function NamePage() {
name==''?
<></>
:
<BlackButton text="Continue" to='/name'/>
<BlackButton text="Continue" to='/introduction'/>
}
</div>
)

View file

@ -4,4 +4,5 @@ import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
assetsInclude: ['**/*.zip'],
})