UE4 C++ 开发游戏
粉胖香菇
资源导入与人物创建

新增一个c++的character类 取名叫 benben
然后基于 benben 新建一个蓝图类 取名 BP_benben
打开 BP_benben 蓝图类 为其选择一个骨骼网格体,并且条件碰撞体的大小。

创建全局摄像机
新建一个 gamemodebase 蓝图类,用于控制游戏的模式,取 BP_gamemode

在项目设置中 选择 default gamemode(默认游戏模式) 为BP_gamemode
default pawn class(默认角色类)为BP_benben
这样在视图中 playerstart 就是 BP_benben 了
添加一个C++ actor 类 取名 BP_gamecamera


在.h文件中声明camercomp类 然后在.cpp中实例化
需要先声明UCameraComponent 类 然后申请一个 CameraComp的类
CameraComp = CreateDefaultSubobject
CameraComp->SetupAttachment(RootComponent); 将CameraComp附加到RootComponent根组件下
CreateDefaultSubobject
该函数是个模板函数,用于创建组件或子对象,然后返回指向新建组件内存区域的指针。
此函数只能在无参构造器中使用而不能在BeginPlay等函数中使用!
参数中的TEXT或者FName参数在同一个Actor中不能重复!
可以使用SetupAttachment将一个组件附加到另一个组件上
UPROPERTY 用途广泛。它允许变量被复制、被序列化,并可从蓝图中进行访问。Category 指定在Blueprint编辑工具中显示的属性的类别

编译后如图所示 CameraComp 被附加到了 BP_gameCamera 组件下
指定默认摄像机


声明一个玩家控制器 PC ,然后利用 视角混合 将视角给我们申请的摄像机
然后调节视图中个各位置即可。
主角跟随鼠标移动
在benben.h中申明 APlayerController* PC;
直接利用 GetController() 方法 赋给 PC
把 bShowMouseCursor 属性设置成true ,这样就可以显示鼠标了

在.h中声明 MoveTowardsCursor 函数用于主角跟随鼠标移动
APlayerController* PlayerController = GetWorld()->GetFirstPlayerController(); //获取鼠标位置
FVector2D MousePos = FVector2D(0, 0);
FVector MouseLocation, MouseDirection;
PlayerController->GetMousePosition(MousePos.X, MousePos.Y); //获取2d鼠标坐标
PC->DeprojectMousePositionToWorld(MouseLocation, MouseDirection);
FVector2D ObjLocation(0, 0);
PlayerController->ProjectWorldLocationToScreen(GetActorLocation(), ObjLocation); //获取球在屏幕上2d坐标
float XDirection = FMath::Clamp(ObjLocation.X - MousePos.X, -1.0f, 1.0f);
FVector Direction = FVector(XDirection, 0, 0);
float ScaleValue = FMath::Clamp( FMath::Abs(MouseLocation.Y - GetActorLocation().Y / 100),0.0f,1.0f);
AddMovementInput(Direction, ScaleValue);
DeprojectMousePositionToWorld : Convert current mouse 2D position to World Space 3D position and direction. Returns false if unable to determine value.

获取鼠标的3d世界坐标,再获取主角的2d坐标,用 鼠标位置.x - 主角位置.x = 移动的方向 Direction ; Scalevalue 是移动的大小 可以为空

因为需要不断的调用,所以要放在 tick 里面

主角垂直方向跳跃
在.h中定义
void Lanuch();和
void LaunchOnAnyKeyPressed();
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
void Abenben::Lanuch()LaunchOnAnyKeyPressed()函数 分别实现点击时跳跃和碰到云时跳跃
{
LaunchCharacter(LaunchVelocity,false,true);
}
LaunchVelocity是一个三维向量代表跳跃的方向 LaunchVelocity = FVector(0, 0, 1500);
void Abenben::LaunchOnAnyKeyPressed()
{
if (!GetCharacterMovement()->IsFalling() && !bGameStarted) {
Lanuch();
}
if (bGameStarted == false) {
bGameStarted = true;
}
}
为角色动作进行绑定
void Abenben::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &Abenben::LaunchOnAnyKeyPressed);
}

为Jump设置输入为任意键

允许 Pawn 设置自定义输入绑定。由玩家控制器使用由创建玩家输入组件创建的输入组件()调用。
摄像机跟随
只需让摄像机.z的值跟主角一致即可
先在Game Camera.h 中获得 Abenben类型的指针
class Abenben;
Abenben* benben;
在Game Camera.cpp 中用类型转换获取主角
void AGameCamera::BeginPlay()
{
Super::BeginPlay();
benben = Cast<Abenben>(UGameplayStatics::GetPlayerPawn(this, 0)); //获取主角
PC = UGameplayStatics::GetPlayerController(this, 0); //获取playercontroller
PC->SetViewTargetWithBlend(this, 0); //调用playercontroller下的一个方法对视角进行混合,开始为刚进入游戏及0
}
在Game Camera.h声明 void MoveCamera(); 用于移动摄像机
void AGameCamera::MoveCamera()
{
FVector TargetPos = FVector(GetActorLocation().X, GetActorLocation().Y, benben->GetActorLocation().Z); //获得小球的Z方向,和自身的XY
SetActorLocation(TargetPos); //该方法可以设置本Actor的位置
}
自身的x y 保持不变,z随benben改变
void AGameCamera::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (bFollowPlayer)
{
MoveCamera();
CheckIfFalling();
}
}
写入tick随时调用
创建云彩
新建一个actor类 ,需要有碰撞检测和mesh模型

绑定碰撞盒子和模型到根组件


编译后创建基于Cloud的蓝图类

指定好模型和贴图材质
云的随机生成和文本

我们要为Cloud的材质设定多个随机值,需要修改的就是这个 Texture

在cpp的SetARandomCloudTexture()中随机生成材质
void ACloud::SetARandomCloudTexture()
{
MatInterface = CloudPlane->GetMaterial(0); //为云设置材质
MatInstance = CloudPlane->CreateDynamicMaterialInstance(0, MatInterface);
int Index = FMath::RandRange(0, 2); //随机生成材质
if (Textures[Index])
{
MatInstance->SetTextureParameterValue(FName(TEXT("Texture")), Textures[Index]); //将材质设置好贴图
CloudPlane->SetMaterial(0, MatInstance);
}
}

为Cloud 设置材质
检测云与主角的碰撞

当此执行组件与另一个执行组件重叠时发生的事件,例如,玩家走进触发器。有关对象发生阻塞碰撞时的事件,例如玩家撞墙,请参阅“命中”事件。
此 Actor 和其他 Actor 上的组件都必须将“b生成过重叠事件”设置为 true 才能生成重叠事件。
Syntax
virtual void NotifyActorBeginOverlap
(
AActor * OtherActor
)
Remarks
Event when this actor overlaps another actor, for example a player walking into a trigger. For events when objects have a blocking collision, for example a player hitting a wall, see 'Hit' events.
Components on both this and the other Actor must have bGenerateOverlapEvents set to true to generate overlap events.
重写 SetARandomCloudTexture 函数当碰撞的类是

TSubclassOf<ACloud> Cloud; //限定赋值的只有Cloud及其子类
因为有TSubclassOf 限定了赋值的可以是Cloud及其子类,将其设置为BP_CLoud , 我们打算利用SpawnArea 生成云;TriggerArea清除云
调整SpawnArea 和 TriggerArea 大小
云彩生成器及其上升
新建一个cloudSpawn类
ACloudSpawner::ACloudSpawner()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
SpawnArea = CreateDefaultSubobject<UBoxComponent>(TEXT("SpawnArea"));
TriggerArea = CreateDefaultSubobject<UBoxComponent>(TEXT("TriggerArea"));
DefaultRootComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("DefaultRootComponent"));
RootComponent = DefaultRootComponent;
SpawnArea->SetupAttachment(RootComponent); //将SpawnArea绑定到RootComponent下
TriggerArea->SetupAttachment(RootComponent); //将TriggerArea绑定到RootComponent下
InitialSpawnAmount = 6;
SpawnSpacing = 300.0f;
//DefaultRootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("DefaultRootComponent"));
//RootComponent = DefaultRootComponent;
//SpawnArea = CreateDefaultSubobject<UBoxComponent>(TEXT("SpawnArea"));
//SpawnArea->SetupAttachment(RootComponent);
//TriggerArea = CreateDefaultSubobject<UBoxComponent>(TEXT("TriggerArea"));
//TriggerArea->SetupAttachment(RootComponent);
}
声明并且实例化云彩生成器 SpawnArea 生成云;TriggerArea清除云,并将他们绑定到根组件下,方便其整体的移动

游戏开始时,先进入reset()函数
先把获取所有的云放进指针数组里面,然后通过遍历删除所有云彩,如果类型转换不为空就进入 Destroy() ,然后根据InitialSpawnAmount记录的云彩数量进行生成。
void ACloudSpawner::Reset()
{
InitialSpawnAmount = 6;
SetActorLocation(FVector::ZeroVector); //将云彩生成器的位置归零
TArray<AActor*> FoundClouds;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ACloud::StaticClass(), FoundClouds); //通过获取所有actor来找到所以云
for (AActor* TActor : FoundClouds)
{
ACloud* MyCloud = Cast<ACloud>(TActor);
if (MyCloud != nullptr)
{
MyCloud->Destroy();
}
}
while (InitialSpawnAmount >= 0)
{
SpawnCloud();
InitialSpawnAmount--;
}
}
利用 SpawnCloud() 来生成云彩。 注释写的比较清楚了
AddActorWorldOffset(FVector(0, 0, SpawnSpacing)); 是将CloudSpawn向上移动 SpawnSpacing 的距离
void ACloudSpawner::SpawnCloud()
{
FVector SpawnOrigin = SpawnArea->Bounds.Origin; //获得盒子中心点
FVector SpawnExtent = SpawnArea->Bounds.BoxExtent; //获得盒子范围
float YLocation = UKismetMathLibrary::RandomPointInBoundingBox(SpawnOrigin, SpawnExtent).X; //随机范围内获得Y方向的点
//FVector SpawnLocation = FVector(SpawnArea->GetComponentLocation().X, YLocation, SpawnArea->GetComponentLocation().Z); //是Component组件类型所以调用GetComponentLocation,若为actor类型则调用GetActorLocation
FVector SpawnLocation = FVector(YLocation, SpawnArea->GetComponentLocation().X, SpawnArea->GetComponentLocation().Z); //是Component组件类型所以调用GetComponentLocation,若为actor类型则调用GetActorLocation
FActorSpawnParameters SpawnParams;
GetWorld()->SpawnActor<ACloud>(Cloud, SpawnLocation, FRotator::ZeroRotator, SpawnParams); //生成cloud
AddActorWorldOffset(FVector(0, 0, SpawnSpacing));
}
ps:
冒号起分割作用,是类给成员变量赋值的方法,初始化列表,更适用于成员变量的常量const型。
struct _XXX{
_XXX() : y(0xc0) {}
};
NotifyActorBeginOverlap用于处理碰撞。当碰撞的对象是主角 benben 时 就在生成一个云彩
void ACloudSpawner::NotifyActorBeginOverlap(AActor* OtherActor)
{
Super::NotifyActorBeginOverlap(OtherActor);
benben = Cast<Abenben>(OtherActor);
if (benben) {
SpawnCloud();
}
}
InitialSpawnAmount 用于设置云彩生成打算数量
SpawnSpacing 用于生成云彩生成的距离
UPROPERTY(EditAnywhere, Category = "Cloud")
int InitialSpawnAmount;
UPROPERTY(EditAnywhere, Category = "Cloud")
float SpawnSpacing;
显示分数
在.h中声明两个函数
void IncreaseScore();
int Score;
int GetScore() const;
在 .cpp 中
void Abenben::IncreaseScore()
{
Score++;
}
int Abenben::GetScore() const
{
return Score;
}
在cloud.h中声明void DisplayScore();函数

创建一个在蓝图中的默认对象,并把他绑定在根目录下。
displayscore函数用来修改显示分数的文本

编译后调整文本的位置大小,把初始文本设置为 空格 。
TimeLine控制云彩字体的缩放和透明度
在cloud.h中
UPROPERTY(VisibleAnyWhere, BlueprintReadOnly, Category = "Show") //可以在蓝图中读取,指定可以在外部查看,类别为show
UStaticMeshComponent* CloudPlane;
UFUNCTION(BlueprintImplementableEvent)
void FadeOut();
UPROPERTY(VisibleAnyWhere, BlueprintReadOnly, Category = "Show")
UMaterialInstanceDynamic* MatInstance; //用于指定不透明度
使得FadeOut可以在蓝图中调用。
在cloud.cpp中调用
void ACloud::NotifyActorBeginOverlap(AActor* OtherActor)
{
Super::NotifyActorBeginOverlap(OtherActor); //先调用父类的该方法
Benben = Cast<Abenben>(OtherActor);
if (Benben != NULL)
{
Benben->Lanuch();
DisplayScore();
UGameplayStatics::PlaySoundAtLocation(this, CloudSound, GetActorLocation());
//Destroy();
FadeOut();
}
}
编译
因为Cloud Plane 和 MatInstance都已经在C++中暴露给蓝图,所以可以直接调用,先由updata控制逐帧物体缩放和不透明度

时间轴如下,物体先放大一下然后缩小
透明度逐渐减小,然后消失,要设置的浮点型对象为Opacity

找到模型所使用的材质,可以发现不透明度 的设置是由 A-Opacity控制的,这就是上面要设置Opacity的原因

游戏结束
用bDead表示角色是否死亡,用bGameStarted来表示游戏是否结束
void GameOver();
UPROPERTY(BlueprintReadOnly)
bool bDead;
bool bGameStarted;
当游戏结束时bDead设置为0 然后开启输入 并且展示结束的UI界面(这个在下一个小结来写)
void Abenben::GameOver()
{
bDead = true;
SetActorRotation(FRotator::ZeroRotator);
EnableInput(PC);
DisplayRestart();
}
把它放在Tick中不断调用
void Abenben::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
MoveTowardsCursor();
if (bGameStarted)
{
if (GetActorLocation().Z <= -48)
{
GameOver();
}
}
}
UI界面 重新开始按钮
创建一个控件蓝图

设置大小对齐 和 Style,添加一个动画

在游戏开始时把该控件创建出来,并且用一个变量保存 。执行displayrestart 时把该变量显示出来,并且播放动画

点击时触发事件

void Abenben::Reset()
{
bGameStarted = false;
Score = 0;
bDead = false;
}
void ACloudSpawner::Reset()
{
InitialSpawnAmount = 6;
SetActorLocation(FVector::ZeroVector);
TArray<AActor*> FoundClouds;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ACloud::StaticClass(), FoundClouds); //通过获取所有actor来找到所以云
for (AActor* TActor : FoundClouds)
{
ACloud* MyCloud = Cast<ACloud>(TActor);
if (MyCloud != nullptr)
{
MyCloud->Destroy();
}
}
while (InitialSpawnAmount >= 0)
{
SpawnCloud();
InitialSpawnAmount--;
}
}
当点击时,分别执行0.1.2.3这些事件 ,先调用benben->REset 设置bGameStarted Score bDead 这些属性
获得摄像机让其跟随玩家
用 ACloudSpawner::Reset 销毁所有云,然后重新生成。
最后销毁自身。
随机显示下雨
.h 中
UPROPERTY(VisibleAnyWhere, BlueprintReadOnly, Category = "Show") //指定可以在外部查看,类别为show
UStaticMeshComponent* RainPlane;
UPROPERTY(EditAnyWhere, Category = "Sound")
USoundCue* CloudSound;
UPROPERTY(VisibleAnyWhere, Category = "Sound")
UAudioComponent* AudioComp;
.cpp中 添加组件
RainPlane = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("RainPlane")); //添加模型组件
RainPlane->SetupAttachment(CloudPlane);
AudioComp = CreateDefaultSubobject<UAudioComponent>(TEXT("AudioComp"));
AudioComp->SetupAttachment(CloudPlane);
导入声音并且关闭自动激活

把声音创建为cue这样才有根据距离调整音量的效果。

把衰减音量的选项打开,根据实际情况调整衰减半径
添加雨的贴图然后把visible设置为不可见。


每次生成云时生成一个index随机数index若其>=10则将雨设为可见,如果是有雨的云就打开声效。在开始生成云时调用。
导入并且指定主角材质
如图就不多说了

新建一个动画蓝图,并设置主角的骨架。

在BPbinbin下指定动画

用状态机播放动画
用状态机处理角色走路 跳跃 下落 死亡 等动画
为每个状态指定好动画

新建 isfalling isjumping 变量来判断角色运动状态。

当isjumping为true时播放跳跃动画,isfalling为true时播放下落动画,都为false时播放走路动画
那么我们如果给isfalling isjumping 赋值呢?
设置状态切换条件
通过判断角色z方向的速度设置变量

混合空间
新建一个混合空间,其骨架设置为主角。

将该混合空间命名为speed拖入两个速度时的动画

把刚才的混合空间作为动画的输出,其参数为新声明的bool变量 speed。

在蓝图中设置speed

把bDead暴露给蓝图。在蓝图中把bDead设置为空(见上图)

当isdead为真时才播放死亡动画,为假时播放其他动画,把loop设置为false否则会死了又死

这个reset child on activation让其可以从头开始播放。

背景音乐 动画通知播放脚步声 碰撞云彩声
把背景音乐放到场景中,设置loop即可
如下图播放到相应位置时添加通知 playsound 并添加 声音

在cloud.h中声明
UPROPERTY(EditAnyWhere, Category = "Sound")
USoundCue* CloudSound;
在cloud.cpp中
void ACloud::NotifyActorBeginOverlap(AActor* OtherActor)
{
Super::NotifyActorBeginOverlap(OtherActor); //先调用父类的该方法
Benben = Cast<Abenben>(OtherActor);
if (Benben != NULL)
{
Benben->Lanuch();
DisplayScore();
UGameplayStatics::PlaySoundAtLocation(this, CloudSound, GetActorLocation());
//Destroy();
FadeOut();
}
}
在显示分数后播放
将音效创建为 cue
在蓝图中选定资源

游戏发布
打包就完事了
但是我却遇到了一些问题
包含了一个UE无法编译的头文件,多亏了学长的帮助才解决了




