Godot-StartUP

创建于:2018-07-28

创建人: Justus

35 信息 11 成员
讨论基于Godot以及Unity引擎的游戏开发经验,理论和最佳实践。共享一些通用思路以启发另一种生产工具中的实践。QQ互助群691534145
小组说明
Justus 2018-09-28

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

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

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

    半水半严肃的讨论Q群691534145

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

(转发自:原日志地址
[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.cn 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://www.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.cn 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.cn 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



(转发自:原日志地址
编译godot 的GDNative 库
imdjs 2018-11-10

如果没有安装python就先安装python,再安装scons.

设置系统环境变量PATH. 增加E:\Python\Scripts 路径 (因为scons安装后在这个目录里).

Image title


(1):如果你想在scons里指定INCLUDE与LIB 自定义路径,可以通过修改scons源码实现,找到E:\Python\Lib\site-packages\scons-3.0.0\SCons\Script\Main.py, 在def main(): 函数的开头里增加以下代码.

LIBS=['user32','kernel32','uuid','ws2_32','winmm','ole32'];

LIBPATH=['E:/Microsoft Visual Studio 12.0/VC/Tools/MSVC/lib/x64','E:/Microsoft Visual Studio 12.0/VC/Windows Kits/7.1A/Lib/x64'];

CCFLAGS = ' /D "UNICODE" /D "WIN32" /D "_UNICODE" ';

SCons.Defaults.DefaultEnvironment(LIBS=LIBS,LIBPATH=LIBPATH,CCFLAGS=CCFLAGS);

(这是自定义path路径,就是你vc12里的cl.exe路径)

(2):然后在E:\Python\Lib\site-packages\scons-3.0.0\SCons\Platform\win32.py 的def generate(env): 函数

把env.AppendENVPath('PATH', get_system_root() + '\System32') 注释掉替换以下代码:

myvs_home="E:\\Microsoft Visual Studio 12.0"

garfield_path1=myvs_home+"\\VC\\Tools\\MSVC\\bin\\HostX64\\x64"

garfield_path2=myvs_home+"\\Common7\\IDE"

garfield_path3=myvs_home+"\\Common7\\Tools"

garfield_path_string=garfield_path1+';'+garfield_path2+';'+garfield_path3

env.AppendENVPath('PATH', garfield_path_string);#SCons\Environment.py

env.Append(CPPPATH=[ 'E:/Microsoft Visual Studio 12.0/VC/Tools/MSVC/include']);

(这是增加自定义的include路径)

以上两种增加自定义路径的方法是以修改scons源码实现的,这样 可以不用每个项目都改路径.
如果你不想修改源码,也可以在你项目的SConstruct 文件里修改自定义路径与包含目录.

(3):跟着就是编译gdnative库,假设你已经下载好ndnative库(分别下载:https://github.com/GodotNativeTools/godot_headers https://github.com/GodotNativeTools/godot-cpp),解压为GDNative文件夹.

Image title


打开GDNative的源码目录,在空白处按shift 加右键 选择在此处打开Powershell窗口,弹出命令窗口,写入scons p=windows tagert="release"  regenerate_bindings=yes VERBOSE=1 命令开始编译如下gif.

Image title

我上面是编译成debug版本,不过我发现编译成debug版本不能在godot里正常导入dll,不知什么原因,只有我编译成release版本才解决问题.
要编译成release版本我直接修改 gdnative下的SConstruct 文件 注释一行,增加一行:
#opts.Add(EnumVariable('target', 'Compilation target', 'debug', ('debug', 'release')));#■■--
opts.Add(EnumVariable('target', 'Compilation target', 'release', ('debug', 'release')));#■■++


(转发自:原日志地址
6边形网格地图,格子间的距离计算
logoss 2018-11-01

Image title

这样一个6边形网格地图,有没有公式计算两个格子间的距离呢?

比如(0,0)走到(1,1)需要走几步,我们看图可以发现走两步就行了,但是有公式可以计算就更好了。

下面跟群里的小伙伴讨论之后的出的结果。

我们需要借助三维坐标

Image title

像这样的坐标体系,有x,y,z三个维度,x,y,z的和始终是0,我们称之为cube坐标,距离计算公式是

distance=(abs(cubePos1.x-cubePos2.x)+abs(cubePos1.y-cubePos2.y)+abs(cubePos1.z-cubePos2.z))/2

或者

diatance=max(cubePos1.x-cubePos2.x)+abs(cubePos1.y-cubePos2.y)+abs(cubePos1.z-cubePos2.z))

上面两个公式都能算出距离,至于具体原理我就不懂了,反正可以这么用


然后要做的就是把二维坐标转成三维坐标

其实很简单

我们原来的坐标是这样的

0,0 --- 1,0 --- 2,0 --- 3,0
--- 0,1 --- 1,1 --- 2,1 --- 2,2
0,2 --- 1,2 --- 2,2 --- 3,2
--- 0,3 --- 1,3 --- 2,3 --- 3,3

如果调整成这样

0,0 --- 1,0 --- 2,0 --- 3,0
--- 0,1 --- 1,1 --- 2,1 --- 3,1
--- --- 0,2 --- 1,2 --- 2,2 --- 3,2
--- --- --- 0,3 --- 1,3 --- 2,3 --- 3,3

这样二维中的x,y就对应了三维中的x,y。至于三维的z,因为x,y,z的和始终是0,只要z=-x-y就行了

调整过程其实就是x=x-int(y/2)

最终我们得到二维坐标转三维坐标的函数

func getCubePosFromPos(pos):

    var x=pos.x-int(pos.y)/2

    var y=pos.y

    var z=-x-y

    return Vector3(x,y,z)

计算距离的函数

func posDistance(pos1,pos2):
    var cubePos1=getCubePosFromPos(pos1)
    var cubePos2=getCubePosFromPos(pos2)
    var distance=(abs(cubePos1.x-cubePos2.x)+abs(cubePos1.y-cubePos2.y)+abs(cubePos1.z-cubePos2.z))/          2
    return distance

把这个公式放到游戏中试验下

Image title

黑色小圆圈里的数字表示这个格子到主角的距离,计算很准确


顺便说下从三维坐标转回到二维坐标的算法

func getPosFromCubePos(cubePos):
    var y=cubePos.y
    var x=cubePos.x+int(y)/2
    return Vector2(x,y)


参考资料

https://www.redblobgames.com/grids/hexagons/

(转发自:原日志地址
笔记:个人对于godot中yield的一些理解
logoss 2018-10-25

研究了一下yield,做个笔记

这个对象保存了中断时函数的状态,这个对象有个函数resume(),可以继续执行中断的函数。

这个对象运行完成会发出completed信号,有了这个信号就可以实现yield的嵌套。

extends Node #嵌套演示
func _ready():
    funA()

func funA():
    print("a_start")
    yield(funB(),"completed")
    print("a_end")
func funB():
    print("b_start")
    yield(get_tree().create_timer(2),"timeout")
    print("b_end")

结果是先输出a_start,b_start,等待2秒后输出b_end,a_end


  • yield(target,signal)

目前猜测,由于yield返回的是一个GDScriptFunctionStat实例,该类继承reference,没有引用会被回收。

所以必须有个对象保持它的引用,由于信号没有保持它的引用,所以是target保持了它的引用。

target在emit_signal时会顺便检测有没有GDScriptFunctionStat的实例需要resume,如果有就resume它。

这就实现了yield等待信号的效果。


yield可以传递参数

extends Node

signal sig

func _ready():
    var a=yield(self,"sig")
    print(a)

func _process(delta):
    if Input.is_action_just_pressed("ui_accept"):
        emit_signal("sig",10)

输出结果是10.

yield也可以用来传递参数,可以实现选择的效果。


更加灵活的yield方式,可以被打断或者做其他操作

extends Node

var funcState
var die=false
func _ready():
    funcState=testFunc()

func testFunc():
    print(1)
    wait(2)
    yield()
    print(2)
    wait(2)
    yield()
    print(3)

func wait(time):
    get_tree().create_timer(time).connect("timeout",self,"onTimeOut")
func onTimeOut():
    if !die:  
        funcState=funcState.resume() //死亡就不会继续运行,被打断了

目前想到这些,yield用来做剧情应该是没问题的

做技能系统还有待研究

欢迎讨论

(转发自:原日志地址
godot 3.x 信号
JamBob 2018-10-22

一、类自定义信号

extends Node
#1.定义信号
signal custom_signal(para1) 

func _ready():
    #2.连接信号
    self.connect("custom_signal", self, "_on_test_node_custom_signal", ["ab",1000])

func _input(event):
    if event is InputEventMouseButton:
        if event.button_index == BUTTON_LEFT and event.pressed:
            #3.发送信号
            emit_signal("custom_signal","what")

#信号回调函数
func _on_test_node_custom_signal(para1 , para2 , para3):
    print(para1 ," ", para2 , " " , para3)

信号传参:

    自定信号可以在connect 中传递参数,也可以在emit_signal 中传递参数。因此定义回调函数的参数顺序是先是emit_signal,然后connect中的参数。

    上面的代码中,para1 对应"what",para2对应"ab",para3对应1000

    回调函数的参数一定要和发送的参数数量一致,才能成功接收到回调消息

    当你定义了signal 之后,对应的也会被添加到编辑器里面,可以选择编辑器操作来连接信号

    Image title

另外是关于回调函数命名的个人建议:

    _on_发送信号的节点名_信号名()

    这样做的好处是,方便别人和自己查看以及查询信号发送源

二、内置信号

    引擎的节点已经定义好的信号,连接信号的接收函数的两种方式

        1.手动代码调用connect函数连接

        2.在编辑器内编辑信号连接

    手动连接的好处的可以传任何你想要的参数,而使用编辑器进行连接的话参数类型是有限制的

Image title

三、实例化对象添加信号

    在对象已经实例化后,我们还想给它添加信号,使用add_user_signal 函数

    

extends Node
var node_ins

func _ready():
    node_ins = load("res://node.gd").new()
    #1.定义信号
    node_ins.add_user_signal("custom_user_signal")
    #2.连接信号
    node_ins.connect("custom_user_signal",self,"_on_node_custom_user_signal")

func _input(event):
    if event is InputEventMouseButton:
        if event.button_index == BUTTON_LEFT and event.pressed:
            #3发送信号
            node_ins.emit_signal("custom_user_signal")

#回调函数
func _on_node_custom_user_signal():
    print("_on_node_custom_user_signal")

signal 创建的信号是和类绑定在一起,所以当实例化这个类,信号也会被创建;add_user_signal是实例绑定的一起,只属于当前绑定实例

四、信号相关的其他函数

void add_user_signal ( String signal, Array arguments=[ ] )

int connect ( String signal, Object target, String method, Array binds=[ ], int flags=0 )

void disconnect ( String signal, Object target, String method )

Variant emit_signal ( String signal ) vararg

Array get_signal_connection_list ( String signal ) const

Array get_signal_list ( ) const

bool has_user_signal ( String signal ) const

bool is_blocking_signals ( ) const

bool is_connected ( String signal, Object target, String method ) const

void set_block_signals ( bool enable )

(转发自:原日志地址
Godot的Shader入门+制作像素文字Shader
logoss 2018-10-17

Shader在哪写?

Image title

如图,新建一个sprite,在它的Meterial属性里点击新建shaderMaerial,在shaderMateril里点击新建shader,就会弹出着色器面板,可以在里面写shader了。

Shader格式

第一行我们先写上:

shader_type canvas_item;

void fragment(){

}

shader_type是shader的类型,有spatial,canvas_item和particles 三种。必须在第一行指定shader_type。

spatial: 用来渲染3D

canvas_item: 用来渲染2D

particles:用来渲染粒子

fragment函数里面就是我们要写处理逻辑的地方。

fragment()是shader的回调函数,

每一个像素点都会运行一次fragment函数。

每一个像素点都会运行一次fragment函数。

每一个像素点都会运行一次fragment函数。

重要的事说三遍。

当然你会发现语法和gdscript有些不同,语句结尾要加分号,函数要用大括号括起来。

详细的请参考官方文档。

Shader基本逻辑

全白效果

先从简单的开始

shader_type canvas_item;

void fragment(){
    COLOR=vec4(1.0,1.0,1.0,1.0);
}

Image title

效果如图,整个sprite都变白了。

COLOR是shader内置属性,它表示像素点的颜色。

vec4 是shader的数据类型,4维向量,可以用来表示颜色。

vec4有四个属性,x,y,z,w。每个属性都是float类型(所以用1.0而不是1)。

vec4的四个属性也可以用r,g,b,a来表示,就是说vec4.x和vec4.r是完全一样的。

shader常用类型有float,vec2,vec3,vec4等。


现在我们来看COLOR=vec4(1.0,1.0,1.0,1.0);这句话,

vec4(1.0,1.0,1.0,1.0)表示rgba都是1.0的颜色,就是白色。

每个像素点都会运行一次这个语句,所以每个像素点的颜色都变成了白色。


剪影效果

我们先写上

shader_type canvas_item;

void fragment(){
    COLOR=texture(TEXTURE,UV);
}

Image title

效果如图

你可能会想:咦?这不是和原来一样吗。

是的,确实和原来一样。这句话是什么意思呢?

UV 是shader内置属性,用来表示当前像素点的坐标。它是一个vec2。

Image title

每个像素点都有一个UV值,每个像素点的UV值不一样。

左上角的像素点UV值是(0.0,0.0),

右下角的像素点UV值是(1.0,1.0),

正中心的像素点UV值是(0.5,0.5)。

TEXTURE是shader的内置属性(反正这些全大写的都是内置属性)。它表示原始的纹理。

texture函数是shader的内置函数,传入纹理和uv值,可以返回纹理上uv坐标的颜色(vec4)。

我们来看:COLOR=texture(TEXTURE,UV);

就是每个像素获取自己原来的颜色,赋值给COLOR。当然不会有任何变化。

接下来我们写

shader_type canvas_item;

void fragment(){
    COLOR=texture(TEXTURE,UV);
    COLOR.rgb=vec3(1.0,1.0,1.0);
}

效果如图,成功实现了剪影效果。

Image title

COLOR.rgb=xxx  这是shader中的语法,可以同时给r,g,b三个值赋值,非常方便。

你可能会说,为什么要写COLOR=texture(TEXTURE,UV);?

我不能直接写 COLOR.rgb=vec3(1.0,1.0,1.0);吗?

这是不行的,COLOR默认值其实是(1.0,1.0,1.0,1.0)。

只要调用了COLOR,COLOR就会变成(1.0,1.0,1.0,1.0)。

你可以试试这样

shader_type canvas_item;

void fragment(){
    COLOR;
}

如图,也能让图片变全白。

Image title

制作像素化文字shader

我这里用的字体是思源黑体Medium。新建一个label,字体大小设成12,我们看看默认的效果。

Image title

放大后:

Image title

为什么看上去这么不像素,主要是字体的抗锯齿,使它有半透明的部分。

所以要做的就是去掉半透明的部分。

学习了上面的知识,你应该知道怎么做了。

在label的Meterial属性里点击,新建ShaderMeterial,然后新建Shader

Shader代码如下

shader_type canvas_item;

void fragment(){
    COLOR=texture(TEXTURE,UV);
    if(COLOR.a<0.5){
        COLOR.a=0.0; 
    }else{
        COLOR.a=1.0;
    }
}

如果像素的alpha<0.5,就把alpha变成0。如果像素的alpha>=0.5,就把alpha变成1。

看看效果

Image title

放大后:

Image title

很好很像素。

当然临界值可以不是0.5,如果我们希望可以微调临界值该怎么办?

这时要声明变量,代码如下

shader_type canvas_item;

uniform float limit=0.5;//声明变量 limit,默认值是0.5
void fragment(){
    COLOR=texture(TEXTURE,UV);
    if(COLOR.a<limit){      //把0.5替换成limit
        COLOR.a=0.0;
    }else{
        COLOR.a=1.0;
    }
}

用uniform指定的变量可以在外部修改,可以微调像素化的效果。Image title

最后点击保存按钮把shader保存成文件,就可以重复使用了Image title

更多关于shader的信息请参考官方文档:

http://docs.godotengine.org/zh_CN/latest/tutorials/shading/shading_language.html

(转发自:原日志地址
Godot中锚点(anchor)和边界(margin)的用法
logoss 2018-10-12

在godot中,Control类都有anchor和margin属性。

Image title

这两个属性是给ui自适配用的。

每个control都有四个边,上(top),下(bottom),左(left),右(right)

anchor(锚点):表示每条边的参照值。这个参照值是相对于父节点的百分比取值,一般会取0,0.5,1这三个值。

以anchor_left属性为例,

anchor_left=0表示当前节点的左边界以父节点的左边界为参照值,

archor_left=0.5表示当前节点的左边界以父节点的中心为参照值,

archor_left=1表示当前节点的左边界以父节点的右边界为参照值。

margin(边界):表示每条边和参照值得相对位置,这个值的就是像素距离。


举例来说明比较好理解,我新建了一个Panel,一个TextureRect

Image title

Image title

1.我希望TextureRect和Panel的边界始终保持50个像素

Image title

TextureRect的Anchor和Margin要这么设置

Image title

2.接下来新建个按钮,是它始终位于右上方,长度保持100,高度保持50

Image title

按钮的anchor和margin属性

Image title

3.接下来新建个开始游戏按钮,长100,宽50,x方向居中,y方向距离底面50的距离

Image title

开始游戏按钮的anchor和margin

Image title


顺便说一下布局其实就是一些常用的anchor和margin的组合
Image title

(转发自:原日志地址
[关于godot引擎FunCrf]一个new bee小白的自白,对old bee暴政的不满!
gakki_sama 2018-10-10
  •    我一个刚刚入坑godot一周的小白,怎么就跑到这来写日志介绍funcref类?(黑人问号)hello you kitty ,me?还不是在godot某吹水群里的大佬的压迫下,事情就是这样:
  •   "弄明白funcrf了吧,来去论坛写个教程,我要下线了..........."  [へ:justus]waht? are you 弄啥嘞. "臣妾做不到啊"都不让我说出口.
  •   既然这样我这个看了2天python教程的noob就来教育教育一下你们:"小屁孩们做好,乖乖听课"
  •   你说funcrf是什么?看图
  • Image title
  • 嗯,英文不好怎么办,没关系google一下你就知道了(不对是百度那个垃圾)
  • 现在是英文教学(好累啊,上完语文课,还要上英文课,晕死,giao,giao)
  • inherits: 继承      reference:参考(什么鬼,理解不能)    Funcref:函数引用类型
  • refernce to a function in a object:引用对象中的函数(逗我吧?)
  • 没错,要是不搞你都不知道我的英文课有多重要,坐好,不要起立,更不要敬礼,爷我受不起这三叩九拜的大礼啊(舒服,当老师真伟大.......#_#smile ->LoL)
  • 那到底怎么用类look at
  • Image title
  • 直接call_func(实例,str(函数))就行啦,简单吧,老师教得好不好(吼>>>>>>>>)
  • 可是老师我还是不会,怎么办?没关系,我不是还准备了计算机课嘛,不要怕,老师想得很周全,我先去去就来,把课件偷来(より:沉迷伪娘dk)
  • Image title
  • "输出的是什么?同学们大声告诉我">>>>>>>>>>>>>老师真帅

    今天的课就到这里,还不懂的同学,收拾东西滚回家,太蠢了,你已经被开除了,以后不用来上课了,家里蹲好(^_^)



(转发自:原日志地址
学习在GODOT3.1中编写类型化的GDSCRIPT
JamBob 2018-10-02

文章来源

本文的作者:Nathan Lovato

原文地址在:http://gdquest.com/tutorial/game-design/godot/gdscript/typed-gdscript/

Image title

Godot 3.1的GDscript可选输入语法。你将将学习:

  • 如何在GDscript中使用类型
  • 静态类型能帮助你避免错误

静态类型可用于变量,常量,函数,参数和返回类型

Image title

为什么你要学习GDscript的类型定义?

在GDscript中给变量定义类型,Godot可以检测到更多错误!它会在你工作时为你和你的团队提供更多信息,因为当你调用方法时,参数的类型很明显。

如下代码所示,在类 Inventory中有add方法中reference:Item表示参数reference是Item类型,amount : int表示参数amount是int类型

"""in Item.gd"""
class_name Item

"""in Inventory.gd"""
class_name Inventory

func add(reference : Item, amount : int = 1):
    var item = find_item(reference)
    if not item:
        item = _instance_item_from_db(reference)
    item.amount += amount

在GDScript使用类型的另一个显着优点是新的警告系统。从版本3.1开始,Godot会在你编写代码时向你发出有关代码的警告:引擎会识别代码中可能导致运行时出现问题的部分,但你可以决定是否要保留代码。稍后详细介绍。

静态类型还为你提供了更好的代码完成选项。下面,你可以看到被调用类PlayerController的动态类型和静态类型自动完成的区别。

在你没有给body定义类型的时候,自动完成是没有提示的:

Image title

这是由于动态代码。Godot无法知道你传递给函数的节点或值类型。但是,如果你明确地编写类型,则将从节点获取所有公共方法和变量:

Image title

将来,GDScript类型还将提高代码性能:Just In Time编译和其他编译器改进已经在规划图上了!

总的来说,类型化编程给你提供了一种更结构化的体验。它有助于防止错误并改进脚本的自文档化方面。当你在团队或长期项目中工作时,这一点尤其有用:研究表明,开发人员将大部分时间花在阅读其他人的代码或过去编写的脚本上,而忘记了这些内容。代码越清晰,结构越结构化,理解起来就越快,前进的速度就越快。

如何在GODOT 3.1中使用静态类型?

要定义变量或常量的类型,请在变量名称后面加上一个冒号,后跟其类型。例如var health : int。这会强制变量的类型始终保持不变:

Image title

如果你写一个冒号,Godot会尝试推断类型,但你省略了类型:

Image title

目前你可以使用三种类型的......类型:

  1. 内置类型
  2. 核心类和节点(Object,Node,Area2D,Camera2D,等等)
  3. 你自己的自定义类。查看新的class_name功能以在编辑器中注册类型

自定义变量类型

你可以将任何类(包括自定义类)用作类型。有两种方法可以在脚本中使用它们。第一种方法是预加载要用作常量类型的脚本:

const Rifle = preload('res://player/weapons/Rifle.gd')
var my_rifle : Rifle

第二种方法是class_name在创建时使用关键字。对于上面的示例,Rifle.gd将如下所示:

extends Node2D
class_name Rifle

如果你使用class_name,Godot会在编辑器中全局注册Rifle类型,你可以在任何地方使用它而无需将其预加载到常量中:

var my_rifle : Rifle

变量转换

类型转换是类型化语言中的一个关键概念。我们从另一种类型的类型转换成另一种类型


想象一下你的游戏中的敌人,extends Area2D类型。你想让它与Player发生碰撞,一个extend kinematicBody2D的PlayerController脚本。使用on_body_entered信号来检测碰撞。使用类型化代码,你检测到的主体将是通用的PhysicsBody2D,而不是PlayerController_on_body_entered回调。


你可以PhysicsBody2D使用ascast关键字检查这是否是你的Player ,并:再次使用冒号强制变量使用此类型。这会强制变量保持PlayerController类型:

func _on_body_entered(body : PhysicsBody2D) -> void:
    var player := body as PlayerController
    if not player:
        return
    player.damage()

当我们处理自定义类型时,如果body未扩展PlayerController,则player变量将设置为null。我们可以用它来检查身body是否是玩家。通过这样的转换,使用player时可以获得PlayerController全部方法和变量的自动完成

Image title

安全线

你还可以使用转换来确认安全线。安全线是Godot 3.1中的一个新工具,用于告诉你什么时候不明确的代码行是类型安全的。由于你可以混合并匹配键入的和动态的代码,有时,Godot没有足够的信息来判断一条指令是否会在运行时触发错误。


你获得子节点时会发生这种情况。让我们以一个Timer为例:使用动态代码,你可以获取节点$Timer。GDscript支持duck-typing,所以即使你的计时器是类型Timer,它extend自Node和Object。使用动态GDscript,你不需要关心节点的类型,只要它有需要调用的方法。


当你得到一个节点时,你可以使用强制转换告诉Godot你期望的类型:($Timer as Timer)($Player as KinematicBody2D)等等。戈多将确保这种类型有效,如果是这样,行号将在脚本编辑器的左侧变为绿色。

Image title

Image title

使用箭头定义函数的返回类型 - >

要定义函数的返回类型,请->在声明后写一个破折号和一个右尖括号,然后是返回类型:

func _process(delta : float) -> void:
    pass

该类型void表示该函数不返回任何内容。你可以使用任何类型作为变量:

func hit(damage : float) -> bool:
    health_points -= damage
    return health_points <= 0

你还可以使用自己的节点作为返回类型:

"""Inventory.gd"""

"""Adds an item to the inventory and returns it"""
func add(reference : Item, amount : int) -> Item:
    var item : Item = find_item(reference)
    if not item:
        item = ItemDatabase.get_instance(reference)
        item.amount += amount
    return item

类型化(typed)or DYNAMIC:坚持一种风格

类型化GDscript和动态GDscript可以在同一个项目中共存。但我建议,为了代码库的一致性,以及同行的一致性,应该坚持使用这两种风格。如果你遵循相同的指导原则,那么每个人都可以更容易地一起工作,并且可以更快地阅读和理解其他人的代码。


类型化代码需要更多的编写,但是你可以获得我们上面讨论的好处。下面是一个相同的空脚本示例,使用DYNAMIC样式:

extends Node
    func _ready():
        pass
    func _process(delta):
        pass

并使用静态类型:

extends Node
    func _ready() -> void:
        pass
    func _process(delta : float) -> void:
        pass

如你所见,你还可以将类型与引擎virtual methods一起使用。与任何方法一样,信号回调也可以使用类型。下面是动态样式的body_entered信号:

func _on_Area2D_body_entered(body):
    pass

和相同的回调,类型提示:

func _on_area_entered(area : CollisionObject2D) -> void:
    pass

你可以自由替换,例如PhysicsBody2D,使用你自己的类型,自动转换参数:

func _on_area_entered(bullet : Bullet) -> void:
    if not bullet:
        return
    take_damage(bullet.damage)

该bullet变量可以持有任何CollisionObject2D,但我们要确保这是我们的Bullet,我们为我们的项目创建了一个节点。如果它是其他任何东西,比如一个Area2D或者没有extend 的任何节点Bullet,那么bullet变量就是null。


警告系统

警告系统补充了类型化的GDscript。它可以帮助你避免在开发过程中难以发现的错误,并可能导致运行时错误。

你可以在项目设置中配置警告GDscript:

Image title

你可以在脚本编辑器的状态栏中找到活动GDscript文件的警告列表。以下示例有3个警告:

Image title

要忽略一个文件中的特定警告,请插入表单的特殊注释#warning-ignore:warning-id,或单击警告说明右侧的忽略链接。Godot将在相应的行上方添加注释,代码将不再触发相应的警告:

Image title

警告不会阻止游戏运行,但如果你愿意,可以将它们变成错误。这样,除非你修复所有警告,否则你的游戏将无法编译。前往GDscript项目设置的部分以打开此选项。这是与前一个示例相同的文件,并在启用错误时显示警告:

Image title

无法指定类型的情况

为了总结这个介绍,我们将介绍一些不能使用类型提示的情况。以下所有示例都会触发错误。

你不能将Enums用作类型

enum MoveDirection { UP, DOWN, LEFT, RIGHT }
var current_direction : MoveDirection

你无法指定数组中单个成员的类型。这会给你一个错误:

var enemies : Array = [$Goblin : Enemy, $Zombie : Enemy]

你不能强制在for循环中分配类型,因为for关键字循环已经具有不同类型的每个元素。所以你不能写:

var names ['John', 'Marta', 'Samantha', 'Jimmy']
for name : String in names:
    pass

两个脚本不能以循环方式相互依赖:

"""Player.gd"""
extends Area2D
class_name Player

var rifle : Rifle

"""Rifle.gd"""
extends Area2D
class_name Rifle

var player : Player

概要类型的情况

GDscript类型化是一个强大的工具。使用Godot 3.1,它已经可以帮助你编写更多结构化代码,帮助你避免常见错误,并创建可扩展系统。将来,由于即将进行的编译器优化,静态类型也将为你带来不错的性能提升。

(转发自:原日志地址
在Godot中制作杀戮尖塔的箭头
logoss 2018-09-28

杀戮尖塔里面使用卡牌时的箭头是这样的:

Image title

箭头的形态非常符合贝塞尔曲线

PS中的钢笔工具就是用的贝塞尔曲线

Image title

如图,一条贝塞尔曲线需要用四个点来确定,一个起点,一个终点,加上两个控制点。

我们把四个点分别命名:起点(startPos),终点(endPos),控制点A(ctrlAPos),控制点B(ctrlBPos)

贝塞尔曲线的公式是:

position=startPos*(1-t)*(1-t)*(1-t)+3*ctrlAPos*t*(1-t)*(1-t)+3*ctrlBPos*t*t*(1-t)+endPos*t*t*t

四个点都确定后,公式里的t就是唯一的变量,t是指从起点到终点的百分比,取值是0-1。

比如0代表曲线起点,0.2代表曲线从起点开始20%的位置,0.5代表曲线中间位置,1代表曲线终点。

公式的计算结果position就是当前t值所对应的曲线上的点。


不过在游戏中我们是使用两个点来确定曲线的,卡牌所在位置是曲线的起点,鼠标所在位置是曲线的终点。

那么两个控制点就要根据起点和终点来进行计算。

杀戮尖塔里的曲线大致是这样:

Image title

我们可以大致的写出控制点的计算公式:

ctrlAPos.x=startPos.x+(startPos.x-endPos.x)*0.2
ctrlAPos.y=endPos.y-(endPos.y-startPos.y)*0.2

ctrlBPos.x=startPos.x-(startPos.x-endPos.x)*0.2
ctrlBPos.y=endPos.y+(endPos.y-startPos.y)*0.2

当然这个计算公式可以自己微调,使曲线更符合自己想要的形态


理解了曲线的原理,现在开始在godot中实现。

我画了两个箭头,箭头1和箭头2,如图。

Image titleImage title

在godot中新建一个场景,新建Node2D,命名为贝塞尔箭头。添加脚本。

Image title

开始写脚本。首先我们的箭头有20节,我们需要在初始化的时候准备好。

之后更新箭头时重新排好每一节就能形成一条曲线。

extends Node2D
var list=[] #数组,用来保存20节小箭头
func _ready():
    #生成19节尾巴小箭头,用箭头1的图片
    for i in range(19):
        var sprite=Sprite.new()    #新建Sprite节点
        add_child(sprite)          #添加到场景里
        list.append(sprite)        #添加到数组里
        sprite.texture=load("res://Sprites/箭头1.png")  #把图片换成箭头1
        sprite.scale=Vector2(1,1)*(0.2+float(i)/18*0.8) #改变缩放,根据杀戮尖塔,箭头是一节节越来越大的
        sprite.offset=Vector2(-25,0)  #由于我画的图片中心点在箭头中间,
                                      #这里改变一下图片偏移,把图片中心点移动到箭头头部
    #最后生成终点的箭头,用箭头2的图片
    var sprite=Sprite.new()   
    add_child(sprite)
    list.append(sprite)
    sprite.texture=load("res://Sprites/箭头2.png")
    sprite.offset=Vector2(-25,0)

然后我们需要一个函数来设置箭头的起点和终点

func reset(startPos,endPos):
    #根据传入的起点和终点来计算两个控制点
    var ctrlAPos=Vector2()
    var ctrlBPos=Vector2()
    ctrlAPos.x=startPos.x+(startPos.x-endPos.x)*0.1 #这里我把参数做了微调,感觉这样更加符合杀戮尖塔的效果
    ctrlAPos.y=endPos.y-(endPos.y-startPos.y)*0.2
    ctrlBPos.y=endPos.y+(endPos.y-startPos.y)*0.3
    ctrlBPos.x=startPos.x-(startPos.x-endPos.x)*0.3
   #根据贝塞尔曲线重新设置所有小箭头的位置
    for i in range(20):
        var t=float(i)/19 
        var pos=startPos*(1-t)*(1-t)*(1-t)+3*ctrlAPos*t*(1-t)*(1-t)+3*ctrlBPos*t*t*(1-t)+endPos*t*t*t
        list[i].position=pos 
   #虽然更改了箭头的位置,不过还需要重新计算箭头的方向   
   updateAngle()   #重新计算所有箭头的方向

接下来我们需要完成updateAngle()这个函数

思路是每个小箭头根据前一个箭头和自己的位置来计算角度

func updateAngle():
    for i in range(20):
        if i==0:
            list[0].rotation_degrees=270    #第一个小箭头就让他固定朝上好了
        else:
            var current=list[i]    #当前的小箭头
            var last=list[i-1]     #前一个小箭头
            var lenVec=current.position-last.position      #两个箭头连线的向量
            var a=lenVec.angle()            #计算这个向量的角度,这个angle()返回值是弧度
            a=rad2deg(a)               #弧度转成角度
           
            current.rotation_degrees=a   #更新小箭头的方向

我们的贝塞尔箭头就做好了,外界只要调用reset方法就能更新箭头,

不需要用的时候可以用visible=false把它隐藏掉

我把它放到游戏中看看效果

Image title

非常完美!





(转发自:原日志地址

加入 indienova

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