基础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

相关文章

一文详解Java异常处理你都了解哪些知识

《一文详解Java异常处理你都了解哪些知识》:本文主要介绍Java异常处理的相关资料,包括异常的分类、捕获和处理异常的语法、常见的异常类型以及自定义异常的实现,文中通过代码介绍的非常详细,需要的朋... 目录前言一、什么是异常二、异常的分类2.1 受检异常2.2 非受检异常三、异常处理的语法3.1 try-

Android Mainline基础简介

《AndroidMainline基础简介》AndroidMainline是通过模块化更新Android核心组件的框架,可能提高安全性,本文给大家介绍AndroidMainline基础简介,感兴趣的朋... 目录关键要点什么是 android Mainline?Android Mainline 的工作原理关键

mysql的基础语句和外键查询及其语句详解(推荐)

《mysql的基础语句和外键查询及其语句详解(推荐)》:本文主要介绍mysql的基础语句和外键查询及其语句详解(推荐),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋... 目录一、mysql 基础语句1. 数据库操作 创建数据库2. 表操作 创建表3. CRUD 操作二、外键

Python基础语法中defaultdict的使用小结

《Python基础语法中defaultdict的使用小结》Python的defaultdict是collections模块中提供的一种特殊的字典类型,它与普通的字典(dict)有着相似的功能,本文主要... 目录示例1示例2python的defaultdict是collections模块中提供的一种特殊的字

Python基础文件操作方法超详细讲解(详解版)

《Python基础文件操作方法超详细讲解(详解版)》文件就是操作系统为用户或应用程序提供的一个读写硬盘的虚拟单位,文件的核心操作就是读和写,:本文主要介绍Python基础文件操作方法超详细讲解的相... 目录一、文件操作1. 文件打开与关闭1.1 打开文件1.2 关闭文件2. 访问模式及说明二、文件读写1.

C#基础之委托详解(Delegate)

《C#基础之委托详解(Delegate)》:本文主要介绍C#基础之委托(Delegate),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 委托定义2. 委托实例化3. 多播委托(Multicast Delegates)4. 委托的用途事件处理回调函数LINQ

国内环境搭建私有知识问答库踩坑记录(ollama+deepseek+ragflow)

《国内环境搭建私有知识问答库踩坑记录(ollama+deepseek+ragflow)》本文给大家利用deepseek模型搭建私有知识问答库的详细步骤和遇到的问题及解决办法,感兴趣的朋友一起看看吧... 目录1. 第1步大家在安装完ollama后,需要到系统环境变量中添加两个变量2. 第3步 “在cmd中

0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型的操作流程

《0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeekR1模型的操作流程》DeepSeekR1模型凭借其强大的自然语言处理能力,在未来具有广阔的应用前景,有望在多个领域发... 目录0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型,3步搞定一个应

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 声明式事物