蓝图可视化脚本简称“蓝图”或“蓝图脚本”,是一种需要编译的面向对象的可视化编程语言,蓝图完全集成在UE4中,蓝图通过节点与连线工作。
蓝图系统又称“蓝图”或“蓝图类”,蓝图类可以看成是一个包含游戏内容的容器,其中可以包含组件、脚本或可以仅仅包含数据。
蓝图类有点类似Unity3d中的预设(prefb),蓝图类可以类似预设一般保存对象状态,可以随时拖入场景中使用,只是在其他功能上不如预设,如预设在U3D中的打包中可以发挥出优秀的效果,但是蓝图类只能作为容器使用。
蓝图类包含蓝图脚本,每一个蓝图类都包含一个默认的蓝图脚本。
创建蓝图的方法很多,这里介绍比较常用的方法
在蓝图中创建变量的方式有两种,其一:直接点击My BluePrint/Variables/+Variable,默认创建bool行的变量,可以点击变量前的色块更换变量类型。
其二:在蓝图脚本区域右键->输入Get New Var,可以直接在蓝图脚本中以节点的形式创建变量,在右侧细节面板中设置变量的值。
所有创建好的变量都会在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:
UE4蓝图中基本数据类型只有5个,需要注意的是蓝图中没有char、double、short、long等数据类型,在C++与蓝图的配合使用时需要注意这些类型的变量。
8位,一字节,0-255。
32位整型,四字节。
64位整型,八字节,能存储更大的数。
32位单精度浮点型,其中1符号位、8指数位、23数值位,UE4中默认保留小数点后6位。UE4中只有Float类型没有Double类型。
蓝图中String类型就是蓝图C++中的FString类型,普通字符串,可以对其中的字符进行增删查改。
Name是一种固定字符串类型,变量初始化以后不能在对其中的内容进行修改,且Name类型的字符串不区分大小写,多用于作为全局变量来唯一标识对象。Name类型的字符串在运行过程中存储于内存的字符串表中,使用效率高。
Text是String的长度加强版,通常用于存储一段文字并可以进行国际化处理。
默认创建的Vector是Vector 3D,即三维量,包含x、y、z三个值,可以表示任何任何三维量,如:三维坐标和RGB等。
Vector 2D就是二维量,Vector 4就是四维量,常用于表示四元素。
Two Vectors就是一个关于Vector的pair(对组),pair在C++的STL种较为常用,是一个只包含两个元素的容器。
包含:
x:roll,翻滚角,以x为轴,进行yz平面的旋转;
y:pitch,俯仰角,以y为轴,进行xz平面的旋转;
z:yaw,航向角,以z为轴,进行xy平面的旋转。
Transfrom类型是一个3*3的矩阵,包含Location、Rotation和Scale三个三维向量。
传入一个bool值,进行分流,通常和比较组件配合使用,如:“==”、“>”等。
使用方法和For Loop类似。
专门有用遍历数组的循环。
用法和Foreach Loop一样,只是多了一个跳出执行分支。
创建数组要比创建变量多一个步骤,首先我们需要创建一个变量,然后再将这个变量的Details/Varialble Type选项的右侧色块选择九宫格,即可将对应变量转换成对应类型的数组。
传入一个数组和一个元素即可通过Add(Array)组件为组数赋值
如:
循环完毕后数组Array内便存储有0-10的是个int型元素。
获取指定索引的元素的拷贝。
获取指定索引的元素的引用。
直接在数组的末尾插入元素的方法也是使用Add(Array)组件,Add(Array)组件的的插入过程就是直接在数组的末尾插入元素。
指定位置插入使用Insert组件,输入参数从上至下依次为目标数组、要插入的元素、目标插入的索引位置。
当要使用Insert组件在数组末尾插入,而又不知道数组大小时,可以使用Last Index组件直接获取输入数组的尾索引,当然直接使用只会在最后一个索引位置插入,而使最后一位元素向后移动一位,而达不到在数组尾部插入的效果,所以需要加一食用。
设置输入数组指定索引的元素的值,Size to Fit选项可以设置如果指定索引不存在时数组自动扩充size大小。
Contains Item组件可以查找指定数组中是否包含某一元素,但是Contains Item只返回bool的判断结果,不会返回目标的索引值。
Find Item则是从输入数组中查找指定元素并返回第一次查找到该元素的索引值。
通过索引删除元素
从输入数组中移除指定元素,Remove Item会删除数组中所有指定的元素,如果删除成功则返回true,失败则返回false。
过滤数组可以按类型来筛选数组元素,用于父类型数组存储子类型元素时进行类型筛选,如: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。
和蓝图函数的使用方法一致。
除了蓝图提供给我们的条件语句和循环语句等流程控制语句外,我还可以使用宏编写自己的流程控制,因为宏可以有多个输入和输出引脚,使用方法就是使用宏的形式定义自己的流程控制逻辑,通过不同的输入给予不同输出,即可达到自定义流程控制的目的。
除了UE4蓝图中给我们提供大量的预定义事件,我也可以自定义事件。
事件的定义也很简单,在All Actions for this Buleprint中选择Add Custom Event,便可以创建一个自定义事件的入口,之后通过这个入口实现事件的内部逻辑,一个事件便定义完成。
事件调用必须通过实现这个事件的蓝图类才可以对其中的事件进行调用。如:
Event Blueprint为实现自定义事件的蓝图类;
Print Str为自定义事件。
事件调度器的创建与使用请阅读目录:十七、蓝图类的通信/3.使用事件调度器进行通信
蓝图中可以将一个复杂了逻辑处理合并成一个节点模块,从而简化整体的执行流复杂度,使蓝图的执行流变得简单明了。我们可以将需要合并的执行流多选选中然后右键,即可在Organization栏中通过Collapse Nodes选项来合并执行流。
除了将执行流合并成节点外,蓝图还提供将多个执行流合并成一个函数或一个宏的功能。合并好的模块也可以在Organizition/Expand Node进行模块分离。
相对U3D来说,UE4的相机跟随要简单得多,UE4中直接将相机设置成了蓝图类的组件,当蓝图类时Player时,即可实现相机对Player的跟随了。
相机跟随需要通过一个物理组件Spring Arm
来链接相机和Player,将相机附着在Spring Arm下,即可在Spring Arm的作用下实现相机跟随。
在UE4的游戏制作过程中,我们不难发现当我们运行游戏时,在世界大纲中多出了很多东西,如下图:
其实,UE4为我们提供很多已经预制好的必须品,如上图的PlayerController(角色控制器)、PlayerCameraMenager(角色相机管理器)等。也就是说不可以直接使用UE4预制给我们的角色控制器来控制我们的Player,而不需要自己再去为Player蓝图配置角色控制器了,我们只需将Player的细节面板上的Auto Possess Player设置成Player0即可,Player0即表示本地角色。
需要注意的是,对于角色控制更优的方式是使用Player Start
组件去持有Player蓝图类,再通过Player Controller来控制Player Start达到控制角色的目的。
在我们创建一个UE4工程后,UE4会启用UE4默认的游戏模式GameModeBase
,GameModeBase就控制着Player Controller、PlayerCameraMenager、PlayerState等运行时UE4动态生成的一些游戏物体,我们可以通过启用我们自定义的游戏模式来设置这些游戏物体按自己的定义的规则来生成运行。
游戏模式也是一个蓝图类,创建游戏模式就是创建蓝图类,只是这个蓝图类需要继承自GameModeBase类,继承自GameModeBase类的蓝图就是一个游戏模式蓝图。不过在我们第一此=次打开游戏模式蓝图时,UE4会将游戏模式蓝图识别成一个普通蓝图,而进入普通蓝图的编辑模式,可能是bug吧,此时我们只需将蓝图关闭,再打开一次,UE4便可以识别出游戏模式蓝图了。
游戏模式蓝图中可以定义自己的游戏模式规则,比如我们可以通过修改Spectator Class选项为自定义的游戏物体,这样在游戏运行时,UE4将不再生成DefaulPawn,而是会生成我们指定的游戏物体。
配置好游戏模式后我们还需要让当前项目使用我们自定义的游戏模式,通过Seettings ->Project Settings->Project/Maps&Modes->Dfault GameModes,选择我们自己的配置的游戏模式即可让当前项目启用我们自己定义的游戏模式了
我们要操控角色控制器就需要配置键盘按键映射,配置好的键盘映射会以函数的形式加入蓝图脚本组件集中。
我们在Seettings ->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。
在游戏设计中触发器的使用是非常平凡的,而在蓝图中触发器的实现也并不复杂,我们来看一下具体操作:
当触发器放置于场景中,有物体进入触发器碰撞盒时,触发器便会出自动发触发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:需要绑定的事件
如此一个基于事件调度器的通信机制便完成了。