射击游戏案例(四)

一、瞄准偏移(AimOffset)

现在已经给角色添加了射击和准星,当角色在Pitch轴上偏移的时候,一般的游戏中都会有角色的手臂武器进行朝向的改变,比如角色向上看,手臂武器的指向向上。

附虚幻引擎5.2版本瞄准偏移如何创建、使用:5.2版本瞄准偏移

在将瞄准偏移所用到的动画帧批处理的时候,在附加设置处要注意附加动画类型、基础姿势类型、基础姿势动画设置好。

 创建出来之后,将做好的单帧动画拖入。

 CharacterAnim.h

UPROPERTY(EditAnywhere,BlueprintReadOnly,Category=Properties,meta=(AllowPrivateAccess="true"))
float PitchValue = 0.0f;//俯仰值

CharacterAnim.cpp

void UCharacterAnim::NativeUpdateAnimation(float DeltaSeconds)
{
	Super::NativeUpdateAnimation(DeltaSeconds);
	if (!CharacterRef)
	{
		CharacterRef = Cast<AManCharacter>(TryGetPawnOwner());
	}
	else
	{
		FVector CharacterVelocity = CharacterRef->GetVelocity();
		Speed = CharacterVelocity.Size();
		IsInAir = CharacterRef->GetMovementComponent()->IsFalling();
		Direction = CalculateDirection(CharacterVelocity,CharacterRef->GetControlRotation());
		
		const FRotator CharacterRotator = CharacterRef->GetActorRotation();
		const FRotator ControlRotator = CharacterRef->GetControlRotation();
		const FRotator DeltaRotator = UKismetMathLibrary::NormalizedDeltaRotator(ControlRotator,CharacterRotator);
		const FRotator CurrentRotator = FRotator(PitchValue,0.0f,0.0f);
		const FRotator TargetRotator = UKismetMathLibrary::RInterpTo(CurrentRotator,DeltaRotator,DeltaSeconds,15.0f);
		PitchValue = FMath::ClampAngle(TargetRotator.Pitch,-90.0f,90.0f);
	}
}

打开动画蓝图,将瞄准偏移添加到状态机中,并用通过骨骼层混合动画。

 当向上瞄准的时候,武器和手部会向上偏移一些。

 二、射击

2.1 射击射线检测

在这里将使用从屏幕中央,也就是准星处射出一条射线去进行射线检测,判断是否射击到了物体。

在这里将BaseCharacter中的FireWeapon方法在ManCharacter中进行了重写。

ManCharacter.h

protected:
	virtual void BeginPlay() override;
	/**
	 * 武器开火
	 */
	virtual void FireWeapon() override;
	/**
	 * 获取以屏幕中心为射线检测开始点的世界位置和方向
	 * @param StartLocation 世界位置
	 * @param Direction 方向
	 */
    void GetScreenFireStartLocationAndDirection(FVector& StartLocation,FVector& Direction);

ManCharacter.cpp

void AManCharacter::GetScreenFireStartLocationAndDirection(FVector& StartLocation, FVector& Direction)
{
	if (GEngine && GEngine->GameViewport)
	{
		FVector2D OutViewportSize;
		GEngine->GameViewport->GetViewportSize(OutViewportSize);
		FVector2D HalfViewportSize = FVector2D(OutViewportSize.X/2,OutViewportSize.Y/2);
		APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(),0);
		PC->DeprojectScreenPositionToWorld(HalfViewportSize.X,HalfViewportSize.Y,StartLocation,Direction);
	}
}

HalfViewportSize就是屏幕中央的位置,使用DeprojectScreenPositionToWorld将屏幕坐标转化成世界坐标并获取了指向的方向。

在FireWeapon中进行射线检测:

void AManCharacter::FireWeapon()
{
	Super::FireWeapon();
	FVector OutWorldLocation;
	FVector OutWorldDirection;
	GetScreenFireStartLocationAndDirection(OutWorldLocation,OutWorldDirection);
	FVector StartLocation = OutWorldLocation;
	FVector EndLocation = StartLocation + OutWorldDirection * ShootDistance;
	FHitResult ScreenHitResult;
	FCollisionQueryParams QueryParams;
	QueryParams.AddIgnoredActor(this);
	//单通道射线检测
	GetWorld()->LineTraceSingleByChannel(ScreenHitResult,StartLocation,EndLocation,ECC_Visibility,QueryParams);
	FColor DrawColor = FColor::Red;
	if (ScreenHitResult.GetActor())
	{
		EndLocation = ScreenHitResult.Location;
		DrawColor = FColor::Green;
	}
	DrawDebugLine(GetWorld(),StartLocation,EndLocation,DrawColor,false,2.0f,0,2.0f);
}

如果在ManCharacter中重写了FireWeapon的话,要Super::FireWeapon();调用一下父类,会将父类中的FireWeapon内容执行一遍,再去往下执行。

在这里使用了一个DrawDebugLine画一条线的方式进行调试,看下是否检测到了东西,注意射线检测的通道,这里是ECC_Visibility,如果没检测到的话,是红线,检测到则是绿线。

 2.2 枪口特效

射击的时候给武器添加射击的特效

使用的方法是用SpawnEmitterAtLocation在指定位置生成的粒子特效,这里使用的是ParticleSystem特效。

武器的模型中添加一个Muzzle的插槽,提供生成的位置。

 ManCharacter.h

UPROPERTY(EditAnywhere,BlueprintReadOnly,Category=Properties,meta=(AllowPrivateAccess="true"))
UParticleSystem* MuzzleParticle;//枪口特效

ManCharacter.cpp

//枪口特效
if (MuzzleParticle)
{
	FTransform MuzzleTransform = WeaponMesh->GetSocketTransform("Muzzle");
	UGameplayStatics::SpawnEmitterAtLocation(GetWorld(),MuzzleParticle,MuzzleTransform.GetLocation(),
		MuzzleTransform.Rotator(),FVector(0.5f));
}

2.3  射击到物体的撞击特效

ManCharacter.h

UPROPERTY(EditAnywhere,BlueprintReadOnly,Category=Properties,meta=(AllowPrivateAccess="true"))
UParticleSystem* ImpactParticle;//枪口特效

ManCharacter.cpp

//撞击特效
if (ImpactParticle)
{
    UGameplayStatics::SpawnEmitterAtLocation(GetWorld(),ImpactParticle,EndLocation);
}

注意:撞击效果要放到射线检测成功处,如果没有射击到物体,不需要显示撞击特效。

2.4  射击轨道特效

射击轨道同样需要根据射击结束点来处理轨道的长短。

在轨道特效中有一个目标,其中可以使用目标命名,SetVectorParameter设置向量参数去处理。

 ManCharacter.h

UPROPERTY(EditAnywhere,BlueprintReadOnly,Category=Properties,meta=(AllowPrivateAccess="true"))
UParticleSystem* BeamParticle;//射击轨迹特效

ManCharacter.cpp

//射击轨迹特效
if (BeamParticle)
{
	FTransform MuzzleTransform = WeaponMesh->GetSocketTransform("Muzzle");
	UParticleSystemComponent* PSComp = UGameplayStatics::SpawnEmitterAtLocation(GetWorld(),BeamParticle,MuzzleTransform.GetLocation());
	if (PSComp)
	{
		PSComp->SetVectorParameter("Target",EndLocation);
	}
}

 2.5 射击音效

ManCharacter.h

UPROPERTY(EditAnywhere,BlueprintReadOnly,Category=Properties,meta=(AllowPrivateAccess="true"))
USoundBase* FireSound;//射击音效

ManCharacter.cpp

//射击音效
if(FireSound)
{
	UGameplayStatics::PlaySoundAtLocation(GetWorld(),FireSound,GetActorLocation());
}

2.6 射击效果优化

如图所示,准星瞄准的前方有一块立方体挡住,射击落点在准星处,而实际上射击轨道是穿过了立方体,所以,此时射击落点应当设置在碰撞到立方体处的位置。

 这里修复的方式是在以准星为落击点时,进行了射击检测的时候,如果射击到物体时,再以枪口为起点,准星的落击碰撞点为终点进行射线检测,如果碰撞到的话,落击点为枪口射线检测的结果为最终落击点。

 在ManCharacter.cpp中的FireWeapon方法:

FVector OutWorldLocation;
FVector OutWorldDirection;
GetScreenFireStartLocationAndDirection(OutWorldLocation,OutWorldDirection);
FVector StartLocation = OutWorldLocation;
FVector EndLocation = StartLocation + OutWorldDirection * ShootDistance;
FHitResult ScreenHitResult;
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(this);
//单通道射线检测
GetWorld()->LineTraceSingleByChannel(ScreenHitResult,StartLocation,EndLocation,ECC_Visibility,QueryParams);
if (ScreenHitResult.GetActor())
{
	EndLocation = ScreenHitResult.Location;
	//以枪口为初始点的射线检测
	FHitResult MuzzleHitResult;
	FCollisionQueryParams MuzzleQueryParams;
	MuzzleQueryParams.AddIgnoredActor(this);
	FTransform MuzzleTransform = WeaponMesh->GetSocketTransform("Muzzle");
	FVector MuzzleStart = MuzzleTransform.GetLocation();
	FVector MuzzleEnd = EndLocation;
	GetWorld()->LineTraceSingleByChannel(MuzzleHitResult,MuzzleStart,MuzzleEnd,ECC_Visibility,MuzzleQueryParams);
	if (MuzzleHitResult.GetActor())
	{
		EndLocation = MuzzleHitResult.Location;
	}
	//撞击特效
	if (ImpactParticle)
	{
		UGameplayStatics::SpawnEmitterAtLocation(GetWorld(),ImpactParticle,EndLocation);
	}
}

2.7 代码重构和对应代码

2.7.1 ManCharacter.h

#pragma once

#include "CoreMinimal.h"
#include "Gameplay/BaseCharacter.h"
#include "ManCharacter.generated.h"

class USkeletalMeshComponent;
class UParticleSystem;
class USoundBase;

/**
 * 小白人角色
 */
UCLASS()
class SHOOTINGGAME_API AManCharacter : public ABaseCharacter
{
	GENERATED_BODY()

public:
	AManCharacter();
protected:
	virtual void BeginPlay() override;
	/**
	 * 武器开火
	 */
	virtual void FireWeapon() override;
	/**
	 * 获取以屏幕中心为射线检测开始点的世界位置和方向
	 * @param StartLocation 世界位置
	 * @param Direction 方向
	 */
	void GetScreenFireStartLocationAndDirection(FVector& StartLocation,FVector& Direction);

	/**
	 * 屏幕起始点射线检测
	 * @param EndLocation 屏幕起始点射线检测的碰撞点
	 * @param HitResult 碰撞结果
	 */
	void ScreenLineTrace(FVector& EndLocation,FHitResult& HitResult);
	
	/**
	 * 枪口射线检测
	 * @param OutLocation 输出的位置
	 * @param ScreenHitLocation 屏幕点为起始点的射线检测碰撞点
	 */
	void MuzzleLineTrace(FVector& OutLocation,FVector ScreenHitLocation);
	
	/**
	 * 设置开火的枪口特效,射击轨道特效,射击音效
	 * @param TargetLocation 目标点
	 * @param Hit 射线检测撞击结果
	 */
	void FireEffect(FVector TargetLocation,FHitResult Hit);
private:
	UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category=Components,meta=(AllowPrivateAccess="true"))
	USkeletalMeshComponent* WeaponMesh;

	UPROPERTY(EditAnywhere,BlueprintReadOnly,Category=Properties,meta=(AllowPrivateAccess="true"))
	float ShootDistance;//射击距离

	UPROPERTY(EditAnywhere,BlueprintReadOnly,Category=Properties,meta=(AllowPrivateAccess="true"))
	UParticleSystem* MuzzleParticle;//枪口特效

	UPROPERTY(EditAnywhere,BlueprintReadOnly,Category=Properties,meta=(AllowPrivateAccess="true"))
	UParticleSystem* ImpactParticle;//枪口特效

	UPROPERTY(EditAnywhere,BlueprintReadOnly,Category=Properties,meta=(AllowPrivateAccess="true"))
	UParticleSystem* BeamParticle;//射击轨迹特效

	UPROPERTY(EditAnywhere,BlueprintReadOnly,Category=Properties,meta=(AllowPrivateAccess="true"))
	USoundBase* FireSound;//射击音效
};

2.7.2 ManCharacter.cpp

#include "Gameplay/ManCharacter.h"
#include "Kismet/GameplayStatics.h"
#include "Particles/ParticleSystemComponent.h"

AManCharacter::AManCharacter()
{
	PrimaryActorTick.bCanEverTick = true;
	WeaponMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("WeaponMesh"));
	WeaponMesh->SetupAttachment(GetMesh(),"WeaponSocket");
	ShootDistance = 10000.0f;
}

void AManCharacter::BeginPlay()
{
	Super::BeginPlay();
}

void AManCharacter::FireWeapon()
{
	Super::FireWeapon();
	FVector OutLocation;
	FHitResult ScreenHitResult;
	//屏幕起始点射线检测
	ScreenLineTrace(OutLocation,ScreenHitResult);
	//射击效果
	FireEffect(OutLocation,ScreenHitResult);
}

void AManCharacter::GetScreenFireStartLocationAndDirection(FVector& StartLocation, FVector& Direction)
{
	if (GEngine && GEngine->GameViewport)
	{
		FVector2D OutViewportSize;
		GEngine->GameViewport->GetViewportSize(OutViewportSize);
		FVector2D HalfViewportSize = FVector2D(OutViewportSize.X/2,OutViewportSize.Y/2);
		APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(),0);
		PC->DeprojectScreenPositionToWorld(HalfViewportSize.X,HalfViewportSize.Y,StartLocation,Direction);
	}
}

void AManCharacter::ScreenLineTrace(FVector& EndLocation,FHitResult& HitResult)
{
	FVector OutWorldLocation;
	FVector OutWorldDirection;
	GetScreenFireStartLocationAndDirection(OutWorldLocation,OutWorldDirection);
	const FVector StartLocation = OutWorldLocation;
	EndLocation = StartLocation + OutWorldDirection * ShootDistance;
	FCollisionQueryParams QueryParams;
	QueryParams.AddIgnoredActor(this);
	//单通道射线检测
	GetWorld()->LineTraceSingleByChannel(HitResult,StartLocation,EndLocation,ECC_Visibility,QueryParams);
	if (HitResult.GetActor())
	{
		EndLocation = HitResult.Location;
		//以枪口为初始点的射线检测
		MuzzleLineTrace(EndLocation,EndLocation);
	}
}

void AManCharacter::MuzzleLineTrace(FVector& OutLocation,FVector ScreenHitLocation)
{
	FHitResult MuzzleHitResult;
	FCollisionQueryParams MuzzleQueryParams;
	MuzzleQueryParams.AddIgnoredActor(this);
	FTransform MuzzleTransform = WeaponMesh->GetSocketTransform("Muzzle");
	FVector MuzzleStart = MuzzleTransform.GetLocation();
	FVector MuzzleEnd = ScreenHitLocation;
	GetWorld()->LineTraceSingleByChannel(MuzzleHitResult,MuzzleStart,MuzzleEnd,ECC_Visibility,MuzzleQueryParams);
	if (MuzzleHitResult.GetActor())
	{
		OutLocation = MuzzleHitResult.Location;
	}
}

void AManCharacter::FireEffect(FVector TargetLocation,FHitResult Hit)
{
	const FTransform MuzzleTransform = WeaponMesh->GetSocketTransform("Muzzle");
	if (Hit.GetActor())
	{
		//撞击特效
		if (ImpactParticle)
		{
			UGameplayStatics::SpawnEmitterAtLocation(GetWorld(),ImpactParticle,TargetLocation);
		}
	}
	//枪口特效
	if (MuzzleParticle)
	{
		UGameplayStatics::SpawnEmitterAtLocation(GetWorld(),MuzzleParticle,MuzzleTransform.GetLocation(),
			MuzzleTransform.Rotator(),FVector(0.5f));
	}
	//射击轨迹特效
	if (BeamParticle)
	{
		UParticleSystemComponent* PSComp = UGameplayStatics::SpawnEmitterAtLocation(GetWorld(),BeamParticle,MuzzleTransform.GetLocation());
		if (PSComp)
		{
			PSComp->SetVectorParameter("Target",TargetLocation);
		}
	}
	//射击音效
	if(FireSound)
	{
		UGameplayStatics::PlaySoundAtLocation(GetWorld(),FireSound,GetActorLocation());
	}
}

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卡西莫多说

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值