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");
}
}
}