本文主要是介绍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.jsonfontLoader.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)// 初始化manacommonStore.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() * 10if (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 = 4let _number = 0let 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 = 4let _number = 0let 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.positionlet 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 = 0let 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)// 更新flowIndexcommonStore.updateFlowIndex(1)// p2玩家手牌召唤上场逻辑,轮询手牌中Mana最大且不超过此时p2Mana的卡牌,直到能上场的都上场后进行下一阶段function p2FindHandToSiteCards() {let p2_handGroup = scene.getObjectByName("p2_handGroup")let cards = p2_handGroup.childrenlet mana = commonStore.$state.p2Manalet 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.userDataif (userData.type === "怪兽") {let meshes = sitePlane.children.filter((v: any) => v.name.indexOf("对方怪兽区") > -1 && v.userData.empty === true)if (meshes.length > 0) {let _mesh = nulllet 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可以攻击的cardlet 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 = nullselectedTargetCard.value = null// 如果不能攻击,则将这回合攻击次数归0p2CanAttackCards[0].userData.AttackFrequency = 0nextRound()}} 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].pageXlet clientY = ev.clientY || ev.changedTouches[0].pageYlet 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].pageXpointer.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].objectif (_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].pointlet 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 = nullreturn}let arr = raycaster.intersectObjects(p2Cards, true)if (arr.length > 0) {let _selectedTargetCard: any = arr[0].objectif (_selectedTargetCard.name === "攻击力") {_selectedTargetCard = _selectedTargetCard.parent}selectedTargetCard.value = _selectedTargetCard} else {selectedTargetCard.value = null}
}// 战斗
const fight = () => {if (commonStore.$state.flowIndex !== 2) {return}if (selectedCard.value) { // 攻击次数减1let 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.areaTypelet isP1 = areaType.indexOf("己方") > -1let graveyardGroup = nulllet graveyardGroupPos = new THREE.Vector3(0, 0, 0)let cards = []card.material.forEach((v: any) => {v.transparent = falsev.opacity = 1v.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.valuelet _selectedTargetCard = selectedTargetCard.valuecardAttack(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("己方") > -1if (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("己方") > -1if (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 = nullselectedTargetCard.value = null} else if (selectedCard.value) { // 如果只存在selectedCard// 直接攻击let _selectedCard: any = selectedCard.valuelet areaType = _selectedCard.userData.areaTypelet isP1 = areaType.indexOf("己方") > -1let 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.ATKif (p2HP < 0) {p2HP = 0}commonStore.updateP2HP(p2HP)} else {let p1HP = JSON.parse(JSON.stringify(commonStore.$state.p1HP))p1HP -= _selectedCard.userData.ATKif (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 = trueevent.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 = trueevent.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 = trueevent.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.userDataif (userData.type === "怪兽") {let meshes = sitePlane.children.filter((v: any) => v.name.indexOf("己方怪兽区") > -1 && v.userData.empty === true)if (meshes.length > 0) {let _mesh = nulllet 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 = falselet 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") {// 更新manalet mana = JSON.parse(JSON.stringify(commonStore.$state.p1Mana))mana -= newMesh.userData.ManacommonStore.updateP1Mana(mana)// handRef.value.removeP1HandCard(oldMesh)} else if (handGroup.name === "p2_handGroup") {// 更新manalet mana = JSON.parse(JSON.stringify(commonStore.$state.p2Mana))mana -= newMesh.userData.ManacommonStore.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.currentPlayerif (player === "p1") {commonStore.updateCurrentPlayer("p2")} else {commonStore.updateCurrentPlayer("p1")}console.log(333, commonStore.$state.currentPlayer)// 重置流程commonStore.updateFlowIndex(0)// 重置manacommonStore.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美化等。
这篇关于vue3+threejs新手从零开发卡牌游戏(二十四):添加p2战斗逻辑的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!