这个例子所使用的素材来自Sprout Lands Basic pack,来自:Sprout Lands - 制作人:Cup Nooble
演示环境
Godot版本:4.3 stable
操作系统:Windows 11 x86-64
本章节目标效果:
如果在自行操作的过程中遇到难以解决的困难,也可以直接到文末获取该godot项目完整的代码。
如果你还未下载和安装Godot,可以参考0.下载安装和使用godot
创建你的Godot项目
启动godot程序,点击创建
输入项目名称,选择项目路径。为了便于后续找到你的项目,请保持好良好的文件存放习惯。
建议将你的Godot项目全部存放在电脑C盘以外的某个磁盘(如果你的系统是win),并按照一定逻辑命名项目路径。
为了避免潜在的不兼容风险,请确保项目路径中不要有中文。
由于本项目完全不涉及3D场景,后续可能有发布到Web平台的需求,因此渲染器选择”兼容”。
版本控制元数据:选择”无”。
点击”创建并编辑”,正式创建项目。
素材获取与导入
素材获取
素材包Sprout Lands Basic pack 由创作者Cup Nooble发布于itch.io
itch.io是一个海外知名独立游戏平台,在其上有着大量免费或收费的游戏素材。Godot开发者也可以把自己的游戏作品发布到该平台。
素材获取地址:
Sprout Lands - Asset Pack by Cup Nooble
下载后解压如下图:
素材重命名
godot官方文档建议将项目内所有文件(C#脚本除外)命名为snake_case格式,原因详见官方文档,因此请先把所有目录内所有文件按snake_case风格进行重命名。
snake_case命名风格,即全部单词小写,单词之间用下划线_分割。
下面提供一段Python程序,帮助你快速完成重命名工作。
import os
import re
# 将字符串转换为snake_case(不修改后缀名的点)
def to_snake_case(name):
# 分离文件名和扩展名
name, ext = os.path.splitext(name)
# 使用正则表达式将大写字母转为小写并前面加下划线(只对文件名部分)
name = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', name)
# 将所有空格或其他非字母数字字符转换为下划线(只对文件名部分)
name = re.sub(r'[^a-z0-9\u4e00-\u9fa5]+', '_', name.lower()) # 允许中文字符
# 移除开头和结尾的下划线
name = name.strip('_')
# 保持扩展名的小写(只修改扩展名的字母部分)
return name + ext.lower()
# 重命名目录和文件
def rename_files_and_dirs(root_dir):
for root, dirs, files in os.walk(root_dir, topdown=False):
# 重命名文件
for filename in files:
old_file = os.path.join(root, filename)
new_file = os.path.join(root, to_snake_case(filename))
if old_file != new_file:
print(f'Renaming file: {old_file} -> {new_file}')
os.rename(old_file, new_file)
# 重命名目录
for dirname in dirs:
old_dir = os.path.join(root, dirname)
new_dir = os.path.join(root, to_snake_case(dirname))
if old_dir != new_dir:
print(f'Renaming directory: {old_dir} -> {new_dir}')
os.rename(old_dir, new_dir)
if __name__ == "__main__":
# 输入你想要重命名的目录路径
print("该程序会将路径内(不包括路径根目录)所有的的文件夹和文件的名字按以下规则修改: ")
print("1.所有大写字母将会被修改为小写(包括后缀名中的大写字母)")
print("2.所有空格会被替换为下划线")
print("3.中文字符不受影响")
print()
root_dir = input("请输入要重命名的目录路径: ")
rename_files_and_dirs(root_dir)
导入素材到项目
在godot的左下角可见如下”文件系统”。右键res://,创建文件夹resource。并将素材文件夹内的全部内容拖拽到resource中。
导入过程及完成后的效果如下:
创建角色场景
在Godot中,玩家所看到角色、场景等”物体”都由节点来构成。
对于一个角色,它可能有精灵节点(显示样貌),音频流节点(播放音效),角色身体节点(处理物理交互),碰撞形状节点(表示其能够碰撞的范围)。
用一颗树来表示,可能是这样子的
CharacterBody2D(角色身体节点,处理物理交互)
├── CollisionShape2D(碰撞形状节点,表示碰撞范围)
├── Sprite2D(精灵节点,显示角色样貌)
├── AudioStreamPlayer(音频流节点,播放音效)
当然,这种做法并不唯一。在本章节中,暂时不会使用AudioStreamPlayer。
想要一次性把这些节点打包,可以创建一个场景。在这个场景中创建好上述节点。将这个场景称为角色场景。
在其他的场景中,可以将这个场景作为子场景实例化,一次性把里面的节点放到另一个场景中。
创建角色场景
在文件系统中创建文件夹player。
在其中创建一个CharacterBody2D场景,命名为player。
给角色添加精灵
在左侧”场景树”中,给角色添加一个AnimatedSprite2D节点。
在右侧”检查器”,给AnimatedSprite2D创建一个新的Sprite Frame,并添加动画"go_down"
按下鼠标滑轮,可以移动在中央的场景中移动。滑动鼠标滑轮,可以放大或缩小。按下▶,观察动画效果。发现其播放速度过慢,可调节FPS来更改。
可以看到,在放大后,图像变得模糊不清。需要在左上角点击 项目-项目设置-渲染-纹理-画布纹理-默认纹理过滤 中,改为Nearest。
一般在像素风游戏中,都要进行此更改。
请依照前几步的方法,陆续添加go_left,go_right,go_down等动画。
给角色添加碰撞形状
在场景树中,添加一个CollisionShape2D节点。
在右侧”检查器”中,给碰撞体添加形状,并调整到一个合适的大小。以能够覆盖角色的大部分为宜,但不必完全覆盖。
创建背景场景
创建场景
在文件系统中,创建文件夹ground,用于存放角色活动的背景的godot场景。
创建场景ground,类型为Node2D(只需要在根节点类型处选择”2D场景”即可)。
实现场景边界
为了避免角色移动到ground场景之外,我们使用边界碰撞形状。
在检查器中,将Shape设成WorldBoundaryShape2D。
这个操作将会创建一个无限长的碰撞箱。角色场景有碰撞形状,两者之间的交互会使得角色无法离开碰撞形状划分开的区域。
可以对节点进行复制粘贴,创建另外三个边界。注意三个边界的朝向。
效果如下:
可以留意到,每个节点旁边都有警告⚠。这是因为,CollisionShape2D必须作为Area2D(或者Area2D的继承类,如角色场景中的CharacterBody2D)的子节点存在。
由于我们需要让角色的CharacterBody2D无法通过边界,因此使用Area2D的另一个继承类StaticBody2D。
创建一个Area2D场景,将四个CollisionShape2D作为子节点放入其中。
实例化角色子场景
将角色场景Player.tscn作为子场景实例化,并移动到合适的位置
调整角色的大小
先确保此时选中的是角色
在右侧检查器中的Transform的Scale属性中,调整角色缩放。
也可以直接用鼠标在数字上左右移动来调整它的值。
可以将x和y的值都设为5。
测试场景
按F5。或者点击下图所示按钮。
在弹出的窗口中,询问选择项目主场景。
所谓主场景,就是游戏启动时打开的场景。
点击”选择当前”。
可以在弹出的游戏窗口中看到角色。
给角色创建脚本
添加输入映射
在正式创建脚本前,先添加用于控制角色的输入映射。
在 项目-项目设置-输入映射 中添加输入映射。请仿照下面的动图,添加控制角色向四个方向走动的输入映射:go_up,go_left,go_right,go_down。
创建脚本
选中角色根节点,点击右上角的图标创建一个脚本。在弹出的窗口中直接点击创建。
在弹出的窗口中编写脚本。
声明速率变量
Godot使用的脚本语言叫GDscript,缩写gds,语法类似python。
gds使用的是动态变量,变量的类型并不固定。
在gds中,使用关键字var来创建变量。可以在变量名后使用:变量类型的方式指定类型。但是,这个也类型并非是固定的,在后续也可以给变量更改类型。
var speed:float=100
单位为:像素/秒。
重写_physics_process函数
_physics_process函数是Node类的方法。而CharacterBody2D作为Node的继承节点,也有这个方法。
_physics_process函数,在游戏的每一个物理处理帧中都会被调用。
它的参数delta是每个物理帧之间的时间间隔。
目前想实现的操作是:当我使用按键映射输入"go_up"时,角色向上移动,速度为变量speed。其他三个方向同理。
在此之前,需要了解一些前置知识。
函数重写
在godot中,如果一个类拥有某个函数fun(),而它的继承类也会拥有这个函数。
在继承类的代码中,如果重新定义函数fun(),就可以把函数fun()给覆盖,这种操作称为函数的重写。
坐标系
在计算机图像中,通常x,y坐标是这样的,Godot中也不例外:
图像最左上角的像素为坐标原点。x坐标向右为正方向,y坐标向下为正方向。单位长度为1px,即一个像素的边长。与通常的数学中的坐标系不同。
Vector2二维向量
在Godot中,有一个数据类型叫Vector2 。即2维向量。可以使用这样的代码初始化一个Vector2
var a=Vector2(100,200)
用这样的方式,创建了一个Vector2二维向量,它的x值为100,y值为200,再将它的值赋给变量a。
在godot中,Vector2可以与另外一个Vector2进行加减乘除,可以与一个float或int进行乘除,或者进行更加复杂的数学运算,详见官方文档。
代码:向上移动
在脚本中,写入以下内容:
if Input.is_action_pressed("go_up"):
velocity=Vector2(0,-speed)
else:
velocity=Vector2(0,0) #Vector2(0,0)也可以替换为Vector2.ZERO
move_and_slide()
Input是godot中处理键盘按钮,鼠标等输入的一个单例
is_action_pressed是该单例的成员方法,检测传入字符串对应的操作事件是否被按下
例如:前文中设定了w键对应"go_up",那么当w键按下时,该方法返回逻辑真true,反之返回逻辑假false。
velocity(中文:速率)是CharacterBody2D类的成员属性,它的类型是二维变向量Vector2.但是仅设置velocity并不会使角色移动。
move_and_slide()是CharacterBody2D类的成员属性,当它被执行时,会按照velocity的速度进行移动。
为了使”go_up”按下时,角色速度应该被设定为向上(即y轴负方向)移动,在其他情况下,再将速度设为0.使得按钮松开时角色停止移动。
按F5启动游戏,测试一下效果。
为了让角色向上移动时,能够播放相应的动画,还应该进行以下操作:
使用@export修饰变量sprite,指定它的类型为AnimatableBody2D。
@export var sprite:AnimatedSprite2D
保存代码后,在角色检查器中可以看到新的属性sprite。
使用@export可以将属性暴露在检查器。
将AnimatedSprite2D拖到这个属性中。
后续在代码中,就可以通过变量sprite来操作这个AnimatedSprite2D节点。
在代码中,使用sprite.play("go_up")来播放向上走的动画,使用stop()停止动画。
@export var sprite:AnimatedSprite2D
func _physics_process(delta: float) -> void:
if Input.is_action_pressed("go_up"):
velocity=Vector2(0,-speed)
sprite.play("go_up")
else:
velocity=Vector2(0,0)
sprite.stop()
move_and_slide()
代码:向四个方向移动
仿照上面的代码,继续实现向四个方向移动的操作。
下面直接给出player.gd完整的代码
extends CharacterBody2D
@export var sprite:AnimatedSprite2D
var speed:float=100
func _physics_process(delta: float) -> void:
if Input.is_action_pressed("go_up"):
velocity=Vector2(0,-speed)
sprite.play("go_up")
elif Input.is_action_pressed("go_down"):
velocity=Vector2(0,speed)
sprite.play("go_down")
elif Input.is_action_pressed("go_right"):
velocity=Vector2(speed,0)
sprite.play("go_right")
elif Input.is_action_pressed("go_left"):
velocity=Vector2(-speed,0)
sprite.play("go_left")
else:
velocity=Vector2(0,0)
sprite.stop()
move_and_slide()
效果如下:
写在文末
至此,本章内容结束。如果本文反响(点赞、评论)较好在后续章节,将会继续完善该项目,使之更加”接近”一个完整的游戏。
如果想要获取godot工程文件,可以到笔者的Gitee仓库中获取。
HuLunTunTao/GodotForBeginners - Gitee.com
如有纰漏/代码问题/错别字,欢迎批评与指正。
如果觉得有帮助,记得给本文或上面的仓库点个赞(star,like)。