vue3+threejs新手从零开发卡牌游戏(六):手牌区卡牌添加叠放逻辑和拖曳事件

当手牌过多时,我们可以考虑将卡牌进行叠放,这样就不会超出之前设计好的手牌区长度了。

思路如下:

1.获取手牌区的长度

2.根据长度和卡牌数量实时计算叠放位置

上一节中,其实已经拿到了手牌区的起始点位置,那么我们再按同样方法拿到结束点位置,就可以计算出手牌区的长度了,我们修改hand/p1.vue代码:

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

// 设置手牌区位置
const setHandPos = () => {
  nextTick(() => {
    let plane = scene.getObjectByName("地面")
    let point1 = transPos(0, window.innerHeight) // 手牌区起始位置的屏幕坐标
    let point2 = transPos(window.innerWidth * 0.65, window.innerHeight) // 手牌区结束位置的屏幕坐标
    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.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)
      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)
    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.02 * i, 0)
        handGroup.add( mesh );
      }
    })
  })
}

// 坐标归一化
const transPos = (x: any, y: any) => {
  let pointer = new THREE.Vector2()
  
  pointer.x = ( x / window.innerWidth ) * 2 - 1;
  pointer.y = - ( y / window.innerHeight ) * 2 + 1;

  return pointer
}

刷新页面效果如下:

我们看到已经有叠放效果了,修改下测试卡组添加更多的卡牌,效果如下:

这里还有一个问题,如果卡牌过少的话间距会很大:

所以再修改下space的计算逻辑,限制最大间距为1(这里卡牌几何体的宽度是1):

let space = ((_width - 1) / (deckList.length - 1)) <= 1 ? (_width - 1) / (deckList.length - 1) : 1

调整后展示如下:

之后我们为手牌区里的卡牌添加拖曳事件,threejs里有一个拖放控制器(DragControls),我们直接用它,修改p1.vue代码:

引入DragControls:

import { DragControls } from 'three/addons/controls/DragControls.js';

指定手牌区为拖曳数组:

const controls = new DragControls( handGroup.children, camera, renderer.domElement );

监听拖曳事件,这里变化了拖曳卡牌的y轴位置,可以达到一个点击卡牌层级突出的一个效果:

controls.addEventListener( 'dragstart', function ( event: any ) {
  event.object.position.y += 0.04

} );

controls.addEventListener( 'drag', function ( event: any ) {
  event.object.position.y += 0.04
  console.log(event)

} );

controls.addEventListener( 'dragend', function ( event: any ) {
  event.object.position.y -= 0.04
} );

效果如下:

至此我们基本完成了手牌区的功能开发

p1.vue完整代码如下:

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

<script setup lang="ts">
import { reactive, ref, onMounted, onBeforeUnmount, watch, defineComponent, getCurrentInstance, nextTick } from 'vue'
import { DragControls } from 'three/addons/controls/DragControls.js';
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()
handGroup.name = "p1_handGroup"
scene.add(handGroup)

const controls = new DragControls( handGroup.children, camera, renderer.domElement );

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

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

// 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 plane = scene.getObjectByName("地面")
    let point1 = transPos(0, window.innerHeight) // 手牌区起始位置的屏幕坐标
    let point2 = transPos(window.innerWidth * 0.65, window.innerHeight) // 手牌区结束位置的屏幕坐标
    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.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)
      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.02 * i, 0)
        handGroup.add( mesh );
      }
    })
  })
}

// 坐标归一化
const transPos = (x: any, y: any) => {
  let pointer = new THREE.Vector2()
  
  pointer.x = ( x / window.innerWidth ) * 2 - 1;
  pointer.y = - ( y / window.innerHeight ) * 2 + 1;

  return pointer
}

controls.addEventListener( 'dragstart', function ( event: any ) {
  event.object.position.y += 0.04

} );

controls.addEventListener( 'drag', function ( event: any ) {
  event.object.position.y += 0.04
  console.log(event)

} );

controls.addEventListener( 'dragend', function ( event: any ) {
  event.object.position.y -= 0.04
} );

defineExpose({
  init
})
</script>

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

  • 19
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清岚_lxn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值