Initial commit of template
This commit is contained in:
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Lockfiles
|
||||||
|
bun.lock
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
.vite
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
cache
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
29
eslint.config.js
Normal file
29
eslint.config.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,jsx}'],
|
||||||
|
extends: [
|
||||||
|
js.configs.recommended,
|
||||||
|
reactHooks.configs.flat.recommended,
|
||||||
|
reactRefresh.configs.vite,
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
ecmaFeatures: { jsx: true },
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
14
index.html
Normal file
14
index.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Three.js Game</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
|
<script src="https://content.trynoah.ai/mg-utils.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
30
package.json
Normal file
30
package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "noah-threejs-template",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --host",
|
||||||
|
"build": "vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"watch": "vite build --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@supabase/supabase-js": "2.57.4",
|
||||||
|
"react": "19.1.0",
|
||||||
|
"react-dom": "19.1.0",
|
||||||
|
"three": "0.160.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "9.9.1",
|
||||||
|
"@types/react": "19.0.0",
|
||||||
|
"@types/react-dom": "19.0.0",
|
||||||
|
"@vitejs/plugin-react-swc": "4.3.0",
|
||||||
|
"eslint": "9.9.1",
|
||||||
|
"eslint-plugin-react-hooks": "5.1.0-rc.0",
|
||||||
|
"eslint-plugin-react-refresh": "0.4.11",
|
||||||
|
"globals": "15.9.0",
|
||||||
|
"vite": "8.0.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/App.jsx
Normal file
18
src/App.jsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
import { ThreeScene } from "./game/ThreeScene";
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const containerRef = useRef(null);
|
||||||
|
const sceneRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (containerRef.current && !sceneRef.current) {
|
||||||
|
sceneRef.current = new ThreeScene(containerRef.current);
|
||||||
|
sceneRef.current.init();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <div id="game-container" ref={containerRef}></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
171
src/game/ThreeScene.js
Normal file
171
src/game/ThreeScene.js
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import * as THREE from "three";
|
||||||
|
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
||||||
|
|
||||||
|
export class ThreeScene {
|
||||||
|
constructor(container) {
|
||||||
|
this.container = container;
|
||||||
|
this.scene = new THREE.Scene();
|
||||||
|
this.camera = new THREE.PerspectiveCamera(
|
||||||
|
75,
|
||||||
|
this.container.clientWidth / this.container.clientHeight,
|
||||||
|
0.1,
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
this.renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||||
|
this.controls = null;
|
||||||
|
this.clock = new THREE.Clock();
|
||||||
|
|
||||||
|
this.playerObject = null;
|
||||||
|
this.planetObject = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.renderer.setSize(
|
||||||
|
this.container.clientWidth,
|
||||||
|
this.container.clientHeight
|
||||||
|
);
|
||||||
|
this.renderer.setPixelRatio(window.devicePixelRatio);
|
||||||
|
this.renderer.setClearColor(0x101020);
|
||||||
|
this.container.appendChild(this.renderer.domElement);
|
||||||
|
|
||||||
|
this.camera.position.set(0, 10, 20);
|
||||||
|
|
||||||
|
const ambientLight = new THREE.AmbientLight(0x404040);
|
||||||
|
this.scene.add(ambientLight);
|
||||||
|
|
||||||
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
||||||
|
directionalLight.position.set(10, 15, 10);
|
||||||
|
this.scene.add(directionalLight);
|
||||||
|
|
||||||
|
this.addStars();
|
||||||
|
|
||||||
|
this.createPlanet();
|
||||||
|
this.createPlayer();
|
||||||
|
|
||||||
|
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
||||||
|
this.controls.enableDamping = true;
|
||||||
|
this.controls.dampingFactor = 0.05;
|
||||||
|
this.controls.screenSpacePanning = false;
|
||||||
|
this.controls.minDistance = 5;
|
||||||
|
this.controls.maxDistance = 100;
|
||||||
|
this.controls.target.set(0, 0, 0);
|
||||||
|
|
||||||
|
window.addEventListener("resize", () => this.onWindowResize());
|
||||||
|
|
||||||
|
this.animate();
|
||||||
|
}
|
||||||
|
|
||||||
|
addStars() {
|
||||||
|
const starsGeometry = new THREE.BufferGeometry();
|
||||||
|
const starsMaterial = new THREE.PointsMaterial({
|
||||||
|
color: 0xffffff,
|
||||||
|
size: 0.05,
|
||||||
|
sizeAttenuation: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const starsVertices = [];
|
||||||
|
for (let i = 0; i < 10000; i++) {
|
||||||
|
const x = THREE.MathUtils.randFloatSpread(200);
|
||||||
|
const y = THREE.MathUtils.randFloatSpread(200);
|
||||||
|
const z = THREE.MathUtils.randFloatSpread(200);
|
||||||
|
if (Math.sqrt(x * x + y * y + z * z) > 20) {
|
||||||
|
starsVertices.push(x, y, z);
|
||||||
|
} else {
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
starsGeometry.setAttribute(
|
||||||
|
"position",
|
||||||
|
new THREE.Float32BufferAttribute(starsVertices, 3)
|
||||||
|
);
|
||||||
|
const stars = new THREE.Points(starsGeometry, starsMaterial);
|
||||||
|
this.scene.add(stars);
|
||||||
|
}
|
||||||
|
|
||||||
|
createPlanet() {
|
||||||
|
const planetGeometry = new THREE.SphereGeometry(4, 32, 32);
|
||||||
|
const planetMaterial = new THREE.MeshPhongMaterial({
|
||||||
|
color: 0x4a6f8a,
|
||||||
|
shininess: 10,
|
||||||
|
});
|
||||||
|
this.planetObject = new THREE.Mesh(planetGeometry, planetMaterial);
|
||||||
|
this.scene.add(this.planetObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
createPlayer() {
|
||||||
|
const playerGroup = new THREE.Group();
|
||||||
|
|
||||||
|
const bodyGeometry = new THREE.ConeGeometry(0.8, 2, 8);
|
||||||
|
const bodyMaterial = new THREE.MeshPhongMaterial({
|
||||||
|
color: 0xe88a5b,
|
||||||
|
shininess: 30,
|
||||||
|
});
|
||||||
|
const playerBody = new THREE.Mesh(bodyGeometry, bodyMaterial);
|
||||||
|
playerBody.rotation.x = Math.PI / 2;
|
||||||
|
playerGroup.add(playerBody);
|
||||||
|
|
||||||
|
const cockpitGeometry = new THREE.SphereGeometry(
|
||||||
|
0.4,
|
||||||
|
16,
|
||||||
|
16,
|
||||||
|
0,
|
||||||
|
Math.PI * 2,
|
||||||
|
0,
|
||||||
|
Math.PI / 2
|
||||||
|
);
|
||||||
|
const cockpitMaterial = new THREE.MeshPhongMaterial({
|
||||||
|
color: 0xadd8e6,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.6,
|
||||||
|
shininess: 50,
|
||||||
|
});
|
||||||
|
const cockpit = new THREE.Mesh(cockpitGeometry, cockpitMaterial);
|
||||||
|
cockpit.rotation.x = Math.PI;
|
||||||
|
cockpit.position.set(0, 0, -0.6);
|
||||||
|
playerBody.add(cockpit);
|
||||||
|
|
||||||
|
this.playerObject = playerGroup;
|
||||||
|
this.playerObject.position.set(0, 0, 10);
|
||||||
|
this.scene.add(this.playerObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowResize() {
|
||||||
|
this.camera.aspect =
|
||||||
|
this.container.clientWidth / this.container.clientHeight;
|
||||||
|
this.camera.updateProjectionMatrix();
|
||||||
|
this.renderer.setSize(
|
||||||
|
this.container.clientWidth,
|
||||||
|
this.container.clientHeight
|
||||||
|
);
|
||||||
|
this.renderer.setPixelRatio(window.devicePixelRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
animate() {
|
||||||
|
requestAnimationFrame(() => this.animate());
|
||||||
|
|
||||||
|
const delta = this.clock.getDelta();
|
||||||
|
const elapsedTime = this.clock.getElapsedTime();
|
||||||
|
|
||||||
|
if (this.planetObject) {
|
||||||
|
this.planetObject.rotation.y += 0.2 * delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.playerObject) {
|
||||||
|
const orbitRadius = 10;
|
||||||
|
const orbitSpeed = 0.5;
|
||||||
|
this.playerObject.position.x =
|
||||||
|
Math.cos(elapsedTime * orbitSpeed) * orbitRadius;
|
||||||
|
this.playerObject.position.z =
|
||||||
|
Math.sin(elapsedTime * orbitSpeed) * orbitRadius;
|
||||||
|
this.playerObject.lookAt(this.planetObject.position);
|
||||||
|
this.playerObject.rotateY(Math.PI);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.controls) {
|
||||||
|
this.controls.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.renderer.render(this.scene, this.camera);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/index.css
Normal file
13
src/index.css
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#game-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
10
src/main.jsx
Normal file
10
src/main.jsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { StrictMode } from 'react'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import './index.css'
|
||||||
|
import App from './App.jsx'
|
||||||
|
|
||||||
|
createRoot(document.getElementById('root')).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>,
|
||||||
|
)
|
||||||
21
vite.config.js
Normal file
21
vite.config.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react-swc";
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => ({
|
||||||
|
base: "/games/",
|
||||||
|
server: {
|
||||||
|
host: "0.0.0.0",
|
||||||
|
port: 3000,
|
||||||
|
allowedHosts: true,
|
||||||
|
hmr: {
|
||||||
|
overlay: false,
|
||||||
|
timeout: 15000,
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
usePolling: true,
|
||||||
|
interval: 500,
|
||||||
|
binaryInterval: 500,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [react()],
|
||||||
|
}));
|
||||||
Reference in New Issue
Block a user