UE C++

环境配置

实在受不了visual studio code了,提示没有,报错离谱。unity用了很久很久还是没有适应,直接转Rider了。

内容参考这一篇Unreal 开发环境(Rider for unreal)配置 – 知乎,同时,对于项目的启动,也可以直接右键.uproject然后用Rider打开了,省去了自己构建.sin的步骤。

框架

MVC架构

启动流程

基本的游戏流程:Player的构造函数,随后Controller的Onposses,最后Player->Beginplay();
其中Onposses就是控制器获取对Pawn的控制权的时候触发的函数。

  • Player的构造函数:通常用于初始化默认值和组件,但不会涉及与其他游戏对象的交互(因为世界还没准备好)。例如初始化默认变量(如速度、生命值)、创建组件(如相机、碰撞盒、武器等)。
  • BeginPlay:当 Pawn(玩家或AI角色) 正式进入游戏世界 时调用。此时,世界(World)已准备好,Controller也已经 Possess 了 Pawn,可以访问World和 Controller。一般可以设置动画状态、初始化 HUD 或 UI、访问其他 Actor,比如武器、队友、敌人等。
  • Onposses:存储 Pawn的引用,用来后续控制角色,然后做一些控制层面的初始化。

基本概念

面向对象

UE5高度面向对象,使用Cpp和其反射系统共同管理脚本,对Cpp面向对象有一定更改。

  • 类和对象:一个是抽象一个是具体的实例
  • 继承和接口:虚幻采用单继承,一个类只有一个父类;但支持接口(通过UInterface),允许实现多个接口,从而实现一个多重继承的效果。
  • 封装:
    • 访问控制:public,protected,private
    • UPROPERTY:可以用这个宏来控制访问权限和反射信息,一般自己写Cpp类就会用到
  • 虚函数和反射:virtual和override;反射系统允许运行时动态的访问和调用对象的属性和函数
继承和组合

继承是一个类从另一个类获取属性和行为的方式。相当于is-a

组合是把不同组件放在一起构建复杂系统的设计方式,比如说Actor和Component。相当于has-a,因此灵活性比较高,可以复用组件之类的。

AActor、APawn、ACharacter
  • UObject:万物皆生于UObject,一个公共的基类便于实现UE的反射系统。
  • AActor:UE世界中的一个个“演员”,有了一些额外的功能,最重要的就是位置坐标。因为一些演员没有碰撞体积(观战者),因此AActor没有赋予碰撞检测功能。
  • APawn:可以操作的棋子,也就是角色,主要提供三个功能:
    • 可被Controller控制
    • PhysicsCollision表示
    • MovementInput的基本响应接口
  • ACharacter:简单理解就是人形的角色,这是因为游戏中代入的角色大部分是人。提供了像人一样行走的CharacterMovementComponent, 尽量贴合的CapsuleComponent,再加上骨骼上蒙皮的网格。
GameModeBase 和 GameMode
  • AGameModeBase
    • 游戏模式的基类,定义了游戏的规则,行为,逻辑
    • 可以通过创建自定义的游戏模式类扩展它
  • AGameMode
    • 是AGameModeBase的一个子类,提供了更多控制权和功能,比如玩家控制,团队管理,记分系统等(一般也不用,所以一般GameModoBase就够了)
    • 可以使用AGameMode类来实现更复杂的游戏逻辑和规则
GameSession

用来控制联机状态下,会话管理,游戏状态同步,跨服务器通信等情况,也可以管理控制权限

GameState 和 PlayerState

一个是游戏全局的状态,全局唯一包含了整个游戏状态;而后面的代表了单个玩家的状态,针对特定玩家

DefaultPawnClass

用来指定没有特定玩家控制情况下的默认角色,一般可以设置为游戏中任何Pawn类或其他子类

APlayerController和AUHD

前者控制玩家的输入,视角,动作;后者是用于显示游戏界面,可以显示各种元素,如生命值,得分,道具信息之类的

MVC思想

按照MVC思想,一个Gameplay可以分为三个部分,表现(View)、逻辑(Controller)、数据(Model)

GamePlay框架

对于游戏本身有GameMode和GameState两个大类,GameMode由游戏的基础规则构成,基于GameMode发生事件的信息则由GameState保存并同步给客户端。

玩家通过 PlayerController 加入游戏并交互,所以这个类也会提供一些与玩家的交互接口/功能,比如Input接收玩家的输入(键盘/鼠标/手柄),同时PlayerController 作为中间层,对于接收到玩家的交互,就会去管理控制 Pawn,产生对应行为如攻击,移动这些
HUD 提供UI显示,比如玩家的血条之类的
PlayerCameraManager 管理玩家的相机

对于在联机中,有很多数据/属性都是玩家固有的(比如玩家health/coins),保存在PlayerController中本身不会同步给其他客户端,在UE中还有一个PlayerState类,用来保存玩家强相关的一些数据/属性

对应过来UE的设置,在世界场景设置之中可以看到这些东西。

图上为世界场景设置下的一些选项,可以看到还有一个 SpectatorPawn 没有介绍,介绍功能来说,其相当于一个能让玩家在游戏中自由观察但不可以互动的东西,个人理解可能类似于王者或pubg里面的观战

  • 职责核心:驱动一个无实体碰撞,无游戏逻辑的相机,提供观察者视角
  • 主要用途:玩家死亡后等待观察,观众管理员视角,赛前赛后视角
  • 状态关联:代表“非活跃参与者”,即观察者状态
  • 一般来说会提供组件关联,即 SpectatorPawnMovement 组件来控制移动

Controller与Pawn

这个疑惑来自于我配置时候,即下面这段代码

void APlayCharacter::Look(const FInputActionValue& Value)
{
FVector2D LookAxis = Value.Get<FVector2D>();

// 对于继承自Pawn类的,一定会有Controller
// 对于配置,应当在GameMode中配置过,默认绑定了,外部可见
if (Controller)
{
// 控制摄像头旋转
AddControllerYawInput(LookAxis.X); // 控制摄像头水平旋转
AddControllerPitchInput(LookAxis.Y); // 控制摄像头垂直旋转
}
}

这是一个Pawn类中的对象,我知道controller是可以控制Pawn的,但是为什么这里也可以反向检查一下Controller是否存在?同时,我并没有进行绑定,只有在Gamemode里面,配置了Pawn和Controller,二者是怎么就关联起来了呢?

检查后吧,发现二者创建了一个关系,是一个相互引用的关系,即双向绑定:

  • Pawn持有Controller指针​:通过Controller成员变量,Pawn可以知道谁在控制它
  • Controller持有Pawn指针​:通过PossessedPawnPawn成员,Controller知道它控制哪个Pawn

那绑定的是什么时候发生的呢?反正总之UE自动搞定了,GameMode配置好就行……

具体来说,玩家加入时候引擎自动创建PlayerController实例,然后生成Pawn时自动调用Possess()SpawnDefaultPawnFor()完成一个双向绑定

// 引擎内部大致执行以下流程:
APlayerController* PC = SpawnPlayerController();
APawn* Pawn = SpawnDefaultPawn();
PC->Possess(Pawn); // 这里建立双向引用

开发类方式

目前阶段,我总觉得纯依靠Cpp不太可行。同时,蓝图和Cpp都还是有不可替代的优势的。因此还是采用二者结合的方式,比如说我创建C++类继承ACharacter,然后创建蓝图类继承该C++类。官方也是比较推荐这一种开发方式。

虽然使用了蓝图,但是属于“蓝图与C++协同开发模式”,C++去完成基础架构功能,核心逻辑函数网络之类的,然后蓝图只负责一些简单的可视化编辑和参数快速调整。

不过,随之而来的,是一堆问题。典型的来说,你在Cpp中比如说修改设置了一个变量,结果回到游戏发现没有任何更改,最后检查了很久,才发现蓝图中没有更改,而由于你蓝图是继承的关系,那肯定还是以蓝图中的值为准…….

这种对于自己写的变量还好说,不要用EditAnywhere解决,但是有些你使用的默认参数,就没办法解决。

解决方案是创建配置数据资产类,比如说我们控制相机杆子,就创建一个UCameraSettings 类,里面写我们需要控制的相机参数;接着我们在角色类中,使用我们这个配置资产

    // 配置资产引用
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Camera")
    UCameraSettings* CameraSettings;

然后,创建并配置数据资产:内容浏览器 → 右键 → 蓝图/资源 → CameraSettings,命名为DA_DefaultCameraSettings,然后配置参数,在Player蓝图里面,面板的CameraSettings设置为DA_DefaultCameraSettings

UE里面的C++包含了一堆堆宏

UPROPERTY

举例来说,对于下面这个常见的

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Camera", meta = (AllowPrivateAccess = "true"))
  • EditAnywhere:表示什么时候编辑器可以编辑这个属性
    • EditAnywhere,对应的所有
    • EditDefaultsOnly(只有默认面板,即对于一个类的默认值可以修改,那么实例出来的所有对象都是一样的),EditInstanceOnly(只有实例面板,意思是只能够修改实力出来的这个单个对象,不会影响到全局),VisibleAnywhere(仅可查看)
  • BlueprintReadOnly:表示蓝图控制访问层级
    • BlueprintReadOnly:可读不可写,用于计算值or派生属性
    • BlueprintReadWrite:可读可写,用于配置参数
    • (无蓝图标志):均不可
  • Category:给变量分组,提供高可读性
    • Category = "Camera|Zoom",表示在Camera目录下的Zoom目录下
    • 便于快速过滤查找,继承分组之类
  • meta = (AllowPrivateAccess = "true"):允许蓝图访问标记为private成员变量
    • 保持C++封装性同时开放特定权限

基础操作

日志输出

需要用到 UE_LOG 宏,按照特定的方式去传递,下面是一个简单的例子

UE_LOG(LogTemp, Warning, TEXT("Hello World"));
  • 第一个参数代表的是 LogCategory(日志分类),用来对日志进行分组筛选
    • LogTemp一般默认都是这个,用作临时日志。当然,还有LogInit,LogInit,LogGame,LogNet等
  • 第二个参数代表 LogLevel (日志级别),点那个过滤器就可以看到区别
    • 一般自己输出Warning够了,当然还有Log,Display,Error这些
  • 第三个参数代表 FormatString (格式化字符串),必须用TEXT()宏包裹,但是也支持printf格式说明符解释,后面的就是解释占位符的内容了。
    • %s 字符串,%d 整数 ,%f 浮点数,%v向量,%c字符
    • UE_LOG(LogTemp, Warning, TEXT("状态: %s"), bIsActive ? TEXT("true") : TEXT("false"));

新建类

工具->新建C++类

删除特定类

UE里面删除一个类非常非常麻烦,原因大概可以认为C++编译后UE优化后,形成了一个复杂的依赖网关系,UHT,编译系统,资源系统,配置文件。这些方面的连接都需要删除。

那UE你为什么不做自动删除呢?一个是基于安全考量,UE保守策略宁不删除也不误删(所以就把问题推给程序员?)二是UE设计哲学就这么考量的…..

操作步骤:

  • 关闭UE编辑器和VS;
    • 这俩东西会在内存中锁定源文件(特别是.pdb和dll),那么就会导致删除失败or残留
  • 在项目文件夹中找到要删除的C++类的.h头文件和.cpp文件,直接删除它们
  • 删除项目文件夹 Binaries/Intermediate/
    • Binaries/ 存储编译后的机器码,包含已删除类成员函数地址表,肯定要删
    • Intermediate/ 包含Unreal Build工具生成的文件,有一些缓存配置所以可删
  • 然后退到外面,找到开始那个.uproject后缀文件,右键选择 Generate Visual Stduio project files 重新生成一批VS文件

特定功能实现

自拍杆式摄像机

需要用到两个组件(component)

  • USpringArmComponent,用来实现摄像机的平滑跟随和碰撞处理。
    • 原理是通过虚拟的弹簧臂连接摄像机到一个父物体,自动处理摄像机与环境的碰撞和调整。
  • UCameraComponent,用来控制UE中相机视角的组件

在角色的构造函数中去绑定

.h文件定义,类去最前面定义

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	TObjectPtr<USpringArmComponent> CameraBoom;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	TObjectPtr<UCameraComponent> PlayerCamera;

.cpp文件,实现类的构造函数

#include "Player/PlayCharacter.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"

// Sets default values
APlayCharacter::APlayCharacter()
{
 	// 确认是否开启每一帧调用的
	PrimaryActorTick.bCanEverTick = true;

	CameraBoom = CreateDefaultSubobject<USpringArmComponent>("CameraBoom");
	// 相当于设置父组件
	CameraBoom->SetupAttachment(RootComponent); 
	CameraBoom->TargetArmLength = 300.0f;
	CameraBoom->SetRelativeRotation(FRotator(-40.0f, 0.0f, 0.0f));
	
	// 相机附着到杆子上
	PlayerCamera = CreateDefaultSubobject<UCameraComponent>("PlayerCamera");
	PlayerCamera->SetupAttachment(CameraBoom);

	// 使用LOG,会输出在日志中
	UE_LOG(LogTemp, Warning, TEXT("YES"));
}

控制输入

传统的通过 Input Mapping Context 和 Input Action实现,前者定义了触发规则,后者定义了对应的角色行为

不过现在已经优化了,用了更好的 EnhancedInputComponent,优点是灵活绑定好,输入流程有更好的改进之类的(比如控制触发优先级)

下面是实现一个第三人称跑动的效果

.h文件中,相机和相机杆自然是必要的。当然还不止于此,还需要创建下面这三个,创建了三个指针对象

	// UPROPERTY 属性说明符
	// Camera
	// 相机杆
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	TObjectPtr<USpringArmComponent> CameraBoom;
	// 相机
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	TObjectPtr<UCameraComponent> PlayerCamera;
	
	// Input
	// 用于存储映射上下文
	UPROPERTY(EditAnywhere,Category="Input")
	TObjectPtr<UInputMappingContext> DefaultMapping;
	// 用于存储移动输入动作
	UPROPERTY(EditAnywhere,Category="Input")
	TObjectPtr<UInputAction> MoveAction;
	// 存储移动输出动作
	UPROPERTY(EditAnywhere,Category="Input")
	TObjectPtr<UInputAction> LookAction;

这个InputMap,是用作输入和输出的映射使用,剩下两个Action,是在InputMap触发后,产生对应的动作

具体实现见下文,代码注释已详细给出

详细实现
// PlayerCharacter.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "PlayerCharacter.generated.h"

class USpringArmComponent;
class UCameraComponent;
class UInputMappingContext;
class UInputAction;
struct FInputActionValue;

UCLASS()
class MYCPP_API APlayerCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	APlayerCharacter();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	void Move(const FInputActionValue& Value);
	void Look(const FInputActionValue& Value);
private:
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	TObjectPtr<USpringArmComponent> CameraBoom;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	TObjectPtr<UCameraComponent> PlayerCamera;

	// Input
	// 声明一个 UInputMappingContext 类型的指针,用于存储输入映射上下文。
	UPROPERTY(EditAnywhere, Category = "Input")
	TObjectPtr<UInputMappingContext> DefaultMapping;
	// 声明一个 UInputAction 类型的指针,用于存储移动输入动作。
	UPROPERTY(EditAnywhere, Category = "Input")
	TObjectPtr<UInputAction> MoveAction;
	// 声明一个 UInputAction 类型的指针,用于存储视角输入动作。
	UPROPERTY(EditAnywhere, Category = "Input")
	TObjectPtr<UInputAction> LookAction;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

};
//.cpp
#include "Player/PlayCharacter.h"
#include "EnhancedInputSubsystems.h"
#include "Camera/CameraComponent.h"
#include <EnhancedInputComponent.h>
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/SpringArmComponent.h"

// Sets default values
APlayCharacter::APlayCharacter()
{
	PrimaryActorTick.bCanEverTick = true;

	// 摄像机弹簧臂设置
	CameraBoom = CreateDefaultSubobject<USpringArmComponent>("CameraBoom");
	CameraBoom->SetupAttachment(RootComponent);
	CameraBoom->TargetArmLength = 300.0f;
	CameraBoom->SetRelativeRotation(FRotator(-40.0f, 0.0f, 0.0f));
	CameraBoom->bUsePawnControlRotation = true; // 相机杆可以旋转

	// 摄像机设置
	PlayerCamera = CreateDefaultSubobject<UCameraComponent>("PlayerCamera");
	PlayerCamera->SetupAttachment(CameraBoom);
	PlayerCamera->bUsePawnControlRotation = false;// 相机不可以旋转

	// 旋转控制设置
	// 不让角色跟着控制器旋转
	bUseControllerRotationPitch = false; 
	bUseControllerRotationYaw = false;   
	bUseControllerRotationRoll = false;

	// 移动设置
	// 让角色根据真实的运动方向去旋转
	GetCharacterMovement()->bOrientRotationToMovement = true;
	GetCharacterMovement()->RotationRate = FRotator(0.f, 400.f, 0.f);
}


// Called when the game starts or when spawned
void APlayCharacter::BeginPlay()
{
	Super::BeginPlay(); //父类的 BeginPlay 函数

	// 获取本地玩家
	// 检查 GEngine(全局游戏引擎实例指针) 和 GetWorld(获取当前游戏世界上下文) 是否有效
	// 如果有效,第当前世界第一个玩家为本地玩家
	// 如果无效,把 Player 设置为 nullper
	if (const ULocalPlayer * Player = (GEngine && GetWorld()) ? GEngine->GetFirstGamePlayer(GetWorld()): nullptr)
	{
		// 获取 Enhanced Input 子系统
		// 用来管理输入映射上下文
		UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(Player);

		// 如果 DefaultMapping 有效,将其添加到子系统中
		if (DefaultMapping)
		{
			//将输入映射上下文添加到子系统中
			Subsystem->AddMappingContext(DefaultMapping, 0);
		}
	}
	
}

// Called every frame
void APlayCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// Called to bind functionality to input
void APlayCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	// 调用父类逻辑
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	// 强制转换完增强组件
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent)) 
	{
		// 绑定 LookAction 到 Look 函数
		// 四个参数分别代表:输入动作资源,触发条件,接收对象,处理函数
		// ETriggerEvent::Triggered表示持续时间内每一帧都触发
		// 当然还有 Started,Completed,Canceld,对应开始,结束,中断时候触发
		EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &APlayCharacter::Look);
		// 绑定 MoveAction 到 Move 函数
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &APlayCharacter::Move);
	}
}

// 自己写的两个有关函数
void APlayCharacter::Move(const FInputActionValue& Value)
{
	FVector2D MoveVector = Value.Get<FVector2D>();
    
	if (Controller)
	{
		// 获取控制器的 Yaw 旋转(摄像头方向)
		// 控制器的朝向就是摄像头的方向,因为之前设置了CameraBoom->bUsePawnControlRotation = true;
		// 因此此时弹簧臂跟随控制器旋转
		const FRotator YawRotation(0, Controller->GetControlRotation().Yaw, 0);

		// 获取角色自身的旋转(忽略俯仰和滚动)
		// const FRotator YawRotation(0, GetActorRotation().Yaw, 0);
		
		// 根据 Yaw 旋转计算前后和左右方向
		// FRotationMatrix:将旋转转换为方向矩阵
		// GetUnitAxis:获取标准化方向向量
		const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
		const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);

		// 添加移动输入
		AddMovementInput(ForwardDirection, MoveVector.Y);
		AddMovementInput(RightDirection, MoveVector.X);
	}
}

void APlayCharacter::Look(const FInputActionValue& Value)
{
	FVector2D LookAxis = Value.Get<FVector2D>();

	// 对于继承自Pawn类的,一定会有Controller
	// 对于配置,应当在GameMode中配置过,默认绑定了,外部可见
	if (Controller)
	{
		// 控制摄像头旋转
		AddControllerYawInput(LookAxis.X);  // 控制摄像头水平旋转
		AddControllerPitchInput(LookAxis.Y); // 控制摄像头垂直旋转
	}
}

选择出生点

先放置一个出生点,命名为PlayerStart。在场景内没有PlayerStart时候,会默认在摄像头位置;当场景中有,就会从这几个出生点中随机选择一个出来,我们需要选择一个

首先在PlayerStart,可以给它打一个tag

接着在GameMode里面设置,打开蓝图,重写选择玩家出生点函数,对于其中的Player改为寻找特定Player

动画示例

任务:

  • 创建C++ Anim Instance
  • 获取Character Movement、Player、Speed暴露给蓝图

开始还是先创建一个Cpp类,继承自UAnimInstance;然后,我们需要重写几个函数

#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "Player/PlayCharacter.h"
#include "PlayerAnimInstance.generated.h"
UCLASS()
class CPP_STUDY_API UPlayerAnimInstance : public UAnimInstance
{
	GENERATED_BODY()
public:
	// 相当于 Start
	virtual void NativeInitializeAnimation() override;
	// 相当于 Update
	virtual void NativeUpdateAnimation(float DeltaSeconds) override;

	UPROPERTY(BlueprintReadOnly)
	TObjectPtr<APlayCharacter> PlayerCharacter;

	UPROPERTY(BlueprintReadOnly)
	TObjectPtr<UCharacterMovementComponent> PlayerCharacterMovement;

	UPROPERTY(BlueprintReadOnly)
	float Speed;
};

我们需要两个指针,一个PlayerCharacter,一个PlayerCharacterMovement,同时,我们动作判断条件很有可能用到Speed,因此都添加进来

对于 NativeInitializeAnimation 函数和 NativeUpdateAnimation 函数,有点类似Start和Update函数感觉

void UPlayerAnimInstance::NativeInitializeAnimation()
{
// 父类的先用上
Super::NativeInitializeAnimation();
// 获取当前动画实例所属的角色对象,Cast把APown强制转化为APlayCharacter
PlayerCharacter = Cast<APlayCharacter>(TryGetPawnOwner());
if (PlayerCharacter)
{
// 如果角色存在,获取角色的移动组件
PlayerCharacterMovement = PlayerCharacter->GetCharacterMovement();
}
}
void UPlayerAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);
if (PlayerCharacterMovement) { // 检查移动组件是否有效
// 获取角色的运动分量,但只保留水平分量,求的水平方向的速度
Speed = UKismetMathLibrary::VSizeXY(PlayerCharacterMovement->Velocity);
}
}

在蓝图部分,我们创建一个动画蓝图,选择骨骼开始使用,记得继承我们刚才创建的类

接着,新建一个状态机连线,然后状态机创建两个状态,Idle和Run,速度为0就Idle,不为0就Run

记得绑定一下转换条件,当前状态的动作,转化的条件,完成后编译即可

倒计时部分

主要用到两个控件蓝图,在用户界面里面,一个代表屏幕一个代表倒计时的组件

倒计时部分我们完成核心逻辑,这里的数据都是存在蓝图里面的,虽然我感觉很不合适,但是只是对于这个任务而言还是比较合适的。

这是一个Widght下来的类,因此我们可以先做一个画板设计我们的组件,默认就会打开设计器

然后对于内部,我们可以创建一个ResTime的变量,自己创建一个DeTimer函数,这个函数的作用是每调用一个这个ResTime就会-1,下面这是一个计时器委托,我们把、这个函数和出发时间放上去,然后回去设计组件的那个文本块为我们的ResTime

小地图

最终效果展示:

实现先懒得写了,参考 【UE5】【蓝图】UMG-小地图制作分享_哔哩哔哩_bilibili

广播调用机制

【虚幻5基础课堂】3. 一个案例学会使用事件分发器_UE5_UnrealEngine_哔哩哔哩_bilibili

不过这是纯蓝图,如果是Cpp可以参考一下内容,但是注意需要相关网络内容开启后才可以使用

.h
// 声明动态多播委托(BlueprintAssignable 必须用动态委托)
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnStateChangedSignature, int, NewState);

UCLASS()
class UE_PROJ01_API AMyGameStateBase : public AGameStateBase
{
	GENERATED_BODY()
	AMyGameStateBase();
	public:

	// 属性同步时的回调函数
	UFUNCTION()
	void OnRep_State();

	// 事件分发器(供蓝图绑定)
	UPROPERTY(BlueprintAssignable, Category = "Event")
	FOnStateChangedSignature OnStateChanged;
	
	UPROPERTY(ReplicatedUsing = OnRep_State, EditAnywhere, BlueprintReadWrite, Category = UserState, meta = (AllowPrivateAccess = "true"))
	int State; // 0.表示在出生岛 1.表示在飞机上 2.表示跳伞 3.表示开伞后状态 4.表示又落地
}
.cpp
void AMyGameStateBase::OnRep_State()
{
	OnStateChanged.Broadcast(State);
}

问题汇总

编译时出错

解决LiveCoding激活时编译错误的方法-CSDN博客

UE5 Rider C++环境使用,编译无法通过的解决方法 – 哔哩哔哩

我们每次对于C++内的文件需要编译一下,build即可。因此需要在IDE内build,或者引擎内右下角点一下

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇