Godot-StartUP

创建于:2018-07-28

创建人: Justus

44 信息 140 成员
讨论基于Godot以及Unity引擎的游戏开发经验,理论和最佳实践。共享一些通用思路以启发另一种生产工具中的实践。独立开发群QQ: 122017359
JamBob@Godot-StartUP 的内容(查看所有内容
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 )

(转发自:原日志地址
学习在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,它已经可以帮助你编写更多结构化代码,帮助你避免常见错误,并创建可扩展系统。将来,由于即将进行的编译器优化,静态类型也将为你带来不错的性能提升。

(转发自:原日志地址
如何使A *寻路适应2D网格平台:理论
JamBob 2018-09-23

首先算法是居于2d网格的,寻路使用进过改造的a*算法

知识准备

    1.a*寻路算法(不熟悉的强烈建议先去熟悉一下)

        https://blog.csdn.net/zhulichen/article/details/78786493

     2.优先级队列

        https://www.cnblogs.com/yangecnu/p/Introduce-Priority-Queue-And-Heap-Sort.html

规则定义

   寻路算法只能通过上下左右的方式移动单元格,不能斜线移动单元格

    

定义角色跳跃高度为三个单元格,以下是一种最大距离的跳跃路径示例


Image title


当然角色也是垂直向上跳跃3格单元格

用跳转值表示跳转路径

首先我们定义角色在地面的跳转值为永远都为0,也就是说只要角色的下一个单元格为地面,那么角色在当前的跳转值就是0

一.当前的角色的跳转值为偶数

    1.角色可以上/下/左/右移动(前提是没有大于最大跳跃值)

        情况1:角色处于上升(上/左/右)

        情况2:角色处于下落(下/左/右)

    2.下一步左/右移动,跳转值+1

    3.下一步上/下移动,跳转值+2

二.当前的角色的跳转值为奇数

    1.角色只能上/下移动

        情况1:角色处于上升(上)

        情况1:角色处于下落(下)

    2.下一步上/下移动 跳转值+1

三、当角色头顶的单元格为障碍(当前(5,5),下个单元格(5,6)为障碍物)

    maxCharacterJumpHeight = 3 角色最大跳转值

    1.当前的角色单元格的x坐标!=下一个移动单元格坐标 跳转值定义为 jumpValue = max(maxCharacterJumpHeight * 2 + 1, jumpLength + 1)

    2.如果相等jumpValue  = max(maxCharacterJumpHeight * 2, jumpLength + 2)


如何确定下一个移动单元格?

    我们使用的是A*算法进行寻路,唯一不同的G值得计算方式不一样。因此我们最终要计算出来的就是G值。

    这里的下一个移动的单元格不是角色真正移动的单元格,而是角色上下左右的单元格。通过上面的规则计算从起点移动到指定方格的移动代价,

    H值使用哈曼顿计算,这样有个G和H我们就可以计算每个单元格的F值。        


如果觉得跳跃值为3,那么它的最大跳跃值为3*2

以下是一些示例:

Image title

这是一个更复杂的案例:

Image title

以下是跳数值的计算方法:

  1. 从地面开始:跳跃值为0
  2. 水平跳转:将跳转值增加1,因此跳转值为1
  3. 垂直跳转:将跳转值增加到下一个偶数:2
  4. 垂直跳转:将跳跃值增加到下一个偶数:4
  5. 水平跳跃:将跳跃值增加1  跳跃值现在是5
  6. 垂直跳转:将值增加到下一个偶数:6。(角色不能再向上移动,因此只有左,右和底节点可用。)
  7. 水平下降:将跳跃值增加1; 跳跃值现在是7
  8. 垂直下降:将跳跃值增加到下一个偶数:8
  9. 垂直下降:将值增加到下一个偶数:10
  10. 垂直落下:因为角色现在在地上。将跳转值设置为0



情景分析:

Image title

位置(3,1)处的绿色单元   是字符; 位置(2,5)处的蓝色单元格是目标。让我们绘制一条A *算法可以先选择尝试达到目标的路线


Image title

由于角色的最大跳转值是6,所以角色无法到达(2,5),但是我们可以发现还有另外一条更好路可以走

Image title

正如你所看到的,跳到角色右侧的区块允许它跳得足够高以达到目标!但是,这里有一个很大的问题...... 

很可能,算法将采用的第一条路线是我们检查的第一条路线。在获取之后,算法将不会走得太远,并且将最终返回节点(3,2)。然后它可以搜索节点(4,2)(4,3)(3,3)  (再次),(3,4)  (再次),(3,5),最后搜索目标单元格,(2) ,5)。 

在A *算法的基本版本中,如果已经访问过一个节点,那么我们不需要再次处理它。但是,在这个版本中,我们这样做。这是因为节点不仅仅由x坐标和y坐标区分,而且还由跳跃值区分。 

在我们的第一次尝试找到一个路径,在节点跳跃值(3,3)  是4 ; 在我们的第二次尝试中,它是3。因为在第二次尝试中,该单元格的跳跃值较小,这意味着我们可能会比第一次尝试时更高。 

这基本上意味着,节点(3,3)具有的跳跃值4是一个不同的节点以比该节点(3,3)用的跳跃值3。网格基本上需要在某些坐标处变为三维以适应这些差异,如下所示:


Image title

这也是和a*算法不一样的地方,由于每个单元格可以被访问多次,我们需要对每个单元格创建一个数组来储存跳跃值。

参考地址:

https://gamedevelopment.tutsplus.com/tutorials/how-to-adapt-a-pathfinding-to-a-2d-grid-based-platformer-theory--cms-24662


(转发自:原日志地址

加入 indienova

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