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

推荐订阅源

P
Privacy International News Feed
爱范儿
爱范儿
H
Help Net Security
博客园 - 三生石上(FineUI控件)
Engineering at Meta
Engineering at Meta
WordPress大学
WordPress大学
博客园 - 叶小钗
Google DeepMind News
Google DeepMind News
GbyAI
GbyAI
T
Tenable Blog
Project Zero
Project Zero
腾讯CDC
Spread Privacy
Spread Privacy
V
Vulnerabilities – Threatpost
T
Threatpost
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Latest news
Latest news
L
Lohrmann on Cybersecurity
B
Blog RSS Feed
小众软件
小众软件
G
Google Developers Blog
T
Tor Project blog
P
Palo Alto Networks Blog
The Cloudflare Blog
Scott Helme
Scott Helme
D
Darknet – Hacking Tools, Hacker News & Cyber Security
A
Arctic Wolf
博客园 - 聂微东
AWS News Blog
AWS News Blog
L
LINUX DO - 热门话题
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
D
Docker
博客园 - Franky
Know Your Adversary
Know Your Adversary
人人都是产品经理
人人都是产品经理
博客园 - 【当耐特】
P
Privacy & Cybersecurity Law Blog
A
About on SuperTechFans
Cisco Talos Blog
Cisco Talos Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
量子位
C
Cisco Blogs
P
Proofpoint News Feed
雷峰网
雷峰网
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
B
Blog
Security Latest
Security Latest
C
Cybersecurity and Infrastructure Security Agency CISA
Jina AI
Jina AI
Y
Y Combinator 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-03-21 · via 机核

平台跳跃游戏中从平台上落入万丈深渊通常都伴随着死亡。有了前面积累的知识,实际上我们容易想到实现这种效果的做法。

最简单的做法就是在画面下方放一个足够宽的Area2D。当玩家碰到它的时候,我们就执行和死亡相关的代码。考虑到我们可能不止在有万丈深渊的地方放上这样的一个area——比如我们可以用它来实现其它的惩罚玩家的机制,我们把它作为一个单独的场景来创建。

新建场景,比如叫它KillArea(你叫它KillZone我觉得也可以)。由于它没有过多的花哨功能,一般来说也不会移动,所以我们可以直接把根节点改成Area2D。和之前一样,我们需要给它添加一个CollisionShape2D,具体的形状创建一个矩形就好。场景很简单,就这样:

把它直接放到我们的主场景中,调整它的大小,让它的宽度足以覆盖住场景——当然你也可以根据自己的需要来调整。简要复习一下。调整大小就是调整它的scale。除了在检视面板中的Transform栏中的Scale属性中调整(记得解锁X和Y同时调整),也可以在2D场景视口上方的工具按钮中选择缩放工具直接在场景中调整:

我的场景现在大概是这样:

CollisionShape的形状在启动游戏时不会显示,如果想要在游戏运行时也能看到它可以启用Debug菜单中的这一选项:

现在需要为KillArea添加脚本实现相关功能。

脚本不会太复杂,主要是连接上Area2D的body_entered信号——毕竟我们的玩家角色使用CharacterBody2D制作的。

碰到KillArea之后玩家就会死,死的时候有些什么要做的呢。首先我们想播放一个动画,素材里刚好有个可以用的素材叫player_hurt(虽然叫“受伤”但是不影响)。然后我们自然想让这个节点死掉,或者说消失。

我们在前面已经添加了好几段动画,现在应当不是问题。除了添加动画之外,我们再给表示玩家状态的State枚举类型再加上一个DEAD状态,这样在其他处理输入和重力的相关方法中如果发现玩家已经死亡我们就不再进行相关处理。

动画添加好后,修改播放动画的方法:

注意我把处理跳跃和下落的代码也修改了。由于我们会跌落到KillArea上然后修改状态,所以跳跃和下落的动画只有在状态不为DEAD时才播放。

然后修改_physics_process。发现状态为DEAD时直接返回。当然你也可以把if的条件反过来,发现状态部位DEAD时才执行后面的代码。但是像我这样写的话可以马上return,不用多一级缩进。

随后我们添加一个die方法,这样在KillArea中我们就可以调用这个方法让玩家死掉。目前来说就简单在这个方法中把状态设为DEAD即可。

然后在KillArea中调用——但是等等。body_entered信号连接的函数的参数是Node2D类型,但是我们的die方法定义在玩家——也就是Player类型(或者其他你给它取的名字)上。我们应当在试图调用die方法之前,先检查一下到底是谁进入了area。由于目前我们只考虑在这个area里杀死玩家,所以我们只检查它是不是Player就行。在GDScript精要部分我提到过,如果要把脚本作为一个类型让它在其他脚本中也可以使用,需要额外写一行代码:

在玩家角色的脚本中,我添加了一行class_name Player。这样在其他脚本中就可以通过Player这个类名来指代它了。

要检查某个对象的类型,直接用is运算符加上类型名即可:

很遗憾的是目前GDScript没法把它写成body is not Player。顺口提一句在Python和C#中是可以写is not的,只不过Python没法用它做类型检查。is的优先级高于not,所以这里不写括号也可以,当然拿不准的时候加上括号准没错。

判断类型之后,我们用as运算符将body转换为Player,然后调用die方法。这里为了方便我就写到一行没有单独定义一个局部变量。由于前面有类型检查作为保证,这里不用as转换也没问题,但是在写代码时就没有自动补全提示了。

另一方面,我们其实也可以设置Area的物理层,让它只检测玩家所在层。不过现在我们没有那么多不同的场景节点要处理,所以就简单检查一下是不是玩家就行了。

现在运行游戏,玩家落下的时候碰到KillArea就会停止并播放死亡动画。

现在播放可以正常播放,但是玩家场景节点依然存在。我们要调用一个方法让它消失。queue_free方法可以销毁节点。它定义在Object(绝大部分东西的基类,也是Node的基类)上,是一个方法,没有参数,指的就是销毁“这个”东西。queue_free不会立即销毁,它会等到当前帧结束,并且一些函数调用完毕后才会销毁对象,即使一帧多次调用它也是安全的。所以我们一般都会用它来执行销毁节点的工作。

在die方法中加上queue_free的调用,运行游戏。

嗯……玩家是没了,但是我们看不到动画了。

我们希望,在碰到KillArea,播放动画,然后过一段时间再执行queue_free。聪明的你可能已经想到,可以用之前学习过的Timer节点做一个计时器在timeout信号连接的函数上销毁自己。

这样做可以,但是没必要。因为我们并不会多次用到这个计时器,计时结束整个玩家节点都会没,让它一直背着一个不怎么用的节点有点浪费。因此我们学习一个新东西。

SceneTimer

SceneTimer如其名,也是一种计时器。但是它不是节点(不继承Node)。场景树会负责管理SceneTimer实例。正如文档中所说的那样,常用于创建一次性计时器。

要创建一个SceneTimer直接调用场景树的`create_timer`方法。要获得当前场景树则需要调用get_tree。create_timer只有一个必填参数那就是时间。SceneTimer和Timer节点一样,有一个timeout信号,我们只需要把计时结束时要执行的代码连上即可:

SceneTimer不是节点,所以也就没法在编辑器中连接信号了。所以这里在代码中用connect方法连接信号。我们目前的想法是“一段时间之后销毁节点”,就一行代码,所以没必要单独写一个方法,这里直接写了一个lambda函数传给connect。

现在运行游戏,碰到KillArea之后3秒,玩家就会被销毁。

await关键字

上面的代码实际上可以用Godot 4+中引入的await关键字来简化。首先来看简化后的版本:

运行游戏,效果和之前完全一样。

await关键字的作用和它的字面意思类似,会“等待”某个东西。await后面只能是两种东西,要么是一个信号,要么是另一个含有await的函数调用(注意是函数调用而不是函数本身)——即协程。含有await关键字的函数会成为一个协程(coroutine)。协程是一段可以被挂起(暂停),并在合适的时候可以继续执行的程序。

代码执行过程中遇到await时,会暂停(挂起)当前函数的执行,直到await后面的信号发出或者协程执行完毕,再继续执行后面的代码(如果有)。

调用协程的函数(不在协程前加上await关键字)在代码执行到首个await时会立即返回执行自己接下来的代码,直到协程中的代码可以继续执行时才会接着执行协程中的代码。试看这个例子:

运行一下代码看看效果。“吃吃吃”和“再来一个汉堡”毫无疑问首先按顺序输出。紧接着调用coroutine函数,此时“老板:好”紧接着也正常输出。按道理来说接下来需要等待计时器3秒倒计时,但由于调用coroutine时我们没有使用await关键字“等待”它,因此遇到coroutine中的await时执行过程立即返回到ready中,然后接着执行后面的代码输出“吃吃吃喝喝喝”。range函数会构造一个含有5个数字的数组,我们这里的循环会执行5次、每次间隔一秒,输出“吃吃吃喝喝喝”。与此同时,三秒后(吃吃吃喝喝喝输出三次)coroutine中的await等到了计时器的timeout信号发出,然后老板才说汉堡好了。

这给人的感觉就好像是两段代码在同时运行、互不干扰一般。从某种程度上来说这种特性类似于各种编程语言中的异步编程(asynchronous programming)模型。并且一些编程语言同样使用await(以及async)关键字来实现相关功能。异步编程旨在遇到耗时的方法调用时不阻塞(block,它可以是一个术语)当前的代码执行。在游戏开发中过程中,从直观感受(和某些层面)上来说,我们的时间单位更像是“一帧”。在一帧中会有很多操作完成。但是,有时候我们不希望(比如刚才的死亡机制)或者说不能(比如说耗时的下载过程)让某些操作在一帧内完成,但又希望不影响其他代码的执行,此时就可以用协程来解决。

注意,真正的异步编程和协程可能和多线程、多任务、多进程有关,但并不能划等号。

重生

各种游戏在死了之后还得重生。怎么死我们知道了,现在还得知道怎么生。

现在的Player实际上是一个放在主场景中的一个场景实例。我们要学习如何从场景中构造一个场景实例。

先来新建一个Node2D场景作为一个spawner。目前来说我们只是需要一个位置来重生玩家,所以就不需要其他的节点了,创建好场景添加一个脚本就行。

Spawner只产生玩家可不够,我们以后可能还需要用它来产生各种不同的东西,所以我们必然不能写死让它只产生玩家。那么怎么去引用一个场景呢?场景也是一种资源,它也有对应的类型。代表场景资源的类型是PackedScene。它就是对我们正在编辑的、可以保存为文件的各种场景的抽象。为了方便随时编辑,我们直接定义一个变量并且用export暴露出来:

接下来,只需要编写一个简单的spawn方法来实例化这个场景就好了:

场景的instantiate方法顾名思义就是实例化的意思。它会直接根据场景造一个实例出来。但是光有这样一个实例还不行,我们要把它添加到场景中。下一行代码就是获得spawner所在的场景树。add_child顾名思义就是给某个节点添加子节点。我们这里是在场景树的root节点上调用的,它会把玩家加到场景根节点下面,而不是添加到spawner下面。

此外为了便于在其他脚本中引用Spawner,我还加上了class_name Spawner这行来暴露它的类名。

接下来我们给主场景添加一个脚本。在脚本中我们需要找到这个生成玩家的spawner,然后在合适的地方调用spawn。我们之前为了方便是直接把玩家场景丢到主场景中的,现在我们需要利用spawner来生成玩家,所以我们就在ready中调用spawn方法。现在删除之前放在主场景中的Player场景节点,然后放入Spawner场景,调整到合适的地方,并把玩家场景放到它的Scene To Spawn属性槽中,然后主场景的脚本如下:

我们希望在游戏开始时生成玩家,就是这样,很简单。

启动游戏,啊?报错了。错误信息说父节点忙于准备子节点,添加节点失败了。让我们考虑在add_child上使用call_deferred方法(要记得函数/方法也是对象,它们是Callable类型的,这个deferred是定义在Callable上的)。这个call_deferred啥意思呢,字面上来说是延迟调用的意思。从文档中可知,在Callable上调用这个方法会把方法的调用延迟到这帧结束时。

_ready会在节点及其子节点进入场景树时调用,并且此时子节点的ready都先于父节点调用完毕了。但是我们偏偏在这个时候加入新的子节点,虽然其他节点ready了,但是新加入的节点还没有ready,这样的操作不太合适。因此需要延迟一下它的执行。不过考虑到我们后面会编写用于重生的代码,因此不会总是在某个场景的ready中调用spawn,所以我们不延迟add_child而是直接延迟spawn:

注意回忆Callable的相关知识。加上括号是函数调用,不加括号就是在引用函数(Callable)本身。

再次运行游戏,没有报错,有玩家角色成功生成,但是它位置不在spawner的位置!

位置默认是(0, 0),我们把生成的玩家场景节点加到了主场景的根节点中,所以它是在场景中的左上角生成的。所以我们在spawn方法中把实例化出来的玩家场景的位置设置为spawner的位置就好:

注意,我没有给instance标注类型,而instantiate返回类型为Node。Node上面没有定义position属性,所以编写这段代码时不会有提示。但是,我们要生成的场景(不是场景资源本身)必然是Node2D的子类,所以我可以断定这个position是存在的。如果还是不放心,可以在instantiate后面加上as Node2D来保险(不写Player是因为我们可能会用来生成其他类型的场景)。

不过,我们还是没有完整实现重生的功能。比如我们希望在玩家死亡(销毁玩家场景节点)后,按任意键重生。我们知道怎么死,也知道怎么生,但是什么时候让谁去生呢?

考虑到玩家死亡时我们需要进行不止一项操作,比如除了重生玩家之外,我们还可能需要展示UI元素(后面会讲)、播放声音等等。这些操作不止涉及玩家场景自己,但是其他场景却不一定知道玩家什么时候死亡。这种时候就是在暗示我们可以自定义一个信号。

发出死亡宣告

其实之前讲信号的时候简单提了一下如何自定义信号,忘了也没关系,本身也很简单。

我们希望玩家死亡时发出一个died信号表示玩家已经死亡。目前来说这个信号不会提供任何参数。定义信号时和定义一个“没有主体的函数”类似,只不过func变成了signal。在玩家的脚本中加上:

任何连接到died信号且函数签名兼容(和信号定义的参数类型相同)的函数,都会在died信号发出时得到通知(被调用)。

然后我们在处理玩家死亡的函数中发出该信号。信号的emit方法就是发射信号,如果信号有参数则需要传入相应的参数:

和其他信号类似,我们需要连接它。我们在主场景的脚本中连接它。不过……

我们的spawn方法没有返回值,所以我们拿不到对玩家场景节点的引用。当然你也有方法可以直接找到主场景中的Player场景,但是没必要。我们直接修改Spawner的spawn方法,让它返回spawn出来的东西:

方便起见我同时给它标注了返回值。然后修改主场景的代码,但是……

我们在主场景中的代码使用了call_deferred来延迟调用spawn。但是call_deferred不会返回Callable的返回值(其返回值类型为void),毕竟这个call_deferred立刻就返回了,真正的调用会发生在未来。我们拿不到生成的玩家角色。不过好办,我们可以先拿到刚生成的玩家,但是我们自己延迟把它加入场景树中。

这里你可以选择直接修改spwan方法,让它不调用add_child,也可以像我这样单独写一个不加入场景树的方法,需要的时候调用它:

然后在主场景中:

嗯……看起来有点复杂。我们慢慢来看。首先我们调用spawner实例化场景后不会自行把新场景实例加入当前场景树的spwan方法,这样我们就能在主场景的代码中拿到新产生的player。

随后我们连接玩家的died信号。因为这个player是我们用代码生成的所以没办法在编辑器中连接信号,这里用signal的connect方法连接我们定义的一个方法来响应玩家死亡的信号。

接下来,我们希望延迟加入刚生成的玩家角色,因此需要call_deferred。但是add_child(player)是一条表达式而不是Callable,你没法直接在它身上调用call_deferred,所以我们用一个lambda函数把它包起来获得一个Callable然后延迟调用。

实际上`call_deferred`还有另一个定义在Object上的版本(节点也是Object),它需要传入要调用的方法的名称,然后传入它的参数:

个人不太喜欢用函数名字符串传函数,但是这样写也可以达成目的。

在这个信号响应方法中,我们等待半秒钟——这是随便写的,然后调用spawner的spawn方法重新生成玩家。由于此时场景的ready早已调用完毕,我们也就没必要延迟加入新生成的玩家。但是我们依然需要连接上新生成的玩家的died信号。

现在运行游戏,死亡和自动重生的相关功能就能正常工作了!