上一次修改时间:2019-04-04 22:46:21

UE4编程入门

  1. UE4游戏架构

    图片.png

    1)UE4world里,所有的物体大部分都要继承自Actor,world容器同时存在于服务端和客户端;

    2)GameInstance:游戏实例,架构上凌驾于最顶端,实例里的数据不会随着关卡的切换而消失;

    3)GameMode:联网状态下,只存在于服务端,可以充当一个服务端,客户端不能访问;

    4)GameSession:联网状态下的游戏房间;

    图片.png

    5)GameState:数据的全局管理,服务端和客户端都存在一份,用来保持数据的同步,也可将其中的部分数据设置为不同步;

    图片.png

    6)PlayerController:非常重要的一个类,关联了客户端和服务端,通过该类,客户端可以向服务端发送请求;

    图片.png

    7)PlayerState:角色数据,该类需要通过PlayerController来访问;

    8)Pawn:角色的外在表现类,同PlayerState,也需要通过PlayerController来访问,服务端和客户端都存在一份,同时保持同步;

    图片.png

    9)HUD:只存在于客户端;

    图片.png

    10)GameViewportClient:游戏视口,在编辑器中开启多个游戏视口时,会有多个GameViewportClient实例;

    图片.png

    11)GameUserSettings:游戏配置,该类里有save和load方法,来方便存储和读取配置信息;

    图片.png

    12)WorldSettings:当前world的属性;

  2. Actor的生命周期

    1)新建一个C++的Actor类,在Actor上单击右键,创建一个基于该Actor类的蓝图类并放入场景中;

    注:新创建的C++Actor类,不能直接放入场景中

    图片.png

    图片.png

    图片.png

    注:1)Actor可以看成是构成level的最基本单位,存在于world里的实例大部都是Actor,除了Actor,还有UObject;

          2)gameMode,gameState,gameSession,playController,pawn,HUD等都是继承自AActor;

          3)Actor存在于wrold上,将Actor放置在world中时,需要通过SpawnActor实现

          4)Actor中集成了主要类的一些共性,如网络同步,按键响应,角色缩放等

          5)辅助功能,如读取配置,保存等是直接继承自UObject

  3. UE4初始化-Actor

    图片.png

    1)上面同一个文件的同一个方法中,开始创建gameInstance

    图片.png

    按F11,可以进入CreatePIEGameInstance函数里面

    图片.png

    如果有自定义的gameInstance,就生成自定义的,没有就生成默认的,生成后添加到根目录,以防止被GC垃圾回收

    图片.png

    有了gameInstance后,要用gameInstance来初始化编辑器

    图片.png

    初始化编辑器里,会先生成一个world

    图片.png

    判断是客户端还是服务端,如果是服务端,则复制一个wrold,复制时,会将Actor全部复制到新的world里,该world为编辑器world,是没有图形界面的

    图片.png

    复制好了后,会重新设置world,将新的world放入wroldContext中,再进行初始化

    图片.png

    2)创建好gameInstance后,同一个文件里同一个方法下面开始执行gameSinstance

    图片.png

    2-1)startPlayInEditorGameInstance里,先获取一个引擎的实例,并判断该实例是否为客户端,不是的分支为服务端

    图片.png

    服务端的分支里,进行gameMode的初始化

    图片.png

    初始化gameMode时,会用URL来创建gameMode

    图片.png

    如果有蓝图的gameMode,则载入蓝图的gameMode,载入后,如果为空,则生成一个全局的

    图片.png

    图片.png

    图片.png

    2-2)服务端分支里,创建好gameMode后,创建AIS,初始化Actor,并通过URL来判断是哪个world

    图片.png

    Actor初始化

    图片.png

    如果Actor没有被初始化,则开始Actor的初始化

    图片.png

    遍历levels里的所有level,并且初始化每个level里的Actor

    图片.png

    正式开始初始化Actor

    图片.png

    初始化Actor之前,先判断本Actor是否已经初始化,没有的话,先预初始化Actor的组件

    图片.png

    预初始化前,会先调用父类的函数

    图片.png

    父类的函数里,会先判断是否支持按键响应,默认是不支持的,只有playController支持

    图片.png

    注:Actor的预初始化PreInitializeComponents的主要作用是判断,本Actor是否会输出按键响应,默认是不能的,只有playController是可以输出按键响应的

    预初始化后,会开始正式的Actor初始化,实始化结束后,在PostInitializeComponents判断本组件是否需要销毁,不需要时,更新该组件

    图片.png

    初始化组件时,先获取组件,且如果本组件没注册,则不执行初始化

    图片.png

    判断本组件是否需要被销毁

    图片.png

    组件更新时,还需判断本组件是否支持复制

    图片.png

    2-3)服务端分支里,组件初始化完成后,会初始化world

    图片.png

    2-4)beginPlay的初始化,PlayWorld就是一个UWorld的变量

    图片.png

    图片.png

    BeginPlay里会先获取GameMode,再用GameMode来调用StartPlay

    图片.png

    2-4-1)GameMode下的StartPlay

    图片.png

    图片.png

    图片.png

    图片.png

    开始调用Actor里面的BeginPlay

    图片.png

    BeginPlay里,InitialLifeSpan表示Actor的存在时间,0表示不一直存在,1表示存在1秒;

    RegisterAllActorTickFunctions开始注册tick;

    如果本Actor里有多个组件时,则会循环所有组件,如果本组件注册了,则注册组件的tick函数,且执行组件里的BeginPlay,在执行组件里的BeginPlay时,还会调用组件蓝图里的BeginPlay;

    ReceiveBeginPlay()表示调用蓝图里的BeginPlay,由此可见,C++文件里的BeginPlay是先于蓝图里的BeginPlay执行的

    图片.png

    图片.png

    设置InitialLifeSpan的存在时间的底层原理,也是通过计时器来完成的

    图片.png

  4. 断言

    1)check():check里的条件,如果为null或者0或者false,就会触发中断,如check((ne = PrintC()) != false)、check(GameBase);

    2)verifyf():类似check,条件为null或者0或者为false时,就会触发中断,verifyf除了条件外,还可以输出信息,如verifyf(false , TEXT("pikaqiu %s") , *this->GetName());

    3)checkf():类似verifyf,如checkf(false , TEXT("pikaqiu %s") , *this->GetName());

    4)checkNoEntry():函数中断,写在函数体里,如果该函数体执行了,就会触发中断;

    5)checkNoReentry():函数中断,写在函数体里,如果该函数体执行了两次,就会触发中断;

    6)checkNoRecursion():函数中断,写在函数体里,如果该函数体有递归,则会触发中断;

    7)ensure():类似check,但ensure触发的是断点,触发点点击继续,程序可以继续执行,check触发程序会直接崩溃,不能继续执行;

    8)ensureMsgf():类似ensure,多了一个可以附加的额外信息,类似check和checkf;

  5. 智能指针-共享指针

    UE4里的UObject是支持GC垃圾回归的,但如果是非继承自UObject的类,就需要用智能指针来管理,以防止出现野指针;

    共享指针和UObject都支持GC垃圾回归,但两者不兼容,继承自UObject的类不能使用共享指针

    1)共享指针,共享指针必须使用游戏线程,且共享指针可以为null;共享指针不支持复制,但可利用引用计数,当引用计数为0时,GC就会进行回归,以防止出现野指针;使用共享指针时,不能使用TArray,因为TArray会将类或指针进行复制

    图片.png

    图片.png

    图片.png


  6. 智能指针-共享引用

    2)共享引用:和共享指针不同,共享引用没有IsValid方法,共享引用只要存在,就一定是有效的;共享引用必须在定义的时候,使用有效的对象进行初始化;共享指针转换成共享引用是不安全的,转换的过程中有一个断言,如果对角为空,触发后程序会崩溃;共享引用不能设置为null;

    共享引用示例代码:

  7. // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "PikaTestActor.generated.h"
    
    class PikaBase 
    {
    
    };
    
    class Pikaqiu : public PikaBase
    {
    public:
        void PikaPinrt() {};
    };
    
    //继承自TSharedFromThis<PikaNoObj>后,获取到类实例后,可以直接转换成共享指针
    class PikaNoObj :public TSharedFromThis<PikaNoObj>
    {
    public:
        int32 PikaInt;
        float PikaFloat;
    
    //单例模式
        static TSharedRef<PikaNoObj> Get()
        {
            if (PointPikaNoObj.IsValid() || PointPikaNoObj.Get())
            {
                //获取本实例
                PointPikaNoObj = MakeShareable(new PikaNoObj());
            }
            //返回共享指针
            return PointPikaNoObj->AsShared();
        }
    private:
        static TSharedPtr<PikaNoObj> PointPikaNoObj;
    };
    
    class PikaSettings :public TSharedFromThis<PikaSettings>
    {
    public:
        void PikaPrint() 
        {
        
        }
    };
    
    
    UCLASS()
    class PIKAFIRSTDEBUG_API APikaTestActor : public AActor
    {
        GENERATED_BODY()
        
    public:    
        // Sets default values for this actor's properties
        APikaTestActor();
    
    protected:
        // Called when the game starts or when spawned
        virtual void BeginPlay() override;
        //共享指针
        void PikaTestFun();
        //共享引用
        void PikaShareRef();
        //共享指针和共享引用之间的转换
        void PikaShareTurn();
        //弱指针
        void PikaTweakPtr();
    public:    
        // Called every frame
        virtual void Tick(float DeltaTime) override;
    
        //共享指针
        TSharedPtr<PikaNoObj> PikaNoObjInstance;
        //弱指针
        TWeakPtr<PikaNoObj> PikaTweakPoint;
    };
    
    
    
    ---------------------------上面为.h文件,下面为.cpp文件-------------------------------
    // Fill out your copyright notice in the Description page of Project Settings.
    
    #include "PikaTestActor.h"
    
    TSharedPtr<PikaNoObj>PikaNoObj::PointPikaNoObj;
    
    // Sets default values
    APikaTestActor::APikaTestActor()
    {
         // 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;
    
    }
    
    // Called when the game starts or when spawned
    void APikaTestActor::BeginPlay()
    {
        Super::BeginPlay();
    
        //智能转换
        //用派生类获取基类共享指针
        TSharedPtr<PikaBase> PikaBasePtr = MakeShareable(new Pikaqiu());
        //用基类共享指针转换成派生类共享指针
        TSharedPtr<Pikaqiu> PikaqiuPtr = StaticCastSharedPtr<Pikaqiu>(PikaBasePtr);
        //共享引用的转换类型共享指针,只需将StaticCastSharedPtr换成StaticCastSharedRef
        
        if (PikaqiuPtr.IsValid()) 
        {
            PikaqiuPtr->PikaPinrt();
        }
    
        //常量指针转换成变量指针
        const TSharedPtr<PikaBase> PikaBaseConst = MakeShareable(new PikaBase());
        //常量转换时,不能使用派生类
        TSharedPtr<PikaBase> PikaBaseVar = ConstCastSharedPtr<PikaBase>(PikaBaseConst);
        TSharedPtr<Pikaqiu> PikaqiuVar = StaticCastSharedPtr<Pikaqiu>(PikaBaseVar);
        if (PikaqiuVar.IsValid())
        {
            PikaqiuVar->PikaPinrt();
        }
    }
    
    // Called every frame
    void APikaTestActor::Tick(float DeltaTime)
    {
        Super::Tick(DeltaTime);
    
    }
    
    void APikaTestActor::PikaTestFun()
    {
        //共享指针的使用
        PikaNoObjInstance = MakeShareable(new PikaNoObj());//PikaNoObjInstance转换成共享指针
        if (PikaNoObjInstance.IsValid() || PikaNoObjInstance.Get())
        {
            PikaNoObjInstance.Get()->PikaFloat;
            //释放共享指针
            PikaNoObjInstance.Reset();
        }
        /*PikaNoObjInstance = NULL;
        //利用引用计数
        TSharedPtr<PikaNoObj> PikaNoObjInstance_b;
        PikaNoObjInstance_b = PikaNoObjInstance;*/
    }
    
    //共享引用
    void APikaTestActor::PikaShareRef()
    {
        //共享引用创建
        TSharedRef<PikaNoObj> PikaRef(new PikaNoObj());
        //共享引用的两种访问方法
        PikaRef->PikaFloat;
        (*PikaRef).PikaFloat;
    }
    
    //共享指针和共享引用之间的转换
    void APikaTestActor::PikaShareTurn()
    {
        //共享引用创建,PikaRef是局部函数,作用域只是本函数周期
        TSharedRef<PikaNoObj> PikaRef(new PikaNoObj());
        //共享引用转换成共享指针,隐式转换
        PikaNoObjInstance = PikaRef;
    
        //普通指针转换成共享指针
        PikaNoObj* PikaNew = new PikaNoObj();
        PikaNoObjInstance = MakeShareable(PikaNew);
    
        //共享指针转换成共享引用
        //共享指针转换成共享引用是不安全的,转换的过程中有一个断言,如果对角为空,触发后程序会崩溃
        PikaRef = PikaNoObjInstance.ToSharedRef();
    
        //通过单例模式访问属性
        PikaNoObj::Get()->PikaFloat;
    }
    
    //弱指针
    void APikaTestActor::PikaTweakPtr()
    {
        //创建弱指针的两种方式
        //1-共享指针创建
        TSharedPtr<PikaNoObj> PikaPtr = MakeShareable(new PikaNoObj());
        TWeakPtr<PikaNoObj> PikaWeak_a(PikaPtr);
        PikaTweakPoint = PikaWeak_a;
    
        //2-共享引用创建
        TSharedRef<PikaNoObj> PikaRef(new PikaNoObj());
        TWeakPtr<PikaNoObj> PikaWeak_b(PikaRef);
        PikaTweakPoint = PikaWeak_b;
    
        //置空弱指针,置空后,弱指针不会阻止对象被销毁,置空后,对象依然存在
        PikaTweakPoint = nullptr;
    
        //弱指针转换成共享指针
        TSharedPtr<PikaNoObj> PikaNewPtr(PikaTweakPoint.Pin());
        if (PikaNewPtr.IsValid()) 
        {
            PikaNewPtr->PikaFloat;
        }
    
    }
    
    //弱指针的应用示例-树结构
    class PikaTreeNode 
    {
        TArray<TSharedPtr<PikaTreeNode>> Children;//子节点
    
        TWeakPtr<PikaTreeNode> Parent;//父节点
    };
    
    void NewMain()
    {
        //获取共享指针
        TSharedPtr<PikaSettings> PikaSetInstance = MakeShareable(new PikaSettings());
        PikaSetInstance->PikaPrint();
    
        //解引用,共享指针转换成普通类指针
        PikaSettings * PikaSetNormal = PikaSetInstance.Get();
    
        //类指针再次转换成共享指针
        if (PikaSetNormal)
        {
            PikaSetNormal->AsShared();
        }
    }
  8. 智能指针-弱指针

    弱指针就是对对象一个弱引用,与共享指针不同的是,弱指针不会阻止对象被销毁,如果对象被共享指针销毁,弱指针会自动清空;

    示例代码如上;

  9. 智能指针的公优缺点

    使用UE4智能库的原因

    1)标准库std::shared_ptr(and even trl::shared_ptr)不是所有平台上都可用;

    2)使得在所有编译器和平台上有更加一致的实现;

    3)可以和其他虚幻容器及类型无缝地协作;

    4)更好地控制平台特性,包括线程处理和优化;

    5)我们想提供线程安全的功能(以获得好的性能);

    6)我们想在性能方面有更多的控制权(内联函数、内存、虚函数的应用等);

    7)在不需要的时候倾向于不引入新的第三方依赖;

    整体优点

    1)您可以像操作常规的c++指针那样来复制、解引用及比较共享指针;

    2)当没有共享引用时资源自动销毁;

    3)包含了可以通过多个线程安全进行访问的“线程安全”版本;

    4)您几乎可以创建到任何类型的对象的共享指针;

    5)支持指针“const”、前置声明的不完全类型、类型转换等;

    6)所占内存大小是C++指针在64-位系统中所占内存的二倍(外加了一个共享的16字节的引用控制器);

    在性能上的优势

    1)所有操作所占时间都是固定的;

    2)共享指针解引用的速度和C++指针一样快;

    3)复制共享指针永远不会分配内存;

    4)线程安全的版本是无锁的;

    5)和Boost或STL相比,其实现更快;

    劣势

    1)创建及复制指针所带来的性能消耗;

    2)引用计数处理;

    3)共享指针使用的内存比c++指针多;

    4)引用控制器的额外的堆分配;

    5)由多个共享指针引用的每个独立对象都有性能消耗;

    6)弱指针访问速度比共享指针访问速度略慢;

    内存使用情况(在32位操作系统):

    1)所有的共享指针(TSharedPtr,TSharedRef,TWeakPtr)都占8个字节(当针对32-位系统编译时);

    2)C++指针(无符号32位整型);

    3)引用控制器指针(无符号32位整型);

    4)TSharedFromThis也占8个字节,因为它内嵌了弱指针;

    引用控制器(当针对32-位系统编译时)

    1)引用控制器对象占12个字节;

    2)C++指针(无符号32位整型);

    3)共享引用计数(无符号32位整型);

    4)弱引用计数(无符号32位整型)

    注:无论有多少个共享指针或弱指针引用一个对象,都仅为每个对象创建一个引用控制器;

    共享指针

    1)最常用的指针,用来存储;

    共享引用优点

    1)共享引用永远不会为null,且总是可以进行解引用;

    2)共享指针的性能消耗最小,所有操作所占时间都是固定的;

    弱指针

    1)弱指针允许您安全地检查一个对象是否已经被销毁;

    2)使用弱引用来断开引用循环;

    缺点:最慢;

  10. 智能转换

    示例代码如上面的BeginPlay();

  11. 强指针和弱指针的应用

  12. #pragma once
    
    #include "CoreMinimal.h"
    //玩家ID接口
    class IPikaID 
    {
        IPikaID() 
        {
            ID = FMath::RandRange(100, 10000);
        }
        FORCEINLINE uint64 GetID() {return ID;}
    private:
        uint64 ID;
    };
    //数据类
    class PikaData 
    {
    public:
        PikaData() 
        {
            PikaHealth = 100.f;
            PikaDeath = 0;
            PikaPlayerName = TEXT("piqiu");
        }
        float PikaHealth;
        uint8 PikaDeath : 1;
    
        FName PikaPlayerName;
    };
    
    //数据管理类
    class PikaDataManage 
    {
        //单例模式
    public:
        TSharedRef <PikaDataManage> Get() 
        {
            if (!PikaDataManInstance.IsValid())
            {
                PikaDataManInstance = MakeShareable(new PikaDataManage());
            }
    
            return PikaDataManInstance.ToSharedRef();
        }
    
        TSharedRef<PikaData> PikaCreateData()
        {
            TSharedPtr<PikaData> PikaTempData = MakeShareable(new PikaData());
            PikaMyData.Add(PikaTempData->GetID() , PikaTempData);
            
            return PikaTempData.ToSharedRef();
        }
    
        //析构函数
        ~PikaDataManage() 
        {
            //清空数据
            /*for (auto & Tmp: PikaMyData)
            {
                delete Tmp.Value;
            }
            PikaMyData.Empty();*/
        }
    private:
        static TSharedPtr<PikaDataManage> PikaDataManInstance;
        //强引用
        TMap<uint64, TSharedPtr<PikaData>> PikaMyData;
    };
    
    //角色实例(显示层)
    class PikaCharacter 
    {
    public:
        //更新角色数据
        void PikaUpdateData(TSharedRef<PikaData> PikaCurData) {PikaCharData = PikaCurData;}
        //更新角色名字
        void PikaUpdateName(FString PikaNewName) 
        {
            if (IsValid()) 
            {
                PikaCharData.Pin()->PikaPlayerName = FName(*PikaNewName);
            }
        }
    
        //返回数据
        FORCEINLINE bool IsValid() { return PikaCharData;}
    private:
        //弱指针指向角色数据,可以对数据(上面强引用的PikaMyData)进行修改
        //好处:当强引用里的数据被析构掉后,该弱指针就自动为空了,
        //如果此处不是弱指针,数据被强引用析构掉后,该指针就会变成野指针
        TWeakPtr<PikaData> PikaCharData;
    };
    
    //假设的场景函数
    void PikaMain() 
    {
        //注册
        PikaCharacter * PikaMyCharacter = new PikaCharacter();
        PikaMyCharacter->PikaUpdateData(PikaDataManage->Get()->PikaCreateData());
        PikaMyCharacter->PikaUpdateName("pikaqiu");
    }
  13. UE4代理

    1)代理是指不需要知对象是什么,就可以执行对象里的函数,条件是需要提前进行函数绑定;

    2)UE4的代理分为3种,单播代理、多播代理和动态代理;

    3)单播代理是指可以支持绑定单个函数,多播代理是指可以支持绑定多个函数,动态代理可以支持序列化(UObject);

    4)单播代理可以有返回值,但多播代理是没有返回值的;

    5)动态代理要求,被绑定的函数是继承自UObject;

    单播,共享指针绑定的执行如下:

    图片.png

    单播绑定UObject的执行如下:

    图片.png

    动态代理绑定UObject的执行如下:

    图片.png

    动态事件代理绑定执行如下:

    图片.png

  14. 代理的相关示例代码

  15. // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "PikaFun.generated.h"
    
    //单播代理
    //普通代理
    DECLARE_DELEGATE(FPikaTestA);
    //带一个参数的代理
    DECLARE_DELEGATE_OneParam(FPikaTestB , bool);
    //带返回值的代理
    DECLARE_DELEGATE_RetVal(bool , FPikaTestC);
    //带一个参数和返回值的代理
    DECLARE_DELEGATE_RetVal_OneParam(int32 , FPikaTestD , FString &);
    
    //多播代理
    DECLARE_MULTICAST_DELEGATE_OneParam(FPikaTestE , FString &);
    
    //动态代理
    DECLARE_DYNAMIC_DELEGATE_OneParam(FPikaTestF , FString& , PikaStr);
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FPikaTestG ,FString & , PikaStr);
    
    static void PikaPrint(FString PikaStr) 
    {
        if (GEngine) 
        {
            GEngine->AddOnScreenDebugMessage(-1, 20, FColor::Red, PikaStr);
        }
    }
    
    //代理
    class PikaA 
    {
    public:
        //单播代理
        FPikaTestA PikaTestA;
        FPikaTestB PikaTestB;
        FPikaTestC PikaTestC;
        FPikaTestD PikaTestD;
        //多播代理
        FPikaTestE PikaTestE;
        //动态代理
        FPikaTestF PikaTestF;
        FPikaTestG PikaTestG;
    
        //调用执行代理
        void init() 
        {
            bool PikaIsRight = false;
            //INDEX_NONE常量为UE4定义,值为-1
            int32 PikIndex = INDEX_NONE;
            FString PikaStr = TEXT("pikaqiu");
    
            //代理执行
            PikaTestA.ExecuteIfBound();
            PikaTestB.ExecuteIfBound(PikaIsRight);
    
            if(PikaTestC.IsBound())
            {
                PikaIsRight = PikaTestC.Execute();
            }
    
            if (PikaTestD.IsBound())
            {
                PikIndex = PikaTestD.Execute(PikaStr);
            }
    
            FString PikaNewStr = TEXT("pikaqiuhuang");
            PikaTestE.Broadcast(PikaNewStr);
    
            PikaTestF.ExecuteIfBound(PikaNewStr);
            PikaTestG.Broadcast(PikaNewStr);
        }
    };
    
    class PikaB : public TSharedFromThis<PikaB>
    {
    public:
        PikaB() 
        {
            //绑定UObject
            PikaAInstance = MakeShareable(new PikaA());
        }
        //共享指针的初始化
        /*
        void init() 
        {
            PikaAInstance = MakeShareable(new PikaA());
            PikaAInstance->PikaTestA.BindSP(this, &PikaB::PikaExecuteA);
    
            PikaAInstance->init();
        }*/
        //原生绑定
        /*PikaB(PikaA * PikaNewA) 
        {
            if (PikaNewA)
            {
                PikaAInstance = PikaNewA;
                //C++的原生绑定
                PikaAInstance->PikaTestA.BindRaw(this , &PikaB::PikaExecuteA);
                PikaAInstance->PikaTestB.BindRaw(this , &PikaB::PikaExecuteB);
                PikaAInstance->PikaTestC.BindRaw(this , &PikaB::PikaExecuteC);
                PikaAInstance->PikaTestD.BindRaw(this , &PikaB::PikaExecuteD);
    
                //原生多播绑定
                PikaAInstance->PikaTestE.AddRaw(this, &PikaB::PikaExecuteE_A);
                PikaAInstance->PikaTestE.AddRaw(this, &PikaB::PikaExecuteE_B);
            }
        }*/
        void PikaExecuteA() 
        {
            PikaPrint(TEXT("PikaExecuteA"));
        }
    
        void PikaExecuteB(bool PikaIsRight)
        {
            PikaPrint(TEXT("PikaExecuteB = ture"));
        }
    
        bool PikaExecuteC()
        {
            PikaPrint(TEXT("bool PikaIsRight = ExecuteC"));
    
            return false;
        }
    
        int32 PikaExecuteD(FString & PikaStr)
        {
            PikaPrint(TEXT("bool PikaIsRight = ExecuteD") + PikaStr);
    
            return 0;
        }
    
        void PikaExecuteE_A(FString & PikaStr)
        {
            PikaPrint(TEXT("PikaExecuteE_A"));
        }
    
        void PikaExecuteE_B(FString & PikaStr)
        {
            PikaPrint(TEXT("PikaExecuteE_B"));
        }
    
        ~PikaB() 
        {
            //原生绑定
            //if (PikaAInstance)
            //共享指针
            if(PikaAInstance.Get())
            {
                //解绑定
                PikaAInstance->PikaTestA.Unbind();
                PikaAInstance->PikaTestB.Unbind();
                PikaAInstance->PikaTestC.Unbind();
                PikaAInstance->PikaTestD.Unbind();
                PikaAInstance->PikaTestE.Clear();
    
                PikaAInstance->init();
    
                //原生,改为共享指针后,会自动释放,不需手动
                //delete PikaAInstance;
                //PikaAInstance = nullptr;
            }
        }
        //强制内敛共享指针
        FORCEINLINE TSharedRef<PikaA> PikaGet() {return PikaAInstance.ToSharedRef();};
    private:
        //PikaA * PikaAInstance;
        //共享指针
        TSharedPtr<PikaA> PikaAInstance;
    };
    
    //增加头文件,编译通过后,生成的反射数据位置为:
    //\PikaFirstDebug\Intermediate\Build\Win64\UE4Editor\Inc\PikaFirstDebug\PikaFun.generated.h
    UCLASS()
    class UPikaDynamicObj : public UObject
    {
        GENERATED_BODY()
    public:
        void init() 
        {
            PikaA *PikaNewA = new PikaA();
            PikaNewA->PikaTestF.BindDynamic(this , &UPikaDynamicObj::PikaPirntObj_A);
    
            PikaNewA->PikaTestG.AddDynamic(this , &UPikaDynamicObj::PikaPirntObj_B);
            PikaNewA->PikaTestG.AddDynamic(this , &UPikaDynamicObj::PikaPirntObj_C);
    
            PikaNewA->init();
            //解绑
            PikaNewA->PikaTestF.Unbind();
            PikaNewA->PikaTestG.RemoveDynamic(this , &UPikaDynamicObj::PikaPirntObj_B);
            PikaNewA->PikaTestG.RemoveDynamic(this , &UPikaDynamicObj::PikaPirntObj_C);
            //再次调用PikaA里的init,如果打印一两次,说明解绑失败
            PikaNewA->init();
    
            delete PikaNewA;
        }
        UFUNCTION()
        void PikaPirntObj_A(FString& PikaStr) 
        {
            PikaPrint(PikaStr + TEXT("PikaPirntObj_A"));
        }
    
        UFUNCTION()
            void PikaPirntObj_B(FString& PikaStr)
        {
            PikaPrint(PikaStr + TEXT("PikaPirntObj_B"));
        }
    
        UFUNCTION()
            void PikaPirntObj_C(FString& PikaStr)
        {
            PikaPrint(PikaStr + TEXT("PikaPirntObj_C"));
        }
    };
    
    //动态事件代理
    class FPikaEvent
    {
    public:
        //绑定一个事件FPikaEventName
        DECLARE_EVENT_OneParam(FPikaEvent, FPikaEventName, FString &);
        //必须用on来获取
        virtual FPikaEventName &OnPikaEventName() = 0;
    
        DECLARE_EVENT_OneParam(FPikaEvent, FPikaqiuEventName, FString &);
        FPikaqiuEventName &OnPikaqiuEventName() { return PikaqiuEventName; }
    
        virtual ~FPikaEvent() {}
    private:
        FPikaqiuEventName PikaqiuEventName;
    };
    
    class FPikaEventA : public FPikaEvent
    {
    public:
        DECLARE_DERIVED_EVENT(FPikaEventA, FPikaEvent::FPikaEventName, FPikaEventB);
        virtual FPikaEventB &OnPikaEventName() { return PikaEventB; };
    
        void init()
        {
            FString PikaqiuNewStr = "pika";
            OnPikaEventName().Broadcast(PikaqiuNewStr);
    
            PikaqiuNewStr = "pikaqiu";
            OnPikaqiuEventName().Broadcast(PikaqiuNewStr);
        }
    private:
        FPikaEventB PikaEventB;
    };
    
    class APikaTest
    {
    public:
        void init()
        {
            FPikaEventA *PikaNew = new FPikaEventA();
    
            PikaNew->OnPikaEventName().AddRaw(this , &APikaTest::PikaExecuteA);
            PikaNew->OnPikaEventName().AddRaw(this, &APikaTest::PikaExecuteB);
    
            PikaNew->OnPikaqiuEventName().AddRaw(this, &APikaTest::PikaExecuteC);
            PikaNew->OnPikaqiuEventName().AddRaw(this, &APikaTest::PikaExecuteD);
    
            PikaNew->init();
    
            delete PikaNew;
        }
    
        void PikaExecuteA(FString & PikaStr)
        {
            PikaPrint(TEXT("PikaExecuteA"));
        }
    
        void PikaExecuteB(FString & PikaStr)
        {
            PikaPrint(TEXT("PikaExecuteB"));
        }
    
        void PikaExecuteC(FString & PikaStr)
        {
            PikaPrint(TEXT("PikaExecuteC"));
    
        }
    
        void PikaExecuteD(FString & PikaStr)
        {
            PikaPrint(TEXT("PikaExecuteD"));
    
        }
    };
    1
    1
    -------------------------上面为定义的PikaFun.h文件---------------------------
    -------------------------下面为执行代码的PikaMainActor文件-------------------
    // Fill out your copyright notice in the Description page of Project Settings.
    
    #include "PikaMainActor.h"
    #include "PikaFun.h"
    
    static void PikaStatic() 
    {
        PikaPrint(TEXT("PikaStatic"));
    }
    // Sets default values
    APikaMainActor::APikaMainActor()
    {
         // 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;
    
    }
    
    // Called when the game starts or when spawned
    void APikaMainActor::BeginPlay()
    {
        Super::BeginPlay();
        
        //原生
        /*PikaA *PikaNewA = new PikaA();
    
        PikaB *PikaNewB = new PikaB(PikaNewA);
    
        PikaNewA->init();
    
        delete PikaNewB;
    
        //PikaNewA,PikaNewB此时都为野指针
        PikaNewA = nullptr;
        PikaNewB = nullptr;*/
    
        //共享指针
        //TSharedPtr<PikaB> PikaNewB = MakeShareable(new PikaB);
        //PikaNewB->init();
    
    
        //单播绑定UObject
        //PikaB *PikaNewB = new PikaB();
        //PikaNewB->PikaGet()->PikaTestA.BindUObject(this, &APikaMainActor::PikaqiuPrint);
        //绑定静态函数
        //PikaNewB->PikaGet()->PikaTestA.BindStatic(PikaStatic);
        //绑定普通函数,函数绑定前一定要在声明时加上UFUNCTION()
        //PikaNewB->PikaGet()->PikaTestA.BindUFunction(this, TEXT("PikaqiuPrint"));
        //PikaNewB->PikaGet()->init();
    
        //动态代理调用
        /*UPikaDynamicObj *PikaDynObj = NewObject<UPikaDynamicObj>(this);
        if (PikaDynObj) 
        {
            PikaDynObj->init();
        }*/
    
        //动态事件代理调用
        APikaTest *EPikaTest = new APikaTest();
    
        if (EPikaTest) 
        {
            EPikaTest->init();
        }
        delete EPikaTest;
        EPikaTest = nullptr;
    }
    
    // Called every frame
    void APikaMainActor::Tick(float DeltaTime)
    {
        Super::Tick(DeltaTime);
    
    }
    
    void APikaMainActor::PikaqiuPrint() 
    {
        PikaPrint(TEXT("pipikaqiusou"));
    }
  16. 多线程

    进程是一段程序的执行过程,一个进程可以包含多个线程,且最少有一个线程,线程是程序不同的执行路径;一个线程如果崩溃了,进程也会崩溃;

    线程不拥有资源,进程才拥有资源;

    UE4里在网络通信及加载level时,会用到多线程

    创建线程操作的代码,本身是运行在主线程之中的

    创建线程的示例代码中,运行的主线程及自己创建的Pikaqiu线程;

    图片.png

    图片.png

    图片.png

    输出日志中的打印信息

    图片.png

    图片.png

    图片.png

    线程切换的运行图

    图片.png

    切换线程时,代理里绑定的函数

    图片.png

    代理里绑定的函数执行完成后,又变回Pikaqiu线程

    图片.png

  17. 创建线程及切换线程的示例代码

  18. // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "PikaInterface.generated.h"
    
    // This class does not need to be modified.
    UINTERFACE(MinimalAPI)
    class UPikaInterface : public UInterface
    {
        GENERATED_BODY()
    };
    
    /**
     * 
     */
    class PIKAFIRSTDEBUG_API IPikaInterface
    {
        GENERATED_BODY()
    
        // Add interface functions to this class. This is the class that will be inherited to implement this interface.
    public:
        
        void PikaWork() {};
        
    };
    ---------------------上面为接口类PikaInterface.h文件---------------------------
    ---------------------下面为线程类PikaThread.h文件------------------------------
    // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "PikaInterface.h"
    
    DECLARE_DELEGATE(FPikaThread);
    
    /**
     * 
     */
    class PIKAFIRSTDEBUG_API PikaThread : public FRunnable
    {
    public:
        PikaThread();
        ~PikaThread();
    
        FPikaThread PikaThreadDelegate;
    
    
        virtual uint32 Run();
    
        //创建线程
        void PikaCreateThread(IPikaInterface * PikaInterfaceInstancePar);
    private:
        //线程实例
        FRunnableThread * PikaThreadInstance;
        //接口实例
        IPikaInterface * PikaInterfaceInstance;
    };
    ---------------------上面为线程类PikaThread.h文件------------------------------
    ---------------------下面为线程类PikaThread.cpp文件----------------------------
    // Fill out your copyright notice in the Description page of Project Settings.
    
    #include "PikaThread.h"
    
    PikaThread::PikaThread()
    {
        PikaInterfaceInstance = nullptr;
    }
    
    PikaThread::~PikaThread()
    {
    }
    
    
    //线程创建成功后,会运行Run函数
    uint32 PikaThread::Run()
    {
        UE_LOG(LogTemp , Log , TEXT("PikaThreadRun"));
        if (PikaInterfaceInstance)
        {
            PikaInterfaceInstance->PikaWork();
            //将线程切换到主线程,ENamedThreads::GameThread为切换的线程
            FGraphEventRef PikaThreadChange = FFunctionGraphTask::CreateAndDispatchWhenReady([&]() 
            {
                //执行代理绑定的函数
                PikaThreadDelegate.ExecuteIfBound();
            } , TStatId() , nullptr , ENamedThreads::GameThread
            );
            //等待线程切换任务完成
            FTaskGraphInterface::Get().WaitUntilTaskCompletes(PikaThreadChange);
            
        }
        return 0;
    }
    
    void PikaThread::PikaCreateThread(IPikaInterface * PikaInterfaceInstancePar)
    {
        PikaInterfaceInstance = PikaInterfaceInstancePar;
        //创建线程,第二个参数为线程名称,第三个为堆栈大小,0表示当前堆栈大小
        PikaThreadInstance = FRunnableThread::Create(this, TEXT("Pikaqiu") , 0 , TPri_Normal);
    }
    ---------------------上面为线程类PikaThread.cpp文件------------------------------
    ---------------------下面为GameMode类调用的.h文件和.cpp文件----------------------
    // Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/GameModeBase.h"
    #include "PikaObj.h"
    #include "PikaInterface.h"
    #include "RunnableThread.h"
    #include "PikaThread.h"
    #include "PikaFirstDebugGameMode.generated.h"
    
    
    
    UCLASS(minimalapi)
    class APikaFirstDebugGameMode : public AGameModeBase
    {
        GENERATED_BODY()
    
    public:
        APikaFirstDebugGameMode();
    
        virtual void BeginPlay() override;
    
        virtual void EndPlay(const EEndPlayReason::Type EndPlayReason);
    
        //由主线程执行
        void PikaPrint();
    private:
        IPikaInterface * PikaInteface;
        PikaThread * PikaThreadIns;
    };
    
    ---------------------上面为.h文件,下面为.cpp文件--------------------------------
    // Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
    
    #include "PikaFirstDebugGameMode.h"
    #include "PikaFirstDebugHUD.h"
    #include "PikaFirstDebugCharacter.h"
    #include "PikaObj.h"
    #include "UObject/ConstructorHelpers.h"
    #include "PikaGraph.h"
    
    APikaFirstDebugGameMode::APikaFirstDebugGameMode()
        : Super()
    {
        // set default pawn class to our Blueprinted character
        static ConstructorHelpers::FClassFinder<APawn> PlayerPawnClassFinder(TEXT("/Game/FirstPersonCPP/Blueprints/FirstPersonCharacter"));
        DefaultPawnClass = PlayerPawnClassFinder.Class;
    
        // use our custom HUD class
        HUDClass = APikaFirstDebugHUD::StaticClass();
        
    }
    
    void APikaFirstDebugGameMode::BeginPlay()
    {
        Super::BeginPlay();
    
        //创建线程
        PikaInteface = new IPikaInterface();
        PikaThreadIns = new PikaThread();
    
        //绑定PikaPrint
        PikaThreadIns->PikaThreadDelegate.BindUObject(this, &APikaFirstDebugGameMode::PikaPrint);
    
        PikaThreadIns->PikaCreateThread(PikaInteface);
    
        //使用闲置线程,ConstructAndDispatchWhenReady为延时构造
        TGraphTask<FPikaGraph>::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(4.6f);
    }
    
    void APikaFirstDebugGameMode::EndPlay(const EEndPlayReason::Type EndPlayReason)
    {
        Super::EndPlay(EndPlayReason);
    
        //删除线程
        delete PikaThreadIns;
        //置空接口实例
        PikaInteface = nullptr;
    }
    
    //由主线程执行
    void APikaFirstDebugGameMode::PikaPrint()
    {
        UE_LOG(LogTemp, Log, TEXT("PikaMainThreadRun"));
    }
  19. 调用闲置线程

    使用闲置线程执行图,使用前使用的是主线程

    图片.png

    调用闲置线程后为

    图片.png

  20. 调用闲置线程的示例代码

  21. // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "CoreMinimal.h"
    
    //使用闲置线程
    class FPikaGraph 
    {
    public:
        FPikaGraph(float PikaFloatA) 
            :PikaFloatB(PikaFloatA)//初始化列表
        {
        
        }
        static ESubsequentsMode::Type GetSubsequentsMode() 
        {
            return ESubsequentsMode::TrackSubsequents;
        }
    
        FORCEINLINE TStatId GetStatId() 
        {
            RETURN_QUICK_DECLARE_CYCLE_STAT(FPikaGraph ,STATGROUP_TaskGraphTasks);
        }
    
        void DoTask(ENamedThreads::Type CurrentThread , FGraphEventRef Subsequents) 
        {
            UE_LOG(LogTemp , Log , TEXT("Hello Pikaqiu %f") , PikaFloatB);
        }
    
        //AnyThread表示使用任意的闲置线程
        static ENamedThreads::Type GetDesiredThread() 
        {
            return ENamedThreads::AnyThread;
        }
    private:
        float PikaFloatB;
    };
    
    ------------------------上面为定义的PikaGraph.h文件--------------------------------
    ------------------------下面为在GameMode类里调用的代码-----------------------------
    void APikaFirstDebugGameMode::BeginPlay()
    {
        Super::BeginPlay();
    
        //创建线程
        //PikaInteface = new IPikaInterface();
        //PikaThreadIns = new PikaThread();
    
        //绑定PikaPrint
        //PikaThreadIns->PikaThreadDelegate.BindUObject(this, &APikaFirstDebugGameMode::PikaPrint);
    
        //PikaThreadIns->PikaCreateThread(PikaInteface);
    
        //使用闲置线程,ConstructAndDispatchWhenReady为延时构造
        TGraphTask<FPikaGraph>::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(4.6f);
    }
  22. 异步调用线程运行图

    图片.png

    使用线程池中的PoolThread5来执行代码

    图片.png

    图片.png

  23. 异步调用线程的示例代码

  24. // Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    
    class PikaAsyncThread : public FNonAbandonableTask 
    {
    public:
        friend class FAsyncTask<PikaAsyncThread>;
    
        int32 PikaInt;
        PikaAsyncThread( int32 _PikaInt)
            :PikaInt(_PikaInt)//初始化变量
        {
        
        }
    
        //异步线程执行逻辑
        void DoWork() 
        {
            UE_LOG(LogTemp , Log , TEXT("PikaWork %d") , PikaInt);
        }
    
        FORCEINLINE TStatId GetStatId() const 
        {
            RETURN_QUICK_DECLARE_CYCLE_STAT(PikaAsyncThread , STATGROUP_ThreadPoolAsyncTasks);
        }
    };
    --------------------------上面为异步调用线程的PikaAsyncThread.h文件-------------------
    --------------------------下面为GameMode中调用的代码----------------------------------
    //本函数执行在主线程中
    void APikaFirstDebugGameMode::BeginPlay()
    {
        Super::BeginPlay();
    
        //创建线程
        PikaInteface = new IPikaInterface();
        PikaThreadIns = new PikaThread();
    
        //绑定PikaPrint
        PikaThreadIns->PikaThreadDelegate.BindUObject(this, &APikaFirstDebugGameMode::PikaPrint);
    
        PikaThreadIns->PikaCreateThread(PikaInteface);
    
        //使用闲置线程,ConstructAndDispatchWhenReady为延时构造
        TGraphTask<FPikaGraph>::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(4.6f);
    
        //异步线程调用
        FAsyncTask<PikaAsyncThread> * PikaAsyncThreadInstance = new FAsyncTask<PikaAsyncThread>(3);
        //从线程池里调用后台线程
        PikaAsyncThreadInstance->StartBackgroundTask();
        //同步调用线程,同步的对象为程序主线程
        //PikaAsyncThreadInstance->StartSynchronousTask();
    
        //判断线程是否执行完毕
        if (PikaAsyncThreadInstance->IsDone())
        {
            UE_LOG(LogTemp , Log , TEXT("PikaAsyncThreadInstance is done"));
        }
        //固定用法
        //确保线程执行完毕后,删除该线程
        PikaAsyncThreadInstance->EnsureCompletion();
        delete PikaAsyncThreadInstance;
    }
  25. 线程的使用区别

    图片.png

    注:SpawnActor、NewObject、Destory Uobject/Actor、DrawDebugline只能由主线程执行,如果是用非主线程执行时,会触发代码里的断言,导致程序崩溃

  26. 线程安全

    进程创建多个线程后,多个线程并发对同一块内存进行操作的话,会导致数据不一致的问题;

    线程锁的种类:互斥锁、条件锁、自旋锁、读写锁、递归锁、超时锁;

    全局变量、静态变量对于多线程来说,都是不安全的,如果将全局资源变成某个线程的资源,那么,对于该线程来说,就是安全的;

    UE4提供的线程安全,默认情况下是线程不安全的,线程安全会消耗额外的资源;

    图片.png