
前言
今天开始跟着教程,制作我的第一个UE5游戏。
游戏的原教程引用自一本教程书,这本书会在这篇教程完成之后介绍给大家。
游戏十分简单,但是我在原教程看完之后还是有些蒙圈,因为讲解的方式比较程序化,难以食用,因此我在这里用自己的方式重新整理了这份教程,希望可以给有需要的同学提供帮助。
作为游戏人,在学习游戏制作流程时,技术是一方面,更重要的是需要一个良好的思维习惯。理清设计思路后,可以保证做出来的东西思路清晰,并非对着教程依葫芦画瓢,避免丢掉教程后一头雾水。
这篇文章中有大量实战图片,主要针对的是UE5蓝图新手,建议收藏❤️后,打开虚幻引擎一步一步去做,每一步怎么做,为什么这么做,都有详细的说明,如果有不清楚的,还可以随时私信我!
接下来我们开始吧!
在本次教程中,你将学习如何设计游戏,建立游戏场景和界面,运用蓝图实现游戏规则,最终完成用鼠标控制丢出保龄球的力度和角度,撞击圆柱完成游戏。
_(:з」∠)_
当你完成这篇教学后,相信你已经可以熟练掌握基本蓝图的使用,并且你将接触到一个简单的游戏设计流程,为接下来的学习打好扎实基础了!
^0^
注意:本教程新手大约需要10-15小时。
目录
制作游戏之前,先画一个脑图,以及最基础的界面设计图,这可以帮助我们理清思路,并为接下来的制作内容制定计划。这里可以直接看菜鸡博主自己绘制的,也可以自己尝试做一个。

脑图

界面设计图
虽然有点简陋,但是意思基本都清楚了。
根据上图的脑图,拆分游戏制作的步骤为:
完成游戏布景及UI排布
使用鼠标将球丢出,并控制方向和力度
在圆柱全部倒下时游戏结束
首先建立游戏布景。
新建一个关卡(File>new level),这里选择只有地面的basic默认关卡类型。

接下来完成:
1、将地面扩大(更改地面物体的scale值)
2、添加球体和四个盒体(拖动预制体到场景中并调节尺寸和位置)
(后文提到这里踩了一个不大不小的坑,我先在这里预警一下)
3、为所有的物体添加上材质(将材质拖动到预制体上)
完成后可以得到以下效果,图中所有材质均来自新手包,请同学们创建工程的时候记得勾选新手包。当然,如果没有勾选也没有关系,材质不会影响任何逻辑,也可以随便选择一个颜色材质填上去:

【相关知识点参考】
如何创建物体
https://blog.csdn.net/weixin_35106335/article/details/127441304
新建一个控件蓝图,新建方法在之前的笔记里有详细描写,如有需要请在下文中跳转参考知识点。
根据设计图,需要三块内容,因此做以下排布:

其中用到了一些之前文章中没有用到的控件,这里整理一下:

通过控件容器,将多个控件组合成一个整体,便于后续的调整和管理。
接下来将中间的text和右上角的text做文字绑定,绑定方法前面有非常详细的描述,有需要的同学可以查看下方参考知识点中的跳转链接。
【知识点】锚点Anchor的用法
使用过unity的同学一定对这个界面再熟悉不过了。是的没错,UE也有这套Anchor系统,看快捷键都差不多,不知道先后存在的顺序是啥哈哈。
这里简单的说一下锚点的作用。界面编辑器上有一个巨大的小菊花,这个里面的小格子代表的是小菊花出现在不同位置的时候该控件与菊花的相对关系。
这是用来做屏幕自适应用的,打个比方,如果选择的是屏幕左上角的方格,代表小菊花在左上角,且这个控件与小菊花的相对位置保持不变。也就是说无论屏幕有多长或者多高,一定会在左上角出现这个控件。
【知识点】Progress Bar的使用
1、大小设置:进度条的尺寸在正常情况下是可以自由设置的,如果是在控件容器中,会变成另外一套设置方式,目前支持自动(根据控件容器变化),填充(填满控件容器剩下控件),以及自由设置长度
2、比例显示方式:Bar Fill Type也是很常见的设置内容,控制的是进度条由空到满的填充过程
因为都是最简单的英文单词,一看就懂一学就会,这里就不一一整理了。
【知识点】控件容器的使用
控件容器是一个比较新奇的概念,目前看到的用法是可以在里面放入多个控件组成控件组。在设计UI的软件,或是其他开发软件中,控件会有分组的概念,帮助用户去整理界面上大量的控件。(UE是否有这项功能,还是说它的这项功能被控件容器代替,目前小白的我暂时不下定论。后续如果确认后会更新该内容)
【相关知识点参考】
如何建立一个界面
https://blog.csdn.net/weixin_35106335/article/details/127637553
下面进入关键的蓝图阶段。
根据之前的功能逻辑,将所有的蓝图以函数拆分,简单说来,就是把一个完整的简单逻辑,合在一个函数里面去这,这是之前提到的结构化。这个方法可以让整个蓝图结构变得清晰。
根据脑图将游戏根据内容分为四块:初始化、鼠标控制、结束处理、串联所有函数。
根据这四块再向下拆分功能,得到以下函数。如果可以的话,尽可能把功能拆细,刚开始拆得越细,每一个单独的函数写起来就越简单。
如果实在整理不出来也没关系,可以在写的过程中再去整理这张表。
| 大类 | 函数名 | 实现内容 |
| 初始化 | Set UI | 显示UI |
|
| Create Ball | 初始化球体 |
|
| Create Boxes | 初始化盒体 |
| 鼠标控制 | Mouse Button Down | 按住鼠标左键调整力度 |
|
| Mouse Button Up | 松开鼠标左键发射 |
|
| Mouse Move UpDown | 鼠标移动控制球体角度(上下) |
|
| Mouse Move LeftRight | 鼠标移动控制球体角度(左右) |
| 结束处理 | Is Turn Over | 判断球体是否停止,用于判定当前轮是否结束 |
|
| Is Win | 判断盒体是否全部倒下,用于判定游戏是否结束 |
|
| Game Over | 游戏结束时的处理 |
【相关知识点参考】
什么是蓝图的结构化
https://blog.csdn.net/weixin_35106335/article/details/127382868
根据上文的函数图,我们可以开始进行函数设计了。大部分蓝图都在关卡蓝图(level blueprint)中完成的,因此可以先将关卡蓝图打开,把标签页拖拽到顶部主关卡的旁边,方便切换。

先为蓝图添加必须的函数SetUI和变量UI,其中变量UI的类型修改为之前建好的UI文件,名称这里我取的是比较简单的UI两个字母。

然后双击SetUI打开这个函数,在里面添加蓝图逻辑。
用到的节点有:

连线后获得:

因为希望看到每次修改的结果,所以将完成的函数连接BeginGame上,运行后获得:


【完成!】
先考虑创建球体需要的逻辑内容:
1、创建一个球体
2、初始化球体位置
3、初始化球体设置
4、为球体计数加一
添加与创建球体相关的函数Create Ball与变量Ball和变量Count:

其中球体的网格控件变量需要修改类型,方法如下。

随后双击函数Create Ball,开始添加节点,根据逻辑内容,将节点分为三组:



将上述所有蓝图连起来,并调整好参数,搭配好注释:

加入到beginplay后面,运行一下,完美,场景中出现了另一个球。
注意:这时候发现球体没有材质,这是因为先前给球体增加材质时,是直接拖拽到已创建的物体上面。想要解决这个问题,需要打开物体,在这网格物体上去添加材质,新建的时候才会带有材质。
【完成!】
先分析需求:
1、需要建立5个盒体
2、每个盒体在一定范围中随机
3、为盒体设置物理引擎
添加函数Create Boxes和变量Boxes:

其中,变量Boxes的建立方式与上文的Ball相同,但是编译后需要在细节Detial里面修改两个内容,一个是修改成组,一个是添加组的数量,目前是添加5个。

随后双击Create Boxes开始添加蓝图节点,根据需求将节点分为3组:



将上述所有蓝图连起来,并调整好参数,搭配好注释:

连接BeginPlay运行一下,天降盒体(笑cry):

但是这里哪里不太对,于是我意识到,刚开始建立物体的时候,我偷了一个懒,使用的是UE里面默认的盒体建立和拉伸,因为默认盒体不需要转化成静态网格物体,而且自带碰撞。(这就是上文提到的坑啦)
但是当时的我没有意识到一点:预设体和实例化后的物体是两个东西,因此当我修改了实例化物体之后,预设体并没有发生变化。
于是偷懒的我必须要付出代价,将所有的物件重新建立……

正确的建立方式是这样的:
1、使用图形笔刷在地图上做出图形,调整好大小(最好是画出来就调好,转之后就很难调整了,别问我是怎么知道的)
2、转化为静态网格图形,保存为预制体,起好名称。

3、双击图形,进入后添加碰撞体,添加材质,勾选阴影和独立

4、保存之后将原本场景中的物件删除,就可以通过蓝图进行物体创建了。这次创建出来的物体就是想怎么改就怎么改了。
经过一番操作之后,我将原本创建好的球体和盒体重新换成了自己创建的物体。

并且适当调整了大小和位置,于是完成了下图效果:

【完成!】
先分析需求:
1、判断是否按住左键
2、按住左键时百分比随时间增长
3、当百分比达到1时保持1不变
然后创建所需函数Mouse Button Down和函数Delta

由于使用到进度条,为了避免进度条名称混淆,回到UI的蓝图中,将三个进度条改成好识别的名字。

为该需求添加有关节点,根据内容分为四组:




将上述所有蓝图连起来,并调整好参数,搭配好注释:

像往常一样将函数连接到BeginPlay,运行一下,发现没有任何效果,是坏了么?
当然不是,首先观察一下蓝图中,有一个关键的detla值是没有被赋值的。别着急,这个值会随时间变化,由于还有其他值随时间变化,我们会在处理时间的时候一起解决。这里我们可以先做临时处理,验证一下可用性。

其次,判断键盘点击的函数应该连在每帧运行的Tick上,我们调整一下后再次运行,点击鼠标左键后,发现左下角的进度条发生了变化:

【完成!】
先分析需求:
1、当释放鼠标左键时,推动球体
2、推动的力取决于力度和角度
创建相关函数MouseButtonUp和变量IsMove(boolean类型),这个变量的默认值是true,是用来判断球体是否正在运动,或是完全停止。当球体正在运动的时候,是不会对球体施加力的,如果发现球体不动,有可能是这个值没有设置正确。

为该需求添加有关节点,根据需求将节点分为了3组:



将上述所有蓝图连起来,并调整好参数,搭配好注释:

在这里解释一下底部的减去0.5的作用。这里处理的值是球体向左向右的方向,正中间为0,通过减去0.5调整为为左右方向的力。而高度值由于一定在地面之上不会出现负数,所以不用修正。
将函数加入Tick中运行一下,发现小球像是被踢了一脚,功能达成!
【完成!】
上一个函数中用到了DirectBar两个进度条值,但是这两个进度条都没有被赋值,于是接下来补充一下。
先分析需求:
鼠标移动控制球体发生方向
这次的内容比较简单,主要目的是赋值,添加一下所需的函数Mouse Move UpDown\Mouse Move LeftRight。
为需求添加相关节点,分为2组:


将上述所有蓝图连起来,并调整好参数,搭配好注释,两个函数就完成了:


上述给出的乘法公式中上下左右有-0.05的修正,这是用来修正负关系,以及让鼠标的值更加缓慢的控制百分比值。有兴趣的同学可以将值更改为1,在游戏中看一下效果。
将两个蓝图连接上Tick后就可以在游戏中进行测试了,如果测试过程中发现有XY颠倒,或者是值的变化与鼠标移动相反的,可以自行检查修复,如果有解决不了的问题,请私信我哦!
运行后就可以控制球球的方向进行弹射啦!

飞出去咯!你别说,还挺有趣!
【完成!】
基本功能已经完成了,下面该轮到游戏外围的规则了。
分析需求:
1、判断球体是否停止
2、如果停止则摧毁球体
3、重置各值
根据需求先建立所需函数IsTurnOver。

需要添加以下节点,根据逻辑分为3组:



将上述所有蓝图连起来,并调整好参数,搭配好注释:

这个函数目前无法测试,因为需要的条件是必须球体在移动状态停止下来,且变量IsMove开启时才可以实现。
【完成!未测试】
分析需求:
在盒体全部倒下的情况下,游戏胜利
根据需求新建所需函数Is Win以及一个局部变量flag,注意,这里使用的是局部变量,需要在local variables中建立。

大致解释一下全局变量和局部变量。全局的是在所有蓝图中都能看到的变量,局部是只在一个蓝图中可以看到和使用的变量。
整理以下节点:



最后一个特殊的节点,需要通过详情添加一个输出的布尔值。
将上述所有蓝图连起来,并调整好参数,搭配好注释:

该函数等待后续测试。
【完成!未测试】
分析需求:
当游戏结束时,显示“Game Over”,并标记为结束
先添加函数GameOVer,和变量IsOver,该变量用于识别当前游戏是否结束。

需要用到的节点如下:

将上述所有蓝图连起来,并调整好参数,由于内容比较简单,没有添加注释:

该函数等待后续测试。
【完成!未测试】
在之前的函数测试中,我们也尝试了将写好的函数连接在BeginPlay和Tick上进行运行。目前所有函数都已经完成了,我们再正式的将他们连接起。
首先是BeginPlay,在刚开始运行游戏时,我们会用到的函数及逻辑内容是:
1、初始化球体Create Ball
2、初始化盒体Create Boxes
3、初始化UI Set UI
4、屏幕显示开始后一会,文字消失
由于用到的节点简单,这里直接上蓝图:

这里用到了一个delay的常用节点,用法也比较简单,就是延迟xx时长之后运行下一个节点。
编译后进入游戏运行一下,没有任何问题。
【完成!】
接下来是Tick,Tick会在每一帧运行时进行一次判断,这些判断中包括:
1、鼠标左键按下MouseButtonDown
2、鼠标左键抬起MouseButtonUp
3、鼠标移动MouseMoveUpDown/MouseMoveLeftRight
4、球体是否停止Is Turn Over
由于节点简单,这里也是直接上蓝图:

有两个点需要注意一下:
第一个是为了减少无效计算,给Delta赋值的节点放在了条件节点后面。
第二个是在函数没有前后因果的情况下,使用序列将所有节点并联,相互之间不影响。
这个蓝图中处理了之前一直看到但是没有用到的Delta。这个值之前有提过,这里再说明一下。这个值用在这里,并不会对目前的内容有任何影响。它的用途是通过统一的时间去修正不同的设备之前帧率差别。
举一个不恰当的例子,两个人跑步,步伐一定不会完全一致,所以到达终点的时间一定不一致。但是如果不看表,这个人每跑一步,都会有人去量他的步长,去换算他的时长。结果就是无论有多少个人,无论腿长腿短,只要跑的距离相同,时间就一定相同。
目前所有的大块逻辑都已经完成了,但是运行之后发现还是无法正常运作。这是因为用于串联的关键条件还没有连接清楚。
于是我们全局再考虑一下布尔值开关改变及判定的关键位置:
1、刚进入游戏时,球体未发射且运动停止,允许运行发射的函数
2、释放鼠标时,球体已发射,但目前球还没动,此时要等待球体动起来后,再判定是否静止,游戏未结束
3、球体已发射且运动停止时,判断盒体情况,如果未全倒下则重置球体,如果全倒下则游戏结束
综上所述,一共有三个布尔值开关用于判定:
1、球体是否运动IsMove(已有),true为运动,false为停止
2、游戏是否结束IsOver(已有),true为结束,false为继续
3、球体是否发射IsShot(未有),true为发射,false为未发射
有两种办法添加布尔值开关:
第一种是添加全局布尔值,和上面两个一样,这种办法用于在多个函数中修改此布尔值。
第二种是添加局部变量,可以在一个函数中进行修改。这种办法的好处在于该局部变量只由一个函数操控,全局上更干净。
这里由于球体是否发射只需要通过MouseButtonUp控制,也就是只需要在抬起按钮时判定是否对球体施加力即可。在确定球体发射后,等待球体运动,再进行静止判断。
所以在MouseButtonUp中添加一个局部变量IsShot,并将这个值的结果作为MouseButtonUp的传出值,用于激活下一次的静止判断。调整蓝图如下:

打开MouseButtonUp,添加一个局部变量。

如果不施加压力就不改变值,施加就更改值为true,最后返回值。
打开Tick事件所在蓝图,做以下修改:

当运行到按钮抬起时,如果是起作用的,则延迟1秒后将运动的布尔判断值更改为true。
可能有些同学做到这里出现了一个疑问(比如我),就是从逻辑上看,这个IsShot其实是用于控制IsMove的,可是为什么还需要多一个局部变量。这个问题我尝试了一下后发现,在函数蓝图中,是不能添加Delay节点的,至于为什么,烦请有大佬知道的,务必私信我为我答疑解惑!
然后还有最后一步,就是如果盒子没有全部倒下,就再次生成一个球球,如果全部倒下,游戏结束。需要修改IsTurnOver蓝图如下:

整理一下所有的内容后运行,实际检查一下,检查报错情况,检查逻辑是否正确,检查参数是否合理。上述的贴出的蓝图中大体上没有什么问题,但是细节上有一些不合理的地方,后续又进行了一些修改和调整,这里就不一一列出了。同学们可以自己尝试调整一下,如果遇到无法解决的问题,也可以私信我,一起讨论debug,找出问题所在。

至此,这个简陋的保龄球游戏就基本完成了,在这个基础上,还可以调整的更有趣,比如将下方的力度和方向变成球体上的箭头指示,比如盒体不是盒体,而是一个个移动的小汽车,比如球球还有各种技能,可以在空中加速等。这些乐趣都等待着游戏人一点点挖掘。
【这么长的教程,可以看到这里,衷心向你表示感谢!】
【你的支持是我前进的动力!有任何问题请回复或私信留言!】

一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame
Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u
修改(澄清问题)我已经花了几天时间试图弄清楚如何从Facebook游戏中抓取特定信息;但是,我遇到了一堵又一堵砖墙。据我所知,主要问题如下。我可以使用Chrome的检查元素工具手动查找我需要的html-它似乎位于iframe中。但是,当我尝试抓取该iframe时,它是空的(属性除外):如果我使用浏览器的“查看页面源代码”工具,这与我看到的输出相同。我不明白为什么我看不到iframe中的数据。答案不是它是由AJAX之后添加的。(我知道这既是因为“查看页面源代码”可以读取Ajax添加的数据,也是因为我有b/c我一直等到我可以看到数据页面之后才抓取它,但它仍然不存在)。发生这种情况是因为
关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于StackOverflow来说是偏离主题的,因为它们往往会吸引自以为是的答案和垃圾邮件。相反,describetheproblem以及迄今为止为解决该问题所做的工作。关闭9年前。Improvethisquestion是否有适用于这些的3d游戏引擎?
所以我看到unity支持c#、JS和Boo。我可以学习其中一个,但我想制作一个“编译器”或类似的东西,让我可以编写ruby代码并输出JS代码或制作一个可以被Unity编译器读取的层。这有可能吗?我愿意在这方面投入很多时间并且有相当多的经验。 最佳答案 如果您的问题实际上是“我如何将Ruby编译为JavaScript”,那么这更容易回答:Opal:RubytoJavaScriptcompiler但是,学习其中一种受支持的语言会更好。当运行的是用另一种语言解释的代码时,很难调试“您的”代码。
文章目录认识unity打包目录结构游戏逆向流程Unity游戏攻击面可被攻击原因mono的打包建议方案锁血飞天无限金币攻击力翻倍以上统称内存挂透视自瞄压枪瞬移内购破解Unity游戏防御开发时注意数据安全接入第三方反作弊系统外挂检测思路狠人自爆实战查看目录结构用il2cppdumper例子2-森林whoishe后记认识unity打包目录结构dll一般很大,因为里面是所有的游戏功能编译成的二进制码游戏逆向流程开发人员代码被编译打包到GameAssembly.dll中使用il2ppDumper工具,并借助游戏名_Data\il2cpp_data\Metadata\global-metadata.dat
背包是游戏中经常使用的一个组件,它负责管理玩家在游戏中所获得的道具。一个完整的背包系统应当具有将物品放置进背包、对背包内物品进行管理和使用背包内物品等功能。而往往一个背包系统的逻辑关系较为复杂,如果把所有功能都放在一个脚本中实现会使代码显得十分冗杂且缺乏逻辑。所以在背包系统的设计过程中,我们常将其分解为数据、逻辑和UI三部分分别来进行完成。一、UI设计以CottonPuzzle中的背包设计为例,我们需要有物品展示栏、物品切换按键和物品提示信息等部分。在Canvas中创建ItemHolder,在ItemHolder中创建LeftButton和RightButton控制物品的左右切换、Slot来控
关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。关闭7年前。Improvethisquestion我发现很难调查使用Ruby进行游戏编程的选项。其他帖子和文章中提到的几个包装器和框架不再维护或使用。Gosu/Ruby似乎仍然活跃:官方论坛上的讨论一直很稳定。是否还有其他积极维护的ruby游戏框架?编辑:我发现使用MacRuby进行大量游戏开发。
我正在玩用Ruby编写MUD/文本冒险(请不要笑)。谁能给我任何关于解析输入文本的优雅的、基于oop的解决方案的建议?我们在这里谈论的只是“把魔杖放在table上”更复杂的事情。但是一切都需要柔软;我想稍后轻松地扩展命令集。我目前的想法,稍微简化一下:每个项目类别(盒子、table、房间、播放器)都知道如何识别“属于”它的命令。游戏类理解一种特定于领域的语言,涉及诸如“将对象X移入对象Y”、“显示对象X的描述”等Action。游戏类询问房间中的每个项目是否识别输入命令。先说是赢。然后它将控制传递给项目类中处理命令的方法。此方法重新表述DSL中的命令,将其传递回游戏对象以实现它。必须有一
问题有没有可以保持的最佳值(value),这样我才能赢得尽可能多的比赛?如果是这样,那是什么?编辑:是否可以为给定的限制计算出确切的获胜概率,而与对手的所作所为无关?(自大学以来,我还没有做过概率和统计)。我有兴趣将其作为与模拟结果进行对比的答案。编辑:修复了我算法中的错误,更新了结果表。背景我一直在玩改进的二十一点游戏,其中对标准规则进行了一些相当烦人的规则调整。我已将与标准二十一点规则不同的规则斜体化,并为不熟悉的人添加了二十一点规则。修改二十一点规则正是两个人类玩家(经销商无关)每个玩家面朝下发两张牌双方玩家_ever_都不知道对手纸牌的_any_的值在_both_完成手牌之前,