仿黑神话悟空跑动-脚下波纹特效(键盘wasd控制走动)

server/2024/9/24 15:42:50/

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>

http://www.ppmy.cn/server/121408.html

相关文章

在vue中嵌入vitepress,基于markdown文件生成静态网页从而嵌入社团周报系统的一些想法和思路

什么是vitepress vitepress是一种将markdown文件渲染成静态网页的技术 其使用仅需几行命令即可 //在根目录安装vitepress npm add -D vitepress //初始化vitepress&#xff0c;添加相关配置文件&#xff0c;选择主题&#xff0c;描述&#xff0c;框架等 npx vitepress init //…

黑马头条day3-2 自媒体文章管理

前边还有一个 素材列表查询 没什么难度 就略过了 查询所有频道和查询自媒体文章也是和素材列表查询类似 就是普通的查询 所以略过了 文章发布 这个其实挺复杂的 一共三张表 一个文章表 一个素材表 一个文章和素材的关联表 区分修改与新增就是看是否存在id 如果是保存草稿…

CertiK因发现Apple Vision Pro眼动追踪技术漏洞,第6次获苹果认可

​2024年9月20日&#xff0c;头部Web3.0安全机构CertiK自豪地宣布&#xff0c;CertiK的工程师因发现Apple Vision Pro MR&#xff08;混合现实&#xff09;头显设备中的关键漏洞而获得Apple公司认可&#xff0c;这已经是Apple公司第六次公开发布对CertiK的致谢&#xff0c;Cert…

解决Matlab串口通信中接收到的消息不能正常显示

问题描述 如图&#xff0c;经过函数把接收到的十六进制字符串转换为EEE754标准浮点数后速度角度无法正常解析显示&#xff0c;其中速度角度的解码过程如下&#xff1a; &#xff08;以速度为例&#xff09; yv_temp1 dec2hex(data_receive(2)); yv_temp2 dec2hex(data_receive…

二叉树遍历、查找、深度等

在面试中&#xff0c;二叉树问题是一个常见的主题。下面我将展示如何在 Python 3.11 中实现二叉树的基本结构和几种常见的面试题解法&#xff0c;包括二叉树的遍历、查找、深度等。 1. 二叉树节点的定义 class TreeNode:def __init__(self, value0, leftNone, rightNone):sel…

文献笔记 - Reinforcement Learning for UAV Attitude Control

这篇博文是自己看文章顺手做的笔记 只是简单翻译和整理 仅做个人参考学习和分享 如果作者看到觉得内容不妥请联系我 我会及时处理 本人非文章作者&#xff0c;文献的引用格式如下&#xff0c;原文更有价值 Koch W, Mancuso R, West R, et al. Reinforcement learning for UA…

SpringBoot+Aop+注解方式 实现多数据源动态切换

整体思路&#xff1a; 引入基本依赖SpringBootAopMySqlMyBatislombok在配置文件中配置多个数据源创建数据源配置类用于读取配置编写用于标识切换数据源的注解创建数据源切换工具类DataSourceContextHolder编写切面类用于在注解生效处切换数据源编写配置类&#xff0c;加载数据…

【截稿更新 | 11月杭州 | EI稳定 】

【截稿更新 | 11月杭州 | EI稳定 】2024年人机交互与虚拟现实国际会议&#xff08;HCIVR 2024&#xff09; ✅会议时间&#xff1a;2024年11月15-17日 ✅会议地点&#xff1a;中国杭州 &#x1f525;二轮截稿日期&#xff1a;2024年10月15日 &#x1f308;投稿通道已开启&#…