Godot-StartUP

创建于:2018-07-28

创建人: Justus

44 信息 154 成员
讨论基于Godot以及Unity引擎的游戏开发经验,理论和最佳实践。共享一些通用思路以启发另一种生产工具中的实践。独立开发群QQ: 122017359
发文原则保持不变,鼓励笔记型日志。
Justus 2022-04-11

小组不再限制加入条件。通过了所有加入请求。

发文原则保持不变,鼓励笔记型日志。

另外小组说明中的qq群目前不处于开放状态。有特别需要加入的独狼开发者可以私信加入。

小组说明
Justus 2018-09-28

1. 本小组用于笔记收集,作为资料目录使用。

2. 请先发布个人日志再推送至小组,而后大家就可以通过帖子的原文链接追溯原文的更新。

3. 日常讨论在qq群进行,鼓励在日常讨论后记笔记,汇聚成文以回馈社区。

    Godot独立开发游戏技术群:QQ: 122017359

    如果你正在使用Godot制作自己的独立游戏,需要和一起奋斗的小伙伴们互相交流技巧,请加入我们。

Godot实践 Q&A
Justus 2018-09-28

Godot Q&A

Godot 3.0.6

1. _enter_tree() vs _ready()

[reddit] enter_tree() vs. ready(), etc. Are there rules of thumb regarding which initialization function to do my housekeeping?

2. 如何用脚本connect signals?

[reddit] explain to me how to use the signals in Godot, please.

3. collision layer 和 collision mask有什么区别

[GodotQ&A] What's the difference between Collision layers and Collision masks?
[GodotQ&A] Collision masks and its propper uses

4. 如何在pc上打开触屏模式?

Project Settings->Display->Emulate Touchscreen
[GodotQ&A] How can i show touch screenbuttons only on android devices?

5. 为什么tilemap会闪烁

Open issue, 把quadrant size 设为1就不闪了
[GithubIssue] Tilemap/draw_rect random flickering with Nvidia drivers

6. windows平台的导出exe图标设置没有效果

godot已知bug,自己用resource hack改

Resource Hacker 简介

A freeware resource compiler & decompiler for Windows® applications
Resource Hacker HomePage

7. NavigationPolygonInstance 为什么无法多个同时起效?

注意拼接要严丝合缝,即相邻边拥有共同顶点。
[Blog] [godot]navigation2d的一些坑

8. 如何用print格式化输出?

print("%s" % value)  
print("%s...%s" % [value1,value2,...])

9. 两个引用所指的碰撞对象是不是同一个,有什么办法判断吗?

Resource Id.

CollisionObject.get_rid().get_id()
CollisionObject2D.get_rid().get_id()

10. 有什么方式记录下collisionLayer的用途约定么?

有。给每个layer命名。
Project Settings->General->Layer Names

11. free() vs queue_free()

12. 为什么RayCast/RayCast2D 只有 Collision Mask而没有Collision Layer? 这意味着什么?

这意味着其他人不能决定自己是否和raycast碰撞,只有RayCast可以决定自己打算和处于哪些Layer的对象碰撞。

13. 为什么Godot没有现成的FSM?这会影响它的易用性么?

J: 我不知道。不会影响易用性。
因为动画系统通常包含着一个天然的FSM,除了transition。Godot 在之后的版本中会加入一个与动画系统协作的状态机。但这并不是很有必要。
因为对于简易的状态机而言,几乎总是能用enum加一个状态转移方法来完成。对于复杂的状态机而言,动画系统的状态和实际的逻辑状态机通常会不一样,并且FSM在并发和层次用途同时出现的情况下并不是最好的选择。 个人推荐仅在简单和并发状态下考虑状态机。通常层次状态机的场景你可以通过一些设计来改变成并发状态。
如果你真的寻求复杂的决策实现并且被层次状态机的可扩展性弄得精疲力尽,那么推荐使用行为树(behavior tree)

14. 什么是行为树?Godot 3有现成的实现么?

一种AI算法。
一种梳理逻辑决策的解决方案。
它解决的问题与复杂的状态机类似。通常由一个树结构+黑板来实现。
Godot中没有内置的实现。有其他开发者提供的插件实现(Godot 3可用)。
开发者务必阅读链接文章。如果你毫无编程经验,那么请向你家的软件设计师寻求符合你需要的简单解释。在一些游戏引擎中会内置BT功能,他们的文档也许是不错的选择。

行为树参考:
[Wikipedia] Behavior tree (artificial intelligence, robotics and control)
Behavior trees for AI: How they work
[SO] Behavior Tree Implementations

15. 默认函数_xxxx在子类中如何被调用?

总是在子类的覆写实现之前自动被调用。

Remember that default functions like _init, and most notifications such as _enter_tree_exit_tree_process_physics_process, etc. are called in all base classes automatically. So there is only a need to call the function explicitly when overloading them in some way.

[GodotDocs] GDScript

16. 如何设置新的随机种子?

randomize()

17. 如何设置断言?

assert a==b

18. 如何获取run time type?

is
get_class()

19. 如何脚本创建timer?

get_tree().create_timer(1.5)

20. 某个节点如何获取编辑他的场景的根节点?

Node.owner

21. 如何使用AutoTile?

[GodotQ&A] How do I use the AutoTile in Godot 3.0?

22. 使用其他线程创建实例为什么感觉这么慢?

对,是个已知问题。
[GithubIssue] Multithreaded instancing performance (RID caching needs to be completed) #10970 
[blog] 关于godot中多线程生成场景加入主场景问题

23. Tiled可以和godot 3一起工作么?

可以,AssetLib搜索插件Tile Map Importer。
[Youtube] How to Import Maps from Tiled in Godot
[reddit] Importing from Tiled with collisions?

24. set_process,set_physics_process,set_process_input 是默认为true的吗?

是的。不同于Godot 2, 在Godot 3中他们默认为true。

25. 什么情况下属性的setter,getter才会被调用?

外部访问时。内部用self.prop1的方式访问时。

26. gdscript 有哪些内建函数可用?

[GodotDocs] @GDScript

27. gdscript 有访问限定符么?没有的话如何控制访问权限?

没有 access modifier.
语法上不能控制。设计上依然可以通过良好的约定和命名法来表征设计约束。
Keep it simple and stupid.
[Widipedia] Access Modifiers
KISS (Keep it Simple, Stupid) - A Design Principle

28. 为什么我的派生场景无法访问父场景的成员变量?

情况之一: 如果你的父场景自带一个脚本,派生场景不带脚本,会无法访问。这种情况下,应取消父场景中脚本的绑定,在子场景中重新绑定一次父脚本或者创建一个继承于父脚本的新脚本。
[GithubIssue] GDScript: Parent variables aren't accessible in sub classes

29. 为什么godot编辑器对我的键盘输入没反应了?

切换完输入法以后请额外按一次Ctrl以解除这个状态。

30. 如何全局自定义鼠标光标? (为什么它不工作?)

TBD
[GodotDocs] Customizing mouse cursor

31. 关于svg格式的支持

[GodotQ&A] Is there .SVG file support for Godot?

32. 如何得到String中的每一个字符?

[GodotQ&A] How to split a string character by character?

33. 如何得知node是否被free?

[GodotQ&A] How to know a node is freed (or deleted)

34. 如何检查手柄是否连接?

[GodotQ&A] Check if Controller connected or not

35. 如何检查一个变量是否存在?

if "varName" in get_parent(): print("varName is defined in parent!")
[GodotQ&A] Check if script of a node has variable

36. 为什么游戏总是会对手柄输入进行响应,即使窗口已经失去焦点?有什么办法能控制是否屏蔽这种行为么?

取决于OS的处理,通常OS总是会把手柄响应发给程序,而键盘输入则不同。
如下方法可以获知焦点状况。

func _notification(what):
    if what == MainLoop.NOTIFICATION_WM_FOCUS_OUT: 
        Unfocused = true
    elif what == MainLoop.NOTIFICATION_WM_FOCUS_IN: 
        Unfocused = false

[GithubIssue] Games receive joypad input even if the game window is not focused

37. 如何导出Android apk?

导出三件套

Godot 3.1

1. 如何使用3.1新特性 typed GDScript?

LEARN TO WRITE TYPED GDSCRIPT IN GODOT 3.1

Networking

1. localhost、127.0.0.1 和 本机IP 三者的区别?

[知乎] localhost、127.0.0.1 和 本机IP 三者的区别?
[百度知道] localhost,127.0.0.1 和 本机IP 三者的区别

一般话题

1. 如何在GLSL中将RGB值转化为HSV?

[SO] From RGB to HSV in OpenGL GLSL

2. 如何描述FSM?该从哪里开始?

可以使用UML来描述。
[Wikipedia] Unified Modeling Language
[Wikipedia] UML state machine
State Machine Diagram Tutorial
[blog] 【学习小记】UML——状态机图

Git

1. fast forward 是什么?

Git fast forwards and branch management

2. rebase 是什么?

Merging vs. Rebasing
Don't Be Scared of git rebase

MarkDown

  1. 关于MarkDownDaring Fireball: Markdown Syntax Documentation

Other Tools

1. 有哪些开源免费的绘画工具?

GIMP, Krita, Aseprite
GIMP - GNU Image Manipulation Program
Krita | Digital Painting. Creative Freedom.
Aseprite - Animated sprite editor & pixel art tool

2. 有哪些tilemap 编辑器?

Tiled Map Editor | A flexible level editor
[Docs] Tiled Documentation

3. 制作系统UML图和FSM图有什么工具吗?

VP Online
Lucidchart: Online Diagram Software & Visual Solution

4. 常见游戏术语的定义

Gaming Dictionary

5. 动作设计软件

MakeHuman
[Youtube] MakeHuman 1.1 -- A Completely Free 3D Character Creator
design doll
[Youtube] [TUTORIAL] Design Doll
iclone
Daz3D
Daz3D Documentation Center

6.函数绘制

Desmos

7.界面设计原型工具

Pencil Project

(转发自:原日志地址
《千手英雄》开发日志 - 可以结束了~
Bitca Games 2022-12-21

《千手英雄》这个游戏在 2019 年底 2020 年初开始制作,当时参与人员 3 人,同年 6 月份完成了 6 个游戏并发布了 EA 版本。3 年之后的 2022 年底,这游戏终于开发完毕了!


Steam 商店页面 : Steam 上的 千手英雄 (steampowered.com)

steam shop


这个游戏的上一篇日志,也就是第一篇日志在这里:《千手英雄》开发手记 | indienova 独立游戏


第一篇日志发布在 EA 时,两年多之前了,喜欢看 UP 主被打脸的同学可以先看那个再回来~


注意,本文大量动图~


温故……EA阶段的 6 个游戏

1 / 10 Star Num

Image title

一个简简单单的射击游戏,我方战机固定在中央,可以正反向旋转。

从四面八方靠近的是各种数字形状的陨石。可以发射'加号'和'减号'两种子弹,打中后数字增加到 10 或者减少到 0 就会爆掉。

武器有温度槽,过热后需要冷却才能使用,打爆所有陨石后游戏胜利。


2 / 10 Candy Box

Image title

消除游戏一个,就是基本的俄罗斯方块的玩法,把最底一行方块消除后游戏胜利。


3 / 10 Cyber Puck

Image title

这个是一个迷宫吃豆游戏,豆子换成黑客帝国中的红蓝药丸,敌人换成 agent,吃到红色药丸后可以爆发将敌人打飞。

吃完所有药丸后会出现一个电话亭,进入电话亭后游戏胜利。


4 / 10 Gun Hat

Image title

一个纵向自动卷轴的射击游戏,西部主题,使用两个按键控制 3 个射击方向,到了关底打败 Boss 游戏胜利。


5 / 10 Jump Man

Image title

接下来是个体育游戏,跨栏,你只有一个对手,除了要跨栏,你还得避开水坑和香蕉皮。

吃到完整的香蕉可以爆发冲刺,比对手先跨过终点便算胜利。


6 / 10 Well Diver

Image title

一个下 100 层的游戏,你不需要真正的下 100 层,但你碰到尖刺就会像气球一样爆掉。

安全到达终点就算胜利了。


知新……完成阶段的 4 个游戏

7 / 10 FAT-CHI

Image title

照顾一只宠物猫咪,方式是输入指令,在规定时间内完全输入正确就会得到一颗心,满心后游戏胜利。


8 / 10 Backstreet Fighter

Image title

只有一个招式的格斗游戏,适合喜欢扇耳光和被扇耳光的玩家,随着力量的积蓄,攻击力度会加大。打到对方血槽空就胜利了。


9 / 10 Hot Road

Image title

死亡赛车,可以安装 6 管机枪射击前方车辆,也可以使用超级加速进行弯道超车,规定时间内跑完 3 圈就算胜利。


10 /10 Legend Of Lin

Image title

不是林克,是林道长,但同样要走迷宫。发射符咒定住敌人,木剑是自动攻击的,找到钥匙开门打败最终 Boss 就胜利了。


设计

挑战模式

这 10 个小游戏,每个长度差不多都在 1分钟左右,这个长度主要就是为了挑战模式而设计的:

这些游戏在挑战模式中可以并行执行,其中一个是全速状态,而其它的则是'子弹时间'状态。

Image title



画廊模式

当然每个游戏都可以单独的玩,我设置了个'画廊'模式:

Image title



编辑器模式

以防有人很喜欢这个游戏,并想自己'制造'一些挑战,我们做了个编辑器,自己编个关卡,还可以分享给朋友:

Image title


那么,以上就是这个游戏的全部了~


而在这个作品之前,我上一个发布的游戏是《孙悟空大战机器金刚》:Steam 上的 孙悟空大战机器金刚 / Sun Wukong VS Robot (steampowered.com)

Image title

那是一个像素的平台动作游戏,相关的日志写了十多篇,就不在这里赘述。

这个游戏除了登录 Steam 之外,还由 Indienova 搬到了任天堂 Switch 平台:Sun Wukong VS Robot for Nintendo Switch - Nintendo Official Site

Image title


而在《千手英雄》中,为了取得更好的成绩,我开始考虑怎样的游戏才能吸引玩家购买。

于是我选择了做一些有'创意'的东西,因为我当时觉得独立游戏就是古古怪怪的专门吸引猎奇的玩家,

而《千手英雄》的特别之处就是这个'同时运行多个游戏的机制',简单点说就是同时开了若干个不同游戏的窗口。

那么效果如何呢?请接着往下看……


市场

对比销量

上一篇开发日志的结尾中,我定下的目标是卖出比《孙悟空大战机器金刚》更多的份数,毕竟想给合作者一个交代。那么《孙悟空大战机器金刚》卖出了多少呢?

Steam平台到现在大约 2400 份,愿望单数为 3913,评论数为 63。


而《千手英雄》EA直到现在售出 33 份,愿望单 657,如果我没记错的话,在EA之前,在其乐搞的抽奖活动,愿望单已经 450 左右了。


差太远了,真烂啊,对吧?


入围 WePlay

唯一算得上有点建树的可能就是游戏入围了 2020 年WePlay的'最佳创新'游戏评选,

注意只是入围,这很有可能是这个游戏唯一的高光时刻:2020 indiePlay中国独立游戏大赛入围名单公布!

Image title

Image title

Image title

上图的这位阿姨,我猜这应该是她在这个展馆中唯一会玩的一个游戏。


不过能参加这个展会真的很高兴,增长了许多见识,也与很多素未谋面的开发者见了面。


为何无人问津呢?

好了,让我来猜测一下失败的原因,开发者朋友们可以参考一下:

  • Steam 的自然流量越来越少了。
  • 海外宣传工作难开展,所谓的出海,不是一般的国内独立开发者能操作的。
  • 国内的独立游戏越来越多,反响却越来越少了,关注度被稀释了。
  • 单机PC游戏在国内是被冷落得对象,TapTap 这样以手游为主的平台已经不再支持开发者登记PC单机游戏,当然除非你名声在外。
  • 国内能够连接开发者和普通玩家的平台太少了,围脖什么的,各种莫名奇妙的限流。

以上可以算作外因,环境因素,东西不行,还是多从自身找原因:

  • EA策略不对,EA 没有流量,EA 只适合众筹的作品。
  • 没有宣传,很多跟我一样的独立游戏的朋友都会收到这样的反馈“你的游戏还不错,为什么就不会宣传一下呢?”,这是个通病。
  • 没有话题性,就是个平庸的独立游戏。
  • 所谓的'创新'并没有能够成为商品的卖点,这种点子用在 gamejam 上可以,用到商品上很勉强,除非这创意很好,并且有大量的内容支撑,但事实上不行。
  • 画面太复古,像素画给人一种廉价的感觉,做得不惊艳等于赶跑了很多玩家。
  • 目标受众太小圈子,而且也没有曝光在目标受众面前。本来复古的画面就不吸引人,如果还想吸引那些喜欢速攻和挑战的 GDQ 玩家就更难了,毕竟可以拿来速攻的游戏大多有广泛的群众基础和知名度。
  • 作品体量太小了,不耐玩,也不好玩。把这放在最后一点,就是说只要这点做到了,前面的都不是问题了。


结案

游戏在 EA 之后的表现,似乎已经被证明游戏没有什么市场价值,所以继续开发基本没有什么动力了,能够完成也是为了做到有始有终。

有失必有得,下面说点正面的:


历练

做完 10 个小游戏,但这 10 个游戏基本类型各异,可以说相当于参加了 10 次 Gamejam。其实我们半年时间就已经完成了完整版的游戏了,而且都是在业余时间做,因为白天还有主要项目要做。所以要说三年开发了这个游戏,其实所花的时间成本并没有太高,当然,在后期的 4 个游戏以及各种优化打磨方面我确实花了蛮多时间。


Godot 引擎

游戏开发启动时,就选择了使用 Godot 引擎,2019 年底时 Godot 引擎已经相当成熟了,特别是对于 2D 游戏来说,而且这个引擎的使用体验很符合我的理念:小巧快速。我比较喜欢开源软件,以前做动画时用 Blender,那时可以看着它逐渐成长变强。到现在做游戏也一样,我基本上每天都关注引擎的代码更新,遇到引擎 BUG 立马提交。通过这两三年的使用,我已经很熟悉这个引擎了,以后将一直使用这个引擎,更多的精力可以花在游戏本身了。


Switch

在 2020 年参加 WePlay 的时候,我去参加了一个任天堂关于独立游戏的线上讲座。当时他们的负责人就说 Switch 欢迎全世界的独立游戏开发者提交自己的游戏到他们的游戏商店中。后来有朋友就跟我说何不试试申请他们的开发机?于是我抱着试一试的心态在网上提交了申请,没想到最后还真申请到了。后来我开始研究怎么把 Godot 移植到 Switch 平台,移植成功后的优化等问题。等《千手英雄》完成后,我打算尝试发布到这个平台上。我想这也是机缘巧合下的一种收获吧。


继续前进

我的独立游戏之路还在继续,《千手英雄》这个游戏给我带来了不少收获,对于做什么怎么做等问题有了更明确的思路。新的一年将要开始,新的旅程也要展开了。感谢大家的关注,希望以后能有更多有趣的内容能跟大家分享,感谢观看,再会!

Image title



(转发自:原日志地址
在Godot实现按照固定大小切割数组(分组)

https://godoter.cn/d/308-zai-godotshi-xian-an-zhao-gu-ding-da-xiao-qie-ge-shu-zu-fen-zu

extends Object

class_name ArrayUtils

static func split_array(array:Array, step:int) -> Array:

var output = []

for i in range(0, len(array), step):

var each = array.slice(i,i + step)

output.append(each)

return output

pass

(转发自:原日志地址
时隔2年,修复了一下安卓多分辨率的问题

时隔2年,修复了一下安卓多分辨率的问题

(转发自:原日志地址
【踩坑记录】关于godot中的Resource引用问题

【踩坑记录】关于godot中的Resource引用问题


(转发自:原日志地址
【干货】制作 oblique/cabinet 投影的2D游戏时,确认精灵先后顺序的排序方法
Lanza 2021-02-02

我经常说,我们以前玩过的很多游戏虽然看上去是2D的,但其实是3D游戏——比如热血物语、双截龙系列,它们具有一套三维的立体空间逻辑,从而不再使得游戏的舞台限制在只有左右移动和上下跳跃这两种轴向——拥有三个维度极为自由开放的移动范围相信也给很多玩家留下了深刻的印象,在上次的文章(https://indienova.com/indie-game-development/make-a-fc-nes-collision/)里,我们已经介绍了这种游戏的物理部分简单实现,本次将会聊到这类游戏在渲染上的重要技巧。

Image title

显然,在用2D精灵模拟的3D空间里,任何一个物体都不简单:如图所示的一个立方体,具有一个oblique视角(或者,我们叫cabinet视角更准确)的造型。这样的视角下,如果有另外一个精灵和它相互遮挡,应该怎么处理它们的渲染顺序关系呢?

注:为了简化描述,本文章的3D空间假设所有物件的z坐标都相同,即"没有和地面的高度差"。

任意两个物体之间的排序

按照我们一般的简单思路(比如在经典的topdown视角下,如RPGMAKER系列),谁在世界上具有较大的y值,谁就靠近屏幕,谁就应该更靠后地渲染,对吧?不过在这样的视角下,就没有那么简单了,来看一个例子:

Image title

如图,根据十字形标记,我们可以轻松地发现shari的y坐标小于立方体的坐标,所以她理应被挡在立方体的后面(先于立方体渲染),对吧?可是当她来到另外一侧的时候,情况就变化了:

Image title

如图,shari的y坐标仍然小于立方体的y坐标,但这个位置上,我们是希望shari遮挡立方体而不是被立方体遮挡的,所以我们需要考虑到更多的情况。

Image title

显然,这个世界里的任何一个物体都应该携带它的"底座"信息。如图,红色的平行四边形就是上面立方体的"底座"信息,而绿色是它的高度信息(如果你需要一些更细致的排序,比如算上z轴,本文暂不讨论)有了这个信息,我们就可以确定任意两个对象之间的绘制顺序。


我们把两个要排序的物体分别命名为A和B,A底座的上边缘称为aTop,下边缘称为aBottom;B底座的上边缘称为bTop,下边缘称为bBottom。

aBottom的y坐标小于bTop的y坐标:

Image title

这时候A显然是在B的后方,也就是A先于B渲染。

aTop的y坐标大于bBottom的y坐标:

Image title

这时候A显然是在B的前方,也就是B先于A渲染。

其它的情况:

Image title

由于我们这里的视角下,侧面是一个45度的斜线,所以我们可以很轻松地把两个物体下边缘的y轴距离加到aBottom的右端点去进行判断,像是这样:

distanceY = bBottom.y - aBottom.y

(我们用left和right表示一个线段的左端点右端点)

aBottom.left.x > bBottom.right.x + distanceY

如果这个判断是成立的,那就是上图所述的情况:A在B的前方,也就是B先于A渲染。

否则就是下图所述的情况:A在B的后方,也就是A先于B渲染。

Image title

对场景内所有物体的排序

好,至此我们已经可以为场景内任意的两个对象进行排序了,那么,我们也可以为场景中所有的对象组织排序。很多人的第一反应是,直接用冒泡/选择/归并/快排这样的算法来对场景中需要排序的物体进行排序不就好了吗?其实这是不合适的,因为场景中的物体遮挡存在拓扑关系,如果直接使用线性表的排序方法,可能在swap一对物体以确保他们遮挡关系的同时又会直接破坏了另一对物体的遮挡关系,因此,我们的第一步是对场景中需要排序的物体进行连线。

Image title带有弧线的那一端是射线的末端,末端的物体比首端的物体更先渲染(位置更靠后)。

这里说明一下,我们只对需要排序的一对物体进行连线(出于性能考虑),所以你的显示对象可能需要有相应的bounding_box或是什么的。

Image title

当场景中的物体足够多,遮挡关系足够复杂时,不难发现,我们的物体遮挡关系实际上构建出了一个有向图。而有向图可以通过拓扑排序来获取一个不唯一的线性序列,照着这个线性序列进行渲染就可以获得正确的场景排序了。

Image title

(图中蓝绿色的矩形是判断精灵相交/重叠用的bounding_box,DRAWIDX则是拓扑排序后得到的渲染顺序值)

这样,就得到了整个场景的正确呈现顺序。

最后一点问题

相信我们都看到过一些"视觉错觉"的趣味图片,对吧?譬如三个无限循环的台阶,相互遮挡的三个棱柱……之类的,很有意思,不过在游戏开发当中可一点也不有趣。很显然,当游戏场景中出现了几个互相存在遮挡和被遮挡关系的对象时,我们构造出的有向图就成环了。而成环的有向图是不能进行拓扑排序的,这就导致我们没有办法去按正确顺序呈现画面。解决这个问题的最好方法就是把容易引起该问题的那个物体分割为两个物体,使得其中的一个部分"专门遮挡"而另一个部分"专门被遮挡",这样就可以消除我们构造的有向图中的环。

本次的讨论就到这里,祝大家开发顺利,新年快乐!

(转发自:原日志地址
godot游戏引擎实现窗口失去焦点后暂停

学习godot游戏引擎也有一段时间了,这的确是对2D游戏支持比较友好的游戏引擎。封装了许多好用的函数。


在大部分主流桌面操作系统,都拥有“窗口”的概念。因此,运行在电脑上的游戏,也许要考虑窗口的影响。因为对于运行在手机上的游戏来说,几乎所有手机都是全屏运行的,因此不会考虑这个问题。

窗口失去焦点后暂停,是对运行在电脑上的单机游戏的一个可选功能,也仅限单机游戏,因为网游一般没有游戏暂停功能。

而这个功能我刚好要用到,就学习了一下。可以在在玩家切换窗口时,或者受到弹窗影响时,游戏会自动暂停。


游戏暂停


首先,实现游戏暂停。在godot游戏引擎中实现游戏暂停非常简单,在任何一个继承Node类或者是Node类的子类的脚本中输入一句代码就可以实现:

#暂停
get_tree().paused = true

(这些代码是godot游戏引擎内置的脚本语言:GDScript


运行这句代码后,游戏就会卡住,玩家无法对游戏进行任何操作。

当然,我肯定还是希望玩家能够返回游戏的,所以在暂停之前,先显示一个取消暂停的菜单。


制作暂停菜单是话题之外的内容,如果解释得太详细会写得很长……

因此暂停菜单我用一个注释来表示,GDScript的注释用井号#

#暂停前显示一个暂停菜单
#暂停
get_tree().paused = true

不过这个菜单也会受到暂停的影响而失效,因此要让这个菜单不受到暂停影响。

在属性面板中,将这个菜单的暂停属性改成Process,这样子,这个菜单就不会受到暂停影响了。

节点的暂停模式属性

此时,游戏暂停就做好了。接下来,做个窗口失去焦点的判断就行了。


窗口失焦判断


要知道窗口是否失去焦点,需要覆盖一个叫做_notification()的函数。这个函数会被引擎调用,并且传入一个参数,这个参数就是游戏运行的状态。只要判断这个状态就可以了:

#重写_notification()函数,what参数是引擎的状态
#判断:如果窗口失去焦点
#暂停

func _notification(what): 
    if what == MainLoop.NOTIFICATION_WM_FOCUS_OUT: 
        get_tree().paused = true

可以看到,godot实现窗口失焦暂停还是很简单的,3句代码就搞定了。因为制作暂停菜单是额外的代码。


如果需要了解怎么解除暂停,只需要将paused变量设置为false就可以了:

#解除暂停
get_tree().paused = false


效果示范

下图为演示效果(图中“已暂停,继续”的菜单表示暂停菜单):

用鼠标在窗口之间切换


代码参考

下图为实现上图效果的代码:

窗口失去焦点暂停代码例子

(转发自:原日志地址
tile based随机地图--检测地图连通性
廿木 2020-01-06

        最近在做一个游戏,用到随机地图算法,参考了这篇文章[随机生成 Tile Based 地图之——洞穴],深受启发的同时,又想彻底解决概率极小的生成了不可连通地图的情况,所以就写了个地图连通性检测的函数,这里跟大家分享一下思路。

大概思路:

假设有一个地图,白色方块表示路,黑色表示墙:Image title

地图连通意味着,任意两个方块可以互相抵达,那不连通就意味着,如果我从任意一条路开始走,走完我能走的所有路,地图上仍然有路,那就是不连通的。所以我只要走完我能走的路,再看看地图上还有没有路就可以了,如下图(红色是起始点,绿色表示我能走的我都走过了):

Image title

如果从右下角的方块开始走也是同理:

Image title

以上就是大概思路,简单粗暴。



编程思路:

可用广度优先或深度优先算法来暴力走图。我这里用的是广度优先。

1. 建一个跟地图大小一样的数组,用于表示哪些方块走过了:(黑色表示没走过,白色表示走过,初始值为都没走过)

Image title

2.  建一个队列用来表示哪个方块是可以走的,并以队列第一个方块为起点检测四周的方块是否连通,如果连通并且在第1步中的数组中表示该方块没有走过,就把联通的方块再放到队列里:

Image title

3.  每走过一个方块,或者每检测到一个连通的方块就更新第1步的数组:

Image title


4. 把队列第一个方块丢掉,重新返回第2步,直到队列里的所有方块都清空。Image title

5. 最后,把原地图与第1步建的数组做匹配,如果相等,则连通,否则不连通:

Image title



[godot]效果展示:

有请劳模godot图标(Image title)上场,左边的是算法生成的地图,godot图标表示墙,空地方表示路,右边是上面第1步的数组:

连通图:左右相等

Image title


不连通图:左右不相等

Image title


[spoilerblock text="godot代码"]

#广度优先算法,检查连通性

func checkConnectivity(map) ->bool:

    var queue:=[]#队列
    var myMap:=[]#与地图等大小的数组
    var wall:=false #墙
    var road:=true #路

    for i in range(map.size()):
        var mRow :=[]
        for j in range(map[i].size()):
            mRow.append(wall)
        myMap.append(mRow)

    for i in range(map.size()):
        var isIt:=false
        for j in range(map[i].size()):
            if map[i][j] == road and queue.empty():#找到第一块路
                queue.append(Vector2(i,j))
                isIt = true
                myMap[i][j] = map[i][j]
                break
         if isIt:
             break

    while ! queue.empty() :
        var curPos = queue.pop_front()
        #放入myMap表示这个位置检查过了
        #如果某个方向上也是非空的位置,并且没有检查过,就放入队列
        var upX = max(curPos.x -1,0)
        var downX = min(curPos.x+1,map.size()-1)
        var leftY = max(curPos.y-1,0)
        var rightY = min(curPos.y+1,map[curPos.x].size()-1)
        if map[upX][curPos.y] and !myMap[upX][curPos.y]:#上
            queue.append(Vector2(upX,curPos.y))
            myMap[upX][curPos.y] = road
        if map[downX][curPos.y] and !myMap[downX][curPos.y]:#下
            queue.append(Vector2(downX,curPos.y))
            myMap[downX][curPos.y] = road
        if map[curPos.x][leftY] and !myMap[curPos.x][leftY]:#左
            queue.append(Vector2(curPos.x,leftY))
            myMap[curPos.x][leftY] = road
        if map[curPos.x][rightY] and !myMap[curPos.x][rightY]:#右
            queue.append(Vector2(curPos.x,rightY))
            myMap[curPos.x][rightY] = road

    #上面这一段while在检查完一片完整的区域后就会结束,所以如果还有第二片完整的区域,map!=myMap,就是不连通地图
    for i in range(map.size()):
        for j in range(map[i].size()):
            if map[i][j] != myMap[i][j]:
            return false
    return true

#按上面这段代码的缩进复制到godot里面是会出错的,所以不能直接用的哦

[/spoilerblock]




(转发自:原日志地址
Godot 武侠与江湖 开发杂记
ttwings 2019-02-12

开发随笔

git地址: https://github.com/ttwings/WuXiaAndJiangHu_Godot

  • RPGmaker类行走图素材处理

    含金量不高,只是作为自己的一点记录。 注:绝大数素材来源于网络,仅为学习使用(极个别是自己绘制的)

    1. 行走图素材
    Image title


    1. 新建Sprite,将序列图纹理素材导入 
    2. Image title
    3. 新建AnimationPlayer,新建4个方向的行走动画
    4. 点击frame右边的钥匙标记,可以快速添加动画 
    5. Image title
    6. 添加RayCast射线检测,用于简单碰撞 
    7. Image title
    8. 添加脚本,实现控制,脚本未做清理
          extends Area2D
     export(int) var head_id = 0
     var tile_size = 32
     var can_move = true
     var facing = 'right'
     var moves = {'right': Vector2(1, 0),
      'left': Vector2(-1, 0),
      'up': Vector2(0, -1),
      'down': Vector2(0, 1)}
     var raycasts = {'right': 'RayCastRight',
     'left' : 'RayCastLeft',
     'up': 'RayCastUp',
     'down': 'RayCastDown'}
    
     func move(dir):
     if get_node(raycasts[facing]).is_colliding():
     return
     facing = dir
     can_move = false
     $AnimationPlayer.play(facing)
     $MoveTween.interpolate_property(self, "position", position,
     position + moves[facing] * tile_size, 0.6,
     Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
     $MoveTween.start()
     return true
    
     func _on_MoveTween_tween_completed( object, key ):
     can_move = true
    
    
  • RPGmaker类自动地图在godot中使用

    1. 新建Tiledmap,新建TileSet,设置合适的cell大小

    2. 注意:rpgmaker类,自动地图,表面是3232大小,但在实际处理时,底层是再次分解为1616计算的,因此这里需要设置为16*16

    3. 点击tilemap里面的tileset下面的"打开编辑器"

    4. 新建autotile,设置snap options里面的 step为16*16,设置bitmask(这里是自动地图的关键),如图测试,如果不对可以重新编辑

      Image title


    5. 注意图中的例子并不完整,剩下的需要自己摸索,下面给点提醒

    6. 自动地图,需要底层和表层同时设置才行。像例子中并没有弄底层,只考虑了表层,而且图中的素材为网络拼凑而成,缺少合适的底层,并不可取。

    7. 简单图形的自动地图,如矩形类地毯,可以只管下面的部分,这个时候,cell大小可以设置为3232,同理snap options里面的 step也可以设置为 3232。

  • 基本保存

  func save(content):
    var file = File.new()
    file.open("user://save_game.dat", file.WRITE)
    file.store_string(content)
    file.close()
func load():
    var file = File.new()
    file.open("user://save_game.dat", file.READ)
    var content = file.get_as_text()
    file.close()
    return content
  • json的基本使用
  var p = JSON.parse('["hello", "world", "!"]')
if typeof(p.result) == TYPE_ARRAY:
    print(p.result[0]) # prints 'hello'
else:
    print("unexpected results")
  func load_data(path:String):
var load_data = File.new()
if not load_data.file_exists(path):
print_debug("not exists file")
return

load_data.open(path,File.READ)

var data_str = load_data.get_as_text()
var p = JSON.parse(data_str)
return p.result
  • 获取目录下特定后缀文件
  func dir_files(path,suffix):
var dir = Directory.new()
var files = []
if dir.open(path) == OK:
dir.list_dir_begin()
var file_name = dir.get_next()
while (file_name!= ""):
if dir.current_is_dir():
print("Found directory" + file_name)
elif file_name.split(".")[-1] == suffix:
files.append(file_name)
print("Found file: " + file_name)
else:
print("Found file: " + file_name)
file_name = dir.get_next()
else:
print("An error ccurred when trying to access the path.")
return files
  • 读取文件夹下图片素材生成动画

    假如我们的动画文件在单个文件夹中

            Image title

  extends Node2D
# 选择目录下的png
export(String,DIR) var path
export(String) var anim

func _ready():
    $AnimatedSprite.frames = creatAnimation(path)
    $AnimatedSprite.speed_scale = 4
    $AnimatedSprite.play("default")
    pass

func dir_contents(path):
    var dir = Directory.new()
    var file_list = []
    if dir.open(path) == OK:
        dir.list_dir_begin()
        var file_name = dir.get_next()
        while(file_name != ""):
            if dir.current_is_dir():
                print("Found directory: " + file_name)
            else:
                if file_name.split(".")[-1] == "png":
                    file_list.append(file_name)
                    print("Found file: " + file_name)
            file_name = dir.get_next()
    else:
        print("An Error occurred when trying to access the path.")
    return file_list

func creatAnimation(path:String):
    var sprite_sheet = dir_contents(path)
    var sprite_frames = SpriteFrames.new()
    for i in sprite_sheet.size() :
        var frame = load(path + "/" + sprite_sheet[i])
        sprite_frames.add_frame("default",frame)
    return sprite_frames
  • 利用ninepach简单风格的对话框

    一些国产游戏会用到的内圆弧的风格 Image titleImage title

    做这个效果很简单

    1. 准备素材 Image title

    2. 新建NinePatchRect然后设置合适的pachmargin Image titleImage title

    3. 将其他的控件放到NinePatchRect下。按钮类,可能需要设置flat为true。否则会调用按钮的风格。 也可以以此风格为主,深入修改Theme,那样需要较多的绘制,不擅长,不做累述。 

(转发自:原日志地址
[addons] rotbone(用于像blender那样在3d视图中旋转选定的bone)win x64
imdjs 2019-01-01

这是我的第一个插件 for godot 3.1,还需要改进。

我写这个工具,主要是想简单地修改一下从其它软件导入godot中的骨架的pose,而不用重复导入,但操作很像在blender里调整骨骼的过程.

这个工具使用了gdnative写的.但以后可以用gdscript重写一遍,主要也是为了学习与实践gdnative.

使用方法:

在场景串选中骨架 Sceleton 后,按中键是选择单个bone 然后点击R键一下并移动鼠标是旋转bone,按alt+r 一下 是归零pose.

当按了R键进入旋转状态时再按 X 或Y 或Z 键是限定轴向旋转. 

Image title



相关帖子:

https://godotdevelopers.org/forum/discussion/20360/addons-rotbone-for-rotating-a-selected-bone-in-the-3d-viewport-like-blender-does-win-x64?new=1

下载:

https://pan.baidu.com/s/1gRqwcoM2dkQn9njq0ftJDA

(转发自:原日志地址
gdscript自动补全与语法高亮(for notepad++)
imdjs 2018-12-24

相比godot的文本编辑器,我个人更喜欢用notepad++写代码.当然用外部编辑器写gd脚本会用点不方便,

于是我改造了notepad++ 的自定义语言,增加了gdscript的支持,包括自动补全与语法高亮.

Image title



以下是两个放进notepad++ 的文件:

链接:https://pan.baidu.com/s/1Efx13JREwZWX3V2uMR4uUA   提取码:r5zd

使用方法:

打开notepad++的自定义语言面板,导入notepad++自定义语言(gdscript)版.xml,

然后打开E:\Notepad++\plugins\APIs\文件夹 把 gdscript.xml 文件放进去,

那么在notepad++ 编辑gdscript脚本时就有自动补全与语法高亮.


(转发自:原日志地址
从零开始学习并参加 Godot Game Jam 的快速指南
Bitca Games 2018-12-20

从零开始学习并参加 Godot Game Jam 的快速指南

引擎下载

Godot 完全开源免费,整体只有一个 50m 左右的执行文件,Windows,Mac,Linux 版本都有,可到官网 https://godotengine.org/ 下载。

项目目录组织方式

Godot 项目以目录作为单位,所有的代码/资源/插件都在这个目录里,可以进行整体的复制和移动。在项目管理器中可以导入/打开和新建项目,每个项目的根目录下都有一个 project.godot 文件,对应的就是菜单中的 Project Settings 里面设置的内容。



Image title


创建场景 Scene

场景 Scene 是 Godot 的基本资源组织方式,每个场景都是一棵节点树,场景可以层叠嵌套。



Image title


创建节点 Node

Node 节点是按照功能进行划分的游戏内物体,在场景面板中添加节点时会弹出一个树状结构的节点图,可以看出来,Godot 的节点系统是完全按照面向对象 OOP 方式进行设计的:



Image title


游戏窗口设置

菜单 -> Project -> Project Settings -> General (选项卡) -> Display (左侧目录) -> Window



Image title


编程

Godot 首推使用 GDScript,一种类 Python 的脚本语言,只需读完这个页面就可基本掌握: http://docs.godotengine.org/en/3.0/getting_started/scripting/gdscript/gdscript_basics.html

Godot 内置代码编辑器,在 Godot 内编写代码即可。

切换场景编辑和脚本编辑

在工具栏中有 4 个按钮可以进行编辑主视图的切换,快捷键对应 F1 ~ F4



Image title


为节点挂载脚本

在场景面板点击这个按钮可以为所选节点创建或者挂载一个脚本:



Image title


节点的两个重要函数

_ready 和 _proecces 一般来说你的代码会写在这两个重要的函数里,对应的分别是节点初始化完成后和每帧结束前,这两个函数都会被调用。



Image title


运行游戏

使用快捷键或者右上角的按钮:



Image title


F5 运行项目
F6 运行当前场景

资源的导入

将资源(比如 srpite sheet 图片)复制到项目目录即可,Godot会自行导入,如果要更改导入设置,点击相对应的文件后在 Import 面板进行修改。

活用搜索

面对大量的节点,文件,属性,Godot 在很多面板中都设置有搜索框,在这些搜索框中输入关键字可以快速的定位自己想要的内容:

添加节点面板



Image title


文件面板



Image title


属性面板



Image title


另外场景和脚本可以使用快捷键快速打开,这里可以进行快速的搜索:

CTRL + SHIFT + O 快速打开场景



Image title


CTRL + ALT + O 快速打开脚本



Image title


更多有用的中文社区资源

indienova 的 Godot-StartUP 小组 https://indienova.com/groups/309
,小组内有相关 Q 群。

最终参考

Godot 文档已经相当完备,包括新手指引,引擎介绍,API等都在这里了:http://docs.godotengine.org/


顺带附上 Godot Game Jam 地址 : https://itch.io/jam/godot-jam

(转发自:原日志地址
在gdnative里卸载自身dll
imdjs 2018-12-20

在gdnative里卸载自身dll,而不用重启godot

godot的gdnative库是以dll的动态链接库的形式载入到主程序的.所以一但打开了godot成功载入了dll 那么,就无法重新编译dll的代码,直到关闭godot,dll从内存卸载才可以重编译.

我也没有在gdnative的相关代码里找到可以用脚本卸载dll的方法.于是百度了一下.找到一个可以用dll卸载自身的方法.也就是这个卸载的函数是在dll自己的代码里.

HMODULE hmSelfG=NULL;

//====Unload Self dll ====================================

DWORD UnloadSelf(PVOID param)

    {    

    FreeLibraryAndExitThread(hmSelfG, 0); 

    return 0;  

    }  

void  UnloadSelfEX()

    {

    CloseHandle( CreateThread(NULL, 0, UnloadSelf, NULL, 0, NULL) ); 

    }

BOOL DllMain(HINSTANCE hinstDLL, DWORD fdwReason, PVOID lpvReserved)  

{  

    switch (fdwReason)

        {

        case  DLL_PROCESS_ATTACH:

            { 

            hmSelfG = (HMODULE)hinstDLL;  //当dll加载到godot时,获取这个 dll句柄

            break;

            }

    }   

    return TRUE;  

}  

当dll载入时 自动运行DllMain,并把自身的句柄保存到hmSelfG全局变量,当要卸载自己时,只要运行了UnloadSelfEX()函数就可以把自己卸载了.

(转发自:原日志地址
Godot 学习笔记 #02
Bitca Games 2018-12-17

Godot 学习笔记 #02

编辑器的使用

输入法问题

在现在的版本中,在使用中文输入法后,godot 会处在一种 ctrl 键被按下的状态(至少在 windows 平台下如此),这个时候需要按一次 ctrl 键来恢复正常的输入。

操作效率

参看 油管播主 GDQuest 的视频:
Fast Project Navigation Tips in Godot (beginner tutorial)
Make Sense of the Code Reference in Godot (beginner tutorial)

Debug

运行时切换到显示 Remote 的 Tree 和每个节点的属性

这是个不小发现,原来我总是需要打印出场景的树结构,其实运行时在这里可以切换到当前运行的树结构状态:

Image title

而且节点的每个属性都是可以查看和修改的!!!
变化是同步的:

Image title

节点与场景构造

注意场景 inherited 某个 scene 会对属性进行覆盖性调整

有时调整源 scene 的地方,要回头到 inherited scene 中查看是不是被覆盖的。

节点的 Modulate 与 Self Modulate 的区别

Image title

Modulate的 调整会影响子物体,而 Self Modulate 则不会。

脚本相关

GDScript 关于下划线

原来 add_child 可以用 addc... 来自动补完的,不需要输入下划线,排除了下划线对输入的影响,这样的话使用下划线的函数名就方便了

3.1 typing

https://godotengine.org/article/optional-typing-gdscript

现阶段 signal 函数的参数还没有 type hints

Editor Tool

各种资源类型也可以方便的在脚本中设置为编辑器参数:

Image title

export(Material) var output_material

Signal

在连接 signal 到函数时,一定要确保接收函数的参数数量跟事件的参数一致,否则可能无法触发,在 connect 时一定要查看 signal 的参数。

Image title

Image title

Image title

关于 _process 和 _physics_process

https://docs.godotengine.org/en/3.0/getting_started/step_by_step/scripting_continued.html

_process 的执行时间是在每次帧绘制完成后,可以使用 set_process() 来打开或者关闭,只要写了这个函数,默认是打开执行的。而 _physics_process 则是在物理引擎计算之前执行,而物理引擎的计算间隔是固定的,更新频率可以自行设定。

_draw()

https://docs.godotengine.org/en/3.0/tutorials/2d/custom_drawing_in_2d.html

_draw() 只会执行一次,画出来的内容会缓存并一直显示,如果需要更改,可以执行 update(),如果需要每帧执行,那么可以把 update 放到 _process 里

setget 没有触发?

如果在本脚本范围想要触发 setget,需要在变量面前加 self.

export var 的数值范围,参看:

http://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/gdscript_basics.html#exports

# Allow integer values from 0 to 20.
export(int, 20) var i
# Allow integer values from -10 to 20.
export(int, -10, 20) var j
# Allow floats from -10 to 20, with a step of 0.2.
export(float, -10, 20, 0.2) var k
# Allow values y = exp(x) where y varies between 100 and 1000
# while snapping to steps of 20. The editor will present a
# slider for easily editing the value.
export(float, EXP, 100, 1000, 20) var l

场景转换

https://godotengine.org/qa/24773/how-to-load-and-change-scenes

Autoload

如果你设置一个场景(.tscn)为 singleton,那么这个场景上的脚本函数会因为这个 scene 没有 instance 而无法执行,所以应该设置一个 .gd 为 singleton,然后通过这个 .gd 来 preload 并 instance 一个 .tscn。

输入相关

关于 input

https://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html

输入事件有个检查顺序,可以看文档中的图。

  1. 首先是节点上的 _input(ev),这个事件可以用 Node.set_process_input() 来禁用,如果需要不再往下传递,可以使用 SceneTree.set_input_as_handled()。
  2. 然后再到 GUI 各 control 的 Control._gui_input() ,可以通过 Control.accept_event() 停止往下传递。同时可以使用 Control.mouse_filter (参数界面上也有设置)来设置这个控件是否响应鼠标事件。
  3. 然后没有被“消费”的事件,轮到 unhandled input callback 来处理,所以游戏中的操作尽量选择这个来进行处理,特别是需要 GUI 阻断的。

更多详细描述可以看文档。

一个重要的事实:
当你的游戏输入使用 _unhandled_input 来处理鼠标,而且屏幕上覆盖有可见的控件时,鼠标事件会被阻断,这个时候你要设置 MouseFilter 为 MOUSE_FILTER_IGNORE。在使用全屏的控件根物体时,可选择这个选项。如果是一个面板,可以选择 pass ,这个会阻止鼠标事件传递到 _unhandled_input。

https://docs.godotengine.org/en/3.0/classes/class_control.html#enum-control-mousefilter

在节点上取得鼠标在世界中的坐标

    func _input(event):
        if event is InputEventMouseButton and event.pressed and event.button_index == 1:
            print("Mouse Click at: event.position", event.position)
            print("Mouse Click at: ", get_global_mouse_position())

材质和 Shader

Material 多个物体要使用同一个材质

多个物体要使用同一个材质,可以设置为 local to scene ,这样这个材质就单独在这个内在场景中实例化了。多个物体使用一个材质时不会互相影响。

Image title

visual shader

用 visual shader 的时候注意,默认的shader方式是3d的,2d的需要改这里
Mode : CanvasItem

Image title

图像相关

画面的缩放选项

最常规的 2D 像素缩放设置: Mode=2D,Aspect=keep

Image title

关于像素图片资源的设定

第一次打开 godot 3.1 记得将 Import 界面单独放到 Inspector 旁边,否则你有可能会忽略图片资源的导入设置,特别是像素图的 Filter 选项,有可能在视图中显示是不抗锯齿的,实际运行使用的还是 import 里面的配置。另外这里有 Preset 是可以直接设置为 2D Pixel 的。

在低像素分辨率下得到高清字体的方法

先设置字体到合适大小,比如 12,然后根据需要乘于倍数比如 12x4=48,然后将 scale 值设置为 1/4 也就是 0.25。那么比如你的画面分辨率是 480x270 , 1920x1080 的四分之一,那么实际渲染的字体效果在全高清下是不会看到锯齿的。

Image title

Image title

(转发自:原日志地址
Godot 学习笔记 #01
Bitca Games 2018-12-12

Godot 学习笔记 #01

程序部分

关于 _ready

在 _ready 中你可以访问完整的 tree,但是你不能更改 tree 的结构。当我想在 _ready 中把一个子物体移动到另外一个地方时,操作总是无法成功的。这个时候有两种方法:

  • 使用 call_deferred(函数名)
  • 使用 yield(get_tree(),"idle_frame")

相关阅读:
https://twitter.com/reduzio/status/966037750918057987
https://godotengine.org/qa/7336/what-are-the-semantics-of-call_deferred

Editor 相关

GDScript 脚本中只要加入 tool 关键字就会在编辑器中执行

包括子物体的 Visible 显示都可以在编辑器的界面中实时反馈了,要研究的是:

  • tool 所放置的位置有没有关系,之前的代码会不会在编辑器中运行
  • extend 的脚本跟 tool 的关系,(测试是可以运行的)

检测当前是否在 Editor 中运行,使用 Engine.editor_hint 检查

https://docs.godotengine.org/en/3.0/classes/class_engine.html?highlight=editor_hint

渲染相关

Viewport 内的 Node 无法使用移动工具

这个官方还没有解决:
https://github.com/godotengine/godot/issues/20619
https://github.com/godotengine/godot/issues/17739

viewport 中不显示子物体内容?

在使用 viewport 时一定要给一个 camera 配合这个 viewport(最近的一个),否则这个 viewport遇到别的摄影机时就会用别的摄影机.

实现 mask 的尝试

这里有 light mask 的演示 :https://www.youtube.com/watch?v=e5EUZGbrMRY

但是,新渲染引擎中这个功能失效了,需要等待下个版本修复:https://github.com/godotengine/godot/issues/8685

现在的效果是这样:

Image title

已经实现半透明 mask,问题是颜色被灯光叠加了

灯光相关设置为:

Image title

材质需要为 CanvasItemMaterial:

Image title

Light2D

会照亮相应的 Light Mask 层

Image title

CanvasModulate

Image title

这个节点会调整最近一个 viewport 的颜色,不过只要有 Light2D 存在就会失效

Image title

Image title

Outline Shader

https://github.com/steincodes/godot-shader-tutorials/blob/master/Shaders/outline.shader

这个 Shader 用来做人物高亮选择框,不过最好配合 Viewport 使用,在 Sprite 中使用这个 Viewport 作为贴图,然后再加 Shader 使用

Image title

两个数值这么调,正好是一个像素

Image title

UI 相关

UI想要不跟随摄影机移动?

添加一个 CanvasLayer 节点,把相关的 UI Node 放里面

Image title

CanvasLayer 上的 Node 受到了 2D 光照影响?

Image title

需要去掉 UI Node 的 Light Mask

Image title

Image title

打开 2D 光照后 CanvasLayer 下的文字渲染不出来?

Image title

把 Lyaer 调为 0,是什么机制现在还不清楚

Image title

(转发自:原日志地址
Godot学习-2D粒子
godog 2018-11-15


Godot学习-2D粒子


Youtube视频学习笔记


Particles2D 面板属性

Image title




  • Emitting 发射粒子。默认值:true。
  • Amount 在一个发射周期内发射的粒子数量
  • Time
    • Lifetime 每个粒子将存在的时间量。默认值:1
    • One Shot 是否只发射一次。默认是:1
    • Preprocess 粒子系统一开始就好像它已经运行了这么多秒。
    • Speed Scale 粒子系统的运行速度缩放比例。默认值:1。
    • Explosiveness 粒子的发射周期内的发射时间。如果等于0,则周期内均匀发射,如果等于1,则周期一开始就全部发射,可以是小数。默认值:0。
    • Randomness 发射寿命随机比。默认值:0。
    • Fixed Fps
    • Fract Delta
  • Drawing
    • Visibility Rect
    • Local Coords 粒子是否使用父节点的坐标空间。如果为false,则使用全局坐标。默认值:true。
    • Draw Order 粒子绘制顺序。使用DRAW_ORDER_ *值。缺省值:DRAW_ORDER_INDEX
  • Process Material
    • Material 用于处理颗粒的材料。可以是ParticlesMaterial或ShaderMaterial。
  • Texures
    • Texture 粒子纹理。如果 null 粒子将是正方形
    • Normal Map
    • H Frames 纹理中的水平框架的数量。(精灵图水平方向的图片数量)
    • V Frames 纹理中的垂直帧数。(精灵图垂直方向的图片数量)


ParticlesMaterial 面板

Image title




  • Trail(线索)
    • Divisor(除数) 发射器将发射除以trail_divisor粒子的量。剩余的粒子将被用作踪迹。
    • Size Modifile 轨迹粒子的大小将沿着这个CurveTexture变化。
    • Color Modifile 轨迹粒子的颜色会随着这个GradientTexture而变化
  • Emission Shape
    • Shape 粒子将从一个区域内产生并发射出去。使用EMISSION_SHAPE_ *常量作为值。默认值:EMISSION_SHAPE_POINT。
  • Flags
    • Align Y
    • Rotate Y
    • Disable Z 如果真,粒子不会在z轴上移动。默认值:对于Particles2D为true,对于粒子为false。
  • Spread(扩散)
    • Spread 每个粒子发射时的初始方向范围从 +spread-spread。默认值:45。
    • Flatness
  • Gravity(重力)
    • Gravity
  • Initial(初始) Velocity(速度)
    • Velocity 每个粒子的初始速度。
    • Velocity Random
    • Velocity Curve(曲线)
  • Angular(角度) Velocity
    • Velocity 应用于每个粒子的初始角速度。
    • Velocity Random
    • Velocity Curve(曲线)
  • Orbit(轨道的) Velocity
    • Velocity 应用于每个粒子的轨道速度
    • Velocity Random
    • Velocity Curve(曲线)
  • Linear Accel(加速度)
    • Accel 应用于每个粒子的线性加速度。
    • Accel Random
    • Accel Curve
  • Radial(径向) Accel
    • Accel 应用于每个粒子的线性加速度。
    • Accel Random 径向加速度随机比。默认值:0。
    • Accel Curve(曲线)
  • Tangential(切) Accel
    • Accel 切向加速度应用于每个粒子。切向加速度垂直于粒子的速度。
    • Accel Random 切向加速度随机比。默认值:0。
    • Accel Curve 每个粒子的切向加速度将沿着这个CurveTexture变化。
  • Damping(阻尼)
    • Damping 粒子失速的速率。
    • Damping Random 阻尼随机比。默认值:0。
    • Damping Curve 这个CurveTexture上的阻尼会有所不同。
  • Angle(角度)
    • Angle 应用于每个粒子的初始角度。
    • Angle Random 旋转随机比。默认值:0。
    • Angle Curve 每个粒子的旋转将沿着这个 CurveTexture 进行动画处理。
  • Scale
    • Scale 应用于每个粒子的初始缩放。
    • Scale Random
    • Scale Curve(曲线) 每个粒子的比例将沿着这个CurveTexture变化。
  • Color
    • Color 每个粒子的初始颜色。如果定义了Particle2D的纹理,它将乘以该颜色。
    • Color Random 每个粒子的颜色将沿着这个GradientTexture变化。
  • Hue(色调) Variation(变动)
    • Variation 应用于每个粒子的初始色调变化。
    • Variation Random 色调变化随机比。默认值:0。
    • Variation Curve(曲线) 每个粒子的色调将沿着这个CurveTexture变化。
  • Animation
    • Speed 粒子动画速度。
    • Speed Random
    • Speed Curve 每个粒子的动画偏移将沿着此CurveTexture变化。
    • Offset 粒子动画偏移。
    • Offset Random 动画偏移随机比例。默认值:0。
    • Offset Curve
    • Loop 动画是否循环。默认值:false。


制作火炬

Image title




* 新建 scene,根添加 Sprite,将火炬把手添加为Sprite的纹理,Sprite 下添加 Particles2D,在 Particles2D>Process Material>Material 新建 ParticlesMaterial,此时界面如下,红色圈出的白色点就是粒子(此时粒子是很小的白色方块,受重力影响方向为竖直向下,Particles2D中的纹理为空的话,粒子会是正方形)
复制代码

Image title


* 去掉重力影响,如下图

Image title



* 增加粒子射出速度,粒子自左向右射出,可以看到是从同一个点生成粒子向右朝不同方向分散发射,如下图 Image title


* 使粒子从一块区域随机生成,修改扩散角度使粒子朝同一方向发射,而不是扇形,粒子默认是水平射出的,需将Particles2D旋转方向,操作后如下图

Image title

* 将粒子存活时间加长,显示到Sprite的后边,粒子放大,操作后如下图

Image title

* 修改缩放曲线,使粒子逐渐减小,如下图

Image title

* 修改颜色由白色渐变到橙色再渐变到红色,如下图

Image title

* 增加粒子数量,放大粒子所有速度属性,修改随机旋转速度,增加粒子加速度,如下图

Image title

####附:
> 视频地址:https://www.youtube.com/watch?v=awBfTnmgn7k
> Godot官方文档地址
    * Particles2D : https://docs.godotengine.org/en/3.0/classes/class_particles2d.html?highlight=Particles2D
    * ParticlesMaterial : https://docs.godotengine.org/en/3.0/classes/class_particlesmaterial.html?highlight=ParticlesMaterial



关于Godot引擎的preload关键字
carlcc 2018-11-13

版权所有,欢迎转载,转载请说明出处。


关于Godot引擎的preload关键字

1. 本文组织

  1.     背景
  2.     源码分析
  3.     实验验证
  4.     结论


2. 背景

    Godot引擎指引Step By Step -> Resources的Loading resources from code节中有这样一段话:

The second way is more optimal, but only works with a string constant parameter because it loads the resource at compile-time.

    这段话引起了群组成员的讨论,什么叫compile-time?我们认识的GDScript是一门脚本语言还需要编译么?那么编译时加载是什么鬼?

    于是纷纷猜想:

是不是就是说运行时就在内存里了?
 
启动的时候加载在内存里?
 
GD可能是运行时编译,编译的同时加载?
 
c++的运行时加载的?
 
只要是preload的,启动程序的时候都已经读到内存了

    到底是什么呢?咱们来对照源码,并启动C++调试,探究一下这究竟是什么鬼。


3. 源码分析

    先交代一下源码版本:godot-3.0.6-stable

    我们开始吧。


    首先,我们不难找到,在godot源码的 modules/gdscript/gdscript.cpp 第1736行,可以发现,preload是gds语言的一个关键字,并不是函数(教程里经常出现的PI,居然也是关键字),也难怪C#版本的脚本不支持preload(当然,我们可以用别的方式来达到相同的效果)。

    既然preload是关键字,那么肯定会在做词法分析的时候有专门的case处理,好的,我们来到词法分析器的类 modules/gdscript/gdscript_tokenizer.cpp,我们顺利地在104行发现preloadtoken类型。继续,195行发现preloadtoken对应的token常量是GDScriptTokenizer::TK_PR_PRELOAD。

    当然,词法分析期这里具体的实现我们并不关心,我们只关心GDScriptTokenizer::TK_PR_PRELOAD这个token被怎么处理的。于是,我们来到module/gdscript/gdscript_parser.cpp,直接搜索GDScriptTokenizer::TK_PR_PRELOAD就能发现在 388 行是处理改token的case。代码略长,有兴趣的可以自己看看代码,这里只摘出我们感兴趣的部分(第457行开始的部分):

Ref<Resource> res;
if (!validating) {
    //this can be too slow for just validating code
    if (for_completion && ScriptCodeCompletionCache::get_singleton()) {
        res = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(path);
    } else { // essential; see issue 15902
        res = ResourceLoader::load(path);
    }
    if (!res.is_valid()) {
        _set_error("Can't preload resource at path: " + path);
        return NULL;
    }
} else {

    if (!FileAccess::exists(path)) {
        _set_error("Can't preload resource at path: " + path);
        return NULL;
    }
}
ConstantNode *constant = alloc_node<ConstantNode>();
constant->value = res;
expr = constant;

    因为语法分析不只是执行脚本要用,内置的脚本编辑器也会用来检查你的代码的合法性,因此,第一个if (!validating)正如注释所说,作用是加快运行速度,毕竟写代码的时候并不需要加载资源,因此else分支里面也仅仅检查文件是否存在。

    我们真正感兴趣的部分在 if 的 true 分支里,首先检查资源是否已经缓存了,是则用已经缓冲好的,否则现场加载资源,并在后面校验资源合法性。最后三行代码,就是设置 preload语句的返回值,返回的是一个“常量”(对GDScript来说)啦。


说点题外话

相信有经验的C++程序员已经能看出,这里使用的是“某种“智能指针,事实上,Godot的智能指针实现和C++11标准库的实现方式不相同,这里的智能指针的引用计数是被引用的对象所持有的(非常类似于OpenScenGraph里面的,WebRTC源码里面也有大量的这种风格智能指针,核心接口叫RefCountInterface),两种智能指针各有优劣。

    其实到这里,我们就已经能得到结论了:preload在GDScript编译的时候加载,更准确地说,在对GDScript做词法分析的时候加载。 

    因此不再多说。于是……

再说点题外话

其实我们往外找,可以找到modules/gdscript/gdscript.cpp第1831行,ResourceFormatLoaderGDScript::load函数,该函数的作用是加载GDScript(不会直接调用,而是注册到ResourceLoader以后被间接调用)。可以看到,其实Godot引擎除了支持GDScript本身以外,似乎还支持两种扩展名的字节码:.gde和.gdc(有没有想起pyc?)。不过目前我没看到相关文档说支持,可能是我没看到,也可能是还没暴露出来吧(自己脑补:难道因为preload的行为可能得单独处理?)。

    第二部分到此结束,激动人心的时刻到了……做!实!验!!!


4. 实验验证

    我们设计一个实验来验证上面得到的结论。我这里用到的工具Visual Studio 2017 Community(以及其SDK和附带的工具)。

4.1. 获取源码,编译

    此过程略,不会的请恕我不多说,自己看文档

4.2. 使用我们编译出来的Godot引擎编辑器启动

    同上,略

4.3 创建一个叫做TestPreload的项目

    同上,略

4.4 创建Scene1

Image title



如图,创建一个场景,依次创建Node,在其下创建一个Button,将这个场景保存为Scene1.tscn。为Node分配GDS脚本,命名为Scene1.gd,内容如下:

extends Node

func _ready():
    $Button.connect("pressed", self, "on_button_pressed")

func on_button_pressed():
    get_tree().change_scene("res://Scene2.tscn")


4.5 创建Scene2

Image title



如图,创建一个场景,依次创建Node,在其下创建一个Button以及一个TextureRect(不为其设置纹理),将这个场景保存为Scene2.tscn。为Node分配GDS脚本,命名为Scene2.tscn,内容如下:

extends Node

func _ready():
    $Button.connect("pressed", self, "on_button_pressed")

func on_button_pressed():
    var tex = preload("res://icon.png")
    $TextureRect.texture = tex


说明

    要从Scene1跳到Scene2是考虑到调试的问题。
    即使你不查看代码,你也能发现Godot的游戏进程和Editor并不是同一个进程,调试Editor是无法调试游戏进程的。(不过不幸的是,即使它们是同一个进程也没法调试。如果你查看代码,会发现Godot的Project Manager和Editor并不是同一个进程,在Project Manager中选定项目后,Project  Manager会在一个新进程中打开Editor,随后自己退出。)
    我们想要在C++的级别对Godot游戏进行调试,需要使用调试器启动游戏进程(配置麻烦),所以选择了一个偷懒一点的做法:让游戏先启动,然后attach这个游戏进程。
    而游戏进程一起动就会加载Default Scene内相关的资源,为了方便对照load和preload的行为,我让Default Scene做跳转,当我Debugger  attacher上游戏进程以后,再跳转到Scene2。


4.6 启动游戏,使用Debugger

    启动游戏之前,我们设置一下游戏名称,方便等下找到游戏进程。项目->项目设置->Application->Config->Name设为TestPreload。Ok,启动!

Image title



    使用VS的调试器附加到游戏进程。在VS的菜单中选择调试->附加到进程,在可用进程列表中找到窗口标题为TestPreload的进程,点击附加按钮。附加成功的话,就可以看到VS的界面出现变化,可以看到很多被附加的进程的信息。当然,我们关心的只有preload。

4.7 设置断点,切换场景

    只有一处断点是我们关心的:preload的资源什么时候加载。为此,我们顺着gdscript_parser.cpp第464行的藤,摸ResourceLoader::load(在core/io/resource_load.cpp第190行)的瓜,我们在resource_load.cpp第192行设置断点。看何时触发该断点。

    开始实验前我们不妨预期一下实验结果:加载Scene2的时候,需要加载Scene2的场景文件、脚本文件,因此,这里至少会触发2次(当然我们并不关心),加载脚本文件的时候会编译脚本,此时preload会触发加载icon.png。好吧,开始~~

    我们从调试器自动窗口p_path可以看到,首先触发此断点是因为加载Scene2.tscn,继续运行,第二次触发此断点是因为加载Scene2.gd,继续运行,第三次触发此断点是因为加载icon.png,第四次触发此断点又是因为加载Scene2.gd。

    可以看到,preload确实是在加载(编译)Scene2.gd的时候完成加载的(查看第三次触发断点时的调用栈可以看出)。此时因为还没有执行on_button_pressed函数,TextureRect也是空的。好的,继续运行,不再触发断点,点击按钮,Texture显示了,但是仍然没有触发断点,继续点击按钮也不触发(因为preload的返回值是编译时产生的,对于GDScript来说是个常量)。




4.8 修改Scene2.gd的代码

    将preload改成load。

4.9 将4.6的步骤再做一次。

    可以看到,第一次触发断点是因为加载Scene2.tscn,第二次和第三次触发都是因为加载Scene2.gd,不会触发第四次断点,也就是说,这次在加载Scene2的时候,并没有加载icon.png。继续运行后,点击按钮,触发断点,因为要加载icon.png。继续运行,Texture显示了,不再触发断点。(如果再次点击按钮,仍然会触发断点,加载icon.png,这个好理解吧?因为调用了load函数。)


未解之谜
可以看到,两次实验中,脚本都被加载了2次,原因我目前也没看懂。求解!



5. 结论

    所谓的“编译时”加载,就是指的编译 gd源码的时候加载的,更具体一点,做语法分析的时候加载 。

(转发自:原日志地址
编译godot官方 gdnative 例子
imdjs 2018-11-10

这个例子可在这里下载 https://github.com/BastiaanOlij/gdnative_cpp_example

我在这里主要讲一下在编译过程中遇到的问题与解决方法.

前提是你已经编译好gdnative的库,并设置好include路径,

打开gdnative_cpp_example-master 目录 修改SConstruct:

#target = ARGUMENTS.get("target", "debug");#■■--
target = ARGUMENTS.get("target", "release");#■■++


# env.Append(LIBPATH=[cpp_bindings_path + 'bin/'])#■■--

# env.Append(LIBS=[cpp_library])#■■--

env.Append(LIBS=['libgodot-cpp.windows.release.64'])#■■++ 这个是你编译好gdnative库后在 bin/libgodot-cpp.windows.release.64.lib 的静态库文件,我这这里是编译为release版本,我发现如果编译成debug版本会出错.

env.Append(LIBPATH=[  'E:/godot/GDNative/bin' ])#■■++ 这个是libgodot-cpp.windows.release.64.lib 静态库路径


注:因为我在编译过程中遇到一些问题,所以我自己在这个样例前面增加了一个gdnative源码的include 目录以确保编译过程中不会发生依赖路径的错误.
以下代码可以直接加在这个样例gdexample.cpp的代码开头,(也可以把这些代码保存为一个头文件再在dexample.cpp的代码开头include一下).
//====h==========================
#include <Godot.hpp>
#include"E:\godot\GDNative\include\core\GodotGlobal.hpp"
#include"E:\godot\GDNative\include\core\Transform2D.hpp"
#include"E:\godot\GDNative\include\core\Quat.hpp"
#include"E:\godot\GDNative\include\core\Basis.hpp"
#include"E:\godot\GDNative\include\core\Array.hpp"
#include"E:\godot\GDNative\include\core\Vector2.hpp"
#include"E:\godot\GDNative\include\core\Vector3.hpp"
#include"E:\godot\GDNative\include\core\Dictionary.hpp"
#include"E:\godot\GDNative\include\core\Rect2.hpp"
#include"E:\godot\GDNative\include\core\Variant.hpp"
#include"E:\godot\GDNative\include\core\PoolArrays.hpp"
#include"E:\godot\GDNative\include\core\NodePath.hpp"
//====cpp==========================
#include"E:\godot\GDNative\src\core\GodotGlobal.cpp"
#include"E:\godot\GDNative\src\core\Transform2D.cpp"
#include"E:\godot\GDNative\src\core\Quat.cpp"
#include"E:\godot\GDNative\src\core\Basis.cpp"
#include"E:\godot\GDNative\src\core\Array.cpp"
#include"E:\godot\GDNative\src\core\Vector2.cpp"
#include"E:\godot\GDNative\src\core\Vector3.cpp"
#include"E:\godot\GDNative\src\core\Dictionary.cpp"
#include"E:\godot\GDNative\src\core\Rect2.cpp"
#include"E:\godot\GDNative\src\core\Variant.cpp"
#include"E:\godot\GDNative\src\core\PoolArrays.cpp"
#include"E:\godot\GDNative\src\core\NodePath.cpp"
#include"E:\godot\GDNative\src\core\String.cpp"
//-----------------------------------------------------------------------

然后在gdnative_cpp_example-master 右键打印命令窗口 直接输入scons 按回车就 会生成一个libgdexample.dll 文件在E:\godot\Godot_example\GDNative-demos-master\gdnative_cpp_example-master\demo\bin\win64

Image title

//-----------------------------------------------------------------------

在godot打开这个demo 运行游戏,然后会在控制窗口看到自己在代码中添加的打印信息.


Image title



(转发自:原日志地址

加入 indienova

  • 建立个人/工作室档案
  • 建立开发中的游戏档案
  • 关注个人/工作室动态
  • 寻找合作伙伴共同开发
  • 寻求线上发行
  • 更多服务……
登录/注册