仿黑神话悟空跑动-脚下波纹特效(键盘wasd控制走动)
vue使用three.js实现仿黑神话悟空跑动-脚下波纹特效
玩家角色的正面始终朝向鼠标方向,且在按下
W键时,玩家角色会朝着鼠标方向前进空格建跳跃
<template><div ref="container" class="container" @click="onClick" @mousedown="onMouseDown"></div>
</template><script>
import * as THREE from 'three';
import { PointerLockControls } from 'three/examples/jsm/controls/PointerLockControls';
import TWEEN from '@tweenjs/tween.js';
export default {name: 'WaterRipple',data() {return {scene: null,camera: null,renderer: null,player: null,clock: new THREE.Clock(),rippleMaterial: null,ripples: [],controls: null,moveForward: false,moveBackward: false,moveLeft: false,moveRight: false,velocity: new THREE.Vector3(),canJump: true,attackReady: true,playerHealth: 100,npcHealth: 100,npcList: [],npcMoveDirection: new THREE.Vector3(),direction: new THREE.Vector3(),};},mounted() {this.init();this.animate();document.addEventListener('keydown', this.onDocumentKeyDown, false);document.addEventListener('keyup', this.onDocumentKeyUp, false);},beforeDestroy() {document.removeEventListener('keydown', this.onDocumentKeyDown, false);document.removeEventListener('keyup', this.onDocumentKeyUp, false);window.removeEventListener('resize', this.onWindowResize, false);if (this.controls) {this.controls.dispose();}},methods: {init() {// 创建场景this.scene = new THREE.Scene();// 创建相机this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);this.camera.position.set(0, 15, -20);this.camera.lookAt(0, 0, 0);// 创建渲染器this.renderer = new THREE.WebGLRenderer();this.renderer.setSize(window.innerWidth, window.innerHeight);this.$refs.container.appendChild(this.renderer.domElement);// 创建PointerLockControlsthis.controls = new PointerLockControls(this.camera, this.renderer.domElement);this.scene.add(this.controls.getObject());// 创建平面const geometry = new THREE.PlaneGeometry(200, 200, 32, 32);const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true });const plane = new THREE.Mesh(geometry, material);plane.rotation.x = -Math.PI / 2;this.scene.add(plane);// 创建玩家this.createPlayer();// 创建NPCthis.createNPCs();// 窗口调整window.addEventListener('resize', this.onWindowResize, false);},createPlayer() {// 创建头部const headGeometry = new THREE.SphereGeometry(1, 32, 32);const headMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });const head = new THREE.Mesh(headGeometry, headMaterial);head.position.set(0, 2.5, 0);// 创建眼睛const eyeGeometry = new THREE.EllipseCurve(0, 0, 0.2, 0.4, 0, 2 * Math.PI, false, 0);const eyeShape = new THREE.Shape(eyeGeometry.getPoints(50));const eyeExtrudeSettings = { depth: 0.05, bevelEnabled: false };const eyeGeometry3D = new THREE.ExtrudeGeometry(eyeShape, eyeExtrudeSettings);const eyeMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff });const leftEye = new THREE.Mesh(eyeGeometry3D, eyeMaterial);const rightEye = new THREE.Mesh(eyeGeometry3D, eyeMaterial);leftEye.position.set(-0.4, 2.9, 0.9);leftEye.rotation.set(Math.PI / 2, 0, 0);rightEye.position.set(0.4, 2.9, 0.9);rightEye.rotation.set(Math.PI / 2, 0, 0);// 创建鼻子const noseGeometry = new THREE.CircleGeometry(0.2, 32);const noseMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });const nose = new THREE.Mesh(noseGeometry, noseMaterial);nose.position.set(0, 2.6, 0.95);nose.rotation.set(Math.PI / 2, 0, 0);// 创建嘴巴const mouthShape = new THREE.Shape();mouthShape.moveTo(-0.5, 0);mouthShape.quadraticCurveTo(0, -0.3, 0.5, 0);const mouthExtrudeSettings = { depth: 0.05, bevelEnabled: false };const mouthGeometry = new THREE.ExtrudeGeometry(mouthShape, mouthExtrudeSettings);const mouthMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });const mouth = new THREE.Mesh(mouthGeometry, mouthMaterial);mouth.position.set(0, 2.3, 0.95);mouth.rotation.set(Math.PI / 2, 0, 0);// 创建上半身const bodyGeometry = new THREE.BoxGeometry(0.5, 3, 0.5);const bodyMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff });const body = new THREE.Mesh(bodyGeometry, bodyMaterial);body.position.set(0, 1, 0);// 创建上肢const armMaterial = new THREE.LineBasicMaterial({ color: 0xffffff });const armGeometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(-1, 1.5, 0),new THREE.Vector3(-2, 0.5, 0),new THREE.Vector3(1, 1.5, 0),new THREE.Vector3(2, 0.5, 0)]);const arms = new THREE.LineSegments(armGeometry, armMaterial);// 创建下肢const legGeometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(-0.25, -0.5, 0),new THREE.Vector3(-0.25, -2, 0),new THREE.Vector3(0.25, -0.5, 0),new THREE.Vector3(0.25, -2, 0)]);const legs = new THREE.LineSegments(legGeometry, armMaterial);// 创建大刀const swordBladeGeometry = new THREE.BoxGeometry(0.2, 5, 0.05);const swordBladeMaterial = new THREE.MeshBasicMaterial({ color: 0xc0c0c0, metalness: 0.9, roughness: 0.2 });const swordBlade = new THREE.Mesh(swordBladeGeometry, swordBladeMaterial);swordBlade.position.set(1, 1, 0);const swordHandleGeometry = new THREE.CylinderGeometry(0.1, 0.1, 1, 32);const swordHandleMaterial = new THREE.MeshBasicMaterial({ color: 0x8b4513 });const swordHandle = new THREE.Mesh(swordHandleGeometry, swordHandleMaterial);swordHandle.position.set(1, 3, 0);const swordGuardGeometry = new THREE.BoxGeometry(0.5, 0.1, 0.1);const swordGuardMaterial = new THREE.MeshBasicMaterial({ color: 0xd4af37 });const swordGuard = new THREE.Mesh(swordGuardGeometry, swordGuardMaterial);swordGuard.position.set(1, 2.5, 0);const sword = new THREE.Group();sword.add(swordBlade);sword.add(swordHandle);sword.add(swordGuard);// 创建玩家组this.player = new THREE.Group();this.player.add(head);this.player.add(leftEye);this.player.add(rightEye);this.player.add(nose);this.player.add(mouth);this.player.add(body);this.player.add(arms);this.player.add(legs);this.player.add(sword);this.player.position.set(0, 2, 0); // 初始高度this.scene.add(this.player);},createNPCs() {for (let i = 0; i < 3; i++) {// 创建与玩家相同的模型const npc = this.player.clone();// 放大NPCnpc.scale.set(5, 5, 5);// 设置颜色为灰色npc.traverse((child) => {if (child instanceof THREE.Mesh) {child.material = child.material.clone();child.material.color.set(0x888888);}});// 随机位置const x = Math.random() * 100 - 50;const z = Math.random() * 100 - 50;npc.position.set(x, 2, z);// 为NPC添加名称npc.name = '张老师'; this.scene.add(npc);this.npcList.push(npc);}},onWindowResize() {this.camera.aspect = window.innerWidth / window.innerHeight;this.camera.updateProjectionMatrix();this.renderer.setSize(window.innerWidth, window.innerHeight);},createRipple(x, z) {const rippleGeometry = new THREE.RingGeometry(0.1, 0.5, 32);const rippleMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff, transparent: true, opacity: 1, wireframe: true });const ripple = new THREE.Mesh(rippleGeometry, rippleMaterial);ripple.position.set(x, 0.1, z);ripple.rotation.x = -Math.PI / 2;ripple.scale.set(1, 1, 1);this.scene.add(ripple);this.ripples.push({ mesh: ripple, startTime: this.clock.getElapsedTime() });},onDocumentKeyDown(event) {switch (event.code) {case 'ArrowUp':case 'KeyW':this.moveForward = true;break;case 'ArrowLeft':case 'KeyA':this.moveLeft = true;break;case 'ArrowDown':case 'KeyS':this.moveBackward = true;break;case 'ArrowRight':case 'KeyD':this.moveRight = true;break;case 'Space':if (this.canJump) {this.velocity.y = 10; // 跳跃速度this.canJump = false;}break;}},onDocumentKeyUp(event) {switch (event.code) {case 'ArrowUp':case 'KeyW':this.moveForward = false;break;case 'ArrowLeft':case 'KeyA':this.moveLeft = false;break;case 'ArrowDown':case 'KeyS':this.moveBackward = false;break;case 'ArrowRight':case 'KeyD':this.moveRight = false;break;}},onClick() {this.controls.lock();},onMouseDown(event) {if (event.button === 0) { // 左键点击this.attack();}},attack() {if (!this.attackReady) return;this.attackReady = false;// 挥刀动作const sword = this.player.children[8]; // 大刀是玩家的子对象const initialRotation = sword.rotation.x;new TWEEN.Tween(sword.rotation).to({ x: initialRotation + Math.PI / 2 }, 200).onComplete(() => {// 恢复原始位置new TWEEN.Tween(sword.rotation).to({ x: initialRotation }, 200).start();}).start();// 检查 NPC 是否在攻击范围内this.npcList.forEach(npc => {const distance = this.player.position.distanceTo(npc.position);if (distance < 10) { // 攻击范围this.npcHealth -= 10;console.log(`${npc.name} Health:`, this.npcHealth);if (this.npcHealth <= 0) {this.scene.remove(npc);}}});// 击退效果this.velocity.y += 5;setTimeout(() => {this.attackReady = true;}, 500); // 攻击冷却时间},npcAttack(player) {if (this.playerHealth <= 0) return;const distance = player.position.distanceTo(this.player.position);if (distance < 10) { // 攻击范围this.playerHealth -= 1;console.log('Player Health:', this.playerHealth);if (this.playerHealth <= 0) {console.log('Game Over');}}},animate() {requestAnimationFrame(this.animate);const delta = this.clock.getDelta();const elapsedTime = this.clock.getElapsedTime();const step = 10 * delta;// 获取控制器方向this.controls.getDirection(this.direction);this.direction.y = 0; // 保持水平朝向this.direction.normalize();// 水平方向移动const moveDirection = new THREE.Vector3();if (this.moveForward) moveDirection.add(this.direction);if (this.moveBackward) moveDirection.addScaledVector(this.direction, -1);if (this.moveLeft) moveDirection.addScaledVector(new THREE.Vector3(-this.direction.z, 0, this.direction.x), 1);if (this.moveRight) moveDirection.addScaledVector(new THREE.Vector3(this.direction.z, 0, -this.direction.x), 1);moveDirection.normalize().multiplyScalar(step);// 更新玩家位置this.player.position.add(moveDirection);// 更新玩家朝向if (this.moveForward || this.moveBackward || this.moveLeft || this.moveRight) {const targetQuaternion = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 0, -1),this.direction);// 创建一个额外的旋转 180 度的四元数const extraRotation = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);// 组合旋转targetQuaternion.multiply(extraRotation);this.player.quaternion.slerp(targetQuaternion, 0.1);}// 防止穿透地面if (this.player.position.y < 2) {this.player.position.y = 2;this.canJump = true;this.velocity.y = 0;} else {this.velocity.y -= 30 * delta; // 模拟重力}// 更新垂直位置this.player.position.y += this.velocity.y * delta;this.createRipple(this.player.position.x, this.player.position.z);// 使相机跟随玩家,并设置合适的角度const cameraOffset = new THREE.Vector3(0, 15, -20);const cameraLookAt = new THREE.Vector3(0, 0, 30);// 计算相机位置和角度相对玩家的位置const playerDirection = new THREE.Vector3();this.controls.getObject().getWorldDirection(playerDirection);playerDirection.y = 0; // 保持水平朝向// 相机位置基于玩家位置和方向偏移const cameraPosition = playerDirection.clone().multiplyScalar(cameraOffset.z).add(this.player.position);cameraPosition.y += cameraOffset.y;// 更新相机位置和朝向this.camera.position.copy(cameraPosition);this.camera.lookAt(this.player.position.clone().add(playerDirection.clone().multiplyScalar(cameraLookAt.z)));// 更新 NPC 行为this.npcList.forEach(npc => {// 随机移动if (Math.random() < 0.01) {this.npcMoveDirection.set(Math.random() - 0.5, 0, Math.random() - 0.5).normalize();}npc.position.addScaledVector(this.npcMoveDirection, step);// 攻击玩家this.npcAttack(npc);});// 更新水波纹this.ripples.forEach((ripple) => {const age = elapsedTime - ripple.startTime;ripple.mesh.scale.set(1 + age * 10, 1 + age * 10, 1 + age * 10);ripple.mesh.material.opacity = Math.max(0, 1 - age / 0.5); // 水波纹消散更快ripple.mesh.material.color.setHSL(0.6, 1, 0.5 * (1 - age / 0.5)); // 颜色随着扩散变淡});this.ripples = this.ripples.filter((ripple) => ripple.mesh.material.opacity > 0);this.renderer.render(this.scene, this.camera);TWEEN.update(); // 更新 TWEEN 动画},},
};
</script><style scoped>
.container {width: 100vw;height: 100vh;overflow: hidden;
}
</style>相关文章:
仿黑神话悟空跑动-脚下波纹特效(键盘wasd控制走动)
vue使用three.js实现仿黑神话悟空跑动-脚下波纹特效 玩家角色的正面始终朝向鼠标方向,且在按下 W 键时,玩家角色会朝着鼠标方向前进 空格建跳跃 <template><div ref"container" class"container" click"onClick"…...
`torch.utils.data`模块
在PyTorch中,torch.utils.data模块提供了许多有用的工具来处理和加载数据。以下是对您提到的DataLoader, Subset, BatchSampler, SubsetRandomSampler, 和 SequentialSampler的详细解释以及使用示例。 1. DataLoader DataLoader是PyTorch中用于加载数据的一个非常…...
深入理解 `strncat()` 函数:安全拼接字符串
目录: 前言一、 strncat() 函数的基本用法二、 示例代码三、 strncat() 与 strcat() 的区别四、 注意事项五、 实际应用场景总结 前言 在C语言中,字符串操作是编程中非常常见的需求。strncat() 函数是标准库中用于字符串拼接的一个重要函数,…...
OpenCV_自定义线性滤波(filter2D)应用详解
OpenCV filter2D将图像与内核进行卷积,将任意线性滤波器应用于图像。支持就地操作。当孔径部分位于图像之外时,该函数根据指定的边界模式插值异常像素值。 卷积核本质上是一个固定大小的系数数组,数组中的某个元素被作为锚点(一般…...
设计模式之装饰模式(Decorator)
前言 这个模式带给我们有关组合跟继承非常多的思考 定义 “单一职责” 模式。动态(组合)的给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码 & 减少…...
大数据-146 Apache Kudu 安装运行 Dockerfile 模拟集群 启动测试
点一下关注吧!!!非常感谢!!持续更新!!! 目前已经更新到了: Hadoop(已更完)HDFS(已更完)MapReduce(已更完&am…...
React入门准备
React是什么 React是一个用于构建用户界面的JavaScript框架,用于构建“可预期的”和“声明式的”Web用户界面,特别适合于构建那些数据会随时间改变的大型应用的用户界面。 它起源于Facebook的内部项目,因为对市场上所有JavaScript MVC框架都…...
robomimic基础教程(四)——开源数据集
robomimic开源了大量数据集及仿真环境,数据集标准格式为HDF5 目录 一、基础要求 二、使用步骤 1. 下载数据集 2. 后处理 3. 训练 4. 查看训练结果 三、HDF5数据集结构与可视化 1. 数据集结构 (1)根级别(data 组 group&a…...
胤娲科技:AI界的超级充电宝——忆阻器如何让LLM告别电量焦虑
当AI遇上“记忆橡皮擦”,电量不再是问题! 嘿,朋友们,你们是否曾经因为手机电量不足而焦虑得像个无头苍蝇?想象一下,如果这种“电量焦虑”也蔓延到了AI界, 特别是那些聪明绝顶但“耗电如喝水”的…...
前端大模型入门:使用Transformers.js手搓纯网页版RAG(二)- qwen1.5-0.5B - 纯前端不调接口
书接上文,本文完了RAG的后半部分,在浏览器运行qwen1.5-0.5B实现了增强搜索全流程。但受限于浏览器和模型性能,仅适合于研究、离线和高隐私场景,但对前端小伙伴来说大模型也不是那么遥不可及了,附带全部代码,…...
K-means聚类分析对比
K-means聚类分析,不同K值聚类对比,该内容是关于K-means聚类分析的,主要探讨了不同K值对聚类结果的影响。K-means聚类是一种常见的数据分析方法,用于将数据集划分为K个不同的类别。在这个过程中,选择合适的K值是非常关键…...
tar命令:压缩、解压的好工具
一、命令简介 用途: tar 命令用于创建归档文件(tarball),以及从归档文件中提取文件。 标签: 文件管理,归档。 特点: 归档文件可以保留原始文件和目录的层次结构,通常使用 .tar …...
Mac电脑上最简单安装Python的方式
背景 最近换了一台新的 MacBook Air 电脑,所有的开发软件都没有了,需要重新配环境,而我现在最常用的开发程序就是Python。这篇文章记录一下我新Mac电脑安装Python的全过程,也给大家一些思路上的提醒。 以下是我新电脑的配置&…...
Linux基础命令cd详解
cd(change directory)命令是 Linux 中用于更改当前工作目录的基础命令。它没有很多复杂的参数,但它的使用非常频繁。以下是 cd 命令的详细说明及示例。 基本语法 cd [选项] [路径] 常用选项 -L : 使用逻辑路径(默认选项&…...
【大模型对话 的界面搭建-Open WebUI】
Open WebUI 前身就是 Ollama WebUI,为 Ollama 提供一个可视化界面,可以完全离线运行,支持 Ollama 和兼容 OpenAI 的 API。 github网址 https://github.com/open-webui/open-webui安装 第一种 docker安装 如果ollama 安装在同一台服务器上&…...
如何在算家云搭建text-generation-webui(文本生成)
一、text-generation-webui 简介 text-generation-webui 是一个流行的用于文本生成的 Gradio Web UI。支持 transformers、GPTQ、AWQ、EXL2、llama.cpp (GGUF)、Llama 模型。 它的特点如下, 3 种界面模式:default (two columns), notebook, chat支持多…...
【Java SE】初遇Java,数据类型,运算符
🔥博客主页🔥:【 坊钰_CSDN博客 】 欢迎各位点赞👍评论✍收藏⭐ 1. Java 概述 1.1 Java 是什么 Java 是一种高级计算机语言,是一种可以编写跨平台应用软件,完全面向对象的程序设计语言。Java 语言简单易学…...
XSS(内含DVWA)
目录 一.XSS的攻击方式: 1. 反射型 XSS(Reflected XSS) 2. 存储型 XSS(Stored XSS) 3. DOM型 XSS(DOM-based XSS) 总结 二..XSS的危害 三.常见的XSS方式 1.script标签 四.常见基本过滤方…...
【SpringCloud】环境和工程搭建
环境和工程搭建 1. 案例介绍1.1 需求1.2 服务拆分服务拆分原则服务拆分⽰例 2. 项目搭建 1. 案例介绍 1.1 需求 实现⼀个电商平台(不真实实现, 仅为演⽰) ⼀个电商平台包含的内容⾮常多, 以京东为例, 仅从⾸⻚上就可以看到巨多的功能 我们该如何实现呢? 如果把这些功能全部…...
基于Java开发的(控制台)模拟的多用户多级目录的文件系统
多级文件系统 1 设计目的 为了加深对文件系统内部功能和实现过程的理解,设计一个模拟的多用户多级目录的文件系统,并实现具体的文件物理结构、目录结构以及较为完善的文件操作命令集。 2 设计内容 2.1系统操作 操作命令风格:本文件系统的…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...
