vue3+threejs新手从零开发卡牌游戏(十):创建己方战域

首先在game目录下创建site文件夹,用来存放战域相关代码:

这里思考下如何创建战域,我的想法是添加一个平面,将己方战域和对方战域都添加进这个平面中,所以首先我们先添加一个战域plane,site/index.vue代码如下:

<!-- 场地 -->
<template><P1 ref="p1Ref"/><!-- <P2 ref="p2Ref"/> -->
</template><script setup lang="ts">
import { reactive, ref, onMounted, onBeforeUnmount, watch, defineComponent, getCurrentInstance, nextTick } from 'vue'
import { transPos } from "@/utils/common.ts"
import P1 from "./p1.vue"
// import P2 from "./p2.vue"// 引入threejs变量
const {proxy} = getCurrentInstance()
const THREE = proxy['THREE']
const scene = proxy['scene']
const camera = proxy['camera']
const renderer = proxy['renderer']
const TWEEN = proxy['TWEEN']const raycaster = new THREE.Raycaster();const p1Ref = ref()
// const p2Ref = ref()onMounted(() => {// init()
})const init = async () => {await addSitePlane()// setSite()p1Ref.value.init()// p2Ref.value.init()
}// 添加战域plane
const addSitePlane = () => {return new Promise((resolve, reject) => {// 设置战域大小和位置let plane = scene.getObjectByName("地面")let point = transPos(0, 0) // 战域起始位置的屏幕坐标// raycaster.setFromCamera( point, camera );const intersects = raycaster.intersectObject( plane );if (intersects.length > 0) {let point = intersects[0].point// 进行裁剪// let x = Math.abs(point.x) * 2 - 2.4// let y = Math.abs(point.z) * 2 - 4let x = Math.abs(point.x) * 2let y = Math.abs(point.z) * 2const geometry = new THREE.PlaneGeometry( x, y );geometry.center()const material = new THREE.MeshBasicMaterial({color: new THREE.Color("gray"),side: THREE.FrontSide, alphaHash: true,// alphaTest: 0,opacity: 0});const site = new THREE.Mesh( geometry, material );site.name = "战域"site.rotateX(-90 * (Math.PI / 180)) // 弧度site.userData["width"] = xsite.userData["height"] = yscene.add(site)resolve(true)}})
}defineExpose({init,
})
</script><style lang="scss" scoped>
</style>

这里计算出了平面的长和宽,将长宽数据存放进site的userData中,这个长和宽可以用来帮助我们后续绘制格子的时候做位置计算。然后我们在site/p1.vue中开始绘制己方战域:

1.绘制一个线框格子,格子Box的大小和卡牌设置成一样,如果在屏幕中看着比较大也可以通过scale进行适当的缩放:

// 创建线框网格
const createWireframe = () => {const geometry = new THREE.BoxGeometry( 1, 0, 1.4 );const edges = new THREE.EdgesGeometry( geometry );const line = new THREE.LineSegments( edges, new THREE.LineBasicMaterial( { color: 0xffffff } ) );line.rotateX(-90 * (Math.PI / 180)) // 弧度line.scale.set(0.8, 0.8, 0.8) // 缩放return line
}

2.我们可以按照自己的想法绘制游戏中有几个格子,这里我设计的是前5个格子(包括一个场地格子和一个墓地格子),后三个格子,site/p1.vue相关代码如下:

// 初始化己方战域
const init = () => {let sitePlane = scene.getObjectByName("战域")let planeWidth = sitePlane.userData.widthlet spaceX = (planeWidth - 5) / 4 // 设计一行有5个格子// console.log(123, spaceX)for (let i=0; i<5; i++) {let mesh = createWireframe()if (i == 0) {mesh.name = "场地"} else if (i == 1) {mesh.name = "己方场地1"} else if (i == 2) {mesh.name = "己方场地2"} else if (i == 3) {mesh.name = "己方场地3"} else if (i == 4) {mesh.name = "墓地"}if (i == 0 || i == 4) {mesh.position.set(-planeWidth/2 + 0.5, -0.65, 0)} else {mesh.position.set(-planeWidth/2 + 0.5, -0.7, 0)}mesh.translateX(spaceX * i + i)sitePlane.add(mesh)}for (let i=0; i<5; i++) {if (i > 0 && i < 4) {let mesh = createWireframe()if (i == 1) {mesh.name = "己方场地4"} else if (i == 2) {mesh.name = "己方场地5"} else if (i == 3) {mesh.name = "己方场地6"}mesh.position.set(-planeWidth/2 + 0.5, -2.2, 0)mesh.translateX(spaceX * i + i)sitePlane.add(mesh)}}
}

这里我为每个格子添加了name,方便后续卡牌上场,进墓等操作逻辑。

页面效果如下:

附录:

game/index.vue完整代码:
 

<template><div ref="sceneRef" class="scene"></div><!-- 手牌 --><Hand ref="handRef"/><!-- 卡组 --><Deck ref="deckRef"/><!-- 战域 --><Site ref="siteRef"/><!-- 抽卡逻辑 --><DrawCard ref="drawCardRef" :handRef="handRef"/>
</template><script setup lang="ts">
import { reactive, ref, onMounted, onBeforeUnmount, watch, defineComponent, getCurrentInstance, nextTick } from 'vue'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // 轨道控制器
import { FontLoader } from 'three/addons/loaders/FontLoader.js';
import { useCommonStore } from "@/stores/common.ts"
import { transPos, editDeckCard, renderText } from "@/utils/common.ts"
import { Card } from "./Card.ts"
import { CARD_DICT } from "@/utils/dict/card.ts"
import Hand from "./hand/index.vue"
import Deck from "./deck/index.vue"
import Site from "./site/index.vue"
import DrawCard from "@/components/DrawCard.vue"// 引入threejs变量
const {proxy} = getCurrentInstance()
const THREE = proxy['THREE']
const scene = proxy['scene']
const camera = proxy['camera']
const renderer = proxy['renderer']
const TWEEN = proxy['TWEEN']const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();const commonStore = useCommonStore()// 场景ref
const sceneRef = ref()
const siteRef = ref()
const handRef = ref()
const deckRef = ref()
const drawCardRef = ref()// 坐标轴辅助
const axesHelper = new THREE.AxesHelper(5);
// 创建轨道控制器
const controls = new OrbitControls( camera, renderer.domElement );
// 字体加载器
const fontLoader = new FontLoader();onMounted(async () => {await initResource()initScene()initGame()// 鼠标按下window.addEventListener('touchstart', onMousedown)// window.addEventListener('click', onMousedown)// 监听浏览器窗口变化进行场景自适应window.addEventListener('resize', onWindowResize, false);
})// 资源加载
const initResource = () => {// 字体加载return new Promise((resolve, reject) => {fontLoader.load('fonts/helvetiker_regular.typeface.json', (font: any) => {commonStore.loadFont(font)resolve(true)});})
}// 初始化场景
const initScene = () => {renderer.setSize( window.innerWidth, window.innerHeight );sceneRef.value.appendChild( renderer.domElement );scene.add(axesHelper)// camera.position.set( 5, 5, 5 );camera.position.set( 0, 6.5, 0 );camera.lookAt(0, 0, 0)addPlane()animate();
}// scene中添加plane几何体
const addPlane = () => {const geometry = new THREE.PlaneGeometry( 20, 20);const material = new THREE.MeshBasicMaterial( {color: new THREE.Color("gray"), side: THREE.FrontSide, alphaHash: true,// alphaTest: 0,opacity: 0} );const plane = new THREE.Mesh( geometry, material );plane.rotateX(-90 * (Math.PI / 180)) // 弧度plane.name = "地面"scene.add( plane );
}// 用requestAnimationFrame进行渲染循环
const animate = () => {requestAnimationFrame( animate );TWEEN.update()renderer.render( scene, camera );
}// 场景跟随浏览器窗口大小自适应
const onWindowResize = () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);
}// 初始化游戏
const initGame = async () => {// 初始化场地initSite()// 初始化卡组await initDeck()// 初始化手牌initHand()
}// 初始化场地
const initSite = () => {siteRef.value.init()
}// 初始化卡组
const initDeck = () => {return new Promise((resolve, reject) => {let p1Deck = ["YZ-01","YZ-02","YZ-03","YZ-04","YZ-01","YZ-02",// "YZ-03",// "YZ-04",// "YZ-01",// "YZ-02",// "YZ-03",// "YZ-04",]// 洗牌p1Deck.sort(() => {return Math.random() - 0.5})let newDeck: any = []p1Deck.forEach((v: any, i: any) => {let obj = CARD_DICT.find((b: any) => b.card_id === v)if (obj) {newDeck.push({card_id: v,name: `${obj.name}_${i}`})}})// console.log("p1Deck", newDeck)commonStore.updateP1Deck(newDeck)nextTick(() => {handRef.value.init()deckRef.value.init()resolve(true)})})
}// 初始化手牌
const initHand = () => {let cardNumber = 4let _number = 0let p1Deck = JSON.parse(JSON.stringify(commonStore.$state.p1Deck))let deckGroup = scene.getObjectByName("p1_deckGroup")let position = new THREE.Vector3(0, 0.005 * p1Deck.length, 0)let _interval = setInterval(async() => {// console.log(123, p1Deck)if (_number < cardNumber) {let obj = p1Deck[p1Deck.length - 1]p1Deck.splice(p1Deck.length-1, 1)commonStore.updateP1Deck(p1Deck)// 修改卡组await editDeckCard(deckGroup, obj, "remove")await renderText(deckGroup, `${commonStore.$state.p1Deck.length}`, commonStore.$state._font, position)// 手牌区添加手牌handRef.value.addHandCard(obj, deckGroup)} else {clearInterval(_interval)}_number++}, 200)}// 鼠标按下事件
const onMousedown = (ev: any) => {// console.log(222, ev.target)// 判断是否点击到canvas上if(!(ev.target instanceof HTMLCanvasElement)){return;}// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)let clientX = ev.clientX || ev.changedTouches[0].pageXlet clientY = ev.clientY || ev.changedTouches[0].pageY// pointer.x = ( clientX / window.innerWidth ) * 2 - 1;// pointer.y = - ( clientY / window.innerHeight ) * 2 + 1;let point = transPos(clientX, clientY) // 卡组起始位置的屏幕坐标// 通过摄像机和鼠标位置更新射线raycaster.setFromCamera( point, camera );// 点击卡组事件onP1DeckEvent()
}// 点击卡组事件
const onP1DeckEvent = () => {if (commonStore.$state.p1Deck.length <= 0) {return}let p1_deckGroup = scene.getObjectByName("p1_deckGroup")let arr = raycaster.intersectObject(p1_deckGroup, true)if (arr.length <= 0) {return}let pos1 = p1_deckGroup.userData.positionlet pos2 = new THREE.Vector3(0, 2, 0)// console.log(444, pos1, pos2)if (p1_deckGroup.position.x !== pos2.x) {drawCardRef.value.drawCardAnimate1(p1_deckGroup, pos1, pos2)}
}
</script><style lang="scss" scoped>
.scene {position: fixed;top: 0;left: 0;width: 100%;height: 100vh;
}
</style>

site/p1.vue完整代码:

<template><div></div>
</template><script setup lang="ts">
import { reactive, ref, onMounted, onBeforeUnmount, watch, defineComponent, getCurrentInstance, nextTick } from 'vue'
import { useCommonStore } from "@/stores/common.ts"
import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
import { Card } from "@/views/game/Card.ts"
import { CARD_DICT } from "@/utils/dict/card.ts"
import { transPos, renderText } from "@/utils/common.ts"// 引入threejs变量
const {proxy} = getCurrentInstance()
const THREE = proxy['THREE']
const scene = proxy['scene']
const camera = proxy['camera']
const renderer = proxy['renderer']
const TWEEN = proxy['TWEEN']const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();const commonStore = useCommonStore()// 初始化己方战域
const init = () => {let sitePlane = scene.getObjectByName("战域")let planeWidth = sitePlane.userData.widthlet spaceX = (planeWidth - 5) / 4 // 设计一行有5个格子// console.log(123, spaceX)for (let i=0; i<5; i++) {let mesh = createWireframe()if (i == 0) {mesh.name = "场地"} else if (i == 1) {mesh.name = "己方场地1"} else if (i == 2) {mesh.name = "己方场地2"} else if (i == 3) {mesh.name = "己方场地3"} else if (i == 4) {mesh.name = "墓地"}if (i == 0 || i == 4) {mesh.position.set(-planeWidth/2 + 0.5, -0.65, 0)} else {mesh.position.set(-planeWidth/2 + 0.5, -0.7, 0)}mesh.translateX(spaceX * i + i)sitePlane.add(mesh)}for (let i=0; i<5; i++) {if (i > 0 && i < 4) {let mesh = createWireframe()if (i == 1) {mesh.name = "己方场地4"} else if (i == 2) {mesh.name = "己方场地5"} else if (i == 3) {mesh.name = "己方场地6"}mesh.position.set(-planeWidth/2 + 0.5, -2.2, 0)mesh.translateX(spaceX * i + i)sitePlane.add(mesh)}}
}// 创建线框网格
const createWireframe = () => {const geometry = new THREE.BoxGeometry( 1, 0, 1.4 );const edges = new THREE.EdgesGeometry( geometry );const line = new THREE.LineSegments( edges, new THREE.LineBasicMaterial( { color: 0xffffff } ) );line.rotateX(-90 * (Math.PI / 180)) // 弧度line.scale.set(0.8, 0.8, 0.8) // 缩放return line
}defineExpose({init,
})
</script><style lang="scss" scoped>
</style>

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

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

相关文章

[蓝桥杯 2020 省 AB1] 网络分析

一开始写的暴力合并 卡n^2过的不是正解 看正解是类似 虚拟点树形DP的思路 很巧妙 记录一下 #include<bits/stdc.h> using namespace std; using ll long long; using pii pair<int,int>; #define int long long const int N 3e510; const int inf 0x3f3f3f3f; …

【RPG Maker MV 仿新仙剑 战斗场景UI (八)】

RPG Maker MV 仿新仙剑 战斗场景UI 八 状态及装备场景代码效果 状态及装备场景 本计划在战斗场景中直接制作的&#xff0c;但考虑到在战斗场景中加入太多的窗口这不太合适&#xff0c;操作也繁琐&#xff0c;因此直接使用其他场景。 代码 Pal_Window_EquipStatus.prototype.…

Word邮件合并

Word邮件合并功能可以解决在Word中批量填写内容的需求&#xff0c;当需要大量格式相同&#xff0c;只修改少数相关内容时&#xff0c;例如利用Word制作工资条&#xff0c;通知函&#xff0c;奖状等等&#xff0c;同时操作也非常简单灵活。下面通过例子来说明邮件合并的使用方法…

突破距离限制 远程级联测径仪 让您使用更安心!

关键词&#xff1a;在线测径仪,测径仪,远程级联 在现代工业领域&#xff0c;测量的准确性和高效性至关重要。在线测径仪不仅具备了这两项特质&#xff0c;更能进行远程级联&#xff0c;能更快速的为您解决软件系统在使用中遇到的问题。 在线测径仪能做到以下几点 精准测量&am…

过了30岁,要具备翻篇的能力

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 杨绛先生曾经说过&#xff1a; “人生没有彩排&#xff0c;每一天都是现场直播。只有翻篇&#xff0c;才能重新开始。” 一个人如果整天回顾以前&#xff0c;想着自己做的不好的事情&#xff0c;想着曾经遇到的挫折&am…

vue.js制作学习计划表案例

通俗易懂&#xff0c;完成“学习计划表”用于对学习计划进行管理&#xff0c;包括对学习计划进行添加、删除、修改等操作。 一. 初始页面效果展示 二.添加学习计划页面效果展示 三.修改学习计划完成状态的页面效果展示 四.删除学习计划 当学习计划处于“已完成”状态时&…

Vue使用font-face自定义字体详解

目录 1 介绍2 使用2.1 语法2.2 属性说明2.3 Vue使用案例2.3.1 全局定义字体2.3.2 在页面使用 3 注意事项 1 介绍 font-face 是 CSS 中的一个规则&#xff0c;它允许你加载服务器上的字体文件&#xff08;远程或者本地&#xff09;&#xff0c;并在网页中使用这些字体。这样&am…

2024年3月26日 十二生肖 今日运势

小运播报&#xff1a;2024年3月26日&#xff0c;星期二&#xff0c;农历二月十七 &#xff08;甲辰年丁卯月己丑日&#xff09;&#xff0c;法定工作日。 红榜生肖&#xff1a;鸡、鼠、猴 需要注意&#xff1a;马、狗、羊 喜神方位&#xff1a;东北方 财神方位&#xff1a;…

22.保护性暂停扩展(一对一)

如果需要多个类之间使用GuardedObject对象&#xff0c;作为参数传递不是很方便&#xff0c;因此设计一个解耦的中间类&#xff0c;这样不仅能够解耦结果的等待者和结果生产者&#xff0c;还能够支持多个任务的管理。 Futures就好比居民楼一层的信箱&#xff0c;每个信箱有房间的…

HarmonyOS实战开发-如何使用首选项能力实现一个简单示例。

介绍 本篇Codelab是基于HarmonyOS的首选项能力实现的一个简单示例。实现如下功能&#xff1a; 创建首选项数据文件。将用户输入的水果名称和数量&#xff0c;写入到首选项数据库。读取首选项数据库中的数据。删除首选项数据文件。 最终效果图如下&#xff1a; 相关概念 首选…

Facebook如何使用增强技术提升广告效果?

AR in AD - case study 脸书2021年宣布了引入AR的新方法&#xff0c;以推动其应用套件中的产品发现和购买。但他们首先考虑是技术。据脸书称&#xff0c;技术一直是增强现实在其应用程序中更广泛使用的主要障碍。这就是为什么它现在正在做出改变&#xff0c;使企业主和广告商更…

TextMeshPro图文混排的两种实现方式,不打图集

TMP图文混排 方案一&#xff1a;TMP自带图文混排使用方法打包图集使用 方案二&#xff1a;不打图集&#xff0c;可以使用任何图片 接到一个需求&#xff0c;TextMeshPro 图文混排。 方案一&#xff1a;TMP自带图文混排 优点布局适应优秀&#xff0c;字体左中右布局位置都很不错…

Web常见标签属性

应用软件&#xff1a;c/s&#xff08;客户端与服务端&#xff09; b/s&#xff08;服务器与浏览器架构&#xff09;web前端&#xff1a;html5、css3、JavaScriptHtml5&#xff1a;超文本标记语言 超链接标签 语法规范<标签名> marquee 标签之间可以嵌套属性&#xff1a;…

【项目】均衡代码评测

TOC 目录 项目介绍 开发环境 主要技术 项目实现 公共模块 日志 工具类 编译运行模块 介绍 编译 运行 编译和运行结合起来 业务逻辑模块 介绍 MVC模式框架 模型&#xff08;Model&#xff09; 视图&#xff08;View) 控制器&#xff08;Controller&#xff09…

使用html做一个2048小游戏

下载地址: https://pan.xunlei.com/s/VNtiF13HxmmE4gglflvS1BUhA1?pwdvjrt# 提取码&#xff1a;vjrt”

Linux文件系统 底层原理

linux文件、目录、Inode inode负责文件的元数据和数据存储&#xff0c;文件存储块负责实际数据的存储&#xff0c;而目录文件维护文件名和inode之间的联系。 1. 用户空间到内核空间 首先&#xff0c;当用户程序请求打开一个文件时&#xff08;例如使用open系统调用&#xff09…

Excel 打开后提示:MicrosoftExcel无法计算某个公式。在打开的工作簿中有一个循环引用...

目录预览 一、问题描述二、原因分析三、解决方案四、参考链接 一、问题描述 MicrosoftExcel无法计算某个公式。在打开的工作簿中有一个循环引用&#xff0c;但无法列出导致循环的引I用。请尝试编辑上次输入的公式&#xff0c;或利用“撤消”命令删除该公式&#xff0c;如下图&…

微服务(基础篇-003-Nacos)

目录 Nacos注册中心&#xff08;1&#xff09; 认识和安装Nacos&#xff08;1.1&#xff09; Nacos快速入门&#xff08;1.2&#xff09; 服务注册到Nacos(1.2.1) Nacos服务分级存储模型&#xff08;1.3&#xff09; 配置集群&#xff08;1.3.1&#xff09; 根据集群修改…

如何用Airtest脚本连接无线Android设备?

之前我们已经详细介绍过如何用AirtestIDE无线连接Android设备&#xff0c;它的关键点在于&#xff0c;需要先 adb connect 一次&#xff0c;才能点击 connect 按钮无线连接上该设备&#xff1a; 但是有很多同学&#xff0c;在使用纯Airtest脚本的形式连接无线设备时&#xff0c…

K8s-网络原理-下篇

引言 本文是《深入剖析 K8s》的学习笔记&#xff0c;相关图片和案例可从https://github.com/WeiXiao-Hyy/k8s_example中获取&#xff0c;欢迎Star! K8s 的网络隔离: NetWorkPolicy K8s 如何考虑容器之间网络的“隔离” -> NetWorkPolicy 以下是一个 NetWorkPolicy 的定义…