武器基类扩展

前面介绍了武器基类的实现,现在在这个武器基类的基础上进行扩展,实现其派生类——射弹类武器和射线类武器。

射弹类武器

.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函数,并声明一个子弹类成员。.cpp文件中对Fire函数进行重写,因为需要武器基类中Fire函数的功能,因此使用Super::Fire(HitTarget)进行父类函数的调用。后续获取该武器对象的拥有者以及枪口插槽并保存为变量,获取枪口插槽的Transform和枪口指向射线检测的目标,以此来确定子弹生成时的位置和旋转,之后用SpawnActor方法生成子弹即可。子弹的相关功能由子弹类去实现。

射线类武器

.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文件中实现三个方法,接下来依次介绍。

TraceEndWithScatter:传入一个起始位置向量和目标点向量,使用FMath::FRandRange生成一个随机偏移,加到终点位置上,实现子弹轨迹在圆形范围内随机落点的效果。

WeaponTraceHit:传入起始位置向量和目标点向量,以及一个打击点引用(OutHit),先判断武器bUseScatter是否为true,为true说明是适用子弹偏移的武器,就使用TraceEndWithScatter计算偏移后的射线终点,否则直接以HitTarget延长作为射线终点,接下来使用LineTraceSingleByChannel方法检测射线碰撞到的第一个可视物体,并赋给OutHit,以此获取打击点,之后就是拖尾特效的生成。

Fire:同射弹类武器一样,射线类武器也需要开火动画、弹壳弹出和子弹计算等功能,因此调用Super::Fire(HitTarget)。接下来的实现与射弹类武器类似,获取武器持有者和枪口插槽,只不过接下来不是实例化子弹,而是调用WeaponTraceHit方法获取打击点,FireHit变量中存储了打击点的信息,可通过FireHit.GetActor()方法获取打击物体的Actor,如果能够转换为角色类以外的类型,那就适用接下来的一系列命中方法(因为不想造成打击队友的效果,所以如果能够将打击物体转换为角色类型,就忽略)。满足命中条件后,判断命中部位是否为头部,并直接使用ApplyDamage方法应用伤害,后续就是粒子效果、打击音效的生成了。

猜你喜欢

转载自blog.csdn.net/weixin_47260762/article/details/127707188
今日推荐