UE5-虚幻引擎学习笔记

HUD -----抬头显示器

计时器 Timer

目的:设置子弹发射间隔

延迟生成
更新到上一帧数
bPreviousCanSeePlayer = bCanSeePlayer;  

这张图片显示了一段游戏引擎Unreal Engine使用的C++代码。这段代码似乎与敌人的AI有关,敌人会在特定条件下进行攻击。

以下是这段代码的大致解释:

- `AEnemy::Tick(Float DeltaTime)` 函数每帧都会调用,用于更新敌人的状态。
- `Super::Tick(DeltaSeconds: DeltaTime);` 表示先执行父类的Tick函数。
- `bCanSeePlayer = LookComponent->CanSeeTarget();` 判断敌人是否能看到玩家。
- 如果敌人看到玩家的情况发生了变化(即 `if (CanSeePlayer != bPreviousCanSeePlayer)`),则:
  - 如果敌人现在能看到玩家(`if (bCanSeePlayer)`),则启动定时器让敌人开始持续射击(`Fire` 方法)。定时器将以 `FireInterval` 的间隔重复触发,并在第一次延迟 `FireDelay` 时间后开始。
  - 否则,如果敌人看不到玩家(`else`),则清除定时器,停止敌人的射击行为。
- 最后,更新 `bPreviousCanSeePlayer` 变量的值,以便记录上一次敌人能否看到玩家的状态。

总的来说,这段代码描述了一个敌人的行为模式,当敌人能看见玩家时,它会不断地向玩家开火;而当敌人看不见玩家时,则停止射击。这种行为使得游戏更具挑战性,增加了玩家需要躲避敌人的需求。

效果展示:敌人间隔发射子弹杀我

在C++中,protected 是一个访问修饰符,它用于控制类成员(如变量、方法)的访问级别。当你将变量声明为 protected 时,意味着这个变量可以在以下几种情况下被访问:

类内部:类内部的方法可以直接访问其 protected 成员。
派生类:当一个类继承自另一个类时,基类中的 protected 成员可以被派生类访问。
友元函数或类:被声明为当前类的友元的其他函数或类也可以访问 protected 成员。
protected 访问修饰符的主要用途是在面向对象编程中支持继承机制。通过将某些成员设置为 protected,你可以允许子类访问这些成员,从而实现行为的扩展或覆盖,同时又保护这些成员不被外部代码直接修改。

例如,假设你有一个 Animal 类,其中有一些应该被所有动物共享的行为,但是你不希望外部代码能够直接修改这些行为,这时你可以把它们设为 protected。然后,任何继承自 Animal 的子类,比如 Dog 或 Cat,都可以访问这些 protected 成员来定制自己的行为。

这里是一个简单的例子:

class Animal {
protected:
    int age;
public:
    Animal() : age(0) {}
    void grow() { ++age; }
};

class Dog : public Animal {
public:
    Dog() {}
    void bark() {
        // 可以访问 protected 成员
        grow();
        std::cout << "Age of dog: " << age << std::endl;
    }
};

1. 碰撞

碰撞反应

在Unreal Engine 5(UE5)中,碰撞反应主要通过设置物体的碰撞预设(Collision Preset)以及碰撞响应来控制。UE5提供了多种方式来管理和调整碰撞行为,无论是通过蓝图视觉脚本还是C++代码。

蓝图中的碰撞反应


在蓝图中,你可以通过以下步骤来设置碰撞反应:

选择物体:在编辑器中选择你想要设置碰撞反应的物体。
碰撞预设:在物体的属性面板中找到“碰撞”部分,可以选择预设的碰撞类型,如“Block”、“Overlap”或“Ignore”。这决定了物体与其他物体接触时的行为。
碰撞响应:对于更细致的控制,可以单独设定X轴、Y轴、Z轴的碰撞响应。
事件节点:利用蓝图系统,可以创建事件节点来监听碰撞或重叠事件,并根据这些事件执行特定的动作,比如播放动画、改变物体状态等。


C++中的碰撞反应


如果你更倾向于使用C++进行开发,可以按照以下步骤设置碰撞反应:

添加组件:在物体的类中添加一个碰撞组件,例如UBoxComponent。
初始化组件:在构造函数或成员初始化列表中初始化这个组件,并将其附加到物体的根组件。
碰撞回调:覆写碰撞组件提供的回调函数,例如OnComponentBeginOverlap和OnComponentEndOverlap,以便在发生碰撞或物体离开碰撞区域时执行代码。
碰撞响应:可以通过设置碰撞组件的属性来改变碰撞响应,例如使用SetCollisionResponseToChannel来指定对于不同类型的物体,碰撞组件应该阻挡、重叠还是忽略它们。
实现细节
自定义碰撞类型:如果你需要更复杂的碰撞类型,可以自定义碰撞类型并设置相应的响应规则。
运行时切换碰撞视图:在UE5中,可以通过按下键盘上的特定按键(如“P”键)来在运行时切换碰撞视图,这样可以帮助调试碰撞行为。
延迟和触发:在处理碰撞事件时,可能会遇到延迟问题,比如多个物体快速连续通过同一碰撞区域时的效果同步。可以通过调整事件处理逻辑或使用延迟执行来解决这些问题。

在Unreal Engine 5 (UE5) 中使用C++来处理碰撞事件通常涉及到几个关键步骤:定义一个包含碰撞组件的Actor类,然后在这个类中覆写特定的方法来处理碰撞开始和结束的事件。下面是一个简单的C++代码示例,展示如何在一个Actor类中设置碰撞组件并处理碰撞事件。

首先,你需要创建一个新的Actor类,该类继承自AActor。然后,在这个类中添加一个碰撞组件,通常是UCollisionComponent的一个子类,例如UBoxComponent。接下来,你需要覆写碰撞组件提供的事件处理函数,例如OnComponentBeginOverlap和OnComponentEndOverlap。

假设你已经创建了一个名为ACollisionActor的Actor类,下面是这个类的一个简化版本的实现:

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/BoxComponent.h"

// 定义一个Actor类,用于处理碰撞事件
class ACollisionActor : public AActor
{
    GENERATED_BODY()

public:
    // 构造函数
    ACollisionActor();

    // 指定Actor的碰撞组件
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
    UBoxComponent* CollisionBox;

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

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

    // 当其他Actor进入碰撞区域时调用
    void OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

    // 当其他Actor离开碰撞区域时调用
    void OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
};

ACollisionActor::ACollisionActor()
{
    // 创建一个BoxComponent作为碰撞检测器
    CollisionBox = CreateDefaultSubobject<UBoxComponent>(TEXT("CollisionBox"));
    RootComponent = CollisionBox;

    // 设置碰撞响应为阻挡所有物理对象
    CollisionBox->SetCollisionObjectType(ECC_WorldDynamic);
    CollisionBox->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
    CollisionBox->SetCollisionResponseToAllChannels(ECR_Block);

    // 设置碰撞事件
    CollisionBox->OnComponentBeginOverlap.AddDynamic(this, &ACollisionActor::OnOverlapBegin);
    CollisionBox->OnComponentEndOverlap.AddDynamic(this, &ACollisionActor::OnOverlapEnd);
}

void ACollisionActor::BeginPlay()
{
    Super::BeginPlay();
    // 初始化Actor
}

void ACollisionActor::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
    // 每帧更新
}

void ACollisionActor::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    // 打印消息表示有其他Actor进入了碰撞区域
    UE_LOG(LogTemp, Warning, TEXT("Overlap Begin!"));
}

void ACollisionActor::OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
    // 打印消息表示其他Actor离开了碰撞区域
    UE_LOG(LogTemp, Warning, TEXT("Overlap End!"));
}

它有一个UBoxComponent作为其碰撞检测器。当另一个Actor进入或离开这个碰撞盒时,会分别调用OnOverlapBegin和OnOverlapEnd函数,并打印一条消息。

模型精度
对于Fortnite短片,Epic决定使用比游戏更高精度的模型,
不仅仅是角色还有场景环境物件。更高精度的模型增加动画
表现,在加面的同时也带来一些其他的考量
工具可以自动加面,但新增加的多边形必须手动调整使
其更好地表现结构和动画
加面的同时也需要同步增加贴图分辨率
增加精度的同时会增加回放动画时内存的开销。必须非
常谨慎的增加精度,以免由于超出内存无法回放
角色绑定需要同时调整甚至重做来匹配更高精度的模型
及增加动画真实性
高精度角色在短片中实时的变形动画和游戏里的非常不
同,这意味着低模上的绑定和关键帧也需要被调整
因为对于角色头部新的绑定和动画方式的需要,头部模型网
格线被布置成高精度通用拓扑结构。尽管每个角色头部张的
不同,但网格拓扑结构是类似的。这种不同角色类似网格拓
扑结构方式缩短了绑定和动画的时间,因为类似绑定可以被
通用到所有角色头部上
每个角色头部模型大约有 185,000 个三角面

2. 与3DUI面板互动实现半自动和全自动射击

实现一个半自动和全自动的枪击射击

1.新建actor

2.添加组件 -widget(展示UI)

2.在widget Class选择我们之前创建的半自动面板

2.3 改UI大小-1280 x 720

2.4 设置 3DUI设置微微曲的3d效果

Cylinder-- 圆柱体

缩放改变ui大小

点击红色射线,debug打勾-运行后看到红色射线

2.5  玩家枪的蓝图

枪激光旋转90度

模拟鼠标左键与3DUI的互动

如果没有和3DUI互动就发射子弹,假的代表没有3DUI进行互动,就指向发射子弹

延迟0.1秒触发,全自动射击事件

在设计一个射击游戏中的全自动和半自动开枪逻辑时,我们通常需要考虑游戏引擎、输入管理、武器状态以及射击频率等要素。下面是一个简化的C++代码示例,展示了如何实现这两种射击模式。这里假设有一个`Weapon`类,其中包含基本的射击逻辑。```cpp

```

在这个示例中,`Weapon`类包含了射击的逻辑,包括是否为全自动、最大弹药量、射击间隔、当前弹药量和上一次射击的时间。`HandleInput`方法用于处理输入,根据是否是全自动模式来决定是否射击。在主循环中,我们通过模拟时间的更新和输入状态来调用射击逻辑。

请注意,这个代码是一个高度简化的示例,并不包括实际游戏开发中可能涉及的复杂性,如网络同步、动画、声音效果或碰撞检测等。此外,在实际游戏开发中,通常会使用游戏引擎提供的功能来处理这些逻辑,而不是直接编写底层代码。

最终实现效果

全自动射击

63.1----c++代码实现全自动半自动射击

#include <iostream>
#include <chrono>
#include <thread>

// 模拟游戏中的时间更新函数
void Update(float deltaTime) {
    // 游戏主循环中的时间更新逻辑
}

class Weapon {
public:
    Weapon(bool isAutomatic, int maxAmmo, float fireRate)
        : m_isAutomatic(isAutomatic), m_maxAmmo(maxAmmo), m_fireRate(fireRate),
          m_currentAmmo(maxAmmo), m_lastShotTime(0.0f) {}

    void Reload() {
        m_currentAmmo = m_maxAmmo;
        std::cout << "Reloading... " << m_maxAmmo << " rounds." << std::endl;
    }

    void Shoot(float currentTime) {
        if (m_currentAmmo > 0 && (currentTime - m_lastShotTime >= m_fireRate)) {
            m_lastShotTime = currentTime;
            m_currentAmmo--;
            std::cout << "Bang! Ammo left: " << m_currentAmmo << std::endl;
        } else {
            if (m_currentAmmo == 0) {
                std::cout << "Out of ammo, reloading..." << std::endl;
                Reload();
            }
        }
    }

    void HandleInput(float currentTime, bool isFiring) {
        if (isFiring) {
            if (m_isAutomatic) {
                Shoot(currentTime);
            } else {
                // 半自动模式下,每次按下射击键只发射一发子弹
                if (currentTime - m_lastInputTime >= m_fireRate) {
                    m_lastInputTime = currentTime;
                    Shoot(currentTime);
                }
            }
        }
    }

private:
    bool m_isAutomatic;       // 是否为全自动模式
    int m_maxAmmo;            // 最大弹药量
    float m_fireRate;         // 射击间隔
    int m_currentAmmo;        // 当前弹药量
    float m_lastShotTime;     // 上次射击的时间
    float m_lastInputTime;    // 上次输入的时间(仅半自动模式)
};

int main() {
    Weapon rifle(true, 30, 0.1f);   // 全自动步枪
    Weapon pistol(false, 7, 0.5f);  // 半自动手枪

    float currentTime = 0.0f;
    float deltaTime = 0.016f; // 假设帧率为60fps,每帧时间为1/60秒

    bool isFiring = true;

    while (true) { // 主游戏循环
        Update(deltaTime);
        currentTime += deltaTime;

        rifle.HandleInput(currentTime, isFiring);
        pistol.HandleInput(currentTime, isFiring);

        std::this_thread::sleep_for(std::chrono::milliseconds(16)); // 模拟帧率限制
    }

    return 0;
}

4.渲染  (头发渲染)

图片渲染

4k分辨率输出的图片

头发渲染

4.1  渲染管线

渲染场景的优化是一个涉及多个方面的过程,旨在提高渲染效率和性能,同时保持高质量的视觉效果。下面是一些常见的优化策略:

1. 减少绘制调用

  • 批处理:将相同的材质或纹理的对象合并成一个批次进行绘制,减少状态切换。
  • 实例化:对于多个相同或相似的模型,使用实例化技术来减少重复的数据。

2. 降低复杂度

  • 模型简化:使用LOD(Level of Detail)技术,在远距离时使用较简化的模型。
  • 剔除
    • 视锥体剔除:剔除不在视锥体内的物体。
    • 背面对剔除:剔除面向摄像机背面的多边形。
    • 遮挡剔除:利用深度缓冲或其他技术剔除被遮挡的物体。

3. 使用高效的纹理

  • 压缩纹理:使用压缩格式如PVRTC、ETC1/2、ASTC等来减少纹理占用的内存。
  • Mipmap:为纹理生成多个分辨率版本,在不同的距离使用不同分辨率的纹理。

4. 光照优化

  • 烘焙光照:对于静态场景,可以预先计算光照并将结果存储在贴图中。
  • 阴影映射:使用阴影贴图而不是光线追踪等高成本的技术。
  • 延迟渲染:延迟光照计算直到像素最终着色阶段。

5. 合理使用着色器

  • 简化着色器:避免复杂的着色器逻辑,特别是那些包含循环或条件分支的着色器。
  • 使用预编译着色器:预编译可以减少加载时间和提高性能。
  • 避免不必要的采样:减少纹理采样的次数,特别是在片段着色器中。

6. 缓存和异步处理

  • 缓存计算结果:例如缓存光照计算结果或中间帧数据。
  • 异步加载资源:在后台加载和处理资源,避免阻塞主线程。

7. 利用现代图形API特性

  • 管线并行处理:使用现代图形API(如Vulkan、Metal)中的特性来并行处理渲染命令。
  • 计算着色器:使用计算着色器执行CPU密集型任务,如物理模拟、后处理效果等。

8. 分析和监控

  • 性能分析工具:使用工具(如RenderDoc、NVIDIA Nsight Graphics)来分析渲染路径,找出瓶颈。
  • 热区检测:确定哪些部分占用了最多的GPU时间。

实施步骤

  1. 评估:使用性能分析工具来识别性能瓶颈。
  2. 优先级排序:根据影响程度对优化点进行排序。
  3. 逐步实施:从最显著的优化开始,逐步实施。
  4. 测试:每次更改后都要重新测试性能。

5.序列化技术

Unreal Engine 4(UE4)的序列化机制是其核心架构的关键组成部分之一,它负责将游戏状态和对象数据转换为可以存储或在网络上传输的形式,以及从这种形式还原回原始数据。UE4的序列化系统非常强大且高度定制化,能够适应复杂的游戏开发需求。以下是一些关于UE4序列化技术的核心要点:

序列化流程

在UE4中,序列化通常涉及以下几个步骤:

  1. 对象引用处理:UE4使用“弱引用”和“强引用”的概念来处理对象间的链接。在序列化过程中,对象引用被转换为索引或GUID,以确保跨平台和网络传输的一致性。
  2. 属性遍历:序列化系统会遍历对象的所有属性,包括基础类型(如整数、浮点数)、复杂类型(如数组、结构体)、以及对象引用。
  3. 定制序列化:开发者可以为特定的类或属性提供自定义的序列化逻辑,通过实现FObject& operator<<(FObject& Ar, UObject* Object)等接口,控制序列化和反序列化的具体行为。

核心组件

UE4的序列化机制依赖于几个关键的组件和概念:

  • 归档器(Archiver)FArchive是序列化的核心接口,它定义了读写数据的基本操作。UE4提供了多种归档器类型,如FMemoryWriterFMemoryReader,用于不同的序列化场景。
  • 对象版本控制:UE4支持对象版本,这允许引擎在不同的游戏版本之间兼容序列化数据,即使数据结构有所改变。
  • 网络序列化:对于多人游戏,UE4提供了专门的网络序列化机制,它会根据玩家视角和网络条件智能地选择哪些数据需要发送,以优化带宽使用。

自定义序列化

UE4允许开发者通过几种方式来自定义序列化行为:

  • Pre-/Post-Load/Save:对象可以实现PreLoadPostLoad等回调函数,在序列化前后执行自定义逻辑。
  • 自定义序列化函数:开发者可以为特定的类实现自定义的序列化函数,以便控制如何读写特定的数据结构。

性能优化

为了提高序列化效率,UE4实施了一系列优化措施:

  • Delta压缩:只发送自上次序列化以来发生改变的数据,这对于网络传输特别重要。
  • 缓存和预读:利用缓存机制和预测算法来减少序列化和反序列化的时间。

6. 反射技术

Unreal Engine 4(UE4)的反射系统是其框架的重要组成部分,它允许在运行时检查和操作程序中的类和对象。反射在UE4中被广泛应用于编辑器功能、蓝图可视化脚本、热重载、序列化、网络复制等方面。以下是UE4反射系统的一些关键特点和用途:

关键特点

  1. 动态类型信息:UE4的反射系统提供了对类和对象的类型信息的访问,包括类名、属性、函数、枚举等。

  2. 对象模型:UE4使用基于对象的模型,其中所有的非原始数据类型都是从UObject派生的。这使得所有类都具有相同的元数据结构,便于反射系统处理。

  3. 属性访问:反射系统允许运行时访问和修改对象的属性,包括基本数据类型、数组、结构体、枚举和对象引用。

  4. 函数调用:可以使用反射来动态调用对象的方法,这对于实现动态行为和扩展功能非常有用。

  5. 蓝图可视化脚本:UE4的蓝图系统依赖于反射,允许用户在不编写代码的情况下构建游戏逻辑。

使用场景

  1. 编辑器功能:UE4编辑器利用反射系统来生成UI界面,显示对象属性,以及提供编辑、调试和测试工具。

  2. 热重载:UE4的热重载特性允许在不重启游戏的情况下加载新的类和资源,这在开发过程中非常有用。

  3. 序列化和网络复制:UE4的序列化机制使用反射来确定哪些数据需要保存和传输,以及如何处理这些数据。

  4. 动态绑定和事件系统:UE4的事件系统允许在运行时动态连接和断开事件处理器,这依赖于反射来查找和调用正确的函数。

  5. 插件和扩展:UE4的插件系统利用反射来发现并注册新的类和资源,允许开发者轻松地扩展引擎的功能。

实现细节

UE4的反射系统在底层使用了大量的元数据,这些元数据在编译时由编译器生成,并存储在   .uasset文件中。这些元数据包括类的继承关系、属性和函数的信息,以及其他与对象相关的数据。

总之,UE4的反射系统是一个强大而灵活的工具,它贯穿于引擎的各个方面,使得UE4能够提供丰富多样的功能,同时也为开发者提供了极高的灵活性和生产力。

 7.UE4-GC(垃圾回收机制)

1.引用计数GC和追踪式GC

引用计数式GC通过额外的计数来实时计算对单个对象的引用次数,当引用次数为0时回收对象。引用计数的GC是实时的。像微软COM对象的加减引用值以及C++中的智能指针都是通过引用计数来实现GC的。

UE4中的垃圾回收机制是周期性的,并且主要针对引用计数无法自动处理的情况,例如循环引用。引擎会在特定时刻运行垃圾回收器来查找和销毁不再被任何活动对象引用的对象。这有助于防止内存泄漏,确保游戏性能和稳定性。

然而,与一些其他语言(如Java或C#)中的垃圾回收机制不同,UE4的垃圾回收更加偏向于手动触发和控制。这意味着开发者需要理解何时以及如何调用垃圾回收,以优化游戏的性能和资源管理。

在UE4中,可以通过以下几种方式触发垃圾回收:
1. Tick事件:UE4的每个Actor都有一个Tick函数,可以在其中调用`GarbageCollect()`函数。
2. **关卡加载/卸载**:当加载新的关卡或者卸载旧的关卡时,UE4会自动进行一次垃圾回收。
3. **编辑器命令**:在编辑器中可以使用命令行GarbageCollection来触发垃圾回收。
4. **代码中直接调用**:在C++代码中,可以直接调用`FPlatformMisc::LowLevelMemStats();` 或者 `FPlatformMisc::CollectGarbage();` 来收集内存统计信息或执行垃圾回收。

对于游戏开发人员来说,理解并合理使用垃圾回收机制对于创建高性能、资源高效的游戏至关重要。不过,在实际应用中,过度频繁的垃圾回收可能会对游戏性能产生负面影响,因此需要平衡使用。

1.2 对象防止被GC

跟随生命周期一起消亡

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值