vue3+threejs新手从零开发卡牌游戏(二十四):添加p2战斗逻辑

用代码模拟p2战斗逻辑,按流程进行步骤拆分:

1.p2抽卡

2.p2召唤怪兽上场

3.p2战斗

其中战斗部分分为几种情况:

情况一:p2场上卡牌由大到小进行排序,按序轮询可以攻击的卡牌,然后攻击p1场上卡牌由大到小攻击力最高的同时攻击力不超过当前p2卡牌的那个,如果p2场上最大的攻击力不能突破p1场上卡牌,那就不攻击并将该卡牌攻击次数置为0,进行下一个循环,当p2场上所以卡牌攻击次数均为0时,认为战斗阶段结束,可以进行下一个回合。

情况二:p1场上卡牌已经全部被破坏,而此时p2场上还有卡牌未行动,那么此时卡牌可以直接攻击p1玩家。

情况三:p2战斗阶段时,p1场上怪兽区没有卡牌时,全员即可轮询直接攻击p1玩家。

修改utils/common.ts:


// 卡牌攻击特效
const cardAttack = (card1: any, card2: any, callback: any) => {
  console.log("cardAttack", card1, card2)
  let isP1 = card1.userData.areaType.indexOf("己方") > -1
  // 获取card1世界坐标
  let pos1 = new THREE.Vector3(0, 0, 0)
  card1.getWorldPosition(pos1)
  // 获取card2世界坐标
  let pos2 = new THREE.Vector3(0, 0, 0)
  card2.getWorldPosition(pos2)

  // 动画1:移动到对方卡面前
  const twA = new TWEEN.Tween({
    x: pos1.x,
    y: pos1.y,
    z: pos1.z,
    card1,
  })
  twA.to({
    x: pos2.x,
    y: pos2.y + 0.1,
    z: isP1 ? pos2.z + 1.4 * 0.8 : pos2.z - 0.7 * 0.8,
  }, 300)
  twA.easing(TWEEN.Easing.Quadratic.Out)
  twA.onUpdate((obj: any) => {
    obj.card1.position.set(obj.x, obj.y, obj.z)
  })
  twA.onComplete(function() {
    //动画结束:关闭允许透明,恢复到模型原来状态
    TWEEN.remove(twA)
    callback && callback()
  })
  // 动画2:退回到原位置
  const twB = new TWEEN.Tween({
    x: pos2.x,
    y: pos2.y + 0.1,
    z: isP1 ? pos2.z + 1.4 * 0.8 : pos2.z - 0.7 * 0.8,
    card1,
  })
  twB.to({
    x: pos1.x,
    y: pos1.y,
    z: pos1.z,
  }, 400)
  twB.easing(TWEEN.Easing.Quadratic.In)
  twB.onUpdate((obj: any) => {
    obj.card1.position.set(obj.x, obj.y, obj.z)
  })
  twB.onComplete(function() {
    //动画结束:关闭允许透明,恢复到模型原来状态
    // TWEEN.remove(twA)
    // callback && callback()
  })
  twA.chain(twB)
  twA.start();
}
export { cardAttack }


// p2玩家手牌召唤上场逻辑
const p2FindHandToSiteCard = (cards: any, mana: any) => {
  let newCards = cards.filter((v: any) => v.userData.Mana <= mana)
  console.log("p2玩家手牌召唤上场逻辑", cards, newCards)
  if (newCards.length > 0) {
    newCards.sort((a: any, b: any) => {
      return a.Mana - b.Mana
    })
    return newCards[0]
  }
  return null
}
export { p2FindHandToSiteCard }

game/index.vue(由于涉及代码较多,所以直接贴上该文件全部代码,主要部分看p2DrawCardEvent相关方法):

<template>
  <div ref="sceneRef" class="scene"></div>
  <!-- 玩家区 -->
  <Player ref="playerRef"/>
  <!-- 手牌 -->
  <Hand ref="handRef"/>
  <!-- 卡组 -->
  <Deck ref="deckRef"/>
  <!-- 战域 -->
  <Site ref="siteRef"/>
  <!-- 墓地 -->
  <Graveyard ref="graveyardRef"/>
  <!-- 抽卡逻辑 -->
  <DrawCard ref="drawCardRef" :handRef="handRef"/>
  <!-- 对话框 -->
  <Dialog ref="dialogRef" @handToSite="handToSite" @onCancel="onCancel"/>
  <!-- 阶段操作 -->
  <ul class="stage-box">
    <li v-if="commonStore.$state.currentPlayer==='p1'&&commonStore.$state.flowIndex==1">
      <el-button type="primary" size="small" @click.stop="nextFlow">下一阶段</el-button>
    </li>
    <li v-if="commonStore.$state.currentPlayer==='p1'&&commonStore.$state.flowIndex===2">
      <el-button type="danger" size="small" round @click.stop="nextRound">回合结束</el-button>
    </li>
  </ul>
  
  <div class="info-box">
    <!-- 当前操作玩家 -->
    <div class="">player:{{ commonStore.$state.currentPlayer }}</div>
    <!-- 回合数 -->
    <div class="round-box">round:{{ commonStore.$state.round }}</div>
  </div>
</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 { DragControls } from 'three/addons/controls/DragControls.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js';
import { FontLoader } from 'three/addons/loaders/FontLoader.js';
import { useCommonStore } from "@/stores/common.ts"
import { transPos, editDeckCard, renderDeckText, editGraveyardCard, renderGraveyardText, renderSiteCardText, cardAttack, cardDestroy, cardDirectAttack, p2FindHandToSiteCard } from "@/utils/common.ts"
import { Card } from "./Card.ts"
import { CARD_DICT } from "@/utils/dict/card.ts"
import { p1TestDeck, p2TestDeck} from "@/utils/deck/test.ts"
import Hand from "./hand/index.vue"
import Deck from "./deck/index.vue"
import Site from "./site/index.vue"
import Player from "./player/index.vue"
import Graveyard from "./graveyard/index.vue"
import DrawCard from "@/components/DrawCard.vue"
import Dialog from "@/components/Dialog.vue"

// 引入threejs变量
const {proxy} = getCurrentInstance()
const THREE = proxy['THREE']
const scene = proxy['scene']
const camera = proxy['camera']
const renderer = proxy['renderer']
const composer = proxy['composer']
const TWEEN = proxy['TWEEN']

// 后期处理
const renderPass = new RenderPass( scene, camera );
composer.addPass( renderPass );

// 
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();

const commonStore = useCommonStore()


// 场景ref
const sceneRef = ref()
const playerRef = ref()
const siteRef = ref()
const handRef = ref()
const deckRef = ref()
const graveyardRef = ref()
const drawCardRef = ref()
const dialogRef = ref()
const selectedCard = ref() // 选中的场上card
const selectedTargetCard = ref() // 选中的目标场上card

// 坐标轴辅助
const axesHelper = new THREE.AxesHelper(5);
// 创建轨道控制器
// const orbitControls = new OrbitControls( camera, renderer.domElement );
// 字体加载器
const fontLoader = new FontLoader();

watch(() => commonStore.$state.p1HP, () => {
  if (commonStore.$state.p1HP <=0) {
    // alert("你输了")
    // window.location.reload()
  }
})

watch(() => commonStore.$state.p2HP, () => {
  if (commonStore.$state.p2HP <=0) {
    // alert("你赢了")
    // window.location.reload()
  }
})

onMounted(async () => {
  await initResource()
  initScene()
  initGame()

  // 鼠标按下
  window.addEventListener('touchstart', onMousedown)
  window.addEventListener('touchmove', onMousemove)
  window.addEventListener('touchend', onMouseup)
  // window.addEventListener('click', onMousedown)

  // 监听浏览器窗口变化进行场景自适应
  window.addEventListener('resize', onWindowResize, false);
})

// 资源加载
const initResource = () => {
  // 字体加载
  return new Promise((resolve, reject) => {
    // Microsoft YaHei_Regular
    // fonts/helvetiker_regular.typeface.json
    fontLoader.load('fonts/helvetiker_bold.typeface.json', (font: any) => {
      commonStore.loadFont(font)
      resolve(true)
    });
  })
}

// 初始化场景
const initScene = async () => {
  renderer.setSize( window.innerWidth, window.innerHeight );
  sceneRef.value.appendChild( renderer.domElement );
  scene.add(axesHelper)
  // addSceneBg()

  // camera.position.set( 5, 5, 5 );
  camera.position.set( 0, 6.5, 0 );
  camera.lookAt(0, 0, 0)
  // const glitchPass = new GlitchPass();
  // composer.addPass( glitchPass );

  // const outputPass = new OutputPass();
  // composer.addPass( outputPass );

  addPlane()

  animate();
}

// scene添加动态背景
const addSceneBg = () => {
  const vertices = [];

  for ( let i = 0; i < 5000; i ++ ) {
    const x = THREE.MathUtils.randFloatSpread( 1000 );
    const y = THREE.MathUtils.randFloatSpread( 1000 );
    const z = THREE.MathUtils.randFloatSpread( 1000 );
    vertices.push( x, y, z );
  }

  const particlesGeometry = new THREE.BufferGeometry();
  particlesGeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );

  // 设置点材质
  const pointsMaterial = new THREE.PointsMaterial();
  // pointsMaterial.size = 0.9;
  pointsMaterial.color.set(new THREE.Color("#fff"));
  // 相机深度而衰减
  pointsMaterial.sizeAttenuation = true;

  const points = new THREE.Points(particlesGeometry, pointsMaterial);

  scene.add(points);

  // const texture = new THREE.TextureLoader().load( "textures/bg.jpg" );
  // // const geometry = new THREE.SphereGeometry( 1, 32, 16 );
  // const geometry = new THREE.CircleGeometry( 6, 32 );
  // const material = new THREE.MeshBasicMaterial({
  //   map: texture
  // });
  // const sphere = new THREE.Mesh( geometry, material );
  // sphere.name = "场景背景"
  // sphere.position.set(0, 0, 0)
  // sphere.rotateX(-90 * (Math.PI / 180)) // 弧度
  // scene.add( sphere );

  // texture.wrapS = THREE.RepeatWrapping;
  // texture.wrapT = THREE.RepeatWrapping;
  // scene.background = texture
}

// 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进行渲染循环
// let bgMesh = scene.getObjectByName("场景背景")
// console.log(123, bgMesh)

// 移动线纹理
let arrowTexture = new THREE.TextureLoader().load('textures/arrow.png');
arrowTexture.wrapT = THREE.RepeatWrapping;
arrowTexture.repeat.set(1, 4);
arrowTexture.center = new THREE.Vector2(0.5, 0.5)
arrowTexture.rotation = (-90 * (Math.PI / 180))
arrowTexture.needsUpdate = true;

const animate = () => {
  requestAnimationFrame( animate );
  if (arrowTexture) {
    arrowTexture.offset.y -= 0.02; //更新箭头纹理偏移量
  }
  TWEEN.update()
  // let bgMesh = scene.getObjectByName("场景背景")
  // if (bgMesh) {
  //   bgMesh.rotation.z += 0.0002
  // }
  // renderer.render( scene, camera );
  composer.render()
}

// 场景跟随浏览器窗口大小自适应
const onWindowResize = () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}

// 初始化游戏
const initGame = async () => {
  // 初始化生命值
  commonStore.updateP1HP(4000)
  commonStore.updateP2HP(4000)
  // 初始化mana
  commonStore.updateP1Mana(6)
  commonStore.updateP2Mana(10)
  // 初始化游戏流程
  commonStore.updateFlowIndex(0)
  // 初始化游戏回合数
  commonStore.updateRound(1)

  // 确定先后手
  initFirst()
  // 初始化玩家
  initPlayer()
  // 初始化场地
  initSite()
  // 初始化墓地
  initGraveyard()
  // 初始化卡组
  await initDeck()
  // 初始化手牌
  await initHand()
  // 绑定手牌事件
  onHandEvent()

  // 执行游戏流
  if (commonStore.$state.currentPlayer === "p1") {
    // p1Flow()
    p1DrawCardEvent()
  } else {
    p2DrawCardEvent(1)
  }
  // 
  // await p2DrawCardEvent(1)
  // let p2_handGroup = scene.getObjectByName("p2_handGroup")
  // let cards = p2_handGroup.children.filter((v: any) => v.userData.type === "怪兽")
  // p2HandToSite(cards[0])
}

// 确定先后手
const initFirst = () => {
  let index = Math.random() * 10
  if (index > 0) { // p1先手
    commonStore.updateCurrentPlayer("p1")
    console.log("p1先手")
  } else { // p1后手
    commonStore.updateCurrentPlayer("p2")
    console.log("p2先手")
  }
}

// 初始化玩家
const initPlayer = () => {
  playerRef.value.init()
}

// 初始化场地
const initSite = () => {
  siteRef.value.init()
}

// 初始化墓地
const initGraveyard = () => {
  graveyardRef.value.init()
}

// 初始化卡组
const initDeck = () => {
  return new Promise((resolve, reject) => {
    let p1Deck: any = []
    let p2Deck: any = []
    // 洗牌
    p1TestDeck.sort(() => {
      return Math.random() - 0.5
    })
    p2TestDeck.sort(() => {
      return Math.random() - 0.5
    })
    p1TestDeck.forEach((v: any, i: any) => {
      let obj = CARD_DICT.find((b: any) => b.card_id === v)
      if (obj) {
        p1Deck.push({
          card_id: v,
          name: `${obj.name}_${i}`
        })
      }
    })
    p2TestDeck.forEach((v: any, i: any) => {
      let obj = CARD_DICT.find((b: any) => b.card_id === v)
      if (obj) {
        p2Deck.push({
          card_id: v,
          name: `${obj.name}_${i}`
        })
      }
    })
    // console.log("p1Deck", newDeck)
    commonStore.updateP1Deck(p1Deck)
    commonStore.updateP2Deck(p2Deck)
    
    nextTick(() => {
      handRef.value.init()
      deckRef.value.init()
      resolve(true)
    })
  })
}

// 初始化手牌
const initHand = () => {
  let p1 = new Promise((resolve, reject) => {
    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 renderDeckText(deckGroup, `${commonStore.$state.p1Deck.length}`, commonStore.$state._font, position)
        // 手牌区添加手牌
        handRef.value.addP1HandCard(obj, deckGroup)
      } else {
        clearInterval(_interval)
        resolve(true)
      }
      _number++
    }, 200)
  })

  let p2 = new Promise((resolve, reject) => {
    let cardNumber = 4
    let _number = 0
    let p2Deck = JSON.parse(JSON.stringify(commonStore.$state.p2Deck))
    let deckGroup = scene.getObjectByName("p2_deckGroup")
    let position = new THREE.Vector3(0, 0.005 * p2Deck.length, 0)
    let _interval = setInterval(async() => {
      // console.log(123, p1Deck)
      if (_number < cardNumber) {
        let obj = p2Deck[p2Deck.length - 1]
        p2Deck.splice(p2Deck.length-1, 1)
        commonStore.updateP2Deck(p2Deck)
        // 修改卡组
        await editDeckCard(deckGroup, obj, "remove")
        await renderDeckText(deckGroup, `${commonStore.$state.p2Deck.length}`, commonStore.$state._font, position)
        // 手牌区添加手牌
        handRef.value.addP2HandCard(obj, deckGroup)
      } else {
        clearInterval(_interval)
        resolve(true)
      }
      _number++
    }, 200)
  })
  return new Promise((resolve, reject) => {
    Promise.all([p1, p2]).then((res: any) => {
      resolve(true)
    })
  })
}

// p1抽牌事件
const p1DrawCardEvent = () => {
  setTimeout(() => {
    if (commonStore.$state.p1Deck.length <= 0) {
      alert("你输了")
      window.location.reload()
      return
    }
    let p1_deckGroup = scene.getObjectByName("p1_deckGroup")
    let pos1 = p1_deckGroup.userData.position
    let pos2 = new THREE.Vector3(0, 2, 0)
    if (p1_deckGroup.position.x !== pos2.x) {
      drawCardRef.value.drawCardAnimate1(p1_deckGroup, pos1, pos2)
    }
  }, 300)
}

// p2抽牌事件
const p2DrawCardEvent = (cardNumber: any) => {
  return new Promise((resolve, reject) => {
    let _number = 0
    let p2Deck = JSON.parse(JSON.stringify(commonStore.$state.p2Deck))
    if (p2Deck.length <= 0) {
      alert("你赢了")
      window.location.reload()
      return
    }
    let deckGroup = scene.getObjectByName("p2_deckGroup")
    let position = new THREE.Vector3(0, 0.005 * p2Deck.length, 0)
    let _interval = setInterval(async() => {
      // console.log(123, p1Deck)
      if (_number < cardNumber) {
        let obj = p2Deck[p2Deck.length - 1]
        p2Deck.splice(p2Deck.length-1, 1)
        commonStore.updateP2Deck(p2Deck)
        // 修改卡组
        await editDeckCard(deckGroup, obj, "remove")
        await renderDeckText(deckGroup, `${commonStore.$state.p2Deck.length}`, commonStore.$state._font, position)
        // 手牌区添加手牌
        handRef.value.addP2HandCard(obj, deckGroup)
      } else {
        clearInterval(_interval)
        // 更新flowIndex
        commonStore.updateFlowIndex(1)
        // p2玩家手牌召唤上场逻辑,轮询手牌中Mana最大且不超过此时p2Mana的卡牌,直到能上场的都上场后进行下一阶段
        function p2FindHandToSiteCards() {
          let p2_handGroup = scene.getObjectByName("p2_handGroup")
          let cards = p2_handGroup.children
          let mana = commonStore.$state.p2Mana
          let card = p2FindHandToSiteCard(cards, mana)
          if (card && card.userData.type === "怪兽") {
            p2HandToSite(card)
            nextTick(() => {
              let p2SiteCards = scene.children.filter((v: any) => v.userData && v.userData.areaType && (v.userData.areaType.indexOf("对方怪兽区")>-1))
              if (commonStore.$state.p2Mana > 0 && p2SiteCards.length < 3) {
                setTimeout(function() {
                  p2FindHandToSiteCards()
                }, 400)
              } else {
                // 执行p2战斗阶段
                console.log("执行p2战斗阶段")
                nextTick(() => {
                  p2SiteCards.forEach((v: any) => {
                    v.userData.AttackFrequency = 1
                  })
                  p2FightEvent()
                })
              }
            })
          } else {
            // 执行p2战斗阶段
            console.log("执行p2战斗阶段")
            nextTick(() => {
              let p2SiteCards = scene.children.filter((v: any) => v.userData && v.userData.areaType && (v.userData.areaType.indexOf("对方怪兽区")>-1))
              p2SiteCards.forEach((v: any) => {
                v.userData.AttackFrequency = 1
              })
              p2FightEvent()
            })
          }
        }
        p2FindHandToSiteCards()
        
        resolve(true)
      }
      _number++
    }, 200)
  })
}

// p2卡牌上场事件
const p2HandToSite = (card: any) => {
  let sitePlane = scene.getObjectByName("对方战域Plane")
  let userData = card.userData
  if (userData.type === "怪兽") {
    let meshes = sitePlane.children.filter((v: any) => v.name.indexOf("对方怪兽区") > -1 && v.userData.empty === true)
    if (meshes.length > 0) {
      let _mesh = null
      let m1 = meshes.find((v: any) => v.name.indexOf(1) > -1)
      let m2 = meshes.find((v: any) => v.name.indexOf(2) > -1)
      let m3 = meshes.find((v: any) => v.name.indexOf(3) > -1)
      if (m2) {
        _mesh = m2
      } else if (m1) {
        _mesh = m1
      } else if (m3) {
        _mesh = m3
      }
      let p2_handGroup = scene.getObjectByName("p2_handGroup")
      // card.rotateX(180 * (Math.PI / 180)) // 弧度
      renderSiteCard(p2_handGroup, card, _mesh)
    }
  }
}

// p2战斗事件
const p2FightEvent = () => {
  commonStore.updateFlowIndex(2)
  let p1SiteCards = scene.children.filter((v: any) => (v.userData && v.userData.areaType && v.userData?.areaType?.indexOf("己方怪兽区") > -1))
  let p2SiteCards = scene.children.filter((v: any) => (v.userData && v.userData.areaType && v.userData?.areaType?.indexOf("对方怪兽区") > -1))
  console.log(222, p1SiteCards,p2SiteCards )
  if (p2SiteCards.length > 0) {
    if (p1SiteCards.length > 0) {
      function p2CardAttack() {
        // 遍历p2可以攻击的card
        let p2CanAttackCards = p2SiteCards.filter((v: any) => v.userData.AttackFrequency > 0)
        if (p2CanAttackCards.length > 0) {
          p2CanAttackCards.sort((a: any, b: any) => {
            return b.userData._ATK - a.userData._ATK
          })
          let p2ATK = Number(p2CanAttackCards[0].userData._ATK)
          let p1AttackedCards = p1SiteCards.filter((v: any) => Number(v.userData._ATK) <= p2ATK) // 找出p1可以被p2攻击的卡牌,即当前选中的p2卡牌的攻击力大于等于p1卡牌{
          if (p1AttackedCards.length > 0) {
            p1AttackedCards.sort((a: any, b: any) => {
              return a.userData._ATK - b.userData._ATK
            })
            // let _selectedCard = scene.children.find((v: any) => v.name === p2CanAttackCards[0].name && (v.userData.areaType && v.userData.areaType.indexOf("对方怪兽区") > -1))
            // let _selectedTargetCard = scene.children.find((v: any) => v.name === p1AttackedCards[0].name && (v.userData.areaType && v.userData.areaType.indexOf("己方怪兽区") > -1))
            selectedCard.value = p2CanAttackCards[0]
            selectedTargetCard.value = p1AttackedCards[0]
            // console.log(666, selectedCard.value, selectedTargetCard.value)
            nextTick(() => {
              fight()
              setTimeout(function() {
                let p2CanAttackCards = p2SiteCards.filter((v: any) => v.userData.AttackFrequency > 0)
                if (p2CanAttackCards.length > 0) {
                  p2FightEvent()
                }
              }, 1000)
            })
          } else {
            selectedCard.value = null
            selectedTargetCard.value = null
            // 如果不能攻击,则将这回合攻击次数归0
            p2CanAttackCards[0].userData.AttackFrequency = 0
            nextRound()
          }
        } else {
          fight()
        }
      }
      p2CardAttack()
    } else {
      // 直接攻击
      let p2CanAttackCards = p2SiteCards.filter((v: any) => v.userData.AttackFrequency > 0)
      if (p2CanAttackCards.length > 0) {
        selectedCard.value = p2CanAttackCards[0]
        fight()
        setTimeout(function() {
          let p2CanAttackCards = p2SiteCards.filter((v: any) => v.userData.AttackFrequency > 0)
          if (p2CanAttackCards.length > 0) {
            p2FightEvent()
          }
        }, 1000)
      }
    }
  }
  // 如果p2所有卡牌攻击次数都为0,则回合结束
  let p2CanAttackCards = p2SiteCards.filter((v: any) => v.userData.AttackFrequency > 0)
  if (p2CanAttackCards.length <= 0) {
    setTimeout(() => {
      nextRound()
    }, 500)
  }
}

// 鼠标按下事件
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

  let point = transPos(clientX, clientY)

  // 通过摄像机和鼠标位置更新射线
  raycaster.setFromCamera( point, camera );
  
  // 点击卡组事件
  // onP1DeckEvent()
  // 鼠标选中场上卡牌事件
  onSelectSiteCard()
}

// 鼠标移动事件
const onMousemove = (ev: any) => {
  // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
  pointer.x = ev.clientX || ev.changedTouches[0].pageX
  pointer.y = ev.clientY || ev.changedTouches[0].pageY
  // 画线
  if (selectedCard.value && (commonStore.$state.flowIndex === 2)) {
    drawLine()
  }
}

// 鼠标抬起事件
const onMouseup = (ev: any) => {
  let lineMesh = scene.getObjectByName("移动线")
  if (lineMesh) {
    scene.remove(lineMesh)
  }
  fight()
}

// 鼠标选中场上卡牌事件
const onSelectSiteCard = () => {
  if (commonStore.$state.flowIndex !== 2) { // 非战斗阶段不进行该操作
    return
  }
  if (commonStore.$state.round === 1) { // 第一回合不能进行攻击
    return
  }
  let p1Cards = scene.children.filter((v: any) => v.userData?.areaType?.indexOf("己方怪兽区") > -1)
  if (p1Cards.length <= 0) {
    return
  }
  let arr = raycaster.intersectObjects(p1Cards, true)
  if (arr.length > 0) {
    // 找到对应卡牌
    let _selectedCard: any = arr[0].object
    if (_selectedCard.name === "攻击力") {
      _selectedCard = _selectedCard.parent
    }
    if (_selectedCard.userData.AttackFrequency <= 0) { // 如果该卡攻击次数小于等于0,那么跳过
      return
    }
    selectedCard.value = _selectedCard
  }

}

// 画线
const drawLine = () => {
  let mesh = scene.getObjectByName("移动线")
  if (mesh) {
    scene.remove(mesh)
  }
  
  let startPos = new THREE.Vector3(0, 0, 0)
  selectedCard.value.getWorldPosition(startPos)
  let point = transPos(pointer.x, pointer.y)
  
  // 通过摄像机和鼠标位置更新射线
  raycaster.setFromCamera( point, camera );

  let plane = scene.getObjectByName("地面")
  let arr = raycaster.intersectObject(plane)
  if (arr.length > 0) {
    // let pos = new THREE.Vector3(0, 0, 0)
    // arr[0].object.getWorldPosition(pos)
    let point = arr[0].point
    let curve = new THREE.CatmullRomCurve3([
      new THREE.Vector3(startPos.x, startPos.y + 0.2, startPos.z),
      new THREE.Vector3(point.x, point.y + 0.2, point.z)
      
    ]);
    let tubeGeometry = new THREE.TubeGeometry(curve, 80, 0.08);

    const material = new THREE.MeshBasicMaterial({
      map: arrowTexture,
      side: THREE.BackSide, //显示背面
      transparent: true,
      // color: new THREE.Color("#ff6347")
    });
    const _mesh = new THREE.Mesh( tubeGeometry, material );
    _mesh.name = "移动线"
    scene.add(_mesh);
  }
  
  // 获取目标对象
  getTargetMesh()
}

// 获取目标对象
const getTargetMesh = () => {
  let p2Cards = scene.children.filter((v: any) => v.userData?.areaType?.indexOf("对方怪兽区") > -1)
  if (p2Cards.length <= 0) {
    selectedTargetCard.value = null
    return
  }
  let arr = raycaster.intersectObjects(p2Cards, true)
  if (arr.length > 0) {
    let _selectedTargetCard: any = arr[0].object
    if (_selectedTargetCard.name === "攻击力") {
      _selectedTargetCard = _selectedTargetCard.parent
    }
    selectedTargetCard.value = _selectedTargetCard
  } else {
    selectedTargetCard.value = null
  }
}

// 战斗
const fight = () => {
  if (commonStore.$state.flowIndex !== 2) {
    return
  }
  if (selectedCard.value) { // 攻击次数减1
    let AttackFrequency = JSON.parse(JSON.stringify(selectedCard.value.userData.AttackFrequency))
    if (AttackFrequency > 0) {
      AttackFrequency--
    } else {
      AttackFrequency = 0
    }
    selectedCard.value.userData.AttackFrequency = AttackFrequency
  }
  // console.log("fight", selectedCard.value, selectedTargetCard.value)
  if (selectedCard.value && selectedTargetCard.value) { // 如果selectedCard和selectedTargetCard都存在
    // 移除卡牌
    const removeCard = async (card: any) => {
      if (card.children && card.children.length > 0) {
        card.children.forEach((v: any) => {
          card.remove(v)
        })
      }
      let areaType = card.userData.areaType
      let isP1 = areaType.indexOf("己方") > -1
      let graveyardGroup = null
      let graveyardGroupPos = new THREE.Vector3(0, 0, 0)
      let cards = []
      card.material.forEach((v: any) => {
        v.transparent = false
        v.opacity = 1
        v.alphaTest = 0.1;
      })
      // card.rotateX(180 * (Math.PI / 180)) // 弧度
      if (isP1) {
        card.userData.areaType = "己方墓地"
        graveyardGroup = scene.getObjectByName("p1_graveyardGroup")
        graveyardGroup.getWorldPosition(graveyardGroupPos)
        card.position.set(graveyardGroupPos.x, graveyardGroupPos.y, graveyardGroupPos.z)
        cards = scene.children.filter((v: any) => v.userData?.areaType === "己方墓地")
      } else {
        card.userData.areaType = "对方墓地"
        graveyardGroup = scene.getObjectByName("p2_graveyardGroup")
        graveyardGroup.getWorldPosition(graveyardGroupPos)
        card.position.set(graveyardGroupPos.x, graveyardGroupPos.y, graveyardGroupPos.z)
        cards = scene.children.filter((v: any) => v.userData?.areaType === "对方墓地")
      }
    
      // 修改墓地
      let position = new THREE.Vector3(0, 0.005 * cards.length, 0)
      await editGraveyardCard(graveyardGroup, card, "remove")
      await renderGraveyardText(graveyardGroup, `${cards.length}`, commonStore.$state._font, position)

      if (isP1) {
        let sitePlane = scene.getObjectByName("己方战域Plane")
        let mesh = sitePlane.children.find((v: any) => v.name === areaType)
        if (mesh) {
          mesh.userData.empty = true
        }
      } else {
        let sitePlane = scene.getObjectByName("对方战域Plane")
        let mesh = sitePlane.children.find((v: any) => v.name === areaType)
        if (mesh) {
          mesh.userData.empty = true
        }
      }
    }
    let _selectedCard = selectedCard.value
    let _selectedTargetCard = selectedTargetCard.value
    cardAttack(selectedCard.value, selectedTargetCard.value, () => {
      // console.log(888, Number(selectedCard.value.userData.ATK), Number(selectedTargetCard.value.userData.ATK))
      if (Number(_selectedCard.userData.ATK) > Number(_selectedTargetCard.userData.ATK)) {
        cardDestroy(_selectedTargetCard, () => {
          removeCard(_selectedTargetCard)
          // 削减血量
          let isP1 = _selectedTargetCard.userData.areaType.indexOf("己方") > -1
          if (isP1) {
            let p1HP = JSON.parse(JSON.stringify(commonStore.$state.p1HP))
            p1HP -= (Number(_selectedCard.userData.ATK) - Number(_selectedTargetCard.userData.ATK))
            if (p1HP < 0) {
              p1HP = 0
            }
            commonStore.updateP1HP(p1HP)
          } else {
            let p2HP = JSON.parse(JSON.stringify(commonStore.$state.p2HP))
            p2HP -= (Number(_selectedCard.userData.ATK) - Number(_selectedTargetCard.userData.ATK))
            if (p2HP < 0) {
              p2HP = 0
            }
            commonStore.updateP2HP(p2HP)
          }
        })
      } else if (Number(_selectedCard.userData.ATK) === Number(_selectedTargetCard.userData.ATK)) {
        cardDestroy(_selectedCard, () => {
          removeCard(_selectedCard)
        })
        cardDestroy(_selectedTargetCard, () => {
          removeCard(_selectedTargetCard)
        })
      } else {
        cardDestroy(_selectedCard, () => {
          removeCard(_selectedCard)
          // 削减血量
          let isP1 = _selectedCard.userData.areaType.indexOf("己方") > -1
          if (isP1) {
            let p1HP = JSON.parse(JSON.stringify(commonStore.$state.p1HP))
            p1HP -= (Number(_selectedCard.userData.ATK) - Number(_selectedCard.userData.ATK))
            if (p1HP < 0) {
              p1HP = 0
            }
            commonStore.updateP1HP(p1HP)
          } else {
            let p2HP = JSON.parse(JSON.stringify(commonStore.$state.p2HP))
            p2HP -= (Number(_selectedCard.userData.ATK) - Number(_selectedCard.userData.ATK))
            if (p2HP < 0) {
              p2HP = 0
            }
            commonStore.updateP2HP(p2HP)
          }
        })
      }
    })
    selectedCard.value = null
    selectedTargetCard.value = null
  } else if (selectedCard.value) { // 如果只存在selectedCard
    // 直接攻击
    let _selectedCard: any = selectedCard.value
    let areaType = _selectedCard.userData.areaType
    let isP1 = areaType.indexOf("己方") > -1
    let cards = []
    if (isP1) {
      let plane = scene.getObjectByName("对方战域Plane")
      let arr = raycaster.intersectObjects(plane.children)
      if (arr.length <= 0) {
        return
      }
      cards = scene.children.filter((v: any) => v.userData?.areaType?.indexOf("对方怪兽区") > -1)
    } else {
      cards = scene.children.filter((v: any) => v.userData?.areaType?.indexOf("己方方怪兽区") > -1)
    }
    if (cards.length > 0) {
      return
    }
    cardDirectAttack(scene, _selectedCard, () => {
      if (isP1) {
        let p2HP = JSON.parse(JSON.stringify(commonStore.$state.p2HP))
        p2HP -= _selectedCard.userData.ATK
        if (p2HP < 0) {
          p2HP = 0
        }
        commonStore.updateP2HP(p2HP)
      } else {
        let p1HP = JSON.parse(JSON.stringify(commonStore.$state.p1HP))
        p1HP -= _selectedCard.userData.ATK
        if (p1HP < 0) {
          p1HP = 0
        }
        commonStore.updateP1HP(p1HP)
      }
    })
  }
  
  selectedCard.value = null
}

// 手牌事件
const onHandEvent = () => {
  let handGroup = scene.getObjectByName("p1_handGroup")
  const dragControls = new DragControls( handGroup.children, camera, renderer.domElement );

  dragControls.addEventListener( 'dragstart', function ( event: any ) {
    if (commonStore.$state.currentPlayer === "p1" && commonStore.$state.flowIndex === 1) {
      dragControls.enabled = true
      event.object.position.y += 0.04
    } else {
      dragControls.enabled = false
    }
  });

  dragControls.addEventListener( 'drag', function ( event: any ) {
    if (commonStore.$state.currentPlayer === "p1" && commonStore.$state.flowIndex === 1) {
      dragControls.enabled = true
      event.object.position.y += 0.04
    } else {
      dragControls.enabled = false
    }
  });

  dragControls.addEventListener( 'dragend', function ( event: any ) {
    if (commonStore.$state.currentPlayer === "p1" && commonStore.$state.flowIndex === 1) {
      dragControls.enabled = true
      event.object.position.y -= 0.04
      // 如果mana不够则不能上场
      if (commonStore.$state.p1Mana < event.object?.userData.Mana) {
        handRef.value.backPosition(event.object)
        return
      }
      let p1SitePlane = scene.getObjectByName("己方战域Plane")
      let point = transPos(pointer.x, pointer.y)
      // 通过摄像机和鼠标位置更新射线
      raycaster.setFromCamera( point, camera );
      const intersects = raycaster.intersectObjects(p1SitePlane.children);
      if (intersects.length > 0) {
        dialogRef.value.init({
          type: "handToSite",
          obj: event.object,
          message: "是否上场该卡牌"
        })
      } else {
        handRef.value.backPosition(event.object)
      }
    } else {
      dragControls.enabled = false
    }
  });
}

// 手牌移入己方战域
const handToSite = (data: any) => {
  let sitePlane = scene.getObjectByName("己方战域Plane")
  // console.log(data)
  let userData = data.userData
  if (userData.type === "怪兽") {
    let meshes = sitePlane.children.filter((v: any) => v.name.indexOf("己方怪兽区") > -1 && v.userData.empty === true)
    if (meshes.length > 0) {
      let _mesh = null
      let m1 = meshes.find((v: any) => v.name.indexOf(1) > -1)
      let m2 = meshes.find((v: any) => v.name.indexOf(2) > -1)
      let m3 = meshes.find((v: any) => v.name.indexOf(3) > -1)
      if (m2) {
        _mesh = m2
      } else if (m1) {
        _mesh = m1
      } else if (m3) {
        _mesh = m3
      }
      let p1_handGroup = scene.getObjectByName("p1_handGroup")
      renderSiteCard(p1_handGroup, data, _mesh)
    }
  }
}

// 绘制场上卡牌
const renderSiteCard = async (handGroup: any, data: any, mesh: any) => {
  let position = new THREE.Vector3(0, 0, 0)
  mesh.getWorldPosition(position)
  mesh.userData.empty = false
  let oldMesh = handGroup.children.find((v: any) => v.name === data.name)
  let newMesh = oldMesh.clone()
  newMesh.userData.areaType = mesh.name // 用来记录卡牌在哪个区域,怪兽区、墓地、手牌、卡组、场地等
  newMesh.scale.set(0.8, 0.8, 0.8)
  if (handGroup.name === "p1_handGroup") {
    // 更新mana
    let mana = JSON.parse(JSON.stringify(commonStore.$state.p1Mana))
    mana -= newMesh.userData.Mana
    commonStore.updateP1Mana(mana)
    // 
    handRef.value.removeP1HandCard(oldMesh)
  } else if (handGroup.name === "p2_handGroup") {
    // 更新mana
    let mana = JSON.parse(JSON.stringify(commonStore.$state.p2Mana))
    mana -= newMesh.userData.Mana
    commonStore.updateP2Mana(mana)
    // 
    handRef.value.removeP2HandCard(oldMesh)
    newMesh.rotateX(180 * (Math.PI / 180)) // 弧度
    newMesh.rotateY(180 * (Math.PI / 180)) // 弧度
  }
  scene.add(newMesh)
  newMesh.position.set(position.x, position.y, position.z)
  await renderSiteCardText(handGroup, newMesh, commonStore.$state._font)

  // 创建伽马校正通道
  // const gammaPass= new ShaderPass(GammaCorrectionShader)
  // composer.addPass( gammaPass );
  
  // const outlinePass = new OutlinePass(new THREE.Vector2( window.innerWidth, window.innerHeight ), scene, camera); // 模糊
  // outlinePass.edgeStrength = 1.0; // 调整边缘强度
  // outlinePass.edgeGlow = 1.0; // 边缘发光强度
  // outlinePass.usePatternTexture = false; // 是否使用纹理
  // outlinePass.visibleEdgeColor.set(0xffffff); // 设置边缘颜色
  // outlinePass.hiddenEdgeColor.set(0x000000); // 设置隐藏边缘的颜色

  // outlinePass.selectedObjects = [newMesh.children[0]]
  // composer.addPass( outlinePass );
  // console.log(123, newMesh)
}

// 下一阶段
const nextFlow = () => {
  let flowIndex = JSON.parse(JSON.stringify(commonStore.$state.flowIndex))
  flowIndex++
  commonStore.updateFlowIndex(flowIndex)
}

// 回合结束
const nextRound = () => {
  // 切换玩家
  let player = commonStore.$state.currentPlayer
  if (player === "p1") {
    commonStore.updateCurrentPlayer("p2")
  } else {
    commonStore.updateCurrentPlayer("p1")
  }
  console.log(333, commonStore.$state.currentPlayer)
  // 重置流程
  commonStore.updateFlowIndex(0)
  // 重置mana
  commonStore.updateP1Mana(6)
  commonStore.updateP2Mana(10)
  // 回合数增加
  let round = JSON.parse(JSON.stringify(commonStore.$state.round))
  round++
  commonStore.updateRound(round)
  // 重置怪兽攻击次数
  let p1SiteCards = scene.children.filter((v: any) => v.userData && v.userData.areaType && (v.userData.areaType.indexOf("己方怪兽区")>-1))
  // let p2SiteCards = scene.children.filter((v: any) => v.userData && v.userData.areaType && (v.userData.areaType.indexOf("对方怪兽区")>-1))
  if (p1SiteCards.length > 0) {
    p1SiteCards.forEach((v: any) => {
      v.userData.AttackFrequency = 1
    })
  }
  // if (p2SiteCards.length > 0) {
  //   p2SiteCards.forEach((v: any) => {
  //     v.userData.AttackFrequency = 1
  //   })
  // }
  
  // 重新执行游戏流
  if (commonStore.$state.currentPlayer === "p1") {
    p1DrawCardEvent()
  } else {
    p2DrawCardEvent(1)
  }
}

// 取消
const onCancel = (data: any) => {
  handRef.value.backPosition(data)
}
</script>

<style lang="scss" scoped>
.scene {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
}
.stage-box {
  position: fixed;
  top: 50%;
  right: 4px;
  z-index: 10;
  margin-top: -20px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  list-style: none;
  // max-width: 200px;
  // width: 40%;
  height: 40px;
  li {
    // margin: 6px;
  }
}
.info-box {
  position: fixed;
  top: 50%;
  left: 4px;
  z-index: 10;
  // display: flex;
  // flex-wrap: wrap;
  align-items: center;
  margin-top: -20px;
  height: 40px;
  color: #fff;
  > div {
    line-height: 20px;
  }
}
</style>

页面效果如下:

这里输了是因为卡组没牌了。。。

其实到这里基本已经实现了和电脑简单对战的游玩,基本的游戏架构初步完成,剩下的就是往这个框里添加各种卡牌逻辑,比如基础的加攻减攻,恢复血量等等,以及优化一些战斗效果和交互逻辑、UI美化等。

  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
使用Vue3和Three.js渲染室内地图有几个主要步骤。 首先,你需要准备好室内地图的数据。这可以是一个包含房间、墙壁、家具等元素的3D模型文件,如OBJ或GLTF格式。你还可以考虑将地图数据转换为JSON格式,并使用自定义脚本生成3D对象。 接下来,在Vue3中创建一个Three.js的场景(Scene)。你可以使用Vue Composition API来创建一个自定义的Vue组件,该组件将负责Three.js的初始化和场景的绘制。在这个组件中,你将使用Three.js的PerspectiveCamera来设置透视投影相机,并使用OrbitControls插件来实现用户交互控制。 然后,你需要加载地图数据并将其转换为Three.js的3D对象。你可以使用Three.js提供的加载器(Loader)来加载3D模型文件。完成加载后,你可以将模型添加到场景中,并设置其位置、旋转等属性。 最后,你可以根据需要添加光源、阴影效果、材质等来提高渲染效果。你可以通过创建Three.js的光源对象,如DirectionalLight或SpotLight,来模拟现实世界的光照。你可以使用Three.js的材质(Material)来定义模型的外观和反射属性,如颜色、贴图等。 通过以上步骤,你可以在Vue3应用中使用Three.js渲染室内地图。不过,请注意,使用Three.js进行复杂的渲染可能对性能有一定要求,你可能需要优化性能,如使用LOD(多级细分)模型、合并几何体等技术来提高性能并避免卡顿。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

清岚_lxn

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

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

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

打赏作者

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

抵扣说明:

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

余额充值