射擊遊戲中準心與子彈彈道的探索

前言

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

射擊遊戲的分類

  目前的射擊遊戲主要分爲兩種,一種爲「第一人稱射擊遊戲」(FPS)與「第三人稱射擊遊戲」(TPS)。c++

第一人稱

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

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

第三人稱

  第三人稱射擊遊戲與第一人稱區別在於,屏幕上顯示的主角的視野不一樣,而且第三人稱中玩家控制的遊戲人物在遊戲屏幕上是可見的,於是第三人稱射擊遊戲增強調更強調動做感。測試

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

準心

  經過上述的介紹能夠看到,不管是第一人稱仍是第三人稱,屏幕中央都會有「準心」以方便玩家瞄準。那爲何要有準心呢?爲何有了準心以後,咱們在遊戲中就能夠瞄準目標呢?this

現實中的射擊

  咱們在物理課程中瞭解過,若是拋去槍的複雜結構不談,槍的工做原理簡單來說就是,彈頭在槍管中受到推動力後作拋物線運動。spa

  若是彈頭在運動過程當中碰撞到目標,則表示擊中;若未碰撞到目標,則表示未擊中。那麼咱們如何瞄準才能擊中目標呢?code

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

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

遊戲中的射擊

  而在遊戲中,未開瞄準鏡的情形下,角色的持槍動做每每如上圖所示,將槍固定於肘關節處。爲何要這樣持槍呢?由於這樣持槍姿式能夠減少在第一人稱人下槍所遮住的視野,提高遊戲體驗,而這樣的持槍動做,在現實中是打不許目標的(由於沒有三點一線的瞄準)。在遊戲裏爲了能更加便捷開槍,所以加入了準心的概念。

  有了準心以後,玩家就能夠更加方便地瞄準目標,從而得到更好的射擊體驗。但這種準心「指哪打哪」的遊戲效果是如何實現的呢?

UE4中的子彈彈道

  我剛開始在UE4中實現子彈彈道時,想法很簡答,既然子彈是從槍中射出的,那麼子彈的飛行方向固然就是槍口的朝向。爲了使子彈可以擊中「準心」的位置,我調整了槍在手中的朝向。

問題

<iframe src="//player.bilibili.com/player.html?aid=74612974&cid=127622995&page=1&high_quality=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" width="800" height="550"> </iframe> &emsp;&emsp;通過反覆測試後發現,不管怎麼調,只能保證在某一固定射擊距離時,彈着點能與準心重合。當射擊距離改變時,子彈就沒法擊中準心位置,這是什麼緣由呢?

  如上圖所示,假定玩家(攝像機)與槍處在同一水平面,從俯視的角度來看,玩家的準心永遠指向正前方(用紅色線條表示),而槍則在玩家左手或右手,其朝向(用綠色線條表示)與玩家朝向存在夾角Θ。在該夾角下,只有玩家與目標距離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,每幀會更新子彈軌跡特效的位置,顯示子彈的飛行過程,若是超過了子彈的飛行時間,則在服務器已計算出的擊中點產生擊中特效(受傷害特效)等等,實現了子彈老是能擊中游戲準心瞄準的目標。

<iframe src="//player.bilibili.com/player.html?aid=74613873&cid=127622995&page=1&high_quality=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" width="800" height="550"> </iframe> # 展望

  上述解決方案是彈道實現的較簡化版本,由於沒有考慮子彈的飛行時間、子彈重力、槍械後坐力、空氣阻力等諸多因素。若是考慮這些因素的話,服務器就不適合直接用射線檢測來計算彈着點,而是一樣改用GamePlayTask來實現,以便可以精確計算子彈飛行過程當中的受力、飛行碰撞等等問題。但這樣作勢必會增長服務器中彈着點計算耗時(由於會增長子彈飛行時間),加大客戶端的表現困難(讓玩家感覺到開槍有延時),這也是該方案後續的難點。

相關文章
相關標籤/搜索