武器基类用于设置一些武器通用的属性和方法,供之后的具体武器派生类使用。
先写一个武器状态枚举类,当武器状态发生改变时(如装备为主武器、装备为副武器、掉落等)需要适时地切换状态,可以用于一些判断和武器状态改变时OnRep函数的执行。
UENUM(BlueprintType)
enum class EWeaponState : uint8
{
EWS_Initial UMETA(DisplayName = "Initial State"),
EWS_Equipped UMETA(DisplayName = "Equipped"),
EWS_EquippedSecondary UMETA(DisplayName = "Equipped Secondary"),
EWS_Dropped UMETA(DisplayName = "Dropped"),
EWS_MAX UMETA(DisplayName = "DefaultMAX")
};
接下来考虑一下武器的一些通用变量,可以想到的有 武器Mesh,瞄准线,瞄准时镜头拉近的深度和拉近速度,是否允许连续开火,连续开火的间隔,武器装备的声音(开火的声音和粒子效果等不需要声明在C++类中,可以直接放进武器的开火动画里),与拾取相关的球形组件,拾取提示Widget,武器状态,武器类型,开火动画,开火弹出的弹壳,子弹数,拥有这把武器的角色以及角色的控制器等等。
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon Properties")
USkeletalMeshComponent* WeaponMesh;
//武器准星相关
UPROPERTY(EditAnywhere, Category = Crosshairs)
class UTexture2D* CrosshairsCenter;
UPROPERTY(EditAnywhere, Category = Crosshairs)
UTexture2D* CrosshairsLeft;
UPROPERTY(EditAnywhere, Category = Crosshairs)
UTexture2D* CrosshairsRight;
UPROPERTY(EditAnywhere, Category = Crosshairs)
UTexture2D* CrosshairsTop;
UPROPERTY(EditAnywhere, Category = Crosshairs)
UTexture2D* CrosshairsBottom;
//瞄准时拉近相关参数
UPROPERTY(EditAnywhere)
float ZoomedFOV = 30.f;
UPROPERTY(EditAnywhere)
float ZoomInterpSpeed = 20.f;
//连续开火相关
UPROPERTY(EditAnywhere, Category = Combat)
float FireDelay = .15f;
UPROPERTY(EditAnywhere, Category = Combat)
bool bAutomatic = true;
//装备音效
UPROPERTY(EditAnywhere)
class USoundCue* EquipSound;
//拾取相关
UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")
class USphereComponent* AreaSphere;
UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")
class UWidgetComponent* PickupWidget;
//武器状态
UPROPERTY(ReplicatedUsing = OnRep_WeaponState, VisibleAnywhere, Category = "Weapon Properties")
EWeaponState WeaponState;
//开火动画
UPROPERTY(EditAnywhere, Category = "Weapon Properties")
class UAnimationAsset* FireAnimation;
//弹壳
UPROPERTY(EditAnywhere)
TSubclassOf<class ACasing> CasingClass;
//当前子弹和弹夹最大子弹
UPROPERTY(EditAnywhere, ReplicatedUsing = OnRep_Ammo)
int32 Ammo;
UPROPERTY(EditAnywhere)
int32 MagCapacity;
//拥有者
UPROPERTY()
class ABlasterCharacter* BlasterOwnerCharacter;
UPROPERTY()
class ABlasterPlayerController* BlasterOwnerController;
//所属武器类型
UPROPERTY(EditAnywhere)
EWeaponType WeaponType;
声明所要用到的函数,之后讲解这些函数功能的实现:
public:
AWeapon();
virtual void Tick(float DeltaTime) override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
virtual void OnRep_Owner() override;
void SetHUDAmmo();
void ShowPickupWidget(bool bShowWidget);
virtual void Fire(const FVector& HitTarget);
void Dropped(); //角色死亡时武器掉落
void AddAmmo(int32 AmmoToAdd); //装填子弹
//武器描边相关
void EnableCustomDepth(bool bEnable);
protected:
virtual void BeginPlay() override;
virtual void OnWeaponStateSet();
virtual void OnEquipped();
virtual void OnDropped();
virtual void OnEquippedSecondary();
UFUNCTION()
virtual void OnSphereOverlap(
UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult
);
UFUNCTION()
void OnSphereEndOverlap(
UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex
);
private:
UFUNCTION()
void OnRep_WeaponState();
UFUNCTION()
void OnRep_Ammo();
void SpendRound();
构造函数:声明该对象为可复制属性,设置武器骨骼,设置初始碰撞属性为无碰撞,开启武器描边,设置球形组件,暂时先设为无碰撞(会在BeginPlay修改),设置拾取UI提示并附加到根组件上。
AWeapon::AWeapon()
{
PrimaryActorTick.bCanEverTick = false;
bReplicates = true;
SetReplicateMovement(true);
WeaponMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("WeaponMesh"));
WeaponMesh->SetupAttachment(RootComponent);
SetRootComponent(WeaponMesh);
WeaponMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);
WeaponMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Ignore);
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
WeaponMesh->SetCustomDepthStencilValue(CUSTOM_DEPTH_BLUE);
WeaponMesh->MarkRenderStateDirty();
EnableCustomDepth(true);
AreaSphere = CreateDefaultSubobject<USphereComponent>(TEXT("AreaSphere"));
AreaSphere->SetupAttachment(RootComponent);
AreaSphere->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
PickupWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("PickupWidget"));
PickupWidget->SetupAttachment(RootComponent);
}
武器描边开启,调用Mesh的SetRenderCustomDepth函数。
void AWeapon::EnableCustomDepth(bool bEnable)
{
if(WeaponMesh)
{
WeaponMesh->SetRenderCustomDepth(bEnable);
}
}
BeginPlay:设置拾取提示UI显示为false,等到玩家靠近时才显示。设置球形碰撞组件为只与Pawn进行Overlap交互,并绑定重叠事件与结束重叠事件。
void AWeapon::BeginPlay()
{
Super::BeginPlay();
if(PickupWidget)
{
PickupWidget->SetVisibility(false);
}
if(HasAuthority())
{
AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
AreaSphere->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
AreaSphere->OnComponentBeginOverlap.AddDynamic(this, &AWeapon::OnSphereOverlap);
AreaSphere->OnComponentEndOverlap.AddDynamic(this, &AWeapon::OnSphereEndOverlap);
}
}
GetLifetimeReplicatedProps:设置复制相关属性需要重写的函数,使用DOREPLIFETIME对需要复制的变量进行设置,这里将武器状态和当前子弹数设为复制变量。
void AWeapon::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AWeapon, WeaponState);
DOREPLIFETIME(AWeapon, Ammo);
}
重叠事件:尝试将OtherActor转换为角色类型,若转换成功,说明是角色与球体发生了重叠,将角色中OverlappingWeapon变量设为该武器,后续当角色按下拾取键时,就会判断OverlappingWeapon是否为空,不为空就会将该武器拾取。控制UI显示功能也放到角色SetOverlappingWeapon函数中了。
结束重叠事件:将角色中的OverlappingWeapon设为空。
void AWeapon::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor);
if(BlasterCharacter)
{
BlasterCharacter->SetOverlappingWeapon(this);
}
}
void AWeapon::OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor);
if(BlasterCharacter)
{
BlasterCharacter->SetOverlappingWeapon(nullptr);
}
}
OnRep_Ammo:Ammo变量的回调函数,将该武器拥有者的UI面板中子弹数进行更新,由于霰弹枪填充子弹动画不是固定的,需要按填充子弹数进行改变,因此对于霰弹枪,判断子弹是否为满,是的话进一步判断是否应该将动画跳转至填充结束的Section。
void AWeapon::OnRep_Ammo()
{
BlasterOwnerCharacter = BlasterOwnerCharacter == nullptr ? Cast<ABlasterCharacter>(GetOwner()) : BlasterOwnerCharacter;
if(BlasterOwnerCharacter && BlasterOwnerCharacter->GetCombat() && IsFull())
{
BlasterOwnerCharacter->GetCombat()->JumpToShotgunEnd();
}
SetHUDAmmo();
}
OnRep_Owner:所有者改变时,意味着所有者玩家切换或拾取了武器,对其所有者的UI面板进行更新。
void AWeapon::OnRep_Owner()
{
Super::OnRep_Owner();
if(GetOwner() == nullptr)
{
BlasterOwnerCharacter = nullptr;
BlasterOwnerController = nullptr;
}
else
{
BlasterOwnerCharacter = BlasterOwnerCharacter == nullptr ? Cast<ABlasterCharacter>(GetOwner()) : BlasterOwnerCharacter;
if(BlasterOwnerCharacter && BlasterOwnerCharacter->GetEquippedWeapon() && BlasterOwnerCharacter->GetEquippedWeapon() == this)
{
SetHUDAmmo();
}
}
}
SpendRound:开枪后子弹减一,但要控制在0到弹夹上限之间,然后更新UI。
void AWeapon::SpendRound()
{
Ammo = FMath::Clamp(Ammo - 1, 0, MagCapacity);
SetHUDAmmo();
}
SetHUDAmmo:更新UI中的子弹显示,调用Controller中的SetHUDWeaponAmmo方法进行更新,传入当前子弹数。
void AWeapon::SetHUDAmmo()
{
BlasterOwnerCharacter = BlasterOwnerCharacter == nullptr ? Cast<ABlasterCharacter>(GetOwner()) : BlasterOwnerCharacter;
if (BlasterOwnerCharacter)
{
BlasterOwnerController = BlasterOwnerController == nullptr ? Cast<ABlasterPlayerController>(BlasterOwnerCharacter->Controller) : BlasterOwnerController;
if (BlasterOwnerController)
{
BlasterOwnerController->SetHUDWeaponAmmo(Ammo);
}
}
}
与武器状态相关的函数:设置武器状态,在武器状态复制回调函数中,判断当前武器状态,若为装备状态,取消拾取提示UI显示,将球形碰撞体取消,禁用武器骨骼的物理、重力和碰撞,关闭描边, 进入武器装备状态;若为第二武器装备状态,也就是放到后背上的状态,在武器类中需要做的事与主武器装备相同,不同的地方在角色对应的装备函数中,如Attach的骨骼不同等;若为丢弃状态,则启用Mesh的物理模拟和重力效果使其自由落地,并开启碰撞,同时重新启用球形检测和描边。
void AWeapon::SetWeaponState(EWeaponState State)
{
WeaponState = State;
OnWeaponStateSet();
}
void AWeapon::OnWeaponStateSet()
{
switch (WeaponState)
{
case EWeaponState::EWS_Equipped:
OnEquipped();
break;
case EWeaponState::EWS_EquippedSecondary:
OnEquippedSecondary();
break;
case EWeaponState::EWS_Dropped:
OnDropped();
break;
}
}
void AWeapon::OnRep_WeaponState()
{
OnWeaponStateSet();
}
void AWeapon::OnEquipped()
{
ShowPickupWidget(false);
AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
WeaponMesh->SetSimulatePhysics(false);
WeaponMesh->SetEnableGravity(false);
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
EnableCustomDepth(false);
}
void AWeapon::OnDropped()
{
if(HasAuthority())
{
AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
}
WeaponMesh->SetSimulatePhysics(true);
WeaponMesh->SetEnableGravity(true);
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
WeaponMesh->SetCustomDepthStencilValue(CUSTOM_DEPTH_BLUE);
WeaponMesh->MarkRenderStateDirty();
EnableCustomDepth(true);
}
void AWeapon::OnEquippedSecondary()
{
ShowPickupWidget(false);
AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
WeaponMesh->SetSimulatePhysics(false);
WeaponMesh->SetEnableGravity(false);
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
EnableCustomDepth(false);
}
ShowPickupWidget:显示拾取提示的UI。
void AWeapon::ShowPickupWidget(bool bShowWidget)
{
if(PickupWidget)
{
PickupWidget->SetVisibility(bShowWidget);
}
}
Fire:开火时,播放开火动画,若设置了弹壳,则生成一个弹壳,之后调用SpendRound函数更新子弹。
void AWeapon::Fire(const FVector& HitTarget)
{
if(FireAnimation)
{
WeaponMesh->PlayAnimation(FireAnimation, false);
}
if(CasingClass)
{
const USkeletalMeshSocket* AmmoEjectSocket = WeaponMesh->GetSocketByName(FName("AmmoEject"));
if(AmmoEjectSocket)
{
FTransform SocketTransform = AmmoEjectSocket->GetSocketTransform(GetWeaponMesh());
UWorld* World = GetWorld();
if(World)
{
World->SpawnActor<ACasing>(
CasingClass,
SocketTransform.GetLocation(),
SocketTransform.GetRotation().Rotator()
);
}
}
}
SpendRound();
}
Dropped:丢弃武器时,设置武器状态为丢弃状态,将武器从角色手上脱离,设Owner、BlasterOwnerCharacter 和 BlasterOwnerController 均为nullptr。
void AWeapon::Dropped()
{
SetWeaponState(EWeaponState::EWS_Dropped);
FDetachmentTransformRules DetachRules(EDetachmentRule::KeepWorld, true);
WeaponMesh->DetachFromComponent(DetachRules);
SetOwner(nullptr);
BlasterOwnerCharacter = nullptr;
BlasterOwnerController = nullptr;
}
AddAmmo:添加子弹。
IsEmpty:判断子弹是否为空。
IsFull:判断子弹是否为满。
void AWeapon::AddAmmo(int32 AmmoToAdd)
{
Ammo = FMath::Clamp(Ammo - AmmoToAdd, 0, MagCapacity);
SetHUDAmmo();
}
bool AWeapon::IsEmpty()
{
return Ammo <= 0;
}
bool AWeapon::IsFull()
{
return Ammo == MagCapacity;
}
总体.h文件如下:
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "WeaponTypes.h"
#include "GameFramework/Actor.h"
#include "Weapon.generated.h"
UENUM(BlueprintType)
enum class EWeaponState : uint8
{
EWS_Initial UMETA(DisplayName = "Initial State"),
EWS_Equipped UMETA(DisplayName = "Equipped"),
EWS_EquippedSecondary UMETA(DisplayName = "Equipped Secondary"),
EWS_Dropped UMETA(DisplayName = "Dropped"),
EWS_MAX UMETA(DisplayName = "DefaultMAX")
};
UCLASS()
class TPS_API AWeapon : public AActor
{
GENERATED_BODY()
public:
AWeapon();
virtual void Tick(float DeltaTime) override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
virtual void OnRep_Owner() override;
void SetHUDAmmo();
void ShowPickupWidget(bool bShowWidget);
virtual void Fire(const FVector& HitTarget);
void Dropped(); //角色死亡时武器掉落
void AddAmmo(int32 AmmoToAdd); //装填子弹
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon Properties")
USkeletalMeshComponent* WeaponMesh;
//武器准星相关
UPROPERTY(EditAnywhere, Category = Crosshairs)
class UTexture2D* CrosshairsCenter;
UPROPERTY(EditAnywhere, Category = Crosshairs)
UTexture2D* CrosshairsLeft;
UPROPERTY(EditAnywhere, Category = Crosshairs)
UTexture2D* CrosshairsRight;
UPROPERTY(EditAnywhere, Category = Crosshairs)
UTexture2D* CrosshairsTop;
UPROPERTY(EditAnywhere, Category = Crosshairs)
UTexture2D* CrosshairsBottom;
//瞄准时拉近相关参数
UPROPERTY(EditAnywhere)
float ZoomedFOV = 30.f;
UPROPERTY(EditAnywhere)
float ZoomInterpSpeed = 20.f;
//连续开火相关
UPROPERTY(EditAnywhere, Category = Combat)
float FireDelay = .15f;
UPROPERTY(EditAnywhere, Category = Combat)
bool bAutomatic = true;
UPROPERTY(EditAnywhere)
class USoundCue* EquipSound;
//武器描边相关
void EnableCustomDepth(bool bEnable);
protected:
virtual void BeginPlay() override;
virtual void OnWeaponStateSet();
virtual void OnEquipped();
virtual void OnDropped();
virtual void OnEquippedSecondary();
UFUNCTION()
virtual void OnSphereOverlap(
UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult
);
UFUNCTION()
void OnSphereEndOverlap(
UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex
);
private:
UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")
class USphereComponent* AreaSphere;
UPROPERTY(ReplicatedUsing = OnRep_WeaponState, VisibleAnywhere, Category = "Weapon Properties")
EWeaponState WeaponState;
UFUNCTION()
void OnRep_WeaponState();
UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")
class UWidgetComponent* PickupWidget;
UPROPERTY(EditAnywhere, Category = "Weapon Properties")
class UAnimationAsset* FireAnimation;
UPROPERTY(EditAnywhere)
TSubclassOf<class ACasing> CasingClass;
UPROPERTY(EditAnywhere, ReplicatedUsing = OnRep_Ammo)
int32 Ammo;
UFUNCTION()
void OnRep_Ammo();
void SpendRound();
UPROPERTY(EditAnywhere)
int32 MagCapacity;
UPROPERTY()
class ABlasterCharacter* BlasterOwnerCharacter;
UPROPERTY()
class ABlasterPlayerController* BlasterOwnerController;
UPROPERTY(EditAnywhere)
EWeaponType WeaponType;
public:
void SetWeaponState(EWeaponState State);
FORCEINLINE USphereComponent* GetAreaSphere() const {return AreaSphere;}
FORCEINLINE USkeletalMeshComponent* GetWeaponMesh() const {return WeaponMesh;}
FORCEINLINE float GetZoomedFOV() const {return ZoomedFOV;}
FORCEINLINE float GetZoomInterpSpeed() const {return ZoomInterpSpeed;}
bool IsEmpty();
bool IsFull();
FORCEINLINE EWeaponType GetWeaponType() const {return WeaponType;}
FORCEINLINE int32 GetAmmo() const {return Ammo;}
FORCEINLINE int32 GetMagCapacity() const {return MagCapacity;}
};
.cpp文件如下:
// Fill out your copyright notice in the Description page of Project Settings.
#include "Weapon.h"
#include "Components/SphereComponent.h"
#include "Components/WidgetComponent.h"
#include "Net/UnrealNetwork.h"
#include "TPS/Character/BlasterCharacter.h"
#include "Casing.h"
#include "Engine/SkeletalMeshSocket.h"
#include "TPS/BlasterComponents/CombatComponent.h"
#include "TPS/PlayerController/BlasterPlayerController.h"
AWeapon::AWeapon()
{
PrimaryActorTick.bCanEverTick = false;
bReplicates = true;
SetReplicateMovement(true);
WeaponMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("WeaponMesh"));
WeaponMesh->SetupAttachment(RootComponent);
SetRootComponent(WeaponMesh);
WeaponMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);
WeaponMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Ignore);
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
WeaponMesh->SetCustomDepthStencilValue(CUSTOM_DEPTH_BLUE);
WeaponMesh->MarkRenderStateDirty();
EnableCustomDepth(true);
AreaSphere = CreateDefaultSubobject<USphereComponent>(TEXT("AreaSphere"));
AreaSphere->SetupAttachment(RootComponent);
AreaSphere->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
PickupWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("PickupWidget"));
PickupWidget->SetupAttachment(RootComponent);
}
void AWeapon::EnableCustomDepth(bool bEnable)
{
if(WeaponMesh)
{
WeaponMesh->SetRenderCustomDepth(bEnable);
}
}
void AWeapon::BeginPlay()
{
Super::BeginPlay();
if(PickupWidget)
{
PickupWidget->SetVisibility(false);
}
if(HasAuthority())
{
AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
AreaSphere->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
AreaSphere->OnComponentBeginOverlap.AddDynamic(this, &AWeapon::OnSphereOverlap);
AreaSphere->OnComponentEndOverlap.AddDynamic(this, &AWeapon::OnSphereEndOverlap);
}
}
void AWeapon::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AWeapon::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AWeapon, WeaponState);
DOREPLIFETIME(AWeapon, Ammo);
}
void AWeapon::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor);
if(BlasterCharacter)
{
BlasterCharacter->SetOverlappingWeapon(this);
}
}
void AWeapon::OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor);
if(BlasterCharacter)
{
BlasterCharacter->SetOverlappingWeapon(nullptr);
}
}
void AWeapon::OnRep_Ammo()
{
BlasterOwnerCharacter = BlasterOwnerCharacter == nullptr ? Cast<ABlasterCharacter>(GetOwner()) : BlasterOwnerCharacter;
if(BlasterOwnerCharacter && BlasterOwnerCharacter->GetCombat() && IsFull() )
{
BlasterOwnerCharacter->GetCombat()->JumpToShotgunEnd();
}
SetHUDAmmo();
}
void AWeapon::OnRep_Owner()
{
Super::OnRep_Owner();
if(GetOwner() == nullptr)
{
BlasterOwnerCharacter = nullptr;
BlasterOwnerController = nullptr;
}
else
{
BlasterOwnerCharacter = BlasterOwnerCharacter == nullptr ? Cast<ABlasterCharacter>(GetOwner()) : BlasterOwnerCharacter;
if(BlasterOwnerCharacter && BlasterOwnerCharacter->GetEquippedWeapon() && BlasterOwnerCharacter->GetEquippedWeapon() == this)
{
SetHUDAmmo();
}
}
}
void AWeapon::SpendRound()
{
Ammo = FMath::Clamp(Ammo - 1, 0, MagCapacity);
SetHUDAmmo();
}
void AWeapon::SetHUDAmmo()
{
BlasterOwnerCharacter = BlasterOwnerCharacter == nullptr ? Cast<ABlasterCharacter>(GetOwner()) : BlasterOwnerCharacter;
if (BlasterOwnerCharacter)
{
BlasterOwnerController = BlasterOwnerController == nullptr ? Cast<ABlasterPlayerController>(BlasterOwnerCharacter->Controller) : BlasterOwnerController;
if (BlasterOwnerController)
{
BlasterOwnerController->SetHUDWeaponAmmo(Ammo);
}
}
}
void AWeapon::SetWeaponState(EWeaponState State)
{
WeaponState = State;
OnWeaponStateSet();
}
void AWeapon::OnWeaponStateSet()
{
switch (WeaponState)
{
case EWeaponState::EWS_Equipped:
OnEquipped();
break;
case EWeaponState::EWS_EquippedSecondary:
OnEquippedSecondary();
break;
case EWeaponState::EWS_Dropped:
OnDropped();
break;
}
}
void AWeapon::OnRep_WeaponState()
{
OnWeaponStateSet();
}
void AWeapon::OnEquipped()
{
ShowPickupWidget(false);
AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
WeaponMesh->SetSimulatePhysics(false);
WeaponMesh->SetEnableGravity(false);
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
EnableCustomDepth(false);
}
void AWeapon::OnDropped()
{
if(HasAuthority())
{
AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
}
WeaponMesh->SetSimulatePhysics(true);
WeaponMesh->SetEnableGravity(true);
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
WeaponMesh->SetCustomDepthStencilValue(CUSTOM_DEPTH_BLUE);
WeaponMesh->MarkRenderStateDirty();
EnableCustomDepth(true);
}
void AWeapon::OnEquippedSecondary()
{
ShowPickupWidget(false);
AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
WeaponMesh->SetSimulatePhysics(false);
WeaponMesh->SetEnableGravity(false);
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
EnableCustomDepth(false);
}
void AWeapon::ShowPickupWidget(bool bShowWidget)
{
if(PickupWidget)
{
PickupWidget->SetVisibility(bShowWidget);
}
}
void AWeapon::Fire(const FVector& HitTarget)
{
if(FireAnimation)
{
WeaponMesh->PlayAnimation(FireAnimation, false);
}
if(CasingClass)
{
const USkeletalMeshSocket* AmmoEjectSocket = WeaponMesh->GetSocketByName(FName("AmmoEject"));
if(AmmoEjectSocket)
{
FTransform SocketTransform = AmmoEjectSocket->GetSocketTransform(GetWeaponMesh());
UWorld* World = GetWorld();
if(World)
{
World->SpawnActor<ACasing>(
CasingClass,
SocketTransform.GetLocation(),
SocketTransform.GetRotation().Rotator()
);
}
}
}
SpendRound();
}
void AWeapon::Dropped()
{
SetWeaponState(EWeaponState::EWS_Dropped);
FDetachmentTransformRules DetachRules(EDetachmentRule::KeepWorld, true);
WeaponMesh->DetachFromComponent(DetachRules);
SetOwner(nullptr);
BlasterOwnerCharacter = nullptr;
BlasterOwnerController = nullptr;
}
void AWeapon::AddAmmo(int32 AmmoToAdd)
{
Ammo = FMath::Clamp(Ammo - AmmoToAdd, 0, MagCapacity);
SetHUDAmmo();
}
bool AWeapon::IsEmpty()
{
return Ammo <= 0;
}
bool AWeapon::IsFull()
{
return Ammo == MagCapacity;
}