首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何实现RTS游戏中鼠标在屏幕边缘时移动视角功能

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

作者头像
CoderZ
发布于 2022-12-26 13:06:51
发布于 2022-12-26 13:06:51
1.4K01
代码可运行
举报
运行总次数:1
代码可运行

🧨 Preface

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

移动视角

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

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

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

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

屏幕边缘

代码部分:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//边缘宽度
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
代码运行次数:0
运行
AI代码解释
复制
//判断光标是否处于屏幕边缘
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
代码运行次数:0
运行
AI代码解释
复制
if (IsMouseOnEdge(out Vector2 direction))
{
    ts += (Vector3.right * direction.x + Vector3.forward * direction.y) * mouseMovementSensitivity;
}

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//活动区域限制
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
代码运行次数:0
运行
AI代码解释
复制
#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
代码运行次数:0
运行
AI代码解释
复制
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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Unity 如何实现游戏中技能的扇形攻击范围
假设人物A向正前方释放一个技能,攻击范围为一个扇形,如何判断人物B是否在该范围内受到攻击。
CoderZ
2022/08/29
2K0
Unity 如何实现游戏中技能的扇形攻击范围
Unity 如何实现游戏中技能的矩形攻击范围
假设人物A向正前方释放一个技能,攻击范围为一个矩形,如何判断人物B是否在该范围内受到攻击。
CoderZ
2022/08/29
7710
Unity 如何实现游戏中技能的矩形攻击范围
Unity Metaverse(三)、Protobuf & Socket 实现多人在线
•根据语法规则编写.proto文件;•通过编译工具protoc.exe将.proto文件编译成.cs文件;
CoderZ
2022/08/29
1.3K0
Unity Metaverse(三)、Protobuf & Socket 实现多人在线
PUN ☀️六、机器人基础设置:运动、相机、攻击与生命值
为机器人添加 CharacterController,调整Center等,属性如下:
星河造梦坊官方
2024/08/15
1180
PUN ☀️六、机器人基础设置:运动、相机、攻击与生命值
[Unity3d]虚拟3D汽车展示项目
今天完善成了虚拟3D汽车展示项目的部分功能,虽然用的汽车模型有点粗糙,但感觉还不错,下面我就贴下源码供初学者学习!
py3study
2020/01/14
1.1K0
MMORPG游戏开发实战(一)
新建目录工程 关于工程的文件夹创建 代码注释修改 可以百度,这种代码不需要记忆。一大堆,直接用就可以 using System; using System.Collections; using System.IO; using UnityEngine; using UnityEditor; public class ScriptsCreat : UnityEditor.AssetModificationProcessor { public static void OnWillCreateAss
孙寅
2020/06/02
1K0
MMORPG游戏开发实战(一)
使用贝塞尔曲线制作迁徙图
贝塞尔曲线是图形学中非常重要的参数曲线,在此不做详细介绍,这里我们用到的是二次方公式:
CoderZ
2022/08/29
3480
使用贝塞尔曲线制作迁徙图
unity--实现新手引导功能 一:矩形镂空功能 三、新手引导的方法封装四、事件渗透五、完善优化
2、导入shader,创建两个材质,将两个shader拖到两个材质上。将材质拖动到Image组件的Material上。
酱紫安
2021/03/16
5.8K0
unity--实现新手引导功能
    





        一:矩形镂空功能 三、新手引导的方法封装四、事件渗透五、完善优化
Unity SKFramework框架(十四)、Extension 扩展函数
该部分是框架中使用this关键字给一些类型做的拓展函数,为了支持链式编程或记录、封装一些功能,内容会持续补充,本文给出其中部分示例。
CoderZ
2022/08/29
6800
Unity SKFramework框架(十四)、Extension 扩展函数
鼠标控制物体旋转、移动、缩放(Unity3D)
一、前言 Unity3D对于鼠标操作物体的旋转、移动、缩放的功能点使用的比较多。 今天就分享如何使用Unity实现鼠标对于物体的旋转、移动、缩放。 效果图: 二、知识点 Input.GetMouseButton(0) 获取鼠标输入,参数为一个int值 为0的时候获取的是左键 Input.GetMouseButton(1) 为1的时候获取的是右键 Input.GetMouseButton(2) 为2的时候获取的是中键(就是那个滑轮) Input.GetMouseButton 鼠标按压 I
恬静的小魔龙
2022/08/07
4.8K1
鼠标控制物体旋转、移动、缩放(Unity3D)
unity3d 第三人称视角摄像机(线性锁视域)
private const float radian = Mathf.PI / 180.0f; //1弧度的浮点数
用户5875740
2019/07/22
1.4K0
unity3d:CatmullRom采样曲线,沿着曲线移动
立羽
2023/08/24
3050
unity3d:CatmullRom采样曲线,沿着曲线移动
Unity下如何实现低延迟的全景RTMP|RTSP流渲染
Unity3D可以用于创建各种类型的的应用程序,包括虚拟现实、培训模拟器等。以下是一些可以使用Unity3D全景播放的场景:
音视频牛哥
2023/07/27
3940
Unity下如何实现低延迟的全景RTMP|RTSP流渲染
Unity 从UI中拖拽对象放置并拖动[通俗易懂]
需求:点击UI,在场景中生成3D对象,对象跟随鼠标移动,放置后可再次拖拽对象,改变其位置。做了一个小Demo,如下图所示:
全栈程序员站长
2022/09/10
3K0
Unity 从UI中拖拽对象放置并拖动[通俗易懂]
Unity第三人称视角解决方案
镜头跟随 在实现第三人称时,镜头问题困扰了我一整天,参考了官方的脚本 SmoothFollow,虽然能实现镜头跟在人物身后,但是发现几个问题。 脚本实现太繁琐,有几个属性目前根本就用不到。 人物旋转时不能控制摄像机跟着旋转,也就是说,不能让镜头一直跟在人物身后。 脚本代码如下: public class SmoothFollow : MonoBehaviour { // The target we are following [SerializeField] private Transfo
xferris
2018/06/01
2.7K0
unity摄像机控制篇
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
bering
2019/12/03
1.4K0
Unity 【Wheel Collider】实现游戏中的车具控制
Wheel Collider专门用于模拟车具轮胎的物理属性,内置了碰撞检测、轮胎摩擦模型等,可以用于实现车具的控制。
CoderZ
2022/08/29
1.3K0
Unity 【Wheel Collider】实现游戏中的车具控制
Unity LineRenderer 根据圆的中心、半径、朝向在三维空间中画圆
在三维空间中生成一个圆,需要知道圆的中心点位置、圆的半径以及圆的朝向这三个参数,通过这三个参数求得在圆上的点坐标,最终通过LineRenderer组件将圆绘制出来:
CoderZ
2022/08/29
1.6K0
Unity LineRenderer 根据圆的中心、半径、朝向在三维空间中画圆
Unity3D基础项目(一):打箱子升级版之豪华特效
最终效果图 半成品效果图: cube.gif 1、开始阶段,场景搭建,资源导入 场景搭建 加群获取资源 2、添加第一个脚本,创建我们要打击的墙体 public class CreatBox : MonoBehaviour { void Start () { for (int i = 0; i < x; i++) // 控制横排为X { for (int j = 0; j < y; j++) // 控制竖排为Y, 当横排为0时,垂直方块个数为8个 { GameOb
孙寅
2020/06/02
9690
【Unity3D】官方自带人物控制器研究
5.0.0f4版本的官方自带资源包http://pan.baidu.com/s/1o8Ujrxo
恬静的小魔龙
2020/03/10
4.8K0
推荐阅读
相关推荐
Unity 如何实现游戏中技能的扇形攻击范围
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档