Unreal GamePlay 框架

Unreal GamePlay 框架

image-20240814162359593 image-20240814162506880

一个游戏由一个GameMode和一个GameState构成。加入游戏的人类玩家与PlayerController关联。 这些PlayerController允许玩家持有游戏中的Pawn,以便在关卡中有物理表示。 PlayerController还为玩家提供了输入功能按钮、平视显示器(简称HUD)和用于处理摄像机视图的PlayerCameraManager。

虚幻引擎中的Gameplay框架提供了多个类和组件,可用作项目的构建块。

  • Actor是可以在关卡中放置或生成的对象的基类。Actor可以包含一系列Actor组件,这些组件用于控制Actor的移动方式和渲染方式。Actor支持在游戏期间在网络上复制属性函数调用。
  • 摄像机表示玩家的视角,例如世界在玩家眼里的样子。PlayerController指定摄像机类并实例化摄像机Actor,用于计算玩家查看世界的位置和方向。
  • Pawn类是可以由玩家或AI控制的所有Actor的基类。Pawn是世界中的玩家或AI实体的物理呈现。角色是能够四处行走的一种特殊类型的Pawn。默认情况下,控制器和Pawn之间存在一一对应关系,即每个控制器在任何给定时间仅控制一个Pawn。
  • 控制器是可持有Pawn或从Pawn派生的类(例如角色)以控制其操作的非物理Actor。玩家控制器由人类玩家用于控制Pawn,而AI控制器为它们控制的Pawn实现人工智能。控制器使用Possess函数控制Pawn,并使用UnPossess函数放弃对Pawn的控制。
  • Gameplay定时器创建对特定函数指针的异步回调,以在延迟后或一段时间内触发要执行的事件。
  • 游戏框架的基类是GameModeAGameModeBase Actor在为Gameplay初始化关卡时实例化。GameMode设置游戏的规则,它仅在服务器上实例化,从不存在于客户端。
  • 游戏功能和模块化Gameplay插件可帮助开发人员为其项目创建独立功能。这些插件能使项目的代码库保持整洁、易于阅读,并避免不相关的功能之间出现意外的交互或依赖性。
  • 用户界面(UI)和平视显示器(HUD)用于向玩家提供有关游戏的信息,并且在一些情况下允许玩家与游戏交互。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class UInputComponent;
class USkeletalMeshComponent;
class UCameraComponent;
class UInputAction;
class UInputMappingContext;
struct FInputActionValue;

DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);

UCLASS(config=Game)
class ATestShooterCharacter : public ACharacter
{
GENERATED_BODY()

/** Pawn mesh: 1st person view (arms; seen only by self) */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Mesh, meta = (AllowPrivateAccess = "true"))
USkeletalMeshComponent* Mesh1P; //

/** First person camera */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
UCameraComponent* FirstPersonCameraComponent;

/** Jump Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Input, meta=(AllowPrivateAccess = "true"))
UInputAction* JumpAction;

/** Move Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Input, meta=(AllowPrivateAccess = "true"))
UInputAction* MoveAction;

public:
ATestShooterCharacter();

protected:
virtual void BeginPlay();

public:

/** Look Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
class UInputAction* LookAction;

protected:
/** Called for movement input */
void Move(const FInputActionValue& Value);

/** Called for looking input */
void Look(const FInputActionValue& Value);

protected:
// APawn interface
virtual void SetupPlayerInputComponent(UInputComponent* InputComponent) override;
// End of APawn interface

public:
/** Returns Mesh1P subobject **/
USkeletalMeshComponent* GetMesh1P() const { return Mesh1P; }
/** Returns FirstPersonCameraComponent subobject **/
UCameraComponent* GetFirstPersonCameraComponent() const { return FirstPersonCameraComponent; }

};

C++ 代理(Delegate)和 Unreal Delegate

C++ 代理模式

img

代理模式是一种结构型模式

允许在运行时动态地绑定和调用一个或多个函数,这对于事件驱动的编程非常有用。在 Unreal Engine 中,代理主要有三种类型:单播代理多播代理动态代理。它们各自有不同的应用场景和特性。

优点:
可以实现对原对象的访问控制。代理对象可以在访问原对象之前执行一些额外操作,例如检查权限、记录日志等。
可以提供额外的功能。代理对象可以在不修改原对象的情况下,为原对象提供额外的功能。
可以减少客户端代码的复杂性。客户端代码只需要与代理对象进行交互,而不需要直接与原对象进行交互。
缺点:
可能会增加系统的复杂性。需要为每个原对象创建一个对应的代理对象。
可能会降低系统的性能。每次访问原对象时,都需要经过代理对象,这可能会增加访问延迟。

满足的设计原理:
开闭原则(Open-Closed Principle, OCP):代理模式允许您在不修改原对象的情况下,为原对象提供额外的功能。这样,您可以在不修改现有代码的情况下,扩展系统的功能。
单一职责原则(Single Responsibility Principle, SRP):代理模式将访问控制和其他额外功能从原对象中分离出来,封装到代理对象中。这样,原对象只需要负责其核心职责,而不需要关心访问控制和其他额外功能。
常用实例:
当需要实现对原对象的访问控制时,可以使用代理模式。代理对象可以在访问原对象之前执行一些额外操作,例如检查权限、记录日志等。
当需要为原对象提供额外的功能时,可以使用代理模式。代理对象可以在不修改原对象的情况下,为原对象提供额外的功能。
当需要减少客户端代码的复杂性时,可以使用代理模式。客户端代码只需要与代理对象进行交互,而不需要直接与原对象进行交互。
例如,在网络编程中,可以使用代理模式来实现远程代理。远程代理允许客户端代码通过网络访问位于远程服务器上的对象。客户端代码只需要与本地的代理对象进行交互,而不需要直接与远程服务器进行通信。此外,在图形界面开发中,可以使用代理模式来实现虚拟代理。
虚拟代理允许您延迟创建复杂的图形对象,直到真正需要时才创建。这可以减少内存占用和初始化时间。

UE 代理模式

真实实现对象以指针形式指给了用户,用户这边只有一个任何操作都给真实实现对象的代理对象 编译也只会涉及这个代理对象 真实的实现以预编译的lib方式提供,最后直接链接

在 Unreal Engine (UE) 中,代理(Delegate)是一种强大的事件处理机制,类似于 C++ 中的函数指针或 C# 中的委托(Delegate)。代理允许你在运行时动态地绑定和调用一个或多个函数,这对于事件驱动的编程非常有用。在 Unreal Engine 中,代理主要有三种类型:单播代理多播代理动态代理。它们各自有不同的应用场景和特性。

1. 单播代理(Single-cast Delegate)

单播代理是最简单的代理类型,它只能绑定一个目标函数。当该代理被调用时,仅调用这个绑定的函数。单播代理通常用于一个事件只需要一个处理器的情况。

用法:

1
2
3
4
5
6
7
8
9
10
11
12
cppCopy codeDECLARE_DELEGATE(FMyDelegate);

class MyClass
{
public:
FMyDelegate MyDelegate;

void MyFunction()
{
MyDelegate.ExecuteIfBound();
}
};

关键点:

  • 只能绑定一个函数。
  • 如果代理没有绑定函数,调用时会导致崩溃,因此通常使用 ExecuteIfBound() 检查是否已绑定。

2. 多播代理(Multi-cast Delegate)

多播代理允许绑定多个目标函数。当代理被调用时,所有绑定的函数都会按顺序依次被调用。多播代理非常适合需要通知多个监听者的事件,例如广播事件或通知多个系统更新。

用法:

1
2
3
4
5
6
7
8
9
10
11
12
cppCopy codeDECLARE_MULTICAST_DELEGATE(FMyMulticastDelegate);

class MyClass
{
public:
FMyMulticastDelegate MyMulticastDelegate;

void MyFunction()
{
MyMulticastDelegate.Broadcast();
}
};

关键点:

  • 可以绑定多个函数。
  • 调用时,所有绑定的函数都会依次执行。
  • Broadcast() 是用于调用多播代理的函数。

3. 动态代理(Dynamic Delegate)

动态代理是可以在 Unreal Engine 的反射系统(UObject系统)中使用的代理。它们可以绑定到 UObject 的方法,并且可以在蓝图中暴露和使用。动态代理在蓝图和 C++ 代码之间的交互中非常有用。

用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cppCopy codeDECLARE_DYNAMIC_MULTICAST_DELEGATE(FMyDynamicMulticastDelegate);

class MyClass : public UObject
{
GENERATED_BODY()

public:
UPROPERTY(BlueprintAssignable)
FMyDynamicMulticastDelegate MyDynamicDelegate;

void MyFunction()
{
MyDynamicDelegate.Broadcast();
}
};

关键点:

  • 支持蓝图绑定,因此通常用于在蓝图中实现的事件响应。
  • 只能在继承自 UObject 的类中使用。
  • BlueprintAssignable 允许蓝图绑定这个代理。
  • DECLARE_DYNAMIC_MULTICAST_DELEGATE 支持多播代理,如果需要单播,可以使用 DECLARE_DYNAMIC_DELEGATE

区别总结

  1. 绑定数量
    • 单播代理:只能绑定一个函数。
    • 多播代理:可以绑定多个函数,所有绑定的函数都会依次执行。
    • 动态代理:也支持单播和多播,且可以在蓝图中使用,适合与蓝图交互。
  2. 蓝图支持
    • 单播代理和多播代理:通常用于纯 C++,不支持直接暴露给蓝图。
    • 动态代理:支持与蓝图交互,可以在蓝图中绑定、解绑和调用。
  3. 使用场景
    • 单播代理:适合一个事件只需要一个处理函数的情况。
    • 多播代理:适合广播事件,多个监听者都需要响应的情况。
    • 动态代理:适合需要在蓝图中响应事件的情况,常用于需要跨越 C++ 和蓝图的项目。

代理是 Unreal Engine 中处理事件的重要机制,不同类型的代理适合不同的应用场景。单播代理适合简单的事件处理,多播代理适合广播通知,动态代理则是桥接 C++ 和蓝图的工具。理解并合理选择代理类型可以显著提升代码的灵活性和可维护性。