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 - 4
      let x = Math.abs(point.x) * 2
      let y = Math.abs(point.z) * 2

      const 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"] = x
      site.userData["height"] = y
      scene.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.width
  let 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 = 4
  let _number = 0
  let 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].pageX
  let 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.position
  let 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.width
  let 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>

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

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

相关文章

C是用什么语言写出来的?

C是用什么语言写出来的? C语言的起源和发展是一个迭代过程&#xff1a; 1. 最初的C语言编译器的开发始于对B语言的改进。B语言是由Ken Thompson设计的&#xff0c;它是基于BCPL语言简化而来的。在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「 C语言的…

2024.3.20 使用maven打包jar文件和保存到本地仓库

2024.3.20 使用maven打包jar文件和保存到本地仓库 使用maven可以很方便地打包jar文件和导入jar文件&#xff0c;同时还可以将该文件保存在本地仓库重复调用。 使用maven打包jar文件和保存到本地仓库 package打包文件。 install导入本地仓库。 使用maven导入jar文件 点击“…

基于GIS、RS、VORS模型、CCDM模型、geodetecto、GWR模型集成的生态系统健康的耦合协调分析技术

集成多源数据&#xff0c;依托ArcGIS Pro和R语言环境&#xff0c;采用“活力-组织力-恢复力-贡献力”&#xff08;VORS&#xff09;模型定量测算生态系统健康指数&#xff08;EHI&#xff09;&#xff1b;如何从经济城镇化&#xff08;GDPD&#xff09;、人口城镇化&#xff08…

【算法杂货铺】分治

目录 &#x1f308;前言&#x1f308; &#x1f4c1; 快速排序 &#x1f4c2;75. 颜色分类 - 力扣&#xff08;LeetCode&#xff09; &#x1f4c2; 912. 排序数组 - 力扣&#xff08;LeetCode&#xff09; &#x1f4c2; 215. 数组中的第K个最大元素 - 力扣&#xff08;Lee…

以RISC-V架构的CLIC中断机制讲解:中断咬尾、中断抢占、中断晚到

1、中断的相关属性 中断所属特权模式&#xff08;M模式 > S模式 > U模式&#xff09;中断等级&#xff1a;决定是否能够抢占当前的中断中断优先级&#xff1a;影响中断的仲裁&#xff0c;优先级高时优先被响应中断编号&#xff1a;区分中断&#xff0c;影响中断的仲裁 …

操作系统面经-什么是操作系统?

通过以下四点可以概括操作系统到底是什么&#xff1a; 操作系统&#xff08;Operating System&#xff0c;简称 OS&#xff09;是管理计算机硬件与软件资源的程序&#xff0c;是计算机的基石。操作系统本质上是一个运行在计算机上的软件程序 &#xff0c;主要用于管理计算机硬…

视频素材库哪家好?我给大家来分享

视频素材库哪家好&#xff1f;这是很多短视频创作者都会遇到的问题。别着急&#xff0c;今天我就来给大家介绍几个视频素材库哪家好的推荐&#xff0c;让你的视频创作更加轻松有趣&#xff01; 视频素材库哪家好的首选当然是蛙学网啦&#xff01;这里有大量的高质量视频素材&am…

成都百洲文化传媒有限公司电商新浪潮的领航者

在当今电商行业风起云涌的时代&#xff0c;成都百洲文化传媒有限公司以其独特的视角和专业的服务&#xff0c;成为了众多商家争相合作的伙伴。今天&#xff0c;就让我们一起走进百洲文化的世界&#xff0c;探索其背后的成功密码。 一、百洲文化的崛起之路 成都百洲文化传媒有限…

python共享单车信息系统的设计与实现flask-django-php-nodejs

课题主要分为二大模块&#xff1a;即管理员模块和用户模块&#xff0c;主要功能包括&#xff1a;用户、区域、共享单车、单车租赁、租赁归还、报修信息、检修信息等&#xff1b; 语言&#xff1a;Python 框架&#xff1a;django/flask 软件版本&#xff1a;python3.7.7 数据库…

从内存巷弄到指针大道(一)

文章目录 1.内存和地址1.1理解内存地址酒店大堂&#xff1a;内存的入口房间号&#xff1a;内存地址的意义酒店的楼层划分&#xff1a;内存的结构酒店的房间单位&#xff1a;计算机中的常见单位 1.2如何理解编址 2.指针变量和地址2.1取地址操作符&#xff08;&)2.2 指针变量…

windows系统下python进程管理系统

两年来&#xff0c;我们项目的爬虫代码大部分都是放在公司的windows机器上运行的&#xff0c;原因是服务器太贵&#xff0c;没有那么多资源&#xff0c;而windows主机却有很多用不上。为了合理利用公司资源&#xff0c;降低数据采集成本&#xff0c;我在所以任务机器上使用anac…

力扣热门算法题 59. 螺旋矩阵 II,60. 排列序列,61. 旋转链表

59. 螺旋矩阵 II&#xff0c;60. 排列序列&#xff0c;61. 旋转链表&#xff0c;每题做详细思路梳理&#xff0c;配套Python&Java双语代码&#xff0c; 2024.03.21 可通过leetcode所有测试用例。 目录 59. 螺旋矩阵 II 解题思路 完整代码 Java Python 60. 排列序列 …

Linux基础命令[20]-useradd

文章目录 1. useradd 命令说明2. useradd 命令语法3. useradd 命令示例3.1 不加参数3.2 -d&#xff08;指定家目录&#xff09;3.3 -g&#xff08;指定用户组&#xff09;3.4 -G&#xff08;指定附属组&#xff09;3.5 -p&#xff08;加密密码&#xff09;3.6 -e&#xff08;指…

东方博宜 1449. 求满足条件的数的和

东方博宜 1449. 求满足条件的数的和 这道题我苦想了很久&#xff0c;觉得2个及2个以上很难解决&#xff0c;但是后面发现&#xff0c;可以用一个变量记录次数&#xff0c;次数大于等于2就好了。 #include<iostream> using namespace std; int main() {int n ;cin >…

JetPack之DataBinding基础使用

目录 一、简介二、使用2.1 使用环境2.2 xml文件绑定数据2.3 数据绑定的对象2.3.1 object2.3.2 ObseravbleField2.3.3 ObseravbleCollection 2.4 绑定数据 三、应用场景 一、简介 DataBinding是谷歌15年推出的library,DataBinding支持双向绑定&#xff0c;能大大减少绑定app逻辑…

防火墙在解决方案及典型项目中的应用

防火墙在解决方案及典型项目中的应用 防火墙作为基础安全防护产品&#xff0c;在各种解决方案、业务场景中配套应用&#xff0c;本节给出各类方案资料链接方便查阅。 防火墙在华为网络解决方案中的应用 解决方案 文档 主要应用 CloudFabric云数据中心网解决方案 资料专区…

游戏引擎开发公司 Unity 调查:超六成游戏工作室采纳AI助力开发,效率与质量双提升

Unity是一家专注于游戏引擎开发的公司&#xff0c;其开发的Unity引擎被广泛应用于游戏开发领域&#xff0c;为开发者提供了强大的工具来创建高质量的游戏。Unity引擎不仅支持多种平台&#xff0c;而且具有易用性和灵活性&#xff0c;使得开发者能够高效地进行游戏开发。近年来&…

一文速通自监督学习(Self-supervised Learning):教机器自我探索的艺术

一文速通自监督学习&#xff08;Self-supervised Learning&#xff09;&#xff1a;教机器自我探索的艺术 前言自监督学习是什么&#xff1f;自监督学习的魔力常见的自监督学习方法1. 对比学习2. 预测缺失部分3. 旋转识别4. 时间顺序预测 结语 &#x1f308;你好呀&#xff01;…

Springboot 博客_002 项目环境配置

引入相关依赖 mysqlmybatis <dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-…

数据库关系运算理论:专门的关系运算概念解析

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…