Three.js做了一个网页版的我的世界

news2024/11/26 3:52:30

在这里插入图片描述

前言

笔者在前一阵子接触到 Three.js 后, 发现了它能为前端 3D 可视化 / 动画 / 游戏方向带来的无限可能, 正好最近在与朋友重温我的世界, 便有了用 Three.js 来仿制 MineCraft 的想法, 正好也可以通过一个有趣的项目来学习一下前端 3D 领域

介绍

游戏介绍

相信大家对我的世界应该都不太陌生, 他是一款 3D 像素风的生存类游戏, 本项目是模仿我的世界的来进行实现的, 目前大致支持了以下的功能:

  • 方块的放置 / 破坏
  • 选择不同的方块类型
  • 移动和碰撞检测
  • 随机的地形和树木生成
  • 无限的世界
  • 保存 / 读取游戏
  • 音效和背景音乐
  • 可调节的渲染距离和视野范围
  • 基本的 UI

除此之外, 笔者目前也在尝试着为项目添加一些别的特性:

  • 生成水
  • 更多的保存栏位
  • 手机支持

玩法介绍

玩法介绍

玩法介绍也可以在 游戏体验地址 中的操作介绍下找到

技术栈介绍

因为项目的初衷便是探索一下 Three.js, 所以除了 Three.js 之外并没有别的第三方库依赖, 因此最后的打包大小其实是十分轻量级的, gzipped 后仅有 140kb 左右.

在此之上笔者还添加了 TypeScript 来进行类型检测, 并且使用了 Vite 进行的开发

源码介绍

源码结构

上图是整体项目的源码架构, 开发主要基于了 Class 写法, 主要有五大类以及一些子类, 分别为:

  • Core: 包含了 Three.js 中的一些核心内容, 并且进行一些初始化设定

  • Player: 包含了玩家的一些基础属性以及当前模式(行走, 奔跑, 作弊等)

  • Audio: 主要进行声音的导入, 并且暴露了一些用于播放音乐的 api

  • Terrain
    

    : 包含了与地形相关的各种内容:

    • Noise: 通过柏林噪音实现了地形, 树木, 方块类型的随机生成算法

    • GenerateWorker: 通过 web worker 的方式实现了地形动态生成的算法

    • Mesh
      

      : 场景中基础的网格体(方块)

      • Blocks: 自定义方块类
      • Materials: 各种方块的材质加载
    • Highlight: 实现了实时高亮准心位置方块的算法

  • Control
    

    : 包含了各种与操作相关的算法, 比如移动, 镜头转动, 碰撞检测等

    • CollideWorker: 在 web worker 中实现碰撞检测以提高运行效率
  • ui:包含了 ui 界面以及其功能:

    • Bag: 放置不同方块的背包
    • FPS: 实时展示当前 fps

核心技术点

笔者会在这一部分深入的分析一下项目的核心技术点以及一些遇到的难点, 如果大家只是来试玩看看 / 图一乐儿的话, 这一部分就可以跳过啦~ 不过要是有同学对底层的实现或者 Three.js 感兴趣的话, 也可以看看这一部分

对于文中大部分的涉及代码部分, 笔者尽可能的删去了不相关的内容, 让代码更加的易懂, 有兴趣的小伙伴也可以直接去 GitHub 上查看源码

随机的地形生成

地形生成采用了柏林噪音来进行实现的, Three.js 中自带了噪音的底层算法实现, 所以笔者只对其进行了一下简单的封装:

ts复制代码import {
    ImprovedNoise } from 'three/examples/jsm/math/ImprovedNoise'

export default class Noise {
   
  noise = new ImprovedNoise()
  seed = Math.random()
  stoneSeed = this.seed * 0.4
  coalSeed = this.seed * 0.5
  treeSeed = this.seed * 0.7
  leafSeed = this.seed * 0.8
  
  get = (x: number, y: number, z: number) => {
   
    return this.noise.noise(x, y, z)
  }
}

然后在地形生成的模块下, 首先为不同的方块类型创建了对应数量的 InstancedMesh, 然后将其存放到名为 blocks 的数组中:

ts复制代码const blocks: THREE.InstancedMesh[] = []
blocks[i].instanceMatrix = new THREE.InstancedBufferAttribute(
  new Float32Array(maxCount * blocksFactor[i] * 16),
  16
)

然后在具体的循环中首先依据前面的种子来判断地形的高度, 然后依据具体方块类型的种子来判断具体该渲染什么样的方块类型, 最后将每一个方块的位移量写入对应的 InstancedMesh 中:

ts复制代码
  for (
    let x = -chunkSize * distance + chunkSize * chunk.x;
    x < chunkSize * distance + chunkSize + chunkSize * chunk.x;
    x++
  ) {
   
    for (
      let z = -chunkSize * distance + chunkSize * chunk.y;
      z < chunkSize * distance + chunkSize + chunkSize * chunk.y;
      z++
    ) 
      const yOffset = Math.floor(
        noise.get(x / noise.gap, z / noise.gap, noise.seed) * noise.amp
      )
      matrix.setPosition(x, y + yOffset, z)
      // 如果为草方块
      blocks[BlockType.grass].setMatrixAt(
        blocksCount[BlockType.grass]++,
        matrix
      )
      // 如果为其他方块
      ...
    }
  }

除了地形外, 对于树和树叶的生成也是大同小异, 这里就不展开了.

无限动态地形生成

除去最基本的地形外, 笔者还添加了无限动态地形生成的算法从而实现的一个无限大小的世界, 这样就不至于说会走到地形的边界然后掉出世界了 XD

具体的实现的话是通过在 requestAnimationFrame (以下简称 raf) 的回调函数中判断玩家是否移动到了新的区块, 如果区块发生了变化, 则会触发一次渲染:

ts复制代码  update = () => {
   
    this.chunk.set(
      Math.floor(this.camera.position.x / this.chunkSize),
      Math.floor(this.camera.position.z / this.chunkSize)
    )

    // 当进入新的区块时, 触发一次渲染
    if (
      this.chunk.x !== this.previousChunk.x ||
      this.chunk.y !== this.previousChunk.y
    ) {
   
      this.generate()
    }

    this.previousChunk.copy(this.chunk)
  }

对于具体的 generate 部分, 因为对于地形的位置的计算是一个比较耗时的过程, 所以如果直接在主线程中进行运算的话则会带来卡顿的感觉, 所以具体计算的部分则是移动到了 web worker 中去实现的, 然后只在主线程中行进最后的渲染.

笔者也是在这个项目中发现了 web worker 不允许传输函数, 所以像各种 Three.js 中的类都无法直接进行传输, 最后不得不封装了一些自定义的数据结构来进行数据的沟通:

ts复制代码  // 将数据传入 web worker
  generate = () => {
   
    this.blocksCount = new Array(this.blocks.length).fill(0)
    this.generateWorker.postMessage({
   
      distance: this.distance,
      chunk: this.chunk,
      noiseSeed: this.noise.seed,
      treeSeed: this.noise.treeSeed,
      stoneSeed: this.noise.stoneSeed,
      coalSeed: this.noise.coalSeed,
      idMap: new Map<string, number>(<

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1826236.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Swift开发——循环执行方式

本文将介绍 Swift 语言的循环执行方式 01、循环执行方式 在Swift语言中,主要有两种循环执行控制方式: for-in结构和while结构。while结构又细分为当型while结构和直到型while结构,后者称为repeat-while结构。下面首先介绍for-in结构。 循环控制方式for-in结构可用于区间中的…

推荐这两款非常良心的录屏和文字转语音工具,很是让人心动,不要错过

VPot FREE 吾爱大神制作的文字转音频工具&#xff0c;免费使用。 支持英语、韩语、法语、日语等语言&#xff0c;还是支持男声、女声和儿童声音。 支持将以导入文本的格式转换成音频&#xff0c;并保存为MP3、WAV等常见的音频格式。 VPot FREE提供智能断句的功能&#xff0…

深度学习(八)——神经网络:卷积层

一、卷积层Convolution Layers函数简介 官网网址&#xff1a;torch.nn.functional — PyTorch 2.0 documentation 由于是图像处理&#xff0c;所以主要介绍Conv2d。 class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride1, padding0, dilation1, groups1, b…

Linux host映射 设置主机名并通过主机名找到指定系统

一、windows ping linux 1.windows进入到 C:\Windows\System32\drivers\etc\hosts 内&#xff0c;使用edit with notepad打开hosts文件&#xff0c;在下面添加需要寻找的ip以及其主机名。该ip以及主机名即linux的一致。需要查看linux主机名的在终端使用 hostname进行查看&#…

ARP协议相关

把ip地址解析成mac地址这里的mac地址就是路由器的mac地址 免费ARP 源ip和目的ip都是一样的&#xff0c;那怎么让其他人更新arp表呢&#xff1f;&#xff1f; 是因为目标mac是全f&#xff0c;是一个广播报文 如果冲突就是ip一样但是mac又不一样 代理ARP pc1和pc4是在同一个子网…

MySQL----慢查询日志

慢日志 MySQL可以设置慢查询日志&#xff0c;当SQL执行的时间超过我们设定的时间&#xff0c;那么这些SQL就会被记录在慢查询日志当中&#xff0c;然后我们通过查看日志&#xff0c;用explain分析这些SQL的执行计划&#xff0c;来判定为什么效率低下。 查看相关信息 show va…

计算机体系结构重点学习(一)

从外部I/O与上层应用交互的整体软硬件过程 上层应用发出I/O请求&#xff1a;上层应用程序&#xff0c;如一个文本编辑器、网络浏览器或者任何软件应用&#xff0c;需要读取或写入数据时&#xff0c;会通过调用操作系统提供的API&#xff08;如文件操作API、网络操作API等&…

【CDN】逆天 CDN !BootCDN 向 JS 文件中植入恶意代码

今天在调试代码&#xff0c;突然控制台出现了非常多报错。 这非常可疑&#xff0c;报错指向的域名也证实了这一点。 因为我的 HTML 中只有一个外部开源库&#xff08;qrcode.min.js&#xff09;&#xff0c;因此只有可能是它出现了问题。 我翻看了请求记录&#xff0c;发现这…

西南石油大学24计算机考研数据,能源与人工智能招收调剂,专硕录取均分355!西南石油大学计算机考研考情分析!

西南石油大学&#xff08;Southwest Petroleum University&#xff09;&#xff0c;简称“西南石大”&#xff0c;坐落于四川省成都市&#xff0c;是经中华人民共和国教育部备案的一所中央与地方共建、以四川省人民政府管理为主的高等院校&#xff0c;是世界一流学科建设高校、…

孝子黄香与颍川□董超

“香九龄&#xff0c;能温席&#xff0c;孝于亲&#xff0c;所当执。”家喻户晓、妇孺皆知的《三字经》让孝子黄香名扬千秋&#xff0c;成为“二十四孝”中闻名于世的“扇枕温衾”故事的主角。 黄香&#xff08;公元68—122年&#xff09;&#xff0c;字文强&#xff0c;东汉江…

为什么微信输入法是比搜狗输入法更好的选择?

微信输入法官网&#xff1a;https://z.weixin.qq.com/ 最近使用搜狗输入法时&#xff0c;频繁弹出广告&#xff0c;实在令人烦恼&#xff0c;于是我干脆卸载了它。然而&#xff0c;电脑上没有输入法是不行的。经过在网上对比了许多输入法软件后&#xff0c;我发现了微信输入法。…

解读ROS功能包模块的步骤

系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 TODO:写完再整理 文章目录 系列文章目录前言解读ROS功能包模块的步骤前言 认知有限,望大家多多包涵,有什么问题也希望能够与大家多交流,共同成长! 推荐开发经验及方法博客专栏: [https:/…

(游戏:三个数的加法)编写程序,随机产生三个一位整数,并提示用户输入这三个整数的和,判断用户输入的和是否正确。

(游戏:三个数的加法)编写程序&#xff0c;随机产生三个一位整数&#xff0c;并提示用户输入这三个整 数的和&#xff0c;判断用户输入的和是否正确。 package myjava; import java.math.*; import java.util.Scanner; public class cy {public static void main(String[]args)…

flask南京市旅游景点信息可视化-计算机毕业设计源码02941

摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对南京市旅游景点信息可视化等问题&#xff0…

鸿蒙轻内核M核源码分析系列二一 02 文件系统LittleFS

1、LFS文件系统结构体介绍 会分2部分来介绍结构体部分&#xff0c;先介绍LittleFS文件系统的结构体&#xff0c;然后介绍LiteOS-M内核中提供的和LittleFS相关的一些结构体。 1.1 LittleFS的枚举结构体 在openharmony/third_party/littlefs/lfs.h头文件中定义LittleFS的枚举、…

2024新消费特点---探索消费升级与品牌力量

最近看到不少消费视频&#xff0c;想起之前听过江南春的一场分享&#xff0c;结尾总结了张思维导图&#xff0c;分享给大家&#xff01; 随着时代的变迁&#xff0c;消费者的需求和市场环境也在不断演进。今天&#xff0c;我们将深入探讨消费升级的深层含义以及品牌如何在竞争…

【python】python指南(四):typing静态类型注解综述

一、引言 对于算法工程师来说&#xff0c;语言从来都不是关键&#xff0c;关键是快速学习以及解决问题的能力。大学的时候参加ACM/ICPC一直使用的是C语言&#xff0c;实习的时候做一个算法策略后台用的是php&#xff0c;毕业后做策略算法开发&#xff0c;因为要用spark&#x…

java安装并配置环境

安装前请确保本机没有java的残留&#xff0c;否则将会安装报错 1.安装java jdk&#xff1a;安装路径Java Downloads | Oracle 中国 百度网盘链接&#xff1a;https://pan.baidu.com/s/11-3f2QEquIG3JYw4syklmQ 提取码&#xff1a;518e 2.双击 按照流程直接点击下一步&#x…

L52--- 144. 二叉树的后序遍历(深搜)---Java版

1.题目描述 2.思路 (1)二叉树后序遍历&#xff1a;左右根 (2)根节点的压入: 根节点首先被压入stack中&#xff0c;然后被弹出并压入output中。 遍历过程: stack用于存储需要遍历的节点。 output用于反转遍历顺序。 入栈顺序: 左子节点先入栈&#xff0c;右子节点后入栈。这…

基于负相关误差函数的4集成BP神经网络matlab建模与仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 ...............................................................…