您的位置:首页 > 编程语言 > C语言/C++

Learning C++ by Creating Games With UE4(15.05.20)(Chapter 11-1)Monster

2015-05-20 16:53 716 查看
Chapter11-1 怪物

我们这一章节需要在场景中添加一个岛屿的场景作为范例。玩家将可以在整个岛上漫游,同时他也会遇到一些敌人,NPC可以在他遇到敌人之前给予他一定帮助和建议

岛屿

地形创建并不是我主要翻译的内容,而且这一部分也非常简单,大家有兴趣可以从我另一个文章中将这本书下载下来看一下,那么我们进入这一讲比较主要的部分。

怪物

我们将会创建许多的怪物,同样用我们创建NPC的方式或者捡起物品的方式。首先我们需要写一个基础的类去展现怪物。我们需要创建一个蓝图来对应每个怪物的类型。每个怪物将会有一些共同的属性来决定他们的行为,我们归纳有以下几点:

1. float类型的速度

2. Float类型的血量

3. Int32类型获取怪物的经验

4. UClass的方法来设置怪物掉落的战利品

5. Float 类型设置每次基础攻击的伤害

6. float类型的变量设置怪物两次攻击的间隔

7. 两个USphereComponents对象:一个用来设置他们可以探查到的范围,另一个用来设置攻击范围,要比探查范围小一些

那么下面我们需要添加怪物类型的代码进入我们的工程了,选择Character作为怪物的父类

下面是怪物的头文件

#pragma once

#include "GameFramework/Character.h"

#include "Monster.generated.h"

UCLASS()

class CPLUSLEARN_API AMonster : public ACharacter

{

GENERATED_UCLASS_BODY()

public:

// Sets default values for this character's properties

AMonster();

//怪物的速度

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;

};

.cpp源文件

#include "CplusLearn.h"

#include "Monster.h"

// Sets default values

AMonster::AMonster(const class FObjectInitializer&PCIP) :Super(PCIP)

{

//初始化怪物信息

Speed = 20;

HitPoints = 20;

Experiences = 0;

BPLoot = 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);

}

这个怪物类的创建基本上和我们创建NPC的方式一样,唯一区别在于怪物有两个碰撞范围一个是怪物感知范围,一个是怪物攻击范围。

怪物模型我们可以在UE4自带的市场中寻找一些免费模型进行下载

最基础的怪物智能设计

在我们的游戏中,我们将要添加一个基础智能的功能给我们的怪物角色。这些怪物需要有两个基础功能:

1.跟踪玩家

2.攻击玩家

怪物将不会做其他多余的事情,你也可以当玩家第一次见到怪物时,让怪物来嘲讽玩家,这个将会作为本章节的一个练习

移动怪物——怪物转向行为

怪物在一些非常基础的游戏中不需要非常复杂的情感和表现。大部分他们只是漫无目的的行走然后找到目标最后攻击他们的目标。我们就来通过编码制作这样的怪物在我们的游戏中,但是提醒你,你也可以获得更多更有趣的事情与怪物进行交互的时候。我们尽管不会去编写,但是这些可以作为你学习的扩展功能。

为了获得怪物角色向玩家的方向移动,我们需要动态更新怪物的方向,怪物要一直朝向玩家在每一帧之中。因此我们需要不断更新怪物的朝向,这个在UE4中使用Monster::Tick()也就是每一帧都会调用这个函数内的内容,和unity3d中的Update()是一样的。

Tick函数是会在游戏的每一帧进行运行,我们声明他的方式是:

Virtual void Tick(float DeltaSeconds)override

你需要添加这个功能在你怪物的原型类之中,也就是我们创建好的monster.h中。如果你要覆盖Tick的方法,你就可以把我们自定义的怪物行为运行在每一帧之中。这里有些基础的代码来使得怪物每一帧朝向玩家移动。



我们可以看到所有怪物都在向我跑来,那么下面上代码

头文件中只需要添加这一句

//每一帧调用使得怪物朝向玩家

virtual void Tick(float DeltaSeconds)override;

源文件中我们来实现:

void AMonster::Tick(float DeltaSeconds)

{

Super::Tick(DeltaSeconds);

AAvatar *avatar = Cast<AAvatar>(UGameplayStatics::GetPlayerPawn(GetWorld(), 0));

//若没有找到玩家则返回

if (!avatar)return;

//找到玩家时,设置朝向玩家的三维向量是 玩家的坐标-当前怪物的坐标

FVector toPlayer = avatar->GetActorLocation() - GetActorLocation();

toPlayer.Normalize();//减少单位向量

//怪物实际向玩家移动

AddMovementInput(toPlayer, Speed*DeltaSeconds);

//怪物朝向玩家旋转

FRotator toPlayerRotation = toPlayer.Rotation();

//让怪物朝向的高为0

toPlayerRotation.Pitch = 0;

//将根节点也就是怪物的整体设置面向玩家

RootComponent->SetWorldRotation(toPlayerRotation);

}

下面我们来简单介绍一下这个实现的原理:



M代表怪物,P代表人。其实核心实现过程就是:人在1的时候,怪物指向人的方向并开始移动,当人走到2,怪物此时到M2的时候,怪物指向人P2的方向进行移动,那么人移动到P3也是同理。

小提示:

怪物以每秒60移动的原因主要是由于硬件的限制。对于一般的显示器来说刷新率是60HZ,那么其表现就限于每秒有多少内容需要更新。帧速率比刷新率快也是有可能的,但是这对在游戏中使用并不重要因为你只需要看见一个图像在每六十分之一秒在大多数的硬件上。

怪物运动的离散性质

电脑游戏大部分都是离散的。在前面截图中我们可以看到这些帧的序列都是叠加在一起的。玩家看起来在屏幕上好像沿着一条直线运动就像1-2-3,但是对于怪物来说,他们都是在不断地改变着朝向向玩家移动。怪物跟随玩家的路径就像是一条曲线。

为了让怪物向玩家移动,我们首先需要获得玩家的位置。因为玩家是在全局中可以获取到的,UGameplayStatics::GetPlayerPawn,我们通过这种方式获取能够控制玩家的一个指针。下面我们需要找到指向怪物的三维向量坐标GetActorLocation()以及指向玩家的三维向量avatar->GetActorLocation().我们需要找到从怪物指向人的三维向量坐标


就像这样,通过两个三维向量做一个差值便获取了从怪物到玩家的这样一个方向。

怪物的可侦察范围

现在,这些怪物并没有通过这个可侦察范围来触发跟踪玩家的动作。我们需要对代码进行一些修改。



像这幅图片给我展示的,当玩家进入怪物可侦察范围的圆形后,只要在r半径内,就会触发怪物的动作

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 (distanceToPlayer > SightSphere->GetScaledSphereRadius())

{

return;

}

//标准化向量 用三维向量除以直线距离

toPlayer /= distanceToPlayer;

//怪物实际向玩家移动

AddMovementInput(toPlayer, Speed*DeltaSeconds);

//怪物朝向玩家旋转

FRotator toPlayerRotation = toPlayer.Rotation();

//让怪物朝向的高为0

toPlayerRotation.Pitch = 0;

//将根节点也就是怪物的整体设置面向玩家

RootComponent->SetWorldRotation(toPlayerRotation);

}

最终效果如下



我们可以通过简单的内联函数将视野距离和攻击距离包裹起来

头文件中我们添加下面内容

//追踪可视范围

inline bool isInSightRange(float d)

{

return d < SightSphere->GetScaledSphereRadius();

}

//攻击可视范围

inline bool isInAttackRange(float d)

{

return d < AttackRangeSphere->GetScaledSphereRadius();

}

源文件中我们只需要修改

//如果与玩家的距离比侦测范围大,返回

if (!isInSightRange(distanceToPlayer))

{

return;

}

小提示:内联函数与宏的使用方式非常相似,内联函数一般用于频繁执行的函数。 一个小内存空间的函数非常受益。同时这样使得代码易于控制,可以重用。具体内联函数的使用大家自行百度好了~~~~

第11章下一部分主要是让这些可以跟着我们人物行走的怪物拿上武器开始攻击。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: