蓝图可视化脚本简称“蓝图”或“蓝图脚本”,是一种需要编译的面向对象的可视化编程语言,蓝图完全集成在UE4中,蓝图通过节点与连线工作。
蓝图脚本的节点
蓝图类的分类
直接点击My BluePrint/Variables/+Variable,默认创建bool行的变量,可以点击变量前的色块更换变量类型。
所有创建好的变量都会在My Blueprint面板Variables栏中显示。
在创建好的变量的Details/Variable/Variable Type选项中可以设置变量为普通变量、数组、Set容器和Map容器。
可以直接在蓝图脚本区域右键->输入Get/Set [变量名]即可获取变量的获取/设置组件,也可以直接从左侧的My Blueprint面板Variables栏中拖拽变量到蓝图脚本区域选择Get/Set。
蓝图中的公有与私有与程序中的公有私有有一些不同,蓝图中只分为生成时公开和私有两种访问权限。
勾选生成时公开后,在蓝图类使用Spawn Actor from Class组件生成蓝图类实例时,勾选生成时公开的变量将可以接受外部值输入。
如上图可以看到勾选生成时公开的变量会以输入接口的形式出现在Spawn Actor from Class节点里。这里需要注意的是,勾选Expose on Spawn,还需要勾选Instance Editable,否则会报警告。
设置成私有变量的变量只能在蓝图类里访问,蓝图类的外部实例也无法访问这个变量,如上图,变量L没有设置成私有,所以可以通过Spawn Actor from Class组件生成的实例访问这个变量,而设置成私有的K变量搜索不到Set K节点。
很多时候我们需要调节自身Actor上的组件的某些参数,那么我们就需要获得这个组件的引用以便获取组件上的参数进行调节,那么我们如何获得组件的引用呢?
其实方法也很简单,在My Blueprint/Variabales下有一个子栏“Components”其中存放着当前Actor上的所有的组件的引用,使用方法就是直接将需要的组件引用拖拽到蓝图脚本区域即可。获得了组件引用我们就可以通过引用使用不同的方法获取组件上的不同的参数了。
在蓝图脚本中有四个方法获取外部Actor:
Get All Actor of Class:通过类型来获取外部Actor
Get All Actor with Tag:通过标签来获取外部Actor
Get All Actor with Interface:通过外部接口在获取外部Actor
Get Actors:通过层来获取Actor
UE4蓝图中基本数据类型只有5个,需要注意的是蓝图中没有char、double、short、long等数据类型,在C++与蓝图的配合使用时需要注意这些类型的变量。
包含:
Transfrom类型是一个3*3的矩阵,包含Location、Rotation和Scale三个三维向量。
1.Branch(if条件判断)
传入一个bool值,进行分流,通常和比较组件配合使用,如:“==”、“>”等。
2.Switch
使用方法和For Loop类似。
专门有用遍历数组的循环。
用法和Foreach Loop一样,只是多了一个跳出执行分支。
创建数组要比创建变量多一个步骤,首先我们需要创建一个变量,然后再将这个变量的Details/Varialble Type选项的右侧色块选择九宫格,即可将对应变量转换成对应类型的数组。
Add(Array)
传入一个数组和一个元素即可通过Add(Array)组件为组数赋值
如:
循环完毕后数组Array内便存储有0-10的是个int型元素。
Get(a copy)
获取指定索引的元素的拷贝。
Get(a ref)
获取指定索引的元素的引用。
上面这两个节点外观上不一样的就是输出引脚有区别,copy是圆形,ref是菱形。
Insert
指定位置插入使用Insert组件,输入参数从上至下依次为目标数组、要插入的元素、目标插入的索引位置。
当要使用Insert组件在数组末尾插入,而又不知道数组大小时,可以使用Last Index组件直接获取输入数组的尾索引,当然直接使用只会在最后一个索引位置插入,而使最后一位元素向后移动一位,而达不到在数组尾部插入的效果,所以需要加一使用。
Set Array Elem
设置输入数组指定索引的元素的值,Size to Fit选项可以设置如果指定索引不存在时数组自动扩充size大小。
Contains Item
Contains Item组件可以查找指定数组中是否包含某一元素,但是Contains Item只返回bool的判断结果,不会返回目标的索引值。
Find Item
Find Item则是从输入数组中查找指定元素并返回第一次查找到该元素的索引值。
Remove Index
通过索引删除元素
Remove Item
从输入数组中移除指定元素,Remove Item会删除数组中所有指定的元素,如果删除成功则返回true,失败则返回false。
Filter Array
过滤数组可以按类型来筛选数组元素,用于父类型数组存储子类型元素时进行类型筛选,如:Actor数组筛选MyClass类型元素,Filter Array返回一个筛选后的新数组。
使用Clear组件可以将数组内的元素全部清空。
蓝图中结构体的创建是没办法在蓝图脚本视图中创建的,UE4蓝图中结构体是与蓝图脚本同级的蓝图类,创建蓝图结构体的方法:在Content Browser中右键->Create Advanced Assect/Blueprint栏->Structure,即可创建一个结构体,创建出来的结构体默认拥有一个bool值得成员变量。
蓝图中得结构体只能创建成员变量,不能创建成员函数,成员变量可以直接设置默认值。
将结构体创建好以后便可以直接在蓝图脚本中搜索到并使用了。
枚举的创建和使用和结构体区别不大,创建方式一如结构体一般,Content Browser中右键->Create Advanced Assect/Blueprint栏->Enumeration,创建好后UE4也会在一个新的视图中对枚举进行编辑,枚举的编辑操作也相当简单,只有向枚举中添加与删除元素的操作,这些元素都可以添加注解提示,枚举也可以添加整个枚举的注解提示。
枚举的使用就如同变量的使用一般,没有什么可说的。
接口的定义和和蓝图函数库,蓝图宏库一样,但是接口的不像函数库和宏库一般可以再一个蓝图类中定义多个函数和宏,接口就是一个蓝图类,并且接口的编辑界面及其简约,可编辑视图只有一个My Blueprint和一个Detials,在My Blueprint/Functions栏可以新建接口函数,在对应的接口函数的Details/Graph可以设置一些接口函数的描述和Const属性,在Details/Inputs和Outputs栏可以设置接口函数的输入与输出参数。
接口是一个抽象蓝图,接口的实现必须通过继承接口的蓝图来实现,在蓝图中接口继承通过Toolbar/Class Settings/Interfaces栏即可添加接口到当前蓝图类是指继承此接口。
无返回值的接口实现
在一个蓝图继承了一个无返回值得接口后,这个接口将以事件的形式出现在这个蓝图类的All Actions for this Blueprint里
如:
蓝图以这个事件作为接口函数的入口,以此节点开始便可以开始实现各个蓝图类各自继承的接口函数的逻辑了。
有返回值得接口实现
有返回值的接口不会以事件的形式出现在Add Event里,而是会直接出现在My Blueprint/Interface栏里,我们双击即可打开接口函数的编辑视图,在视图中实现接口即可。
蓝图通过[接口名](Mesasge)组件来调用接口中的函数,如下图中的Interface1(Message)和Interface2(Message)。
接口的使用需要指定使用的接口是哪个蓝图类中实现的接口和接口需要传入的参数。在蓝图类的My Blueprint/Variables栏是可以搜索到我们自己创建的蓝图类的,通过过去蓝图类的引用便可以指定接口函数的实现者了。当然我们也可以使用Get All Actors with Interface组件来获取实现了指定接口的所有蓝图类,然后再对这些蓝图类中实现的函数进行调用。
蓝图函数库是一个可以被所有蓝图类调用的函数集合,蓝图函数库里的方法全部都被定义为静态方法,用于定义一些不与特定游戏对象绑定的功能性函数的实现。
创建蓝图函数库的方式和创建结构体类似,在Content Browser中右键->Create Advanced Assect/Blueprint栏->Bluprint Function LiberLibrary。
和结构体一样,创建好的蓝图函数库UE4会创建一个新的界面用于编辑库里函数;
要使用蓝图函数库里的函数,可直接在脚本编辑区右键输入函数名即可在Class栏中看到蓝图函数库里的函数了。
宏在UE4中几乎无处不在,蓝图宏库是开发者使用蓝图自定义的一系列的宏的容器,蓝图宏库和蓝图函数库一样可以定义多个宏;
蓝图宏库与蓝图函数库不同的是,蓝图宏库中的宏是在预编译过程中完成处理的,即蓝图宏库不需要编译;
自定义的宏,可以有任意数量的输入输出引脚,即一个宏可以没有输出引脚,这样的宏可以只进行数据处理,一个宏也可以有多个输入输出引脚,这样的宏可以根据不同的逻辑选择不同的执行流,而蓝图函数库中的函数必须有且只有一个输入和一个输出引脚;
与函数不同,宏中不可以再定义成员变量,即宏只负责处理逻辑,不提供暂存能力。、
宏库的创建与函数库的创建是一样的,在Content Browser中右键->Create Advanced Assect/Blueprint栏->Bluprint Macro LiberLibrary。
在My Blueprint/Macro中可以声明一个宏;
在指定宏的Details/Inputs和Outputs中可以定义输入与输出引脚和参数。
和蓝图函数的使用方法一致。
除了蓝图提供给我们的条件语句和循环语句等流程控制语句外,我还可以使用宏编写自己的流程控制,因为宏可以有多个输入和输出引脚,使用方法就是使用宏的形式定义自己的流程控制逻辑,通过不同的输入给予不同输出,即可达到自定义流程控制的目的。
除了UE4蓝图中给我们提供大量的预定义事件,我也可以自定义事件。
事件的定义也很简单,在All Actions for this Buleprint中选择Add Custom Event,便可以创建一个自定义事件的入口,之后通过这个入口实现事件的内部逻辑,一个事件便定义完成。
事件调用必须通过实现这个事件的蓝图类才可以对其中的事件进行调用。如:
Event Blueprint为实现自定义事件的蓝图类;
Print Str为自定义事件。
事件调度器的创建与使用请阅读目录:十六、蓝图类的通信/3.使用事件调度器进行通信
蓝图中可以将一个复杂了逻辑处理合并成一个节点模块,从而简化整体的执行流复杂度,使蓝图的执行流变得简单明了。我们可以将需要合并的执行流多选选中然后右键,即可在Organization栏中通过Collapse Nodes选项来合并执行流。
除了将执行流合并成节点外,蓝图还提供将多个执行流合并成一个函数或一个宏的功能。合并好的模块也可以在Organizition/Expand Node进行模块分离。
UE4中直接将相机设置成了蓝图类的组件,当蓝图类是Player时,即可实现相机对Player的跟随了。
相机跟随需要通过一个物理组件Spring Arm来链接相机和Player,将相机附着在Spring Arm下,即可在Spring Arm的作用下实现相机跟随。
在UE4的游戏制作过程中,我们不难发现当我们运行游戏时,在世界大纲中多出了很多东西,如下图:
其实,UE4为我们提供很多已经预制好的必须品,如上图的PlayerController(角色控制器)、PlayerCameraManager(角色相机管理器)等。也就是说不可以直接使用UE4预制给我们的角色控制器来控制我们的Player,而不需要自己再去为Player蓝图配置角色控制器了,我们只需将Player的细节面板上的Auto Possess Player设置成Player0即可,Player0即表示本地角色。
需要注意的是,对于角色控制更优的方式是使用Player Start组件去持有Player蓝图类,再通过Player Controller来控制Player Start达到控制角色的目的。
在我们创建一个UE4工程后,UE4会启用UE4默认的游戏模式GameModeBase,GameModeBase就控制着Player Controller、PlayerCameraManager、PlayerState等运行时UE4动态生成的一些游戏物体,我们可以通过启用我们自定义的游戏模式来设置这些游戏物体按自己的定义的规则来生成运行。
游戏模式也是一个蓝图类,创建游戏模式就是创建蓝图类,只是这个蓝图类需要继承自GameModeBase类,继承自GameModeBase类的蓝图就是一个游戏模式蓝图。不过在我们第一次打开游戏模式蓝图时,UE4会将游戏模式蓝图识别成一个普通蓝图,而进入普通蓝图的编辑模式,可能是bug吧,此时我们只需将蓝图关闭,再打开一次,UE4便可以识别出游戏模式蓝图了。
游戏模式蓝图中可以定义自己的游戏模式规则,比如我们可以通过修改Spectator Class选项为自定义的游戏物体,这样在游戏运行时,UE4将不再生成DefaulPawn,而是会生成我们指定的游戏物体。
配置好游戏模式后我们还需要让当前项目使用我们自定义的游戏模式,通过Seettings ->Project Settings->Project/Maps&Modes->Dfault GameModes,选择我们自己的配置的游戏模式即可让当前项目启用我们自己定义的游戏模式了
我们要操控角色控制器就需要配置键盘按键映射,配置好的键盘映射会以函数的形式加入蓝图脚本组件集中。
我们在Settings ->Project Settings->Engine/Input->Bindings/Axis Mappings设置键盘映射并可以配置映射的值,
配置好键盘映射以后便可以在蓝图脚本中使用这些函数主键来控制我们Player了,控制角色运动的方法很多,我这里以旋转力矩的方式来控制小球运动,具体的控制方法如下:
加入两个键盘映射函数组件(InputAxis MoveF/B,InputAxis MoveL/R)、两个添加角度力矩的组件(Add Torque in Radians,Add Torque in Radians),但是添加角度力矩组件是需要一个控制目标的,我们需要从左侧的My Blueprint面板的Components中将需要控制的物体Player拖入脚本蓝图中,分别连接到添加角度力矩组件的Target上。
这里还需要注意的一点是,在我们为Player添加相机时,相机使用的是相对于Player的局部坐标,使用局部坐标的相机会跟随Player旋转,而使用世界坐标的相机将会固定某一个角度跟随Player移动。切换方法为:下拉Spring Arm的细节面板/Transform/Rotation的三角箭头选择切换。
并且相机默认情况下是启用了物理碰撞的,但大多数情况下我们是不需要让相机出现物理碰撞的,所以我们也应当取消相机的物理碰撞。操作方法为:取消Spring Arm的细节面板/Camera Collision/Do Collision Test选项。
当然要想比较真实的模拟出物理效果,光这样是不行的,因为默认情况下Player是没有启动物理模拟的,我们需要启用Player的细节面板/Physics/Simulate Physics选项,并且将调节Player的细节面板/Linear Damping(线性摩檫)和Player的细节面板/Angular Damping(旋转摩檫)到适当的值,应为UE4默认是没有旋转摩檫的并且,默认线性摩檫为0.01。
小知识
大多数时候我们都需要勾选作用力函数组件的Acel Change选项,如:上面的Add Torque in Radians,勾选了Acel Change选项作用力组件会在作用目标物体时忽略物体的质量因素,因为存在质量影响的话,我们可能需要给一个极大的力才能推动目标物体;
有时可能默认大小的作用力对目标物体的移动作用达不到我们理想的效果,此时可以先将作用力加倍后在传递给作用力组件,蓝图脚本组件集中提供多种多样的乘积组件,我们上面使用的是float*float组件;
在游戏设计中触发器的使用是非常平凡的,而在蓝图中触发器的实现也并不复杂,我们来看一下具体操作:
蓝图模式中游戏的一切都是蓝图,触发器也不例外,所以要做一个触发器,首先我们的创建一个蓝图的壳子,并继承自Actor类
触发器还需要一个极为重要的东西就是碰撞体,当然这里的碰撞体不是具有物理碰撞的网格碰撞体,而是蓝图的物理组件集中的Box Collison,一个没有物理碰撞效果专门用于触发器的盒碰撞体,我们给蓝图类添加这个组件,一个触发器便完成了
当触发器放置于场景中,有物体进入触发器碰撞盒时,触发器便会出自动发触发Event ActorBeginOverlap时间组件,之后我们便可以进行一系列逻辑操作了。
两个蓝图类要想通过引用进行通信,那么其中一个蓝图类就必须拥有另一个蓝图类的引用,如何获取一个蓝图类的引用呢?
在My Blueprint/Variables栏中添加一个所需要引用的蓝图类类型的变量,勾选它的Details/Variable/Instance Editable选项,就可以在这个蓝图类的实例的Details/Default栏中看到引用变量(如果不勾选是没有default栏的),如此便可以方便的获取另一个蓝图类的实例的引用了。
通过引用两个蓝图类之间便可以进行单向的信息传递了。即拥有引用的蓝图类可以获取所引用蓝图的传递过来的信息,反向则不行。
使用接口进行通信的好处是可以很便利的通过同一事件让各种不同的蓝图做出不同的反应,如:当受到子弹攻击时,墙会反弹子弹,人会受伤,通过接口通信来实现这个效果就是,创建一个受到子弹攻击的接口OnTakeAttack,然后分别让墙和人都继承这个接口并实现不同的效果,如此当受到子弹攻击时,我们便可以通过Get All Actor with Interface来调用实现这些接口的Actor来做出各自的反应。
接口的实现与使用请阅读目录:九、接口。
事件调度器的通信过程是一个调度者与一个或多个被调度者之间的通信过程,类似观察者模式的通信过程。
创建事件调度器
使用事件调度器来进行蓝图通信,首先我们需要拥有一个事件调度器;
事件调度器只能被拥有者调用,所以我们需要在调度者蓝图上创建事件调度器,在My Blueprint/Event Dispatchers栏可以新建一个蓝图调度器,此时这个蓝图便拥有了一个事件调度器,我们可以通过拖拽事件调度器到脚本编辑视图或直接在All Actions for this Blueprint中搜索,可以对事件调度器进行事件绑定(Bind)、事件解绑(Unbind)、解绑全部事件(Unbind all),直接为事件调度器绑定新事件(Assign)和调用事件调度器(Call)。当一个事件调度器被调用后,与这个事件调度器绑定的事件都会被调用,一次来达到信息传递的目的。
绑定事件
调度者要事件调度器调用被调度者的事件,那么被调度者就需要向调度者的事件调度器上绑定事件,通过Bind Event to [事件调度器名]组件来绑定事件到事件调度器上。
Target:输入事件调度器的拥有者对象
Event:需要绑定的事件
如此一个基于事件调度器的通信机制便完成了。