1. 网格组件 UStaticMeshComponent
指定什么样的网格,就显示什么样的形状。
头文件:#include "Components/StaticMeshComponent.h"
声明:UStaticMeshComponent *CollectableMesh;
2. 碰撞体组件
检测两个物体的碰撞
3.
EditDefaultsOnly:该属性可通过属性窗口来编辑,但仅能对原型编辑
EditAnywhere:该属性可从编辑器内的属性窗口编辑。
1. 网格组件 UStaticMeshComponent
指定什么样的网格,就显示什么样的形状。
头文件:#include "Components/StaticMeshComponent.h"
声明:UStaticMeshComponent *CollectableMesh;
2. 碰撞体组件
检测两个物体的碰撞
3.
EditDefaultsOnly:该属性可通过属性窗口来编辑,但仅能对原型编辑
EditAnywhere:该属性可从编辑器内的属性窗口编辑。
在【模式】搜索Sound, 选择【环境音效】拖放到关卡中,在细节面板指定 WAV 的音效文件,即可播放BGM;
其他音乐格式转换的WAV的网站:
// 获取GameMode // UGameplayStatics 需要引入 #include "Kismet/GameplayStatics.h"
GameMode = Cast<APacManGameModeBase>(UGameplayStatics::GetGameMode(this));
// 使用 ConstructorHelpers 需要引入的头文件:#include "Public/UObject/ConstructorHelpers.h" 找到了想要的变成的球体
static ConstructorHelpers::FObjectFinder<UStaticMesh> Sphere(TEXT("StaticMesh'/Engine/BasicShapes/Sphere.Sphere'"));
// 使用GetCapsuleComponent,需要引入头文件:#include "Components/CapsuleComponent.h"
GetCapsuleComponent()->SetCapsuleRadius(40.0f);
GetCapsuleComponent()->SetCapsuleHalfHeight(50.0f);
// 使用GetCharacterMovement(),需要引入的头文件:#include "GameFramework/CharacterMovementComponent.h"
GetCharacterMovement()->MaxWalkSpeed = 50.0f;
// 使用 GetCapsuleComponent() ,需要引入的头文件:#include "Components/CapsuleComponent.h"
// 第一个参数是当前类,第二个参数是 要绑定的函数
// 注册碰撞函数
GetCapsuleComponent()->OnComponentBeginOverlap.AddDynamic(this, &APacManCharacter::OnCollision);
// EditDefaultsOnly 可以在编辑器里面编辑,EditDefaultsOnly 只能对其原型进行修改;Category 分类
// VisibleAnywhere 在任何地方都可以看到它,创建实例后,仍然可以修改它;不改成VisibleAneywhere,无法修改其材质
UPROPERTY(VisibleAnywhere, Category = Collectable)
UStaticMeshComponent* CollectableMesh;
// EditAnyWhere 不仅可以对其原型进行修改,还可以对其实例修改
UPROPERTY(EditAnywhere, Category = Collectable)
bool bIsSuperCollectable;
// UFont 字体,显示的文字,字体,为了寻找字体方便,修改字体方便,一个蓝图类来继承它,在蓝图中修改字体
// EditAnyWhere 在哪块都能修改
// BlueprintReadWrite 表示可以在蓝图中修改这个属性/定义
// Category 分类,分到 HUDFont 类中去
UPROPERTY(EditAnyWhere,BlueprintReadWrite,Category=HUDFont)
#include "Public/TimerManager.h"
GetWorldTimerManager().ClearTimer(TimeVulnerable);
AIEnemy.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "Enemy.h"
#include "PacManGameModeBase.h"
#include "AIEnemy.generated.h"
/**
*
*/
UCLASS()
class PACMAN_API AAIEnemy : public AAIController
{
GENERATED_BODY()
public:
// 要重写的函数
// 相当于beginplay,每当开启一个AI的时候调用,首先执行OnPossess
void OnPossess(class APawn* InPawn) override;
// 移动停止后调用
virtual void OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result) override;
void SearchNewPoint();
void GoHome();
void ReArm();
void StopMove();
private:
class AEnemy* Bot;
FVector HomeLocation;
FTimerHandle DeadTime;
APacManGameModeBase* GameMode;
};
AIEnemy.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Public/AIEnemy.h"
#include "NavigationSystem.h"
#include "Public/TimerManager.h"
#include "Kismet/GameplayStatics.h"
void AAIEnemy::OnPossess(class APawn* InPawn)
{
Super::OnPossess(InPawn);
// 获得Enemy,肉体
Bot = Cast<AEnemy>(InPawn);
HomeLocation = Bot->GetActorLocation();
// 获取GameMode // UGameplayStatics 需要引入 #include "Kismet/GameplayStatics.h"
GameMode = Cast<APacManGameModeBase>(UGameplayStatics::GetGameMode(this));
SearchNewPoint();
}
void AAIEnemy::OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult & Result)
{
// 当敌人不是死亡的时候 并且 游戏不是暂停的时候
if (!Bot->bIsDead &&GameMode->GetCurrentState() != EGameState::EPause)
{
// 找新的随机点
SearchNewPoint();
}
}
void AAIEnemy::SearchNewPoint()
{
// 判断导航网格在不在
UNavigationSystemV1* NavMesh = UNavigationSystemV1::GetCurrent(this);
// 如果在
if (NavMesh)
{
// 设置搜索半径
// 1000.0f 太小了,改成 10000.0f
const float SearchRadius = 10000.0f;
// 设置一个点
FNavLocation RandomPt;
// 以敌人的位置为中心,这个半径为范围,找一个点,
const bool bFound = NavMesh->GetRandomReachablePointInRadius(Bot->GetActorLocation(), SearchRadius, RandomPt);
// 如果能找到
if (bFound)
{
// 移动到这个点
MoveToLocation(RandomPt);
}
}
}
void AAIEnemy::GoHome()
{
MoveToLocation(HomeLocation);
GetWorldTimerManager().SetTimer(DeadTime, this, &AAIEnemy::ReArm, 5.0f, false);
}
void AAIEnemy::ReArm()
{
GetWorldTimerManager().ClearTimer(DeadTime);
Bot->ReArm();
}
void AAIEnemy::StopMove()
{
//StopMovement();
MoveToLocation(Bot->GetActorLocation());
}
Collectables.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/SphereComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Collectables.generated.h"
UCLASS()
class PACMAN_API ACollectables : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ACollectables();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// EditDefaultsOnly 可以在编辑器里面编辑,EditDefaultsOnly 只能对其原型进行修改;Category 分类
// VisibleAnywhere 在任何地方都可以看到它,创建实例后,仍然可以修改它;不改成VisibleAneywhere,无法修改其材质
UPROPERTY(VisibleAnywhere, Category = Collectable)
UStaticMeshComponent* CollectableMesh;
UPROPERTY(EditDefaultsOnly, Category = Collectable)
USphereComponent* BaseCollisionComponent;
// EditAnyWhere 不仅可以对其原型进行修改,还可以对其实例修改
UPROPERTY(EditAnywhere, Category = Collectable)
bool bIsSuperCollectable;
};
Collectables.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Public/Collectables.h"
#include "Public/UObject/ConstructorHelpers.h"
// Sets default values
ACollectables::ACollectables()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
// 等于true,会使得下面这个函数(Tick)每帧运行,这个是豆的,不需要这个,设置成false
PrimaryActorTick.bCanEverTick = false;
// 开启碰撞功能(默认不开启碰撞功能)
SetActorEnableCollision(true);
// 创建网格组件
CollectableMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("CollectableMesh"));
// 创建碰撞体组件
BaseCollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("BaseCollisionComponent"));
// 将 碰撞体 和 网格 绑定到一起
CollectableMesh->AttachTo(BaseCollisionComponent);
// 使用 ConstructorHelpers 需要引入的头文件:#include "Public/UObject/ConstructorHelpers.h" 找到了想要的变成的球体
static ConstructorHelpers::FObjectFinder<UStaticMesh> Sphere(TEXT("StaticMesh'/Engine/BasicShapes/Sphere.Sphere'"));
if (Sphere.Succeeded())
{
CollectableMesh->SetStaticMesh(Sphere.Object);
}
// 指定球的大小和尺寸
CollectableMesh->SetWorldScale3D(FVector(0.3, 0.3, 0.3));
BaseCollisionComponent->SetSphereRadius(16);
}
// Called when the game starts or when spawned
void ACollectables::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ACollectables::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
Enemy.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Components/StaticMeshComponent.h"
#include "Enemy.generated.h"
UCLASS()
class PACMAN_API AEnemy : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AEnemy();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UPROPERTY(VisibleAnywhere,Category=Body)
UStaticMeshComponent* EnemyBody;
/*
Vulnerable adj. 易受攻击的;易受伤害的;脆弱的;有局方的;有身价的
InVulnerable adj. 不能伤害的;刀枪不入的;无懈可击的;不可攻破的
*/
// 易受攻击
void SetVulnerable();
// 不易受攻击
void SetInVulnerable();
// 是否移动
void SetMove(bool bMoveIt);
// 被杀死,回出生点
void Killed();
// 速度回到150
void ReArm();
// 敌人是否死亡
bool bIsDead;
UFUNCTION()
void OnCollision(class UPrimitiveComponent* HitComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult);
private:
// 默认材质
class UMaterialInterface* DefaultMaterial;
// 能被主角吃掉的时候的材质
// 容易收到主角攻击
class UMaterialInterface* VulnerableMaterial;
FTimerHandle TimeVulnerable;
// 定义一个变量来判断当前的状态
bool bIsVulnerable;
};
Enemy.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Public/Enemy.h"
#include "UObject/ConstructorHelpers.h"
#include "Components/CapsuleComponent.h"
#include "Public/TimerManager.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Public/PacManCharacter.h"
#include "Public/AIEnemy.h"
// Sets default values
AEnemy::AEnemy()
{
// 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;
// 显示组件
EnemyBody = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Body"));
// 创建样子
// 找到对应的材质
static ConstructorHelpers::FObjectFinder<UStaticMesh> CylinderObj(TEXT("'/Game/StarterContent/Shapes/Shape_Cylinder'"));
// 成功找到就
// Succeeded()需要加括号
if (CylinderObj.Succeeded())
{
// 指定它的样子
EnemyBody->SetStaticMesh(CylinderObj.Object);
}
// 设置它的大小
EnemyBody->SetRelativeScale3D(FVector(0.7f, 0.7f, 1.0f));
// 使得碰撞体和圆柱体重合在一起
// CapsuleComponent 和 UStaticMeshComponent 重合
EnemyBody->SetRelativeLocation(FVector(0, 0, -50));
// 绑定到根组件上
// AttachTo 过时了,换成:SetupAttachment
EnemyBody->SetupAttachment(RootComponent);
// 设置碰撞体的大小
// 使用GetCapsuleComponent,需要引入头文件:#include "Components/CapsuleComponent.h"
GetCapsuleComponent()->SetCapsuleRadius(40.0f);
GetCapsuleComponent()->SetCapsuleHalfHeight(50.0f);
static ConstructorHelpers::FObjectFinder<UMaterial> VulnerableMat(TEXT("'/Game/Materials/M_Enemy_Vulnerable'"));
// 获得AI,设置好AIController
AIControllerClass = AAIEnemy::StaticClass();
// 启用碰撞函数
SetActorEnableCollision(true);
}
// Called when the game starts or when spawned
void AEnemy::BeginPlay()
{
Super::BeginPlay();
// 获得当前的材质
DefaultMaterial = EnemyBody->GetMaterial(0);
// 注册碰撞函数
GetCapsuleComponent()->OnComponentBeginOverlap.AddDynamic(this, &AEnemy::OnCollision);
// 刚开始的速度太大了,设定初始的速度小一些
GetCharacterMovement()->MaxWalkSpeed = 150.0f;
}
// Called every frame
void AEnemy::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void AEnemy::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
void AEnemy::SetVulnerable()
{
// 吃到豆子可以增加10
// 过一段时间可以不受攻击
// 使用GetWorldTimerManager(),需要引入头文件:#include "Public/TimerManager.h"
// 第一个参数是TimeVulnerable;第二个参数:作用在哪个类;第三个参数:作用于哪个函数;第四个参数:时间间隔;第五个参数:是否循环
GetWorldTimerManager().SetTimer(TimeVulnerable, this, &AEnemy::SetInVulnerable, 10.0f, false);
if (bIsVulnerable)
{
return;
}
bIsVulnerable = true;
// 连续吃到豆子,敌人当前的状态已经设置成了易受攻击的状态
// 没必要执行 EnemyBody->SetMaterial(0, VulnerableMaterial);
// 定义一个变量来判断当前的状态,已经是易受到攻击的状态,没必要设置材质了
// 时间段可以从10秒变成20秒外,剩下的都不需要再改变了
EnemyBody->SetMaterial(0, VulnerableMaterial);
// 使用GetCharacterMovement(),需要引入的头文件:#include "GameFramework/CharacterMovementComponent.h"
GetCharacterMovement()->MaxWalkSpeed = 50.0f;
}
void AEnemy::SetInVulnerable()
{
GetWorldTimerManager().ClearTimer(TimeVulnerable);
bIsVulnerable = false;
EnemyBody->SetMaterial(0, DefaultMaterial);
GetCharacterMovement()->MaxWalkSpeed = 150.0f;
}
// 是否移动
void AEnemy::SetMove(bool bMoveIt)
{
// 获得AI
// 最后括号里面的AIControllerClass弃用 换成 GetController()
AAIEnemy* AI = Cast<AAIEnemy>(GetController());
// 移动
if (bMoveIt)
{
// 找下一个点
AI->SearchNewPoint();
}
else
{
// 停止移动
AI->StopMove();
}
}
void AEnemy::Killed()
{
if (bIsDead)
{
return;
}
bIsDead = true;
GetCharacterMovement()->MaxWalkSpeed = 300.0f;
// 获得AI
AAIEnemy* AI = Cast<AAIEnemy>(GetController());
// 让敌人回家
AI->GoHome();
}
void AEnemy::ReArm()
{
bIsDead = false;
GetCharacterMovement()->MaxWalkSpeed = 150.0f;
if (bIsVulnerable)
{
SetInVulnerable();
}
// 重新出来
SetMove(true);
}
void AEnemy::OnCollision(UPrimitiveComponent * HitComp, AActor * OtherActor, UPrimitiveComponent * OtherComp, int OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
{
// #include "Public/PacManCharacter.h"
if (OtherActor->IsA(APacManCharacter::StaticClass()))
{
if (bIsVulnerable)
{
Killed();
}
else
{
APacManCharacter* PacMan = Cast<APacManCharacter>(OtherActor);
PacMan->Killed();
}
}
}
PacManCharacter.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "PacManGameModeBase.h"
#include "PacManCharacter.generated.h"
UCLASS()
class PACMAN_API APacManCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
APacManCharacter();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
void MoveXAxis(float AxisValue);
void MoveYAxis(float AxisValue);
// 重启游戏
void ReStart();
// 重玩一局游戏
void NewGame();
// 暂停游戏
void Pause();
// 主角死亡
void Killed();
// 最后2个参数可写可不写
// 需要注意的是 class AActor* otherActor ,碰撞的物体是不是敌人or食物
// 使用了反射机制,需要添加关键字 UFUNCTION()
UFUNCTION()
void OnCollision(class UPrimitiveComponent* HitComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult);
// 下面这个2个,改成了Public可供PacManHUD显示调用
// 需要吃的 豆 的数量
int CollectablesToEat;
// 当前的生命值
int Lives;
private:
// 定义一个向量
FVector CurrentVelocity;
// 初始的位置
FVector StartPoint;
//
APacManGameModeBase* GameMode;
};
PacManCharacter.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Public/PacManHUD.h"
#include "PacManGameModeBase.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/Canvas.h"
#include "Public/PacManCharacter.h"
void APacManHUD::DrawHUD()
{
// 获得当前的游戏模式
class APacManGameModeBase* GameMode = Cast<APacManGameModeBase>(UGameplayStatics::GetGameMode(this));
// 根据不同的状态,显示不同的文字
switch (GameMode->GetCurrentState())
{
case EGameState::EMenu:
// 显示文字的函数,
// 第一个参数:需要显示的文字;
// 第二个参数:文字的颜色;
// 第三个参数:获得横的屏幕的宽度;
// 第四个参数:获得纵的屏幕的长度
// 第五个参数:显示的字体
// (Canvas->SizeX/2.0f) 就是在屏幕中间
// 使用 Canvas 需要引入头文件:#include "Engine/Canvas.h"
// 向下,向右为正方向,为了移动正中心,进行了减法运算到屏幕中间
DrawText(TEXT("Welcome To PacMan!\n\nN to start a new game \nP to pause the game"), FColor::White, (Canvas->SizeX / 2.0f) - 150.0f, (Canvas->SizeY / 2.0f) - 100.0f, HUDFont);
break;
case EGameState::EPlaying:
{// 这个括号需要
// 获得主角,0 表示第一个玩家,场景只有一个玩家
APacManCharacter* PacMan = Cast<APacManCharacter>(UGameplayStatics::GetPlayerPawn(this, 0));
if (PacMan)
{
// FString::FromInt() 将 int类型的变量转换成字符串
FString LiveString = TEXT("Lives: ") + FString::FromInt(PacMan->Lives);
DrawText(LiveString, FColor::Green, 50, 50, HUDFont);
FString CollectablesToEatString = TEXT("CollectablesToEat: ") + FString::FromInt(PacMan->CollectablesToEat);
DrawText(CollectablesToEatString, FColor::Green, Canvas->SizeX - 150, 50, HUDFont);
}
}// 这个括号需要
break;
case EGameState::EPause:
DrawText(TEXT("P To Continue"), FColor::White, (Canvas->SizeX / 2.0f) - 150.0f, (Canvas->SizeY / 2.0f) - 100.0f, HUDFont);
break;
case EGameState::EWin:
DrawText(TEXT("You Win!\n\n R for another"), FColor::White, (Canvas->SizeX / 2.0f) - 150.0f, (Canvas->SizeY / 2.0f) - 100.0f, HUDFont);
break;
case EGameState::EGameOver:
DrawText(TEXT("CameOver!\n\nR for another"), FColor::White, (Canvas->SizeX / 2.0f) - 150.0f, (Canvas->SizeY / 2.0f) - 100.0f, HUDFont);
break;
default:
break;
}
}
PacManGameModeBase.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "Public/Enemy.h"
#include "PacManGameModeBase.generated.h"
/**
*
*/
enum class EGameState : short {
EMenu,
EPlaying,
EPause,
EWin,
EGameOver
};
UCLASS()
class PACMAN_API APacManGameModeBase : public AGameModeBase
{
GENERATED_BODY()
public:
// 但是由于我们的碰撞在,吃食物在,当前的状态是EPlaying,游戏中的时候才能吃食物,物体才会被销毁,把当前的状态设置成EPlaying,这样方便测试了
virtual void BeginPlay() override;
// 获得当前状态 加 const 不能修改我们的成员
EGameState GetCurrentState() const;
// 设置当前状态
void SetCurrentState(EGameState value);
// 易受攻击的方法
void SetEnemyVulnerable();
private:
// 要知道当前状态,才能做出不同的反应
EGameState currentState;
// 定义一个数组,保存所有的敌人
TArray<class AEnemy*> Enemys;
};
// 为了程序更加快捷,定义一个 内联
// APcManGameModeBase 和 命名的项目名称一致,视频中的AMyPacManGameModeBase,项目名称为:MyPacMan
FORCEINLINE EGameState APacManGameModeBase::GetCurrentState() const
{
return currentState;
};
PacManGameModeBase.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "PacManGameModeBase.h"
#include "Public/EngineUtils.h"
// 设置当前状态
void APacManGameModeBase::SetCurrentState(EGameState value)
{
currentState = value;
// 判断暂停等状态
// 输入switch,Tab 两下,在switch_on中按住Tab输入value,然后回车,自动补全
switch (value)
{
case EGameState::EPlaying:
// 遍历所有的敌人
for (auto Iter(Enemys.CreateIterator()); Iter; Iter++)
{
// 可以移动
(*Iter)->SetMove(true);
}
break;
case EGameState::EPause:
// 遍历所有的敌人
for (auto Iter(Enemys.CreateIterator()); Iter; Iter++)
{
// 不可以移动
(*Iter)->SetMove(false);
}
break;
case EGameState::EWin:
// 遍历所有的敌人
for (auto Iter(Enemys.CreateIterator()); Iter; Iter++)
{
// 销毁敌人
(*Iter)->Destroy();
}
break;
case EGameState::EGameOver:
// 遍历所有的敌人
for (auto Iter(Enemys.CreateIterator()); Iter; Iter++)
{
// 销毁敌人
(*Iter)->Destroy();
}
break;
default:
break;
}
}
// 易受攻击的事情,用 GameMode 来处理所有的敌人,在 Character 中直接调用 GameMode 的这个方法,就可以把所有的敌人都设置成易受到攻击的状态,而不必分别设置了
// 设置成易受攻击,被主角吃的行为就好了
void APacManGameModeBase::SetEnemyVulnerable()
{
// 遍历所有的Enemy,调用它身上的SetVulnerable方法就可以了
// Iter 表示存在的话,Iter++
for (auto Iter(Enemys.CreateIterator()); Iter; Iter++)
{
// 调用它身上的SetVulnerable方法
(*Iter)->SetVulnerable();
}
}
void APacManGameModeBase::BeginPlay()
{
// 设置成菜单模式,开局有UI提示
SetCurrentState(EGameState::EMenu);
// 使用迭代器,需要引入头文件:Public/EngineUtils.h
// 获得所有的敌人 ;起个名字:enemyItr; GetWorld():从世界得到; enemyItr 存在的话就去遍历
for (TActorIterator<AEnemy> enemyItr(GetWorld()); enemyItr; ++enemyItr)
{
// 强制转化成 AEnemy
AEnemy* enemy = Cast<AEnemy>(*enemyItr);
// 转化后,看存不存在
if (enemy)
{
// 如果存在的话,就添加到数组里面
Enemys.Add(enemy);
}
}
}
使用 UGameplayStatics 需要引入头文件 #include "Kismet/GameplayStatics.h"
使用 UNavigationSystemV1* 需要引入头文件 #include "NavigationSystem.h"
使用 GetWorldTimerManager() 需要引入头文件 #include "Public/TimerManager.h"
使用 ConstructorHelpers 需要引入头文件:#include "Public/UObject/ConstructorHelpers.h"
使用 ConstructorHelpers 需要引入头文件 #include "UObject/ConstructorHelpers.h"
使用 GetCapsuleComponent,需要引入头文件:#include "Components/CapsuleComponent.h"
使用 GetCharacterMovement(),需要引入头文件:#include "GameFramework/CharacterMovementComponent.h"
使用 GetWorld() 需要引入头文件:#include "Engine/World.h"
使用 TActorIterator 需要引入头文件:#include "Public/EngineUtils.h"
使用 InputComponent.BindAxis() 和 BindAction() 需要引入头文件:#include "Components/InputComponent.h"
使用 Canvas 需要引入头文件:#include "Engine/Canvas.h"
使用 USphereComponent* 需要引入头文件:#include "Components/SphereComponent.h"
使用 UStaticMeshComponent* 需要引入头文件:#include "Components/StaticMeshComponent.h"
UPROPERTY(VisibleAnywhere, Category = Collectable)
UPROPERTY(EditDefaultsOnly, Category = Collectable)
UPROPERTY(EditAnywhere, Category = Collectable)
UPROPERTY(VisibleAnywhere,Category=Body)
UFUNCTION()
void OnCollision(class UPrimitiveComponent* HitComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult);
UPROPERTY(EditAnyWhere,BlueprintReadWrite,Category=HUDFont)
显示UI,需要继承HUD这个类:
创建基于PacManHUD的蓝图类
我们自定义的HUDFont需要指定字体:视图选项中 勾选 显示引擎内容,方可选择 RobotoDistanceField 字体。
修改完蓝图需要编辑保存
对 BP_PacManGameMode 的 HUD Class 指定成我们刚刚转化的 BP_PacManHUD 。
Canvas->SizeX / 2.0f , Canvas->SizeY / 2.0f 显示的文字不在正中间
PacManHUD.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "PacManHUD.generated.h"
/**
*
*/
UCLASS()
class PACMAN_API APacManHUD : public AHUD
{
GENERATED_BODY()
public:
// UFont 字体,显示的文字,字体,为了寻找字体方便,修改字体方便,一个蓝图类来继承它,在蓝图中修改字体
// EditAnyWhere 在哪块都能修改
// BlueprintReadWrite 表示可以在蓝图中修改这个属性/定义
// Category 分类,分到HUDFont类中去
UPROPERTY(EditAnyWhere,BlueprintReadWrite,Category=HUDFont)
UFont* HUDFont;
// 介绍一个函数,这个函数很重要,在屏幕上显示文字,靠这个函数来实现的
// DrawHUD 就像 Tick 一样,每帧调用一次,这样把我输入的文字显示在屏幕上
virtual void DrawHUD() override;
};
PacManHUD.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Public/PacManHUD.h"
#include "PacManGameModeBase.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/Canvas.h"
#include "Public/PacManCharacter.h"
void APacManHUD::DrawHUD()
{
// 获得当前的游戏模式
class APacManGameModeBase* GameMode = Cast<APacManGameModeBase>(UGameplayStatics::GetGameMode(this));
// 根据不同的状态,显示不同的文字
switch (GameMode->GetCurrentState())
{
case EGameState::EMenu:
// 显示文字的函数,
// 第一个参数:需要显示的文字;
// 第二个参数:文字的颜色;
// 第三个参数:获得横的屏幕的宽度;
// 第四个参数:获得纵的屏幕的长度
// 第五个参数:显示的字体
// (Canvas->SizeX/2.0f) 就是在屏幕中间
// 使用 Canvas 需要引入头文件:#include "Engine/Canvas.h"
// 向下,向右为正方向,为了移动正中心,进行了减法运算到屏幕中间
DrawText(TEXT("Welcome To PacMan!\n\nN to start a new game \nP to pause the game"), FColor::White, (Canvas->SizeX / 2.0f) - 150.0f, (Canvas->SizeY / 2.0f) - 100.0f, HUDFont);
break;
case EGameState::EPlaying:
{// 这个括号需要
// 获得主角,0 表示第一个玩家,场景只有一个玩家
APacManCharacter* PacMan = Cast<APacManCharacter>(UGameplayStatics::GetPlayerPawn(this, 0));
if (PacMan)
{
// FString::FromInt() 将 int类型的变量转换成字符串
FString LiveString = TEXT("Lives: ") + FString::FromInt(PacMan->Lives);
DrawText(LiveString, FColor::Green, 50, 50, HUDFont);
FString CollectablesToEatString = TEXT("CollectablesToEat: ") + FString::FromInt(PacMan->CollectablesToEat);
DrawText(CollectablesToEatString, FColor::Green, Canvas->SizeX - 150, 50, HUDFont);
}
}// 这个括号需要
break;
case EGameState::EPause:
DrawText(TEXT("P To Continue"), FColor::White, (Canvas->SizeX / 2.0f) - 150.0f, (Canvas->SizeY / 2.0f) - 100.0f, HUDFont);
break;
case EGameState::EWin:
DrawText(TEXT("You Win!\n\n R for another"), FColor::White, (Canvas->SizeX / 2.0f) - 150.0f, (Canvas->SizeY / 2.0f) - 100.0f, HUDFont);
break;
case EGameState::EGameOver:
DrawText(TEXT("CameOver!\n\nR for another"), FColor::White, (Canvas->SizeX / 2.0f) - 150.0f, (Canvas->SizeY / 2.0f) - 100.0f, HUDFont);
break;
default:
break;
}
}
PacManCharacter.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "PacManGameModeBase.h"
#include "PacManCharacter.generated.h"
UCLASS()
class PACMAN_API APacManCharacter : public ACharacter
{
// 改成了Public可供PacManHUD显示调用
public:
// 需要吃的 豆 的数量
int CollectablesToEat;
// 当前的生命值
int Lives;
};
Enemy.cpp
void AEnemy::BeginPlay()
{
Super::BeginPlay();
// 刚开始的速度太大了,设定初始的速度小一些
GetCharacterMovement()->MaxWalkSpeed = 150.0f;
}
void AEnemy::Killed()
{
if (bIsDead)
{
return;
}
bIsDead = true;
GetCharacterMovement()->MaxWalkSpeed = 300.0f;
// 获得AI
AAIEnemy* AI = Cast<AAIEnemy>(GetController());
// 让敌人回家
AI->GoHome();
}
void AEnemy::ReArm()
{
bIsDead = false;
GetCharacterMovement()->MaxWalkSpeed = 150.0f;
if (bIsVulnerable)
{
SetInVulnerable();
}
// 重新出来
SetMove(true);
}
AIEnemy.h
#include "PacManGameModeBase.h"
UCLASS()
class PACMAN_API AAIEnemy : public AAIController
{
GENERATED_BODY()
private:
APacManGameModeBase* GameMode;
};
AIEnemy.cpp
#include "Kismet/GameplayStatics.h"
void AAIEnemy::OnPossess(class APawn* InPawn)
{
Super::OnPossess(InPawn);
// 获取GameMode // UGameplayStatics 需要引入 #include "Kismet/GameplayStatics.h"
GameMode = Cast<APacManGameModeBase>(UGameplayStatics::GetGameMode(this));
}
void AAIEnemy::OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult & Result)
{
// 当敌人不是死亡的时候 并且 游戏不是暂停的时候
if (!Bot->bIsDead &&GameMode->GetCurrentState() != EGameState::EPause)
{
// 找新的随机点
SearchNewPoint();
}
}
void AAIEnemy::StopMove()
{
//StopMovement();
MoveToLocation(Bot->GetActorLocation());
}
PacManCharacter.cpp
void APacManCharacter::MoveXAxis(float AxisValue)
{
// 只有游戏开始的时候才能移动
if (GameMode->GetCurrentState() == EGameState::EPlaying)
{
CurrentVelocity.X = AxisValue;
AddMovementInput(CurrentVelocity);
}
}
void APacManCharacter::MoveYAxis(float AxisValue)
{
// 只有游戏开始的时候才能移动
if (GameMode->GetCurrentState() == EGameState::EPlaying)
{
CurrentVelocity.Y = AxisValue;
AddMovementInput(CurrentVelocity);
}
}
Enemy.cpp
AEnemy::AEnemy()
{
// 启用碰撞函数
SetActorEnableCollision(true);
}
void AEnemy::BeginPlay()
{
Super::BeginPlay();
// 注册碰撞函数
GetCapsuleComponent()->OnComponentBeginOverlap.AddDynamic(this, &AEnemy::OnCollision);
}
// 是否移动
void AEnemy::SetMove(bool bMoveIt)
{
// 获得AI
// 最后括号里面的AIControllerClass弃用 换成 GetController()
AAIEnemy* AI = Cast<AAIEnemy>(GetController());
// 移动
if (bMoveIt)
{
// 找下一个点
AI->SearchNewPoint();
}
else
{
// 停止移动
AI->StopMove();
}
}
项目设置---引擎---Collision---新建对象通道
新建通道:
Preset:
新建
新建概述文件:
只需要和墙、地板发生碰撞,静态
Block :区块
区块:就是阻碍的意思
EnemyBody---碰撞预设值 改成 Enemy
CapsuleComponent---碰撞预设值 也改成 Enemy
解决场景中的敌人不移动的办法:
把场景中的敌人都删除,然后再重新拖入试一下
这样场景中的敌人就可以移动了
注册碰撞函数
启用碰撞函数:
主角的碰撞预设值是:Pawn
重叠:Overlap,一致才能发生作用
控制游戏的暂停:就是控制怪物的移动
输入switch,Tab 两下,在switch_on中按住Tab输入value,然后回车,自动补全
Enemy.cpp
#include "Public/AIEnemy.h"
AEnemy::AEnemy()
{
// 获得AI,设置好AIController
AIControllerClass = AAIEnemy::StaticClass();
}
// 是否移动
void AEnemy::SetMove(bool bMoveIt)
{
// 获得AI
AAIEnemy* AI = Cast<AAIEnemy>(AIControllerClass);
// 移动
if (bMoveIt)
{
// 找下一个点
AI->SearchNewPoint();
}
else
{
// 停止移动
AI->StopMove();
}
}
PacManGameModeBase.cpp
// 设置当前状态
void APacManGameModeBase::SetCurrentState(EGameState value)
{
currentState = value;
// 判断暂停等状态
// 输入switch,Tab 两下,在switch_on中按住Tab输入value,然后回车,自动补全
switch (value)
{
case EGameState::EPlaying:
// 遍历所有的敌人
for (auto Iter(Enemys.CreateIterator()); Iter; Iter++)
{
// 可以移动
(*Iter)->SetMove(true);
}
break;
case EGameState::EPause:
// 遍历所有的敌人
for (auto Iter(Enemys.CreateIterator()); Iter; Iter++)
{
// 不可以移动
(*Iter)->SetMove(false);
}
break;
case EGameState::EWin:
// 遍历所有的敌人
for (auto Iter(Enemys.CreateIterator()); Iter; Iter++)
{
// 销毁敌人
(*Iter)->Destroy();
}
break;
case EGameState::EGameOver:
// 遍历所有的敌人
for (auto Iter(Enemys.CreateIterator()); Iter; Iter++)
{
// 销毁敌人
(*Iter)->Destroy();
}
break;
default:
break;
}
}
void APacManGameModeBase::BeginPlay()
{
// 设置成菜单模式,开局有UI提示
SetCurrentState(EGameState::EMenu);
}
PacManGameModeBase.h
#include "Public/Enemy.h"
UCLASS()
class PACMAN_API APacManGameModeBase : public AGameModeBase
{
GENERATED_BODY()
public:
// 易受攻击的方法
void SetEnemyVulnerable();
private:
// 定义一个数组,保存所有的敌人
TArray<class AEnemy*> Enemys;
};
PacManGameMode.cpp
#include "Public/EngineUtils.h"
// 易受攻击的事情,用 GameMode 来处理所有的敌人,在 Character 中直接调用 GameMode 的这个方法,就可以把所有的敌人都设置成易受到攻击的状态,而不必分别设置了
// 设置成易受攻击,被主角吃的行为就好了
void APacManGameModeBase::SetEnemyVulnerable()
{
// 遍历所有的Enemy,调用它身上的SetVulnerable方法就可以了
// Iter 表示存在的话,Iter++
for (auto Iter(Enemys.CreateIterator()); Iter; Iter++)
{
// 调用它身上的SetVulnerable方法
(*Iter)->SetVulnerable();
}
}
void APacManGameModeBase::BeginPlay()
{
SetCurrentState(EGameState::EPlaying);
// 使用迭代器,需要引入头文件:Public/EngineUtils.h
// 获得所有的敌人 ;起个名字:enemyItr; GetWorld():从世界得到; enemyItr 存在的话就去遍历
for (TActorIterator<AEnemy> enemyItr(GetWorld()); enemyItr; ++enemyItr)
{
// 强制转化成 AEnemy
AEnemy* enemy = Cast<AEnemy>(*enemyItr);
// 转化后,看存不存在
if (enemy)
{
// 如果存在的话,就添加到数组里面
Enemys.Add(enemy);
}
}
}
PacManCharacter.cpp
void APacManCharacter::OnCollision(UPrimitiveComponent * HitComp, AActor * OtherActor, UPrimitiveComponent * OtherComp, int OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
{
// 当游戏正在运行才去检测碰撞
if (GameMode->GetCurrentState() == EGameState::EPlaying)
{
// 调用易受攻击的状态,什么时候调用:就是在主角吃到了超级大力丸的时候调用,把敌人设置成易受攻击的状态,找到主角,找到碰撞函数 OnCollision
// 碰到是食物的话,就把它销毁
// 当碰撞的对象是 ACollectables 类时销毁掉
// 使用自己定义的类AColletables,也需要引入对应的头文件: #include "Public/Collectables.h"
if (OtherActor->IsA(ACollectables::StaticClass()))
{
// 吃到豆子
// 强制转化成豆子
ACollectables* collectable = Cast<ACollectables>(OtherActor);
// 判断是不是大力丸
if (collectable->bIsSuperCollectable)
{
// 把敌人变成易受攻击的状态
GameMode->SetEnemyVulnerable();
}
// 销毁豆子
OtherActor->Destroy();
// 检测一下豆子的数量是否为 0
if (--CollectablesToEat == 0)
{
GameMode->SetCurrentState(EGameState::EWin);
}
UE_LOG(LogTemp, Warning, TEXT("Remain Collectable is %d"), CollectablesToEat);
}
}
}
GameMode 是定制游戏规则的地方
按下 P 键,所有的敌人都不能移动
GameMode 来管理所有的敌人
把敌人易受攻击的状态设置
单独一个一个设置太麻烦了
在GameMode 里面获得所有敌人
调用易受攻击的方法就可以了
GameModeBase 获得所有敌人,需要引入头文件:敌人
使用默认的摄像机是第一人称
在【模式】里搜索Camera
设置默认的摄像机
选中 CameraActor ,点击【蓝图】---【打开关卡蓝图】
右键空白区域,选择【创建一个到CameraActor的引用】
获得了 摄像机 的引用
Get Player Controller
取消勾选【情景关联】
view target
Set view Target with Blend
绑定了:
与当前视野一致:
修改默认的面向:
修改PlayerStart
AIEnemy.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "Enemy.h"
#include "AIEnemy.generated.h"
/**
*
*/
UCLASS()
class PACMAN_API AAIEnemy : public AAIController
{
GENERATED_BODY()
public:
// 要重写的函数
// 相当于beginplay,每当开启一个AI的时候调用,首先执行OnPossess
void OnPossess(class APawn* InPawn) override;
// 移动停止后调用
virtual void OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result) override;
void SearchNewPoint();
void GoHome();
void ReArm();
void StopMove();
private:
class AEnemy* Bot;
FVector HomeLocation;
FTimerHandle DeadTime;
};
AIEnemy.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Public/AIEnemy.h"
#include "NavigationSystem.h"
#include "Public/TimerManager.h"
void AAIEnemy::OnPossess(class APawn* InPawn)
{
Super::OnPossess(InPawn);
// 获得Enemy,肉体
Bot = Cast<AEnemy>(InPawn);
HomeLocation = Bot->GetActorLocation();
SearchNewPoint();
}
void AAIEnemy::OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult & Result)
{
if (!Bot->bIsDead)
{
SearchNewPoint();
}
}
void AAIEnemy::SearchNewPoint()
{
// 判断导航网格在不在
UNavigationSystemV1* NavMesh = UNavigationSystemV1::GetCurrent(this);
// 如果在
if (NavMesh)
{
// 设置搜索半径
const float SearchRadius = 1000.0f;
// 设置一个点
FNavLocation RandomPt;
// 以敌人的位置为中心,这个半径为范围,找一个点,
const bool bFound = NavMesh->GetRandomReachablePointInRadius(Bot->GetActorLocation(), SearchRadius, RandomPt);
// 如果能找到
if (bFound)
{
// 移动到这个点
MoveToLocation(RandomPt);
}
}
}
void AAIEnemy::GoHome()
{
MoveToLocation(HomeLocation);
GetWorldTimerManager().SetTimer(DeadTime, this, &AAIEnemy::ReArm, 5.0f, false);
}
void AAIEnemy::ReArm()
{
GetWorldTimerManager().ClearTimer(DeadTime);
Bot->ReArm();
}
void AAIEnemy::StopMove()
{
StopMovement();
}
Enemy.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Components/StaticMeshComponent.h"
#include "Enemy.generated.h"
UCLASS()
class PACMAN_API AEnemy : public ACharacter
{
public:
// 敌人是否死亡
bool bIsDead;
};
设置导航网格:
模式---体积---Nav Mesh Bounds Volume
拖到场景中,设置 Brush Settings
选择视口,按下 P 键,显示导肮网格的样子
上面的能拐弯,有绿色的线
需要设置导航网格
编辑---项目设置---引擎--导航网格物体
把Generation下的 Cell Size 改成 5
Cell Height 改成 2
这样导航网格不会穿墙
绿色就是可以通过的路径
这些绿色就是导航网格的范围
使用 GetWorldTimerManager(),需要引入头文件:#include "Public/TimerManager.h"
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "Enemy.h"
#include "AIEnemy.generated.h"
/**
*
*/
UCLASS()
class PACMAN_API AAIEnemy : public AAIController
{
GENERATED_BODY()
public:
// 要重写的函数
// 相当于beginplay,每当开启一个AI的时候调用,首先执行OnPossess
void OnPossess(class APawn* InPawn) override;
// 移动停止后调用
virtual void OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result) override;
void SearchNewPoint();
private:
class AEnemy* Bot;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "Public/AIEnemy.h"
#include "NavigationSystem.h"
void AAIEnemy::OnPossess(class APawn* InPawn)
{
Super::OnPossess(InPawn);
// 获得Enemy,肉体
Bot = Cast<AEnemy>(InPawn);
}
void AAIEnemy::OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult & Result)
{
}
void AAIEnemy::SearchNewPoint()
{
// 判断导航网格在不在
UNavigationSystemV1* NavMesh = UNavigationSystemV1::GetCurrent(this);
// 如果在
if (NavMesh)
{
// 设置搜索半径
const float SearchRadius = 1000.0f;
// 设置一个点
FNavLocation RandomPt;
// 以敌人的位置为中心,这个半径为范围,找一个点,
const bool bFound = NavMesh->GetRandomReachablePointInRadius(Bot->GetActorLocation(), SearchRadius, RandomPt);
// 如果能找到
if (bFound)
{
// 移动到这个点
MoveToLocation(RandomPt);
}
}
}
注意:
Possess()使用OnPossess代替
使用UNavigationSystemV1*,需要引入头文件:#include "NavigationSystem.h"
public:
// 是否移动
void SetMove(bool MoveIt);
// 被杀死,回出生点
void Killed();
// 速度回到150
void ReArm();
UFUNCTION()
void OnCollision(class UPrimitiveComponent* HitComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult);
private:
// 敌人是否死亡
bool bIsDead;
void AEnemy::SetMove(bool MoveIt)
{
}
void AEnemy::Killed()
{
if (bIsDead)
{
return;
}
bIsDead = true;
GetCharacterMovement()->MaxWalkSpeed = 300.0f;
}
void AEnemy::ReArm()
{
bIsDead = false;
GetCharacterMovement()->MaxWalkSpeed = 150.0f;
if (bIsVulnerable)
{
SetInVulnerable();
}
}
void AEnemy::OnCollision(UPrimitiveComponent * HitComp, AActor * OtherActor, UPrimitiveComponent * OtherComp, int OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
{
// #include "Public/PacManCharacter.h"
if (OtherActor->IsA(APacManCharacter::StaticClass()))
{
if (bIsVulnerable)
{
Killed();
}
else
{
APacManCharacter* PacMan = Cast<APacManCharacter>(OtherActor);
PacMan->Killed();
}
}
}
计时器的功能
当敌人处在易受到攻击的时候
不会一直处在易受攻击的状态
过了10秒之后返回正常的状态
这个时候需要10秒的计时
private:
FTimerHandle TimeVulnerable;
// 定义一个变量来判断当前的状态
bool bIsVulnerable;
#include "Public/TimerManager.h"
#include "GameFramework/CharacterMovementComponent.h"
void AEnemy::SetVulnerable()
{
// 吃到豆子可以增加10
// 过一段时间可以不受攻击
// 使用GetWorldTimerManager(),需要引入头文件:#include "Public/TimerManager.h"
// 第一个参数是TimeVulnerable;第二个参数:作用在哪个类;第三个参数:作用于哪个函数;第四个参数:时间间隔;第五个参数:是否循环
GetWorldTimerManager().SetTimer(TimeVulnerable, this, &AEnemy::SetInVulnerable, 10.0f, false);
if (bIsVulnerable)
{
return;
}
bIsVulnerable = true;
// 连续吃到豆子,敌人当前的状态已经设置成了易受攻击的状态
// 没必要执行 EnemyBody->SetMaterial(0, VulnerableMaterial);
// 定义一个变量来判断当前的状态,已经是易受到攻击的状态,没必要设置材质了
// 时间段可以从10秒变成20秒外,剩下的都不需要再改变了
EnemyBody->SetMaterial(0, VulnerableMaterial);
// 使用GetCharacterMovement(),需要引入的头文件:#include "GameFramework/CharacterMovementComponent.h"
GetCharacterMovement()->MaxWalkSpeed = 50.0f;
}
void AEnemy::SetInVulnerable()
{
GetWorldTimerManager().ClearTimer(TimeVulnerable);
bIsVulnerable = false;
EnemyBody->SetMaterial(0, DefaultMaterial);
GetCharacterMovement()->MaxWalkSpeed = 150.0f;
}
// 使用GetWorldTimerManager(),需要引入头文件:#include "Public/TimerManager.h"
// 使用GetCharacterMovement(),需要引入的头文件:#include "GameFramework/CharacterMovementComponent.h"
// 使得碰撞体和圆柱体重合在一起
// CapsuleComponent 和 UStaticMeshComponent 重合
public:
// 易受攻击
void SetVulnerable();
// 不易受攻击
void SetInVulnerable();
private:
// 默认材质
class UMaterialInterface* DefaultMaterial;
// 能被主角吃掉的时候的材质
// 容易收到主角攻击
class UMaterialInterface* VulnerableMaterial;
AEnemy::AEnemy()
{
// 使得碰撞体和圆柱体重合在一起
// CapsuleComponent 和 UStaticMeshComponent 重合
EnemyBody->SetRelativeLocation(FVector(0, 0, -50));
static ConstructorHelpers::FObjectFinder<UMaterial> VulnerableMat(TEXT("'/Game/Materials.M_Enemy_Vulnerable"));
}
void AEnemy::BeginPlay()
{
Super::BeginPlay();
// 获得当前的材质
DefaultMaterial = EnemyBody->GetMaterial(0);
}
void AEnemy::SetVulnerable()
{
EnemyBody->SetMaterial(0, VulnerableMaterial);
// 过一段时间可以不受攻击
}
void AEnemy::SetInVulnerable()
{
}
找内容浏览器下的文件
需要引入#include "UObject/ConstructorHelpers.h"
// 创建样子
// 找到对应的材质
static ConstructorHelpers::FObjectFinder<UStaticMesh> CylinderObj(TEXT("'/Game/StarterContent/Shapes/Shape_Cylinder'"));
static ConstructorHelpers::FObjectFinder<UMaterial> VulnerableMat(TEXT("'/Game/Materials.M_Enemy_Vulnerable‘"));
Vulnerable adj. 易受攻击的;易受伤害的;脆弱的;有局方的;有身价的
InVulnerable adj. 不能伤害的;刀枪不入的;无懈可击的;不可攻破的
选中C++类,点击添加新项
新建C++类---继承自Character类---Enemy
#include "Public/Enemy.h"
新建C++类--- 需要勾选【显示所有类】里搜索---AIController
Enemy.h
Enemy.cpp
是肉体
AIEnemy.h
AIEnemy.cpp
是灵魂,用AI来控制
#include "Public/AIEnemy.h"
Enemy 脚本
继承自Character的类上有CapsuleComponent组件
只需要写控制样子的组件
Enemy.h
#include "Components/StaticMeshComponent.h"
UPROPERTY(VisibleAnywhere,Category=body)
UStaticMeshComponent* EnemyBody;
Enemy.cpp
#include "UObject/ConstructorHelpers.h"
AEnemey::AEnemy()
{
PrimaryActorTick.bCanEverTick = true;
EnemyBody=CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Body"));
static ConstructorHelpers::FObjectFinder<UStaticMesh> CylinderObj(TEXT("'/Game/StarterContent/Shapes/Shape_Cylinder'"));
if(CylinderObje.Succeeded())
{
EnemyBody->SetStaticMesh(CylinderObj.Object);
}
EnemyBody->SetRelativeScale3D(FVector(0.7f, 0.7f, 1.0f));
EnemyBody->SetupAttachment(RootComponent);
GetCapsuleComponent()->SetCapsuleRadius(40.0f);
GetCapsuleComponent()->SetCapsuleHalfHeight(50.0f);
}
// 使用GetCapsuleComponent,需要引入头文件:#include "Components/CapsuleComponent.h"
static ConstructorHelpers::FObjectFinder<UStaticMesh> CylinderObj(TEXT("'/Game/StarterContent/Shapes/Shape_Cylinder'"));
#include "Components/StaticMeshComponent.h"
public:
UPROPERTY(VisibleAnywhere,Category=Body)
UStaticMeshComponent* EnemyBody;
#include "UObject/ConstructorHelpers.h"
#include "Components/CapsuleComponent.h"
AEnemy::AEnemy()
{
// 显示组件
EnemyBody = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Body"));
// 创建样子
// 找到对应的材质
static ConstructorHelpers::FObjectFinder<UStaticMesh> CylinderObj(TEXT("'/Game/StarterContent/Shapes/Shape_Cylinder'"));
// 成功找到就
// Succeeded()需要加括号
if (CylinderObj.Succeeded())
{
// 指定它的样子
EnemyBody->SetStaticMesh(CylinderObj.Object);
}
// 设置它的大小
EnemyBody->SetRelativeScale3D(FVector(0.7f, 0.7f, 1.0f));
// 绑定到根组件上
// AttachTo 过时了,换成:SetupAttachment
EnemyBody->SetupAttachment(RootComponent);
// 设置碰撞体的大小
// 使用GetCapsuleComponent,需要引入头文件:#include "Components/CapsuleComponent.h"
GetCapsuleComponent()->SetCapsuleRadius(40.0f);
GetCapsuleComponent()->SetCapsuleHalfHeight(50.0f);
}
private:
// 初始的位置
FVector StartPoint;
public:
// 主角死亡
void Killed();
void APacManCharacter::BeginPlay()
{
Super::BeginPlay();
// 初始化角色生命值,被怪物碰到,血量减一,回到出生点
Lives = 3;
// 获得游戏一开始的位置
StartPoint = GetActorLocation();
UE_LOG(LogTemp, Warning, TEXT("StartPoint:%s"), *StartPoint.ToString());
}
void APacManCharacter::Killed()
{
if (--Lives == 0)
{
GameMode->SetCurrentState(EGameState::EGameOver);
}
else
{
SetActorLocation(StartPoint);
}
}
// 最后2个参数可写可不写
// 需要注意的是 class AActor* otherActor ,碰撞的物体是不是敌人or食物
// 新增:使用了反射机制,需要添加关键字 UFUNCTION()
UFUNCTION()
void OnCollision(class UPrimitiveComponent* HitComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult);
// PacManGameModeBase.h
public:
// 但是由于我们的碰撞在,吃食物在,当前的状态是EPlaying,
// 游戏中的时候才能吃食物,物体才会被销毁,把当前的状态设置成EPlaying,
// 这样方便测试了
virtual void BeginPlay() override;
// PacManGameModeBase.cpp
void APacManGameModeBase::BeginPlay()
{
SetCurrentState(EGameState::EPlaying);
}
PacManCharacter.h
private:
// 需要吃的 豆 的数量
int CollectablesToEat;
// 当前的生命值
int Lives;
PacManCharacter.cpp
#include "Public/EngineUtils.h"
void APacManCharacter::BeginPlay()
{
Super::BeginPlay();
// 迭代器:
// 使用TActorIterator 需要引入的头文件:#include "Public/EngineUtils.h"
for (TActorIterator<ACollectables>CollectableItr(GetWorld()); CollectableItr; ++CollectableItr)
{
CollectablesToEat++;
}
UE_LOG(LogTemp, Warning, TEXT("Total Collectable is %d"), CollectablesToEat);
}
void APacManCharacter::OnCollision(UPrimitiveComponent * HitComp, AActor * OtherActor, UPrimitiveComponent * OtherComp, int OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
{
// 当游戏正在运行才去检测碰撞
if (GameMode->GetCurrentState() == EGameState::EPlaying)
{
// 当碰撞的对象是 ACollectables 类时销毁掉
// 使用自己定义的类AColletables,也需要引入对应的头文件: #include "Public/Collectables.h"
if (OtherActor->IsA(ACollectables::StaticClass()))
{
OtherActor->Destroy();
if (--CollectablesToEat == 0)
{
GameMode->SetCurrentState(EGameState::EWin);
}
UE_LOG(LogTemp, Warning, TEXT("Remain Collectable is %d"), CollectablesToEat);
}
}
}
// 使用TActorIterator 需要引入的头文件:#include "Public/EngineUtils.h"
PacManCharacter.h
public:
// 最后2个参数可写可不写
// 需要注意的是 class AActor* otherActor ,碰撞的物体是不是敌人or食物
void OnCollision(class UPrimitiveComponent* HitComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult);
PacManCharacter.cpp
#include "Public/Collectables.h"
#include "Components/CapsuleComponent.h"
void APacManCharacter::BeginPlay()
{
Super::BeginPlay();
// 使用 GetCapsuleComponent() ,需要引入的头文件:#include "Components/CapsuleComponent.h"
// 第一个参数是当前类,第二个参数是 要绑定的函数
GetCapsuleComponent()->OnComponentBeginOverlap.AddDynamic(this, &APacManCharacter::OnCollision);
}
void APacManCharacter::OnCollision(UPrimitiveComponent * HitComp, AActor * OtherActor, UPrimitiveComponent * OtherComp, int OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
{
// 当游戏正在运行才去检测碰撞
if (GameMode->GetCurrentState() == EGameState::EPlaying)
{
// 当碰撞的对象是 ACollectables 类时销毁掉
// 使用自己定义的类AColletables,也需要引入对应的头文件: #include "Public/Collectables.h"
if (OtherActor->IsA(ACollectables::StaticClass()))
{
OtherActor->Destroy();
}
}
}
主角是 CapsuleComponent的碰撞体
使用 UGameplayStatics :: GetGameMode,需要引入的头文件是:#include "Kismet/GameplayStatics.h"
PacManCharacter.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "PacManGameModeBase.h"
#include "PacManCharacter.generated.h"
UCLASS()
class PACMAN_API APacManCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
APacManCharacter();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
void MoveXAxis(float AxisValue);
void MoveYAxis(float AxisValue);
// 重启游戏
void ReStart();
// 重玩一局游戏
void NewGame();
// 暂停游戏
void Pause();
private:
// 定义一个向量
FVector CurrentVelocity;
APacManGameModeBase* GameMode;
};
private:
APacManGameModeBase* GameMode;
PacManCharacter.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Public/PacManCharacter.h"
#include "Components/InputComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/World.h"
// Sets default values
APacManCharacter::APacManCharacter()
{
// 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;
}
// Called when the game starts or when spawned
void APacManCharacter::BeginPlay()
{
Super::BeginPlay();
// 获得当前的游戏模式
// 使用 UGameplayStatics 需要引入的头文件:#include "Kismet/GameplayStatics.h"
// 需要强制转换,使用 Cast <APacManGameModeBase>()
GameMode = Cast<APacManGameModeBase>(UGameplayStatics::GetGameMode(this));
}
// Called every frame
void APacManCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void APacManCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// 第一个参数,绑定的轴的名字,与编辑器里面对应的;
// 第二个参数,this,绑定这个类上;
// 第三个参数,绑定的哪个函数
// InputComponent 报红,引入#include "Components/InputComponent.h"头文件解决
// 传递的引用,需要加上 & 符号
PlayerInputComponent->BindAxis("MoveX", this, &APacManCharacter::MoveXAxis);
PlayerInputComponent->BindAxis("MoveY", this, &APacManCharacter::MoveYAxis);
// 绑定行为,多了一个参数,IE_Pressed 按下的时候触发
PlayerInputComponent->BindAction("NewGame", IE_Pressed, this, &APacManCharacter::NewGame);
PlayerInputComponent->BindAction("ReStart", IE_Pressed, this, &APacManCharacter::ReStart);
PlayerInputComponent->BindAction("Pause", IE_Pressed, this, &APacManCharacter::Pause);
}
void APacManCharacter::MoveXAxis(float AxisValue)
{
CurrentVelocity.X = AxisValue;
AddMovementInput(CurrentVelocity);
}
void APacManCharacter::MoveYAxis(float AxisValue)
{
CurrentVelocity.Y = AxisValue;
AddMovementInput(CurrentVelocity);
}
// 重启游戏
void APacManCharacter::ReStart()
{
// 使用GetWorld()需要引入头文件:#include "Engine/World.h"
GetWorld()->GetFirstPlayerController()->ConsoleCommand(TEXT("RestartLevel"));
}
// 重来一局游戏
void APacManCharacter::NewGame()
{
if (GameMode->GetCurrentState() == EGameState::EMenu)
{
GameMode->SetCurrentState(EGameState::EPlaying);
}
}
// 暂停游戏
void APacManCharacter::Pause()
{
if (GameMode->GetCurrentState() == EGameState::EPlaying)
{
GameMode->SetCurrentState(EGameState::EPause);
}
else if (GameMode->GetCurrentState() == EGameState::EPause)
{
GameMode->SetCurrentState(EGameState::EPlaying);
}
}
#include "Kismet/GameplayStatics.h"
#include "Engine/World.h"
void APacManCharacter::BeginPlay()
{
Super::BeginPlay();
// 获得当前的游戏模式
// 使用 UGameplayStatics 需要引入的头文件:#include "Kismet/GameplayStatics.h"
// 需要强制转换,使用 Cast <APacManGameModeBase>()
GameMode = Cast<APacManGameModeBase>(UGameplayStatics::GetGameMode(this));
}
void APacManCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// 第一个参数,绑定的轴的名字,与编辑器里面对应的;
// 第二个参数,this,绑定这个类上;
// 第三个参数,绑定的哪个函数
// InputComponent 报红,引入#include "Components/InputComponent.h"头文件解决
// 传递的引用,需要加上 & 符号
PlayerInputComponent->BindAxis("MoveX", this, &APacManCharacter::MoveXAxis);
PlayerInputComponent->BindAxis("MoveY", this, &APacManCharacter::MoveYAxis);
// 绑定行为,多了一个参数,IE_Pressed 按下的时候触发
PlayerInputComponent->BindAction("NewGame", IE_Pressed, this, &APacManCharacter::NewGame);
PlayerInputComponent->BindAction("ReStart", IE_Pressed, this, &APacManCharacter::ReStart);
PlayerInputComponent->BindAction("Pause", IE_Pressed, this, &APacManCharacter::Pause);
}
// 重启游戏
void APacManCharacter::ReStart()
{
// 使用GetWorld()需要引入头文件:#include "Engine/World.h"
GetWorld()->GetFirstPlayerController()->ConsoleCommand(TEXT("RestartLevel"));
}
// 重来一局游戏
void APacManCharacter::NewGame()
{
if (GameMode->GetCurrentState() == EGameState::EMenu)
{
GameMode->SetCurrentState(EGameState::EPlaying);
}
}
// 暂停游戏
void APacManCharacter::Pause()
{
if (GameMode->GetCurrentState() == EGameState::EPlaying)
{
GameMode->SetCurrentState(EGameState::EPause);
}
else if (GameMode->GetCurrentState() == EGameState::EPause)
{
GameMode->SetCurrentState(EGameState::EPlaying);
}
}
引擎- 输入
Actiong Mappings
NewGame 按键N
ReStart 按键 R
Pause 按键 P
1
5个状态:
①当我们刚进入游戏的时候,给我们一个提示说,欢迎来到PacMan,menu菜单
②游戏中 的状态,
③游戏暂停,
④游戏胜利,
⑤GameOver
状态之间可以切换
按下P键,暂停
按下N键,重新开始游戏
状态放到 GameMode 的里面 PacManGameModeBase.cpp
GameMode 用来处理游戏规则的
在 PacManGaemModeBase.h 文件中新增
enum class EGameState : short {
EMenu,
EPlaying,
EPause,
EWin,
EGameOver
};
要知道当前状态,才能做出不同的反应
private:
// 要知道当前状态,才能做出不同的反应
EGameState currentState;
2个方法:获得当前状态,给当前状态设值
public:
// 获得当前状态 加 const 不能修改我们的成员
EGameState GetCurrentState() const;
// 设置当前状态
void SetCurrentState(EGameState value);
// 为了程序更加快捷,定义一个 内联
// APcManGameModeBase 和 命名的项目名称一致,视频中的AMyPacManGameModeBase,项目名称为:MyPacMan
FORCEINLINE EGameState APacManGameModeBase::GetCurrentState() const
{
return currentState;
};
去PacManGameModeBase.cpp 中实现
void APacManGameModeBase::SetCurrentState(EGameState value)
{
currentState = value;
}
在PacManCharacter.h 中
public:
void ReStart();
void NewGame();
void Pause();
在PacManCharacter.cpp 中
// 重启游戏
void APacManCharacter :: ReStart()
{
}
// 重来一局游戏
void APacManCharacter :: NewGame()
{
}
// 暂停游戏
void APacManCharacter :: Pause()
{
}
PacManGameModeBase.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "PacManGameModeBase.generated.h"
/**
*
*/
enum class EGameState : short {
EMenu,
EPlaying,
EPause,
EWin,
EGameOver
};
UCLASS()
class PACMAN_API APacManGameModeBase : public AGameModeBase
{
GENERATED_BODY()
public:
// 获得当前状态 加 const 不能修改我们的成员
EGameState GetCurrentState() const;
// 设置当前状态
void SetCurrentState(EGameState value);
private:
// 要知道当前状态,才能做出不同的反应
EGameState currentState;
};
// 为了程序更加快捷,定义一个 内联
// APcManGameModeBase 和 命名的项目名称一致,视频中的AMyPacManGameModeBase,项目名称为:MyPacMan
FORCEINLINE EGameState APacManGameModeBase::GetCurrentState() const
{
return currentState;
};
PacManGameModeBase.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "PacManGameModeBase.h"
// 设置当前状态
void APacManGameModeBase::SetCurrentState(EGameState value)
{
currentState = value;
}
PacManCharacter.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "PacManCharacter.generated.h"
UCLASS()
class PACMAN_API APacManCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
APacManCharacter();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
void MoveXAxis(float AxisValue);
void MoveYAxis(float AxisValue);
// 重启游戏
void ReStart();
// 重玩一局游戏
void NewGame();
// 暂停游戏
void Pause();
// 定义一个向量
FVector CurrentVelocity;
};
PacManCharacter.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Public/PacManCharacter.h"
#include "Components/InputComponent.h"
// Sets default values
APacManCharacter::APacManCharacter()
{
// 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;
}
// Called when the game starts or when spawned
void APacManCharacter::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void APacManCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void APacManCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// 第一个参数,绑定的轴的名字,与编辑器里面对应的;
// 第二个参数,this,绑定这个类上;
// 第三个参数,绑定的哪个函数
// InputComponent 报红,引入#include "Components/InputComponent.h"头文件解决
// 传递的引用,需要加上 & 符号
PlayerInputComponent->BindAxis("MoveX", this, &APacManCharacter::MoveXAxis);
PlayerInputComponent->BindAxis("MoveY", this, &APacManCharacter::MoveYAxis);
}
void APacManCharacter::MoveXAxis(float AxisValue)
{
CurrentVelocity.X = AxisValue;
AddMovementInput(CurrentVelocity);
}
void APacManCharacter::MoveYAxis(float AxisValue)
{
CurrentVelocity.Y = AxisValue;
AddMovementInput(CurrentVelocity);
}
// 重启游戏
void APacManCharacter::ReStart()
{
}
// 重来一局游戏
void APacManCharacter::NewGame()
{
}
// 暂停游戏
void APacManCharacter::Pause()
{
}
正黄色:
RGB={1,1,0}
设置GameMode,定制游戏规则的一个类:
先找到C++类下,MyPacMan文件夹下的 MyPacManGameModeBase
双击 MyPacManGameModeBase 进入代码编辑
以 GameMode 为父类 ,创建一个基于它的C++蓝图类
右键MyPacManGameModeBase,【创建基于MyPacManGameModeBase的蓝图类】
命名您的新 MyPacManGameModeBase
蓝图类命名加前缀 BP_
保存位置在BluePrints文件夹下
/*
此项目创建的名称为MyPacMan
我的项目创建的名称为:PacMan
所有没有My
但是引擎好像默认就加上了My,疑问???
*/
创建后:自动打开蓝图编辑器:
Default Pawn Class 就是 默认创建的角色
修改成,我们创建的BP_PacManCharacter
修改后,先编辑,再保存
修改默认的地图模式
Default Modes
Unreal 编辑器---【编辑】---【项目设置】---【项目-- 地图&模式】
选择我们刚刚创建的 BP_MyPacManGameModeBase
Player Start 用来确定主角刚开始的位置的
删除了 Player Start ,系统默认生成主角的位置在(0,0,0)
不小心删除掉了 Player Start ,在【模式】--【基本】---【玩家起始】拖到 关卡 中
Player Start 报BADsize,是因为碰撞体 和 其他碰撞体重合了
移动到不重合的地方就没有报 BADsize
淡蓝色的箭头代表生成的主角 正对的方向:
前后键控制Y轴的移动
把Y轴当成它的正方向
却发现X轴是它的正方向
Player Start 和 BP_PacManCharacter 也是把X轴当成了 正方向
修改前:
修改后:
X轴是前后
Y轴是左右
2个碰撞体一碰就不能往前走了