vue3+threejs新手从零开发卡牌游戏(七):创建卡组

在开始前先优化下之前的代码:

在之前hand/p1.vue中为了定位

utils文件夹下新建common.ts,将一些公用方法提取出来放在这里:


在game/Cards.ts中,我们调整下卡牌的厚度,由原来的0.02改为0.005,原因是之前的太厚了,其他涉及的地方需要同步修改:

const geometry = new THREE.BoxGeometry( 1, 0.005, 1.4 ); 

在game/hand/p1.vue中,同步调整手牌区卡牌的叠放高度和手牌区起始位置:

const setHandPos = () => {
  nextTick(() => {
    let plane = scene.getObjectByName("地面")
    let point1 = transPos(10, window.innerHeight - 10) // 手牌区起始位置的屏幕坐标
    let point2 = transPos(window.innerWidth * 0.65, window.innerHeight - 10) // 手牌区结束位置的屏幕坐标
    let x1 = 0 // 手牌区起始位置的世界x坐标
    let x2 = 0 // 手牌区结束位置的世界x坐标
    // 
    raycaster.setFromCamera( point1, camera );
    const intersects1 = raycaster.intersectObject( plane );
    if (intersects1.length > 0) {
      let point = intersects1[0].point
      // 由于卡牌几何体大小设置的是(1, 0.005, 1.4),所以我们对应进行偏移
      // handGroup.position.set(point.x, point.y, point.z)
      handGroup.position.set(point.x + 0.5, point.y, point.z - 0.7)
      x1 = handGroup.position.x
    }
    // 
    raycaster.setFromCamera( point2, camera );
    const intersects = raycaster.intersectObject( plane );
    if (intersects.length > 0) {
      let point = intersects[0].point
      x2 = point.x + 0.5
    }

    // 用绝对值相加得到手牌区长度
    let _width = Math.abs(x1) + Math.abs(x2)
    
    // 计算叠放间距
    let space = ((_width - 1) / (deckList.length - 1)) <= 1 ? (_width - 1) / (deckList.length - 1) : 1
    deckList.forEach((v: any, i: any) => {
      let obj = CARD_DICT.find((b: any) => b.card_id === v)
      if (obj) {
        let card = new Card(obj)
        let mesh = card.init()
        mesh.position.set(i * space, 0.005 * i, 0)
        handGroup.add( mesh );
      }
    })
  })
}

此时页面效果应该如下所示:

现在我们开始创建卡组:

1.和之前新建hand手牌区一样,我们在目录下新建deck文件夹,存放卡组相关代码:

然后我们在game/index.vue中引入deck组件:

<template>
  <div ref="sceneRef" class="scene"></div>
  <!-- 手牌 -->
  <Hand ref="handRef"/>
  <!-- 卡组 -->
  <Deck ref="deckRef"/>
</template>

<script setup lang="ts">
import Hand from "./hand/index.vue"
import Deck from "./deck/index.vue"

...
</script>

2.我们使用一个测试卡组来生成卡组里的卡牌,由于卡组是多张卡牌摞在一起,所以我们需要调整每张卡牌的位置高度,同时由于卡牌是盖放,所以我们通过rotateX进行180度翻转:

// 卡组group
const deckGroup = new THREE.Group()
deckGroup.name = "p1_deckGroup"
scene.add(deckGroup)

// 测试卡组
const deckList = [
  "YZ-01",
  "YZ-02",
  "YZ-03",
  "YZ-04",
  "YZ-01",
  "YZ-02",
  "YZ-03",
  "YZ-04",
]

const init = () => {
  ...
  deckList.forEach((v: any, i: any) => {
    let obj = CARD_DICT.find((b: any) => b.card_id === v)
    if (obj) {
      let card = new Card(obj)
      let mesh = card.init()
      mesh.position.set(0, 0.005 * i, 0)
      mesh.rotateX(180 * (Math.PI / 180)) // 弧度
      deckGroup.add( mesh );
    }
  })
}

刷新页面效果如下:

稍微旋转下可以看到卡牌是叠放在一起的:

下面我想在卡组顶上加一个数字用来标示卡组中卡牌的数量,从threejs文档中可以看到有几种生成文字的方式,这里我选择的是用threejs自带的文字几何体绘制:

首先从node_modules中找到three内置的font,然后复制到public目录下:

修改deck/p1.vue代码,这里除了生成文字,还添加了文字的阴影:

const _font = ref()

loader.load('fonts/helvetiker_regular.typeface.json', (font: any) => {
  _font.value = font
  renderText()
});

// 渲染文字
const renderText = () => {
  const geometry = new TextGeometry( `${deckList.length}`, {
    font: _font.value,
    size: 0.4,
    height: 0,
    curveSegments: 4,
    bevelEnabled: true,
    bevelThickness: 0,
    bevelSize: 0,
    bevelSegments: 0
  });
  geometry.center()
  const material = new THREE.MeshBasicMaterial( { color: new THREE.Color("white") } )
  const mesh = new THREE.Mesh( geometry, material ) ;
  mesh.position.set(0, 0.005 * deckList.length + 0.01, 0) // 弧度
  mesh.rotateX(-90 * (Math.PI / 180)) // 弧度

  // 阴影
  let shadowGeometry = geometry.clone()
  shadowGeometry.translate(0.02, 0.02, 0);
  let shadowMaterial = new THREE.MeshBasicMaterial( { color: new THREE.Color("black") } );
  let shadowMesh = new THREE.Mesh(shadowGeometry, shadowMaterial);
  shadowMesh.position.set(0, 0.005 * deckList.length, 0) // 弧度
  shadowMesh.rotateX(-90 * (Math.PI / 180)) // 弧度

  deckGroup.add(mesh)
  deckGroup.add(shadowMesh)
}

页面效果如下:

接下来调整卡组位置,将它移动到右下角:

const init = () => {
  setDeckPos()
  ...
}

// 设置卡组位置
const setDeckPos = () => {
  nextTick(() => {
    let plane = scene.getObjectByName("地面")
    let point = transPos(window.innerWidth - 15, window.innerHeight - 15) // 卡组起始位置的屏幕坐标
    // 
    raycaster.setFromCamera( point, camera );
    const intersects1 = raycaster.intersectObject( plane );
    if (intersects1.length > 0) {
      let point = intersects1[0].point
      // deckGroup.position.set(point.x, point.y, point.z)
      deckGroup.position.set(point.x - 0.5, point.y, point.z - 0.7)
    }
  })
}

页面效果如下:

deck/p1.vue完整代码如下:

<template>
  <div></div>
</template>

<script setup lang="ts">
import { reactive, ref, onMounted, onBeforeUnmount, watch, defineComponent, getCurrentInstance, nextTick } from 'vue'
import { FontLoader } from 'three/addons/loaders/FontLoader.js';
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 } 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 loader = new FontLoader();

const _font = ref()

// 卡组group
const deckGroup = new THREE.Group()
deckGroup.name = "p1_deckGroup"
scene.add(deckGroup)

// 测试卡组
const deckList = [
  "YZ-01",
  "YZ-02",
  "YZ-03",
  "YZ-04",
  "YZ-01",
  "YZ-02",
  "YZ-03",
  "YZ-04",
]

const init = () => {
  setDeckPos()
  deckList.forEach((v: any, i: any) => {
    let obj = CARD_DICT.find((b: any) => b.card_id === v)
    if (obj) {
      let card = new Card(obj)
      let mesh = card.init()
      mesh.position.set(0, 0.005 * i, 0)
      mesh.rotateX(180 * (Math.PI / 180)) // 弧度
      deckGroup.add( mesh );
    }
  })

  loader.load('fonts/helvetiker_regular.typeface.json', (font: any) => {
    _font.value = font
    renderText()
  });

}

// 渲染文字
const renderText = () => {
  const geometry = new TextGeometry( `${deckList.length}`, {
    font: _font.value,
    size: 0.4,
    height: 0,
    curveSegments: 4,
    bevelEnabled: true,
    bevelThickness: 0,
    bevelSize: 0,
    bevelSegments: 0
  });
  geometry.center()
  const material = new THREE.MeshBasicMaterial( { color: new THREE.Color("white") } )
  const mesh = new THREE.Mesh( geometry, material ) ;
  mesh.position.set(0, 0.005 * deckList.length + 0.01, 0) // 弧度
  mesh.rotateX(-90 * (Math.PI / 180)) // 弧度

  // 阴影
  let shadowGeometry = geometry.clone()
  shadowGeometry.translate(0.02, 0.02, 0);
  let shadowMaterial = new THREE.MeshBasicMaterial( { color: new THREE.Color("black") } );
  let shadowMesh = new THREE.Mesh(shadowGeometry, shadowMaterial);
  shadowMesh.position.set(0, 0.005 * deckList.length, 0) // 弧度
  shadowMesh.rotateX(-90 * (Math.PI / 180)) // 弧度

  deckGroup.add(mesh)
  deckGroup.add(shadowMesh)
}

// 设置卡组位置
const setDeckPos = () => {
  nextTick(() => {
    let plane = scene.getObjectByName("地面")
    let point = transPos(window.innerWidth - 15, window.innerHeight - 15) // 卡组起始位置的屏幕坐标
    // 
    raycaster.setFromCamera( point, camera );
    const intersects1 = raycaster.intersectObject( plane );
    if (intersects1.length > 0) {
      let point = intersects1[0].point
      // deckGroup.position.set(point.x, point.y, point.z)
      deckGroup.position.set(point.x - 0.5, point.y, point.z - 0.7)
    }
  })
}

defineExpose({
  init
})
</script>

<style lang="scss" scoped>
</style>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入剖析Java并发库(JUC)之StampedLock的应用与原理

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 在现代多核处理器架构下&#xff0c;并发编程成为提升程序性能的关键手段。Java作为一门广泛使用的编程语言&#xff0c;提供了丰…

谷歌浏览器调用相同url数据不刷新

原代码 原因 谷歌浏览访问相同接口默认调用缓存数据 解决方案 添加时间戳

机器视觉学习(六)—— 图像的颜色识别

目录 一、色彩空间 1.1 RGB色彩空间 1.2 HSV色彩空间 1.3 灰度 1.4 CMYK色彩空间 1.5 Lab色彩空间 二、色彩空间转换 三、识别颜色 3.1 识别一种特定的颜色 3.2 识别多种颜色 一、色彩空间 计算机视觉中常用的色彩空间有RGB色彩空间、HSV色彩空间、CMYK色彩空间、La…

TR1 - Transformer起源与发展

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 1. Transformer的起源与发展 2017年Google在《Attention Is All You Need》中提出了Transformer结构用于序列标注&#xff0c;在翻译任务…

simulink里枚举量的使用--在m文件中创建枚举量实践操作(推荐)

本文将介绍一种非常重要的概念&#xff0c;枚举量&#xff0c;以及它在simulink状态机中的使用&#xff0c;并且给出模型&#xff0c;方便大家学习。 枚举量&#xff1a;实际上是用一个名字表示了一个变量&#xff0c;能够比较方便的表示标志信息 A.简单举例&#xff1a; 1&a…

“低代码+平台”:驱动企业数字化转型与创新的新引擎

“低代码平台”作为一种新兴的软件开发范式&#xff0c;正逐渐成为企业快速响应市场变化、优化业务流程、提升数字化水平的重要手段。它的价值在于&#xff0c;将传统软件开发的复杂性大大降低&#xff0c;赋予了非技术人员或轻量级开发者快速构建应用的能力&#xff0c;并能灵…

docker 哲学 - 网络桥接器、容器网络接口 、容器间的通信方式

1、解释 docker0 veth eth 2、vethXX 和 ethXX 是肯定一一对应吗 比如 eth1 对应 veth1 3、如果 A容器使用 默认创建方式 。定义他内部网络为 eth0&#xff0c;容器B使用 --network 连上 已创建的网络 172.89.2.1 。此时假设 B的 ip是 172.89.2.2 &#xff0c;容器网络接口是 e…

【蓝桥杯嵌入式】四、各种外设驱动(十一)ADC(1):软件触发与定时器触发

温馨提示&#xff1a;本文不会重复之前提到的内容&#xff0c;如需查看&#xff0c;请参考附录 【蓝桥杯嵌入式】附录 目录 重点提炼&#xff1a; 一、需求分析 1、需要的外设资源分析&#xff1a; 2、外设具体分析&#xff1a; 比赛时ADC可能需要配置的部分&#xff1a;…

视频批量爬虫下载工具|可导出视频分享链接|抖音视频提取软件

便捷的视频批量爬虫软件操作指南 抖音视频下载界面图解 主要功能&#xff1a; 关键词批量提取视频和单独视频提取&#xff0c;提取后下载功能。 功能解析&#xff1a; 1. 关键词批量采集视频的解析 对特定关键词进行搜索和视频提取&#xff0c;例如输入“汽车配件”&#x…

JVM面试篇

面试篇就是复习前面学的 什么是JVM 1.定义&#xff1a;JVM指的是Java虚拟机&#xff0c;本质是一个运行在计算机上的程序 2.作用&#xff1a;为了支持Java中Write Once &#xff0c;Run Anywhere 编写一次 到处运行的跨平台特性 功能&#xff1a; 1.解释和运行 2.内存管理…

XSKY 智能存储,助力“数据要素 X”先进制造

3 月 21-22 日&#xff0c;主题为“突破 智行”的 IMC2024 第七届中国智造数字科技峰会在重庆召开。作为在先进制造领域拥有领先存储解决方案以及众多应用实践的企业&#xff0c;星辰天合受邀参加了此次峰会并荣获大会颁发的“最佳存储解决方案奖”。同时&#xff0c;星辰天合先…

Django日志(三)

内置TimedRotatingFileHandler 按时间自动切分的log文件,文件后缀 %Y-%m-%d_%H-%M-%S , 初始化参数: 注意 发送邮件的邮箱,开启SMTP服务 filename when=h 时间间隔类型,不区分大小写 S:秒 M:分钟 H:小时 D:天 W0-W6:星期几(0 = 星期一) midnight:如果atTime未指定,…

Swift知识点(二)

17. 字面量协议、模式匹配、条件编译 字面量&#xff08;Literal&#xff09; var age 10 var isRed false var name "Jack"上面代码中&#xff1a;10、false、"Jack"就是字面量 可以看到&#xff0c;初始化过程很简单&#xff0c;直接赋值即可 Swif…

Java微服务分布式事务框架seata的TCC模式

&#x1f339;作者主页&#xff1a;青花锁 &#x1f339;简介&#xff1a;Java领域优质创作者&#x1f3c6;、Java微服务架构公号作者&#x1f604; &#x1f339;简历模板、学习资料、面试题库、技术互助 &#x1f339;文末获取联系方式 &#x1f4dd; 往期热门专栏回顾 专栏…

蓝桥杯 EDA 组 2023模拟+真题原理图解析

本文解析了标题内的原理图蓝桥杯EDA组真题&#xff0c;2021-2022 省赛真题/模拟题在上一篇文中。本文中重复或者是简单的电路节约篇幅不在赘述。 其中需要补充和计算原理图的题目解析都放在最下面 一、2023 年第十四届省赛模拟题1 1.1 Type-C 接口电路 通过 CH340N 将数据转化为…

List系列集合:ArrayList、LinkedList --java学习笔记

List系列集合 特点&#xff1a;有序、可重复、有索引 ArrayList&#xff1a;有序、可重复、有索引LinkedList&#xff1a;有序、可重复、有索引 List集合的特有方法 List集合因为支持索引&#xff0c;所以多了很多与索引相关的方法&#xff0c;当然&#xff0c;Collection的…

Visual Studio 插件 AnAPI++ for VS 2022

Anmial API abbreviation AnAPIis an automatically generated WebAPI project that has encapsulated Jwt Oauth2 token authentication, SqlSugar, Swagger, Nlog, Cross domain technologies, and supports Net6 and above versions Anmial API缩写AnAPI是一个自动生成的Web…

pytest简介以及命令行执行

pytest简介以及安装 pytest简介导入第三方库修改工具类 pytest命令方式执行函数执行pytest中的参数详解 pytest简介 pytest有很多强大的插件 pytest-html &#xff08;生成html格式的自动化测试报告&#xff09; pytest-xdist &#xff08;测试用例分布式执行&#xff0c;多cpu…

如何在 Odoo 17 中创建进度条

Odoo 提供各种字段小部件&#xff0c;例如单选按钮、浮点数、百分比、颜色选择器、复选框、状态栏和 URL。通过使用不同的渲染模板&#xff0c;我们可以使用小部件修改视图。它还帮助我们根据自己的需求进行设计&#xff0c;从而简化、加速、扩展和提高开发效率。在本博客中&am…

三分钟教会你水果音乐编曲软件 FL Studio v21.2.3 中文免费版安装方法

随着数字音乐制作的发展&#xff0c;音乐编曲软件已经成为音乐制作人和爱好者不可或缺的工具。FL Studio v21.2.3是一款功能强大的水果音乐编曲软件&#xff0c;它具有直观的界面&#xff0c;易于学习和使用。本文将介绍FL Studio v21.2.3的特点和优势&#xff0c;以及它在音乐…