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

news2025/1/31 3:02:13

文章目录

  • 🧨 Preface
  • 🎏 判断鼠标是否处于屏幕边缘
  • ⚽ 获取鼠标处于屏幕边缘时的移动方向
  • 🎨 控制相机在x、z轴形成的平面上移动
  • 🏓 完整示例代码


🧨 Preface

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

移动视角

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

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

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

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

屏幕边缘
代码部分:

//边缘宽度
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参数将移动的方向进行传递:

//判断光标是否处于屏幕边缘
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坐标值:

if (IsMouseOnEdge(out Vector2 direction))
{
    ts += (Vector3.right * direction.x + Vector3.forward * direction.y) * mouseMovementSensitivity;
}
  • ts(translation):移动的方向
  • mouseMovementSensitivity:移动的灵敏度

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

//活动区域限制
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函数中将限制范围绘制出来:

#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

区域限制

🏓 完整示例代码

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);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/39814.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

创新赋能合作伙伴,亚马逊云科技re:Invent科技盛宴

北京时间11月29号&#xff0c;亚马逊云科技年度峰会re:Invent 2022将在拉斯维加斯开幕。这场年度最重磅的云计算技术大会不仅是科技盛宴&#xff0c;也是亚马逊云科技与诸多客户交流互鉴的绝佳平台&#xff0c;今天带大家认识一下几位资深云计算用户&#xff0c;以及他们和re:I…

MyBatis ---- 搭建MyBatis

MyBatis ---- 搭建MyBatis1. 开发环境2. 创建maven工程a>打包方式&#xff1a;jarb>引入依赖3. 创建MyBatis的核心配置文件4. 创建mapper接口5. 创建MyBatis的映射文件6. 通过junit测试功能7. 加入log4j日志功能a>加入依赖b>加入log4j的配置文件1. 开发环境 IDE&a…

Linus 文件处理(四)

目录 一、前言 二、高级主题: fcntl和mmap 1、fcntl 2、mmap 3、Using mmap 一、前言 本文将简单介绍Linux文件和目录&#xff0c;以及如何操作它们&#xff08;如何创建文件、打开、读、写和关闭&#xff0c;程序如何操作目录&#xff0c;如创建、扫描和删除目录等&…

池风水利用工具

引用 这篇文章的目的是介绍一种基于内核态内存的越界写入通用利用技术和相关工具复现. 文章目录引用简介分析调试分析漏洞利用工具使用方法工具使用效果相关引用参与贡献简介 笔者的在原作者池风水利用工具(以下简称工具)基础上进行二次开发,新增了全自动获取内核调试模块符号…

QT-QTableWidget中的cell和item的区别

文章目录QTableWidget中单击一个单元格响应不同的函数&#xff1a;cell和item的区别&#xff1a;单击单元格响应自定义函数我的错误思路&#xff1a;已剪辑自: https://blog.csdn.net/CCLasdfg/article/details/114691478 QTableWidget中单击一个单元格响应不同的函数&#xf…

【服务器搭建】教程三:怎样购买域名并怎样进行域名解析 来啦

前言 购买一台服务器&#xff0c;再来个域名&#xff0c;搭建一个自己的个人博客网站&#xff0c;把一些教程、源码、想要分享的好玩的放到网站上&#xff0c;供小伙伴学习玩耍使用。我把这个过程记录下来&#xff0c;想要尝试的小伙伴&#xff0c;可以按照步骤&#xff0c;自己…

多云加速云原生数仓生态,华为与 HashData 联合打造方案

多云的兴起&#xff0c;源于用户应用对于基础设施、云服务功能、安全性等的差异化需求&#xff0c;用户希望根据需求将应用、数据因“云”制宜&#xff0c;实现业务的高度灵活性和高效性。这也直接驱动着云原生数据仓库等一批云原生应用的流行&#xff0c;以及存储等基础设施加…

为什么用公钥加密却不能用公钥解密?

一直以来我都在逃避写HTTPS。 毕竟。 HTTPS里名词太多。概念又巨繁琐。 实在是太难解释了&#xff0c;能不写我尽量不写。。。。 但为了让图解网络的知识体系尽量完整些。 今天&#xff0c;大家忍一忍。 我们就从对称加密和非对称加密聊起吧。 对称加密和非对称加密 小学…

Python学习笔记-语言基础

1.基础语法特点 1.1 注释 注释&#xff0c;是用于在代码中添加的标注性的文字&#xff0c;程序中并不会执行&#xff0c;知识用于告知程序员&#xff0c;该代码是干什么的&#xff0c;怎么用的等。 注释主要用于防止别人阅读代码是可以识别到代码的意图或者一段时间之后我们…

教程五 在Go中使用Energy创建跨平台GUI - 执行开发者工具方法

教程-示例-文档 介绍 本文介绍在Energy中如何执行开发者工具方法 开发者工具方法&#xff0c;可以设置浏览器头&#xff0c;模拟仿真设备等. 使用方式 字典对象创建 cef.NewCefDictionaryValue() 字典对象是keyvalue方式, chromium定义的字典名称和对应的值&#xff0c;通…

APOLLO UDACITY自动驾驶课程笔记——感知、预测

1、计算机视觉 无人驾驶车有四个感知世界的核心任务&#xff1a;检测——指找出物体在环境中的位置&#xff1b;分类——指明确对象是什么&#xff1b;跟踪——指随时间的推移观察移动物体&#xff1b;语义分割——将图像中的每个像素与语义类别进行匹配如道路、汽车、天空。 …

Adb找不到设备解决方法

有时候使用adb devices 却找不到设备&#xff0c;而用一些第三方的软件却可以找的到&#xff0c; 除了没有打开USB调试模式&#xff0c;或者没装驱动&#xff0c;数据线有问题&#xff0c;操作系统是精简版系统外&#xff0c; 一般是因为设备的安卓版本太高&#xff0c;导致a…

操作系统内存管理-01分段

前言 本文讲述本文博主在学习80386下 window下段式内存管理。 内存管理往往需要软硬件结合进行管理&#xff0c;CPU定制一套官方规范&#xff0c;要求操作系统按要求实现某些操作即可。本文所述的CPU 分段规范 在intel 第三卷 第三章。 分段概述 我们知道每一个程序会被赋予…

可自定义评教系统(教学质量评估系统)设计与实现(SSM)毕业论文+设计源码+mysql文件

目 录 摘要 I Abstract II 第1章 绪论 1 1.1 课题背景 1 1.2 研究意义 1 1.3 国内外研究现状分析 2 1.3.1 评教指标的过于简单化 2 1.3.2 评教指标的权重过于平均 2 1.3.3 评教结果的可信度和缺乏科学的数据分析 2 1.4 系统开发的主要目标和内容 3 1.4.1 开发目标 3 1.4.2 开发…

C++ 内存模型

1、内存分区模型 C程序在执行时&#xff0c;将内存大方向划分为4个区域 代码区&#xff1a;存放函数体的二进制代码&#xff0c;由操作系统进行管理的&#xff08;所有代码包含中文注释&#xff09;全局区&#xff1a;存放全局变量和静态变量以及常量栈区&#xff1a;由编译器自…

使用 Clion (cmake) 运行FreeRTOS

这里使用的是 f4071. 先用STM32CubeMX 建立一个可以运行的stm32项目&#xff0c;toolchain选择 SW4STM322. 官网下载源码www.freertos.org我这里下载的是FreeRTOSv202112.00.zip 解压缩后得到需要以下几个文件夹或档案FreeRTOS/Source下的所有的 .c 档案FreeRTOS/Source/includ…

【附源码】计算机毕业设计JAVA在线图书超市

【附源码】计算机毕业设计JAVA在线图书超市 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JAVA mybati…

算法刷题打卡第28天:省份数量---广度优先搜索

省份数量 难度&#xff1a;中等 有 n 个城市&#xff0c;其中一些彼此相连&#xff0c;另一些没有相连。如果城市 a 与城市 b 直接相连&#xff0c;且城市 b 与城市 c 直接相连&#xff0c;那么城市 a 与城市 c 间接相连。 省份 是一组直接或间接相连的城市&#xff0c;组内不…

BP神经网络PID控制从Simulink仿真到PLC控制实现(含博途PLC完整SCL源代码)

单神经元自适应PID控制博途PLC完整源代码,请参看下面的文章链接: 博途PLC单神经元自适应PID控制_RXXW_Dor的博客-CSDN博客_单神经元pid控制1、单神经元作为构成神经网络的基本单位,具有自学习和自适应能力,且结构简单易于计算,传统的PID具有结构简单、调整方便和参数整定…

数据结构 | 树和二叉树的基本概念和性质【考点精析】

树和二叉树&#x1f333;树&#x1f343;树的概念&#x1f343;树的相关概念&#x1f343;树的性质和常用公式总结 ⭐⭐⭐⭐⭐&#x1f343;树的表示&#x1f343;树在实际中的运用&#xff08;表示文件系统的目录树结构&#xff09;&#x1f333;二叉树&#x1f343;二叉树的概…