from: https://threejs.org/manual/examples/game-just-player.html
Created
March 19, 2023 23:17
-
-
Save generge/0b09ed8273141b9733bb3e218361a645 to your computer and use it in GitHub Desktop.
Three.js - Game - Just Player
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<canvas id="c"></canvas> | |
<div id="loading"> | |
<div> | |
<div>...loading...</div> | |
<div class="progress"><div id="progressbar"></div></div> | |
</div> | |
</div> | |
<script type="importmap">{ | |
"imports": { | |
"three": "https://threejs.org/build/three.module.js", | |
"three/addons/": "https://threejs.org/examples/jsm/" | |
} | |
}</script><!-- Remove this when import maps will be widely supported --> | |
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Three.js - Game - Just Player | |
// from https://threejs.org/manual/examples/game-just-player.html | |
import * as THREE from 'three'; | |
import {OrbitControls} from 'three/addons/controls/OrbitControls.js'; | |
import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js'; | |
import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js'; | |
function main() { | |
const canvas = document.querySelector('#c'); | |
const renderer = new THREE.WebGLRenderer({antialias: true, canvas}); | |
renderer.outputEncoding = THREE.sRGBEncoding; | |
const fov = 45; | |
const aspect = 2; // the canvas default | |
const near = 0.1; | |
const far = 1000; | |
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far); | |
camera.position.set(0, 40, 80); | |
const controls = new OrbitControls(camera, canvas); | |
controls.target.set(0, 5, 0); | |
controls.update(); | |
const scene = new THREE.Scene(); | |
scene.background = new THREE.Color('white'); | |
function addLight(...pos) { | |
const color = 0xFFFFFF; | |
const intensity = 0.8; | |
const light = new THREE.DirectionalLight(color, intensity); | |
light.position.set(...pos); | |
scene.add(light); | |
scene.add(light.target); | |
} | |
addLight(5, 5, 2); | |
addLight(-5, 5, 5); | |
const manager = new THREE.LoadingManager(); | |
manager.onLoad = init; | |
const progressbarElem = document.querySelector('#progressbar'); | |
manager.onProgress = (url, itemsLoaded, itemsTotal) => { | |
progressbarElem.style.width = `${itemsLoaded / itemsTotal * 100 | 0}%`; | |
}; | |
const models = { | |
pig: { url: 'https://threejs.org/manual/examples/resources/models/animals/Pig.gltf' }, | |
cow: { url: 'https://threejs.org/manual/examples/resources/models/animals/Cow.gltf' }, | |
llama: { url: 'https://threejs.org/manual/examples/resources/models/animals/Llama.gltf' }, | |
pug: { url: 'https://threejs.org/manual/examples/resources/models/animals/Pug.gltf' }, | |
sheep: { url: 'https://threejs.org/manual/examples/resources/models/animals/Sheep.gltf' }, | |
zebra: { url: 'https://threejs.org/manual/examples/resources/models/animals/Zebra.gltf' }, | |
horse: { url: 'https://threejs.org/manual/examples/resources/models/animals/Horse.gltf' }, | |
knight: { url: 'https://threejs.org/manual/examples/resources/models/knight/KnightCharacter.gltf' }, | |
}; | |
{ | |
const gltfLoader = new GLTFLoader(manager); | |
for (const model of Object.values(models)) { | |
gltfLoader.load(model.url, (gltf) => { | |
model.gltf = gltf; | |
}); | |
} | |
} | |
function prepModelsAndAnimations() { | |
Object.values(models).forEach(model => { | |
const animsByName = {}; | |
model.gltf.animations.forEach((clip) => { | |
animsByName[clip.name] = clip; | |
// Should really fix this in .blend file | |
if (clip.name === 'Walk') { | |
clip.duration /= 2; | |
} | |
}); | |
model.animations = animsByName; | |
}); | |
} | |
function removeArrayElement(array, element) { | |
const ndx = array.indexOf(element); | |
if (ndx >= 0) { | |
array.splice(ndx, 1); | |
} | |
} | |
class SafeArray { | |
constructor() { | |
this.array = []; | |
this.addQueue = []; | |
this.removeQueue = new Set(); | |
} | |
get isEmpty() { | |
return this.addQueue.length + this.array.length > 0; | |
} | |
add(element) { | |
this.addQueue.push(element); | |
} | |
remove(element) { | |
this.removeQueue.add(element); | |
} | |
forEach(fn) { | |
this._addQueued(); | |
this._removeQueued(); | |
for (const element of this.array) { | |
if (this.removeQueue.has(element)) { | |
continue; | |
} | |
fn(element); | |
} | |
this._removeQueued(); | |
} | |
_addQueued() { | |
if (this.addQueue.length) { | |
this.array.splice(this.array.length, 0, ...this.addQueue); | |
this.addQueue = []; | |
} | |
} | |
_removeQueued() { | |
if (this.removeQueue.size) { | |
this.array = this.array.filter(element => !this.removeQueue.has(element)); | |
this.removeQueue.clear(); | |
} | |
} | |
} | |
class GameObjectManager { | |
constructor() { | |
this.gameObjects = new SafeArray(); | |
} | |
createGameObject(parent, name) { | |
const gameObject = new GameObject(parent, name); | |
this.gameObjects.add(gameObject); | |
return gameObject; | |
} | |
removeGameObject(gameObject) { | |
this.gameObjects.remove(gameObject); | |
} | |
update() { | |
this.gameObjects.forEach(gameObject => gameObject.update()); | |
} | |
} | |
const globals = { | |
time: 0, | |
deltaTime: 0, | |
}; | |
const gameObjectManager = new GameObjectManager(); | |
class GameObject { | |
constructor(parent, name) { | |
this.name = name; | |
this.components = []; | |
this.transform = new THREE.Object3D(); | |
parent.add(this.transform); | |
} | |
addComponent(ComponentType, ...args) { | |
const component = new ComponentType(this, ...args); | |
this.components.push(component); | |
return component; | |
} | |
removeComponent(component) { | |
removeArrayElement(this.components, component); | |
} | |
getComponent(ComponentType) { | |
return this.components.find(c => c instanceof ComponentType); | |
} | |
update() { | |
for (const component of this.components) { | |
component.update(); | |
} | |
} | |
} | |
// Base for all components | |
class Component { | |
constructor(gameObject) { | |
this.gameObject = gameObject; | |
} | |
update() { | |
} | |
} | |
class SkinInstance extends Component { | |
constructor(gameObject, model) { | |
super(gameObject); | |
this.model = model; | |
this.animRoot = SkeletonUtils.clone(this.model.gltf.scene); | |
this.mixer = new THREE.AnimationMixer(this.animRoot); | |
gameObject.transform.add(this.animRoot); | |
this.actions = {}; | |
} | |
setAnimation(animName) { | |
const clip = this.model.animations[animName]; | |
// turn off all current actions | |
for (const action of Object.values(this.actions)) { | |
action.enabled = false; | |
} | |
// get or create existing action for clip | |
const action = this.mixer.clipAction(clip); | |
action.enabled = true; | |
action.reset(); | |
action.play(); | |
this.actions[animName] = action; | |
} | |
update() { | |
this.mixer.update(globals.deltaTime); | |
} | |
} | |
class Player extends Component { | |
constructor(gameObject) { | |
super(gameObject); | |
const model = models.knight; | |
this.skinInstance = gameObject.addComponent(SkinInstance, model); | |
this.skinInstance.setAnimation('Run'); | |
} | |
} | |
function init() { | |
// hide the loading bar | |
const loadingElem = document.querySelector('#loading'); | |
loadingElem.style.display = 'none'; | |
prepModelsAndAnimations(); | |
{ | |
const gameObject = gameObjectManager.createGameObject(scene, 'player'); | |
gameObject.addComponent(Player); | |
} | |
} | |
function resizeRendererToDisplaySize(renderer) { | |
const canvas = renderer.domElement; | |
const width = canvas.clientWidth; | |
const height = canvas.clientHeight; | |
const needResize = canvas.width !== width || canvas.height !== height; | |
if (needResize) { | |
renderer.setSize(width, height, false); | |
} | |
return needResize; | |
} | |
let then = 0; | |
function render(now) { | |
// convert to seconds | |
globals.time = now * 0.001; | |
// make sure delta time isn't too big. | |
globals.deltaTime = Math.min(globals.time - then, 1 / 20); | |
then = globals.time; | |
if (resizeRendererToDisplaySize(renderer)) { | |
const canvas = renderer.domElement; | |
camera.aspect = canvas.clientWidth / canvas.clientHeight; | |
camera.updateProjectionMatrix(); | |
} | |
gameObjectManager.update(); | |
renderer.render(scene, camera); | |
requestAnimationFrame(render); | |
} | |
requestAnimationFrame(render); | |
} | |
main(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
html { | |
box-sizing: border-box; | |
} | |
*, *:before, *:after { | |
box-sizing: inherit; | |
} | |
html, body { | |
margin: 0; | |
height: 100%; | |
} | |
#c { | |
width: 100%; | |
height: 100%; | |
display: block; | |
} | |
#loading { | |
position: absolute; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
text-align: center; | |
font-size: xx-large; | |
font-family: sans-serif; | |
} | |
#loading>div>div { | |
padding: 2px; | |
} | |
.progress { | |
width: 50vw; | |
border: 1px solid black; | |
} | |
#progressbar { | |
width: 0%; | |
transition: width ease-out .5s; | |
height: 1em; | |
background-color: #888; | |
background-image: linear-gradient( | |
-45deg, | |
rgba(255, 255, 255, .5) 25%, | |
transparent 25%, | |
transparent 50%, | |
rgba(255, 255, 255, .5) 50%, | |
rgba(255, 255, 255, .5) 75%, | |
transparent 75%, | |
transparent | |
); | |
background-size: 50px 50px; | |
animation: progressanim 2s linear infinite; | |
} | |
@keyframes progressanim { | |
0% { | |
background-position: 50px 50px; | |
} | |
100% { | |
background-position: 0 0; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment