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

推荐订阅源

博客园_首页
I
InfoQ
The Register - Security
The Register - Security
L
LangChain Blog
H
Help Net Security
The GitHub Blog
The GitHub Blog
S
Schneier on Security
博客园 - 【当耐特】
W
WeLiveSecurity
Attack and Defense Labs
Attack and Defense Labs
IT之家
IT之家
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Google DeepMind News
Google DeepMind News
The Cloudflare Blog
H
Heimdal Security Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Y
Y Combinator Blog
雷峰网
雷峰网
N
Netflix TechBlog - Medium
Security Archives - TechRepublic
Security Archives - TechRepublic
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
L
Lohrmann on Cybersecurity
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
The Exploit Database - CXSecurity.com
P
Privacy & Cybersecurity Law Blog
G
GRAHAM CLULEY
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
V
Visual Studio Blog
博客园 - 聂微东
PCI Perspectives
PCI Perspectives
Last Week in AI
Last Week in AI
A
Arctic Wolf
宝玉的分享
宝玉的分享
T
The Blog of Author Tim Ferriss
S
Secure Thoughts
T
Threat Research - Cisco Blogs
GbyAI
GbyAI
云风的 BLOG
云风的 BLOG
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
SegmentFault 最新的问题
SecWiki News
SecWiki News
月光博客
月光博客
大猫的无限游戏
大猫的无限游戏
Schneier on Security
Schneier on Security
P
Proofpoint News Feed
博客园 - Franky
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
AI
AI
Engineering at Meta
Engineering at Meta

机核

游戏性能旗舰最强之选,一加 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入门到弃坑:3D空间中的化身
cameLcAsE · 2024-11-19 · via 机核

3D的CharacterBody

我们之前已经使用过CharacterBody2D——一个便于创建2D场景角色的节点。

在3D场景中也有对等的CharacterBody3D。我们再一次新建一个Player场景,用CharacterBody3D作为其根节点。模板保持默认,如果默认不是Basic Movement请手动选择,我们稍后在模板代码的基础上修改。

实际上从模板中可以看出,3D的CharacterBody很多属性、方法和2D版本的名字是一样的——只不过相关类型从Vector2变成了Vector3

总之模板给我们实现了移动和跳,我们暂时不看它是怎么实现的。其实大部分代码和2D版本差不多。模板默认把移动绑定到了箭头上下左右,跳是空格。按照我的习惯,我选择重新定义相关的输入操作,然后绑定。

类似地,我们需要给它一个CollisionShape,自然,在3D场景中我们需要一个CollisionShape3D。一般来说对于类人形的角色,大家都爱选胶囊状(capsule)的——就是类似于一个球中间拉开了的感觉。

别掉下去了

有可能,你现在启动游戏你的角色会穿过地板。如果你的地板或者其它场景是用CSG搭建的,那么你只需要在检视面板中找到Use Collision勾选启用碰撞即可。

如果你用的是MeshInstance搭建的场景,那么就需要给它套上一个StaticBody3D并配置CollisionShape。所以相比之下CSG确实方便。

摄像机

之前我们把摄像机放在了场景中。

当然,有不少3D游戏的摄像机的位置确实(至少在部分场景中)是固定的。比如早期的《生化危机》和《潜龙谍影》。

不过在接一下来的一系列文章里,我主要以FPS的典型形式来讲解。毕竟FPS从3D游戏诞生之初至今都是非常受欢迎的门类。

对于一个典型的第一人称视角游戏来说,摄像机通常放在直接受(真实世界的)玩家控制的东西上。这个东西作为玩家的化身,它身上的摄像机就相当于我们的眼睛。

因此一般来说我们就把摄像机放在接近这个胶囊顶部的位置。

不过……

前面是哪个前面

要注意Godot的摄像机的“前方”总是朝向z的负方向。你或许会觉得有点别扭,但这样做的原因之一是为了和OpenGL的选择保持一致。

可以看到在往场景中加入Camera3D节点后,摄像机默认朝向-z方向。

基于这样的决定,很多和方向相关的概念都是围绕它来确定的。在Vector3类型上定义了四个常量,FORWARD、BACK、LEFT、RIGHT分别代表前后左右四个方向(四个长度为1的向量)。其中代表前方的FORWARD就是(0, 0, -1),其他方向对应的值也就不言而喻了。

另外,Vector3上还有好几个以MODEL(模型)开头的和方向对应的常量。这是为导入的3D素材选用的坐标系,其实就是和3D模型格式glTF相对应的方向。对于一些有涉及朝向的模型来说,需要注意这些坐标系之间的区别。

旋转的表示

接下来着手实现用鼠标调整摄像机的角度。

你会说OK,很简单,就是调整一下rotation属性呗。

确实没错。但是你很快就会发现事情比想象的复杂。

在2D中,Node2D的rotation属性是一个标量、一个小数而已。因为在一个2D画面中,我们可以想象观察游戏场景的方向(视线方向)始终是穿过屏幕的一个向量。所以物体自身的旋转始终始终是绕这个方向来谈的。

然而在3D空间中,你可以随便点一个Node3D节点看看它的rotation,可以注意到它的类型是Vector3

想象一下飞机——或者你可以拿一个飞机模型在手上。它自身在3D空间中的“旋转”涉及三种动作:机头抬头低头、机头绕竖直方向左右旋转、“机翼一边抬高的同时另一边降低”。

虽然上面我是用非常随意的语言描述的,但是它们都分别对应着飞机绕不同的坐标轴旋转。用Godot的坐标系来说,分别就对应着绕x轴、y轴、z轴旋转。因此自然而然地,3D空间中的旋转需要用Vector3来描述。

在这样的场景中,我们有更专业的术语来指代这三种旋转(角度)。Pitch(俯仰)、yaw(偏航)、roll(翻滚),这些术语在航空器相关的领域内应该有更专业的中文说法,欢迎相关行业的读者补充。

使用这三个单词除了可以很好地对应约定熟成的术语之外,还有个好处就是它的含义是和具体的坐标系无关的。例如Unreal的API中很多地方就是直接使用这三个词。

控制摄像机

至少近十多二十年典型的FPS操作是用鼠标来控制摄像机以表现“四处看”的感觉。

我们要做的“很简单”,无非是在前后移动鼠标时,让摄像机也跟着调整俯仰角度。在左右移动鼠标时……

这里值得一个停顿。如果不假思索地说在此时让摄像机也调整围绕y轴的旋转角度,那么就会得到一种可能不符合预期的行为——不叫它“问题”是因为在某些类型的游戏中可能就是需要这种行为。

左右移动鼠标时如果只旋转摄像机,那么就会出现视线和玩家角色的前方不在同一直线的问题。在目前主流的FPS中并没有采取这种做法,你肯定知道,我们左右移动鼠标时,整个玩家角色也会同时旋转,然后带动摄像机旋转,进而保持视线前方和玩家角色的前方在同一方向上。另一方面,我们在移动过程中也可以保持移动方向的前方和摄像机的前方指的是同一个方向。

当然,也有一些游戏并不是这样。比如很多有坦克的游戏,鼠标一般是控制炮塔的角度——也同时对应摄像机的角度。而移动是按照坦克底盘的前方来决定方向的。

不过话说回来,典型的FPS操控也是“很假的”。毕竟我们在现实生活中很多时候头部的方向和移动方向都并不一致。但是在快节奏的FPS中,这种真实感很容易让操作变得复杂而影响流畅度。

因此,这里我们希望在左右移动鼠标时直接旋转整个玩家角色。

抓老鼠

我的意思是“捕获鼠标”(capture mouse)。

func _ready() -> void: Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

喜报:我换了个软件写,代码样式可以直接复制粘贴到机核的编辑器了,不用发图片了。机核的编辑器真的还有很多改善空间。

默认情况下在游戏里你是看得见鼠标指针,也可以把鼠标指针移动到游戏画面外的。将鼠标模式设置为captured就可以实现一般FPS游戏的效果。

获得鼠标移动

Godot对于鼠标移动的处理目前来看比较奇怪,它没法直接映射到InputMap中。不过经常和鼠标移动对应的手柄摇杆倒是可以——我这里还是以鼠标来讲解。

为此我们需要实现/重写节点的_input(event)方法。要用到这个方法,原因自然是因为鼠标移动没法映射为一个action然后通过Input的静态方法来处理。

但是聪明的你可能会问,如果两者都可以处理输入,那么如何选择呢?

_input方法会在任意输入事件发生时调用。Input的相关方法则是我们主动调用的。因此两者首先在执行频率(时机)上有区别。前者和输入事件保持同步,而后者是我们自行控制、主动查询输入事件的发生。

_input唯一的参数是类型为InputEvent的事件对象,其中包含了各种输入事件的属性。由于_input会在任意输入事件发生时执行,所以我们必须对事件进行筛选,以便只在感兴趣的输入事件发生时运行相关代码。

尽早返回是个好习惯,只要不感兴趣就直接return:

func _input(event: InputEvent) -> void: if event is not InputEventMouseMotion: return var mouse_input = event as InputEventMouseMotion var motion = mouse_input.screen_relative # TODO

注意:这段代码中的is not必须要4.3+的版本才能够正常运作。尽管is和not在之前的版本就已经存在,但是在进行类型检查的时候连着这样用在4.3才开始支持。在之前的版本中只能把not写在最前面。

首先,只要不是鼠标移动我就不管(比如按下左键我们暂也时不管)。InputEvent是所有输入事件的基类,鼠标相关事件是InputEventMouse,鼠标移动事件是更具体的InputEventMouseMotion

InputEventMouseMotion中定义了很多和事件有关的属性,其中screen_relative顾名思义是鼠标移动前后的坐标差值,得到的向量就表示我们鼠标移动的方向和距离。

实际上还存在一个叫relative的属性。不过如果你的游戏画面设置了某种拉伸(stretch,还记得我们在之前的文章中一开始为适应像素游戏风格素材设置画面拉伸模式的时候吗),relative可能会被缩放,进而造成不同分辨率情况下移动鼠标给人的感觉不一样。

基本实现

可以写点控制摄像机的代码了。

按照刚才说的,鼠标前后移动,摄像机也要跟着“俯仰”。因此定义一个look_up方法:

func _input(event: InputEvent) -> void: # ... look_up(motion.y * 0.01 * mouse_sensitivity) func look_up(value: float): camera.rotate_x(deg_to_rad(-value))

毕竟已经写到这里了,很多东西的定义我就不展示了,免得占篇幅。

camera是对摄像机节点的引用。我们在_input中获得的鼠标移动状况会传入到look_up中。

rotate_x方法的名称已经很直白了,就是绕x轴旋转,但是它的参数是以弧度为单位,所以需要转换一下。或者你也可以采用另一种写法:

camera.rotattion_degrees.x -= value

rotation_degreesrotation是对应的,只不过是以角度表示的,对于人类来说可能方便一点。

mouse_sensitivity是一个export了的整数属性,也就是鼠标灵敏度,我这里默认值设置为50。鼠标移动得到的screen_relative从一位数到三位数都有可能。如果直接把这个值当成度数传给look_up,我觉得一般人类可能是无法接受那么快的旋转速度的。因此这里乘以一个系数再乘上灵敏度便于调整。当然这里的数字不是什么魔法数,你可以根据自己的需要调整。

现在上下(前后)移动鼠标,可以看到我们的视野跟着在旋转了。

你的问题可能是,value前面这个负号哪来的?

角度的正负

首先,你可以试一下不要这个符号是什么效果。

角度在很多场合中有符号,如果我们把角看成是一条线段/射线/向量绕“起点”旋转出来产生的,那么它的符号体现了角度“是从哪个方向转出来的”。习惯上,你可能接受逆时针为正,顺时针为负。

有可能,这种习惯源自于上学的时候,2D坐标系+x方向朝上,+y方向朝右,一条线段/直线和y轴正方向形成的夹角,其角度我们视作一个正的角度。Godot在2D中以逆时针为负,顺时针为正——但是这和前面提到的习惯来说,也没毛病,毕竟Godot的2D坐标系原点在左上角,即+y方向朝下,+x方向朝右。

在3D中情况还要复杂一些,因为我们有更多的观察角度。例如以围绕x轴的角度为例,如果我们顺着x轴正方向看,那么逆时针为负;如果顺着x轴负方向看,那么逆时针为正。

你可以在场景中使用旋转工具观察一下这些角度的变化。

回到代码的问题。我们的摄像机朝向-z方向。从右边来看(顺着-x方向),“抬头”是逆时针旋转,摄像机的rotation的x分量会加上一个正数。“那为什么value要加负号啊?”,因为InputEventMouseMotionscreen_relative是一个2D向量,其坐标的定义和Godot的2D坐标系是一致的,也就是说鼠标向前是在朝-y方向移动,得到的是一个负数。

限制角度

目前如果持续前后移动鼠标的话,摄像机会360度旋转。无论是从现实还是FPS的典型设计来说,我们都最多只能转一定角度。简单,还是用clamp:

camera.rotation_degrees.x = \ clampf(camera.rotation_degrees.x, -70, 70)

这里的70度是我自己随便选的,你可以自己定义一个export属性。

环顾四周

正如前面所说,鼠标左右移动时,我们不再旋转摄像机,而是直接旋转整个玩家角色,因此定义一个turn方法:

func trun(value: float): rotate_y(-value)

实现它要比俯仰简单得多。并且它是不需要限制角度的,毕竟我们可以转圈。类似地,这里也要加个负号。这个函数放在look_up下方调用就行。

注意,正如前面所说,这里是直接在自己身上调用rotate_y而不是在camera上面。我们是直接转整个角色,而不是旋转摄像机。

移动的实现

模板已经为我们实现了移动的功能。我们简单看一下是如何实现的。

var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_backward") var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

主要需要看的就是在_physics_process中这两行代码。

当然首先介绍一个之前在我的代码中没有用到的运算符:=。鉴于GDScript和Python的相似性,需要提醒一句它和Python中的“海象运算符”(walrus operator,横着看)有不同的语义。

在GDScript中这个运算符意味着在变量初始化时,对变量的类型进行推断,这样就不用自己主动写类型。

言归正传。这两行代码要求出想要移动的方向(最终的direction是一个长度为1的向量)。首先是求玩家输入的方向,Input.get_vector根据传入的四个输入操作名称来构造一个2D向量。四个参数分别是-x、+x、-y、+y方向。

实际上如果你接受了摄像机朝-z方向的设定,这里反而就更有道理了。这里的-x方向是左,+y方向是向后。实际上,如果我们从上往下看,让-z为前方(正北),+z和+y方向刚好就和Godot的2D坐标系的+y和+x对齐了!是不是一下就不觉得哪里别扭了。

求得输入的方向后,进行“某种运算”求得角色要移动的方向direction并归一化。这里不得不卖个关子,因为没有一些额外的解释可能无法理解这个“某种运算”怎么就让从输入得到的方向和玩家角色的朝向对齐了。

接下来的代码就没什么新鲜的了。它会根据SPEED的定义来控制移动的速率,然后根据direction的值设置速度,最后调用和CharacterBody2D功能一样的move_and_slide实现移动。

跳跃在模板中也实现了,和2D版基本上一样,不多解释。

至此,一个典型的可以在3D空间中漫游的第一人称角色就基本实现了。