IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> Unity3D 解决检测碰撞某类物体的一种方法 -> 正文阅读

[游戏开发]Unity3D 解决检测碰撞某类物体的一种方法

目录

前言

启发

实现前的失败案例

实现

总结

思考?

后言


前言

????????2022年十月份报名参加了Unity和Bilibili联合举办的NewbiesJam游戏开发挑战。在处理不同物体之间的碰撞逻辑时,由于自身知识浅薄,选择了使用Tag去判断触碰了哪一个物体,这就导致了随着物体类型的增加,不单是Tag,写在OnColliderEnter、OnColliderExit、OnColliderStay等等等等方法里面的语句也会越来越庞大臃肿,并且一旦物体的Tag没有进行标识,编写的碰撞逻辑就会失效。

? ? ? ? 在学习 《Unity3D?网络游戏实战(第2版)》这一本书时,Server在分发接收到的网络消息时,会根据消息的协议名,通过反射的方式从MsgHandler获取与协议名同名的方法,在自定义需要传入的object[]类型的参数列表parameters后,通过Invoke(null,parameters)的方式传入参数并且执行方法。

服务端

NetManager分发消息

public class NetManager
{

    public static void ReadClient()
    {
        ....

        //假设收到的协议消息
        MsgBase msg = new MsgMove();

        //从MsgHandler获取和协议同名的方法
        MethodInfo mi = typeof(MsgHandler).GetMethod(msg.protoName);

        //定义需要传入的参数列表
        object[] o = {state,msg}

        //执行同名方法,并传入参数
        mi?.Invoke(null,o)

        ....
    }

}

MsgHandler消息处理

public partial class MsgHandler
{
    public static void MsgMove(SocketState state,MsgBase msgBase)
    {
        //向下转型还原为MsgMove类型
        MsgMove msg = (MsgMove)msgBase;

        //通过msg的内容执行其他逻辑
        ........
    }
}

启发

? ? ? ? 在检测物体之间的碰撞时,一般都是两个挂载有脚本的物体进行检测,那么是否可以在OnCollisionEnter方法中,一旦某个物体进入了Collider,就尝试检测这类物体是否是自己要监听的类型,通过GetComponent方式尝试获取物体的某一类脚本,如果获取的结果不为空,则触发相应的方法。


实现前的失败案例

? ? ? ? 假设有两类物体。一类物体挂载类型为人物(BaseCharactor)的脚本,并且带有Rigidbody。另一类物体挂载类型为格子(Node)的脚本。当人物走到某类格子上时,双方都会触发OnCollisionEnter(或OnTriggerEnter)

以人物(BaseCharactor)为视角

? ? ? ? 现在有这样一个情景,玩家控制的人物(CtrlPlayer,继承于BaseCharactor)走到陷阱类型(TrapNode,继承于Node)的格子上时会死亡,走到泥地类型(MudNode,继承与Node)的格子上时速度会变慢。

? ? ? ? 为了检测双方,我添加了三个Tag,分别是CtrlPlayer、TrapNode和MudNode。在Node物体进入玩家的Collider的时候,触发了玩家的OnCollisionEnter,此时如果要实现触碰到不同格子实现不同的方法,我一开始是这么写的:

public class CtrlPlayer:BaseCharactor
{
    private void OnCollisionEnter(Collision collision)
    {
        if(collision.gameObject.tag == "TrapNode")
        {
            //玩家死亡
            .....
        }else if(collision.gameObject.tag == "MudNode")
        {
            //玩家速度变慢
            ....
        }
    }
}

? ? ? ? 典中典。

? ? ? ? 当我准备继续加不同类型的角色和格子的时候,我就开始发现Tag真的越来越多了,在OnCollisionEnter里面的if-else语句也越来越多。所以现在存在两个问题:

  1. Tag太多
  2. if-else语句太多。

解决Tag太多

? ? ? ? 后来我改成了只使用两个Tag,BaseCharactor和Node。然后我的逻辑就变成了这样:

public class CtrlPlayer:BaseCharactor
{
    private void OnCollisionEnter(Collision collision)
    {
        if(collision.gameObject.tag == "Node")
        {
           OnNodeEnter(collision.transform.GetComponent<Node>());
        }
    }

    private void OnNodeEnter(Node node)
    {
        if(node.NodeType == Node.NodeType.Trap)
        {
            //玩家死亡
            ....
        }
        else if(node.NodeType == Node.NodeType.Mud)
        {
            //玩家移速变慢
            ...
        }
    }
}

public class Node:Monobehaviour
{
    public enum NodeType
    {
        Normal,
        Trap,
        Mud,
        ....
    }
}

? ? ? ? 新典中典。

? ? ? ? 这个时候Tag确实减少了,但是依旧没有减少if-else语句,并且甚至在添加新的Node或Charactor的时候需要维护NodeType。于是在这个时候,我灵光一闪想到了前面所提到的使用反射,通过名字获取方法并且调用


实现

? ? ? ? 这个是我在开发挑战中使用的方法,这一方法在我看来仍然不够好,但已经非常足够我的项目去使用,并且解决了一定关于if-else的问题

?以人物(BaseCharactor)为视角

??????由于项目很小,我没有去考虑性能的开销问题,因此我把检测格子的逻辑直接写在了BaseCharactor里,如果希望一些角色不去检测格子(比如场景中不断飞,用来装饰的鸟),在鸟的类中重写OnCollisionEnter就好了。

public class BaseCharactor:Monobehaviour
{
    protected virtual void OnCollisionEnter(Collision collision)
    {    
        //声明Node对象
        Node node;
        //获取碰撞对象身上挂载的Node组件
        node = collision.gameobject.GetComponent<Node>();

        //如果结果不为空,说明对象身上挂载有Node类型脚本
        if(node != null)
        {
            //获取脚本类型名字,如果是TrapNode名字就是TrapNode,如果是MudNode名字就是MudNode
            string name = node.GetType().Name;

            //获取同名方法
            MethodInfo mi = this.GetType().GetMethod($"On{name}Enter");
            
            //定义需要传给方法的参数,参数包括了参与碰撞的双方
            object[] o = {this,node};

            //如果方法存在,则执行
            mi?.Invoke(this,o);
        }
    }

    public void OnTrapNodeEnter(BaseCharactor actor,Node node)
    {
        //角色死亡逻辑
        ...
    }
    public void OnMudNodeEnter(BaseCharactor actor,Node node)
    {
        //角色移动速度变慢
        ...
    }
}

? ? ? ? 通过尝试获取进入物体的Node脚本,如果获取到了就通过Node类型脚本的名字,去执行On<脚本名字>Enter方法。? ? ? ??

????????这一方法不仅直接不使用Tag去检测,并且直接从表层面上消除了if-else语句,看上去舒心很多。并且子类可以重写检测方法,实现不同的逻辑。


总结

? ? ? ? 这一方法适用于检测的大类别不怎么变化的情况。我的作品中,格子(Node)只需要检测人物(BaseCharactor),人物也只需要检测格子,只需要考虑了不同的人物走到不同的格子所触发的逻辑。如果检测的类别越来越多,比如还需要检测是否触碰了物体(Item),是否触碰到了场景中可以交互的建筑(Architecture),这个时候就需要往OnCollisionEnter等检测方法添加新的逻辑了,会违反开闭原则。

? ? ? ? 使用这一方法后,在检测物体触发碰撞时候,只需要类中添加方法On<想要检测对象的类型>Enter就好了。假设旱鸭子(Landlubber,继承BaseCharactor)走到了水格子(WaterNode,继承于Node),只需要在Landlubber类中添加方法OnWaterNodeEnter,就会在进入水格子的时候触发OnWaterNodeEnter。由于使用了mi.?Invoke()方式去触发方法,会在触发前判断是否有这一方法存在,否则就不触发。所以当检测到了WaterNode,类中没有OnWaterNodeEnter这一方法也没有关系。


思考?

? ? ? ? 关于由于如果检测的大类频繁地修改的话,会违反开闭原则的事情,有一个想法是给物体声明一个检测列表List<Type>,在开始时把需要检测的物体的脚本基类添加在里面就好了。在OnCollisionEnter等检测方法中把List遍历一遍就知道进入的物体是不是希望被检测的了。但是这样的话,mi.Invoke()传入的参数就不能够变化了。

? ? ? ? 另外,使用反射这一方式是否会有什么不安全的情况吗?我对C#的了解还是过于浅薄,只是这一方式确实对我的游戏开发起到了帮助。


后言

????????我的NewbiesJam开发挑战的作品《Shadow》链接在这里:

? ? ? ? 【萌新开发挑战 NewbiesJam】 作品《Shadow》试玩视频_哔哩哔哩bilibili

????????因为只有一个人并且只有七天的时间开发,所以无论是美术还是玩法都十分的简单(好像看上去甚至和挑战主题不搭边,这就要看评委怎么理解了哈哈哈哈哈)。简介还有试玩链接可以试玩(Unity的云渲染有时候会卡住玩不了),欢迎来试试我的游戏。

? ? ? ? 欢迎提出问题和修改意见!

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-10-31 12:31:34  更:2022-10-31 12:32:37 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/17 6:13:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码