threejs实现一个三维户外山上滑雪运动游戏代码
代码语言:html
所属分类:游戏
代码描述:threejs实现一个三维户外山上滑雪运动游戏代码,键盘左右键操作方向避开石头和树木。
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
<!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <style> @import url('https://fonts.googleapis.com/css?family=Ranchers'); html, body { height: 100%; width: 100%; padding: 0; margin: 0; overflow: hidden; font-family: 'Ranchers', sans-serif; } body { background-image: url('//repo.bfw.wiki/bfwrepo/images/ski/snowmountain.jpg'); background-size: cover; background-position: center; } canvas { width: 100%; height: 100%; margin: 0; padding: 0; } div { user-select: none; } .label-death { display: none; z-index: 10; position: absolute; top: 25%; left: 50%; transform: translate(-50%, -50%); font-size: 10rem; color: white; &.active { display: block; } } .label-death-bg { content: " "; z-index: 9; position: absolute; top: 50%; left: 50%; width: 0; height: 20rem; transform: translate(-50%, -50%); transition: all 2s ease; background-color: black; opacity: 0.7; &.active { top: 50%; width: 100%; height: 100%; } } .label-score { z-index: 10; position: absolute; left: 5vw; bottom: 5vh; font-size: 10rem; transition: all 500ms ease; color: black; &.stopped { top: 50%; left: 50%; bottom: auto; font-size: 15rem; transform: translate(-50%, -50%); color: white; } } .label-restart { display: none; z-index: 100; position: absolute; padding: 1rem 2rem; top: 75%; left: 50%; width: 100%; text-align: center; font-size: 5rem; color: white; transform: translate(-50%, -50%); &.active { display: block; } } </style> </head> <body > <div class="label-death">REKT</div> <div class="label-death-bg"></div> <div class="label-score"></div> <div class="label-restart">PRESS ENTER TO PLAY AGAIN</div> <script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/three.92.js"></script> <script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/mountain.plugins.js"></script> <script > class World { constructor(wnd) { this.window = wnd; this.clock = new THREE.Clock(); this.isLoading = true; this.loader = THREE.DefaultLoadingManager; this.onLoadedCallbacks = []; this.loader.onLoad = () => { this.isLoading = false; this.onLoadedCallbacks.forEach(cb => cb()); }; this.loader.onError = url => console.error(`There was an error loading ${url}`); this.setupRenderer(); this.setupScene(); this.setupLighting(); // Auto resize engine wnd.addEventListener('resize', () => { this.renderer.setSize(wnd.innerWidth, wnd.innerHeight); }); this.onRenderCallbacks = []; this.animationMixers = []; this.loadedFbx = {}; } drawGridQuadrant(signX, signZ) { const GRID_SIZE = 10; const GRID_N = 20; const sX = signX > 0 ? 1 : -1; const sZ = signZ > 0 ? 1 : -1; for (let i = 0; i < GRID_N; i++) { for (let j = 0; j < GRID_N; j++) { const offX = i * GRID_SIZE * sX; const offZ = j * GRID_SIZE * sZ; const geo = new THREE.BufferGeometry(); const verts = new Float32Array([ offX, 0, offZ, offX, 0, offZ + GRID_SIZE, offX + GRID_SIZE, 0, offZ + GRID_SIZE, offX + GRID_SIZE, 0, offZ, offX, 0, offZ]); geo.addAttribute('position', new THREE.BufferAttribute(verts, 3)); const mat = new THREE.LineBasicMaterial({ color: 0 }); const line = new THREE.Line(geo, mat); this.scene.add(line); } } } setupRenderer() { const renderer = new THREE.WebGLRenderer({ alpha: true }); renderer.setSize(this.window.innerWidth, this.window.innerHeight); this.renderer = renderer; this.window.document.body.appendChild(renderer.domElement); } setupScene() { const scene = new THREE.Scene(); scene.background = new THREE.Color(0xeeeeee); this.scene = scene; } setupLighting() { const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); this.scene.add(ambientLight); this.ambientLight = ambientLight; const hemisphericLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.5); hemisphericLight.position.y += 1500; this.scene.add(hemisphericLight); } addAnimationMixer(mixer) { this.animationMixers.push(mixer); } loadFbx(name, filename, addToScene = false, cb = () => {}) { const fbxLoader = new THREE.FBXLoader(this.loader); fbxLoader.load(filename, object => { object.name = name; if (this.loadedFbx[name]) { console.log(`Warning: overwriting existing FBX '${name}'!`); } this.loadedFbx[name] = object; if (addToScene) this.scene.add(object); cb(null, object); }, xhr => { // console.log(xhr.loaded/xhr.total*100 + '% loaded') }, xhr => { const errMsg = `Error loading FBX '${name}': ${JSON.stringify(xhr)}!`; console.error(errMsg); cb(new Error(errMsg), null); }); } onLoaded(cb) { if (typeof cb !== 'function') { throw new Error(`${cb} must be a function!`); } if (this.isLoading) { this.onLoadedCallbacks.push(cb); } else { // Already loaded, invoke callback immediately cb(); } } onRender(cb) { if (typeof cb !== 'function') { throw new Error(`${cb} must be a function!`); } else { this.onRenderCallbacks.push(cb); } } setCamera(camera) { this.camera = camera; } teardown() { cancelAnimationFrame(this.animationFrameId); while (this.scene.children.length) { const child = this.scene.children[0]; child.traverse(c => { if (typeof c.dispose === 'function') { c.dispose(); } }); if (typeof child.dispose === 'function') { child.dispose(); } this.scene.remove(child); } this.scene = null; this.camera = null; this.clock = null; this.loader = null; this.onLoadedCallbacks = null; this.onRenderCallbacks = null; this.animationMixers = null; Object.keys(this.loadedFbx).forEach(key => { this.loadedFbx[key].traverse(child => { if (typeof child.dispose === 'function') { child.dispose(); } }); this.loadedFbx[key] = null; delete this.loadedFbx[key]; }); this.renderer.domElement.remove(); this.renderer = null; } render() { // Store the delta so it can be passed around (for consistency) const clockDelta = this.clock.getDelta(); // Run animations this.animationMixers.forEach(mixer => mixer.update(clockDelta)); // Run onRender subscriptions this.onRenderCallbacks.forEach(cb => cb(clockDelta)); // Render current frame only if camera available if (this.camera) { this.renderer.render(this.scene, this.camera); } else { // console.error('No camera has been setup yet!') } // Next frame this.animationFrameId = requestAnimationFrame(() => this.render()); }} class Player { constructor(world) { this.world = world; this.speed = 100.; // scalar, pos units per tick this.bearing = 0; this.moveForward = true; this.moveBackward = false; this.moveLeft = false; this.moveRight = false; this.ROTATION_OFFSET_Y = 0; this.dead = false; this.attachControl(); this.setupModel(); } get position() { const model = this.model; return model ? model.position : new THREE.Vector3(0, 0, 0); } setupModel() { const world = this.world; world.loadFbx('player', '//repo.bfw.wiki/bfwrepo/threemodel/player@skateboarding.fbx', true); world.loadFbx('playerDying', '//repo.bfw.wiki/bfwrepo/threemodel/player@dying.fbx', false); world.loadFbx('snowboard', '//repo.bfw.wiki/bfwrepo/threemodel/snowboard.fbx', true); world.onLoaded(() => { const player = world.loadedFbx['player']; const playerDying = world.loadedFbx['playerDying']; const snowboard = world.loadedFbx['snowboard']; this.model = player; let footBone; player.traverse(child => { if (child.type === 'Bone' && child.name === 'mixamorigLeftFoot') { footBone = child; } }); // Position camera, set the scale, etc snowboard.scale.set(4, 4, 4); footBone.add(snowboard); snowboard.rotateX(-2.1); snowboard.rotateZ(-0.6); snowboard.translateX(105); snowboard.translateZ(13); player.traverse(m => { if (m.type === 'SkinnedMesh' || m.type === 'Mesh') { m.castShadow = true; } }); player.scale.set(0.1, 0.1, 0.1); player.rotation.x = Math.PI / 48; // Add the loaded animations to the base mesh // Name them for convenience player.animations[0].name = 'idle'; playerDying.animations[0].name = 'dying'; player.animations.push(...playerDying.animations); // Setup AnimationMixer for loaded model const mixer = new THREE.AnimationMixer(player); world.addAnimationMixer(mixer); this.animationMixer = mixer; // Reset clip durations player.animations.forEach(clip => { clip.resetDuration(); }); // Play idle animation this.playAnimation('idle'); world.onRender(clockDelta => this.move(clockDelta)); }); } attachControl(container = this.world.window) { let mouseDownRunning = false; container.addEventListener('keydown', event => { switch (event.code) { case 'KeyW': this.moveForward = true; this.moveBackward = false; break; case 'KeyS': // this.moveForward = false // this.moveBackward = true break; case 'KeyA': case 'ArrowLeft': this.moveLeft = true; this.moveRight = false; break; case 'KeyD': case 'ArrowRight': this.moveLeft = false; this.moveRight = true; break;} }); container.addEventListener('keyup', event => { switch (event.code) { case 'KeyW': // if (!mouseDownRunning) this.moveForward = false break; case 'KeyS': this.moveBackward = false; break; case 'KeyA': case 'ArrowLeft': this.moveLeft = false; break; case 'KeyD': case 'ArrowRight': this.moveRight = false; break;} }); container.addEventListener('touchstart', event => { const touches = event.changedTouches; const touch = touches[0]; if (touch.clientX < window.innerWidth / 2) { this.moveLeft = true; this.moveRight = false; } else { this.moveLeft = false; this.moveRight = true; } }); container.addEventListener('touchend', event => { this.moveLeft = false; this.moveRight = false; }); } playAnimation(name, loop = true) { if (this.lastAnimation === name) return; const loopMode = loop ? THREE.LoopRepeat : THREE.LoopOnce; const lastClip = THREE.AnimationClip.findByName(this.model, this.lastAnimation); const nextClip = THREE.AnimationClip.findByName(this.model, name); if (nextClip instanceof THREE.AnimationClip) { const existingAction = this.animationMixer.existingAction(lastClip); this.animationMixer.stopAllAction(); const nextAction = this.animationMixer.clipAction(nextClip). setLoop(loopMode); nextAction.clampWhenFinished = !loop; if (existingAction) { nextAction.play().crossFadeFrom(existingAction, 0.2); } else { nextAction.play(); } } this.lastAnimation = name; } die() { this.timeOfDeath = this.world.clock.elapsedTime; this.speed = -50; this.playAnimation('dying', false.........完整代码请登录后点击上方下载按钮下载查看
网友评论0