如何用高效的方式实现游戏世界中的击中效果

5 篇文章 0 订阅

前言

模拟一个导弹或者炮弹击中一个标目,是大部分游戏经常要做得的一件事,通常我们会使用游戏引擎中自带的碰撞检测事件来实现这个效果,或者是判断距离,但这两者都不是最优的方法,下面我会介绍一种我目前使用的方法。这里着重以Unity举例来说,但是不限制于Unity引擎,也就是适用于任何一款游戏引擎。

为什么不使用碰撞检测

1.理由

  1. 碰撞检测是一个很耗费性能的操作,读者可以想象一下,为什么引擎自带的碰撞系统会在物体和目标发生碰撞时会有现实世界中的物理特性,比如物体上的某一个点与目标发生碰撞就会导致物体的自身旋转属性发生一系列变化,这都是非常复杂的数学计算产生的结果,而且最重要的一点,CPU还要去计算到底是物体的哪一个点和目标发生碰撞,所以这一系列的数学计算绝对都是很复杂的,所以如果你要做的是一个或者几个单位还可以,但是一旦单位太多的话,CPU机会计算太多的物理数据,所以这是个很耗费性能的操作,如果读者想做一个多单位的项目,那么这种方法基本可以放弃。
  2. 当然还有一点在Unity中,游戏是按帧执行的,读者可以理解为自己看到的游戏就是又一系列连环画组成的,只是以极小时间间隔来这些画面,一般帧率在24帧以上,人眼就认为这是一个连贯不卡顿的动画,也就是一秒钟人眼至少要处理24张连环画,才能认为这是副连贯的画面,在Unity中有Update函数,在虚幻中也有Tick函数,来实现这个效果;
    为什么我要说这个,游戏是按真执行的,也就是游戏物体的位置是按帧更新的,如果一个游戏物体的速度太快,也就是说,每帧位置之间的距离很大,就会出现碰撞失效的方法,当然前提是目标位置刚好在物体的两帧位置之间。
    2.结论
    碰撞检测是一个很不靠谱的方法,能不用就不用!

用计算距离来判断

1.理由

  1. 除开碰撞检测之外还可以通过距离去判断物体是否到达目标点,一般这应该是一个废弃碰撞检测方法后首选的方案,因为相对于碰撞检测,他不需要CPU去计算那么复杂的数据,大大降低了CPU的运算量,这个方法接收的计算量肯定是远远少于碰撞检测的,也就是说它能计算更多的单位,至少比碰撞检测多,但是他仍然不是最优的方法,要理解这一点就要去理解在游戏引擎中的两个位置之间的距离是如何计算的:
    (这里略过向量的讲解)
    我们知道,如果我们想去计算一个物体的和另一个物体的距离,那么我们首先要知道这两个物体的位置,在游戏中也就是Position属性,然后做差,得到一个新的向量,再取这个向量的模,下面我用C#代码演示:
	    void Distance()
		{
    		Vector3 tempPos = new Vector3();//建立一个临时的变量

   			tempPos = target - self;//计算出从自身指向目标的一个向量

    		NormOfVector(tempPos);//求出距离
		}

		float NormOfVector(Vector3 vector)//求一个向量的模
		{
    		return Mathf.Sqrt(Mathf.Pow(vector.x, 2) 
    		+ Mathf.Pow(vector.y, 2) + Mathf.Pow(vector.z, 2));
		}

		void Start()
		{
   			Distance();//最后调用计算距离
		}
注:“^”运算符在C#中适用与Int类型变量,Mathf.Pow()为求一个值的平方的函数

我们可以看到在这个方法中CPU要进行一次开根号的操作,作为程序员,我们要知道的是CPU在计算 “+,*”的速度是要比计算“-,/, Sqrt”的速度快的,所以频繁的开根号也是一个很耗时的操作,能少用就少 用,最好不用,那么以这个例子来说开个为什么耗时呢?这里不再多说,读者可以自己去找找相关的实现 代码;
2. 结论
对于这个方法来说,虽然相对碰撞比较实用,但是他需要每帧都计算一个距离,这也是很耗费时间的操作,另外你还需要控制导弹的运动轨迹,如果轨迹是直线,也仍需要判断距离。

第三种方法——Lerp()插值

在我之前的项目中,我尝试了一种新的方法,用插值去实现导弹的飞行和击中判定。
思路:
如何用插值实现?
Unity系统内置的Vector3中提供了一种对Vector3变量进行线性插值的方法,Lerp()函数,它的函数原型是这样:

public static Vector3 Lerp(Vector3 a, Vector3 b, float t);

有了这个函数,我们可以先获取开始位置和目标位置,然后用时间去判断是否击中目标,其中t的值是限制在0 - 1之间的数,当 t == 0时,这个函数范围的位置是开始位置,当 t == 0.5f 时这个函数返回的位置时位于开始位置和结束位置之间的位置,当 t == 1 时函数返回的位置就是结束位置,这也是我们判断的依据,
其中他是线性插值也是我们的重要判断依据:
在这里插入图片描述
这个测试证明了它是一个线性插值,下面是测试代码:

		public float tempValue;
		
		public float interval;
		
		public Vector3 self;
		
		public Vector3 target;
		
		public Transform testCube;
		
		void Update()
		{
			SetLerpVector3();
		}
		
		void SetLerpVector3()
		{
			tempValue = 0;
			for(int i = 0; i < testCube.childCount; ++i)
			{
		    	tempValue += interval;
		    	testCube.GetChild(i).position = Vector3.Lerp(self, target, tempValue);
			}
		}
我这里也提供一个Lerp函数内部实现的版本(当然,这是以我自己的理解写的):
    public float Lerp(float a, float b, float t)
	{
	    return a + (b - a) * Mathf.Clamp01(t);
	}
很好理解的函数对吧,Vedctor3.Lerp()内部就是把每个分量都调用这个函数进行插值;

这样我们把判断距离的问题转化为了判段一个值是否大于1的问题,大大的减少了计算量,当然你会说这么做只适用于直线飞行的导弹,实际上我们可以对这个位置上的值做任何改变,只要保持头和尾都在对应的起点和终点就行了,那么怎么做呢?

我会在下一次更新时说明解决方法,当前前提是你得有3D数学的基础,否则你可能会看的一头雾水,同时我也会解决导弹的朝向问题。

结尾

我所介绍的东西都是偏于实战,因为这些知识都是我从实战中摸索所来的,所以理论东西可能欠缺,只是我的理解,由于本人不喜欢吧东西讲的太深奥,而往往讲出一些有逻辑推理而得到的结论,往往会更有说服力,更容易让读者理解,所以文档基于实践,不基于理论。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值