3D堆叠游戏——第一步 基础 初始化游戏

1,040 阅读4分钟

应用技术栈

webpack

threejs

typescript

第一步,准备工作(思考游戏玩法,绘制思维导图,搭建项目,创建场景)

文件目录

│  bash.exe.stackdump
│  package-lock.json
│  package.json
│  README.md
│  tsconfig.json
│  webpack.config.js
│  webpack.plugins.js
│
└─src
    ├─html
    │      index.html  // html
    │
    └─screen
            index.ts  // ts入口文件

下面是主要的功能以及场景元素的选择

来自灵魂画师绘制的构图

首先创建场景以及其他必要元素

一堆代码

const THREE = require("three");
import { OrbitControls } from "../../node_modules/three/examples/jsm/controls/OrbitControls";

class CreateScene {
  // 屏幕宽度
  width: number = window.innerWidth;
  // 屏幕高度
  height: number = window.innerHeight;
  // 3d容器
  container: any = document.body;
  frustumSize = 2000;
  scene // 场景
  renderer  // 渲染器
  camera // 相机
  controls // 控制器 
  constructor() {
    this.createScene()
    this.createCamera()
    this.createRenderer()
    this.createControls()
    this.render()
    this.axesHelper(100)
    this.createBackground()
    // 监听屏幕尺寸变化
    window.addEventListener("resize", this.onWindowResized.bind(this), false);
  }
  // 创建场景
  createScene(): void {
    this.scene = new THREE.Scene();
  }
  // 创建渲染器
  createRenderer(): void {
    this.renderer = new THREE.WebGLRenderer();
    this.renderer.setSize(this.width, this.height);
    document.body.appendChild(this.renderer.domElement);
  }
  // 创建相机
  createCamera(): void {
    this.camera = new THREE.OrthographicCamera(this.width / - 2, this.width / 2, this.height / 2, this.height / - 2, 1, this.frustumSize);
    this.camera.zoom = 3
    console.log(this.camera)
    this.camera.position.set(200, 250, 200)
    this.scene.add(this.camera);
    this.camera.updateProjectionMatrix()
  }
  // 创建控制器
  createControls(): void {
    this.controls = new OrbitControls(
      this.camera,
      this.renderer.domElement
    );
    this.controls.screenSpacePanning = true
    this.controls.target = new THREE.Vector3(0, 100, 0)

  }
  // 渲染动画
  animate(): void {
    requestAnimationFrame(this.render.bind(this));
  }
  // 渲染
  render(): void {
    this.animate()
    this.renderer.render(this.scene, this.camera);
    this.controls.update();

  }
  // 创建坐标轴辅助线
  axesHelper(len: number): void {
    const axesHelper = new THREE.AxesHelper(len);
    this.scene.add(axesHelper);
  }
  // 监听屏幕改变
  onWindowResized() {
    this.camera.left = this.width / - 2;
    this.camera.right = this.width / 2;
    this.camera.top = this.height / 2;
    this.camera.bottom = this.height / - 2;
    this.camera.updateProjectionMatrix();
    this.width = window.innerWidth
    this.height = window.innerHeight
    this.renderer.setSize(this.width, this.height);
  }
  // 创建天空背景
  createBackground(): any {
    const canvas = document.createElement('canvas');
    canvas.width = 1;
    canvas.height = 32;

    const context = canvas.getContext('2d');
    const gradient = context.createLinearGradient(0, 0, 0, 32);
    gradient.addColorStop(0.0, '#014a84');
    gradient.addColorStop(0.5, '#0561a0');
    gradient.addColorStop(1.0, '#437ab6');
    context.fillStyle = gradient;
    context.fillRect(0, 0, 1, 32);

    const sky = new THREE.Mesh(
      new THREE.SphereBufferGeometry(1000),
      new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(canvas), side: THREE.BackSide })
    );
    this.scene.add(sky);
  }

}
export { CreateScene, THREE }

然后在screen/index.ts 入口文件直接引用并实例化

import {CreateScene,THREE} from '../createScene/index'
const _this = new CreateScene()

创建后的场景如下

第二步 创建底座以及第一个可以移动的主角

创建主角

initFloor() {
    const w: number = 30
    const h: number = 50
    const l: number = 30
    const floorParams = {
      w: w,
      h: h,
      l: l,
      x: w / 2,
      y: h / 2,
      z: l / 2
    }
    this.floorCube = createCube(floorParams)
    this.floorGroup.add(this.floorCube)
  }

createCube 是封装的一个方法,主要功能创建方块,包括底板和主角

// 创建方块时候需要的参数
interface cubeParams {
  w: number // 宽度  对应X轴
  h: number // 高度  对应Y轴
  l: number // 长度  对应Z轴
  x: number // x轴位置
  y: number // y轴位置
  z: number // z轴位置
}
// 创建方块
export function createCube(p: cubeParams): any {
  const geometry = new THREE.BoxGeometry(p.w, p.h, p.l);
  const material = new THREE.MeshNormalMaterial();
  const cube = new THREE.Mesh(geometry, material);
  cube.position.set(p.x, p.y, p.z)
  return cube
}

创建完成后

接下来需要创建主角(可移动方块)

需要获取的信息为底板的高度,底板的顶点信息 底板的位置信息,过往主角都将视为底板内容,

所以底板不直接添加到scene场景内,而是创建一个group

可以通过封装好的getBox方法获取size

封装好的getBox方法

const THREE = require("three");
function getBox(mesh: any) {
  let b = new THREE.Box3();
  b.expandByObject(mesh);
  return b

}
// 获取尺寸
// 模型,vector3
export function getSize(mesh: any, v3:any) {
  getBox(mesh).getSize(v3);
}
// 获取世界坐标
export function getPosition(mesh:any, v3: any) {
  mesh.getWorldPosition(v3)
}

创建主角并设置位置信息

createlead() {
    const size = new THREE.Vector3()
    const mesh = this.floorGroup
    // 获取尺寸
    getSize(mesh, size)
    const position = new THREE.Vector3()
    // 获取底板的位置 默认应该都是0
    getPosition(mesh, position)
    const gy = position.y // 底板的Y值
    const y = size.y + gy + this.leadY / 2 // 主角的Y值
    // 设定第奇数个主角从z轴的负方向来,第偶数个主角从X轴方向来 
    // 需要一个主角计数器,同样可以用来计算分数
    // 起始点距离底板30
    // 主角初始位置
    const flag = this.leadCount % 2 === 0 // 是否是偶数主角
    // x 起始点
    let sx = (flag ? -this.startPoint : 0) + this.size / 2
    // z 起始点
    let sz = (flag ? 0 : -this.startPoint) + this.size / 2
    // 创建一个主角
    const leadParam = {
      w: this.size,
      h: this.leadY,
      l: this.size,
      x: sx,
      y: y,
      z: sz
    }
    const leadCube = createCube(leadParam)
    this.scene.add(leadCube)
    // 创建角色后计数器自增1
    this.leadCount++
  }

多调用几次便可以从两侧创建主角

下面是initGame代码 又是一堆代码 汗!!!

const THREE = require("three");
import { createCube } from '../utils/tools'
import { getSize, getPosition } from '../utils/getBox'
class CreateGame {
  scene: any
  floorCube: any // 初始底板
  floorGroup: any // 底板组
  size: number = 30 // 主角宽度和长度
  leadY: number = 5 // 主角高度
  leadCount: number = 0 // 计数器
  startPoint: number = 60 // 主角起始位置 x或z
  leadInterval: any = null // 循环
  constructor(element: any) {
    this.scene = element.scene
    this.floorGroup = new THREE.Group()
    this.scene.add(this.floorGroup)
    this.initFloor()
  }
  initFloor() {
    const w: number = this.size
    const h: number = 50
    const l: number = this.size
    const floorParams = {
      w: w,
      h: h,
      l: l,
      x: w / 2,
      y: h / 2,
      z: l / 2
    }
    this.floorCube = createCube(floorParams)
    this.floorGroup.add(this.floorCube)
    this.floorGroup.updateMatrix()
  }
  createlead() {
    const size = new THREE.Vector3()
    const mesh = this.floorGroup
    // 获取尺寸
    getSize(mesh, size)
    const position = new THREE.Vector3()
    // 获取底板的位置 默认应该都是0
    getPosition(mesh, position)
    const gy = position.y // 底板的Y值
    const y = size.y + gy + this.leadY / 2 // 主角的Y值
    // 设定第奇数个主角从z轴的负方向来,第偶数个主角从X轴方向来 
    // 需要一个主角计数器,同样可以用来计算分数
    // 起始点距离底板30
    // 主角初始位置
    const flag:boolean = this.leadCount % 2 === 0 // 是否是偶数主角
    // x 起始点
    let sx:number = (flag ? -this.startPoint : 0) + this.size / 2
    // z 起始点
    let sz:number = (flag ? 0 : -this.startPoint) + this.size / 2
    // 创建一个主角
    const leadParam = {
      w: this.size,
      h: this.leadY,
      l: this.size,
      x: sx,
      y: y,
      z: sz
    }
    const leadCube = createCube(leadParam)
    this.floorGroup.add(leadCube)
    // 创建角色后计数器自增1
    this.leadCount++
  }
}

export { CreateGame }

游戏共分为4个步骤来写文章

第一步 基础 初始化游戏

第二步 控制 控制主角移动以及停止

第三步 切割 将主角切割为底板内部方块,底板外部方块 外部方块进行自由落体

第四步 记分器、加载中等其他功能