基础GamePlay知识-碰撞检测

2024-03-08 23:36

本文主要是介绍基础GamePlay知识-碰撞检测,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

将会持续更新gameplay的一些基础知识,一同学习。

扇形检测

扇形检测是Gameplay里面很常见的场景。比如荒野乱斗中,大部分的近战角色都是扇形攻击。在扇形范围内就认为是受击。
扇形检测只有两个参数,一个是扇形的角度一个是扇形的半径大小。
在这里插入图片描述

效果

在这里插入图片描述

获取鼠标朝向

技能必然是和鼠标朝向一致的,所以学习检测务必先学一下怎么得到鼠标朝向,以及得到朝向对应的旋转角度。
思路是利用Camera.main.ScreenPointToRay(Input.mousePosition)方法,能够得到相机朝向屏幕空间下某点的射线,从而得到射线的碰撞点,然后计算角色朝向碰撞点的方向,这时候碰撞点的横坐标x和轴坐标z是已知的,所以用反三角函数Atan2可以得到旋转到碰撞点的旋转角度。
值得一提的是Camera.main.ScreenPointToRay(Input.mousePosition)在诸如用户注视某个物品/3dui的交互上面都可以使用。

 void Update(){ray = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hit;if (Physics.Raycast(ray, out hit)){//射线检测的是平面点的位置,实际需要的是平行于平面的方向,所以修改y坐标Vector3 horizontalPoint = new Vector3(hit.point.x, transform.position.y, hit.point.z);Debug.DrawLine(transform.position, horizontalPoint, Color.red);//方便观察skillDirection = (horizontalPoint - transform.position).normalized;//z轴取反是因为unity的旋转api是顺时针旋转的skillAngle = Mathf.Atan2(-skillDirection.z, skillDirection.x) * Mathf.Rad2Deg;}}

尝试利用画出一个扇形

为了方便我们看检测的结果,先实现一个绘制扇形的工具将技能范围画出来,这里提供使用Debug.DrawLine和使用LineRender两种办法。需要注意如果用LineRender的SetPostition方法,需要在组件里面设置好Position数组的大小,防止出现数组越界的报错。
在这里插入图片描述
利用LineRender和Debug.DrawLine的思路是一样的,先绘制扇形的中心点以及左边界点,再从左边界点往另一个边界以某个角度间隔绘制一个个点,连接起来就是一个圆弧

private void DrawFan_DebugDrawLine(float radius, int euler){int segments = 100; //圆弧那一段需要用多少个点来表示float deltaAngle = euler / segments;Vector3 forward = transform.forward;Vector3[] vertices = new Vector3[segments + 2];vertices[0] = transform.position;for (int i = 1; i < vertices.Length; i++){float curAngle = -euler / 2 + deltaAngle * (i - 1) + skillAngle;//从-1/2扇形角度开始绘制,每次偏移deltaAngleVector3 pos = Quaternion.Euler(0f, curAngle, 0f) * forward * radius + transform.position;vertices[i] = pos;}// 画圆弧for (int i = 1; i < vertices.Length - 1; i++){Debug.Log(vertices[i]);Debug.DrawLine(vertices[i], vertices[i + 1], showColor);}// 画两条边Debug.DrawLine(vertices[0], vertices[vertices.Length - 1], showColor);Debug.DrawLine(vertices[0], vertices[1], showColor);}/// <summary>/// 利用LinerRender绘制扇形/// </summary>private void DrawFan_LineRender(float radius, int euler){ResetLinerRenderPoints();m_LineRenderer.startColor = showColor; //碰撞的时候会切换展示的颜色m_LineRenderer.endColor = showColor;m_LineRendererPoints.Add(transform.position);//每一度一个点,绘制思路和debug.DrawLine相同for(int angles = -euler/2; angles <= euler/2; angles++){m_LineRendererPoints.Add(Quaternion.Euler(0, angles + skillAngle, 0) * transform.right * radius + transform.position);}m_LineRenderer.SetPositions(m_LineRendererPoints.ToArray());}

扇形和点的碰撞检测

判断某个点在扇形内的办法
距离判断:
点和玩家的距离小于扇形半径。
角度判断:
设玩家到受击角色的向量为a,玩家技能朝向为b
方法一:判断a和b所形成的角度小于1/2的扇形夹角。
只需要拿到a和单位向量和b的单位向量进行点积,就能够得到a和b夹角的cos值,再利用Acos得到夹角大小。

设扇形左边界为left,扇形右边界为right
方法二:利用向量叉乘,如果a在扇形的左边界之外或扇形的右边界之外则不满足。
这里需要注意Unity的世界坐标系是左手系,叉乘的方向满足的是左手定则
a x left所得向量的y轴坐标大于0说明在左边界以左,
right x a所得向量的的y轴左边大于0说明在右边界以右

这里两个办法都实现一下

三角函数法

    public bool Dectect_ACos(Vector3 enemyPos){float distance = Vector3.Distance(transform.position, enemyPos);//距离超过检测半径if (distance > radius){return false;}Vector3 enemyDirection = (enemyPos - transform.position).normalized;//两个单位向量的点乘等于其夹角的余弦值float enemyAngle = Mathf.Acos(Vector3.Dot(skillDirection, enemyDirection)) * Mathf.Rad2Deg;//敌人朝向和技能朝向的夹角小于二分之一扇形角说明在扇形范围内if (enemyAngle <= angle / 2){return true;}return false;}

向量叉乘

    public bool Dectect_Cross(Vector3 enemyPos){float distance = Vector3.Distance(transform.position, enemyPos);//距离超过检测半径if (distance > radius){return false;}//扇形左边界Vector3 leftBound = Quaternion.Euler(0f, -angle / 2 + skillAngle, 0f) * transform.right;Debug.DrawLine(transform.position, transform.position + leftBound, Color.blue);//扇形右边界Vector3 RightBound = Quaternion.Euler(0f, angle / 2 + skillAngle, 0f) * transform.right;Debug.DrawLine(transform.position, transform.position + RightBound , Color.yellow);Vector3 enemyDir = enemyPos - transform.position;//注意左手系的叉乘是左手定则bool isLeft = Vector3.Cross(enemyDir, leftBound).y > 0 ? true : false; bool isRight = Vector3.Cross(RightBound, enemyDir).y > 0 ? true : false;Debug.Log("isLeft:" + isLeft + "isRight" + isRight);return !isLeft && !isRight;}

完整实现

挂载在角色身上的脚本,注意鼠标是利用射线检测,所以需要再角色下面放一个平面接收射线。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class FanDetector : MonoBehaviour
{public float radius; //扇形半径public int angle;  //扇形角度public Vector3 skillDirection;private Ray ray;private LineRenderer m_LineRenderer;private List<Vector3> m_LineRendererPoints;private Color showColor;public float skillAngle;// Start is called before the first frame updatevoid Start(){InitLineRender();}// Update is called once per framevoid Update(){GetSkillDirection(Color.green);//DrawFan_DebugDrawLine(radius, angle);DrawFan_LineRender(radius, angle);}/// <summary>/// 对外接口,敌人是否在技能范围内/// </summary>public bool Dectect(Vector3 enemyPos){bool isDecteced = Dectect_Cross(enemyPos);if(isDecteced){showColor = Color.red;return isDecteced;}showColor = Color.green;return isDecteced;}/// <summary>/// 利用Acos检测碰撞/// </summary>/// <param name="enemyPos"></param>/// <returns></returns>public bool Dectect_ACos(Vector3 enemyPos){float distance = Vector3.Distance(transform.position, enemyPos);//距离超过检测半径if (distance > radius){return false;}Vector3 enemyDirection = (enemyPos - transform.position).normalized;//两个单位向量的点乘等于其夹角的余弦值float enemyAngle = Mathf.Acos(Vector3.Dot(skillDirection, enemyDirection)) * Mathf.Rad2Deg;//敌人朝向和技能朝向的夹角小于二分之一扇形角说明在扇形范围内if (enemyAngle <= angle / 2){return true;}return false;}/// <summary>/// 利用叉乘检测/// </summary>/// <param name="enemyPos"></param>/// <returns></returns>public bool Dectect_Cross(Vector3 enemyPos){float distance = Vector3.Distance(transform.position, enemyPos);//距离超过检测半径if (distance > radius){return false;}//扇形左边界Vector3 leftBound = Quaternion.Euler(0f, -angle / 2 + skillAngle, 0f) * transform.right;Debug.DrawLine(transform.position, transform.position + leftBound, Color.blue);//扇形右边界Vector3 RightBound = Quaternion.Euler(0f, angle / 2 + skillAngle, 0f) * transform.right;Debug.DrawLine(transform.position, transform.position + RightBound , Color.yellow);Vector3 enemyDir = enemyPos - transform.position;//注意左手系的叉乘是左手定则bool isLeft = Vector3.Cross(enemyDir, leftBound).y > 0 ? true : false; bool isRight = Vector3.Cross(RightBound, enemyDir).y > 0 ? true : false;Debug.Log("isLeft:" + isLeft + "isRight" + isRight);return !isLeft && !isRight;}/// <summary>/// 获取鼠标朝向/// </summary>private void GetSkillDirection(Color color){ray = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hit;if (Physics.Raycast(ray, out hit)){//射线检测的是平面点的位置,实际需要的是平行于平面的方向,所以修改y坐标Vector3 horizontalPoint = new Vector3(hit.point.x, transform.position.y, hit.point.z);Debug.DrawLine(transform.position, horizontalPoint, color);skillDirection = (horizontalPoint - transform.position).normalized;//z轴取反是因为unity的旋转api是顺时针旋转的skillAngle = Mathf.Atan2(-skillDirection.z, skillDirection.x) * Mathf.Rad2Deg;}}/// <summary>/// 利用Debug工具绘制扇形/// </summary>private void DrawFan_DebugDrawLine(float radius, int euler){int segments = 100; //圆弧那一段需要用多少个点来表示float deltaAngle = euler / segments;Vector3 right = transform.right;Vector3[] vertices = new Vector3[segments + 2];vertices[0] = transform.position;for (int i = 1; i < vertices.Length; i++){float curAngle = -euler / 2 + deltaAngle * (i - 1) + skillAngle;//从-1/2扇形角度开始绘制,每次偏移deltaAngleVector3 pos = Quaternion.Euler(0f, curAngle, 0f) * right * radius + transform.position;vertices[i] = pos;}// 画圆弧for (int i = 1; i < vertices.Length - 1; i++){Debug.Log(vertices[i]);Debug.DrawLine(vertices[i], vertices[i + 1], showColor);}// 画两条边Debug.DrawLine(vertices[0], vertices[vertices.Length - 1], showColor);Debug.DrawLine(vertices[0], vertices[1], showColor);}/// <summary>/// 利用LinerRender绘制扇形/// </summary>private void DrawFan_LineRender(float radius, int euler){ResetLinerRenderPoints();m_LineRenderer.startColor = showColor;m_LineRenderer.endColor = showColor;m_LineRendererPoints.Add(transform.position);//每一度一个点,绘制思路和debug.DrawLine相同for(int angles = -euler/2; angles <= euler/2; angles++){m_LineRendererPoints.Add(Quaternion.Euler(0, angles + skillAngle, 0) * transform.right * radius + transform.position);}m_LineRenderer.SetPositions(m_LineRendererPoints.ToArray());}private void InitLineRender(){m_LineRendererPoints = new List<Vector3>();m_LineRenderer = GetComponent<LineRenderer>();m_LineRenderer.endWidth = 0.1f;m_LineRenderer.startWidth = 0.1f;m_LineRenderer.loop = true; //绘制路径将会闭合}private void ResetLinerRenderPoints(){m_LineRendererPoints.Clear();}
}

敌人身上的脚本,可以移动

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Enemy : MonoBehaviour
{// Start is called before the first frame updatepublic float speed = 2f;private FanDetector fanSkill;void Start(){fanSkill = GameObject.FindWithTag("Player").GetComponent<FanDetector>();}// Update is called once per framevoid Update(){transform.position += Input.GetAxis("Horizontal") * speed * transform.right * Time.deltaTime;transform.position += Input.GetAxis("Vertical") * speed * transform.forward * Time.deltaTime;SkillDetect();}private void SkillDetect(){if (fanSkill.Dectect(transform.position)){Debug.Log("进入扇形技能范围");}}}

这篇关于基础GamePlay知识-碰撞检测的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/788790

相关文章

MySQL中my.ini文件的基础配置和优化配置方式

《MySQL中my.ini文件的基础配置和优化配置方式》文章讨论了数据库异步同步的优化思路,包括三个主要方面:幂等性、时序和延迟,作者还分享了MySQL配置文件的优化经验,并鼓励读者提供支持... 目录mysql my.ini文件的配置和优化配置优化思路MySQL配置文件优化总结MySQL my.ini文件

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

sqlite3 相关知识

WAL 模式 VS 回滚模式 特性WAL 模式回滚模式(Rollback Journal)定义使用写前日志来记录变更。使用回滚日志来记录事务的所有修改。特点更高的并发性和性能;支持多读者和单写者。支持安全的事务回滚,但并发性较低。性能写入性能更好,尤其是读多写少的场景。写操作会造成较大的性能开销,尤其是在事务开始时。写入流程数据首先写入 WAL 文件,然后才从 WAL 刷新到主数据库。数据在开始

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

AI基础 L9 Local Search II 局部搜索

Local Beam search 对于当前的所有k个状态,生成它们的所有可能后继状态。 检查生成的后继状态中是否有任何状态是解决方案。 如果所有后继状态都不是解决方案,则从所有后继状态中选择k个最佳状态。 当达到预设的迭代次数或满足某个终止条件时,算法停止。 — Choose k successors randomly, biased towards good ones — Close

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

C 语言基础之数组

文章目录 什么是数组数组变量的声明多维数组 什么是数组 数组,顾名思义,就是一组数。 假如班上有 30 个同学,让你编程统计每个人的分数,求最高分、最低分、平均分等。如果不知道数组,你只能这样写代码: int ZhangSan_score = 95;int LiSi_score = 90;......int LiuDong_score = 100;int Zhou

c++基础版

c++基础版 Windows环境搭建第一个C++程序c++程序运行原理注释常亮字面常亮符号常亮 变量数据类型整型实型常量类型确定char类型字符串布尔类型 控制台输入随机数产生枚举定义数组数组便利 指针基础野指针空指针指针运算动态内存分配 结构体结构体默认值结构体数组结构体指针结构体指针数组函数无返回值函数和void类型地址传递函数传递数组 引用函数引用传参返回指针的正确写法函数返回数组