射擊游戲中准心與子彈彈道的探索


前言

  現如今,精品游戲競爭激烈,各大游戲廠商都在爭相推出“3A”級品質的游戲。而目前比較熱門的科幻、戰爭、動作等品類大多都有槍械射擊內容,因此,“玩槍”便成了很大一部分游戲中常見的玩法。當前比較熱門的使命召喚系列、戰地系列、無主之地系列、戰爭機器系列還有最近流行的吃雞類游戲(絕地求生、堡壘之夜)等等都是玩槍,核心玩法類型上都是屬於射擊類游戲。因此做好武器與射擊體驗,還原逼真的射擊情景,便成了這類游戲的核心賣點。

射擊游戲的分類

  目前的射擊游戲主要分為兩種,一種為“第一人稱射擊游戲”(FPS)與“第三人稱射擊游戲”(TPS)。

第一人稱

  第一人稱射擊游戲是以玩家主視角進行的射擊游戲。玩家不再像別的游戲類型一樣操縱屏幕中的虛擬人物來進行游戲,而是身臨其境的主視角,體驗游戲帶來的視覺沖擊,這就大大增強了游戲的主動性和真實感。

  如上兩張圖分別是1999年發售的反恐精英( Counter-Strike )與2019年發售的使命召喚:現代戰爭 ( Call of Duty: Modern Warfare )。可以看到,時隔20年,場景與槍械變得更加精細與逼真,界面變得精美與合理,但不變的是,屏幕中間都做有一個“准心”。

第三人稱

  第三人稱射擊游戲與第一人稱區別在於,屏幕上顯示的主角的視野不同,並且第三人稱中玩家控制的游戲人物在游戲屏幕上是可見的,因而第三人稱射擊游戲加強調更強調動作感。

  如上3張圖都是“幽靈行動:斷點”的游戲畫面,可以看到,在沒有瞄准時屏幕中央是沒有准心的,這時可以將注意力集中在環境、角色動作、游戲劇情等等。當打開瞄准時,就會出現屏幕中央的准心,還可以通過Alt鍵切換第一人稱與第三人稱(第二張圖與第三張圖)。

准心

  通過上述的介紹可以看到,無論是第一人稱還是第三人稱,屏幕中央都會有“准心”以方便玩家瞄准。那為什么要有准心呢?為什么有了准心之后,我們在游戲中就可以瞄准目標呢?

現實中的射擊

  我們在物理課程中了解過,如果拋去槍的復雜結構不談,槍的工作原理簡單來講就是,彈頭在槍管中受到推進力后做拋物線運動。

  如果彈頭在運動過程中碰撞到目標,則表示擊中;若未碰撞到目標,則表示未擊中。那么我們如何瞄准才能擊中目標呢?

  如圖所示,藍色的水平線則是瞄准線,代表瞄准方向;綠色是槍管軸心線,代表槍管的指向方向;紅色則是子彈的飛行軌跡。假定子彈每次從槍口中射出的速度是固定的,空氣阻力也是固定的,子彈受到的重力也是固定的,那么按照上圖的瞄准方式,射擊命中的“遠交點”也是固定的。換句話說,用這把槍和這個瞄准角度的話,只能擊中距離槍口x米的目標。那么如果我們射擊同一方向上比x米更近或更遠的目標怎么辦呢?

  如上圖所示,分別為步槍和狙擊槍的射擊距離調整方法。步槍中有瞄准距離刻度線,調節刻度即可;狙擊槍在瞄准高倍鏡中有刻度線,根據目標的距離,使用對應的瞄准刻度線即可;手槍因為射擊距離短(大部分射擊距離在50米以內),瞄准誤差較小,所以不用調節。

游戲中的射擊

  而在游戲中,未開瞄准鏡的情形下,角色的持槍動作往往如上圖所示,將槍固定於肘關節處。為什么要這樣持槍呢?因為這樣持槍姿勢可以減小在第一人稱人下槍所遮住的視野,提升游戲體驗,而這樣的持槍動作,在現實中是打不准目標的(因為沒有三點一線的瞄准)。在游戲里為了能更加便捷開槍,因此加入了准心的概念。

  有了准心之后,玩家就可以更加方便地瞄准目標,從而獲得更好的射擊體驗。但這種准心“指哪打哪”的游戲效果是如何實現的呢?

UE4中的子彈彈道

  我剛開始在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來實現,以便能夠精確計算子彈飛行過程中的受力、飛行碰撞等等問題。但這樣做勢必會增加服務器中彈着點計算耗時(因為會增加子彈飛行時間),加大客戶端的表現困難(讓玩家感受到開槍有延時),這也是該方案后續的難點。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM