由于之前的卡组位置占了玩家信息的位置,所以这里将它调整到site区域:
修改game/site/p1.vue,在site右下角添加一个卡组区域:
// 初始化己方战域
const init = () => {let sitePlane = scene.getObjectByName("己方战域Plane")let planeWidth = sitePlane.userData.widthlet 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"mesh.userData.empty = true // empty用来判断当前格子是否有空位} else if (i == 2) {mesh.name = "己方怪兽区2"mesh.userData.empty = true} else if (i == 3) {mesh.name = "己方怪兽区3"mesh.userData.empty = true} 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++) {let mesh = createWireframe()if (i > 0 && i < 4) {if (i == 1) {mesh.name = "己方战术1"} else if (i == 2) {mesh.name = "己方战术2"} else if (i == 3) {mesh.name = "己方战术3"}mesh.position.set(-planeWidth/2 + 0.5, -2.2, 0)mesh.translateX(spaceX * i + i)sitePlane.add(mesh)}if (i == 4) {mesh.name = "卡组"mesh.position.set(-planeWidth/2 + 0.5, -2.15, 0)mesh.translateX(spaceX * i + i)sitePlane.add(mesh)}}
}
然后修改game/deck/p1.vue,将卡组位置调整到site卡组位置:
// 设置卡组位置
const setDeckPos = () => {nextTick(() => {let sitePlane = scene.getObjectByName("己方战域Plane")let siteDeckMesh = sitePlane.getObjectByName("卡组")let pos = siteDeckMesh.positionsiteDeckMesh.getWorldPosition(pos)deckGroup.position.set(pos.x, pos.y, pos.z)deckGroup.userData["position"] = posdeckGroup.scale.set(0.8, 0.8, 0.8) // 缩放console.log(222, siteDeckMesh)})
}
此时页面展示如下:
可以看到右下角区域已经空出来了,这个位置可以展示生命值等信息,这里直接用div+css写就可以,在game目录下新建player文件夹:
player/p1.vue代码如下:
<template><div class="player-box"><!-- <p class="name">Name:<span>Player1</span></p> --><div v-if="visible" class="info"><p class="HP">HP:<span>4000</span></p><p class="Mana">Mana:<span>0</span>/<span>10</span></p></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 { Card } from "@/views/game/Card.ts"
import { CARD_DICT } from "@/utils/dict/card.ts"
import { transPos } 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 commonStore = useCommonStore()const visible = ref(false)const init = () => {visible.value = true
}defineExpose({init,
})
</script><style lang="scss" scoped>
.player-box {position: fixed;bottom: 10px;right: 0;display: flex;align-items: center;justify-content: center;flex-wrap: wrap;width: 35vw;height: 14%;.info {position: relative;.HP {text-align: left;font-size: 18px;color: #fff;> span {font-size: 24px;}}.Mana {text-align: left;font-size: 16px;color: #fff;> span {font-size: 18px;}}}
}
</style>
此时页面展示如下:
附:
此时game/index.vue完整代码:
<template><div ref="sceneRef" class="scene"></div><!-- 玩家区 --><Player ref="playerRef"/><!-- 手牌 --><Hand ref="handRef"/><!-- 卡组 --><Deck ref="deckRef"/><!-- 战域 --><Site ref="siteRef"/><!-- 抽卡逻辑 --><DrawCard ref="drawCardRef" :handRef="handRef"/><!-- 对话框 --><Dialog ref="dialogRef" @handToSite="handToSite" @onCancel="onCancel"/>
</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, renderSiteCardText } 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 Player from "./player/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 drawCardRef = ref()
const dialogRef = ref()// 坐标轴辅助
const axesHelper = new THREE.AxesHelper(5);
// 创建轨道控制器
// const orbitControls = new OrbitControls( camera, renderer.domElement );
// 字体加载器
const fontLoader = new FontLoader();onMounted(async () => {await initResource()initScene()initGame()// 鼠标按下window.addEventListener('touchstart', onMousedown)window.addEventListener('touchmove', onMousemove)// 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)
const animate = () => {requestAnimationFrame( animate );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 () => {// 初始化玩家initPlayer()// 初始化场地initSite()// 初始化卡组await initDeck()// 初始化手牌await initHand()// 绑定手牌事件onHandEvent()
}// 初始化玩家
const initPlayer = () => {playerRef.value.init()
}// 初始化场地
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 = () => {return 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.addHandCard(obj, deckGroup)} else {clearInterval(_interval)resolve(true)}_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].pageXlet clientY = ev.clientY || ev.changedTouches[0].pageYlet 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.positionlet 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)}
}// 鼠标移动事件
const onMousemove = (ev: any) => {// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)pointer.x = ev.clientX || ev.changedTouches[0].pageXpointer.y = ev.clientY || ev.changedTouches[0].pageY
}// 手牌事件
const onHandEvent = () => {let handGroup = scene.getObjectByName("p1_handGroup")const dragControls = new DragControls( handGroup.children, camera, renderer.domElement );dragControls.addEventListener( 'dragstart', function ( event: any ) {event.object.position.y += 0.04});dragControls.addEventListener( 'drag', function ( event: any ) {event.object.position.y += 0.04});dragControls.addEventListener( 'dragend', function ( event: any ) {event.object.position.y -= 0.04let 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)}});
}// 手牌移入己方战域
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}renderSiteCard(data, _mesh)}}
}// 绘制场上卡牌
const renderSiteCard = async (data: any, mesh: any) => {let p1_handGroup = scene.getObjectByName("p1_handGroup")let position = new THREE.Vector3(0, 0, 0)mesh.getWorldPosition(position)mesh.userData.empty = falselet oldMesh = p1_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)handRef.value.removeHandCard(oldMesh)scene.add(newMesh)newMesh.position.set(position.x, position.y, position.z)await renderSiteCardText(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 onCancel = (data: any) => {handRef.value.backPosition(data)
}
</script><style lang="scss" scoped>
.scene {position: fixed;top: 0;left: 0;width: 100%;height: 100vh;
}
</style>