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(TEXT(“CameraComp”)); //实例化摄像机
CameraComp->SetupAttachment(RootComponent); 将CameraComp附加到RootComponent根组件下

CreateDefaultSubobject

该函数是个模板函数,用于创建组件或子对象,然后返回指向新建组件内存区域的指针。

此函数只能在无参构造器中使用而不能在BeginPlay等函数中使用!
参数中的TEXT或者FName参数在同一个Actor中不能重复!

可以使用SetupAttachment将一个组件附加到另一个组件上

UPROPERTY 用途广泛。它允许变量被复制、被序列化,并可从蓝图中进行访问。Category 指定在Blueprint编辑工具中显示的属性的类别

编译后如图所示 CameraComp 被附加到了 BP_gameCamera 组件下

指定默认摄像机

声明一个玩家控制器 PC ,然后利用 视角混合 将视角给我们申请的摄像机

SetViewTargetWithBlend官方文档

然后调节视图中个各位置即可。

主角跟随鼠标移动

在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无法编译的头文件,多亏了学长的帮助才解决了