前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何实现RTS游戏中鼠标在屏幕边缘时移动视角功能

如何实现RTS游戏中鼠标在屏幕边缘时移动视角功能

作者头像
CoderZ
发布2022-12-26 21:06:51
1.2K0
发布2022-12-26 21:06:51
举报
文章被收录于专栏:用户10004205的专栏

🧨 Preface

本文简单介绍如何在Unity中实现即时战略游戏中鼠标在屏幕边缘的时候移动视角的功能,如图所示:

移动视角

该功能的实现包括以下部分:

•判断鼠标是否处于屏幕边缘;•获取鼠标处于屏幕边缘时的移动方向;•控制相机在x、z轴形成的平面上移动;

🎏 判断鼠标是否处于屏幕边缘

首先声明一个float类型的变量,用于定义屏幕边缘的宽度,当光标距离屏幕边缘的距离在该宽度值范围内,表示已经处于屏幕边缘:

屏幕边缘

代码部分:

代码语言:javascript
复制
//边缘宽度
private readonly float edgeSize = 10f;

private bool IsMouseOnEdge()
{
    bool flag = Input.mousePosition.x <= edgeSize || Input.mousePosition.x >= Screen.width - edgeSize;
    flag |= Input.mousePosition.y <= edgeSize || Input.mousePosition.y >= Screen.height - edgeSize;
    return flag;
}

⚽ 获取鼠标处于屏幕边缘时的移动方向

在上述接口的基础上,通过out参数将移动的方向进行传递:

代码语言:javascript
复制
//判断光标是否处于屏幕边缘
private bool IsMouseOnEdge(out Vector2 direction)
{
    direction = Vector2.zero;
    bool flag = Input.mousePosition.x <= edgeSize || Input.mousePosition.x >= Screen.width - edgeSize;
    if (flag)
    {
        direction += Input.mousePosition.x < edgeSize ? Vector2.left : Vector2.right;
    }
    if (Input.mousePosition.y <= edgeSize || Input.mousePosition.y >= Screen.height - edgeSize)
    {
        direction += Input.mousePosition.y < edgeSize ? Vector2.down : Vector2.up;
        flag = true;
    }
    //归一化
    direction = direction.normalized;
    return flag;
}

🎨 控制相机在x、z轴形成的平面上移动

在平移时,保持相机的y坐标值不动,只控制x和z坐标值:

代码语言:javascript
复制
if (IsMouseOnEdge(out Vector2 direction))
{
    ts += (Vector3.right * direction.x + Vector3.forward * direction.y) * mouseMovementSensitivity;
}

•ts(translation):移动的方向•mouseMovementSensitivity:移动的灵敏度

为了保证相机在指定范围内移动,为其增加坐标限制:

代码语言:javascript
复制
//活动区域限制
private readonly float xMinValue;
private readonly float xMaxValue;
private readonly float zMinValue;
private readonly float zMaxValue;

/// <summary>
/// 移动
/// </summary>
public void Translate(Vector3 translation, bool mouseScroll)
{
    Vector3 rotatedTranslation = Quaternion.Euler(mouseScroll ? rotX : 0f, rotY, rotZ) * translation;
    posX += rotatedTranslation.x;
    posY += rotatedTranslation.y;
    posZ += rotatedTranslation.z;

    posX = Mathf.Clamp(posX, xMinValue, xMaxValue);
    posZ = Mathf.Clamp(posZ, zMinValue, zMaxValue);
}

OnDrawGizmosSelected函数中将限制范围绘制出来:

代码语言:javascript
复制
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{
    //如果限制活动范围 将区域范围绘制出来
    if (isRangeClamped)
    {
        Handles.color = Color.cyan;
        Vector3[] points = new Vector3[8]
        {
                    new Vector3(xMinValue, 0f, zMinValue),
                    new Vector3(xMaxValue, 0f, zMinValue),
                    new Vector3(xMaxValue, 0f, zMaxValue),
                    new Vector3(xMinValue, 0f, zMaxValue),
                    new Vector3(xMinValue, 10f, zMinValue),
                    new Vector3(xMaxValue, 10f, zMinValue),
                    new Vector3(xMaxValue, 10f, zMaxValue),
                    new Vector3(xMinValue, 10f, zMaxValue)
        };
        for (int i = 0; i < 4; i++)
        {
            int start = i % 4;
            int end = (i + 1) % 4;
            Handles.DrawLine(points[start], points[end]);
            Handles.DrawLine(points[start + 4], points[end + 4]);
            Handles.DrawLine(points[start], points[i + 4]);
        }
    }
}
#endif

区域限制

🏓 完整示例代码

代码语言:javascript
复制
using UnityEngine;

#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif

#if UNITY_EDITOR
using UnityEditor;
#endif

public class CameraController : MonoBehaviour
{
    /// <summary>
    /// 相机状态类
    /// </summary>
    private class CameraState
    {
        /// <summary>
        /// 坐标x值
        /// </summary>
        public float posX;
        /// <summary>
        /// 坐标y值
        /// </summary>
        public float posY;
        /// <summary>
        /// 坐标z值
        /// </summary>
        public float posZ;
        /// <summary>
        /// 旋转x值
        /// </summary>
        public float rotX;
        /// <summary>
        /// 旋转y值
        /// </summary>
        public float rotY;
        /// <summary>
        /// 旋转z值 
        /// </summary>
        public float rotZ;

        //活动区域限制
        private readonly float xMinValue;
        private readonly float xMaxValue;
        private readonly float zMinValue;
        private readonly float zMaxValue;

        /// <summary>
        /// 默认构造函数
        /// </summary>
        public CameraState()
        {
            xMinValue = float.MinValue;
            xMaxValue = float.MaxValue;
            zMinValue = float.MinValue;
            zMaxValue = float.MaxValue;
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="xMinValue"></param>
        /// <param name="xMaxValue"></param>
        /// <param name="zMinValue"></param>
        /// <param name="zMaxValue"></param>
        public CameraState(float xMinValue, float xMaxValue, float zMinValue, float zMaxValue)
        {
            this.xMinValue = xMinValue;
            this.xMaxValue = xMaxValue;
            this.zMinValue = zMinValue;
            this.zMaxValue = zMaxValue;
        }

        /// <summary>
        /// 根据Transform组件更新状态
        /// </summary>
        /// <param name="t">Transform组件</param>
        public void SetFromTransform(Transform t)
        {
            posX = t.position.x;
            posY = t.position.y;
            posZ = t.position.z;
            rotX = t.eulerAngles.x;
            rotY = t.eulerAngles.y;
            rotZ = t.eulerAngles.z;
        }
        /// <summary>
        /// 移动
        /// </summary>
        public void Translate(Vector3 translation, bool mouseScroll)
        {
            Vector3 rotatedTranslation = Quaternion.Euler(mouseScroll ? rotX : 0f, rotY, rotZ) * translation;
            posX += rotatedTranslation.x;
            posY += rotatedTranslation.y;
            posZ += rotatedTranslation.z;

            posX = Mathf.Clamp(posX, xMinValue, xMaxValue);
            posZ = Mathf.Clamp(posZ, zMinValue, zMaxValue);
        }
        /// <summary>
        /// 根据目标状态插值运算
        /// </summary>
        /// <param name="target">目标状态</param>
        /// <param name="positionLerpPct">位置插值率</param>
        /// <param name="rotationLerpPct">旋转插值率</param>
        public void LerpTowards(CameraState target, float positionLerpPct, float rotationLerpPct)
        {
            posX = Mathf.Lerp(posX, target.posX, positionLerpPct);
            posY = Mathf.Lerp(posY, target.posY, positionLerpPct);
            posZ = Mathf.Lerp(posZ, target.posZ, positionLerpPct);
            rotX = Mathf.Lerp(rotX, target.rotX, rotationLerpPct);
            rotY = Mathf.Lerp(rotY, target.rotY, rotationLerpPct);
            rotZ = Mathf.Lerp(rotZ, target.rotZ, rotationLerpPct);
        }
        /// <summary>
        /// 根据状态刷新Transform组件
        /// </summary>
        /// <param name="t">Transform组件</param>
        public void UpdateTransform(Transform t)
        {
            t.position = new Vector3(posX, posY, posZ);
            t.rotation = Quaternion.Euler(rotX, rotY, rotZ);
        }
    }

    //控制开关
    [SerializeField] private bool toggle = true;

    //是否限制活动范围
    [SerializeField] private bool isRangeClamped;
    //限制范围 当isRangeClamped为true时起作用
    [SerializeField] private float xMinValue = -100f;   //x最小值
    [SerializeField] private float xMaxValue = 100f;    //x最大值
    [SerializeField] private float zMinValue = -100f;   //z最小值
    [SerializeField] private float zMaxValue = 100f;    //z最大值

    //移动速度
    [SerializeField] private float translateSpeed = 10f;
    //加速系数 Shift按下时起作用
    [SerializeField] private float boost = 3.5f;
    //插值到目标位置所需的时间
    [Range(0.01f, 1f), SerializeField] private float positionLerpTime = 1f;
    //插值到目标旋转所需的时间
    [Range(0.01f, 1f), SerializeField] private float rotationLerpTime = 1f;
    //鼠标运动的灵敏度
    [Range(0.1f, 1f), SerializeField] private float mouseMovementSensitivity = 0.5f;
    //鼠标滚轮运动的速度
    [SerializeField] private float mouseScrollMoveSpeed = 10f;
    //用于鼠标滚轮移动 是否反转方向
    [SerializeField] private bool invertScrollDirection = false;
    //移动量
    private Vector3 translation = Vector3.zero;
    //边缘大小
    private readonly float edgeSize = 10f;

    private CameraState initialCameraState;
    private CameraState targetCameraState;
    private CameraState interpolatingCameraState;

    private void Awake()
    {
        //初始化
        if (isRangeClamped)
        {
            initialCameraState = new CameraState(xMinValue, xMaxValue, zMinValue, zMaxValue);
            targetCameraState = new CameraState(xMinValue, xMaxValue, zMinValue, zMaxValue);
            interpolatingCameraState = new CameraState(xMinValue, xMaxValue, zMinValue, zMaxValue);
        }
        else
        {
            initialCameraState = new CameraState();
            targetCameraState = new CameraState();
            interpolatingCameraState = new CameraState();
        }
    }
    private void OnEnable()
    {
        initialCameraState.SetFromTransform(transform);
        targetCameraState.SetFromTransform(transform);
        interpolatingCameraState.SetFromTransform(transform);
    }
    private void LateUpdate()
    {
        if (!toggle) return;
        if (OnResetUpdate()) return;
        OnTranslateUpdate();
    }

    private bool OnResetUpdate()
    {
#if ENABLE_INPUT_SYSTEM
            bool uPressed = Keyboard.current.uKey.wasPressedThisFrame;
#else
        bool uPressed = Input.GetKeyDown(KeyCode.U);
#endif
        //U键按下重置到初始状态
        if (uPressed)
        {
            ResetCamera();
            return true;
        }
        return false;
    }
    private void OnTranslateUpdate()
    {
        translation = GetInputTranslation(out bool mouseScroll) * Time.deltaTime * translateSpeed;
        targetCameraState.Translate(translation, mouseScroll);
        float positionLerpPct = 1f - Mathf.Exp(Mathf.Log(1f - .99f) / positionLerpTime * Time.deltaTime);
        float rotationLerpPct = 1f - Mathf.Exp(Mathf.Log(1f - .99f) / rotationLerpTime * Time.deltaTime);
        interpolatingCameraState.LerpTowards(targetCameraState, positionLerpPct, rotationLerpPct);
        interpolatingCameraState.UpdateTransform(transform);
    }

    //获取输入
    private Vector3 GetInputTranslation(out bool mouseScroll)
    {
        Vector3 ts = new Vector3();

#if ENABLE_INPUT_SYSTEM
            //读取鼠标滚轮滚动值
            float wheelValue = Mouse.current.scroll.ReadValue().y;
#else
        float wheelValue = Input.GetAxis("Mouse ScrollWheel");
#endif
        ts += (wheelValue == 0 ? Vector3.zero : (wheelValue > 0 ? Vector3.forward : Vector3.back) * (invertScrollDirection ? -1 : 1)) * mouseScrollMoveSpeed;

        if (IsMouseOnEdge(out Vector2 direction))
        {
            ts += (Vector3.right * direction.x + Vector3.forward * direction.y) * mouseMovementSensitivity;
        }
#if ENABLE_INPUT_SYSTEM
            //左Shift键按下时加速
            if (Keyboard.current.leftShiftKey.isPressed) ts *= boost;
#else
        if (Input.GetKey(KeyCode.LeftShift)) ts *= boost;
#endif
        mouseScroll = wheelValue != 0f;
        return ts;
    }

    //判断光标是否处于屏幕边缘
    private bool IsMouseOnEdge(out Vector2 direction)
    {
        direction = Vector2.zero;
        bool flag = Input.mousePosition.x <= edgeSize || Input.mousePosition.x >= Screen.width - edgeSize;
        if (flag)
        {
            direction += Input.mousePosition.x < edgeSize ? Vector2.left : Vector2.right;
        }
        if (Input.mousePosition.y <= edgeSize || Input.mousePosition.y >= Screen.height - edgeSize)
        {
            direction += Input.mousePosition.y < edgeSize ? Vector2.down : Vector2.up;
            flag = true;
        }
        direction = direction.normalized;
        return flag;
    }

#if UNITY_EDITOR
    private void OnDrawGizmosSelected()
    {
        //如果限制活动范围 将区域范围绘制出来
        if (isRangeClamped)
        {
            Handles.color = Color.cyan;
            Vector3[] points = new Vector3[8]
            {
                    new Vector3(xMinValue, 0f, zMinValue),
                    new Vector3(xMaxValue, 0f, zMinValue),
                    new Vector3(xMaxValue, 0f, zMaxValue),
                    new Vector3(xMinValue, 0f, zMaxValue),
                    new Vector3(xMinValue, 10f, zMinValue),
                    new Vector3(xMaxValue, 10f, zMinValue),
                    new Vector3(xMaxValue, 10f, zMaxValue),
                    new Vector3(xMinValue, 10f, zMaxValue)
            };
            for (int i = 0; i < 4; i++)
            {
                int start = i % 4;
                int end = (i + 1) % 4;
                Handles.DrawLine(points[start], points[end]);
                Handles.DrawLine(points[start + 4], points[end + 4]);
                Handles.DrawLine(points[start], points[i + 4]);
            }
        }
    }
#endif

    /// <summary>
    /// 重置摄像机到初始状态
    /// </summary>
    public void ResetCamera()
    {
        initialCameraState.UpdateTransform(transform);
        targetCameraState.SetFromTransform(transform);
        interpolatingCameraState.SetFromTransform(transform);
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-11-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 当代野生程序猿 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 🧨 Preface
  • 🎏 判断鼠标是否处于屏幕边缘
  • ⚽ 获取鼠标处于屏幕边缘时的移动方向
  • 🎨 控制相机在x、z轴形成的平面上移动
  • 🏓 完整示例代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档