Unity 如何实现框选游戏战斗单位

news2025/7/11 5:38:05

文章目录

  • 🍔 Preface
  • ✨ 如何在屏幕坐标系内绘制框选框
  • 🎉 根据框选范围定位其在世界坐标系中对应的区域
  • 🥇 在该区域内进行物理检测


🍔 Preface

本文简单介绍如何实现即时战略游戏中框选战斗单位的功能,如图所示:

框选战斗单位

🍺 实现思路:

本文将该功能的实现拆分为以下部分:

  • 在屏幕坐标系中绘制框选范围;
  • 根据框选范围定位其在世界坐标系中对应的区域;
  • 在该区域内进行物理检测。

✨ 如何在屏幕坐标系内绘制框选框

使用Line Renderer光线渲染器组件来进行范围绘制,当鼠标按下时,可以获得框选范围的起始点,鼠标持续按下时,鼠标位置则是框选范围的结束点,根据这两个点的坐标可以求得另外两个顶点的坐标,如图所示:

框选范围
首先设置Line Renderer光线渲染器的属性:

Line Renderer

  • Enable:默认设为false,当鼠标按下时将其设为true;
  • Loop:设为true,为了让第三个顶点与起始点相连形成闭环;
  • Size:设为4,框选范围有4个顶点;
  • Width:设为0.001即可,线框不需要很粗,可适当调整;

代码部分:

using UnityEngine;
using SK.Framework;
using System.Collections.Generic;

public class Example : MonoBehaviour
{
    //光线渲染器组件
    private LineRenderer lineRenderer;
    //屏幕坐标系起始点
    private Vector3 screenStartPoint;
    //屏幕坐标系结束点
    private Vector3 screenEndPoint;
    
    private void Start()
    {
        //获取光线渲染器组件
        lineRenderer = GetComponent<LineRenderer>();
    }

    private void Update()
    {
        //鼠标按下
        if (Input.GetMouseButtonDown(0))
        {
            //激活光线渲染器
            lineRenderer.enabled = true;
            //屏幕坐标系起始点
            screenStartPoint = Input.mousePosition;
            screenStartPoint.z = 1;
        }
        //鼠标持续按下
        if (Input.GetMouseButton(0))
        {
            //屏幕坐标系结束点
            screenEndPoint = Input.mousePosition;
            screenEndPoint.z = 1;
            //求得框选框的另外两个顶点的位置
            Vector3 point1 = new Vector3(screenEndPoint.x, screenStartPoint.y, 1);
            Vector3 point2 = new Vector3(screenStartPoint.x, screenEndPoint.y, 1);
            
            //接下来使用光线渲染器画出框选范围
            lineRenderer.SetPosition(0, Camera.main.ScreenToWorldPoint(screenStartPoint));
            lineRenderer.SetPosition(1, Camera.main.ScreenToWorldPoint(point1));
            lineRenderer.SetPosition(2, Camera.main.ScreenToWorldPoint(screenEndPoint));
            lineRenderer.SetPosition(3, Camera.main.ScreenToWorldPoint(point2));
        }
        //鼠标抬起
        if (Input.GetMouseButtonUp(0))
        {
            //取消光线渲染器
            lineRenderer.enabled = false;
        }
    }
}

如图所示,已经实现框选范围的绘制:

绘制框选范围

🎉 根据框选范围定位其在世界坐标系中对应的区域

该部分的实现主要依靠物理射线检测,在鼠标位置发出射线,检测与地面的碰撞点,首先为Plane地面设置Layer层级:

地面层级
在鼠标按下时根据射线检测信息确定世界坐标系中的起始点:


//鼠标按下
if (Input.GetMouseButtonDown(0))
{
    //激活光线渲染器
    lineRenderer.enabled = true;
    //屏幕坐标系起始点
    screenStartPoint = Input.mousePosition;
    screenStartPoint.z = 1;
    //射线检测
    if (Physics.Raycast(mainCamera.ScreenPointToRay(Input.mousePosition), out hit, 1 << LayerMask.NameToLayer("Ground")))
    {
        //世界坐标系起始点
        worldStartPoint = hit.point;
    }
}

在鼠标抬起时根据射线检测信息确定世界坐标系中的结束点:


//鼠标抬起
if (Input.GetMouseButtonUp(0))
{
    //取消光线渲染器
    lineRenderer.enabled = false;

    //射线检测
    if (Physics.Raycast(mainCamera.ScreenPointToRay(Input.mousePosition), out hit, 1 << LayerMask.NameToLayer("Ground")))
    {
        //世界坐标系结束点
        worldEndPoint = hit.point;
    }
}

🥇 在该区域内进行物理检测

该部分用的的核心API:

Physics OverlapBox

可以理解为创建一个碰撞盒来检测该范围内的碰撞体,首先计算出该API需要传入的参数:

  • center:该盒子的中心点;
  • halfExtents:该盒子长宽高的一半。
//盒子中心点
Vector3 center = new Vector3((worldEndPoint.x + worldStartPoint.x) * .5f, 1f, (worldEndPoint.z + worldStartPoint.z) * .5f);
//盒子长宽高的一半
Vector3 halfExtents = new Vector3(Mathf.Abs(worldEndPoint.x - worldStartPoint.x) * .5f, 1f, Mathf.Abs(worldEndPoint.z - worldStartPoint.z) * .5f);

有了这两个参数,调用该API可以获得该区域内的所有碰撞体,遍历判断碰撞体身上如果包含指定的组件,则将其选中,这里使用Outline高亮组件来表示:

//盒子中心点
Vector3 center = new Vector3((worldEndPoint.x + worldStartPoint.x) * .5f, 1f, (worldEndPoint.z + worldStartPoint.z) * .5f);
//盒子长宽高的一半
Vector3 halfExtents = new Vector3(Mathf.Abs(worldEndPoint.x - worldStartPoint.x) * .5f, 1f, Mathf.Abs(worldEndPoint.z - worldStartPoint.z) * .5f);
//检测到盒子内的碰撞器
Collider[] colliders = Physics.OverlapBox(center, halfExtents);

for (int i = 0; i < colliders.Length; i++)
{
    var collider = colliders[i];
    var outline = collider.GetComponent<Outline>();
    if (outline != null)
    {
        outline.enabled = true;
    }
}

如图所示,我们已经实现了基本的框选功能:

框选功能
在框选时,还需要清除上一次框选的内容,因此我们使用一个List列表来记录当前框选的战斗单位,框选前遍历该列表来清除框选记录,完整代码如下:

public class Example : MonoBehaviour
{
    //光线渲染器组件
    private LineRenderer lineRenderer;
    //屏幕坐标系起始点
    private Vector3 screenStartPoint;
    //屏幕坐标系结束点
    private Vector3 screenEndPoint;
    //主相机
    private Camera mainCamera;
    //碰撞信息
    private RaycastHit hit;
    //世界坐标系起始点
    private Vector3 worldStartPoint;
    //世界坐标系结束点
    private Vector3 worldEndPoint;
    //框选记录列表
    private List<Outline> list = new List<Outline>();

    private void Start()
    {
        //获取光线渲染器组件
        lineRenderer = GetComponent<LineRenderer>();
        //获取主相机
        mainCamera = Camera.main != null ? Camera.main : FindObjectOfType<Camera>();
    }

    private void Update()
    {
        //鼠标按下
        if (Input.GetMouseButtonDown(0))
        {
            //激活光线渲染器
            lineRenderer.enabled = true;
            //屏幕坐标系起始点
            screenStartPoint = Input.mousePosition;
            screenStartPoint.z = 1;
            //射线检测
            if (Physics.Raycast(mainCamera.ScreenPointToRay(Input.mousePosition), out hit, 1 << LayerMask.NameToLayer("Ground")))
            {
                //世界坐标系起始点
                worldStartPoint = hit.point;
            }
        }
        //鼠标持续按下
        if (Input.GetMouseButton(0))
        {
            //屏幕坐标系结束点
            screenEndPoint = Input.mousePosition;
            screenEndPoint.z = 1;
            //求得框选框的另外两个顶点的位置
            Vector3 point1 = new Vector3(screenEndPoint.x, screenStartPoint.y, 1);
            Vector3 point2 = new Vector3(screenStartPoint.x, screenEndPoint.y, 1);
            
            //接下来使用光线渲染器画出框选范围
            lineRenderer.SetPosition(0, Camera.main.ScreenToWorldPoint(screenStartPoint));
            lineRenderer.SetPosition(1, Camera.main.ScreenToWorldPoint(point1));
            lineRenderer.SetPosition(2, Camera.main.ScreenToWorldPoint(screenEndPoint));
            lineRenderer.SetPosition(3, Camera.main.ScreenToWorldPoint(point2));
        }
        //鼠标抬起
        if (Input.GetMouseButtonUp(0))
        {
            //取消光线渲染器
            lineRenderer.enabled = false;

            //首先清除上一次的框选记录
            for (int i = 0; i < list.Count; i++)
            {
                list[i].enabled = false;
            }
            list.Clear();

            //射线检测
            if (Physics.Raycast(mainCamera.ScreenPointToRay(Input.mousePosition), out hit, 1 << LayerMask.NameToLayer("Ground")))
            {
                //世界坐标系结束点
                worldEndPoint = hit.point;
                //盒子中心点
                Vector3 center = new Vector3((worldEndPoint.x + worldStartPoint.x) * .5f, 1f, (worldEndPoint.z + worldStartPoint.z) * .5f);
                //盒子长宽高的一半
                Vector3 halfExtents = new Vector3(Mathf.Abs(worldEndPoint.x - worldStartPoint.x) * .5f, 1f, Mathf.Abs(worldEndPoint.z - worldStartPoint.z) * .5f);
                //检测到盒子内的碰撞器
                Collider[] colliders = Physics.OverlapBox(center, halfExtents);

                for (int i = 0; i < colliders.Length; i++)
                {
                    var collider = colliders[i];
                    var outline = collider.GetComponent<Outline>();
                    if (outline != null)
                    {
                        list.Add(outline);
                        outline.enabled = true;
                    }
                }
            }
        }
    }
}

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

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

相关文章

NeRF-SLAM 学习笔记

NeRF-SLAM: Real-Time Dense Monocular SLAM with Neural Radiance Fields 主页&#xff1a;https://deepai.org/publication/nerf-slam-real-time-dense-monocular-slam-with-neural-radiance-fields 论文&#xff1a;https://arxiv.org/pdf/2210.13641.pdf Code&#xff1a;…

RabbitMQ复习笔记

文章目录RabbitMQ一、MQ引言1.1、什么是MQ1.2、MQ有哪些1.3、不同MQ特点二、RabbitMQ 的引言2.1、RabbitMQ2.2、RabbitMQ的安装2.2.1、下载2.2.2、下载的安装包2.2.3、安装步骤三、RabiitMQ 配置3.1、RabbitMQ 管理命令行3.2、web管理界面介绍3.2.1、overview概览3.2.2 Admin用…

手摸手利在idea中利用maven创建web项目

1. 下载maven&#xff0c;目前最新版本是3.8.6。在环境变量中创建MAVEN_HOME变量&#xff0c;并在PATH中添加。maven的运行依赖于java环境&#xff0c;这意味着在配置MAVEN前&#xff0c;应该已经在环境变量中配置有JAVA_HOME和PATH中配置好了JAVA相关环境。 2. 配置完毕后&…

力扣(LeetCode)84. 柱状图中最大的矩形 (C++)

朴素思想 朴素思想&#xff0c;找左右边界&#xff0c;依次乘以区间内最小上边界&#xff0c;取最大值。这个做法需要遍历左边界&#xff0c;对于每个左边界遍历右边界。因此需要 O(n2)O(n^2)O(n2) 的时间复杂度。 转换思路&#xff0c;确定上边界&#xff0c;找左右边界。这…

数据结构--循环链表

目录 1.为什么要有循环链表 2.定义 3.循环链表和单链表的图示对比 4.循环链表和单链表的代码对比 5.循环链表的操作 1.clist.h 2.clist.cpp 1.初始化plist 2.往plist中头部插入数字val 3.往plist中的尾部插入数字val 4.在plist中查找val值,找到返回该节点地址,失败返…

SpringAOP(2)-spring源码详解(七)

上篇文章说了spring的aop&#xff0c;在启动类加一个启动注解&#xff0c;注解点进源码可以看到注册了bean定义的后置处理器。每个切入点都会生成一个adviosr&#xff0c;他们会排序好依次注册。 他的顺序是before、after、afterRturning、afterThrowing 原创 SpringAOP&…

Mac解决鼠标滚轮反方向移动逻辑--Mos(又免费又好用哦~)

目录&#xff1a;&#x1f335;&#x1f335;&#x1f335;前言什么&#xff1f;Mac竟然需要鼠标&#xff1f;Mos &#xff0c;让你的鼠标丝般顺滑&#xff5e;❤️❤️❤️忙碌的敲代码也不要忘了浪漫鸭&#xff01;&#x1f335;&#x1f335;&#x1f335;前言 ✨你好啊&…

PHP连接外部服务器的MySQL参考教程

PHP连接AWS服务器 云服务器上安装MySQL参考教程 linux-安装MySQL&#xff1a;https://blog.csdn.net/xhmico/article/details/125197747云端服务器(linux版)部署mysql&#xff1a;https://blog.csdn.net/m0_51406695/article/details/123886966linux云服务器从零开始安装mysq…

你不能错过的【Python爬虫】测试2(完整源代码+架构+结果)

目录 一、关键工具包的环境以及版本二、架构展示三、各部分code3.1 yjs.py3.2 items.py3.3 middlewares.py3.4 pipelines.py3.5 settings.py四、结果4.1 控制台输出结果4.2 结果截图一、关键工具包的环境以及版本 scrapy:2.7.1版本(这里主要用到的工具包) 二、架构展示 三…

HyperLynx(三十二)高速串行总线仿真(四)

高速串行总线仿真&#xff08;四&#xff09; 在上节的基础上 1.通过导出到LineSim验证一个串行通道 2.快速眼图仿真 1.通过导出到LineSim验证一个串行通道 接下来&#xff0c;将导出网络到LineSim&#xff0c;并为电容分配SPICE模型。然后&#xff0c;将添加封装和串行电容…

WENO格式自动推导

简介 WENO格式是CFD中的一种高精度的数值格式。如果函数光滑&#xff0c;使用rrr个模板可以在空间上达到2r−12r-12r−1。如果出现间断&#xff0c;那么WENO格式退化为ENO格式。 理论上WENO可达任意阶精度&#xff0c;但是推导过程比较繁琐。本文使用Mathematica软件完成WENO…

一个简单的HTML网页(千与千寻电影) 大二学生网页设计与制作 电影主题网页制作

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 文章目录一、网页介绍一…

C++ 多态之虚函数表

虚函数表概述 C 的多态&#xff0c;使用动态绑定的技术,技术的核心是虚函数表&#xff08;简称虚表&#xff09;&#xff0c;每个包含了虚函数的类都包含一个虚表&#xff0c;虚表是属于类的&#xff0c;而不是属于某个具体的对象&#xff0c;一个类只需要一个虚表即可。同一个…

猴子也能学会的jQuery第十期——jQuery元素操作(下)

&#x1f4da;系列文章—目录&#x1f525; 猴子也能学会的jQuery第一期——什么是jQuery 猴子也能学会的jQuery第二期——引用jQuery 猴子也能学会的jQuery第三期——使用jQuery 猴子也能学会的jQuery第四期——jQuery选择器大全 猴子也能学会的jQuery第五期——jQuery样式操作…

树莓派系统安装,使用SSD/U盘启动centos

树莓派系统安装&#xff0c;使用SSD/U盘启动centos argon m2 外壳厂家资料 https://www.waveshare.net/wiki/PI4-CASE-ARGON-ONE-M.2 TF卡安装系统 如果是使用TF卡安装&#xff0c;则参考官方文档按步骤安装即可&#xff1a; https://www.waveshare.net/wiki/Raspberry_Pi_Do…

FAlphaBlend——Unreal中的插值助手

游戏开发中经常要进行插值处理&#xff0c;这个东西处理虽然不复杂&#xff0c;但如果自己重新做&#xff0c;还是要写额外的代码&#xff0c;好消息是&#xff0c;Unreal已经为我们提供了插值助手——FAlphaBlend。 我们以一个非常简单的应用场景来说明FAlphaBlend的用处&…

matplotlib详细教学

Matplotlib初相识 认识matplotlib Matplotlib是一个Python 2D绘图库&#xff0c;能够以多种硬拷贝格式和跨平台的交互式环境生成出版物质量的图形&#xff0c;用来绘制各种静态&#xff0c;动态&#xff0c;交互式的图表 一个最简单的绘图例子 matplotlib的图像都是画在对应…

Spring事务与事务传播机制

目录 1.事务的基本概念 2.Spring事务的实现 3.事务隔离级别 4.事务传播机制 1.事务的基本概念 关于事务的一些基础概念我已经在MYSQL中讲解过了&#xff0c;有不了解的可以移步至此篇文章&#xff1a;MySQL基础——数据库索引与事务_invictusQAQ的博客-CSDN博客 2.Spring…

基于AlexNet卷积神经网络的手写体数字倾斜校正系统研究-附Matlab代码

⭕⭕ 目 录 ⭕⭕✳️ 一、引言✳️ 二、AlexNet 网络✳️ 三、实验验证✳️ 3.1 实验数据集✳️ 3.2 数据训练✳️ 3.3 手写体倾斜数字校正结果✳️ 四、参考文献✳️ 五、Matlab代码获取✳️ 一、引言 手写体数字识别是光学字符识别(Optical Character&#xff32;ecognition…

破圈的《张朝阳的物理课》,开启“知识突围”的搜狐视频

在互联网耕耘二十多年的搜狐&#xff0c;正在焕发出新的活力。 从搜狐最近公布的2022年第三季度财报来看&#xff0c;营收1.85亿美元&#xff0c;其中在线游戏业务实现收入1.49亿美元&#xff0c;广告收入环比增长3%达到2600万美元。同时&#xff0c;净亏损好于此前预期。 对…