武器基本クラスの実装は以前に導入されましたが、今回はこの武器基本クラスに基づいて拡張され、その派生クラスである発射武器と光線武器が実装されました。
発射兵器
.h ファイル:
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Weapon.h"
#include "ProjectileWeapon.generated.h"
/**
*
*/
UCLASS()
class TPS_API AProjectileWeapon : public AWeapon
{
GENERATED_BODY()
public:
virtual void Fire(const FVector& HitTarget) override;
private:
UPROPERTY(EditAnywhere)
TSubclassOf<class AProjectile> ProjectileClass;
};
.cpp ファイル:
// Fill out your copyright notice in the Description page of Project Settings.
#include "ProjectileWeapon.h"
#include "Projectile.h"
#include "Engine/SkeletalMeshSocket.h"
void AProjectileWeapon::Fire(const FVector& HitTarget)
{
Super::Fire(HitTarget);
if(!HasAuthority()) return;
APawn* InstigatorPawn = Cast<APawn>(GetOwner());
const USkeletalMeshSocket* MuzzleFlashSocket = GetWeaponMesh()->GetSocketByName(FName("MuzzleFlash"));
if(MuzzleFlashSocket)
{
//获取枪口插槽的Transform,也就是子弹射出的点
FTransform SocketTransform = MuzzleFlashSocket->GetSocketTransform(GetWeaponMesh());
//枪口指向射线检测的目标
FVector ToTarget = HitTarget - SocketTransform.GetLocation();
FRotator TargetRotation = ToTarget.Rotation();
if(ProjectileClass && InstigatorPawn)
{
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = GetOwner();
SpawnParams.Instigator = InstigatorPawn;
UWorld* World = GetWorld();
if(World)
{
World->SpawnActor<AProjectile>(
ProjectileClass,
SocketTransform.GetLocation(),
TargetRotation,
SpawnParams
);
}
}
}
}
発射兵器に必要な変更はそれほど多くなく、ほとんどの機能は兵器基本クラスのメソッドを直接使用できます。武器の基本クラスでは、Fire 関数が武器の発砲アニメーション、砲弾の排出、および弾丸の計算を実装するため、発射武器はこれに基づいてインスタンス化された弾丸と発射関数を追加するだけで済みます。
.h ファイルでオーバーライドされた Fire 関数を宣言し、bullet クラスのメンバーを宣言します。Fire 関数は .cpp ファイル内で書き換えられます。武器の基本クラスの Fire 関数の関数が必要なため、Super::Fire(HitTarget) を使用して親クラスの関数を呼び出します。次に、武器オブジェクトと銃口スロットの所有者を取得して変数として保存し、銃口スロットの変換と光線検出のターゲットを指す銃口を取得して、弾丸の位置と回転を決定します。これが生成されたら、SpawnActor メソッドを使用して箇条書きを作成します。Bullet 関連の関数は、bullet クラスによって実装されます。
光線武器
.h ファイル:
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Weapon.h"
#include "HitScanWeapon.generated.h"
/**
*
*/
UCLASS()
class TPS_API AHitScanWeapon : public AWeapon
{
GENERATED_BODY()
public:
virtual void Fire(const FVector& HitTarget) override;
protected:
FVector TraceEndWithScatter(const FVector& TraceStart, const FVector& HitTarget);
void WeaponTraceHit(const FVector& TraceStart, const FVector& HitTarget, FHitResult& OutHit);
UPROPERTY(EditAnywhere)
class UParticleSystem* ImpactParticles;
UPROPERTY(EditAnywhere)
USoundCue* HitSound;
UPROPERTY(EditAnywhere)
float Damage = 20.f;
private:
UPROPERTY(EditAnywhere)
UParticleSystem* BeamParticles;
UPROPERTY(EditAnywhere, Category = "Weapon Scatter")
float DistanceToSphere = 800.f;
UPROPERTY(EditAnywhere, Category = "Weapon Scatter")
float SphereRadius = 75.f;
UPROPERTY(EditAnywhere, Category = "Weapon Scatter")
bool bUseScatter = false;
};
.cpp ファイル:
// Fill out your copyright notice in the Description page of Project Settings.
#include "HitScanWeapon.h"
#include "DrawDebugHelpers.h"
#include "Engine/SkeletalMeshSocket.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"
#include "Particles/ParticleSystemComponent.h"
#include "PhysicalMaterials/PhysicalMaterial.h"
#include "Sound/SoundCue.h"
#include "TPS/TPS.h"
#include "TPS/BlasterComponents/CombatComponent.h"
#include "TPS/Character/BlasterCharacter.h"
#include "TPS/Enemy/Enemy.h"
void AHitScanWeapon::Fire(const FVector& HitTarget)
{
Super::Fire(HitTarget);
APawn* OwnerPawn = Cast<APawn>(GetOwner());
if(OwnerPawn == nullptr) return;
AController* InstigatorController = OwnerPawn->GetController();
const USkeletalMeshSocket* MuzzleFlashSocket = GetWeaponMesh()->GetSocketByName("MuzzleFlash");
if(MuzzleFlashSocket && InstigatorController)
{
FTransform SocketTransform = MuzzleFlashSocket->GetSocketTransform(GetWeaponMesh());
FVector Start = SocketTransform.GetLocation();
FHitResult FireHit;
WeaponTraceHit(Start, HitTarget, FireHit);
if(FireHit.GetActor() && HasAuthority() && InstigatorController && !Cast<ABlasterCharacter>(FireHit.GetActor()))
{
const float FinalDamage = FireHit.BoneName.ToString() == FString("head") ? Damage * 1.4 : Damage;
UGameplayStatics::ApplyDamage(
FireHit.GetActor(),
FinalDamage,
InstigatorController,
this,
UDamageType::StaticClass()
);
}
if(ImpactParticles)
{
UGameplayStatics::SpawnEmitterAtLocation(
GetWorld(),
ImpactParticles,
FireHit.ImpactPoint,
FireHit.ImpactNormal.Rotation()
);
}
if(HitSound)
{
UGameplayStatics::PlaySoundAtLocation(
this,
HitSound,
FireHit.ImpactPoint
);
}
}
}
void AHitScanWeapon::WeaponTraceHit(const FVector& TraceStart, const FVector& HitTarget, FHitResult& OutHit)
{
UWorld* World = GetWorld();
if (World)
{
FVector End = bUseScatter ? TraceEndWithScatter(TraceStart, HitTarget) : TraceStart + (HitTarget - TraceStart) * 1.25f;
World->LineTraceSingleByChannel(
OutHit,
TraceStart,
End,
ECollisionChannel::ECC_Visibility
);
FVector BeamEnd = End;
if (OutHit.bBlockingHit)
{
BeamEnd = OutHit.ImpactPoint;
}
if (BeamParticles)
{
UParticleSystemComponent* Beam = UGameplayStatics::SpawnEmitterAtLocation(
World,
BeamParticles,
TraceStart,
FRotator::ZeroRotator,
true
);
if (Beam)
{
Beam->SetVectorParameter(FName("Target"), BeamEnd);
}
}
}
}
FVector AHitScanWeapon::TraceEndWithScatter(const FVector& TraceStart, const FVector& HitTarget)
{
FVector ToTargetNormalized = (HitTarget - TraceStart).GetSafeNormal();
FVector SphereCenter = TraceStart + ToTargetNormalized * DistanceToSphere;
FVector RandVec = UKismetMathLibrary::RandomUnitVector() * FMath::FRandRange(0.f, SphereRadius);
FVector EndLoc = SphereCenter + RandVec;
FVector ToEndLoc = EndLoc - TraceStart;
return FVector(TraceStart + ToEndLoc * TRACE_LENGTH / ToEndLoc.Size());
}
光線武器は大きく変更されたようです。主な理由は、物理的な弾丸がないため、弾丸クラスが必要なくなり、ヒットに関連する機能関数と変数が武器クラス自体に配置されるためです。.cpp ファイルには 3 つのメソッドが実装されており、順番に紹介します。
TraceEndWithScatter: 開始位置ベクトルとターゲット点ベクトルを渡し、FMath::FRandRange を使用してランダムなオフセットを生成し、それを終了位置に追加して、円形の範囲内にランダムに収まる弾丸の軌道の効果を実現します。
WeaponTraceHit: 開始位置ベクトルと目標点ベクトル、およびヒット ポイント参照 (OutHit) を渡し、まず武器 bUseScatter が true であるかどうかを判断し、true であれば弾丸オフセットに適した武器であることを意味します。 TraceEndWithScatter を使用してオフセット光線の終点を計算します。それ以外の場合は、HitTarget を光線の終点として直接拡張し、次に LineTraceSingleByChannel メソッドを使用して光線が当たる最初の可視オブジェクトを検出し、それを OutHit に割り当ててヒット ポイントを取得します。そしてトレーリング効果が生成されます。
Fire: 発射武器と同様に、光線武器にも発射アニメーション、砲弾の排出、弾丸の計算などの機能が必要なので、Super::Fire(HitTarget) を呼び出します。次の実装は発射武器と似ており、武器ホルダーと銃口スロットを取得しますが、弾丸をインスタンス化する代わりに、WeaponTraceHit メソッドを呼び出してヒット ポイントを取得し、ヒット ポイント情報は FireHit 変数に格納されます。オブジェクトにヒットしたものは、FireHit.GetActor() メソッドを通じて取得できます。文字クラス以外の型に変換できる場合は、次の一連のヒット メソッドが適用できます (影響を与えたくないため)チームメイトを攻撃するため、攻撃オブジェクトが役割タイプに変換できる場合、それは無視されます)。ヒット条件成立後、ヒット部位が頭かどうかを判定し、ApplyDamageメソッドを直接使用してダメージを適用し、パーティクルエフェクトや打撃効果音を生成します。