闲来无事,VUE 封装一个游戏类的虚拟摇杆组件

本文正在参加「金石计划」

虚拟摇杆组件的封装

最近面试了一家 web3d 元宇宙的公司,需要我做一个 虚拟摇杆,虽然没有去,但是闲下来还是做了下

思路

  1. 两个圆圈,一个大圆,一个小圆
  2. 大圆位置,鼠标点击固定,小圆位置,跟随鼠标移动
  3. 小圆永远在大圆内

嗯,就这么简单

实现

首先,画出两个圆,效果图

闲来无事,VUE 封装一个游戏类的虚拟摇杆组件

<template>
    <div v-if="isShowVirtualRocker" id="virtual-rocker" ref="virtualRocker">
        <div id="pointer-target" ref="pointerTarget"></div>
    </div>
</template>

<script lang="ts" setup>
import { Ref, onMounted, onUnmounted, ref, reactive, nextTick } from 'vue';

// size 决定 虚拟摇杆大小
const props = defineProps({
    size1: {
        type: String,
        default: "200px"
    },
    size2: {
        type: String,
        default: "40px"
    }
})

let isShowVirtualRocker: Ref<boolean> = ref(false) // 控制虚拟摇杆的显示和隐藏
let virtualRocker: Ref<HTMLElement | null> = ref(null) // 大圆的 dom
let pointerTarget: Ref<HTMLElement | null> = ref(null); // 小圆的 dom
let startPosition = reactive({ x: 0, y: 0 }) // 初始位置
let endPosition = reactive({ x: 0, y: 0 }) // 移动位置



</script>

<style lang="scss" scoped>
#virtual-rocker {
    width: v-bind(size1);
    height: v-bind(size1);
    background-color: #444444;
    opacity: 0.5;
    position: absolute;
    border-radius: 50%;
    z-index: 999;

    #pointer-target {
        width: v-bind(size2);
        height: v-bind(size2);
        background-color: #eeeeee;
        opacity: 0.5;
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        border-radius: 50%;
    }
}
</style>

其次,添加监听事件 和 销毁事件

注意1,这里获取鼠标位置使用的是 clientX 和 clientY ,用 offsetX 和 offsetY 会存在一个原点闪烁的BUG,这个问题在我鼠标点击后,使用微信截图,此时鼠标放开,但是 mouseup 并未监听到,在之后的获取位置中,原点位置会在页面的左上角和父元素的左上角反复横跳,造成原点闪烁

注意2,vue中对虚拟dom的操作是异步操作,vue会创建一个队列,在确定没有响应式的数据更新后再执行渲染,来进行优化,所以在对 dom 元素设置可见后,直接获取是获取不到更新后的元素,需要在 nexttick 中获取,或者在settimeout 中获取

onMounted(() => {
    // 事件监听
    window.addEventListener("mousedown", onMousedown)
    window.addEventListener("mousemove", onMousemove)
    window.addEventListener("mouseup", onMouseup)
})

onUnmounted(() => {
    // 事件监听移除
    window.removeEventListener("mousedown", onMousedown)
    window.removeEventListener("mousemove", onMousemove)
    window.removeEventListener("mouseup", onMouseup)
})

mousedown

const onMousedown: (this: Window, ev: MouseEvent) => any = (event) => {
    // 如果已经处于mousedown状态,不做处理
    if (isShowVirtualRocker.value) return

    const X = event.clientX
    const Y = event.clientY
    startPosition.x = X
    startPosition.y = Y
    isShowVirtualRocker.value = true

    // vue 对虚拟dom的操作是异步操作,所以需要 异步 才可以获取到组件
    nextTick(() => {
        if (!virtualRocker.value) return
        virtualRocker.value.style.left = X + "px"
        virtualRocker.value.style.top = Y + "px"
        virtualRocker.value.style.transform = "translate(-50%,-50%)"
    });

}

mousemove

const onMousemove: (this: Window, ev: MouseEvent) => any = (event) => {

    if (!virtualRocker.value || !pointerTarget.value) return

    // 获取 当前鼠标 坐标
    const X = event.clientX
    const Y = event.clientY
    endPosition.x = X
    endPosition.y = Y

    // 计算 当前鼠标坐标与 初始位置的距离
    const distance = Math.sqrt(Math.pow((X - startPosition.x), 2) + Math.pow((Y - startPosition.y), 2))

    // 鼠标移动到虚拟摇杆范围外,必须要控制 摇杆中心在 虚拟摇杆范围内
    if (distance > parseInt(props.size1) / 2) {
        // 利用三角函数计算 摇杆中心 在虚拟摇杆的中的位置
        const tan = (Y - startPosition.y) / (X - startPosition.x)
        const sin = Math.abs(Math.sin(Math.atan(tan)))
        const cos = Math.abs(Math.cos(Math.atan(tan)))

        const moveX = (X - startPosition.x) > 0 ? parseInt(props.size1) / 2 * cos : -parseInt(props.size1) / 2 * cos
        const moveY = (Y - startPosition.y) > 0 ? parseInt(props.size1) / 2 * sin : -parseInt(props.size1) / 2 * sin

        pointerTarget.value.style.left = moveX + parseInt(props.size1) / 2 + "px"
        pointerTarget.value.style.top = moveY + parseInt(props.size1) / 2 + "px"
        pointerTarget.value.style.transform = "translate(-50%,-50%)"

    } else {
        // 鼠标在虚拟摇杆范围内
        pointerTarget.value.style.left = X - startPosition.x + parseInt(props.size1) / 2 + "px"
        pointerTarget.value.style.top = Y - startPosition.y + parseInt(props.size1) / 2 + "px"
        pointerTarget.value.style.transform = "translate(-50%,-50%)"
    }


}

mouseup

const onMouseup: (this: Window, ev: MouseEvent) => any = (event) => {
    isShowVirtualRocker.value = false
    startPosition.x = 0
    startPosition.y = 0
    endPosition.x = 0
    endPosition.y = 0
}

计算部分,简单的三角函数,鼠标超出边界,将摇杆中心固定在 摇杆大圆圈的边界

闲来无事,VUE 封装一个游戏类的虚拟摇杆组件

就实现了,简单的摇杆,WASD 控制的话,可以自行再添加 8 个相对坐标

原文链接:https://juejin.cn/post/7221551421832118328 作者:你也向往长安城吗

(0)
上一篇 2023年4月14日 上午10:44
下一篇 2023年4月14日 上午10:54

相关推荐

发表回复

登录后才能评论