Chapter11-4
炮彈和範圍攻擊
範圍攻擊經常會包含一些炮彈。炮彈既可以包含槍發出的子彈,也可以算作包含一些魔法攻擊或者火球攻擊。爲了編碼製作出一個炮彈的攻擊,你需要製作一個新的物體並且只包含一些對玩家的傷害如果打到玩家的話
我們先新建一個ABullet類繼承自Actor類
#pragma once
#include "GameFramework/Actor.h"
#include "Bullet.generated.h"
UCLASS()
class CPLUSLEARN_API ABullet : public AActor
{
GENERATED_UCLASS_BODY()
public:
// Sets default values for this actor's properties
ABullet();
//傷害
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Properties)
float Damage;
//Mesh
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = Collision)
UStaticMeshComponent *Mesh;
//碰撞體
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = Collision)
USphereComponent* ProxSphere;
//碰撞函數
UFUNCTION(BlueprintNativeEvent, Categroy = Collision)
void Prox(AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult);
};
炮彈類需要有以上這些基本的屬性
構建跑單的時候應該需要初始化Mesh和碰撞體的變量,在構造中,我們設置根節點是Mesh然後將碰撞體放到Mesh下面。碰撞檢測Mesh的變量是否應該顯示
#include "CplusLearn.h"
#include "Bullet.h"
// Sets default values
ABullet::ABullet(const class FObjectInitializer& PCIP) :Super(PCIP)
{
Mesh = PCIP.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("Mesh"));
RootComponent = Mesh;
ProxSphere = PCIP.CreateDefaultSubobject<USphereComponent>(this, TEXT("ProxSphere"));
ProxSphere->AttachTo(RootComponent);
ProxSphere->OnComponentBeginOverlap.AddDynamic(this, &ABullet::Prox);
Damage = 1;
}
void ABullet::Prox_Implementation(AActor * otherActor, UPrimitiveComponent* otherComp, int32 otherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
{
//如果碰撞到的物體組件不是碰撞到物體的根組件那麼返回
if (otherComp != otherActor->GetRootComponent())
{
return;
}
otherActor->TakeDamage(Damage, FDamageEvent(), NULL, this);
Destroy();
}
以上所用的知識和內容我們在前面的學習中都有已經瞭解過,這次主要是重複練習一下,並且添加新的功能。
我們希望怪物可以配置成1種使用武器攻擊我們玩家,1種使用遠程法術,那麼我們需要對monster和avatar進行再一次的配置。
#pragma once
#include "GameFramework/Character.h"
#include "Avatar.generated.h"
class APickupItem;
UCLASS()
class CPLUSLEARN_API AAvatar : public ACharacter
{
GENERATED_BODY()
public:
//揹包系統
//揹包
TMap<FString, int> Backpack;
//圖標
TMap<FString, UTexture2D*> Icons;
//標記揹包是否打開
bool inventoryShowing;
//可讓玩家撿起的物品
void PickUp(APickupItem *item);
//切換庫存顯示
void ToggleInventory();
//人物控制
// Sets default values for this character's properties
AAvatar();
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
// 綁定用戶輸入操作
virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
//向右移動
void MoveRight(float amount);
//向前移動
void MoveForward(float amount);
//水平方向轉動
void Yaw(float amount);
//垂直方向轉動
void Pitch(float amount);
//生命
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = HPInformation)
float HP;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = HPInformation)
float MAXHP;
//鼠標點擊事件
void MouseClicked();
//擊退
FVector knockback;
//受到傷害
float TakeDamage(float Damage, struct FDamageEvent const&DamageEvent, AController* EventInstigator, AActor* DamageCauser);
};
.cpp文件內容
#include "CplusLearn.h"
#include "Avatar.h"
#include "MyHUDMessage.h"
#include "PickupItem.h"
// Sets default values
AAvatar::AAvatar()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
HP = 50;
MAXHP = 150;
}
// Called when the game starts or when spawned
void AAvatar::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AAvatar::Tick( float DeltaTime )
{
Super::Tick( DeltaTime );
//添加擊退效果
AddMovementInput(knockback, 1.f);
knockback *= 0.5f;//擊退效果的衰退
}
// 綁定用戶按鍵輸入操作
void AAvatar::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
Super::SetupPlayerInputComponent(InputComponent);
check(InputComponent);
//綁定項目設置中按鍵與頭文件中函數功能
InputComponent->BindAxis("MyMoveForward", this, &AAvatar::MoveForward);
InputComponent->BindAxis("MyMoveRight", this, &AAvatar::MoveRight);
InputComponent->BindAxis("MyYaw", this, &AAvatar::Yaw);
InputComponent->BindAxis("MyPitch", this, &AAvatar::Pitch);
//打開庫存的按鍵綁定
InputComponent->BindAction("Inventory", IE_Pressed, this, &AAvatar::ToggleInventory);
//綁定鼠標點擊事件
InputComponent->BindAction("MouseClickedLMB", IE_Pressed, this, &AAvatar::MouseClicked);
}
void AAvatar::MoveForward(float amount)
{
//若控制器和移動的數值都存在
if (Controller && amount)
{
//獲取前進三維向量
FVector fwd = GetActorForwardVector();
//將移動輸入的大小添加到玩家前進向量之中
AddMovementInput(fwd, amount);
}
}
void AAvatar::MoveRight(float amount)
{
//若控制器和移動的數值都存在
if (Controller && amount)
{
//獲取右轉三維向量
FVector right = GetActorRightVector();
//將移動輸入的大小添加到玩家前進向量之中
AddMovementInput(right, amount);
}
}
void AAvatar::Yaw(float amount)
{
//揹包打開玩家不可移動
if (inventoryShowing)
{
APlayerController *PController = GetWorld()->GetFirstPlayerController();
AMyHUDMessage *hud = Cast<AMyHUDMessage>(PController->GetHUD());
hud->MouseMoved();
return;
}
else{
AddControllerYawInput(200.f*amount*GetWorld()->GetDeltaSeconds());
}
}
void AAvatar::Pitch(float amount)
{
AddControllerPitchInput(200.f*amount*GetWorld()->GetDeltaSeconds());
}
//開關揹包
void AAvatar::ToggleInventory()
{
/*if (GEngine)
{
GEngine->AddOnScreenDebugMessage(0, 5.f, FColor::Red, "Show Inventory");
}*/
//獲取當前玩家控制器
APlayerController *PController = GetWorld()->GetFirstPlayerController();
//獲取當前玩家UI界面
AMyHUDMessage *hud = Cast<AMyHUDMessage>(PController->GetHUD());
//當揹包打開時觸發
if (inventoryShowing)
{
//清除所有控件
hud->clearWidgets();
inventoryShowing = false;//揹包處於關閉狀態
PController->bShowMouseCursor = false;//設置鼠標不可見
return;
}
//若揹包沒有打開
inventoryShowing = true;
PController->bShowMouseCursor = true;
//循環讀取揹包中物品
for (TMap<FString, int>::TIterator it = Backpack.CreateIterator(); it; ++it)
{
FString fs = it->Key + FString::Printf(TEXT("x%d"), it->Value);
UTexture2D* tex;
if (Icons.Find(it->Key))
{
tex = Icons[it->Key];
//添加控件
hud->addWidget(Widget(Icon(fs, tex)));
}
}
}
void AAvatar::PickUp(APickupItem *item)
{
if (Backpack.Find(item->Name))
{
Backpack[item->Name] += item->Quantity;
}
else
{
Backpack.Add(item->Name, item->Quantity);
Icons.Add(item->Name, item->Icon);
}
}
void AAvatar::MouseClicked()
{
APlayerController* PController = GetWorld()->GetFirstPlayerController();
AMyHUDMessage *hud = Cast<AMyHUDMessage>(PController->GetHUD());
hud->MouseClicked();
}
float AAvatar::TakeDamage(float Damage, struct FDamageEvent const&DamageEvent, AController* EventInstigator,AActor* DamageCauser)
{
knockback = GetActorLocation() - DamageCauser->GetActorLocation();//擊退的距離=玩家當前距離減去傷害程度
knockback.Normalize();//單位化
knockback *= Damage * 500;//按比例計算傷害
return Damage;
}
Monster.h
#pragma once
#include "GameFramework/Character.h"
#include "Monster.generated.h"
class ABullet;
class AMeleeWeapon;
UCLASS()
class CPLUSLEARN_API AMonster : public ACharacter
{
GENERATED_UCLASS_BODY()
public:
// Sets default values for this character's properties
AMonster();
//每一幀調用使得怪物朝向玩家
virtual void Tick(float DeltaSeconds)override;
//怪物的速度
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MonsterProperties)
float Speed;
//怪物的生命值
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MonsterProperties)
float HitPoints;
//怪物的經驗
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MonsterProperties)
int32 Experiences;
//怪物掉落的物品
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MonsterProperties)
UClass* BPLoot;
//基礎傷害
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MonsterProperties)
float BaseAttackDamage;
//攻擊時間間隔
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MonsterProperties)
float AttackTimeout;
//怪物上次打擊到現在的時間
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = MonsterProperties)
float TimeSinceLastStrike;
//怪物可視範圍
UPROPERTY(VisibleDefaultsOnly,BlueprintReadOnly,Category=Collision)
USphereComponent* SightSphere;
//怪物攻擊範圍
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = Collision)
USphereComponent * AttackRangeSphere;
//武器
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MonsterProperties)
UClass* BPMeleeWeapon;
//初始化武器
AMeleeWeapon* MeleeWeapon;
//初始化組件
virtual void PostInitializeComponents()override;
//是否在攻擊範圍
UFUNCTION(BlueprintCallable,Category = "Collision")
bool IsInAttackRangeOfPlayer();
//揮劍
UFUNCTION(BlueprintCallable, Category = "Collision")
void SwordSwung();
//重置玩家效果
UFUNCTION(BlueprintCallable, Category = "Collision")
void ResetPlayer();
//追蹤可視範圍
inline bool isInSightRange(float d)
{
return d < SightSphere->GetScaledSphereRadius();
}
//攻擊可視範圍
inline bool isInAttackRange(float d)
{
return d < AttackRangeSphere->GetScaledSphereRadius();
}
//發射的子彈
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MonsterProperties)
UClass* BPBullet;
//子彈發射推力
UPROPERTY(EditAnywhere, BlueprintReadWrite,Category = MonsterProperties)
float BulletLaunchImpulse;
//怪物攻擊指定人物
void Attack(AActor*thing);
};
.cpp文件
#include "CplusLearn.h"
#include "Monster.h"
#include "Avatar.h"
#include "MeleeWeapon.h"
#include "Bullet.h"
// Sets default values
AMonster::AMonster(const class FObjectInitializer&PCIP) :Super(PCIP)
{
//初始化怪物信息
Speed = 20;
HitPoints = 20;
Experiences = 0;
BPLoot = NULL;
BPBullet = NULL;
BPMeleeWeapon = NULL;
BaseAttackDamage = 1;
AttackTimeout = 1.5f;
TimeSinceLastStrike = 0;
//將怪物可視範圍設置的碰撞體附在根節點下
SightSphere = PCIP.CreateDefaultSubobject<USphereComponent>(this, TEXT("SightSphere"));
SightSphere->AttachTo(RootComponent);
AttackRangeSphere = PCIP.CreateDefaultSubobject<USphereComponent>(this, TEXT("AttackRangeSphere"));
AttackRangeSphere->AttachTo(RootComponent);
}
void AMonster::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
AAvatar *avatar = Cast<AAvatar>(UGameplayStatics::GetPlayerPawn(GetWorld(), 0));
//若沒有找到玩家則返回
if (!avatar)return;
//找到玩家時,設置朝向玩家的三維向量是 玩家的座標-當前怪物的座標
FVector toPlayer = avatar->GetActorLocation() - GetActorLocation();
//與玩家的距離
float distanceToPlayer = toPlayer.Size();
//如果與玩家的距離比偵測範圍大,返回
if (!isInSightRange(distanceToPlayer))
{
return;
}
//標準化向量 用三維向量除以直線距離
toPlayer /= distanceToPlayer;
//怪物實際向玩家移動
//怪物朝向玩家旋轉
FRotator toPlayerRotation = toPlayer.Rotation();
//讓怪物朝向的高爲0
toPlayerRotation.Pitch = 0;
//將根節點也就是怪物的整體設置面向玩家
RootComponent->SetWorldRotation(toPlayerRotation);
if (isInAttackRange(distanceToPlayer))
{
//顯示攻擊
if (!TimeSinceLastStrike)
{
Attack(avatar);
}
TimeSinceLastStrike += DeltaSeconds;
//如果上次攻擊事件長度大於攻擊時間,清零
if (TimeSinceLastStrike > AttackTimeout)
{
TimeSinceLastStrike = 0;
}
return;
}
else
{
if (MeleeWeapon)
{
// rest the melee weapon (not swinging)
MeleeWeapon->Reset();
}
AddMovementInput(toPlayer, Speed*DeltaSeconds);
}
}
void AMonster::PostInitializeComponents()
{
Super::PostInitializeComponents();
//若武器存在
if (BPMeleeWeapon)
{
//獲取武器
MeleeWeapon = GetWorld()->SpawnActor<AMeleeWeapon>(BPMeleeWeapon, FVector(0), FRotator(0));
//有武器
if (MeleeWeapon)
{
MeleeWeapon->WeaponHolder = this;
//綁定到模型的手上
const USkeletalMeshSocket * socket = Mesh->GetSocketByName("Sword");
socket->AttachActor(MeleeWeapon, Mesh);
}
}
}
bool AMonster::IsInAttackRangeOfPlayer()
{
AAvatar *avatar = Cast<AAvatar>(UGameplayStatics::GetPlayerPawn(GetWorld(), 0));
//若沒有找到玩家則返回
if (!avatar)return false;
//找到玩家時,設置朝向玩家的三維向量是 玩家的座標-當前怪物的座標
FVector toPlayer = avatar->GetActorLocation() - GetActorLocation();
//與玩家的距離
float distanceToPlayer = toPlayer.Size();
return isInAttackRange(distanceToPlayer);
}
void AMonster::SwordSwung()
{
if (MeleeWeapon)
{
MeleeWeapon->Swing();
}
}
void AMonster::ResetPlayer()
{
if (MeleeWeapon)
{
MeleeWeapon->Reset();
}
}
void AMonster::Attack(AActor* thing)
{
//如果是有武器的
if (MeleeWeapon)
{
MeleeWeapon->Swing();
}
else if (BPBullet)
{
//如果是子彈
FVector fwd = GetActorForwardVector();//獲取怪物向前的向量
FVector nozzle = GetMesh()->GetBoneLocation("RightHand");//獲取怪物Mesh中右手的位置三維向量
nozzle += fwd * 155;//怪物手中向量+=155倍怪物前方的單位向量
FVector toOpponent = thing->GetActorLocation() - nozzle;//獲取攻擊目標位置減去法術攻擊長度
toOpponent.Normalize();//單位化
//設置一個臨時子彈變量
ABullet *bullet = GetWorld()->SpawnActor<ABullet>(BPBullet, nozzle, RootComponent->GetComponentRotation());//在nozzle出生成
if (bullet)
{
//bullet->Firer = this;
bullet->ProxSphere->AddImpulse(fwd*BulletLaunchImpulse);//給子彈加力
}
else
{
GEngine->AddOnScreenDebugMessage(0, 5.f, FColor::Yellow, "monster:no bullet actor could be spawned");
}
}
}