惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Fortinet All Blogs
Microsoft Security Blog
Microsoft Security Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Vercel News
Vercel News
Application and Cybersecurity Blog
Application and Cybersecurity Blog
C
Check Point Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
W
WeLiveSecurity
The Hacker News
The Hacker News
L
LINUX DO - 热门话题
T
Tenable Blog
Hugging Face - Blog
Hugging Face - Blog
Google Online Security Blog
Google Online Security Blog
博客园 - Franky
P
Proofpoint News Feed
H
Hacker News: Front Page
P
Privacy & Cybersecurity Law Blog
月光博客
月光博客
P
Proofpoint News Feed
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
The GitHub Blog
The GitHub Blog
云风的 BLOG
云风的 BLOG
博客园_首页
www.infosecurity-magazine.com
www.infosecurity-magazine.com
C
CERT Recently Published Vulnerability Notes
Forbes - Security
Forbes - Security
I
InfoQ
Stack Overflow Blog
Stack Overflow Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Attack and Defense Labs
Attack and Defense Labs
N
News and Events Feed by Topic
博客园 - 叶小钗
T
Threat Research - Cisco Blogs
aimingoo的专栏
aimingoo的专栏
D
Darknet – Hacking Tools, Hacker News & Cyber Security
小众软件
小众软件
大猫的无限游戏
大猫的无限游戏
MongoDB | Blog
MongoDB | Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Hacker News - Newest:
Hacker News - Newest: "LLM"
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 司徒正美
O
OpenAI News
G
Google Developers Blog
Martin Fowler
Martin Fowler
罗磊的独立博客
S
SegmentFault 最新的问题
T
Tor Project blog
量子位

机核

游戏性能旗舰最强之选,一加 Ace 6 至尊版国补到手价2999元起 6元钱自己更换电动车刹车线 《生化危机9:安魂曲》编剧Haris Orkin专访 摸金游戏?音乐游戏!暗区新赛季这把能弹的琴有何来历? 好评国产武侠SRPG《息风谷战略》免费DLC现已推出 | 机核 GCORES 碎片 《生化危机:安魂曲》将于5月实装首个免费更新内容 | 机核 GCORES 新锐东方游戏,谱写世界新章! 沉浸式恋爱视觉小说游戏《心象演算》免费试玩版现已正式上线 | 机核 GCORES 互动影游《代号三国:龙起》上线!穿越三国与曹操并肩、与佳人同行、与权谋博弈! 《老头收集梦想生活》,游戏酒桌会6,录音笔VOL.689 | 机核 GCORES 破界·共生——《白日梦:无限世界》五大核心游戏特点解析 价格已到史低,锐龙5 9600X/锐龙7 9700X正适合抄底 时间循环之旅即刻启程!《归环》一周目测试今日开启 热门在线韩游变魂游,洛奇衍生作能否打破“花瓶”魔咒? LG UltraGear evo 全新高端显示器系列:当“5K”遇见“AI”,不止强大,更懂热爱 反套路三国互动影游《代号三国:龙起》今日上线! 愿望单登记人数突破10万!备受瞩目的“女儿养成游戏” 《梦幻魔法公主》今日于Steam平台上线!限时八折优惠中 《黑神话:悟空》全球音乐会2026巡演将于4月29日12时开票 | 机核 GCORES “Snowguelike”生存肉鸽挖矿新作《蛙穿雪境》公布发售日期,5月7日正式上线 | 机核 GCORES 重塑移动办公、AI创作新境!全新华硕灵耀Air系列、ProArt 骁龙版震撼首发,创芯未来 均分88:《Saros》媒体评分汇总 | 机核 GCORES 上海烛龙公布合作遗迹探险游戏《吉时已到》首支预告片 | 机核 GCORES 首个独立游戏《萝薇日记》已上线Steam! 烛龙新IP《吉时已到》首曝,打造国内首款中式合作遗迹探险游戏 喜加一:《暗黑破坏神Ⅳ》国服现已开启限时免费领取本体活动 | 机核 GCORES 新版《生化危机》电影定于9月18日上映,官方网站现已上线 | 机核 GCORES 《冲就完事模拟器2》“星球大战”联动DLC正式宣布 | 机核 GCORES SteamController将于5月4日发售,售价99美元 | 机核 GCORES 事已至此,内存这么用也算省钱了,“2+1”非对称双通道应用实测 505游戏母公司现已收购《明末:渊虚之羽》IP | 机核 GCORES 基石 手动杂谈12|格斗游戏也能讲好故事 | 机核 GCORES I Love You Mr Snowball 我的向日葵小姐 会比GTA6先发售吗?最硬核的生存游戏《DAYZ》要推新DLC了 周记02:在2026遇到新怪谈 什么硬件,能让游戏Loading界面快速消失? | 机核 GCORES GadioSpec《百年风云世界杯》免费试听集 | 机核 GCORES 百年风云世界杯Vol.1丨足球崛起 | 机核 GCORES 100年前,一群人提前替我们经历了AI恐惧 | 机核 GCORES 《呼啸山庄》2026 ——一辆当代艺术的大卡车冲撞了我的大脑 手游《天穗之咲稻姬:日之香巡灵传》宣布将于7月27日停服 | 机核 GCORES 集结梦之队,征战世界杯,《最佳球会ONLINE》上线Steam 山水绝景随心拼 休闲建造游戏《千里山河录》Steam商店页公开 巫师帽、法袍、魔杖,为什么它们是影视、游戏里的法师必备三件套? 《无鞘信使》-第一章 复古风自动战斗肉鸽《终结之终结》Steam商店页面现已上线 电脑里有一款不破不立的MMO,录音笔VOL.688 | 机核 GCORES 《生化危机》30周年纪念周边发售,这次是真的“保护伞” 可靠耐用+AI全能,惠普战66 2025锐龙版深度体验 《时之书:无尽终章》关卡“大航海时代”全球首次公开 经典名作《乌龙派出所》改编经营模拟游戏《乌龙派出所~阿两的商店街物语~》正式宣布支持简体中文 明日开冲,解锁反套路三国互动影游《代号三国:龙起》即将上线 独立游戏《这是我的宝藏!》已发售~ 降低难度不是唯一解,无压力死亡也是好体验 Netflix官宣新片《普通人》:讲述韩国现代史上的权力风暴 《生化危机:安魂曲》全球销量现已突破700万份 《绝地鸭卫》PC版5月15日正式发售 亡妻回忆录?女性向情感叙事游戏《S-mail》现已正式发售 全新酷黑风格,酷睿Ultra 200S PLUS的高性价比搭档!七彩虹BATTLE-AX B860M-PLUS S WIFI7 V20 超级黑刃主板测评 胖狗 索尼发布了港台地区PS5产品价格调整公告,将于5月1日起实施 二次元怪猎+性感美女!《碧蓝幻想:无尽黄昏》开启Beta公测 这款怪谈类型中式恐怖游戏居然更新了?! 《百日战纪 -最终防卫学园-》改编漫画将于今年冬季开启连载 《如被附身,请致电我们》:匈牙利黑色幽默恐怖小说 《竹屿山房杂部卷五》(译文) 大树 超越引擎 摄影分享丨四月 碎片杂记vol.72 当老式FPS与老式动画技术碰撞出新时代的火花—《神探杰克鼠》 想做独游,如何避免首个项目就褒姒? 在线多人动作游戏《OCTOPinbs》将于5月12日上线Steam! 《酒鬼女神的酒诡》确定将于2026年登陆Steam 《CRYMELIGHT》将于11月5日(周四)正式发售!4月25日开始预购! 钢铁国度MKI部落Evolution,蛮兵部分 【钢铁国度】部落-Primal MK I,蛮兵部分 这是一个高中生用ai跑出来的作品,我自称他为物理神话 钢铁国度MKI部落Metamorphosis,蛮兵部分上 钢铁国度MKI部落Metamorphosis,蛮兵部分下 INDIE Live Expo于4月25日举办:首发9款新作,超200款独立游戏亮相 暗黑卡牌策略新作《魔忌:穷鼠啮狸》发布全新中文试玩版和发售预告片,4月30日正式上线在即 战锤40K长篇小说:变节者・苦难主宰(三)(全书完) 战锤40K长篇小说:变节者・苦难主宰(二) 战锤40K长篇小说:变节者・苦难主宰(一) 英语语言学习丨短语专题1:短语的特点 唯一的EVE战争 【少前同人】【M200】战术人形会梦见音乐会吗 【昏迷3】即将发售,前作主角悉数到场!“恶灵”宋老师化身可操控角色 业内人士:游戏公司“十有八九”使用生成式AI,包括卡普空 欢庆一周年:《光与影:33号远征队》全新纪念艺术图、超值折扣与免费更新同步上线 才刚刚开始呢 【抽奖】《星际卡车司机》推出免费大型更新,四折平史低折扣进行中 四人合作FPS游戏《佣兵猎手》抢先体验重大更新 1 现已上线 Raw Fury新作《深馅地牢 Deep Dish Dungeon》将于今秋加入 XGP 塔防幸存者游戏《魔怪来袭》推出首个 DLC 《饿狼传说:群狼之城》1周年纪念!新DLC“沃尔夫冈·克劳萨”今日参战 猫狗相伴 欢乐闯关 双人合作平台跳跃游戏《猫狗同行》上线Steam商店页 《同行:月球逃脱》(Together: Moon Escape)上线将于明日上线Steam
Godot入门到弃坑:主角登场
cameLcAsE · 2024-02-21 · via 机核

欢迎到合集查看之前的GDScript基础部分内容

这篇文章中,我们来学习如何构建一个2D平台跳跃游戏的主角。我们终于要运用前面所学的各种知识,来构建游戏中最常见、最基础也是最必要的元素了!激不激动?

我们先来看用我们现在已经学到的知识能够做到哪种程度。

毕竟我们算是正式开始做游戏了,我们可以新建一个项目,然后新建一个空的2D主场景。

最后再新建一个2D场景(暂时以Node2D作为根节点)作为主角单独的场景。假设我们只知道前面讲解过的节点,那么主角首先得有一个sprite作为它的图像表示,因此给根节点加上一个Sprite2D节点。保存场景,重命名为Player或者你喜欢的名字。然后给它一个脚本。

下面我们导入需要用的素材,机器人logo也还算好看但不可能一直用下去。这里我们用在Godot官方文档中也出现过的素材。

这个小狐狸来自由ansimuz创作的Sunny Land系列素材包。许可为CC0。

内容因剧透、敏感不适等原因被隐藏

点击查看

其实一开始我也考虑过用Keney的素材库。但是Keney的2D素材的动画帧太少了,做出来可能没有那么“有成就感”。

下载这个小狐狸素材。下载完成后直接拖入Godot的文件系统面板中即可开始导入。当然你也可以导入你自己喜欢的素材。不过要记得建好文件夹,好好整理素材!这里方便起见直接把所有素材都导入到Godot中(其实PSD文件夹可以删掉)。

我们将PNG/sprites/player/idle文件夹中的player-idle-1作为我们的玩家场景的Sprite2D的纹理,拖动到检视面板Texture处。此时检视面板会显示成这样:

此时在2D场景视图中看起来是这样的:

莫名其妙地有点糊。这里需要修改一个设置。在Project/Project Setting对话框中,找到Rendering/Textures,把右侧的Default Texture Filter(默认纹理过滤器)从Linear改成Nearest,我们就可以看到场景中的小狐狸没有那么糊了。

目前Sprites2D节点虽然位置位于(0, 0),但是实际上(0, 0)在狐狸的脑门上。为了便于后续的操作,我们把Sprite2D的Y坐标降低16个单位(这套素材的尺寸为33x32),这样它大概就位于地面(姑且将Y=0的平面视作地面)上了。

Position是相对位置

节点的position属性是位置,不过更准确地说是相对于父节点的位置,文档中也是这样描述的。

想象一辆在行驶过程中的汽车。汽车载着我不断前进,汽车的position相对于世界(场景)来说在不断变化。我作为汽车的子节点,如果我坐在座位上(0, 0)处不动,我的position就一直是(0, 0)——尽管我相对场景来说也在移动。如果我从座位上站起来向右走到(1, 0),这实际上也是相对于汽车来说的位置。

在某些情况下我们可能需要获得“绝对位置”即在场景中的确切位置而不是相对于父节点的位置,这时需要用到一些函数来转换。当然具体如何求得这些值是实现细节(当然要用到线性代数的知识),我们在现阶段可以忽略。

另一方面,我们的玩家场景用了一个没有任何图像呈现的Node2D节点作为根节点(而不是直接用Sprite2D)也是为了方便调整sprite的位置而不会影响整个场景(在其他场景中)的位置。当然直接用Sprite2D作为根节点也可以,要调整sprite到地面上就必须调整Offset(偏移)属性。

调整缩放方式

这套素材是按照像素游戏的尺寸来做的。如果我们把Player节点放到主场景中,大概率它会显得特别小。注意场景中的紫色框,它是作为游戏画面尺寸的参照物的:

这里需要调整一下窗口的设置。在Project/Project Settings中找到Display部分。右侧的Viewport Height/Width分别代表视口的高度和宽度。默认的数字对于我们的素材来说太大了。把它修改成一个较小的数字,例如384x216。

还没完,在下方的Stretch部分修改Mode为canvas_item,其他可以保持不变。

现在启动游戏,窗口变小了,但是如果我们拉伸或者直接最大化它,我们可以看到画面会根据设置直接放大,我们就能看清楚了。也可以在刚才的设置里直接让它以最大化窗口启动。

实现水平方向移动

在早先的文章中已经介绍到了如何接收输入并移动节点。我们先在Project/Project Settings/Input Map中添加两个新的输入映射作为左右移动的按键设置。你可以随意设置自己喜欢的按键,也可以连接手柄设置手柄的按键。

之前我们直接在process中每帧处理输入,但是考虑到处理操作的代码会逐渐增多,我们把处理输入的代码写到一个单独的方法中。同时为了便于随时修改角色的移动速度,我们用一个export变量把速度暴露到编辑器中。目前的代码大概是这样:

永远要记得,不要追求和我的代码一模一样。你在创作你自己的游戏!即使你想要达成同样的效果,你的代码也有无数种写法,而我的写法也不一定是最好的!我只能引导你思考,但是不能代替你思考!

注意Input中有两个名字类似的方法的区别。pressed和just_pressed。pressed在按下一个键(不松开)后会持续返回true;just_pressed顾名思义是刚按下的时候才会返回true,要松开再按下才会再次返回true。

现在我们来思考一个之前一直忽略的问题。process会每帧调用。但是玩家朋友们大多知道,每台设备的硬件水平不一样,玩游戏的帧率也不一样。这就造成一个问题,如果一台设备以每秒90帧的速度运行这个游戏,那么一秒钟process就会大概调用90次,那么一秒之内这个节点就会移动90x5个单位;另一台电脑差一点,每秒85帧,那么它移动得就会慢一点!这显然不是我们想要的结果,一般来说我们不希望玩家玩同一个游戏时能因为电脑更好而在游戏中获得明显优势!

显然process有一个参数,这个参数是什么意思呢。它是两次调用process之间的时间差。如果单位时间内一台设备运行游戏的速度越快,那么单位时间内process调用次数就越多,进而delta就越小——每次移动它时就应该少移动一点;相反,慢一点的设备就应该在同样的时间内移动更多才能赶上快的设备!因此为了解决这个问题,我们就要乘上delta来作为移动的距离。由于delta是一个很小的数,因此speed也必须得改大一点:

动起来

好吧,现在能动了,但是又没动。无论怎么移动这个小狐狸都是一个样——我们明明有那么多素材!

容易想到的是,我们可以在不同的状态下设置Sprite2D的texture属性为不同的素材,这样就可以让角色有不同的视觉效果了。

我们需要用一个变量来记录角色当前的状态。比如它是否在移动,是否在跳跃。这样我们就可以根据其状态来显示不同的图片。角色的状态在一瞬间必然是几种状态中的一个,这就暗示我们可以用枚举类型来表示。定义一个State枚举,暂时给它两个状态。然后定义一个变量来表示角色的当前状态。

为了方便,我们还可以定义一个export变量,这样我们就可以直接把素材拖到检视面板中同时也可以在代码中引用:

我们把素材文件夹中的player/player-idle-1.png拖到检视面板的Run Texture槽中。提醒一下,如果发现检视面板中没有出现Run Texture,先保存一下编辑后的脚本。

由于我们在从跑动的状态转换回闲置状态后需要切换会闲置的小狐狸素材,这里给闲置的素材也准备一个export变量便于编辑。此外还需要在代码中创建一个onready变量用于引用Sprite2D节点。于是乎我们现在有了这几个带标记的变量:

如果忘记了onready和引用节点的相关内容,可以复习一下前面的文章:

接下来需要适时改变状态。道理很简单,任意方向移动时我们修改状态为RUN,否则就是IDLE:

注意和前面相比修改了哪些地方。由于在没有任何操作的情况下就相当于默认是IDLE状态,所以我在最后加上了这一句。而前面处理不同操作的if中我加上了return来提早结束方法的执行。

接受完输入、状态修改完毕后,我们在最后调用播放动画的方法来修改图片:

这里用的是match语句(见上一篇文章),当然用if也不是不行。由于目前就简单两个状态,也不用写通配符来兜底了。不同的状态下我们设置不同的素材。效果大概是这样:

嗯……算是动起来了,但是有两个问题。

第一我们只能“朝一个方向跑”,这里我的意思是即使我们往左走还是播的向右的素材。毕竟素材里只有向右的版本。

第二我们怎么只有一帧动画!

我们逐一解决。

翻转

很多时候2D素材在左右两个方向只有一个方向的。如果角色是左右对称的其实直接水平翻转一下也不会露馅。

要翻转sprite,我们需要关注Sprite2D的两个flip_属性。flip就是我们所说的翻转,而flip_h和flip_v分别对应水平(horizontal-ly)和垂直(vertical-ly)翻转。这两个属性都是bool类型,可以修改它们来控制sprite翻转与否。

由于素材的默认方向是朝右,所以要朝左时我们就进行水平翻转。否则就不翻转:

没错,只需要加上这两行代码就好了。

多帧动画

素材包里自然有多帧跑动动画。我们知道如何export单个Texture2D变量,但是如何引用多个素材呢?这就要用到上一篇文章中提到的“有类型的数组”。这里我们新建一个Texture2D数组并将其export:

这样检视面板中的属性就变成了这样:

我们可以添加多个纹理到里面。这里有两种操作方式。第一种可以点击检视面板中的这个条目,显示详细内容之后按按钮添加新的条目,然后一个一个拖进去。

也可以直接选中多个纹理直接拖进去。显然这种要简单不少。

接下来要思考如何在代码中让它们逐个播放。

利用数组的各种属性和方法,可以用索引去引用其中的纹理。那么我们的问题就变成了找到一个方法“隔一段时间给索引加一,然后又变回0,周而复始”。

在Godot中计时的方法其实不止一种,比如我们可以在process中根据delta记录时间,然后按照我们需要的频率来检查是否被整除。也可以直接调用函数获取操作系统时间。不过这里我们学习一个专门用来计时的节点:Timer(计时器)。

打开玩家的场景,添加新的节点,搜索Timer并添加。计时器节点的图标是一个沙漏。

选中节点树中的Timer节点,在右侧的检视面板中可以看到它的各种属性。第一个属性暂时忽略,它是设置要在何时更新计时器。

Wait Time(等待时间)是我们要关注的。它用于控制计时器多久报告一次,也就是我们需要的“隔多久”。我们把它修改得稍微小一点比如0.1,这样几帧的动画看起来会连贯一些。

One Shot(一次性)用于控制在等待时间结束时计时器是停止还是重新计时。我们需要隔一定时间调整动画帧索引,所以我们需要重新计时而不是一次性的。

Autostart顾名思义是自动启动。由于我们在需要播放动画时才启动所以这里不需要勾选。

添加引用计时器的onready变量以便在后续代码中使用。现在我们准备在ready方法中对计时器进行一些设置。

还有一个问题,我们有计时器了,也设置了时间间隔。但是我们怎么在“计时器到点时执行代码更新索引”呢?

Godot的事件系统:信号

在上一篇文章中我提到函数也是对象,并且可以通过变量引用。我们马上就要了解到它的实际用途。

Timer节点有一个“属性”叫timeout(这个一般翻译叫“超时”,但实际上叫“时间到”比较好理解)。文档中提到“在计时器到0时发出”。

实际上这是一个信号(signal)。在某种条件下各种节点会发出各种信号告诉各个节点发生了某种事件。当然,场景中必然存在很多节点,一个节点只会对某些特定的事件(信号)感兴趣。因此我们需要告诉信号,“我对这个事情很感兴趣,请在发出信号时告诉我”。

信号有一个connect(连接)方法,信号发出时,它会告诉所有和它连接的东西。那么什么东西才能和信号连接呢。

connect方法的必填参数的类型是Callable。这就是我说的“表示函数的类型”。也就是说我们要在这里传入一个函数。信号发出时,所有连接到信号的函数都会被调用,我们就可以得知事件的发生。

连接信号时,需要注意函数签名(参数列表和返回类型)必须和信号匹配。例如计时器的timeout信号在文档中是这样写的:

这个括号就表示它没有参数。实际上也没有返回值。因为和信号连接的函数都是通过信号调用的,它根本不关心你返回什么,它只负责告诉你什么时候调用。

以另一个信号为例,这个信号会在子节点进入场景树时发出。它就有一个代表这个子节点的node参数:

因此我们连接到信号的函数就可以使用这些信号交给我们的参数,并按需进行进一步的处理。

现在编写一个方法,用于处理计时器发出的到点信号。比如叫它timer_timeout,我们就这样把它连接到计时器上:

注意不要加括号!编辑器的自动补全会为你加上括号,但是这里不需要。因为加上括号的意思是我们要调用这个函数,不加括号把函数名给它才叫“以函数为参数传递”。

为了测试一下有没有正常工作,我们可以在这个函数中写个print,然后勾选自动启动(也可以在ready中调用timer的start方法启动),启动游戏检查一下!

这里顺带一提,如果要连接到信号的函数比较简单甚至是只会在这里使用的“一次性函数”,可以像这样写:

这种简单的、没有名字的函数写法叫lambda(λ)。直观上来看它和一般函数定义的区别在于它没有名字(有些编程语言中也称之匿名函数,但是在某些语言中匿名函数和lambda有微妙的区别),并且可以把冒号和函数主体(很多时候只有一句)写到一行。

另外,我们也可以用编辑器帮我们连接信号。选中某个节点时,右侧的检视面板旁边的Node标签页中默认显示的就是节点的信号。可以折叠的行实际上展示了下列的信号来自哪个类型的定义。节点的基类中定义的信号也会显示在这里。

双击信号名称就会弹出对话框。首先要选择连接到哪个节点的脚本,默认情况下一般就是打开的脚本。下面的Receiver Method(接收者方法)用于选择要连接哪个方法。默认是以_on_节点名_信号名为名称新建一个方法。右侧的Pick(选择)按钮可以选择一个方法签名兼容的现有方法。然后点击connect就连上了。这里和我们写代码调用connect方法的作用是相同的。

信号也可以自行定义。它的定义方式和函数类似,只不过func改成了signal,并且不需要定义函数主体:

发出信号时使用信号的emit方法,并传入适当的参数。这样所有连接到这个信号的方法就会被调用了。

目前暂不需要自定义信号,我们会在后续有必要时细说。

此外,C#在语法层面有自己的事件机制,用C#编写Godot脚本时除了可以用C#的事件/委托之外,也可以利用Godot提供的功能将C#事件作为Godot信号供GDScript使用。

内容因剧透、敏感不适等原因被隐藏

点击查看

至于Godot的事件系统为什么要叫信号,合理推测是根据Qt的信号机制来的。创始人早年应当有Qt开发经历。

回到主线。我们知道如何定时完成工作,接下来就让动画播起来。我们定义一个变量用于记录当前动画播到哪里了:

然后在计时器连接的函数中修改它即可:

我们首先定义一个局部变量用于引用当前在播放的那些帧。然后根据状态来给它赋值。由于目前只设置了跑动的动画帧所以其他的暂时不管。

由于我们只给跑动设置了那些帧,所以这里在match之后要判断它是不是为空。如果没有可以播的帧我们就直接返回不管了。

此外还要注意的是,由于给current_frames变量标注了类型,我们就没法给它初始化为null。数组的默认值是空数组([])。所以这里用is_empty方法来判断它是否为空,而不是用null来检查。

随后给帧索引加一,如果发现超出了当前动画的长度(注意索引从0开始因此这里是等于其size作为条件)就让它回到0。

随后我们编写一个play_animation方法,根据当前状态和帧索引播放。这个方法需要在process方法中,调用处理输入的方法之后调用:

状态为RUN时,我们根据当前帧索引获得那一帧的纹理然后设置。

运行游戏,看看效果!

我们左右翻转的代码也是正常工作的!

按照同样的套路,你可以给角色添加更多的动画。但是你会说,哇,就播个动画也太麻烦了。这才一个动画呢,就搞了这么多事情!

帧动画的正确做法

关于动画,上面的做法是最笨的办法——当然层层面纱之后什么东西都是“笨”的。

要播放帧动画的sprite有一个更合适的节点那就是AnimatedSprite2D。这个节点自带播放帧动画的相关功能。

停,先别急着一边骂我一边新建场景添加节点。我们可以直接在Sprite2D节点上右键选择Change Type(更改类型)把它改成AnimatedSprite2D就好。

如果启动游戏,你会发现报错了。

由于进行了类型标注,所以之前的sprite变量的类型不兼容了。需要把它改成AnimatedSprite2D。

由于AnimatedSprite2D的存在,我们也不需要Timer和相关计时代码了!删掉!

我们在开发过程中可能会不止一次修改场景树的结构,每当结构发生改变,通过节点路径引用节点的变量就会在运行时报错。但是对于一些有特殊性的节点,比如我们这里的sprite,我们可以给它取一个专用的名字比如(Sprite),然后通过右键菜单标记:

这样一来,在场景变得复杂时,我们在代码中可以直接以下述形式直接找到它而不需要用复杂且写死的路径来引用它:

$改成了%,这表示我们在使用Unique Name引用节点。

我们接下来配置一下AnimatedSprite2D节点的相关属性并修改代码。

可以看到Sprite节点旁边有个警告标志,它说我们没有设置它的Frames(帧)。我们就给它设置吧。视线来到右边的检视面板:

展开Animation部分,新建Sprite Frames。点击一下框中新出现的SpriteFrames,下方的SpriteFrames面板自动打开:

这就是我们编辑帧动画的面板。

default所在的列表是所有动画的列表。现在只有个default,为了和玩家角色的状态对应,我们把它改成idle。拖入player/idle文件夹中的帧到右侧的窗口中即可设置该动画的帧:

实际上我们现在切换到2D视图然后点击SpriteFrames面板上方的播放按钮就可以预览动画!

现在给它添加跑动的动画!点击这个按钮新建动画,取名为run:

接下来添加帧。这里用另一种方式来添加。有时候你的素材可能是一系列帧构成的单张图片,这种图片叫spritesheet(sheet是表的意思)。也可以用spritesheet来添加动画。点击这个按钮:

这个素材包中作者也包含了spritesheet的版本,所以选中这个文件:

弹出这个编辑器:

我们需要指定一些参数让它知道如何切分这几帧。看到右侧的输入框。

Horizontal和Vertical分别代表这个表有几行几列。显然这里是1行6列。所以Horizon改成6,Vertical改成1。由于这个spritesheet比较简单且规整,实际上这里两个参数设置好之后切分就算完成了:

如果spritesheet不够规整,我们可能就还需要调整右侧的各项参数比如按尺寸来设置切分。

然后按下Select All(选中全部)!这样才算选择了我们要添加哪些帧!如果spritesheet很大,可能我们只需要其中一部分的sprite,所以我们是可以手动选择需要的部分的。

选择完毕并添加。此时我们也可以在2D视图中预览。

使用AnimateSprite2D时我们就不能再直接设置texture属性进行动画切换了,这里全部交给这个节点自己的功能,所以这些代码全部都要修改。那些用于设置不同状态的纹理的export变量也不需要了!保存帧索引的变量也不需要了!

接下来只需要修改play_animation方法的代码即可。我们这里需要用AnimatedSprite2D的play方法并传入动画名称(SpriteFrames面板中我们给动画取的那些名字)即可播放对应动画!

代码变得更简单了!

运行游戏试一下!一切照常!并且我们还有了闲置动画!左右翻转的功能也依旧照常工作!

你还可以在SpriteFrames面板中编辑播放速度,注意这个面板要选中AnimatedSprite2D节点(你可能已经跟我一样把它改名成Sprite了)才会显示内容:

现在默认是5FPS。左侧的循环图标表示已启用自动循环播放动画。

实际上整个移动的实现又更方便、更地道的做法。我们下一篇文章再进行讲解!

没错,我又骗你用了笨办法。但是不管怎么说,在这篇文章中,我们学到了不少以后会用到的东西!

欢迎订阅合集、在评论留下你想问的问题!