threejs实现一个三维户外山上滑雪运动游戏代码

代码语言:html

所属分类:游戏

代码描述:threejs实现一个三维户外山上滑雪运动游戏代码,键盘左右键操作方向避开石头和树木。

代码标签: three 三维 户外 滑雪 游戏

下面为部分代码预览,完整代码请点击下载或在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