初学UE4和C++,在尝试将蓝图函数转为C++代码时候,发现了一个很坑人的玩意,就是蓝图中的构造脚本(Consttruction Script)和C++中类的构造函数不是一个东西!因此想记录下来。
先创建一个继承Actor的类,现在我有个现成的继承Pawn的类(下文就直接说Actor),可以凑合着用。
CameraOnPlane.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "CameraOnPlane.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(TestLog, Log, All);
UCLASS()
class TRAJECTORY_API ACameraOnPlane : public APawn
{
GENERATED_BODY()
public:
// Sets default values for this pawn's properties
ACameraOnPlane();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
virtual void OnConstruction(const FTransform& Transform) override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
};
CameraOnPlane.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "CameraOnPlane.h"
DEFINE_LOG_CATEGORY(TestLog);
// Sets default values
ACameraOnPlane::ACameraOnPlane()
{
// Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
UE_LOG(TestLog, Warning, TEXT("构造函数被调用"));
}
void ACameraOnPlane::OnConstruction(const FTransform& Transform)
{
UE_LOG(TestLog, Warning, TEXT("OnConstruction"));
}
// Called when the game starts or when spawned
void ACameraOnPlane::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ACameraOnPlane::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void ACameraOnPlane::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
其中,这两行代码是用来输出一个名字为TestLog的日志:
// 这个放在.h文件中
DECLARE_LOG_CATEGORY_EXTERN(TestLog, Log, All);
// 这个放在.cpp文件中
DEFINE_LOG_CATEGORY(TestLog);
最后就是打开关卡蓝图,通过SpawnActor让它在开始运行的时候自动在场景中生成一个Actor。
好了,准备工作完成,先看一下编译和运行的效果。
编译日志输出:
开始运行日志输出:
编译结果在编译的时候调用了一次构造函数,运行结果显示,在运行的时候构造函数调用了一次,OnConstruction函数也被调用了一次,先来说一下OnConstruction函数,这个函数是Actor生命周期的一部分,在官方文档中是这么说的(这里是原文):
文档怎么一直在说蓝图蓝图的,这不是写在C++里的吗,管蓝图什么事情!(气愤)等等!蓝图好像也有个构造什么玩意:
对!就是这个玩意,Construction Script——构造脚本,我经常在这个函数里边给类弄点默认参数啊什么的,说白了就是为Actor的生成来一些准备工作,我这么说可能不太准确,来看看官方的说法(这里是原文):
创建蓝图类的实例时,构造脚本(Construction Script) 在组件列表之后运行。它包含的节点图表允许蓝图实例执行初始化操作。构造脚本的功能可以非常丰富,它们可以执行场景射线追踪、设置网格体和材质等操作,从而根据场景环境来进行设置。 例如,光源蓝图可判断其所在地面类型,然后从一组网格体中选择合适的网格体, 或者,栅栏蓝图可以向各个方向射出射线, 从而确定栅栏可以有多长。
可能我语文不太好,第一次看完一头雾水,只知道它用来初始化,功能非常丰富。等等,初始化?我自然而然又想到了C++里边的构造函数:
// Sets default values
ACameraOnPlane::ACameraOnPlane()
{
// Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
UE_LOG(TestLog, Warning, TEXT("构造函数被调用"));
}
好了,构造脚本,OnConstruction和构造函数三个东西穿起来了,都能初始化,那这就是一个东西嘛(顺带一说,我起初认为的是构造脚本和构造函数是一个东西,只不过一个在蓝图一个在C++),如果这么想的话,那刚刚的测试结果怎么说?
直到看到了这篇文档(这里是原文),才懂了那么一丝。
virtual void OnConstruction(const FTransform& Transform) override;
其实,这个OnConstruction函数才是和蓝图里边的构造脚本是一个玩意,跟构造函数毛关系都没有(其实我也不知道是不是毛关系都没有),这个玩意你可得小心着点用,不然就像那个文章里说的,指不定你的编辑器怎么就crash(崩)掉了,而且一运行就让send and restart(别问我是怎么知道的),所以,我们得大概了解一下这个玩意是怎么玩的。
先是这个C++里边的构造函数,这个不多说了,从官方文档里边的好多例子也看出来可以在里边创建一些组件啊,这个组件加到这个组件上,那个组件加到这个组件上,在给组件整点默认的参数什么的,你看那方法上也写了Sets default values,还有就是这个构造函数会在编译和运行的时候调用(从我那个测试结果看出来)。
然后就是这个构造脚本和OnConstruction函数了,这玩意主要在下面三种情况调用:
在那个文章中我注意到一句话:
It is the most interesting point of the Construction Script: it is called in the editor, and not at runtime
这句话的意思是说ConstructionScript会在编译的时候调用而不是在运行的时候调用吗?如果是这样的话好像跟我测试的结果不太一样,难道我哪里搞错了?欢迎指正讨论!
咱接着说这个构造脚本,文章中谈到这个滥用构造脚本会出现一些问题:
总的来说,这次学习算是知道了构造脚本和构造函数不是一个东西,和OnConstruction才是一个东西,用的时候还是要慎重一点,鬼知道会出现什么匪夷所思的问题。
第一次写这么长的文章,如有错误和不足之处还请指正。
参考文章:UE4 - Be careful with the Construction Script - Isara Tech.