Gorgeous 3D with godot
关于本教程旨在快速先容如何更有效地利用GDScript。它只关注特定于该措辞的常见情形,但是也涉及许多关于动态类型措辞的信息。
对付没有或险些没有动态类型措辞履历的程序员来说,它特殊有用。
动态性动态类型的优缺陷GDScript是一种动态类型措辞。因此,它的紧张优点是:
这种措辞大略易学。大多数代码都可以快速地编写和变动,而且没有任何麻烦。编写更少的代码意味着要修复的缺点和失落误更少。更随意马虎阅读代码(减少混乱)。测试不须要编译。运行时(Runtime)很小。实质上是鸭子类型和多态性。紧张缺陷是:
比静态类型措辞的性能要低。重构更加困难(无法追踪符号)在静态类型措辞中,一些常日在编译时检测到的缺点只会在运行代码时涌现(由于表达式解析更严格)。代码完成的灵巧性较低(一些变量类型只在运行时知道)。这转化为现实,意味着Godot+GDScript是一个旨在快速高效创建游戏的组合。对付打算量很大并且无法从引擎内置工具(例如向量类型、物理引擎、数学库等)中受益的游戏,也存在利用C++的可能性。这许可您依旧利用GDScript创建游戏的绝大部分,并在须要性能的地方添加少量C++代码。
变量与赋值动态类型措辞中的所有变量都是类似 变体 的。这意味着它们的类型不是固定的,只能通过赋值修正。示例:
静态的:
int a; // Value uninitialized.a = 5; // This is valid.a = "Hi!"; // This is invalid.
动态的:
var a # 'null' by default.a = 5 # Valid, 'a' becomes an integer.a = "Hi!" # Valid, 'a' changed to a string.
作为函数参数:
函数也是动态的,这意味着它们可以用不同的参数调用,例如:
静态的:
void print_value(int value) { printf("value is %i\n", value);}[..]print_value(55); // Valid.print_value("Hello"); // Invalid.
动态的:
func print_value(value): print(value)[..]print_value(55) # Valid.print_value("Hello") # Valid.
指针和引用:
在静态措辞,如C或C++(以及在一定程度上,Java和C#)中,变量和变量的指针/引用之间存在差异。后者通过通报对原始函数的引用,许可其他函数修正该工具。
在C# 或Java中,不是内置类型(int,float,有时的String)的任何东西都是指针或引用。引用也是自动垃圾回收的,这意味着它们在不再利用时被删除。动态类型的措辞也方向于利用这种内存模型。一些示例:
C++:void use_class(SomeClass instance) { instance->use();}void do_something() { SomeClass instance = new SomeClass; // Created as pointer. use_class(instance); // Passed as pointer. delete instance; // Otherwise it will leak memory.}
Java:
@Overridepublic final void use_class(SomeClass instance) { instance.use();}public final void do_something() { SomeClass instance = new SomeClass(); // Created as reference. use_class(instance); // Passed as reference. // Garbage collector will get rid of it when not in // use and freeze your game randomly for a second.}
GDScript:
func use_class(instance): # Does not care about class type instance.use() # Will work with any class that has a ".use()" method.func do_something(): var instance = SomeClass.new() # Created as reference. use_class(instance) # Passed as reference. # Will be unreferenced and deleted.
在GDScript中,只有基本类型(int、float、string和vector类型)通过值通报给函数(值被复制)。其他所有(实例、数组、字典等)作为引用通报。继续 Reference (如果没有指定任何内容,则默认从这里继续)的类在不该用时将被开释,但对手动继续自 Object 的类大概可手动管理内存。
数组动态类型措辞的数组内部可以包含许多不同的稠浊数据类型,并且始终是动态的(可以随时调度大小)。比较在静态类型措辞中的数组示例:
int array = new int[4]; // Create array.array[0] = 10; // Initialize manually.array[1] = 20; // Can't mix types.array[2] = 40;array[3] = 60;// Can't resize.use_array(array); // Passed as pointer.delete[] array; // Must be freed.// orstd::vector<int> array;array.resize(4);array[0] = 10; // Initialize manually.array[1] = 20; // Can't mix types.array[2] = 40;array[3] = 60;array.resize(3); // Can be resized.use_array(array); // Passed reference or value.// Freed when stack ends.
以及在GDScript中:
var array = [10, "hello", 40, 60] # Simple, and can mix types.array.resize(3) # Can be resized.use_array(array) # Passed as reference.# Freed when no longer in use.
在动态类型措辞中,数组也可以作为其他数据类型有多种用法,例如列表:
var array = []array.append(4)array.append(5)array.pop_front()
或无序凑集:
var a = 20if a in [10, 20, 30]: print("We have a winner!")
字典
字典是动态类型化措辞中的一个强大工具。来自静态类型措辞(例如C++或C#)的大多数程序员都忽略了它们的存在,并不必要地增加了他们的事情难度。这种数据类型常日不存在于此类措辞中(或仅以受限的形式)。
字典可以完备忽略用作键或值的数据类型,将任何值映射到任何其他值。与盛行的不雅观点相反,它们是有效的,由于它们可以通过哈希表实现。事实上,它们非常高效,一些措辞乃至可以像实现字典一样实现数组。
字典的示例:
var d = {"name": "John", "age": 22} # Simple syntax.print("Name: ", d["name"], " Age: ", d["age"])
字典也是动态的,键可以在任何一点添加或删除,花费很少:
d["mother"] = "Rebecca" # Addition.d["age"] = 11 # Modification.d.erase("name") # Removal.
在大多数情形下,利用字典可以更随意马虎地实现二维数组。这里有一个大略的战舰游戏的示例:
# Battleship Gameconst SHIP = 0const SHIP_HIT = 1const WATER_HIT = 2var board = {}func initialize(): board[Vector2(1, 1)] = SHIP board[Vector2(1, 2)] = SHIP board[Vector2(1, 3)] = SHIPfunc missile(pos): if pos in board: # Something at that position. if board[pos] == SHIP: # There was a ship! hit it. board[pos] = SHIP_HIT else: print("Already hit here!") # Hey dude you already hit here. else: # Nothing, mark as water. board[pos] = WATER_HITfunc game(): initialize() missile(Vector2(1, 1)) missile(Vector2(5, 8)) missile(Vector2(2, 3))
字典还可以用作数据标记或快速构造。虽然GDScript字典类似于python字典,但它也支持Lua风格的语法和索引,这使得它对付编写初始状态和快速构造非常有用:
# Same example, lua-style support.# This syntax is a lot more readable and usable.# Like any GDScript identifier, keys written in this form cannot start# with a digit.var d = { name = "John", age = 22}print("Name: ", d.name, " Age: ", d.age) # Used "." based indexing.# Indexingd["mother"] = "Rebecca"d.mother = "Caroline" # This would work too to create a new key.
For & while
在一些静态类型的措辞中迭代可能非常繁芜:
const char strings = new const char[50];[..]for (int i = 0; i < 50; i++) { printf("Value: %s\n", i, strings[i]);}// Even in STL:for (std::list<std::string>::const_iterator it = strings.begin(); it != strings.end(); it++) { std::cout << it << std::endl;}
这常日在动态类型措辞中得到极大简化:
for s in strings: print(s)
容器数据类型(数组和字典)是可迭代的。字典许可迭代键:
for key in dict: print(key, " -> ", dict[key])
迭代索引也是可能的:
for i in range(strings.size()): print(strings[i])
range() 函数可以有3个参数:
range(n) # Will go from 0 to n-1.range(b, n) # Will go from b to n-1.range(b, n, s) # Will go from b to n-1, in steps of s.
一些静态类型的编程措辞示例:
for (int i = 0; i < 10; i++) {}for (int i = 5; i < 10; i++) {}for (int i = 5; i < 10; i += 2) {}
转变成:
for i in range(10): passfor i in range(5, 10): passfor i in range(5, 10, 2): pass
反向循环是通过一个负计数器完成的:
for (int i = 10; i > 0; i--) {}
变成:
for i in range(10, 0, -1): pass
While
while() 循环在任何地方都是相同的:
var i = 0while i < strings.size(): print(strings[i]) i += 1
自定义迭代器
在默认迭代器无法完备知足您的需求的情形下,您可以通过重写脚本中 Variant 类的 _iter_init、 _iter_next、和 _iter_get 函数来创建自定义迭代器。正向迭代器的一个示例实现如下:
class ForwardIterator: var start var current var end var increment func _init(start, stop, increment): self.start = start self.current = start self.end = stop self.increment = increment func should_continue(): return (current < end) func _iter_init(arg): current = start return should_continue() func _iter_next(arg): current += increment return should_continue() func _iter_get(arg): return current
它可以像任何其他迭代器一样利用:
var itr = ForwardIterator.new(0, 6, 2)for i in itr: print(i) # Will print 0, 2, and 4.
确保在 _iter_init 中重置迭代器的状态,否则利用自定义迭代器的嵌套for循环将无法正常事情。
鸭子类型当从静态类型措辞迁移到动态类型措辞时,最难节制的观点之一是鸭子类型。鸭子类型使全体代码设计更加大略和直接,但是它的事情办法并不明显。
举个示例,想象一个大石头从隧道里掉下来,在路上砸碎了统统。在静态类型措辞中石头的代码有点像:
void BigRollingRock::on_object_hit(Smashable entity) { entity->smash();}
这样,任何能被岩石砸碎的东西都必须继续 Smashable。如果一个角色、仇敌、家具、小石块都易碎,它们须要从 Smashable 类继续,可能须要多次继续。如果不肯望进行多重继续,那么它们必须继续像 Entity 这样的公共类。然而,如果只是个中几个能被粉碎的话,在 Entity 中添加一个虚拟方法 smash() 并不十分优雅。
利用动态类型的措辞,这将不是问题。鸭子类型确保您只需在须要的地方定义一个 smash() 函数,就行了。无需考虑继续、基类等。
func _on_object_hit(object): object.smash()
便是这样。如果击中大岩石的工具有一个 smash() 方法,它将被调用。不须要考虑继续或多态性。动态类型化措辞只关心具有所需方法或成员的实例,而不关心它继续什么或其类型。鸭子类型的定义该当使这一点更清楚:
“当我看到一只鸟像鸭子一样走路,像鸭子一样拍浮,像鸭子一样呱呱叫时,我就叫它鸭子”
在这种情形下,它可转变成:
“如果物体可以被砸碎,不要在意它是什么,只管砸碎它。”
是的,称它为 绿巨人(Hulk) 类型彷佛更得当。
有可能被击中的工具没有 smash() 函数。一些动态类型措辞在方法不存在时,大略地忽略方法调用(如Objective C),但是GDScript更严格,因此须要检讨函数是否存在:
func _on_object_hit(object): if object.has_method("smash"): object.smash()
然后,大略地定义这个方法,岩石触碰的任何东西都可以被粉碎了。
https://docs.godotengine.org/zh_CN/stable/getting_started/scripting/gdscript/gdscript_advanced.html#doc-gdscript-more-efficiently