分类
dev

Godot engine学习 (1)

自打去年在搜索引擎上看到这个开源的游戏引擎来,足足一年时间过去了,却没有真正开始学习/尝试使用这个引擎.

今起,可以利用业余时间开始一步步学习这个引擎,目前看来,作为移动平台小游戏的基础,它应该是满足条件的.

MIT的许可证,也比较适合我们来使用.

将会在博客每天持续更新学习记录.

内容会是以官方文档为基础的学习, 暂不会参考其他网上教程(其实好像也还没有). 时间尽量定在1~2周.

Day 1

第一天,就来了解一些关于Godot引擎的基础好了.

基本上,介绍页解释的是Godot里元素的基础是node, 暂且翻译为“节点”. 一个节点(node)有名称、有可编辑的属性、能接受回调、能被扩展、能被挂到其他节点下作为子节点.

也强调了子节点的组织方式, 称非常重要, 以这种方式组织时,这些nodes形成了tree(树). (感觉...非常...寻常的方式...- -!)

另外,就是Scene(场景). 起特征是:

一个Scene有且只有一个根节点(root node)、Scene可以被保存到磁盘也能被载入、可以被实例化、运行一款游戏意味着运行一个Scene、一个工程里可有多个Scene.

(基本上和其他引擎定义一致)

Godot editor其实就是个Scene editor.

hello, world

  • 选择路径, 创建工程(New Project), 并打开
  • 在Scene面板中, 使用+新增node(Create New Node), Node|CanvasItem|Label
  • Inspector中编辑label的内容, 比如“hello, world”
  • 保存, 并设置该Scene为main scene, 运行即可看到效果

Instancing

基本原理

Godot里,Scene的特征上面已描述过:

godot-scene-1

对于一个正在编辑的Scene来说,其他Scene的示例也可以加到其下:

godot-scene-2

上面图里展示的是Scene B以一个instance加到Scene A中.

官方的demo示例中,在Scene面板中点按“链接形状”按钮选择一个Scene创建instance.

Instancing

Design language

When making games with Godot, 
the recommended approach is to leave aside other design patterns 
such as MVC or Entity-Relationship diagrams 
and start thinking about games in a more natural way. 
Start by imagining the visible elements in a game, 
the ones that can be named not just by a programmer but by anyone.

举例一个简单的射击游戏的思路:
godot-instancing-1

记下想到的元素, 并用箭头表示其ownership.

Day 2

Scripting 脚本

使用的脚本语言称作GDScript. 其设计时有两个目标:

  • First and most importantly, making it simple, familiar and as easy to learn as possible.
  • Making the code readable and error safe. The syntax is mostly borrowed from Python.

任意情况下, 若有性能需求, 可以用C++编写核心块然后交给脚本调用.

Reference

右击node, add script, 意味着extend该node, 故会自动填上Inherit该node.

默认脚本里, 会有一个_ready()函数. 它会在该node(和它所有的children)进入活动scene时被调用. Constructor函数是_init().

# 获取元素
Node.get_node()
# 默认树结构 是 从该脚本控制的node的直接子node开始
# 例如Panel->Label->Button的结构的话, 就是get_node("Label/Button")

# 连接signal信号与响应
Object.connect()
# get_node("Button").connect("pressed",self,"_on_button_pressed")

Processing

有些情况下, 需要脚本处理每一帧. 有两类processing: idle processingfixed processing.

Idle processing由Node.set_process()函数激活. 一旦其激活, Node._process()回调将会被每一帧调用. 示例:

func _ready():
    set_process(true)

func _process(delta):
    # do something...
# delta参数描述了时间间隔, 单位秒, float类型, 间隔自上一次调用_process().

Groups

Nodes可以被添加到groups(想加多少加多少).

  1. 通过界面添加: Node面板下的Groups按钮.
  2. 通过代码添加: add_to_group("enemies")

在此情况下, SceneTree.call_group()可通知一个group下的所有node.

# if the player is discovered sneaking into the secret base
# all enemies can be notified about the alarm sounding
# by using SceneTree.call_group()
func _on_discovered():
    get_tree().call_group(0, "guards", "player_was_discovered")

当然, 也可通过group获取其所有nodes的列表: SceneTree.get_nodes_in_group()

var guards = get_tree().get_nodes_in_group("guards")

Notifications

Godot系统包含一个通知系统.

添加Object._notification()函数即可使用:

func _notification(what):
    if (what == NOTIFICATION_READY):
        print("this is the same as overriding _ready()...")
    elif (what == NOTIFICATION_PROCESS):
        var delta = get_process_time()
        print("this is the same as overriding _process()...")

Overrideable functions

func _enter_tree():
    # When the node enters the _Scene Tree_, it becomes active
    # and  this function is called. Children nodes have not entered
    # the active scene yet. In general, it's better to use _ready()
    # for most cases.
    pass

func _ready():
    # This function is called after _enter_tree, but it ensures
    # that all children nodes have also entered the _Scene Tree_,
    # and became active.
    pass

func _exit_tree():
    # When the node exits the _Scene Tree_, this function is called.
    # Children nodes have all exited the _Scene Tree_ at this point
    # and all became inactive.
    pass

func _process(delta):
    # When set_process() is enabled, this function is called every frame.
    pass

func _fixed_process(delta):
    # When set_fixed_process() is enabled, this is called every physics
    # frame.
    pass

func _paused():
    # Called when game is paused. After this call, the node will not receive
    # any more process callbacks.
    pass

func _unpaused():
    # Called when game is unpaused.
    pass

Creating nodes

代码创建node. 调用.new()方法. 例如:

# 代码创建node. 调用`.new()`方法
var s
func _ready():
    s = Sprite.new() # create a new sprite
    add_child(s)     # add it as a child of this node

# 删除node
func _someaction():
    s.free() # immediately removes the node from the scene and frees it
# 释放一个node, 它的子nodes也会跟着被释放.

删除一个正处于“blocked”状态的node将会导致crashing, 因为它正在发出信号(emitting a signal)/调用方法(calling a function).

最为安全的删除node的办法是Node.queue_free(). 它将在idle状态下删除安全的删除node.

Instancing scenes

代码里instancing一个scene分两步:

  1. 从磁盘载入: var scene = load("res://myscene.scn"), 或是var scene = preload("res://myscene.scn")
  2. 此时scene才是以PackedScene的资源包形式存在着的, 要创建一个实在的node, 需使用PackedScene.instance().
var node = scene.instance()
add_child(node)

二步初始化的优势是, packed的scene可以一直被保持着, 用来快速初始化多个instances. 例如创建多个enemies, bullets等等.

Day 3

Author

Luo Yu

indie.luo@gmail.com