diff --git a/Particles/Player.gd b/Particles/Player.gd new file mode 100644 index 0000000..19a39c6 --- /dev/null +++ b/Particles/Player.gd @@ -0,0 +1,27 @@ +extends PlayerController + +onready var particles = $"../Particles" +onready var camera = $Camera + +func _ready(): + pass + #particles = get_node(NodePath("../Particles")) + +func jump(to, strength): + var pm = particles.get_process_material() as ParticlesMaterial + pm.set_direction(to) + particles.set_amount(strength * 2000) + particles.set_emitting(true) + #particles.restart() + +func move(to): + .move_forwards(self, to, camera) + +func yaw(to): + .look_around(self, Vector3(0, to, 0), camera) + +func pitch(to): + .look_around(self, Vector3(to, 0, 0), camera) + +func escape(): + .quit() diff --git a/Particles/Spatial.tscn b/Particles/Spatial.tscn new file mode 100644 index 0000000..748b60f --- /dev/null +++ b/Particles/Spatial.tscn @@ -0,0 +1,164 @@ +[gd_scene load_steps=23 format=2] + +[ext_resource path="res://plane.tres" type="PlaneMesh" id=1] +[ext_resource path="res://terrain.tres" type="Material" id=2] +[ext_resource path="res://pointmesh.tres" type="PointMesh" id=3] +[ext_resource path="res://Player.gd" type="Script" id=4] + +[sub_resource type="PhysicsMaterial" id=22] + +[sub_resource type="OpenSimplexNoise" id=27] +seed = 405 +period = 119.4 + +[sub_resource type="NoiseTexture" id=28] +noise = SubResource( 27 ) + +[sub_resource type="SpatialMaterial" id=29] +params_blend_mode = 1 +albedo_color = Color( 0.486275, 0.556863, 1, 1 ) +subsurf_scatter_enabled = true +subsurf_scatter_strength = 0.07 +subsurf_scatter_texture = SubResource( 28 ) +refraction_enabled = true +refraction_scale = 1.0 +refraction_texture_channel = 0 + +[sub_resource type="CubeMesh" id=26] +material = SubResource( 29 ) + +[sub_resource type="ConvexPolygonShape" id=25] +points = PoolVector3Array( 1, 1, 0, 1, -1, 0, 0.995106, 1, -0.0980814, 0.995106, 1, 0.0978857, 0.995106, -1, 0.0978857, 0.995106, -1, -0.0980814, 0.980619, 1, -0.195184, 0.980619, 1, 0.194988, 0.980619, -1, 0.194988, 0.980619, -1, -0.195184, 0.95693, 1, -0.290329, 0.95693, 1, 0.290133, 0.95693, -1, 0.290133, 0.95693, -1, -0.290329, 0.923845, 1, -0.382733, 0.923845, 1, 0.382537, 0.923845, -1, 0.382537, 0.923845, -1, -0.382733, 0.881754, 1, -0.471417, 0.881754, 1, 0.471222, 0.881754, -1, 0.471222, 0.881754, -1, -0.471417, 0.831441, 1, -0.555599, 0.831441, 1, 0.555403, 0.831441, -1, 0.555403, 0.831441, -1, -0.555599, 0.772905, 1, -0.634495, 0.772905, 1, 0.634299, 0.772905, -1, 0.634299, 0.772905, -1, -0.634495, 0.70693, 1, -0.707126, 0.70693, 1, 0.70693, 0.70693, -1, 0.70693, 0.70693, -1, -0.707126, 0.634299, 1, -0.773101, 0.634299, 1, 0.772905, 0.634299, -1, 0.772905, 0.634299, -1, -0.773101, 0.555403, 1, -0.831637, 0.555403, 1, 0.831441, 0.555403, -1, 0.831441, 0.555403, -1, -0.831637, 0.471222, 1, -0.88195, 0.471222, 1, 0.881754, 0.471222, -1, 0.881754, 0.471222, -1, -0.88195, 0.382537, 1, -0.924041, 0.382537, 1, 0.923845, 0.382537, -1, 0.923845, 0.382537, -1, -0.924041, 0.290133, 1, -0.957126, 0.290133, 1, 0.95693, 0.290133, -1, 0.95693, 0.290133, -1, -0.957126, 0.194988, 1, -0.980814, 0.194988, 1, 0.980619, 0.194988, -1, 0.980619, 0.194988, -1, -0.980814, 0.0978857, 1, -0.995301, 0.0978857, 1, 0.995106, 0.0978857, -1, 0.995106, 0.0978857, -1, -0.995301, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0, -1, -1, -0.0980814, 1, -0.995301, -0.0980814, 1, 0.995106, -0.0980814, -1, 0.995106, -0.0980814, -1, -0.995301, -0.195184, 1, -0.980814, -0.195184, 1, 0.980619, -0.195184, -1, 0.980619, -0.195184, -1, -0.980814, -0.290329, 1, -0.957126, -0.290329, 1, 0.95693, -0.290329, -1, 0.95693, -0.290329, -1, -0.957126, -0.382733, 1, -0.924041, -0.382733, 1, 0.923845, -0.382733, -1, 0.923845, -0.382733, -1, -0.924041, -0.471417, 1, -0.88195, -0.471417, 1, 0.881754, -0.471417, -1, 0.881754, -0.471417, -1, -0.88195, -0.555599, 1, -0.831637, -0.555599, 1, 0.831441, -0.555599, -1, 0.831441, -0.555599, -1, -0.831637, -0.634495, 1, -0.773101, -0.634495, 1, 0.772905, -0.634495, -1, 0.772905, -0.634495, -1, -0.773101, -0.707126, 1, -0.707126, -0.707126, 1, 0.70693, -0.707126, -1, 0.70693, -0.707126, -1, -0.707126, -0.773101, 1, -0.634495, -0.773101, 1, 0.634299, -0.773101, -1, 0.634299, -0.773101, -1, -0.634495, -0.831637, 1, -0.555599, -0.831637, 1, 0.555403, -0.831637, -1, 0.555403, -0.831637, -1, -0.555599, -0.88195, 1, -0.471417, -0.88195, 1, 0.471222, -0.88195, -1, 0.471222, -0.88195, -1, -0.471417, -0.924041, 1, -0.382733, -0.924041, 1, 0.382537, -0.924041, -1, 0.382537, -0.924041, -1, -0.382733, -0.957126, 1, -0.290329, -0.957126, 1, 0.290133, -0.957126, -1, 0.290133, -0.957126, -1, -0.290329, -0.980814, 1, -0.195184, -0.980814, 1, 0.194988, -0.980814, -1, 0.194988, -0.980814, -1, -0.195184, -0.995301, 1, -0.0980814, -0.995301, 1, 0.0978857, -0.995301, -1, 0.0978857, -0.995301, -1, -0.0980814, -1, 1, 0, -1, -1, 0 ) + +[sub_resource type="PanoramaSky" id=7] + +[sub_resource type="Environment" id=8] +background_mode = 2 +background_sky = SubResource( 7 ) +ambient_light_sky_contribution = 0.87 +tonemap_exposure = 0.53 +tonemap_white = 1.13 + +[sub_resource type="CylinderMesh" id=20] + +[sub_resource type="SphereMesh" id=9] + +[sub_resource type="ConvexPolygonShape" id=5] +points = PoolVector3Array( -10, 0, -10, -10, 0, 10, 10, 0, -10, 10, 0, 10 ) + +[sub_resource type="PhysicsMaterial" id=23] + +[sub_resource type="BoxShape" id=6] + +[sub_resource type="Gradient" id=16] + +[sub_resource type="GradientTexture" id=17] +gradient = SubResource( 16 ) + +[sub_resource type="Gradient" id=18] +offsets = PoolRealArray( 0.0110803, 0.0692521, 0.127424, 0.360111, 0.567867, 0.847645 ) +colors = PoolColorArray( 0.921712, 0.680273, 0.417154, 1, 0.698765, 0.4876, 1, 1, 1, 1, 1, 1, 0.452041, 0.315712, 0.865322, 1, 1, 0.176724, 0.176724, 1, 1, 0.840588, 0.277709, 1 ) + +[sub_resource type="GradientTexture" id=19] +gradient = SubResource( 18 ) + +[sub_resource type="ParticlesMaterial" id=14] +direction = Vector3( 0, 1, 0 ) +gravity = Vector3( 0, -0.15, 0 ) +initial_velocity = 1.14 +color_ramp = SubResource( 19 ) +color_initial_ramp = SubResource( 17 ) + +[node name="Spatial" type="Spatial"] + +[node name="Player" type="RigidBody" parent="."] +transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 8.67695, -2.77891 ) +visible = false +physics_material_override = SubResource( 22 ) +script = ExtResource( 4 ) + +[node name="Camera" type="Camera" parent="Player"] +transform = Transform( 1, 0, 0, 0, 0.874095, 0.485756, 0, -0.485756, 0.874095, 0, 1.51341, 1.9432 ) + +[node name="MeshInstance" type="MeshInstance" parent="Player"] +transform = Transform( 0.358557, 0, 0, 0, -1.72723e-08, -1, 0, 0.395144, -4.37114e-08, 0, 0, 0 ) +mesh = SubResource( 26 ) +skeleton = NodePath("../../jelly") + +[node name="CollisionShape" type="CollisionShape" parent="Player"] +transform = Transform( 0.358557, 0, 0, 0, 1.49012e-08, -1, 0, 0.395144, 2.98023e-08, 0, 0, 0 ) +shape = SubResource( 25 ) + +[node name="primary" type="DirectionalLight" parent="."] +transform = Transform( 0.965259, 0.00662554, 0.261212, -0.00840352, -0.998374, 0.0563769, 0.261161, -0.0566134, -0.963634, -3.31137, 5.15251, 0 ) +light_energy = 0.31 + +[node name="secdary" type="DirectionalLight" parent="."] +transform = Transform( 0.988944, 0.0339409, -0.144356, 0, 0.973455, 0.228878, 0.148293, -0.226347, 0.962692, -3.31137, 5.15251, 0.00479805 ) +light_energy = 0.106 + +[node name="OmniLight" type="OmniLight" parent="."] +transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4.21154, 0 ) +light_energy = 0.131 +light_indirect_energy = 0.0 +shadow_enabled = true +shadow_bias = 0.012 +omni_range = 9.102 +omni_attenuation = 1.80251 + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource( 8 ) + +[node name="jelly" type="SoftBody" parent="."] +transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 4.12673, 9.84985, -0.922198 ) +mesh = SubResource( 20 ) +skeleton = NodePath("../Player") + +[node name="sphere" type="MeshInstance" parent="."] +transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.27243, 0 ) +mesh = SubResource( 9 ) + +[node name="ground" type="StaticBody" parent="."] +transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5591, 0 ) +__meta__ = { +"_edit_lock_": true +} + +[node name="MeshInstance" type="MeshInstance" parent="ground"] +mesh = ExtResource( 1 ) +skeleton = NodePath("../..") +material/0 = ExtResource( 2 ) + +[node name="CollisionShape" type="CollisionShape" parent="ground"] +transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.78303, 0 ) +shape = SubResource( 5 ) + +[node name="CSG" type="StaticBody" parent="."] +transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.2311, 0 ) +physics_material_override = SubResource( 23 ) + +[node name="CSGCombiner" type="CSGCombiner" parent="CSG"] + +[node name="CSGBox" type="CSGBox" parent="CSG/CSGCombiner"] + +[node name="CSGBox2" type="CSGBox" parent="CSG/CSGCombiner"] +transform = Transform( 1, 0, 0, 0, 0.428308, 0, 0, 0, 1, 0, 0, 0 ) +operation = 2 + +[node name="CollisionShape" type="CollisionShape" parent="CSG"] +shape = SubResource( 6 ) + +[node name="Particles" type="Particles" parent="."] +transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3.16058, 0 ) +emitting = false +amount = 1981 +lifetime = 27.31 +one_shot = true +preprocess = 0.1 +explosiveness = 0.08 +randomness = 0.61 +process_material = SubResource( 14 ) +draw_pass_1 = ExtResource( 3 ) + +[node name="GIProbe" type="GIProbe" parent="."] +visible = false diff --git a/Particles/default_env.tres b/Particles/default_env.tres new file mode 100644 index 0000000..20207a4 --- /dev/null +++ b/Particles/default_env.tres @@ -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 ) diff --git a/Particles/icon.png b/Particles/icon.png new file mode 100644 index 0000000..c98fbb6 Binary files /dev/null and b/Particles/icon.png differ diff --git a/Particles/noise.tres b/Particles/noise.tres new file mode 100644 index 0000000..2ae20a1 --- /dev/null +++ b/Particles/noise.tres @@ -0,0 +1,11 @@ +[gd_resource type="NoiseTexture" load_steps=2 format=2] + +[sub_resource type="OpenSimplexNoise" id=9] +octaves = 1 +period = 110.8 +persistence = 1.0 +lacunarity = 4.0 + +[resource] +seamless = true +noise = SubResource( 9 ) diff --git a/Particles/noise_normal.tres b/Particles/noise_normal.tres new file mode 100644 index 0000000..d99ca03 --- /dev/null +++ b/Particles/noise_normal.tres @@ -0,0 +1,15 @@ +[gd_resource type="NoiseTexture" load_steps=2 format=2] + +[sub_resource type="OpenSimplexNoise" id=9] +octaves = 1 +period = 110.8 +persistence = 1.0 +lacunarity = 4.0 + +[resource] +width = 251 +height = 251 +seamless = true +as_normalmap = true +bump_strength = 1.2 +noise = SubResource( 9 ) diff --git a/Particles/plane.tres b/Particles/plane.tres new file mode 100644 index 0000000..0dc49fb --- /dev/null +++ b/Particles/plane.tres @@ -0,0 +1,6 @@ +[gd_resource type="PlaneMesh" format=2] + +[resource] +size = Vector2( 20, 20 ) +subdivide_width = 256 +subdivide_depth = 256 diff --git a/Particles/player_ct.gd b/Particles/player_ct.gd new file mode 100644 index 0000000..c3b0ef3 --- /dev/null +++ b/Particles/player_ct.gd @@ -0,0 +1,130 @@ +extends Node +class_name PlayerController + +const TIPS = """ +Simple Player Controller +============================================================ +Nodes structure sugested: +- Player(Node2D/3D) + +- Camera + +- Mesh... +============================================================ +Setting InputMap: Project -> Project Settings -> Input Map +define InpuMap action: + - move_left: move player towards left + - move_right: move player towards right + - move_up: move player towards up + - move_down: move player towards left + - yaw_left: make player turn to left + - yaw_right: make player turn to right + - pitch_up: make player pitch up + - pitch_down: make player pitch down +""" +const ACTIONS = ["move_left", "move_right", "move_up", "move_down", + "jump", "yaw_left", "yaw_right", "pitch_up", "pitch_down",] + +var use_mouse_motion = true +var speed = 10.0 +var sensitivity = 0.4 +var sens_captured = 0.3 +var tips = true + +func _unhandled_input(event): + + var undefs = Array() + for action in ACTIONS: + if not InputMap.has_action(action): + undefs.append(action) + if tips and undefs.size(): + tips = false + return print_debug("InputMap Undefined: ", undefs, TIPS) + + + if use_mouse_motion and event is InputEventMouseMotion: + var vy = event.relative.x * sensitivity * sens_captured + var vp = event.relative.y * sensitivity * sens_captured + if vy: + yaw(deg2rad(vy)) + if vp: + pitch(deg2rad(vp)) + + if Input.is_action_pressed("jump"): + jump(Vector3(0,1,0), 1.0) + + if event is InputEventMouseButton: + Input.mouse_mode = Input.MOUSE_MODE_CAPTURED + + if event is InputEventJoypadMotion: + pass + + if not event is InputEventKey: + return + if event.is_pressed() and event.scancode == KEY_ESCAPE: + if Input.mouse_mode != Input.MOUSE_MODE_CAPTURED: + escape() + Input.mouse_mode = Input.MOUSE_MODE_CAPTURED ^ Input.mouse_mode + +func _physics_process(delta): + #var axis_x = int(Input.is_key_pressed(KEY_D)) - int(Input.is_key_pressed(KEY_A)) + #var axis_z = int(Input.is_key_pressed(KEY_S)) - int(Input.is_key_pressed(KEY_W)) + ## motion relative of global_transform.basis + #var direction: Vector3 = axis_x * basis.x + axis_z * basis.z + #global_position += direction.normalized() * speed * delta + var vy = Input.get_axis("yaw_left", "yaw_right") + var vp = Input.get_axis("pitch_up", "pitch_down") + if vy: + yaw(deg2rad(vy)) + if vp: + pitch(deg2rad(vp)) + var mh = Input.get_axis("move_left", "move_right") + var mv = Input.get_axis("move_up", "move_down") + var toward = Vector3(mh, 0, mv).normalized() * speed * delta + if toward.length(): + move(toward) + + +# Default methods + + +func quit(): + get_tree().quit() + + +func deg2rad(val: float): + return deg2rad(val) # Godot 3.x + # return deg_to_rad(val) # Godot 4.x + + +func move_forwards(player: Node, to: Vector3, camera: Node = null): + if camera == null: + camera = player + if player is Node2D and camera is Node2D: + player.global_position += to + elif player is Spatial and camera is Spatial: + var basis = player.transform.basis + to = basis.x * to.x + basis.y * to.y + basis.z * to.z + player.transform.origin += to + else: + print_debug("Player node must be Node2D/3D.", player, camera) + + +func look_around(player: Node, to: Vector3, camera): + if player is Node2D: + player.roatate(to.y) + elif player is Spatial: + var basis = player.transform.basis + camera.rotation += basis.x * to.x + basis.y * to.y + basis.z * to.z + # player.rotation += player.transform.basis.y * to.y + # use Euler angles to rotate camera + # var angle = clamp(camera.rotation.x - to.x, -PI * 0.4, PI * 0.4) + # camera.rotate_x(angle - camera.rotation.x) + else: + print_debug("Player node must be Node2D/3D.", player) + + +# Virtual methods +func move(_to: Vector3): pass +func jump(_to: Vector3, _strength: float): pass +func yaw(_rad: float): pass +func pitch(_rad: float): pass +func escape(): pass diff --git a/Particles/pointmesh.tres b/Particles/pointmesh.tres new file mode 100644 index 0000000..b9e26b4 --- /dev/null +++ b/Particles/pointmesh.tres @@ -0,0 +1,12 @@ +[gd_resource type="PointMesh" load_steps=2 format=2] + +[sub_resource type="SpatialMaterial" id=1] +flags_transparent = true +flags_use_point_size = true +vertex_color_use_as_albedo = true +params_diffuse_mode = 2 +params_blend_mode = 1 +params_point_size = 5.6 + +[resource] +material = SubResource( 1 ) diff --git a/Particles/project.godot b/Particles/project.godot new file mode 100644 index 0000000..4de0ab0 --- /dev/null +++ b/Particles/project.godot @@ -0,0 +1,111 @@ +; 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": "Node", +"class": "PlayerController", +"language": "GDScript", +"path": "res://player_ct.gd" +} ] +_global_script_class_icons={ +"PlayerController": "" +} + +[application] + +config/name="3.x Particles" +run/main_scene="res://Spatial.tscn" +config/icon="res://icon.png" + +[display] + +window/size/width=640 +window/size/height=320 +window/size/always_on_top=true + +[gui] + +common/drop_mouse_on_gui_input_disabled=true + +[input] + +move_left={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":0,"physical_scancode":65,"unicode":0,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null) + ] +} +move_right={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":0,"physical_scancode":68,"unicode":0,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null) + ] +} +move_up={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":0,"physical_scancode":87,"unicode":0,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null) + ] +} +move_down={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":0,"physical_scancode":83,"unicode":0,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null) + ] +} +jump={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":0,"physical_scancode":32,"unicode":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":0,"pressure":0.0,"pressed":false,"script":null) +, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":2,"pressed":false,"doubleclick":false,"script":null) + ] +} +yaw_left={ +"deadzone": 0.5, +"events": [ Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":2,"axis_value":-1.0,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":0,"physical_scancode":16777231,"unicode":0,"echo":false,"script":null) + ] +} +yaw_right={ +"deadzone": 0.5, +"events": [ Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":2,"axis_value":1.0,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":0,"physical_scancode":16777233,"unicode":0,"echo":false,"script":null) + ] +} +pitch_up={ +"deadzone": 0.5, +"events": [ Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":3,"axis_value":-1.0,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":0,"physical_scancode":16777232,"unicode":0,"echo":false,"script":null) + ] +} +pitch_down={ +"deadzone": 0.5, +"events": [ Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":3,"axis_value":1.0,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":0,"physical_scancode":16777234,"unicode":0,"echo":false,"script":null) + ] +} +attack={ +"deadzone": 0.5, +"events": [ Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":1,"pressure":0.0,"pressed":false,"script":null) +, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":1,"pressed":false,"doubleclick":false,"script":null) + ] +} + +[mono] + +project/assembly_name="Particles" + +[physics] + +common/enable_pause_aware_picking=true + +[rendering] + +environment/default_environment="res://default_env.tres" diff --git a/Particles/quadmesh.tres b/Particles/quadmesh.tres new file mode 100644 index 0000000..710d0f7 --- /dev/null +++ b/Particles/quadmesh.tres @@ -0,0 +1,34 @@ +[gd_resource type="QuadMesh" load_steps=6 format=2] + +[sub_resource type="OpenSimplexNoise" id=1] + +[sub_resource type="NoiseTexture" id=2] +seamless = true +noise = SubResource( 1 ) + +[sub_resource type="OpenSimplexNoise" id=4] + +[sub_resource type="NoiseTexture" id=5] +noise = SubResource( 4 ) + +[sub_resource type="SpatialMaterial" id=3] +params_billboard_mode = 3 +params_grow = true +params_grow_amount = 8.037 +params_use_alpha_scissor = true +params_alpha_scissor_threshold = 0.98 +particles_anim_h_frames = 1 +particles_anim_v_frames = 1 +particles_anim_loop = false +albedo_texture = SubResource( 2 ) +emission_enabled = true +emission = Color( 0.988235, 0.909804, 0.741176, 1 ) +emission_energy = 16.0 +emission_operator = 1 +emission_on_uv2 = false +emission_texture = SubResource( 5 ) + +[resource] +material = SubResource( 3 ) +flip_faces = true +size = Vector2( 0.005, 0.068 ) diff --git a/Particles/terrain.shader b/Particles/terrain.shader new file mode 100644 index 0000000..d376ab5 --- /dev/null +++ b/Particles/terrain.shader @@ -0,0 +1,26 @@ +shader_type spatial; + +uniform sampler2D height_map; +uniform sampler2D normal_map; +uniform float scale = 1.0; +uniform float multiply = 1.0; +uniform float offset = -0.5; +uniform vec2 translation = vec2(0,0); + +varying vec2 txt_position; + +float height(vec2 pos) { + //return sin(pos.x) + cos(pos.y); + return (length(texture(height_map, pos)) + offset) * multiply; +} + +void vertex() { + vec2 pos = VERTEX.xz / scale + translation; + txt_position = pos; + VERTEX.y += height(pos); +} + +void fragment() { + NORMALMAP = texture(normal_map, txt_position).xyz; + ALBEDO = vec3(5.0) * texture(height_map, txt_position).xyz; +} \ No newline at end of file diff --git a/Particles/terrain.tres b/Particles/terrain.tres new file mode 100644 index 0000000..67407ac --- /dev/null +++ b/Particles/terrain.tres @@ -0,0 +1,14 @@ +[gd_resource type="ShaderMaterial" load_steps=4 format=2] + +[ext_resource path="res://terrain.shader" type="Shader" id=1] +[ext_resource path="res://noise_normal.tres" type="Texture" id=2] +[ext_resource path="res://noise.tres" type="Texture" id=3] + +[resource] +shader = ExtResource( 1 ) +shader_param/scale = 21.149 +shader_param/multiply = 7.213 +shader_param/offset = -1.591 +shader_param/translation = Vector2( 0, 0 ) +shader_param/height_map = ExtResource( 3 ) +shader_param/normal_map = ExtResource( 2 ) diff --git a/Particles/terrain_v01.shader b/Particles/terrain_v01.shader new file mode 100644 index 0000000..70c27a9 --- /dev/null +++ b/Particles/terrain_v01.shader @@ -0,0 +1,16 @@ +shader_type spatial; + +uniform sampler2D height_map; +uniform float scale = 1.0; +uniform float multiply = 1.0; +uniform float offset = -0.5; +uniform vec2 translation = vec2(0,0); + +float height(vec2 pos) { + //return sin(pos.x) + cos(pos.y); + return (length(texture(height_map, pos / scale + translation)) + offset) * multiply; +} + +void vertex() { + VERTEX.y += height(VERTEX.xz); +} \ No newline at end of file diff --git a/Particles/test.tscn b/Particles/test.tscn new file mode 100644 index 0000000..67d01a8 --- /dev/null +++ b/Particles/test.tscn @@ -0,0 +1,17 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://quadmesh.tres" type="QuadMesh" id=1] + +[sub_resource type="ParticlesMaterial" id=1] +initial_velocity = 2.0 + +[node name="Spatial" type="Spatial"] + +[node name="Particles" type="Particles" parent="."] +use_in_baked_light = true +emitting = false +amount = 67 +lifetime = 2.01 +one_shot = true +process_material = SubResource( 1 ) +draw_pass_1 = ExtResource( 1 ) diff --git a/Particles/visualshader.tres b/Particles/visualshader.tres new file mode 100644 index 0000000..f91e82d --- /dev/null +++ b/Particles/visualshader.tres @@ -0,0 +1,98 @@ +[gd_resource type="VisualShader" load_steps=8 format=2] + +[ext_resource path="res://noise.tres" type="Texture" id=1] + +[sub_resource type="VisualShaderNodeTexture" id=1] +texture = ExtResource( 1 ) + +[sub_resource type="VisualShaderNodeExpression" id=2] +size = Vector2( 1096.18, 348.37 ) +expression = "out = VERTEX.xzy + vec3(0, sin( TIME) + cos( TIME), 0) +" + +[sub_resource type="VisualShaderNodeInput" id=3] +input_name = "vertex" + +[sub_resource type="VisualShaderNodeVectorOp" id=4] +default_input_values = [ 0, Vector3( 0, 0, 0 ), 1, Vector3( 1, 0, 1 ) ] +operator = 2 + +[sub_resource type="VisualShaderNodeVectorOp" id=5] +default_input_values = [ 0, Vector3( 0, 0, 0 ), 1, Vector3( 0, 1, 0 ) ] +operator = 2 + +[sub_resource type="VisualShaderNodeVectorOp" id=6] + +[resource] +code = "shader_type spatial; +render_mode specular_schlick_ggx, async_visible; + +uniform sampler2D tex_vtx_2; + + + +void vertex() { +// Input:6 + vec3 n_out6p0 = VERTEX; + +// VectorOp:7 + vec3 n_in7p1 = vec3(1.00000, 0.00000, 1.00000); + vec3 n_out7p0 = n_out6p0 * n_in7p1; + +// Expression:5 + vec3 n_out5p0; + n_out5p0 = vec3(0.0, 0.0, 0.0); + { + n_out5p0 = VERTEX.xzy + vec3(0, sin( TIME) + cos( TIME), 0) + + } + +// Texture:2 + vec4 tex_vtx_2_read = texture(tex_vtx_2, n_out5p0.xy); + vec3 n_out2p0 = tex_vtx_2_read.rgb; + float n_out2p1 = tex_vtx_2_read.a; + +// VectorOp:8 + vec3 n_in8p1 = vec3(0.00000, 1.00000, 0.00000); + vec3 n_out8p0 = n_out2p0 * n_in8p1; + +// VectorOp:9 + vec3 n_out9p0 = n_out7p0 + n_out8p0; + +// Output:0 + VERTEX = n_out9p0; + +} + +void fragment() { +// Output:0 + +} + +void light() { +// Output:0 + +} +" +graph_offset = Vector2( -213.833, -33.6997 ) +nodes/vertex/0/position = Vector2( 1900, 300 ) +nodes/vertex/2/node = SubResource( 1 ) +nodes/vertex/2/position = Vector2( 980, 120 ) +nodes/vertex/5/node = SubResource( 2 ) +nodes/vertex/5/position = Vector2( -240, 260 ) +nodes/vertex/5/size = Vector2( 1096.18, 348.37 ) +nodes/vertex/5/input_ports = "" +nodes/vertex/5/output_ports = "0,1,out;" +nodes/vertex/5/expression = "out = VERTEX.xzy + vec3(0, sin( TIME) + cos( TIME), 0) +" +nodes/vertex/6/node = SubResource( 3 ) +nodes/vertex/6/position = Vector2( 980, -20 ) +nodes/vertex/7/node = SubResource( 4 ) +nodes/vertex/7/position = Vector2( 1340, -40 ) +nodes/vertex/8/node = SubResource( 5 ) +nodes/vertex/8/position = Vector2( 1340, 200 ) +nodes/vertex/9/node = SubResource( 6 ) +nodes/vertex/9/position = Vector2( 1740, -40 ) +nodes/vertex/connections = PoolIntArray( 5, 0, 2, 0, 6, 0, 7, 0, 8, 0, 9, 1, 7, 0, 9, 0, 2, 0, 8, 0, 9, 0, 0, 0 ) +nodes/fragment/0/position = Vector2( 620, 0 ) +nodes/light/0/position = Vector2( 340, 160 ) diff --git a/README.MD b/README.MD index a372e9f..f75e151 100644 --- a/README.MD +++ b/README.MD @@ -1,8 +1,3 @@ -# Contents - -[TOC] - - ## 🟡 Godot Docs 官方文档阅读指南 - [动画师救生手册 - 动画十二原则](https://www.bilibili.com/video/BV1x54y1e7J9) - [GAMES101-现代计算机图形学入门 - 闫令琪](https://www.bilibili.com/video/BV1X7411F744/) @@ -69,6 +64,37 @@ make markdown 进行格式转换。 克隆命令 git clone 不支持通过 -b 指定多个分支,只有最后指定的分支才有效。 + +为了方便在文档之间快速跳转,推荐在 Sublime Text 环境下阅读文档,只需要安装 run-snippet 插件, +使用 F9 快捷键跳转到光标下的文件或链接。 + +快速安装 RunSnippet 插件: + +- Ctrl+Shift+P 打开 Sublime Text 命令调板; +- 执行 Add Repository 添加本插件代码仓库地址: https://github.com/jimboyeah/run-snippet +- 然后执行 Install Package 并输入 RunSnippt 进行确认安装; + +手动添加 Repository,执行菜单: Perferences 🡒 Package Settings 🡒 Package Control 🡒 Settings + + "repositories": + [ + "https://github.com/jimboyeah/run-snippet", + ], + +可以在 Packages 目录执行以下命令安装 RunSnippet 插件: + + git clone git@github.com/jimboyeah/run-snippet.git + +添加配置文件,默认启用分组跳转,如果 Sublime Text 没有设置分组,则在当前 View 弹出文件跳转窗口, +这种行为有点打断思路。更合理的做法是 GUI 设置 2 个 Group,并且在另一个 Group 弹出文件 Panel。 +Load settings from /Packages/Users/RunSnippet.sublime-settings + +```json + { + "jump_between_group": true, + } +``` + Godot 文档是非常人性化的,体现在以下几点: Godot IDE 编辑器本身集成了在线手册,按 F1 或直接在代码中按下 Ctrl 单击关键字即跳转到相关手册文档。 @@ -100,16160 +126,3 @@ Godot IDE 编辑器本身集成了在线手册,按 F1 或直接在代码中按 - Solid Drawing 立体造型,符合透视的画面才更真实; - Appeale 吸引力,让画面更讨喜、比例更突出,使用画面简洁而突出重点,这也是动画与插画的区别; - -## 🟡 Godot Architecture 构架介绍 -- [Introduction to Godot development](https://docs.godotengine.org/en/stable/development/cpp/introduction_to_godot_development.html) -- [Optimization using Servers](https://docs.godotengine.org/en/3.3/tutorials/optimization/using_servers.html) -- [Inheritance class tree](https://docs.godotengine.org/en/stable/development/cpp/inheritance_class_tree.html) -- [Overview of Godot's key concepts](https://docs.godotengine.org/en/stable/getting_started/introduction/key_concepts_overview.html) -- [Nodes and Scenes](https://docs.godotengine.org/en/stable/getting_started/step_by_step/nodes_and_scenes.html) -- [Using SceneTree](https://docs.godotengine.org/en/3.5/tutorials/scripting/scene_tree.html) -- [Using Viewports](https://docs.godotengine.org/en/3.5/tutorials/rendering/viewports.html) -- [Canvas layers](https://docs.godotengine.org/en/3.5/tutorials/2d/canvas_layers.html) -- [User Interface(UI)](https://docs.godotengine.org/en/3.5/tutorials/ui/index.html) -- [What were the motivations behind creating GDScript?](https://docs.godotengine.org/en/latest/about/faq.html#doc-faq-what-is-gdscript) -- [WebGL 可视化相机](https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-visualizing-the-camera.html) -- [《迷失岛2》游戏框架开发01:实现场景转换|Godot教程](https://www.bilibili.com/video/BV1jr4y1V7xJ/) -- [Scene Transitions By: Henrique Campos](https://www.gdquest.com/tutorial/godot/2d/scene-transition-rect/) - -Godot 架构是模块化的,通过扩展模块可以实现各种功能,而各种编程语言则是通过以下三个模块实现的。 -Godot 本身是 C++ 开发的,可以实现各种模块以扩展 Godot 功能,模块源代码位于 modules 子目录: - -- GDScript 模块引入了 GDScript 脚本编程; -- Mono 模块引入 C# 语言; -- GDNative 模块引入原生语言,如通过 GDNative C++ Binding 使用 C++ 语言; - -入门先阅读 Introduction to Godot development,- Architecture diagram 了解工程架构,搞清楚 -**SceneTree** -> Nodes -> CanvasItem(Node2D, Control) and Spatial (3D Nodes) 基本关系。 -Godot IDE 编辑器本身就是和游戏工程一样的应用,每个游戏可以看作一个场景树,它的节点是场景,而场景又 -包含一组节点,节点是一个可以绑定脚本进行编程的对象。 - -![Architecture diagram](https://docs.godotengine.org/en/stable/_images/architecture_diagram.jpg) - -Godot 本身支持多平台,通过源代码 platform 目录下对应各平台的实现,detect.py 脚本则用于检测当前 -支持的平台,主程序入口则是在 main 目录下,入口方法是 **Main::start()**。SceneTree 类型就是一个 -MainLoop 子类型,代表了游戏中的主循环,它会在程序入口中执行。 - -- OS_Unix <- OS -- OS_UWP <- OS -- OS_Windows <- OS -- OS_X11 <- OS_Unix -- OS_OSX <- OS_Unix -- OSIPhone <- OS_Unix -- OS_Android <- OS_Unix -- OS_JavaScript <- OS_Unix - -不同平台的入口会包装 OS 类,例如 OS_Windows,但是 Web 平台特殊一点,因为它运行在浏览器上。因为 -大多数系统都是类 Unix 操作系统,所以除了 Windows 平台,几乎都包装为一个 OS_Unix 类型。操作系统 -与窗口的交互消息全部通过 Notification 机制转达场景树。Godot IDE 本身就是一个 EditorNode 节点, -它也是基于 Godot 引擎开发的一个游戏,只不过这个游戏是用来开发其它游戏的。 - -以下是入口代码的流程摘要,演示了 UWP 平台下的入口运行基本流程。Android 平台窗口消息传递方式差别 -较大,MainLoop 的方法包装到了不同的函数内: - -```cpp -bool Main::start() { - - MainLoop *main_loop = nullptr; - if (editor) { - main_loop = memnew(SceneTree); - }; - - SceneTree *sml = Object::cast_to(main_loop); - - if (!project_manager && !editor) { // game - // ... - } - - if (editor) { - EditorNode *editor_node = nullptr; - editor_node = memnew(EditorNode); - sml->get_root()->add_child(editor_node); - } - - OS::get_singleton()->set_main_loop(main_loop); -} - -void OS_UWP::run() { - if (!main_loop) - return; - - main_loop->init(); - - uint64_t last_ticks = get_ticks_usec(); - - int frames = 0; - uint64_t frame = 0; - - while (!force_quit) { - CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent); - if (managed_object->alert_close_handle) - continue; - process_events(); // get rid of pending events - if (Main::iteration()) // -> VisualServer tick() - break; - }; - - main_loop->finish(); -} -``` - -整个引擎的消息处理循环结构就是 while 循环,Main::iteration() 函数就是游戏循环中要处理的工作, -在这里会发送各种通知,让节点有处理的机会。在入口方法中,会判断当前的运行状态,根据是否在编辑器状态 -执行不同的代码。 - -游戏循环中,引擎会按 FPS 频率执行事件处理回调函数,当游戏进入空闲状态时,可以主动关闭事件处理回调, -以降低空闲时的 CPU 消耗: - -```py -void _physics_process(delta: float) virtual -void _process(delta: float) virtual - -void set_physics_process(enable: bool) -void set_process(enable: bool) - -var fps = ProjectSettings.get_setting("physics/common/physics_ticks_per_second", 60) -Engine.set_physics_ticks_per_second(fps/3) - -get_tree().paused = true -``` - -你可以使用两种类型的处理: - -其中,`_process()` 这个事件处理回调函数在早期版本中,也称为 `_idle()` 即空闲事件处理。相对的, -物理事件的处理更优先,因为有更高的实时性需求。任何与物理引擎相关的,比如移动可能与环境相碰撞的实体, -都应使用 `_physics_process()`。 - -- Idle processing 空闲处理可以用来执行每帧更新节点的代码,执行频率会尽可能地快。 -- Physics processing 物理处理的频率是固定的,和游戏的实际帧率无关,可以让物理平滑执行。 - -在一些游戏中,可以大大降低物理处理周期率,比如说,将物理新 60FPS 改变为 30 次甚至 20 次,就大大 -降低 CPU 的负载。 - -Godot 中的节点都有“Pause Mode”设置,暂停模式定义它们应该在何时进行处理。要将游戏暂停,必须设置 -暂停状态。将 SceneTree.paused 属性赋值为 true 即可。 - -设置暂停后会导致两件事:首先,所有节点的 2D 和 3D 物理系统都会停止。其次,根据处理模式不同,某些 -节点的行为会停止或者开始。物理系统可以通过 set_active 方法激活。 - -- Inherit:处理与否取决于父、祖父等等节点中第一个非**继承**状态的节点。 -- Stop:无条件停止节点,暂停时该节点不会进行处理。 -- Process:无条件处理节点,无论暂停与否,该节点都会进行处理。 - -节点停止处理时会发生不少事情,`_process`、`_physics_process`、`_input`、`_input_event` 等 -函数都不会再被调用。但信号正常工作,所连接的函数也正常执行,即便脚本附加在“Stop”暂停模式的节点上。 -动画节点会暂停它们的当前动画,音频节点会暂停它们的当前音频流,粒子也会暂停。游戏不再暂停时,它们都会 -自动继续运行。 - - -Godot 编程体系的继承层次结构中,所有对象的父类是 **Object**,然后派生出各种功能的类对象,主要是 -引用类和节点类两大块: - - ● Object - +-- ● References - | +-- ● Resources - +-- ● Node - | +-- ● CanvasItem - | | +-- ● Control - | | +-- ● Node2D - | +-- ● Spatial - -Object 提供了最基本的能力,包括观察者编程模式 Signal 和 Notifications 两种机制的实现。 -信号 **Signal** 在 Godot 中大量使用,这是观察者编程模式的一种实现,可以实现复杂逻辑的解耦。 - -相对于 Object、References、Resources 等类型,Node 是一个功能丰富的对象,因此它也更消耗 CPU, -所以,可以使用简单对象实现的功能,就尽量避免使用 Node 类型对象来实现。 - -场景树中可以添加的所有节点类型统一在源代码 scene 目录下: - -- /scene/main 主要节点类型,包括 SceneTree、CanvasLayer、ViewPort、Node、Timer、HTTPRequest; -- /scene/gui 控件类型,包括 Control 及子类的实现,如文件对话框 FileDialog 等; -- /scene/2d 二维节点类型,包括 Node2D 及子类的实现; -- /scene/3d 三维节点类型,包括 Spatial 及子类的实现; -- /scene/animation 动画节点类型,包括AnimationPlayer、AnimationTree 等等; -- /scene/audio 音频播放节点类型,包括 AudioStreamPlayer; -- /scene/resource 资源节点类型,包括 Animation、AudioStreamPlaybackSample 等等; - -和 UnrealEngine 这样的大型引擎不同,Godot 引擎没有将二维图形作为一个独立的模块包装,而是在更底层 -架构了一个服务系统,以不同的服务器为音频、图形、物理及空间位置相结合的音频等提供相应的渲染服务。最后, -音频合成数据将送往系统的音频驱动器,视频则交由 OpenGL 或 Vulkan 接口进行渲染。 - -- VisualServer (RenderingServer) 可视渲染服务; -- Physics2DServer 二维物理引擎服务; -- PhysicsServer 三维物理引擎服务; -- SpatialSound2DServer 二维空间的音频服务; -- SpatialSoundServer 三维空间的音频服务; -- AudioServer 所有音频服务的最终于合成器; - -节点类型本身不包含绘图逻辑,只是存储数据、功能逻辑以及用户交实现等等。因为节点类型众多,从音频到图形, -从网络到 AI 算法相关都有,也就不可能直接在节点类型中编写绘图等具体行为的实现代码,这些复杂的细节将 -交由各种服务器类型实现。 - -2D 场景中,需要一个画布来绘制所有的视觉元素。而在 3D 中,所有可见对象都需要一个 scenario,它代表 -可视的三维世界,通过 Spatial **get_world()** 方法访问。所有视觉元素都会绘制到 Viewport 上, -可以将其附加到场景树上,也可以使用渲染引擎 **viewport_create()** 方法实例化一个 Viewport 对象。 -使用自定义场景或者画布时,通过 **viewport_set_scenario()**、**viewport_attach_canvas()** -方法将它们附加到视口中,**scenario_create()** 创建场景。 - -所有三维对象都由相关资源和实例组成,可以是网格、粒子系统、灯光或任何其他 3D 对象。所以,VisualServer -实质上就是实时的游戏渲染引擎,最新版本 Godot 4.x 中,名字也改为了 **RenderingServer**。为了渲染 -3D 资源,必须调用渲染引擎方法 **instance_set_base()** 将资源附加到渲染引擎实例上,还必须附加场景。 - - -```py - # Create a visual instance (for 3D). - var instance = VisualServer.instance_create() - # Set the scenario from the world, this ensures it - # appears with the same objects as the scene. - var scenario = get_world().scenario - VisualServer.instance_set_scenario(instance, scenario) - # Add a mesh to it. - # Remember, keep the reference. - mesh = load("res://mymesh.obj") - VisualServer.instance_set_base(instance, mesh) - # Move the mesh around. - var xform = Transform(Basis(), Vector3(20, 100, 0)) - VisualServer.instance_set_transform(instance, xform) -``` - -引擎内部,所有资源都有一个 Resource ID (RID),是服务器内部为了管理资源内存分配实现的句柄。服务器 -内部几乎每个功能都需要 RID 来访问实际资源。大多数 Godot 节点和资源类型都包含来自服务器内部的 RID, -它们可以通过不同的函数获得。事实上,继承 Resource 的任何类型都可以转型为 RID。但并非所有资源都包含 -RID,这种情况下,转型得到的结果为空。 - -- CanvasItem.get_canvas_item() -- CanvasLayer.get_canvas() -- Viewport.get_viewport_rid() - - -官方提供了一个 Control Gallery 示范工程,用于演示各种控件、布局容器的使用,2D 控件支持主题设置, -定制控件外观非常方便。2D 对象使用 Anchors 模型进行图形的比例、多分辨率处理: - -![Size and Anchors](https://docs.godotengine.org/en/3.5/_images/anchors.png) - -Godot IDE 中的每个场景就是一个 Node 对象,每个节点可以附加脚,以实现程序化操控。每个游戏结构和 -Godot IDE 是一样的,都是一个场景树,每个程序中可以有多个场景,但是当前场景树中加载的活动场景只有 -`root` 节点加载的这个 SceneTree,这个根节点是一个 Viewport,并且始终是加载的节点,不可以卸下。 - -![Scene tree](https://docs.godotengine.org/en/3.5/_images/activescene.png) - -在运行之前,可以设置工程,指定一个主场景: - - Project -> Project Settings -> Application -> Run -> Main Scene - -场景中 Viewports 就相当于一个屏幕,游戏世界的内容会投射到这里形成图像。场景树顶点就是一个 Viewport, -在 Godot IDE 运行调试时,点开 Scene 面板中的 Remote 栏目即可以查看运行中的顶层节点,应用运行时, -它总是处于加载状态,而且不可以手动清除。 - -在 3D 场景中,用一个 Viewport 节点装载 2D 节点,然后将 Viewport 的纹理图像作为 3D 场景对象的 -材质贴图使用,这是一种常用的 3D 表现 2D 内容的手段。注意,Viewport 的尺寸要足够容纳 2D 内容,如果 -2D 节点位置超出 Viewport 的尺寸,则内容会被裁剪掉。另外,如果内容出现上下颠倒,可以设置 Reander -Target - V Flip 进行一次竖直方向的反转即可以解决。 - -Viewport 可以设置背景透明,赋值给 SpatialMaterial 材质的 Albedo 作为表面反射色,需要启用材质 -的透明功能,Flags -> Transparent,否则带 Alpha 通道的贴图会出现黑色块。设置 - -Viewport 可以装入 ViewportContainer 这个特殊容器,且只包含一个 Viewport 子节点,用来显示图像, -方便在编辑器中观察图像输出效果,并且可以用来显示多个视图图像输出。 - - -在计算机模拟的世界中,需要虚构一台用于成像的相机,目的是模拟人的眼眼所看到的世界。视界中的光线入射 -到视网膜形成图像,在模拟世界中的成像用 Viewport 这个概念表示,光线从远处透过视口,到达相机的位置, -就是一个坐标点,而光线与视口平面相交的点就对应一个像素。通过调整相机的参数,例如改变视域 FOV 就可以 -改变成像结果。而视口又可以再进行几何变换,以控制图像的渲染。 - -![From "Ray tracing" on Wikipedia](https://jamie-wong.com/images/16-07-11/raytrace.png) - -材质设置可以使用 **SpatialMaterial**,适当调整贴图 UV1 坐标绽放、偏移,使用纹理适合于模型表面。 -Godot 支持每个材质使用两个 UV 通道,次级的 UV2 通常用于环境光遮挡或发射(烘焙光)。设置 Triplanar -启用 Triplanar Mapping,在使用重复纹理时非常有用。 - -在 2D 场景中使用 3D 场景也是类似的思路,只过不反过来,将 Viewport 中的纹理图像设置到 2D 节点的 -纹理属性上,如 Sprite 或 TextureRect 等节点都可以指定 ViewportTexture 以使用 3D 场景产生的 -纹理图像。注意,要得到 3D 场景的图像,必需设置 Camera,因为需要确实世界空间的投影关系才能得到相应 -的平面图像,这一点和 2D 有些差别。为 3D 节点材质指定纹理贴图时,如漫反射纹理 Albedo Texture, -需要开启 resource_local_to_scene 选项,对应材质面板的 Resource -> Local To Scene 选项。 - -可以参考官方示范项目,Dynamic Split Screen Demo 演示了如何使用 Viewports 在同一个显示器中 -显示两个玩家的独立摄像机视口,使用了着色器程序,有一定的复杂度。 - - -当一个节点连接到 Root Viewport,它就成为场景树中的一个部分,最后节点被移除,过程会触发系列回调方法: - -```py -_enter_tree() -_ready() -_process(delta) -_exit_tree() -``` - -节点树所有节点会按位置先后、由表层到内层依次执行 enter_tree 方法,而 ready 和 exit_tree 方法, -则不同,会先由内层到外层的顺序执行,父级节点需要等待内层节点工作完成才能执行 ready 等动作。 -节点从非活跃状态转变为活跃状态,此时才会处理所有程序逻辑,包括用户输入、信号、消息处理,播放声音等。 - -节点提供了一个 request_ready() 方法用于请求再次执行 `_ready()` 回调,由于场景树中是后序遍历来 -执行这个回调的,当需要在顶级节点执行完后再紧接进行其它任务,就可以使用利用 ready 信号和 yield 方法, -通过 yield(root, "ready"),可以等待顶级节点完成后再执行 yield 后续的代码。 - - -完成初始化后就会进入循环处理,Godot 每帧所花销的计算时间会通过 delta 值传递到回调函数中。当计算 -量大时,delta 值可能会增加,这意味着 FPS 会下降。但是,physics process 回调中接收到的 delta -值是一个恒定值,因为物理引擎需要一个精确的时间以进行模拟世界中的物理计算。 - -通常计算属性值需要与 delta 相乘,使用其与时间关联,而不是依赖 FPS 的变化。 - -使用 load、preload 方法加载场景文件得到 **PackedScene**,传递给 change_scene_to() 方法就 -可以切换场景,原场景内容就会卸载掉。使用 change_scene() 方法切换场景直接使用 res:// 指定场景文件。 - -```py -# Methods -Error change_scene(path: String) - -Error change_scene_to(packed_scene: PackedScene) - -# Properties -Viewport root - get_root() getter - - Node current_scene - set_current_scene(value) setter - get_current_scene() getter -``` - -场景可以使用 change 方法进行切换,这会触发原场景所有节点卸载动作,也可以将场景作为一个节点实例, -再附加到当前活动的场景树中,这不会触发节点卸载行为。以下代码片段演示了节点与节点树连接的事件流程, -以及通过 load() 方法加载场景,并作为节点加载它: - -```py -extends CanvasItem - -onready var list = $"/root/Node2D/ItemList" - -func _enter_tree(): - print(self.name, " _enter_tree") - if list != null: - list.add_item(self.name + " _enter_tree") - -func _ready(): - print(self.name, " _ready") - list.add_item(self.name + " _ready") - -func _exit_tree(): - print(self.name, " _exit_tree") - list.add_item(self.name + " _exit_tree") - -func _on_Button_pressed(): - print(self.name, " pressed") - list.add_item(self.name + " pressed") - - scene_change() - #self.queue_free() - -func scene_change(): - # change sence - var cover = load("res://L1 Scene Tree/cover.tscn") - #get_tree().change_scene_to(cover) - #get_tree().change_scene("res://L1 Scene Tree/cover.tscn") - - # sence as node - var acover: Node2D = cover.instance() - print("button: ",self) - var child = acover.get_child(0) as Control - #acover.position = get_local_mouse_position() - acover.position = get_global_mouse_position() - child.rect_size / 2 - get_node("/root").add_child(acover) - list.add_item("Load a scene as a node") - - var tween:SceneTreeTween = create_tween().set_trans(Tween.TRANS_ELASTIC) - tween.tween_property(acover, "modulate", Color.red, 2) - tween.tween_callback(acover, "queue_free") -``` - -所有 **Control** 子类者都具有 mouse_filter 属性,将其设置为 MOUSE_FILTER_STOP 就可以阻挡 -鼠标事件穿透到后面的对象上,用来避免在某些情形下不会触发相应的事件。配合 **SceneTreeTween** 可以 -创建转场动画效果。 - - -在节点的脚本中,使用 get_tree() 方法获取场景树引用,要获取其它节点引用则使用 get_node() 方法。 -这个方法可以简写为 $,如 $"/root",又如 $SomeNode 等价于 get_node("SomeNode")。还有一个 -更方便的方法创建节点引用,只需要在 Scene 节点列表中拖动节点到脚本中即可自动生成引用,按 Ctrl 拖放 -时还可以生成 onready 引用: - -```py -SceneTree get_tree() const -Node get_node(path: NodePath) const -``` - -参数中的 **NodePath** 是一个表示节点路径的字符串,根节点 /root 始终是同一个顶级的 Viewport。 -可以使用点或两点表示当前节点位置和父级位置,除了使用 / 开头表示绝对路径,其它路径都相对当前节点: - -```py -get_node(".") -get_node("..") -get_node("Backpack/Dagger") -get_node("../Swamp/Alligator") -get_node("/root/MyGame") -``` - -虽然当前活动场景树只有一个,但是可以使用 Godot 的自动加载功能,工程配置 AutoLoad 栏目中设置, -可以实现将其它场景、或者脚本加载到全局空间运行,例如背景音乐需要避免转场中断,可以在自动加载场景中 -播放 BGM 即可以解决。 - - -可以激活 Scene Unique Nodes 模式,在节点右键菜单中,这样就在原节点名前缀 % 号作为唯一名称来访问它。 -使用拖动的方式,可以很方便地将节点从场景树中拖放到脚本中,会自动生成引用,按下 Ctrl 生成引用类似如下: - - onready var label = $"%Label" - -Godot 工程中的脚本文件或者场景文件都是一种资源类型 Resource,使用 load 或 preload 方法加载, -根据不同的文件返回的各种子类型,就场景资源和脚本而言,它们分别是 PackedScene 和 GDScript 类型。 -并且,资源类型不同,实例化方法也不同。 - -对于场景,使用 instance() 方法实例化,返回的类型就是 Node,打印时会将场景根节点的名称和父类型信息, -如 RootNode:[Node2D:724501] 这样的格式。而脚本资源的实例化就是使用脚本中通用的 new() 方法,返回 -的类型就是 extends 继承的类型,因为不像场景那样有节点树,所以打印脚本类型信息中只显示了父类信息。 -如果指定了 class_name,这个脚本的类型就是命名的类型,会在类型列表中显示。 - -基类方法 Object get_class() 用来获取对象的类型,这个方法不考虑脚本中通过 class_name 定义的类型, -而是获取引擎 C++ 内部定义的类型,类似地,is_class() 方法判断的也是内部类型。内部类类型信息记录在 -**ClassDB** 这个信息类中,通过 **get_class_list()** 方法可以查询引擎现有的类型列表。 - -有趣的是 Godot 基于节点化的编程思维中,节点只是一种数据结构,而将脚本绑定到节点上运行,就使得节点 -拥有了交互的能力。脚本附加到节点后,此时 **self** 关键字既引用了类实例,又引用场景树中的节点。做类型 -判断时,如 is 关键字,判断的是引擎 C++ 内部实现的类型,或者脚本定义类型。尽管场景也可以继承其它场景, -但是这仅仅是复用节点树结构的一种方法,并不是编程语言的类型系统的继承。 - -GDScript 脚本使用单继承,每一个脚本文件就是一个类型定义,当然可以定义内部类类。这种结合了节点的 -Object-Oriented Programming (OOP) 编程语言真的很特别,省略了复杂 OOP 规则,没有了 C++ 中的 -Private、Protected、Public 访问控制机制,语法结构使用 Python 风格,脚本中定义的变量就是类成员, -具体访问规则由开发者自行把握,通过 setget 可以控制成员的读写逻辑。使用脚本能快速迭代开发产品,只在 -遇到性能瓶颈时,才考虑将脚本迁移为 C/C++ 等高效的语言实现: - -```py -tool - -# BasicScene defined in L0 Basic Guide/entry.gd -extends BasicScene - -# (optional) class definition with a custom icon -class_name SceneL8, "res://icon.png" - -const tscn = preload("res://L0 Basic Guide/entry.tscn") # class PackedScene -const gd = preload("res://L0 Basic Guide/entry.gd") # class GDScript - -func _ready(): - - print("basic scene ", tscn, tscn.instance()) - print("basic script ", gd, gd.new()) - - var scene = get_tree().current_scene - print(self, " current_scene ", scene, " is instacne of gd? ", self is gd) - - print("note ", Something.new().note) - print("NOTE ", Something.NOTE) - print("enum ", Something.UNIT_ALLY, Something.Named.ANOTHER_THING) - -# Inner class - -class Something: - # Member variables - var note = "This is InnerClass." - - # Constants - const NOTE = "This is a constant." - - # Enums - enum { UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY} - enum Named {THING_1, THING_2, ANOTHER_THING = -1} -``` - - - -## 🟡 Project Settings 项目配置 -- [Multiple resolutions](https://docs.godotengine.org/en/3.5/tutorials/rendering/multiple_resolutions.html) -- [Setting up the project](https://docs.godotengine.org/en/3.5/getting_started/first_2d_game/01.project_setup.html) - -Godot 项目设置中 display/window/stretch/mode 拉伸模式会影响画面的呈现: - -如果设置 Stretch Mode = Viewport 意味着将根视口的大小精确设置为“项目设置 Display 配置中指定 -的基本大小。调整窗口大小时,会缩放此视口以适合屏幕,除非设置了保留宽高比为 Stretch Aspect = keep。 -使用像素精度游戏时,或者为了渲染到较低的分辨率以提高性能,此模式很有用。设置 2D 伸展模式,则可以保持 -图像在不同显示屏幕下布局缩放的一致性。 - -![Stretch Mode](https://docs.godotengine.org/en/3.5/_images/stretch.png) - - -在高分屏设备中,默认情况下,操作系统会认为 Godot 项目是 DPI 无关的。因为操作系统的 DPI 回退缩放 -比应用程序内做缩放要快很多,即便使用 viewport 拉伸模式,所以这样做可以提高在低端系统上的性能。 - -不过操作系统的 DPI 回退缩放功能在全屏模式下并不好用。如果你想在 hiDPI 显示器下得到清晰的画面,又 -或者想要支持全屏,那么推荐启用项目设置中的 Display > Window > Dpi > Allow Hidpi 以获得高清 -画面。否则,可能在高分屏幕上看到像素化的画面,这是因为像素都按 DPI 比例缩放了。 - -Godot 4.x 中需要启用 Advanced Settings 才可以设置 hiDPI 选项。 - -注意,Allow Hidpi 选项仅在 Windows 和 macOS 上有效,其它平台会忽略这个选项。 - -Godot IDE 本身是打开这个选项的,与 DPI 相关的,可以在编辑器中设置缩放比例,高分屏默认为 200%。 -在编辑器中运行项目时,只有在项目设置里启用 Allow Hidpi 才会让项目与 DPI 相关。 - -Godot 尚未支持手动设置 2D 缩放比例,所以无法在非游戏应用中支持 hiDPI。因此,推荐为非游戏应用禁用 -Allow Hidpi 选项,操作系统会回退到低 DPI。 - -高分屏系统中,还可能出现调试游戏时,在没有启用 hiDPI 时,窗口位置超出右下角的情况。这种情况可以通过 -修改编辑器配置 run/window_placement 改变窗口位置,默认是居中,是启用高分屏支持就没有问题。 - - -虽然当前活动场景树只有一个,但是可以使用 Godot 的自动加载功能,工程配置 AutoLoad 栏目中设置, -可以实现将其它场景、或者脚本加载到全局空间运行,例如背景音乐需要避免转场中断,可以在自动加载场景中 -播放 BGM 即可以解决。 - -Godot 全局空间 @GDScript 和 @GlobalScope 包含所有脚本中可以直接使用的函数,用户要向全局空间 -添加对象,可以使用工程设置自动加载,Singletons (AutoLoad),自动加载得到位于全局空间的单态对象。 - -例如,将以下脚本保存到 Global.gd,并设置工程的 AutoLoad 加载它,即可以实现自动剧中窗口。运行程序 -时,引擎将加载并附着 GlobalNS 到场景树上,初始化方法会被调用: - -```py -extends Node - -class_name GlobalNS - -func _init(): - print("Global.gd _init") - # Center window on screen - var screen_size = OS.get_screen_size(OS.get_current_screen()) - var window_size = OS.get_window_size() - var centered_pos = (screen_size - window_size) / 2 - OS.set_window_position(centered_pos) -``` - -VSync 垂直同步信号,又称场同步,显示器完整显示一个画面时产生的一个同步信号,可用于和 GPU 协作用途。 -在旧式的 Cathode Ray Tube (CRT) 阴极射线显像管式显示中,一幅画面的显示需要电子枪从左到右,从上 -到下,完整地扫描一遍显示屏才算完成,电子枪由高压电驱动发射电子束击打屏幕上的荧光点显示相应的图像。 - -游戏中打开垂直同步可以使得画面平滑、稳定,因为游戏画面的渲染需要等待显示器的同步信号。而关闭垂直同步, -则可以明显提高帧数,在高端设备上可以获得更快的速度,但是这可能带来图像不稳定的问题。一般图形引擎会 -使用双缓冲区技术 Double buffering,即一个缓冲区连接到显示作为当前显示图像的数据源,另一个缓冲区 -则作为下一帧图像的渲染输出缓冲区。在关闭 Vsync 的情况下,高端设备可能渲染好了画面,但显示器还示来得 -及调用,又开发下一帧图像的渲染了。而下一帧图面又不一定完整,这就导致画面有断裂的现象。 - -FPS 低于游戏设计时,可能会有输入延迟和卡顿。在需要快速反应和复杂输入的游戏中,输入延迟尤其令人恼火。 - -为了提高设备性能厂家也在硬件上不断改良 VSync 技术: - -- Adaptive VSync 由 Nvidia 发起,会根据监视到的 FPS 帧率来决定是否启用 VSync。 -- Fast Sync 由 Nvidia 发起的更高级的自适应垂直同步形式,在必要时启用 VSync,并添加自动三重缓冲。 -- Enhanced Sync 由 AMD 发起,会在帧率低于显示刷新率时禁用 VSync。 - -另外,还有 Nvidia 的 G-Sync 和 AMD 的 FreeSync,这两种 GPU 技术都可以将刷新率和数据与 GPU -的帧速率同步。这些公司希望解决 VSync 的问题,特别是图像精度和均匀性,以及撕裂。但它们要求 GTX 1050 -以及 AMD Radeon RX 200 级别以上的显卡。 - - - - -## 🟡 MT Downloader -- [Requests: HTTP for Humans](https://requests.readthedocs.io/en/latest/) - -一个 Python 实现的多线程下载器,将链接地址复制到粘贴板上,执行它就会自动下载文件并保存到当前目录。 -需要安装 clipboard 模块,使用 pip install clipboard 命令安装。 - -尽管 Python 有一个全局锁,不能并行多线程运行,但是在多线程下的程序也有各种问题需要注意的,比如, -print 函数打印信息到控制台上,尽管这个函数本身是线程安全的,但是可能因为线程切换导致缓冲区被破坏 -而输出一些奇怪的字符,包括 NULL 字符,应该在打印操作后执行 flush 清空缓冲区,以免输出内容被破坏。 - -```py,ignore -import os -import clipboard -import requests -import threading -import concurrent.futures -import sys -import re -import pathlib - -thread_local = threading.local() - -def pagelist(items): - for line in items: - if not line: - continue - p = line.split()[-1].replace("P","") - page = int(int(p) / 3) + 1 - url = line.split()[0] - print(url) - for no in range(page): - print(url.replace(".html", "_%s.html" % no)) - -def checkfile(items): - for item in items: - file = item.split("/")[-1] - if not item or os.path.exists(file): - continue - print(item) - -def checkalbum(items): - albumsum = 0 - error = 0 - for item in items: - if not item: - continue - p = int(item.split()[-1].replace("P", "")) - id = item.split('/')[-1].split('.')[0] - match = pathlib.Path().cwd().glob("%s*" % id) - count = len(sorted(match)) - state = "===Done!" if p == count else "---Miss!" - print(state, item, " <== ", count) - albumsum += p - error += p - count - print("Album: %d/%d, error: %d " %((albumsum-error), albumsum, error)) - -def default_filter(content, url): - root = "/".join(url.split("/")[:3]) - lines = content.decode().split('\r\n') - contents = "" - for line in lines: - if len(contents)>0 and re.search(r"') or line.startswith('
-1: - print(line) - return None - -def get_session(): - if not hasattr(thread_local, "session"): - thread_local.session = requests.Session() - return thread_local.session - - -def download(url): - session = get_session() - headers = { - 'Accept-Encoding': 'gzip, deflate', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' - } - try: - res = session.get(url, headers=headers) - if not res.ok or res.text.find("页面出错")>-1: - return print(url, " status", res.status_code, res.reason) - save(url.split('/')[-1], res.content, url) - except Exception as ex: - print("%s %s" %(url, type(ex))) # class.__class__ - -def save(name, content, url): - content = content_filter(content, url) - if type(content) is not bytes: - return None - fd = os.open(name, os.O_BINARY|os.O_RDWR|os.O_CREAT) - ret = os.write(fd, content) - os.close(fd) - print(['# save to: ', name, ret, content[:9]]) - # fd = open(name, "wb") - # ret = fd.write(content) - # fd.flush() - # fd.close() - -def download_all(urls): - with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor: - executor.map(download, urls) - - -if __name__ == "__main__": - txt = clipboard.paste() - items = txt.split('\r\n') - if "checkfile" in sys.argv: - checkfile(items) - elif "pagelist" in sys.argv: - pagelist(items) - elif "checkalbum" in sys.argv: - checkalbum(items) - else: - download_all(items) -``` - -## 🟡 sango2 三国群英传2兵种与阵法相克规则设计 - -三国群英传2兵种相克表: - -- 以朴刀、武斗、藤甲为代表的短兵器; -- 以长枪、大刀、铁锤为代表的长兵器; -- 以弓箭、弩兵、飞刀为代表的远程兵种。 - -黄巾兵、朴刀兵作为最常见的两个兵种,它们克制的兵种相对少,前者克制蛮族、女兵、飞刀,后者在这些之上还克制弓箭、武斗、黄巾。 - -相克顺口溜: - - 远克长,长克短,短兵循环又克远。 - 大刀破短很能干,飞刀爆的链锤惨。 - 弩兵全能很能干,飞弓藤甲干就完。 - 武斗不惧箭飞锤,黄巾只克女飞蛮。 - 藤甲不畏箭飞朴,朴蛮黄女倒霉蛋。 - -长克短、短克远,远克长,具体如下,最倒霉的三大兵种是蛮族、黄巾、女兵: - - | 朴刀 长枪 大刀 弓箭 链锤 飞刀 武斗 蛮族 铁锤 藤甲 黄巾 弩兵 女兵 | 总和 - ----|--------------------------------------------------------|---- - 朴刀 | 0 -42 -58 44 -47 47 21 14 -51 -31 22 -8 10 | -79 - 长枪 | 42 0 -12 -58 -23 -53 60 49 27 66 44 -52 39 | 129 - 大刀 | 58 12 0 -66 22 -64 63 53 -14 68 68 -18 -43 | 139 - 弓箭 | -44 58 66 0 54 8 -56 -36 47 -54 28 32 -29 | 74 - 链锤 | 47 23 -22 -54 0 -48 -26 36 13 65 37 -45 32 | 58 - 飞刀 | -47 53 64 -8 48 0 -55 -39 39 -67 -24 56 -32 | -12 - 武斗 | -21 -60 -63 56 26 55 0 8 42 16 33 -18 6 | 80 - 蛮族 | -14 -49 -53 36 -36 39 -8 0 -41 17 -62 -10 30 | -151 - 铁锤 | 51 -27 14 -47 -13 -39 -42 41 0 64 41 -28 36 | 51 - 藤甲 | 31 -66 -68 54 -65 67 -16 -17 -64 0 21 8 26 | -89 - 黄巾 | -22 -44 -68 -28 -37 24 -33 62 -41 -21 0 -24 56 | -176 - 弩兵 | 8 52 18 -32 45 -56 18 10 28 -8 24 0 12 | 119 - 女兵 | -10 -39 43 29 -32 32 -6 -30 -36 -26 -56 -12 0 | -143 - - -阵型相克表: - -- 以方形、园形、鱼丽形为代表的平头阵,强度依次减弱,并且鱼丽有倒克方形之势; -- 以钩形、雁形、为代表的凹头阵,强度依次减弱; -- 以冲锋形、锥形、箭矢形为代表的尖头阵,强度依次减弱; - -玄襄形像十字星,既有平头属性,又有凹头属性,克制尖头阵,冲锋形,还有箭矢形,同时又被圆形、方形克制。 - -《左传·桓公五年》 晋 杜预:“《司马法》:‘车战二十五乘为偏。’以车居前,以伍次之,承偏之隙而弥缝阙漏也。五人为伍。此盖鱼丽陈法。” - -平克凹、凹克尖、尖克平。负值表示在表格竖向的阵法被横向所示的阵法克制,负数绝对值越大克制越强: - - 方形 圆形 锥形 雁形 玄襄 鱼丽 钩形 冲锋 箭矢 - ----|-------------------------------------- - 方形 | 0 - 圆形 | -7 0 - 锥形 | 18 12 0 - 雁形 | -19 -22 19 0 - 玄襄 | -13 -21 11 5 0 - 鱼丽 | 6 -6 -25 11 18 0 - 钩形 | -23 -12 25 -3 6 -23 0 - 冲锋 | 23 24 7 -15 -24 10 -14 0 - 箭矢 | 15 18 -6 -20 -19 17 -15 12 0 - ----|-------------------------------------- - 总和 | 0 0 1 0 -1 0 1 1 -2 - - -## 🟡 GDNative/GDExtension 扩展开发 -- [Godot Engine Download](https://downloads.tuxfamily.org/godotengine/) -- [Godot API Headers](https://github.com/godotengine/godot-headers) -- [godot-cpp](https://github.com/godotengine/godot-cpp) -- [GDNative demos](https://github.com/godotengine/gdnative-demos) -- [GDNative C++](https://docs.godotengine.org/en/latest/tutorials/scripting/gdnative/index.html) -- [Custom modules in C++](https://docs.godotengine.org/en/stable/development/cpp/custom_modules_in_cpp.html) -- [Godot Compiling](https://docs.godotengine.org/en/stable/development/compiling/index.html) -- [Godot Engine – Multi-platform 2D and 3D game engine](https://github.com/godotengine/godot/releases) -- [Godot Rust bindings - GDNative and GDExtension APIs](https://godot-rust.github.io/) -- [Platform Support](https://doc.rust-lang.org/nightly/rustc/platform-support.html) -- [The godot-rust Book](https://godot-rust.github.io/book/gdextension/) -- [Introducing GDNative's successor, GDExtension](https://godotengine.org/article/introducing-gd-extensions) - -避免重新编译 Godot 源代码进行功能扩展的方法有 GDNative 和 GDExtension,它们分别在 Godot 3.x 和 4.x 中使用。 - -通常,在产品需要快速迭代前期开发阶段,GDScript 主要的开发语言工具,只有在需要处理性能瓶颈问题时, -或者有潜在性能瓶颈的功能需要处理,才会考虑使用 C/C++ 或者 Rust 等语言进行静态编译,以获得高性能 -模块,这当然是以软件结构复杂性为代价的。 - -Godot 本身是 C++ 开发的,使用 C++ 可以实现各种模块以扩展 Godot 功能。通过现有的 GDNative C++ Binding -模块,可以使用 C++ 来开发 Godot 应用。源代码开发需要使用 Python,编译工具使用 SCons,支持 C++14 规范, -可以使用 `clang`, `gcc` 等兼容的编译器。 - -GDNative 是 Godot 用于和原生共享库交互的扩展模块,和编写 Godot C++ 模块不同,因为 Godot 已经包含了 -GDNative 扩展模块,所以基于 GDNative 使用 C++ 开发时不需要重新编译 Godot 源代码。而为 Godot 开发 -C++ 扩展模块则不同,需要重新编译 Godot 源代码。通过 GDNative 可以很方便地开发插件,而不必重新编译引擎。 - -需要注意的是,GDNative 要求严格的版本兼容,例如 Godot 3.4.x GDNative 就不兼容 3.3.x 或 3.5.x 版本。 -所以在项目创建时,需要使用正确的 Godot API Headers 版本,GDNative demos 演示项目最好也选择兼容版本。 - -使用 git 克隆仓库时可以指定分支版本,注意 godot-cpp 依赖 godot-headers 仓库,并作为子模块,在初始化 -项目时,需要递归获取子模块: - -```sh -mkdir gdnative_cpp_example -cd gdnative_cpp_example -git init -git submodule add -b 3.x https://github.com/godotengine/godot-cpp -cd godot-cpp -git submodule update --init --recursive -# just download the repositories or clone them into your project folder, -# git clone --recursive -b 3.x https://github.com/godotengine/godot-cpp -``` - -下载 GDNative C++ Bindings 后,需要为 Python 环境安装 SCons 模块,然后再编译绑定模块,再 -编译基于 GDNative 的共享库供 Godot 工程使用: - -```sh -pip install SCons -# scons platform=PLATFORM -# Building the C++ bindings -cd godot-cpp -scons platform=windows generate_bindings=yes -j4 -# Building the GDNative library project -cd .. -scons platform=windows target=release -j4 -``` - -SCons 支持多平台编译,PLATFORM 可以指定 `windows`, `linux`, `osx`,编译 godot-cpp 生成文件 -保存在 bin 和 gen 目录下,共享库可以供 GDNative demos 示范项目使用,其配置脚本 SConstruct 文件 -已经设置好相关目录路径。先编译 godot-cpp,再编译项目使用的共享,库文件生成后保存于 `project/gdnative`。 - -```py -#!python -import os - -opts = Variables([], ARGUMENTS) - -# Define the relative path to the Godot headers. -godot_headers_path = "godot-cpp/godot-headers" -godot_bindings_path = "godot-cpp" - -# Gets the standard flags CC, CCX, etc. -env = DefaultEnvironment() - -# Define our options. Use future-proofed names for platforms. -platform_array = ["", "windows", "linuxbsd", "macos", "x11", "linux", "osx"] -opts.Add(EnumVariable("target", "Compilation target", "debug", ["d", "debug", "r", "release"])) -opts.Add(EnumVariable("platform", "Compilation platform", "", platform_array)) -opts.Add(EnumVariable("p", "Alias for 'platform'", "", platform_array)) -opts.Add(BoolVariable("use_llvm", "Use the LLVM / Clang compiler", "no")) -opts.Add(PathVariable("target_path", "The path where the lib is installed.", "project/gdnative/")) -opts.Add(PathVariable("target_name", "The library name.", "libdodgethecreeps", PathVariable.PathAccept)) - - -# ellipsis ... - -env.Append( - CPPPATH=[ - godot_headers_path, - godot_bindings_path + "/include", - godot_bindings_path + "/include/gen/", - godot_bindings_path + "/include/core/", - ] -) - -env.Append( - LIBS=[ - env.File(os.path.join("godot-cpp/bin", "libgodot-cpp.%s.%s.64%s" % (platform, env["target"], env["LIBSUFFIX"]))) - ] -) - -env.Append(LIBPATH=[godot_bindings_path + "/bin/"]) -# ellipsis ... -``` - -ClassDB 是扩展类型数据库管理类型,提供了各种绑定方法,用于向 GDScript 提供方法绑定。以下代码片段 -摘自 Godot-CPP 中的示范工程: - -```cpp,ignore - // Methods. - ClassDB::bind_method(D_METHOD("simple_func"), &Example::simple_func); - ClassDB::bind_method(D_METHOD("simple_const_func"), &Example::simple_const_func); - ClassDB::bind_method(D_METHOD("return_something"), &Example::return_something); - ClassDB::bind_method(D_METHOD("return_something_const"), &Example::return_something_const); - ClassDB::bind_method(D_METHOD("return_empty_ref"), &Example::return_empty_ref); - ClassDB::bind_method(D_METHOD("return_extended_ref"), &Example::return_extended_ref); - ClassDB::bind_method(D_METHOD("extended_ref_checks", "ref"), &Example::extended_ref_checks); - - ClassDB::bind_method(D_METHOD("test_array"), &Example::test_array); - ClassDB::bind_method(D_METHOD("test_tarray_arg", "array"), &Example::test_tarray_arg); - ClassDB::bind_method(D_METHOD("test_tarray"), &Example::test_tarray); - ClassDB::bind_method(D_METHOD("test_dictionary"), &Example::test_dictionary); - ClassDB::bind_method(D_METHOD("test_node_argument"), &Example::test_node_argument); - - ClassDB::bind_method(D_METHOD("def_args", "a", "b"), &Example::def_args, DEFVAL(100), DEFVAL(200)); - - ClassDB::bind_static_method("Example", D_METHOD("test_static", "a", "b"), &Example::test_static); - ClassDB::bind_static_method("Example", D_METHOD("test_static2"), &Example::test_static2); - - { - MethodInfo mi; - mi.arguments.push_back(PropertyInfo(Variant::STRING, "some_argument")); - mi.name = "varargs_func"; - ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "varargs_func", &Example::varargs_func, mi); - } - - { - MethodInfo mi; - mi.arguments.push_back(PropertyInfo(Variant::STRING, "some_argument")); - mi.name = "varargs_func_nv"; - ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "varargs_func_nv", &Example::varargs_func_nv, mi); - } - - { - MethodInfo mi; - mi.arguments.push_back(PropertyInfo(Variant::STRING, "some_argument")); - mi.name = "varargs_func_void"; - ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "varargs_func_void", &Example::varargs_func_void, mi); - } - - // Properties. - ADD_GROUP("Test group", "group_"); - ADD_SUBGROUP("Test subgroup", "group_subgroup_"); - - ClassDB::bind_method(D_METHOD("get_custom_position"), &Example::get_custom_position); - ClassDB::bind_method(D_METHOD("get_v4"), &Example::get_v4); - ClassDB::bind_method(D_METHOD("set_custom_position", "position"), &Example::set_custom_position); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "group_subgroup_custom_position"), "set_custom_position", "get_custom_position"); - - // Signals. - ADD_SIGNAL(MethodInfo("custom_signal", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::INT, "value"))); - ClassDB::bind_method(D_METHOD("emit_custom_signal", "name", "value"), &Example::emit_custom_signal); - - // Constants. - BIND_ENUM_CONSTANT(FIRST); - BIND_ENUM_CONSTANT(ANSWER_TO_EVERYTHING); - - BIND_CONSTANT(CONSTANT_WITHOUT_ENUM); -``` - -Godot 4.x 引入 GDExtension 作为 GDNative 的替代模块,或者说是 GDNative 应用层的重构版本, -扩展的目标还为了减轻给 Godot 引擎开发插件的难度。GDExtension 是 C API,提供方法注册动态库中的类。 -注册后的类,就可以供 Godot 引擎使用,因为是平台编译的共享库,所以能更好地集成到引擎内。配合现有的 -godot-cpp 库,可以非常方便地使用 C++ 开发静态编译的模块。 - -与 C++ 扩展中直接使用 Godot 引擎源代码的头文件不同,Rust 绑定需要使用它强大的宏编程能力,以生成 -Godot 引擎的 API 绑定代码,Godot 命令行提供 dump-extension-api 选项导出当前版本的 GDNative -类型信息到 JSON 文件,Godot Rust 绑定模块会分析 JSON 文件并生成相应的绑定代码。 - -使用 Rust 编写好的扩展时,先为 Godot 工程准备好 GDExtension 配置文件 example.gdextension: - -```sh -[configuration] - -entry_symbol = "example_library_init" - -[libraries] - -macos.debug = "res://bin/libgdexample.macos.template_debug.framework" -macos.release = "res://bin/libgdexample.macos.template_release.framework" -windows.debug.x86_64 = "res://bin/libgdexample.windows.template_debug.x86_64.dll" -windows.release.x86_64 = "res://bin/libgdexample.windows.template_release.x86_64.dll" -... -``` - -然后在 godot 工程文件中添加 GDExtension 配置: - -```sh -[native_extensions] - -paths=["res://example.gdextension"] -``` - -配置文件中的入口需要对应 C++ 代码中的 GDE_EXPORT 导出函数: - -```cpp -extern "C" { -// Initialization. -GDExtensionBool GDE_EXPORT example_library_init(const GDExtensionInterface *p_interface, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { - godot::GDExtensionBinding::InitObject init_obj(p_interface, p_library, r_initialization); - - init_obj.register_initializer(initialize_example_module); - init_obj.register_terminator(uninitialize_example_module); - init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); - - return init_obj.init(); -} -} -``` - - -使用 Rust 语言编写扩展,目前 GDExtension 还不稳定,编译后的扩展库可能不可以正常使用: - - git clone --depth=1 git@github.com:godot-rust/book - git clone --depth=1 git@github.com:godot-rust/gdnative - git clone --depth=1 git@github.com:godot-rust/gdextension - -编程思想 Rust vs. C++:前者只有能被证明是正确代码的才被允许,后者则是允许任何不能证明是错误的代码。 - -Rust 编译脚本中,需要根据具体情况添加 C++ 头文件的目录,因为 godot-rust 绑定依赖 godot-headers, -这些 C/C++ 头文件需要通过 Rust FFI 接口绑定,Rust 中常用的 API 绑定工具有以下这些: - -- `bindgen` automatically generates Rust FFI bindings to C (and some C++) libraries. -- `cbindgen` creates C/C++11 headers for Rust libraries which expose a public C API. -- `cpp` rust-cpp - Embed C++ code directly in Rust. - -其中,gdnative-sys 这个模块会包装 godot-headers,它会依赖一部分平台相关的头文件,可以直接修改 -buider.rs 编译脚本,添加 clang_arg 指定编译器参数 -I 来指定头文件路径,但不推荐直接修改。 - -Godot 4.x 中的 GDExtension 代码结构有改变,并且目前还未发布稳定版本。并且 godot-ffi 模块会调用 -godot4 可执行程序用于生成 API JSON 信息文件,也可以配置 `GODOT4_BIN` 环境变量指向可执行程序: - -```sh - # powershell - [System.Environment]::SetEnvironmentVariable("GODOT4_BIN", "C:\games\Godot_v4.0-beta10_win64.exe") -``` - -交叉编译涉及问题很多,通常根据当前平台的架构来编译是容易通过的,可以使用 rustup 查询当前的平台构架 -及工具链。即使在交叉编译时,bindgen 也需要访问平台标头以了解平台的类型定义么。gdnative syscrate -尝试根据平台检测头文件路径,也许可以通过 C_INCLUDE_PATH 环境变量提供自定义路径。 - -```sh -> rustup toolchain install stable -> rustup toolchain install stable-x86_64-pc-windows-gnu -> rustup toolchain list -stable-x86_64-pc-windows-msvc (default) -stable-x86_64-pc-windows-gnu -> rustc --print target-list -... -x86_64-pc-windows-gnu -x86_64-pc-windows-gnullvm -x86_64-pc-windows-msvc -... -> cargo build --target=x86_64-pc-windows-msvc -``` - -```rust -let mut builder = bindgen::Builder::default() - .header("godot_headers/gdnative_api_struct.gen.h") - .allowlist_type("godot.*") - .allowlist_function("godot.*") - .allowlist_var("godot.*") - .allowlist_type("GDNATIVE.*") - .derive_default(true) - .ignore_functions() - .size_t_is_usize(true) - .ctypes_prefix("libc") - // .clang_arg("-IC:/mingw/include") - .clang_arg("-IC:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.18362.0\\ucrt") - .clang_arg("-IC:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.26.28801\\include") - .clang_arg(format!("-I{}/godot_headers", manifest_dir)); -``` - -错误的交叉编译配置,可以会导致类型不匹配等问题: - - error[E0308]: mismatched types - types differ in mutability - -Clang 自身没有配备自己完整的工具链,它通常使用本地平台使用的标准的 C/C++ 库和头文件。Windows 平台上 -,Visual C++ 和 Mingw 是两个比较常用的工具链,应该指定合适的 target 参数: - - clang --target=i686-pc-vs2013 - clang --target=i686-pc-mingw32 - clang --target=x86_64-pc-mingw32 - -Rust 中使用 `cpp` crate 定义的 `cpp!` 宏来嵌入 C++ 代码,它通过获取所有的内联 C++ 代码并将 -其写入一个单独的 cpp 文件来实现这一点,该文件将被编译为 Rust crate 的最终目标代码。 - -当将 Rust 库绑定到 C/C++ 时,核心逻辑层和 FFI 层之间应该存在明显的分离。在做好的情况下,FFI 代码 -应该在单独的 crate,因此设计 Rust API 不会受到 FFI 的太多影响,并且选择可变性修饰符变得更加容易。 - -Rust 编译生成的库文件类型有以下这些: - -- **lib** — Generates a library kind preferred by the compiler, currently defaults to rlib. -- **rlib** — A Rust static library. -- **staticlib** — A native static library. -- **dylib** — A Rust dynamic library. -- **cdylib** — A native dynamic library. -- **bin** — A runnable executable program. -- **proc-macro** — Generates a format suitable for a procedural macro library that may be loaded by the compiler. - -GDExtension 扩展入口函数原型定义在 gdextension\godot-codegen\input\gdnative_interface.h - -```cpp -/* Define a C function prototype that implements the function below and expose it to dlopen() (or similar). - * This is the entry point of the GDExtension library and will be called on initialization. - * It can be used to set up different init levels, which are called during various stages of initialization/shutdown. - * The function name must be a unique one specified in the .gdextension config file. - */ -typedef GDNativeBool (*GDNativeInitializationFunction)(const GDNativeInterface *p_interface, const GDNativeExtensionClassLibraryPtr p_library, GDNativeInitialization *r_initialization); -``` - -使用 #[gdextension] 过程宏定义入口函数,默认入口函数名为 `gdextension_rust_init`: - -```rust -/// Defines the global entry point for the GDExtension library. -/// -/// Typical usage: -/// ``` -/// use godot::init::{gdextension, ExtensionLibrary}; -/// -/// // This is just a type tag without any functionality -/// struct MyExtension; -/// -/// #[gdextension] -/// unsafe impl ExtensionLibrary for MyExtension {} -/// ``` -/// -/// # Safety -/// By using godot-rust, you opt-in to the library's safety requirements (to be described in detail). -/// The library cannot enforce any guarantees outside Rust code, which means users need to adhere to certain rules for a safe usage. -#[proc_macro_attribute] -pub fn gdextension(meta: TokenStream, input: TokenStream) -> TokenStream { - translate_meta(meta, input, gdextension::transform) -} -``` - -参考 GDExtension 扩展配置文件内容如下,参考文件名 some.gdextension: - -```sh - [configuration] - entry_symbol = "gdextension_rust_init" - - [libraries] - linux.64 = "res://../../../target/debug/libsome.so" - macos.64 = "res://../../../target/debug/libsome.dylib" - windows.64 = "res://../../../target/debug/some.dll" -``` - - - -## 🟡 Godot with CSharp -- http://www.mono-project.com/docs/about-mono/compatibility -- https://docs.godotengine.org/en/3.5/tutorials/scripting/c_sharp/c_sharp_features.html -- https://docs.godotengine.org/en/3.5/tutorials/scripting/c_sharp/c_sharp_differences.html -- https://docs.godotengine.org/en/stable/tutorials/scripting/cross_language_scripting.html -- https://docs.godotengine.org/en/stable/tutorials/scripting/creating_script_templates.html -- https://docs.godotengine.org/en/stable/development/compiling/compiling_with_mono.html -- https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_with_mono.html -- https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/index.html -- https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/c_sharp_basics.html -- https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/c_sharp_exports.html -- https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/c_sharp_differences.html -- https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/c_sharp_signals.html -- https://docs.godotengine.org/en/latest/contributing/development/core_and_modules/object_class.html#doc-binding-properties-using-set-get-property-list -- [Bunnymark](https://github.com/cart/godot3-bunnymark) -- [Godot.NET.Sdk](https://www.nuget.org/packages/Godot.NET.Sdk/#versions-body-tab) -- [Mono SDK](https://www.mono-project.com/download/stable/#download-lin) -- [.NET SDK](https://dotnet.microsoft.com/download) -- [Visual Studio Code](https://code.visualstudio.com/) -- [Godot 3.5.1 Mono](https://downloads.tuxfamily.org/godotengine/3.5.1/mono/) -- [CustomType for Godot 3.x](https://github.com/Jeangowhy/Godot-Tour/tree/main/mono-3x/addons/CustomType) -- [CustomType for Godot 4.x](https://github.com/Jeangowhy/Godot-Tour/tree/4.x/mono-4x/addons/CustomType) -- godot-docs\tutorials\scripting\c_sharp\c_sharp_basics.rst - -Godot 有两套语言支持构建: - -- 默认的支持 GDScript,GDExtension -- 另一套则集成了 .NET 6 支持 C#, GDScript, GDExtension。 - -要使用 C# 语言进行 Godot 项目开发,系统上需要安装 .NET SDK 6.0 或 7.0,.NET 7.0 的支持还不完善。 -相比 GDScript 脚本,C# 是一个编译型的高级语言,Godot 通过开源的 Mono 6.x 框架支持 C# 8.0 语言版本。 -与作用快速原型开发使用 GDScript 脚本不同,每次执行之前都需要进行编译,以生成最新的 C# 程序集。但是, -作为预编译语言,它的运行效率虽然不能和 C++ 看齐,但比 GDSCript 有非常大的效率提升,简单情况有 4x 提升。 -并且,在使用的便利程序上,要比 C++ 好,所以在不是极限性能需要情况下,C# 是值得一试的方案。 - -怎么选开发语言,就是权衡开发效率与程序运行性能: - -- 选择 GDScript 可以快速地做原型迭代; -- 选择 C# 一方面提升了性能,另外它比 C++/Rust 更容易上手,同时编译速度也不太慢; -- 最后,极致性能要求,那么就选择 C++/Rust 折腾去吧! - -参考 Bunnymark V2 测试数据,数值越大越好,测试 Sprite 绘制并使用 GetChildren() 迭代: - -| Language | Bunnies Rendered | -|----------------------|------------------| -| ECMAScript/Javascript| 4660 | -| GDScript (Release) | 18560 | -| C#/Mono | 27555 | -| GDNative (D) | 28020 | -| GDNative (Nim) | 29920 | -| GDNative (C++) | 37480 | - -测试平台信息: - - ### Hardware: - - * CPU: Intel i7 7700k 4.2GHz - * GPU: Nvidia GeForce GTX 1070 - * RAM: 16GB DDR4 - - ### Build Info: - - * OS: Arch Linux - * Official Godot 3.1 release - -Godot 3.2.3 开始,不需安装 Mono SDK,除非需要从源代码构建 Godot,但是 .NET SDK 还是要安装。 -注意,使用的 Godot 要与 SDK 的版本比特位一致,建议使用 64-bit 的版本。由于 Godot 只提供了 C# -的最小支持,可以考虑使用外部编辑器,如 Visual Studio Code,以提供更完善的自动完成、调试等功能。 -Godot 目前支持以下作用外部编辑器,可以通过 Editor → Editor Settings → Mono → Editor 修改: - -- Visual Studio 2019 -- Visual Studio Code -- MonoDevelop -- Visual Studio for Mac -- JetBrains Rider - -以 VSCode 配置为例,最新的 Godot 4 不需要配置 Builds: - -- Set **Mono** -> **Editor** -> **External Editor** to **Visual Studio Code**. -- Set **Mono** -> **Builds** -> **Build Tool** to **dotnet CLI**. - -并且在 Visual Studio Code 中安装以下扩展: - -- [C#](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp) -- [Mono Debug](https://marketplace.visualstudio.com/items?itemName=ms-vscode.mono-debug) -- [C# Tools for Godot](https://marketplace.visualstudio.com/items?itemName=neikeq.godot-csharp-vscode) - -C# Tools for Godot 扩展支持 Godot 3.2.2 以上版本,可以提供以下功能: - -- Debugging. -- Launch a game directly in the Godot editor from VSCode. -- Additional code completion for Node paths, Input actions, Resource paths, Scene paths and Signal names. - -如果使用 Linux 操作系统,需要安装 Mono SDK 以支持 Godot 的 C# 工具插件。 - -配置 VSCdoe 以实现 Godot 工程的运行: - -- 执行菜单 Run → **Add Configuration...** → **C# Godot** -- 编辑配置文件 ``tasks.json`` 和 ``launch.json``,使用 executable 和 Command 指向正确的 Godot 可执行文件。 - -完成以上配置后,就可以直接在 VScode 中运行 Godot 项目: - -- 使用命令调板,Ctrl-Shift-P 打开面板并输入 C# Godot 查询相关命令; -- 在状态栏中,点击 Godot Project 字样处选择工程目录; -- 在状态栏中,点击 Play in Editor (mono) 字样处运 Godot 工程; -- 直接打开 Run and Debug 侧栏面板选择 ``launch.json`` 配置的调试的方式: - - Play in Editor 直接通过编辑器中运行; - - Launch 执行 `Godot.exe --path ${workspaceRoot}` - - Launch (Select Scene) 选择场景执行 `Godot.exe --path ${workspaceRoot} ${command:SelectLaunchScene}` - - Attach 附着到本地调试进程; - -如果工程不能正常运行,而 VScode 又看不到错误信息,就可以到 Godot 的 MSBuild 面板查询编译日志信息, -例如,当前系统曾经删除过 Xamarin 的 NuGet 模块目录,导致 Godot 编译时出现问题,只需要恢复目录即可: - - SDK 解析程序失败: - "尝试解析 SDK "Godot.NET.Sdk/4.0.0-beta.16" 时,SDK 解析程序 "NuGetSdkResolver" 失败。 - 异常: "NuGet.Packaging.Core.PackagingException: 无法找到回退包文件夹“C:\Microsoft\Xamarin\NuGet\”。 - -NuGet 是依赖管理工具,包含为开放源代码 .NET 库而创建的 NuGet.org 包管理平台,上面公开发布了 -所有包的推荐元数据。NuGet 包 `*.nupkg` 是一个 zip 文件,其中包含 .NET 程序集和关联的元数据。 - -NuGet 是 .NET 生态系统的包管理器,并且是开发人员用来发现并获取 .NET 开放源代码库的主要方法。 -NuGet.org(由托管 NuGet 包的 Microsoft 提供的免费服务)是公共 NuGet 包的主要主机,但可以 -发布到自定义 NuGet 服务,如 MyGet 和 Azure Artifacts。 - -创建 NuGet 包有两种主要方式。 较新的推荐方式是从 SDK 样式项目,其内容以 `` -开头的项目文件创建包。 程序集和目标会自动添加到包,剩余元数据会添加到 MSBuild 文件,如包名称和版本号。 -使用 `dotnet pack` 命令编译会输出 `*.nupkg` 文件,而不是程序集。 - -本地下载安装的缓冲目录可以使用以下命令查询及清理: - - nuget locals all -list - nuget locals all -clear - -配置文件 ``tasks.json`` 默认只提供了一个构建任务,可以通过菜单 Terminal → Run Build Task... 执行: - -```json -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "Godot_v4.0-beta16_mono_win64.exe", //"", - "type": "process", - "args": [ - "--build-solutions", - "--path", - "${workspaceRoot}", - "--no-window", - "-q" - ], - "problemMatcher": "$msCompile" - } - ] -} -``` - -如果添加运行配置时,没有提供 **C# Godot** 配置,请检查以上三个扩展是否完成安装,并且处于启用状态。 -配置好开发环境,就可以在 Godot 给节点附加脚本时,在 Attach Script 对话框中选择 C# 语言。 - -Node 节点类扩展代码示例: - -```C#,ignore - using Godot; - using System; - - public class MyNode2D : Node - { - // Member variables here, example: - private int a = 2; - private string b = "textvar"; - - public override void _Ready() - { - // Called every time the node is added to the scene. - // Initialization here. - GD.Print("Hello from C# to Godot :)", Time.GetTicksMsec()); - } - - public override void _Process(float delta) - { - // Called every frame. Delta is time since the last frame. - // Update game logic here. - } - } -``` - -使用 C# 与 GDScript 的一些差异: - -- C# 使用 ``PascalCase`` 代码风格,GDSCript/C++ 使用 ``snake_case`` 风格,`AddChild()` vs. `add_child()`。 -- C# 类名要求代码文件名一致,否由会提示 *"Cannot find class XXX for script res://XXX.cs"* -- C# 中使用 Godot 命令空间下的 GD 管理 @GDscript 和 @GlobbalScope 全局函数符号。 -- C# 导出符号生效之前,需要重新编译程序集,通过 Godot 界面右上角的 Build 按钮构建工程。 -- C# 语句使用分号作为结束符号,而 GDScript 不需要。 -- C# 中以 `Godot.Object` 作为所有类型的基类,新版本更名为 `Godot.GodotObject`。 -- C# 使用 `this` 引用当前类实例,GDScript 使用 `self` 引用当前类实例。 -- C# 使用 `base` 引用父类实例或构造器,GDScript 使用 `super(args)` 引用父类构造。 - -在 C# 中,也不能像 GDScript 那样,直接拖动节点到脚本中创建引用,也不能使用 onready,而需要在 -Ready 这类事件中,使用 FindNode 或者 GetNode 获取节点引用: - -```C# - //Button n = FindNode("Button") as Button; // FindNode -> FindChild (Godot 4) - Button n = (Button) GetNode("Button"); - if (n != null) { - // n.Connect(nameof(Button.Pressed), this, "handle_button_click"); - n.Connect("pressed", this, "handle_button_click"); - GD.Print($"button: {n.Text}"); - } -``` - -使用 C# 进行 Godot 项目开发,还需要解决以下这些基本问题: - -- 不同语言之间的相互调用问题; -- Godot 信号系统的使用方式的差异; -- C# 与 GDScript API 之间的差异; -- Godot 不同版本之间的差异处理等等; - - -Godot 考虑到了不同语言之间相互调用,C# 调用 GDScript API 或者属性读写使用 GodotObject 提供的方法: - -```C#,ignore - // - // 摘要: - // Calls the method on the object and returns the result. This method supports a - // variable number of arguments, so parameters are passed as a comma separated list. - // Example: - // call("set", "position", Vector2(42.0, 0.0)) - [GodotMethodAttribute("call")] - public object Call(string method, params object[] args); - // - // 摘要: - // Calls the method on the object during idle time. This method supports a variable - // number of arguments, so parameters are passed as a comma separated list. Example: - // call_deferred("set", "position", Vector2(42.0, 0.0)) - [GodotMethodAttribute("call_deferred")] - public void CallDeferred(string method, params object[] args); - // - // 摘要: - // Calls the method on the object and returns the result. Contrarily to Godot.Object.Call(System.String,System.Object[]), - // this method does not support a variable number of arguments but expects all parameters - // to be via a single Godot.Collections.Array. - // callv("set", [ "position", Vector2(42.0, 0.0) ]) - [GodotMethodAttribute("callv")] - public object Callv(string method, Collections.Array argArray); - - // - // 摘要: - // Assigns a new value to the given property. If the property does not exist or - // the given value's type doesn't match, nothing will happen. - [GodotMethodAttribute("set")] - public void Set(string property, object value); - // - // 摘要: - // Returns the Variant value of the given property. If the property doesn't exist, - // this will return null. - [GodotMethodAttribute("get")] - public object Get(string property); -``` - -而在 GDscript 访问 C# API 则是直接调用,就像使用其它 GDScript 对象一样,实例化操作如下所示: - -```py - # Instantiating C# nodes from GDScript - var my_csharp_script = load("res://path_to_cs_file.cs") # CSharpScript - var my_csharp_node = my_csharp_script.new() - print(my_csharp_node.some_property) -``` - -```C# - // Instantiating GDScript nodes from C# - GDScript MyGDScript = (GDScript) GD.Load("res://path_to_gd_file.gd"); - Object myGDScriptNode = (Godot.Object) MyGDScript.New(); - myGDScriptNode.Call("some_method", new int[] { 1, 2, 3 }); // some_method(1, 2, 3) - myGDScriptNode.Call("some_method", (object)new int[] { 1, 2, 3 }); // some_method([1, 2, 3]) -``` - -注意,实例化得到的类型以 Godot 内置类型为准,而不是按 C# 或 GDScript 中声明的类型作为判断标准。 -比如,后面的 MyNode2D 在使用 GDScript `is` 关键字做类型判断时,需要使用内置类型 Node 作为参考。 -C# 调用 GDScript API 时注意,如果第一个参数是一个数组,那么就需要显式转换为 `object` 类型。否则, -数组元素就会被当作一个参数使用,并可能导致函数签名不匹配。 - - -编写 C# 类代码时注意,类名与 ``.cs`` 代码文件名一致,否则提示错误: - - Invalid call. Nonexistent function `new` in base. - -比如,MyCoolNode.cs 文件就应该定义 MyCoolNode 类型。并且需要继承自 ``Godot.Object`` 或其它 -子类。最后,C# 工程文件 ``.csproj``中要正确引用``.cs`` 文件,这样才会生效。 - - - -Godot 4.x Mono 信号机制在 C# 使用委托机制实现,并且可以使用更高效的 += 和 -= 运算符监听、或者 -取消监听。另外,Connect 方法也有更新,使用 Callable 对象包装回调函数及回调参数。另外,通过节点的 -嵌套类 SignalName 可以访问信号名称,它继承自 GodotObject.SignalName。清理节点时,Godot 会 -负责所有信号监听事件的清理: - -```C# - Timer myTimer = GetNode("Timer"); - myTimer.Timeout += () => GD.Print("Timeout!"); - // public Error Connect(StringName signal, Callable callable, uint flags = 0); - - await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame); -``` - -以下是 Callable 类参考文档中展示的用法,使用了嵌套类 MethodName 或者 nameof 获取方法名称。但是 -在默认参数绑定操作上有差别,GDScript 中可以直接调用 Callable 对象的 bind() 方法绑定默认参数。 -而目前在 C# 中给信号绑定默认参数则需要使用 lambdas 来构造出一个包含默认参数的 Callable 对象。 -如果方法没有参数,也没有返回值,可以包装成 Action,由 Callable.From() 再包装成可调用对象。 - -```C#,ignore - // Default parameter values are not supported. - public void PrintArgs(Variant arg1, Variant arg2, Variant arg3 = default) - { - GD.PrintS(arg1, arg2, arg3); - } - - public void Test() - { - Callable callback = Callable.From(() => GD.Print("Hello World")); - - // Invalid calls fail silently. - Callable callable = new Callable(this, MethodName.PrintArgs); - callable.Call("hello", "world"); // Default parameter values are not supported, should have 3 arguments. - callable.Call(Vector2.Up, 42, callable); // Prints "(0, -1) 42 Node(Node.cs)::PrintArgs". - callable.Call("invalid"); // Invalid call, should have 3 arguments. - } -``` - -Godot 4.x Mono 所有信号定义名称使用 EventHandler 结尾,定义好信号后,完成后,Godot 在幕后 -自动使用 C# `event` 关键字创建相应的事件。然后,可以像其他 Godot 信号一样使用自定义信号事件。 -注意,类型定义使用的 partial class,即表示类定义代码是分开文件存放的,还可以包含在其它代码文件。 -除了直接使用委托方式定义信号,还可以使用 AddUserSignal() 方法添加自定义信号。 - - -Godot 3.x Mono 自定义信号的使用有些差别,获取信号名称使用 nameof(MySignal) 语法,并且信号 -连接依然是使用节点的 Connect 方法进行。信号名称的获取这种操作一致性不够好,例如,获取按钮节点的 -信号就不能使用 nameof(Button.Pressed) 这样的表达,而是直接使用 "pressed" 字符串字面量。 - -总结起来,C# 中有三种获取或使用信号名称的方式,Godot 3.x 只支持前两种: - -- `EmitSignal("MySignal");` 直接使用字符串字面量 -- `EmitSignal(nameof(MySignal));` 使用 nameof 关键字 -- `EmitSignal(SignalName.MySignal);` 使用内嵌类 SignalName - - -```C#,ignore - // Represents the method that handles the Godot.EditorPlugin.SceneChanged event - // of a Godot.EditorPlugin class. - //public delegate void SceneChangedEventHandler(Node sceneRoot); - //public event SceneChangedEventHandler SceneChanged; - - public partial class MyNode2D : Node2D - { - // Declaring a signal in Godot 3.x with C# is done with the [Signal] attribute on a delegate. - [Signal] - delegate void MySignal(); - - [Signal] - delegate void MySignalWithArguments(string foo, int bar); - - public override void _Ready() - { - MyMethodEmittingSignals(); - } - - // Emitting signals is done with the EmitSignal method. - public void MyMethodEmittingSignals() - { - //AddUserSignal("MyOtherSignal"); - EmitSignal(nameof(MySignal)); - EmitSignal("MySignalWithArguments", "hello there", 28); - } - } - - public partial class AnotherNode : Node2D - { - MyNode instance; - - public override void _Ready() - { - instance = GetNode("MyNode") - } - // These signals can then be connected either in the editor or from code with Connect. - public void MyCallback() - { - GD.Print("My callback!"); - } - - public void MyCallbackWithArguments(string foo, int bar) - { - GD.Print("My callback with: ", foo, " and ", bar, "!"); - } - - public void SomeFunction() - { - instance.Connect("MySignal", this, "MyCallback"); - instance.Connect("MySignal", this, "MyCallbackWithArguments", new Godot.Collections.Array{"Mono", 123}); - instance.Connect(nameof(MySignalWithArguments), this, "MyCallbackWithArguments"); - } - } -``` - -使用自定义信号可能遇到的问题是:调用 EmitSignal() 时报错,表示信号不存在,而调用 AddUserSignal() -手动添加信号时,又表示不能重复添加已经定义的信号,这可能是定义信号的代码没有写到类体内部: - - E 0:00:07.120 emit_signal: Can't emit non-existing signal "MySignal". - Condition "!signal_is_valid && !script.is_null() && !Ref