vue3+threejs新手从零开发卡牌游戏(五):在scene场景中添加手牌区

首先为了方便,我们修改camera相机的位置,将它从正上方往下看,x和z轴值设为0即可

javascript 复制代码
camera.position.set( 0, 6.5, 0 );

此时刷新页面,看到的效果应该是这样的:

然后我想在最下方的一片区域创建手牌区、卡组区、生命值等信息,如下图红框中所示:

接下来我们一步步实现:

第一步 :首先在game目录下新建hand文件夹,里面的index.vue是入口文件,这里我设计的是将玩家1(player1)和玩家2(player2)手牌区分别建立,对应就是p1.vue和p2.vue,当然这个demo里的玩家2其实指的是电脑操控,而不是真的联网和别人对战。

这里我们先不考虑玩家2的手牌区,那么hand/index.vue代码如下:

javascript 复制代码
<!-- 手牌区 -->
<template>
  <P1 ref="p1Ref"/>
  <!-- <P2 ref="p2Ref"/> -->
</template>

<script setup lang="ts">
import { reactive, ref, onMounted, onBeforeUnmount, watch, defineComponent, getCurrentInstance, nextTick } from 'vue'
import P1 from "./p1.vue"
// import P2 from "./p2.vue"

const p1Ref = ref()
// const p2Ref = ref()

onMounted(() => {
  init()
})

const init = () => {
  p1Ref.value.init()
  // p2Ref.value.init()
}
</script>

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

之后在p1.vue中,我们通过new一个Group来代表手牌区,然后我们在手牌区渲染4张卡牌,这里我们写了一个测试卡组列表,里面保存的是各个卡牌对应的card_id,代码如下:

javascript 复制代码
<template>
  <div></div>
</template>

<script setup lang="ts">
import { reactive, ref, onMounted, onBeforeUnmount, watch, defineComponent, getCurrentInstance, nextTick } from 'vue'
import { Card } from "@/views/game/Card.ts"
import { CARD_DICT } from "@/utils/dict/card.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']

// 手牌区group
const handGroup = new THREE.Group()
scene.add(handGroup)

// 测试卡组
const deckList = [
  "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(i*1, 0.02 * i, 0)
      // mesh.position.set(i*0.4, 0.02 * i, 0)
      handGroup.add( mesh );
    }
  })
}

defineExpose({
  init
})
</script>

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

刷新页面效果如下(之前在game/index.vue中测试用的renderCard方法可以删掉了):

我们看到手牌区已经添加进scene场景中了,现在我们需要把手牌区移动到左下角,这里我自己用的方式是:

1.先确定左下角的屏幕坐标(0, window.innerHeight)

2.将屏幕坐标转换成世界坐标

3.之后在scene中添加一个底层的地面PlaneGeometry,将世界坐标和相机传入光线投射Raycaster中,然后获取射线与地面的相交点,这个相交点的位置就是手牌区Group的位置,p1.vue完整代码如下:

javascript 复制代码
<template>
  <div></div>
</template>

<script setup lang="ts">
import { reactive, ref, onMounted, onBeforeUnmount, watch, defineComponent, getCurrentInstance, nextTick } from 'vue'
import { Card } from "@/views/game/Card.ts"
import { CARD_DICT } from "@/utils/dict/card.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();

// 手牌区group
const handGroup = new THREE.Group()
scene.add(handGroup)

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

const init = () => {
  addPlane()
  setHandPos()

  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 * 1, 0.02 * i, 0)
      // mesh.position.set(i*0.4, 0.02 * i, 0)
      handGroup.add( mesh );
    }
  })

}

// 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 );
}

// 设置手牌区位置
const setHandPos = () => {
  nextTick(() => {
    let x = 0
    let y = window.innerHeight
    pointer.x = ( x / window.innerWidth ) * 2 - 1;
    pointer.y = - ( y / window.innerHeight ) * 2 + 1;

    raycaster.setFromCamera( pointer, camera );
    let plane = scene.getObjectByName("地面")
    const intersects = raycaster.intersectObject( plane );
    if (intersects.length > 0) {
      let point = intersects[0].point
      // 由于卡牌几何体大小设置的是(1, 0.02, 1.4),所以我们对应进行偏移
      handGroup.position.set(point.x, point.y, point.z)
      // handGroup.position.set(point.x + 0.5, point.y, point.z - 0.7)
    }
    console.log(55,intersects)
  })
}

defineExpose({
  init
})
</script>

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

刷新页面后效果如下:

我们看到手牌区Group已经移动到左下角了,但由于中心点问题,我们需要再进行偏移下:

javascript 复制代码
handGroup.position.set(point.x + 0.5, point.y, point.z - 0.7)

调整后效果如下:

相关推荐
XinZong8 分钟前
【VSCode插件推荐】想准时下班,你需要codemoss的帮助,分享AI写代码的愉快体验,附详细安装教程
前端·程序员
ErvinHowell16 分钟前
文件MD5生成性能大提升!如何实现分片与Worker优化
前端·vue.js·算法
想做白天梦32 分钟前
LeetCode :150. 逆波兰表达式求值(含求后缀表达式和中缀转后缀表达式)
java·前端·算法
迃-幵37 分钟前
力扣:225 用队列实现栈
android·javascript·leetcode
s甜甜的学习之旅1 小时前
前端js处理list(数组)
开发语言·前端·javascript
小布布的不1 小时前
MyBatis 返回 Map 或 List<Map>时,时间类型数据,默认为LocalDateTime,响应给前端默认含有‘T‘字符
前端·mybatis·springboot
aPurpleBerry1 小时前
Vue3+axios+Vite配置Proxy代理解决跨域
前端·javascript·vue.js
星月前端2 小时前
【vue-pdf】简单封装pdf预览组件
javascript·vue.js·pdf
JustCouvrir2 小时前
macOS|前端工程部署到Nginx服务器
服务器·前端·nginx
代码哈士奇2 小时前
mqtt 传递和推送 温湿度计消息 js
开发语言·前端·javascript·硬件·esp8266