init project

pull/2/head
jaysunxiao 2021-09-12 18:07:51 +08:00
parent cd45473ceb
commit 346418abba
35 changed files with 948 additions and 0 deletions

71
.gitignore vendored Normal file
View File

@ -0,0 +1,71 @@
# Compiled class file
*.class
# Log file
*.log
*.log*
**/logs/
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# 忽略IntelliJ IDEA生成的项目描述文件
.idea
*.iml
**/target/
# 忽略lucene生成的索引文件
**/lucene-data/
# 忽略生成的协议文件
**/jsProtocol/
**/CsProtocol/
**/LuaProtocol/
**/zapp-user/protocol/
**/protobuf/
# 以下是web前端需要忽略的文件
# 忽略npm生成的package描述文件
package-lock.json
# 忽略web前端前端package.jsonbower.json所有依赖的包
**/node_modules/
**/bower_components/
# 忽略npm编译后的文件
**/dist/
# 忽略webapp中部署的文件
**/resources/static/
**/resources/templates/index.html
# test文件
tests/**/coverage/
tests/e2e/reports
# Godot-specific ignores
.import/
export.cfg
export_presets.cfg
# Mono-specific ignores
.mono/
data_*/

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 jaysunxiao
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,2 +1,5 @@
# godot-util
godot engine util and example
关于godot的基本使用示例

7
default_env.tres Normal file
View File

@ -0,0 +1,7 @@
[gd_resource type="Environment" load_steps=2 format=2]
[sub_resource type="ProceduralSky" id=1]
[resource]
background_mode = 2
background_sky = SubResource( 1 )

17
doc/demo02_base/base.md Normal file
View File

@ -0,0 +1,17 @@
- 参考资料
- [godot官方文档](https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_basics.html)
- [Godot Tutorials的youtube播放量最高的godot教程视频](https://www.youtube.com/watch?v=JJQa3xrRNM0&list=PLJ690cxlZTgL4i3sjTPRQTyrJ5TTkYJ2_)
- [B站视频](https://www.bilibili.com/video/BV17g4y1z7uS)
# 数据类型
![Image text](integer.JPG)
![Image text](integer_overflow.JPG)

BIN
doc/demo02_base/integer.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/integer.JPG-43a607bfd4c2c4f83e738b20fb6c9816.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://doc/demo02_base/integer.JPG"
dest_files=[ "res://.import/integer.JPG-43a607bfd4c2c4f83e738b20fb6c9816.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/integer_overflow.JPG-08e89d4444868183c3be1155d3451030.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://doc/demo02_base/integer_overflow.JPG"
dest_files=[ "res://.import/integer_overflow.JPG-08e89d4444868183c3be1155d3451030.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/godot_lifecycle.jpg-b26541e87e5327f35f02175dab2eb781.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://doc/demo03_lifecycle/godot_lifecycle.jpg"
dest_files=[ "res://.import/godot_lifecycle.jpg-b26541e87e5327f35f02175dab2eb781.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/godot_node.jpg-16331f3f19dfee3528c1c8a0616ee82e.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://doc/demo03_lifecycle/godot_node.jpg"
dest_files=[ "res://.import/godot_node.jpg-16331f3f19dfee3528c1c8a0616ee82e.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/godot_process.jpg-0257d8dc365734710f5f2b2802378ee8.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://doc/demo03_lifecycle/godot_process.jpg"
dest_files=[ "res://.import/godot_process.jpg-0257d8dc365734710f5f2b2802378ee8.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View File

@ -0,0 +1,3 @@
![Image text](./godot_process.jpg)
![Image text](./godot_node.jpg)
![Image text](./godot_lifecycle.jpg)

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

34
icon.png.import Normal file
View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.png"
dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

33
project.godot Normal file
View File

@ -0,0 +1,33 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=4
_global_script_classes=[ {
"base": "Reference",
"class": "MyClass",
"language": "GDScript",
"path": "res://script/demo02_base/MyClass.gd"
} ]
_global_script_class_icons={
"MyClass": "res://icon.png"
}
[application]
config/name="godot-util"
run/main_scene="res://scene/demo01_hello/HelloWorld.tscn"
config/icon="res://icon.png"
[physics]
common/enable_pause_aware_picking=true
[rendering]
environment/default_environment="res://default_env.tres"

View File

@ -0,0 +1,19 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://script/demo01_hello/hello_world.gd" type="Script" id=1]
[node name="Node2D" type="Node2D"]
script = ExtResource( 1 )
[node name="Label" type="Label" parent="."]
margin_left = 353.108
margin_top = 248.378
margin_right = 589.108
margin_bottom = 358.378
text = "Hello World"
align = 1
valign = 1
max_lines_visible = 2
__meta__ = {
"_edit_use_anchors_": false
}

View File

@ -0,0 +1,7 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://script/demo02_base/base.gd" type="Script" id=1]
[node name="Node2D" type="Node2D"]
script = ExtResource( 1 )
a = 9

View File

@ -0,0 +1,14 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://script/demo03_lifecycle/sub_node1.gd" type="Script" id=1]
[ext_resource path="res://script/demo03_lifecycle/sub_node2.gd" type="Script" id=2]
[ext_resource path="res://script/demo03_lifecycle/parent.gd" type="Script" id=3]
[node name="Node2D" type="Node2D"]
script = ExtResource( 3 )
[node name="SubNode1" type="Node2D" parent="."]
script = ExtResource( 1 )
[node name="SubNode2" type="Node" parent="SubNode1"]
script = ExtResource( 2 )

View File

@ -0,0 +1,64 @@
[gd_scene load_steps=5 format=2]
[ext_resource path="res://script/demo04_signal/signal.gd" type="Script" id=1]
[ext_resource path="res://script/demo04_signal/mySignal.gd" type="Script" id=2]
[ext_resource path="res://script/demo04_signal/yield.gd" type="Script" id=3]
[ext_resource path="res://script/demo04_signal/thread.gd" type="Script" id=4]
[node name="Node2D" type="Node2D"]
script = ExtResource( 1 )
[node name="Button1" type="Button" parent="."]
margin_left = 322.927
margin_top = 50.5072
margin_right = 441.927
margin_bottom = 90.5072
text = "button1"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Button2" type="Button" parent="."]
margin_left = 541.436
margin_top = 44.0454
margin_right = 661.436
margin_bottom = 94.0454
text = "button2"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="MySignal" type="Button" parent="."]
margin_left = 323.703
margin_top = 148.643
margin_right = 430.703
margin_bottom = 192.643
text = "mySignal"
script = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Yield" type="Button" parent="."]
margin_left = 322.941
margin_top = 244.0
margin_right = 433.941
margin_bottom = 287.0
text = "yield"
script = ExtResource( 3 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Thread" type="Button" parent="."]
margin_left = 321.059
margin_top = 331.0
margin_right = 435.059
margin_bottom = 374.0
text = "thread"
script = ExtResource( 4 )
__meta__ = {
"_edit_use_anchors_": false
}
[connection signal="pressed" from="Button1" to="." method="_on_Button1_pressed"]

View File

@ -0,0 +1,11 @@
extends Node2D
# Declare member variables here. Examples:
# var a = 2
# var b = "text"
# Called when the node enters the scene tree for the first time.
func _ready():
print("Hello World")

View File

@ -0,0 +1,170 @@
# extends Node2D
var StringUtils = load("res://util/StringUtils.gd")
# (optional) class definition with a custom icon
class_name MyClass, "res://icon.png"
# Member variables
# gds有5种基础类型BooleanInteger(Java long)Float(Java double)StringNull
# &&=and ||=or !=not
var a = (true or false) && true
var b = 9223372036854775807
var c = 1.5
var s = "Hello"
var arr = [1, 2, 3]
var dict = {
2: 3,
"key": "字符串作为key",
arr: "数组作为key"
}
var typed_var: int
var inferred_type: String = "String"
# Constants
const ANSWER = 42
const THE_NAME = "Charly"
# Enums
enum {LEFT, RRIGHT, FRONT, BACK}
# 等价于
# const LEFT = 0
# const RIGHT = 1
# const FRONT = 2
# const BACK = 3
enum FOOD {GOOD, NORMAL, BAD = -1}
# Built-in vector types
var v2 = Vector2(1, 2)
var v3 = Vector3(1, 2, 3)
func _init():
pass
static func getAnswer():
return ANSWER
func typeInfo():
var template = "[Variant:{}] [type:{}] [value:{}]"
print(StringUtils.format(template, [TYPE_BOOL, typeof(a), a]))
print(StringUtils.format(template, [TYPE_INT, typeof(b), b]))
print(StringUtils.format(template, [TYPE_INT, typeof(c), c]))
print(StringUtils.format(template, [TYPE_STRING, typeof(s), s]))
print(StringUtils.format(template, [TYPE_ARRAY, typeof(arr), arr]))
print(StringUtils.format(template, [TYPE_DICTIONARY, typeof(dict), dict]))
print(StringUtils.format(template, [TYPE_DICTIONARY, typeof(FOOD), FOOD]))
print(StringUtils.format(template, [TYPE_DICTIONARY, typeof(FOOD.GOOD), FOOD.GOOD]))
print(self is Reference)
print(a is Dog) # 类似于instanceof
func some_function(param1, param2):
var local_var = 5
if param1 < local_var:
print(param1)
elif param2 > 5:
print(param2)
else:
print("Fail!")
match local_var:
1:
print("match1")
2:
print("match2")
5:
print("match5")
# continue
6:
print("match6")
_:
print("match_")
for i in range(20):
print(i)
while param2 != 0:
param2 -= 1
var local_var2 = param1 + 3
return local_var2
# Function
func arrayIterator():
# range等价于for(int i = 0; i < 20; i++)
print("数组遍历方法1")
for i in range(3):
print(i)
print("数组遍历方法2")
for ele in arr:
print(ele)
print("数组遍历方法3")
for index in range(arr.size()):
print(arr[index])
func dictionaryIterator():
print("字典遍历方法1")
for key in dict:
print("key:" + key as String)
print("value:" + dict[key] as String)
print("字典遍历方法2")
for key in dict.keys():
print("key:" + key as String)
print("value:" + dict[key] as String)
print("字典遍历方法3")
for value in dict.values():
print("value:" + value as String)
func innerClassTest():
var dog = Dog.new(1)
dog.move()
dog.info()
dog.height = 2
dog.info()
# Inner class默认继承Object
class Animal:
extends Object # 如果不指定继承的类默认基础Object
const STATIC_FIELD = "静态变量"
# 属性
var height: int
func _init():
print("Animal 构造方法")
func move():
print("animal移动")
static func staticFuction():
pass
class Dog:
extends Animal
# Constructor
func _init(height):
self.height = height
print("Dog 构造方法")
# Functions override functions with the same name on the base/parent class.
func move():
print("dog用4个脚跑")
# If you still want to call them, use '.' (like 'super' in other languages).
.move()
func info(parm = 100):
print("height" + height as String)

View File

@ -0,0 +1,59 @@
extends Node2D
# Declare member variables here. Examples:
export var a = 1
export var b:NodePath
export(NodePath) var c
export(String, FILE) var e
export(String, FILE, "*.txt") var d
export(Resource) var f
export(Color, RGB) var g
var arr = []
# Called when the node enters the scene tree for the first time.
func _ready():
print("new一个对象--------------------------------")
var myClass = MyClass.new()
print(myClass.to_string())
print("类型信息--------------------------------")
myClass.typeInfo()
print("基础语法--------------------------------")
myClass.some_function(1, 2)
print("访问静态变量--------------------------------")
var answer = MyClass.ANSWER
print("访问const变量类似于static变量" + answer as String)
print("调用静态方法--------------------------------")
print("调用静态方法:" + MyClass.getAnswer() as String)
print("数组遍历--------------------------------")
myClass.arrayIterator()
print("字典遍历--------------------------------")
myClass.dictionaryIterator()
print("内部类测试--------------------------------")
myClass.innerClassTest()
print("垃圾回收--------------------------------")
# 如果一个类没有指明继承哪个类则默认继承Reference可以被自动的垃圾回收类似其他语言的那种垃圾回收
for i in range(10000):
var instance = MyClass.new()
arr.append(instance)
# instance.unreference()
# 立即释放对象
# instance.free()
# 放在队列里,等系统统一释放对象,推荐
# instance.queue_free()
print("立即输出数组的第一位内容:")
var firstElement = arr[0]
print(arr[0])
var frame = 0
func _process(delta):
frame = frame + 1
if frame == 300:
print("等待一会数组的第一位内容")
arr[0].typeInfo()
print(typeof(arr[0]))

View File

@ -0,0 +1,28 @@
extends Node2D
func _enter_tree():
# When the node enters the Scene Tree, it becomes active
# and this function is called. Children nodes have not entered
# the active scene yet. In general, it's better to use _ready()
# for most cases.
print("parent _enter_tree")
func _ready():
# This function is called after _enter_tree, but it ensures
# that all children nodes have also entered the Scene Tree,
# and became active.
print("parent _ready")
func _exit_tree():
# When the node exits the Scene Tree, this function is called.
# Children nodes have all exited the Scene Tree at this point
# and all became inactive.
print("parent _exit_tree")
func _process(delta):
# This function is called every frame.
pass
func _physics_process(delta):
# This is called every physics frame.
pass

View File

@ -0,0 +1,55 @@
extends Node2D
func _enter_tree():
# When the node enters the Scene Tree, it becomes active
# and this function is called. Children nodes have not entered
# the active scene yet. In general, it's better to use _ready()
# for most cases.
print("sub node1 _enter_tree")
func _ready():
# This function is called after _enter_tree, but it ensures
# that all children nodes have also entered the Scene Tree,
# and became active.
print("sub node1 _ready")
nodeUsage()
func _exit_tree():
# When the node exits the Scene Tree, this function is called.
# Children nodes have all exited the Scene Tree at this point
# and all became inactive.
print("sub node1 _exit_tree")
func _process(delta):
# This function is called every frame.
pass
func _physics_process(delta):
# This is called every physics frame.
pass
# 申明和存储节点等价于在_ready()下赋值
onready var readySubNode = $SubNode2
func nodeUsage():
# 获取当前节点
var currentNode1 = $"."
var currentNode2 = self
# 获取父节点
var parentNode1 = get_parent()
var parentNode2 = $"../"
# 获取子节点
var subNode1 = $SubNode2
var subNode2 = $"SubNode2"
var subNode3 = get_node("SubNode2")
# 根节点查找法,会返回节点树从上到下找到的第一个节点
var subNode4 = get_tree().root.find_node("SubNode2", true, false)
print(currentNode1.name)
print(currentNode2.name)
print(parentNode1.name)
print(parentNode2.name)
print(subNode1.name)
print(subNode2.name)
print(subNode3.name)
print(subNode4.name)

View File

@ -0,0 +1,30 @@
extends Node
func _enter_tree():
# When the node enters the Scene Tree, it becomes active
# and this function is called. Children nodes have not entered
# the active scene yet. In general, it's better to use _ready()
# for most cases.
print("sub node2 _enter_tree")
func _ready():
# This function is called after _enter_tree, but it ensures
# that all children nodes have also entered the Scene Tree,
# and became active.
print("sub node2 _ready")
func _exit_tree():
# When the node exits the Scene Tree, this function is called.
# Children nodes have all exited the Scene Tree at this point
# and all became inactive.
print("sub node2 _exit_tree")
func _process(delta):
# This function is called every frame.
pass
func _physics_process(delta):
# This is called every physics frame.
pass

View File

@ -0,0 +1,22 @@
extends Button
# 自定义信号
signal mySignal(a, b)
# 发送信号
# emit_signal("mySignal", 1, 2)
# disconnect("mySignal", 1, 2)
# 类似于设计模式中的观察者
func _ready():
self.connect("mySignal", self, "onMySingalCallback")
self.connect("pressed", self, "onButton")
func onMySingalCallback(a, b):
print("a:" + a as String)
print("b:" + b as String)
func onButton():
emit_signal("mySignal", 1, 2)

View File

@ -0,0 +1,16 @@
extends Node2D
# 第一种信号接受方法,通过在场景中配置信号的接收方法
func _on_Button1_pressed():
print("hello button1")
# 第二种信号接受方法,通过代码控制信号的接受,更加的灵活,比较推荐方式
func _ready():
$Button2.connect("pressed", self, "onButton2")
func onButton2():
print("button2 pressed")

View File

@ -0,0 +1,29 @@
extends Button
func _ready():
self.connect("pressed", self, "onButton2")
func onButton2():
var myThread = Thread.new()
print("Create Thread Id: ", myThread)
print("Thread Active: ", myThread.is_active())
myThread.start(self, "threadTest", null, 0)
print()
print("Thread Active: ", myThread.is_active())
var waitForThread = myThread.wait_to_finish() # wait for our thread to finish before moving on
print()
print("Thread is Finished with result: ", waitForThread)
print("Thread Active: ", myThread.is_active())
# 使用thread运行的函数必须有一个参数要不然无法运行
func threadTest(param):
print("thread test start")
return 999

View File

@ -0,0 +1,31 @@
extends Button
func _ready():
self.connect("pressed", self, "onButton")
# yield(obj, signal),函数立即返回,并且保存当前执行的位置和状态
# yield返回GDScriptFunctionState类型对象类似于Java的CompleteFuture
# resume恢复GDScriptFunctionState保存的调用函数状态
func onButton():
var yieldResult1 = doSomething1()
yieldResult1.resume()
var yieldResult2 = doSomething2()
# 等待yieldResult2执行完毕
yield(yieldResult2, "completed")
print("end")
func doSomething1():
yield()
print("doSomething")
func doSomething2():
print(1)
yield(get_tree().create_timer(1), "timeout")
print(2)
yield(get_tree().create_timer(1), "timeout")
print(3)
yield(get_tree().create_timer(1), "timeout")

5
util/ArrayUtils.gd Normal file
View File

@ -0,0 +1,5 @@
extends Object
static func isEmpty(array: Array) -> bool:
return array == null or array.size() == 0

29
util/StringUtils.gd Normal file
View File

@ -0,0 +1,29 @@
extends Object
const ArrayUtils = preload("res://util/ArrayUtils.gd")
const EMPTY: String = ""
const EMPTY_JSON: String = "{}"
# Checks if a String is empty ("") or null
static func isEmpty(s: String) -> bool:
return s == null or s.length() == 0
# 检查是否为空的字符串
static func isBlank(s: String) -> bool:
if isEmpty(s):
return true
if isEmpty(s.strip_edges(true, true)):
return true
return false
# 格式化字符串
static func format(template: String, args: Array) -> String:
if isEmpty(template) or ArrayUtils.isEmpty(args):
return template
return template.format(args, EMPTY_JSON)