现现在,精品游戏竞争激烈,各大游戏厂商都在争相推出“3A”级品质的游戏。而目前比较热门的科幻、战争、动做等品类大多都有枪械射击内容,所以,“玩枪”便成了很大一部分游戏中常见的玩法。当前比较热门的使命召唤系列、战地系列、无主之地系列、战争机器系列还有最近流行的吃鸡类游戏(绝地求生、堡垒之夜)等等都是玩枪,核心玩法类型上都是属于射击类游戏。所以作好武器与射击体验,还原逼真的射击情景,便成了这类游戏的核心卖点。html
目前的射击游戏主要分为两种,一种为“第一人称射击游戏”(FPS)与“第三人称射击游戏”(TPS)。服务器
第一人称射击游戏是以玩家主视角进行的射击游戏。玩家再也不像别的游戏类型同样操纵屏幕中的虚拟人物来进行游戏,而是身临其境的主视角,体验游戏带来的视觉冲击,这就大大加强了游戏的主动性和真实感。函数
如上两张图分别是1999年发售的反恐精英( Counter-Strike )与2019年发售的使命召唤:现代战争 ( Call of Duty: Modern Warfare )。能够看到,时隔20年,场景与枪械变得更加精细与逼真,界面变得精美与合理,但不变的是,屏幕中间都作有一个“准心”。测试
第三人称射击游戏与第一人称区别在于,屏幕上显示的主角的视野不一样,而且第三人称中玩家控制的游戏人物在游戏屏幕上是可见的,于是第三人称射击游戏增强调更强调动做感。动画
如上3张图都是“幽灵行动:断点”的游戏画面,能够看到,在没有瞄准时屏幕中央是没有准心的,这时能够将注意力集中在环境、角色动做、游戏剧情等等。当打开瞄准时,就会出现屏幕中央的准心,还能够经过Alt键切换第一人称与第三人称(第二张图与第三张图)。this
经过上述的介绍能够看到,不管是第一人称仍是第三人称,屏幕中央都会有“准心”以方便玩家瞄准。那为何要有准心呢?为何有了准心以后,咱们在游戏中就能够瞄准目标呢?spa
咱们在物理课程中了解过,若是抛去枪的复杂结构不谈,枪的工做原理简单来说就是,弹头在枪管中受到推动力后作抛物线运动。code
若是弹头在运动过程当中碰撞到目标,则表示击中;若未碰撞到目标,则表示未击中。那么咱们如何瞄准才能击中目标呢?orm
如图所示,蓝色的水平线则是瞄准线,表明瞄准方向;绿色是枪管轴心线,表明枪管的指向方向;红色则是子弹的飞行轨迹。假定子弹每次从枪口中射出的速度是固定的,空气阻力也是固定的,子弹受到的重力也是固定的,那么按照上图的瞄准方式,射击命中的“远交点”也是固定的。换句话说,用这把枪和这个瞄准角度的话,只能击中距离枪口x米的目标。那么若是咱们射击同一方向上比x米更近或更远的目标怎么办呢?htm
如上图所示,分别为步枪和狙击枪的射击距离调整方法。步枪中有瞄准距离刻度线,调节刻度便可;狙击枪在瞄准高倍镜中有刻度线,根据目标的距离,使用对应的瞄准刻度线便可;手枪由于射击距离短(大部分射击距离在50米之内),瞄准偏差较小,因此不用调节。
而在游戏中,未开瞄准镜的情形下,角色的持枪动做每每如上图所示,将枪固定于肘关节处。为何要这样持枪呢?由于这样持枪姿式能够减少在第一人称人下枪所遮住的视野,提高游戏体验,而这样的持枪动做,在现实中是打不许目标的(由于没有三点一线的瞄准)。在游戏里为了能更加便捷开枪,所以加入了准心的概念。
有了准心以后,玩家就能够更加方便地瞄准目标,从而得到更好的射击体验。但这种准心“指哪打哪”的游戏效果是如何实现的呢?
我刚开始在UE4中实现子弹弹道时,想法很简答,既然子弹是从枪中射出的,那么子弹的飞行方向固然就是枪口的朝向。为了使子弹可以击中“准心”的位置,我调整了枪在手中的朝向。
通过反复测试后发现,不管怎么调,只能保证在某一固定射击距离时,弹着点能与准心重合。当射击距离改变时,子弹就没法击中准心位置,这是什么缘由呢?
如上图所示,假定玩家(摄像机)与枪处在同一水平面,从俯视的角度来看,玩家的准心永远指向正前方(用红色线条表示),而枪则在玩家左手或右手,其朝向(用绿色线条表示)与玩家朝向存在夹角Θ。在该夹角下,只有玩家与目标距离y时,弹着点才能恰好在准心上。不管怎么调整夹角Θ,都只有某一个距离下弹着点恰好落在准心上(由于两条不平行的直线永远只有一个交点)。而实际上枪与玩家(摄像头)并不在同一水平面,在三维坐标系下,两条不平行的直线最多有一个交点(可能没有交点)。
那么该如何解决该问题呢?既然两条线只有一个交点,那么能不能根据不一样的射击距离改变枪的朝向,使交点始终在准心瞄准位置呢?
要想调整枪的朝向,就必须调整枪在玩家动画的骨骼中的相对位置。并且要根据射击距离实时调整(玩家准心瞄到的障碍物的距离),这个 运算量会很是大,几乎是不可能实现的,因此这条路走不通,该怎么办呢?
既然枪的方向不方便调整,子弹的方向总能够控制吧!能够在开枪时,先根据玩家(摄像机)的朝向,作射线检测,肯定目标弹着点,而后再控制子弹的飞行方向为枪口飞向弹着点,这不就能够实现不管玩家瞄向哪里,子弹均可以击中“准心”的位置的需求吗。
如上蓝图代码所示,用摄像机方向(即准心的朝向)作射线检测,返回击中的弹着点坐标与材质(方便后续播放击中特效),若未击中任何物体(例如朝天上开枪),则返回射线检测的终点,方便客户端展现弹道。
如上蓝图代码所示,得到弹着点坐标与材质后进行广播,在全部客户端上播放该玩家的开枪动画(武器特效),并调用C++函数,利用GamePlayTask生成弹道。
void AWeapon::GenerateBulletTrack(FVector HitLocation, UPhysicalMaterial* HitMaterial) { // 枪口坐标 FVector MuzzleLocation = Mesh->GetSocketLocation("Muzzle"); // 向量方向:终点 - 起点 FVector Direction = HitLocation - MuzzleLocation; // 飞行速度 FVector Speed = Direction.Rotation().Vector() * 10000; // 飞行时间:距离 / 速度 float Time = Direction.Size() / Speed.Size(); // 击中点特效 UParticleSystem* HitFX = nullptr; // 击中点材质 if (HitMaterial) { EPhysicalSurface SurfaceType = HitMaterial->SurfaceType; // 该枪已设置该材质类型的击中特效 if (VarHitFXs.Contains(SurfaceType)) { HitFX = VarHitFXs[SurfaceType]; } } // 运行子弹轨迹的GamePlayTask UBulletTrackTask * TrackTask = UBulletTrackTask::InitBulletTrack(BulletTrackComponent, MuzzleLocation, Speed, TargetFX, HitFX, Time, this, HitLocation); TrackTask->ReadyForActivation(); }
如上C++代码所示,根据击中点坐标,计算出子弹的飞行方向与距离,而后计算出飞行时间,并用这些参数开启子弹轨迹的GamePlayTask,用于显示客户端的子弹轨迹及击中特效。
void UBulletTrackTask::TickTask(float DeltaTime) { // 记录飞行时间 ActiveTime += DeltaTime; UWorld* World = GetWorld(); check(World); const FVector OldLocation = CurrentLocation; // 匀速距离公式:距离 = 速度 * 时间 FVector MoveDistance = CurrentVelocity * DeltaTime; // 新位置 CurrentLocation = OldLocation + MoveDistance; // 更新轨迹特效的位置与朝向 if (TrackComponent) { TrackComponent->SetWorldLocationAndRotation(CurrentLocation, UKismetMathLibrary::MakeRotFromX(CurrentVelocity.GetSafeNormal())); } // 超过飞行时间 if (ActiveTime >= LifeTime) { // 有击中特效 if (HitFX) { // 显示击中特效 UGameplayStatics::SpawnEmitterAtLocation(World, HitFX, HitLocation); } EndTask(); } }
经过运行该子弹轨迹的GamePlayTask,每帧会更新子弹轨迹特效的位置,显示子弹的飞行过程,若是超过了子弹的飞行时间,则在服务器已计算出的击中点产生击中特效(受伤害特效)等等,实现了子弹老是能击中游戏准心瞄准的目标。
上述解决方案是弹道实现的较简化版本,由于没有考虑子弹的飞行时间、子弹重力、枪械后坐力、空气阻力等诸多因素。若是考虑这些因素的话,服务器就不适合直接用射线检测来计算弹着点,而是一样改用GamePlayTask来实现,以便可以精确计算子弹飞行过程当中的受力、飞行碰撞等等问题。但这样作势必会增长服务器中弹着点计算耗时(由于会增长子弹飞行时间),加大客户端的表现困难(让玩家感觉到开枪有延时),这也是该方案后续的难点。