diff --git a/01_about/01_introduction.md b/01_about/01_introduction.md index 5960e14..1ebf9f7 100644 --- a/01_about/01_introduction.md +++ b/01_about/01_introduction.md @@ -3,8 +3,8 @@ # Introduction ``` - func _ready(): - $Label.text = "Hello world!" +func _ready(): + $Label.text = "Hello world!" ``` Welcome to the official documentation of Pandemonium Engine, the free and open source diff --git a/02_tutorials/02_step_by_step/03_scripting_first_script.md b/02_tutorials/02_step_by_step/03_scripting_first_script.md index 61ba177..14dfe16 100644 --- a/02_tutorials/02_step_by_step/03_scripting_first_script.md +++ b/02_tutorials/02_step_by_step/03_scripting_first_script.md @@ -77,7 +77,7 @@ following line of code: gdscript GDScript ``` - extends Sprite +extends Sprite ``` Every GDScript file is implicitly a class. The `extends` keyword defines the @@ -114,8 +114,8 @@ Add the following code to your script: gdscript GDScript ``` - func _init(): - print("Hello, world!") +func _init(): + print("Hello, world!") ``` @@ -148,8 +148,8 @@ angular speed in radians per second. gdscript GDScript ``` - var speed = 400 - var angular_speed = PI +var speed = 400 +var angular_speed = PI ``` Member variables sit near the top of the script, after any "extends" lines, @@ -187,8 +187,8 @@ At the bottom of the script, define the function: gdscript GDScript ``` - func _process(delta): - rotation += angular_speed * delta +func _process(delta): + rotation += angular_speed * delta ``` The `func` keyword defines a new function. After it, we have to write the @@ -225,9 +225,9 @@ them. gdscript GDScript ``` - var velocity = Vector2.UP.rotated(rotation) * speed +var velocity = Vector2.UP.rotated(rotation) * speed - position += velocity * delta +position += velocity * delta ``` As we already saw, the `var` keyword defines a new variable. If you put it at @@ -264,16 +264,16 @@ Here is the complete `Sprite.gd` file for reference. gdscript GDScript ``` - extends Sprite +extends Sprite - var speed = 400 - var angular_speed = PI +var speed = 400 +var angular_speed = PI - func _process(delta): - rotation += angular_speed * delta +func _process(delta): + rotation += angular_speed * delta - var velocity = Vector2.UP.rotated(rotation) * speed + var velocity = Vector2.UP.rotated(rotation) * speed - position += velocity * delta + position += velocity * delta ``` diff --git a/02_tutorials/02_step_by_step/05_scripting_player_input.md b/02_tutorials/02_step_by_step/05_scripting_player_input.md index 94a8878..e542fa4 100644 --- a/02_tutorials/02_step_by_step/05_scripting_player_input.md +++ b/02_tutorials/02_step_by_step/05_scripting_player_input.md @@ -34,13 +34,13 @@ code below. gdscript GDScript ``` - var direction = 0 - if Input.is_action_pressed("ui_left"): - direction = -1 - if Input.is_action_pressed("ui_right"): - direction = 1 +var direction = 0 +if Input.is_action_pressed("ui_left"): + direction = -1 +if Input.is_action_pressed("ui_right"): + direction = 1 - rotation += angular_speed * direction * delta +rotation += angular_speed * direction * delta ``` Our `direction` local variable is a multiplier representing the direction in @@ -79,9 +79,9 @@ velocity. Replace the line starting with `var velocity` with the code below. gdscript GDScript ``` - var velocity = Vector2.ZERO - if Input.is_action_pressed("ui_up"): - velocity = Vector2.UP.rotated(rotation) * speed +var velocity = Vector2.ZERO +if Input.is_action_pressed("ui_up"): + velocity = Vector2.UP.rotated(rotation) * speed ``` We initialize the `velocity` with a value of `Vector2.ZERO`, another @@ -98,26 +98,26 @@ Here is the complete `Sprite.gd` file for reference. gdscript GDScript ``` - extends Sprite +extends Sprite - var speed = 400 - var angular_speed = PI +var speed = 400 +var angular_speed = PI - func _process(delta): - var direction = 0 - if Input.is_action_pressed("ui_left"): - direction = -1 - if Input.is_action_pressed("ui_right"): - direction = 1 +func _process(delta): + var direction = 0 + if Input.is_action_pressed("ui_left"): + direction = -1 + if Input.is_action_pressed("ui_right"): + direction = 1 - rotation += angular_speed * direction * delta + rotation += angular_speed * direction * delta - var velocity = Vector2.ZERO - if Input.is_action_pressed("ui_up"): - velocity = Vector2.UP.rotated(rotation) * speed + var velocity = Vector2.ZERO + if Input.is_action_pressed("ui_up"): + velocity = Vector2.UP.rotated(rotation) * speed - position += velocity * delta + position += velocity * delta ``` If you run the scene, you should now be able to rotate with the left and right diff --git a/02_tutorials/02_step_by_step/06_signals.md b/02_tutorials/02_step_by_step/06_signals.md index 753cb1b..abd5f9b 100644 --- a/02_tutorials/02_step_by_step/06_signals.md +++ b/02_tutorials/02_step_by_step/06_signals.md @@ -151,8 +151,8 @@ the `not` keyword to invert the value. gdscript GDScript ``` - func _on_Button_pressed(): - set_process(not is_processing()) +func _on_Button_pressed(): + set_process(not is_processing()) ``` This function will toggle processing and, in turn, the icon's motion on and off @@ -165,10 +165,10 @@ following code, which we saw two lessons ago: gdscript GDScript ``` - func _process(delta): - rotation += angular_speed * delta - var velocity = Vector2.UP.rotated(rotation) * speed - position += velocity * delta +func _process(delta): + rotation += angular_speed * delta + var velocity = Vector2.UP.rotated(rotation) * speed + position += velocity * delta ``` Your complete `Sprite.gd` code should look like the following. @@ -176,20 +176,20 @@ Your complete `Sprite.gd` code should look like the following. gdscript GDScript ``` - extends Sprite +extends Sprite - var speed = 400 - var angular_speed = PI +var speed = 400 +var angular_speed = PI - func _process(delta): - rotation += angular_speed * delta - var velocity = Vector2.UP.rotated(rotation) * speed - position += velocity * delta +func _process(delta): + rotation += angular_speed * delta + var velocity = Vector2.UP.rotated(rotation) * speed + position += velocity * delta - func _on_Button_pressed(): - set_process(not is_processing()) +func _on_Button_pressed(): + set_process(not is_processing()) ``` Run the scene now and click the button to see the sprite start and stop. @@ -242,8 +242,8 @@ in a variable. gdscript GDScript ``` - func _ready(): - var timer = get_node("Timer") +func _ready(): + var timer = get_node("Timer") ``` The function `get_node()` looks at the Sprite's children and gets nodes by @@ -257,9 +257,9 @@ We can now connect the Timer to the Sprite in the `ready()` function. gdscript GDScript ``` - func _ready(): - var timer = get_node("Timer") - timer.connect("timeout", self, "_on_Timer_timeout") +func _ready(): + var timer = get_node("Timer") + timer.connect("timeout", self, "_on_Timer_timeout") ``` The line reads like so: we connect the Timer's "timeout" signal to the node to @@ -270,8 +270,8 @@ at the bottom of our script and use it to toggle our sprite's visibility. gdscript GDScript ``` - func _on_Timer_timeout(): - visible = not visible +func _on_Timer_timeout(): + visible = not visible ``` The `visible` property is a boolean that controls the visibility of our node. @@ -290,29 +290,29 @@ Here is the complete `Sprite.gd` file for reference. gdscript GDScript ``` - extends Sprite +extends Sprite - var speed = 400 - var angular_speed = PI +var speed = 400 +var angular_speed = PI - func _ready(): - var timer = get_node("Timer") - timer.connect("timeout", self, "_on_Timer_timeout") +func _ready(): + var timer = get_node("Timer") + timer.connect("timeout", self, "_on_Timer_timeout") - func _process(delta): - rotation += angular_speed * delta - var velocity = Vector2.UP.rotated(rotation) * speed - position += velocity * delta +func _process(delta): + rotation += angular_speed * delta + var velocity = Vector2.UP.rotated(rotation) * speed + position += velocity * delta - func _on_Button_pressed(): - set_process(not is_processing()) +func _on_Button_pressed(): + set_process(not is_processing()) - func _on_Timer_timeout(): - visible = not visible +func _on_Timer_timeout(): + visible = not visible ``` Custom signals @@ -330,11 +330,11 @@ reaches 0. gdscript GDScript ``` - extends Node2D +extends Node2D - signal health_depleted +signal health_depleted - var health = 10 +var health = 10 ``` Note: @@ -351,10 +351,10 @@ To emit a signal in your scripts, call `emit_signal()`. gdscript GDScript ``` - func take_damage(amount): - health -= amount - if health <= 0: - emit_signal("health_depleted") +func take_damage(amount): + health -= amount + if health <= 0: + emit_signal("health_depleted") ``` A signal can optionally declare one or more arguments. Specify the argument @@ -363,9 +363,9 @@ names between parentheses: gdscript GDScript ``` - extends Node +extends Node - signal health_changed(old_value, new_value) +signal health_changed(old_value, new_value) ``` Note: @@ -382,10 +382,10 @@ To emit values along with the signal, add them as extra arguments to the gdscript GDScript ``` - func take_damage(amount): - var old_health = health - health -= amount - emit_signal("health_changed", old_health, health) +func take_damage(amount): + var old_health = health + health -= amount + emit_signal("health_changed", old_health, health) ``` Summary diff --git a/02_tutorials/03_first_2d_game/01_project_setup.md b/02_tutorials/03_first_2d_game/01_project_setup.md index c002630..307d134 100644 --- a/02_tutorials/03_first_2d_game/01_project_setup.md +++ b/02_tutorials/03_first_2d_game/01_project_setup.md @@ -11,10 +11,10 @@ Launch Pandemonium and create a new project. GDScript ``` - Download :download:`dodge_assets.zip ( files/dodge_assets.zip )`. - The archive contains the images and sounds you'll be using - to make the game. Extract the archive and move the `art/` - and `fonts/` directories to your project's directory. +Download :download:`dodge_assets.zip ( files/dodge_assets.zip )`. +The archive contains the images and sounds you'll be using +to make the game. Extract the archive and move the `art/` +and `fonts/` directories to your project's directory. ``` Your project folder should look like this. diff --git a/02_tutorials/03_first_2d_game/03_coding_the_player.md b/02_tutorials/03_first_2d_game/03_coding_the_player.md index ddf5f47..0d68e55 100644 --- a/02_tutorials/03_first_2d_game/03_coding_the_player.md +++ b/02_tutorials/03_first_2d_game/03_coding_the_player.md @@ -152,9 +152,9 @@ the `process` function (make sure it's not indented under the `else`): gdscript GDScript ``` - position += velocity * delta - position.x = clamp(position.x, 0, screen_size.x) - position.y = clamp(position.y, 0, screen_size.y) +position += velocity * delta +position.x = clamp(position.x, 0, screen_size.x) +position.y = clamp(position.y, 0, screen_size.y) ``` Tip: @@ -186,14 +186,14 @@ movement. Let's place this code at the end of the `process()` function: gdscript GDScript ``` - if velocity.x != 0: - $AnimatedSprite.animation = "walk" - $AnimatedSprite.flip_v = false - # See the note below about boolean assignment. - $AnimatedSprite.flip_h = velocity.x < 0 - elif velocity.y != 0: - $AnimatedSprite.animation = "up" - $AnimatedSprite.flip_v = velocity.y > 0 +if velocity.x != 0: + $AnimatedSprite.animation = "walk" + $AnimatedSprite.flip_v = false + # See the note below about boolean assignment. + $AnimatedSprite.flip_h = velocity.x < 0 +elif velocity.y != 0: + $AnimatedSprite.animation = "up" + $AnimatedSprite.flip_v = velocity.y > 0 ``` Note: @@ -202,10 +202,10 @@ Note: *assigning* a boolean value, we can do both at the same time. Consider this code versus the one-line boolean assignment above: ``` - if velocity.x < 0: - $AnimatedSprite.flip_h = true - else: - $AnimatedSprite.flip_h = false +if velocity.x < 0: + $AnimatedSprite.flip_h = true +else: + $AnimatedSprite.flip_h = false ``` Play the scene again and check that the animations are correct in each of the @@ -223,7 +223,7 @@ When you're sure the movement is working correctly, add this line to gdscript GDScript ``` - hide() +hide() ``` ## Preparing for collisions @@ -237,7 +237,7 @@ Add the following at the top of the script, after `extends Area2D`: gdscript GDScript ``` - signal hit +signal hit ``` This defines a custom signal called "hit" that we will have our player emit @@ -262,11 +262,11 @@ this code to the function: gdscript GDScript ``` - func _on_Player_body_entered(body): - hide() # Player disappears after being hit. - emit_signal("hit") - # Must be deferred as we can't change physics properties on a physics callback. - $CollisionShape2D.set_deferred("disabled", true) +func _on_Player_body_entered(body): + hide() # Player disappears after being hit. + emit_signal("hit") + # Must be deferred as we can't change physics properties on a physics callback. + $CollisionShape2D.set_deferred("disabled", true) ``` Each time an enemy hits the player, the signal is going to be emitted. We need @@ -285,10 +285,10 @@ starting a new game. gdscript GDScript ``` - func start(pos): - position = pos - show() - $CollisionShape2D.disabled = false +func start(pos): + position = pos + show() + $CollisionShape2D.disabled = false ``` With the player working, we'll work on the enemy in the next lesson. diff --git a/02_tutorials/03_first_2d_game/04_creating_the_enemy.md b/02_tutorials/03_first_2d_game/04_creating_the_enemy.md index 86da7fd..164dfa0 100644 --- a/02_tutorials/03_first_2d_game/04_creating_the_enemy.md +++ b/02_tutorials/03_first_2d_game/04_creating_the_enemy.md @@ -57,7 +57,7 @@ Add a script to the `Mob` like this: gdscript GDScript ``` - extends RigidBody2D +extends RigidBody2D ``` Now let's look at the rest of the script. In `ready()` we play the animation @@ -66,10 +66,10 @@ and randomly choose one of the three animation types: gdscript GDScript ``` - func _ready(): - $AnimatedSprite.playing = true - var mob_types = $AnimatedSprite.frames.get_animation_names() - $AnimatedSprite.animation = mob_types[randi() % mob_types.size()] +func _ready(): + $AnimatedSprite.playing = true + var mob_types = $AnimatedSprite.frames.get_animation_names() + $AnimatedSprite.animation = mob_types[randi() % mob_types.size()] ``` First, we get the list of animation names from the AnimatedSprite's `frames` @@ -92,8 +92,8 @@ add this code: gdscript GDScript ``` - func _on_VisibilityNotifier2D_screen_exited(): - queue_free() +func _on_VisibilityNotifier2D_screen_exited(): + queue_free() ``` This completes the `Mob` scene. diff --git a/02_tutorials/03_first_2d_game/05_the_main_game_scene.md b/02_tutorials/03_first_2d_game/05_the_main_game_scene.md index 4bd3ac5..ef42aae 100644 --- a/02_tutorials/03_first_2d_game/05_the_main_game_scene.md +++ b/02_tutorials/03_first_2d_game/05_the_main_game_scene.md @@ -76,10 +76,10 @@ Add a script to `Main`. At the top of the script, we use `export gdscript GDScript ``` - extends Node +extends Node - export(PackedScene) var mob_scene - var score +export(PackedScene) var mob_scene +var score ``` We also add a call to `randomize()` here so that the random number @@ -88,8 +88,8 @@ generator generates different random numbers each time the game is run: gdscript GDScript ``` - func _ready(): - randomize() +func _ready(): + randomize() ``` Click the `Main` node and you will see the `Mob Scene` property in the Inspector @@ -116,14 +116,14 @@ new game: gdscript GDScript ``` - func game_over(): - $ScoreTimer.stop() - $MobTimer.stop() +func game_over(): + $ScoreTimer.stop() + $MobTimer.stop() - func new_game(): - score = 0 - $Player.start($StartPosition.position) - $StartTimer.start() +func new_game(): + score = 0 + $Player.start($StartPosition.position) + $StartTimer.start() ``` Now connect the `timeout()` signal of each of the Timer nodes (`StartTimer`, @@ -133,12 +133,12 @@ the other two timers. `ScoreTimer` will increment the score by 1. gdscript GDScript ``` - func _on_ScoreTimer_timeout(): - score += 1 +func _on_ScoreTimer_timeout(): + score += 1 - func _on_StartTimer_timeout(): - $MobTimer.start() - $ScoreTimer.start() +func _on_StartTimer_timeout(): + $MobTimer.start() + $ScoreTimer.start() ``` In `on_MobTimer_timeout()`, we will create a mob instance, pick a random @@ -154,30 +154,30 @@ Note that a new instance must be added to the scene using `add_child()`. gdscript GDScript ``` - func _on_MobTimer_timeout(): - # Create a new instance of the Mob scene. - var mob = mob_scene.instance() +func _on_MobTimer_timeout(): + # Create a new instance of the Mob scene. + var mob = mob_scene.instance() - # Choose a random location on Path2D. - var mob_spawn_location = get_node("MobPath/MobSpawnLocation") - mob_spawn_location.offset = randi() + # Choose a random location on Path2D. + var mob_spawn_location = get_node("MobPath/MobSpawnLocation") + mob_spawn_location.offset = randi() - # Set the mob's direction perpendicular to the path direction. - var direction = mob_spawn_location.rotation + PI / 2 + # Set the mob's direction perpendicular to the path direction. + var direction = mob_spawn_location.rotation + PI / 2 - # Set the mob's position to a random location. - mob.position = mob_spawn_location.position + # Set the mob's position to a random location. + mob.position = mob_spawn_location.position - # Add some randomness to the direction. - direction += rand_range(-PI / 4, PI / 4) - mob.rotation = direction + # Add some randomness to the direction. + direction += rand_range(-PI / 4, PI / 4) + mob.rotation = direction - # Choose the velocity for the mob. - var velocity = Vector2(rand_range(150.0, 250.0), 0.0) - mob.linear_velocity = velocity.rotated(direction) + # Choose the velocity for the mob. + var velocity = Vector2(rand_range(150.0, 250.0), 0.0) + mob.linear_velocity = velocity.rotated(direction) - # Spawn the mob by adding it to the Main scene. - add_child(mob) + # Spawn the mob by adding it to the Main scene. + add_child(mob) ``` .. important:: Why `PI`? In functions requiring angles, Pandemonium uses *radians*, @@ -195,9 +195,9 @@ call to `ready()`: gdscript GDScript ``` - func _ready(): - randomize() - new_game() +func _ready(): + randomize() + new_game() ``` Let's also assign `Main` as our "Main Scene" - the one that runs automatically diff --git a/02_tutorials/03_first_2d_game/06_heads_up_display.md b/02_tutorials/03_first_2d_game/06_heads_up_display.md index 04ff43c..3d9a2b1 100644 --- a/02_tutorials/03_first_2d_game/06_heads_up_display.md +++ b/02_tutorials/03_first_2d_game/06_heads_up_display.md @@ -97,9 +97,9 @@ Now add this script to `HUD`: gdscript GDScript ``` - extends CanvasLayer +extends CanvasLayer - signal start_game +signal start_game ``` The `start_game` signal tells the `Main` node that the button @@ -108,10 +108,10 @@ has been pressed. gdscript GDScript ``` - func show_message(text): - $Message.text = text - $Message.show() - $MessageTimer.start() +func show_message(text): + $Message.text = text + $Message.show() + $MessageTimer.start() ``` This function is called when we want to display a message @@ -120,16 +120,16 @@ temporarily, such as "Get Ready". gdscript GDScript ``` - func show_game_over(): - show_message("Game Over") - # Wait until the MessageTimer has counted down. - yield($MessageTimer, "timeout") +func show_game_over(): + show_message("Game Over") + # Wait until the MessageTimer has counted down. + yield($MessageTimer, "timeout") - $Message.text = "Dodge the\nCreeps!" - $Message.show() - # Make a one-shot timer and wait for it to finish. - yield(get_tree().create_timer(1), "timeout") - $StartButton.show() + $Message.text = "Dodge the\nCreeps!" + $Message.show() + # Make a one-shot timer and wait for it to finish. + yield(get_tree().create_timer(1), "timeout") + $StartButton.show() ``` This function is called when the player loses. It will show "Game Over" for 2 @@ -145,8 +145,8 @@ Note: gdscript GDScript ``` - func update_score(score): - $ScoreLabel.text = str(score) +func update_score(score): + $ScoreLabel.text = str(score) ``` This function is called by `Main` whenever the score changes. @@ -157,12 +157,12 @@ signal of `StartButton` and add the following code to the new functions: gdscript GDScript ``` - func _on_StartButton_pressed(): - $StartButton.hide() - emit_signal("start_game") +func _on_StartButton_pressed(): + $StartButton.hide() + emit_signal("start_game") - func _on_MessageTimer_timeout(): - $Message.hide() +func _on_MessageTimer_timeout(): + $Message.hide() ``` ## Connecting HUD to Main @@ -186,8 +186,8 @@ In `new_game()`, update the score display and show the "Get Ready" message: gdscript GDScript ``` - $HUD.update_score(score) - $HUD.show_message("Get Ready") +$HUD.update_score(score) +$HUD.show_message("Get Ready") ``` In `game_over()` we need to call the corresponding `HUD` function: @@ -195,7 +195,7 @@ In `game_over()` we need to call the corresponding `HUD` function: gdscript GDScript ``` - $HUD.show_game_over() +$HUD.show_game_over() ``` Finally, add this to `on_ScoreTimer_timeout()` to keep the display in sync @@ -204,7 +204,7 @@ with the changing score: gdscript GDScript ``` - $HUD.update_score(score) +$HUD.update_score(score) ``` Now you're ready to play! Click the "Play the Project" button. You will be asked @@ -229,7 +229,7 @@ the `new_game()` function in `Main`: gdscript GDScript ``` - get_tree().call_group("mobs", "queue_free") +get_tree().call_group("mobs", "queue_free") ``` The `call_group()` function calls the named function on every node in a diff --git a/02_tutorials/04_first_3d_game/03_player_movement_code.md b/02_tutorials/04_first_3d_game/03_player_movement_code.md index 7cf0197..b4d0596 100644 --- a/02_tutorials/04_first_3d_game/03_player_movement_code.md +++ b/02_tutorials/04_first_3d_game/03_player_movement_code.md @@ -45,21 +45,21 @@ using the global `Input` object, in `physics_process()`. gdscript GDScript ``` - func _physics_process(delta): - # We create a local variable to store the input direction. - var direction = Vector3.ZERO +func _physics_process(delta): + # We create a local variable to store the input direction. + var direction = Vector3.ZERO - # We check for each move input and update the direction accordingly. - if Input.is_action_pressed("move_right"): - direction.x += 1 - if Input.is_action_pressed("move_left"): - direction.x -= 1 - if Input.is_action_pressed("move_back"): - # Notice how we are working with the vector's x and z axes. - # In 3D, the XZ plane is the ground plane. - direction.z += 1 - if Input.is_action_pressed("move_forward"): - direction.z -= 1 + # We check for each move input and update the direction accordingly. + if Input.is_action_pressed("move_right"): + direction.x += 1 + if Input.is_action_pressed("move_left"): + direction.x -= 1 + if Input.is_action_pressed("move_back"): + # Notice how we are working with the vector's x and z axes. + # In 3D, the XZ plane is the ground plane. + direction.z += 1 + if Input.is_action_pressed("move_forward"): + direction.z -= 1 ``` Here, we're going to make all calculations using the `physics_process()` @@ -88,12 +88,12 @@ call its `normalize()` method. gdscript GDScript ``` - #func _physics_process(delta): - #... +#func _physics_process(delta): + #... - if direction != Vector3.ZERO: - direction = direction.normalized() - $Pivot.look_at(translation + direction, Vector3.UP) + if direction != Vector3.ZERO: + direction = direction.normalized() + $Pivot.look_at(translation + direction, Vector3.UP) ``` Here, we only normalize the vector if the direction has a length greater than @@ -121,18 +121,18 @@ fall speed separately. Be sure to go back one tab so the lines are inside the gdscript GDScript ``` - func _physics_process(delta): +func _physics_process(delta): + #... + if direction != Vector3.ZERO: #... - if direction != Vector3.ZERO: - #... - # Ground velocity - velocity.x = direction.x * speed - velocity.z = direction.z * speed - # Vertical velocity - velocity.y -= fall_acceleration * delta - # Moving the character - velocity = move_and_slide(velocity, Vector3.UP) + # Ground velocity + velocity.x = direction.x * speed + velocity.z = direction.z * speed + # Vertical velocity + velocity.y -= fall_acceleration * delta + # Moving the character + velocity = move_and_slide(velocity, Vector3.UP) ``` For the vertical velocity, we subtract the fall acceleration multiplied by the @@ -166,36 +166,36 @@ Here is the complete `Player.gd` code for reference. gdscript GDScript ``` - extends KinematicBody +extends KinematicBody - # How fast the player moves in meters per second. - export var speed = 14 - # The downward acceleration when in the air, in meters per second squared. - export var fall_acceleration = 75 +# How fast the player moves in meters per second. +export var speed = 14 +# The downward acceleration when in the air, in meters per second squared. +export var fall_acceleration = 75 - var velocity = Vector3.ZERO +var velocity = Vector3.ZERO - func _physics_process(delta): - var direction = Vector3.ZERO +func _physics_process(delta): + var direction = Vector3.ZERO - if Input.is_action_pressed("move_right"): - direction.x += 1 - if Input.is_action_pressed("move_left"): - direction.x -= 1 - if Input.is_action_pressed("move_back"): - direction.z += 1 - if Input.is_action_pressed("move_forward"): - direction.z -= 1 + if Input.is_action_pressed("move_right"): + direction.x += 1 + if Input.is_action_pressed("move_left"): + direction.x -= 1 + if Input.is_action_pressed("move_back"): + direction.z += 1 + if Input.is_action_pressed("move_forward"): + direction.z -= 1 - if direction != Vector3.ZERO: - direction = direction.normalized() - $Pivot.look_at(translation + direction, Vector3.UP) + if direction != Vector3.ZERO: + direction = direction.normalized() + $Pivot.look_at(translation + direction, Vector3.UP) - velocity.x = direction.x * speed - velocity.z = direction.z * speed - velocity.y -= fall_acceleration * delta - velocity = move_and_slide(velocity, Vector3.UP) + velocity.x = direction.x * speed + velocity.z = direction.z * speed + velocity.y -= fall_acceleration * delta + velocity = move_and_slide(velocity, Vector3.UP) ``` ## Testing our player's movement diff --git a/02_tutorials/04_first_3d_game/04_mob_scene.md b/02_tutorials/04_first_3d_game/04_mob_scene.md index 69d9fa9..0fc4c3b 100644 --- a/02_tutorials/04_first_3d_game/04_mob_scene.md +++ b/02_tutorials/04_first_3d_game/04_mob_scene.md @@ -98,18 +98,18 @@ the `velocity`. gdscript GDScript ``` - extends KinematicBody +extends KinematicBody - # Minimum speed of the mob in meters per second. - export var min_speed = 10 - # Maximum speed of the mob in meters per second. - export var max_speed = 18 +# Minimum speed of the mob in meters per second. +export var min_speed = 10 +# Maximum speed of the mob in meters per second. +export var max_speed = 18 - var velocity = Vector3.ZERO +var velocity = Vector3.ZERO - func _physics_process(_delta): - move_and_slide(velocity) +func _physics_process(_delta): + move_and_slide(velocity) ``` Similarly to the player, we move the mob every frame by calling @@ -139,12 +139,12 @@ between `-PI / 4` radians and `PI / 4` radians. gdscript GDScript ``` - # We will call this function from the Main scene. - func initialize(start_position, player_position): - # We position the mob and turn it so that it looks at the player. - look_at_from_position(start_position, player_position, Vector3.UP) - # And rotate it randomly so it doesn't move exactly toward the player. - rotate_y(rand_range(-PI / 4, PI / 4)) +# We will call this function from the Main scene. +func initialize(start_position, player_position): + # We position the mob and turn it so that it looks at the player. + look_at_from_position(start_position, player_position, Vector3.UP) + # And rotate it randomly so it doesn't move exactly toward the player. + rotate_y(rand_range(-PI / 4, PI / 4)) ``` We then calculate a random speed using `rand_range()` once again and we use it @@ -157,15 +157,15 @@ We start by creating a 3D vector pointing forward, multiply it by our gdscript GDScript ``` - func initialize(start_position, player_position): - # ... +func initialize(start_position, player_position): + # ... - # We calculate a random speed. - var random_speed = rand_range(min_speed, max_speed) - # We calculate a forward velocity that represents the speed. - velocity = Vector3.FORWARD * random_speed - # We then rotate the vector based on the mob's Y rotation to move in the direction it's looking. - velocity = velocity.rotated(Vector3.UP, rotation.y) + # We calculate a random speed. + var random_speed = rand_range(min_speed, max_speed) + # We calculate a forward velocity that represents the speed. + velocity = Vector3.FORWARD * random_speed + # We then rotate the vector based on the mob's Y rotation to move in the direction it's looking. + velocity = velocity.rotated(Vector3.UP, rotation.y) ``` ## Leaving the screen @@ -195,8 +195,8 @@ leaves the screen. gdscript GDScript ``` - func _on_VisibilityNotifier_screen_exited(): - queue_free() +func _on_VisibilityNotifier_screen_exited(): + queue_free() ``` Our monster is ready to enter the game! In the next part, you will spawn @@ -207,28 +207,28 @@ Here is the complete `Mob.gd` script for reference. gdscript GDScript ``` - extends KinematicBody +extends KinematicBody - # Minimum speed of the mob in meters per second. - export var min_speed = 10 - # Maximum speed of the mob in meters per second. - export var max_speed = 18 +# Minimum speed of the mob in meters per second. +export var min_speed = 10 +# Maximum speed of the mob in meters per second. +export var max_speed = 18 - var velocity = Vector3.ZERO +var velocity = Vector3.ZERO - func _physics_process(_delta): - move_and_slide(velocity) +func _physics_process(_delta): + move_and_slide(velocity) - func initialize(start_position, player_position): - look_at_from_position(start_position, player_position, Vector3.UP) - rotate_y(rand_range(-PI / 4, PI / 4)) +func initialize(start_position, player_position): + look_at_from_position(start_position, player_position, Vector3.UP) + rotate_y(rand_range(-PI / 4, PI / 4)) - var random_speed = rand_range(min_speed, max_speed) - velocity = Vector3.FORWARD * random_speed - velocity = velocity.rotated(Vector3.UP, rotation.y) + var random_speed = rand_range(min_speed, max_speed) + velocity = Vector3.FORWARD * random_speed + velocity = velocity.rotated(Vector3.UP, rotation.y) - func _on_VisibilityNotifier_screen_exited(): - queue_free() +func _on_VisibilityNotifier_screen_exited(): + queue_free() ``` diff --git a/02_tutorials/04_first_3d_game/05_spawning_mobs.md b/02_tutorials/04_first_3d_game/05_spawning_mobs.md index 094b6a7..88e2020 100644 --- a/02_tutorials/04_first_3d_game/05_spawning_mobs.md +++ b/02_tutorials/04_first_3d_game/05_spawning_mobs.md @@ -158,13 +158,13 @@ always spawn following the same sequence. gdscript GDScript ``` - extends Node +extends Node - export (PackedScene) var mob_scene +export (PackedScene) var mob_scene - func _ready(): - randomize() +func _ready(): + randomize() ``` We want to spawn mobs at regular time intervals. To do this, we need to go back @@ -213,20 +213,20 @@ Let's code the mob spawning logic. We're going to: gdscript GDScript ``` - func _on_MobTimer_timeout(): - # Create a new instance of the Mob scene. - var mob = mob_scene.instance() +func _on_MobTimer_timeout(): + # Create a new instance of the Mob scene. + var mob = mob_scene.instance() - # Choose a random location on the SpawnPath. - # We store the reference to the SpawnLocation node. - var mob_spawn_location = get_node("SpawnPath/SpawnLocation") - # And give it a random offset. - mob_spawn_location.unit_offset = randf() + # Choose a random location on the SpawnPath. + # We store the reference to the SpawnLocation node. + var mob_spawn_location = get_node("SpawnPath/SpawnLocation") + # And give it a random offset. + mob_spawn_location.unit_offset = randf() - var player_position = $Player.transform.origin - mob.initialize(mob_spawn_location.translation, player_position) + var player_position = $Player.transform.origin + mob.initialize(mob_spawn_location.translation, player_position) - add_child(mob) + add_child(mob) ``` Above, `randf()` produces a random value between `0` and `1`, which is @@ -237,24 +237,24 @@ Here is the complete `Main.gd` script so far, for reference. gdscript GDScript ``` - extends Node +extends Node - export (PackedScene) var mob_scene +export (PackedScene) var mob_scene - func _ready(): - randomize() +func _ready(): + randomize() - func _on_MobTimer_timeout(): - var mob = mob_scene.instance() +func _on_MobTimer_timeout(): + var mob = mob_scene.instance() - var mob_spawn_location = get_node("SpawnPath/SpawnLocation") - mob_spawn_location.unit_offset = randf() - var player_position = $Player.transform.origin - mob.initialize(mob_spawn_location.translation, player_position) + var mob_spawn_location = get_node("SpawnPath/SpawnLocation") + mob_spawn_location.unit_offset = randf() + var player_position = $Player.transform.origin + mob.initialize(mob_spawn_location.translation, player_position) - add_child(mob) + add_child(mob) ``` You can test the scene by pressing :kbd:`F6`. You should see the monsters spawn and diff --git a/02_tutorials/04_first_3d_game/06_jump_and_squash.md b/02_tutorials/04_first_3d_game/06_jump_and_squash.md index 1ebc267..c7bc307 100644 --- a/02_tutorials/04_first_3d_game/06_jump_and_squash.md +++ b/02_tutorials/04_first_3d_game/06_jump_and_squash.md @@ -107,9 +107,9 @@ the `jump_impulse`. gdscript GDScript ``` - #... - # Vertical impulse applied to the character upon jumping in meters per second. - export var jump_impulse = 20 +#... +# Vertical impulse applied to the character upon jumping in meters per second. +export var jump_impulse = 20 ``` Inside `physics_process()`, add the following code before the line where we @@ -118,14 +118,14 @@ called `move_and_slide()`. gdscript GDScript ``` - func _physics_process(delta): - #... +func _physics_process(delta): + #... - # Jumping. - if is_on_floor() and Input.is_action_just_pressed("jump"): - velocity.y += jump_impulse + # Jumping. + if is_on_floor() and Input.is_action_just_pressed("jump"): + velocity.y += jump_impulse - #... + #... ``` That's all you need to jump! @@ -181,9 +181,9 @@ when jumping. gdscript GDScript ``` - # Vertical impulse applied to the character upon bouncing over a mob in - # meters per second. - export var bounce_impulse = 16 +# Vertical impulse applied to the character upon bouncing over a mob in +# meters per second. +export var bounce_impulse = 16 ``` Then, at the bottom of `physics_process()`, add the following loop. With @@ -199,19 +199,19 @@ With this code, if no collisions occurred on a given frame, the loop won't run. gdscript GDScript ``` - func _physics_process(delta): - #... - for index in range(get_slide_count()): - # We check every collision that occurred this frame. - var collision = get_slide_collision(index) - # If we collide with a monster... - if collision.collider.is_in_group("mob"): - var mob = collision.collider - # ...we check that we are hitting it from above. - if Vector3.UP.dot(collision.normal) > 0.1: - # If so, we squash it and bounce. - mob.squash() - velocity.y = bounce_impulse +func _physics_process(delta): + #... + for index in range(get_slide_count()): + # We check every collision that occurred this frame. + var collision = get_slide_collision(index) + # If we collide with a monster... + if collision.collider.is_in_group("mob"): + var mob = collision.collider + # ...we check that we are hitting it from above. + if Vector3.UP.dot(collision.normal) > 0.1: + # If so, we squash it and bounce. + mob.squash() + velocity.y = bounce_impulse ``` That's a lot of new functions. Here's some more information about them. @@ -251,15 +251,15 @@ destroy the mob. gdscript GDScript ``` - # Emitted when the player jumped on the mob. - signal squashed +# Emitted when the player jumped on the mob. +signal squashed - # ... +# ... - func squash(): - emit_signal("squashed") - queue_free() +func squash(): + emit_signal("squashed") + queue_free() ``` We will use the signal to add points to the score in the next lesson. diff --git a/02_tutorials/04_first_3d_game/07_killing_player.md b/02_tutorials/04_first_3d_game/07_killing_player.md index 10a52ac..b48295f 100644 --- a/02_tutorials/04_first_3d_game/07_killing_player.md +++ b/02_tutorials/04_first_3d_game/07_killing_player.md @@ -61,19 +61,19 @@ a `die()` function that helps us put a descriptive label on the code. gdscript GDScript ``` - # Emitted when the player was hit by a mob. - # Put this at the top of the script. - signal hit +# Emitted when the player was hit by a mob. +# Put this at the top of the script. +signal hit - # And this function at the bottom. - func die(): - emit_signal("hit") - queue_free() +# And this function at the bottom. +func die(): + emit_signal("hit") + queue_free() - func _on_MobDetector_body_entered(_body): - die() +func _on_MobDetector_body_entered(_body): + die() ``` Try the game again by pressing :kbd:`F5`. If everything is set up correctly, @@ -99,8 +99,8 @@ Get and stop the timer in the `on_Player_hit()` function. gdscript GDScript ``` - func _on_Player_hit(): - $MobTimer.stop() +func _on_Player_hit(): + $MobTimer.stop() ``` If you try the game now, the monsters will stop spawning when you die, @@ -123,34 +123,34 @@ Starting with `Main.gd`. gdscript GDScript ``` - extends Node +extends Node - export(PackedScene) var mob_scene +export(PackedScene) var mob_scene - func _ready(): - randomize() +func _ready(): + randomize() - func _on_MobTimer_timeout(): - # Create a new instance of the Mob scene. - var mob = mob_scene.instance() +func _on_MobTimer_timeout(): + # Create a new instance of the Mob scene. + var mob = mob_scene.instance() - # Choose a random location on the SpawnPath. - var mob_spawn_location = get_node("SpawnPath/SpawnLocation") - # And give it a random offset. - mob_spawn_location.unit_offset = randf() + # Choose a random location on the SpawnPath. + var mob_spawn_location = get_node("SpawnPath/SpawnLocation") + # And give it a random offset. + mob_spawn_location.unit_offset = randf() - # Communicate the spawn location and the player's location to the mob. - var player_position = $Player.transform.origin - mob.initialize(mob_spawn_location.translation, player_position) + # Communicate the spawn location and the player's location to the mob. + var player_position = $Player.transform.origin + mob.initialize(mob_spawn_location.translation, player_position) - # Spawn the mob by adding it to the Main scene. - add_child(mob) + # Spawn the mob by adding it to the Main scene. + add_child(mob) - func _on_Player_hit(): - $MobTimer.stop() +func _on_Player_hit(): + $MobTimer.stop() ``` Next is `Mob.gd`. @@ -158,39 +158,39 @@ Next is `Mob.gd`. gdscript GDScript ``` - extends KinematicBody +extends KinematicBody - # Emitted when the player jumped on the mob. - signal squashed +# Emitted when the player jumped on the mob. +signal squashed - # Minimum speed of the mob in meters per second. - export var min_speed = 10 - # Maximum speed of the mob in meters per second. - export var max_speed = 18 +# Minimum speed of the mob in meters per second. +export var min_speed = 10 +# Maximum speed of the mob in meters per second. +export var max_speed = 18 - var velocity = Vector3.ZERO +var velocity = Vector3.ZERO - func _physics_process(_delta): - move_and_slide(velocity) +func _physics_process(_delta): + move_and_slide(velocity) - func initialize(start_position, player_position): - look_at_from_position(start_position, player_position, Vector3.UP) - rotate_y(rand_range(-PI / 4, PI / 4)) +func initialize(start_position, player_position): + look_at_from_position(start_position, player_position, Vector3.UP) + rotate_y(rand_range(-PI / 4, PI / 4)) - var random_speed = rand_range(min_speed, max_speed) - velocity = Vector3.FORWARD * random_speed - velocity = velocity.rotated(Vector3.UP, rotation.y) + var random_speed = rand_range(min_speed, max_speed) + velocity = Vector3.FORWARD * random_speed + velocity = velocity.rotated(Vector3.UP, rotation.y) - func squash(): - emit_signal("squashed") - queue_free() + func squash(): + emit_signal("squashed") + queue_free() - func _on_VisibilityNotifier_screen_exited(): - queue_free() +func _on_VisibilityNotifier_screen_exited(): + queue_free() ``` Finally, the longest script, `Player.gd`. @@ -198,65 +198,65 @@ Finally, the longest script, `Player.gd`. gdscript GDScript ``` - extends KinematicBody +extends KinematicBody - # Emitted when a mob hit the player. - signal hit +# Emitted when a mob hit the player. +signal hit - # How fast the player moves in meters per second. - export var speed = 14 - # The downward acceleration when in the air, in meters per second squared. - export var fall_acceleration = 75 - # Vertical impulse applied to the character upon jumping in meters per second. - export var jump_impulse = 20 - # Vertical impulse applied to the character upon bouncing over a mob in meters per second. - export var bounce_impulse = 16 +# How fast the player moves in meters per second. +export var speed = 14 +# The downward acceleration when in the air, in meters per second squared. +export var fall_acceleration = 75 +# Vertical impulse applied to the character upon jumping in meters per second. +export var jump_impulse = 20 +# Vertical impulse applied to the character upon bouncing over a mob in meters per second. +export var bounce_impulse = 16 - var velocity = Vector3.ZERO +var velocity = Vector3.ZERO - func _physics_process(delta): - var direction = Vector3.ZERO +func _physics_process(delta): + var direction = Vector3.ZERO - if Input.is_action_pressed("move_right"): - direction.x += 1 - if Input.is_action_pressed("move_left"): - direction.x -= 1 - if Input.is_action_pressed("move_back"): - direction.z += 1 - if Input.is_action_pressed("move_forward"): - direction.z -= 1 + if Input.is_action_pressed("move_right"): + direction.x += 1 + if Input.is_action_pressed("move_left"): + direction.x -= 1 + if Input.is_action_pressed("move_back"): + direction.z += 1 + if Input.is_action_pressed("move_forward"): + direction.z -= 1 - if direction != Vector3.ZERO: - direction = direction.normalized() - $Pivot.look_at(translation + direction, Vector3.UP) + if direction != Vector3.ZERO: + direction = direction.normalized() + $Pivot.look_at(translation + direction, Vector3.UP) - velocity.x = direction.x * speed - velocity.z = direction.z * speed + velocity.x = direction.x * speed + velocity.z = direction.z * speed - # Jumping. - if is_on_floor() and Input.is_action_just_pressed("jump"): - velocity.y += jump_impulse + # Jumping. + if is_on_floor() and Input.is_action_just_pressed("jump"): + velocity.y += jump_impulse - velocity.y -= fall_acceleration * delta - velocity = move_and_slide(velocity, Vector3.UP) + velocity.y -= fall_acceleration * delta + velocity = move_and_slide(velocity, Vector3.UP) - for index in range(get_slide_count()): - var collision = get_slide_collision(index) - if collision.collider.is_in_group("mob"): - var mob = collision.collider - if Vector3.UP.dot(collision.normal) > 0.1: - mob.squash() - velocity.y = bounce_impulse + for index in range(get_slide_count()): + var collision = get_slide_collision(index) + if collision.collider.is_in_group("mob"): + var mob = collision.collider + if Vector3.UP.dot(collision.normal) > 0.1: + mob.squash() + velocity.y = bounce_impulse - func die(): - emit_signal("hit") - queue_free() +func die(): + emit_signal("hit") + queue_free() - func _on_MobDetector_body_entered(_body): - die() +func _on_MobDetector_body_entered(_body): + die() ``` See you in the next lesson to add the score and the retry option. diff --git a/02_tutorials/04_first_3d_game/08_score_and_replay.md b/02_tutorials/04_first_3d_game/08_score_and_replay.md index 5c33704..7af6858 100644 --- a/02_tutorials/04_first_3d_game/08_score_and_replay.md +++ b/02_tutorials/04_first_3d_game/08_score_and_replay.md @@ -95,9 +95,9 @@ the `score` variable. gdscript GDScript ``` - extends Label +extends Label - var score = 0 +var score = 0 ``` The score should increase by `1` every time we squash a monster. We can use @@ -121,10 +121,10 @@ line. gdscript GDScript ``` - func _on_MobTimer_timeout(): - #... - # We connect the mob to the score label to update the score upon squashing one. - mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed") +func _on_MobTimer_timeout(): + #... + # We connect the mob to the score label to update the score upon squashing one. + mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed") ``` This line means that when the mob emits the `squashed` signal, the @@ -138,9 +138,9 @@ There, we increment the score and update the displayed text. gdscript GDScript ``` - func _on_Mob_squashed(): - score += 1 - text = "Score: %s" % score +func _on_Mob_squashed(): + score += 1 + text = "Score: %s" % score ``` The second line uses the value of the `score` variable to replace the @@ -225,9 +225,9 @@ the game. Add this line to the `ready()` function. gdscript GDScript ``` - func _ready(): - #... - $UserInterface/Retry.hide() +func _ready(): + #... + $UserInterface/Retry.hide() ``` Then, when the player gets hit, we show the overlay. @@ -236,9 +236,9 @@ gdscript GDScript ``` - func _on_Player_hit(): - #... - $UserInterface/Retry.show() +func _on_Player_hit(): + #... + $UserInterface/Retry.show() ``` Finally, when the *Retry* node is visible, we need to listen to the player's @@ -251,10 +251,10 @@ visible, we reload the current scene. gdscript GDScript ``` - func _unhandled_input(event): - if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible: - # This restarts the current scene. - get_tree().reload_current_scene() +func _unhandled_input(event): + if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible: + # This restarts the current scene. + get_tree().reload_current_scene() ``` The function `get_tree()` gives us access to the global `SceneTree @@ -328,36 +328,36 @@ Here is the complete `Main.gd` script for reference. gdscript GDScript ``` - extends Node +extends Node - export (PackedScene) var mob_scene +export (PackedScene) var mob_scene - func _ready(): - randomize() - $UserInterface/Retry.hide() +func _ready(): + randomize() + $UserInterface/Retry.hide() - func _unhandled_input(event): - if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible: - get_tree().reload_current_scene() +func _unhandled_input(event): + if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible: + get_tree().reload_current_scene() - func _on_MobTimer_timeout(): - var mob = mob_scene.instance() +func _on_MobTimer_timeout(): + var mob = mob_scene.instance() - var mob_spawn_location = get_node("SpawnPath/SpawnLocation") - mob_spawn_location.unit_offset = randf() + var mob_spawn_location = get_node("SpawnPath/SpawnLocation") + mob_spawn_location.unit_offset = randf() - var player_position = $Player.transform.origin - mob.initialize(mob_spawn_location.translation, player_position) + var player_position = $Player.transform.origin + mob.initialize(mob_spawn_location.translation, player_position) - add_child(mob) - mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed") + add_child(mob) + mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed") - func _on_Player_hit(): - $MobTimer.stop() - $UserInterface/Retry.show() +func _on_Player_hit(): + $MobTimer.stop() + $UserInterface/Retry.show() ``` diff --git a/02_tutorials/04_first_3d_game/09_adding_animations.md b/02_tutorials/04_first_3d_game/09_adding_animations.md index 35bbf4e..af7279b 100644 --- a/02_tutorials/04_first_3d_game/09_adding_animations.md +++ b/02_tutorials/04_first_3d_game/09_adding_animations.md @@ -187,13 +187,13 @@ vector, add the following code. gdscript GDScript ``` - func _physics_process(delta): - #... - #if direction != Vector3.ZERO: - #... - $AnimationPlayer.playback_speed = 4 - else: - $AnimationPlayer.playback_speed = 1 +func _physics_process(delta): + #... + #if direction != Vector3.ZERO: + #... + $AnimationPlayer.playback_speed = 4 + else: + $AnimationPlayer.playback_speed = 1 ``` This code makes it so when the player moves, we multiply the playback speed by @@ -206,9 +206,9 @@ at the end of `physics_process()`. gdscript GDScript ``` - func _physics_process(delta): - #... - $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse +func _physics_process(delta): + #... + $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse ``` ## Animating the mobs @@ -231,9 +231,9 @@ following line. gdscript GDScript ``` - func initialize(start_position, player_position): - #... - $AnimationPlayer.playback_speed = random_speed / min_speed +func initialize(start_position, player_position): + #... + $AnimationPlayer.playback_speed = random_speed / min_speed ``` And with that, you finished coding your first complete 3D game. @@ -249,70 +249,70 @@ Here's the *Player* script. gdscript GDScript ``` - extends KinematicBody +extends KinematicBody - # Emitted when the player was hit by a mob. - signal hit +# Emitted when the player was hit by a mob. +signal hit - # How fast the player moves in meters per second. - export var speed = 14 - # The downward acceleration when in the air, in meters per second per second. - export var fall_acceleration = 75 - # Vertical impulse applied to the character upon jumping in meters per second. - export var jump_impulse = 20 - # Vertical impulse applied to the character upon bouncing over a mob in meters per second. - export var bounce_impulse = 16 +# How fast the player moves in meters per second. +export var speed = 14 +# The downward acceleration when in the air, in meters per second per second. +export var fall_acceleration = 75 +# Vertical impulse applied to the character upon jumping in meters per second. +export var jump_impulse = 20 +# Vertical impulse applied to the character upon bouncing over a mob in meters per second. +export var bounce_impulse = 16 - var velocity = Vector3.ZERO +var velocity = Vector3.ZERO - func _physics_process(delta): - var direction = Vector3.ZERO +func _physics_process(delta): + var direction = Vector3.ZERO - if Input.is_action_pressed("move_right"): - direction.x += 1 - if Input.is_action_pressed("move_left"): - direction.x -= 1 - if Input.is_action_pressed("move_back"): - direction.z += 1 - if Input.is_action_pressed("move_forward"): - direction.z -= 1 + if Input.is_action_pressed("move_right"): + direction.x += 1 + if Input.is_action_pressed("move_left"): + direction.x -= 1 + if Input.is_action_pressed("move_back"): + direction.z += 1 + if Input.is_action_pressed("move_forward"): + direction.z -= 1 - if direction != Vector3.ZERO: - direction = direction.normalized() - $Pivot.look_at(translation + direction, Vector3.UP) - $AnimationPlayer.playback_speed = 4 - else: - $AnimationPlayer.playback_speed = 1 + if direction != Vector3.ZERO: + direction = direction.normalized() + $Pivot.look_at(translation + direction, Vector3.UP) + $AnimationPlayer.playback_speed = 4 + else: + $AnimationPlayer.playback_speed = 1 - velocity.x = direction.x * speed - velocity.z = direction.z * speed + velocity.x = direction.x * speed + velocity.z = direction.z * speed - # Jumping - if is_on_floor() and Input.is_action_just_pressed("jump"): - velocity.y += jump_impulse + # Jumping + if is_on_floor() and Input.is_action_just_pressed("jump"): + velocity.y += jump_impulse - velocity.y -= fall_acceleration * delta - velocity = move_and_slide(velocity, Vector3.UP) + velocity.y -= fall_acceleration * delta + velocity = move_and_slide(velocity, Vector3.UP) - for index in range(get_slide_count()): - var collision = get_slide_collision(index) - if collision.collider.is_in_group("mob"): - var mob = collision.collider - if Vector3.UP.dot(collision.normal) > 0.1: - mob.squash() - velocity.y = bounce_impulse + for index in range(get_slide_count()): + var collision = get_slide_collision(index) + if collision.collider.is_in_group("mob"): + var mob = collision.collider + if Vector3.UP.dot(collision.normal) > 0.1: + mob.squash() + velocity.y = bounce_impulse - $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse + $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse - func die(): - emit_signal("hit") - queue_free() +func die(): + emit_signal("hit") + queue_free() - func _on_MobDetector_body_entered(_body): - die() +func _on_MobDetector_body_entered(_body): + die() ``` And the *Mob*'s script. @@ -320,40 +320,40 @@ And the *Mob*'s script. gdscript GDScript ``` - extends KinematicBody +extends KinematicBody - # Emitted when the player jumped on the mob. - signal squashed +# Emitted when the player jumped on the mob. +signal squashed - # Minimum speed of the mob in meters per second. - export var min_speed = 10 - # Maximum speed of the mob in meters per second. - export var max_speed = 18 +# Minimum speed of the mob in meters per second. +export var min_speed = 10 +# Maximum speed of the mob in meters per second. +export var max_speed = 18 - var velocity = Vector3.ZERO +var velocity = Vector3.ZERO - func _physics_process(_delta): - move_and_slide(velocity) +func _physics_process(_delta): + move_and_slide(velocity) - func initialize(start_position, player_position): - look_at_from_position(start_position, player_position, Vector3.UP) - rotate_y(rand_range(-PI / 4, PI / 4)) +func initialize(start_position, player_position): + look_at_from_position(start_position, player_position, Vector3.UP) + rotate_y(rand_range(-PI / 4, PI / 4)) - var random_speed = rand_range(min_speed, max_speed) - velocity = Vector3.FORWARD * random_speed - velocity = velocity.rotated(Vector3.UP, rotation.y) + var random_speed = rand_range(min_speed, max_speed) + velocity = Vector3.FORWARD * random_speed + velocity = velocity.rotated(Vector3.UP, rotation.y) - $AnimationPlayer.playback_speed = random_speed / min_speed + $AnimationPlayer.playback_speed = random_speed / min_speed - func squash(): - emit_signal("squashed") - queue_free() + func squash(): + emit_signal("squashed") + queue_free() - func _on_VisibilityNotifier_screen_exited(): - queue_free() +func _on_VisibilityNotifier_screen_exited(): + queue_free() ``` diff --git a/03_usage/01_math/01_vector_math.md b/03_usage/01_math/01_vector_math.md index 010bf6d..f6ba6a5 100644 --- a/03_usage/01_math/01_vector_math.md +++ b/03_usage/01_math/01_vector_math.md @@ -64,7 +64,7 @@ corner of the screen, so to place a 2D node named `Node2D` 400 pixels to the rig gdscript GDScript ``` - $Node2D.position = Vector2(400, 300) +$Node2D.position = Vector2(400, 300) ``` Pandemonium supports both `Vector2` and @@ -78,12 +78,12 @@ The individual components of the vector can be accessed directly by name. gdscript GDScript ``` - # create a vector with coordinates (2, 5) - var a = Vector2(2, 5) - # create a vector and assign x and y manually - var b = Vector2() - b.x = 3 - b.y = 1 +# create a vector with coordinates (2, 5) +var a = Vector2(2, 5) +# create a vector and assign x and y manually +var b = Vector2() +b.x = 3 +b.y = 1 ``` ## Adding vectors @@ -93,7 +93,7 @@ When adding or subtracting two vectors, the corresponding components are added: gdscript GDScript ``` - var c = a + b # (2, 5) + (3, 1) = (5, 6) +var c = a + b # (2, 5) + (3, 1) = (5, 6) ``` We can also see this visually by adding the second vector at the end of @@ -114,8 +114,8 @@ A vector can be multiplied by a **scalar**: gdscript GDScript ``` - var c = a * 2 # (2, 5) * 2 = (4, 10) - var d = b / 3 # (3, 6) / 3 = (1, 2) +var c = a * 2 # (2, 5) * 2 = (4, 10) +var d = b / 3 # (3, 6) / 3 = (1, 2) ``` ![](img/vector_mult1.png) @@ -169,7 +169,7 @@ by its magnitude. Because this is such a common operation, gdscript GDScript ``` - a = a.normalized() +a = a.normalized() ``` Warning: @@ -201,12 +201,12 @@ to handle this. Here is a GDScript example of the diagram above using a gdscript GDScript ``` - # object "collision" contains information about the collision - var collision = move_and_collide(velocity * delta) - if collision: - var reflect = collision.remainder.bounce(collision.normal) - velocity = velocity.bounce(collision.normal) - move_and_collide(reflect) +# object "collision" contains information about the collision +var collision = move_and_collide(velocity * delta) +if collision: + var reflect = collision.remainder.bounce(collision.normal) + velocity = velocity.bounce(collision.normal) + move_and_collide(reflect) ``` ### Dot product @@ -230,8 +230,8 @@ the order of the two vectors does not matter: gdscript GDScript ``` - var c = a.dot(b) - var d = b.dot(a) # These are equivalent. +var c = a.dot(b) +var d = b.dot(a) # These are equivalent. ``` The dot product is most useful when used with unit vectors, making the @@ -264,9 +264,9 @@ In code it would look like this: gdscript GDScript ``` - var AP = A.direction_to(P) - if AP.dot(fA) > 0: - print("A sees P!") +var AP = A.direction_to(P) +if AP.dot(fA) > 0: + print("A sees P!") ``` ### Cross product @@ -285,10 +285,10 @@ The cross product is calculated like this: gdscript GDScript ``` - var c = Vector3() - c.x = (a.y * b.z) - (a.z * b.y) - c.y = (a.z * b.x) - (a.x * b.z) - c.z = (a.x * b.y) - (a.y * b.x) +var c = Vector3() +c.x = (a.y * b.z) - (a.z * b.y) +c.y = (a.z * b.x) - (a.x * b.z) +c.z = (a.x * b.y) - (a.y * b.x) ``` With Pandemonium, you can use the built-in method: @@ -296,7 +296,7 @@ With Pandemonium, you can use the built-in method: gdscript GDScript ``` - var c = a.cross(b) +var c = a.cross(b) ``` Note: @@ -316,12 +316,12 @@ Here is a function to calculate a triangle's normal: gdscript GDScript ``` - func get_triangle_normal(a, b, c): - # find the surface normal given 3 vertices - var side1 = b - a - var side2 = c - a - var normal = side1.cross(side2) - return normal +func get_triangle_normal(a, b, c): + # find the surface normal given 3 vertices + var side1 = b - a + var side2 = c - a + var normal = side1.cross(side2) + return normal ``` ## Pointing to a target diff --git a/03_usage/01_math/02_interpolation.md b/03_usage/01_math/02_interpolation.md index db4a176..a39b2d9 100644 --- a/03_usage/01_math/02_interpolation.md +++ b/03_usage/01_math/02_interpolation.md @@ -11,13 +11,13 @@ As an example if `t` is 0, then the state is A. If `t` is 1, then the state is B Between two real (floating-point) numbers, a simple interpolation is usually described as: ``` - interpolation = A * (1 - t) + B * t +interpolation = A * (1 - t) + B * t ``` And often simplified to: ``` - interpolation = A + (B - A) * t +interpolation = A + (B - A) * t ``` The name of this type of interpolation, which transforms a value into another at *constant speed* is *"linear"*. So, when you hear about *Linear Interpolation*, you know they are referring to this simple formula. @@ -36,12 +36,12 @@ Here is simple pseudo-code for going from point A to B using interpolation: gdscript GDScript ``` - var t = 0.0 +var t = 0.0 - func _physics_process(delta): - t += delta * 0.4 +func _physics_process(delta): + t += delta * 0.4 - $Sprite.position = $A.position.linear_interpolate($B.position, t) + $Sprite.position = $A.position.linear_interpolate($B.position, t) ``` It will produce the following motion: @@ -62,12 +62,12 @@ Using the following pseudocode: gdscript GDScript ``` - var t = 0.0 +var t = 0.0 - func _physics_process(delta): - t += delta +func _physics_process(delta): + t += delta - $Monkey.transform = $Position1.transform.interpolate_with($Position2.transform, t) + $Monkey.transform = $Position1.transform.interpolate_with($Position2.transform, t) ``` And again, it will produce the following motion: @@ -82,12 +82,12 @@ Interpolation can be used to smooth movement, rotation, etc. Here is an example gdscript GDScript ``` - const FOLLOW_SPEED = 4.0 +const FOLLOW_SPEED = 4.0 - func _physics_process(delta): - var mouse_pos = get_local_mouse_position() +func _physics_process(delta): + var mouse_pos = get_local_mouse_position() - $Sprite.position = $Sprite.position.linear_interpolate(mouse_pos, delta * FOLLOW_SPEED) + $Sprite.position = $Sprite.position.linear_interpolate(mouse_pos, delta * FOLLOW_SPEED) ``` Here is how it looks: diff --git a/03_usage/01_math/03_random_number_generation.md b/03_usage/01_math/03_random_number_generation.md index e94e497..2d186c0 100644 --- a/03_usage/01_math/03_random_number_generation.md +++ b/03_usage/01_math/03_random_number_generation.md @@ -44,8 +44,8 @@ Putting it in your main scene script's `ready()` method is a good choice: gdscript GDScript ``` - func _ready(): - randomize() +func _ready(): + randomize() ``` You can also set a fixed random seed instead using `seed() @@ -55,10 +55,10 @@ across runs: gdscript GDScript ``` - func _ready(): - seed(12345) - # To use a string as a seed, you can hash it to a number. - seed("Hello world".hash()) +func _ready(): + seed(12345) + # To use a string as a seed, you can hash it to a number. + seed("Hello world".hash()) ``` When using the RandomNumberGenerator class, you should call `randomize()` on @@ -67,8 +67,8 @@ the instance since it has its own seed: gdscript GDScript ``` - var random = RandomNumberGenerator.new() - random.randomize() +var random = RandomNumberGenerator.new() +random.randomize() ``` @@ -85,11 +85,11 @@ denominator: gdscript GDScript ``` - # Prints a random integer between 0 and 49. - print(randi() % 50) +# Prints a random integer between 0 and 49. +print(randi() % 50) - # Prints a random integer between 10 and 60. - print(randi() % 51 + 10) +# Prints a random integer between 10 and 60. +print(randi() % 51 + 10) ``` @@ -107,10 +107,10 @@ varying by the deviation (1.0 by default): gdscript GDScript ``` - # Prints a random floating-point number from a normal distribution with a mean 0.0 and deviation 1.0. - var random = RandomNumberGenerator.new() - random.randomize() - print(random.randfn()) +# Prints a random floating-point number from a normal distribution with a mean 0.0 and deviation 1.0. +var random = RandomNumberGenerator.new() +random.randomize() +print(random.randfn()) ``` `rand_range()` takes two arguments @@ -120,8 +120,8 @@ and `to`: gdscript GDScript ``` - # Prints a random floating-point number between -4 and 6.5. - print(rand_range(-4, 6.5)) +# Prints a random floating-point number between -4 and 6.5. +print(rand_range(-4, 6.5)) ``` `RandomNumberGenerator.randi_range() @@ -131,10 +131,10 @@ and `to`, and returns a random integer between `from` and `to`: gdscript GDScript ``` - # Prints a random integer between -10 and 10. - var random = RandomNumberGenerator.new() - random.randomize() - print(random.randi_range(-10, 10)) +# Prints a random integer between -10 and 10. +var random = RandomNumberGenerator.new() +random.randomize() +print(random.randi_range(-10, 10)) ``` ## Get a random array element @@ -144,21 +144,21 @@ We can use random integer generation to get a random element from an array: gdscript GDScript ``` - var _fruits = ["apple", "orange", "pear", "banana"] +var _fruits = ["apple", "orange", "pear", "banana"] - func _ready(): - randomize() +func _ready(): + randomize() - for i in range(100): - # Pick 100 fruits randomly. - print(get_fruit()) + for i in range(100): + # Pick 100 fruits randomly. + print(get_fruit()) - func get_fruit(): - var random_fruit = _fruits[randi() % _fruits.size()] - # Returns "apple", "orange", "pear", or "banana" every time the code runs. - # We may get the same fruit multiple times in a row. - return random_fruit +func get_fruit(): + var random_fruit = _fruits[randi() % _fruits.size()] + # Returns "apple", "orange", "pear", or "banana" every time the code runs. + # We may get the same fruit multiple times in a row. + return random_fruit ``` To prevent the same fruit from being picked more than once in a row, we can add @@ -167,32 +167,32 @@ more logic to this method: gdscript GDScript ``` - var _fruits = ["apple", "orange", "pear", "banana"] - var _last_fruit = "" +var _fruits = ["apple", "orange", "pear", "banana"] +var _last_fruit = "" - func _ready(): - randomize() +func _ready(): + randomize() - # Pick 100 fruits randomly. - for i in range(100): - print(get_fruit()) + # Pick 100 fruits randomly. + for i in range(100): + print(get_fruit()) - func get_fruit(): - var random_fruit = _fruits[randi() % _fruits.size()] - while random_fruit == _last_fruit: - # The last fruit was picked, try again until we get a different fruit. - random_fruit = _fruits[randi() % _fruits.size()] +func get_fruit(): + var random_fruit = _fruits[randi() % _fruits.size()] + while random_fruit == _last_fruit: + # The last fruit was picked, try again until we get a different fruit. + random_fruit = _fruits[randi() % _fruits.size()] - # Note: if the random element to pick is passed by reference, - # such as an array or dictionary, - # use `last_fruit = random_fruit.duplicate()` instead. - _last_fruit = random_fruit + # Note: if the random element to pick is passed by reference, + # such as an array or dictionary, + # use `last_fruit = random_fruit.duplicate()` instead. + _last_fruit = random_fruit - # Returns "apple", "orange", "pear", or "banana" every time the code runs. - # The function will never return the same fruit more than once in a row. - return random_fruit + # Returns "apple", "orange", "pear", or "banana" every time the code runs. + # The function will never return the same fruit more than once in a row. + return random_fruit ``` This approach can be useful to make random number generation feel less @@ -207,25 +207,25 @@ We can apply similar logic from arrays to dictionaries as well: gdscript GDScript ``` - var metals = { - "copper": {"quantity": 50, "price": 50}, - "silver": {"quantity": 20, "price": 150}, - "gold": {"quantity": 3, "price": 500}, - } +var metals = { + "copper": {"quantity": 50, "price": 50}, + "silver": {"quantity": 20, "price": 150}, + "gold": {"quantity": 3, "price": 500}, +} - func _ready(): - randomize() +func _ready(): + randomize() - for i in range(20): - print(get_metal()) + for i in range(20): + print(get_metal()) - func get_metal(): - var random_metal = metals.values()[randi() % metals.size()] - # Returns a random metal value dictionary every time the code runs. - # The same metal may be selected multiple times in succession. - return random_metal +func get_metal(): + var random_metal = metals.values()[randi() % metals.size()] + # Returns a random metal value dictionary every time the code runs. + # The same metal may be selected multiple times in succession. + return random_metal ``` ## Weighted random probability @@ -237,25 +237,25 @@ floating-point number between 0.0 and 1.0. We can use this to create a gdscript GDScript ``` - func _ready(): - randomize() +func _ready(): + randomize() - for i in range(100): - print(get_item_rarity()) + for i in range(100): + print(get_item_rarity()) - func get_item_rarity(): - var random_float = randf() +func get_item_rarity(): + var random_float = randf() - if random_float < 0.8: - # 80% chance of being returned. - return "Common" - elif random_float < 0.95: - # 15% chance of being returned. - return "Uncommon" - else: - # 5% chance of being returned. - return "Rare" + if random_float < 0.8: + # 80% chance of being returned. + return "Common" + elif random_float < 0.95: + # 15% chance of being returned. + return "Uncommon" + else: + # 5% chance of being returned. + return "Rare" ``` ## "Better" randomness using shuffle bags @@ -270,31 +270,31 @@ element from the array after choosing it. After multiple selections, the array ends up empty. When that happens, you reinitialize it to its default value: ``` - var _fruits = ["apple", "orange", "pear", "banana"] - # A copy of the fruits array so we can restore the original value into `fruits`. - var _fruits_full = [] +var _fruits = ["apple", "orange", "pear", "banana"] +# A copy of the fruits array so we can restore the original value into `fruits`. +var _fruits_full = [] - func _ready(): - randomize() - _fruits_full = _fruits.duplicate() +func _ready(): + randomize() + _fruits_full = _fruits.duplicate() + _fruits.shuffle() + + for i in 100: + print(get_fruit()) + + +func get_fruit(): + if _fruits.empty(): + # Fill the fruits array again and shuffle it. + _fruits = _fruits_full.duplicate() _fruits.shuffle() - for i in 100: - print(get_fruit()) - - - func get_fruit(): - if _fruits.empty(): - # Fill the fruits array again and shuffle it. - _fruits = _fruits_full.duplicate() - _fruits.shuffle() - - # Get a random fruit, since we shuffled the array, - # and remove it from the `fruits` array. - var random_fruit = _fruits.pop_front() - # Prints "apple", "orange", "pear", or "banana" every time the code runs. - return random_fruit + # Get a random fruit, since we shuffled the array, + # and remove it from the `fruits` array. + var random_fruit = _fruits.pop_front() + # Prints "apple", "orange", "pear", or "banana" every time the code runs. + return random_fruit ``` When running the above code, there is a chance to get the same fruit twice in a @@ -316,18 +316,18 @@ terrain. Pandemonium provides `opensimplexnoise` for this, which supports gdscript GDScript ``` - var _noise = OpenSimplexNoise.new() +var _noise = OpenSimplexNoise.new() - func _ready(): - randomize() - # Configure the OpenSimplexNoise instance. - _noise.seed = randi() - _noise.octaves = 4 - _noise.period = 20.0 - _noise.persistence = 0.8 +func _ready(): + randomize() + # Configure the OpenSimplexNoise instance. + _noise.seed = randi() + _noise.octaves = 4 + _noise.period = 20.0 + _noise.persistence = 0.8 - for i in 100: - # Prints a slowly-changing series of floating-point numbers - # between -1.0 and 1.0. - print(_noise.get_noise_1d(i)) + for i in 100: + # Prints a slowly-changing series of floating-point numbers + # between -1.0 and 1.0. + print(_noise.get_noise_1d(i)) ``` diff --git a/03_usage/01_math/04_matrices_and_transforms.md b/03_usage/01_math/04_matrices_and_transforms.md index c658fc1..e457a55 100644 --- a/03_usage/01_math/04_matrices_and_transforms.md +++ b/03_usage/01_math/04_matrices_and_transforms.md @@ -73,11 +73,11 @@ To do this in code, we can simply multiply each of the vectors: gdscript GDScript ``` - var t = Transform2D() - # Scale - t.x *= 2 - t.y *= 2 - transform = t # Change the node's transform to what we just calculated. +var t = Transform2D() +# Scale +t.x *= 2 +t.y *= 2 +transform = t # Change the node's transform to what we just calculated. ``` If we wanted to return it to its original scale, we can multiply @@ -151,13 +151,13 @@ Here's how that would be done in code (place the script on a Node2D): gdscript GDScript ``` - var rot = 0.5 # The rotation to apply. - var t = Transform2D() - t.x.x = cos(rot) - t.y.y = cos(rot) - t.x.y = sin(rot) - t.y.x = -sin(rot) - transform = t # Change the node's transform to what we just calculated. +var rot = 0.5 # The rotation to apply. +var t = Transform2D() +t.x.x = cos(rot) +t.y.y = cos(rot) +t.x.y = sin(rot) +t.y.x = -sin(rot) +transform = t # Change the node's transform to what we just calculated. ``` To calculate the object's rotation from an existing transformation @@ -231,19 +231,19 @@ you to try and reproduce the screenshot without looking at the code! gdscript GDScript ``` - var t = Transform2D() - # Translation - t.origin = Vector2(350, 150) - # Rotation - var rot = -0.5 # The rotation to apply. - t.x.x = cos(rot) - t.y.y = cos(rot) - t.x.y = sin(rot) - t.y.x = -sin(rot) - # Scale - t.x *= 3 - t.y *= 3 - transform = t # Change the node's transform to what we just calculated. +var t = Transform2D() +# Translation +t.origin = Vector2(350, 150) +# Rotation +var rot = -0.5 # The rotation to apply. +t.x.x = cos(rot) +t.y.y = cos(rot) +t.x.y = sin(rot) +t.y.x = -sin(rot) +# Scale +t.x *= 3 +t.y *= 3 +transform = t # Change the node's transform to what we just calculated. ``` ### Shearing the transformation matrix (advanced) @@ -283,10 +283,10 @@ As an example, let's set Y to (1, 1): gdscript GDScript ``` - var t = Transform2D() - # Shear by setting Y to (1, 1) - t.y = Vector2.ONE - transform = t # Change the node's transform to what we just calculated. +var t = Transform2D() +# Shear by setting Y to (1, 1) +t.y = Vector2.ONE +transform = t # Change the node's transform to what we just calculated. ``` Note: @@ -350,8 +350,8 @@ world space as using the "xform" method: gdscript GDScript ``` - # World space vector 100 units below the player. - print(transform.xform(Vector2(0, 100))) +# World space vector 100 units below the player. +print(transform.xform(Vector2(0, 100))) ``` And we can use the "xform_inv" method to find a what world space position @@ -360,8 +360,8 @@ would be if it was instead defined relative to the player: gdscript GDScript ``` - # Where is (0, 100) relative to the player? - print(transform.xform_inv(Vector2(0, 100))) +# Where is (0, 100) relative to the player? +print(transform.xform_inv(Vector2(0, 100))) ``` Note: @@ -384,7 +384,7 @@ This code moves an object 100 units to its own right: gdscript GDScript ``` - transform.origin += transform.x * 100 +transform.origin += transform.x * 100 ``` For moving in 3D, you would need to replace "x" with "basis.x". @@ -426,20 +426,20 @@ the code we would use: gdscript GDScript ``` - # Set up transforms just like in the image, except make positions be 100 times bigger. - var parent = Transform2D(Vector2(2, 0), Vector2(0, 1), Vector2(100, 200)) - var child = Transform2D(Vector2(0.5, 0), Vector2(0, 0.5), Vector2(100, 100)) +# Set up transforms just like in the image, except make positions be 100 times bigger. +var parent = Transform2D(Vector2(2, 0), Vector2(0, 1), Vector2(100, 200)) +var child = Transform2D(Vector2(0.5, 0), Vector2(0, 0.5), Vector2(100, 100)) - # Calculate the child's world space transform - # origin = (2, 0) * 100 + (0, 1) * 100 + (100, 200) - var origin = parent.x * child.origin.x + parent.y * child.origin.y + parent.origin - # basis_x = (2, 0) * 0.5 + (0, 1) * 0 - var basis_x = parent.x * child.x.x + parent.y * child.x.y - # basis_y = (2, 0) * 0 + (0, 1) * 0.5 - var basis_y = parent.x * child.y.x + parent.y * child.y.y +# Calculate the child's world space transform +# origin = (2, 0) * 100 + (0, 1) * 100 + (100, 200) +var origin = parent.x * child.origin.x + parent.y * child.origin.y + parent.origin +# basis_x = (2, 0) * 0.5 + (0, 1) * 0 +var basis_x = parent.x * child.x.x + parent.y * child.x.y +# basis_y = (2, 0) * 0 + (0, 1) * 0.5 +var basis_y = parent.x * child.y.x + parent.y * child.y.y - # Change the node's transform to what we just calculated. - transform = Transform2D(basis_x, basis_y, origin) +# Change the node's transform to what we just calculated. +transform = Transform2D(basis_x, basis_y, origin) ``` In actual projects, we can find the world transform of the child by @@ -448,12 +448,12 @@ applying one transform onto another using the `*` operator: gdscript GDScript ``` - # Set up transforms just like in the image, except make positions be 100 times bigger. - var parent = Transform2D(Vector2(2, 0), Vector2(0, 1), Vector2(100, 200)) - var child = Transform2D(Vector2(0.5, 0), Vector2(0, 0.5), Vector2(100, 100)) +# Set up transforms just like in the image, except make positions be 100 times bigger. +var parent = Transform2D(Vector2(2, 0), Vector2(0, 1), Vector2(100, 200)) +var child = Transform2D(Vector2(0.5, 0), Vector2(0, 0.5), Vector2(100, 100)) - # Change the node's transform to what would be the child's world transform. - transform = parent * child +# Change the node's transform to what would be the child's world transform. +transform = parent * child ``` Note: @@ -477,9 +477,9 @@ transformations: gdscript GDScript ``` - var ti = transform.affine_inverse() - var t = ti * transform - # The transform is the identity transform. +var ti = transform.affine_inverse() +var t = ti * transform +# The transform is the identity transform. ``` Transforming a position by a transform and its inverse results in the @@ -488,10 +488,10 @@ same position (same for "xform_inv"): gdscript GDScript ``` - var ti = transform.affine_inverse() - position = transform.xform(position) - position = ti.xform(position) - # The position is the same as before. +var ti = transform.affine_inverse() +position = transform.xform(position) +position = ti.xform(position) +# The position is the same as before. ``` ## How does it all work in 3D? diff --git a/03_usage/01_math/05_beziers_and_curves.md b/03_usage/01_math/05_beziers_and_curves.md index 579a500..d722291 100644 --- a/03_usage/01_math/05_beziers_and_curves.md +++ b/03_usage/01_math/05_beziers_and_curves.md @@ -27,9 +27,9 @@ change the value of `t` from 0 to 1. gdscript GDScript ``` - func _quadratic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, t: float): - var q0 = p0.linear_interpolate(p1, t) - var q1 = p1.linear_interpolate(p2, t) +func _quadratic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, t: float): + var q0 = p0.linear_interpolate(p1, t) + var q1 = p1.linear_interpolate(p2, t) ``` We then interpolate `q0` and `q1` to obtain a single point `r` that moves @@ -38,8 +38,8 @@ along a curve. gdscript GDScript ``` - var r = q0.linear_interpolate(q1, t) - return r +var r = q0.linear_interpolate(q1, t) +return r ``` This type of curve is called a *Quadratic Bezier* curve. @@ -61,7 +61,7 @@ We first use a function with four parameters to take four points as an input, gdscript GDScript ``` - func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float): +func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float): ``` We apply a linear interpolation to each couple of points to reduce them to @@ -70,9 +70,9 @@ three: gdscript GDScript ``` - var q0 = p0.linear_interpolate(p1, t) - var q1 = p1.linear_interpolate(p2, t) - var q2 = p2.linear_interpolate(p3, t) +var q0 = p0.linear_interpolate(p1, t) +var q1 = p1.linear_interpolate(p2, t) +var q2 = p2.linear_interpolate(p3, t) ``` We then take our three points and reduce them to two: @@ -80,8 +80,8 @@ We then take our three points and reduce them to two: gdscript GDScript ``` - var r0 = q0.linear_interpolate(q1, t) - var r1 = q1.linear_interpolate(q2, t) +var r0 = q0.linear_interpolate(q1, t) +var r1 = q1.linear_interpolate(q2, t) ``` And to one: @@ -89,8 +89,8 @@ And to one: gdscript GDScript ``` - var s = r0.linear_interpolate(r1, t) - return s +var s = r0.linear_interpolate(r1, t) +return s ``` Here is the full function: @@ -98,16 +98,16 @@ Here is the full function: gdscript GDScript ``` - func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float): - var q0 = p0.linear_interpolate(p1, t) - var q1 = p1.linear_interpolate(p2, t) - var q2 = p2.linear_interpolate(p3, t) +func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float): + var q0 = p0.linear_interpolate(p1, t) + var q1 = p1.linear_interpolate(p2, t) + var q2 = p2.linear_interpolate(p3, t) - var r0 = q0.linear_interpolate(q1, t) - var r1 = q1.linear_interpolate(q2, t) + var r0 = q0.linear_interpolate(q1, t) + var r1 = q1.linear_interpolate(q2, t) - var s = r0.linear_interpolate(r1, t) - return s + var s = r0.linear_interpolate(r1, t) + return s ``` The result will be a smooth curve interpolating between all four points: @@ -159,11 +159,11 @@ Let's do a simple example with the following pseudocode: gdscript GDScript ``` - var t = 0.0 +var t = 0.0 - func _process(delta): - t += delta - position = _cubic_bezier(p0, p1, p2, p3, t) +func _process(delta): + t += delta + position = _cubic_bezier(p0, p1, p2, p3, t) ``` ![](img/bezier_interpolation_speed.gif) @@ -197,11 +197,11 @@ Traversal at constant speed, then, can be done with the following pseudo-code: gdscript GDScript ``` - var t = 0.0 +var t = 0.0 - func _process(delta): - t += delta - position = curve.interpolate_baked(t * curve.get_baked_length(), true) +func _process(delta): + t += delta + position = curve.interpolate_baked(t * curve.get_baked_length(), true) ``` And the output will, then, move at constant speed: diff --git a/03_usage/01_math/06_vectors_advanced.md b/03_usage/01_math/06_vectors_advanced.md index 2e58473..961285f 100644 --- a/03_usage/01_math/06_vectors_advanced.md +++ b/03_usage/01_math/06_vectors_advanced.md @@ -37,7 +37,7 @@ the **distance from the point to the plane**: gdscript GDScript ``` - var distance = normal.dot(point) +var distance = normal.dot(point) ``` But not just the absolute distance, if the point is in the negative half @@ -75,7 +75,7 @@ to reach a point in the plane, you will just do: gdscript GDScript ``` - var point_in_plane = N*D +var point_in_plane = N*D ``` This will stretch (resize) the normal vector and make it touch the @@ -86,7 +86,7 @@ the plane, we do the same but adjusting for distance: gdscript GDScript ``` - var distance = N.dot(point) - D +var distance = N.dot(point) - D ``` The same thing, using a built-in function: @@ -94,7 +94,7 @@ The same thing, using a built-in function: gdscript GDScript ``` - var distance = plane.distance_to(point) +var distance = plane.distance_to(point) ``` This will, again, return either a positive or negative distance. @@ -106,8 +106,8 @@ inverted negative and positive half spaces: gdscript GDScript ``` - N = -N - D = -D +N = -N +D = -D ``` Of course, Pandemonium also implements this operator in `Plane`, @@ -116,7 +116,7 @@ so doing: gdscript GDScript ``` - var inverted_plane = -plane +var inverted_plane = -plane ``` Will work as expected. @@ -139,8 +139,8 @@ the normal and the point. gdscript GDScript ``` - var N = normal - var D = normal.dot(point) +var N = normal +var D = normal.dot(point) ``` For two points in space, there are actually two planes that pass through @@ -152,12 +152,12 @@ degrees to either side: gdscript GDScript ``` - # Calculate vector from `a` to `b`. - var dvec = (point_b - point_a).normalized() - # Rotate 90 degrees. - var normal = Vector2(dvec.y, -dvec.x) - # Alternatively (depending the desired side of the normal): - # var normal = Vector2(-dvec.y, dvec.x) +# Calculate vector from `a` to `b`. +var dvec = (point_b - point_a).normalized() +# Rotate 90 degrees. +var normal = Vector2(dvec.y, -dvec.x) +# Alternatively (depending the desired side of the normal): +# var normal = Vector2(-dvec.y, dvec.x) ``` The rest is the same as the previous example, either point_a or @@ -166,10 +166,10 @@ point_b will work since they are in the same plane: gdscript GDScript ``` - var N = normal - var D = normal.dot(point_a) - # this works the same - # var D = normal.dot(point_b) +var N = normal +var D = normal.dot(point_a) +# this works the same +# var D = normal.dot(point_b) ``` Doing the same in 3D is a little more complex and will be explained @@ -197,12 +197,12 @@ Code should be something like this: gdscript GDScript ``` - var inside = true - for p in planes: - # check if distance to plane is positive - if (p.distance_to(point) > 0): - inside = false - break # with one that fails, it's enough +var inside = true +for p in planes: + # check if distance to plane is positive + if (p.distance_to(point) > 0): + inside = false + break # with one that fails, it's enough ``` Pretty cool, huh? But this gets much better! With a little more effort, @@ -224,37 +224,37 @@ Code should be something like this: gdscript GDScript ``` - var overlapping = true +var overlapping = true - for p in planes_of_A: +for p in planes_of_A: + var all_out = true + for v in points_of_B: + if (p.distance_to(v) < 0): + all_out = false + break + + if (all_out): + # a separating plane was found + # do not continue testing + overlapping = false + break + +if (overlapping): + # only do this check if no separating plane + # was found in planes of A + for p in planes_of_B: var all_out = true - for v in points_of_B: + for v in points_of_A: if (p.distance_to(v) < 0): all_out = false break if (all_out): - # a separating plane was found - # do not continue testing overlapping = false break - if (overlapping): - # only do this check if no separating plane - # was found in planes of A - for p in planes_of_B: - var all_out = true - for v in points_of_A: - if (p.distance_to(v) < 0): - all_out = false - break - - if (all_out): - overlapping = false - break - - if (overlapping): - print("Polygons Collided!") +if (overlapping): + print("Polygons Collided!") ``` As you can see, planes are quite useful, and this is the tip of the @@ -301,73 +301,73 @@ So the final algorithm is something like: gdscript GDScript ``` - var overlapping = true +var overlapping = true - for p in planes_of_A: +for p in planes_of_A: + var all_out = true + for v in points_of_B: + if (p.distance_to(v) < 0): + all_out = false + break + + if (all_out): + # a separating plane was found + # do not continue testing + overlapping = false + break + +if (overlapping): + # only do this check if no separating plane + # was found in planes of A + for p in planes_of_B: var all_out = true - for v in points_of_B: + for v in points_of_A: if (p.distance_to(v) < 0): all_out = false break if (all_out): - # a separating plane was found - # do not continue testing overlapping = false break - if (overlapping): - # only do this check if no separating plane - # was found in planes of A - for p in planes_of_B: - var all_out = true - for v in points_of_A: - if (p.distance_to(v) < 0): - all_out = false - break +if (overlapping): + for ea in edges_of_A: + for eb in edges_of_B: + var n = ea.cross(eb) + if (n.length() == 0): + continue - if (all_out): + var max_A = -1e20 # tiny number + var min_A = 1e20 # huge number + + # we are using the dot product directly + # so we can map a maximum and minimum range + # for each polygon, then check if they + # overlap. + + for v in points_of_A: + var d = n.dot(v) + max_A = max(max_A, d) + min_A = min(min_A, d) + + var max_B = -1e20 # tiny number + var min_B = 1e20 # huge number + + for v in points_of_B: + var d = n.dot(v) + max_B = max(max_B, d) + min_B = min(min_B, d) + + if (min_A > max_B or min_B > max_A): + # not overlapping! overlapping = false break - if (overlapping): - for ea in edges_of_A: - for eb in edges_of_B: - var n = ea.cross(eb) - if (n.length() == 0): - continue + if (not overlapping): + break - var max_A = -1e20 # tiny number - var min_A = 1e20 # huge number - - # we are using the dot product directly - # so we can map a maximum and minimum range - # for each polygon, then check if they - # overlap. - - for v in points_of_A: - var d = n.dot(v) - max_A = max(max_A, d) - min_A = min(min_A, d) - - var max_B = -1e20 # tiny number - var min_B = 1e20 # huge number - - for v in points_of_B: - var d = n.dot(v) - max_B = max(max_B, d) - min_B = min(min_B, d) - - if (min_A > max_B or min_B > max_A): - # not overlapping! - overlapping = false - break - - if (not overlapping): - break - - if (overlapping): - print("Polygons collided!") +if (overlapping): + print("Polygons collided!") ``` ### More information diff --git a/03_usage/02_2d/02_2d_transforms.md b/03_usage/02_2d/02_2d_transforms.md index ad05b94..0286af7 100644 --- a/03_usage/02_2d/02_2d_transforms.md +++ b/03_usage/02_2d/02_2d_transforms.md @@ -64,7 +64,7 @@ coordinates, just multiply in the following order: gdscript GDScript ``` - var screen_coord = get_viewport_transform() * (get_global_transform() * local_pos) +var screen_coord = get_viewport_transform() * (get_global_transform() * local_pos) ``` Keep in mind, however, that it is generally not desired to work with @@ -81,9 +81,9 @@ way: gdscript GDScript ``` - var local_pos = Vector2(10, 20) # local to Control/Node2D - var ie = InputEventMouseButton.new() - ie.button_index = BUTTON_LEFT - ie.position = get_viewport_transform() * (get_global_transform() * local_pos) - get_tree().input_event(ie) +var local_pos = Vector2(10, 20) # local to Control/Node2D +var ie = InputEventMouseButton.new() +ie.button_index = BUTTON_LEFT +ie.position = get_viewport_transform() * (get_global_transform() * local_pos) +get_tree().input_event(ie) ``` diff --git a/03_usage/02_2d/03_particle_systems_2d.md b/03_usage/02_2d/03_particle_systems_2d.md index 082a944..f325290 100644 --- a/03_usage/02_2d/03_particle_systems_2d.md +++ b/03_usage/02_2d/03_particle_systems_2d.md @@ -110,7 +110,7 @@ All physics parameters can be randomized. Random values range from `0` to `1`. The formula to randomize a parameter is: ``` - initial_value = param_value + param_value * randomness +initial_value = param_value + param_value * randomness ``` ### Fixed FPS diff --git a/03_usage/02_2d/04_2d_movement.md b/03_usage/02_2d/04_2d_movement.md index dbc1afd..6853813 100644 --- a/03_usage/02_2d/04_2d_movement.md +++ b/03_usage/02_2d/04_2d_movement.md @@ -37,27 +37,27 @@ Add a script to the kinematic body and add the following code: gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - export (int) var speed = 200 +export (int) var speed = 200 - var velocity = Vector2() +var velocity = Vector2() - func get_input(): - velocity = Vector2() - if Input.is_action_pressed("right"): - velocity.x += 1 - if Input.is_action_pressed("left"): - velocity.x -= 1 - if Input.is_action_pressed("down"): - velocity.y += 1 - if Input.is_action_pressed("up"): - velocity.y -= 1 - velocity = velocity.normalized() * speed +func get_input(): + velocity = Vector2() + if Input.is_action_pressed("right"): + velocity.x += 1 + if Input.is_action_pressed("left"): + velocity.x -= 1 + if Input.is_action_pressed("down"): + velocity.y += 1 + if Input.is_action_pressed("up"): + velocity.y -= 1 + velocity = velocity.normalized() * speed - func _physics_process(delta): - get_input() - velocity = move_and_slide(velocity) +func _physics_process(delta): + get_input() + velocity = move_and_slide(velocity) ``` In the `get_input()` function, we check for the four key events and sum them @@ -90,30 +90,30 @@ while up/down moves it forward or backward in whatever direction it's facing. gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - export (int) var speed = 200 - export (float) var rotation_speed = 1.5 +export (int) var speed = 200 +export (float) var rotation_speed = 1.5 - var velocity = Vector2() - var rotation_dir = 0 +var velocity = Vector2() +var rotation_dir = 0 - func get_input(): - rotation_dir = 0 - velocity = Vector2() - if Input.is_action_pressed("right"): - rotation_dir += 1 - if Input.is_action_pressed("left"): - rotation_dir -= 1 - if Input.is_action_pressed("down"): - velocity = Vector2(-speed, 0).rotated(rotation) - if Input.is_action_pressed("up"): - velocity = Vector2(speed, 0).rotated(rotation) +func get_input(): + rotation_dir = 0 + velocity = Vector2() + if Input.is_action_pressed("right"): + rotation_dir += 1 + if Input.is_action_pressed("left"): + rotation_dir -= 1 + if Input.is_action_pressed("down"): + velocity = Vector2(-speed, 0).rotated(rotation) + if Input.is_action_pressed("up"): + velocity = Vector2(speed, 0).rotated(rotation) - func _physics_process(delta): - get_input() - rotation += rotation_dir * rotation_speed * delta - velocity = move_and_slide(velocity) +func _physics_process(delta): + get_input() + rotation += rotation_dir * rotation_speed * delta + velocity = move_and_slide(velocity) ``` Here we've added two new variables to track our rotation direction and speed. @@ -136,23 +136,23 @@ is set by the mouse position instead of the keyboard. The character will always gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - export (int) var speed = 200 +export (int) var speed = 200 - var velocity = Vector2() +var velocity = Vector2() - func get_input(): - look_at(get_global_mouse_position()) - velocity = Vector2() - if Input.is_action_pressed("down"): - velocity = Vector2(-speed, 0).rotated(rotation) - if Input.is_action_pressed("up"): - velocity = Vector2(speed, 0).rotated(rotation) +func get_input(): + look_at(get_global_mouse_position()) + velocity = Vector2() + if Input.is_action_pressed("down"): + velocity = Vector2(-speed, 0).rotated(rotation) + if Input.is_action_pressed("up"): + velocity = Vector2(speed, 0).rotated(rotation) - func _physics_process(delta): - get_input() - velocity = move_and_slide(velocity) +func _physics_process(delta): + get_input() + velocity = move_and_slide(velocity) ``` Here we're using the `Node2D` `look_at()` method to @@ -162,7 +162,7 @@ could get the same effect by setting the angle like this: gdscript GDScript ``` - rotation = get_global_mouse_position().angle_to_point(position) +rotation = get_global_mouse_position().angle_to_point(position) ``` @@ -176,22 +176,22 @@ on the screen will cause the player to move to the target location. gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - export (int) var speed = 200 +export (int) var speed = 200 - onready var target = position - var velocity = Vector2() +onready var target = position +var velocity = Vector2() - func _input(event): - if event.is_action_pressed("click"): - target = get_global_mouse_position() +func _input(event): + if event.is_action_pressed("click"): + target = get_global_mouse_position() - func _physics_process(delta): - velocity = position.direction_to(target) * speed - # look_at(target) - if position.distance_to(target) > 5: - velocity = move_and_slide(velocity) +func _physics_process(delta): + velocity = position.direction_to(target) * speed + # look_at(target) + if position.distance_to(target) > 5: + velocity = move_and_slide(velocity) ``` diff --git a/03_usage/02_2d/07_custom_drawing_in_2d.md b/03_usage/02_2d/07_custom_drawing_in_2d.md index 08ca956..35b3a09 100644 --- a/03_usage/02_2d/07_custom_drawing_in_2d.md +++ b/03_usage/02_2d/07_custom_drawing_in_2d.md @@ -34,11 +34,11 @@ derived node, like `Control` or gdscript GDScript ``` - extends Node2D +extends Node2D - func _draw(): - # Your draw commands here - pass +func _draw(): + # Your draw commands here + pass ``` Draw commands are described in the `CanvasItem` @@ -59,18 +59,18 @@ redrawn if modified: gdscript GDScript ``` - extends Node2D +extends Node2D - export (Texture) var texture setget _set_texture +export (Texture) var texture setget _set_texture - func _set_texture(value): - # If the texture variable is modified externally, - # this callback is called. - texture = value # Texture was changed. - update() # Update the node's visual representation. +func _set_texture(value): + # If the texture variable is modified externally, + # this callback is called. + texture = value # Texture was changed. + update() # Update the node's visual representation. - func _draw(): - draw_texture(texture, Vector2()) +func _draw(): + draw_texture(texture, Vector2()) ``` In some cases, it may be desired to draw every frame. For this, just @@ -79,14 +79,14 @@ call `update()` from the `process()` callback, like this: gdscript GDScript ``` - extends Node2D +extends Node2D - func _draw(): - # Your draw commands here - pass +func _draw(): + # Your draw commands here + pass - func _process(delta): - update() +func _process(delta): + update() ``` @@ -115,16 +115,16 @@ In our example, we will simply use a fixed number of points, no matter the radiu gdscript GDScript ``` - func draw_circle_arc(center, radius, angle_from, angle_to, color): - var nb_points = 32 - var points_arc = PoolVector2Array() +func draw_circle_arc(center, radius, angle_from, angle_to, color): + var nb_points = 32 + var points_arc = PoolVector2Array() - for i in range(nb_points + 1): - var angle_point = deg2rad(angle_from + i * (angle_to-angle_from) / nb_points - 90) - points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius) + for i in range(nb_points + 1): + var angle_point = deg2rad(angle_from + i * (angle_to-angle_from) / nb_points - 90) + points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius) - for index_point in range(nb_points): - draw_line(points_arc[index_point], points_arc[index_point + 1], color) + for index_point in range(nb_points): + draw_line(points_arc[index_point], points_arc[index_point + 1], color) ``` @@ -168,13 +168,13 @@ it is time to call it inside the `draw()` function: gdscript GDScript ``` - func _draw(): - var center = Vector2(200, 200) - var radius = 80 - var angle_from = 75 - var angle_to = 195 - var color = Color(1.0, 0.0, 0.0) - draw_circle_arc(center, radius, angle_from, angle_to, color) +func _draw(): + var center = Vector2(200, 200) + var radius = 80 + var angle_from = 75 + var angle_to = 195 + var color = Color(1.0, 0.0, 0.0) + draw_circle_arc(center, radius, angle_from, angle_to, color) ``` Result: @@ -190,16 +190,16 @@ the same as before, except that we draw a polygon instead of lines: gdscript GDScript ``` - func draw_circle_arc_poly(center, radius, angle_from, angle_to, color): - var nb_points = 32 - var points_arc = PoolVector2Array() - points_arc.push_back(center) - var colors = PoolColorArray([color]) +func draw_circle_arc_poly(center, radius, angle_from, angle_to, color): + var nb_points = 32 + var points_arc = PoolVector2Array() + points_arc.push_back(center) + var colors = PoolColorArray([color]) - for i in range(nb_points + 1): - var angle_point = deg2rad(angle_from + i * (angle_to - angle_from) / nb_points - 90) - points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius) - draw_polygon(points_arc, colors) + for i in range(nb_points + 1): + var angle_point = deg2rad(angle_from + i * (angle_to - angle_from) / nb_points - 90) + points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius) + draw_polygon(points_arc, colors) ``` ![](img/result_drawarc_poly.png) @@ -219,11 +219,11 @@ using `get_node()`. gdscript GDScript ``` - extends Node2D +extends Node2D - var rotation_angle = 50 - var angle_from = 75 - var angle_to = 195 +var rotation_angle = 50 +var angle_from = 75 +var angle_to = 195 ``` We make these values change in the _process(delta) function. @@ -241,15 +241,15 @@ calls `draw()`. This way, you can control when you want to refresh the frame. gdscript GDScript ``` - func _process(delta): - angle_from += rotation_angle - angle_to += rotation_angle +func _process(delta): + angle_from += rotation_angle + angle_to += rotation_angle - # We only wrap angles when both of them are bigger than 360. - if angle_from > 360 and angle_to > 360: - angle_from = wrapf(angle_from, 0, 360) - angle_to = wrapf(angle_to, 0, 360) - update() + # We only wrap angles when both of them are bigger than 360. + if angle_from > 360 and angle_to > 360: + angle_from = wrapf(angle_from, 0, 360) + angle_to = wrapf(angle_to, 0, 360) + update() ``` Also, don't forget to modify the `draw()` function to make use of these variables: @@ -257,12 +257,12 @@ Also, don't forget to modify the `draw()` function to make use of these variable gdscript GDScript ``` - func _draw(): - var center = Vector2(200, 200) - var radius = 80 - var color = Color(1.0, 0.0, 0.0) +func _draw(): + var center = Vector2(200, 200) + var radius = 80 + var color = Color(1.0, 0.0, 0.0) - draw_circle_arc( center, radius, angle_from, angle_to, color ) + draw_circle_arc( center, radius, angle_from, angle_to, color ) ``` Let's run! @@ -283,15 +283,15 @@ smaller value, which directly depends on the rendering speed. gdscript GDScript ``` - func _process(delta): - angle_from += rotation_angle * delta - angle_to += rotation_angle * delta +func _process(delta): + angle_from += rotation_angle * delta + angle_to += rotation_angle * delta - # We only wrap angles when both of them are bigger than 360. - if angle_from > 360 and angle_to > 360: - angle_from = wrapf(angle_from, 0, 360) - angle_to = wrapf(angle_to, 0, 360) - update() + # We only wrap angles when both of them are bigger than 360. + if angle_from > 360 and angle_to > 360: + angle_from = wrapf(angle_from, 0, 360) + angle_to = wrapf(angle_to, 0, 360) + update() ``` Let's run again! This time, the rotation displays fine! diff --git a/03_usage/02_2d/08_2d_sprite_animation.md b/03_usage/02_2d/08_2d_sprite_animation.md index 65764e8..c300b5d 100644 --- a/03_usage/02_2d/08_2d_sprite_animation.md +++ b/03_usage/02_2d/08_2d_sprite_animation.md @@ -75,15 +75,15 @@ released. gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - onready var _animated_sprite = $AnimatedSprite +onready var _animated_sprite = $AnimatedSprite - func _process(_delta): - if Input.is_action_pressed("ui_right"): - _animated_sprite.play("run") - else: - _animated_sprite.stop() +func _process(_delta): + if Input.is_action_pressed("ui_right"): + _animated_sprite.play("run") + else: + _animated_sprite.stop() ``` @@ -189,15 +189,15 @@ released. gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - onready var _animation_player = $AnimationPlayer +onready var _animation_player = $AnimationPlayer - func _process(_delta): - if Input.is_action_pressed("ui_right"): - _animation_player.play("walk") - else: - _animation_player.stop() +func _process(_delta): + if Input.is_action_pressed("ui_right"): + _animation_player.play("walk") + else: + _animation_player.stop() ``` Note: diff --git a/03_usage/03_3d/02_using_transforms.md b/03_usage/03_3d/02_using_transforms.md index 5c4c285..9bfc066 100644 --- a/03_usage/03_3d/02_using_transforms.md +++ b/03_usage/03_3d/02_using_transforms.md @@ -90,11 +90,11 @@ A default basis (unmodified) is akin to: gdscript GDScript ``` - var basis = Basis() - # Contains the following default values: - basis.x = Vector3(1, 0, 0) # Vector pointing along the X axis - basis.y = Vector3(0, 1, 0) # Vector pointing along the Y axis - basis.z = Vector3(0, 0, 1) # Vector pointing along the Z axis +var basis = Basis() +# Contains the following default values: +basis.x = Vector3(1, 0, 0) # Vector pointing along the X axis +basis.y = Vector3(0, 1, 0) # Vector pointing along the Y axis +basis.z = Vector3(0, 0, 1) # Vector pointing along the Z axis ``` This is also an analog of a 3x3 identity matrix. @@ -125,12 +125,12 @@ It is possible to rotate a transform, either by multiplying its basis by another gdscript GDScript ``` - var axis = Vector3(1, 0, 0) # Or Vector3.RIGHT - var rotation_amount = 0.1 - # Rotate the transform around the X axis by 0.1 radians. - transform.basis = Basis(axis, rotation_amount) * transform.basis - # shortened - transform.basis = transform.basis.rotated(axis, rotation_amount) +var axis = Vector3(1, 0, 0) # Or Vector3.RIGHT +var rotation_amount = 0.1 +# Rotate the transform around the X axis by 0.1 radians. +transform.basis = Basis(axis, rotation_amount) * transform.basis +# shortened +transform.basis = transform.basis.rotated(axis, rotation_amount) ``` A method in Spatial simplifies this: @@ -138,10 +138,10 @@ A method in Spatial simplifies this: gdscript GDScript ``` - # Rotate the transform around the X axis by 0.1 radians. - rotate(Vector3(1, 0, 0), 0.1) - # shortened - rotate_x(0.1) +# Rotate the transform around the X axis by 0.1 radians. +rotate(Vector3(1, 0, 0), 0.1) +# shortened +rotate_x(0.1) ``` This rotates the node relative to the parent node. @@ -151,8 +151,8 @@ To rotate relative to object space (the node's own transform), use the following gdscript GDScript ``` - # Rotate around the object's local X axis by 0.1 radians. - rotate_object_local(Vector3(1, 0, 0), 0.1) +# Rotate around the object's local X axis by 0.1 radians. +rotate_object_local(Vector3(1, 0, 0), 0.1) ``` # Precision errors @@ -166,7 +166,7 @@ There are two different ways to handle this. The first is to *orthonormalize* th gdscript GDScript ``` - transform = transform.orthonormalized() +transform = transform.orthonormalized() ``` This will make all axes have `1.0` length again and be `90` degrees from each other. However, any scale applied to the transform will be lost. @@ -176,8 +176,8 @@ It is recommended you not scale nodes that are going to be manipulated; scale th gdscript GDScript ``` - transform = transform.orthonormalized() - transform = transform.scaled(scale) +transform = transform.orthonormalized() +transform = transform.scaled(scale) ``` # Obtaining information @@ -189,8 +189,8 @@ Imagine you need to shoot a bullet in the direction your player is facing. Just gdscript GDScript ``` - bullet.transform = transform - bullet.speed = transform.basis.z * BULLET_SPEED +bullet.transform = transform +bullet.speed = transform.basis.z * BULLET_SPEED ``` Is the enemy looking at the player? Use the dot product for this (see the `doc_vector_math` tutorial for an explanation of the dot product): @@ -198,10 +198,10 @@ Is the enemy looking at the player? Use the dot product for this (see the `doc_v gdscript GDScript ``` - # Get the direction vector from player to enemy - var direction = enemy.transform.origin - player.transform.origin - if direction.dot(enemy.transform.basis.z) > 0: - enemy.im_watching_you(player) +# Get the direction vector from player to enemy +var direction = enemy.transform.origin - player.transform.origin +if direction.dot(enemy.transform.basis.z) > 0: + enemy.im_watching_you(player) ``` Strafe left: @@ -209,9 +209,9 @@ Strafe left: gdscript GDScript ``` - # Remember that +X is right - if Input.is_action_pressed("strafe_left"): - translate_object_local(-transform.basis.x) +# Remember that +X is right +if Input.is_action_pressed("strafe_left"): + translate_object_local(-transform.basis.x) ``` Jump: @@ -219,11 +219,11 @@ Jump: gdscript GDScript ``` - # Keep in mind Y is up-axis - if Input.is_action_just_pressed("jump"): - velocity.y = JUMP_SPEED +# Keep in mind Y is up-axis +if Input.is_action_just_pressed("jump"): + velocity.y = JUMP_SPEED - velocity = move_and_slide(velocity) +velocity = move_and_slide(velocity) ``` All common behaviors and logic can be done with just vectors. @@ -239,18 +239,18 @@ Example of looking around, FPS style: gdscript GDScript ``` - # accumulators - var rot_x = 0 - var rot_y = 0 +# accumulators +var rot_x = 0 +var rot_y = 0 - func _input(event): - if event is InputEventMouseMotion and event.button_mask & 1: - # modify accumulated mouse rotation - rot_x += event.relative.x * LOOKAROUND_SPEED - rot_y += event.relative.y * LOOKAROUND_SPEED - transform.basis = Basis() # reset rotation - rotate_object_local(Vector3(0, 1, 0), rot_x) # first rotate in Y - rotate_object_local(Vector3(1, 0, 0), rot_y) # then rotate in X +func _input(event): + if event is InputEventMouseMotion and event.button_mask & 1: + # modify accumulated mouse rotation + rot_x += event.relative.x * LOOKAROUND_SPEED + rot_y += event.relative.y * LOOKAROUND_SPEED + transform.basis = Basis() # reset rotation + rotate_object_local(Vector3(0, 1, 0), rot_x) # first rotate in Y + rotate_object_local(Vector3(1, 0, 0), rot_y) # then rotate in X ``` As you can see, in such cases it's even simpler to keep the rotation outside, then use the transform as the *final* orientation. @@ -264,13 +264,13 @@ Converting a rotation to quaternion is straightforward. gdscript GDScript ``` - # Convert basis to quaternion, keep in mind scale is lost - var a = Quat(transform.basis) - var b = Quat(transform2.basis) - # Interpolate using spherical-linear interpolation (SLERP). - var c = a.slerp(b,0.5) # find halfway point between a and b - # Apply back - transform.basis = Basis(c) +# Convert basis to quaternion, keep in mind scale is lost +var a = Quat(transform.basis) +var b = Quat(transform2.basis) +# Interpolate using spherical-linear interpolation (SLERP). +var c = a.slerp(b,0.5) # find halfway point between a and b +# Apply back +transform.basis = Basis(c) ``` The `Quat` type reference has more information on the datatype (it diff --git a/03_usage/03_3d/portals/04_advanced_room_and_portal_usage.md b/03_usage/03_3d/portals/04_advanced_room_and_portal_usage.md index 0b46fbb..e232ba3 100644 --- a/03_usage/03_3d/portals/04_advanced_room_and_portal_usage.md +++ b/03_usage/03_3d/portals/04_advanced_room_and_portal_usage.md @@ -22,12 +22,12 @@ You can choose to either receive these callbacks as `signals`, or as `notificati Notifications can be handled in GDScript or other scripting languages: ``` - func _notification(what): - match what: - NOTIFICATION_ENTER_GAMEPLAY: - print("notification enter gameplay") - NOTIFICATION_EXIT_GAMEPLAY: - print("notification exit gameplay") +func _notification(what): + match what: + NOTIFICATION_ENTER_GAMEPLAY: + print("notification enter gameplay") + NOTIFICATION_EXIT_GAMEPLAY: + print("notification exit gameplay") ``` Signals are sent just as any other signal. They can be attached to functions using the editor inspector. The signals are called `gameplay_entered` and `gameplay_exited`. diff --git a/03_usage/03_3d/procedural_geometry/02_arraymesh.md b/03_usage/03_3d/procedural_geometry/02_arraymesh.md index de54bc8..8a0c7c8 100644 --- a/03_usage/03_3d/procedural_geometry/02_arraymesh.md +++ b/03_usage/03_3d/procedural_geometry/02_arraymesh.md @@ -52,7 +52,7 @@ Under `ready()`, create a new Array. gdscript GDScript ``` - var surface_array = [] +var surface_array = [] ``` This will be the array that we keep our surface information in - it will hold @@ -62,8 +62,8 @@ size `Mesh.ARRAY_MAX`, so resize it accordingly. gdscript GDScript ``` - var surface_array = [] - surface_array.resize(Mesh.ARRAY_MAX) +var surface_array = [] +surface_array.resize(Mesh.ARRAY_MAX) ``` Next create the arrays for each data type you will use. @@ -71,10 +71,10 @@ Next create the arrays for each data type you will use. gdscript GDScript ``` - var verts = PoolVector3Array() - var uvs = PoolVector2Array() - var normals = PoolVector3Array() - var indices = PoolIntArray() +var verts = PoolVector3Array() +var uvs = PoolVector2Array() +var normals = PoolVector3Array() +var indices = PoolIntArray() ``` Once you have filled your data arrays with your geometry you can create a mesh @@ -83,12 +83,12 @@ by adding each array to `surface_array` and then committing to the mesh. gdscript GDScript ``` - surface_array[Mesh.ARRAY_VERTEX] = verts - surface_array[Mesh.ARRAY_TEX_UV] = uvs - surface_array[Mesh.ARRAY_NORMAL] = normals - surface_array[Mesh.ARRAY_INDEX] = indices +surface_array[Mesh.ARRAY_VERTEX] = verts +surface_array[Mesh.ARRAY_TEX_UV] = uvs +surface_array[Mesh.ARRAY_NORMAL] = normals +surface_array[Mesh.ARRAY_INDEX] = indices - mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array) # No blendshapes or compression used. +mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array) # No blendshapes or compression used. ``` Note: @@ -100,30 +100,30 @@ Put together, the full code looks like: gdscript GDScript ``` - extends MeshInstance +extends MeshInstance - func _ready(): - var surface_array= [] - surface_array.resize(Mesh.ARRAY_MAX) +func _ready(): + var surface_array= [] + surface_array.resize(Mesh.ARRAY_MAX) - # PoolVector**Arrays for mesh construction. - var verts = PoolVector3Array() - var uvs = PoolVector2Array() - var normals = PoolVector3Array() - var indices = PoolIntArray() + # PoolVector**Arrays for mesh construction. + var verts = PoolVector3Array() + var uvs = PoolVector2Array() + var normals = PoolVector3Array() + var indices = PoolIntArray() - ####################################### - ## Insert code here to generate mesh ## - ####################################### + ####################################### + ## Insert code here to generate mesh ## + ####################################### - # Assign arrays to mesh array. - surface_array[Mesh.ARRAY_VERTEX] = verts - surface_array[Mesh.ARRAY_TEX_UV] = uvs - surface_array[Mesh.ARRAY_NORMAL] = normals - surface_array[Mesh.ARRAY_INDEX] = indices + # Assign arrays to mesh array. + surface_array[Mesh.ARRAY_VERTEX] = verts + surface_array[Mesh.ARRAY_TEX_UV] = uvs + surface_array[Mesh.ARRAY_NORMAL] = normals + surface_array[Mesh.ARRAY_INDEX] = indices - # Create mesh surface from mesh array. - mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array) # No blendshapes or compression used. + # Create mesh surface from mesh array. + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array) # No blendshapes or compression used. ``` The code that goes in the middle can be whatever you want. Below we will present some example code @@ -141,62 +141,62 @@ that you find online. gdscript GDScript ``` - extends MeshInstance +extends MeshInstance - var rings = 50 - var radial_segments = 50 - var height = 1 - var radius = 1 +var rings = 50 +var radial_segments = 50 +var height = 1 +var radius = 1 - func _ready(): +func _ready(): - # Insert setting up the PoolVector**Arrays here. + # Insert setting up the PoolVector**Arrays here. - # Vertex indices. - var thisrow = 0 - var prevrow = 0 - var point = 0 + # Vertex indices. + var thisrow = 0 + var prevrow = 0 + var point = 0 - # Loop over rings. - for i in range(rings + 1): - var v = float(i) / rings - var w = sin(PI * v) - var y = cos(PI * v) + # Loop over rings. + for i in range(rings + 1): + var v = float(i) / rings + var w = sin(PI * v) + var y = cos(PI * v) - # Loop over segments in ring. - for j in range(radial_segments): - var u = float(j) / radial_segments - var x = sin(u * PI * 2.0) - var z = cos(u * PI * 2.0) - var vert = Vector3(x * radius * w, y, z * radius * w) - verts.append(vert) - normals.append(vert.normalized()) - uvs.append(Vector2(u, v)) - point += 1 + # Loop over segments in ring. + for j in range(radial_segments): + var u = float(j) / radial_segments + var x = sin(u * PI * 2.0) + var z = cos(u * PI * 2.0) + var vert = Vector3(x * radius * w, y, z * radius * w) + verts.append(vert) + normals.append(vert.normalized()) + uvs.append(Vector2(u, v)) + point += 1 - # Create triangles in ring using indices. - if i > 0 and j > 0: - indices.append(prevrow + j - 1) - indices.append(prevrow + j) - indices.append(thisrow + j - 1) + # Create triangles in ring using indices. + if i > 0 and j > 0: + indices.append(prevrow + j - 1) + indices.append(prevrow + j) + indices.append(thisrow + j - 1) - indices.append(prevrow + j) - indices.append(thisrow + j) - indices.append(thisrow + j - 1) + indices.append(prevrow + j) + indices.append(thisrow + j) + indices.append(thisrow + j - 1) - if i > 0: - indices.append(prevrow + radial_segments - 1) - indices.append(prevrow) - indices.append(thisrow + radial_segments - 1) + if i > 0: + indices.append(prevrow + radial_segments - 1) + indices.append(prevrow) + indices.append(thisrow + radial_segments - 1) - indices.append(prevrow) - indices.append(prevrow + radial_segments) - indices.append(thisrow + radial_segments - 1) + indices.append(prevrow) + indices.append(prevrow + radial_segments) + indices.append(thisrow + radial_segments - 1) - prevrow = thisrow - thisrow = point + prevrow = thisrow + thisrow = point - # Insert committing to the ArrayMesh here. + # Insert committing to the ArrayMesh here. ``` ## Saving @@ -207,6 +207,6 @@ This is useful when you want to generate a mesh and then use it later without ha gdscript GDScript ``` - # Saves mesh to a .tres file with compression enabled. - ResourceSaver.save("res://sphere.tres", mesh, ResourceSaver.FLAG_COMPRESS) +# Saves mesh to a .tres file with compression enabled. +ResourceSaver.save("res://sphere.tres", mesh, ResourceSaver.FLAG_COMPRESS) ``` \ No newline at end of file diff --git a/03_usage/03_3d/procedural_geometry/03_meshdatatool.md b/03_usage/03_3d/procedural_geometry/03_meshdatatool.md index a7c9901..b9606df 100644 --- a/03_usage/03_3d/procedural_geometry/03_meshdatatool.md +++ b/03_usage/03_3d/procedural_geometry/03_meshdatatool.md @@ -21,8 +21,8 @@ In the examples below, assume an ArrayMesh called `mesh` has already been create gdscript GDScript ``` - var mdt = MeshDataTool.new() - mdt.create_from_surface(mesh, 0) +var mdt = MeshDataTool.new() +mdt.create_from_surface(mesh, 0) ``` `create_from_surface()` uses the vertex arrays from the ArrayMesh to calculate two additional arrays, @@ -42,10 +42,10 @@ To access information from these arrays you use a function of the form `get_**** gdscript GDScript ``` - mdt.get_vertex_count() # Returns number of vertices in vertex array. - mdt.get_vertex_faces(0) # Returns array of faces that contain vertex[0]. - mdt.get_face_normal(1) # Calculates and returns face normal of the second face. - mdt.get_edge_vertex(10, 1) # Returns the second vertex comprising the edge at index 10. +mdt.get_vertex_count() # Returns number of vertices in vertex array. +mdt.get_vertex_faces(0) # Returns array of faces that contain vertex[0]. +mdt.get_face_normal(1) # Calculates and returns face normal of the second face. +mdt.get_edge_vertex(10, 1) # Returns the second vertex comprising the edge at index 10. ``` What you choose to do with these functions is up to you. A common use case is to iterate over all vertices @@ -54,10 +54,10 @@ and transform them in some way: gdscript GDScript ``` - for i in range(get_vertex_count): - var vert = mdt.get_vertex(i) - vert *= 2.0 # Scales the vertex by doubling size. - mdt.set_vertex(i, vert) +for i in range(get_vertex_count): + var vert = mdt.get_vertex(i) + vert *= 2.0 # Scales the vertex by doubling size. + mdt.set_vertex(i, vert) ``` These modifications are not done in place on the ArrayMesh. If you are dynamically updating an existing ArrayMesh, @@ -66,8 +66,8 @@ first delete the existing surface before adding a new one using `commit_to_surfa gdscript GDScript ``` - mesh.surface_remove(0) # Deletes the first surface of the mesh. - mdt.commit_to_surface(mesh) +mesh.surface_remove(0) # Deletes the first surface of the mesh. +mdt.commit_to_surface(mesh) ``` Below is a complete example that turns a spherical mesh called `mesh` into a randomly deformed blob complete with updated normals and vertex colors. @@ -76,47 +76,47 @@ See `ArrayMesh tutorial ( doc_arraymesh )` for how to generate the base mesh. gdscript GDScript ``` - extends MeshInstance +extends MeshInstance - var sn = OpenSimplexNoise.new() - var mdt = MeshDataTool.new() +var sn = OpenSimplexNoise.new() +var mdt = MeshDataTool.new() - func _ready(): - sn.period = 0.7 +func _ready(): + sn.period = 0.7 - mdt.create_from_surface(mesh, 0) + mdt.create_from_surface(mesh, 0) - for i in range(mdt.get_vertex_count()): - var vertex = mdt.get_vertex(i).normalized() - # Push out vertex by noise. - vertex = vertex * (sn.get_noise_3dv(vertex) * 0.5 + 0.75) - mdt.set_vertex(i, vertex) + for i in range(mdt.get_vertex_count()): + var vertex = mdt.get_vertex(i).normalized() + # Push out vertex by noise. + vertex = vertex * (sn.get_noise_3dv(vertex) * 0.5 + 0.75) + mdt.set_vertex(i, vertex) - # Calculate vertex normals, face-by-face. - for i in range(mdt.get_face_count()): - # Get the index in the vertex array. - var a = mdt.get_face_vertex(i, 0) - var b = mdt.get_face_vertex(i, 1) - var c = mdt.get_face_vertex(i, 2) - # Get vertex position using vertex index. - var ap = mdt.get_vertex(a) - var bp = mdt.get_vertex(b) - var cp = mdt.get_vertex(c) - # Calculate face normal. - var n = (bp - cp).cross(ap - bp).normalized() - # Add face normal to current vertex normal. - # This will not result in perfect normals, but it will be close. - mdt.set_vertex_normal(a, n + mdt.get_vertex_normal(a)) - mdt.set_vertex_normal(b, n + mdt.get_vertex_normal(b)) - mdt.set_vertex_normal(c, n + mdt.get_vertex_normal(c)) + # Calculate vertex normals, face-by-face. + for i in range(mdt.get_face_count()): + # Get the index in the vertex array. + var a = mdt.get_face_vertex(i, 0) + var b = mdt.get_face_vertex(i, 1) + var c = mdt.get_face_vertex(i, 2) + # Get vertex position using vertex index. + var ap = mdt.get_vertex(a) + var bp = mdt.get_vertex(b) + var cp = mdt.get_vertex(c) + # Calculate face normal. + var n = (bp - cp).cross(ap - bp).normalized() + # Add face normal to current vertex normal. + # This will not result in perfect normals, but it will be close. + mdt.set_vertex_normal(a, n + mdt.get_vertex_normal(a)) + mdt.set_vertex_normal(b, n + mdt.get_vertex_normal(b)) + mdt.set_vertex_normal(c, n + mdt.get_vertex_normal(c)) - # Run through vertices one last time to normalize normals and - # set color to normal. - for i in range(mdt.get_vertex_count()): - var v = mdt.get_vertex_normal(i).normalized() - mdt.set_vertex_normal(i, v) - mdt.set_vertex_color(i, Color(v.x, v.y, v.z)) + # Run through vertices one last time to normalize normals and + # set color to normal. + for i in range(mdt.get_vertex_count()): + var v = mdt.get_vertex_normal(i).normalized() + mdt.set_vertex_normal(i, v) + mdt.set_vertex_color(i, Color(v.x, v.y, v.z)) - mesh.surface_remove(0) - mdt.commit_to_surface(mesh) + mesh.surface_remove(0) + mdt.commit_to_surface(mesh) ``` \ No newline at end of file diff --git a/03_usage/03_3d/procedural_geometry/04_surfacetool.md b/03_usage/03_3d/procedural_geometry/04_surfacetool.md index d6281ec..22f22d5 100644 --- a/03_usage/03_3d/procedural_geometry/04_surfacetool.md +++ b/03_usage/03_3d/procedural_geometry/04_surfacetool.md @@ -13,11 +13,11 @@ Attributes are added before each vertex is added: gdscript GDScript ``` - st.add_normal() # Overwritten by normal below. - st.add_normal() # Added to next vertex. - st.add_color() # Added to next vertex. - st.add_vertex() # Captures normal and color above. - st.add_normal() # Normal never added to a vertex. +st.add_normal() # Overwritten by normal below. +st.add_normal() # Added to next vertex. +st.add_color() # Added to next vertex. +st.add_vertex() # Captures normal and color above. +st.add_normal() # Normal never added to a vertex. ``` When finished generating your geometry with the `SurfaceTool` @@ -28,9 +28,9 @@ in, `commit()` returns an ArrayMesh. gdscript GDScript ``` - st.commit(mesh) - # Or: - var mesh = st.commit() +st.commit(mesh) +# Or: +var mesh = st.commit() ``` Code creates a triangle with indices @@ -38,26 +38,26 @@ Code creates a triangle with indices gdscript GDScript ``` - var st = SurfaceTool.new() +var st = SurfaceTool.new() - st.begin(Mesh.PRIMITIVE_TRIANGLES) +st.begin(Mesh.PRIMITIVE_TRIANGLES) - # Prepare attributes for add_vertex. - st.add_normal(Vector3(0, 0, 1)) - st.add_uv(Vector2(0, 0)) - # Call last for each vertex, adds the above attributes. - st.add_vertex(Vector3(-1, -1, 0)) +# Prepare attributes for add_vertex. +st.add_normal(Vector3(0, 0, 1)) +st.add_uv(Vector2(0, 0)) +# Call last for each vertex, adds the above attributes. +st.add_vertex(Vector3(-1, -1, 0)) - st.add_normal(Vector3(0, 0, 1)) - st.add_uv(Vector2(0, 1)) - st.add_vertex(Vector3(-1, 1, 0)) +st.add_normal(Vector3(0, 0, 1)) +st.add_uv(Vector2(0, 1)) +st.add_vertex(Vector3(-1, 1, 0)) - st.add_normal(Vector3(0, 0, 1)) - st.add_uv(Vector2(1, 1)) - st.add_vertex(Vector3(1, 1, 0)) +st.add_normal(Vector3(0, 0, 1)) +st.add_uv(Vector2(1, 1)) +st.add_vertex(Vector3(1, 1, 0)) - # Commit to a mesh. - var mesh = st.commit() +# Commit to a mesh. +var mesh = st.commit() ``` You can optionally add an index array, either by calling `add_index()` and adding @@ -67,18 +67,18 @@ to remove duplicate vertices. gdscript GDScript ``` - # Creates a quad from four corner vertices. - # Add_index does not need to be called before add_vertex. - st.add_index(0) - st.add_index(1) - st.add_index(2) +# Creates a quad from four corner vertices. +# Add_index does not need to be called before add_vertex. +st.add_index(0) +st.add_index(1) +st.add_index(2) - st.add_index(1) - st.add_index(3) - st.add_index(2) +st.add_index(1) +st.add_index(3) +st.add_index(2) - # Alternatively: - st.index() +# Alternatively: +st.index() ``` Similarly, if you have an index array, but you want each vertex to be unique (e.g. because @@ -87,7 +87,7 @@ you want to use unique normals or colors per face instead of per-vertex), you ca gdscript GDScript ``` - st.deindex() +st.deindex() ``` If you don't add custom normals yourself, you can add them using `generate_normals()`, which should @@ -101,8 +101,8 @@ that each vertex have UVs and normals set already. gdscript GDScript ``` - st.generate_normals() - st.generate_tangents() +st.generate_normals() +st.generate_tangents() ``` By default, when generating normals, they will be calculated on a per-face basis. If you want diff --git a/03_usage/03_3d/procedural_geometry/05_immediategeometry.md b/03_usage/03_3d/procedural_geometry/05_immediategeometry.md index 7a6e136..7d7cf66 100644 --- a/03_usage/03_3d/procedural_geometry/05_immediategeometry.md +++ b/03_usage/03_3d/procedural_geometry/05_immediategeometry.md @@ -25,10 +25,10 @@ Then you call `add_vertex()` to add a vertex with those attributes. For example: gdscript GDScript ``` - # Add a vertex with normal and uv. - set_normal(Vector3(0, 1, 0)) - set_uv(Vector2(1, 1)) - add_vertex(Vector3(0, 0, 1)) +# Add a vertex with normal and uv. +set_normal(Vector3(0, 1, 0)) +set_uv(Vector2(1, 1)) +add_vertex(Vector3(0, 0, 1)) ``` Only attributes added before the call to `add_vertex()` will be included in that vertex. @@ -40,29 +40,29 @@ The example code below draws a single triangle. gdscript GDScript ``` - extends ImmediateGeometry +extends ImmediateGeometry - func _process(_delta): - # Clean up before drawing. - clear() +func _process(_delta): + # Clean up before drawing. + clear() - # Begin draw. - begin(Mesh.PRIMITIVE_TRIANGLES) + # Begin draw. + begin(Mesh.PRIMITIVE_TRIANGLES) - # Prepare attributes for add_vertex. - set_normal(Vector3(0, 0, 1)) - set_uv(Vector2(0, 0)) - # Call last for each vertex, adds the above attributes. - add_vertex(Vector3(-1, -1, 0)) + # Prepare attributes for add_vertex. + set_normal(Vector3(0, 0, 1)) + set_uv(Vector2(0, 0)) + # Call last for each vertex, adds the above attributes. + add_vertex(Vector3(-1, -1, 0)) - set_normal(Vector3(0, 0, 1)) - set_uv(Vector2(0, 1)) - add_vertex(Vector3(-1, 1, 0)) + set_normal(Vector3(0, 0, 1)) + set_uv(Vector2(0, 1)) + add_vertex(Vector3(-1, 1, 0)) - set_normal(Vector3(0, 0, 1)) - set_uv(Vector2(1, 1)) - add_vertex(Vector3(1, 1, 0)) + set_normal(Vector3(0, 0, 1)) + set_uv(Vector2(1, 1)) + add_vertex(Vector3(1, 1, 0)) - # End drawing. - end() + # End drawing. + end() ``` \ No newline at end of file diff --git a/03_usage/04_ui/01_size_and_anchors.md b/03_usage/04_ui/01_size_and_anchors.md index 6a9e617..b543496 100644 --- a/03_usage/04_ui/01_size_and_anchors.md +++ b/03_usage/04_ui/01_size_and_anchors.md @@ -44,18 +44,18 @@ to half of its relevant dimension. For example, the code below shows how a TextureRect can be centered in its parent: ``` - var rect = TextureRect.new() - rect.texture = load("res://icon.png)") - rect.anchor_left = 0.5 - rect.anchor_right = 0.5 - rect.anchor_top = 0.5 - rect.anchor_bottom = 0.5 - var texture_size = rect.texture.get_size() - rect.margin_left = -texture_size.x / 2 - rect.margin_right = -texture_size.x / 2 - rect.margin_top = -texture_size.y / 2 - rect.margin_bottom = -texture_size.y / 2 - add_child(rect) +var rect = TextureRect.new() +rect.texture = load("res://icon.png)") +rect.anchor_left = 0.5 +rect.anchor_right = 0.5 +rect.anchor_top = 0.5 +rect.anchor_bottom = 0.5 +var texture_size = rect.texture.get_size() +rect.margin_left = -texture_size.x / 2 +rect.margin_right = -texture_size.x / 2 +rect.margin_top = -texture_size.y / 2 +rect.margin_bottom = -texture_size.y / 2 +add_child(rect) ``` Setting each anchor to 0.5 moves the reference point for the margins to diff --git a/03_usage/04_ui/02_gui_containers.md b/03_usage/04_ui/02_gui_containers.md index 13231db..2a8269f 100644 --- a/03_usage/04_ui/02_gui_containers.md +++ b/03_usage/04_ui/02_gui_containers.md @@ -154,16 +154,16 @@ to its rect size: gdscript GDScript ``` - extends Container +extends Container - func _notification(what): - if what == NOTIFICATION_SORT_CHILDREN: - # Must re-sort the children - for c in get_children(): - # Fit to own size - fit_child_in_rect( c, Rect2( Vector2(), rect_size ) ) +func _notification(what): + if what == NOTIFICATION_SORT_CHILDREN: + # Must re-sort the children + for c in get_children(): + # Fit to own size + fit_child_in_rect( c, Rect2( Vector2(), rect_size ) ) - func set_some_setting(): - # Some setting changed, ask for children re-sort - queue_sort() +func set_some_setting(): + # Some setting changed, ask for children re-sort + queue_sort() ``` diff --git a/03_usage/04_ui/03_custom_gui_controls.md b/03_usage/04_ui/03_custom_gui_controls.md index 97fee64..2d43f79 100644 --- a/03_usage/04_ui/03_custom_gui_controls.md +++ b/03_usage/04_ui/03_custom_gui_controls.md @@ -40,11 +40,11 @@ exists. Example gdscript GDScript ``` - func _draw(): - if has_focus(): - draw_selected() - else: - draw_normal() +func _draw(): + if has_focus(): + draw_selected() + else: + draw_normal() ``` ## Sizing @@ -64,8 +64,8 @@ for example: gdscript GDScript ``` - func get_minimum_size(): - return Vector2(30, 30) +func get_minimum_size(): + return Vector2(30, 30) ``` Alternatively, set it using a function: @@ -73,8 +73,8 @@ Alternatively, set it using a function: gdscript GDScript ``` - func _ready(): - set_custom_minimum_size(Vector2(30, 30)) +func _ready(): + set_custom_minimum_size(Vector2(30, 30)) ``` ## Input @@ -101,11 +101,11 @@ Simply override it in your control. No processing needs to be set. gdscript GDScript ``` - extends Control +extends Control - func _gui_input(event): - if event is InputEventMouseButton and event.button_index == BUTTON_LEFT and event.pressed: - print("Left mouse button was pressed!") +func _gui_input(event): + if event is InputEventMouseButton and event.button_index == BUTTON_LEFT and event.pressed: + print("Left mouse button was pressed!") ``` For more information about events themselves, check the `doc_inputevent` @@ -119,26 +119,26 @@ exists, but which can be checked with the _notification callback: gdscript GDScript ``` - func _notification(what): - match what: - NOTIFICATION_MOUSE_ENTER: - pass # Mouse entered the area of this control. - NOTIFICATION_MOUSE_EXIT: - pass # Mouse exited the area of this control. - NOTIFICATION_FOCUS_ENTER: - pass # Control gained focus. - NOTIFICATION_FOCUS_EXIT: - pass # Control lost focus. - NOTIFICATION_THEME_CHANGED: - pass # Theme used to draw the control changed; - # update and redraw is recommended if using a theme. - NOTIFICATION_VISIBILITY_CHANGED: - pass # Control became visible/invisible; - # check new status with is_visible(). - NOTIFICATION_RESIZED: - pass # Control changed size; check new size - # with get_size(). - NOTIFICATION_MODAL_CLOSE: - pass # For modal pop-ups, notification - # that the pop-up was closed. +func _notification(what): + match what: + NOTIFICATION_MOUSE_ENTER: + pass # Mouse entered the area of this control. + NOTIFICATION_MOUSE_EXIT: + pass # Mouse exited the area of this control. + NOTIFICATION_FOCUS_ENTER: + pass # Control gained focus. + NOTIFICATION_FOCUS_EXIT: + pass # Control lost focus. + NOTIFICATION_THEME_CHANGED: + pass # Theme used to draw the control changed; + # update and redraw is recommended if using a theme. + NOTIFICATION_VISIBILITY_CHANGED: + pass # Control became visible/invisible; + # check new status with is_visible(). + NOTIFICATION_RESIZED: + pass # Control changed size; check new size + # with get_size(). + NOTIFICATION_MODAL_CLOSE: + pass # For modal pop-ups, notification + # that the pop-up was closed. ``` diff --git a/03_usage/04_ui/04_gui_navigation.md b/03_usage/04_ui/04_gui_navigation.md index f16a7d3..7e88b96 100644 --- a/03_usage/04_ui/04_gui_navigation.md +++ b/03_usage/04_ui/04_gui_navigation.md @@ -63,8 +63,8 @@ do anything. Here is a basic example of setting initial focus with code: gdscript GDScript ``` - func _ready(): - $StartButton.grab_focus() +func _ready(): + $StartButton.grab_focus() ``` Now when the scene starts the "Start Button" node will be focused, and the keyboard diff --git a/03_usage/04_ui/control_node_tutorials/01_bbcode_in_richtextlabel.md b/03_usage/04_ui/control_node_tutorials/01_bbcode_in_richtextlabel.md index 5cb1154..daeb0a9 100644 --- a/03_usage/04_ui/control_node_tutorials/01_bbcode_in_richtextlabel.md +++ b/03_usage/04_ui/control_node_tutorials/01_bbcode_in_richtextlabel.md @@ -114,12 +114,12 @@ For example, the following method can be connected to `meta_clicked` to open clicked URLs using the user's default web browser: ``` - # This assumes RichTextLabel's `meta_clicked` signal was connected to - # the function below using the signal connection dialog. - func _richtextlabel_on_meta_clicked(meta): - # `meta` is not guaranteed to be a String, so convert it to a String - # to avoid script errors at run-time. - OS.shell_open(str(meta)) +# This assumes RichTextLabel's `meta_clicked` signal was connected to +# the function below using the signal connection dialog. +func _richtextlabel_on_meta_clicked(meta): + # `meta` is not guaranteed to be a String, so convert it to a String + # to avoid script errors at run-time. + OS.shell_open(str(meta)) ``` For more advanced use cases, it's also possible to store JSON in an `[url]` @@ -237,89 +237,89 @@ Here are some examples of custom effects: ### Ghost ``` - tool - extends RichTextEffect - class_name RichTextGhost +tool +extends RichTextEffect +class_name RichTextGhost - # Syntax: [ghost freq=5.0 span=10.0][/ghost] +# Syntax: [ghost freq=5.0 span=10.0][/ghost] - # Define the tag name. - var bbcode = "ghost" +# Define the tag name. +var bbcode = "ghost" - func _process_custom_fx(char_fx): - # Get parameters, or use the provided default value if missing. - var speed = char_fx.env.get("freq", 5.0) - var span = char_fx.env.get("span", 10.0) +func _process_custom_fx(char_fx): + # Get parameters, or use the provided default value if missing. + var speed = char_fx.env.get("freq", 5.0) + var span = char_fx.env.get("span", 10.0) - var alpha = sin(char_fx.elapsed_time * speed + (char_fx.absolute_index / span)) * 0.5 + 0.5 - char_fx.color.a = alpha - return true + var alpha = sin(char_fx.elapsed_time * speed + (char_fx.absolute_index / span)) * 0.5 + 0.5 + char_fx.color.a = alpha + return true ``` ### Pulse ``` - tool - extends RichTextEffect - class_name RichTextPulse +tool +extends RichTextEffect +class_name RichTextPulse - # Syntax: [pulse color=#00FFAA height=0.0 freq=2.0][/pulse] +# Syntax: [pulse color=#00FFAA height=0.0 freq=2.0][/pulse] - # Define the tag name. - var bbcode = "pulse" +# Define the tag name. +var bbcode = "pulse" - func _process_custom_fx(char_fx): - # Get parameters, or use the provided default value if missing. - var color = char_fx.env.get("color", char_fx.color) - var height = char_fx.env.get("height", 0.0) - var freq = char_fx.env.get("freq", 2.0) +func _process_custom_fx(char_fx): + # Get parameters, or use the provided default value if missing. + var color = char_fx.env.get("color", char_fx.color) + var height = char_fx.env.get("height", 0.0) + var freq = char_fx.env.get("freq", 2.0) - var sined_time = (sin(char_fx.elapsed_time * freq) + 1.0) / 2.0 - var y_off = sined_time * height - color.a = 1.0 - char_fx.color = char_fx.color.linear_interpolate(color, sined_time) - char_fx.offset = Vector2(0, -1) * y_off - return true + var sined_time = (sin(char_fx.elapsed_time * freq) + 1.0) / 2.0 + var y_off = sined_time * height + color.a = 1.0 + char_fx.color = char_fx.color.linear_interpolate(color, sined_time) + char_fx.offset = Vector2(0, -1) * y_off + return true ``` ### Matrix ``` - tool - extends RichTextEffect - class_name RichTextMatrix +tool +extends RichTextEffect +class_name RichTextMatrix - # Syntax: [matrix clean=2.0 dirty=1.0 span=50][/matrix] +# Syntax: [matrix clean=2.0 dirty=1.0 span=50][/matrix] - # Define the tag name. - var bbcode = "matrix" +# Define the tag name. +var bbcode = "matrix" - func _process_custom_fx(char_fx): - # Get parameters, or use the provided default value if missing. - var clear_time = char_fx.env.get("clean", 2.0) - var dirty_time = char_fx.env.get("dirty", 1.0) - var text_span = char_fx.env.get("span", 50) +func _process_custom_fx(char_fx): + # Get parameters, or use the provided default value if missing. + var clear_time = char_fx.env.get("clean", 2.0) + var dirty_time = char_fx.env.get("dirty", 1.0) + var text_span = char_fx.env.get("span", 50) - var value = char_fx.character + var value = char_fx.character - var matrix_time = fmod(char_fx.elapsed_time + (char_fx.absolute_index / float(text_span)), \ - clear_time + dirty_time) + var matrix_time = fmod(char_fx.elapsed_time + (char_fx.absolute_index / float(text_span)), \ + clear_time + dirty_time) - matrix_time = 0.0 if matrix_time < clear_time else \ - (matrix_time - clear_time) / dirty_time + matrix_time = 0.0 if matrix_time < clear_time else \ + (matrix_time - clear_time) / dirty_time - if value >= 65 && value < 126 && matrix_time > 0.0: - value -= 65 - value = value + int(1 * matrix_time * (126 - 65)) - value %= (126 - 65) - value += 65 - char_fx.character = value - return true + if value >= 65 && value < 126 && matrix_time > 0.0: + value -= 65 + value = value + int(1 * matrix_time * (126 - 65)) + value %= (126 - 65) + value += 65 + char_fx.character = value + return true ``` This will add a few new BBCode commands, which can be used like so: ``` - [center][ghost]This is a custom [matrix]effect[/matrix][/ghost] made in - [pulse freq=5.0 height=2.0][pulse color=#00FFAA freq=2.0]GDScript[/pulse][/pulse].[/center] +[center][ghost]This is a custom [matrix]effect[/matrix][/ghost] made in +[pulse freq=5.0 height=2.0][pulse color=#00FFAA freq=2.0]GDScript[/pulse][/pulse].[/center] ``` \ No newline at end of file diff --git a/03_usage/04_ui/skinning_and_themes/01_gui_skinning.md b/03_usage/04_ui/skinning_and_themes/01_gui_skinning.md index 00f8a03..f133bdc 100644 --- a/03_usage/04_ui/skinning_and_themes/01_gui_skinning.md +++ b/03_usage/04_ui/skinning_and_themes/01_gui_skinning.md @@ -116,8 +116,8 @@ is applied to them. Those methods accept the theme type as one of the arguments. gdscript ``` - var accent_color = get_color("accent_color", "MyType") - label.add_color_override("font_color", accent_color) +var accent_color = get_color("accent_color", "MyType") +label.add_color_override("font_color", accent_color) ``` To give more customization opportunities types can also be linked together as diff --git a/03_usage/05_animation/04_animation_tree.md b/03_usage/05_animation/04_animation_tree.md index a126b9b..604f7c2 100644 --- a/03_usage/05_animation/04_animation_tree.md +++ b/03_usage/05_animation/04_animation_tree.md @@ -95,15 +95,15 @@ After setting the time and changing the animation playback, the seek node automa gdscript GDScript ``` - # Play child animation from the start. - anim_tree.set("parameters/Seek/seek_position", 0.0) - # Alternative syntax (same result as above). - anim_tree["parameters/Seek/seek_position"] = 0.0 +# Play child animation from the start. +anim_tree.set("parameters/Seek/seek_position", 0.0) +# Alternative syntax (same result as above). +anim_tree["parameters/Seek/seek_position"] = 0.0 - # Play child animation from 12 second timestamp. - anim_tree.set("parameters/Seek/seek_position", 12.0) - # Alternative syntax (same result as above). - anim_tree["parameters/Seek/seek_position"] = 12.0 +# Play child animation from 12 second timestamp. +anim_tree.set("parameters/Seek/seek_position", 12.0) +# Alternative syntax (same result as above). +anim_tree["parameters/Seek/seek_position"] = 12.0 ``` #### TimeScale @@ -184,7 +184,7 @@ Afterwards, the actual motion can be retrieved via the `AnimationTree` API as a gdscript GDScript ``` - anim_tree.get_root_motion_transform() +anim_tree.get_root_motion_transform() ``` This can be fed to functions such as `KinematicBody.move_and_slide` to control the character movement. @@ -221,9 +221,9 @@ Which allows setting them or reading them: gdscript GDScript ``` - anim_tree.set("parameters/eye_blend/blend_amount", 1.0) - # Simpler alternative form: - anim_tree["parameters/eye_blend/blend_amount"] = 1.0 +anim_tree.set("parameters/eye_blend/blend_amount", 1.0) +# Simpler alternative form: +anim_tree["parameters/eye_blend/blend_amount"] = 1.0 ``` @@ -241,7 +241,7 @@ object from the `AnimationTree` node (it is exported as a property). gdscript GDScript ``` - var state_machine = anim_tree["parameters/playback"] +var state_machine = anim_tree["parameters/playback"] ``` Once retrieved, it can be used by calling one of the many functions it offers: @@ -249,7 +249,7 @@ Once retrieved, it can be used by calling one of the many functions it offers: gdscript GDScript ``` - state_machine.travel("SomeState") +state_machine.travel("SomeState") ``` The state machine must be running before you can travel. Make sure to either call `start()` or choose a node to **Autoplay on Load**. diff --git a/03_usage/05_animation/05_playing_videos.md b/03_usage/05_animation/05_playing_videos.md index 076514f..462671c 100644 --- a/03_usage/05_animation/05_playing_videos.md +++ b/03_usage/05_animation/05_playing_videos.md @@ -212,7 +212,7 @@ space in parts of the video/audio that don't require a high bitrate (such as static scenes). ``` - ffmpeg -i input.mp4 -q:v 6 -q:a 6 output.ogv +ffmpeg -i input.mp4 -q:v 6 -q:a 6 output.ogv ``` #### FFmpeg: Resize the video then convert it @@ -222,5 +222,5 @@ preserving its existing aspect ratio. This helps decrease the file size significantly if the source is recorded at a higher resolution than 720p: ``` - ffmpeg -i input.mp4 -vf "scale=-1:720" -q:v 6 -q:a 6 output.ogv +ffmpeg -i input.mp4 -vf "scale=-1:720" -q:v 6 -q:a 6 output.ogv ``` diff --git a/03_usage/06_inputs/01_inputevent.md b/03_usage/06_inputs/01_inputevent.md index e8bf3bf..fbdfa56 100644 --- a/03_usage/06_inputs/01_inputevent.md +++ b/03_usage/06_inputs/01_inputevent.md @@ -15,10 +15,10 @@ Here is a quick example, closing your game if the escape key is hit: gdscript GDScript ``` - func _unhandled_input(event): - if event is InputEventKey: - if event.pressed and event.scancode == KEY_ESCAPE: - get_tree().quit() +func _unhandled_input(event): + if event is InputEventKey: + if event.pressed and event.scancode == KEY_ESCAPE: + get_tree().quit() ``` However, it is cleaner and more flexible to use the provided `InputMap` feature, @@ -32,9 +32,9 @@ You can set up your InputMap under **Project > Project Settings > Input Map** an gdscript GDScript ``` - func _process(delta): - if Input.is_action_pressed("ui_right"): - # Move right. +func _process(delta): + if Input.is_action_pressed("ui_right"): + # Move right. ``` ## How does it work? @@ -153,12 +153,12 @@ The Input singleton has a method for this: gdscript GDScript ``` - var ev = InputEventAction.new() - # Set as move_left, pressed. - ev.action = "move_left" - ev.pressed = true - # Feedback. - Input.parse_input_event(ev) +var ev = InputEventAction.new() +# Set as move_left, pressed. +ev.action = "move_left" +ev.pressed = true +# Feedback. +Input.parse_input_event(ev) ```` ## InputMap diff --git a/03_usage/06_inputs/02_input_examples.md b/03_usage/06_inputs/02_input_examples.md index 32df2f7..050a154 100644 --- a/03_usage/06_inputs/02_input_examples.md +++ b/03_usage/06_inputs/02_input_examples.md @@ -29,15 +29,15 @@ Examples: gdscript GDScript ``` - func _input(event): - if event.is_action_pressed("jump"): - jump() +func _input(event): + if event.is_action_pressed("jump"): + jump() - func _physics_process(delta): - if Input.is_action_pressed("move_right"): - # Move as long as the key/button is pressed. - position.x += speed * delta +func _physics_process(delta): + if Input.is_action_pressed("move_right"): + # Move as long as the key/button is pressed. + position.x += speed * delta ``` This gives you the flexibility to mix-and-match the type of input processing @@ -56,25 +56,25 @@ attach the following script: gdscript GDScript ``` - extends Node +extends Node - func _input(event): - print(event.as_text()) +func _input(event): + print(event.as_text()) ``` As you press keys, move the mouse, and perform other inputs, you'll see each event scroll by in the output window. Here's an example of the output: ``` - A - InputEventMouseMotion : button_mask=0, position=(108, 108), relative=(26, 1), speed=(164.152496, 159.119843), pressure=(0), tilt=(0, 0) - InputEventMouseButton : button_index=BUTTON_LEFT, pressed=true, position=(108, 107), button_mask=1, doubleclick=false - InputEventMouseButton : button_index=BUTTON_LEFT, pressed=false, position=(108, 107), button_mask=0, doubleclick=false - S - F - Alt - InputEventMouseMotion : button_mask=0, position=(108, 107), relative=(0, -1), speed=(164.152496, 159.119843), pressure=(0), tilt=(0, 0) +A +InputEventMouseMotion : button_mask=0, position=(108, 108), relative=(26, 1), speed=(164.152496, 159.119843), pressure=(0), tilt=(0, 0) +InputEventMouseButton : button_index=BUTTON_LEFT, pressed=true, position=(108, 107), button_mask=1, doubleclick=false +InputEventMouseButton : button_index=BUTTON_LEFT, pressed=false, position=(108, 107), button_mask=0, doubleclick=false +S +F +Alt +InputEventMouseMotion : button_mask=0, position=(108, 107), relative=(0, -1), speed=(164.152496, 159.119843), pressure=(0), tilt=(0, 0) ``` As you can see, the results are very different for the different types of @@ -99,9 +99,9 @@ avoid this, make sure to test the event type first: gdscript GDScript ``` - func _input(event): - if event is InputEventMouseButton: - print("mouse button event at ", event.position) +func _input(event): + if event is InputEventMouseButton: + print("mouse button event at ", event.position) ``` ## InputMap @@ -124,9 +124,9 @@ the action you're looking for: gdscript GDScript ``` - func _input(event): - if event.is_action_pressed("my_action"): - print("my_action occurred!") +func _input(event): + if event.is_action_pressed("my_action"): + print("my_action occurred!") ``` ## Keyboard events @@ -139,10 +139,10 @@ the :kbd:`T`: gdscript GDScript ``` - func _input(event): - if event is InputEventKey and event.pressed: - if event.scancode == KEY_T: - print("T was pressed") +func _input(event): + if event is InputEventKey and event.pressed: + if event.scancode == KEY_T: + print("T was pressed") ``` Tip: @@ -174,13 +174,13 @@ different when it's :kbd:`Shift + T`: gdscript GDScript ``` - func _input(event): - if event is InputEventKey and event.pressed: - if event.scancode == KEY_T: - if event.shift: - print("Shift+T was pressed") - else: - print("T was pressed") +func _input(event): + if event is InputEventKey and event.pressed: + if event.scancode == KEY_T: + if event.shift: + print("Shift+T was pressed") + else: + print("T was pressed") ``` Tip: @@ -205,12 +205,12 @@ also counts as a button - two buttons, to be precise, with both gdscript GDScript ``` - func _input(event): - if event is InputEventMouseButton: - if event.button_index == BUTTON_LEFT and event.pressed: - print("Left button was clicked at ", event.position) - if event.button_index == BUTTON_WHEEL_UP and event.pressed: - print("Wheel up") +func _input(event): + if event is InputEventMouseButton: + if event.button_index == BUTTON_LEFT and event.pressed: + print("Left button was clicked at ", event.position) + if event.button_index == BUTTON_WHEEL_UP and event.pressed: + print("Wheel up") ``` ### Mouse motion @@ -225,26 +225,26 @@ node: gdscript GDScript ``` - extends Node +extends Node - var dragging = false - var click_radius = 32 # Size of the sprite. +var dragging = false +var click_radius = 32 # Size of the sprite. - func _input(event): - if event is InputEventMouseButton and event.button_index == BUTTON_LEFT: - if (event.position - $Sprite.position).length() ( click_radius: - # Start dragging if the click is on the sprite. - if not dragging and event.pressed: - dragging = true - # Stop dragging if the button is released. - if dragging and not event.pressed: - dragging = false +func _input(event): + if event is InputEventMouseButton and event.button_index == BUTTON_LEFT: + if (event.position - $Sprite.position).length() ( click_radius: + # Start dragging if the click is on the sprite. + if not dragging and event.pressed: + dragging = true + # Stop dragging if the button is released. + if dragging and not event.pressed: + dragging = false - if event is InputEventMouseMotion and dragging: - # While dragging, move the sprite with the mouse. - $Sprite.position = event.position + if event is InputEventMouseMotion and dragging: + # While dragging, move the sprite with the mouse. + $Sprite.position = event.position ``` ## Touch events diff --git a/03_usage/06_inputs/03_mouse_and_input_coordinates.md b/03_usage/06_inputs/03_mouse_and_input_coordinates.md index 081b71c..0c1c8d6 100644 --- a/03_usage/06_inputs/03_mouse_and_input_coordinates.md +++ b/03_usage/06_inputs/03_mouse_and_input_coordinates.md @@ -24,15 +24,15 @@ for example: gdscript GDScript ``` - func _input(event): - # Mouse in viewport coordinates. - if event is InputEventMouseButton: - print("Mouse Click/Unclick at: ", event.position) - elif event is InputEventMouseMotion: - print("Mouse Motion at: ", event.position) +func _input(event): + # Mouse in viewport coordinates. + if event is InputEventMouseButton: + print("Mouse Click/Unclick at: ", event.position) + elif event is InputEventMouseMotion: + print("Mouse Motion at: ", event.position) - # Print the size of the viewport. - print("Viewport Resolution is: ", get_viewport_rect().size) + # Print the size of the viewport. + print("Viewport Resolution is: ", get_viewport_rect().size) ``` @@ -41,7 +41,7 @@ Alternatively, it's possible to ask the viewport for the mouse position: gdscript GDScript ``` - get_viewport().get_mouse_position() +get_viewport().get_mouse_position() ``` Note: diff --git a/03_usage/06_inputs/04_custom_mouse_cursor.md b/03_usage/06_inputs/04_custom_mouse_cursor.md index b49c349..9b02b4b 100644 --- a/03_usage/06_inputs/04_custom_mouse_cursor.md +++ b/03_usage/06_inputs/04_custom_mouse_cursor.md @@ -40,21 +40,21 @@ Create a Node and attach the following script. gdscript GDScript ``` - extends Node +extends Node - # Load the custom images for the mouse cursor. - var arrow = load("res://arrow.png)") - var beam = load("res://beam.png)") +# Load the custom images for the mouse cursor. +var arrow = load("res://arrow.png)") +var beam = load("res://beam.png)") - func _ready(): - # Changes only the arrow shape of the cursor. - # This is similar to changing it in the project settings. - Input.set_custom_mouse_cursor(arrow) +func _ready(): + # Changes only the arrow shape of the cursor. + # This is similar to changing it in the project settings. + Input.set_custom_mouse_cursor(arrow) - # Changes a specific shape of the cursor (here, the I-beam shape). - Input.set_custom_mouse_cursor(beam, Input.CURSOR_IBEAM) + # Changes a specific shape of the cursor (here, the I-beam shape). + Input.set_custom_mouse_cursor(beam, Input.CURSOR_IBEAM) ``` Note: diff --git a/03_usage/06_inputs/05_handling_quit_requests.md b/03_usage/06_inputs/05_handling_quit_requests.md index c96b164..b749a70 100644 --- a/03_usage/06_inputs/05_handling_quit_requests.md +++ b/03_usage/06_inputs/05_handling_quit_requests.md @@ -30,9 +30,9 @@ Handling the notification is done as follows (on any node): gdscript GDScript ``` - func _notification(what): - if what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST: - get_tree().quit() # default behavior +func _notification(what): + if what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST: + get_tree().quit() # default behavior ``` When developing mobile apps, quitting is not desired unless the user is @@ -44,7 +44,7 @@ behavior to quit when quit is requested, this can be changed: gdscript GDScript ``` - get_tree().set_auto_accept_quit(false) +get_tree().set_auto_accept_quit(false) ``` ## Sending your own quit notification @@ -61,5 +61,5 @@ Instead, you should send a quit request: gdscript GDScript ``` - get_tree().notification(MainLoop.NOTIFICATION_WM_QUIT_REQUEST) +get_tree().notification(MainLoop.NOTIFICATION_WM_QUIT_REQUEST) ``` diff --git a/03_usage/06_inputs/06_controllers_gamepads_joysticks.md b/03_usage/06_inputs/06_controllers_gamepads_joysticks.md index 7697839..4c6aa55 100644 --- a/03_usage/06_inputs/06_controllers_gamepads_joysticks.md +++ b/03_usage/06_inputs/06_controllers_gamepads_joysticks.md @@ -66,11 +66,11 @@ gdscript GDScript gdscript GDScript ``` - # `walk` will be a floating-point number between `-1.0` and `1.0`. - var walk = Input.get_axis("move_left", "move_right") +# `walk` will be a floating-point number between `-1.0` and `1.0`. +var walk = Input.get_axis("move_left", "move_right") - # The line above is a shorter form of: - var walk = Input.get_action_strength("move_right") - Input.get_action_strength("move_left") +# The line above is a shorter form of: +var walk = Input.get_action_strength("move_right") - Input.get_action_strength("move_left") ``` - For other types of analog input, such as handling a trigger or handling @@ -79,8 +79,8 @@ gdscript GDScript gdscript GDScript ``` - # `strength` will be a floating-point number between `0.0` and `1.0`. - var strength = Input.get_action_strength("accelerate") +# `strength` will be a floating-point number between `0.0` and `1.0`. +var strength = Input.get_action_strength("accelerate") ``` For non-analog digital/boolean input (only "pressed" or "not pressed" values), @@ -90,8 +90,8 @@ use `Input.is_action_pressed()`: gdscript GDScript ``` - # `jumping` will be a boolean with a value of `true` or `false`. - var jumping = Input.is_action_pressed("jump") +# `jumping` will be a boolean with a value of `true` or `false`. +var jumping = Input.is_action_pressed("jump") ``` In Pandemonium versions before 3.4, such as 3.3, `Input.get_vector()` and @@ -172,22 +172,22 @@ the `SDL_GAMECONTROLLERCONFIG` environment variable before running Pandemonium: bash Linux/macOS ``` - export SDL_GAMECONTROLLERCONFIG="your:mapping:here" - ./path/to/pandemonium.x86_64 +export SDL_GAMECONTROLLERCONFIG="your:mapping:here" +./path/to/pandemonium.x86_64 ``` bat Windows (cmd) ``` - set SDL_GAMECONTROLLERCONFIG=your:mapping:here - path\to\pandemonium.exe +set SDL_GAMECONTROLLERCONFIG=your:mapping:here +path\to\pandemonium.exe ``` powershell Windows (powershell) ``` - $env:SDL_GAMECONTROLLERCONFIG="your:mapping:here" - path\to\pandemonium.exe +$env:SDL_GAMECONTROLLERCONFIG="your:mapping:here" +path\to\pandemonium.exe ``` To test mappings on non-desktop platforms or to distribute your project with diff --git a/03_usage/07_io/01_background_loading.md b/03_usage/07_io/01_background_loading.md index 54fc543..d68fb38 100644 --- a/03_usage/07_io/01_background_loading.md +++ b/03_usage/07_io/01_background_loading.md @@ -25,7 +25,7 @@ Usage is generally as follows ### Obtaining a ResourceInteractiveLoader ``` - Ref( ResourceInteractiveLoader> ResourceLoader::load_interactive(String p_path); +Ref( ResourceInteractiveLoader> ResourceLoader::load_interactive(String p_path); ``` This method will give you a ResourceInteractiveLoader that you will use @@ -34,7 +34,7 @@ to manage the load operation. ### Polling ``` - Error ResourceInteractiveLoader::poll(); +Error ResourceInteractiveLoader::poll(); ``` Use this method to advance the progress of the load. Each call to @@ -50,8 +50,8 @@ Any other return value means there was an error and loading has stopped. To query the progress of the load, use the following methods: ``` - int ResourceInteractiveLoader::get_stage_count() const; - int ResourceInteractiveLoader::get_stage() const; +int ResourceInteractiveLoader::get_stage_count() const; +int ResourceInteractiveLoader::get_stage() const; ``` `get_stage_count` returns the total number of stages to load. @@ -60,7 +60,7 @@ To query the progress of the load, use the following methods: ### Forcing completion (optional) ``` - Error ResourceInteractiveLoader::wait(); +Error ResourceInteractiveLoader::wait(); ``` Use this method if you need to load the entire resource in the current @@ -69,7 +69,7 @@ frame, without any more steps. ### Obtaining the resource ``` - Ref ResourceInteractiveLoader::get_resource(); +Ref ResourceInteractiveLoader::get_resource(); ``` If everything goes well, use this method to retrieve your loaded @@ -84,15 +84,15 @@ First, we set up some variables and initialize the `current_scene` with the main scene of the game: ``` - var loader - var wait_frames - var time_max = 100 # msec - var current_scene +var loader +var wait_frames +var time_max = 100 # msec +var current_scene - func _ready(): - var root = get_tree().get_root() - current_scene = root.get_child(root.get_child_count() -1) +func _ready(): + var root = get_tree().get_root() + current_scene = root.get_child(root.get_child_count() -1) ``` The function `goto_scene` is called from the game when the scene @@ -102,19 +102,19 @@ callback. It also starts a "loading" animation, which could show a progress bar or loading screen. ``` - func goto_scene(path): # Game requests to switch to this scene. - loader = ResourceLoader.load_interactive(path) - if loader == null: # Check for errors. - show_error() - return - set_process(true) +func goto_scene(path): # Game requests to switch to this scene. + loader = ResourceLoader.load_interactive(path) + if loader == null: # Check for errors. + show_error() + return + set_process(true) - current_scene.queue_free() # Get rid of the old scene. + current_scene.queue_free() # Get rid of the old scene. - # Start your "loading..." animation. - get_node("animation").play("loading") + # Start your "loading..." animation. + get_node("animation").play("loading") - wait_frames = 1 + wait_frames = 1 ``` `process` is where the loader is polled. `poll` is called, and then @@ -130,34 +130,34 @@ more than your value for `time_max`, so keep in mind we won't have precise control over the timings. ``` - func _process(time): - if loader == null: - # no need to process anymore - set_process(false) - return +func _process(time): + if loader == null: + # no need to process anymore + set_process(false) + return - # Wait for frames to let the "loading" animation show up. - if wait_frames > 0: - wait_frames -= 1 - return + # Wait for frames to let the "loading" animation show up. + if wait_frames > 0: + wait_frames -= 1 + return - var t = OS.get_ticks_msec() - # Use "time_max" to control for how long we block this thread. - while OS.get_ticks_msec() ( t + time_max: - # Poll your loader. - var err = loader.poll() + var t = OS.get_ticks_msec() + # Use "time_max" to control for how long we block this thread. + while OS.get_ticks_msec() ( t + time_max: + # Poll your loader. + var err = loader.poll() - if err == ERR_FILE_EOF: # Finished loading. - var resource = loader.get_resource() - loader = null - set_new_scene(resource) - break - elif err == OK: - update_progress() - else: # Error during loading. - show_error() - loader = null - break + if err == ERR_FILE_EOF: # Finished loading. + var resource = loader.get_resource() + loader = null + set_new_scene(resource) + break + elif err == OK: + update_progress() + else: # Error during loading. + show_error() + loader = null + break ``` Some extra helper functions. `update_progress` updates a progress bar, @@ -168,22 +168,22 @@ newly loaded scene on the tree. Because it's a scene being loaded, loader. ``` - func update_progress(): - var progress = float(loader.get_stage()) / loader.get_stage_count() - # Update your progress bar? - get_node("progress").set_progress(progress) +func update_progress(): + var progress = float(loader.get_stage()) / loader.get_stage_count() + # Update your progress bar? + get_node("progress").set_progress(progress) - # ...or update a progress animation? - var length = get_node("animation").get_current_animation_length() + # ...or update a progress animation? + var length = get_node("animation").get_current_animation_length() - # Call this on a paused animation. Use "true" as the second argument to - # force the animation to update. - get_node("animation").seek(progress * length, true) + # Call this on a paused animation. Use "true" as the second argument to + # force the animation to update. + get_node("animation").seek(progress * length, true) - func set_new_scene(scene_resource): - current_scene = scene_resource.instance() - get_node("/root").add_child(current_scene) +func set_new_scene(scene_resource): + current_scene = scene_resource.instance() + get_node("/root").add_child(current_scene) ``` ## Using multiple threads @@ -212,32 +212,32 @@ You can find an example class for loading resources in threads here: :download:`resource_queue.gd ( files/resource_queue.gd )`. Usage is as follows: ``` - func start() +func start() ``` Call after you instance the class to start the thread. ``` - func queue_resource(path, p_in_front = false) +func queue_resource(path, p_in_front = false) ``` Queue a resource. Use optional argument "p_in_front" to put it in front of the queue. ``` - func cancel_resource(path) +func cancel_resource(path) ``` Remove a resource from the queue, discarding any loading done. ``` - func is_ready(path) +func is_ready(path) ``` Returns `true` if a resource is fully loaded and ready to be retrieved. ``` - func get_progress(path) +func get_progress(path) ``` Get the progress of a resource. Returns -1 if there was an error (for example if the @@ -247,7 +247,7 @@ progress bars, etc), use `is_ready` to find out if a resource is actually ready. ``` - func get_resource(path) +func get_resource(path) ``` Returns the fully loaded resource, or `null` on error. If the resource is @@ -258,34 +258,34 @@ and finish the load. If the resource is not on the queue, it will call ### Example: ``` - # Initialize. - queue = preload("res://resource_queue.gd").new() - queue.start() +# Initialize. +queue = preload("res://resource_queue.gd").new() +queue.start() - # Suppose your game starts with a 10 second cutscene, during which the user - # can't interact with the game. - # For that time, we know they won't use the pause menu, so we can queue it - # to load during the cutscene: - queue.queue_resource("res://pause_menu.tres") - start_cutscene() +# Suppose your game starts with a 10 second cutscene, during which the user +# can't interact with the game. +# For that time, we know they won't use the pause menu, so we can queue it +# to load during the cutscene: +queue.queue_resource("res://pause_menu.tres") +start_cutscene() - # Later, when the user presses the pause button for the first time: - pause_menu = queue.get_resource("res://pause_menu.tres").instance() - pause_menu.show() +# Later, when the user presses the pause button for the first time: +pause_menu = queue.get_resource("res://pause_menu.tres").instance() +pause_menu.show() - # When you need a new scene: - queue.queue_resource("res://level_1.tscn", true) - # Use "true" as the second argument to put it at the front of the queue, - # pausing the load of any other resource. +# When you need a new scene: +queue.queue_resource("res://level_1.tscn", true) +# Use "true" as the second argument to put it at the front of the queue, +# pausing the load of any other resource. - # To check progress. - if queue.is_ready("res://level_1.tscn"): - show_new_level(queue.get_resource("res://level_1.tscn")) - else: - update_progress(queue.get_progress("res://level_1.tscn")) +# To check progress. +if queue.is_ready("res://level_1.tscn"): + show_new_level(queue.get_resource("res://level_1.tscn")) +else: + update_progress(queue.get_progress("res://level_1.tscn")) - # When the user walks away from the trigger zone in your Metroidvania game: - queue.cancel_resource("res://zone_2.tscn") +# When the user walks away from the trigger zone in your Metroidvania game: +queue.cancel_resource("res://zone_2.tscn") ``` **Note**: this code, in its current form, is not tested in real world diff --git a/03_usage/07_io/03_saving_games.md b/03_usage/07_io/03_saving_games.md index 4d80d27..c4354a7 100644 --- a/03_usage/07_io/03_saving_games.md +++ b/03_usage/07_io/03_saving_games.md @@ -35,9 +35,9 @@ to save them and then tell them all to save with this script: gdscript GDScript ``` - var save_nodes = get_tree().get_nodes_in_group("Persist") - for i in save_nodes: - # Now, we can call our save function on each node. +var save_nodes = get_tree().get_nodes_in_group("Persist") +for i in save_nodes: + # Now, we can call our save function on each node. ``` ## Serializing @@ -54,28 +54,28 @@ like this: gdscript GDScript ``` - func save(): - var save_dict = { - "filename" : get_filename(), - "parent" : get_parent().get_path(), - "pos_x" : position.x, # Vector2 is not supported by JSON - "pos_y" : position.y, - "attack" : attack, - "defense" : defense, - "current_health" : current_health, - "max_health" : max_health, - "damage" : damage, - "regen" : regen, - "experience" : experience, - "tnl" : tnl, - "level" : level, - "attack_growth" : attack_growth, - "defense_growth" : defense_growth, - "health_growth" : health_growth, - "is_alive" : is_alive, - "last_attack" : last_attack - } - return save_dict +func save(): + var save_dict = { + "filename" : get_filename(), + "parent" : get_parent().get_path(), + "pos_x" : position.x, # Vector2 is not supported by JSON + "pos_y" : position.y, + "attack" : attack, + "defense" : defense, + "current_health" : current_health, + "max_health" : max_health, + "damage" : damage, + "regen" : regen, + "experience" : experience, + "tnl" : tnl, + "level" : level, + "attack_growth" : attack_growth, + "defense_growth" : defense_growth, + "health_growth" : health_growth, + "is_alive" : is_alive, + "last_attack" : last_attack + } + return save_dict ``` @@ -96,31 +96,31 @@ way to pull the data out of the file as well. gdscript GDScript ``` - # Note: This can be called from anywhere inside the tree. This function is - # path independent. - # Go through everything in the persist category and ask them to return a - # dict of relevant variables. - func save_game(): - var save_game = File.new() - save_game.open("user://savegame.save", File.WRITE) - var save_nodes = get_tree().get_nodes_in_group("Persist") - for node in save_nodes: - # Check the node is an instanced scene so it can be instanced again during load. - if node.filename.empty(): - print("persistent node '%s' is not an instanced scene, skipped" % node.name) - continue +# Note: This can be called from anywhere inside the tree. This function is +# path independent. +# Go through everything in the persist category and ask them to return a +# dict of relevant variables. +func save_game(): + var save_game = File.new() + save_game.open("user://savegame.save", File.WRITE) + var save_nodes = get_tree().get_nodes_in_group("Persist") + for node in save_nodes: + # Check the node is an instanced scene so it can be instanced again during load. + if node.filename.empty(): + print("persistent node '%s' is not an instanced scene, skipped" % node.name) + continue - # Check the node has a save function. - if !node.has_method("save"): - print("persistent node '%s' is missing a save() function, skipped" % node.name) - continue + # Check the node has a save function. + if !node.has_method("save"): + print("persistent node '%s' is missing a save() function, skipped" % node.name) + continue - # Call the node's save function. - var node_data = node.call("save") + # Call the node's save function. + var node_data = node.call("save") - # Store the save dictionary as a new line in the save file. - save_game.store_line(to_json(node_data)) - save_game.close() + # Store the save dictionary as a new line in the save file. + save_game.store_line(to_json(node_data)) + save_game.close() ``` @@ -133,40 +133,40 @@ load function: gdscript GDScript ``` - # Note: This can be called from anywhere inside the tree. This function - # is path independent. - func load_game(): - var save_game = File.new() - if not save_game.file_exists("user://savegame.save"): - return # Error! We don't have a save to load. +# Note: This can be called from anywhere inside the tree. This function +# is path independent. +func load_game(): + var save_game = File.new() + if not save_game.file_exists("user://savegame.save"): + return # Error! We don't have a save to load. - # We need to revert the game state so we're not cloning objects - # during loading. This will vary wildly depending on the needs of a - # project, so take care with this step. - # For our example, we will accomplish this by deleting saveable objects. - var save_nodes = get_tree().get_nodes_in_group("Persist") - for i in save_nodes: - i.queue_free() + # We need to revert the game state so we're not cloning objects + # during loading. This will vary wildly depending on the needs of a + # project, so take care with this step. + # For our example, we will accomplish this by deleting saveable objects. + var save_nodes = get_tree().get_nodes_in_group("Persist") + for i in save_nodes: + i.queue_free() - # Load the file line by line and process that dictionary to restore - # the object it represents. - save_game.open("user://savegame.save", File.READ) - while save_game.get_position() < save_game.get_len(): - # Get the saved dictionary from the next line in the save file - var node_data = parse_json(save_game.get_line()) + # Load the file line by line and process that dictionary to restore + # the object it represents. + save_game.open("user://savegame.save", File.READ) + while save_game.get_position() < save_game.get_len(): + # Get the saved dictionary from the next line in the save file + var node_data = parse_json(save_game.get_line()) - # Firstly, we need to create the object and add it to the tree and set its position. - var new_object = load(node_data["filename"]).instance() - get_node(node_data["parent"]).add_child(new_object) - new_object.position = Vector2(node_data["pos_x"], node_data["pos_y"]) + # Firstly, we need to create the object and add it to the tree and set its position. + var new_object = load(node_data["filename"]).instance() + get_node(node_data["parent"]).add_child(new_object) + new_object.position = Vector2(node_data["pos_x"], node_data["pos_y"]) - # Now we set the remaining variables. - for i in node_data.keys(): - if i == "filename" or i == "parent" or i == "pos_x" or i == "pos_y": - continue - new_object.set(i, node_data[i]) + # Now we set the remaining variables. + for i in node_data.keys(): + if i == "filename" or i == "parent" or i == "pos_x" or i == "pos_y": + continue + new_object.set(i, node_data[i]) - save_game.close() + save_game.close() ``` Now we can save and load an arbitrary number of objects laid out diff --git a/03_usage/07_io/04_binary_serialization_api.md b/03_usage/07_io/04_binary_serialization_api.md index bef36e3..fa7502b 100644 --- a/03_usage/07_io/04_binary_serialization_api.md +++ b/03_usage/07_io/04_binary_serialization_api.md @@ -20,8 +20,8 @@ The lowest value two bytes are used to determine the type, while the highest val two bytes contain flags ``` - base_type = val & 0xFFFF; - flags = val >> 16; +base_type = val & 0xFFFF; +flags = val >> 16; ``` | Type | Value | diff --git a/03_usage/07_io/05_encrypting_save_games.md b/03_usage/07_io/05_encrypting_save_games.md index f6637a1..8ba66ce 100644 --- a/03_usage/07_io/05_encrypting_save_games.md +++ b/03_usage/07_io/05_encrypting_save_games.md @@ -34,10 +34,10 @@ It also supports encryption. To create an encrypted file, a passphrase must be provided, like this: ``` - var f = File.new() - var err = f.open_encrypted_with_pass("user://savedata.bin", File.WRITE, "mypass") - f.store_var(game_state) - f.close() +var f = File.new() +var err = f.open_encrypted_with_pass("user://savedata.bin", File.WRITE, "mypass") +f.store_var(game_state) +f.close() ``` This will make the file unreadable to users, but will still not prevent @@ -45,10 +45,10 @@ them from sharing savefiles. To solve this, use the device unique id or some unique user identifier, for example: ``` - var f = File.new() - var err = f.open_encrypted_with_pass("user://savedata.bin", File.WRITE, OS.get_unique_id()) - f.store_var(game_state) - f.close() +var f = File.new() +var err = f.open_encrypted_with_pass("user://savedata.bin", File.WRITE, OS.get_unique_id()) +f.store_var(game_state) +f.close() ``` diff --git a/03_usage/08_networking/01_high_level_multiplayer.md b/03_usage/08_networking/01_high_level_multiplayer.md index 27d2e3d..659045e 100644 --- a/03_usage/08_networking/01_high_level_multiplayer.md +++ b/03_usage/08_networking/01_high_level_multiplayer.md @@ -81,35 +81,35 @@ To create that object, it first has to be initialized as a server or client. Initializing as a server, listening on the given port, with a given maximum number of peers: ``` - var peer = NetworkedMultiplayerENet.new() - peer.create_server(SERVER_PORT, MAX_PLAYERS) - get_tree().network_peer = peer +var peer = NetworkedMultiplayerENet.new() +peer.create_server(SERVER_PORT, MAX_PLAYERS) +get_tree().network_peer = peer ``` Initializing as a client, connecting to a given IP and port: ``` - var peer = NetworkedMultiplayerENet.new() - peer.create_client(SERVER_IP, SERVER_PORT) - get_tree().network_peer = peer +var peer = NetworkedMultiplayerENet.new() +peer.create_client(SERVER_IP, SERVER_PORT) +get_tree().network_peer = peer ``` Get the previously set network peer: ``` - get_tree().get_network_peer() +get_tree().get_network_peer() ``` Checking whether the tree is initialized as a server or client: ``` - get_tree().is_network_server() +get_tree().is_network_server() ``` Terminating the networking feature: ``` - get_tree().network_peer = null +get_tree().network_peer = null ``` (Although it may make sense to send a message first to let the other peers know you're going away instead of letting the connection close or timeout, depending on your game.) @@ -183,53 +183,53 @@ There is also `SceneTree.get_rpc_sender_id()`, which can be used to check which Let's get back to the lobby. Imagine that each player that connects to the server will tell everyone about it. ``` - # Typical lobby implementation; imagine this being in /root/lobby. +# Typical lobby implementation; imagine this being in /root/lobby. - extends Node +extends Node - # Connect all functions +# Connect all functions - func _ready(): - get_tree().connect("network_peer_connected", self, "_player_connected") - get_tree().connect("network_peer_disconnected", self, "_player_disconnected") - get_tree().connect("connected_to_server", self, "_connected_ok") - get_tree().connect("connection_failed", self, "_connected_fail") - get_tree().connect("server_disconnected", self, "_server_disconnected") +func _ready(): + get_tree().connect("network_peer_connected", self, "_player_connected") + get_tree().connect("network_peer_disconnected", self, "_player_disconnected") + get_tree().connect("connected_to_server", self, "_connected_ok") + get_tree().connect("connection_failed", self, "_connected_fail") + get_tree().connect("server_disconnected", self, "_server_disconnected") - # Player info, associate ID to data - var player_info = {} - # Info we send to other players - var my_info = { name = "Johnson Magenta", favorite_color = Color8(255, 0, 255) } +# Player info, associate ID to data +var player_info = {} +# Info we send to other players +var my_info = { name = "Johnson Magenta", favorite_color = Color8(255, 0, 255) } - func _player_connected(id): - # Called on both clients and server when a peer connects. Send my info to it. - rpc_id(id, "register_player", my_info) +func _player_connected(id): + # Called on both clients and server when a peer connects. Send my info to it. + rpc_id(id, "register_player", my_info) - func _player_disconnected(id): - player_info.erase(id) # Erase player from info. +func _player_disconnected(id): + player_info.erase(id) # Erase player from info. - func _connected_ok(): - pass # Only called on clients, not server. Will go unused; not useful here. +func _connected_ok(): + pass # Only called on clients, not server. Will go unused; not useful here. - func _server_disconnected(): - pass # Server kicked us; show error and abort. +func _server_disconnected(): + pass # Server kicked us; show error and abort. - func _connected_fail(): - pass # Could not even connect to server; abort. +func _connected_fail(): + pass # Could not even connect to server; abort. - remote func register_player(info): - # Get the id of the RPC sender. - var id = get_tree().get_rpc_sender_id() - # Store the info - player_info[id] = info +remote func register_player(info): + # Get the id of the RPC sender. + var id = get_tree().get_rpc_sender_id() + # Store the info + player_info[id] = info - # Call function to update lobby UI here + # Call function to update lobby UI here ``` You might have already noticed something different, which is the usage of the `remote` keyword on the `register_player` function: ``` - remote func register_player(info): +remote func register_player(info): ``` This keyword is one of many that allow a function to be called by a remote procedure call (RPC). There are six of them total: @@ -254,8 +254,8 @@ The `master` keyword means a call can be made from any network puppet to the net If `sync` is included, the call can also be made locally. For example, to allow the network master to change the player's position on all peers: ``` - puppetsync func update_position(new_position): - position = new_position +puppetsync func update_position(new_position): + position = new_position ``` Tip: @@ -284,29 +284,29 @@ The solution is to simply name the *root nodes of the instanced player scenes as every peer and RPC will work great! Here is an example: ``` - remote func pre_configure_game(): - var selfPeerID = get_tree().get_network_unique_id() +remote func pre_configure_game(): + var selfPeerID = get_tree().get_network_unique_id() - # Load world - var world = load(which_level).instance() - get_node("/root").add_child(world) + # Load world + var world = load(which_level).instance() + get_node("/root").add_child(world) - # Load my player - var my_player = preload("res://player.tscn").instance() - my_player.set_name(str(selfPeerID)) - my_player.set_network_master(selfPeerID) # Will be explained later - get_node("/root/world/players").add_child(my_player) + # Load my player + var my_player = preload("res://player.tscn").instance() + my_player.set_name(str(selfPeerID)) + my_player.set_network_master(selfPeerID) # Will be explained later + get_node("/root/world/players").add_child(my_player) - # Load other players - for p in player_info: - var player = preload("res://player.tscn").instance() - player.set_name(str(p)) - player.set_network_master(p) # Will be explained later - get_node("/root/world/players").add_child(player) + # Load other players + for p in player_info: + var player = preload("res://player.tscn").instance() + player.set_name(str(p)) + player.set_network_master(p) # Will be explained later + get_node("/root/world/players").add_child(player) - # Tell server (remember, server is always ID=1) that this peer is done pre-configuring. - # The server can call get_tree().get_rpc_sender_id() to find out who said they were done. - rpc_id(1, "done_preconfiguring") + # Tell server (remember, server is always ID=1) that this peer is done pre-configuring. + # The server can call get_tree().get_rpc_sender_id() to find out who said they were done. + rpc_id(1, "done_preconfiguring") ``` Note: @@ -319,32 +319,32 @@ Setting up players might take different amounts of time for every peer due to la To make sure the game will actually start when everyone is ready, pausing the game until all players are ready can be useful: ``` - remote func pre_configure_game(): - get_tree().set_pause(true) # Pre-pause - # The rest is the same as in the code in the previous section (look above) +remote func pre_configure_game(): + get_tree().set_pause(true) # Pre-pause + # The rest is the same as in the code in the previous section (look above) ``` When the server gets the OK from all the peers, it can tell them to start, as for example: ``` - var players_done = [] - remote func done_preconfiguring(): - var who = get_tree().get_rpc_sender_id() - # Here are some checks you can do, for example - assert(get_tree().is_network_server()) - assert(who in player_info) # Exists - assert(not who in players_done) # Was not added yet +var players_done = [] +remote func done_preconfiguring(): + var who = get_tree().get_rpc_sender_id() + # Here are some checks you can do, for example + assert(get_tree().is_network_server()) + assert(who in player_info) # Exists + assert(not who in players_done) # Was not added yet - players_done.append(who) + players_done.append(who) - if players_done.size() == player_info.size(): - rpc("post_configure_game") + if players_done.size() == player_info.size(): + rpc("post_configure_game") - remote func post_configure_game(): - # Only the server is allowed to tell a client to unpause - if 1 == get_tree().get_rpc_sender_id(): - get_tree().set_pause(false) - # Game starts now! +remote func post_configure_game(): + # Only the server is allowed to tell a client to unpause + if 1 == get_tree().get_rpc_sender_id(): + get_tree().set_pause(false) + # Game starts now! ``` @@ -367,20 +367,20 @@ Checking that a specific node instance on a peer is the network master for this If you have paid attention to the previous example, it's possible you noticed that each peer was set to have network master authority for their own player (Node) instead of the server: ``` - [...] - # Load my player - var my_player = preload("res://player.tscn").instance() - my_player.set_name(str(selfPeerID)) - my_player.set_network_master(selfPeerID) # The player belongs to this peer; it has the authority. - get_node("/root/world/players").add_child(my_player) +[...] +# Load my player +var my_player = preload("res://player.tscn").instance() +my_player.set_name(str(selfPeerID)) +my_player.set_network_master(selfPeerID) # The player belongs to this peer; it has the authority. +get_node("/root/world/players").add_child(my_player) - # Load other players - for p in player_info: - var player = preload("res://player.tscn").instance() - player.set_name(str(p)) - player.set_network_master(p) # Each other connected peer has authority over their own player. - get_node("/root/world/players").add_child(player) - [...] +# Load other players +for p in player_info: + var player = preload("res://player.tscn").instance() + player.set_name(str(p)) + player.set_network_master(p) # Each other connected peer has authority over their own player. + get_node("/root/world/players").add_child(player) +[...] ``` Each time this piece of code is executed on each peer, the peer makes itself master on the node it controls, and all other nodes remain as puppets with the server being their network master. @@ -401,26 +401,26 @@ Similarly to the `remote` keyword, functions can also be tagged with them: Example bomb code: ``` - for p in bodies_in_area: - if p.has_method("exploded"): - p.rpc("exploded", bomb_owner) +for p in bodies_in_area: + if p.has_method("exploded"): + p.rpc("exploded", bomb_owner) ``` Example player code: ``` - puppet func stun(): - stunned = true +puppet func stun(): + stunned = true - master func exploded(by_who): - if stunned: - return # Already stunned +master func exploded(by_who): + if stunned: + return # Already stunned - rpc("stun") - - # Stun this player instance for myself as well; could instead have used - # the remotesync keyword above (in place of puppet) to achieve this. - stun() + rpc("stun") + + # Stun this player instance for myself as well; could instead have used + # the remotesync keyword above (in place of puppet) to achieve this. + stun() ``` In the above example, a bomb explodes somewhere (likely managed by whoever is the master of this bomb-node, e.g. the host). @@ -457,7 +457,7 @@ Note that you could also send the `stun()` message only to a specific player by This may not make much sense for an area-of-effect case like the bomb, but might in other cases, like single target damage. ``` - rpc_id(TARGET_PEER_ID, "stun") # Only stun the target peer +rpc_id(TARGET_PEER_ID, "stun") # Only stun the target peer ``` ## Exporting for dedicated servers diff --git a/03_usage/08_networking/02_http_request_class.md b/03_usage/08_networking/02_http_request_class.md index 6431537..397f6ce 100644 --- a/03_usage/08_networking/02_http_request_class.md +++ b/03_usage/08_networking/02_http_request_class.md @@ -31,17 +31,17 @@ Below is all the code we need to make it work. The URL points to an online API m gdscript GDScript ``` - extends CanvasLayer +extends CanvasLayer - func _ready(): - $HTTPRequest.connect("request_completed", self, "_on_request_completed") +func _ready(): + $HTTPRequest.connect("request_completed", self, "_on_request_completed") - func _on_Button_pressed(): - $HTTPRequest.request("http://www.mocky.io/v2/5185415ba171ea3a00704eed") +func _on_Button_pressed(): + $HTTPRequest.request("http://www.mocky.io/v2/5185415ba171ea3a00704eed") - func _on_request_completed(result, response_code, headers, body): - var json = JSON.parse(body.get_string_from_utf8()) - print(json.result) +func _on_request_completed(result, response_code, headers, body): + var json = JSON.parse(body.get_string_from_utf8()) + print(json.result) ``` With this, you should see `(hello:world)` printed on the console; hello being a key, and world being a value, both of them strings. @@ -56,7 +56,7 @@ For example, to set a custom user agent (the HTTP `user-agent` header) you could gdscript GDScript ``` - $HTTPRequest.request("http://www.mocky.io/v2/5185415ba171ea3a00704eed", ["user-agent: YourCustomUserAgent"]) +$HTTPRequest.request("http://www.mocky.io/v2/5185415ba171ea3a00704eed", ["user-agent: YourCustomUserAgent"]) ``` Please note that, for SSL/TLS encryption and thus HTTPS URLs to work, you may need to take some steps as described `here ( doc_ssl_certificates )`. @@ -71,12 +71,12 @@ Until now, we have limited ourselves to requesting data from a server. But what gdscript GDScript ``` - func _make_post_request(url, data_to_send, use_ssl): - # Convert data to json string: - var query = JSON.print(data_to_send) - # Add 'Content-Type' header: - var headers = ["Content-Type: application/json"] - $HTTPRequest.request(url, headers, use_ssl, HTTPClient.METHOD_POST, query) +func _make_post_request(url, data_to_send, use_ssl): + # Convert data to json string: + var query = JSON.print(data_to_send) + # Add 'Content-Type' header: + var headers = ["Content-Type: application/json"] + $HTTPRequest.request(url, headers, use_ssl, HTTPClient.METHOD_POST, query) ``` Keep in mind that you have to wait for a request to finish before sending another one. Making multiple request at once requires you to have one node per request. diff --git a/03_usage/08_networking/03_http_client_class.md b/03_usage/08_networking/03_http_client_class.md index 3bc1e37..9b1faf4 100644 --- a/03_usage/08_networking/03_http_client_class.md +++ b/03_usage/08_networking/03_http_client_class.md @@ -20,7 +20,7 @@ class. It's just a script, so it can be run by executing: console GDScript ``` - c:\pandemonium> pandemonium -s http_test.gd +c:\pandemonium> pandemonium -s http_test.gd ``` @@ -29,92 +29,92 @@ It will connect and fetch a website. gdscript GDScript ``` - extends SceneTree +extends SceneTree - # HTTPClient demo - # This simple class can do HTTP requests; it will not block, but it needs to be polled. +# HTTPClient demo +# This simple class can do HTTP requests; it will not block, but it needs to be polled. - func _init(): - var err = 0 - var http = HTTPClient.new() # Create the Client. +func _init(): + var err = 0 + var http = HTTPClient.new() # Create the Client. - err = http.connect_to_host("www.php.net", 80) # Connect to host/port. - assert(err == OK) # Make sure connection is OK. + err = http.connect_to_host("www.php.net", 80) # Connect to host/port. + assert(err == OK) # Make sure connection is OK. - # Wait until resolved and connected. - while http.get_status() == HTTPClient.STATUS_CONNECTING or http.get_status() == HTTPClient.STATUS_RESOLVING: + # Wait until resolved and connected. + while http.get_status() == HTTPClient.STATUS_CONNECTING or http.get_status() == HTTPClient.STATUS_RESOLVING: + http.poll() + print("Connecting...") + if not OS.has_feature("web"): + OS.delay_msec(500) + else: + yield(Engine.get_main_loop(), "idle_frame") + + assert(http.get_status() == HTTPClient.STATUS_CONNECTED) # Check if the connection was made successfully. + + # Some headers + var headers = [ + "User-Agent: Pirulo/1.0 (Pandemonium)", + "Accept: */*" + ] + + err = http.request(HTTPClient.METHOD_GET, "/ChangeLog-5.php", headers) # Request a page from the site (this one was chunked..) + assert(err == OK) # Make sure all is OK. + + while http.get_status() == HTTPClient.STATUS_REQUESTING: + # Keep polling for as long as the request is being processed. + http.poll() + print("Requesting...") + if OS.has_feature("web"): + # Synchronous HTTP requests are not supported on the web, + # so wait for the next main loop iteration. + yield(Engine.get_main_loop(), "idle_frame") + else: + OS.delay_msec(500) + + assert(http.get_status() == HTTPClient.STATUS_BODY or http.get_status() == HTTPClient.STATUS_CONNECTED) # Make sure request finished well. + + print("response? ", http.has_response()) # Site might not have a response. + + if http.has_response(): + # If there is a response... + + headers = http.get_response_headers_as_dictionary() # Get response headers. + print("code: ", http.get_response_code()) # Show response code. + print("**headers:\\n", headers) # Show headers. + + # Getting the HTTP Body + + if http.is_response_chunked(): + # Does it use chunks? + print("Response is Chunked!") + else: + # Or just plain Content-Length + var bl = http.get_response_body_length() + print("Response Length: ", bl) + + # This method works for both anyway + + var rb = PoolByteArray() # Array that will hold the data. + + while http.get_status() == HTTPClient.STATUS_BODY: + # While there is body left to be read http.poll() - print("Connecting...") - if not OS.has_feature("web"): - OS.delay_msec(500) - else: - yield(Engine.get_main_loop(), "idle_frame") - - assert(http.get_status() == HTTPClient.STATUS_CONNECTED) # Check if the connection was made successfully. - - # Some headers - var headers = [ - "User-Agent: Pirulo/1.0 (Pandemonium)", - "Accept: */*" - ] - - err = http.request(HTTPClient.METHOD_GET, "/ChangeLog-5.php", headers) # Request a page from the site (this one was chunked..) - assert(err == OK) # Make sure all is OK. - - while http.get_status() == HTTPClient.STATUS_REQUESTING: - # Keep polling for as long as the request is being processed. - http.poll() - print("Requesting...") - if OS.has_feature("web"): - # Synchronous HTTP requests are not supported on the web, - # so wait for the next main loop iteration. - yield(Engine.get_main_loop(), "idle_frame") - else: - OS.delay_msec(500) - - assert(http.get_status() == HTTPClient.STATUS_BODY or http.get_status() == HTTPClient.STATUS_CONNECTED) # Make sure request finished well. - - print("response? ", http.has_response()) # Site might not have a response. - - if http.has_response(): - # If there is a response... - - headers = http.get_response_headers_as_dictionary() # Get response headers. - print("code: ", http.get_response_code()) # Show response code. - print("**headers:\\n", headers) # Show headers. - - # Getting the HTTP Body - - if http.is_response_chunked(): - # Does it use chunks? - print("Response is Chunked!") - else: - # Or just plain Content-Length - var bl = http.get_response_body_length() - print("Response Length: ", bl) - - # This method works for both anyway - - var rb = PoolByteArray() # Array that will hold the data. - - while http.get_status() == HTTPClient.STATUS_BODY: - # While there is body left to be read - http.poll() - # Get a chunk. - var chunk = http.read_response_body_chunk() - if chunk.size() == 0: - if not OS.has_feature("web"): - # Got nothing, wait for buffers to fill a bit. - OS.delay_usec(1000) - else: - yield(Engine.get_main_loop(), "idle_frame") + # Get a chunk. + var chunk = http.read_response_body_chunk() + if chunk.size() == 0: + if not OS.has_feature("web"): + # Got nothing, wait for buffers to fill a bit. + OS.delay_usec(1000) else: - rb = rb + chunk # Append to read buffer. - # Done! + yield(Engine.get_main_loop(), "idle_frame") + else: + rb = rb + chunk # Append to read buffer. + # Done! - print("bytes got: ", rb.size()) - var text = rb.get_string_from_ascii() - print("Text: ", text) + print("bytes got: ", rb.size()) + var text = rb.get_string_from_ascii() + print("Text: ", text) - quit() + quit() ``` diff --git a/03_usage/08_networking/04_ssl_certificates.md b/03_usage/08_networking/04_ssl_certificates.md index 5f91be8..411dea9 100644 --- a/03_usage/08_networking/04_ssl_certificates.md +++ b/03_usage/08_networking/04_ssl_certificates.md @@ -58,7 +58,7 @@ If you are using Linux, you can use the supplied certs file, generally located in: ``` - /etc/ssl/certs/ca-certificates.crt +/etc/ssl/certs/ca-certificates.crt ``` This file allows HTTPS connections to virtually any website (i.e., diff --git a/03_usage/08_networking/05_websocket.md b/03_usage/08_networking/05_websocket.md index e7453f3..beb7982 100644 --- a/03_usage/08_networking/05_websocket.md +++ b/03_usage/08_networking/05_websocket.md @@ -31,62 +31,62 @@ This example will show you how to create a WebSocket connection to a remote serv ``` - extends Node +extends Node - # The URL we will connect to - export var websocket_url = "wss://libwebsockets.org" +# The URL we will connect to +export var websocket_url = "wss://libwebsockets.org" - # Our WebSocketClient instance - var _client = WebSocketClient.new() +# Our WebSocketClient instance +var _client = WebSocketClient.new() - func _ready(): - # Connect base signals to get notified of connection open, close, and errors. - _client.connect("connection_closed", self, "_closed") - _client.connect("connection_error", self, "_closed") - _client.connect("connection_established", self, "_connected") - # This signal is emitted when not using the Multiplayer API every time - # a full packet is received. - # Alternatively, you could check get_peer(1).get_available_packets() in a loop. - _client.connect("data_received", self, "_on_data") +func _ready(): + # Connect base signals to get notified of connection open, close, and errors. + _client.connect("connection_closed", self, "_closed") + _client.connect("connection_error", self, "_closed") + _client.connect("connection_established", self, "_connected") + # This signal is emitted when not using the Multiplayer API every time + # a full packet is received. + # Alternatively, you could check get_peer(1).get_available_packets() in a loop. + _client.connect("data_received", self, "_on_data") - # Initiate connection to the given URL. - var err = _client.connect_to_url(websocket_url, ["lws-mirror-protocol"]) - if err != OK: - print("Unable to connect") - set_process(false) - - func _closed(was_clean = false): - # was_clean will tell you if the disconnection was correctly notified - # by the remote peer before closing the socket. - print("Closed, clean: ", was_clean) + # Initiate connection to the given URL. + var err = _client.connect_to_url(websocket_url, ["lws-mirror-protocol"]) + if err != OK: + print("Unable to connect") set_process(false) - func _connected(proto = ""): - # This is called on connection, "proto" will be the selected WebSocket - # sub-protocol (which is optional) - print("Connected with protocol: ", proto) - # You MUST always use get_peer(1).put_packet to send data to server, - # and not put_packet directly when not using the MultiplayerAPI. - _client.get_peer(1).put_packet("Test packet".to_utf8()) +func _closed(was_clean = false): + # was_clean will tell you if the disconnection was correctly notified + # by the remote peer before closing the socket. + print("Closed, clean: ", was_clean) + set_process(false) - func _on_data(): - # Print the received packet, you MUST always use get_peer(1).get_packet - # to receive data from server, and not get_packet directly when not - # using the MultiplayerAPI. - print("Got data from server: ", _client.get_peer(1).get_packet().get_string_from_utf8()) +func _connected(proto = ""): + # This is called on connection, "proto" will be the selected WebSocket + # sub-protocol (which is optional) + print("Connected with protocol: ", proto) + # You MUST always use get_peer(1).put_packet to send data to server, + # and not put_packet directly when not using the MultiplayerAPI. + _client.get_peer(1).put_packet("Test packet".to_utf8()) - func _process(delta): - # Call this in _process or _physics_process. Data transfer, and signals - # emission will only happen when calling this function. - _client.poll() +func _on_data(): + # Print the received packet, you MUST always use get_peer(1).get_packet + # to receive data from server, and not get_packet directly when not + # using the MultiplayerAPI. + print("Got data from server: ", _client.get_peer(1).get_packet().get_string_from_utf8()) + +func _process(delta): + # Call this in _process or _physics_process. Data transfer, and signals + # emission will only happen when calling this function. + _client.poll() ``` This will print: ``` - Connected with protocol: - Got data from server: Test packet +Connected with protocol: +Got data from server: Test packet ``` #### Minimal server example @@ -95,65 +95,65 @@ This example will show you how to create a WebSocket server that listens for rem ``` - extends Node +extends Node - # The port we will listen to - const PORT = 9080 - # Our WebSocketServer instance - var _server = WebSocketServer.new() +# The port we will listen to +const PORT = 9080 +# Our WebSocketServer instance +var _server = WebSocketServer.new() - func _ready(): - # Connect base signals to get notified of new client connections, - # disconnections, and disconnect requests. - _server.connect("client_connected", self, "_connected") - _server.connect("client_disconnected", self, "_disconnected") - _server.connect("client_close_request", self, "_close_request") - # This signal is emitted when not using the Multiplayer API every time a - # full packet is received. - # Alternatively, you could check get_peer(PEER_ID).get_available_packets() - # in a loop for each connected peer. - _server.connect("data_received", self, "_on_data") - # Start listening on the given port. - var err = _server.listen(PORT) - if err != OK: - print("Unable to start server") - set_process(false) +func _ready(): + # Connect base signals to get notified of new client connections, + # disconnections, and disconnect requests. + _server.connect("client_connected", self, "_connected") + _server.connect("client_disconnected", self, "_disconnected") + _server.connect("client_close_request", self, "_close_request") + # This signal is emitted when not using the Multiplayer API every time a + # full packet is received. + # Alternatively, you could check get_peer(PEER_ID).get_available_packets() + # in a loop for each connected peer. + _server.connect("data_received", self, "_on_data") + # Start listening on the given port. + var err = _server.listen(PORT) + if err != OK: + print("Unable to start server") + set_process(false) - func _connected(id, proto): - # This is called when a new peer connects, "id" will be the assigned peer id, - # "proto" will be the selected WebSocket sub-protocol (which is optional) - print("Client %d connected with protocol: %s" % [id, proto]) +func _connected(id, proto): + # This is called when a new peer connects, "id" will be the assigned peer id, + # "proto" will be the selected WebSocket sub-protocol (which is optional) + print("Client %d connected with protocol: %s" % [id, proto]) - func _close_request(id, code, reason): - # This is called when a client notifies that it wishes to close the connection, - # providing a reason string and close code. - print("Client %d disconnecting with code: %d, reason: %s" % [id, code, reason]) +func _close_request(id, code, reason): + # This is called when a client notifies that it wishes to close the connection, + # providing a reason string and close code. + print("Client %d disconnecting with code: %d, reason: %s" % [id, code, reason]) - func _disconnected(id, was_clean = false): - # This is called when a client disconnects, "id" will be the one of the - # disconnecting client, "was_clean" will tell you if the disconnection - # was correctly notified by the remote peer before closing the socket. - print("Client %d disconnected, clean: %s" % [id, str(was_clean)]) +func _disconnected(id, was_clean = false): + # This is called when a client disconnects, "id" will be the one of the + # disconnecting client, "was_clean" will tell you if the disconnection + # was correctly notified by the remote peer before closing the socket. + print("Client %d disconnected, clean: %s" % [id, str(was_clean)]) - func _on_data(id): - # Print the received packet, you MUST always use get_peer(id).get_packet to receive data, - # and not get_packet directly when not using the MultiplayerAPI. - var pkt = _server.get_peer(id).get_packet() - print("Got data from client %d: %s ... echoing" % [id, pkt.get_string_from_utf8()]) - _server.get_peer(id).put_packet(pkt) +func _on_data(id): + # Print the received packet, you MUST always use get_peer(id).get_packet to receive data, + # and not get_packet directly when not using the MultiplayerAPI. + var pkt = _server.get_peer(id).get_packet() + print("Got data from client %d: %s ... echoing" % [id, pkt.get_string_from_utf8()]) + _server.get_peer(id).put_packet(pkt) - func _process(delta): - # Call this in _process or _physics_process. - # Data transfer, and signals emission will only happen when calling this function. - _server.poll() +func _process(delta): + # Call this in _process or _physics_process. + # Data transfer, and signals emission will only happen when calling this function. + _server.poll() ``` This will print (when a client connects) something similar to this: ``` - Client 1348090059 connected with protocol: selected-protocol - Got data from client 1348090059: Test packet ... echoing +Client 1348090059 connected with protocol: selected-protocol +Got data from client 1348090059: Test packet ... echoing ``` #### Advanced chat demo diff --git a/03_usage/08_networking/06_webrtc.md b/03_usage/08_networking/06_webrtc.md index 5f546f4..023f21c 100644 --- a/03_usage/08_networking/06_webrtc.md +++ b/03_usage/08_networking/06_webrtc.md @@ -47,55 +47,55 @@ This example will show you how to create a WebRTC connection between two peers i This is not very useful in real life, but will give you a good overview of how a WebRTC connection is set up. ``` - extends Node +extends Node - # Create the two peers - var p1 = WebRTCPeerConnection.new() - var p2 = WebRTCPeerConnection.new() - # And a negotiated channel for each each peer - var ch1 = p1.create_data_channel("chat", {"id": 1, "negotiated": true}) - var ch2 = p2.create_data_channel("chat", {"id": 1, "negotiated": true}) +# Create the two peers +var p1 = WebRTCPeerConnection.new() +var p2 = WebRTCPeerConnection.new() +# And a negotiated channel for each each peer +var ch1 = p1.create_data_channel("chat", {"id": 1, "negotiated": true}) +var ch2 = p2.create_data_channel("chat", {"id": 1, "negotiated": true}) - func _ready(): - # Connect P1 session created to itself to set local description - p1.connect("session_description_created", p1, "set_local_description") - # Connect P1 session and ICE created to p2 set remote description and candidates - p1.connect("session_description_created", p2, "set_remote_description") - p1.connect("ice_candidate_created", p2, "add_ice_candidate") +func _ready(): + # Connect P1 session created to itself to set local description + p1.connect("session_description_created", p1, "set_local_description") + # Connect P1 session and ICE created to p2 set remote description and candidates + p1.connect("session_description_created", p2, "set_remote_description") + p1.connect("ice_candidate_created", p2, "add_ice_candidate") - # Same for P2 - p2.connect("session_description_created", p2, "set_local_description") - p2.connect("session_description_created", p1, "set_remote_description") - p2.connect("ice_candidate_created", p1, "add_ice_candidate") + # Same for P2 + p2.connect("session_description_created", p2, "set_local_description") + p2.connect("session_description_created", p1, "set_remote_description") + p2.connect("ice_candidate_created", p1, "add_ice_candidate") - # Let P1 create the offer - p1.create_offer() + # Let P1 create the offer + p1.create_offer() - # Wait a second and send message from P1 - yield(get_tree().create_timer(1), "timeout") - ch1.put_packet("Hi from P1".to_utf8()) + # Wait a second and send message from P1 + yield(get_tree().create_timer(1), "timeout") + ch1.put_packet("Hi from P1".to_utf8()) - # Wait a second and send message from P2 - yield(get_tree().create_timer(1), "timeout") - ch2.put_packet("Hi from P2".to_utf8()) + # Wait a second and send message from P2 + yield(get_tree().create_timer(1), "timeout") + ch2.put_packet("Hi from P2".to_utf8()) - func _process(_delta): - # Poll connections - p1.poll() - p2.poll() +func _process(_delta): + # Poll connections + p1.poll() + p2.poll() - # Check for messages - if ch1.get_ready_state() == ch1.STATE_OPEN and ch1.get_available_packet_count() > 0: - print("P1 received: ", ch1.get_packet().get_string_from_utf8()) - if ch2.get_ready_state() == ch2.STATE_OPEN and ch2.get_available_packet_count() > 0: - print("P2 received: ", ch2.get_packet().get_string_from_utf8()) + # Check for messages + if ch1.get_ready_state() == ch1.STATE_OPEN and ch1.get_available_packet_count() > 0: + print("P1 received: ", ch1.get_packet().get_string_from_utf8()) + if ch2.get_ready_state() == ch2.STATE_OPEN and ch2.get_available_packet_count() > 0: + print("P2 received: ", ch2.get_packet().get_string_from_utf8()) ``` This will print: ``` - P1 received: Hi from P1 - P2 received: Hi from P2 +P1 received: Hi from P1 +P2 received: Hi from P2 ``` #### Local signaling example @@ -103,41 +103,41 @@ This will print: This example expands on the previous one, separating the peers in two different scenes, and using a `singleton ( doc_singletons_autoload )` as a signaling server. ``` - # An example P2P chat client (chat.gd) - extends Node +# An example P2P chat client (chat.gd) +extends Node - var peer = WebRTCPeerConnection.new() +var peer = WebRTCPeerConnection.new() - # Create negotiated data channel - var channel = peer.create_data_channel("chat", {"negotiated": true, "id": 1}) +# Create negotiated data channel +var channel = peer.create_data_channel("chat", {"negotiated": true, "id": 1}) - func _ready(): - # Connect all functions - peer.connect("ice_candidate_created", self, "_on_ice_candidate") - peer.connect("session_description_created", self, "_on_session") +func _ready(): + # Connect all functions + peer.connect("ice_candidate_created", self, "_on_ice_candidate") + peer.connect("session_description_created", self, "_on_session") - # Register to the local signaling server (see below for the implementation) - Signaling.register(get_path()) + # Register to the local signaling server (see below for the implementation) + Signaling.register(get_path()) - func _on_ice_candidate(mid, index, sdp): - # Send the ICE candidate to the other peer via signaling server - Signaling.send_candidate(get_path(), mid, index, sdp) +func _on_ice_candidate(mid, index, sdp): + # Send the ICE candidate to the other peer via signaling server + Signaling.send_candidate(get_path(), mid, index, sdp) - func _on_session(type, sdp): - # Send the session to other peer via signaling server - Signaling.send_session(get_path(), type, sdp) - # Set generated description as local - peer.set_local_description(type, sdp) +func _on_session(type, sdp): + # Send the session to other peer via signaling server + Signaling.send_session(get_path(), type, sdp) + # Set generated description as local + peer.set_local_description(type, sdp) - func _process(delta): - # Always poll the connection frequently - peer.poll() - if channel.get_ready_state() == WebRTCDataChannel.STATE_OPEN: - while channel.get_available_packet_count() > 0: - print(get_path(), " received: ", channel.get_packet().get_string_from_utf8()) +func _process(delta): + # Always poll the connection frequently + peer.poll() + if channel.get_ready_state() == WebRTCDataChannel.STATE_OPEN: + while channel.get_available_packet_count() > 0: + print(get_path(), " received: ", channel.get_packet().get_string_from_utf8()) - func send_message(message): - channel.put_packet(message.to_utf8()) +func send_message(message): + channel.put_packet(message.to_utf8()) ``` And now for the local signaling server: @@ -146,63 +146,63 @@ Note: This local signaling server is supposed to be used as a `singleton ( doc_singletons_autoload )` to connect two peers in the same scene. ``` - # A local signaling server. Add this to autoloads with name "Signaling" (/root/Signaling) - extends Node +# A local signaling server. Add this to autoloads with name "Signaling" (/root/Signaling) +extends Node - # We will store the two peers here - var peers = [] +# We will store the two peers here +var peers = [] - func register(path): - assert(peers.size() < 2) - peers.append(path) - # If it's the second one, create an offer - if peers.size() == 2: - get_node(peers[0]).peer.create_offer() +func register(path): + assert(peers.size() < 2) + peers.append(path) + # If it's the second one, create an offer + if peers.size() == 2: + get_node(peers[0]).peer.create_offer() - func _find_other(path): - # Find the other registered peer. - for p in peers: - if p != path: - return p - return "" +func _find_other(path): + # Find the other registered peer. + for p in peers: + if p != path: + return p + return "" - func send_session(path, type, sdp): - var other = _find_other(path) - assert(other != "") - get_node(other).peer.set_remote_description(type, sdp) +func send_session(path, type, sdp): + var other = _find_other(path) + assert(other != "") + get_node(other).peer.set_remote_description(type, sdp) - func send_candidate(path, mid, index, sdp): - var other = _find_other(path) - assert(other != "") - get_node(other).peer.add_ice_candidate(mid, index, sdp) +func send_candidate(path, mid, index, sdp): + var other = _find_other(path) + assert(other != "") + get_node(other).peer.add_ice_candidate(mid, index, sdp) ``` Then you can use it like this: ``` - # Main scene (main.gd) - extends Node +# Main scene (main.gd) +extends Node - const Chat = preload("res://chat.gd") +const Chat = preload("res://chat.gd") - func _ready(): - var p1 = Chat.new() - var p2 = Chat.new() - add_child(p1) - add_child(p2) - yield(get_tree().create_timer(1), "timeout") - p1.send_message("Hi from %s" % p1.get_path()) +func _ready(): + var p1 = Chat.new() + var p2 = Chat.new() + add_child(p1) + add_child(p2) + yield(get_tree().create_timer(1), "timeout") + p1.send_message("Hi from %s" % p1.get_path()) - # Wait a second and send message from P2 - yield(get_tree().create_timer(1), "timeout") - p2.send_message("Hi from %s" % p2.get_path()) + # Wait a second and send message from P2 + yield(get_tree().create_timer(1), "timeout") + p2.send_message("Hi from %s" % p2.get_path()) ``` This will print something similar to this: ``` - /root/main/@@3 received: Hi from /root/main/@@2 - /root/main/@@2 received: Hi from /root/main/@@3 +/root/main/@@3 received: Hi from /root/main/@@2 +/root/main/@@2 received: Hi from /root/main/@@3 ``` #### Remote signaling with WebSocket diff --git a/03_usage/09_audio/03_sync_with_audio.md b/03_usage/09_audio/03_sync_with_audio.md index 6d7076d..d3c0f8e 100644 --- a/03_usage/09_audio/03_sync_with_audio.md +++ b/03_usage/09_audio/03_sync_with_audio.md @@ -34,24 +34,24 @@ Add these two and it's possible to guess almost exactly when sound or music will gdscript GDScript ``` - var time_begin - var time_delay +var time_begin +var time_delay - func _ready(): - time_begin = OS.get_ticks_usec() - time_delay = AudioServer.get_time_to_next_mix() + AudioServer.get_output_latency() - $Player.play() +func _ready(): + time_begin = OS.get_ticks_usec() + time_delay = AudioServer.get_time_to_next_mix() + AudioServer.get_output_latency() + $Player.play() - func _process(delta): - # Obtain from ticks. - var time = (OS.get_ticks_usec() - time_begin) / 1000000.0 - # Compensate for latency. - time -= time_delay - # May be below 0 (did not begin yet). - time = max(0, time) - print("Time is: ", time) +func _process(delta): + # Obtain from ticks. + var time = (OS.get_ticks_usec() - time_begin) / 1000000.0 + # Compensate for latency. + time -= time_delay + # May be below 0 (did not begin yet). + time = max(0, time) + print("Time is: ", time) ``` @@ -71,7 +71,7 @@ Adding the return value from this function to *get_playback_position()* increase gdscript GDScript ``` - var time = $Player.get_playback_position() + AudioServer.get_time_since_last_mix() +var time = $Player.get_playback_position() + AudioServer.get_time_since_last_mix() ``` To increase precision, subtract the latency information (how much it takes for the audio to be heard after it was mixed): @@ -79,7 +79,7 @@ To increase precision, subtract the latency information (how much it takes for t gdscript GDScript ``` - var time = $Player.get_playback_position() + AudioServer.get_time_since_last_mix() - AudioServer.get_output_latency() +var time = $Player.get_playback_position() + AudioServer.get_time_since_last_mix() - AudioServer.get_output_latency() ``` The result may be a bit jittery due how multiple threads work. Just check that the value is not less than in the previous frame (discard it if so). This is also a less precise approach than the one before, but it will work for songs of any length, or synchronizing anything (sound effects, as an example) to music. @@ -89,13 +89,13 @@ Here is the same code as before using this approach: gdscript GDScript ``` - func _ready(): - $Player.play() +func _ready(): + $Player.play() - func _process(delta): - var time = $Player.get_playback_position() + AudioServer.get_time_since_last_mix() - # Compensate for output latency. - time -= AudioServer.get_output_latency() - print("Time is: ", time) +func _process(delta): + var time = $Player.get_playback_position() + AudioServer.get_time_since_last_mix() + # Compensate for output latency. + time -= AudioServer.get_output_latency() + print("Time is: ", time) ``` diff --git a/03_usage/09_audio/04_recording_with_microphone.md b/03_usage/09_audio/04_recording_with_microphone.md index a760022..c2d46ad 100644 --- a/03_usage/09_audio/04_recording_with_microphone.md +++ b/03_usage/09_audio/04_recording_with_microphone.md @@ -27,16 +27,16 @@ An `AudioStreamPlayer` named `AudioStreamRecord` is used for recording. gdscript GDScript ``` - var effect - var recording +var effect +var recording - func _ready(): - # We get the index of the "Record" bus. - var idx = AudioServer.get_bus_index("Record") - # And use it to retrieve its first effect, which has been defined - # as an "AudioEffectRecord" resource. - effect = AudioServer.get_bus_effect(idx, 0) +func _ready(): + # We get the index of the "Record" bus. + var idx = AudioServer.get_bus_index("Record") + # And use it to retrieve its first effect, which has been defined + # as an "AudioEffectRecord" resource. + effect = AudioServer.get_bus_effect(idx, 0) ``` The audio recording is handled by the `AudioEffectRecord` resource @@ -48,20 +48,20 @@ and `set_recording_active()`. gdscript GDScript ``` - func _on_RecordButton_pressed(): - if effect.is_recording_active(): - recording = effect.get_recording() - $PlayButton.disabled = false - $SaveButton.disabled = false - effect.set_recording_active(false) - $RecordButton.text = "Record" - $Status.text = "" - else: - $PlayButton.disabled = true - $SaveButton.disabled = true - effect.set_recording_active(true) - $RecordButton.text = "Stop" - $Status.text = "Recording..." +func _on_RecordButton_pressed(): + if effect.is_recording_active(): + recording = effect.get_recording() + $PlayButton.disabled = false + $SaveButton.disabled = false + effect.set_recording_active(false) + $RecordButton.text = "Record" + $Status.text = "" + else: + $PlayButton.disabled = true + $SaveButton.disabled = true + effect.set_recording_active(true) + $RecordButton.text = "Stop" + $Status.text = "Recording..." ``` @@ -76,16 +76,16 @@ the recorded stream can be stored into the `recording` variable by calling gdscript GDScript ``` - func _on_PlayButton_pressed(): - print(recording) - print(recording.format) - print(recording.mix_rate) - print(recording.stereo) - var data = recording.get_data() - print(data) - print(data.size()) - $AudioStreamPlayer.stream = recording - $AudioStreamPlayer.play() +func _on_PlayButton_pressed(): + print(recording) + print(recording.format) + print(recording.mix_rate) + print(recording.stereo) + var data = recording.get_data() + print(data) + print(data.size()) + $AudioStreamPlayer.stream = recording + $AudioStreamPlayer.play() ``` To playback the recording, you assign the recording as the stream of the @@ -94,10 +94,10 @@ To playback the recording, you assign the recording as the stream of the gdscript GDScript ``` - func _on_SaveButton_pressed(): - var save_path = $SaveButton/Filename.text - recording.save_to_wav(save_path) - $Status.text = "Saved WAV file to: %s\n(%s)" % [save_path, ProjectSettings.globalize_path(save_path)] +func _on_SaveButton_pressed(): + var save_path = $SaveButton/Filename.text + recording.save_to_wav(save_path) + $Status.text = "Saved WAV file to: %s\n(%s)" % [save_path, ProjectSettings.globalize_path(save_path)] ``` diff --git a/03_usage/10_physics/01_physics_introduction.md b/03_usage/10_physics/01_physics_introduction.md index 7f1ce37..f5ce64d 100644 --- a/03_usage/10_physics/01_physics_introduction.md +++ b/03_usage/10_physics/01_physics_introduction.md @@ -157,20 +157,20 @@ The code equivalent of the above example where layers 1, 3 and 4 were enabled would be as follows: ``` - # Example: Setting mask value for enabling layers 1, 3 and 4 +# Example: Setting mask value for enabling layers 1, 3 and 4 - # Binary - set the bit corresponding to the layers you want to enable (1, 3, and 4) to 1, set all other bits to 0. - # Note: Layer 20 is the first bit, layer 1 is the last. The mask for layers 4,3 and 1 is therefore - 0b00000000000000001101 - # (This can be shortened to 0b1101) +# Binary - set the bit corresponding to the layers you want to enable (1, 3, and 4) to 1, set all other bits to 0. +# Note: Layer 20 is the first bit, layer 1 is the last. The mask for layers 4,3 and 1 is therefore +0b00000000000000001101 +# (This can be shortened to 0b1101) - # Hexadecimal equivalent (1101 binary converted to hexadecimal) - 0x000d - # (This value can be shortened to 0xd) +# Hexadecimal equivalent (1101 binary converted to hexadecimal) +0x000d +# (This value can be shortened to 0xd) - # Decimal - Add the results of 2 to the power of (layer to be enabled - 1). - # (2^(1-1)) + (2^(3-1)) + (2^(4-1)) = 1 + 4 + 8 = 13 - pow(2, 1) + pow(2, 3) + pow(2, 4) +# Decimal - Add the results of 2 to the power of (layer to be enabled - 1). +# (2^(1-1)) + (2^(3-1)) + (2^(4-1)) = 1 + 4 + 8 = 13 +pow(2, 1) + pow(2, 3) + pow(2, 4) ``` @@ -258,22 +258,22 @@ For example, here is the code for an "Asteroids" style spaceship: gdscript GDScript ``` - extends RigidBody2D +extends RigidBody2D - var thrust = Vector2(0, 250) - var torque = 20000 +var thrust = Vector2(0, 250) +var torque = 20000 - func _integrate_forces(state): - if Input.is_action_pressed("ui_up"): - applied_force = thrust.rotated(rotation) - else: - applied_force = Vector2() - var rotation_dir = 0 - if Input.is_action_pressed("ui_right"): - rotation_dir += 1 - if Input.is_action_pressed("ui_left"): - rotation_dir -= 1 - applied_torque = rotation_dir * torque +func _integrate_forces(state): + if Input.is_action_pressed("ui_up"): + applied_force = thrust.rotated(rotation) + else: + applied_force = Vector2() + var rotation_dir = 0 + if Input.is_action_pressed("ui_right"): + rotation_dir += 1 + if Input.is_action_pressed("ui_left"): + rotation_dir -= 1 + applied_torque = rotation_dir * torque ``` Note that we are not setting the `linear_velocity` or `angular_velocity` @@ -332,14 +332,14 @@ occurred: gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - var velocity = Vector2(250, 250) +var velocity = Vector2(250, 250) - func _physics_process(delta): - var collision_info = move_and_collide(velocity * delta) - if collision_info: - var collision_point = collision_info.position +func _physics_process(delta): + var collision_info = move_and_collide(velocity * delta) + if collision_info: + var collision_point = collision_info.position ``` Or to bounce off of the colliding object: @@ -347,14 +347,14 @@ Or to bounce off of the colliding object: gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - var velocity = Vector2(250, 250) +var velocity = Vector2(250, 250) - func _physics_process(delta): - var collision_info = move_and_collide(velocity * delta) - if collision_info: - velocity = velocity.bounce(collision_info.normal) +func _physics_process(delta): + var collision_info = move_and_collide(velocity * delta) + if collision_info: + velocity = velocity.bounce(collision_info.normal) ``` #### `move_and_slide` @@ -376,31 +376,31 @@ the ground (including slopes) and jump when standing on the ground: gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - var run_speed = 350 - var jump_speed = -1000 - var gravity = 2500 +var run_speed = 350 +var jump_speed = -1000 +var gravity = 2500 - var velocity = Vector2() +var velocity = Vector2() - func get_input(): - velocity.x = 0 - var right = Input.is_action_pressed('ui_right') - var left = Input.is_action_pressed('ui_left') - var jump = Input.is_action_just_pressed('ui_select') +func get_input(): + velocity.x = 0 + var right = Input.is_action_pressed('ui_right') + var left = Input.is_action_pressed('ui_left') + var jump = Input.is_action_just_pressed('ui_select') - if is_on_floor() and jump: - velocity.y = jump_speed - if right: - velocity.x += run_speed - if left: - velocity.x -= run_speed + if is_on_floor() and jump: + velocity.y = jump_speed + if right: + velocity.x += run_speed + if left: + velocity.x -= run_speed - func _physics_process(delta): - velocity.y += gravity * delta - get_input() - velocity = move_and_slide(velocity, Vector2(0, -1)) +func _physics_process(delta): + velocity.y += gravity * delta + get_input() + velocity = move_and_slide(velocity, Vector2(0, -1)) ``` diff --git a/03_usage/10_physics/02_rigid_body.md b/03_usage/10_physics/02_rigid_body.md index d6c5188..e98f1f5 100644 --- a/03_usage/10_physics/02_rigid_body.md +++ b/03_usage/10_physics/02_rigid_body.md @@ -30,19 +30,19 @@ Here is a custom `look_at()` method that will work reliably with rigid bodies: gdscript GDScript ``` - extends RigidBody +extends RigidBody - func look_follow(state, current_transform, target_position): - var up_dir = Vector3(0, 1, 0) - var cur_dir = current_transform.basis.xform(Vector3(0, 0, 1)) - var target_dir = (target_position - current_transform.origin).normalized() - var rotation_angle = acos(cur_dir.x) - acos(target_dir.x) +func look_follow(state, current_transform, target_position): + var up_dir = Vector3(0, 1, 0) + var cur_dir = current_transform.basis.xform(Vector3(0, 0, 1)) + var target_dir = (target_position - current_transform.origin).normalized() + var rotation_angle = acos(cur_dir.x) - acos(target_dir.x) - state.set_angular_velocity(up_dir * (rotation_angle / state.get_step())) + state.set_angular_velocity(up_dir * (rotation_angle / state.get_step())) - func _integrate_forces(state): - var target_position = $my_target_spatial_node.get_global_transform().origin - look_follow(state, get_global_transform(), target_position) +func _integrate_forces(state): + var target_position = $my_target_spatial_node.get_global_transform().origin + look_follow(state, get_global_transform(), target_position) ``` diff --git a/03_usage/10_physics/03_using_area_2d.md b/03_usage/10_physics/03_using_area_2d.md index c320ed9..ff4d9ea 100644 --- a/03_usage/10_physics/03_using_area_2d.md +++ b/03_usage/10_physics/03_using_area_2d.md @@ -68,10 +68,10 @@ Note: gdscript GDScript ``` - extends Area2D +extends Area2D - func _on_Coin_body_entered(body): - queue_free() +func _on_Coin_body_entered(body): + queue_free() ``` Now our player can collect the coins! diff --git a/03_usage/10_physics/04_using_kinematic_body_2d.md b/03_usage/10_physics/04_using_kinematic_body_2d.md index 4e3a4b9..dbea3d4 100644 --- a/03_usage/10_physics/04_using_kinematic_body_2d.md +++ b/03_usage/10_physics/04_using_kinematic_body_2d.md @@ -115,16 +115,16 @@ and `get_slide_collision()`: gdscript GDScript ``` - # Using move_and_collide. - var collision = move_and_collide(velocity * delta) - if collision: - print("I collided with ", collision.collider.name) +# Using move_and_collide. +var collision = move_and_collide(velocity * delta) +if collision: + print("I collided with ", collision.collider.name) - # Using move_and_slide. - velocity = move_and_slide(velocity) - for i in get_slide_count(): - var collision = get_slide_collision(i) - print("I collided with ", collision.collider.name) +# Using move_and_slide. +velocity = move_and_slide(velocity) +for i in get_slide_count(): + var collision = get_slide_collision(i) + print("I collided with ", collision.collider.name) ``` Note: @@ -147,13 +147,13 @@ the same collision response: gdscript GDScript ``` - # using move_and_collide - var collision = move_and_collide(velocity * delta) - if collision: - velocity = velocity.slide(collision.normal) +# using move_and_collide +var collision = move_and_collide(velocity * delta) +if collision: + velocity = velocity.slide(collision.normal) - # using move_and_slide - velocity = move_and_slide(velocity) +# using move_and_slide +velocity = move_and_slide(velocity) ``` Anything you do with `move_and_slide()` can also be done with `move_and_collide()`, @@ -198,27 +198,27 @@ Attach a script to the KinematicBody2D and add the following code: gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - var speed = 250 - var velocity = Vector2() +var speed = 250 +var velocity = Vector2() - func get_input(): - # Detect up/down/left/right keystate and only move when pressed. - velocity = Vector2() - if Input.is_action_pressed('ui_right'): - velocity.x += 1 - if Input.is_action_pressed('ui_left'): - velocity.x -= 1 - if Input.is_action_pressed('ui_down'): - velocity.y += 1 - if Input.is_action_pressed('ui_up'): - velocity.y -= 1 - velocity = velocity.normalized() * speed +func get_input(): + # Detect up/down/left/right keystate and only move when pressed. + velocity = Vector2() + if Input.is_action_pressed('ui_right'): + velocity.x += 1 + if Input.is_action_pressed('ui_left'): + velocity.x -= 1 + if Input.is_action_pressed('ui_down'): + velocity.y += 1 + if Input.is_action_pressed('ui_up'): + velocity.y -= 1 + velocity = velocity.normalized() * speed - func _physics_process(delta): - get_input() - move_and_collide(velocity * delta) +func _physics_process(delta): + get_input() + move_and_collide(velocity * delta) ``` @@ -258,35 +258,35 @@ uses the mouse pointer. Here is the code for the Player, using `move_and_slide() gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - var Bullet = preload("res://Bullet.tscn") - var speed = 200 - var velocity = Vector2() +var Bullet = preload("res://Bullet.tscn") +var speed = 200 +var velocity = Vector2() - func get_input(): - # Add these actions in Project Settings -> Input Map. - velocity = Vector2() - if Input.is_action_pressed('backward'): - velocity = Vector2(-speed/3, 0).rotated(rotation) - if Input.is_action_pressed('forward'): - velocity = Vector2(speed, 0).rotated(rotation) - if Input.is_action_just_pressed('mouse_click'): - shoot() +func get_input(): + # Add these actions in Project Settings -> Input Map. + velocity = Vector2() + if Input.is_action_pressed('backward'): + velocity = Vector2(-speed/3, 0).rotated(rotation) + if Input.is_action_pressed('forward'): + velocity = Vector2(speed, 0).rotated(rotation) + if Input.is_action_just_pressed('mouse_click'): + shoot() - func shoot(): - # "Muzzle" is a Position2D placed at the barrel of the gun. - var b = Bullet.instance() - b.start($Muzzle.global_position, rotation) - get_parent().add_child(b) +func shoot(): + # "Muzzle" is a Position2D placed at the barrel of the gun. + var b = Bullet.instance() + b.start($Muzzle.global_position, rotation) + get_parent().add_child(b) - func _physics_process(delta): - get_input() - var dir = get_global_mouse_position() - global_position - # Don't move if too close to the mouse pointer. - if dir.length() > 5: - rotation = dir.angle() - velocity = move_and_slide(velocity) +func _physics_process(delta): + get_input() + var dir = get_global_mouse_position() - global_position + # Don't move if too close to the mouse pointer. + if dir.length() > 5: + rotation = dir.angle() + velocity = move_and_slide(velocity) ``` @@ -295,25 +295,25 @@ And the code for the Bullet: gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - var speed = 750 - var velocity = Vector2() +var speed = 750 +var velocity = Vector2() - func start(pos, dir): - rotation = dir - position = pos - velocity = Vector2(speed, 0).rotated(rotation) +func start(pos, dir): + rotation = dir + position = pos + velocity = Vector2(speed, 0).rotated(rotation) - func _physics_process(delta): - var collision = move_and_collide(velocity * delta) - if collision: - velocity = velocity.bounce(collision.normal) - if collision.collider.has_method("hit"): - collision.collider.hit() +func _physics_process(delta): + var collision = move_and_collide(velocity * delta) + if collision: + velocity = velocity.bounce(collision.normal) + if collision.collider.has_method("hit"): + collision.collider.hit() - func _on_VisibilityNotifier2D_screen_exited(): - queue_free() +func _on_VisibilityNotifier2D_screen_exited(): + queue_free() ``` The action happens in `physics_process()`. After using `move_and_collide()`, if a @@ -345,35 +345,35 @@ Here's the code for the player body: gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - export (int) var run_speed = 100 - export (int) var jump_speed = -400 - export (int) var gravity = 1200 +export (int) var run_speed = 100 +export (int) var jump_speed = -400 +export (int) var gravity = 1200 - var velocity = Vector2() - var jumping = false +var velocity = Vector2() +var jumping = false - func get_input(): - velocity.x = 0 - var right = Input.is_action_pressed('ui_right') - var left = Input.is_action_pressed('ui_left') - var jump = Input.is_action_just_pressed('ui_select') +func get_input(): + velocity.x = 0 + var right = Input.is_action_pressed('ui_right') + var left = Input.is_action_pressed('ui_left') + var jump = Input.is_action_just_pressed('ui_select') - if jump and is_on_floor(): - jumping = true - velocity.y = jump_speed - if right: - velocity.x += run_speed - if left: - velocity.x -= run_speed + if jump and is_on_floor(): + jumping = true + velocity.y = jump_speed + if right: + velocity.x += run_speed + if left: + velocity.x -= run_speed - func _physics_process(delta): - get_input() - velocity.y += gravity * delta - if jumping and is_on_floor(): - jumping = false - velocity = move_and_slide(velocity, Vector2(0, -1)) +func _physics_process(delta): + get_input() + velocity.y += gravity * delta + if jumping and is_on_floor(): + jumping = false + velocity = move_and_slide(velocity, Vector2(0, -1)) ``` ![](img/k2d_platform.gif) diff --git a/03_usage/10_physics/05_ray_casting.md b/03_usage/10_physics/05_ray_casting.md index b707bb0..964709c 100644 --- a/03_usage/10_physics/05_ray_casting.md +++ b/03_usage/10_physics/05_ray_casting.md @@ -50,9 +50,9 @@ Use the following code in 2D: gdscript GDscript ``` - func _physics_process(delta): - var space_rid = get_world_2d().space - var space_state = Physics2DServer.space_get_direct_state(space_rid) +func _physics_process(delta): + var space_rid = get_world_2d().space + var space_state = Physics2DServer.space_get_direct_state(space_rid) ``` Or more directly: @@ -60,8 +60,8 @@ Or more directly: gdscript GDScript ``` - func _physics_process(delta): - var space_state = get_world_2d().direct_space_state +func _physics_process(delta): + var space_state = get_world_2d().direct_space_state ``` And in 3D: @@ -69,8 +69,8 @@ And in 3D: gdscript GDScript ``` - func _physics_process(delta): - var space_state = get_world().direct_space_state +func _physics_process(delta): + var space_state = get_world().direct_space_state ``` ## Raycast query @@ -82,10 +82,10 @@ may be used. For example: gdscript GDScript ``` - func _physics_process(delta): - var space_state = get_world_2d().direct_space_state - # use global coordinates, not local to node - var result = space_state.intersect_ray(Vector2(0, 0), Vector2(50, 100)) +func _physics_process(delta): + var space_state = get_world_2d().direct_space_state + # use global coordinates, not local to node + var result = space_state.intersect_ray(Vector2(0, 0), Vector2(50, 100)) ``` The result is a dictionary. If the ray didn't hit anything, the dictionary will @@ -94,23 +94,23 @@ be empty. If it did hit something, it will contain collision information: gdscript GDScript ``` - if result: - print("Hit at point: ", result.position) +if result: + print("Hit at point: ", result.position) ``` The `result` dictionary when a collision occurs contains the following data: ``` - { - position: Vector2 # point in world space for collision - normal: Vector2 # normal in world space for collision - collider: Object # Object collided or null (if unassociated) - collider_id: ObjectID # Object it collided against - rid: RID # RID it collided against - shape: int # shape index of collider - metadata: Variant() # metadata of collider - } +{ + position: Vector2 # point in world space for collision + normal: Vector2 # normal in world space for collision + collider: Object # Object collided or null (if unassociated) + collider_id: ObjectID # Object it collided against + rid: RID # RID it collided against + shape: int # shape index of collider + metadata: Variant() # metadata of collider +} ``` The data is similar in 3D space, using Vector3 coordinates. @@ -132,11 +132,11 @@ collision object node: gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - func _physics_process(delta): - var space_state = get_world_2d().direct_space_state - var result = space_state.intersect_ray(global_position, enemy_position, [self]) +func _physics_process(delta): + var space_state = get_world_2d().direct_space_state + var result = space_state.intersect_ray(global_position, enemy_position, [self]) ``` The exceptions array can contain objects or RIDs. @@ -154,12 +154,12 @@ member variable: gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - func _physics_process(delta): - var space_state = get_world().direct_space_state - var result = space_state.intersect_ray(global_position, enemy_position, - [self], collision_mask) +func _physics_process(delta): + var space_state = get_world().direct_space_state + var result = space_state.intersect_ray(global_position, enemy_position, + [self], collision_mask) ``` See `doc_physics_introduction_collision_layer_code_example` for details on how to set the collision mask. @@ -185,13 +185,13 @@ To obtain it using a camera, the following code can be used: gdscript GDScript ``` - const ray_length = 1000 +const ray_length = 1000 - func _input(event): - if event is InputEventMouseButton and event.pressed and event.button_index == 1: - var camera = $Camera - var from = camera.project_ray_origin(event.position) - var to = from + camera.project_ray_normal(event.position) * ray_length +func _input(event): + if event is InputEventMouseButton and event.pressed and event.button_index == 1: + var camera = $Camera + var from = camera.project_ray_origin(event.position) + var to = from + camera.project_ray_normal(event.position) * ray_length ``` diff --git a/03_usage/10_physics/06_ragdoll_system.md b/03_usage/10_physics/06_ragdoll_system.md index 1980a22..5d34ac0 100644 --- a/03_usage/10_physics/06_ragdoll_system.md +++ b/03_usage/10_physics/06_ragdoll_system.md @@ -56,8 +56,8 @@ This is the final result: The ragdoll is now ready to use. To start the simulation and play the ragdoll animation, you need to call the `physical_bones_start_simulation` method. Attach a script to the skeleton node and call the method in the `ready` method: ``` - func _ready(): - physical_bones_start_simulation() +func _ready(): + physical_bones_start_simulation() ``` To stop the simulation, call the `physical_bones_stop_simulation()` method. diff --git a/03_usage/10_physics/07_kinematic_character_2d.md b/03_usage/10_physics/07_kinematic_character_2d.md index 32427a3..155689f 100644 --- a/03_usage/10_physics/07_kinematic_character_2d.md +++ b/03_usage/10_physics/07_kinematic_character_2d.md @@ -54,10 +54,10 @@ or lose precision if the frame rate is too high or too low. gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - func _physics_process(delta): - pass +func _physics_process(delta): + pass ``` @@ -106,10 +106,10 @@ So, let's move our sprite downwards until it hits the floor: gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - func _physics_process(delta): - move_and_collide(Vector2(0, 1)) # Move down 1 pixel per physics frame +func _physics_process(delta): + move_and_collide(Vector2(0, 1)) # Move down 1 pixel per physics frame ``` The result is that the character will move, but stop right when @@ -121,16 +121,16 @@ little more like a regular game character: gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - const GRAVITY = 200.0 - var velocity = Vector2() +const GRAVITY = 200.0 +var velocity = Vector2() - func _physics_process(delta): - velocity.y += delta * GRAVITY +func _physics_process(delta): + velocity.y += delta * GRAVITY - var motion = velocity * delta - move_and_collide(motion) + var motion = velocity * delta + move_and_collide(motion) ``` Now the character falls smoothly. Let's make it walk to the sides, left @@ -142,28 +142,28 @@ This adds simple walking support by pressing left and right: gdscript GDScript ``` - extends KinematicBody2D +extends KinematicBody2D - const GRAVITY = 200.0 - const WALK_SPEED = 200 +const GRAVITY = 200.0 +const WALK_SPEED = 200 - var velocity = Vector2() +var velocity = Vector2() - func _physics_process(delta): - velocity.y += delta * GRAVITY +func _physics_process(delta): + velocity.y += delta * GRAVITY - if Input.is_action_pressed("ui_left"): - velocity.x = -WALK_SPEED - elif Input.is_action_pressed("ui_right"): - velocity.x = WALK_SPEED - else: - velocity.x = 0 + if Input.is_action_pressed("ui_left"): + velocity.x = -WALK_SPEED + elif Input.is_action_pressed("ui_right"): + velocity.x = WALK_SPEED + else: + velocity.x = 0 - # We don't need to multiply velocity by delta because "move_and_slide" already takes delta time into account. + # We don't need to multiply velocity by delta because "move_and_slide" already takes delta time into account. - # The second parameter of "move_and_slide" is the normal pointing up. - # In the case of a 2D platformer, in Pandemonium, upward is negative y, which translates to -1 as a normal. - move_and_slide(velocity, Vector2(0, -1)) + # The second parameter of "move_and_slide" is the normal pointing up. + # In the case of a 2D platformer, in Pandemonium, upward is negative y, which translates to -1 as a normal. + move_and_slide(velocity, Vector2(0, -1)) ``` And give it a try. diff --git a/03_usage/10_physics/interpolation/02_physics_interpolation_introduction.md b/03_usage/10_physics/interpolation/02_physics_interpolation_introduction.md index 4135921..8d6d7c1 100644 --- a/03_usage/10_physics/interpolation/02_physics_interpolation_introduction.md +++ b/03_usage/10_physics/interpolation/02_physics_interpolation_introduction.md @@ -62,8 +62,8 @@ If our physics ticks are happening 10 times per second (for this example), what First of all, we have to calculate how far through the physics tick we want the object to be. If the last physics tick took place at 0.1 seconds, we are 0.02 seconds *(0.12 - 0.1)* through a tick that we know will take 0.1 seconds (10 ticks per second). The fraction through the tick is thus: ``` - fraction = 0.02 / 0.10 - fraction = 0.2 +fraction = 0.02 / 0.10 +fraction = 0.2 ``` This is called the **physics interpolation fraction**, and is handily calculated for you by Pandemonium. It can be retrieved on any frame by calling `Engine.get_physics_interpolation_fraction( Engine_method_get_physics_interpolation_fraction )`. @@ -73,15 +73,15 @@ This is called the **physics interpolation fraction**, and is handily calculated Once we have the interpolation fraction, we can insert it into a standard linear interpolation equation. The X coordinate would thus be: ``` - x_interpolated = x_prev + ((x_curr - x_prev) * 0.2) +x_interpolated = x_prev + ((x_curr - x_prev) * 0.2) ``` So substituting our `x_prev` as 10, and `x_curr` as 30: ``` - x_interpolated = 10 + ((30 - 10) * 0.2) - x_interpolated = 10 + 4 - x_interpolated = 14 +x_interpolated = 10 + ((30 - 10) * 0.2) +x_interpolated = 10 + 4 +x_interpolated = 14 ``` Let's break that down: diff --git a/03_usage/10_physics/interpolation/04_advanced_physics_interpolation.md b/03_usage/10_physics/interpolation/04_advanced_physics_interpolation.md index eaa46d7..26de8e3 100644 --- a/03_usage/10_physics/interpolation/04_advanced_physics_interpolation.md +++ b/03_usage/10_physics/interpolation/04_advanced_physics_interpolation.md @@ -55,32 +55,32 @@ Note: Here is an example of a simple fixed Camera which follows an interpolated target: ``` - extends Camera - - # Node that the camera will follow - var _target - - # We will smoothly lerp to follow the target - # rather than follow exactly - var _target_pos : Vector3 = Vector3() - - func _ready() -> void: - # Find the target node - _target = get_node("../Player") - - # Turn off automatic physics interpolation for the Camera, - # we will be doing this manually - set_physics_interpolation_mode(Node.PHYSICS_INTERPOLATION_MODE_OFF) - - func _process(delta: float) -> void: - # Find the current interpolated transform of the target - var tr : Transform = _target.get_global_transform_interpolated() - - # Provide some delayed smoothed lerping towards the target position - _target_pos = lerp(_target_pos, tr.origin, min(delta, 1.0)) - - # Fixed camera position, but it will follow the target - look_at(_target_pos, Vector3(0, 1, 0)) +extends Camera + +# Node that the camera will follow +var _target + +# We will smoothly lerp to follow the target +# rather than follow exactly +var _target_pos : Vector3 = Vector3() + +func _ready() -> void: + # Find the target node + _target = get_node("../Player") + + # Turn off automatic physics interpolation for the Camera, + # we will be doing this manually + set_physics_interpolation_mode(Node.PHYSICS_INTERPOLATION_MODE_OFF) + +func _process(delta: float) -> void: + # Find the current interpolated transform of the target + var tr : Transform = _target.get_global_transform_interpolated() + + # Provide some delayed smoothed lerping towards the target position + _target_pos = lerp(_target_pos, tr.origin, min(delta, 1.0)) + + # Fixed camera position, but it will follow the target + look_at(_target_pos, Vector3(0, 1, 0)) ``` #### Mouse look diff --git a/03_usage/11_navigation/01_navigation_introduction_2d.md b/03_usage/11_navigation/01_navigation_introduction_2d.md index 73b36be..3a7516a 100644 --- a/03_usage/11_navigation/01_navigation_introduction_2d.md +++ b/03_usage/11_navigation/01_navigation_introduction_2d.md @@ -119,45 +119,45 @@ NavigationServer2D and a NavigationAgent2D for path movement. GDScript ``` - extends CharacterBody2D +extends CharacterBody2D - var movement_speed: float = 200.0 - var movement_target_position: Vector2 = Vector2(60.0,180.0) +var movement_speed: float = 200.0 +var movement_target_position: Vector2 = Vector2(60.0,180.0) - @onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D +@onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D - func _ready(): - # These values need to be adjusted for the actor's speed - # and the navigation layout. - navigation_agent.path_desired_distance = 4.0 - navigation_agent.target_desired_distance = 4.0 +func _ready(): + # These values need to be adjusted for the actor's speed + # and the navigation layout. + navigation_agent.path_desired_distance = 4.0 + navigation_agent.target_desired_distance = 4.0 - # Make sure to not await during _ready. - call_deferred("actor_setup") + # Make sure to not await during _ready. + call_deferred("actor_setup") - func actor_setup(): - # Wait for the first physics frame so the NavigationServer can sync. - await get_tree().physics_frame +func actor_setup(): + # Wait for the first physics frame so the NavigationServer can sync. + await get_tree().physics_frame - # Now that the navigation map is no longer empty, set the movement target. - set_movement_target(movement_target_position) + # Now that the navigation map is no longer empty, set the movement target. + set_movement_target(movement_target_position) - func set_movement_target(movement_target: Vector2): - navigation_agent.target_position = movement_target +func set_movement_target(movement_target: Vector2): + navigation_agent.target_position = movement_target - func _physics_process(delta): - if navigation_agent.is_navigation_finished(): - return +func _physics_process(delta): + if navigation_agent.is_navigation_finished(): + return - var current_agent_position: Vector2 = global_position - var next_path_position: Vector2 = navigation_agent.get_next_path_position() + var current_agent_position: Vector2 = global_position + var next_path_position: Vector2 = navigation_agent.get_next_path_position() - var new_velocity: Vector2 = next_path_position - current_agent_position - new_velocity = new_velocity.normalized() - new_velocity = new_velocity * movement_speed + var new_velocity: Vector2 = next_path_position - current_agent_position + new_velocity = new_velocity.normalized() + new_velocity = new_velocity * movement_speed - velocity = new_velocity - move_and_slide() + velocity = new_velocity + move_and_slide() ``` Note: diff --git a/03_usage/11_navigation/02_navigation_introduction_3d.md b/03_usage/11_navigation/02_navigation_introduction_3d.md index ab312fe..bf0a7f9 100644 --- a/03_usage/11_navigation/02_navigation_introduction_3d.md +++ b/03_usage/11_navigation/02_navigation_introduction_3d.md @@ -124,45 +124,45 @@ a NavigationAgent3D for path movement. GDScript ``` - extends CharacterBody3D +extends CharacterBody3D - var movement_speed: float = 2.0 - var movement_target_position: Vector3 = Vector3(-3.0,0.0,2.0) +var movement_speed: float = 2.0 +var movement_target_position: Vector3 = Vector3(-3.0,0.0,2.0) - @onready var navigation_agent: NavigationAgent3D = $NavigationAgent3D +@onready var navigation_agent: NavigationAgent3D = $NavigationAgent3D - func _ready(): - # These values need to be adjusted for the actor's speed - # and the navigation layout. - navigation_agent.path_desired_distance = 0.5 - navigation_agent.target_desired_distance = 0.5 +func _ready(): + # These values need to be adjusted for the actor's speed + # and the navigation layout. + navigation_agent.path_desired_distance = 0.5 + navigation_agent.target_desired_distance = 0.5 - # Make sure to not await during _ready. - call_deferred("actor_setup") + # Make sure to not await during _ready. + call_deferred("actor_setup") - func actor_setup(): - # Wait for the first physics frame so the NavigationServer can sync. - await get_tree().physics_frame +func actor_setup(): + # Wait for the first physics frame so the NavigationServer can sync. + await get_tree().physics_frame - # Now that the navigation map is no longer empty, set the movement target. - set_movement_target(movement_target_position) + # Now that the navigation map is no longer empty, set the movement target. + set_movement_target(movement_target_position) - func set_movement_target(movement_target: Vector3): - navigation_agent.set_target_position(movement_target) +func set_movement_target(movement_target: Vector3): + navigation_agent.set_target_position(movement_target) - func _physics_process(delta): - if navigation_agent.is_navigation_finished(): - return +func _physics_process(delta): + if navigation_agent.is_navigation_finished(): + return - var current_agent_position: Vector3 = global_position - var next_path_position: Vector3 = navigation_agent.get_next_path_position() + var current_agent_position: Vector3 = global_position + var next_path_position: Vector3 = navigation_agent.get_next_path_position() - var new_velocity: Vector3 = next_path_position - current_agent_position - new_velocity = new_velocity.normalized() - new_velocity = new_velocity * movement_speed + var new_velocity: Vector3 = next_path_position - current_agent_position + new_velocity = new_velocity.normalized() + new_velocity = new_velocity * movement_speed - velocity = new_velocity - move_and_slide() + velocity = new_velocity + move_and_slide() ``` Note: diff --git a/03_usage/11_navigation/03_navigation_using_navigationservers.md b/03_usage/11_navigation/03_navigation_using_navigationservers.md index fdba5fc..bd344da 100644 --- a/03_usage/11_navigation/03_navigation_using_navigationservers.md +++ b/03_usage/11_navigation/03_navigation_using_navigationservers.md @@ -110,54 +110,54 @@ Afterwards the function waits for the next physics_frame before continuing with GDScript ``` - extends Node3D +extends Node3D - func _ready(): - # use call deferred to make sure the entire SceneTree Nodes are setup - # else await / yield on 'physics_frame' in a _ready() might get stuck - call_deferred("custom_setup") +func _ready(): + # use call deferred to make sure the entire SceneTree Nodes are setup + # else await / yield on 'physics_frame' in a _ready() might get stuck + call_deferred("custom_setup") - func custom_setup(): +func custom_setup(): - # create a new navigation map - var map: RID = NavigationServer3D.map_create() - NavigationServer3D.map_set_up(map, Vector3.UP) - NavigationServer3D.map_set_active(map, true) + # create a new navigation map + var map: RID = NavigationServer3D.map_create() + NavigationServer3D.map_set_up(map, Vector3.UP) + NavigationServer3D.map_set_active(map, true) - # create a new navigation region and add it to the map - var region: RID = NavigationServer3D.region_create() - NavigationServer3D.region_set_transform(region, Transform()) - NavigationServer3D.region_set_map(region, map) + # create a new navigation region and add it to the map + var region: RID = NavigationServer3D.region_create() + NavigationServer3D.region_set_transform(region, Transform()) + NavigationServer3D.region_set_map(region, map) - # create a procedural navigation mesh for the region - var new_navigation_mesh: NavigationMesh = NavigationMesh.new() - var vertices: PackedVector3Array = PackedVector3Array([ - Vector3(0,0,0), - Vector3(9.0,0,0), - Vector3(0,0,9.0) - ]) - new_navigation_mesh.set_vertices(vertices) - var polygon: PackedInt32Array = PackedInt32Array([0, 1, 2]) - new_navigation_mesh.add_polygon(polygon) - NavigationServer3D.region_set_navigation_mesh(region, new_navigation_mesh) + # create a procedural navigation mesh for the region + var new_navigation_mesh: NavigationMesh = NavigationMesh.new() + var vertices: PackedVector3Array = PackedVector3Array([ + Vector3(0,0,0), + Vector3(9.0,0,0), + Vector3(0,0,9.0) + ]) + new_navigation_mesh.set_vertices(vertices) + var polygon: PackedInt32Array = PackedInt32Array([0, 1, 2]) + new_navigation_mesh.add_polygon(polygon) + NavigationServer3D.region_set_navigation_mesh(region, new_navigation_mesh) - # wait for NavigationServer sync to adapt to made changes - await get_tree().physics_frame + # wait for NavigationServer sync to adapt to made changes + await get_tree().physics_frame - # query the path from the navigationserver - var start_position: Vector3 = Vector3(0.1, 0.0, 0.1) - var target_position: Vector3 = Vector3(1.0, 0.0, 1.0) - var optimize_path: bool = true + # query the path from the navigationserver + var start_position: Vector3 = Vector3(0.1, 0.0, 0.1) + var target_position: Vector3 = Vector3(1.0, 0.0, 1.0) + var optimize_path: bool = true - var path: PackedVector3Array = NavigationServer3D.map_get_path( - map, - start_position, - target_position, - optimize_path - ) + var path: PackedVector3Array = NavigationServer3D.map_get_path( + map, + start_position, + target_position, + optimize_path + ) - print("Found a path!") - print(path) + print("Found a path!") + print(path) ``` ### Server Avoidance Callbacks diff --git a/03_usage/11_navigation/04_navigation_using_navigationmaps.md b/03_usage/11_navigation/04_navigation_using_navigationmaps.md index 377a381..2cb098a 100644 --- a/03_usage/11_navigation/04_navigation_using_navigationmaps.md +++ b/03_usage/11_navigation/04_navigation_using_navigationmaps.md @@ -25,9 +25,9 @@ The 3D default navigation `map` can be obtained with ``get_world_3d().get_naviga GDScript ``` - extends Node2D +extends Node2D - var default_2d_navigation_map_rid: RID = get_world_2d().get_navigation_map() +var default_2d_navigation_map_rid: RID = get_world_2d().get_navigation_map() ``` ### Creating new navigation maps @@ -49,19 +49,19 @@ Note: GDScript ``` - extends Node2D +extends Node2D - var new_navigation_map: RID = NavigationServer2D.map_create() - NavigationServer2D.map_set_active(true) +var new_navigation_map: RID = NavigationServer2D.map_create() +NavigationServer2D.map_set_active(true) ``` GDScript ``` - extends Node3D +extends Node3D - var new_navigation_map: RID = NavigationServer3D.map_create() - NavigationServer3D.map_set_active(true) +var new_navigation_map: RID = NavigationServer3D.map_create() +NavigationServer3D.map_set_active(true) ``` Note: diff --git a/03_usage/11_navigation/05_navigation_using_navigationregions.md b/03_usage/11_navigation/05_navigation_using_navigationregions.md index a6821e4..19226ac 100644 --- a/03_usage/11_navigation/05_navigation_using_navigationregions.md +++ b/03_usage/11_navigation/05_navigation_using_navigationregions.md @@ -38,9 +38,9 @@ The region RID can then be obtained from NavigationRegion Nodes with `get_region GDScript ``` - extends NavigationRegion3D +extends NavigationRegion3D - var navigationserver_region_rid: RID = get_region_rid() +var navigationserver_region_rid: RID = get_region_rid() ``` New regions can also be created with the NavigationServer API and added to any existing map. @@ -50,21 +50,21 @@ If regions are created with the NavigationServer API directly they need to be as GDScript ``` - extends Node2D +extends Node2D - var new_2d_region_rid: RID = NavigationServer2D.region_create() - var default_2d_map_rid: RID = get_world_2d().get_navigation_map() - NavigationServer2D.region_set_map(new_2d_region_rid, default_2d_map_rid) +var new_2d_region_rid: RID = NavigationServer2D.region_create() +var default_2d_map_rid: RID = get_world_2d().get_navigation_map() +NavigationServer2D.region_set_map(new_2d_region_rid, default_2d_map_rid) ``` GDScript ``` - extends Node3D +extends Node3D - var new_3d_region_rid: RID = NavigationServer3D.region_create() - var default_3d_map_rid: RID = get_world_3d().get_navigation_map() - NavigationServer3D.region_set_map(new_3d_region_rid, default_3d_map_rid) +var new_3d_region_rid: RID = NavigationServer3D.region_create() +var default_3d_map_rid: RID = get_world_3d().get_navigation_map() +NavigationServer3D.region_set_map(new_3d_region_rid, default_3d_map_rid) ``` Note: diff --git a/03_usage/11_navigation/06_navigation_using_navigationmeshes.md b/03_usage/11_navigation/06_navigation_using_navigationmeshes.md index e423580..4f0a63b 100644 --- a/03_usage/11_navigation/06_navigation_using_navigationmeshes.md +++ b/03_usage/11_navigation/06_navigation_using_navigationmeshes.md @@ -78,24 +78,24 @@ If the navigation mesh resource is already prepared, the region can be updated w GDScript ``` - extends NavigationRegion3D +extends NavigationRegion3D - func update_navigation_mesh(): +func update_navigation_mesh(): - # use bake and update function of region - var on_thread: bool = true - bake_navigation_mesh(on_thread) + # use bake and update function of region + var on_thread: bool = true + bake_navigation_mesh(on_thread) - # or use the NavigationMeshGenerator Singleton - var _navigationmesh: NavigationMesh = navigation_mesh - NavigationMeshGenerator.bake(_navigationmesh, self) - # remove old resource first to trigger a full update - navigation_mesh = null - navigation_mesh = _navigationmesh + # or use the NavigationMeshGenerator Singleton + var _navigationmesh: NavigationMesh = navigation_mesh + NavigationMeshGenerator.bake(_navigationmesh, self) + # remove old resource first to trigger a full update + navigation_mesh = null + navigation_mesh = _navigationmesh - # or use NavigationServer API to update region with prepared navigation mesh - var region_rid: RID = get_region_rid() - NavigationServer3D.region_set_navigation_mesh(region_rid, navigation_mesh) + # or use NavigationServer API to update region with prepared navigation mesh + var region_rid: RID = get_region_rid() + NavigationServer3D.region_set_navigation_mesh(region_rid, navigation_mesh) ``` Note: @@ -130,32 +130,32 @@ navigationmesh from outline data the shapes cannot overlap. GDScript ``` - extends NavigationRegion2D +extends NavigationRegion2D - var new_navigation_polygon: NavigationPolygon = get_navigation_polygon() +var new_navigation_polygon: NavigationPolygon = get_navigation_polygon() - func _ready(): +func _ready(): - parse_2d_collisionshapes(self) + parse_2d_collisionshapes(self) - new_navigation_polygon.make_polygons_from_outlines() - set_navigation_polygon(new_navigation_polygon) + new_navigation_polygon.make_polygons_from_outlines() + set_navigation_polygon(new_navigation_polygon) - func parse_2d_collisionshapes(root_node: Node2D): +func parse_2d_collisionshapes(root_node: Node2D): - for node in root_node.get_children(): + for node in root_node.get_children(): - if node.get_child_count() > 0: - parse_2d_collisionshapes(node) + if node.get_child_count() > 0: + parse_2d_collisionshapes(node) - if node is CollisionPolygon2D: + if node is CollisionPolygon2D: - var collisionpolygon_transform: Transform2D = node.get_global_transform() - var collisionpolygon: PackedVector2Array = node.polygon + var collisionpolygon_transform: Transform2D = node.get_global_transform() + var collisionpolygon: PackedVector2Array = node.polygon - var new_collision_outline: PackedVector2Array = collisionpolygon_transform * collisionpolygon + var new_collision_outline: PackedVector2Array = collisionpolygon_transform * collisionpolygon - new_navigation_polygon.add_outline(new_collision_outline) + new_navigation_polygon.add_outline(new_collision_outline) ``` ### Procedual 2D NavigationMesh @@ -165,24 +165,24 @@ The following script creates a new 2D navigation region and fills it with proced GDScript ``` - extends Node2D +extends Node2D - var new_2d_region_rid: RID = NavigationServer2D.region_create() +var new_2d_region_rid: RID = NavigationServer2D.region_create() - var default_2d_map_rid: RID = get_world_2d().get_navigation_map() - NavigationServer2D.region_set_map(new_2d_region_rid, default_2d_map_rid) +var default_2d_map_rid: RID = get_world_2d().get_navigation_map() +NavigationServer2D.region_set_map(new_2d_region_rid, default_2d_map_rid) - var new_navigation_polygon: NavigationPolygon = NavigationPolygon.new() - var new_outline: PackedVector2Array = PackedVector2Array([ - Vector2(0.0, 0.0), - Vector2(50.0, 0.0), - Vector2(50.0, 50.0), - Vector2(0.0, 50.0), - ]) - new_navigation_polygon.add_outline(new_outline) - new_navigation_polygon.make_polygons_from_outlines() +var new_navigation_polygon: NavigationPolygon = NavigationPolygon.new() +var new_outline: PackedVector2Array = PackedVector2Array([ + Vector2(0.0, 0.0), + Vector2(50.0, 0.0), + Vector2(50.0, 50.0), + Vector2(0.0, 50.0), +]) +new_navigation_polygon.add_outline(new_outline) +new_navigation_polygon.make_polygons_from_outlines() - NavigationServer2D.region_set_navigation_polygon(new_2d_region_rid, new_navigation_polygon) +NavigationServer2D.region_set_navigation_polygon(new_2d_region_rid, new_navigation_polygon) ``` ### Procedual 3D NavigationMesh @@ -192,25 +192,25 @@ The following script creates a new 3D navigation region and fills it with proced GDScript ``` - extends Node3D +extends Node3D - var new_3d_region_rid: RID = NavigationServer3D.region_create() +var new_3d_region_rid: RID = NavigationServer3D.region_create() - var default_3d_map_rid: RID = get_world_3d().get_navigation_map() - NavigationServer3D.region_set_map(new_3d_region_rid, default_3d_map_rid) +var default_3d_map_rid: RID = get_world_3d().get_navigation_map() +NavigationServer3D.region_set_map(new_3d_region_rid, default_3d_map_rid) - var new_navigation_mesh: NavigationMesh = NavigationMesh.new() - # Add vertices for a triangle. - new_navigation_mesh.vertices = PackedVector3Array([ - Vector3(-1.0, 0.0, 1.0), - Vector3(1.0, 0.0, 1.0), - Vector3(1.0, 0.0, -1.0) - ]) - # Add indices for the polygon. - new_navigation_mesh.add_polygon( - PackedInt32Array([0, 1, 2]) - ) - NavigationServer3D.region_set_navigation_mesh(new_3d_region_rid, new_navigation_mesh) +var new_navigation_mesh: NavigationMesh = NavigationMesh.new() +# Add vertices for a triangle. +new_navigation_mesh.vertices = PackedVector3Array([ + Vector3(-1.0, 0.0, 1.0), + Vector3(1.0, 0.0, 1.0), + Vector3(1.0, 0.0, -1.0) +]) +# Add indices for the polygon. +new_navigation_mesh.add_polygon( + PackedInt32Array([0, 1, 2]) +) +NavigationServer3D.region_set_navigation_mesh(new_3d_region_rid, new_navigation_mesh) ``` ### Navmesh for 3D GridMaps @@ -220,42 +220,42 @@ The following script creates a new 3D navigation mesh for each GridMap items, cl GDScript ``` - extends GridMap +extends GridMap - # enable navigation mesh for grid items - set_bake_navigation(true) +# enable navigation mesh for grid items +set_bake_navigation(true) - # get grid items, create and set a new navigation mesh for each item in the MeshLibrary - var gridmap_item_list: PackedInt32Array = mesh_library.get_item_list() - for item in gridmap_item_list: - var new_item_navigation_mesh: NavigationMesh = NavigationMesh.new() - # Add vertices and polygons that describe the traversable ground surface. - # E.g. for a convex polygon that resembles a flat square. - new_item_navigation_mesh.vertices = PackedVector3Array([ - Vector3(-1.0, 0.0, 1.0), - Vector3(1.0, 0.0, 1.0), - Vector3(1.0, 0.0, -1.0), - Vector3(-1.0, 0.0, -1.0), - ]) - new_item_navigation_mesh.add_polygon( - PackedInt32Array([0, 1, 2, 3]) - ) - mesh_library.set_item_navigation_mesh(item, new_item_navigation_mesh) - mesh_library.set_item_navigation_mesh_transform(item, Transform3D()) +# get grid items, create and set a new navigation mesh for each item in the MeshLibrary +var gridmap_item_list: PackedInt32Array = mesh_library.get_item_list() +for item in gridmap_item_list: + var new_item_navigation_mesh: NavigationMesh = NavigationMesh.new() + # Add vertices and polygons that describe the traversable ground surface. + # E.g. for a convex polygon that resembles a flat square. + new_item_navigation_mesh.vertices = PackedVector3Array([ + Vector3(-1.0, 0.0, 1.0), + Vector3(1.0, 0.0, 1.0), + Vector3(1.0, 0.0, -1.0), + Vector3(-1.0, 0.0, -1.0), + ]) + new_item_navigation_mesh.add_polygon( + PackedInt32Array([0, 1, 2, 3]) + ) + mesh_library.set_item_navigation_mesh(item, new_item_navigation_mesh) + mesh_library.set_item_navigation_mesh_transform(item, Transform3D()) - # clear the cells - clear() +# clear the cells +clear() - # add procedual cells using the first item - var _position: Vector3i = Vector3i(global_transform.origin) - var _item: int = 0 - var _orientation: int = 0 - for i in range(0,10): - for j in range(0,10): - _position.x = i - _position.z = j - gridmap.set_cell_item(_position, _item, _orientation) - _position.x = -i - _position.z = -j - gridmap.set_cell_item(_position, _item, _orientation) +# add procedual cells using the first item +var _position: Vector3i = Vector3i(global_transform.origin) +var _item: int = 0 +var _orientation: int = 0 +for i in range(0,10): + for j in range(0,10): + _position.x = i + _position.z = j + gridmap.set_cell_item(_position, _item, _orientation) + _position.x = -i + _position.z = -j + gridmap.set_cell_item(_position, _item, _orientation) ``` diff --git a/03_usage/11_navigation/07_navigation_using_navigationpaths.md b/03_usage/11_navigation/07_navigation_using_navigationpaths.md index ea9b9b9..8de516a 100644 --- a/03_usage/11_navigation/07_navigation_using_navigationpaths.md +++ b/03_usage/11_navigation/07_navigation_using_navigationpaths.md @@ -31,33 +31,33 @@ Outside of grids due to polygons often covering large open areas with a single, GDScript ``` - extends Node2D - # basic query for a navigation path in 2D using the default navigation map - var default_2d_map_rid: RID = get_world_2d().get_navigation_map() - var start_position: Vector2 = Vector2(0.0, 0.0) - var target_position: Vector2 = Vector2(5.0, 0.0) - var path: PackedVector2Array = NavigationServer2D.map_get_path( - default_2d_map_rid, - start_position, - target_position, - true - ) +extends Node2D + # basic query for a navigation path in 2D using the default navigation map +var default_2d_map_rid: RID = get_world_2d().get_navigation_map() +var start_position: Vector2 = Vector2(0.0, 0.0) +var target_position: Vector2 = Vector2(5.0, 0.0) +var path: PackedVector2Array = NavigationServer2D.map_get_path( + default_2d_map_rid, + start_position, + target_position, + true +) ``` GDScript ``` - extends Node3D - # basic query for a navigation path in 3D using the default navigation map - var default_3d_map_rid: RID = get_world_3d().get_navigation_map() - var start_position: Vector3 = Vector3(0.0, 0.0, 0.0) - var target_position: Vector3 = Vector3(5.0, 0.0, 3.0) - var path: PackedVector3Array = NavigationServer3D.map_get_path( - default_3d_map_rid, - start_position, - target_position, - true - ) +extends Node3D +# basic query for a navigation path in 3D using the default navigation map +var default_3d_map_rid: RID = get_world_3d().get_navigation_map() +var start_position: Vector3 = Vector3(0.0, 0.0, 0.0) +var target_position: Vector3 = Vector3(5.0, 0.0, 3.0) +var path: PackedVector3Array = NavigationServer3D.map_get_path( + default_3d_map_rid, + start_position, + target_position, + true +) ``` A returned `path` by the NavigationServer will be a `PackedVector2Array` for 2D or a `PackedVector3Array` for 3D. @@ -78,48 +78,48 @@ the default navigation map by setting the target position with `set_movement_tar GDScript ``` - var movement_speed: float = 4.0 - var movement_delta: float - var path_point_margin: float = 0.5 - var default_3d_map_rid: RID = get_world_3d().get_navigation_map() +var movement_speed: float = 4.0 +var movement_delta: float +var path_point_margin: float = 0.5 +var default_3d_map_rid: RID = get_world_3d().get_navigation_map() - var current_path_index: int = 0 - var current_path_point: Vector3 - var current_path: PackedVector3Array +var current_path_index: int = 0 +var current_path_point: Vector3 +var current_path: PackedVector3Array - func set_movement_target(target_position: Vector3): +func set_movement_target(target_position: Vector3): - var start_position: Vector3 = global_transform.origin + var start_position: Vector3 = global_transform.origin - current_path = NavigationServer3D.map_get_path( - default_3d_map_rid, - start_position, - target_position, - true - ) + current_path = NavigationServer3D.map_get_path( + default_3d_map_rid, + start_position, + target_position, + true + ) - if not current_path.is_empty(): + if not current_path.is_empty(): + current_path_index = 0 + current_path_point = current_path[0] + +func _physics_process(delta): + + if current_path.is_empty(): + return + + movement_delta = move_speed * delta + + if global_transform.origin.distance_to(current_path_point) <= path_point_margin: + current_path_index += 1 + if current_path_index >= current_path.size(): + current_path = [] current_path_index = 0 - current_path_point = current_path[0] - - func _physics_process(delta): - - if current_path.is_empty(): + current_path_point = global_transform.origin return - movement_delta = move_speed * delta + current_path_point = current_path[current_path_index] - if global_transform.origin.distance_to(current_path_point) <= path_point_margin: - current_path_index += 1 - if current_path_index >= current_path.size(): - current_path = [] - current_path_index = 0 - current_path_point = global_transform.origin - return + var new_velocity: Vector3 = (current_path_point - global_transform.origin).normalized() * movement_delta - current_path_point = current_path[current_path_index] - - var new_velocity: Vector3 = (current_path_point - global_transform.origin).normalized() * movement_delta - - global_transform.origin = global_transform.origin.move_toward(global_transform.origin + new_velocity, movement_delta) + global_transform.origin = global_transform.origin.move_toward(global_transform.origin + new_velocity, movement_delta) ``` diff --git a/03_usage/11_navigation/08_navigation_using_navigationpathqueryobjects.md b/03_usage/11_navigation/08_navigation_using_navigationpathqueryobjects.md index b0b9cbf..a3c0fae 100644 --- a/03_usage/11_navigation/08_navigation_using_navigationpathqueryobjects.md +++ b/03_usage/11_navigation/08_navigation_using_navigationpathqueryobjects.md @@ -30,33 +30,33 @@ has a large quantity of simultaneous agents that regularly update their paths. GDScript ``` - # prepare query objects - var query_parameters = NavigationPathQueryParameters2D.new() - var query_result = NavigationPathQueryResult2D.new() +# prepare query objects +var query_parameters = NavigationPathQueryParameters2D.new() +var query_result = NavigationPathQueryResult2D.new() - # update parameters object - query_parameters.map = get_world_2d().get_navigation_map() - query_parameters.start_position = agent2d_current_global_position - query_parameters.target_position = agent2d_target_global_position +# update parameters object +query_parameters.map = get_world_2d().get_navigation_map() +query_parameters.start_position = agent2d_current_global_position +query_parameters.target_position = agent2d_target_global_position - # update result object - NavigationServer2D.query_path(query_parameters, query_result) - var path: PackedVector2Array = query_result.get_path() +# update result object +NavigationServer2D.query_path(query_parameters, query_result) +var path: PackedVector2Array = query_result.get_path() ``` GDScript ``` - # prepare query objects - var query_parameters = NavigationPathQueryParameters3D.new() - var query_result = NavigationPathQueryResult3D.new() +# prepare query objects +var query_parameters = NavigationPathQueryParameters3D.new() +var query_result = NavigationPathQueryResult3D.new() - # update parameters object - query_parameters.map = get_world_3d().get_navigation_map() - query_parameters.start_position = agent3d_current_global_position - query_parameters.target_position = agent3d_target_global_position +# update parameters object +query_parameters.map = get_world_3d().get_navigation_map() +query_parameters.start_position = agent3d_current_global_position +query_parameters.target_position = agent3d_target_global_position - # update result object - NavigationServer3D.query_path(query_parameters, query_result) - var path: PackedVector3Array = query_result.get_path() +# update result object +NavigationServer3D.query_path(query_parameters, query_result) +var path: PackedVector3Array = query_result.get_path() ``` diff --git a/03_usage/11_navigation/09_navigation_using_navigationagents.md b/03_usage/11_navigation/09_navigation_using_navigationagents.md index 7e29f91..fbc141f 100644 --- a/03_usage/11_navigation/09_navigation_using_navigationagents.md +++ b/03_usage/11_navigation/09_navigation_using_navigationagents.md @@ -132,39 +132,39 @@ toggle avoidance on agents, create or delete avoidance callbacks or switch avoid GDScript ``` - extends NavigationAgent2D +extends NavigationAgent2D - var agent: RID = get_rid() - # Enable avoidance - NavigationServer2D.agent_set_avoidance_enabled(agent, true) - # Create avoidance callback - NavigationServer2D.agent_set_avoidance_callback(agent, Callable(self, "_avoidance_done")) +var agent: RID = get_rid() +# Enable avoidance +NavigationServer2D.agent_set_avoidance_enabled(agent, true) +# Create avoidance callback +NavigationServer2D.agent_set_avoidance_callback(agent, Callable(self, "_avoidance_done")) - # Disable avoidance - NavigationServer2D.agent_set_avoidance_enabled(agent, false) - # Delete avoidance callback - NavigationServer2D.agent_set_avoidance_callback(agent, Callable()) +# Disable avoidance +NavigationServer2D.agent_set_avoidance_enabled(agent, false) +# Delete avoidance callback +NavigationServer2D.agent_set_avoidance_callback(agent, Callable()) ``` GDScript ``` - extends NavigationAgent3D +extends NavigationAgent3D - var agent: RID = get_rid() - # Enable avoidance - NavigationServer3D.agent_set_avoidance_enabled(agent, true) - # Create avoidance callback - NavigationServer3D.agent_set_avoidance_callback(agent, Callable(self, "_avoidance_done")) - # Switch to 3D avoidance - NavigationServer3D.agent_set_use_3d_avoidance(agent, true) +var agent: RID = get_rid() +# Enable avoidance +NavigationServer3D.agent_set_avoidance_enabled(agent, true) +# Create avoidance callback +NavigationServer3D.agent_set_avoidance_callback(agent, Callable(self, "_avoidance_done")) +# Switch to 3D avoidance +NavigationServer3D.agent_set_use_3d_avoidance(agent, true) - # Disable avoidance - NavigationServer3D.agent_set_avoidance_enabled(agent, false) - # Delete avoidance callback - NavigationServer3D.agent_set_avoidance_callback(agent, Callable()) - # Switch to 2D avoidance - NavigationServer3D.agent_set_use_3d_avoidance(agent, false) +# Disable avoidance +NavigationServer3D.agent_set_avoidance_enabled(agent, false) +# Delete avoidance callback +NavigationServer3D.agent_set_avoidance_callback(agent, Callable()) +# Switch to 2D avoidance +NavigationServer3D.agent_set_use_3d_avoidance(agent, false) ``` ## NavigationAgent Script Templates @@ -178,33 +178,33 @@ This script adds basic navigation movement to a Node3D with a NavigationAgent3D GDScript ``` - extends Node3D +extends Node3D - @export var movement_speed: float = 4.0 - @onready var navigation_agent: NavigationAgent3D = get_node("NavigationAgent3D") - var movement_delta: float +@export var movement_speed: float = 4.0 +@onready var navigation_agent: NavigationAgent3D = get_node("NavigationAgent3D") +var movement_delta: float - func _ready() -> void: - navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed)) +func _ready() -> void: + navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed)) - func set_movement_target(movement_target: Vector3): - navigation_agent.set_target_position(movement_target) +func set_movement_target(movement_target: Vector3): + navigation_agent.set_target_position(movement_target) - func _physics_process(delta): - if navigation_agent.is_navigation_finished(): - return +func _physics_process(delta): + if navigation_agent.is_navigation_finished(): + return - movement_delta = movement_speed * delta - var next_path_position: Vector3 = navigation_agent.get_next_path_position() - var current_agent_position: Vector3 = global_position - var new_velocity: Vector3 = (next_path_position - current_agent_position).normalized() * movement_delta - if navigation_agent.avoidance_enabled: - navigation_agent.set_velocity(new_velocity) - else: - _on_velocity_computed(new_velocity) + movement_delta = movement_speed * delta + var next_path_position: Vector3 = navigation_agent.get_next_path_position() + var current_agent_position: Vector3 = global_position + var new_velocity: Vector3 = (next_path_position - current_agent_position).normalized() * movement_delta + if navigation_agent.avoidance_enabled: + navigation_agent.set_velocity(new_velocity) + else: + _on_velocity_computed(new_velocity) - func _on_velocity_computed(safe_velocity: Vector3) -> void: - global_position = global_position.move_toward(global_position + safe_velocity, movement_delta) +func _on_velocity_computed(safe_velocity: Vector3) -> void: + global_position = global_position.move_toward(global_position + safe_velocity, movement_delta) ``` ### Actor as CharacterBody3D @@ -214,32 +214,32 @@ This script adds basic navigation movement to a CharacterBody3D with a Navigatio GDScript ``` - extends CharacterBody3D +extends CharacterBody3D - @export var movement_speed: float = 4.0 - @onready var navigation_agent: NavigationAgent3D = get_node("NavigationAgent3D") +@export var movement_speed: float = 4.0 +@onready var navigation_agent: NavigationAgent3D = get_node("NavigationAgent3D") - func _ready() -> void: - navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed)) +func _ready() -> void: + navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed)) - func set_movement_target(movement_target: Vector3): - navigation_agent.set_target_position(movement_target) +func set_movement_target(movement_target: Vector3): + navigation_agent.set_target_position(movement_target) - func _physics_process(delta): - if navigation_agent.is_navigation_finished(): - return +func _physics_process(delta): + if navigation_agent.is_navigation_finished(): + return - var next_path_position: Vector3 = navigation_agent.get_next_path_position() - var current_agent_position: Vector3 = global_position - var new_velocity: Vector3 = (next_path_position - current_agent_position).normalized() * movement_speed - if navigation_agent.avoidance_enabled: - navigation_agent.set_velocity(new_velocity) - else: - _on_velocity_computed(new_velocity) + var next_path_position: Vector3 = navigation_agent.get_next_path_position() + var current_agent_position: Vector3 = global_position + var new_velocity: Vector3 = (next_path_position - current_agent_position).normalized() * movement_speed + if navigation_agent.avoidance_enabled: + navigation_agent.set_velocity(new_velocity) + else: + _on_velocity_computed(new_velocity) - func _on_velocity_computed(safe_velocity: Vector3): - velocity = safe_velocity - move_and_slide() +func _on_velocity_computed(safe_velocity: Vector3): + velocity = safe_velocity + move_and_slide() ``` ### Actor as RigidBody3D @@ -249,29 +249,29 @@ This script adds basic navigation movement to a RigidBody3D with a NavigationAge GDScript ``` - extends RigidBody3D +extends RigidBody3D - @export var movement_speed: float = 4.0 - @onready var navigation_agent: NavigationAgent3D = get_node("NavigationAgent3D") +@export var movement_speed: float = 4.0 +@onready var navigation_agent: NavigationAgent3D = get_node("NavigationAgent3D") - func _ready() -> void: - navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed)) +func _ready() -> void: + navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed)) - func set_movement_target(movement_target: Vector3): - navigation_agent.set_target_position(movement_target) +func set_movement_target(movement_target: Vector3): + navigation_agent.set_target_position(movement_target) - func _physics_process(delta): - if navigation_agent.is_navigation_finished(): - return +func _physics_process(delta): + if navigation_agent.is_navigation_finished(): + return - var next_path_position: Vector3 = navigation_agent.get_next_path_position() - var current_agent_position: Vector3 = global_position - var new_velocity: Vector3 = (next_path_position - current_agent_position).normalized() * movement_speed - if navigation_agent.avoidance_enabled: - navigation_agent.set_velocity(new_velocity) - else: - _on_velocity_computed(new_velocity) + var next_path_position: Vector3 = navigation_agent.get_next_path_position() + var current_agent_position: Vector3 = global_position + var new_velocity: Vector3 = (next_path_position - current_agent_position).normalized() * movement_speed + if navigation_agent.avoidance_enabled: + navigation_agent.set_velocity(new_velocity) + else: + _on_velocity_computed(new_velocity) - func _on_velocity_computed(safe_velocity: Vector3): - linear_velocity = safe_velocity +func _on_velocity_computed(safe_velocity: Vector3): + linear_velocity = safe_velocity ``` diff --git a/03_usage/11_navigation/10_navigation_using_navigationobstacles.md b/03_usage/11_navigation/10_navigation_using_navigationobstacles.md index 98a654d..d812e9c 100644 --- a/03_usage/11_navigation/10_navigation_using_navigationobstacles.md +++ b/03_usage/11_navigation/10_navigation_using_navigationobstacles.md @@ -55,47 +55,47 @@ For static use an array of `vertices` is required. GDScript ``` - # For 2D +# For 2D - # create a new "obstacle" and place it on the default navigation map. - var new_obstacle_rid: RID = NavigationServer2D.obstacle_create() - var default_2d_map_rid: RID = get_world_2d().get_navigation_map() +# create a new "obstacle" and place it on the default navigation map. +var new_obstacle_rid: RID = NavigationServer2D.obstacle_create() +var default_2d_map_rid: RID = get_world_2d().get_navigation_map() - NavigationServer2D.obstacle_set_map(new_obstacle_rid, default_2d_map_rid) - NavigationServer2D.obstacle_set_position(new_obstacle_rid, global_position) +NavigationServer2D.obstacle_set_map(new_obstacle_rid, default_2d_map_rid) +NavigationServer2D.obstacle_set_position(new_obstacle_rid, global_position) - # Use obstacle dynamic by increasing radius above zero. - NavigationServer2D.obstacle_set_radius(new_obstacle_rid, 5.0) +# Use obstacle dynamic by increasing radius above zero. +NavigationServer2D.obstacle_set_radius(new_obstacle_rid, 5.0) - # Use obstacle static by adding a square that pushes agents out. - var outline = PackedVector2Array([Vector2(-100, -100), Vector2(100, -100), Vector2(100, 100), Vector2(-100, 100)]) - NavigationServer2D.obstacle_set_vertices(new_obstacle_rid, outline) +# Use obstacle static by adding a square that pushes agents out. +var outline = PackedVector2Array([Vector2(-100, -100), Vector2(100, -100), Vector2(100, 100), Vector2(-100, 100)]) +NavigationServer2D.obstacle_set_vertices(new_obstacle_rid, outline) - # Enable the obstacle. - NavigationServer2D.obstacle_set_avoidance_enabled(new_obstacle_rid, true) +# Enable the obstacle. +NavigationServer2D.obstacle_set_avoidance_enabled(new_obstacle_rid, true) ``` GDScript ``` - # For 3D +# For 3D - # Create a new "obstacle" and place it on the default navigation map. - var new_obstacle_rid: RID = NavigationServer3D.obstacle_create() - var default_3d_map_rid: RID = get_world_3d().get_navigation_map() +# Create a new "obstacle" and place it on the default navigation map. +var new_obstacle_rid: RID = NavigationServer3D.obstacle_create() +var default_3d_map_rid: RID = get_world_3d().get_navigation_map() - NavigationServer3D.obstacle_set_map(new_obstacle_rid, default_3d_map_rid) - NavigationServer3D.obstacle_set_position(new_obstacle_rid, global_position) +NavigationServer3D.obstacle_set_map(new_obstacle_rid, default_3d_map_rid) +NavigationServer3D.obstacle_set_position(new_obstacle_rid, global_position) - # Use obstacle dynamic by increasing radius above zero. - NavigationServer3D.obstacle_set_radius(new_obstacle_rid, 0.5) +# Use obstacle dynamic by increasing radius above zero. +NavigationServer3D.obstacle_set_radius(new_obstacle_rid, 0.5) - # Use obstacle static by adding a square that pushes agents out. - var outline = PackedVector3Array([Vector3(-5, 0, -5), Vector3(5, 0, -5), Vector3(5, 0, 5), Vector3(-5, 0, 5)]) - NavigationServer3D.obstacle_set_vertices(new_obstacle_rid, outline) - # Set the obstacle height on the y-axis. - NavigationServer3D.obstacle_set_height(new_obstacle_rid, 1.0) +# Use obstacle static by adding a square that pushes agents out. +var outline = PackedVector3Array([Vector3(-5, 0, -5), Vector3(5, 0, -5), Vector3(5, 0, 5), Vector3(-5, 0, 5)]) +NavigationServer3D.obstacle_set_vertices(new_obstacle_rid, outline) +# Set the obstacle height on the y-axis. +NavigationServer3D.obstacle_set_height(new_obstacle_rid, 1.0) - # Enable the obstacle. - NavigationServer3D.obstacle_set_avoidance_enabled(new_obstacle_rid, true) +# Enable the obstacle. +NavigationServer3D.obstacle_set_avoidance_enabled(new_obstacle_rid, true) ``` diff --git a/03_usage/11_navigation/12_navigation_using_navigationlayers.md b/03_usage/11_navigation/12_navigation_using_navigationlayers.md index ef69682..6d0db72 100644 --- a/03_usage/11_navigation/12_navigation_using_navigationlayers.md +++ b/03_usage/11_navigation/12_navigation_using_navigationlayers.md @@ -23,36 +23,36 @@ In scripts the following helper functions can be used to work with the navigatio GDScript ``` - func change_layers(): - var region: NavigationRegion3D = get_node("NavigationRegion3D") - # enables 4-th layer for this region - region.navigation_layers = enable_bitmask_inx(region.navigation_layers, 4) - # disables 1-rst layer for this region - region.navigation_layers = disable_bitmask_inx(region.navigation_layers, 1) +func change_layers(): + var region: NavigationRegion3D = get_node("NavigationRegion3D") + # enables 4-th layer for this region + region.navigation_layers = enable_bitmask_inx(region.navigation_layers, 4) + # disables 1-rst layer for this region + region.navigation_layers = disable_bitmask_inx(region.navigation_layers, 1) - var agent: NavigationAgent3D = get_node("NavigationAgent3D") - # make future path queries of this agent ignore regions with 4-th layer - agent.navigation_layers = disable_bitmask_inx(agent.navigation_layers, 4) + var agent: NavigationAgent3D = get_node("NavigationAgent3D") + # make future path queries of this agent ignore regions with 4-th layer + agent.navigation_layers = disable_bitmask_inx(agent.navigation_layers, 4) - var path_query_navigation_layers: int = 0 - path_query_navigation_layers = enable_bitmask_inx(path_query_navigation_layers, 2) - # get a path that only considers 2-nd layer regions - var path: PoolVector3Array = NavigationServer3D.map_get_path( - map, - start_position, - target_position, - true, - path_query_navigation_layers - ) + var path_query_navigation_layers: int = 0 + path_query_navigation_layers = enable_bitmask_inx(path_query_navigation_layers, 2) + # get a path that only considers 2-nd layer regions + var path: PoolVector3Array = NavigationServer3D.map_get_path( + map, + start_position, + target_position, + true, + path_query_navigation_layers + ) - static func is_bitmask_inx_enabled(_bitmask: int, _index: int) -> bool: - return _bitmask & (1 << _index) != 0 +static func is_bitmask_inx_enabled(_bitmask: int, _index: int) -> bool: + return _bitmask & (1 << _index) != 0 - static func enable_bitmask_inx(_bitmask: int, _index: int) -> int: - return _bitmask | (1 << _index) +static func enable_bitmask_inx(_bitmask: int, _index: int) -> int: + return _bitmask | (1 << _index) - static func disable_bitmask_inx(_bitmask: int, _index: int) -> int: - return _bitmask & ~(1 << _index) +static func disable_bitmask_inx(_bitmask: int, _index: int) -> int: + return _bitmask & ~(1 << _index) ``` Changing navigation layers for path queries is a performance friendly alternative to diff --git a/03_usage/11_navigation/13_navigation_debug_tools.md b/03_usage/11_navigation/13_navigation_debug_tools.md index cb18fa0..bc787dd 100644 --- a/03_usage/11_navigation/13_navigation_debug_tools.md +++ b/03_usage/11_navigation/13_navigation_debug_tools.md @@ -19,8 +19,8 @@ In Pandemonium debug builds the navigation debug can also be toggled on the Navi GDScript ``` - NavigationServer2D.set_debug_enabled(false) - NavigationServer3D.set_debug_enabled(true) +NavigationServer2D.set_debug_enabled(false) +NavigationServer3D.set_debug_enabled(true) ``` ## Debug navigation settings diff --git a/03_usage/11_navigation/14_navigation_connecting_navmesh.md b/03_usage/11_navigation/14_navigation_connecting_navmesh.md index 21b9a0c..2b78dc7 100644 --- a/03_usage/11_navigation/14_navigation_connecting_navmesh.md +++ b/03_usage/11_navigation/14_navigation_connecting_navmesh.md @@ -44,19 +44,19 @@ The edge connection margin value of any navigation map can also be changed at ru GDScript ``` - extends Node2D - # 2D margins are designed to work with "pixel" values - var default_2d_map_rid: RID = get_world_2d().get_navigation_map() - NavigationServer2D.map_set_edge_connection_margin(default_2d_map_rid, 50.0) +extends Node2D +# 2D margins are designed to work with "pixel" values +var default_2d_map_rid: RID = get_world_2d().get_navigation_map() +NavigationServer2D.map_set_edge_connection_margin(default_2d_map_rid, 50.0) ``` GDScript ``` - extends Node3D - # 3D margins are designed to work with 3D unit values - var default_3d_map_rid: RID = get_world_3d().get_navigation_map() - NavigationServer3D.map_set_edge_connection_margin(default_3d_map_rid, 0.5) +extends Node3D +# 3D margins are designed to work with 3D unit values +var default_3d_map_rid: RID = get_world_3d().get_navigation_map() +NavigationServer3D.map_set_edge_connection_margin(default_3d_map_rid, 0.5) ``` Note: diff --git a/03_usage/11_navigation/15_navigation_different_actor_types.md b/03_usage/11_navigation/15_navigation_different_actor_types.md index 69d7041..05aac13 100644 --- a/03_usage/11_navigation/15_navigation_different_actor_types.md +++ b/03_usage/11_navigation/15_navigation_different_actor_types.md @@ -14,67 +14,67 @@ Note: GDScript ``` - # Create a navigation mesh resource for each actor size. - var navigation_mesh_standard_size: NavigationMesh = NavigationMesh.new() - var navigation_mesh_small_size: NavigationMesh = NavigationMesh.new() - var navigation_mesh_huge_size: NavigationMesh = NavigationMesh.new() +# Create a navigation mesh resource for each actor size. +var navigation_mesh_standard_size: NavigationMesh = NavigationMesh.new() +var navigation_mesh_small_size: NavigationMesh = NavigationMesh.new() +var navigation_mesh_huge_size: NavigationMesh = NavigationMesh.new() - # Set appropriated agent parameters. - navigation_mesh_standard_size.agent_radius = 0.5 - navigation_mesh_standard_size.agent_height = 1.8 - navigation_mesh_small_size.agent_radius = 0.25 - navigation_mesh_small_size.agent_height = 0.7 - navigation_mesh_huge_size.agent_radius = 1.5 - navigation_mesh_huge_size.agent_height = 2.5 +# Set appropriated agent parameters. +navigation_mesh_standard_size.agent_radius = 0.5 +navigation_mesh_standard_size.agent_height = 1.8 +navigation_mesh_small_size.agent_radius = 0.25 +navigation_mesh_small_size.agent_height = 0.7 +navigation_mesh_huge_size.agent_radius = 1.5 +navigation_mesh_huge_size.agent_height = 2.5 - # Get the root node to parse geometry for the baking. - var root_node: Node3D = get_node("NavigationMeshBakingRootNode") +# Get the root node to parse geometry for the baking. +var root_node: Node3D = get_node("NavigationMeshBakingRootNode") - # Create the source geometry resource that will hold the parsed geometry data. - var source_geometry_data: NavigationMeshSourceGeometryData3D = NavigationMeshSourceGeometryData3D.new() +# Create the source geometry resource that will hold the parsed geometry data. +var source_geometry_data: NavigationMeshSourceGeometryData3D = NavigationMeshSourceGeometryData3D.new() - # Parse the source geometry from the SceneTree on the main thread. - # The navigation mesh is only required for the parse settings so any of the three will do. - NavigationServer3D.parse_source_geometry_data(navigation_mesh_standard_size, source_geometry_data, root_node) +# Parse the source geometry from the SceneTree on the main thread. +# The navigation mesh is only required for the parse settings so any of the three will do. +NavigationServer3D.parse_source_geometry_data(navigation_mesh_standard_size, source_geometry_data, root_node) - # Bake the navigation geometry for each agent size from the same source geometry. - # If required for performance this baking step could also be done on background threads. - NavigationServer3D.bake_from_source_geometry_data(navigation_mesh_standard_size, source_geometry_data) - NavigationServer3D.bake_from_source_geometry_data(navigation_mesh_small_size, source_geometry_data) - NavigationServer3D.bake_from_source_geometry_data(navigation_mesh_huge_size, source_geometry_data) +# Bake the navigation geometry for each agent size from the same source geometry. +# If required for performance this baking step could also be done on background threads. +NavigationServer3D.bake_from_source_geometry_data(navigation_mesh_standard_size, source_geometry_data) +NavigationServer3D.bake_from_source_geometry_data(navigation_mesh_small_size, source_geometry_data) +NavigationServer3D.bake_from_source_geometry_data(navigation_mesh_huge_size, source_geometry_data) - # Create different navigation maps on the NavigationServer. - var navigation_map_standard: RID = NavigationServer3D.map_create() - var navigation_map_small: RID = NavigationServer3D.map_create() - var navigation_map_huge: RID = NavigationServer3D.map_create() +# Create different navigation maps on the NavigationServer. +var navigation_map_standard: RID = NavigationServer3D.map_create() +var navigation_map_small: RID = NavigationServer3D.map_create() +var navigation_map_huge: RID = NavigationServer3D.map_create() - # Set the new navigation maps as active. - NavigationServer3D.map_set_active(navigation_map_standard, true) - NavigationServer3D.map_set_active(navigation_map_small, true) - NavigationServer3D.map_set_active(navigation_map_huge, true) +# Set the new navigation maps as active. +NavigationServer3D.map_set_active(navigation_map_standard, true) +NavigationServer3D.map_set_active(navigation_map_small, true) +NavigationServer3D.map_set_active(navigation_map_huge, true) - # Create a region for each map. - var navigation_region_standard: RID = NavigationServer3D.region_create() - var navigation_region_small: RID = NavigationServer3D.region_create() - var navigation_region_huge: RID = NavigationServer3D.region_create() +# Create a region for each map. +var navigation_region_standard: RID = NavigationServer3D.region_create() +var navigation_region_small: RID = NavigationServer3D.region_create() +var navigation_region_huge: RID = NavigationServer3D.region_create() - # Add the regions to the maps. - NavigationServer3D.region_set_map(navigation_region_standard, navigation_map_standard) - NavigationServer3D.region_set_map(navigation_region_small, navigation_map_small) - NavigationServer3D.region_set_map(navigation_region_huge, navigation_map_huge) +# Add the regions to the maps. +NavigationServer3D.region_set_map(navigation_region_standard, navigation_map_standard) +NavigationServer3D.region_set_map(navigation_region_small, navigation_map_small) +NavigationServer3D.region_set_map(navigation_region_huge, navigation_map_huge) - # Set navigation mesh for each region. - NavigationServer3D.region_set_navigation_mesh(navigation_region_standard, navigation_mesh_standard_size) - NavigationServer3D.region_set_navigation_mesh(navigation_region_small, navigation_mesh_small_size) - NavigationServer3D.region_set_navigation_mesh(navigation_region_huge, navigation_mesh_huge_size) +# Set navigation mesh for each region. +NavigationServer3D.region_set_navigation_mesh(navigation_region_standard, navigation_mesh_standard_size) +NavigationServer3D.region_set_navigation_mesh(navigation_region_small, navigation_mesh_small_size) +NavigationServer3D.region_set_navigation_mesh(navigation_region_huge, navigation_mesh_huge_size) - # Create start and end position for the navigation path query. - var start_pos: Vector3 = Vector3(0.0, 0.0, 0.0) - var end_pos: Vector3 = Vector3(2.0, 0.0, 0.0) - var use_corridorfunnel: bool = true +# Create start and end position for the navigation path query. +var start_pos: Vector3 = Vector3(0.0, 0.0, 0.0) +var end_pos: Vector3 = Vector3(2.0, 0.0, 0.0) +var use_corridorfunnel: bool = true - # Query paths for each agent size. - var path_standard_agent = NavigationServer3D.map_get_path(navigation_map_standard, start_pos, end_pos, use_corridorfunnel) - var path_small_agent = NavigationServer3D.map_get_path(navigation_map_small, start_pos, end_pos, use_corridorfunnel) - var path_huge_agent = NavigationServer3D.map_get_path(navigation_map_huge, start_pos, end_pos, use_corridorfunnel) +# Query paths for each agent size. +var path_standard_agent = NavigationServer3D.map_get_path(navigation_map_standard, start_pos, end_pos, use_corridorfunnel) +var path_small_agent = NavigationServer3D.map_get_path(navigation_map_small, start_pos, end_pos, use_corridorfunnel) +var path_huge_agent = NavigationServer3D.map_get_path(navigation_map_huge, start_pos, end_pos, use_corridorfunnel) ``` diff --git a/03_usage/11_navigation/16_navigation_different_actor_locomotion.md b/03_usage/11_navigation/16_navigation_different_actor_locomotion.md index f50e126..c060cae 100644 --- a/03_usage/11_navigation/16_navigation_different_actor_locomotion.md +++ b/03_usage/11_navigation/16_navigation_different_actor_locomotion.md @@ -18,23 +18,23 @@ other agents in the same locomotion state, switch the actors's avoidance agent t GDScript ``` - func update_path(): - - if actor_standing: - path = NavigationServer3D.map_get_path(standing_navigation_map_rid, start_position, target_position, true) - elif actor_crouching: - path = NavigationServer3D.map_get_path(crouched_navigation_map_rid, start_position, target_position, true) - elif actor_crawling: - path = NavigationServer3D.map_get_path(crawling_navigation_map_rid, start_position, target_position, true) - - func change_agent_avoidance_state(): - - if actor_standing: - NavigationServer3D.agent_set_map(avoidance_agent_rid, standing_navigation_map_rid) - elif actor_crouching: - NavigationServer3D.agent_set_map(avoidance_agent_rid, crouched_navigation_map_rid) - elif actor_crawling: - NavigationServer3D.agent_set_map(avoidance_agent_rid, crawling_navigation_map_rid) +func update_path(): + + if actor_standing: + path = NavigationServer3D.map_get_path(standing_navigation_map_rid, start_position, target_position, true) + elif actor_crouching: + path = NavigationServer3D.map_get_path(crouched_navigation_map_rid, start_position, target_position, true) + elif actor_crawling: + path = NavigationServer3D.map_get_path(crawling_navigation_map_rid, start_position, target_position, true) + +func change_agent_avoidance_state(): + + if actor_standing: + NavigationServer3D.agent_set_map(avoidance_agent_rid, standing_navigation_map_rid) + elif actor_crouching: + NavigationServer3D.agent_set_map(avoidance_agent_rid, crouched_navigation_map_rid) + elif actor_crawling: + NavigationServer3D.agent_set_map(avoidance_agent_rid, crawling_navigation_map_rid) ``` Note: diff --git a/03_usage/12_i18n/01_internationalizing_games.md b/03_usage/12_i18n/01_internationalizing_games.md index f2f20b6..e01abbd 100644 --- a/03_usage/12_i18n/01_internationalizing_games.md +++ b/03_usage/12_i18n/01_internationalizing_games.md @@ -61,23 +61,23 @@ and send a `Object.notification( Object_method_notification )` to update the translation: ``` - func _ready(): - # This assumes you have a node called "Label" as a child of the node - # that has the script attached. - var label = get_node("Label") - label.set_message_translation(false) - label.notification(NOTIFICATION_TRANSLATION_CHANGED) +func _ready(): + # This assumes you have a node called "Label" as a child of the node + # that has the script attached. + var label = get_node("Label") + label.set_message_translation(false) + label.notification(NOTIFICATION_TRANSLATION_CHANGED) ``` For more complex UI nodes such as OptionButtons, you may have to use this instead: ``` - func _ready(): - var option_button = get_node("OptionButton") - option_button.set_message_translation(false) - option_button.notification(NOTIFICATION_TRANSLATION_CHANGED) - option_button.get_popup().set_message_translation(false) - option_button.get_popup().notification(NOTIFICATION_TRANSLATION_CHANGED) +func _ready(): + var option_button = get_node("OptionButton") + option_button.set_message_translation(false) + option_button.notification(NOTIFICATION_TRANSLATION_CHANGED) + option_button.get_popup().set_message_translation(false) + option_button.get_popup().notification(NOTIFICATION_TRANSLATION_CHANGED) ``` In code, the `Object.tr()` @@ -85,8 +85,8 @@ function can be used. This will just look up the text in the translations and convert it if found: ``` - level.set_text(tr("LEVEL_5_NAME")) - status.set_text(tr("GAME_STATUS_" + str(status_index))) +level.set_text(tr("LEVEL_5_NAME")) +status.set_text(tr("GAME_STATUS_" + str(status_index))) ``` ### Making controls resizable @@ -125,7 +125,7 @@ For example, to test a game in French, the following argument can be supplied: ``` - pandemonium --language fr +pandemonium --language fr ``` ## Translating the project name diff --git a/03_usage/12_i18n/02_localization_using_gettext.md b/03_usage/12_i18n/02_localization_using_gettext.md index c4f6bbd..020e08d 100644 --- a/03_usage/12_i18n/02_localization_using_gettext.md +++ b/03_usage/12_i18n/02_localization_using_gettext.md @@ -65,12 +65,12 @@ Create a directory named `locale` in the project directory. In this directory, save a file named `messages.pot` with the following contents: ``` - # Don't remove the two lines below, they're required for gettext to work correctly. - msgid "" - msgstr "" +# Don't remove the two lines below, they're required for gettext to work correctly. +msgid "" +msgstr "" - msgid "Hello world!" - msgstr "" +msgid "Hello world!" +msgstr "" ``` Messages in gettext are made of `msgid` and `msgstr` pairs. @@ -88,7 +88,7 @@ create and update the POT file from your scene files and scripts. After installing `babel` and `babel-pandemonium`, for example using pip: ``` - pip3 install babel babel-pandemonium +pip3 install babel babel-pandemonium ``` Write a mapping file (for example `babelrc`) which will indicate which files @@ -96,17 +96,17 @@ pybabel needs to process (note that we process GDScript as Python, which is generally sufficient): ``` - [python: **.gd] - encoding = utf-8 +[python: **.gd] +encoding = utf-8 - [pandemonium_scene: **.tscn] - encoding = utf-8 +[pandemonium_scene: **.tscn] +encoding = utf-8 ``` You can then run pybabel like so: ``` - pybabel extract -F babelrc -k text -k LineEdit/placeholder_text -k tr -o pandemonium-l10n.pot . +pybabel extract -F babelrc -k text -k LineEdit/placeholder_text -k tr -o pandemonium-l10n.pot . ``` Use the `-k` option to specify what needs to be extracted. In this case, @@ -121,7 +121,7 @@ For instance, to create a French localization file, use the following command while in the `locale` directory: ``` - msginit --no-translator --input=messages.pot --locale=fr +msginit --no-translator --input=messages.pot --locale=fr ``` The command above will create a file named `fr.po` in the same directory @@ -150,8 +150,8 @@ present in the PO template. This can be done automatically using the `msgmerge` tool: ``` - # The order matters: specify the message file *then* the PO template! - msgmerge --update --backup=none fr.po messages.pot +# The order matters: specify the message file *then* the PO template! +msgmerge --update --backup=none fr.po messages.pot ``` If you want to keep a backup of the original message file (which would be @@ -174,7 +174,7 @@ It is possible to check whether a gettext file's syntax is valid by running the command below: ``` - msgfmt fr.po --check +msgfmt fr.po --check ``` If there are syntax errors or warnings, they will be displayed in the console. @@ -190,7 +190,7 @@ PO files. You can generate a MO file with the command below: ``` - msgfmt fr.po --no-hash -o fr.mo +msgfmt fr.po --no-hash -o fr.mo ``` If the PO file is valid, this command will create a `fr.mo` file besides @@ -201,7 +201,7 @@ your translation in the future. In case you lose the original PO file and wish to decompile a MO file into a text-based PO file, you can do so with: ``` - msgunfmt fr.mo > fr.po +msgunfmt fr.mo > fr.po ``` The decompiled file will not include comments or fuzzy strings, as these are diff --git a/03_usage/13_platform/android/01_android_plugin.md b/03_usage/13_platform/android/01_android_plugin.md index b106c05..594b220 100644 --- a/03_usage/13_platform/android/01_android_plugin.md +++ b/03_usage/13_platform/android/01_android_plugin.md @@ -67,9 +67,9 @@ The instructions below assumes that you're using Android Studio. - In the `<application>` tag, add a `<meta-data>` tag setup as follow: ``` - + ``` Where `PluginName` is the name of the plugin, and `plugin.init.ClassFullName` is the full name (package + class name) of the plugin loading class. @@ -86,17 +86,17 @@ The instructions below assumes that you're using Android Studio. - The configuration file format is as follow: ``` - [config] +[config] - name="MyPlugin" - binary_type="local" - binary="MyPlugin.aar" +name="MyPlugin" +binary_type="local" +binary="MyPlugin.aar" - [dependencies] +[dependencies] - local=["local_dep1.aar", "local_dep2.aar"] - remote=["example.plugin.android:remote-dep1:0.0.1", "example.plugin.android:remote-dep2:0.0.1"] - custom_maven_repos=["http://repo.mycompany.com/maven2"] +local=["local_dep1.aar", "local_dep2.aar"] +remote=["example.plugin.android:remote-dep1:0.0.1", "example.plugin.android:remote-dep2:0.0.1"] +custom_maven_repos=["http://repo.mycompany.com/maven2"] ``` The `config` section and fields are required and defined as follow: @@ -129,9 +129,9 @@ The Pandemonium editor will automatically parse all `.gdap` files in the `res:// From your script: ``` - if Engine.has_singleton("MyPlugin"): - var singleton = Engine.get_singleton("MyPlugin") - print(singleton.myPluginFunction("World")) +if Engine.has_singleton("MyPlugin"): + var singleton = Engine.get_singleton("MyPlugin") + print(singleton.myPluginFunction("World")) ``` diff --git a/03_usage/13_platform/android/02_android_in_app_purchases.md b/03_usage/13_platform/android/02_android_in_app_purchases.md index 2e6f1da..c852932 100644 --- a/03_usage/13_platform/android/02_android_in_app_purchases.md +++ b/03_usage/13_platform/android/02_android_in_app_purchases.md @@ -41,29 +41,29 @@ To use the `PandemoniumGooglePlayBilling` API you first have to get the `Pandemo singleton and start the connection: ``` - var payment +var payment - func _ready(): - if Engine.has_singleton("PandemoniumGooglePlayBilling"): - payment = Engine.get_singleton("PandemoniumGooglePlayBilling") +func _ready(): + if Engine.has_singleton("PandemoniumGooglePlayBilling"): + payment = Engine.get_singleton("PandemoniumGooglePlayBilling") - # These are all signals supported by the API - # You can drop some of these based on your needs - payment.connect("connected", self, "_on_connected") # No params - payment.connect("disconnected", self, "_on_disconnected") # No params - payment.connect("connect_error", self, "_on_connect_error") # Response ID (int), Debug message (string) - payment.connect("purchases_updated", self, "_on_purchases_updated") # Purchases (Dictionary[]) - payment.connect("purchase_error", self, "_on_purchase_error") # Response ID (int), Debug message (string) - payment.connect("sku_details_query_completed", self, "_on_sku_details_query_completed") # SKUs (Dictionary[]) - payment.connect("sku_details_query_error", self, "_on_sku_details_query_error") # Response ID (int), Debug message (string), Queried SKUs (string[]) - payment.connect("purchase_acknowledged", self, "_on_purchase_acknowledged") # Purchase token (string) - payment.connect("purchase_acknowledgement_error", self, "_on_purchase_acknowledgement_error") # Response ID (int), Debug message (string), Purchase token (string) - payment.connect("purchase_consumed", self, "_on_purchase_consumed") # Purchase token (string) - payment.connect("purchase_consumption_error", self, "_on_purchase_consumption_error") # Response ID (int), Debug message (string), Purchase token (string) + # These are all signals supported by the API + # You can drop some of these based on your needs + payment.connect("connected", self, "_on_connected") # No params + payment.connect("disconnected", self, "_on_disconnected") # No params + payment.connect("connect_error", self, "_on_connect_error") # Response ID (int), Debug message (string) + payment.connect("purchases_updated", self, "_on_purchases_updated") # Purchases (Dictionary[]) + payment.connect("purchase_error", self, "_on_purchase_error") # Response ID (int), Debug message (string) + payment.connect("sku_details_query_completed", self, "_on_sku_details_query_completed") # SKUs (Dictionary[]) + payment.connect("sku_details_query_error", self, "_on_sku_details_query_error") # Response ID (int), Debug message (string), Queried SKUs (string[]) + payment.connect("purchase_acknowledged", self, "_on_purchase_acknowledged") # Purchase token (string) + payment.connect("purchase_acknowledgement_error", self, "_on_purchase_acknowledgement_error") # Response ID (int), Debug message (string), Purchase token (string) + payment.connect("purchase_consumed", self, "_on_purchase_consumed") # Purchase token (string) + payment.connect("purchase_consumption_error", self, "_on_purchase_consumption_error") # Response ID (int), Debug message (string), Purchase token (string) - payment.startConnection() - else: - print("Android IAP support is not enabled. Make sure you have enabled 'Custom Build' and the PandemoniumGooglePlayBilling plugin in your Android export settings! IAP will not work.") + payment.startConnection() + else: + print("Android IAP support is not enabled. Make sure you have enabled 'Custom Build' and the PandemoniumGooglePlayBilling plugin in your Android export settings! IAP will not work.") ``` All API methods only work if the API is connected. You can use `payment.isReady()` to check the connection status. @@ -76,12 +76,12 @@ As soon as the API is connected, you can query SKUs using `querySkuDetails`. Full example: ``` - func _on_connected(): - payment.querySkuDetails(["my_iap_item"], "inapp") # "subs" for subscriptions +func _on_connected(): + payment.querySkuDetails(["my_iap_item"], "inapp") # "subs" for subscriptions - func _on_sku_details_query_completed(sku_details): - for available_sku in sku_details: - print(available_sku) +func _on_sku_details_query_completed(sku_details): + for available_sku in sku_details: + print(available_sku) ``` @@ -92,20 +92,20 @@ You **must** query the SKU details for an item before you can initiate the purchase flow for it. ``` - payment.purchase("my_iap_item") +payment.purchase("my_iap_item") ``` Then, wait for the `on_purchases_updated` callback and handle the purchase result: ``` - func _on_purchases_updated(purchases): - for purchase in purchases: - if purchase.purchase_state == 1: # 1 means "purchased", see https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState#constants_1 - # enable_premium(purchase.sku) # unlock paid content, add coins, save token on server, etc. (you have to implement enable_premium yourself) - if not purchase.is_acknowledged: - payment.acknowledgePurchase(purchase.purchase_token) # call if non-consumable product - if purchase.sku in list_of_consumable_products: - payment.consumePurchase(purchase.purchase_token) # call if consumable product +func _on_purchases_updated(purchases): + for purchase in purchases: + if purchase.purchase_state == 1: # 1 means "purchased", see https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState#constants_1 + # enable_premium(purchase.sku) # unlock paid content, add coins, save token on server, etc. (you have to implement enable_premium yourself) + if not purchase.is_acknowledged: + payment.acknowledgePurchase(purchase.purchase_token) # call if non-consumable product + if purchase.sku in list_of_consumable_products: + payment.consumePurchase(purchase.purchase_token) # call if consumable product ``` @@ -118,14 +118,14 @@ and either an array of purchases or an error message. Only active subscriptions Full example: ``` - var query = payment.queryPurchases("inapp") # Or "subs" for subscriptions - if query.status == OK: - for purchase in query.purchases: - if purchase.sku == "my_iap_item" and purchase.purchase_state == 1: - # enable_premium(purchase.sku) # unlock paid content, save token on server, etc. - if !purchase.is_acknowledged: - payment.acknowledgePurchase(purchase.purchase_token) - # Or wait for the _on_purchase_acknowledged callback before giving the user what they bought +var query = payment.queryPurchases("inapp") # Or "subs" for subscriptions +if query.status == OK: + for purchase in query.purchases: + if purchase.sku == "my_iap_item" and purchase.purchase_state == 1: + # enable_premium(purchase.sku) # unlock paid content, save token on server, etc. + if !purchase.is_acknowledged: + payment.acknowledgePurchase(purchase.purchase_token) + # Or wait for the _on_purchase_acknowledged callback before giving the user what they bought ``` @@ -138,13 +138,13 @@ acknowledges a purchase. Consuming a product allows the user to purchase it again, and removes it from appearing in subsequent `queryPurchases` calls. ``` - var query = payment.queryPurchases("inapp") # Or "subs" for subscriptions - if query.status == OK: - for purchase in query.purchases: - if purchase.sku == "my_consumable_iap_item" and purchase.purchase_state == 1: - # enable_premium(purchase.sku) # add coins, save token on server, etc. - payment.consumePurchase(purchase.purchase_token) - # Or wait for the _on_purchase_consumed callback before giving the user what they bought +var query = payment.queryPurchases("inapp") # Or "subs" for subscriptions +if query.status == OK: + for purchase in query.purchases: + if purchase.sku == "my_consumable_iap_item" and purchase.purchase_state == 1: + # enable_premium(purchase.sku) # add coins, save token on server, etc. + payment.consumePurchase(purchase.purchase_token) + # Or wait for the _on_purchase_consumed callback before giving the user what they bought ``` #### Subscriptions diff --git a/03_usage/13_platform/ios/01_ios_plugin.md b/03_usage/13_platform/ios/01_ios_plugin.md index f51018c..e463f7f 100644 --- a/03_usage/13_platform/ios/01_ios_plugin.md +++ b/03_usage/13_platform/ios/01_ios_plugin.md @@ -21,9 +21,9 @@ An iOS plugin requires a `.gdip` configuration file, a binary file which can be When a plugin is active, you can access it in your using `Engine.get_singleton()`: ``` - if Engine.has_singleton("MyPlugin"): - var singleton = Engine.get_singleton("MyPlugin") - print(singleton.foo()) +if Engine.has_singleton("MyPlugin"): + var singleton = Engine.get_singleton("MyPlugin") + print(singleton.foo()) ``` #### Creating an iOS plugin @@ -59,7 +59,7 @@ To build an iOS plugin: 3. In the `Build Settings` tab, specify the compilation flags for your static library in `OTHER_CFLAGS`. The most important ones are `-fcxx-modules`, `-fmodules`, and `-DDEBUG` if you need debug support. Other flags should be the same you use to compile Pandemonium. For instance: ``` - -DPTRCALL_ENABLED -DDEBUG_ENABLED -DDEBUG_MEMORY_ALLOC -DDISABLE_FORCED_INLINE -DTYPED_METHOD_BIND +-DPTRCALL_ENABLED -DDEBUG_ENABLED -DDEBUG_MEMORY_ALLOC -DDISABLE_FORCED_INLINE -DTYPED_METHOD_BIND ``` 4. Add the required logic for your plugin and build your library to generate a `.a` file. You will probably need to build both `debug` and `release` target `.a` files. Depending on your needs, pick either or both. If you need both debug and release `.a` files, their name should match following pattern: `[PluginName].[TargetType].a`. You can also build the static library with your SCons configuration. @@ -67,7 +67,7 @@ To build an iOS plugin: 5. The iOS plugin system also supports `.xcframework` files. To generate one, you can use a command such as: ``` - xcodebuild -create-xcframework -library [DeviceLibrary].a -library [SimulatorLibrary].a -output [PluginName].xcframework +xcodebuild -create-xcframework -library [DeviceLibrary].a -library [SimulatorLibrary].a -output [PluginName].xcframework ``` 6. Create a Pandemonium iOS Plugin configuration file to help the system detect and load your plugin: @@ -77,35 +77,35 @@ To build an iOS plugin: - The configuration file format is as follow: ``` - [config] - name="MyPlugin" - binary="MyPlugin.a" +[config] +name="MyPlugin" +binary="MyPlugin.a" - initialization="init_my_plugin" - deinitialization="deinit_my_plugin" +initialization="init_my_plugin" +deinitialization="deinit_my_plugin" - [dependencies] - linked=[] - embedded=[] - system=["Foundation.framework"] +[dependencies] +linked=[] +embedded=[] +system=["Foundation.framework"] - capabilities=["arkit", "metal"] +capabilities=["arkit", "metal"] - files=["data.json"] +files=["data.json"] - linker_flags=["-ObjC"] +linker_flags=["-ObjC"] - [plist] - PlistKeyWithDefaultType="Some Info.plist key you might need" - StringPlistKey:string="String value" - IntegerPlistKey:integer=42 - BooleanPlistKey:boolean=true - RawPlistKey:raw=" - - UIInterfaceOrientationPortrait - - " - StringPlistKeyToInput:string_input="Type something" +[plist] +PlistKeyWithDefaultType="Some Info.plist key you might need" +StringPlistKey:string="String value" +IntegerPlistKey:integer=42 +BooleanPlistKey:boolean=true +RawPlistKey:raw=" + + UIInterfaceOrientationPortrait + +" +StringPlistKeyToInput:string_input="Type something" ``` The `config` section and fields are required and defined as follow: diff --git a/03_usage/13_platform/ios/02_plugins_for_ios.md b/03_usage/13_platform/ios/02_plugins_for_ios.md index f8f0516..2ad5ec1 100644 --- a/03_usage/13_platform/ios/02_plugins_for_ios.md +++ b/03_usage/13_platform/ios/02_plugins_for_ios.md @@ -18,19 +18,19 @@ returns a registered singleton. Here's an example of how to do this in GDScript: ``` - var in_app_store - var game_center +var in_app_store +var game_center - func _ready(): - if Engine.has_singleton("InAppStore"): - in_app_store = Engine.get_singleton("InAppStore") - else: - print("iOS IAP plugin is not available on this platform.") +func _ready(): + if Engine.has_singleton("InAppStore"): + in_app_store = Engine.get_singleton("InAppStore") + else: + print("iOS IAP plugin is not available on this platform.") - if Engine.has_singleton("GameCenter"): - game_center = Engine.get_singleton("GameCenter") - else: - print("iOS Game Center plugin is not available on this platform.") + if Engine.has_singleton("GameCenter"): + game_center = Engine.get_singleton("GameCenter") + else: + print("iOS Game Center plugin is not available on this platform.") ``` @@ -40,7 +40,7 @@ When requesting an asynchronous operation, the method will look like this: ``` - Error purchase(Variant params); +Error purchase(Variant params); ``` The parameter will usually be a Dictionary, with the information @@ -52,22 +52,22 @@ the error value is 'OK', a response event will be produced and added to the 'pending events' queue. Example: ``` - func on_purchase_pressed(): - var result = InAppStore.purchase({ "product_id": "my_product" }) - if result == OK: - animation.play("busy") # show the "waiting for response" animation - else: - show_error() +func on_purchase_pressed(): + var result = InAppStore.purchase({ "product_id": "my_product" }) + if result == OK: + animation.play("busy") # show the "waiting for response" animation + else: + show_error() - # put this on a 1 second timer or something - func check_events(): - while in_app_store.get_pending_event_count() > 0: - var event = in_app_store.pop_pending_event() - if event.type == "purchase": - if event.result == "ok": - show_success(event.product_id) - else: - show_error() +# put this on a 1 second timer or something +func check_events(): + while in_app_store.get_pending_event_count() > 0: + var event = in_app_store.pop_pending_event() + if event.type == "purchase": + if event.result == "ok": + show_success(event.product_id) + else: + show_error() ``` Remember that when a call returns OK, the API will *always* produce an @@ -94,18 +94,18 @@ It is initialized automatically. The following methods are available and documented below: ``` - Error purchase(Variant params) - Error request_product_info(Variant params) - Error restore_purchases() - void set_auto_finish_transaction(bool enable) - void finish_transaction(String product_id) +Error purchase(Variant params) +Error request_product_info(Variant params) +Error restore_purchases() +void set_auto_finish_transaction(bool enable) +void finish_transaction(String product_id) ``` and the pending events interface: ``` - int get_pending_event_count() - Variant pop_pending_event() +int get_pending_event_count() +Variant pop_pending_event() ``` ### `purchase` @@ -120,7 +120,7 @@ Takes a dictionary as a parameter, with one field, `product_id`, a string with your product ID. Example: ``` - var result = in_app_store.purchase({ "product_id": "my_product" }) +var result = in_app_store.purchase({ "product_id": "my_product" }) ``` #### Response event @@ -130,21 +130,21 @@ The response event will be a dictionary with the following fields: On error: ``` - { - "type": "purchase", - "result": "error", - "product_id": "the product ID requested", - } +{ + "type": "purchase", + "result": "error", + "product_id": "the product ID requested", +} ``` On success: ``` - { - "type": "purchase", - "result": "ok", - "product_id": "the product ID requested", - } +{ + "type": "purchase", + "result": "ok", + "product_id": "the product ID requested", +} ``` ### `request_product_info` @@ -157,7 +157,7 @@ Takes a dictionary as a parameter, with a single `product_ids` key to which a string array of product IDs is assigned. Example: ``` - var result = in_app_store.request_product_info({ "product_ids": ["my_product1", "my_product2"] }) +var result = in_app_store.request_product_info({ "product_ids": ["my_product1", "my_product2"] }) ``` #### Response event @@ -165,16 +165,16 @@ string array of product IDs is assigned. Example: The response event will be a dictionary with the following fields: ``` - { - "type": "product_info", - "result": "ok", - "invalid_ids": [ list of requested IDs that were invalid ], - "ids": [ list of IDs that were valid ], - "titles": [ list of valid product titles (corresponds with list of valid IDs) ], - "descriptions": [ list of valid product descriptions ] , - "prices": [ list of valid product prices ], - "localized_prices": [ list of valid product localized prices ], - } +{ + "type": "product_info", + "result": "ok", + "invalid_ids": [ list of requested IDs that were invalid ], + "ids": [ list of IDs that were valid ], + "titles": [ list of valid product titles (corresponds with list of valid IDs) ], + "descriptions": [ list of valid product descriptions ] , + "prices": [ list of valid product prices ], + "localized_prices": [ list of valid product localized prices ], +} ``` ### `restore_purchases` @@ -187,11 +187,11 @@ response events for each previously purchased product ID. The response events will be dictionaries with the following fields: ``` - { - "type": "restore", - "result": "ok", - "product_id": "product ID of restored purchase", - } +{ + "type": "restore", + "result": "ok", + "product_id": "product ID of restored purchase", +} ``` ### `set_auto_finish_transaction` @@ -205,7 +205,7 @@ Takes a boolean as a parameter which specifies if purchases should be automatically finalized. Example: ``` - in_app_store.set_auto_finish_transaction(true) +in_app_store.set_auto_finish_transaction(true) ``` ### `finish_transaction` @@ -220,7 +220,7 @@ Takes a string `product_id` as an argument. `product_id` specifies what product finalize the purchase on. Example: ``` - in_app_store.finish_transaction("my_product1") +in_app_store.finish_transaction("my_product1") ``` ## Game Center @@ -231,22 +231,22 @@ The Game Center API is available through the "GameCenter" singleton. It has the following methods: ``` - Error authenticate() - bool is_authenticated() - Error post_score(Variant score) - Error award_achievement(Variant params) - void reset_achievements() - void request_achievements() - void request_achievement_descriptions() - Error show_game_center(Variant params) - Error request_identity_verification_signature() +Error authenticate() +bool is_authenticated() +Error post_score(Variant score) +Error award_achievement(Variant params) +void reset_achievements() +void request_achievements() +void request_achievement_descriptions() +Error show_game_center(Variant params) +Error request_identity_verification_signature() ``` and the pending events interface: ``` - int get_pending_event_count() - Variant pop_pending_event() +int get_pending_event_count() +Variant pop_pending_event() ``` ### `authenticate` @@ -260,22 +260,22 @@ The response event will be a dictionary with the following fields: On error: ``` - { - "type": "authentication", - "result": "error", - "error_code": the value from NSError::code, - "error_description": the value from NSError::localizedDescription, - } +{ + "type": "authentication", + "result": "error", + "error_code": the value from NSError::code, + "error_description": the value from NSError::localizedDescription, +} ``` On success: ``` - { - "type": "authentication", - "result": "ok", - "player_id": the value from GKLocalPlayer::playerID, - } +{ + "type": "authentication", + "result": "ok", + "player_id": the value from GKLocalPlayer::playerID, +} ``` ### `post_score` @@ -292,7 +292,7 @@ Takes a dictionary as a parameter, with two fields: Example: ``` - var result = game_center.post_score({ "score": 100, "category": "my_leaderboard", }) +var result = game_center.post_score({ "score": 100, "category": "my_leaderboard", }) ``` #### Response event @@ -302,21 +302,21 @@ The response event will be a dictionary with the following fields: On error: ``` - { - "type": "post_score", - "result": "error", - "error_code": the value from NSError::code, - "error_description": the value from NSError::localizedDescription, - } +{ + "type": "post_score", + "result": "error", + "error_code": the value from NSError::code, + "error_description": the value from NSError::localizedDescription, +} ``` On success: ``` - { - "type": "post_score", - "result": "ok", - } +{ + "type": "post_score", + "result": "ok", +} ``` ### `award_achievement` @@ -336,7 +336,7 @@ Takes a Dictionary as a parameter, with 3 fields: Example: ``` - var result = award_achievement({ "name": "hard_mode_completed", "progress": 6.1 }) +var result = award_achievement({ "name": "hard_mode_completed", "progress": 6.1 }) ``` #### Response event @@ -346,20 +346,20 @@ The response event will be a dictionary with the following fields: On error: ``` - { - "type": "award_achievement", - "result": "error", - "error_code": the error code taken from NSError::code, - } +{ + "type": "award_achievement", + "result": "error", + "error_code": the error code taken from NSError::code, +} ``` On success: ``` - { - "type": "award_achievement", - "result": "ok", - } +{ + "type": "award_achievement", + "result": "ok", +} ``` ### `reset_achievements` @@ -373,20 +373,20 @@ The response event will be a dictionary with the following fields: On error: ``` - { - "type": "reset_achievements", - "result": "error", - "error_code": the value from NSError::code, - } +{ + "type": "reset_achievements", + "result": "error", + "error_code": the value from NSError::code, +} ``` On success: ``` - { - "type": "reset_achievements", - "result": "ok", - } +{ + "type": "reset_achievements", + "result": "ok", +} ``` ### `request_achievements` @@ -401,22 +401,22 @@ The response event will be a dictionary with the following fields: On error: ``` - { - "type": "achievements", - "result": "error", - "error_code": the value from NSError::code, - } +{ + "type": "achievements", + "result": "error", + "error_code": the value from NSError::code, +} ``` On success: ``` - { - "type": "achievements", - "result": "ok", - "names": [ list of the name of each achievement ], - "progress": [ list of the progress made on each achievement ], - } +{ + "type": "achievements", + "result": "ok", + "names": [ list of the name of each achievement ], + "progress": [ list of the progress made on each achievement ], +} ``` ### `request_achievement_descriptions` @@ -431,27 +431,27 @@ The response event will be a dictionary with the following fields: On error: ``` - { - "type": "achievement_descriptions", - "result": "error", - "error_code": the value from NSError::code, - } +{ + "type": "achievement_descriptions", + "result": "error", + "error_code": the value from NSError::code, +} ``` On success: ``` - { - "type": "achievement_descriptions", - "result": "ok", - "names": [ list of the name of each achievement ], - "titles": [ list of the title of each achievement ], - "unachieved_descriptions": [ list of the description of each achievement when it is unachieved ], - "achieved_descriptions": [ list of the description of each achievement when it is achieved ], - "maximum_points": [ list of the points earned by completing each achievement ], - "hidden": [ list of booleans indicating whether each achievement is initially visible ], - "replayable": [ list of booleans indicating whether each achievement can be earned more than once ], - } +{ + "type": "achievement_descriptions", + "result": "ok", + "names": [ list of the name of each achievement ], + "titles": [ list of the title of each achievement ], + "unachieved_descriptions": [ list of the description of each achievement when it is unachieved ], + "achieved_descriptions": [ list of the description of each achievement when it is achieved ], + "maximum_points": [ list of the points earned by completing each achievement ], + "hidden": [ list of booleans indicating whether each achievement is initially visible ], + "replayable": [ list of booleans indicating whether each achievement can be earned more than once ], +} ``` ### `show_game_center` @@ -474,8 +474,8 @@ Takes a Dictionary as a parameter, with two fields: Examples: ``` - var result = show_game_center({ "view": "leaderboards", "leaderboard_name": "best_time_leaderboard" }) - var result = show_game_center({ "view": "achievements" }) +var result = show_game_center({ "view": "leaderboards", "leaderboard_name": "best_time_leaderboard" }) +var result = show_game_center({ "view": "achievements" }) ``` #### Response event @@ -485,10 +485,10 @@ The response event will be a dictionary with the following fields: On close: ``` - { - "type": "show_game_center", - "result": "ok", - } +{ + "type": "show_game_center", + "result": "ok", +} ``` ### Multi-platform games @@ -502,21 +502,21 @@ valid identifiers (local variable or class member). This is an example of how to work around this in a class: ``` - var GameCenter = null # define it as a class member +var GameCenter = null # define it as a class member - func post_score(score): - if GameCenter == null: - return - GameCenter.post_score({ "value": score, "category": "my_leaderboard" }) +func post_score(score): + if GameCenter == null: + return + GameCenter.post_score({ "value": score, "category": "my_leaderboard" }) - func check_events(): - while GameCenter.get_pending_event_count() > 0: - # do something with events here - pass +func check_events(): + while GameCenter.get_pending_event_count() > 0: + # do something with events here + pass - func _ready(): - # check if the singleton exists - if Globals.has_singleton("GameCenter"): - GameCenter = Globals.get_singleton("GameCenter") - # connect your timer here to the "check_events" function +func _ready(): + # check if the singleton exists + if Globals.has_singleton("GameCenter"): + GameCenter = Globals.get_singleton("GameCenter") + # connect your timer here to the "check_events" function ``` diff --git a/03_usage/13_platform/javascript/01_customizing_html5_shell.md b/03_usage/13_platform/javascript/01_customizing_html5_shell.md index 57973c3..2211509 100644 --- a/03_usage/13_platform/javascript/01_customizing_html5_shell.md +++ b/03_usage/13_platform/javascript/01_customizing_html5_shell.md @@ -21,21 +21,21 @@ The default HTML page is available in the Pandemonium Engine repository at but the following template can be used as a much simpler example: ``` - - - - My Template - - - - - - - - + + + + My Template + + + + + + + + ``` ## Setup @@ -87,13 +87,13 @@ class with the exported configuration, and then call the :js:meth:`engine.startG optionally overriding any :js:attr:`EngineConfig` parameters. ``` - const engine = new Engine($PANDEMONIUM_CONFIG); - engine.startGame({ - /* optional override configuration, eg. */ - // unloadAfterInit: false, - // canvasResizePolicy: 0, - // ... - }); +const engine = new Engine($PANDEMONIUM_CONFIG); +engine.startGame({ + /* optional override configuration, eg. */ + // unloadAfterInit: false, + // canvasResizePolicy: 0, + // ... +}); ``` This snippet of code automatically loads and initializes the engine before starting the game. @@ -112,20 +112,20 @@ the module initialization, but before the engine starts. This process is a bit more complex, but gives you full control over the engine startup process. ``` - const myWasm = 'mygame.wasm'; - const myPck = 'mygame.pck'; - const engine = new Engine(); - Promise.all([ - // Load and init the engine - engine.init(myWasm), - // And the pck concurrently - engine.preloadFile(myPck), - ]).then(() => { - // Now start the engine. - return engine.start({ args: ['--main-pack', myPck] }); - }).then(() => { - console.log('Engine has started!'); - }); +const myWasm = 'mygame.wasm'; +const myPck = 'mygame.pck'; +const engine = new Engine(); +Promise.all([ + // Load and init the engine + engine.init(myWasm), + // And the pck concurrently + engine.preloadFile(myPck), +]).then(() => { + // Now start the engine. + return engine.start({ args: ['--main-pack', myPck] }); +}).then(() => { + console.log('Engine has started!'); +}); ``` To load the engine manually the :js:meth:`Engine.load` static method must be called. As @@ -160,8 +160,8 @@ element the :js:attr:`canvas` override option can be used. It requires a referen element itself. ``` - const canvasElement = document.querySelector("#my-canvas-element"); - engine.startGame({ canvas: canvasElement }); +const canvasElement = document.querySelector("#my-canvas-element"); +engine.startGame({ canvas: canvasElement }); ``` The way the engine resize the canvas can be configured via the :js:attr:`canvasResizePolicy` @@ -172,10 +172,10 @@ the progress. This can be achieved with the :js:attr:`onProgress` callback optio allows to set up a callback function that will be called regularly as the engine loads new bytes. ``` - function printProgress(current, total) { - console.log("Loaded " + current + " of " + total + " bytes"); - } - engine.startGame({ onProgress: printProgress }); +function printProgress(current, total) { + console.log("Loaded " + current + " of " + total + " bytes"); +} +engine.startGame({ onProgress: printProgress }); ``` Be aware that in some cases `total` can be `0`. This means that it cannot be calculated. @@ -196,13 +196,13 @@ Use the :js:attr:`onPrint` override option to set a callback function for the ou and the :js:attr:`onPrintError` override option to set a callback function for the error stream. ``` - function print(text) { - console.log(text); - } - function printError(text) { - console.warn(text); - } - engine.startGame({ onPrint: print, onPrintError: printError }); +function print(text) { + console.log(text); +} +function printError(text) { + console.warn(text); +} +engine.startGame({ onPrint: print, onPrintError: printError }); ``` When handling the engine output keep in mind, that it may not be desirable to print it out in the diff --git a/03_usage/14_rendering/01_viewports.md b/03_usage/14_rendering/01_viewports.md index 425d85e..5ff3bc8 100644 --- a/03_usage/14_rendering/01_viewports.md +++ b/03_usage/14_rendering/01_viewports.md @@ -72,7 +72,7 @@ than one, make sure that the desired one has the "current" property set, or make it the current camera by calling: ``` - camera.make_current() +camera.make_current() ``` By default, cameras will render all objects in their world. In 3D, cameras can use their @@ -90,8 +90,8 @@ It is also possible to scale the 2D content and make the `Viewport` resolution different from the one specified in size, by calling: ``` - viewport.set_size_override(true, Vector2(width, height)) # Custom size for 2D. - viewport.set_size_override_stretch(true) # Enable stretch for custom size. +viewport.set_size_override(true, Vector2(width, height)) # Custom size for 2D. +viewport.set_size_override_stretch(true) # Enable stretch for custom size. ``` The root `Viewport` uses this for the stretch options in the project @@ -129,16 +129,16 @@ It is possible to query a capture of the `Viewport` contents. For the root following code: ``` - # Retrieve the captured Image using get_data(). - var img = get_viewport().get_texture().get_data() - # Flip on the Y axis. - # You can also set "V Flip" to true if not on the root Viewport. - img.flip_y() - # Convert Image to ImageTexture. - var tex = ImageTexture.new() - tex.create_from_image(img) - # Set Sprite Texture. - $sprite.texture = tex +# Retrieve the captured Image using get_data(). +var img = get_viewport().get_texture().get_data() +# Flip on the Y axis. +# You can also set "V Flip" to true if not on the root Viewport. +img.flip_y() +# Convert Image to ImageTexture. +var tex = ImageTexture.new() +tex.create_from_image(img) +# Set Sprite Texture. +$sprite.texture = tex ``` But if you use this in `ready()` or from the first frame of the `Viewport's` initialization, @@ -146,9 +146,9 @@ you will get an empty texture because there is nothing to get as texture. You ca it using (for example): ``` - # Wait until the frame has finished before getting the texture. - yield(VisualServer, "frame_post_draw") - # You can get the image after this. +# Wait until the frame has finished before getting the texture. +yield(VisualServer, "frame_post_draw") +# You can get the image after this. ``` ## Viewport Container @@ -214,9 +214,9 @@ visible in the scene editor. To display the contents, you have to draw the `View This can be requested via code using (for example): ``` - # This gives us the ViewportTexture. - var rtt = viewport.get_texture() - sprite.texture = rtt +# This gives us the ViewportTexture. +var rtt = viewport.get_texture() +sprite.texture = rtt ``` Or it can be assigned in the editor by selecting "New ViewportTexture" diff --git a/03_usage/14_rendering/02_multiple_resolutions.md b/03_usage/14_rendering/02_multiple_resolutions.md index 6683e92..0c37987 100644 --- a/03_usage/14_rendering/02_multiple_resolutions.md +++ b/03_usage/14_rendering/02_multiple_resolutions.md @@ -385,7 +385,7 @@ upon loading. This can be done by calling the method below before the game data is loaded: ``` - VisualServer.texture_set_shrink_all_x2_on_set_data(true) +VisualServer.texture_set_shrink_all_x2_on_set_data(true) ``` Alternatively, you can also enable mipmaps on all your 2D textures. However, diff --git a/03_usage/15_scripting/01_idle_and_physics_processing.md b/03_usage/15_scripting/01_idle_and_physics_processing.md index 933dda4..04ecce4 100644 --- a/03_usage/15_scripting/01_idle_and_physics_processing.md +++ b/03_usage/15_scripting/01_idle_and_physics_processing.md @@ -26,9 +26,9 @@ The engine calls this method every time it draws a frame: gdscript GDScript ``` - func _process(delta): - # Do something... - pass +func _process(delta): + # Do something... + pass ``` Keep in mind that the frequency at which the engine calls `process()` depends @@ -52,9 +52,9 @@ The engine calls this method every time it draws a frame: gdscript GDScript ``` - func _physics_process(delta): - # Do something... - pass +func _physics_process(delta): + # Do something... + pass ``` The function `process()` is not synchronized with physics. Its rate depends on @@ -67,13 +67,13 @@ single Label node, with the following script attached to it: gdscript GDScript ``` - extends Label +extends Label - var time = 0 +var time = 0 - func _process(delta): - time += delta - text = str(time) # 'text' is a built-in Label property. +func _process(delta): + time += delta + text = str(time) # 'text' is a built-in Label property. ``` When you run the scene, you should see a counter increasing each frame. diff --git a/03_usage/15_scripting/02_groups.md b/03_usage/15_scripting/02_groups.md index 5da0e4d..12696f8 100644 --- a/03_usage/15_scripting/02_groups.md +++ b/03_usage/15_scripting/02_groups.md @@ -74,8 +74,8 @@ scene tree. gdscript GDScript ``` - func _ready(): - add_to_group("guards") +func _ready(): + add_to_group("guards") ``` Imagine you're creating an infiltration game. When an @@ -87,8 +87,8 @@ enemies that the player was spotted. gdscript GDScript ``` - func _on_Player_spotted(): - get_tree().call_group("guards", "enter_alert_mode") +func _on_Player_spotted(): + get_tree().call_group("guards", "enter_alert_mode") ``` The above code calls the function `enter_alert_mode` on every member of the @@ -101,7 +101,7 @@ To get the full list of nodes in the `guards` group as an array, you can call gdscript GDScript ``` - var guards = get_tree().get_nodes_in_group("guards") +var guards = get_tree().get_nodes_in_group("guards") ``` The `SceneTree` class provides many more useful methods diff --git a/03_usage/15_scripting/03_nodes_and_scene_instances.md b/03_usage/15_scripting/03_nodes_and_scene_instances.md index b2b0544..47fef0f 100644 --- a/03_usage/15_scripting/03_nodes_and_scene_instances.md +++ b/03_usage/15_scripting/03_nodes_and_scene_instances.md @@ -22,12 +22,12 @@ To do so, you can use the following code. gdscript GDScript ``` - var sprite - var camera2d +var sprite +var camera2d - func _ready(): - sprite = get_node("Sprite") - camera2d = get_node("Camera2D") +func _ready(): + sprite = get_node("Sprite") + camera2d = get_node("Camera2D") ``` Note that you get nodes using their name, not their type. Above, "Sprite" and @@ -56,10 +56,10 @@ To get the Tween node, you would use the following code. gdscript GDScript ``` - var tween +var tween - func _ready(): - tween = get_node("ShieldBar/Tween") +func _ready(): + tween = get_node("ShieldBar/Tween") ``` Note: @@ -76,15 +76,15 @@ You can use two shorthands to shorten your code in GDScript. Firstly, putting th the `ready()` callback. ``` - onready var sprite = get_node("Sprite") +onready var sprite = get_node("Sprite") ``` There is also a short notation for `get_node()`: the dollar sign, "$". You place it before the name or path of the node you want to get. ``` - onready var sprite = $Sprite - onready var tween = $ShieldBar/Tween +onready var sprite = $Sprite +onready var tween = $ShieldBar/Tween ``` ## Creating nodes @@ -99,11 +99,11 @@ script. gdscript GDScript ``` - var sprite +var sprite - func _ready(): - var sprite = Sprite.new() # Create a new Sprite. - add_child(sprite) # Add it as a child of this node. +func _ready(): + var sprite = Sprite.new() # Create a new Sprite. + add_child(sprite) # Add it as a child of this node. ``` To delete a node and free it from memory, you can call its `queue_free()` @@ -114,7 +114,7 @@ the scene and frees the object in memory. gdscript GDScript ``` - sprite.queue_free() +sprite.queue_free() ``` Before calling `sprite.queue_free()`, the remote scene tree looks like this. @@ -147,7 +147,7 @@ steps: gdscript GDScript ``` - var scene = load("res://MyScene.tscn") +var scene = load("res://MyScene.tscn") ``` Preloading the scene can improve the user's experience as the load operation @@ -157,7 +157,7 @@ only available with GDScript. gdscript GDScript ``` - var scene = preload("res://MyScene.tscn") +var scene = preload("res://MyScene.tscn") ``` At that point, `scene` is a packed scene resource, not a node. To create the @@ -168,8 +168,8 @@ as a child of your current node. gdscript GDScript ``` - var instance = scene.instance() - add_child(instance) +var instance = scene.instance() +add_child(instance) ``` The advantage of this two-step process is you can keep a packed scene loaded and diff --git a/03_usage/15_scripting/04_overridable_functions.md b/03_usage/15_scripting/04_overridable_functions.md index 69fb3d0..b71f7a6 100644 --- a/03_usage/15_scripting/04_overridable_functions.md +++ b/03_usage/15_scripting/04_overridable_functions.md @@ -35,18 +35,18 @@ a node exits the scene tree. This can be when you call `Node.remove_child() gdscript GDScript ``` - # Called every time the node enters the scene tree. - func _enter_tree(): - pass +# Called every time the node enters the scene tree. +func _enter_tree(): + pass - # Called when both the node and its children have entered the scene tree. - func _ready(): - pass +# Called when both the node and its children have entered the scene tree. +func _ready(): + pass - # Called when the node is about to leave the scene tree, after all its - # children received the _exit_tree() callback. - func _exit_tree(): - pass +# Called when the node is about to leave the scene tree, after all its +# children received the _exit_tree() callback. +func _exit_tree(): + pass ``` The two virtual methods `process()` and `physics_process()` allow you to @@ -57,13 +57,13 @@ information, read the dedicated documentation: gdscript GDScript ``` - # Called every frame, as often as possible. - func _process(delta): - pass +# Called every frame, as often as possible. +func _process(delta): + pass - # Called every physics frame. - func _physics_process(delta): - pass +# Called every physics frame. +func _physics_process(delta): + pass ``` Two more essential built-in node callback functions are @@ -80,14 +80,14 @@ To learn more about inputs in Pandemonium, see the `Input section ( toc-learn-fe gdscript GDScript ``` - # Called once for every event. - func _unhandled_input(event): - pass +# Called once for every event. +func _unhandled_input(event): + pass - # Called once for every event, before _unhandled_input(), allowing you to - # consume some events. - func _input(event): - pass +# Called once for every event, before _unhandled_input(), allowing you to +# consume some events. +func _input(event): + pass ``` There are some more overridable functions like diff --git a/03_usage/15_scripting/05_cross_language_scripting.md b/03_usage/15_scripting/05_cross_language_scripting.md index c081361..7798a2c 100644 --- a/03_usage/15_scripting/05_cross_language_scripting.md +++ b/03_usage/15_scripting/05_cross_language_scripting.md @@ -12,24 +12,24 @@ The following two scripts will be used as references throughout this page. gdscript GDScript ``` - extends Node +extends Node - var str1 : String = "foo" - var str2 : String setget ,get_str2 +var str1 : String = "foo" +var str2 : String setget ,get_str2 - func get_str2() -> String: - return "foofoo" +func get_str2() -> String: + return "foofoo" - func print_node_name(node : Node) -> void: - print(node.get_name()) +func print_node_name(node : Node) -> void: + print(node.get_name()) - func print_array(arr : Array) -> void: - for element in arr: - print(element) +func print_array(arr : Array) -> void: + for element in arr: + print(element) - func print_n_times(msg : String, n : int) -> void: - for i in range(n): - print(msg) +func print_n_times(msg : String, n : int) -> void: + for i in range(n): + print(msg) ``` ## Instantiating nodes @@ -44,9 +44,9 @@ Using C# from GDScript doesn't need much work. Once loaded with `new()`. ``` - var my_csharp_script = load("res://path_to_cs_file.cs") - var my_csharp_node = my_csharp_script.new() - print(my_csharp_node.str2) # barbar +var my_csharp_script = load("res://path_to_cs_file.cs") +var my_csharp_node = my_csharp_script.new() +print(my_csharp_node.str2) # barbar ``` Warning: @@ -68,8 +68,8 @@ From the C# side, everything work the same way. Once loaded, the GDScript can be instantiated with `GDScript.New()`. ``` - GDScript MyGDScript = (GDScript) GD.Load("res://path_to_gd_file.gd"); - Object myGDScriptNode = (Pandemonium.Object) MyGDScript.New(); // This is a Pandemonium.Object +GDScript MyGDScript = (GDScript) GD.Load("res://path_to_gd_file.gd"); +Object myGDScriptNode = (Pandemonium.Object) MyGDScript.New(); // This is a Pandemonium.Object ``` Here we are using an `Object`, but you can use type conversion like @@ -83,12 +83,12 @@ Accessing C# fields from GDScript is straightforward, you shouldn't have anything to worry about. ``` - print(my_csharp_node.str1) # bar - my_csharp_node.str1 = "BAR" - print(my_csharp_node.str1) # BAR +print(my_csharp_node.str1) # bar +my_csharp_node.str1 = "BAR" +print(my_csharp_node.str1) # BAR - print(my_csharp_node.str2) # barbar - # my_csharp_node.str2 = "BARBAR" # This line will hang and crash +print(my_csharp_node.str2) # barbar +# my_csharp_node.str2 = "BARBAR" # This line will hang and crash ``` Note that it doesn't matter if the field is defined as a property or an @@ -102,12 +102,12 @@ convoluted, you will have to use `Object.Get()` and `Object.Set()`. The first argument is the name of the field you want to access. ``` - GD.Print(myGDScriptNode.Get("str1")); // foo - myGDScriptNode.Set("str1", "FOO"); - GD.Print(myGDScriptNode.Get("str1")); // FOO +GD.Print(myGDScriptNode.Get("str1")); // foo +myGDScriptNode.Set("str1", "FOO"); +GD.Print(myGDScriptNode.Get("str1")); // FOO - GD.Print(myGDScriptNode.Get("str2")); // foofoo - // myGDScriptNode.Set("str2", "FOOFOO"); // This line won't do anything +GD.Print(myGDScriptNode.Get("str2")); // foofoo +// myGDScriptNode.Set("str2", "FOOFOO"); // This line won't do anything ``` Keep in mind that when setting a field value you should only use types the @@ -170,3 +170,4 @@ inherit from a GDScript file. Due to how complex this would be to implement, this limitation is unlikely to be lifted in the future. See `this GitHub issue ( https://github.com/Relintai/pandemonium_engine/issues/38352 )` for more information. +``` \ No newline at end of file diff --git a/03_usage/15_scripting/06_creating_script_templates.md b/03_usage/15_scripting/06_creating_script_templates.md index b568723..54d0373 100644 --- a/03_usage/15_scripting/06_creating_script_templates.md +++ b/03_usage/15_scripting/06_creating_script_templates.md @@ -65,22 +65,22 @@ other templates. gdscript GDScript ``` - extends %BASE% +extends %BASE% - # Declare member variables here. Examples: - # var a%INT_TYPE% = 2 - # var b%STRING_TYPE% = "text" +# Declare member variables here. Examples: +# var a%INT_TYPE% = 2 +# var b%STRING_TYPE% = "text" - # Called when the node enters the scene tree for the first time. - func _ready()%VOID_RETURN%: - pass # Replace with function body. +# Called when the node enters the scene tree for the first time. +func _ready()%VOID_RETURN%: + pass # Replace with function body. - # Called every frame. 'delta' is the elapsed time since the previous frame. - #func _process(delta%FLOAT_TYPE%)%VOID_RETURN%: - # pass +# Called every frame. 'delta' is the elapsed time since the previous frame. +#func _process(delta%FLOAT_TYPE%)%VOID_RETURN%: +# pass ``` ## List of template placeholders diff --git a/03_usage/15_scripting/07_evaluating_expressions.md b/03_usage/15_scripting/07_evaluating_expressions.md index d9bd5ff..7a7aa2c 100644 --- a/03_usage/15_scripting/07_evaluating_expressions.md +++ b/03_usage/15_scripting/07_evaluating_expressions.md @@ -23,10 +23,10 @@ Note: To evaluate a mathematical expression, use: ``` - var expression = Expression.new() - expression.parse("20 + 10*2 - 5/2.0") - var result = expression.execute() - print(result) # 37.5 +var expression = Expression.new() +expression.parse("20 + 10*2 - 5/2.0") +var result = expression.execute() +print(result) # 37.5 ``` The following operators are available: @@ -54,19 +54,19 @@ numbers, strings, arrays, dictionaries, colors, vectors, … Arrays and dictionaries can be indexed like in GDScript: ``` - # Returns 1. - [1, 2][0] +# Returns 1. +[1, 2][0] - # Returns 3. Negative indices can be used to count from the end of the array. - [1, 3][-1] +# Returns 3. Negative indices can be used to count from the end of the array. +[1, 3][-1] - # Returns "green". - {"favorite_color": "green"}["favorite_color"] +# Returns "green". +{"favorite_color": "green"}["favorite_color"] - # All 3 lines below return 7.0 (Vector3 is floating-point). - Vector3(5, 6, 7)[2] - Vector3(5, 6, 7)["z"] - Vector3(5, 6, 7).z +# All 3 lines below return 7.0 (Vector3 is floating-point). +Vector3(5, 6, 7)[2] +Vector3(5, 6, 7)["z"] +Vector3(5, 6, 7).z ``` ## Passing variables to an expression @@ -76,14 +76,14 @@ become available in the expression's "context" and will be substituted when used in the expression: ``` - var expression = Expression.new() - # Define the variable names first in the second parameter of `parse()`. - # In this example, we use `x` for the variable name. - expression.parse("20 + 2 * x", ["x"]) - # Then define the variable values in the first parameter of `execute()`. - # Here, `x` is assigned the integer value 5. - var result = expression.execute([5]) - print(result) # 30 +var expression = Expression.new() +# Define the variable names first in the second parameter of `parse()`. +# In this example, we use `x` for the variable name. +expression.parse("20 + 2 * x", ["x"]) +# Then define the variable values in the first parameter of `execute()`. +# Here, `x` is assigned the integer value 5. +var result = expression.execute([5]) +print(result) # 30 ``` Both the variable names and variable values **must** be specified as an array, @@ -99,22 +99,22 @@ you can set the value of the `base_instance` parameter to a specific object instance such as `self`, another script instance or even a singleton: ``` - func double(number): - return number * 2 +func double(number): + return number * 2 - func _ready(): - var expression = Expression.new() - expression.parse("double(10)") +func _ready(): + var expression = Expression.new() + expression.parse("double(10)") - # This won't work since we're not passing the current script as the base instance. - var result = expression.execute([], null) - print(result) # null + # This won't work since we're not passing the current script as the base instance. + var result = expression.execute([], null) + print(result) # null - # This will work since we're passing the current script (i.e. self) - # as the base instance. - result = expression.execute([], self) - print(result) # 20 + # This will work since we're passing the current script (i.e. self) + # as the base instance. + result = expression.execute([], self) + print(result) # 20 ``` Associating a base instance allows doing the following: @@ -137,67 +137,67 @@ Warning: The script below demonstrates what the Expression class is capable of: ``` - const DAYS_IN_YEAR = 365 - var script_member_variable = 1000 +const DAYS_IN_YEAR = 365 +var script_member_variable = 1000 - func _ready(): - # Constant mathexpression. - evaluate("2 + 2") - # Math expression with variables. - evaluate("x + y", ["x", "y"], [60, 100]) +func _ready(): + # Constant mathexpression. + evaluate("2 + 2") + # Math expression with variables. + evaluate("x + y", ["x", "y"], [60, 100]) - # Call built-in method (hardcoded in the Expression class). - evaluate("deg2rad(90)") + # Call built-in method (hardcoded in the Expression class). + evaluate("deg2rad(90)") - # Call user method (defined in the script). - # We can do this because the expression execution is bound to `self` - # in the `evaluate()` method. - # Since this user method returns a value, we can use it in math expressions. - evaluate("call_me() + DAYS_IN_YEAR + script_member_variable") - evaluate("call_me(42)") - evaluate("call_me('some string')") + # Call user method (defined in the script). + # We can do this because the expression execution is bound to `self` + # in the `evaluate()` method. + # Since this user method returns a value, we can use it in math expressions. + evaluate("call_me() + DAYS_IN_YEAR + script_member_variable") + evaluate("call_me(42)") + evaluate("call_me('some string')") - func evaluate(command, variable_names = [], variable_values = []) -> void: - var expression = Expression.new() - var error = expression.parse(command, variable_names) - if error != OK: - push_error(expression.get_error_text()) - return +func evaluate(command, variable_names = [], variable_values = []) -> void: + var expression = Expression.new() + var error = expression.parse(command, variable_names) + if error != OK: + push_error(expression.get_error_text()) + return - var result = expression.execute(variable_values, self) + var result = expression.execute(variable_values, self) - if not expression.has_execute_failed(): - print(str(result)) + if not expression.has_execute_failed(): + print(str(result)) - func call_me(argument = null): - print("\nYou called 'call_me()' in the expression text.") - if argument: - print("Argument passed: %s" % argument) +func call_me(argument = null): + print("\nYou called 'call_me()' in the expression text.") + if argument: + print("Argument passed: %s" % argument) - # The method's return value is also the expression's return value. - return 0 + # The method's return value is also the expression's return value. + return 0 ``` The output from the script will be: ``` - 4 - 160 - 1.570796 +4 +160 +1.570796 - You called 'call_me()' in the expression text. - 1365 +You called 'call_me()' in the expression text. +1365 - You called 'call_me()' in the expression text. - Argument passed: 42 - 0 +You called 'call_me()' in the expression text. +Argument passed: 42 +0 - You called 'call_me()' in the expression text. - Argument passed: some string - 0 +You called 'call_me()' in the expression text. +Argument passed: some string +0 ``` ## Built-in functions diff --git a/03_usage/15_scripting/08_change_scenes_manually.md b/03_usage/15_scripting/08_change_scenes_manually.md index 6e21e3c..fcfb982 100644 --- a/03_usage/15_scripting/08_change_scenes_manually.md +++ b/03_usage/15_scripting/08_change_scenes_manually.md @@ -11,12 +11,12 @@ scenes which one instances and adds to the tree at runtime: gdscript GDScript ``` - var simultaneous_scene = preload("res://levels/level2.tscn").instance() +var simultaneous_scene = preload("res://levels/level2.tscn").instance() - func _add_a_scene_manually(): - # This is like autoloading the scene, only - # it happens after already loading the main scene. - get_tree().get_root().add_child(simultaneous_scene) +func _add_a_scene_manually(): + # This is like autoloading the scene, only + # it happens after already loading the main scene. + get_tree().get_root().add_child(simultaneous_scene) ``` To complete the cycle and swap out the new scene with the old one, @@ -107,7 +107,7 @@ a scene's data between scene changes (adding the scene to the root node). gdscript GDScript ``` - get_tree().get_root().add_child(scene) +get_tree().get_root().add_child(scene) ``` Perhaps instead they wish to display multiple scenes at the same time using diff --git a/03_usage/15_scripting/09_instancing_with_signals.md b/03_usage/15_scripting/09_instancing_with_signals.md index e83bd8b..d1ebda2 100644 --- a/03_usage/15_scripting/09_instancing_with_signals.md +++ b/03_usage/15_scripting/09_instancing_with_signals.md @@ -22,12 +22,12 @@ given velocity: gdscript GDScript ``` - extends Area2D +extends Area2D - var velocity = Vector2.ZERO +var velocity = Vector2.ZERO - func _physics_process(delta): - position += velocity * delta +func _physics_process(delta): + position += velocity * delta ``` However, if the bullets are added as children of the player, then they will @@ -46,8 +46,8 @@ You could do this by adding the bullet to the main scene directly: gdscript GDScript ``` - var bullet_instance = Bullet.instance() - get_parent().add_child(bullet_instance) +var bullet_instance = Bullet.instance() +get_parent().add_child(bullet_instance) ``` However, this will lead to a different problem. Now if you try to test your @@ -67,19 +67,19 @@ Here is the code for the player using signals to emit the bullet: gdscript GDScript ``` - extends Sprite +extends Sprite - signal shoot(bullet, direction, location) +signal shoot(bullet, direction, location) - var Bullet = preload("res://Bullet.tscn") +var Bullet = preload("res://Bullet.tscn") - func _input(event): - if event is InputEventMouseButton: - if event.button_index == BUTTON_LEFT and event.pressed: - emit_signal("shoot", Bullet, rotation, position) +func _input(event): + if event is InputEventMouseButton: + if event.button_index == BUTTON_LEFT and event.pressed: + emit_signal("shoot", Bullet, rotation, position) - func _process(delta): - look_at(get_global_mouse_position()) +func _process(delta): + look_at(get_global_mouse_position()) ``` In the main scene, we then connect the player's signal (it will appear in the @@ -88,12 +88,12 @@ In the main scene, we then connect the player's signal (it will appear in the gdscript GDScript ``` - func _on_Player_shoot(Bullet, direction, location): - var b = Bullet.instance() - add_child(b) - b.rotation = direction - b.position = location - b.velocity = b.velocity.rotated(direction) +func _on_Player_shoot(Bullet, direction, location): + var b = Bullet.instance() + add_child(b) + b.rotation = direction + b.position = location + b.velocity = b.velocity.rotated(direction) ``` Now the bullets will maintain their own movement independent of the player's diff --git a/03_usage/15_scripting/10_pausing_games.md b/03_usage/15_scripting/10_pausing_games.md index 17158d3..db4f982 100644 --- a/03_usage/15_scripting/10_pausing_games.md +++ b/03_usage/15_scripting/10_pausing_games.md @@ -18,7 +18,7 @@ To pause the game the pause state must be set. This is done by assigning gdscript GDScript ``` - get_tree().paused = true +get_tree().paused = true ``` Doing this will cause two things. First, 2D and 3D physics will be stopped @@ -41,8 +41,8 @@ You can also alter the property with code: gdscript GDScript ``` - func _ready(): - pause_mode = Node.PAUSE_MODE_PROCESS +func _ready(): + pause_mode = Node.PAUSE_MODE_PROCESS ``` This is what each mode tells a node to do: @@ -88,9 +88,9 @@ enable the pause and show the pause screen. gdscript GDScript ``` - func _on_pause_button_pressed(): - get_tree().paused = true - $pause_popup.show() +func _on_pause_button_pressed(): + get_tree().paused = true + $pause_popup.show() ``` To unpause, do the opposite when the pause screen is @@ -99,7 +99,7 @@ closed: gdscript GDScript ``` - func _on_pause_popup_close_pressed(): - $pause_popup.hide() - get_tree().paused = false +func _on_pause_popup_close_pressed(): + $pause_popup.hide() + get_tree().paused = false ``` diff --git a/03_usage/15_scripting/11_filesystem.md b/03_usage/15_scripting/11_filesystem.md index c8345b7..78adf71 100644 --- a/03_usage/15_scripting/11_filesystem.md +++ b/03_usage/15_scripting/11_filesystem.md @@ -26,11 +26,11 @@ Example of file system contents: ``` - /project.pandemonium - /enemy/enemy.tscn - /enemy/enemy.gd - /enemy/enemysprite.png - /player/player.gd +/project.pandemonium +/enemy/enemy.tscn +/enemy/enemy.gd +/enemy/enemysprite.png +/player/player.gd ``` ## project.pandemonium diff --git a/03_usage/15_scripting/12_resources.md b/03_usage/15_scripting/12_resources.md index 7a68c84..241b944 100644 --- a/03_usage/15_scripting/12_resources.md +++ b/03_usage/15_scripting/12_resources.md @@ -68,9 +68,9 @@ There are two ways to load resources from code. First, you can use the `load()` gdscript GDScript ``` - func _ready(): - var res = load("res://robi.png)") # Pandemonium loads the Resource when it reads the line. - get_node("sprite").texture = res +func _ready(): + var res = load("res://robi.png)") # Pandemonium loads the Resource when it reads the line. + get_node("sprite").texture = res ``` You can also `preload` resources. Unlike `load`, this function will read the @@ -80,9 +80,9 @@ with a variable path: you need to use a constant string. gdscript GDScript ``` - func _ready(): - var res = preload("res://robi.png)") # Pandemonium loads the resource at compile-time - get_node("sprite").texture = res +func _ready(): + var res = preload("res://robi.png)") # Pandemonium loads the resource at compile-time + get_node("sprite").texture = res ``` ## Loading scenes @@ -97,9 +97,9 @@ To get an instance of the scene, you have to use the gdscript GDScript ``` - func _on_shoot(): - var bullet = preload("res://bullet.tscn").instance() - add_child(bullet) +func _on_shoot(): + var bullet = preload("res://bullet.tscn").instance() + add_child(bullet) ``` This method creates the nodes in the scene's hierarchy, configures them, and @@ -170,29 +170,29 @@ Let's see some examples. gdscript GDScript ``` - # bot_stats.gd - extends Resource - export(int) var health - export(Resource) var sub_resource - export(Array, String) var strings +# bot_stats.gd +extends Resource +export(int) var health +export(Resource) var sub_resource +export(Array, String) var strings - # Make sure that every parameter has a default value. - # Otherwise, there will be problems with creating and editing - # your resource via the inspector. - func _init(p_health = 0, p_sub_resource = null, p_strings = []): - health = p_health - sub_resource = p_sub_resource - strings = p_strings +# Make sure that every parameter has a default value. +# Otherwise, there will be problems with creating and editing +# your resource via the inspector. +func _init(p_health = 0, p_sub_resource = null, p_strings = []): + health = p_health + sub_resource = p_sub_resource + strings = p_strings - # bot.gd - extends KinematicBody +# bot.gd +extends KinematicBody - export(Resource) var stats +export(Resource) var stats - func _ready(): - # Uses an implicit, duck-typed interface for any 'health'-compatible resources. - if stats: - print(stats.health) # Prints '10'. +func _ready(): + # Uses an implicit, duck-typed interface for any 'health'-compatible resources. + if stats: + print(stats.health) # Prints '10'. ``` Note: @@ -211,19 +211,19 @@ Note: gdscript GDScript ``` - # bot_stats_table.gd - extends Resource +# bot_stats_table.gd +extends Resource - const BotStats = preload("bot_stats.gd") +const BotStats = preload("bot_stats.gd") - var data = { - "PandemoniumBot": BotStats.new(10), # Creates instance with 10 health. - "DifferentBot": BotStats.new(20) # A different one with 20 health. - } +var data = { + "PandemoniumBot": BotStats.new(10), # Creates instance with 10 health. + "DifferentBot": BotStats.new(20) # A different one with 20 health. +} - func _init(): - print(data) - ``` +func _init(): + print(data) +``` Instead of just inlining the Dictionary values, one could also, alternatively... @@ -251,15 +251,15 @@ Warning: gdscript GDScript ``` - extends Node +extends Node - class MyResource: - extends Resource - export var value = 5 +class MyResource: + extends Resource + export var value = 5 - func _ready(): - var my_res = MyResource.new() +func _ready(): + var my_res = MyResource.new() - # This will NOT serialize the 'value' property. - ResourceSaver.save("res://my_res.tres", my_res) - ``` + # This will NOT serialize the 'value' property. + ResourceSaver.save("res://my_res.tres", my_res) +``` diff --git a/03_usage/15_scripting/13_singletons_autoload.md b/03_usage/15_scripting/13_singletons_autoload.md index b42fcef..4c7b047 100644 --- a/03_usage/15_scripting/13_singletons_autoload.md +++ b/03_usage/15_scripting/13_singletons_autoload.md @@ -75,8 +75,8 @@ This means that any node can access a singleton named "PlayerVariables" with: gdscript GDScript ``` - var player_vars = get_node("/root/PlayerVariables") - player_vars.health -= 10 +var player_vars = get_node("/root/PlayerVariables") +player_vars.health -= 10 ``` If the **Enable** column is checked (which is the default), then the singleton can @@ -85,7 +85,7 @@ be accessed directly without requiring `get_node()`: gdscript GDScript ``` - PlayerVariables.health -= 10 +PlayerVariables.health -= 10 ``` Note that autoload objects (scripts and/or scenes) are accessed just like any @@ -140,13 +140,13 @@ means that the last child of root is always the loaded scene. gdscript GDScript ``` - extends Node +extends Node - var current_scene = null +var current_scene = null - func _ready(): - var root = get_tree().root - current_scene = root.get_child(root.get_child_count() - 1) +func _ready(): + var root = get_tree().root + current_scene = root.get_child(root.get_child_count() - 1) ``` Now we need a function for changing the scene. This function needs to free the @@ -155,34 +155,34 @@ current scene and replace it with the requested one. gdscript GDScript ``` - func goto_scene(path): - # This function will usually be called from a signal callback, - # or some other function in the current scene. - # Deleting the current scene at this point is - # a bad idea, because it may still be executing code. - # This will result in a crash or unexpected behavior. +func goto_scene(path): + # This function will usually be called from a signal callback, + # or some other function in the current scene. + # Deleting the current scene at this point is + # a bad idea, because it may still be executing code. + # This will result in a crash or unexpected behavior. - # The solution is to defer the load to a later time, when - # we can be sure that no code from the current scene is running: + # The solution is to defer the load to a later time, when + # we can be sure that no code from the current scene is running: - call_deferred("_deferred_goto_scene", path) + call_deferred("_deferred_goto_scene", path) - func _deferred_goto_scene(path): - # It is now safe to remove the current scene - current_scene.free() +func _deferred_goto_scene(path): + # It is now safe to remove the current scene + current_scene.free() - # Load the new scene. - var s = ResourceLoader.load(path) + # Load the new scene. + var s = ResourceLoader.load(path) - # Instance the new scene. - current_scene = s.instance() + # Instance the new scene. + current_scene = s.instance() - # Add it to the active scene, as child of root. - get_tree().root.add_child(current_scene) + # Add it to the active scene, as child of root. + get_tree().root.add_child(current_scene) - # Optionally, to make it compatible with the SceneTree.change_scene() API. - get_tree().current_scene = current_scene + # Optionally, to make it compatible with the SceneTree.change_scene() API. + get_tree().current_scene = current_scene ``` Using `Object.call_deferred()`, @@ -195,10 +195,10 @@ Finally, we need to fill the empty callback functions in the two scenes: gdscript GDScript ``` - # Add to 'Scene1.gd'. +# Add to 'Scene1.gd'. - func _on_Button_pressed(): - Global.goto_scene("res://Scene2.tscn") +func _on_Button_pressed(): + Global.goto_scene("res://Scene2.tscn") ``` and @@ -206,10 +206,10 @@ and gdscript GDScript ``` - # Add to 'Scene2.gd'. +# Add to 'Scene2.gd'. - func _on_Button_pressed(): - Global.goto_scene("res://Scene1.tscn") +func _on_Button_pressed(): + Global.goto_scene("res://Scene1.tscn") ``` Run the project and test that you can switch between scenes by pressing diff --git a/03_usage/15_scripting/14_scene_tree.md b/03_usage/15_scripting/14_scene_tree.md index 6454853..806c995 100644 --- a/03_usage/15_scripting/14_scene_tree.md +++ b/03_usage/15_scripting/14_scene_tree.md @@ -64,8 +64,8 @@ two different ways: gdscript GDScript ``` - get_tree().get_root() # Access via scene main loop. - get_node("/root") # Access via absolute path. +get_tree().get_root() # Access via scene main loop. +get_node("/root") # Access via absolute path. ``` This node contains the main viewport. Anything that is a child of a @@ -127,8 +127,8 @@ function: gdscript GDScript ``` - func _my_level_was_completed(): - get_tree().change_scene("res://levels/level2.tscn") +func _my_level_was_completed(): + get_tree().change_scene("res://levels/level2.tscn") ``` Rather than using file paths, one can also use ready-made @@ -139,10 +139,10 @@ function gdscript GDScript ``` - var next_scene = preload("res://levels/level2.tscn") +var next_scene = preload("res://levels/level2.tscn") - func _my_level_was_completed(): - get_tree().change_scene_to(next_scene) +func _my_level_was_completed(): + get_tree().change_scene_to(next_scene) ``` These are quick and useful ways to switch scenes but have the drawback diff --git a/03_usage/15_scripting/15_scene_unique_nodes.md b/03_usage/15_scripting/15_scene_unique_nodes.md index 085b215..457d0dd 100644 --- a/03_usage/15_scripting/15_scene_unique_nodes.md +++ b/03_usage/15_scripting/15_scene_unique_nodes.md @@ -31,5 +31,5 @@ name in the path for `get_node()`. For example: gdscript GDScript ``` - get_node("%RedButton").text = "Hello" +get_node("%RedButton").text = "Hello" ``` diff --git a/03_usage/15_scripting/gdscript/01_gdscript_basics.md b/03_usage/15_scripting/gdscript/01_gdscript_basics.md index e9726f5..7009063 100644 --- a/03_usage/15_scripting/gdscript/01_gdscript_basics.md +++ b/03_usage/15_scripting/gdscript/01_gdscript_basics.md @@ -26,83 +26,83 @@ here's a simple example of how GDScript looks. ``` - # A file is a class! +# A file is a class! - # Inheritance +# Inheritance - extends BaseClass +extends BaseClass - # (optional) class definition with a custom icon +# (optional) class definition with a custom icon - class_name MyClass, "res://path/to/optional/icon.svg" +class_name MyClass, "res://path/to/optional/icon.svg" - # Member variables +# Member variables - var a = 5 - var s = "Hello" - var arr = [1, 2, 3] - var dict = {"key": "value", 2: 3} - var typed_var: int - var inferred_type := "String" +var a = 5 +var s = "Hello" +var arr = [1, 2, 3] +var dict = {"key": "value", 2: 3} +var typed_var: int +var inferred_type := "String" - # Constants +# Constants - const ANSWER = 42 - const THE_NAME = "Charly" +const ANSWER = 42 +const THE_NAME = "Charly" - # Enums +# Enums - enum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY} - enum Named {THING_1, THING_2, ANOTHER_THING = -1} +enum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY} +enum Named {THING_1, THING_2, ANOTHER_THING = -1} - # Built-in vector types +# Built-in vector types - var v2 = Vector2(1, 2) - var v3 = Vector3(1, 2, 3) +var v2 = Vector2(1, 2) +var v3 = Vector3(1, 2, 3) - # Function +# Function - func some_function(param1, param2): - var local_var = 5 +func some_function(param1, param2): + var local_var = 5 - if param1 < local_var: - print(param1) - elif param2 > 5: - print(param2) - else: - print("Fail!") + if param1 < local_var: + print(param1) + elif param2 > 5: + print(param2) + else: + print("Fail!") - for i in range(20): - print(i) + for i in range(20): + print(i) - while param2 != 0: - param2 -= 1 + while param2 != 0: + param2 -= 1 - var local_var2 = param1 + 3 - return local_var2 + var local_var2 = param1 + 3 + return local_var2 - # Functions override functions with the same name on the base/parent class. - # If you still want to call them, use '.' (like 'super' in other languages). +# Functions override functions with the same name on the base/parent class. +# If you still want to call them, use '.' (like 'super' in other languages). - func something(p1, p2): - .something(p1, p2) +func something(p1, p2): + .something(p1, p2) - # Inner class +# Inner class - class Something: - var a = 10 +class Something: + var a = 10 - # Constructor +# Constructor - func _init(): - print("Constructed!") - var lv = Something.new() - print(lv.a) +func _init(): + print("Constructed!") + var lv = Something.new() + print(lv.a) ``` If you have previous experience with statically typed languages such as @@ -237,10 +237,10 @@ Integers and floats can have their numbers separated with `_` to make them more The following ways to write numbers are all valid ``` - 12_345_678 # Equal to 12345678. - 3.141_592_7 # Equal to 3.1415927. - 0x8080_0000_ffff # Equal to 0x80800000ffff. - 0b11_00_11_00 # Equal to 0b11001100. +12_345_678 # Equal to 12345678. +3.141_592_7 # Equal to 3.1415927. +0x8080_0000_ffff # Equal to 0x80800000ffff. +0b11_00_11_00 # Equal to 0b11001100. ``` ### Comments @@ -250,7 +250,7 @@ considered a comment. ``` - # This is a comment. +# This is a comment. ``` @@ -390,13 +390,13 @@ Negative indices count from the end. ``` - var arr = [] - arr = [1, 2, 3] - var b = arr[1] # This is 2. - var c = arr[arr.size() - 1] # This is 3. - var d = arr[-1] # Same as the previous line, but shorter. - arr[0] = "Hi!" # Replacing value 1 with "Hi!". - arr.append(4) # Array is now ["Hi!", 2, 3, 4]. +var arr = [] +arr = [1, 2, 3] +var b = arr[1] # This is 2. +var c = arr[arr.size() - 1] # This is 3. +var d = arr[-1] # Same as the previous line, but shorter. +arr[0] = "Hi!" # Replacing value 1 with "Hi!". +arr.append(4) # Array is now ["Hi!", 2, 3, 4]. ``` GDScript arrays are allocated linearly in memory for speed. @@ -420,14 +420,14 @@ Associative container which contains values referenced by unique keys. ``` - var d = {4: 5, "A key": "A value", 28: [1, 2, 3]} - d["Hi!"] = 0 - d = { - 22: "value", - "some_key": 2, - "other_key": [2, 3, 4], - "more_key": "Hello" - } +var d = {4: 5, "A key": "A value", 28: [1, 2, 3]} +d["Hi!"] = 0 +d = { + 22: "value", + "some_key": 2, + "other_key": [2, 3, 4], + "more_key": "Hello" +} ``` Lua-style table syntax is also supported. Lua-style uses `=` instead of `:` @@ -437,28 +437,28 @@ identifier). ``` - var d = { - test22 = "value", - some_key = 2, - other_key = [2, 3, 4], - more_key = "Hello" - } +var d = { + test22 = "value", + some_key = 2, + other_key = [2, 3, 4], + more_key = "Hello" +} ``` To add a key to an existing dictionary, access it like an existing key and assign to it ``` - var d = {} # Create an empty Dictionary. - d.waiting = 14 # Add String "waiting" as a key and assign the value 14 to it. - d[4] = "hello" # Add integer 4 as a key and assign the String "hello" as its value. - d["Pandemonium"] = 3.01 # Add String "Pandemonium" as a key and assign the value 3.01 to it. +var d = {} # Create an empty Dictionary. +d.waiting = 14 # Add String "waiting" as a key and assign the value 14 to it. +d[4] = "hello" # Add integer 4 as a key and assign the String "hello" as its value. +d["Pandemonium"] = 3.01 # Add String "Pandemonium" as a key and assign the value 3.01 to it. - var test = 4 - # Prints "hello" by indexing the dictionary with a dynamic key. - # This is not the same as `d.test`. The bracket syntax equivalent to - # `d.test` is `d["test"]`. - print(d[test]) +var test = 4 +# Prints "hello" by indexing the dictionary with a dynamic key. +# This is not the same as `d.test`. The bracket syntax equivalent to +# `d.test` is `d["test"]`. +print(d[test]) ``` Note: @@ -480,10 +480,10 @@ value upon initialization. ``` - var a # Data type is 'null' by default. - var b = 5 - var c = 3.8 - var d = b + c # Variables are always initialized in order. +var a # Data type is 'null' by default. +var b = 5 +var c = 3.8 +var d = b + c # Variables are always initialized in order. ``` Variables can optionally have a type specification. When a type is specified, @@ -495,16 +495,16 @@ after the variable name, followed by the type. ``` - var my_vector2: Vector2 - var my_node: Node = Sprite.new() +var my_vector2: Vector2 +var my_node: Node = Sprite.new() ``` If the variable is initialized within the declaration, the type can be inferred, so it's possible to omit the type name ``` - var my_vector2 := Vector2() # 'my_vector2' is of type 'Vector2'. - var my_node := Sprite.new() # 'my_node' is of type 'Sprite'. +var my_vector2 := Vector2() # 'my_vector2' is of type 'Vector2'. +var my_node := Sprite.new() # 'my_node' is of type 'Sprite'. ``` Type inference is only possible if the assigned value has a defined type, otherwise @@ -529,16 +529,16 @@ same type or a subtype of the cast type. ``` - var my_node2D: Node2D - my_node2D = $Sprite as Node2D # Works since Sprite is a subtype of Node2D. +var my_node2D: Node2D +my_node2D = $Sprite as Node2D # Works since Sprite is a subtype of Node2D. ``` If the value is not a subtype, the casting operation will result in a `null` value. ``` - var my_node2D: Node2D - my_node2D = $Button as Node2D # Results in 'null' since a Button is not a subtype of Node2D. +var my_node2D: Node2D +my_node2D = $Button as Node2D # Results in 'null' since a Button is not a subtype of Node2D. ``` For built-in types, they will be forcibly converted if possible, otherwise the @@ -546,20 +546,20 @@ engine will raise an error. ``` - var my_int: int - my_int = "123" as int # The string can be converted to int. - my_int = Vector2() as int # A Vector2 can't be converted to int, this will cause an error. +var my_int: int +my_int = "123" as int # The string can be converted to int. +my_int = Vector2() as int # A Vector2 can't be converted to int, this will cause an error. ``` Casting is also useful to have better type-safe variables when interacting with the scene tree ``` - # Will infer the variable to be of type Sprite. - var my_sprite := $Character as Sprite +# Will infer the variable to be of type Sprite. +var my_sprite := $Character as Sprite - # Will fail if $AnimPlayer is not an AnimationPlayer, even if it has the method 'play()'. - ($AnimPlayer as AnimationPlayer).play("walk") +# Will fail if $AnimPlayer is not an AnimationPlayer, even if it has the method 'play()'. +($AnimPlayer as AnimationPlayer).play("walk") ``` ### Constants @@ -573,22 +573,22 @@ We recommend using constants whenever a value is not meant to change. ``` - const A = 5 - const B = Vector2(20, 20) - const C = 10 + 20 # Constant expression. - const D = Vector2(20, 30).x # Constant expression: 20. - const E = [1, 2, 3, 4][0] # Constant expression: 1. - const F = sin(20) # 'sin()' can be used in constant expressions. - const G = x + 20 # Invalid; this is not a constant expression! - const H = A + 20 # Constant expression: 25 (`A` is a constant). +const A = 5 +const B = Vector2(20, 20) +const C = 10 + 20 # Constant expression. +const D = Vector2(20, 30).x # Constant expression: 20. +const E = [1, 2, 3, 4][0] # Constant expression: 1. +const F = sin(20) # 'sin()' can be used in constant expressions. +const G = x + 20 # Invalid; this is not a constant expression! +const H = A + 20 # Constant expression: 25 (`A` is a constant). ``` Although the type of constants is inferred from the assigned value, it's also possible to add explicit type specification ``` - const A: int = 5 - const B: Vector2 = Vector2() +const A: int = 5 +const B: Vector2 = Vector2() ``` Assigning a value of an incompatible type will raise an error. @@ -614,17 +614,17 @@ dictionary of that name. ``` - enum {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT} - # Is the same as: - const TILE_BRICK = 0 - const TILE_FLOOR = 1 - const TILE_SPIKE = 2 - const TILE_TELEPORT = 3 +enum {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT} +# Is the same as: +const TILE_BRICK = 0 +const TILE_FLOOR = 1 +const TILE_SPIKE = 2 +const TILE_TELEPORT = 3 - enum State {STATE_IDLE, STATE_JUMP = 5, STATE_SHOOT} - # Is the same as: - const State = {STATE_IDLE = 0, STATE_JUMP = 5, STATE_SHOOT = 6} - # Access values with State.STATE_IDLE, etc. +enum State {STATE_IDLE, STATE_JUMP = 5, STATE_SHOOT} +# Is the same as: +const State = {STATE_IDLE = 0, STATE_JUMP = 5, STATE_SHOOT = 6} +# Access values with State.STATE_IDLE, etc. ``` @@ -638,10 +638,10 @@ argument, unlike Python). ``` - func my_function(a, b): - print(a) - print(b) - return a + b # Return is optional; without it 'null' is returned. +func my_function(a, b): + print(a) + print(b) + return a + b # Return is optional; without it 'null' is returned. ``` A function can `return` at any point. The default return value is `null`. @@ -663,8 +663,8 @@ The return type of the function can be specified after the arguments list using the arrow token (`- )`) ``` - func my_int_function() -> int: - return 0 +func my_int_function() -> int: + return 0 ``` Functions that have a return type **must** return a proper value. Setting the @@ -673,8 +673,8 @@ return early with the `return` keyword, but they can't return any value. ``` - func void_function() -> void: - return # Can't return a value +func void_function() -> void: + return # Can't return a value ``` Note: @@ -697,13 +697,13 @@ pass it to another function as an argument) one must use the `call` or `funcref` helpers ``` - # Call a function by name in one step. - my_node.call("my_function", args) +# Call a function by name in one step. +my_node.call("my_function", args) - # Store a function reference. - var my_func = funcref(my_node, "my_function") - # Call stored function reference. - my_func.call_func(args) +# Store a function reference. +var my_func = funcref(my_node, "my_function") +# Call stored function reference. +my_func.call_func(args) ``` @@ -714,8 +714,8 @@ access to the instance member variables or `self`. This is mainly useful to make libraries of helper functions ``` - static func sum2(a, b): - return a + b +static func sum2(a, b): + return a + b ``` @@ -734,62 +734,62 @@ nature of the tab-based indentation, `elif` can be used instead of ``` - if [expression]: - statement(s) - elif [expression]: - statement(s) - else: - statement(s) +if [expression]: + statement(s) +elif [expression]: + statement(s) +else: + statement(s) ``` Short statements can be written on the same line as the condition ``` - if 1 + 1 == 2: return 2 + 2 - else: - var x = 3 + 3 - return x +if 1 + 1 == 2: return 2 + 2 +else: + var x = 3 + 3 + return x ``` Sometimes, you might want to assign a different initial value based on a boolean expression. In this case, ternary-if expressions come in handy ``` - var x = [value] if [expression] else [value] - y += 3 if y < 10 else -1 +var x = [value] if [expression] else [value] +y += 3 if y < 10 else -1 ``` Ternary-if expressions can be nested to handle more than 2 cases. When nesting ternary-if expressions, it is recommended to wrap the complete expression over multiple lines to preserve readability ``` - var count = 0 +var count = 0 - var fruit = ( - "apple" if count == 2 - else "pear" if count == 1 - else "banana" if count == 0 - else "orange" - ) - print(fruit) # banana +var fruit = ( + "apple" if count == 2 + else "pear" if count == 1 + else "banana" if count == 0 + else "orange" +) +print(fruit) # banana - # Alternative syntax with backslashes instead of parentheses (for multi-line expressions). - # Less lines required, but harder to refactor. - var fruit_alt = \ - "apple" if count == 2 \ - else "pear" if count == 1 \ - else "banana" if count == 0 \ - else "orange" - print(fruit_alt) # banana +# Alternative syntax with backslashes instead of parentheses (for multi-line expressions). +# Less lines required, but harder to refactor. +var fruit_alt = \ + "apple" if count == 2 \ + else "pear" if count == 1 \ + else "banana" if count == 0 \ + else "orange" +print(fruit_alt) # banana ``` You may also wish to check if a value is contained within something. You can use an `if` statement combined with the `in` operator to accomplish this ``` - # Check if a letter is in a string. - var text = "abc" - if 'b' in text: print("The string contains b") +# Check if a letter is in a string. +var text = "abc" +if 'b' in text: print("The string contains b") - # Check if a variable is contained within a node. - if "varName" in get_parent(): print("varName is defined in parent!") +# Check if a variable is contained within a node. +if "varName" in get_parent(): print("varName is defined in parent!") ``` #### while @@ -799,8 +799,8 @@ using `break` or continued using `continue`: ``` - while [expression]: - statement(s) +while [expression]: + statement(s) ``` #### for @@ -812,30 +812,30 @@ in the loop variable. ``` - for x in [5, 7, 11]: - statement # Loop iterates 3 times with 'x' as 5, then 7 and finally 11. +for x in [5, 7, 11]: + statement # Loop iterates 3 times with 'x' as 5, then 7 and finally 11. - var dict = {"a": 0, "b": 1, "c": 2} - for i in dict: - print(dict[i]) # Prints 0, then 1, then 2. +var dict = {"a": 0, "b": 1, "c": 2} +for i in dict: + print(dict[i]) # Prints 0, then 1, then 2. - for i in range(3): - statement # Similar to [0, 1, 2] but does not allocate an array. +for i in range(3): + statement # Similar to [0, 1, 2] but does not allocate an array. - for i in range(1, 3): - statement # Similar to [1, 2] but does not allocate an array. +for i in range(1, 3): + statement # Similar to [1, 2] but does not allocate an array. - for i in range(2, 8, 2): - statement # Similar to [2, 4, 6] but does not allocate an array. +for i in range(2, 8, 2): + statement # Similar to [2, 4, 6] but does not allocate an array. - for c in "Hello": - print(c) # Iterate through all characters in a String, print every letter on new line. +for c in "Hello": + print(c) # Iterate through all characters in a String, print every letter on new line. - for i in 3: - statement # Similar to range(3) +for i in 3: + statement # Similar to range(3) - for i in 2.2: - statement # Similar to range(ceil(2.2)) +for i in 2.2: + statement # Similar to range(ceil(2.2)) ``` #### match @@ -845,13 +845,13 @@ It's the equivalent of the `switch` statement found in many other languages, but Basic syntax ``` - match [expression]: - [pattern](s): - [block] - [pattern](s): - [block] - [pattern](s): - [block] +match [expression]: + [pattern](s): + [block] + [pattern](s): + [block] + [pattern](s): + [block] ``` @@ -874,26 +874,26 @@ There are 6 pattern types: - Constant pattern Constant primitives, like numbers and strings ``` - match x: - 1: - print("We are number one!") - 2: - print("Two are better than one!") - "test": - print("Oh snap! It's a string!") +match x: + 1: + print("We are number one!") + 2: + print("Two are better than one!") + "test": + print("Oh snap! It's a string!") ``` - Variable pattern Matches the contents of a variable/enum ``` - match typeof(x): - TYPE_REAL: - print("float") - TYPE_STRING: - print("text") - TYPE_ARRAY: - print("array") +match typeof(x): + TYPE_REAL: + print("float") + TYPE_STRING: + print("text") + TYPE_ARRAY: + print("array") ``` @@ -902,13 +902,13 @@ There are 6 pattern types: It can be used as the equivalent of the `default` in a `switch` statement in other languages ``` - match x: - 1: - print("It's one!") - 2: - print("It's one times two!") - _: - print("It's not 1 or 2. I don't care to be honest.") +match x: + 1: + print("It's one!") + 2: + print("It's one times two!") + _: + print("It's not 1 or 2. I don't care to be honest.") ``` @@ -916,13 +916,13 @@ There are 6 pattern types: A binding pattern introduces a new variable. Like the wildcard pattern, it matches everything - and also gives that value a name. It's especially useful in array and dictionary patterns ``` - match x: - 1: - print("It's one!") - 2: - print("It's one times two!") - var new_var: - print("It's not 1 or 2, it's ", new_var) +match x: + 1: + print("It's one!") + 2: + print("It's one times two!") + var new_var: + print("It's not 1 or 2, it's ", new_var) ``` @@ -937,15 +937,15 @@ There are 6 pattern types: ``` - match x: - []: - print("Empty array") - [1, 3, "test", null]: - print("Very specific array") - [var start, _, "test"]: - print("First element is ", start, ", and the last is \"test\"") - [42, ..]: - print("Open ended array") +match x: + []: + print("Empty array") + [1, 3, "test", null]: + print("Very specific array") + [var start, _, "test"]: + print("First element is ", start, ", and the last is \"test\"") + [42, ..]: + print("Open ended array") ``` - Dictionary pattern @@ -963,17 +963,17 @@ There are 6 pattern types: ``` - match x: - {}: - print("Empty dict") - {"name": "Dennis"}: - print("The name is Dennis") - {"name": "Dennis", "age": var age}: - print("Dennis is ", age, " years old.") - {"name", "age"}: - print("Has a name and an age, but it's not Dennis :(") - {"key": "pandemoniumisawesome", ..}: - print("I only checked for one entry and ignored the rest") +match x: + {}: + print("Empty dict") + {"name": "Dennis"}: + print("The name is Dennis") + {"name": "Dennis", "age": var age}: + print("Dennis is ", age, " years old.") + {"name", "age"}: + print("Has a name and an age, but it's not Dennis :(") + {"key": "pandemoniumisawesome", ..}: + print("I only checked for one entry and ignored the rest") ``` - Multiple patterns @@ -981,11 +981,11 @@ There are 6 pattern types: ``` - match x: - 1, 2, 3: - print("It's 1 - 3") - "Sword", "Splash potion", "Fist": - print("Yep, you've taken damage") +match x: + 1, 2, 3: + print("It's 1 - 3") + "Sword", "Splash potion", "Fist": + print("Yep, you've taken damage") ``` ### Classes @@ -994,14 +994,14 @@ By default, all script files are unnamed classes. In this case, you can only reference them using the file's path, using either a relative or an absolute path. For example, if you name a script file `character.gd` ``` - # Inherit from 'Character.gd'. +# Inherit from 'Character.gd'. - extends "res://path/to/character.gd" +extends "res://path/to/character.gd" - # Load character.gd and create a new node instance from it. +# Load character.gd and create a new node instance from it. - var Character = load("res://path/to/character.gd") - var character_node = Character.new() +var Character = load("res://path/to/character.gd") +var character_node = Character.new() ``` @@ -1013,10 +1013,10 @@ editor. For that, you use the `name` keyword. You can optionally add a comma followed by a path to an image, to use it as an icon. Your class will then appear with its new icon in the editor ``` - # Item.gd +# Item.gd - extends Node - class_name Item, "res://interface/icons/item.png)" +extends Node +class_name Item, "res://interface/icons/item.png)" ``` ![](img/class_name_editor_register_example.png) @@ -1033,22 +1033,22 @@ Here's a class file example: ``` - # Saved as a file named 'character.gd'. +# Saved as a file named 'character.gd'. - class_name Character +class_name Character - var health = 5 +var health = 5 - func print_health(): - print(health) +func print_health(): + print(health) - func print_this_script_three_times(): - print(get_script()) - print(ResourceLoader.load("res://character.gd")) - print(Character) +func print_this_script_three_times(): + print(get_script()) + print(ResourceLoader.load("res://character.gd")) + print(Character) ``` @@ -1073,34 +1073,34 @@ Multiple inheritance is not allowed. Inheritance uses the `extends` keyword ``` - # Inherit/extend a globally available class. - extends SomeClass +# Inherit/extend a globally available class. +extends SomeClass - # Inherit/extend a named class file. - extends "somefile.gd" +# Inherit/extend a named class file. +extends "somefile.gd" - # Inherit/extend an inner class in another file. - extends "somefile.gd".SomeInnerClass +# Inherit/extend an inner class in another file. +extends "somefile.gd".SomeInnerClass ``` To check if a given instance inherits from a given class, the `is` keyword can be used ``` - # Cache the enemy class. - const Enemy = preload("enemy.gd") +# Cache the enemy class. +const Enemy = preload("enemy.gd") - # [...] +# [...] - # Use 'is' to check inheritance. - if entity is Enemy: - entity.apply_damage() +# Use 'is' to check inheritance. +if entity is Enemy: + entity.apply_damage() ``` To call a function in a *parent class* (i.e. one `extend`-ed in your current class), prepend `.` to the function name ``` - .base_func(args) +.base_func(args) ``` This is especially useful because functions in extending classes replace @@ -1108,8 +1108,8 @@ functions with the same name in their parent classes. If you still want to call them, you can prefix them with `.` (like the `super` keyword in other languages) ``` - func some_func(x): - .some_func(x) # Calls the same function on the parent class. +func some_func(x): + .some_func(x) # Calls the same function on the parent class. ``` Note: @@ -1130,32 +1130,32 @@ Unlike the call of a regular function, like in the above example with `.some_func`, if the constructor from the inherited class takes arguments, they are passed like this ``` - func _init(args).(parent_args): - pass +func _init(args).(parent_args): + pass ``` This is better explained through examples. Consider this scenario ``` - # State.gd (inherited class) - var entity = null - var message = null +# State.gd (inherited class) +var entity = null +var message = null - func _init(e=null): - entity = e +func _init(e=null): + entity = e - func enter(m): - message = m +func enter(m): + message = m - # Idle.gd (inheriting class) - extends "State.gd" +# Idle.gd (inheriting class) +extends "State.gd" - func _init(e=null, m=null).(e): - # Do something with 'e'. - message = m +func _init(e=null, m=null).(e): + # Do something with 'e'. + message = m ``` There are a few things to keep in mind here: @@ -1170,10 +1170,10 @@ There are a few things to keep in mind here: to the `State.gd` parent class, even if it does nothing. This brings us to the fact that you can pass literals in the base constructor as well, not just variables, e.g. ``` - # Idle.gd +# Idle.gd - func _init().(5): - pass +func _init().(5): + pass ``` #### Inner classes @@ -1184,21 +1184,21 @@ function. ``` - # Inside a class file. +# Inside a class file. - # An inner class in this class file. - class SomeInnerClass: - var a = 5 +# An inner class in this class file. +class SomeInnerClass: + var a = 5 - func print_value_of_a(): - print(a) + func print_value_of_a(): + print(a) - # This is the constructor of the class file's main class. - func _init(): - var c = SomeInnerClass.new() - c.print_value_of_a() +# This is the constructor of the class file's main class. +func _init(): + var c = SomeInnerClass.new() + c.print_value_of_a() ``` @@ -1210,16 +1210,16 @@ must be loaded from disk to access them in other classes. This is done using either the `load` or `preload` functions (see below). Instancing of a loaded class resource is done by calling the `new` function on the class object ``` - # Load the class resource when calling load(). - var MyClass = load("myclass.gd") +# Load the class resource when calling load(). +var MyClass = load("myclass.gd") - # Preload the class only once at compile time. - const MyClass = preload("myclass.gd") +# Preload the class only once at compile time. +const MyClass = preload("myclass.gd") - func _init(): - var a = MyClass.new() - a.some_function() +func _init(): + var a = MyClass.new() + a.some_function() ``` ### Exports @@ -1239,7 +1239,7 @@ It is used directly after a variable definition: ``` - var variable = value setget setterfunc, getterfunc +var variable = value setget setterfunc, getterfunc ``` Whenever the value of `variable` is modified by an *external* source @@ -1249,24 +1249,24 @@ with the new value. Vice versa, when `variable` is accessed, the *getter* functi (`getterfunc` above) must `return` the desired value. Below is an example ``` - var my_var setget my_var_set, my_var_get +var my_var setget my_var_set, my_var_get - func my_var_set(new_value): - my_var = new_value +func my_var_set(new_value): + my_var = new_value - func my_var_get(): - return my_var # Getter must return a value. +func my_var_get(): + return my_var # Getter must return a value. ``` Either of the *setter* or *getter* functions can be omitted ``` - # Only a setter. - var my_var = 5 setget my_var_set - # Only a getter (note the comma). - var my_var = 5 setget ,my_var_get +# Only a setter. +var my_var = 5 setget my_var_set +# Only a getter (note the comma). +var my_var = 5 setget ,my_var_get ``` Setters and getters are useful when `exporting variables ( doc_gdscript_exports )` @@ -1277,14 +1277,14 @@ illustration of this: ``` - func _init(): - # Does not trigger setter/getter. - my_integer = 5 - print(my_integer) +func _init(): + # Does not trigger setter/getter. + my_integer = 5 + print(my_integer) - # Does trigger setter/getter. - self.my_integer = 5 - print(self.my_integer) + # Does trigger setter/getter. + self.my_integer = 5 + print(self.my_integer) ``` @@ -1297,12 +1297,12 @@ inside the editor (as long as they don't execute game code or manually avoid doing so). For this, the `tool` keyword exists and must be placed at the top of the file ``` - tool - extends Button +tool +extends Button - func _ready(): - print("Hello") +func _ready(): + print("Hello") ``` @@ -1328,17 +1328,17 @@ provided for creating weak references. Here is an example: ``` - extends Node +extends Node - var my_node_ref +var my_node_ref - func _ready(): - my_node_ref = weakref(get_node("MyNode")) +func _ready(): + my_node_ref = weakref(get_node("MyNode")) - func _this_is_called_later(): - var my_node = my_node_ref.get_ref() - if my_node: - my_node.do_something() +func _this_is_called_later(): + var my_node = my_node_ref.get_ref() + if my_node: + my_node.do_something() ``` Alternatively, when not using references, the @@ -1354,11 +1354,11 @@ to. To create custom signals for a class, use the `signal` keyword. ``` - extends Node +extends Node - # A signal named health_depleted. - signal health_depleted +# A signal named health_depleted. +signal health_depleted ``` Note: @@ -1379,15 +1379,15 @@ In the example below, we connect the `health_depleted` signal from a signal, the game node's `on_Character_health_depleted` is called ``` - # Game.gd +# Game.gd - func _ready(): - var character_node = get_node('Character') - character_node.connect("health_depleted", self, "_on_Character_health_depleted") +func _ready(): + var character_node = get_node('Character') + character_node.connect("health_depleted", self, "_on_Character_health_depleted") - func _on_Character_health_depleted(): - get_tree().reload_current_scene() +func _on_Character_health_depleted(): + get_tree().reload_current_scene() ``` You can emit as many arguments as you want along with a signal. @@ -1402,40 +1402,40 @@ a `Game` node higher up our scene tree, we connect it to the `Lifebar` using the `Object.connect()` method ``` - # Character.gd +# Character.gd - ... - signal health_changed +... +signal health_changed - func take_damage(amount): - var old_health = health - health -= amount +func take_damage(amount): + var old_health = health + health -= amount - # We emit the health_changed signal every time the - # character takes damage. - emit_signal("health_changed", old_health, health) - ... + # We emit the health_changed signal every time the + # character takes damage. + emit_signal("health_changed", old_health, health) +... ``` ``` - # Lifebar.gd +# Lifebar.gd - # Here, we define a function to use as a callback when the - # character's health_changed signal is emitted. +# Here, we define a function to use as a callback when the +# character's health_changed signal is emitted. - ... - func _on_Character_health_changed(old_value, new_value): - if old_value > new_value: - progress_bar.modulate = Color.red - else: - progress_bar.modulate = Color.green +... +func _on_Character_health_changed(old_value, new_value): + if old_value > new_value: + progress_bar.modulate = Color.red + else: + progress_bar.modulate = Color.green - # Imagine that `animate` is a user-defined function that animates the - # bar filling up or emptying itself. - progress_bar.animate(old_value, new_value) - ... + # Imagine that `animate` is a user-defined function that animates the + # bar filling up or emptying itself. + progress_bar.animate(old_value, new_value) +... ``` Note: @@ -1450,13 +1450,13 @@ node in this case. ``` - # Game.gd +# Game.gd - func _ready(): - var character_node = get_node('Character') - var lifebar_node = get_node('UserInterface/Lifebar') +func _ready(): + var character_node = get_node('Character') + var lifebar_node = get_node('UserInterface/Lifebar') - character_node.connect("health_changed", lifebar_node, "_on_Character_health_changed") + character_node.connect("health_changed", lifebar_node, "_on_Character_health_changed") ``` This allows the `Lifebar` to react to health changes without coupling it to @@ -1466,8 +1466,8 @@ You can write optional argument names in parentheses after the signal's definition ``` - # Defining a signal that forwards two arguments. - signal health_changed(old_value, new_value) +# Defining a signal that forwards two arguments. +signal health_changed(old_value, new_value) ``` These arguments show up in the editor's node dock, and Pandemonium can use them to @@ -1492,26 +1492,26 @@ damage. So when we connect the signal to the in-game console, we can add the character's name in the binds array argument ``` - # Game.gd +# Game.gd - func _ready(): - var character_node = get_node('Character') - var battle_log_node = get_node('UserInterface/BattleLog') +func _ready(): + var character_node = get_node('Character') + var battle_log_node = get_node('UserInterface/BattleLog') - character_node.connect("health_changed", battle_log_node, "_on_Character_health_changed", [character_node.name]) + character_node.connect("health_changed", battle_log_node, "_on_Character_health_changed", [character_node.name]) ``` Our `BattleLog` node receives each element in the binds array as an extra argument ``` - # BattleLog.gd +# BattleLog.gd - func _on_Character_health_changed(old_value, new_value, character_name): - if not new_value <= old_value: - return +func _on_Character_health_changed(old_value, new_value, character_name): + if not new_value <= old_value: + return - var damage = old_value - new_value - label.text += character_name + " took " + str(damage) + " damage." + var damage = old_value - new_value + label.text += character_name + " took " + str(damage) + " damage." ``` @@ -1526,66 +1526,66 @@ function returns. Once resumed, the state object becomes invalid. Here is an example ``` - func my_func(): - print("Hello") - yield() - print("world") +func my_func(): + print("Hello") + yield() + print("world") - func _ready(): - var y = my_func() - # Function state saved in 'y'. - print("my dear") - y.resume() - # 'y' resumed and is now an invalid state. +func _ready(): + var y = my_func() + # Function state saved in 'y'. + print("my dear") + y.resume() + # 'y' resumed and is now an invalid state. ``` Will print ``` - Hello - my dear - world +Hello +my dear +world ``` It is also possible to pass values between `yield()` and `resume()`, for example ``` - func my_func(): - print("Hello") - print(yield()) - return "cheers!" +func my_func(): + print("Hello") + print(yield()) + return "cheers!" - func _ready(): - var y = my_func() - # Function state saved in 'y'. - print(y.resume("world")) - # 'y' resumed and is now an invalid state. +func _ready(): + var y = my_func() + # Function state saved in 'y'. + print(y.resume("world")) + # 'y' resumed and is now an invalid state. ``` Will print ``` - Hello - world - cheers! +Hello +world +cheers! ``` Remember to save the new function state, when using multiple `yield`\s ``` - func co_func(): - for i in range(1, 5): - print("Turn %d" % i) - yield(); +func co_func(): + for i in range(1, 5): + print("Turn %d" % i) + yield(); - func _ready(): - var co = co_func(); - while co is GDScriptFunctionState && co.is_valid(): - co = co.resume(); +func _ready(): + var co = co_func(); + while co is GDScriptFunctionState && co.is_valid(): + co = co.resume(); ``` @@ -1596,28 +1596,28 @@ The real strength of using `yield` is when combined with signals. signal is received, execution will recommence. Here are some examples ``` - # Resume execution the next frame. - yield(get_tree(), "idle_frame") +# Resume execution the next frame. +yield(get_tree(), "idle_frame") - # Resume execution when animation is done playing. - yield(get_node("AnimationPlayer"), "animation_finished") +# Resume execution when animation is done playing. +yield(get_node("AnimationPlayer"), "animation_finished") - # Wait 5 seconds, then resume execution. - yield(get_tree().create_timer(5.0), "timeout") +# Wait 5 seconds, then resume execution. +yield(get_tree().create_timer(5.0), "timeout") ``` Coroutines themselves use the `completed` signal when they transition into an invalid state, for example ``` - func my_func(): - yield(button_func(), "completed") - print("All buttons were pressed, hurray!") +func my_func(): + yield(button_func(), "completed") + print("All buttons were pressed, hurray!") - func button_func(): - yield($Button0, "pressed") - yield($Button1, "pressed") +func button_func(): + yield($Button0, "pressed") + yield($Button1, "pressed") ``` `my_func` will only continue execution once both buttons have been pressed. @@ -1626,28 +1626,28 @@ You can also get the signal's argument once it's emitted by an object: ``` - # Wait for when any node is added to the scene tree. - var node = yield(get_tree(), "node_added") +# Wait for when any node is added to the scene tree. +var node = yield(get_tree(), "node_added") ``` If there is more than one argument, `yield` returns an array containing the arguments ``` - signal done(input, processed) +signal done(input, processed) - func process_input(input): - print("Processing initialized") - yield(get_tree(), "idle_frame") - print("Waiting") - yield(get_tree(), "idle_frame") - emit_signal("done", input, "Processed " + input) +func process_input(input): + print("Processing initialized") + yield(get_tree(), "idle_frame") + print("Waiting") + yield(get_tree(), "idle_frame") + emit_signal("done", input, "Processed " + input) - func _ready(): - process_input("Test") # Prints: Processing initialized - var data = yield(self, "done") # Prints: waiting - print(data[1]) # Prints: Processed Test +func _ready(): + process_input("Test") # Prints: Processing initialized + var data = yield(self, "done") # Prints: waiting + print(data[1]) # Prints: Processed Test ``` If you're unsure whether a function may yield or not, or whether it may yield @@ -1655,22 +1655,22 @@ multiple times, you can yield to the `completed` signal conditionally: ``` - func generate(): - var result = rand_range(-1.0, 1.0) +func generate(): + var result = rand_range(-1.0, 1.0) - if result < 0.0: - yield(get_tree(), "idle_frame") + if result < 0.0: + yield(get_tree(), "idle_frame") - return result + return result - func make(): - var result = generate() +func make(): + var result = generate() - if result is GDScriptFunctionState: # Still working. - result = yield(result, "completed") + if result is GDScriptFunctionState: # Still working. + result = yield(result, "completed") - return result + return result ``` This ensures that the function returns whatever it was supposed to return @@ -1689,11 +1689,11 @@ be obtained when a call to `Node._ready()` is made. ``` - var my_label +var my_label - func _ready(): - my_label = get_node("MyLabel") +func _ready(): + my_label = get_node("MyLabel") ``` This can get a little cumbersome, especially when nodes and external @@ -1702,7 +1702,7 @@ defers initialization of a member variable until `ready()` is called. It can replace the above code with a single line ``` - onready var my_label = get_node("MyLabel") +onready var my_label = get_node("MyLabel") ``` ### Assert keyword @@ -1716,8 +1716,8 @@ depending on whether the project is run in a debug build. ``` - # Check that 'i' is 0. If 'i' is not 0, an assertion error will occur. - assert(i == 0) +# Check that 'i' is 0. If 'i' is not 0, an assertion error will occur. +assert(i == 0) ``` When running a project from the editor, the project will be paused if an diff --git a/03_usage/15_scripting/gdscript/02_gdscript_advanced.md b/03_usage/15_scripting/gdscript/02_gdscript_advanced.md index dfd7833..80a3c8a 100644 --- a/03_usage/15_scripting/gdscript/02_gdscript_advanced.md +++ b/03_usage/15_scripting/gdscript/02_gdscript_advanced.md @@ -53,17 +53,17 @@ assignment. Example: Static: ``` - int a; // Value uninitialized. - a = 5; // This is valid. - a = "Hi!"; // This is invalid. +int a; // Value uninitialized. +a = 5; // This is valid. +a = "Hi!"; // This is invalid. ``` Dynamic: ``` - var a # 'null' by default. - a = 5 # Valid, 'a' becomes an integer. - a = "Hi!" # Valid, 'a' changed to a string. +var a # 'null' by default. +a = 5 # Valid, 'a' becomes an integer. +a = "Hi!" # Valid, 'a' changed to a string. ``` ### As function arguments: @@ -74,27 +74,27 @@ different arguments, for example: Static: ``` - void print_value(int value) { +void print_value(int value) { - printf("value is %i\n", value); - } + printf("value is %i\n", value); +} - [..] +[..] - print_value(55); // Valid. - print_value("Hello"); // Invalid. +print_value(55); // Valid. +print_value("Hello"); // Invalid. ``` Dynamic: ``` - func print_value(value): - print(value) +func print_value(value): + print(value) - [..] +[..] - print_value(55) # Valid. - print_value("Hello") # Valid. +print_value(55) # Valid. +print_value("Hello") # Valid. ``` ### Pointers & referencing: @@ -113,47 +113,47 @@ too. Some Examples: - C++: ``` - void use_class(SomeClass *instance) { +void use_class(SomeClass *instance) { - instance->use(); - } + instance->use(); +} - void do_something() { +void do_something() { - SomeClass *instance = new SomeClass; // Created as pointer. - use_class(instance); // Passed as pointer. - delete instance; // Otherwise it will leak memory. - } + SomeClass *instance = new SomeClass; // Created as pointer. + use_class(instance); // Passed as pointer. + delete instance; // Otherwise it will leak memory. +} ``` - Java: ``` - @Override - public final void use_class(SomeClass instance) { +@Override +public final void use_class(SomeClass instance) { - instance.use(); - } + instance.use(); +} - public final void do_something() { +public final void do_something() { - SomeClass instance = new SomeClass(); // Created as reference. - use_class(instance); // Passed as reference. - // Garbage collector will get rid of it when not in - // use and freeze your game randomly for a second. - } + SomeClass instance = new SomeClass(); // Created as reference. + use_class(instance); // Passed as reference. + // Garbage collector will get rid of it when not in + // use and freeze your game randomly for a second. +} ``` - GDScript: ``` - func use_class(instance): # Does not care about class type - instance.use() # Will work with any class that has a ".use()" method. +func use_class(instance): # Does not care about class type + instance.use() # Will work with any class that has a ".use()" method. - func do_something(): - var instance = SomeClass.new() # Created as reference. - use_class(instance) # Passed as reference. - # Will be unreferenced and deleted. +func do_something(): + var instance = SomeClass.new() # Created as reference. + use_class(instance) # Passed as reference. + # Will be unreferenced and deleted. ``` In GDScript, only base types (int, float, String and PoolArray types) @@ -170,18 +170,18 @@ Note: modify the parameter in a way that is visible from outside the function: ``` - func greet(text): - text = "Hello " + text +func greet(text): + text = "Hello " + text - func _ready(): - # Create a String (passed by value and immutable). - var example = "Pandemonium" +func _ready(): + # Create a String (passed by value and immutable). + var example = "Pandemonium" - # Pass example as a parameter to `greet()`, - # which modifies the parameter and does not return any value. - greet(example) + # Pass example as a parameter to `greet()`, + # which modifies the parameter and does not return any value. + greet(example) - print(example) # Pandemonium + print(example) # Pandemonium ``` A value is **passed by reference** when it is *not* copied every time it's @@ -192,19 +192,19 @@ Note: difficult-to-track bugs if not done carefully: ``` - func greet(text): - text.push_front("Hello") +func greet(text): + text.push_front("Hello") - func _ready(): - # Create an Array (passed by reference and mutable) containing a String, - # instead of a String (passed by value and immutable). - var example = ["Pandemonium"] +func _ready(): + # Create an Array (passed by reference and mutable) containing a String, + # instead of a String (passed by value and immutable). + var example = ["Pandemonium"] - # Pass example as a parameter to `greet()`, - # which modifies the parameter and does not return any value. - greet(example) + # Pass example as a parameter to `greet()`, + # which modifies the parameter and does not return any value. + greet(example) - print(example) # [Hello, Pandemonium] (Array with 2 String elements) + print(example) # [Hello, Pandemonium] (Array with 2 String elements) ``` Compared to passing by value, passing by reference can perform better when @@ -221,53 +221,53 @@ datatypes inside and are always dynamic (can be resized at any time). Compare for example arrays in statically typed languages: ``` - int *array = new int[4]; // Create array. - array[0] = 10; // Initialize manually. - array[1] = 20; // Can't mix types. - array[2] = 40; - array[3] = 60; - // Can't resize. - use_array(array); // Passed as pointer. - delete[] array; // Must be freed. +int *array = new int[4]; // Create array. +array[0] = 10; // Initialize manually. +array[1] = 20; // Can't mix types. +array[2] = 40; +array[3] = 60; +// Can't resize. +use_array(array); // Passed as pointer. +delete[] array; // Must be freed. - // or +// or - std::vector array; - array.resize(4); - array[0] = 10; // Initialize manually. - array[1] = 20; // Can't mix types. - array[2] = 40; - array[3] = 60; - array.resize(3); // Can be resized. - use_array(array); // Passed reference or value. - // Freed when stack ends. +std::vector array; +array.resize(4); +array[0] = 10; // Initialize manually. +array[1] = 20; // Can't mix types. +array[2] = 40; +array[3] = 60; +array.resize(3); // Can be resized. +use_array(array); // Passed reference or value. +// Freed when stack ends. ``` And in GDScript: ``` - var array = [10, "hello", 40, 60] # Simple, and can mix types. - array.resize(3) # Can be resized. - use_array(array) # Passed as reference. - # Freed when no longer in use. +var array = [10, "hello", 40, 60] # Simple, and can mix types. +array.resize(3) # Can be resized. +use_array(array) # Passed as reference. +# Freed when no longer in use. ``` In dynamically typed languages, arrays can also double as other datatypes, such as lists: ``` - var array = [] - array.append(4) - array.append(5) - array.pop_front() +var array = [] +array.append(4) +array.append(5) +array.pop_front() ``` Or unordered sets: ``` - var a = 20 - if a in [10, 20, 30]: - print("We have a winner!") +var a = 20 +if a in [10, 20, 30]: + print("We have a winner!") ``` ## Dictionaries @@ -287,50 +287,50 @@ will go as far as implementing arrays as dictionaries. Example of Dictionary: ``` - var d = {"name": "John", "age": 22} # Simple syntax. - print("Name: ", d["name"], " Age: ", d["age"]) +var d = {"name": "John", "age": 22} # Simple syntax. +print("Name: ", d["name"], " Age: ", d["age"]) ``` Dictionaries are also dynamic, keys can be added or removed at any point at little cost: ``` - d["mother"] = "Rebecca" # Addition. - d["age"] = 11 # Modification. - d.erase("name") # Removal. +d["mother"] = "Rebecca" # Addition. +d["age"] = 11 # Modification. +d.erase("name") # Removal. ``` In most cases, two-dimensional arrays can often be implemented more easily with dictionaries. Here's a simple battleship game example: ``` - # Battleship Game +# Battleship Game - const SHIP = 0 - const SHIP_HIT = 1 - const WATER_HIT = 2 +const SHIP = 0 +const SHIP_HIT = 1 +const WATER_HIT = 2 - var board = {} +var board = {} - func initialize(): - board[Vector2(1, 1)] = SHIP - board[Vector2(1, 2)] = SHIP - board[Vector2(1, 3)] = SHIP +func initialize(): + board[Vector2(1, 1)] = SHIP + board[Vector2(1, 2)] = SHIP + board[Vector2(1, 3)] = SHIP - func missile(pos): - if pos in board: # Something at that position. - if board[pos] == SHIP: # There was a ship! hit it. - board[pos] = SHIP_HIT - else: - print("Already hit here!") # Hey dude you already hit here. - else: # Nothing, mark as water. - board[pos] = WATER_HIT +func missile(pos): + if pos in board: # Something at that position. + if board[pos] == SHIP: # There was a ship! hit it. + board[pos] = SHIP_HIT + else: + print("Already hit here!") # Hey dude you already hit here. + else: # Nothing, mark as water. + board[pos] = WATER_HIT - func game(): - initialize() - missile(Vector2(1, 1)) - missile(Vector2(5, 8)) - missile(Vector2(2, 3)) +func game(): + initialize() + missile(Vector2(1, 1)) + missile(Vector2(5, 8)) + missile(Vector2(2, 3)) ``` Dictionaries can also be used as data markup or quick structures. While @@ -339,22 +339,22 @@ style syntax and indexing, which makes it useful for writing initial states and quick structs: ``` - # Same example, lua-style support. - # This syntax is a lot more readable and usable. - # Like any GDScript identifier, keys written in this form cannot start - # with a digit. +# Same example, lua-style support. +# This syntax is a lot more readable and usable. +# Like any GDScript identifier, keys written in this form cannot start +# with a digit. - var d = { - name = "John", - age = 22 - } +var d = { + name = "John", + age = 22 +} - print("Name: ", d.name, " Age: ", d.age) # Used "." based indexing. +print("Name: ", d.name, " Age: ", d.age) # Used "." based indexing. - # Indexing +# Indexing - d["mother"] = "Rebecca" - d.mother = "Caroline" # This would work too to create a new key. +d["mother"] = "Rebecca" +d.mother = "Caroline" # This would work too to create a new key. ``` ## For & while @@ -362,87 +362,87 @@ states and quick structs: Iterating in some statically typed languages can be quite complex: ``` - const char* strings = new const char*[50]; +const char* strings = new const char*[50]; - [..] +[..] - for (int i = 0; i < 50; i++) { +for (int i = 0; i < 50; i++) { - printf("Value: %s\n", i, strings[i]); - } + printf("Value: %s\n", i, strings[i]); +} - // Even in STL: +// Even in STL: - for (std::list::const_iterator it = strings.begin(); it != strings.end(); it++) { +for (std::list::const_iterator it = strings.begin(); it != strings.end(); it++) { - std::cout << *it << std::endl; - } + std::cout << *it << std::endl; +} ``` This is usually greatly simplified in dynamically typed languages: ``` - for s in strings: - print(s) +for s in strings: + print(s) ``` Container datatypes (arrays and dictionaries) are iterable. Dictionaries allow iterating the keys: ``` - for key in dict: - print(key, " -> ", dict[key]) +for key in dict: + print(key, " -> ", dict[key]) ``` Iterating with indices is also possible: ``` - for i in range(strings.size()): - print(strings[i]) +for i in range(strings.size()): + print(strings[i]) ``` The range() function can take 3 arguments: ``` - range(n) # Will go from 0 to n-1. - range(b, n) # Will go from b to n-1. - range(b, n, s) # Will go from b to n-1, in steps of s. +range(n) # Will go from 0 to n-1. +range(b, n) # Will go from b to n-1. +range(b, n, s) # Will go from b to n-1, in steps of s. ``` Some statically typed programming language examples: ``` - for (int i = 0; i < 10; i++) {} +for (int i = 0; i < 10; i++) {} - for (int i = 5; i < 10; i++) {} +for (int i = 5; i < 10; i++) {} - for (int i = 5; i < 10; i += 2) {} +for (int i = 5; i < 10; i += 2) {} ``` Translate to: ``` - for i in range(10): - pass +for i in range(10): + pass - for i in range(5, 10): - pass +for i in range(5, 10): + pass - for i in range(5, 10, 2): - pass +for i in range(5, 10, 2): + pass ``` And backwards looping is done through a negative counter: ``` - for (int i = 10; i > 0; i--) {} +for (int i = 10; i > 0; i--) {} ``` Becomes: ``` - for i in range(10, 0, -1): - pass +for i in range(10, 0, -1): + pass ``` ## While @@ -450,11 +450,11 @@ Becomes: while() loops are the same everywhere: ``` - var i = 0 +var i = 0 - while i < strings.size(): - print(strings[i]) - i += 1 +while i < strings.size(): + print(strings[i]) + i += 1 ``` ## Custom iterators @@ -464,39 +464,39 @@ needs by overriding the Variant class's `iter_init`, `iter_next`, and `iter_get` functions in your script. An example implementation of a forward iterator follows: ``` - class ForwardIterator: - var start - var current - var end - var increment +class ForwardIterator: + var start + var current + var end + var increment - func _init(start, stop, increment): - self.start = start - self.current = start - self.end = stop - self.increment = increment + func _init(start, stop, increment): + self.start = start + self.current = start + self.end = stop + self.increment = increment - func should_continue(): - return (current < end) + func should_continue(): + return (current < end) - func _iter_init(arg): - current = start - return should_continue() + func _iter_init(arg): + current = start + return should_continue() - func _iter_next(arg): - current += increment - return should_continue() + func _iter_next(arg): + current += increment + return should_continue() - func _iter_get(arg): - return current + func _iter_get(arg): + return current ``` And it can be used like any other iterator: ``` - var itr = ForwardIterator.new(0, 6, 2) - for i in itr: - print(i) # Will print 0, 2, and 4. +var itr = ForwardIterator.new(0, 6, 2) +for i in itr: + print(i) # Will print 0, 2, and 4. ``` Make sure to reset the state of the iterator in `iter_init`, otherwise nested @@ -514,10 +514,10 @@ tunnel, smashing everything on its way. The code for the rock, in a statically typed language would be something like: ``` - void BigRollingRock::on_object_hit(Smashable *entity) { +void BigRollingRock::on_object_hit(Smashable *entity) { - entity->smash(); - } + entity->smash(); +} ``` This way, everything that can be smashed by a rock would have to @@ -533,8 +533,8 @@ makes sure you only have to define a `smash()` function where required and that's it. No need to consider inheritance, base classes, etc. ``` - func _on_object_hit(object): - object.smash() +func _on_object_hit(object): + object.smash() ``` And that's it. If the object that hit the big rock has a smash() method, @@ -558,9 +558,9 @@ doesn't exist, but GDScript is stricter, so checking if the function exists is desirable: ``` - func _on_object_hit(object): - if object.has_method("smash"): - object.smash() +func _on_object_hit(object): + if object.has_method("smash"): + object.smash() ``` Then, simply define that method and anything the rock touches can be diff --git a/03_usage/15_scripting/gdscript/03_gdscript_exports.md b/03_usage/15_scripting/gdscript/03_gdscript_exports.md index d891e4a..3c6ea38 100644 --- a/03_usage/15_scripting/gdscript/03_gdscript_exports.md +++ b/03_usage/15_scripting/gdscript/03_gdscript_exports.md @@ -10,9 +10,9 @@ attached to. They will also be available for editing in the property editor. Exporting is done by using the `export` keyword: ``` - extends Button +extends Button - export var number = 5 # Value will be saved and visible in the property editor. +export var number = 5 # Value will be saved and visible in the property editor. ``` An exported variable must be initialized to a constant expression or have an @@ -35,104 +35,104 @@ Note: ## Examples ``` - # If the exported value assigns a constant or constant expression, - # the type will be inferred and used in the editor. +# If the exported value assigns a constant or constant expression, +# the type will be inferred and used in the editor. - export var number = 5 +export var number = 5 - # Export can take a basic data type as an argument, which will be - # used in the editor. +# Export can take a basic data type as an argument, which will be +# used in the editor. - export(int) var number +export(int) var number - # Export can also take a resource type to use as a hint. +# Export can also take a resource type to use as a hint. - export(Texture) var character_face - export(PackedScene) var scene_file - # There are many resource types that can be used this way, try e.g. - # the following to list them: - export(Resource) var resource +export(Texture) var character_face +export(PackedScene) var scene_file +# There are many resource types that can be used this way, try e.g. +# the following to list them: +export(Resource) var resource - # Integers and strings hint enumerated values. +# Integers and strings hint enumerated values. - # Editor will enumerate as 0, 1 and 2. - export(int, "Warrior", "Magician", "Thief") var character_class - # Editor will enumerate with string names. - export(String, "Rebecca", "Mary", "Leah") var character_name +# Editor will enumerate as 0, 1 and 2. +export(int, "Warrior", "Magician", "Thief") var character_class +# Editor will enumerate with string names. +export(String, "Rebecca", "Mary", "Leah") var character_name - # Named enum values +# Named enum values - # Editor will enumerate as THING_1, THING_2, ANOTHER_THING. - enum NamedEnum {THING_1, THING_2, ANOTHER_THING = -1} - export(NamedEnum) var x +# Editor will enumerate as THING_1, THING_2, ANOTHER_THING. +enum NamedEnum {THING_1, THING_2, ANOTHER_THING = -1} +export(NamedEnum) var x - # Strings as paths +# Strings as paths - # String is a path to a file. - export(String, FILE) var f - # String is a path to a directory. - export(String, DIR) var f - # String is a path to a file, custom filter provided as hint. - export(String, FILE, "*.txt") var f +# String is a path to a file. +export(String, FILE) var f +# String is a path to a directory. +export(String, DIR) var f +# String is a path to a file, custom filter provided as hint. +export(String, FILE, "*.txt") var f - # Using paths in the global filesystem is also possible, - # but only in scripts in "tool" mode. +# Using paths in the global filesystem is also possible, +# but only in scripts in "tool" mode. - # String is a path to a PNG file in the global filesystem. - export(String, FILE, GLOBAL, "*.png)") var tool_image - # String is a path to a directory in the global filesystem. - export(String, DIR, GLOBAL) var tool_dir +# String is a path to a PNG file in the global filesystem. +export(String, FILE, GLOBAL, "*.png)") var tool_image +# String is a path to a directory in the global filesystem. +export(String, DIR, GLOBAL) var tool_dir - # The MULTILINE setting tells the editor to show a large input - # field for editing over multiple lines. - export(String, MULTILINE) var text +# The MULTILINE setting tells the editor to show a large input +# field for editing over multiple lines. +export(String, MULTILINE) var text - # Limiting editor input ranges +# Limiting editor input ranges - # Allow integer values from 0 to 20. - export(int, 20) var i - # Allow integer values from -10 to 20. - export(int, -10, 20) var j - # Allow floats from -10 to 20 and snap the value to multiples of 0.2. - export(float, -10, 20, 0.2) var k - # Allow values 'y = exp(x)' where 'y' varies between 100 and 1000 - # while snapping to steps of 20. The editor will present a - # slider for easily editing the value. - export(float, EXP, 100, 1000, 20) var l +# Allow integer values from 0 to 20. +export(int, 20) var i +# Allow integer values from -10 to 20. +export(int, -10, 20) var j +# Allow floats from -10 to 20 and snap the value to multiples of 0.2. +export(float, -10, 20, 0.2) var k +# Allow values 'y = exp(x)' where 'y' varies between 100 and 1000 +# while snapping to steps of 20. The editor will present a +# slider for easily editing the value. +export(float, EXP, 100, 1000, 20) var l - # Floats with easing hint +# Floats with easing hint - # Display a visual representation of the 'ease()' function - # when editing. - export(float, EASE) var transition_speed +# Display a visual representation of the 'ease()' function +# when editing. +export(float, EASE) var transition_speed - # Colors +# Colors - # Color given as red-green-blue value (alpha will always be 1). - export(Color, RGB) var col - # Color given as red-green-blue-alpha value. - export(Color, RGBA) var col +# Color given as red-green-blue value (alpha will always be 1). +export(Color, RGB) var col +# Color given as red-green-blue-alpha value. +export(Color, RGBA) var col - # Nodes +# Nodes - # Another node in the scene can be exported as a NodePath. - export(NodePath) var node_path - # Do take note that the node itself isn't being exported - - # there is one more step to call the true node: - onready var node = get_node(node_path) +# Another node in the scene can be exported as a NodePath. +export(NodePath) var node_path +# Do take note that the node itself isn't being exported - +# there is one more step to call the true node: +onready var node = get_node(node_path) - # Resources +# Resources - export(Resource) var resource - # In the Inspector, you can then drag and drop a resource file - # from the FileSystem dock into the variable slot. +export(Resource) var resource +# In the Inspector, you can then drag and drop a resource file +# from the FileSystem dock into the variable slot. - # Opening the inspector dropdown may result in an - # extremely long list of possible classes to create, however. - # Therefore, if you specify an extension of Resource such as: - export(AnimationNode) var resource - # The drop-down menu will be limited to AnimationNode and all - # its inherited classes. +# Opening the inspector dropdown may result in an +# extremely long list of possible classes to create, however. +# Therefore, if you specify an extension of Resource such as: +export(AnimationNode) var resource +# The drop-down menu will be limited to AnimationNode and all +# its inherited classes. ``` It must be noted that even if the script is not being run while in the @@ -146,8 +146,8 @@ values in one property. By using the export hint `int, FLAGS, ...`, they can be set from the editor: ``` - # Set any of the given flags from the editor. - export(int, FLAGS, "Fire", "Water", "Earth", "Wind") var spell_elements = 0 +# Set any of the given flags from the editor. +export(int, FLAGS, "Fire", "Water", "Earth", "Wind") var spell_elements = 0 ``` You must provide a string description for each flag. In this example, `Fire` @@ -158,10 +158,10 @@ corresponds to value 8. Usually, constants should be defined accordingly (e.g. Export hints are also provided for the physics and render layers defined in the project settings: ``` - export(int, LAYERS_2D_PHYSICS) var layers_2d_physics - export(int, LAYERS_2D_RENDER) var layers_2d_render - export(int, LAYERS_3D_PHYSICS) var layers_3d_physics - export(int, LAYERS_3D_RENDER) var layers_3d_render +export(int, LAYERS_2D_PHYSICS) var layers_2d_physics +export(int, LAYERS_2D_RENDER) var layers_2d_render +export(int, LAYERS_3D_PHYSICS) var layers_3d_physics +export(int, LAYERS_3D_RENDER) var layers_3d_render ``` Using bit flags requires some understanding of bitwise operations. @@ -176,36 +176,36 @@ values can be set in the inspector by dragging and dropping multiple files from the FileSystem dock at once. ``` - # Default value must be a constant expression. +# Default value must be a constant expression. - export var a = [1, 2, 3] +export var a = [1, 2, 3] - # Exported arrays can specify type (using the same hints as before). +# Exported arrays can specify type (using the same hints as before). - export(Array, int) var ints = [1, 2, 3] - export(Array, int, "Red", "Green", "Blue") var enums = [2, 1, 0] - export(Array, Array, float) var two_dimensional = [[1.0, 2.0], [3.0, 4.0]] +export(Array, int) var ints = [1, 2, 3] +export(Array, int, "Red", "Green", "Blue") var enums = [2, 1, 0] +export(Array, Array, float) var two_dimensional = [[1.0, 2.0], [3.0, 4.0]] - # You can omit the default value, but then it would be null if not assigned. +# You can omit the default value, but then it would be null if not assigned. - export(Array) var b - export(Array, PackedScene) var scenes +export(Array) var b +export(Array, PackedScene) var scenes - # Arrays with specified types which inherit from resource can be set by - # drag-and-dropping multiple files from the FileSystem dock. +# Arrays with specified types which inherit from resource can be set by +# drag-and-dropping multiple files from the FileSystem dock. - export(Array, Texture) var textures - export(Array, PackedScene) var scenes +export(Array, Texture) var textures +export(Array, PackedScene) var scenes - # Typed arrays also work, only initialized empty: +# Typed arrays also work, only initialized empty: - export var vector3s = PoolVector3Array() - export var strings = PoolStringArray() +export var vector3s = PoolVector3Array() +export var strings = PoolStringArray() - # Default value can include run-time values, but can't - # be exported. +# Default value can include run-time values, but can't +# be exported. - var c = [a, 2, 3] +var c = [a, 2, 3] ``` ## Setting exported variables from a tool script @@ -241,14 +241,14 @@ To understand how to better use the sections below, you should understand how to make properties with advanced exports. ``` - func _get_property_list(): - var properties = [] - # Same as "export(int) var my_property" - properties.append({ - name = "my_property", - type = TYPE_INT - }) - return properties +func _get_property_list(): + var properties = [] + # Same as "export(int) var my_property" + properties.append({ + name = "my_property", + type = TYPE_INT + }) + return properties ``` * The `get_property_list()` function gets called by the inspector. You @@ -272,18 +272,18 @@ property or else you may need to override the a variable to to a property also gives you the ability to give it a default state. ``` - # This variable is determined by the function below. - # This variable acts just like a regular gdscript export. - var my_property = 5 +# This variable is determined by the function below. +# This variable acts just like a regular gdscript export. +var my_property = 5 - func _get_property_list(): - var properties = [] - # Same as "export(int) var my_property" - properties.append({ - name = "my_property", - type = TYPE_INT - }) - return properties +func _get_property_list(): + var properties = [] + # Same as "export(int) var my_property" + properties.append({ + name = "my_property", + type = TYPE_INT + }) + return properties ``` ### Adding default values for properties @@ -295,22 +295,22 @@ To define default values for advanced exports, you need to override the `propert * The `property_get_revert()` method takes the name of a property and must return the default value for that property. ``` - func _get_property_list(): - var properties = [] - properties.append({ - name = "my_property", - type = TYPE_INT - }) - return properties +func _get_property_list(): + var properties = [] + properties.append({ + name = "my_property", + type = TYPE_INT + }) + return properties - func property_can_revert(property): - if property == "my_property": - return true - return false +func property_can_revert(property): + if property == "my_property": + return true + return false - func property_get_revert(property): - if property == "my_property": - return 5 +func property_get_revert(property): + if property == "my_property": + return 5 ``` ### Adding script categories @@ -320,20 +320,20 @@ embedded into the inspector to act as a separator. `Script Variables` is one example of a built-in category. ``` - func _get_property_list(): - var properties = [] - properties.append({ - name = "Debug", - type = TYPE_NIL, - usage = PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_SCRIPT_VARIABLE - }) - - # Example of adding a property to the script category - properties.append({ - name = "Logging_Enabled", - type = TYPE_BOOL - }) - return properties +func _get_property_list(): + var properties = [] + properties.append({ + name = "Debug", + type = TYPE_NIL, + usage = PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_SCRIPT_VARIABLE + }) + + # Example of adding a property to the script category + properties.append({ + name = "Logging_Enabled", + type = TYPE_BOOL + }) + return properties ``` * `name` is the name of a category to be added to the inspector; @@ -350,28 +350,28 @@ example of a built-in category. A list of properties with similar names can be grouped. ``` - func _get_property_list(): - var properties = [] - properties.append({ - name = "Rotate", - type = TYPE_NIL, - hint_string = "rotate_", - usage = PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SCRIPT_VARIABLE - }) +func _get_property_list(): + var properties = [] + properties.append({ + name = "Rotate", + type = TYPE_NIL, + hint_string = "rotate_", + usage = PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SCRIPT_VARIABLE + }) - # Example of adding to the group - properties.append({ - name = "rotate_speed", - type = TYPE_REAL - }) + # Example of adding to the group + properties.append({ + name = "rotate_speed", + type = TYPE_REAL + }) - # This property won't get added to the group - # due to not having the "rotate_" prefix. - properties.append({ - name = "trail_color", - type = TYPE_COLOR - }) - return properties + # This property won't get added to the group + # due to not having the "rotate_" prefix. + properties.append({ + name = "trail_color", + type = TYPE_COLOR + }) + return properties ``` * `name` is the name of a group which is going to be displayed as collapsible diff --git a/03_usage/15_scripting/gdscript/04_gdscript_styleguide.md b/03_usage/15_scripting/gdscript/04_gdscript_styleguide.md index 719a16a..59ab368 100644 --- a/03_usage/15_scripting/gdscript/04_gdscript_styleguide.md +++ b/03_usage/15_scripting/gdscript/04_gdscript_styleguide.md @@ -26,67 +26,67 @@ Here is a complete class example based on these guidelines: ``` - class_name StateMachine - extends Node - # Hierarchical State machine for the player. - # Initializes states and delegates engine callbacks - # (_physics_process, _unhandled_input) to the state. +class_name StateMachine +extends Node +# Hierarchical State machine for the player. +# Initializes states and delegates engine callbacks +# (_physics_process, _unhandled_input) to the state. - signal state_changed(previous, new) +signal state_changed(previous, new) - export var initial_state = NodePath() - var is_active = true setget set_is_active +export var initial_state = NodePath() +var is_active = true setget set_is_active - onready var _state = get_node(initial_state) setget set_state - onready var _state_name = _state.name +onready var _state = get_node(initial_state) setget set_state +onready var _state_name = _state.name - func _init(): - add_to_group("state_machine") +func _init(): + add_to_group("state_machine") - func _ready(): - connect("state_changed", self, "_on_state_changed") - _state.enter() +func _ready(): + connect("state_changed", self, "_on_state_changed") + _state.enter() - func _unhandled_input(event): - _state.unhandled_input(event) +func _unhandled_input(event): + _state.unhandled_input(event) - func _physics_process(delta): - _state.physics_process(delta) +func _physics_process(delta): + _state.physics_process(delta) - func transition_to(target_state_path, msg={}): - if not has_node(target_state_path): - return +func transition_to(target_state_path, msg={}): + if not has_node(target_state_path): + return - var target_state = get_node(target_state_path) - assert(target_state.is_composite == false) + var target_state = get_node(target_state_path) + assert(target_state.is_composite == false) - _state.exit() - self._state = target_state - _state.enter(msg) - Events.emit_signal("player_state_changed", _state.name) + _state.exit() + self._state = target_state + _state.enter(msg) + Events.emit_signal("player_state_changed", _state.name) - func set_is_active(value): - is_active = value - set_physics_process(value) - set_process_unhandled_input(value) - set_block_signals(not value) +func set_is_active(value): + is_active = value + set_physics_process(value) + set_process_unhandled_input(value) + set_block_signals(not value) - func set_state(value): - _state = value - _state_name = _state.name +func set_state(value): + _state = value + _state_name = _state.name - func _on_state_changed(previous, new): - print("state changed") - emit_signal("state_changed") +func _on_state_changed(previous, new): + print("state changed") + emit_signal("state_changed") ``` ## Formatting @@ -105,18 +105,18 @@ Each indent level should be one greater than the block containing it. **Good**: ``` - for i in range(10): - print("hello") +for i in range(10): + print("hello") ``` **Bad**: ``` - for i in range(10): - print("hello") +for i in range(10): + print("hello") - for i in range(10): - print("hello") +for i in range(10): + print("hello") ``` Use 2 indent levels to distinguish continuation lines from @@ -125,17 +125,17 @@ regular code blocks. **Good**: ``` - effect.interpolate_property(sprite, "transform/scale", - sprite.get_scale(), Vector2(2.0, 2.0), 0.3, - Tween.TRANS_QUAD, Tween.EASE_OUT) +effect.interpolate_property(sprite, "transform/scale", + sprite.get_scale(), Vector2(2.0, 2.0), 0.3, + Tween.TRANS_QUAD, Tween.EASE_OUT) ``` **Bad**: ``` - effect.interpolate_property(sprite, "transform/scale", - sprite.get_scale(), Vector2(2.0, 2.0), 0.3, - Tween.TRANS_QUAD, Tween.EASE_OUT) +effect.interpolate_property(sprite, "transform/scale", + sprite.get_scale(), Vector2(2.0, 2.0), 0.3, + Tween.TRANS_QUAD, Tween.EASE_OUT) ``` Exceptions to this rule are arrays, dictionaries, and enums. Use a single @@ -144,47 +144,47 @@ indentation level to distinguish continuation lines: **Good**: ``` - var party = [ - "Pandemonium", - "Godette", - "Steve", - ] +var party = [ + "Pandemonium", + "Godette", + "Steve", +] - var character_dict = { - "Name": "Bob", - "Age": 27, - "Job": "Mechanic", - } +var character_dict = { + "Name": "Bob", + "Age": 27, + "Job": "Mechanic", +} - enum Tiles { - TILE_BRICK, - TILE_FLOOR, - TILE_SPIKE, - TILE_TELEPORT, - } +enum Tiles { + TILE_BRICK, + TILE_FLOOR, + TILE_SPIKE, + TILE_TELEPORT, +} ``` **Bad**: ``` - var party = [ - "Pandemonium", - "Godette", - "Steve", - ] +var party = [ + "Pandemonium", + "Godette", + "Steve", +] - var character_dict = { - "Name": "Bob", - "Age": 27, - "Job": "Mechanic", - } +var character_dict = { + "Name": "Bob", + "Age": 27, + "Job": "Mechanic", +} - enum Tiles { - TILE_BRICK, - TILE_FLOOR, - TILE_SPIKE, - TILE_TELEPORT, - } +enum Tiles { + TILE_BRICK, + TILE_FLOOR, + TILE_SPIKE, + TILE_TELEPORT, +} ``` ### Trailing comma @@ -196,23 +196,23 @@ line doesn't need to be modified when adding new elements. **Good**: ``` - enum Tiles { - TILE_BRICK, - TILE_FLOOR, - TILE_SPIKE, - TILE_TELEPORT, - } +enum Tiles { + TILE_BRICK, + TILE_FLOOR, + TILE_SPIKE, + TILE_TELEPORT, +} ``` **Bad**: ``` - enum Tiles { - TILE_BRICK, - TILE_FLOOR, - TILE_SPIKE, - TILE_TELEPORT - } +enum Tiles { + TILE_BRICK, + TILE_FLOOR, + TILE_SPIKE, + TILE_TELEPORT +} ``` Trailing commas are unnecessary in single-line lists, so don't add them in this case. @@ -220,13 +220,13 @@ Trailing commas are unnecessary in single-line lists, so don't add them in this **Good**: ``` - enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT} +enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT} ``` **Bad**: ``` - enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT,} +enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT,} ``` ### Blank lines @@ -234,16 +234,16 @@ Trailing commas are unnecessary in single-line lists, so don't add them in this Surround functions and class definitions with two blank lines: ``` - func heal(amount): - health += amount - health = min(health, max_health) - emit_signal("health_changed", health) +func heal(amount): + health += amount + health = min(health, max_health) + emit_signal("health_changed", health) - func take_damage(amount, effect=null): - health -= amount - health = max(0, health) - emit_signal("health_changed", health) +func take_damage(amount, effect=null): + health -= amount + health = max(0, health) + emit_signal("health_changed", health) ``` Use one blank line inside functions to separate logical sections. @@ -268,25 +268,25 @@ not even with a single line conditional statement. **Good**: ``` - if position.x > width: - position.x = 0 +if position.x > width: + position.x = 0 - if flag: - print("flagged") +if flag: + print("flagged") ``` **Bad**: ``` - if position.x > width: position.x = 0 +if position.x > width: position.x = 0 - if flag: print("flagged") +if flag: print("flagged") ``` The only exception to that rule is the ternary operator: ``` - next_state = "fall" if not is_on_floor() else "idle" +next_state = "fall" if not is_on_floor() else "idle" ``` ### Format multiline statements for readability @@ -308,31 +308,31 @@ end of the previous line. **Good**: ``` - var angle_degrees = 135 - var quadrant = ( - "northeast" if angle_degrees <= 90 - else "southeast" if angle_degrees <= 180 - else "southwest" if angle_degrees <= 270 - else "northwest" - ) +var angle_degrees = 135 +var quadrant = ( + "northeast" if angle_degrees <= 90 + else "southeast" if angle_degrees <= 180 + else "southwest" if angle_degrees <= 270 + else "northwest" +) - var position = Vector2(250, 350) - if ( - position.x > 200 and position.x < 400 - and position.y > 300 and position.y < 400 - ): - pass +var position = Vector2(250, 350) +if ( + position.x > 200 and position.x < 400 + and position.y > 300 and position.y < 400 +): + pass ``` **Bad**: ``` - var angle_degrees = 135 - var quadrant = "northeast" if angle_degrees <= 90 else "southeast" if angle_degrees <= 180 else "southwest" if angle_degrees <= 270 else "northwest" +var angle_degrees = 135 +var quadrant = "northeast" if angle_degrees <= 90 else "southeast" if angle_degrees <= 180 else "southwest" if angle_degrees <= 270 else "northwest" - var position = Vector2(250, 350) - if position.x > 200 and position.x < 400 and position.y > 300 and position.y < 400: - pass +var position = Vector2(250, 350) +if position.x > 200 and position.x < 400 and position.y > 300 and position.y < 400: + pass ``` ### Avoid unnecessary parentheses @@ -344,15 +344,15 @@ they only reduce readability. **Good**: ``` - if is_colliding(): - queue_free() +if is_colliding(): + queue_free() ``` **Bad**: ``` - if (is_colliding()): - queue_free() +if (is_colliding()): + queue_free() ``` ### Boolean operators @@ -368,15 +368,15 @@ This can make long expressions easier to read. **Good**: ``` - if (foo and bar) or baz: - print("condition is true") +if (foo and bar) or baz: + print("condition is true") ``` **Bad**: ``` - if foo && bar || baz: - print("condition is true") +if foo && bar || baz: + print("condition is true") ``` ### Comment spacing @@ -387,15 +387,15 @@ This helps differentiate text comments from disabled code. **Good**: ``` - # This is a comment. - #print("This is disabled code") +# This is a comment. +#print("This is disabled code") ``` **Bad**: ``` - #This is a comment. - # print("This is disabled code") +#This is a comment. +# print("This is disabled code") ``` Note: @@ -413,29 +413,29 @@ in dictionary references and function calls. **Good**: ``` - position.x = 5 - position.y = target_position.y + 10 - dict["key"] = 5 - my_array = [4, 5, 6] - print("foo") +position.x = 5 +position.y = target_position.y + 10 +dict["key"] = 5 +my_array = [4, 5, 6] +print("foo") ``` **Bad**: ``` - position.x=5 - position.y = mpos.y+10 - dict ["key"] = 5 - myarray = [4,5,6] - print ("foo") +position.x=5 +position.y = mpos.y+10 +dict ["key"] = 5 +myarray = [4,5,6] +print ("foo") ``` Don't use spaces to align expressions vertically: ``` - x = 100 - y = 100 - velocity = 500 +x = 100 +y = 100 +velocity = 500 ``` ### Quotes @@ -444,17 +444,17 @@ Use double quotes unless single quotes make it possible to escape fewer characters in a given string. See the examples below: ``` - # Normal string. - print("hello world") +# Normal string. +print("hello world") - # Use double quotes as usual to avoid escapes. - print("hello 'world'") +# Use double quotes as usual to avoid escapes. +print("hello 'world'") - # Use single quotes as an exception to the rule to avoid escapes. - print('hello "world"') +# Use single quotes as an exception to the rule to avoid escapes. +print('hello "world"') - # Both quote styles would require 2 escapes; prefer double quotes if it's a tie. - print("'hello' \"world\"") +# Both quote styles would require 2 escapes; prefer double quotes if it's a tie. +print("'hello' \"world\"") ``` ### Numbers @@ -465,14 +465,14 @@ glance. **Good**: ``` - var float_number = 0.234 - var other_float_number = 13.0 +var float_number = 0.234 +var other_float_number = 13.0 ``` **Bad**: ``` - var float_number = .234 - var other_float_number = 13. +var float_number = .234 +var other_float_number = 13. ``` Use lowercase for letters in hexadecimal numbers, as their lower height makes @@ -480,12 +480,12 @@ the number easier to read. **Good**: ``` - var hex_number = 0xfb8c0b +var hex_number = 0xfb8c0b ``` **Bad**: ``` - var hex_number = 0xFB8C0B +var hex_number = 0xFB8C0B ``` Take advantage of GDScript's underscores in literals to make large numbers more @@ -493,20 +493,20 @@ readable. **Good**: ``` - var large_number = 1_234_567_890 - var large_hex_number = 0xffff_f8f8_0000 - var large_bin_number = 0b1101_0010_1010 - # Numbers lower than 1000000 generally don't need separators. - var small_number = 12345 +var large_number = 1_234_567_890 +var large_hex_number = 0xffff_f8f8_0000 +var large_bin_number = 0b1101_0010_1010 +# Numbers lower than 1000000 generally don't need separators. +var small_number = 12345 ``` **Bad**: ``` - var large_number = 1234567890 - var large_hex_number = 0xfffff8f80000 - var large_bin_number = 0b110100101010 - # Numbers lower than 1000000 generally don't need separators. - var small_number = 12_345 +var large_number = 1234567890 +var large_hex_number = 0xfffff8f80000 +var large_bin_number = 0b110100101010 +# Numbers lower than 1000000 generally don't need separators. +var small_number = 12_345 ``` .. _naming_conventions: @@ -522,15 +522,15 @@ code. Use snake_case for file names. For named classes, convert the PascalCase class name to snake_case: ``` - # This file should be saved as `weapon.gd`. - class_name Weapon - extends Node +# This file should be saved as `weapon.gd`. +class_name Weapon +extends Node ``` ``` - # This file should be saved as `yaml_parser.gd`. - class_name YAMLParser - extends Object +# This file should be saved as `yaml_parser.gd`. +class_name YAMLParser +extends Object ``` This is consistent with how C++ files are named in Pandemonium's source code. This @@ -542,13 +542,13 @@ from Windows to other platforms. Use PascalCase for class and node names: ``` - extends KinematicBody +extends KinematicBody ``` Also use PascalCase when loading a class into a constant or a variable: ``` - const Weapon = preload("res://weapon.gd") +const Weapon = preload("res://weapon.gd") ``` ### Functions and variables @@ -556,16 +556,16 @@ Also use PascalCase when loading a class into a constant or a variable: Use snake\_case to name functions and variables: ``` - var particle_effect - func load_level(): +var particle_effect +func load_level(): ``` Prepend a single underscore (\_) to virtual methods functions the user must override, private functions, and private variables: ``` - var _counter = 0 - func _recalculate_path(): +var _counter = 0 +func _recalculate_path(): ``` ### Signals @@ -573,8 +573,8 @@ override, private functions, and private variables: Use the past tense to name signals: ``` - signal door_opened - signal score_changed +signal door_opened +signal score_changed ``` ### Constants and enums @@ -583,19 +583,19 @@ Write constants with CONSTANT\_CASE, that is to say in all caps with an underscore (\_) to separate words: ``` - const MAX_SPEED = 200 +const MAX_SPEED = 200 ``` Use PascalCase for enum *names* and CONSTANT\_CASE for their members, as they are constants: ``` - enum Element { - EARTH, - WATER, - AIR, - FIRE, - } +enum Element { + EARTH, + WATER, + AIR, + FIRE, +} ``` @@ -607,24 +607,24 @@ This first section focuses on code order. For formatting, see We suggest to organize GDScript code this way: ``` - 01. tool - 02. class_name - 03. extends - 04. # docstring +01. tool +02. class_name +03. extends +04. # docstring - 05. signals - 06. enums - 07. constants - 08. exported variables - 09. public variables - 10. private variables - 11. onready variables +05. signals +06. enums +07. constants +08. exported variables +09. public variables +10. private variables +11. onready variables - 12. optional built-in virtual _init method - 13. built-in virtual _ready method - 14. remaining built-in virtual methods - 15. public methods - 16. private methods +12. optional built-in virtual _init method +13. built-in virtual _ready method +14. remaining built-in virtual methods +15. public methods +16. private methods ``` We optimized the order to make it easy to read the code from top to bottom, to @@ -657,10 +657,10 @@ can use that to explain the role of your class to your teammates, how it works, and how other developers should use it, for example. ``` - class_name MyNode - extends Node - # A brief description of the class's role and functionality. - # Longer description. +class_name MyNode +extends Node +# A brief description of the class's role and functionality. +# Longer description. ``` ### Signals and properties @@ -675,22 +675,22 @@ Then, write constants, exported variables, public, private, and onready variables, in that order. ``` - signal spawn_player(position) +signal spawn_player(position) - enum Jobs {KNIGHT, WIZARD, ROGUE, HEALER, SHAMAN} +enum Jobs {KNIGHT, WIZARD, ROGUE, HEALER, SHAMAN} - const MAX_LIVES = 3 +const MAX_LIVES = 3 - export(Jobs) var job = Jobs.KNIGHT - export var max_health = 50 - export var attack = 5 +export(Jobs) var job = Jobs.KNIGHT +export var max_health = 50 +export var attack = 5 - var health = max_health setget set_health +var health = max_health setget set_health - var _speed = 300.0 +var _speed = 300.0 - onready var sword = get_node("Sword") - onready var gun = get_node("Gun") +onready var sword = get_node("Sword") +onready var gun = get_node("Gun") ``` @@ -733,35 +733,35 @@ The rest of the class's interface, public and private methods, come after that, in that order. ``` - func _init(): - add_to_group("state_machine") +func _init(): + add_to_group("state_machine") - func _ready(): - connect("state_changed", self, "_on_state_changed") - _state.enter() +func _ready(): + connect("state_changed", self, "_on_state_changed") + _state.enter() - func _unhandled_input(event): - _state.unhandled_input(event) +func _unhandled_input(event): + _state.unhandled_input(event) - func transition_to(target_state_path, msg={}): - if not has_node(target_state_path): - return +func transition_to(target_state_path, msg={}): + if not has_node(target_state_path): + return - var target_state = get_node(target_state_path) - assert(target_state.is_composite == false) + var target_state = get_node(target_state_path) + assert(target_state.is_composite == false) - _state.exit() - self._state = target_state - _state.enter(msg) - Events.emit_signal("player_state_changed", _state.name) + _state.exit() + self._state = target_state + _state.enter(msg) + Events.emit_signal("player_state_changed", _state.name) - func _on_state_changed(previous, new): - print("state changed") - emit_signal("state_changed") +func _on_state_changed(previous, new): + print("state changed") + emit_signal("state_changed") ``` @@ -774,13 +774,13 @@ Since Pandemonium 3.1, GDScript supports `optional static typing( doc_gdscript_s To declare a variable's type, use `<variable>: <type>`: ``` - var health: int = 0 +var health: int = 0 ``` To declare the return type of a function, use `-> <type>`: ``` - func heal(amount: int) -> void: +func heal(amount: int) -> void: ``` ### Inferred types @@ -788,7 +788,7 @@ To declare the return type of a function, use `-> <type>`: In most cases you can let the compiler infer the type, using `:=`: ``` - var health := 0 # The compiler will use the int type. +var health := 0 # The compiler will use the int type. ``` However, in a few cases when context is missing, the compiler falls back to @@ -799,7 +799,7 @@ should set the type explicitly. **Good**: ``` - onready var health_bar: ProgressBar = get_node("UI/LifeBar") +onready var health_bar: ProgressBar = get_node("UI/LifeBar") ``` Alternatively, you can use the `as` keyword to cast the return type, and @@ -808,8 +808,8 @@ that type will be used to infer the type of the var. .. rst-class: code-example-good ``` - onready var health_bar := get_node("UI/LifeBar") as ProgressBar - # health_bar will be typed as ProgressBar +onready var health_bar := get_node("UI/LifeBar") as ProgressBar +# health_bar will be typed as ProgressBar ``` This option is also considered more `type-safe( doc_gdscript_static_typing_safe_lines )` than the first. @@ -817,7 +817,7 @@ This option is also considered more `type-safe( doc_gdscript_static_typing_safe_ **Bad**: ``` - # The compiler can't infer the exact type and will use Node - # instead of ProgressBar. - onready var health_bar := get_node("UI/LifeBar") +# The compiler can't infer the exact type and will use Node +# instead of ProgressBar. +onready var health_bar := get_node("UI/LifeBar") ``` diff --git a/03_usage/15_scripting/gdscript/05_static_typing.md b/03_usage/15_scripting/gdscript/05_static_typing.md index d25f396..d2a2460 100644 --- a/03_usage/15_scripting/gdscript/05_static_typing.md +++ b/03_usage/15_scripting/gdscript/05_static_typing.md @@ -30,18 +30,18 @@ who work with your code should always pass an `Item` to the `Inventory.add` method. With types, you can enforce this: ``` - # In 'Item.gd'. - class_name Item - # In 'Inventory.gd'. - class_name Inventory +# In 'Item.gd'. +class_name Item +# In 'Inventory.gd'. +class_name Inventory - func add(reference: Item, amount: int = 1): - var item = find_item(reference) - if not item: - item = _instance_item_from_db(reference) +func add(reference: Item, amount: int = 1): + var item = find_item(reference) + if not item: + item = _instance_item_from_db(reference) - item.amount += amount + item.amount += amount ``` Another significant advantage of typed GDScript is the new **warning @@ -88,17 +88,17 @@ variable's name, followed by its type. E.g. `var health: int`. This forces the variable's type to always stay the same: ``` - var damage: float = 10.5 - const MOVE_SPEED: float = 50.0 +var damage: float = 10.5 +const MOVE_SPEED: float = 50.0 ``` Pandemonium will try to infer types if you write a colon, but you omit the type: ``` - var life_points := 4 - var damage := 10.5 - var motion := Vector2() +var life_points := 4 +var damage := 10.5 +var motion := Vector2() ``` Currently you can use three types of… types: @@ -120,16 +120,16 @@ are two ways to use them in scripts. The first method is to preload the script you want to use as a type in a constant: ``` - const Rifle = preload("res://player/weapons/Rifle.gd") - var my_rifle: Rifle +const Rifle = preload("res://player/weapons/Rifle.gd") +var my_rifle: Rifle ``` The second method is to use the `name` keyword when you create. For the example above, your Rifle.gd would look like this: ``` - extends Node2D - class_name Rifle +extends Node2D +class_name Rifle ``` If you use `name`, Pandemonium registers the Rifle type globally in @@ -137,7 +137,7 @@ the editor, and you can use it anywhere, without having to preload it into a constant: ``` - var my_rifle: Rifle +var my_rifle: Rifle ``` ### Variable casting @@ -158,12 +158,12 @@ to use this type. This forces the variable to stick to the `PlayerController` type: ``` - func _on_body_entered(body: PhysicsBody2D) -> void: - var player := body as PlayerController - if not player: - return +func _on_body_entered(body: PhysicsBody2D) -> void: + var player := body as PlayerController + if not player: + return - player.damage() + player.damage() ``` As we're dealing with a custom type, if the `body` doesn't extend @@ -212,32 +212,32 @@ To define the return type of a function, write a dash and a right angle bracket `- )` after its declaration, followed by the return type: ``` - func _process(delta: float) -> void: - pass +func _process(delta: float) -> void: + pass ``` The type `void` means the function does not return anything. You can use any type, as with variables: ``` - func hit(damage: float) -> bool: - health_points -= damage - return health_points <= 0 +func hit(damage: float) -> bool: + health_points -= damage + return health_points <= 0 ``` You can also use your own nodes as return types: ``` - # Inventory.gd +# Inventory.gd - # Adds an item to the inventory and returns it. - func add(reference: Item, amount: int) -> Item: - var item: Item = find_item(reference) - if not item: - item = ItemDatabase.get_instance(reference) +# Adds an item to the inventory and returns it. +func add(reference: Item, amount: int) -> Item: + var item: Item = find_item(reference) + if not item: + item = ItemDatabase.get_instance(reference) - item.amount += amount - return item + item.amount += amount + return item ``` #### Typed or dynamic: stick to one style @@ -254,29 +254,29 @@ dynamic style: ``` - extends Node +extends Node - func _ready(): - pass +func _ready(): + pass - func _process(delta): - pass +func _process(delta): + pass ``` And with static typing: ``` - extends Node +extends Node - func _ready() -> void: - pass +func _ready() -> void: + pass - func _process(delta: float) -> void: - pass +func _process(delta: float) -> void: + pass ``` As you can see, you can also use types with the engine's virtual @@ -284,26 +284,26 @@ methods. Signal callbacks, like any methods, can also use types. Here's a `body_entered` signal in a dynamic style: ``` - func _on_Area2D_body_entered(body): - pass +func _on_Area2D_body_entered(body): + pass ``` And the same callback, with type hints: ``` - func _on_area_entered(area: CollisionObject2D) -> void: - pass +func _on_area_entered(area: CollisionObject2D) -> void: + pass ``` You're free to replace, e.g. the `CollisionObject2D`, with your own type, to cast parameters automatically: ``` - func _on_area_entered(bullet: Bullet) -> void: - if not bullet: - return +func _on_area_entered(bullet: Bullet) -> void: + if not bullet: + return - take_damage(bullet.damage) + take_damage(bullet.damage) ``` The `bullet` variable could hold any `CollisionObject2D` here, but @@ -326,15 +326,15 @@ use type hints. All the examples below **will trigger errors**. You can't use Enums as types: ``` - enum MoveDirection {UP, DOWN, LEFT, RIGHT} - var current_direction: MoveDirection +enum MoveDirection {UP, DOWN, LEFT, RIGHT} +var current_direction: MoveDirection ``` You can't specify the type of individual members in an array. This will give you an error: ``` - var enemies: Array = [$Goblin: Enemy, $Zombie: Enemy] +var enemies: Array = [$Goblin: Enemy, $Zombie: Enemy] ``` You can't force the assignment of types in a `for` loop, as each @@ -342,31 +342,31 @@ element the `for` keyword loops over already has a different type. So you **cannot** write: ``` - var names = ["John", "Marta", "Samantha", "Jimmy"] - for name: String in names: - pass +var names = ["John", "Marta", "Samantha", "Jimmy"] +for name: String in names: + pass ``` Two scripts can't depend on each other in a cyclic fashion: ``` - # Player.gd +# Player.gd - extends Area2D - class_name Player +extends Area2D +class_name Player - var rifle: Rifle +var rifle: Rifle ``` ``` - # Rifle.gd +# Rifle.gd - extends Area2D - class_name Rifle +extends Area2D +class_name Rifle - var player: Player +var player: Player ``` ## Summary diff --git a/03_usage/15_scripting/gdscript/07_gdscript_format_string.md b/03_usage/15_scripting/gdscript/07_gdscript_format_string.md index 05199d5..9ae2565 100644 --- a/03_usage/15_scripting/gdscript/07_gdscript_format_string.md +++ b/03_usage/15_scripting/gdscript/07_gdscript_format_string.md @@ -20,14 +20,14 @@ strings could be cumbersome. Examine this concrete GDScript example: ``` - # Define a format string with placeholder '%s' - var format_string = "We're waiting for %s." +# Define a format string with placeholder '%s' +var format_string = "We're waiting for %s." - # Using the '%' operator, the placeholder is replaced with the desired value - var actual_string = format_string % "Pandemonium" +# Using the '%' operator, the placeholder is replaced with the desired value +var actual_string = format_string % "Pandemonium" - print(actual_string) - # Output: "We're waiting for Pandemonium." +print(actual_string) +# Output: "We're waiting for Pandemonium." ``` Placeholders always start with a `%`, but the next character or characters, @@ -51,14 +51,14 @@ matters when the index or mixed style of Array is used. A quick example in GDScript: ``` - # Define a format string - var format_string = "We're waiting for {str}" +# Define a format string +var format_string = "We're waiting for {str}" - # Using the 'format' method, replace the 'str' placeholder - var actual_string = format_string.format({"str": "Pandemonium"}) +# Using the 'format' method, replace the 'str' placeholder +var actual_string = format_string.format({"str": "Pandemonium"}) - print(actual_string) - # Output: "We're waiting for Pandemonium" +print(actual_string) +# Output: "We're waiting for Pandemonium" ``` There are other `format specifiers`, but they are only applicable when using @@ -72,11 +72,11 @@ are handed in the form of an array, one value per placeholder (unless using a format specifier with `*`, see `dynamic padding`): ``` - var format_string = "%s was reluctant to learn %s, but now he enjoys it." - var actual_string = format_string % ["Estragon", "GDScript"] +var format_string = "%s was reluctant to learn %s, but now he enjoys it." +var actual_string = format_string % ["Estragon", "GDScript"] - print(actual_string) - # Output: "Estragon was reluctant to learn GDScript, but now he enjoys it." +print(actual_string) +# Output: "Estragon was reluctant to learn GDScript, but now he enjoys it." ``` Note the values are inserted in order. Remember all placeholders must be @@ -145,17 +145,17 @@ used. To pad a string to a minimum length, add an integer to the specifier: ``` - print("%10d" % 12345) - # output: " 12345" - # 5 leading spaces for a total length of 10 +print("%10d" % 12345) +# output: " 12345" +# 5 leading spaces for a total length of 10 ``` If the integer starts with `0`, integral values are padded with zeroes instead of white space: ``` - print("%010d" % 12345) - # output: "0000012345" +print("%010d" % 12345) +# output: "0000012345" ``` Precision can be specified for real numbers by adding a `.` (*dot*) with an @@ -164,19 +164,19 @@ rounding to integral value. The integer to use for padding must appear before the dot. ``` - # Pad to minimum length of 10, round to 3 decimal places - print("%10.3f" % 10000.5555) - # Output: " 10000.556" - # 1 leading space +# Pad to minimum length of 10, round to 3 decimal places +print("%10.3f" % 10000.5555) +# Output: " 10000.556" +# 1 leading space ``` The `-` character will cause padding to the right rather than the left, useful for right text alignment: ``` - print("%-10d" % 12345678) - # Output: "12345678 " - # 2 trailing spaces +print("%-10d" % 12345678) +# Output: "12345678 " +# 2 trailing spaces ``` @@ -188,19 +188,19 @@ format specifier. The values for padding and precision are then passed when formatting: ``` - var format_string = "%*.*f" - # Pad to length of 7, round to 3 decimal places: - print(format_string % [7, 3, 8.8888]) - # Output: " 8.889" - # 2 leading spaces +var format_string = "%*.*f" +# Pad to length of 7, round to 3 decimal places: +print(format_string % [7, 3, 8.8888]) +# Output: " 8.889" +# 2 leading spaces ``` It is still possible to pad with zeroes in integer placeholders by adding `0` before `*`: ``` - print("%0*d" % [2, 3]) - # Output: "03" +print("%0*d" % [2, 3]) +# Output: "03" ``` @@ -210,9 +210,9 @@ To insert a literal `%` character into a format string, it must be escaped to avoid reading it as a placeholder. This is done by doubling the character: ``` - var health = 56 - print("Remaining health: %d%%" % health) - # Output: "Remaining health: 56%" +var health = 56 +print("Remaining health: %d%%" % health) +# Output: "Remaining health: 56%" ``` diff --git a/03_usage/16_shaders/01_introduction_to_shaders.md b/03_usage/16_shaders/01_introduction_to_shaders.md index 7298315..699a6f1 100644 --- a/03_usage/16_shaders/01_introduction_to_shaders.md +++ b/03_usage/16_shaders/01_introduction_to_shaders.md @@ -25,18 +25,18 @@ Suppose you want to update all the pixels in a texture to a given color. In GDScript, your code would use `for` loops: ``` - for x in range(width): - for y in range(height): - set_color(x, y, some_color) +for x in range(width): + for y in range(height): + set_color(x, y, some_color) ``` Your code is already part of a loop in a shader, so the corresponding code would look like this. ``` - void fragment() { - COLOR = some_color; - } +void fragment() { + COLOR = some_color; +} ``` Note: @@ -79,7 +79,7 @@ support different render modes, built-in variables, and processing functions. In Pandemonium, all shaders need to specify their type in the first line, like so: ``` - shader_type spatial; +shader_type spatial; ``` Here are the available types: @@ -94,8 +94,8 @@ Shaders have optional render modes you can specify on the second line, after the shader type, like so: ``` - shader_type spatial; - render_mode unshaded, cull_disabled; +shader_type spatial; +render_mode unshaded, cull_disabled; ``` Render modes alter the way Pandemonium applies the shader. For example, the diff --git a/03_usage/16_shaders/03_screen_reading_shaders.md b/03_usage/16_shaders/03_screen_reading_shaders.md index 0ce9e01..7d27ff0 100644 --- a/03_usage/16_shaders/03_screen_reading_shaders.md +++ b/03_usage/16_shaders/03_screen_reading_shaders.md @@ -22,9 +22,9 @@ special built-in varying: SCREEN_UV can be used to obtain the UV for the current fragment. As a result, this simple canvas_item fragment shader: ``` - void fragment() { - COLOR = textureLod(SCREEN_TEXTURE, SCREEN_UV, 0.0); - } +void fragment() { + COLOR = textureLod(SCREEN_TEXTURE, SCREEN_UV, 0.0); +} ``` results in an invisible object, because it just shows what lies behind. @@ -49,21 +49,21 @@ and learn. One example is a simple shader to adjust brightness, contrast and saturation: ``` - shader_type canvas_item; +shader_type canvas_item; - uniform float brightness = 1.0; - uniform float contrast = 1.0; - uniform float saturation = 1.0; +uniform float brightness = 1.0; +uniform float contrast = 1.0; +uniform float saturation = 1.0; - void fragment() { - vec3 c = textureLod(SCREEN_TEXTURE, SCREEN_UV, 0.0).rgb; +void fragment() { + vec3 c = textureLod(SCREEN_TEXTURE, SCREEN_UV, 0.0).rgb; - c.rgb = mix(vec3(0.0), c.rgb, brightness); - c.rgb = mix(vec3(0.5), c.rgb, contrast); - c.rgb = mix(vec3(dot(vec3(1.0), c.rgb) * 0.33333), c.rgb, saturation); + c.rgb = mix(vec3(0.0), c.rgb, brightness); + c.rgb = mix(vec3(0.5), c.rgb, contrast); + c.rgb = mix(vec3(dot(vec3(1.0), c.rgb) * 0.33333), c.rgb, saturation); - COLOR.rgb = c; - } + COLOR.rgb = c; +} ``` ### Behind the scenes @@ -143,9 +143,9 @@ converted via the inverse projection matrix. The following code retrieves the 3D position below the pixel being drawn: ``` - void fragment() { - float depth = textureLod(DEPTH_TEXTURE, SCREEN_UV, 0.0).r; - vec4 upos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0); - vec3 pixel_position = upos.xyz / upos.w; - } +void fragment() { + float depth = textureLod(DEPTH_TEXTURE, SCREEN_UV, 0.0).r; + vec4 upos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0); + vec3 pixel_position = upos.xyz / upos.w; +} ``` diff --git a/03_usage/16_shaders/05_shaders_style_guide.md b/03_usage/16_shaders/05_shaders_style_guide.md index 9b39050..b981379 100644 --- a/03_usage/16_shaders/05_shaders_style_guide.md +++ b/03_usage/16_shaders/05_shaders_style_guide.md @@ -26,24 +26,24 @@ Note: Here is a complete shader example based on these guidelines: ``` - shader_type canvas_item; - // Screen-space shader to adjust a 2D scene's brightness, contrast - // and saturation. Taken from - // https://github.com/Relintai/pandemonium_engine-demo-projects/blob/master/2d/screen_space_shaders/shaders/BCS.shader +shader_type canvas_item; +// Screen-space shader to adjust a 2D scene's brightness, contrast +// and saturation. Taken from +// https://github.com/Relintai/pandemonium_engine-demo-projects/blob/master/2d/screen_space_shaders/shaders/BCS.shader - uniform float brightness = 0.8; - uniform float contrast = 1.5; - uniform float saturation = 1.8; +uniform float brightness = 0.8; +uniform float contrast = 1.5; +uniform float saturation = 1.8; - void fragment() { - vec3 c = textureLod(SCREEN_TEXTURE, SCREEN_UV, 0.0).rgb; +void fragment() { + vec3 c = textureLod(SCREEN_TEXTURE, SCREEN_UV, 0.0).rgb; - c.rgb = mix(vec3(0.0), c.rgb, brightness); - c.rgb = mix(vec3(0.5), c.rgb, contrast); - c.rgb = mix(vec3(dot(vec3(1.0), c.rgb) * 0.33333), c.rgb, saturation); + c.rgb = mix(vec3(0.0), c.rgb, brightness); + c.rgb = mix(vec3(0.5), c.rgb, contrast); + c.rgb = mix(vec3(dot(vec3(1.0), c.rgb) * 0.33333), c.rgb, saturation); - COLOR.rgb = c; - } + COLOR.rgb = c; +} ``` ## Formatting @@ -62,17 +62,17 @@ Each indent level should be one tab greater than the block containing it. **Good**: ``` - void fragment() { - COLOR = vec3(1.0, 1.0, 1.0); - } +void fragment() { + COLOR = vec3(1.0, 1.0, 1.0); +} ``` **Bad**: ``` - void fragment() { - COLOR = vec3(1.0, 1.0, 1.0); - } +void fragment() { + COLOR = vec3(1.0, 1.0, 1.0); +} ``` Use 2 indent levels to distinguish continuation lines from @@ -81,17 +81,17 @@ regular code blocks. **Good**: ``` - vec2 st = vec2( - atan(NORMAL.x, NORMAL.z), - acos(NORMAL.y)); +vec2 st = vec2( + atan(NORMAL.x, NORMAL.z), + acos(NORMAL.y)); ``` **Bad**: ``` - vec2 st = vec2( - atan(NORMAL.x, NORMAL.z), - acos(NORMAL.y)); +vec2 st = vec2( + atan(NORMAL.x, NORMAL.z), + acos(NORMAL.y)); ``` @@ -107,21 +107,21 @@ an `if` statement or similar. **Good**: ``` - void fragment() { - if (true) { - // ... - } +void fragment() { + if (true) { + // ... } +} ``` **Bad**: ``` - void fragment() - { - if (true) - // ... - } +void fragment() +{ + if (true) + // ... +} ``` ### Blank lines @@ -129,13 +129,13 @@ an `if` statement or similar. Surround function definitions with one (and only one) blank line: ``` - void do_something() { - // ... - } +void do_something() { + // ... +} - void fragment() { - // ... - } +void fragment() { + // ... +} ``` Use one (and only one) blank line inside functions to separate logical sections. @@ -155,27 +155,27 @@ Never combine multiple statements on a single line. **Good**: ``` - void fragment() { - ALBEDO = vec3(1.0); - EMISSION = vec3(1.0); - } +void fragment() { + ALBEDO = vec3(1.0); + EMISSION = vec3(1.0); +} ``` **Bad**: ``` - void fragment() { - ALBEDO = vec3(1.0); EMISSION = vec3(1.0); - } +void fragment() { + ALBEDO = vec3(1.0); EMISSION = vec3(1.0); +} ``` The only exception to that rule is the ternary operator: ``` - void fragment() { - bool should_be_white = true; - ALBEDO = should_be_white ? vec3(1.0) : vec3(0.0); - } +void fragment() { + bool should_be_white = true; + ALBEDO = should_be_white ? vec3(1.0) : vec3(0.0); + } ``` ### Comment spacing @@ -186,21 +186,21 @@ This helps differentiate text comments from disabled code. **Good**: ``` - // This is a comment. - //return; +// This is a comment. +//return; ``` **Bad**: ``` - //This is a comment. - // return; +//This is a comment. +// return; ``` Don't use multiline comment syntax if your comment can fit on a single line: ``` - /* This is another comment. */ +/* This is another comment. */ ``` Note: @@ -218,24 +218,24 @@ in function calls. **Good**: ``` - COLOR.r = 5.0; - COLOR.r = COLOR.g + 0.1; - COLOR.b = some_function(1.0, 2.0); +COLOR.r = 5.0; +COLOR.r = COLOR.g + 0.1; +COLOR.b = some_function(1.0, 2.0); ``` **Bad**: ``` - COLOR.r=5.0; - COLOR.r = COLOR.g+0.1; - COLOR.b = some_function (1.0,2.0); +COLOR.r=5.0; +COLOR.r = COLOR.g+0.1; +COLOR.b = some_function (1.0,2.0); ``` Don't use spaces to align expressions vertically: ``` - ALBEDO.r = 1.0; - EMISSION.r = 1.0; +ALBEDO.r = 1.0; +EMISSION.r = 1.0; ``` ### Floating-point numbers @@ -247,17 +247,17 @@ distinguishing numbers greater than 1 from those lower than 1. **Good**: ``` - void fragment() { - ALBEDO.rgb = vec3(5.0, 0.1, 0.2); - } +void fragment() { + ALBEDO.rgb = vec3(5.0, 0.1, 0.2); +} ``` **Bad**: ``` - void fragment() { - ALBEDO.rgb = vec3(5., .1, .2); - } +void fragment() { + ALBEDO.rgb = vec3(5., .1, .2); +} ``` ## Accessing vector members @@ -270,13 +270,13 @@ understand what the underlying data represents. **Good**: ``` - COLOR.rgb = vec3(5.0, 0.1, 0.2); +COLOR.rgb = vec3(5.0, 0.1, 0.2); ``` **Bad**: ``` - COLOR.xyz = vec3(5.0, 0.1, 0.2); +COLOR.xyz = vec3(5.0, 0.1, 0.2); ``` ## Naming conventions @@ -290,9 +290,9 @@ code. Use snake\_case to name functions and variables: ``` - void some_function() { - float some_variable = 0.5; - } +void some_function() { + float some_variable = 0.5; +} ``` ### Constants @@ -301,7 +301,7 @@ Write constants with CONSTANT\_CASE, that is to say in all caps with an underscore (\_) to separate words: ``` - const float GOLDEN_RATIO = 1.618; +const float GOLDEN_RATIO = 1.618; ``` ## Code order @@ -309,18 +309,18 @@ underscore (\_) to separate words: We suggest to organize shader code this way: ``` - 01. shader type declaration - 02. render mode declaration - 03. // docstring +01. shader type declaration +02. render mode declaration +03. // docstring - 04. uniforms - 05. constants - 06. varyings +04. uniforms +05. constants +06. varyings - 07. other functions - 08. vertex() function - 09. fragment() function - 10. light() function +07. other functions +08. vertex() function +09. fragment() function +10. light() function ``` We optimized the order to make it easy to read the code from top to bottom, to diff --git a/03_usage/16_shaders/06_advanced_postprocessing.md b/03_usage/16_shaders/06_advanced_postprocessing.md index a32eb9e..9139609 100644 --- a/03_usage/16_shaders/06_advanced_postprocessing.md +++ b/03_usage/16_shaders/06_advanced_postprocessing.md @@ -47,11 +47,11 @@ so we need to nullify the effects of Pandemonium's transformations. We do this b and sets the vertex position directly. ``` - shader_type spatial; +shader_type spatial; - void vertex() { - POSITION = vec4(VERTEX, 1.0); - } +void vertex() { + POSITION = vec4(VERTEX, 1.0); +} ``` Even with this vertex shader, the quad keeps disappearing. This is due to frustum @@ -75,7 +75,7 @@ To read from the depth texture, perform a texture lookup using `texture()` and the uniform variable `DEPTH_TEXTURE`. ``` - float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x; +float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x; ``` Note: @@ -99,10 +99,10 @@ Reconstruct the NDC using `SCREEN_UV` for the `x` and `y` axis, and the depth value for `z`. ``` - void fragment() { - float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x; - vec3 ndc = vec3(SCREEN_UV, depth) * 2.0 - 1.0; - } +void fragment() { + float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x; + vec3 ndc = vec3(SCREEN_UV, depth) * 2.0 - 1.0; +} ``` Convert NDC to view space by multiplying the NDC by `INV_PROJECTION_MATRIX`. @@ -110,12 +110,12 @@ Recall that view space gives positions relative to the camera, so the `z` value the distance to the point. ``` - void fragment() { - ... - vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0); - view.xyz /= view.w; - float linear_depth = -view.z; - } +void fragment() { + ... + vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0); + view.xyz /= view.w; + float linear_depth = -view.z; +} ``` Because the camera is facing the negative `z` direction, the position will have a negative `z` value. @@ -126,17 +126,17 @@ that the `CAMERA_MATRIX` is needed to transform the position from view space int it needs to be passed to the fragment shader with a varying. ``` - varying mat4 CAMERA; +varying mat4 CAMERA; - void vertex() { - CAMERA = CAMERA_MATRIX; - } +void vertex() { + CAMERA = CAMERA_MATRIX; +} - void fragment() { - ... - vec4 world = CAMERA * INV_PROJECTION_MATRIX * vec4(ndc, 1.0); - vec3 world_position = world.xyz / world.w; - } +void fragment() { + ... + vec4 world = CAMERA * INV_PROJECTION_MATRIX * vec4(ndc, 1.0); + vec3 world_position = world.xyz / world.w; +} ``` ## An optimization @@ -153,23 +153,23 @@ vertices, normals, colors, etc. Now, attach a script to the MeshInstance and use the following code: ``` - extends MeshInstance +extends MeshInstance - func _ready(): - # Create a single triangle out of vertices: - var verts = PoolVector3Array() - verts.append(Vector3(-1.0, -1.0, 0.0)) - verts.append(Vector3(-1.0, 3.0, 0.0)) - verts.append(Vector3(3.0, -1.0, 0.0)) +func _ready(): + # Create a single triangle out of vertices: + var verts = PoolVector3Array() + verts.append(Vector3(-1.0, -1.0, 0.0)) + verts.append(Vector3(-1.0, 3.0, 0.0)) + verts.append(Vector3(3.0, -1.0, 0.0)) - # Create an array of arrays. - # This could contain normals, colors, UVs, etc. - var mesh_array = [] - mesh_array.resize(Mesh.ARRAY_MAX) #required size for ArrayMesh Array - mesh_array[Mesh.ARRAY_VERTEX] = verts #position of vertex array in ArrayMesh Array + # Create an array of arrays. + # This could contain normals, colors, UVs, etc. + var mesh_array = [] + mesh_array.resize(Mesh.ARRAY_MAX) #required size for ArrayMesh Array + mesh_array[Mesh.ARRAY_VERTEX] = verts #position of vertex array in ArrayMesh Array - # Create mesh from mesh_array: - mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, mesh_array) + # Create mesh from mesh_array: + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, mesh_array) ``` Note: diff --git a/03_usage/16_shaders/07_using_viewport_as_texture.md b/03_usage/16_shaders/07_using_viewport_as_texture.md index 1f5f327..486f7dd 100644 --- a/03_usage/16_shaders/07_using_viewport_as_texture.md +++ b/03_usage/16_shaders/07_using_viewport_as_texture.md @@ -57,11 +57,11 @@ Note: ColorRect > CanvasItem > Material > Material > click / Edit > ShaderMaterial > Shader > `New Shader` > click / Edit: ``` - shader_type canvas_item; +shader_type canvas_item; - void fragment() { - COLOR = vec4(UV.x, UV.y, 0.5, 1.0); - } +void fragment() { + COLOR = vec4(UV.x, UV.y, 0.5, 1.0); +} ``` The above code renders a gradient like the one below. @@ -109,7 +109,7 @@ the sphere in a nice way? One solution is to use a function that repeats on the `sin` and `cos` are two such functions. Let's apply them to the texture and see what happens. ``` - COLOR.xyz = vec3(sin(UV.x * 3.14159 * 4.0) * cos(UV.y * 3.14159 * 4.0) * 0.5 + 0.5); +COLOR.xyz = vec3(sin(UV.x * 3.14159 * 4.0) * cos(UV.y * 3.14159 * 4.0) * 0.5 + 0.5); ``` ![](img/planet_sincos.png) @@ -134,14 +134,14 @@ a pinch point on the pole. The following code converts the `UVs` into Cartesian coordinates. ``` - float theta = UV.y * 3.14159; - float phi = UV.x * 3.14159 * 2.0; - vec3 unit = vec3(0.0, 0.0, 0.0); +float theta = UV.y * 3.14159; +float phi = UV.x * 3.14159 * 2.0; +vec3 unit = vec3(0.0, 0.0, 0.0); - unit.x = sin(phi) * sin(theta); - unit.y = cos(theta) * -1.0; - unit.z = cos(phi) * sin(theta); - unit = normalize(unit); +unit.x = sin(phi) * sin(theta); +unit.y = cos(theta) * -1.0; +unit.z = cos(phi) * sin(theta); +unit = normalize(unit); ``` And if we use `unit` as an output `COLOR` value, we get: @@ -152,28 +152,28 @@ Now that we can calculate the 3D position of the surface of the sphere, we can u to make the planet. We will be using this noise function directly from a `Shadertoy ( https://www.shadertoy.com/view/Xsl3Dl )`: ``` - vec3 hash(vec3 p) { - p = vec3(dot(p, vec3(127.1, 311.7, 74.7)), - dot(p, vec3(269.5, 183.3, 246.1)), - dot(p, vec3(113.5, 271.9, 124.6))); +vec3 hash(vec3 p) { + p = vec3(dot(p, vec3(127.1, 311.7, 74.7)), + dot(p, vec3(269.5, 183.3, 246.1)), + dot(p, vec3(113.5, 271.9, 124.6))); - return -1.0 + 2.0 * fract(sin(p) * 43758.5453123); - } + return -1.0 + 2.0 * fract(sin(p) * 43758.5453123); +} - float noise(vec3 p) { - vec3 i = floor(p); - vec3 f = fract(p); - vec3 u = f * f * (3.0 - 2.0 * f); +float noise(vec3 p) { + vec3 i = floor(p); + vec3 f = fract(p); + vec3 u = f * f * (3.0 - 2.0 * f); - return mix(mix(mix(dot(hash(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)), - dot(hash(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)), u.x), - mix(dot(hash(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)), - dot(hash(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)), u.x), u.y), - mix(mix(dot(hash(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)), - dot(hash(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)), u.x), - mix(dot(hash(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)), - dot(hash(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)), u.x), u.y), u.z ); - } + return mix(mix(mix(dot(hash(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)), + dot(hash(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)), u.x), + mix(dot(hash(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)), + dot(hash(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)), u.x), u.y), + mix(mix(dot(hash(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)), + dot(hash(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)), u.x), + mix(dot(hash(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)), + dot(hash(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)), u.x), u.y), u.z ); +} ``` Note: @@ -182,8 +182,8 @@ Note: Now to use `noise`, add the following to the `fragment` function: ``` - float n = noise(unit * 5.0); - COLOR.xyz = vec3(n * 0.5 + 0.5); +float n = noise(unit * 5.0); +COLOR.xyz = vec3(n * 0.5 + 0.5); ``` ![](img/planet_noise.png) @@ -206,7 +206,7 @@ However, `lerp` is typically reserved for mixing two floats together; `mix` can values whether it be floats or vector types. ``` - COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), n * 0.5 + 0.5); +COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), n * 0.5 + 0.5); ``` The first color is blue for the ocean. The second color is a kind of reddish color (because @@ -221,7 +221,7 @@ land and sea. In order to do that, we will change the last term to `smoothstep(- And thus the whole line becomes: ``` - COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), smoothstep(-0.1, 0.0, n)); +COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), smoothstep(-0.1, 0.0, n)); ``` What `smoothstep` does is return `0` if the third argument is below the first and `1` if the @@ -239,10 +239,10 @@ another, and so on. What we will do is calculate `n` with four lines of shader c instead of just one. `n` becomes: ``` - float n = noise(unit * 5.0) * 0.5; - n += noise(unit * 10.0) * 0.25; - n += noise(unit * 20.0) * 0.125; - n += noise(unit * 40.0) * 0.0625; +float n = noise(unit * 5.0) * 0.5; +n += noise(unit * 10.0) * 0.25; +n += noise(unit * 20.0) * 0.125; +n += noise(unit * 40.0) * 0.0625; ``` And now the planet looks like: @@ -260,7 +260,7 @@ So we want the ocean to shine a little more than the land. We can do this by pas into the `alpha` channel of our output `COLOR` and using it as a Roughness map. ``` - COLOR.a = 0.3 + 0.7 * smoothstep(-0.1, 0.0, n); +COLOR.a = 0.3 + 0.7 * smoothstep(-0.1, 0.0, n); ``` This line returns `0.3` for water and `1.0` for land. This means that the land is going to be quite @@ -287,7 +287,7 @@ go into the `Viewport` and enable the "Transparent Bg" property. Since we are no rendering one transparent object on top of another, we want to enable `blend_premul_alpha`: ``` - render_mode blend_premul_alpha; +render_mode blend_premul_alpha; ``` This pre-multiplies the colors by the `alpha` value and then blends them correctly together. Typically, diff --git a/03_usage/16_shaders/08_custom_postprocessing.md b/03_usage/16_shaders/08_custom_postprocessing.md index d240ce9..b6159a7 100644 --- a/03_usage/16_shaders/08_custom_postprocessing.md +++ b/03_usage/16_shaders/08_custom_postprocessing.md @@ -53,36 +53,36 @@ Note: manually, like so: ``` - // Inside the Shader. - uniform sampler2D ViewportTexture; - ``` +// Inside the Shader. +uniform sampler2D ViewportTexture; +``` And you can pass the texture into the shader from GDScript like so: ``` - # In GDScript. - func _ready(): - $Sprite.material.set_shader_param("ViewportTexture", $Viewport.get_texture()) - ``` +# In GDScript. +func _ready(): + $Sprite.material.set_shader_param("ViewportTexture", $Viewport.get_texture()) +``` Copy the following code to your shader. The above code is a single pass edge detection filter, a `Sobel filter ( https://en.wikipedia.org/wiki/Sobel_operator )`. ``` - shader_type canvas_item; +shader_type canvas_item; - void fragment() { - vec3 col = -8.0 * texture(TEXTURE, UV).xyz; - col += texture(TEXTURE, UV + vec2(0.0, SCREEN_PIXEL_SIZE.y)).xyz; - col += texture(TEXTURE, UV + vec2(0.0, -SCREEN_PIXEL_SIZE.y)).xyz; - col += texture(TEXTURE, UV + vec2(SCREEN_PIXEL_SIZE.x, 0.0)).xyz; - col += texture(TEXTURE, UV + vec2(-SCREEN_PIXEL_SIZE.x, 0.0)).xyz; - col += texture(TEXTURE, UV + SCREEN_PIXEL_SIZE.xy).xyz; - col += texture(TEXTURE, UV - SCREEN_PIXEL_SIZE.xy).xyz; - col += texture(TEXTURE, UV + vec2(-SCREEN_PIXEL_SIZE.x, SCREEN_PIXEL_SIZE.y)).xyz; - col += texture(TEXTURE, UV + vec2(SCREEN_PIXEL_SIZE.x, -SCREEN_PIXEL_SIZE.y)).xyz; - COLOR.xyz = col; - } +void fragment() { + vec3 col = -8.0 * texture(TEXTURE, UV).xyz; + col += texture(TEXTURE, UV + vec2(0.0, SCREEN_PIXEL_SIZE.y)).xyz; + col += texture(TEXTURE, UV + vec2(0.0, -SCREEN_PIXEL_SIZE.y)).xyz; + col += texture(TEXTURE, UV + vec2(SCREEN_PIXEL_SIZE.x, 0.0)).xyz; + col += texture(TEXTURE, UV + vec2(-SCREEN_PIXEL_SIZE.x, 0.0)).xyz; + col += texture(TEXTURE, UV + SCREEN_PIXEL_SIZE.xy).xyz; + col += texture(TEXTURE, UV - SCREEN_PIXEL_SIZE.xy).xyz; + col += texture(TEXTURE, UV + vec2(-SCREEN_PIXEL_SIZE.x, SCREEN_PIXEL_SIZE.y)).xyz; + col += texture(TEXTURE, UV + vec2(SCREEN_PIXEL_SIZE.x, -SCREEN_PIXEL_SIZE.y)).xyz; + COLOR.xyz = col; +} ``` Note: @@ -125,39 +125,39 @@ to each of the `ViewportContainers`. The order in which you apply the shaders does not matter: ``` - shader_type canvas_item; +shader_type canvas_item; - // Blurs the screen in the X-direction. - void fragment() { - vec3 col = texture(TEXTURE, UV).xyz * 0.16; - col += texture(TEXTURE, UV + vec2(SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.15; - col += texture(TEXTURE, UV + vec2(-SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.15; - col += texture(TEXTURE, UV + vec2(2.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.12; - col += texture(TEXTURE, UV + vec2(2.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.12; - col += texture(TEXTURE, UV + vec2(3.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.09; - col += texture(TEXTURE, UV + vec2(3.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.09; - col += texture(TEXTURE, UV + vec2(4.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.05; - col += texture(TEXTURE, UV + vec2(4.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.05; - COLOR.xyz = col; - } +// Blurs the screen in the X-direction. +void fragment() { + vec3 col = texture(TEXTURE, UV).xyz * 0.16; + col += texture(TEXTURE, UV + vec2(SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.15; + col += texture(TEXTURE, UV + vec2(-SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.15; + col += texture(TEXTURE, UV + vec2(2.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.12; + col += texture(TEXTURE, UV + vec2(2.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.12; + col += texture(TEXTURE, UV + vec2(3.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.09; + col += texture(TEXTURE, UV + vec2(3.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.09; + col += texture(TEXTURE, UV + vec2(4.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.05; + col += texture(TEXTURE, UV + vec2(4.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.05; + COLOR.xyz = col; +} ``` ``` - shader_type canvas_item; +shader_type canvas_item; - // Blurs the screen in the Y-direction. - void fragment() { - vec3 col = texture(TEXTURE, UV).xyz * 0.16; - col += texture(TEXTURE, UV + vec2(0.0, SCREEN_PIXEL_SIZE.y)).xyz * 0.15; - col += texture(TEXTURE, UV + vec2(0.0, -SCREEN_PIXEL_SIZE.y)).xyz * 0.15; - col += texture(TEXTURE, UV + vec2(0.0, 2.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.12; - col += texture(TEXTURE, UV + vec2(0.0, 2.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.12; - col += texture(TEXTURE, UV + vec2(0.0, 3.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.09; - col += texture(TEXTURE, UV + vec2(0.0, 3.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.09; - col += texture(TEXTURE, UV + vec2(0.0, 4.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.05; - col += texture(TEXTURE, UV + vec2(0.0, 4.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.05; - COLOR.xyz = col; - } +// Blurs the screen in the Y-direction. +void fragment() { + vec3 col = texture(TEXTURE, UV).xyz * 0.16; + col += texture(TEXTURE, UV + vec2(0.0, SCREEN_PIXEL_SIZE.y)).xyz * 0.15; + col += texture(TEXTURE, UV + vec2(0.0, -SCREEN_PIXEL_SIZE.y)).xyz * 0.15; + col += texture(TEXTURE, UV + vec2(0.0, 2.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.12; + col += texture(TEXTURE, UV + vec2(0.0, 2.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.12; + col += texture(TEXTURE, UV + vec2(0.0, 3.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.09; + col += texture(TEXTURE, UV + vec2(0.0, 3.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.09; + col += texture(TEXTURE, UV + vec2(0.0, 4.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.05; + col += texture(TEXTURE, UV + vec2(0.0, 4.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.05; + COLOR.xyz = col; +} ``` Using the above code, you should end up with a full screen blur effect like below. diff --git a/03_usage/16_shaders/09_making_trees.md b/03_usage/16_shaders/09_making_trees.md index c233be6..39f57da 100644 --- a/03_usage/16_shaders/09_making_trees.md +++ b/03_usage/16_shaders/09_making_trees.md @@ -33,31 +33,31 @@ This is a bit exaggerated, but the idea is that color indicates how much sway af This is a simple example of a shader for leaves: ``` - shader_type spatial; - render_mode depth_draw_alpha_prepass, cull_disabled, world_vertex_coords; +shader_type spatial; +render_mode depth_draw_alpha_prepass, cull_disabled, world_vertex_coords; ``` This is a spatial shader. There is no front/back culling (so leaves can be seen from both sides), and alpha prepass is used, so there are less depth artifacts that result from using transparency (and leaves cast shadow). Finally, for the sway effect, world coordinates are recommended, so the tree can be duplicated, moved, etc. and it will still work together with other trees. ``` - uniform sampler2D texture_albedo : hint_albedo; - uniform vec4 transmission : hint_color; +uniform sampler2D texture_albedo : hint_albedo; +uniform vec4 transmission : hint_color; ``` Here, the texture is read, as well as a transmission color, which is used to add some back-lighting to the leaves, simulating subsurface scattering. ``` - uniform float sway_speed = 1.0; - uniform float sway_strength = 0.05; - uniform float sway_phase_len = 8.0; +uniform float sway_speed = 1.0; +uniform float sway_strength = 0.05; +uniform float sway_phase_len = 8.0; - void vertex() { - float strength = COLOR.r * sway_strength; - VERTEX.x += sin(VERTEX.x * sway_phase_len * 1.123 + TIME * sway_speed) * strength; - VERTEX.y += sin(VERTEX.y * sway_phase_len + TIME * sway_speed * 1.12412) * strength; - VERTEX.z += sin(VERTEX.z * sway_phase_len * 0.9123 + TIME * sway_speed * 1.3123) * strength; - } +void vertex() { + float strength = COLOR.r * sway_strength; + VERTEX.x += sin(VERTEX.x * sway_phase_len * 1.123 + TIME * sway_speed) * strength; + VERTEX.y += sin(VERTEX.y * sway_phase_len + TIME * sway_speed * 1.12412) * strength; + VERTEX.z += sin(VERTEX.z * sway_phase_len * 0.9123 + TIME * sway_speed * 1.3123) * strength; +} ``` This is the code to create the sway of the leaves. It's basic (just uses a sinewave multiplying by the time and axis position, but works well). Notice that the strength is multiplied by the color. Every axis uses a different small near 1.0 multiplication factor so axes don't appear in sync. @@ -66,14 +66,14 @@ This is the code to create the sway of the leaves. It's basic (just uses a sinew Finally, all that's left is the fragment shader: ``` - void fragment() { - vec4 albedo_tex = texture(texture_albedo, UV); - ALBEDO = albedo_tex.rgb; - ALPHA = albedo_tex.a; - METALLIC = 0.0; - ROUGHNESS = 1.0; - TRANSMISSION = transmission.rgb; - } +void fragment() { + vec4 albedo_tex = texture(texture_albedo, UV); + ALBEDO = albedo_tex.rgb; + ALPHA = albedo_tex.a; + METALLIC = 0.0; + ROUGHNESS = 1.0; + TRANSMISSION = transmission.rgb; +} ``` And this is pretty much it. diff --git a/03_usage/16_shaders/shader_reference/01_shading_language.md b/03_usage/16_shaders/shader_reference/01_shading_language.md index 5abc0bc..561a90a 100644 --- a/03_usage/16_shaders/shader_reference/01_shading_language.md +++ b/03_usage/16_shaders/shader_reference/01_shading_language.md @@ -56,17 +56,17 @@ Casting of types of different size is also not allowed. Conversion must be done Example: ``` - float a = 2; // invalid - float a = 2.0; // valid - float a = float(2); // valid +float a = 2; // invalid +float a = 2.0; // valid +float a = float(2); // valid ``` Default integer constants are signed, so casting is always needed to convert to unsigned: ``` - int a = 2; // valid - uint a = 2; // invalid - uint a = uint(2); // valid +int a = 2; // valid +uint a = 2; // invalid +uint a = uint(2); // valid ``` ### Members @@ -84,13 +84,13 @@ position of an object in a mat4 you use `m[3][1]`. Construction of vector types must always pass: ``` - // The required amount of scalars - vec4 a = vec4(0.0, 1.0, 2.0, 3.0); - // Complementary vectors and/or scalars - vec4 a = vec4(vec2(0.0, 1.0), vec2(2.0, 3.0)); - vec4 a = vec4(vec3(0.0, 1.0, 2.0), 3.0); - // A single scalar for the whole vector - vec4 a = vec4(0.0); +// The required amount of scalars +vec4 a = vec4(0.0, 1.0, 2.0, 3.0); +// Complementary vectors and/or scalars +vec4 a = vec4(vec2(0.0, 1.0), vec2(2.0, 3.0)); +vec4 a = vec4(vec3(0.0, 1.0, 2.0), 3.0); +// A single scalar for the whole vector +vec4 a = vec4(0.0); ``` Construction of matrix types requires vectors of the same dimension as the matrix. You can @@ -98,9 +98,9 @@ also build a diagonal matrix using `matx(float)` syntax. Accordingly, `mat4(1.0) an identity matrix. ``` - mat2 m2 = mat2(vec2(1.0, 0.0), vec2(0.0, 1.0)); - mat3 m3 = mat3(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0)); - mat4 identity = mat4(1.0); +mat2 m2 = mat2(vec2(1.0, 0.0), vec2(0.0, 1.0)); +mat3 m3 = mat3(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0)); +mat4 identity = mat4(1.0); ``` Matrices can also be built from a matrix of another dimension. @@ -110,9 +110,9 @@ set to the values they would have in an identity matrix. If a smaller matrix is from a larger matrix, the top, left submatrix of the larger matrix is used. ``` - mat3 basis = mat3(WORLD_MATRIX); - mat4 m4 = mat4(basis); - mat2 m2 = mat2(m4); +mat3 basis = mat3(WORLD_MATRIX); +mat4 m4 = mat4(basis); +mat2 m2 = mat2(m4); ``` ### Swizzling @@ -121,16 +121,16 @@ It is possible to obtain any combination of components in any order, as long as is another vector type (or scalar). This is easier shown than explained: ``` - vec4 a = vec4(0.0, 1.0, 2.0, 3.0); - vec3 b = a.rgb; // Creates a vec3 with vec4 components. - vec3 b = a.ggg; // Also valid; creates a vec3 and fills it with a single vec4 component. - vec3 b = a.bgr; // "b" will be vec3(2.0, 1.0, 0.0). - vec3 b = a.xyz; // Also rgba, xyzw are equivalent. - vec3 b = a.stp; // And stpq (for texture coordinates). - float c = b.w; // Invalid, because "w" is not present in vec3 b. - vec3 c = b.xrt; // Invalid, mixing different styles is forbidden. - b.rrr = a.rgb; // Invalid, assignment with duplication. - b.bgr = a.rgb; // Valid assignment. "b"'s "blue" component will be "a"'s "red" and vice versa. +vec4 a = vec4(0.0, 1.0, 2.0, 3.0); +vec3 b = a.rgb; // Creates a vec3 with vec4 components. +vec3 b = a.ggg; // Also valid; creates a vec3 and fills it with a single vec4 component. +vec3 b = a.bgr; // "b" will be vec3(2.0, 1.0, 0.0). +vec3 b = a.xyz; // Also rgba, xyzw are equivalent. +vec3 b = a.stp; // And stpq (for texture coordinates). +float c = b.w; // Invalid, because "w" is not present in vec3 b. +vec3 c = b.xrt; // Invalid, mixing different styles is forbidden. +b.rrr = a.rgb; // Invalid, assignment with duplication. +b.bgr = a.rgb; // Valid assignment. "b"'s "blue" component will be "a"'s "red" and vice versa. ``` ### Precision @@ -138,9 +138,9 @@ is another vector type (or scalar). This is easier shown than explained: It is possible to add precision modifiers to datatypes; use them for uniforms, variables, arguments and varyings: ``` - lowp vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // low precision, usually 8 bits per component mapped to 0-1 - mediump vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // medium precision, usually 16 bits or half float - highp vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // high precision, uses full float or integer range (default) +lowp vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // low precision, usually 8 bits per component mapped to 0-1 +mediump vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // medium precision, usually 16 bits or half float +highp vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // high precision, uses full float or integer range (default) ``` @@ -165,50 +165,50 @@ Local arrays are declared in functions. They can use all of the allowed datatype The array declaration follows a C-style syntax: `[const] + [precision] + typename + identifier + [array size]`. ``` - void fragment() { - float arr[3]; - } +void fragment() { + float arr[3]; +} ``` They can be initialized at the beginning like: ``` - float float_arr[3] = float[3] (1.0, 0.5, 0.0); // first constructor +float float_arr[3] = float[3] (1.0, 0.5, 0.0); // first constructor - int int_arr[3] = int[] (2, 1, 0); // second constructor +int int_arr[3] = int[] (2, 1, 0); // second constructor - vec2 vec2_arr[3] = { vec2(1.0, 1.0), vec2(0.5, 0.5), vec2(0.0, 0.0) }; // third constructor +vec2 vec2_arr[3] = { vec2(1.0, 1.0), vec2(0.5, 0.5), vec2(0.0, 0.0) }; // third constructor - bool bool_arr[] = { true, true, false }; // fourth constructor - size is defined automatically from the element count +bool bool_arr[] = { true, true, false }; // fourth constructor - size is defined automatically from the element count ``` You can declare multiple arrays (even with different sizes) in one expression: ``` - float a[3] = float[3] (1.0, 0.5, 0.0), - b[2] = { 1.0, 0.5 }, - c[] = { 0.7 }, - d = 0.0, - e[5]; +float a[3] = float[3] (1.0, 0.5, 0.0), +b[2] = { 1.0, 0.5 }, +c[] = { 0.7 }, +d = 0.0, +e[5]; ``` To access an array element, use the indexing syntax: ``` - float arr[3]; +float arr[3]; - arr[0] = 1.0; // setter +arr[0] = 1.0; // setter - COLOR.r = arr[0]; // getter +COLOR.r = arr[0]; // getter ``` Arrays also have a built-in function `.length()` (not to be confused with the built-in `length()` function). It doesn't accept any parameters and will return the array's size. ``` - float arr[] = { 0.0, 1.0, 0.5, -1.0 }; - for (int i = 0; i < arr.length(); i++) { - // ... - } +float arr[] = { 0.0, 1.0, 0.5, -1.0 }; +for (int i = 0; i < arr.length(); i++) { + // ... +} ``` Note: @@ -221,36 +221,36 @@ Note: Use the `const` keyword before the variable declaration to make that variable immutable, which means that it cannot be modified. All basic types, except samplers can be declared as constants. Accessing and using a constant value is slightly faster than using a uniform. Constants must be initialized at their declaration. ``` - const vec2 a = vec2(0.0, 1.0); - vec2 b; +const vec2 a = vec2(0.0, 1.0); +vec2 b; - a = b; // invalid - b = a; // valid +a = b; // invalid +b = a; // valid ``` Constants cannot be modified and additionally cannot have hints, but multiple of them (if they have the same type) can be declared in a single expression e.g ``` - const vec2 V1 = vec2(1, 1), V2 = vec2(2, 2); +const vec2 V1 = vec2(1, 1), V2 = vec2(2, 2); ``` Similar to variables, arrays can also be declared with `const`. ``` - const float arr[] = { 1.0, 0.5, 0.0 }; +const float arr[] = { 1.0, 0.5, 0.0 }; - arr[0] = 1.0; // invalid +arr[0] = 1.0; // invalid - COLOR.r = arr[0]; // valid +COLOR.r = arr[0]; // valid ``` Constants can be declared both globally (outside of any function) or locally (inside a function). Global constants are useful when you want to have access to a value throughout your shader that does not need to be modified. Like uniforms, global constants are shared between all shader stages, but they are not accessible outside of the shader. ``` - shader_type spatial; +shader_type spatial; - const float PI = 3.14159265358979323846; +const float PI = 3.14159265358979323846; ``` ## Operators @@ -279,41 +279,41 @@ Pandemonium shading language supports the same set of operators as GLSL ES 3.0. Pandemonium Shading language supports the most common types of flow control: ``` - // if and else - if (cond) { +// if and else +if (cond) { - } else { +} else { - } +} - // switch - switch(i) { // signed integer expression - case -1: - break; - case 0: - return; // break or return - case 1: // pass-through - case 2: - break; - //... - default: // optional - break; - } +// switch +switch(i) { // signed integer expression + case -1: + break; + case 0: + return; // break or return + case 1: // pass-through + case 2: + break; + //... + default: // optional + break; +} - // for loops - for (int i = 0; i < 10; i++) { +// for loops +for (int i = 0; i < 10; i++) { - } +} - // while - while (true) { +// while +while (true) { - } +} - // do while - do { +// do while +do { - } while(true); +} while(true); ``` Keep in mind that, in modern GPUs, an infinite loop can exist and can freeze your application (including editor). @@ -333,15 +333,15 @@ Fragment and light functions can use the **discard** keyword. If used, the fragm It is possible to define functions in a Pandemonium shader. They use the following syntax: ``` - ret_type func_name(args) { - return ret_type; // if returning a value - } +ret_type func_name(args) { + return ret_type; // if returning a value +} - // a more specific example: +// a more specific example: - int sum2(int a, int b) { - return a + b; - } +int sum2(int a, int b) { + return a + b; +} ``` @@ -357,9 +357,9 @@ Function arguments can have special qualifiers: Example below: ``` - void sum2(int a, int b, inout int result) { - result = a + b; - } +void sum2(int a, int b, inout int result) { + result = a + b; +} ``` ### Varyings @@ -369,74 +369,74 @@ used. They are set for every primitive vertex in the *vertex processor*, and the value is interpolated for every pixel in the *fragment processor*. ``` - shader_type spatial; +shader_type spatial; - varying vec3 some_color; +varying vec3 some_color; - void vertex() { - some_color = NORMAL; // Make the normal the color. - } +void vertex() { + some_color = NORMAL; // Make the normal the color. +} - void fragment() { - ALBEDO = some_color; - } +void fragment() { + ALBEDO = some_color; +} - void light() { - DIFFUSE_LIGHT = some_color * 100; // optionally - } +void light() { + DIFFUSE_LIGHT = some_color * 100; // optionally +} ``` Varying can also be an array: ``` - shader_type spatial; +shader_type spatial; - varying float var_arr[3]; +varying float var_arr[3]; - void vertex() { - var_arr[0] = 1.0; - var_arr[1] = 0.0; - } +void vertex() { + var_arr[0] = 1.0; + var_arr[1] = 0.0; +} - void fragment() { - ALBEDO = vec3(var_arr[0], var_arr[1], var_arr[2]); // red color - } +void fragment() { + ALBEDO = vec3(var_arr[0], var_arr[1], var_arr[2]); // red color +} ``` It's also possible to send data from *fragment* to *light* processors using *varying* keyword. To do so you can assign it in the *fragment* and later use it in the *light* function. ``` - shader_type spatial; +shader_type spatial; - varying vec3 some_light; +varying vec3 some_light; - void fragment() { - some_light = ALBEDO * 100.0; // Make a shining light. - } +void fragment() { + some_light = ALBEDO * 100.0; // Make a shining light. +} - void light() { - DIFFUSE_LIGHT = some_light; - } +void light() { + DIFFUSE_LIGHT = some_light; +} ``` Note that varying may not be assigned in custom functions or a *light processor* function like: ``` - shader_type spatial; +shader_type spatial; - varying float test; +varying float test; - void foo() { - test = 0.0; // Error. - } +void foo() { + test = 0.0; // Error. +} - void vertex() { - test = 0.0; - } +void vertex() { + test = 0.0; +} - void light() { - test = 0.0; // Error too. - } +void light() { + test = 0.0; // Error too. +} ``` This limitation was introduced to prevent incorrect usage before initialization. @@ -447,17 +447,17 @@ Certain values are interpolated during the shading pipeline. You can modify how are done by using *interpolation qualifiers*. ``` - shader_type spatial; +shader_type spatial; - varying flat vec3 our_color; +varying flat vec3 our_color; - void vertex() { - our_color = COLOR.rgb; - } +void vertex() { + our_color = COLOR.rgb; +} - void fragment() { - ALBEDO = our_color; - } +void fragment() { + ALBEDO = our_color; +} ``` There are two possible interpolation qualifiers: @@ -477,15 +477,15 @@ When a shader is later assigned to a material, the uniforms will appear as edita Uniforms can't be written from within the shader. ``` - shader_type spatial; +shader_type spatial; - uniform float some_value; +uniform float some_value; ``` You can set uniforms in the editor in the material. Or you can set them through GDScript: ``` - material.set_shader_param("some_value", some_value) +material.set_shader_param("some_value", some_value) ``` Note: @@ -497,11 +497,11 @@ optional shader hints to make the compiler understand for what the uniform is used, and how the editor should allow users to modify it. ``` - shader_type spatial; +shader_type spatial; - uniform vec4 color : hint_color; - uniform float amount : hint_range(0, 1); - uniform vec4 other_color : hint_color = vec4(1.0); +uniform vec4 color : hint_color; +uniform float amount : hint_range(0, 1); +uniform vec4 other_color : hint_color = vec4(1.0); ``` It's important to understand that textures that are supplied as color require hints for proper sRGB->linear conversion (i.e. `hint_albedo`), as Pandemonium's 3D engine renders in linear color space. @@ -543,10 +543,10 @@ Note: Uniforms can also be assigned default values: ``` - shader_type spatial; +shader_type spatial; - uniform vec4 some_vector = vec4(0.0); - uniform vec4 some_color : hint_color = vec4(1.0); +uniform vec4 some_vector = vec4(0.0); +uniform vec4 some_color : hint_color = vec4(1.0); ``` ## Built-in variables diff --git a/03_usage/16_shaders/shader_reference/02_spatial_shader.md b/03_usage/16_shaders/shader_reference/02_spatial_shader.md index d86011e..b88a758 100644 --- a/03_usage/16_shaders/shader_reference/02_spatial_shader.md +++ b/03_usage/16_shaders/shader_reference/02_spatial_shader.md @@ -73,14 +73,14 @@ Users can disable the built-in modelview transform (projection will still happen it manually with the following code: ``` - shader_type spatial; - render_mode skip_vertex_transform; +shader_type spatial; +render_mode skip_vertex_transform; - void vertex() { - VERTEX = (MODELVIEW_MATRIX * vec4(VERTEX, 1.0)).xyz; - NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz); - // same as above for binormal and tangent, if normal mapping is used - } +void vertex() { + VERTEX = (MODELVIEW_MATRIX * vec4(VERTEX, 1.0)).xyz; + NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz); + // same as above for binormal and tangent, if normal mapping is used +} ``` Other built-ins, such as UV, UV2 and COLOR, are also passed through to the fragment function if not modified. @@ -212,9 +212,9 @@ each light type. Below is an example of a custom light function using a Lambertian lighting model: ``` - void light() { - DIFFUSE_LIGHT += clamp(dot(NORMAL, LIGHT), 0.0, 1.0) * ATTENUATION * ALBEDO; - } +void light() { + DIFFUSE_LIGHT += clamp(dot(NORMAL, LIGHT), 0.0, 1.0) * ATTENUATION * ALBEDO; +} ``` If you want the lights to add together, add the light contribution to `DIFFUSE_LIGHT` using `+=`, rather than overwriting it. diff --git a/03_usage/16_shaders/shader_reference/03_canvas_item_shader.md b/03_usage/16_shaders/shader_reference/03_canvas_item_shader.md index 802baa3..c45db73 100644 --- a/03_usage/16_shaders/shader_reference/03_canvas_item_shader.md +++ b/03_usage/16_shaders/shader_reference/03_canvas_item_shader.md @@ -55,13 +55,13 @@ The user can disable the built-in modelview transform (projection will still hap it manually with the following code: ``` - shader_type canvas_item; - render_mode skip_vertex_transform; +shader_type canvas_item; +render_mode skip_vertex_transform; - void vertex() { +void vertex() { - VERTEX = (EXTRA_MATRIX * (WORLD_MATRIX * vec4(VERTEX, 0.0, 1.0))).xy; - } + VERTEX = (EXTRA_MATRIX * (WORLD_MATRIX * vec4(VERTEX, 0.0, 1.0))).xy; +} ``` Note: @@ -71,19 +71,19 @@ Note: In order to get the world space coordinates of a vertex, you have to pass in a custom uniform like so: ``` - material.set_shader_param("global_transform", get_global_transform()) +material.set_shader_param("global_transform", get_global_transform()) ``` Then, in your vertex shader: ``` - uniform mat4 global_transform; - varying vec2 world_position; +uniform mat4 global_transform; +varying vec2 world_position; - void vertex(){ - world_position = (global_transform * vec4(VERTEX, 0.0, 1.0)).xy; - } +void vertex(){ + world_position = (global_transform * vec4(VERTEX, 0.0, 1.0)).xy; +} ``` `world_position` can then be used in either the vertex or fragment functions. @@ -125,7 +125,7 @@ manually. Pandemonium does not provide the texture color in the `COLOR` built-in the texture color for such nodes, use: ``` - COLOR = texture(TEXTURE, UV); +COLOR = texture(TEXTURE, UV); ``` This differs from the behavior of the built-in normal map. If a normal map is attached, Pandemonium uses @@ -134,7 +134,7 @@ map meant for use in 3D, it will appear inverted. In order to use it in your sha it to the `NORMALMAP` property. Pandemonium will handle converting it for use in 2D and overwriting `NORMAL`. ``` - NORMALMAP = texture(NORMAL_TEXTURE, UV).rgb; +NORMALMAP = texture(NORMAL_TEXTURE, UV).rgb; ``` diff --git a/03_usage/16_shaders/your_first_shader/02_your_first_2d_shader.md b/03_usage/16_shaders/your_first_shader/02_your_first_2d_shader.md index 5803837..a05a651 100644 --- a/03_usage/16_shaders/your_first_shader/02_your_first_2d_shader.md +++ b/03_usage/16_shaders/your_first_shader/02_your_first_2d_shader.md @@ -61,7 +61,7 @@ In Pandemonium, all shaders start with a line specifying what type of shader the It uses the following format: ``` - shader_type canvas_item; +shader_type canvas_item; ``` Because we are writing a CanvasItem shader, we specify `canvas_item` in the @@ -93,9 +93,9 @@ vectors see the `Vector math tutorial ( doc_vector_math )` `COLOR` is both an input variable to the fragment function and the final output from it. ``` - void fragment(){ - COLOR = vec4(0.4, 0.6, 0.9, 1.0); - } +void fragment(){ + COLOR = vec4(0.4, 0.6, 0.9, 1.0); +} ``` ![](img/blue-box.png) @@ -118,9 +118,9 @@ other functions or to assign values to `COLOR` directly. ![](img/iconuv.png) ``` - void fragment() { - COLOR = vec4(UV, 0.5, 1.0); - } +void fragment() { + COLOR = vec4(UV, 0.5, 1.0); +} ``` ![](img/UV.png) @@ -131,10 +131,10 @@ When you want to adjust a color in a Sprite you cannot just adjust the color from the texture manually like in the code below. ``` - void fragment(){ - //this shader will result in an all white rectangle - COLOR.b = 1.0; - } +void fragment(){ + //this shader will result in an all white rectangle + COLOR.b = 1.0; +} ``` The default fragment function reads from a texture and displays it. When you @@ -145,10 +145,10 @@ can be accessed in the shader using `TEXTURE`. Use it together with `UV` and `texture` to draw the Sprite. ``` - void fragment(){ - COLOR = texture(TEXTURE, UV); //read from texture - COLOR.b = 1.0; //set blue channel to 1.0 - } +void fragment(){ + COLOR = texture(TEXTURE, UV); //read from texture + COLOR.b = 1.0; //set blue channel to 1.0 +} ``` ![](img/blue-tex.png) @@ -161,7 +161,7 @@ the entire shader. You can use uniforms by defining them at the top of your shader like so: ``` - uniform float size; +uniform float size; ``` For more information about usage see the `Shading Language doc @@ -170,12 +170,12 @@ For more information about usage see the `Shading Language doc Add a uniform to change the amount of blue in our Sprite. ``` - uniform float blue = 1.0; // you can assign a default value to uniforms +uniform float blue = 1.0; // you can assign a default value to uniforms - void fragment(){ - COLOR = texture(TEXTURE, UV); //read from texture - COLOR.b = blue; - } +void fragment(){ + COLOR = texture(TEXTURE, UV); //read from texture + COLOR.b = blue; +} ``` Now you can change the amount of blue in the Sprite from the editor. Look back @@ -191,8 +191,8 @@ which is called on the node's material resource. With a Sprite node, the following code can be used to set the `blue` uniform. ``` - var blue_value = 1.0 - material.set_shader_param("blue", blue_value) +var blue_value = 1.0 +material.set_shader_param("blue", blue_value) ``` Note that the name of the uniform is a string. The string must match exactly @@ -214,19 +214,19 @@ viewport, or parent nodes). You can offset the vertices by directly adding to `VERTEX`. ``` - void vertex() { - VERTEX += vec2(10.0, 0.0); - } +void vertex() { + VERTEX += vec2(10.0, 0.0); +} ``` Combined with the `TIME` built-in variable, this can be used for simple animation. ``` - void vertex() { - // Animate Sprite moving in big circle around its location - VERTEX += vec2(cos(TIME)*100.0, sin(TIME)*100.0); - } +void vertex() { + // Animate Sprite moving in big circle around its location + VERTEX += vec2(cos(TIME)*100.0, sin(TIME)*100.0); +} ``` ## Conclusion diff --git a/03_usage/16_shaders/your_first_shader/03_your_first_3d_shader.md b/03_usage/16_shaders/your_first_shader/03_your_first_3d_shader.md index 24a4acb..25594fc 100644 --- a/03_usage/16_shaders/your_first_shader/03_your_first_3d_shader.md +++ b/03_usage/16_shaders/your_first_shader/03_your_first_3d_shader.md @@ -105,7 +105,7 @@ type of shader they are. We set the variable `shader_type` to `spatial` because this is a spatial shader. ``` - shader_type spatial; +shader_type spatial; ``` Next we will define the `vertex()` function. The `vertex()` function @@ -116,18 +116,18 @@ make our flat plane appear like a little terrain. We define the vertex shader like so: ``` - void vertex() { +void vertex() { - } +} ``` With nothing in the `vertex()` function, Pandemonium will use its default vertex shader. We can easily start to make changes by adding a single line: ``` - void vertex() { - VERTEX.y += cos(VERTEX.x) * sin(VERTEX.z); - } +void vertex() { + VERTEX.y += cos(VERTEX.x) * sin(VERTEX.z); +} ``` Adding this line, you should get an image like the one below. @@ -144,9 +144,9 @@ What we want to achieve is the look of little hills; after all. `cos` and `cos` and `sin` functions. ``` - void vertex() { - VERTEX.y += cos(VERTEX.x * 4.0) * sin(VERTEX.z * 4.0); - } +void vertex() { + VERTEX.y += cos(VERTEX.x * 4.0) * sin(VERTEX.z * 4.0); +} ``` ![](img/cos4.png) @@ -167,7 +167,7 @@ To access a texture in a shader add the following code near the top of your shader, outside the `vertex()` function. ``` - uniform sampler2D noise; +uniform sampler2D noise; ``` This will allow you to send a noise texture to the shader. Now look in the @@ -197,8 +197,8 @@ values are the same, so we can use any one of the channels as the height. In this case we'll use the `r`, or `x` channel. ``` - float height = texture(noise, VERTEX.xz / 2.0 + 0.5).x; - VERTEX.y += height; +float height = texture(noise, VERTEX.xz / 2.0 + 0.5).x; +VERTEX.y += height; ``` Note: `xyzw` is the same as `rgba` in GLSL, so instead of `texture().x` @@ -224,7 +224,7 @@ that can be used in the shader. To use a uniform, you declare it in your Let's make a uniform that changes the height of the terrain. ``` - uniform float height_scale = 0.5; +uniform float height_scale = 0.5; ``` @@ -235,8 +235,8 @@ passed from GDScript takes precedence over the value used to initialize it in the shader. ``` - # called from the MeshInstance - mesh.material.set_shader_param("height_scale", 0.5) +# called from the MeshInstance +mesh.material.set_shader_param("height_scale", 0.5) ``` Note: @@ -253,7 +253,7 @@ uniform variable anywhere inside your `Shader( Shader )`. Here, we will use it to set the height value instead of arbitrarily multiplying by `0.5`. ``` - VERTEX.y += height * height_scale; +VERTEX.y += height * height_scale; ``` Now it looks much better. @@ -297,7 +297,7 @@ Instead we will rely on the NoiseTexture again to calculate normals for us. We do that by passing in a second noise texture. ``` - uniform sampler2D normalmap; +uniform sampler2D normalmap; ``` Set this second uniform texture to another NoiseTexture with another @@ -310,8 +310,8 @@ assign it in the `fragment()` function. The `fragment()` function will be explained in more detail in the next part of this tutorial. ``` - void fragment() { - } +void fragment() { +} ``` When we have normals that correspond to a specific vertex we set `NORMAL`, but @@ -328,22 +328,22 @@ Above the `vertex()` define a `vec2` called `tex_position`. And inside the `vertex()` function assign `VERTEX.xz` to `tex_position`. ``` - varying vec2 tex_position; +varying vec2 tex_position; - void vertex() { - ... - tex_position = VERTEX.xz / 2.0 + 0.5; - float height = texture(noise, tex_position).x; - ... - } +void vertex() { + ... + tex_position = VERTEX.xz / 2.0 + 0.5; + float height = texture(noise, tex_position).x; + ... +} ``` And now we can access `tex_position` from the `fragment()` function. ``` - void fragment() { - NORMALMAP = texture(normalmap, tex_position).xyz; - } +void fragment() { + NORMALMAP = texture(normalmap, tex_position).xyz; +} ``` With the normals in place the light now reacts to the height of the mesh @@ -359,23 +359,23 @@ Here is the full code for this tutorial. You can see it is not very long as Pandemonium handles most of the difficult stuff for you. ``` - shader_type spatial; +shader_type spatial; - uniform float height_scale = 0.5; - uniform sampler2D noise; - uniform sampler2D normalmap; +uniform float height_scale = 0.5; +uniform sampler2D noise; +uniform sampler2D normalmap; - varying vec2 tex_position; +varying vec2 tex_position; - void vertex() { - tex_position = VERTEX.xz / 2.0 + 0.5; - float height = texture(noise, tex_position).x; - VERTEX.y += height * height_scale; - } +void vertex() { + tex_position = VERTEX.xz / 2.0 + 0.5; + float height = texture(noise, tex_position).x; + VERTEX.y += height * height_scale; +} - void fragment() { - NORMALMAP = texture(normalmap, tex_position).xyz; - } +void fragment() { + NORMALMAP = texture(normalmap, tex_position).xyz; +} ``` That is everything for this part. Hopefully, you now understand the basics of diff --git a/03_usage/16_shaders/your_first_shader/04_your_second_3d_shader.md b/03_usage/16_shaders/your_first_shader/04_your_second_3d_shader.md index 2833361..1248519 100644 --- a/03_usage/16_shaders/your_first_shader/04_your_second_3d_shader.md +++ b/03_usage/16_shaders/your_first_shader/04_your_second_3d_shader.md @@ -32,7 +32,7 @@ For example, if you do not want to have lights affect an object, set the render mode to `unshaded`: ``` - render_mode unshaded; +render_mode unshaded; ``` You can also stack multiple render modes together. For example, if you want to @@ -40,7 +40,7 @@ use toon shading instead of more-realistic PBR shading, set the diffuse mode and specular mode to toon: ``` - render_mode diffuse_toon, specular_toon; +render_mode diffuse_toon, specular_toon; ``` This model of built-in functionality allows you to write complex custom shaders @@ -59,9 +59,9 @@ First let's set the color of the water. We do that by setting `ALBEDO`. Let's set it to a nice shade of blue. ``` - void fragment() { - ALBEDO = vec3(0.1, 0.3, 0.5); - } +void fragment() { + ALBEDO = vec3(0.1, 0.3, 0.5); +} ``` ![](img/albedo.png) @@ -96,11 +96,11 @@ is also highly reflective, so we will set its `ROUGHNESS` property to be quite low as well. ``` - void fragment() { - METALLIC = 0.0; - ROUGHNESS = 0.01; - ALBEDO = vec3(0.1, 0.3, 0.5); - } +void fragment() { + METALLIC = 0.0; + ROUGHNESS = 0.01; + ALBEDO = vec3(0.1, 0.3, 0.5); +} ``` ![](img/plastic.png) @@ -119,7 +119,7 @@ will change the render mode for specular to toon because the toon render mode has larger specular highlights. ``` - render_mode specular_toon; +render_mode specular_toon; ``` ![](img/specular-toon.png) @@ -130,12 +130,12 @@ fabric on the edges of an object, but we will use it here to help achieve a nice watery effect. ``` - void fragment() { - RIM = 0.2; - METALLIC = 0.0; - ROUGHNESS = 0.01; - ALBEDO = vec3(0.1, 0.3, 0.5); - } +void fragment() { + RIM = 0.2; + METALLIC = 0.0; + ROUGHNESS = 0.01; + ALBEDO = vec3(0.1, 0.3, 0.5); +} ``` ![](img/rim.png) @@ -149,7 +149,7 @@ that point on the surface. The dot product between them is a handy way to tell when you are looking at the surface head-on or at a glancing angle. ``` - float fresnel = sqrt(1.0 - dot(NORMAL, VIEW)); +float fresnel = sqrt(1.0 - dot(NORMAL, VIEW)); ``` And mix it into both `ROUGHNESS` and `ALBEDO`. This is the benefit of @@ -159,13 +159,13 @@ set them based on any mathematical function that we can dream up. ``` - void fragment() { - float fresnel = sqrt(1.0 - dot(NORMAL, VIEW)); - RIM = 0.2; - METALLIC = 0.0; - ROUGHNESS = 0.01 * (1.0 - fresnel); - ALBEDO = vec3(0.1, 0.3, 0.5) + (0.1 * fresnel); - } +void fragment() { + float fresnel = sqrt(1.0 - dot(NORMAL, VIEW)); + RIM = 0.2; + METALLIC = 0.0; + ROUGHNESS = 0.01 * (1.0 - fresnel); + ALBEDO = vec3(0.1, 0.3, 0.5) + (0.1 * fresnel); +} ``` ![](img/fresnel.png) @@ -191,33 +191,33 @@ tutorial, we will do the same. Put the heightmap code in a function called `height()`. ``` - float height(vec2 position) { - return texture(noise, position / 10.0).x; // Scaling factor is based on mesh size (this PlaneMesh is 10×10). - } +float height(vec2 position) { + return texture(noise, position / 10.0).x; // Scaling factor is based on mesh size (this PlaneMesh is 10×10). +} ``` In order to use `TIME` in the `height()` function, we need to pass it in. ``` - float height(vec2 position, float time) { - } +float height(vec2 position, float time) { +} ``` And make sure to correctly pass it in inside the vertex function. ``` - void vertex() { - vec2 pos = VERTEX.xz; - float k = height(pos, TIME); - VERTEX.y = k; - } +void vertex() { + vec2 pos = VERTEX.xz; + float k = height(pos, TIME); + VERTEX.y = k; +} ``` Instead of using a normalmap to calculate normals. We are going to compute them manually in the `vertex()` function. To do so use the following line of code. ``` - NORMAL = normalize(vec3(k - height(pos + vec2(0.1, 0.0), TIME), 0.1, k - height(pos + vec2(0.0, 0.1), TIME))); +NORMAL = normalize(vec3(k - height(pos + vec2(0.1, 0.0), TIME), 0.1, k - height(pos + vec2(0.0, 0.1), TIME))); ``` We need to compute `NORMAL` manually because in the next section we will be @@ -227,10 +227,10 @@ Now, we are going to make the `height()` function a little more complicated by offsetting `position` by the cosine of `TIME`. ``` - float height(vec2 position, float time) { - vec2 offset = 0.01 * cos(position + time); - return texture(noise, (position / 10.0) - offset).x; - } +float height(vec2 position, float time) { + vec2 offset = 0.01 * cos(position + time); + return texture(noise, (position / 10.0) - offset).x; +} ``` This results in waves that move slowly, but not in a very natural way. The next @@ -251,24 +251,24 @@ We are going to call `wave()` multiple times in `height()` in order to fake the way waves look. ``` - float wave(vec2 position){ - position += texture(noise, position / 10.0).x * 2.0 - 1.0; - vec2 wv = 1.0 - abs(sin(position)); - return pow(1.0 - pow(wv.x * wv.y, 0.65), 4.0); - } +float wave(vec2 position){ + position += texture(noise, position / 10.0).x * 2.0 - 1.0; + vec2 wv = 1.0 - abs(sin(position)); + return pow(1.0 - pow(wv.x * wv.y, 0.65), 4.0); +} ``` At first this looks complicated. So let's go through it line-by-line. ``` - position += texture(noise, position / 10.0).x * 2.0 - 1.0; +position += texture(noise, position / 10.0).x * 2.0 - 1.0; ``` Offset the position by the `noise` texture. This will make the waves curve, so they won't be straight lines completely aligned with the grid. ``` - vec2 wv = 1.0 - abs(sin(position)); +vec2 wv = 1.0 - abs(sin(position)); ``` Define a wave-like function using `sin()` and `position`. Normally `sin()` @@ -277,7 +277,7 @@ and constrain them to the 0-1 range. And then we subtract it from `1.0` to put the peak on top. ``` - return pow(1.0 - pow(wv.x * wv.y, 0.65), 4.0); +return pow(1.0 - pow(wv.x * wv.y, 0.65), 4.0); ``` Multiply the x-directional wave by the y-directional wave and raise it to a @@ -287,10 +287,10 @@ become peaks and raise that to a power to sharpen the ridges. We can now replace the contents of our `height()` function with `wave()`. ``` - float height(vec2 position, float time) { - float h = wave(position); - return h; - } +float height(vec2 position, float time) { + float h = wave(position); + return h; +} ``` Using this, you get: @@ -301,10 +301,10 @@ The shape of the sin wave is too obvious. So let's spread the waves out a bit. We do this by scaling `position`. ``` - float height(vec2 position, float time) { - float h = wave(position * 0.4); - return h; - } +float height(vec2 position, float time) { + float h = wave(position * 0.4); + return h; +} ``` Now it looks much better. @@ -321,13 +321,13 @@ Here is an example for how you could layer the four waves to achieve nicer looking waves. ``` - float height(vec2 position, float time) { - float d = wave((position + time) * 0.4) * 0.3; - d += wave((position - time) * 0.3) * 0.3; - d += wave((position + time) * 0.5) * 0.2; - d += wave((position - time) * 0.6) * 0.2; - return d; - } +float height(vec2 position, float time) { + float d = wave((position + time) * 0.4) * 0.3; + d += wave((position - time) * 0.3) * 0.3; + d += wave((position + time) * 0.5) * 0.2; + d += wave((position - time) * 0.6) * 0.2; + return d; +} ``` Note that we add time to two and subtract it from the other two. This makes the diff --git a/03_usage/17_performance/02_general_optimization.md b/03_usage/17_performance/02_general_optimization.md index c0c0ab9..2b56948 100644 --- a/03_usage/17_performance/02_general_optimization.md +++ b/03_usage/17_performance/02_general_optimization.md @@ -229,16 +229,16 @@ function `A`, then optimizing `A` can have a massive effect on performance. ``` - A: 9 ms - Everything else: 1 ms - Total frame time: 10 ms +A: 9 ms +Everything else: 1 ms +Total frame time: 10 ms ``` ``` - A: 1 ms - Everything else: 1ms - Total frame time: 2 ms +A: 1 ms +Everything else: 1ms +Total frame time: 2 ms ``` In this example, improving this bottleneck `A` by a factor of 9× decreases @@ -249,16 +249,16 @@ project, then the same improvement can lead to less dramatic gains: ``` - A: 9 ms - Everything else: 50 ms - Total frame time: 59 ms +A: 9 ms +Everything else: 50 ms +Total frame time: 59 ms ``` ``` - A: 1 ms - Everything else: 50 ms - Total frame time: 51 ms +A: 1 ms +Everything else: 50 ms +Total frame time: 51 ms ``` In this example, even though we have hugely optimized function `A`, @@ -270,16 +270,16 @@ of the two. ``` - CPU: 9 ms - GPU: 50 ms - Total frame time: 50 ms +CPU: 9 ms +GPU: 50 ms +Total frame time: 50 ms ``` ``` - CPU: 1 ms - GPU: 50 ms - Total frame time: 50 ms +CPU: 1 ms +GPU: 50 ms +Total frame time: 50 ms ``` In this example, we optimized the CPU hugely again, but the frame time didn't diff --git a/03_usage/17_performance/03_cpu_optimization.md b/03_usage/17_performance/03_cpu_optimization.md index 288f147..0a67f64 100644 --- a/03_usage/17_performance/03_cpu_optimization.md +++ b/03_usage/17_performance/03_cpu_optimization.md @@ -98,13 +98,13 @@ The specifics vary depending on the language, but in GDScript, you would do the following: ``` - var time_start = OS.get_ticks_usec() +var time_start = OS.get_ticks_usec() - # Your function you want to time - update_enemies() +# Your function you want to time +update_enemies() - var time_end = OS.get_ticks_usec() - print("update_enemies() took %d microseconds" % time_end - time_start) +var time_end = OS.get_ticks_usec() +print("update_enemies() took %d microseconds" % time_end - time_start) ``` When manually timing functions, it is usually a good idea to run the function diff --git a/03_usage/17_performance/05_using_multimesh.md b/03_usage/17_performance/05_using_multimesh.md index 5d84cc2..302a9a2 100644 --- a/03_usage/17_performance/05_using_multimesh.md +++ b/03_usage/17_performance/05_using_multimesh.md @@ -50,22 +50,22 @@ efficient for millions of objects, but for a few thousands, GDScript should be f gdscript GDScript ``` - extends MultiMeshInstance +extends MultiMeshInstance - func _ready(): - # Create the multimesh. - multimesh = MultiMesh.new() - # Set the format first. - multimesh.transform_format = MultiMesh.TRANSFORM_3D - multimesh.color_format = MultiMesh.COLOR_NONE - multimesh.custom_data_format = MultiMesh.CUSTOM_DATA_NONE - # Then resize (otherwise, changing the format is not allowed). - multimesh.instance_count = 10000 - # Maybe not all of them should be visible at first. - multimesh.visible_instance_count = 1000 +func _ready(): + # Create the multimesh. + multimesh = MultiMesh.new() + # Set the format first. + multimesh.transform_format = MultiMesh.TRANSFORM_3D + multimesh.color_format = MultiMesh.COLOR_NONE + multimesh.custom_data_format = MultiMesh.CUSTOM_DATA_NONE + # Then resize (otherwise, changing the format is not allowed). + multimesh.instance_count = 10000 + # Maybe not all of them should be visible at first. + multimesh.visible_instance_count = 1000 - # Set the transform of the instances. - for i in multimesh.visible_instance_count: - multimesh.set_instance_transform(i, Transform(Basis(), Vector3(i * 20, 0, 0))) + # Set the transform of the instances. + for i in multimesh.visible_instance_count: + multimesh.set_instance_transform(i, Transform(Basis(), Vector3(i * 20, 0, 0))) ``` \ No newline at end of file diff --git a/03_usage/17_performance/06_batching.md b/03_usage/17_performance/06_batching.md index a945b6f..616834e 100644 --- a/03_usage/17_performance/06_batching.md +++ b/03_usage/17_performance/06_batching.md @@ -108,9 +108,9 @@ objects are rendered from back to front, consider 3 objects `A`, `B` and In painter's order they are ordered: ``` - A - wood - B - grass - C - wood +A - wood +B - grass +C - wood ``` Because of the texture changes, they can't be batched and will be rendered in 3 @@ -135,9 +135,9 @@ balance the costs and benefits in your project. ``` - A - wood - C - wood - B - grass +A - wood +C - wood +B - grass ``` Since the texture only changes once, we can render the above in only 2 draw @@ -156,14 +156,14 @@ lights, they would be drawn as follows, each line being a draw call: ``` - A - A - light 1 - A - light 2 - A - light 3 - B - B - light 1 - B - light 2 - B - light 3 +A +A - light 1 +A - light 2 +A - light 3 +B +B - light 1 +B - light 2 +B - light 3 ``` That is a lot of draw calls: 8 for only 2 sprites. Now, consider we are drawing @@ -181,10 +181,10 @@ so the drawing process is as follows: ``` - AB - AB - light 1 - AB - light 2 - AB - light 3 +AB +AB - light 1 +AB - light 2 +AB - light 3 ``` @@ -396,25 +396,25 @@ as intended, and help you fix these situations to get the best possible performa #### Reading a diagnostic ``` - canvas_begin FRAME 2604 - items - joined_item 1 refs - batch D 0-0 - batch D 0-2 n n - batch R 0-1 [0 - 0] {255 255 255 255 } - joined_item 1 refs - batch D 0-0 - batch R 0-1 [0 - 146] {255 255 255 255 } - batch D 0-0 - batch R 0-1 [0 - 146] {255 255 255 255 } - joined_item 1 refs - batch D 0-0 - batch R 0-2560 [0 - 144] {158 193 0 104 } MULTI - batch D 0-0 - batch R 0-2560 [0 - 144] {158 193 0 104 } MULTI - batch D 0-0 - batch R 0-2560 [0 - 144] {158 193 0 104 } MULTI - canvas_end +canvas_begin FRAME 2604 +items + joined_item 1 refs + batch D 0-0 + batch D 0-2 n n + batch R 0-1 [0 - 0] {255 255 255 255 } + joined_item 1 refs + batch D 0-0 + batch R 0-1 [0 - 146] {255 255 255 255 } + batch D 0-0 + batch R 0-1 [0 - 146] {255 255 255 255 } + joined_item 1 refs + batch D 0-0 + batch R 0-2560 [0 - 144] {158 193 0 104 } MULTI + batch D 0-0 + batch R 0-2560 [0 - 144] {158 193 0 104 } MULTI + batch D 0-0 + batch R 0-2560 [0 - 144] {158 193 0 104 } MULTI +canvas_end ``` @@ -439,18 +439,18 @@ The second number following default batches is the number of commands in the batch, and it is followed by a brief summary of the contents: ``` - l - line - PL - polyline - r - rect - n - ninepatch - PR - primitive - p - polygon - m - mesh - MM - multimesh - PA - particles - c - circle - t - transform - CI - clip_ignore +l - line +PL - polyline +r - rect +n - ninepatch +PR - primitive +p - polygon +m - mesh +MM - multimesh +PA - particles +c - circle +t - transform +CI - clip_ignore ``` You may see "dummy" default batches containing no commands; you can ignore those. @@ -521,8 +521,8 @@ For example, on a screen size of 1920×1080, there are 2,073,600 pixels. At a threshold of 1,000 pixels, the proportion would be: ``` - 1000 / 2073600 = 0.00048225 - 0.00048225 ^ (1/4) = 0.14819 +1000 / 2073600 = 0.00048225 +0.00048225 ^ (1/4) = 0.14819 ``` So a `scissor_area_threshold @@ -534,8 +534,8 @@ Going the other way, for instance with a `scissor_area_threshold of `0.5`: ``` - 0.5 ^ 4 = 0.0625 - 0.0625 * 2073600 = 129600 pixels +0.5 ^ 4 = 0.0625 +0.0625 * 2073600 = 129600 pixels ``` If the number of pixels saved is greater than this threshold, the scissor is diff --git a/03_usage/17_performance/08_using_servers.md b/03_usage/17_performance/08_using_servers.md index 6334871..08ea57d 100644 --- a/03_usage/17_performance/08_using_servers.md +++ b/03_usage/17_performance/08_using_servers.md @@ -89,26 +89,26 @@ gdscript GDScript ``` - extends Node2D +extends Node2D - # VisualServer expects references to be kept around. - var texture +# VisualServer expects references to be kept around. +var texture - func _ready(): - # Create a canvas item, child of this node. - var ci_rid = VisualServer.canvas_item_create() - # Make this node the parent. - VisualServer.canvas_item_set_parent(ci_rid, get_canvas_item()) - # Draw a texture on it. - # Remember, keep this reference. - texture = load("res://my_texture.png)") - # Add it, centered. - VisualServer.canvas_item_add_texture_rect(ci_rid, Rect2(texture.get_size() / 2, texture.get_size()), texture) - # Add the item, rotated 45 degrees and translated. - var xform = Transform2D().rotated(deg2rad(45)).translated(Vector2(20, 30)) - VisualServer.canvas_item_set_transform(ci_rid, xform) +func _ready(): + # Create a canvas item, child of this node. + var ci_rid = VisualServer.canvas_item_create() + # Make this node the parent. + VisualServer.canvas_item_set_parent(ci_rid, get_canvas_item()) + # Draw a texture on it. + # Remember, keep this reference. + texture = load("res://my_texture.png)") + # Add it, centered. + VisualServer.canvas_item_add_texture_rect(ci_rid, Rect2(texture.get_size() / 2, texture.get_size()), texture) + # Add the item, rotated 45 degrees and translated. + var xform = Transform2D().rotated(deg2rad(45)).translated(Vector2(20, 30)) + VisualServer.canvas_item_set_transform(ci_rid, xform) ``` The Canvas Item API in the server allows you to add draw primitives to it. Once added, they can't be modified. @@ -120,7 +120,7 @@ Primitives are cleared this way: gdscript GDScript ``` - VisualServer.canvas_item_clear(ci_rid) +VisualServer.canvas_item_clear(ci_rid) ``` ## Instantiating a Mesh into 3D space @@ -130,27 +130,27 @@ The 3D APIs are different from the 2D ones, so the instantiation API must be use gdscript GDScript ``` - extends Spatial +extends Spatial - # VisualServer expects references to be kept around. - var mesh +# VisualServer expects references to be kept around. +var mesh - func _ready(): - # 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) +func _ready(): + # 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) ``` ## Creating a 2D RigidBody and moving a sprite with it @@ -161,34 +161,34 @@ and moves a `CanvasItem` when the body moves. gdscript GDScript ``` - # Physics2DServer expects references to be kept around. - var body - var shape +# Physics2DServer expects references to be kept around. +var body +var shape - func _body_moved(state, index): - # Created your own canvas item, use it here. - VisualServer.canvas_item_set_transform(canvas_item, state.transform) +func _body_moved(state, index): + # Created your own canvas item, use it here. + VisualServer.canvas_item_set_transform(canvas_item, state.transform) - func _ready(): - # Create the body. - body = Physics2DServer.body_create() - Physics2DServer.body_set_mode(body, Physics2DServer.BODY_MODE_RIGID) - # Add a shape. - shape = Physics2DServer.rectangle_shape_create() - # Set rectangle extents. - Physics2DServer.shape_set_data(shape, Vector2(10, 10)) - # Make sure to keep the shape reference! - Physics2DServer.body_add_shape(body, shape) - # Set space, so it collides in the same space as current scene. - Physics2DServer.body_set_space(body, get_world_2d().space) - # Move initial position. - Physics2DServer.body_set_state(body, Physics2DServer.BODY_STATE_TRANSFORM, Transform2D(0, Vector2(10, 20))) - # Add the transform callback, when body moves - # The last parameter is optional, can be used as index - # if you have many bodies and a single callback. - Physics2DServer.body_set_force_integration_callback(body, self, "_body_moved", 0) +func _ready(): + # Create the body. + body = Physics2DServer.body_create() + Physics2DServer.body_set_mode(body, Physics2DServer.BODY_MODE_RIGID) + # Add a shape. + shape = Physics2DServer.rectangle_shape_create() + # Set rectangle extents. + Physics2DServer.shape_set_data(shape, Vector2(10, 10)) + # Make sure to keep the shape reference! + Physics2DServer.body_add_shape(body, shape) + # Set space, so it collides in the same space as current scene. + Physics2DServer.body_set_space(body, get_world_2d().space) + # Move initial position. + Physics2DServer.body_set_state(body, Physics2DServer.BODY_STATE_TRANSFORM, Transform2D(0, Vector2(10, 20))) + # Add the transform callback, when body moves + # The last parameter is optional, can be used as index + # if you have many bodies and a single callback. + Physics2DServer.body_set_force_integration_callback(body, self, "_body_moved", 0) ``` The 3D version should be very similar, as 2D and 3D physics servers are identical (using diff --git a/03_usage/17_performance/threads/01_using_multiple_threads.md b/03_usage/17_performance/threads/01_using_multiple_threads.md index 946d87e..2a07f8f 100644 --- a/03_usage/17_performance/threads/01_using_multiple_threads.md +++ b/03_usage/17_performance/threads/01_using_multiple_threads.md @@ -26,26 +26,26 @@ Creating a thread is very simple, just use the following code: gdscript GDScript ``` - var thread +var thread - # The thread will start here. - func _ready(): - thread = Thread.new() - # Third argument is optional userdata, it can be any variable. - thread.start(self, "_thread_function", "Wafflecopter") +# The thread will start here. +func _ready(): + thread = Thread.new() + # Third argument is optional userdata, it can be any variable. + thread.start(self, "_thread_function", "Wafflecopter") - # Run here and exit. - # The argument is the userdata passed from start(). - # If no argument was passed, this one still needs to - # be here and it will be null. - func _thread_function(userdata): - # Print the userdata ("Wafflecopter") - print("I'm a thread! Userdata is: ", userdata) +# Run here and exit. +# The argument is the userdata passed from start(). +# If no argument was passed, this one still needs to +# be here and it will be null. +func _thread_function(userdata): + # Print the userdata ("Wafflecopter") + print("I'm a thread! Userdata is: ", userdata) - # Thread must be disposed (or "joined"), for portability. - func _exit_tree(): - thread.wait_to_finish() +# Thread must be disposed (or "joined"), for portability. +func _exit_tree(): + thread.wait_to_finish() ``` Your function will, then, run in a separate thread until it returns. @@ -77,34 +77,34 @@ Here is an example of using a Mutex: gdscript GDScript ``` - var counter = 0 - var mutex - var thread +var counter = 0 +var mutex +var thread - # The thread will start here. - func _ready(): - mutex = Mutex.new() - thread = Thread.new() - thread.start(self, "_thread_function") +# The thread will start here. +func _ready(): + mutex = Mutex.new() + thread = Thread.new() + thread.start(self, "_thread_function") - # Increase value, protect it with Mutex. - mutex.lock() - counter += 1 - mutex.unlock() + # Increase value, protect it with Mutex. + mutex.lock() + counter += 1 + mutex.unlock() - # Increment the value from the thread, too. - func _thread_function(userdata): - mutex.lock() - counter += 1 - mutex.unlock() +# Increment the value from the thread, too. +func _thread_function(userdata): + mutex.lock() + counter += 1 + mutex.unlock() - # Thread must be disposed (or "joined"), for portability. - func _exit_tree(): - thread.wait_to_finish() - print("Counter is: ", counter) # Should be 2. +# Thread must be disposed (or "joined"), for portability. +func _exit_tree(): + thread.wait_to_finish() + print("Counter is: ", counter) # Should be 2. ``` ## Semaphores @@ -122,64 +122,64 @@ ready to be processed: gdscript GDScript ``` - var counter = 0 - var mutex - var semaphore - var thread - var exit_thread = false +var counter = 0 +var mutex +var semaphore +var thread +var exit_thread = false - # The thread will start here. - func _ready(): - mutex = Mutex.new() - semaphore = Semaphore.new() - exit_thread = false +# The thread will start here. +func _ready(): + mutex = Mutex.new() + semaphore = Semaphore.new() + exit_thread = false - thread = Thread.new() - thread.start(self, "_thread_function") + thread = Thread.new() + thread.start(self, "_thread_function") - func _thread_function(userdata): - while true: - semaphore.wait() # Wait until posted. +func _thread_function(userdata): + while true: + semaphore.wait() # Wait until posted. - mutex.lock() - var should_exit = exit_thread # Protect with Mutex. - mutex.unlock() - - if should_exit: - break - - mutex.lock() - counter += 1 # Increment counter, protect with Mutex. - mutex.unlock() - - - func increment_counter(): - semaphore.post() # Make the thread process. - - - func get_counter(): mutex.lock() - # Copy counter, protect with Mutex. - var counter_value = counter - mutex.unlock() - return counter_value - - - # Thread must be disposed (or "joined"), for portability. - func _exit_tree(): - # Set exit condition to true. - mutex.lock() - exit_thread = true # Protect with Mutex. + var should_exit = exit_thread # Protect with Mutex. mutex.unlock() - # Unblock by posting. - semaphore.post() + if should_exit: + break - # Wait until it exits. - thread.wait_to_finish() + mutex.lock() + counter += 1 # Increment counter, protect with Mutex. + mutex.unlock() - # Print the counter. - print("Counter is: ", counter) + +func increment_counter(): + semaphore.post() # Make the thread process. + + +func get_counter(): + mutex.lock() + # Copy counter, protect with Mutex. + var counter_value = counter + mutex.unlock() + return counter_value + + +# Thread must be disposed (or "joined"), for portability. +func _exit_tree(): + # Set exit condition to true. + mutex.lock() + exit_thread = true # Protect with Mutex. + mutex.unlock() + + # Unblock by posting. + semaphore.post() + + # Wait until it exits. + thread.wait_to_finish() + + # Print the counter. + print("Counter is: ", counter) ``` diff --git a/03_usage/17_performance/threads/02_thread_safe_apis.md b/03_usage/17_performance/threads/02_thread_safe_apis.md index c6f3f37..d84fd8d 100644 --- a/03_usage/17_performance/threads/02_thread_safe_apis.md +++ b/03_usage/17_performance/threads/02_thread_safe_apis.md @@ -20,19 +20,19 @@ This makes them ideal for code that creates dozens of thousands of instances in Interacting with the active scene tree is **NOT** thread-safe. Make sure to use mutexes when sending data between threads. If you want to call functions from a thread, the *call_deferred* function may be used: ``` - # Unsafe: - node.add_child(child_node) - # Safe: - node.call_deferred("add_child", child_node) +# Unsafe: +node.add_child(child_node) +# Safe: +node.call_deferred("add_child", child_node) ``` However, creating scene chunks (nodes in tree arrangement) outside the active tree is fine. This way, parts of a scene can be built or instantiated in a thread, then added in the main thread: ``` - var enemy_scene = load("res://enemy_scene.scn") - var enemy = enemy_scene.instance() - enemy.add_child(weapon) # Set a weapon. - world.call_deferred("add_child", enemy) +var enemy_scene = load("res://enemy_scene.scn") +var enemy = enemy_scene.instance() +enemy.add_child(weapon) # Set a weapon. +world.call_deferred("add_child", enemy) ``` Still, this is only really useful if you have **one** thread loading data. diff --git a/03_usage/17_performance/vertex_animation/01_animating_thousands_of_fish.md b/03_usage/17_performance/vertex_animation/01_animating_thousands_of_fish.md index fc82365..6f11c18 100644 --- a/03_usage/17_performance/vertex_animation/01_animating_thousands_of_fish.md +++ b/03_usage/17_performance/vertex_animation/01_animating_thousands_of_fish.md @@ -52,8 +52,8 @@ world orientation. In order to control the speed of the animation, we will start by defining our own time variable using `TIME`. ``` - //time_scale is a uniform float - float time = TIME * time_scale; +//time_scale is a uniform float +float time = TIME * time_scale; ``` The first motion we will implement is the side to side motion. It can be made by offsetting `VERTEX.x` by @@ -61,8 +61,8 @@ The first motion we will implement is the side to side motion. It can be made by of `cos(time)`. ``` - //side_to_side is a uniform float - VERTEX.x += cos(time) * side_to_side; +//side_to_side is a uniform float +VERTEX.x += cos(time) * side_to_side; ``` The resulting animation should look something like this: @@ -75,16 +75,16 @@ rotation matrix for it to rotate around the center of the fish. We construct a rotation matrix like so: ``` - //angle is scaled by 0.1 so that the fish only pivots and doesn't rotate all the way around - //pivot is a uniform float - float pivot_angle = cos(time) * 0.1 * pivot; - mat2 rotation_matrix = mat2(vec2(cos(pivot_angle), -sin(pivot_angle)), vec2(sin(pivot_angle), cos(pivot_angle))); +//angle is scaled by 0.1 so that the fish only pivots and doesn't rotate all the way around +//pivot is a uniform float +float pivot_angle = cos(time) * 0.1 * pivot; +mat2 rotation_matrix = mat2(vec2(cos(pivot_angle), -sin(pivot_angle)), vec2(sin(pivot_angle), cos(pivot_angle))); ``` And then we apply it in the `x` and `z` axes by multiplying it by `VERTEX.xz`. ``` - VERTEX.xz = rotation_matrix * VERTEX.xz; +VERTEX.xz = rotation_matrix * VERTEX.xz; ``` With only the pivot applied you should see something like this: @@ -95,7 +95,7 @@ The next two motions need to pan down the spine of the fish. For that, we need a `body` is a float that is `0` at the tail of the fish and `1` at its head. ``` - float body = (VERTEX.z + 1.0) / 2.0; //for a fish centered at (0, 0) with a length of 2 +float body = (VERTEX.z + 1.0) / 2.0; //for a fish centered at (0, 0) with a length of 2 ``` The next motion is a cosine wave that moves down the length of the fish. To make @@ -103,8 +103,8 @@ it move along the spine of the fish, we offset the input to `cos` by the positio along the spine, which is the variable we defined above, `body`. ``` - //wave is a uniform float - VERTEX.x += cos(time + body) * wave; +//wave is a uniform float +VERTEX.x += cos(time + body) * wave; ``` This looks very similar to the side to side motion we defined above, but in this one, by @@ -117,16 +117,16 @@ The last motion is the twist, which is a panning roll along the spine. Similarly we first construct a rotation matrix. ``` - //twist is a uniform float - float twist_angle = cos(time + body) * 0.3 * twist; - mat2 twist_matrix = mat2(vec2(cos(twist_angle), -sin(twist_angle)), vec2(sin(twist_angle), cos(twist_angle))); +//twist is a uniform float +float twist_angle = cos(time + body) * 0.3 * twist; +mat2 twist_matrix = mat2(vec2(cos(twist_angle), -sin(twist_angle)), vec2(sin(twist_angle), cos(twist_angle))); ``` We apply the rotation in the `xy` axes so that the fish appears to roll around its spine. For this to work, the fish's spine needs to be centered on the `z` axis. ``` - VERTEX.xy = twist_matrix * VERTEX.xy; +VERTEX.xy = twist_matrix * VERTEX.xy; ``` Here is the fish with twist applied: @@ -144,8 +144,8 @@ panning motions to the back half of the fish. To do this, we create a new variab `smoothstep` to control the point at which the transition from `0` to `1` happens. ``` - //mask_black and mask_white are uniforms - float mask = smoothstep(mask_black, mask_white, 1.0 - body); +//mask_black and mask_white are uniforms +float mask = smoothstep(mask_black, mask_white, 1.0 - body); ``` Below is an image of the fish with `mask` used as `COLOR`: @@ -155,8 +155,8 @@ Below is an image of the fish with `mask` used as `COLOR`: For the wave, we multiply the motion by `mask` which will limit it to the back half. ``` - //wave motion with mask - VERTEX.x += cos(time + body) * mask * wave; +//wave motion with mask +VERTEX.x += cos(time + body) * mask * wave; ``` In order to apply the mask to the twist, we use `mix`. `mix` allows us to mix the @@ -166,8 +166,8 @@ adding the motion to the `VERTEX` we are replacing the `VERTEX` with the rotated version. If we multiplied that by `mask`, we would shrink the fish. ``` - //twist motion with mask - VERTEX.xy = mix(VERTEX.xy, twist_matrix * VERTEX.xy, mask); +//twist motion with mask +VERTEX.xy = mix(VERTEX.xy, twist_matrix * VERTEX.xy, mask); ``` Putting the four motions together gives us the final animation. @@ -216,10 +216,10 @@ The second is to loop over all the instances and set their transforms in code. B to loop over all the instances and set their transform to a random position. ``` - for i in range($School.multimesh.instance_count): - var position = Transform() - position = position.translated(Vector3(randf() * 100 - 50, randf() * 50 - 25, randf() * 50 - 25)) - $School.multimesh.set_instance_transform(i, position) +for i in range($School.multimesh.instance_count): + var position = Transform() + position = position.translated(Vector3(randf() * 100 - 50, randf() * 50 - 25, randf() * 50 - 25)) + $School.multimesh.set_instance_transform(i, position) ``` Running this script will place the fish in random positions in a box around the position of the @@ -241,7 +241,7 @@ swim cycle, we only need to offset `time`. We do that by adding the per-instance custom value `INSTANCE_CUSTOM` to `time`. ``` - float time = (TIME * time_scale) + (6.28318 * INSTANCE_CUSTOM.x); +float time = (TIME * time_scale) + (6.28318 * INSTANCE_CUSTOM.x); ``` Next, we need to pass a value into `INSTANCE_CUSTOM`. We do that by adding one line into @@ -249,7 +249,7 @@ the `for` loop from above. In the `for` loop we assign each instance a set of fo random floats to use. ``` - $School.multimesh.set_instance_custom_data(i, Color(randf(), randf(), randf(), randf())) +$School.multimesh.set_instance_custom_data(i, Color(randf(), randf(), randf(), randf())) ``` Now the fish all have unique positions in the swim cycle. You can give them a little more @@ -257,8 +257,8 @@ individuality by using `INSTANCE_CUSTOM` to make them swim faster or slower by m by `TIME`. ``` - //set speed from 50% - 150% of regular speed - float time = (TIME * (0.5 + INSTANCE_CUSTOM.y) * time_scale) + (6.28318 * INSTANCE_CUSTOM.x); +//set speed from 50% - 150% of regular speed +float time = (TIME * (0.5 + INSTANCE_CUSTOM.y) * time_scale) + (6.28318 * INSTANCE_CUSTOM.x); ``` You can even experiment with changing the per-instance color the same way you changed the per-instance diff --git a/03_usage/17_performance/vertex_animation/02_controlling_thousands_of_fish.md b/03_usage/17_performance/vertex_animation/02_controlling_thousands_of_fish.md index fb0e4e3..0a64f1c 100644 --- a/03_usage/17_performance/vertex_animation/02_controlling_thousands_of_fish.md +++ b/03_usage/17_performance/vertex_animation/02_controlling_thousands_of_fish.md @@ -21,31 +21,31 @@ First create a Particles node. Then, under "Draw Passes" set the Particle's "Dra Set the `shader_type` to `particles`. ``` - shader_type particles +shader_type particles ``` Then add the following two functions: ``` - float rand_from_seed(in uint seed) { - int k; - int s = int(seed); - if (s == 0) - s = 305420679; - k = s / 127773; - s = 16807 * (s - k * 127773) - 2836 * k; - if (s < 0) - s += 2147483647; - seed = uint(s); - return float(seed % uint(65536)) / 65535.0; - } +float rand_from_seed(in uint seed) { + int k; + int s = int(seed); + if (s == 0) + s = 305420679; + k = s / 127773; + s = 16807 * (s - k * 127773) - 2836 * k; + if (s < 0) + s += 2147483647; + seed = uint(s); + return float(seed % uint(65536)) / 65535.0; +} - uint hash(uint x) { - x = ((x >> uint(16)) ^ x) * uint(73244475); - x = ((x >> uint(16)) ^ x) * uint(73244475); - x = (x >> uint(16)) ^ x; - return x; - } +uint hash(uint x) { + x = ((x >> uint(16)) ^ x) * uint(73244475); + x = ((x >> uint(16)) ^ x) * uint(73244475); + x = (x >> uint(16)) ^ x; + return x; +} ``` These functions come from the default `ParticlesMaterial`. @@ -66,13 +66,13 @@ built-in variable `RESTART` which becomes `true` for one frame when the particle From a high level, this looks like: ``` - void vertex() { - if (RESTART) { - //Initialization code goes here - } else { - //per-frame code goes here - } +void vertex() { + if (RESTART) { + //Initialization code goes here + } else { + //per-frame code goes here } +} ``` Next, we need to generate 4 random numbers: 3 to create a random position and one for the random @@ -81,26 +81,26 @@ offset of the swim cycle. First, generate 4 seeds inside the `RESTART` block using the `hash` function provided above: ``` - uint alt_seed1 = hash(NUMBER + uint(1) + RANDOM_SEED); - uint alt_seed2 = hash(NUMBER + uint(27) + RANDOM_SEED); - uint alt_seed3 = hash(NUMBER + uint(43) + RANDOM_SEED); - uint alt_seed4 = hash(NUMBER + uint(111) + RANDOM_SEED); +uint alt_seed1 = hash(NUMBER + uint(1) + RANDOM_SEED); +uint alt_seed2 = hash(NUMBER + uint(27) + RANDOM_SEED); +uint alt_seed3 = hash(NUMBER + uint(43) + RANDOM_SEED); +uint alt_seed4 = hash(NUMBER + uint(111) + RANDOM_SEED); ``` Then, use those seeds to generate random numbers using `rand_from_seed`: ``` - CUSTOM.x = rand_from_seed(alt_seed1); - vec3 position = vec3(rand_from_seed(alt_seed2) * 2.0 - 1.0, - rand_from_seed(alt_seed3) * 2.0 - 1.0, - rand_from_seed(alt_seed4) * 2.0 - 1.0); +CUSTOM.x = rand_from_seed(alt_seed1); +vec3 position = vec3(rand_from_seed(alt_seed2) * 2.0 - 1.0, + rand_from_seed(alt_seed3) * 2.0 - 1.0, + rand_from_seed(alt_seed4) * 2.0 - 1.0); ``` Finally, assign `position` to `TRANSFORM[3].xyz`, which is the part of the transform that holds the position information. ``` - TRANSFORM[3].xyz = position * 20.0; +TRANSFORM[3].xyz = position * 20.0; ``` Remember, all this code so far goes inside the `RESTART` block. @@ -113,7 +113,7 @@ or by writing to `VELOCITY`. Let's transform the fish by setting their `VELOCITY`. ``` - VELOCITY.z = 10.0; +VELOCITY.z = 10.0; ``` This is the most basic way to set `VELOCITY` every particle (or fish) will have the same velocity. @@ -122,7 +122,7 @@ Just by setting `VELOCITY` you can make the fish swim however you want. For exam below. ``` - VELOCITY.z = cos(TIME + CUSTOM.x * 6.28) * 4.0 + 6.0; +VELOCITY.z = cos(TIME + CUSTOM.x * 6.28) * 4.0 + 6.0; ``` This will give each fish a unique speed between `2` and `10`. @@ -131,7 +131,7 @@ If you used `CUSTOM.y` in the last tutorial, you can also set the speed of the s on the `VELOCITY`. Just use `CUSTOM.y`. ``` - CUSTOM.y = VELOCITY.z * 0.1; +CUSTOM.y = VELOCITY.z * 0.1; ``` This code gives you the following behavior: diff --git a/03_usage/18_editor/01_command_line_tutorial.md b/03_usage/18_editor/01_command_line_tutorial.md index 95ff5ef..c94c866 100644 --- a/03_usage/18_editor/01_command_line_tutorial.md +++ b/03_usage/18_editor/01_command_line_tutorial.md @@ -129,19 +129,19 @@ This can be done by giving the path to the `project.pandemonium` file of your project as either the first argument, like this: ``` - pandemonium path_to_your_project/project.pandemonium [other] [commands] [and] [args] +pandemonium path_to_your_project/project.pandemonium [other] [commands] [and] [args] ``` Or by using the `--path` argument: ``` - pandemonium --path path_to_your_project [other] [commands] [and] [args] +pandemonium --path path_to_your_project [other] [commands] [and] [args] ``` For example, the full command for exporting your game (as explained below) might look like this: ``` - pandemonium --path path_to_your_project --export my_export_preset_name game.exe +pandemonium --path path_to_your_project --export my_export_preset_name game.exe ``` ## Creating a project @@ -151,9 +151,9 @@ shell to the desired place and making a project.pandemonium file. ``` - mkdir newgame - cd newgame - touch project.pandemonium +mkdir newgame +cd newgame +touch project.pandemonium ``` @@ -166,14 +166,14 @@ must be done from within the project directory or a subdirectory, otherwise the command is ignored and the project manager appears. ``` - pandemonium -e +pandemonium -e ``` If a scene has been created and saved, it can be edited later by running the same code with that scene as argument. ``` - pandemonium -e scene.tscn +pandemonium -e scene.tscn ``` ## Erasing a scene @@ -183,7 +183,7 @@ metadata files. Use `rm` to erase a scene file. Make sure nothing references that scene or else an error will be thrown upon opening. ``` - rm scene.tscn +rm scene.tscn ``` ## Running the game @@ -192,14 +192,14 @@ To run the game, simply execute Pandemonium within the project directory or subdirectory. ``` - pandemonium +pandemonium ``` When a specific scene needs to be tested, pass that scene to the command line. ``` - pandemonium scene.tscn +pandemonium scene.tscn ``` ## Debugging @@ -209,11 +209,11 @@ just fly by. For this, a command line debugger is provided by adding `-d`. It works for running either the game or a simple scene. ``` - pandemonium -d +pandemonium -d ``` ``` - pandemonium -d scene.tscn +pandemonium -d scene.tscn ``` @@ -224,8 +224,8 @@ especially useful for continuous integration setups. The version of Pandemonium that is headless (server build, no video) is ideal for this. ``` - pandemonium --export "Linux/X11" /var/builds/project - pandemonium --export Android /var/builds/project.apk +pandemonium --export "Linux/X11" /var/builds/project +pandemonium --export Android /var/builds/project.apk ``` The preset name must match the name of an export preset defined in the @@ -256,19 +256,19 @@ The script must inherit from `SceneTree` or `MainLoop`. Here is a simple `sayhello.gd` example of how it works: ``` - #!/usr/bin/env -S pandemonium -s - extends SceneTree +#!/usr/bin/env -S pandemonium -s +extends SceneTree - func _init(): - print("Hello!") - quit() +func _init(): + print("Hello!") + quit() ``` And how to run it: ``` - # Prints "Hello!" to standard output. - pandemonium -s sayhello.gd +# Prints "Hello!" to standard output. +pandemonium -s sayhello.gd ``` If no `project.pandemonium` exists at the path, current path is assumed to be the @@ -280,15 +280,15 @@ it allows you to run the script as follows in modern Linux distributions, as well as macOS: ``` - # Mark script as executable. - chmod +x sayhello.gd - # Prints "Hello!" to standard output. - ./sayhello.gd +# Mark script as executable. +chmod +x sayhello.gd +# Prints "Hello!" to standard output. +./sayhello.gd ``` If the above doesn't work in your current version of Linux or macOS, you can always have the shebang run Pandemonium straight from where it is located as follows: ``` - #!/usr/bin/pandemonium -s +#!/usr/bin/pandemonium -s ``` diff --git a/03_usage/19_plugins/01_running_code_in_the_editor.md b/03_usage/19_plugins/01_running_code_in_the_editor.md index ae99312..73b7255 100644 --- a/03_usage/19_plugins/01_running_code_in_the_editor.md +++ b/03_usage/19_plugins/01_running_code_in_the_editor.md @@ -33,8 +33,8 @@ For example, if you want to execute some code only in the editor, use: gdscript GDScript ``` - if Engine.editor_hint: - # Code to execute when in editor. +if Engine.editor_hint: + # Code to execute when in editor. ``` On the other hand, if you want to execute code only in game, simply negate the same statement: @@ -42,8 +42,8 @@ On the other hand, if you want to execute code only in game, simply negate the s gdscript GDScript ``` - if not Engine.editor_hint: - # Code to execute when in game. +if not Engine.editor_hint: + # Code to execute when in game. ``` Pieces of code do not have either of the 2 conditions above will run both in-editor and in-game. @@ -53,14 +53,14 @@ Here is how a `process()` function might look for you: gdscript GDScript ``` - func _process(delta): - if Engine.editor_hint: - # Code to execute in editor. +func _process(delta): + if Engine.editor_hint: + # Code to execute in editor. - if not Engine.editor_hint: - # Code to execute in game. + if not Engine.editor_hint: + # Code to execute in game. - # Code to execute both in editor and in game. + # Code to execute both in editor and in game. ``` Note: @@ -73,11 +73,11 @@ Add a `Sprite` node to your scene and set the texture to Pandemonium icon. Attac gdscript GDScript ``` - tool - extends Sprite +tool +extends Sprite - func _process(delta): - rotation_degrees += 180 * delta +func _process(delta): + rotation_degrees += 180 * delta ``` Save the script and return to the editor. You should now see your object rotate. If you run the game, it will also rotate. @@ -92,11 +92,11 @@ Now let's choose which code runs when. Modify your `process()` function to look gdscript GDScript ``` - func _process(delta): - if Engine.editor_hint: - rotation_degrees += 180 * delta - else: - rotation_degrees -= 180 * delta +func _process(delta): + if Engine.editor_hint: + rotation_degrees += 180 * delta + else: + rotation_degrees -= 180 * delta ``` Save the script. Now the object will spin clockwise in the editor, but if you run the game, it will spin counter-clockwise. @@ -109,21 +109,21 @@ Modify `process()` to include the rotation speed. gdscript GDScript ``` - tool - extends Sprite +tool +extends Sprite - export var speed = 1 setget set_speed +export var speed = 1 setget set_speed - # Update speed and reset the rotation. - func set_speed(new_speed): - speed = new_speed - rotation_degrees = 0 +# Update speed and reset the rotation. +func set_speed(new_speed): + speed = new_speed + rotation_degrees = 0 - func _process(delta): - rotation_degrees += 180 * delta * speed +func _process(delta): + rotation_degrees += 180 * delta * speed ``` @@ -145,13 +145,13 @@ If you are using `tool`: gdscript GDScript ``` - func _ready(): - var node = Spatial.new() - add_child(node) # Parent could be any node in the scene +func _ready(): + var node = Spatial.new() + add_child(node) # Parent could be any node in the scene - # The line below is required to make the node visible in the Scene tree dock - # and persist changes made by the tool script to the saved scene file. - node.set_owner(get_tree().edited_scene_root) + # The line below is required to make the node visible in the Scene tree dock + # and persist changes made by the tool script to the saved scene file. + node.set_owner(get_tree().edited_scene_root) ``` If you are using `EditorScript( EditorScript )`: @@ -159,14 +159,14 @@ If you are using `EditorScript( EditorScript )`: gdscript GDScript ``` - func _run(): - var parent = get_scene().find_node("Parent") # Parent could be any node in the scene - var node = Spatial.new() - parent.add_child(node) +func _run(): + var parent = get_scene().find_node("Parent") # Parent could be any node in the scene + var node = Spatial.new() + parent.add_child(node) - # The line below is required to make the node visible in the Scene tree dock - # and persist changes made by the tool script to the saved scene file. - node.set_owner(get_scene()) + # The line below is required to make the node visible in the Scene tree dock + # and persist changes made by the tool script to the saved scene file. + node.set_owner(get_scene()) ``` Warning: diff --git a/03_usage/19_plugins/editor/02_making_plugins.md b/03_usage/19_plugins/editor/02_making_plugins.md index 06d9dac..384cc01 100644 --- a/03_usage/19_plugins/editor/02_making_plugins.md +++ b/03_usage/19_plugins/editor/02_making_plugins.md @@ -43,14 +43,14 @@ To continue with the example, use the following values: ini GDScript ``` - Plugin Name: My Custom Node - Subfolder: my_custom_node - Description: A custom node made to extend the Pandemonium Engine. - Author: Your Name Here - Version: 1.0.0 - Language: GDScript - Script Name: custom_node.gd - Activate now: No +Plugin Name: My Custom Node +Subfolder: my_custom_node +Description: A custom node made to extend the Pandemonium Engine. +Author: Your Name Here +Version: 1.0.0 +Language: GDScript +Script Name: custom_node.gd +Activate now: No ``` Warning: @@ -99,18 +99,18 @@ like this: gdscript GDScript ``` - tool - extends EditorPlugin +tool +extends EditorPlugin - func _enter_tree(): - # Initialization of the plugin goes here. - pass +func _enter_tree(): + # Initialization of the plugin goes here. + pass - func _exit_tree(): - # Clean-up of the plugin goes here. - pass +func _exit_tree(): + # Clean-up of the plugin goes here. + pass ``` This is a good template to use when creating new plugins. @@ -146,16 +146,16 @@ clicked. For that, we'll need a simple script that extends from gdscript GDScript ``` - tool - extends Button +tool +extends Button - func _enter_tree(): - connect("pressed", self, "clicked") +func _enter_tree(): + connect("pressed", self, "clicked") - func clicked(): - print("You clicked me!") +func clicked(): + print("You clicked me!") ``` That's it for our basic button. You can save this as `my_button.gd` inside the @@ -172,20 +172,20 @@ dialog. For that, change the `custom_node.gd` script to the following: gdscript GDScript ``` - tool - extends EditorPlugin +tool +extends EditorPlugin - func _enter_tree(): - # Initialization of the plugin goes here. - # Add the new type with a name, a parent type, a script and an icon. - add_custom_type("MyButton", "Button", preload("my_button.gd"), preload("icon.png)")) +func _enter_tree(): + # Initialization of the plugin goes here. + # Add the new type with a name, a parent type, a script and an icon. + add_custom_type("MyButton", "Button", preload("my_button.gd"), preload("icon.png)")) - func _exit_tree(): - # Clean-up of the plugin goes here. - # Always remember to remove it from the engine when deactivated. - remove_custom_type("MyButton") +func _exit_tree(): + # Clean-up of the plugin goes here. + # Always remember to remove it from the engine when deactivated. + remove_custom_type("MyButton") ``` With that done, the plugin should already be available in the plugin list in the @@ -214,13 +214,13 @@ add the following content to it: gdscript GDScript ``` - [plugin] +[plugin] - name="My Custom Dock" - description="A custom dock made so I can learn how to make plugins." - author="Your Name Here" - version="1.0" - script="custom_dock.gd" +name="My Custom Dock" +description="A custom dock made so I can learn how to make plugins." +author="Your Name Here" +version="1.0" +script="custom_dock.gd" ``` Then create the script `custom_dock.gd` in the same folder. Fill it with the @@ -252,30 +252,30 @@ The script could look like this: gdscript GDScript ``` - tool - extends EditorPlugin +tool +extends EditorPlugin - # A class member to hold the dock during the plugin life cycle. - var dock +# A class member to hold the dock during the plugin life cycle. +var dock - func _enter_tree(): - # Initialization of the plugin goes here. - # Load the dock scene and instance it. - dock = preload("res://addons/my_custom_dock/my_dock.tscn").instance() +func _enter_tree(): + # Initialization of the plugin goes here. + # Load the dock scene and instance it. + dock = preload("res://addons/my_custom_dock/my_dock.tscn").instance() - # Add the loaded scene to the docks. - add_control_to_dock(DOCK_SLOT_LEFT_UL, dock) - # Note that LEFT_UL means the left of the editor, upper-left dock. + # Add the loaded scene to the docks. + add_control_to_dock(DOCK_SLOT_LEFT_UL, dock) + # Note that LEFT_UL means the left of the editor, upper-left dock. - func _exit_tree(): - # Clean-up of the plugin goes here. - # Remove the dock. - remove_control_from_docks(dock) - # Erase the control from the memory. - dock.free() +func _exit_tree(): + # Clean-up of the plugin goes here. + # Remove the dock. + remove_control_from_docks(dock) + # Erase the control from the memory. + dock.free() ``` Note that, while the dock will initially appear at its specified position, @@ -321,18 +321,18 @@ an autoload. Use the following code to register a singleton from an editor plugin: ``` - tool - extends EditorPlugin +tool +extends EditorPlugin - # Replace this value with a PascalCase autoload name, as per the GDScript style guide. - const AUTOLOAD_NAME = "SomeAutoload" +# Replace this value with a PascalCase autoload name, as per the GDScript style guide. +const AUTOLOAD_NAME = "SomeAutoload" - func _enter_tree(): - # The autoload can be a scene or script file. - add_autoload_singleton(AUTOLOAD_NAME, "res://addons/my_addon/some_autoload.tscn") +func _enter_tree(): + # The autoload can be a scene or script file. + add_autoload_singleton(AUTOLOAD_NAME, "res://addons/my_addon/some_autoload.tscn") - func _exit_tree(): - remove_autoload_singleton(AUTOLOAD_NAME) +func _exit_tree(): + remove_autoload_singleton(AUTOLOAD_NAME) ``` diff --git a/03_usage/19_plugins/editor/03_making_main_screen_plugins.md b/03_usage/19_plugins/editor/03_making_main_screen_plugins.md index d8aaf72..5cd2e3f 100644 --- a/03_usage/19_plugins/editor/03_making_main_screen_plugins.md +++ b/03_usage/19_plugins/editor/03_making_main_screen_plugins.md @@ -24,32 +24,32 @@ methods, but for a main screen plugin we need to add a few extra methods. Add five extra methods such that the script looks like this: ``` - tool - extends EditorPlugin +tool +extends EditorPlugin - func _enter_tree(): - pass +func _enter_tree(): + pass - func _exit_tree(): - pass +func _exit_tree(): + pass - func has_main_screen(): - return true +func has_main_screen(): + return true - func make_visible(visible): - pass +func make_visible(visible): + pass - func get_plugin_name(): - return "Main Screen Plugin" +func get_plugin_name(): + return "Main Screen Plugin" - func get_plugin_icon(): - return get_editor_interface().get_base_control().get_icon("Node", "EditorIcons") +func get_plugin_icon(): + return get_editor_interface().get_base_control().get_icon("Node", "EditorIcons") ``` The important part in this script is the `has_main_screen()` function, @@ -72,12 +72,12 @@ Add a `Button` node, and set the text to "Print Hello" or similar. Add a script to the button like this: ``` - tool - extends Button +tool +extends Button - func _on_PrintHello_pressed(): - print("Hello from the main screen plugin!") +func _on_PrintHello_pressed(): + print("Hello from the main screen plugin!") ``` Then connect the "pressed" signal to itself. If you need help with signals, @@ -92,44 +92,44 @@ instances our main panel scene and places it where it needs to be. Here is the full plugin script: ``` - tool - extends EditorPlugin +tool +extends EditorPlugin - const MainPanel = preload("res://addons/main_screen/main_panel.tscn") +const MainPanel = preload("res://addons/main_screen/main_panel.tscn") - var main_panel_instance +var main_panel_instance - func _enter_tree(): - main_panel_instance = MainPanel.instance() - # Add the main panel to the editor's main viewport. - get_editor_interface().get_editor_viewport().add_child(main_panel_instance) - # Hide the main panel. Very much required. - make_visible(false) +func _enter_tree(): + main_panel_instance = MainPanel.instance() + # Add the main panel to the editor's main viewport. + get_editor_interface().get_editor_viewport().add_child(main_panel_instance) + # Hide the main panel. Very much required. + make_visible(false) - func _exit_tree(): - if main_panel_instance: - main_panel_instance.queue_free() +func _exit_tree(): + if main_panel_instance: + main_panel_instance.queue_free() - func has_main_screen(): - return true +func has_main_screen(): + return true - func make_visible(visible): - if main_panel_instance: - main_panel_instance.visible = visible +func make_visible(visible): + if main_panel_instance: + main_panel_instance.visible = visible - func get_plugin_name(): - return "Main Screen Plugin" +func get_plugin_name(): + return "Main Screen Plugin" - func get_plugin_icon(): - # Must return some kind of Texture for the icon. - return get_editor_interface().get_base_control().get_icon("Node", "EditorIcons") +func get_plugin_icon(): + # Must return some kind of Texture for the icon. + return get_editor_interface().get_base_control().get_icon("Node", "EditorIcons") ``` A couple of specific lines were added. `MainPanel` is a constant that holds diff --git a/03_usage/19_plugins/editor/04_import_plugins.md b/03_usage/19_plugins/editor/04_import_plugins.md index 8fcc39c..9cd9733 100644 --- a/03_usage/19_plugins/editor/04_import_plugins.md +++ b/03_usage/19_plugins/editor/04_import_plugins.md @@ -23,7 +23,7 @@ imported material. In this example it will contain the pure blue color ``` - 0,0,255 +0,0,255 ``` ## Configuration @@ -32,35 +32,35 @@ First we need a generic plugin that will handle the initialization and destruction of our import plugin. Let's add the `plugin.cfg` file first: ``` - [plugin] +[plugin] - name="Silly Material Importer" - description="Imports a 3D Material from an external text file." - author="Yours Truly" - version="1.0" - script="material_import.gd" +name="Silly Material Importer" +description="Imports a 3D Material from an external text file." +author="Yours Truly" +version="1.0" +script="material_import.gd" ``` Then we need the `material_import.gd` file to add and remove the import plugin when needed: ``` - # material_import.gd - tool - extends EditorPlugin +# material_import.gd +tool +extends EditorPlugin - var import_plugin +var import_plugin - func _enter_tree(): - import_plugin = preload("import_plugin.gd").new() - add_import_plugin(import_plugin) +func _enter_tree(): + import_plugin = preload("import_plugin.gd").new() + add_import_plugin(import_plugin) - func _exit_tree(): - remove_import_plugin(import_plugin) - import_plugin = null +func _exit_tree(): + remove_import_plugin(import_plugin) + import_plugin = null ``` When this plugin is activated, it will create a new instance of the import @@ -86,13 +86,13 @@ with files. Let's begin to code our plugin, one method at time: ``` - # import_plugin.gd - tool - extends EditorImportPlugin +# import_plugin.gd +tool +extends EditorImportPlugin - func get_importer_name(): - return "demos.sillymaterial" +func get_importer_name(): + return "demos.sillymaterial" ``` The first method is the @@ -102,8 +102,8 @@ in a certain file. When the files needs to be reimported, the editor will know which plugin to call. ``` - func get_visible_name(): - return "Silly Material" +func get_visible_name(): + return "Silly Material" ``` The `get_visible_name()( EditorImportPlugin_method_get_visible_name )` method is @@ -115,8 +115,8 @@ Silly Material"*. You can name it whatever you want but we recommend a descriptive name for your plugin. ``` - func get_recognized_extensions(): - return ["mtxt"] +func get_recognized_extensions(): + return ["mtxt"] ``` Pandemonium's import system detects file types by their extension. In the @@ -132,8 +132,8 @@ Tip: importing to validate the data. Never expect the file to be well-formed. ``` - func get_save_extension(): - return "material" +func get_save_extension(): + return "material" ``` The imported files are saved in the `.import` folder at the project's root. @@ -148,8 +148,8 @@ resources can use the `res` extension. However, this is not enforced in any way by the engine. ``` - func get_resource_type(): - return "SpatialMaterial" +func get_resource_type(): + return "SpatialMaterial" ``` The imported resource has a specific type, so the editor can know which property @@ -177,22 +177,22 @@ Since there might be many presets and they are identified with a number, it's a good practice to use an enum so you can refer to them using names. ``` - tool - extends EditorImportPlugin +tool +extends EditorImportPlugin - enum Presets { DEFAULT } +enum Presets { DEFAULT } - ... +... ``` Now that the enum is defined, let's keep looking at the methods of an import plugin: ``` - func get_preset_count(): - return Presets.size() +func get_preset_count(): + return Presets.size() ``` The `get_preset_count()` method @@ -201,12 +201,12 @@ now, but we can make this method future-proof by returning the size of our `Presets` enumeration. ``` - func get_preset_name(preset): - match preset: - Presets.DEFAULT: - return "Default" - _: - return "Unknown" +func get_preset_name(preset): + match preset: + Presets.DEFAULT: + return "Default" + _: + return "Unknown" ``` @@ -224,15 +224,15 @@ If you have only one preset you could simply return its name directly, but if you do this you have to be careful when you add more presets. ``` - func get_import_options(preset): - match preset: - Presets.DEFAULT: - return [{ - "name": "use_red_anyway", - "default_value": false - }] - _: - return [] +func get_import_options(preset): + match preset: + Presets.DEFAULT: + return [{ + "name": "use_red_anyway", + "default_value": false + }] + _: + return [] ``` This is the method which defines the available options. @@ -266,8 +266,8 @@ Warning: errors. ``` - func get_option_visibility(option, options): - return true +func get_option_visibility(option, options): + return true ``` For the @@ -285,15 +285,15 @@ resources, is covered by the `import()` method. Our sample code is a bit long, so let's split in a few parts: ``` - func import(source_file, save_path, options, r_platform_variants, r_gen_files): - var file = File.new() - var err = file.open(source_file, File.READ) - if err != OK: - return err +func import(source_file, save_path, options, r_platform_variants, r_gen_files): + var file = File.new() + var err = file.open(source_file, File.READ) + if err != OK: + return err - var line = file.get_line() + var line = file.get_line() - file.close() + file.close() ``` The first part of our import method opens and reads the source file. We use the @@ -304,15 +304,15 @@ If there's an error when opening the file, we return it to let the editor know that the import wasn't successful. ``` - var channels = line.split(",") - if channels.size() != 3: - return ERR_PARSE_ERROR +var channels = line.split(",") +if channels.size() != 3: + return ERR_PARSE_ERROR - var color - if options.use_red_anyway: - color = Color8(255, 0, 0) - else: - color = Color8(int(channels[0]), int(channels[1]), int(channels[2])) +var color +if options.use_red_anyway: + color = Color8(255, 0, 0) +else: + color = Color8(int(channels[0]), int(channels[1]), int(channels[2])) ``` This code takes the line of the file it read before and splits it in pieces @@ -324,8 +324,8 @@ according to the input file. If the `use_red_anyway` option is enabled, then it sets the color as a pure red instead. ``` - var material = SpatialMaterial.new() - material.albedo_color = color +var material = SpatialMaterial.new() +material.albedo_color = color ``` This part makes a new `SpatialMaterial` that is the @@ -333,7 +333,7 @@ imported resource. We create a new instance of it and then set its albedo color as the value we got before. ``` - return ResourceSaver.save("%s.%s" % [save_path, get_save_extension()], material) +return ResourceSaver.save("%s.%s" % [save_path, get_save_extension()], material) ``` This is the last part and quite an important one, because here we save the made @@ -367,8 +367,8 @@ For example, let's say we save a different material for a mobile platform. We would need to do something like the following: ``` - r_platform_variants.push_back("mobile") - return ResourceSaver.save("%s.%s.%s" % [save_path, "mobile", get_save_extension()], mobile_material) +r_platform_variants.push_back("mobile") +return ResourceSaver.save("%s.%s.%s" % [save_path, "mobile", get_save_extension()], mobile_material) ``` The `r_gen_files` argument is meant for extra files that are generated during @@ -381,14 +381,14 @@ save. As an example, let's create another material for the next pass and save it in a different file: ``` - var next_pass = SpatialMaterial.new() - next_pass.albedo_color = color.inverted() - var next_pass_path = "%s.next_pass.%s" % [save_path, get_save_extension()] +var next_pass = SpatialMaterial.new() +next_pass.albedo_color = color.inverted() +var next_pass_path = "%s.next_pass.%s" % [save_path, get_save_extension()] - err = ResourceSaver.save(next_pass_path, next_pass) - if err != OK: - return err - r_gen_files.push_back(next_pass_path) +err = ResourceSaver.save(next_pass_path, next_pass) +if err != OK: + return err +r_gen_files.push_back(next_pass_path) ``` ## Trying the plugin diff --git a/03_usage/19_plugins/editor/05_spatial_gizmos.md b/03_usage/19_plugins/editor/05_spatial_gizmos.md index c5ce85a..6f5f89c 100644 --- a/03_usage/19_plugins/editor/05_spatial_gizmos.md +++ b/03_usage/19_plugins/editor/05_spatial_gizmos.md @@ -25,32 +25,32 @@ the gizmo can be hidden or not. This would be a basic setup: ``` - # MyCustomGizmoPlugin.gd - extends EditorSpatialGizmoPlugin +# MyCustomGizmoPlugin.gd +extends EditorSpatialGizmoPlugin - func get_name(): - return "CustomNode" +func get_name(): + return "CustomNode" ``` ``` - # MyCustomEditorPlugin.gd - tool - extends EditorPlugin +# MyCustomEditorPlugin.gd +tool +extends EditorPlugin - const MyCustomGizmoPlugin = preload("res://addons/my-addon/MyCustomGizmoPlugin.gd") +const MyCustomGizmoPlugin = preload("res://addons/my-addon/MyCustomGizmoPlugin.gd") - var gizmo_plugin = MyCustomGizmoPlugin.new() +var gizmo_plugin = MyCustomGizmoPlugin.new() - func _enter_tree(): - add_spatial_gizmo_plugin(gizmo_plugin) +func _enter_tree(): + add_spatial_gizmo_plugin(gizmo_plugin) - func _exit_tree(): - remove_spatial_gizmo_plugin(gizmo_plugin) +func _exit_tree(): + remove_spatial_gizmo_plugin(gizmo_plugin) ``` @@ -65,48 +65,48 @@ The first step is to, in our custom gizmo plugin, override the `has_gizmo()( Edi method so that it returns `true` when the spatial parameter is of our target type. ``` - # ... +# ... - func has_gizmo(spatial): - return spatial is MyCustomSpatial +func has_gizmo(spatial): + return spatial is MyCustomSpatial - # ... +# ... ``` Then we can override methods like `redraw()( EditorSpatialGizmoPlugin_method_redraw )` or all the handle related ones. ``` - # ... +# ... - func _init(): - create_material("main", Color(1, 0, 0)) - create_handle_material("handles") +func _init(): + create_material("main", Color(1, 0, 0)) + create_handle_material("handles") - func redraw(gizmo): - gizmo.clear() +func redraw(gizmo): + gizmo.clear() - var spatial = gizmo.get_spatial_node() + var spatial = gizmo.get_spatial_node() - var lines = PoolVector3Array() + var lines = PoolVector3Array() - lines.push_back(Vector3(0, 1, 0)) - lines.push_back(Vector3(0, spatial.my_custom_value, 0)) + lines.push_back(Vector3(0, 1, 0)) + lines.push_back(Vector3(0, spatial.my_custom_value, 0)) - var handles = PoolVector3Array() + var handles = PoolVector3Array() - handles.push_back(Vector3(0, 1, 0)) - handles.push_back(Vector3(0, spatial.my_custom_value, 0)) + handles.push_back(Vector3(0, 1, 0)) + handles.push_back(Vector3(0, spatial.my_custom_value, 0)) - gizmo.add_lines(lines, get_material("main", gizmo), false) - gizmo.add_handles(handles, get_material("handles", gizmo)) + gizmo.add_lines(lines, get_material("main", gizmo), false) + gizmo.add_handles(handles, get_material("handles", gizmo)) - # ... +# ... ``` Note that we created a material in the `init` method, and retrieved it in the `redraw` @@ -117,42 +117,42 @@ method retrieves one of the material's variants depending on the state of the gi So the final plugin would look somewhat like this: ``` - extends EditorSpatialGizmoPlugin +extends EditorSpatialGizmoPlugin - const MyCustomSpatial = preload("res://addons/my-addon/MyCustomSpatial.gd") +const MyCustomSpatial = preload("res://addons/my-addon/MyCustomSpatial.gd") - func _init(): - create_material("main", Color(1,0,0)) - create_handle_material("handles") +func _init(): + create_material("main", Color(1,0,0)) + create_handle_material("handles") - func has_gizmo(spatial): - return spatial is MyCustomSpatial +func has_gizmo(spatial): + return spatial is MyCustomSpatial - func redraw(gizmo): - gizmo.clear() +func redraw(gizmo): + gizmo.clear() - var spatial = gizmo.get_spatial_node() + var spatial = gizmo.get_spatial_node() - var lines = PoolVector3Array() + var lines = PoolVector3Array() - lines.push_back(Vector3(0, 1, 0)) - lines.push_back(Vector3(0, spatial.my_custom_value, 0)) + lines.push_back(Vector3(0, 1, 0)) + lines.push_back(Vector3(0, spatial.my_custom_value, 0)) - var handles = PoolVector3Array() + var handles = PoolVector3Array() - handles.push_back(Vector3(0, 1, 0)) - handles.push_back(Vector3(0, spatial.my_custom_value, 0)) + handles.push_back(Vector3(0, 1, 0)) + handles.push_back(Vector3(0, spatial.my_custom_value, 0)) - gizmo.add_lines(lines, get_material("main", gizmo), false) - gizmo.add_handles(handles, get_material("handles", gizmo)) + gizmo.add_lines(lines, get_material("main", gizmo), false) + gizmo.add_handles(handles, get_material("handles", gizmo)) - # You should implement the rest of handle-related callbacks - # (get_handle_name(), get_handle_value(), commit_handle()...). +# You should implement the rest of handle-related callbacks +# (get_handle_name(), get_handle_value(), commit_handle()...). ``` Note that we just added some handles in the redraw method, but we still need to implement @@ -170,62 +170,62 @@ In these cases all we need to do is, in our new gizmo plugin, override for the Spatial nodes we want to target. ``` - # MyCustomGizmoPlugin.gd - extends EditorSpatialGizmoPlugin +# MyCustomGizmoPlugin.gd +extends EditorSpatialGizmoPlugin - const MyCustomSpatial = preload("res://addons/my-addon/MyCustomSpatial.gd") - const MyCustomGizmo = preload("res://addons/my-addon/MyCustomGizmo.gd") +const MyCustomSpatial = preload("res://addons/my-addon/MyCustomSpatial.gd") +const MyCustomGizmo = preload("res://addons/my-addon/MyCustomGizmo.gd") - func _init(): - create_material("main", Color(1, 0, 0)) - create_handle_material("handles") +func _init(): + create_material("main", Color(1, 0, 0)) + create_handle_material("handles") - func create_gizmo(spatial): - if spatial is MyCustomSpatial: - return MyCustomGizmo.new() - else: - return null +func create_gizmo(spatial): + if spatial is MyCustomSpatial: + return MyCustomGizmo.new() + else: + return null ``` This way all the gizmo logic and drawing methods can be implemented in a new class extending `EditorSpatialGizmo( EditorSpatialGizmo )`, like so: ``` - # MyCustomGizmo.gd - extends EditorSpatialGizmo +# MyCustomGizmo.gd +extends EditorSpatialGizmo - # You can store data in the gizmo itself (more useful when working with handles). - var gizmo_size = 3.0 +# You can store data in the gizmo itself (more useful when working with handles). +var gizmo_size = 3.0 - func redraw(): - clear() +func redraw(): + clear() - var spatial = get_spatial_node() + var spatial = get_spatial_node() - var lines = PoolVector3Array() + var lines = PoolVector3Array() - lines.push_back(Vector3(0, 1, 0)) - lines.push_back(Vector3(gizmo_size, spatial.my_custom_value, 0)) + lines.push_back(Vector3(0, 1, 0)) + lines.push_back(Vector3(gizmo_size, spatial.my_custom_value, 0)) - var handles = PoolVector3Array() + var handles = PoolVector3Array() - handles.push_back(Vector3(0, 1, 0)) - handles.push_back(Vector3(gizmo_size, spatial.my_custom_value, 0)) + handles.push_back(Vector3(0, 1, 0)) + handles.push_back(Vector3(gizmo_size, spatial.my_custom_value, 0)) - var material = get_plugin().get_material("main", self) - add_lines(lines, material, false) + var material = get_plugin().get_material("main", self) + add_lines(lines, material, false) - var handles_material = get_plugin().get_material("handles", self) - add_handles(handles, handles_material) + var handles_material = get_plugin().get_material("handles", self) + add_handles(handles, handles_material) - # You should implement the rest of handle-related callbacks - # (get_handle_name(), get_handle_value(), commit_handle()...). +# You should implement the rest of handle-related callbacks +# (get_handle_name(), get_handle_value(), commit_handle()...). ``` Note that we just added some handles in the redraw method, but we still need to implement diff --git a/03_usage/19_plugins/editor/06_inspector_plugins.md b/03_usage/19_plugins/editor/06_inspector_plugins.md index 574aa8c..e2f3774 100644 --- a/03_usage/19_plugins/editor/06_inspector_plugins.md +++ b/03_usage/19_plugins/editor/06_inspector_plugins.md @@ -43,20 +43,20 @@ Note: gdscript GDScript ``` - # plugin.gd - tool - extends EditorPlugin +# plugin.gd +tool +extends EditorPlugin - var plugin +var plugin - func _enter_tree(): - plugin = preload("res://addons/my_inspector_plugin/MyInspectorPlugin.gd").new() - add_inspector_plugin(plugin) +func _enter_tree(): + plugin = preload("res://addons/my_inspector_plugin/MyInspectorPlugin.gd").new() + add_inspector_plugin(plugin) - func _exit_tree(): - remove_inspector_plugin(plugin) +func _exit_tree(): + remove_inspector_plugin(plugin) ``` @@ -88,28 +88,28 @@ specifically add `EditorProperty`-based controls. gdscript GDScript ``` - # MyInspectorPlugin.gd - extends EditorInspectorPlugin +# MyInspectorPlugin.gd +extends EditorInspectorPlugin - var RandomIntEditor = preload("res://addons/my_inspector_plugin/RandomIntEditor.gd") +var RandomIntEditor = preload("res://addons/my_inspector_plugin/RandomIntEditor.gd") - func can_handle(object): - # We support all objects in this example. +func can_handle(object): + # We support all objects in this example. + return true + + +func parse_property(object, type, path, hint, hint_text, usage): + # We handle properties of type integer. + if type == TYPE_INT: + # Create an instance of the custom property editor and register + # it to a specific property path. + add_property_editor(path, RandomIntEditor.new()) + # Inform the editor to remove the default property editor for + # this property type. return true - - - func parse_property(object, type, path, hint, hint_text, usage): - # We handle properties of type integer. - if type == TYPE_INT: - # Create an instance of the custom property editor and register - # it to a specific property path. - add_property_editor(path, RandomIntEditor.new()) - # Inform the editor to remove the default property editor for - # this property type. - return true - else: - return false + else: + return false ``` @@ -138,53 +138,53 @@ followed by `set_bottom_editor()` to position it below the name. gdscript GDScript ``` - # RandomIntEditor.gd - extends EditorProperty +# RandomIntEditor.gd +extends EditorProperty - # The main control for editing the property. - var property_control = Button.new() - # An internal value of the property. - var current_value = 0 - # A guard against internal changes when the property is updated. - var updating = false +# The main control for editing the property. +var property_control = Button.new() +# An internal value of the property. +var current_value = 0 +# A guard against internal changes when the property is updated. +var updating = false - func _init(): - # Add the control as a direct child of EditorProperty node. - add_child(property_control) - # Make sure the control is able to retain the focus. - add_focusable(property_control) - # Setup the initial state and connect to the signal to track changes. - refresh_control_text() - property_control.connect("pressed", self, "_on_button_pressed") +func _init(): + # Add the control as a direct child of EditorProperty node. + add_child(property_control) + # Make sure the control is able to retain the focus. + add_focusable(property_control) + # Setup the initial state and connect to the signal to track changes. + refresh_control_text() + property_control.connect("pressed", self, "_on_button_pressed") - func _on_button_pressed(): - # Ignore the signal if the property is currently being updated. - if (updating): - return +func _on_button_pressed(): + # Ignore the signal if the property is currently being updated. + if (updating): + return - # Generate a new random integer between 0 and 99. - current_value = randi() % 100 - refresh_control_text() - emit_changed(get_edited_property(), current_value) + # Generate a new random integer between 0 and 99. + current_value = randi() % 100 + refresh_control_text() + emit_changed(get_edited_property(), current_value) - func update_property(): - # Read the current value from the property. - var new_value = get_edited_object()[get_edited_property()] - if (new_value == current_value): - return +func update_property(): + # Read the current value from the property. + var new_value = get_edited_object()[get_edited_property()] + if (new_value == current_value): + return - # Update the control with the new value. - updating = true - current_value = new_value - refresh_control_text() - updating = false + # Update the control with the new value. + updating = true + current_value = new_value + refresh_control_text() + updating = false - func refresh_control_text(): - property_control.text = "Value: " + str(current_value) +func refresh_control_text(): + property_control.text = "Value: " + str(current_value) ``` Using the example code above you should be able to make a custom widget that diff --git a/03_usage/19_plugins/editor/07_visual_shader_plugins.md b/03_usage/19_plugins/editor/07_visual_shader_plugins.md index 92db9a6..fc6aa7a 100644 --- a/03_usage/19_plugins/editor/07_visual_shader_plugins.md +++ b/03_usage/19_plugins/editor/07_visual_shader_plugins.md @@ -30,163 +30,163 @@ Create a script which derives from `VisualShaderNodeCustom`. This is all you need to initialize your plugin. ``` - # PerlinNoise3D.gd - tool - extends VisualShaderNodeCustom - class_name VisualShaderNodePerlinNoise3D +# PerlinNoise3D.gd +tool +extends VisualShaderNodeCustom +class_name VisualShaderNodePerlinNoise3D - func _get_name(): - return "PerlinNoise3D" +func _get_name(): + return "PerlinNoise3D" - func _get_category(): - return "MyShaderNodes" +func _get_category(): + return "MyShaderNodes" - func _get_description(): - return "Classic Perlin-Noise-3D function (by Curly-Brace)" +func _get_description(): + return "Classic Perlin-Noise-3D function (by Curly-Brace)" - func _get_return_icon_type(): - return VisualShaderNode.PORT_TYPE_SCALAR +func _get_return_icon_type(): + return VisualShaderNode.PORT_TYPE_SCALAR - func _get_input_port_count(): - return 4 +func _get_input_port_count(): + return 4 - func _get_input_port_name(port): - match port: - 0: - return "uv" - 1: - return "offset" - 2: - return "scale" - 3: - return "time" +func _get_input_port_name(port): + match port: + 0: + return "uv" + 1: + return "offset" + 2: + return "scale" + 3: + return "time" - func _get_input_port_type(port): - match port: - 0: - return VisualShaderNode.PORT_TYPE_VECTOR - 1: - return VisualShaderNode.PORT_TYPE_VECTOR - 2: - return VisualShaderNode.PORT_TYPE_SCALAR - 3: - return VisualShaderNode.PORT_TYPE_SCALAR +func _get_input_port_type(port): + match port: + 0: + return VisualShaderNode.PORT_TYPE_VECTOR + 1: + return VisualShaderNode.PORT_TYPE_VECTOR + 2: + return VisualShaderNode.PORT_TYPE_SCALAR + 3: + return VisualShaderNode.PORT_TYPE_SCALAR - func _get_output_port_count(): - return 1 +func _get_output_port_count(): + return 1 - func _get_output_port_name(port): - return "result" +func _get_output_port_name(port): + return "result" - func _get_output_port_type(port): - return VisualShaderNode.PORT_TYPE_SCALAR +func _get_output_port_type(port): + return VisualShaderNode.PORT_TYPE_SCALAR - func _get_global_code(mode): - return """ - vec3 mod289_3(vec3 x) { - return x - floor(x * (1.0 / 289.0)) * 289.0; - } +func _get_global_code(mode): + return """ + vec3 mod289_3(vec3 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; + } - vec4 mod289_4(vec4 x) { - return x - floor(x * (1.0 / 289.0)) * 289.0; - } + vec4 mod289_4(vec4 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; + } - vec4 permute(vec4 x) { - return mod289_4(((x * 34.0) + 1.0) * x); - } + vec4 permute(vec4 x) { + return mod289_4(((x * 34.0) + 1.0) * x); + } - vec4 taylorInvSqrt(vec4 r) { - return 1.79284291400159 - 0.85373472095314 * r; - } + vec4 taylorInvSqrt(vec4 r) { + return 1.79284291400159 - 0.85373472095314 * r; + } - vec3 fade(vec3 t) { - return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); - } + vec3 fade(vec3 t) { + return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); + } - // Classic Perlin noise. - float cnoise(vec3 P) { - vec3 Pi0 = floor(P); // Integer part for indexing. - vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1. - Pi0 = mod289_3(Pi0); - Pi1 = mod289_3(Pi1); - vec3 Pf0 = fract(P); // Fractional part for interpolation. - vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0. - vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); - vec4 iy = vec4(Pi0.yy, Pi1.yy); - vec4 iz0 = vec4(Pi0.z); - vec4 iz1 = vec4(Pi1.z); + // Classic Perlin noise. + float cnoise(vec3 P) { + vec3 Pi0 = floor(P); // Integer part for indexing. + vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1. + Pi0 = mod289_3(Pi0); + Pi1 = mod289_3(Pi1); + vec3 Pf0 = fract(P); // Fractional part for interpolation. + vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0. + vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); + vec4 iy = vec4(Pi0.yy, Pi1.yy); + vec4 iz0 = vec4(Pi0.z); + vec4 iz1 = vec4(Pi1.z); - vec4 ixy = permute(permute(ix) + iy); - vec4 ixy0 = permute(ixy + iz0); - vec4 ixy1 = permute(ixy + iz1); + vec4 ixy = permute(permute(ix) + iy); + vec4 ixy0 = permute(ixy + iz0); + vec4 ixy1 = permute(ixy + iz1); - vec4 gx0 = ixy0 * (1.0 / 7.0); - vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; - gx0 = fract(gx0); - vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); - vec4 sz0 = step(gz0, vec4(0.0)); - gx0 -= sz0 * (step(0.0, gx0) - 0.5); - gy0 -= sz0 * (step(0.0, gy0) - 0.5); + vec4 gx0 = ixy0 * (1.0 / 7.0); + vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; + gx0 = fract(gx0); + vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); + vec4 sz0 = step(gz0, vec4(0.0)); + gx0 -= sz0 * (step(0.0, gx0) - 0.5); + gy0 -= sz0 * (step(0.0, gy0) - 0.5); - vec4 gx1 = ixy1 * (1.0 / 7.0); - vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; - gx1 = fract(gx1); - vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); - vec4 sz1 = step(gz1, vec4(0.0)); - gx1 -= sz1 * (step(0.0, gx1) - 0.5); - gy1 -= sz1 * (step(0.0, gy1) - 0.5); + vec4 gx1 = ixy1 * (1.0 / 7.0); + vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; + gx1 = fract(gx1); + vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); + vec4 sz1 = step(gz1, vec4(0.0)); + gx1 -= sz1 * (step(0.0, gx1) - 0.5); + gy1 -= sz1 * (step(0.0, gy1) - 0.5); - vec3 g000 = vec3(gx0.x, gy0.x, gz0.x); - vec3 g100 = vec3(gx0.y, gy0.y, gz0.y); - vec3 g010 = vec3(gx0.z, gy0.z, gz0.z); - vec3 g110 = vec3(gx0.w, gy0.w, gz0.w); - vec3 g001 = vec3(gx1.x, gy1.x, gz1.x); - vec3 g101 = vec3(gx1.y, gy1.y, gz1.y); - vec3 g011 = vec3(gx1.z, gy1.z, gz1.z); - vec3 g111 = vec3(gx1.w, gy1.w, gz1.w); + vec3 g000 = vec3(gx0.x, gy0.x, gz0.x); + vec3 g100 = vec3(gx0.y, gy0.y, gz0.y); + vec3 g010 = vec3(gx0.z, gy0.z, gz0.z); + vec3 g110 = vec3(gx0.w, gy0.w, gz0.w); + vec3 g001 = vec3(gx1.x, gy1.x, gz1.x); + vec3 g101 = vec3(gx1.y, gy1.y, gz1.y); + vec3 g011 = vec3(gx1.z, gy1.z, gz1.z); + vec3 g111 = vec3(gx1.w, gy1.w, gz1.w); - vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); - g000 *= norm0.x; - g010 *= norm0.y; - g100 *= norm0.z; - g110 *= norm0.w; - vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); - g001 *= norm1.x; - g011 *= norm1.y; - g101 *= norm1.z; - g111 *= norm1.w; + vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); + g000 *= norm0.x; + g010 *= norm0.y; + g100 *= norm0.z; + g110 *= norm0.w; + vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); + g001 *= norm1.x; + g011 *= norm1.y; + g101 *= norm1.z; + g111 *= norm1.w; - float n000 = dot(g000, Pf0); - float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); - float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); - float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); - float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); - float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); - float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); - float n111 = dot(g111, Pf1); + float n000 = dot(g000, Pf0); + float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); + float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); + float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); + float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); + float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); + float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); + float n111 = dot(g111, Pf1); - vec3 fade_xyz = fade(Pf0); - vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); - vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); - float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); - return 2.2 * n_xyz; - } - """ + vec3 fade_xyz = fade(Pf0); + vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); + vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); + float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); + return 2.2 * n_xyz; + } + """ - func _get_code(input_vars, output_vars, mode, type): - return output_vars[0] + " = cnoise(vec3((%s.xy + %s.xy) * %s, %s)) * 0.5 + 0.5;" % [input_vars[0], input_vars[1], input_vars[2], input_vars[3]] +func _get_code(input_vars, output_vars, mode, type): + return output_vars[0] + " = cnoise(vec3((%s.xy + %s.xy) * %s, %s)) * 0.5 + 0.5;" % [input_vars[0], input_vars[1], input_vars[2], input_vars[3]] ``` Save it and open the Visual Shader. You should see your new node type within the member's dialog (if you can't see your new node, try restarting the editor): diff --git a/03_usage/20_export/01_exporting_basics.md b/03_usage/20_export/01_exporting_basics.md index 1d92a2f..333a824 100644 --- a/03_usage/20_export/01_exporting_basics.md +++ b/03_usage/20_export/01_exporting_basics.md @@ -55,73 +55,73 @@ changed: gdscript GDScript ``` - extends Area2D +extends Area2D - signal hit +signal hit - export var speed = 400 - var screen_size - # Add this variable to hold the clicked position. - var target = Vector2() +export var speed = 400 +var screen_size +# Add this variable to hold the clicked position. +var target = Vector2() - func _ready(): - hide() - screen_size = get_viewport_rect().size +func _ready(): + hide() + screen_size = get_viewport_rect().size - func start(pos): - position = pos - # Initial target is the start position. - target = pos - show() - $CollisionShape2D.disabled = false +func start(pos): + position = pos + # Initial target is the start position. + target = pos + show() + $CollisionShape2D.disabled = false - # Change the target whenever a touch event happens. - func _input(event): - if event is InputEventScreenTouch and event.pressed: - target = event.position +# Change the target whenever a touch event happens. +func _input(event): + if event is InputEventScreenTouch and event.pressed: + target = event.position - func _process(delta): - var velocity = Vector2() - # Move towards the target and stop when close. - if position.distance_to(target) > 10: - velocity = target - position +func _process(delta): + var velocity = Vector2() + # Move towards the target and stop when close. + if position.distance_to(target) > 10: + velocity = target - position - # Remove keyboard controls. - # if Input.is_action_pressed("ui_right"): - # velocity.x += 1 - # if Input.is_action_pressed("ui_left"): - # velocity.x -= 1 - # if Input.is_action_pressed("ui_down"): - # velocity.y += 1 - # if Input.is_action_pressed("ui_up"): - # velocity.y -= 1 +# Remove keyboard controls. +# if Input.is_action_pressed("ui_right"): +# velocity.x += 1 +# if Input.is_action_pressed("ui_left"): +# velocity.x -= 1 +# if Input.is_action_pressed("ui_down"): +# velocity.y += 1 +# if Input.is_action_pressed("ui_up"): +# velocity.y -= 1 - if velocity.length() > 0: - velocity = velocity.normalized() * speed - $AnimatedSprite.play() - else: - $AnimatedSprite.stop() + if velocity.length() > 0: + velocity = velocity.normalized() * speed + $AnimatedSprite.play() + else: + $AnimatedSprite.stop() - position += velocity * delta - # We still need to clamp the player's position here because on devices that don't - # match your game's aspect ratio, Pandemonium will try to maintain it as much as possible - # by creating black borders, if necessary. - # Without clamp(), the player would be able to move under those borders. - position.x = clamp(position.x, 0, screen_size.x) - position.y = clamp(position.y, 0, screen_size.y) + position += velocity * delta + # We still need to clamp the player's position here because on devices that don't + # match your game's aspect ratio, Pandemonium will try to maintain it as much as possible + # by creating black borders, if necessary. + # Without clamp(), the player would be able to move under those borders. + position.x = clamp(position.x, 0, screen_size.x) + position.y = clamp(position.y, 0, screen_size.y) - if velocity.x != 0: - $AnimatedSprite.animation = "walk" - $AnimatedSprite.flip_v = false - $AnimatedSprite.flip_h = velocity.x < 0 - elif velocity.y != 0: - $AnimatedSprite.animation = "up" - $AnimatedSprite.flip_v = velocity.y > 0 + if velocity.x != 0: + $AnimatedSprite.animation = "walk" + $AnimatedSprite.flip_v = false + $AnimatedSprite.flip_h = velocity.x < 0 + elif velocity.y != 0: + $AnimatedSprite.animation = "up" + $AnimatedSprite.flip_v = velocity.y > 0 - func _on_Player_body_entered( body ): - hide() - emit_signal("hit") - $CollisionShape2D.set_deferred("disabled", true) +func _on_Player_body_entered( body ): + hide() + emit_signal("hit") + $CollisionShape2D.set_deferred("disabled", true) ``` ## Setting a main scene @@ -231,7 +231,7 @@ Next, create a debug keystore by running the following command on your system's command line: ``` - keytool -keyalg RSA -genkeypair -alias androiddebugkey -keypass android -keystore debug.keystore -storepass android -dname "CN=Android Debug,O=Android,C=US" -validity 9999 +keytool -keyalg RSA -genkeypair -alias androiddebugkey -keypass android -keystore debug.keystore -storepass android -dname "CN=Android Debug,O=Android,C=US" -validity 9999 ``` Click on *Editor -> Editor Settings* in Pandemonium and select the *Export/Android* @@ -250,7 +250,7 @@ Click the **Export Project** button and Pandemonium will build an APK you can do on your device. To do this on the command line, use the following: ``` - adb install dodge.apk +adb install dodge.apk ``` Note: diff --git a/03_usage/20_export/02_exporting_projects.md b/03_usage/20_export/02_exporting_projects.md index 4be5b4c..2087510 100644 --- a/03_usage/20_export/02_exporting_projects.md +++ b/03_usage/20_export/02_exporting_projects.md @@ -132,7 +132,7 @@ Exporting from the command line still requires an export preset to define the export parameters. A basic invocation of the command would be: ``` - pandemonium --export "Windows Desktop" some_name.exe +pandemonium --export "Windows Desktop" some_name.exe ``` This will export to `some_name.exe`, assuming there is a preset @@ -156,7 +156,7 @@ a single exported main pack file to be used with multiple Pandemonium executable When doing so, the export preset name must still be specified on the command line: ``` - pandemonium --export-pack "Windows Desktop" some_name.pck +pandemonium --export-pack "Windows Desktop" some_name.pck ``` It is often useful to combine the `--export` flag with the `--path` @@ -164,7 +164,7 @@ flag, so that you do not need to `cd` to the project folder before running the command: ``` - pandemonium --path /path/to/project --export "Windows Desktop" some_name.exe +pandemonium --path /path/to/project --export "Windows Desktop" some_name.exe ``` See also: @@ -202,12 +202,12 @@ Warning: the player can double-click or run from a terminal to launch the project: ``` - launch.bat (Windows) - @echo off - my_project.exe --main-pack my_project.zip +launch.bat (Windows) +@echo off +my_project.exe --main-pack my_project.zip - # launch.sh (Linux) - ./my_project.x86_64 --main-pack my_project.zip +# launch.sh (Linux) +./my_project.x86_64 --main-pack my_project.zip ``` Save the launcher script and place it in the same folder as the exported binary. diff --git a/03_usage/20_export/03_exporting_pcks.md b/03_usage/20_export/03_exporting_pcks.md index ebb7ea2..0ab6057 100644 --- a/03_usage/20_export/03_exporting_pcks.md +++ b/03_usage/20_export/03_exporting_pcks.md @@ -92,13 +92,13 @@ The PCK file contains a “mod_scene.tscn” test scene in its root. gdscript GDScript ``` - func _your_function(): - # This could fail if, for example, mod.pck cannot be found. - var success = ProjectSettings.load_resource_pack("res://mod.pck") +func _your_function(): + # This could fail if, for example, mod.pck cannot be found. + var success = ProjectSettings.load_resource_pack("res://mod.pck") - if success: - # Now one can use the assets as if they had them in the project from the start. - var imported_scene = load("res://mod_scene.tscn") + if success: + # Now one can use the assets as if they had them in the project from the start. + var imported_scene = load("res://mod_scene.tscn") ``` Warning: diff --git a/03_usage/20_export/04_feature_tags.md b/03_usage/20_export/04_feature_tags.md index 4cd3138..11040b9 100644 --- a/03_usage/20_export/04_feature_tags.md +++ b/03_usage/20_export/04_feature_tags.md @@ -17,7 +17,7 @@ Each *feature* is represented as a string, which can refer to many of the follow Features can be queried at run-time from the singleton API by calling: ``` - OS.has_feature(name) +OS.has_feature(name) ``` ## Default features diff --git a/03_usage/20_export/09_changing_application_icon_for_windows.md b/03_usage/20_export/09_changing_application_icon_for_windows.md index 7754ccf..522cf02 100644 --- a/03_usage/20_export/09_changing_application_icon_for_windows.md +++ b/03_usage/20_export/09_changing_application_icon_for_windows.md @@ -19,13 +19,13 @@ It is also possible to convert a PNG image to an hiDPI-friendly ICO file using this `ImageMagick ( https://www.imagemagick.org/ )` command: ``` - magick convert icon.png -define icon:auto-resize=256,128,64,48,32,16 icon.ico +magick convert icon.png -define icon:auto-resize=256,128,64,48,32,16 icon.ico ``` Depending on which version of ImageMagick you installed, you might need to leave out the `magick` and run this command instead: ``` - convert icon.png -define icon:auto-resize=256,128,64,48,32,16 icon.ico +convert icon.png -define icon:auto-resize=256,128,64,48,32,16 icon.ico ``` Warning: diff --git a/03_usage/20_export/10_exporting_for_uwp.md b/03_usage/20_export/10_exporting_for_uwp.md index 5aae986..b95086c 100644 --- a/03_usage/20_export/10_exporting_for_uwp.md +++ b/03_usage/20_export/10_exporting_for_uwp.md @@ -46,7 +46,7 @@ You can get more detailed instructions from `Microsoft's documentation First, run `MakeCert` to create a private key: ``` - MakeCert /n publisherName /r /h 0 /eku "1.3.6.1.5.5.7.3.3,1.3.6.1.4.1.311.10.3.13" /e expirationDate /sv MyKey.pvk MyKey.cer +MakeCert /n publisherName /r /h 0 /eku "1.3.6.1.5.5.7.3.3,1.3.6.1.4.1.311.10.3.13" /e expirationDate /sv MyKey.pvk MyKey.cer ``` Where `publisherName` matches the Publisher Name of your package and @@ -55,7 +55,7 @@ Where `publisherName` matches the Publisher Name of your package and Next, create a Personal Information Exchange (.pfx) file using `Pvk2Pfx.exe`: ``` - Pvk2Pfx /pvk MyKey.pvk /pi pvkPassword /spc MyKey.cer /pfx MyKey.pfx [/po pfxPassword] +Pvk2Pfx /pvk MyKey.pvk /pi pvkPassword /spc MyKey.cer /pfx MyKey.pfx [/po pfxPassword] ``` If you don't specify a password with `/po` argument, the PFX will have the @@ -65,7 +65,7 @@ You will also need to trust this certificate in order to be able to install your app. Open the Command Prompt as Administrator and run the following command: ``` - Certutil -addStore TrustedPeople MyKey.cer +Certutil -addStore TrustedPeople MyKey.cer ``` ## Setting up automatic signing diff --git a/03_usage/20_export/12_exporting_for_android.md b/03_usage/20_export/12_exporting_for_android.md index 0af8621..36147ba 100644 --- a/03_usage/20_export/12_exporting_for_android.md +++ b/03_usage/20_export/12_exporting_for_android.md @@ -35,7 +35,7 @@ Download and install the Android SDK. - Once the command line tools are installed, run the `sdkmanager ( https://developer.android.com/studio/command-line/sdkmanager )` command to complete the setup process: ``` - sdkmanager --sdk_root= "platform-tools" "build-tools;30.0.3" "platforms;android-31" "cmdline-tools;latest" "cmake;3.10.2.4988404" "ndk;21.4.7075529" +sdkmanager --sdk_root= "platform-tools" "build-tools;30.0.3" "platforms;android-31" "cmdline-tools;latest" "cmake;3.10.2.4988404" "ndk;21.4.7075529" ``` Note: @@ -56,7 +56,7 @@ If you can't find it or need to generate one, the keytool command from the JDK can be used for this purpose: ``` - keytool -keyalg RSA -genkeypair -alias androiddebugkey -keypass android -keystore debug.keystore -storepass android -dname "CN=Android Debug,O=Android,C=US" -validity 9999 -deststoretype pkcs12 +keytool -keyalg RSA -genkeypair -alias androiddebugkey -keypass android -keystore debug.keystore -storepass android -dname "CN=Android Debug,O=Android,C=US" -validity 9999 -deststoretype pkcs12 ``` This will create a `debug.keystore` file in your current directory. You should move it to a memorable location such as `%USERPROFILE%\.android\`, because you will need its location in a later step. For more information on `keytool` usage, see `this Q&A article ( https://pandemoniumengine.org/qa/21349/jdk-android-file-missing )`. @@ -123,7 +123,7 @@ Uploading an APK to Google's Play Store requires you to sign using a non-debug keystore file; such file can be generated like this: ``` - keytool -v -genkey -keystore mygame.keystore -alias mygame -keyalg RSA -validity 10000 +keytool -v -genkey -keystore mygame.keystore -alias mygame -keyalg RSA -validity 10000 ``` This keystore and key are used to verify your developer identity, remember the password and keep it in a safe place! diff --git a/03_usage/20_export/14_exporting_for_web.md b/03_usage/20_export/14_exporting_for_web.md index b89c411..2242b28 100644 --- a/03_usage/20_export/14_exporting_for_web.md +++ b/03_usage/20_export/14_exporting_for_web.md @@ -275,8 +275,8 @@ This allows interacting with the browser in ways not possible with script languages integrated into Pandemonium. ``` - func my_func(): - JavaScript.eval("alert('Calling JavaScript per GDScript!');") +func my_func(): + JavaScript.eval("alert('Calling JavaScript per GDScript!');") ``` The value of the last JavaScript statement is converted to a GDScript value and @@ -289,9 +289,9 @@ returned by `eval()` under certain circumstances: GDScript `PoolByteArray` ``` - func my_func2(): - var js_return = JavaScript.eval("var myNumber = 1; myNumber + 2;") - print(js_return) # prints '3.0' +func my_func2(): + var js_return = JavaScript.eval("var myNumber = 1; myNumber + 2;") + print(js_return) # prints '3.0' ``` Any other JavaScript value is returned as `null`. @@ -303,13 +303,13 @@ platforms other than HTML5, calling `JavaScript.eval` will also return `JavaScript` `feature tag ( doc_feature_tags )`: ``` - func my_func3(): - if OS.has_feature('JavaScript'): - JavaScript.eval(""" - console.log('The JavaScript singleton is available') - """) - else: - print("The JavaScript singleton is NOT available") +func my_func3(): + if OS.has_feature('JavaScript'): + JavaScript.eval(""" + console.log('The JavaScript singleton is available') + """) + else: + print("The JavaScript singleton is NOT available") ``` Tip: @@ -321,8 +321,8 @@ specifies whether to execute the code in the global execution context, defaulting to `false` to prevent polluting the global namespace: ``` - func my_func4(): - # execute in global execution context, - # thus adding a new JavaScript global variable `SomeGlobal` - JavaScript.eval("var SomeGlobal = {};", true) +func my_func4(): + # execute in global execution context, + # thus adding a new JavaScript global variable `SomeGlobal` + JavaScript.eval("var SomeGlobal = {};", true) ``` diff --git a/03_usage/20_export/15_exporting_for_dedicated_servers.md b/03_usage/20_export/15_exporting_for_dedicated_servers.md index 12a99c8..2cd743c 100644 --- a/03_usage/20_export/15_exporting_for_dedicated_servers.md +++ b/03_usage/20_export/15_exporting_for_dedicated_servers.md @@ -77,7 +77,7 @@ different name, you can specify the path to the PCK file using the `--main-pack` command-line argument: ``` - ./pandemonium-server --main-pack my_project.pck +./pandemonium-server --main-pack my_project.pck ``` ## Starting the dedicated server @@ -88,23 +88,23 @@ can be done by adding the following code snippet in your main scene (or a singleton)'s `ready()` method: ``` - if "--server" in OS.get_cmdline_args(): - # Run your server startup code here... - # Using this check, you can start a dedicated server by running - # a Pandemonium binary (headless or not) with the `--server` command-line argument. - pass +if "--server" in OS.get_cmdline_args(): + # Run your server startup code here... + # Using this check, you can start a dedicated server by running + # a Pandemonium binary (headless or not) with the `--server` command-line argument. + pass ``` Alternatively, you can make the dedicated server always start up if a headless or server binary is detected: ``` - # Note: Feature tags are case-sensitive! It's "Server", not "server". - if OS.has_feature("Server"): - # Run your server startup code here... - # Note that using this check may break unit testing scripts when - # running them with headless or server binaries. - pass +# Note: Feature tags are case-sensitive! It's "Server", not "server". +if OS.has_feature("Server"): + # Run your server startup code here... + # Note that using this check may break unit testing scripts when + # running them with headless or server binaries. + pass ``` If your client and server are separate Pandemonium projects, your server should most diff --git a/03_usage/21_assets_pipeline/04_importing_translations.md b/03_usage/21_assets_pipeline/04_importing_translations.md index 02e12eb..6a2f0d0 100644 --- a/03_usage/21_assets_pipeline/04_importing_translations.md +++ b/03_usage/21_assets_pipeline/04_importing_translations.md @@ -88,11 +88,11 @@ another double quote. Alternatively, you can select another delimiter than comma in the import options. ``` - keys,en,es,ja - GREET,"Hello, friend!","Hola, amigo!",こんにちは - ASK,How are you?,Cómo está?,元気ですか - BYE,Goodbye,Adiós,さようなら - QUOTE,"""Hello"" said the man.","""Hola"" dijo el hombre.",「こんにちは」男は言いました +keys,en,es,ja +GREET,"Hello, friend!","Hola, amigo!",こんにちは +ASK,How are you?,Cómo está?,元気ですか +BYE,Goodbye,Adiós,さようなら +QUOTE,"""Hello"" said the man.","""Hola"" dijo el hombre.",「こんにちは」男は言いました ``` ## CSV importer diff --git a/03_usage/21_assets_pipeline/05_importing_scenes.md b/03_usage/21_assets_pipeline/05_importing_scenes.md index 2e71a72..64be316 100644 --- a/03_usage/21_assets_pipeline/05_importing_scenes.md +++ b/03_usage/21_assets_pipeline/05_importing_scenes.md @@ -158,13 +158,13 @@ with the geometry etc. Create a script like this: ``` - tool # Needed so it runs in the editor. - extends EditorScenePostImport +tool # Needed so it runs in the editor. +extends EditorScenePostImport - func post_import(scene): - # Do your stuff here. - return scene # remember to return the imported scene +func post_import(scene): + # Do your stuff here. + return scene # remember to return the imported scene ``` The `post_import` function takes the imported scene as argument (the @@ -310,7 +310,7 @@ The script must start with an animation filter statement (as denoted by the line ending in `"_Loop"`: ``` - @+*_Loop +@+*_Loop ``` Similarly, additional patterns can be added to the same line, separated by commas. Here is a @@ -318,7 +318,7 @@ modified example to additionally *include* all animations with names that begin but also *exclude* all animations which have names ending in `"Attack"`: ``` - @+*_Loop, +Arm_Left*, -*Attack +@+*_Loop, +Arm_Left*, -*Attack ``` Following the animation selection filter statement, we add track filtering patterns to indicate @@ -335,10 +335,10 @@ tracks affecting a `"Skeleton"` which end in `"Control"`, unless they have `"Arm name: ``` - @+*_Loop - +* - -Skeleton:*Control - +*Arm* +@+*_Loop ++* +-Skeleton:*Control ++*Arm* ``` In the above example, tracks like `"Skeleton:Leg_Control"` would be discarded, while tracks such diff --git a/03_usage/22_best_practices/03_scene_organization.md b/03_usage/22_best_practices/03_scene_organization.md index d7084b5..ee9339d 100644 --- a/03_usage/22_best_practices/03_scene_organization.md +++ b/03_usage/22_best_practices/03_scene_organization.md @@ -54,11 +54,11 @@ initialize it: gdscript GDScript ``` - # Parent - $Child.connect("signal_name", object_with_method, "method_on_the_object") +# Parent +$Child.connect("signal_name", object_with_method, "method_on_the_object") - # Child - emit_signal("signal_name") # Triggers parent-defined behavior. +# Child +emit_signal("signal_name") # Triggers parent-defined behavior. ``` 2. Call a method. Used to start behavior. @@ -66,11 +66,11 @@ gdscript GDScript gdscript GDScript ``` - # Parent - $Child.method_name = "do" +# Parent +$Child.method_name = "do" - # Child, assuming it has String property 'method_name' and method 'do'. - call(method_name) # Call parent-defined method (which child must own). +# Child, assuming it has String property 'method_name' and method 'do'. +call(method_name) # Call parent-defined method (which child must own). ``` 3. Initialize a `FuncRef` property. Safer than a method @@ -79,11 +79,11 @@ gdscript GDScript gdscript GDScript ``` - # Parent - $Child.func_property = funcref(object_with_method, "method_on_the_object") +# Parent +$Child.func_property = funcref(object_with_method, "method_on_the_object") - # Child - func_property.call_func() # Call parent-defined method (can come from anywhere). +# Child +func_property.call_func() # Call parent-defined method (can come from anywhere). ``` 4. Initialize a Node or other Object reference. @@ -91,11 +91,11 @@ gdscript GDScript gdscript GDScript ``` - # Parent - $Child.target = self +# Parent +$Child.target = self - # Child - print(target) # Use parent-defined node. +# Child +print(target) # Use parent-defined node. ``` 5. Initialize a NodePath. @@ -103,11 +103,11 @@ gdscript GDScript gdscript GDScript ``` - # Parent - $Child.target_path = ".." +# Parent +$Child.target_path = ".." - # Child - get_node(target_path) # Use parent-defined NodePath. +# Child +get_node(target_path) # Use parent-defined NodePath. ``` These options hide the points of access from the child node. This in turn @@ -125,18 +125,18 @@ Note: gdscript GDScript ``` - # Parent - $Left.target = $Right.get_node("Receiver") +# Parent +$Left.target = $Right.get_node("Receiver") - # Left - var target: Node - func execute(): - # Do something with 'target'. +# Left +var target: Node +func execute(): + # Do something with 'target'. - # Right - func _init(): - var receiver = Receiver.new() - add_child(receiver) +# Right +func _init(): + var receiver = Receiver.new() + add_child(receiver) ``` The same principles also apply to non-Node objects that maintain dependencies diff --git a/03_usage/22_best_practices/04_scenes_versus_scripts.md b/03_usage/22_best_practices/04_scenes_versus_scripts.md index b3e0759..c5340d6 100644 --- a/03_usage/22_best_practices/04_scenes_versus_scripts.md +++ b/03_usage/22_best_practices/04_scenes_versus_scripts.md @@ -24,12 +24,12 @@ a change in API: gdscript GDScript ``` - const MyNode = preload("my_node.gd") - const MyScene = preload("my_scene.tscn") - var node = Node.new() - var my_node = MyNode.new() # Same method call - var my_scene = MyScene.instance() # Different method call - var my_inherited_scene = MyScene.instance(PackedScene.GEN_EDIT_STATE_MAIN) # Create scene inheriting from MyScene +const MyNode = preload("my_node.gd") +const MyScene = preload("my_scene.tscn") +var node = Node.new() +var my_node = MyNode.new() # Same method call +var my_scene = MyScene.instance() # Different method call +var my_inherited_scene = MyScene.instance(PackedScene.GEN_EDIT_STATE_MAIN) # Create scene inheriting from MyScene ``` Also, scripts will operate a little slower than scenes due to the @@ -137,15 +137,15 @@ with it, and finally adds it as a child of the `Main` node: gdscript GDScript ``` - # Main.gd - extends Node +# Main.gd +extends Node - func _init(): - var child = Node.new() - child.name = "Child" - child.script = preload("Child.gd") - child.owner = self - add_child(child) +func _init(): + var child = Node.new() + child.name = "Child" + child.script = preload("Child.gd") + child.owner = self + add_child(child) ``` Script code like this is much slower than engine-side C++ code. Each instruction @@ -177,13 +177,13 @@ In the end, the best approach is to consider the following: gdscript GDScript ``` - # game.gd - extends Reference - class_name Game # extends Reference, so it won't show up in the node creation dialog - const MyScene = preload("my_scene.tscn") +# game.gd +extends Reference +class_name Game # extends Reference, so it won't show up in the node creation dialog +const MyScene = preload("my_scene.tscn") - # main.gd - extends Node - func _ready(): - add_child(Game.MyScene.instance()) +# main.gd +extends Node +func _ready(): + add_child(Game.MyScene.instance()) ``` \ No newline at end of file diff --git a/03_usage/22_best_practices/07_pandemonium_interfaces.md b/03_usage/22_best_practices/07_pandemonium_interfaces.md index f5de94e..ffcd1e0 100644 --- a/03_usage/22_best_practices/07_pandemonium_interfaces.md +++ b/03_usage/22_best_practices/07_pandemonium_interfaces.md @@ -19,8 +19,8 @@ is to get a reference to an existing object from another acquired instance. gdscript GDScript ``` - var obj = node.object # Property access. - var obj = node.get_object() # Method access. +var obj = node.object # Property access. +var obj = node.get_object() # Method access. ``` The same principle applies for `Reference` objects. @@ -33,32 +33,32 @@ access. gdscript GDScript ``` - var preres = preload(path) # Load resource during scene load - var res = load(path) # Load resource when program reaches statement +var preres = preload(path) # Load resource during scene load +var res = load(path) # Load resource when program reaches statement - # Note that users load scenes and scripts, by convention, with PascalCase - # names (like typenames), often into constants. - const MyScene : = preload("my_scene.tscn") as PackedScene # Static load - const MyScript : = preload("my_script.gd") as Script +# Note that users load scenes and scripts, by convention, with PascalCase +# names (like typenames), often into constants. +const MyScene : = preload("my_scene.tscn") as PackedScene # Static load +const MyScript : = preload("my_script.gd") as Script - # This type's value varies, i.e. it is a variable, so it uses snake_case. - export(Script) var script_type: Script +# This type's value varies, i.e. it is a variable, so it uses snake_case. +export(Script) var script_type: Script - # If need an "export const var" (which doesn't exist), use a conditional - # setter for a tool script that checks if it's executing in the editor. - tool # Must place at top of file. +# If need an "export const var" (which doesn't exist), use a conditional +# setter for a tool script that checks if it's executing in the editor. +tool # Must place at top of file. - # Must configure from the editor, defaults to null. - export(Script) var const_script setget set_const_script - func set_const_script(value): - if Engine.is_editor_hint(): - const_script = value +# Must configure from the editor, defaults to null. +export(Script) var const_script setget set_const_script +func set_const_script(value): + if Engine.is_editor_hint(): + const_script = value - # Warn users if the value hasn't been set. - func _get_configuration_warning(): - if not const_script: - return "Must initialize property 'const_script'." - return "" +# Warn users if the value hasn't been set. +func _get_configuration_warning(): + if not const_script: + return "Must initialize property 'const_script'." + return "" ``` Note the following: @@ -78,55 +78,55 @@ Nodes likewise have an alternative access point: the SceneTree. gdscript GDScript ``` - extends Node +extends Node - # Slow. - func dynamic_lookup_with_dynamic_nodepath(): - print(get_node("Child")) +# Slow. +func dynamic_lookup_with_dynamic_nodepath(): + print(get_node("Child")) - # Faster. GDScript only. - func dynamic_lookup_with_cached_nodepath(): - print($Child) +# Faster. GDScript only. +func dynamic_lookup_with_cached_nodepath(): + print($Child) - # Fastest. Doesn't break if node moves later. - # Note that `onready` keyword is GDScript only. - # Other languages must do... - # var child - # func _ready(): - # child = get_node("Child") - onready var child = $Child - func lookup_and_cache_for_future_access(): - print(child) +# Fastest. Doesn't break if node moves later. +# Note that `onready` keyword is GDScript only. +# Other languages must do... +# var child +# func _ready(): +# child = get_node("Child") +onready var child = $Child +func lookup_and_cache_for_future_access(): + print(child) - # Delegate reference assignment to an external source. - # Con: need to perform a validation check. - # Pro: node makes no requirements of its external structure. - # 'prop' can come from anywhere. - var prop - func call_me_after_prop_is_initialized_by_parent(): - # Validate prop in one of three ways. +# Delegate reference assignment to an external source. +# Con: need to perform a validation check. +# Pro: node makes no requirements of its external structure. +# 'prop' can come from anywhere. +var prop +func call_me_after_prop_is_initialized_by_parent(): + # Validate prop in one of three ways. - # Fail with no notification. - if not prop: - return + # Fail with no notification. + if not prop: + return - # Fail with an error message. - if not prop: - printerr("'prop' wasn't initialized") - return + # Fail with an error message. + if not prop: + printerr("'prop' wasn't initialized") + return - # Fail and terminate. - # Note: Scripts run from a release export template don't - # run `assert` statements. - assert(prop, "'prop' wasn't initialized") + # Fail and terminate. + # Note: Scripts run from a release export template don't + # run `assert` statements. + assert(prop, "'prop' wasn't initialized") - # Use an autoload. - # Dangerous for typical nodes, but useful for true singleton nodes - # that manage their own data and don't interfere with other objects. - func reference_a_global_autoloaded_variable(): - print(globals) - print(globals.prop) - print(globals.my_getter()) +# Use an autoload. +# Dangerous for typical nodes, but useful for true singleton nodes +# that manage their own data and don't interfere with other objects. +func reference_a_global_autoloaded_variable(): + print(globals) + print(globals.prop) + print(globals.my_getter()) ``` ## Accessing data or logic from an object @@ -178,19 +178,19 @@ accesses: gdscript GDScript ``` - # All Objects have duck-typed get, set, and call wrapper methods. - get_parent().set("visible", false) +# All Objects have duck-typed get, set, and call wrapper methods. +get_parent().set("visible", false) - # Using a symbol accessor, rather than a string in the method call, - # will implicitly call the `set` method which, in turn, calls the - # setter method bound to the property through the property lookup - # sequence. - get_parent().visible = false +# Using a symbol accessor, rather than a string in the method call, +# will implicitly call the `set` method which, in turn, calls the +# setter method bound to the property through the property lookup +# sequence. +get_parent().visible = false - # Note that if one defines a _set and _get that describe a property's - # existence, but the property isn't recognized in any _get_property_list - # method, then the set() and get() methods will work, but the symbol - # access will claim it can't find the property. +# Note that if one defines a _set and _get that describe a property's +# existence, but the property isn't recognized in any _get_property_list +# method, then the set() and get() methods will work, but the symbol +# access will claim it can't find the property. ``` - A method check. In the case of @@ -200,61 +200,61 @@ gdscript GDScript gdscript GDScript ``` - var child = get_child(0) +var child = get_child(0) - # Dynamic lookup. - child.call("set_visible", false) +# Dynamic lookup. +child.call("set_visible", false) - # Symbol-based dynamic lookup. - # GDScript aliases this into a 'call' method behind the scenes. - child.set_visible(false) +# Symbol-based dynamic lookup. +# GDScript aliases this into a 'call' method behind the scenes. +child.set_visible(false) - # Dynamic lookup, checks for method existence first. - if child.has_method("set_visible"): - child.set_visible(false) +# Dynamic lookup, checks for method existence first. +if child.has_method("set_visible"): + child.set_visible(false) - # Cast check, followed by dynamic lookup. - # Useful when you make multiple "safe" calls knowing that the class - # implements them all. No need for repeated checks. - # Tricky if one executes a cast check for a user-defined type as it - # forces more dependencies. - if child is CanvasItem: - child.set_visible(false) - child.show_on_top = true +# Cast check, followed by dynamic lookup. +# Useful when you make multiple "safe" calls knowing that the class +# implements them all. No need for repeated checks. +# Tricky if one executes a cast check for a user-defined type as it +# forces more dependencies. +if child is CanvasItem: + child.set_visible(false) + child.show_on_top = true - # If one does not wish to fail these checks without notifying users, - # one can use an assert instead. These will trigger runtime errors - # immediately if not true. - assert(child.has_method("set_visible")) - assert(child.is_in_group("offer")) - assert(child is CanvasItem) +# If one does not wish to fail these checks without notifying users, +# one can use an assert instead. These will trigger runtime errors +# immediately if not true. +assert(child.has_method("set_visible")) +assert(child.is_in_group("offer")) +assert(child is CanvasItem) - # Can also use object labels to imply an interface, i.e. assume it - # implements certain methods. - # There are two types, both of which only exist for Nodes: Names and - # Groups. +# Can also use object labels to imply an interface, i.e. assume it +# implements certain methods. +# There are two types, both of which only exist for Nodes: Names and +# Groups. - # Assuming... - # A "Quest" object exists and 1) that it can "complete" or "fail" and - # that it will have text available before and after each state... +# Assuming... +# A "Quest" object exists and 1) that it can "complete" or "fail" and +# that it will have text available before and after each state... - # 1. Use a name. - var quest = $Quest - print(quest.text) - quest.complete() # or quest.fail() - print(quest.text) # implied new text content +# 1. Use a name. +var quest = $Quest +print(quest.text) +quest.complete() # or quest.fail() +print(quest.text) # implied new text content - # 2. Use a group. - for a_child in get_children(): - if a_child.is_in_group("quest"): - print(quest.text) - quest.complete() # or quest.fail() - print(quest.text) # implied new text content +# 2. Use a group. +for a_child in get_children(): + if a_child.is_in_group("quest"): + print(quest.text) + quest.complete() # or quest.fail() + print(quest.text) # implied new text content - # Note that these interfaces are project-specific conventions the team - # defines (which means documentation! But maybe worth it?). - # Any script that conforms to the documented "interface" of the name or - # group can fill in for it. +# Note that these interfaces are project-specific conventions the team +# defines (which means documentation! But maybe worth it?). +# Any script that conforms to the documented "interface" of the name or +# group can fill in for it. ``` - Outsource the access to a `FuncRef`. These may be useful @@ -264,25 +264,25 @@ gdscript GDScript gdscript GDScript ``` - # child.gd - extends Node - var fn = null +# child.gd +extends Node +var fn = null - func my_method(): - if fn: - fn.call_func() +func my_method(): + if fn: + fn.call_func() - # parent.gd - extends Node +# parent.gd +extends Node - onready var child = $Child +onready var child = $Child - func _ready(): - child.fn = funcref(self, "print_me") - child.my_method() +func _ready(): + child.fn = funcref(self, "print_me") + child.my_method() - func print_me(): - print(name) +func print_me(): + print(name) ``` diff --git a/03_usage/22_best_practices/08_pandemonium_notifications.md b/03_usage/22_best_practices/08_pandemonium_notifications.md index ead26de..6db5318 100644 --- a/03_usage/22_best_practices/08_pandemonium_notifications.md +++ b/03_usage/22_best_practices/08_pandemonium_notifications.md @@ -81,13 +81,13 @@ implementing a Timer-yield-timeout loop is another option. gdscript GDScript ``` - # Infinitely loop, but only execute whenever the Timer fires. - # Allows for recurring operations that don't trigger script logic - # every frame (or even every fixed frame). - while true: - my_method() - $Timer.start() - yield($Timer, "timeout") +# Infinitely loop, but only execute whenever the Timer fires. +# Allows for recurring operations that don't trigger script logic +# every frame (or even every fixed frame). +while true: + my_method() + $Timer.start() + yield($Timer, "timeout") ``` Use `physics_process` when one needs a framerate-independent deltatime @@ -108,17 +108,17 @@ deltatime methods as needed. gdscript GDScript ``` - # Called every frame, even when the engine detects no input. - func _process(delta): - if Input.is_action_just_pressed("ui_select"): - print(delta) +# Called every frame, even when the engine detects no input. +func _process(delta): + if Input.is_action_just_pressed("ui_select"): + print(delta) - # Called during every input event. - func _unhandled_input(event): - match event.get_class(): - "InputEventKey": - if Input.is_action_just_pressed("ui_accept"): - print(get_process_delta_time()) +# Called during every input event. +func _unhandled_input(event): + match event.get_class(): + "InputEventKey": + if Input.is_action_just_pressed("ui_accept"): + print(get_process_delta_time()) ``` @@ -135,21 +135,21 @@ instantiation: gdscript GDScript ``` - # "one" is an "initialized value". These DO NOT trigger the setter. - # If someone set the value as "two" from the Inspector, this would be an - # "exported value". These DO trigger the setter. - export(String) var test = "one" setget set_test +# "one" is an "initialized value". These DO NOT trigger the setter. +# If someone set the value as "two" from the Inspector, this would be an +# "exported value". These DO trigger the setter. +export(String) var test = "one" setget set_test - func _init(): - # "three" is an "init assignment value". - # These DO NOT trigger the setter, but... - test = "three" - # These DO trigger the setter. Note the `self` prefix. - self.test = "three" +func _init(): + # "three" is an "init assignment value". + # These DO NOT trigger the setter, but... + test = "three" + # These DO trigger the setter. Note the `self` prefix. + self.test = "three" - func set_test(value): - test = value - print("Setting: ", test) +func set_test(value): + test = value + print("Setting: ", test) ``` @@ -191,23 +191,23 @@ nodes that one might create at runtime. gdscript GDScript ``` - extends Node +extends Node - var parent_cache +var parent_cache - func connection_check(): - return parent.has_user_signal("interacted_with") +func connection_check(): + return parent.has_user_signal("interacted_with") - func _notification(what): - match what: - NOTIFICATION_PARENTED: - parent_cache = get_parent() - if connection_check(): - parent_cache.connect("interacted_with", self, "_on_parent_interacted_with") - NOTIFICATION_UNPARENTED: - if connection_check(): - parent_cache.disconnect("interacted_with", self, "_on_parent_interacted_with") +func _notification(what): + match what: + NOTIFICATION_PARENTED: + parent_cache = get_parent() + if connection_check(): + parent_cache.connect("interacted_with", self, "_on_parent_interacted_with") + NOTIFICATION_UNPARENTED: + if connection_check(): + parent_cache.disconnect("interacted_with", self, "_on_parent_interacted_with") - func _on_parent_interacted_with(): - print("I'm reacting to my parent's interaction!") +func _on_parent_interacted_with(): + print("I'm reacting to my parent's interaction!") ``` diff --git a/03_usage/22_best_practices/09_data_preferences.md b/03_usage/22_best_practices/09_data_preferences.md index da805b5..c50fe68 100644 --- a/03_usage/22_best_practices/09_data_preferences.md +++ b/03_usage/22_best_practices/09_data_preferences.md @@ -234,18 +234,18 @@ tree structures. gdscript GDScript ``` - extends Object - class_name TreeNode +extends Object +class_name TreeNode - var _parent : TreeNode = null - var _children : = [] setget +var _parent : TreeNode = null +var _children : = [] setget - func _notification(p_what): - match p_what: - NOTIFICATION_PREDELETE: - # Destructor. - for a_child in _children: - a_child.free() +func _notification(p_what): + match p_what: + NOTIFICATION_PREDELETE: + # Destructor. + for a_child in _children: + a_child.free() ``` From here, one can then create their own structures with specific features, diff --git a/03_usage/22_best_practices/10_logic_preferences.md b/03_usage/22_best_practices/10_logic_preferences.md index 0ef8e61..a0dc79b 100644 --- a/03_usage/22_best_practices/10_logic_preferences.md +++ b/03_usage/22_best_practices/10_logic_preferences.md @@ -25,43 +25,43 @@ either? Let's see an example: gdscript GDScript ``` - # my_buildings.gd - extends Node +# my_buildings.gd +extends Node - # Note how constant scripts/scenes have a different naming scheme than - # their property variants. +# Note how constant scripts/scenes have a different naming scheme than +# their property variants. - # This value is a constant, so it spawns when the Script object loads. - # The script is preloading the value. The advantage here is that the editor - # can offer autocompletion since it must be a static path. - const BuildingScn = preload("res://building.tscn") +# This value is a constant, so it spawns when the Script object loads. +# The script is preloading the value. The advantage here is that the editor +# can offer autocompletion since it must be a static path. +const BuildingScn = preload("res://building.tscn") - # 1. The script preloads the value, so it will load as a dependency - # of the 'my_buildings.gd' script file. But, because this is a - # property rather than a constant, the object won't copy the preloaded - # PackedScene resource into the property until the script instantiates - # with .new(). - # - # 2. The preloaded value is inaccessible from the Script object alone. As - # such, preloading the value here actually does not benefit anyone. - # - # 3. Because the user exports the value, if this script stored on - # a node in a scene file, the scene instantiation code will overwrite the - # preloaded initial value anyway (wasting it). It's usually better to - # provide null, empty, or otherwise invalid default values for exports. - # - # 4. It is when one instantiates this script on its own with .new() that - # one will load "office.tscn" rather than the exported value. - export(PackedScene) var a_building = preload("office.tscn") +# 1. The script preloads the value, so it will load as a dependency +# of the 'my_buildings.gd' script file. But, because this is a +# property rather than a constant, the object won't copy the preloaded +# PackedScene resource into the property until the script instantiates +# with .new(). +# +# 2. The preloaded value is inaccessible from the Script object alone. As +# such, preloading the value here actually does not benefit anyone. +# +# 3. Because the user exports the value, if this script stored on +# a node in a scene file, the scene instantiation code will overwrite the +# preloaded initial value anyway (wasting it). It's usually better to +# provide null, empty, or otherwise invalid default values for exports. +# +# 4. It is when one instantiates this script on its own with .new() that +# one will load "office.tscn" rather than the exported value. +export(PackedScene) var a_building = preload("office.tscn") - # Uh oh! This results in an error! - # One must assign constant values to constants. Because `load` performs a - # runtime lookup by its very nature, one cannot use it to initialize a - # constant. - const OfficeScn = load("res://office.tscn") +# Uh oh! This results in an error! +# One must assign constant values to constants. Because `load` performs a +# runtime lookup by its very nature, one cannot use it to initialize a +# constant. +const OfficeScn = load("res://office.tscn") - # Successfully loads and only when one instantiates the script! Yay! - var office_scn = load("res://office.tscn") +# Successfully loads and only when one instantiates the script! Yay! +var office_scn = load("res://office.tscn") ``` Preloading allows the script to handle all the loading the moment one loads the diff --git a/03_usage/22_best_practices/11_project_organization.md b/03_usage/22_best_practices/11_project_organization.md index 4820d01..3562b16 100644 --- a/03_usage/22_best_practices/11_project_organization.md +++ b/03_usage/22_best_practices/11_project_organization.md @@ -26,19 +26,19 @@ such as sprite images, 3D model meshes, materials, and music, etc. They can then use a separate folder to store built levels that use them. ``` - /project.pandemonium - /docs/.gdignore # See "Ignoring specific folders" below - /docs/learning.html - /models/town/house/house.dae - /models/town/house/window.png - /models/town/house/door.png - /characters/player/cubio.dae - /characters/player/cubio.png - /characters/enemies/goblin/goblin.dae - /characters/enemies/goblin/goblin.png - /characters/npcs/suzanne/suzanne.dae - /characters/npcs/suzanne/suzanne.png - /levels/riverdale/riverdale.scn +/project.pandemonium +/docs/.gdignore # See "Ignoring specific folders" below +/docs/learning.html +/models/town/house/house.dae +/models/town/house/window.png +/models/town/house/door.png +/characters/player/cubio.dae +/characters/player/cubio.png +/characters/enemies/goblin/goblin.dae +/characters/enemies/goblin/goblin.png +/characters/npcs/suzanne/suzanne.dae +/characters/npcs/suzanne/suzanne.png +/levels/riverdale/riverdale.scn ``` ## Style guide @@ -108,11 +108,11 @@ you can also make the project folder case-sensitive. After enabling the Windows Subsystem for Linux feature, run the following command in a PowerShell window: ``` - # To enable case-sensitivity: - fsutil file setcasesensitiveinfo enable +# To enable case-sensitivity: +fsutil file setcasesensitiveinfo enable - # To disable case-sensitivity: - fsutil file setcasesensitiveinfo disable +# To disable case-sensitivity: +fsutil file setcasesensitiveinfo disable ``` If you haven't enabled the Windows Subsystem for Linux, you can enter the @@ -120,5 +120,5 @@ following line in a PowerShell window *running as Administrator* then reboot when asked: ``` - Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux +Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux ``` diff --git a/03_usage/22_best_practices/12_version_control_systems.md b/03_usage/22_best_practices/12_version_control_systems.md index bc91851..4b9e755 100644 --- a/03_usage/22_best_practices/12_version_control_systems.md +++ b/03_usage/22_best_practices/12_version_control_systems.md @@ -38,5 +38,5 @@ This can lead to files unnecessarily being marked as modified by Git due to thei It is better to set this option as: ``` - git config --global core.autocrlf input +git config --global core.autocrlf input ``` diff --git a/04_modules/07_terraman.md b/04_modules/07_terraman.md index b6578cf..c61323d 100644 --- a/04_modules/07_terraman.md +++ b/04_modules/07_terraman.md @@ -134,27 +134,27 @@ Note that `_create_chunk` is also responsible for initializing chunks if you hav This is done by `setup_chunk(shunk)` in `TerraWorld`. ``` - func _create_chunk(x : int, y : int, z : int, chunk : TerraChunk) -> TerraChunk: - if !chunk: - chunk = MyChunk.new() +func _create_chunk(x : int, y : int, z : int, chunk : TerraChunk) -> TerraChunk: + if !chunk: + chunk = MyChunk.new() - # We need to check whether or not we need to initialize jobs - if chunk.job_get_count() == 0: - # Setup a blocky (minecratf like) mesher job - var tj : TerraTerrainJob = TerraTerrainJob.new() + # We need to check whether or not we need to initialize jobs + if chunk.job_get_count() == 0: + # Setup a blocky (minecratf like) mesher job + var tj : TerraTerrainJob = TerraTerrainJob.new() - var s : TerraMesherJobStep = TerraMesherJobStep.new() - s.job_type = TerraMesherJobStep.TYPE_NORMAL - tj.add_jobs_step(s) + var s : TerraMesherJobStep = TerraMesherJobStep.new() + s.job_type = TerraMesherJobStep.TYPE_NORMAL + tj.add_jobs_step(s) - tj.add_mesher(TerraMesherBlocky.new()) - tj.add_liquid_mesher(TerraMesherLiquidBlocky.new()) + tj.add_mesher(TerraMesherBlocky.new()) + tj.add_liquid_mesher(TerraMesherLiquidBlocky.new()) - chunk.job_add(tj); + chunk.job_add(tj); - #setup your chunk here + #setup your chunk here - return ._create_chunk(x, y, z, chunk) + return ._create_chunk(x, y, z, chunk) ``` You can look at the world implementations for more examples: [TerraWorldBlocky](https://github.com/Relintai/terraman/blob/master/world/blocky/voxel_world_blocky.cpp). diff --git a/04_modules/08_terraman_2d.md b/04_modules/08_terraman_2d.md index efdd65d..18c8df0 100644 --- a/04_modules/08_terraman_2d.md +++ b/04_modules/08_terraman_2d.md @@ -132,27 +132,27 @@ Note that `_create_chunk` is also responsible for initializing chunks if you hav This is done by `setup_chunk(shunk)` in `TerraWorld`. ``` - func _create_chunk(x : int, y : int, z : int, chunk : TerraChunk) -> TerraChunk: - if !chunk: - chunk = MyChunk.new() +func _create_chunk(x : int, y : int, z : int, chunk : TerraChunk) -> TerraChunk: + if !chunk: + chunk = MyChunk.new() - # We need to check whether or not we need to initialize jobs - if chunk.job_get_count() == 0: - # Setup a blocky (minecratf like) mesher job - var tj : TerraTerrain2DJob = TerraTerrain2DJob.new() + # We need to check whether or not we need to initialize jobs + if chunk.job_get_count() == 0: + # Setup a blocky (minecratf like) mesher job + var tj : TerraTerrain2DJob = TerraTerrain2DJob.new() - var s : TerraMesherJobStep = TerraMesherJobStep.new() - s.job_type = TerraMesherJobStep.TYPE_NORMAL - tj.add_jobs_step(s) + var s : TerraMesherJobStep = TerraMesherJobStep.new() + s.job_type = TerraMesherJobStep.TYPE_NORMAL + tj.add_jobs_step(s) - tj.add_mesher(TerraMesherBlocky.new()) - tj.add_liquid_mesher(TerraMesherLiquidBlocky.new()) + tj.add_mesher(TerraMesherBlocky.new()) + tj.add_liquid_mesher(TerraMesherLiquidBlocky.new()) - chunk.job_add(tj); + chunk.job_add(tj); - #setup your chunk here + #setup your chunk here - return ._create_chunk(x, y, z, chunk) + return ._create_chunk(x, y, z, chunk) ``` You can look at the world implementations for more examples: [TerraWorldBlocky](https://github.com/Relintai/terraman/blob/master/world/blocky/voxel_world_blocky.cpp). diff --git a/04_modules/09_voxelman.md b/04_modules/09_voxelman.md index 8ef05b5..d112364 100644 --- a/04_modules/09_voxelman.md +++ b/04_modules/09_voxelman.md @@ -138,27 +138,27 @@ Note that `_create_chunk` is also responsible for initializing chunks if you hav This is done by `setup_chunk(shunk)` in `VoxelWorld`. ``` - func _create_chunk(x : int, y : int, z : int, chunk : VoxelChunk) -> VoxelChunk: - if !chunk: - chunk = MyChunk.new() +func _create_chunk(x : int, y : int, z : int, chunk : VoxelChunk) -> VoxelChunk: + if !chunk: + chunk = MyChunk.new() - # We need to check whether or not we need to initialize jobs - if chunk.job_get_count() == 0: - # Setup a blocky (minecratf like) mesher job - var tj : VoxelTerrainJob = VoxelTerrainJob.new() + # We need to check whether or not we need to initialize jobs + if chunk.job_get_count() == 0: + # Setup a blocky (minecratf like) mesher job + var tj : VoxelTerrainJob = VoxelTerrainJob.new() - var s : VoxelMesherJobStep = VoxelMesherJobStep.new() - s.job_type = VoxelMesherJobStep.TYPE_NORMAL - tj.add_jobs_step(s) + var s : VoxelMesherJobStep = VoxelMesherJobStep.new() + s.job_type = VoxelMesherJobStep.TYPE_NORMAL + tj.add_jobs_step(s) - tj.add_mesher(VoxelMesherBlocky.new()) - tj.add_liquid_mesher(VoxelMesherLiquidBlocky.new()) + tj.add_mesher(VoxelMesherBlocky.new()) + tj.add_liquid_mesher(VoxelMesherLiquidBlocky.new()) - chunk.job_add(tj); + chunk.job_add(tj); - #setup your chunk here + #setup your chunk here - return ._create_chunk(x, y, z, chunk) + return ._create_chunk(x, y, z, chunk) ``` You can look at the world implementations for more examples: [VoxelWorldBlocky](https://github.com/Relintai/voxelman/blob/master/world/blocky/voxel_world_blocky.cpp), [VoxelWorldMarchingCubes](https://github.com/Relintai/voxelman/blob/master/world/marching_cubes/voxel_world_marching_cubes.cpp). diff --git a/05_engine_development/01_compiling/01_getting_source.md b/05_engine_development/01_compiling/01_getting_source.md index 41d3663..fd33268 100644 --- a/05_engine_development/01_compiling/01_getting_source.md +++ b/05_engine_development/01_compiling/01_getting_source.md @@ -25,9 +25,9 @@ If you are using the `git` command line client, this is done by entering the following in a terminal: ``` - git clone https://github.com/Relintai/pandemonium_engine.git - # You can add the --depth 1 argument to omit the commit history. - # Faster, but not all Git operations (like blame) will work. +git clone https://github.com/Relintai/pandemonium_engine.git +# You can add the --depth 1 argument to omit the commit history. +# Faster, but not all Git operations (like blame) will work. ``` For any stable release, visit the `release page ( https://github.com/Relintai/pandemonium_engine/releases )` @@ -38,11 +38,11 @@ With `git`, you can also clone a stable release by specifying its branch or tag after the `--branch` (or just `-b`) argument: ``` - # Clone the continuously maintained stable branch (`3.x` as of writing). - git clone https://github.com/Relintai/pandemonium_engine.git -b 3.x +# Clone the continuously maintained stable branch (`3.x` as of writing). +git clone https://github.com/Relintai/pandemonium_engine.git -b 3.x - # Clone the `3.2.3-stable` tag. This is a fixed revision that will never change. - git clone https://github.com/Relintai/pandemonium_engine.git -b 3.2.3-stable +# Clone the `3.2.3-stable` tag. This is a fixed revision that will never change. +git clone https://github.com/Relintai/pandemonium_engine.git -b 3.2.3-stable ``` There are also generally branches besides `master` for each major version. diff --git a/05_engine_development/01_compiling/02_introduction_to_the_buildsystem.md b/05_engine_development/01_compiling/02_introduction_to_the_buildsystem.md index cda7808..d4d4ecb 100644 --- a/05_engine_development/01_compiling/02_introduction_to_the_buildsystem.md +++ b/05_engine_development/01_compiling/02_introduction_to_the_buildsystem.md @@ -56,24 +56,24 @@ It will then start building for the target platform right away. To list the available target platforms, use `scons platform=list`: ``` - scons platform=list - scons: Reading SConscript files ... - The following platforms are available: +scons platform=list +scons: Reading SConscript files ... +The following platforms are available: - android - javascript - server - windows - x11 + android + javascript + server + windows + x11 - Please run SCons again and select a valid platform: platform=<string> +Please run SCons again and select a valid platform: platform=<string> ``` To build for a platform (for example, x11), run with the `platform=` (or `p=` to make it short) argument: ``` - scons platform=x11 +scons platform=x11 ``` This will start the build process, which will take a while. If you want @@ -84,7 +84,7 @@ can use your computer for something else :) Example for using 4 cores: ``` - scons platform=x11 -j 4 +scons platform=x11 -j 4 ``` ## Resulting binary @@ -94,14 +94,14 @@ The resulting binaries will be placed in the `bin/` subdirectory, generally with this naming convention: ``` - pandemonium.<platform>.[opt].[tools/debug].<architecture>[extension] +pandemonium.<platform>.[opt].[tools/debug].<architecture>[extension] ``` For the previous build attempt, the result would look like this: ``` - ls bin - bin/pandemonium.x11.tools.64 +ls bin +bin/pandemonium.x11.tools.64 ``` This means that the binary is for X11, is not optimized, has tools (the @@ -110,8 +110,8 @@ whole editor) compiled in, and is meant for 64 bits. A Windows binary with the same configuration will look like this: ``` - C:\pandemonium> dir bin/ - pandemonium.windows.tools.64.exe +C:\pandemonium> dir bin/ +pandemonium.windows.tools.64.exe ``` Copy that binary to any location you like, as it contains the project manager, @@ -132,7 +132,7 @@ run projects but that does not include the editor or the project manager. ``` - scons platform=<platform> tools=yes/no +scons platform=<platform> tools=yes/no ``` ## Target @@ -150,7 +150,7 @@ Target controls optimization and debug flags. Each mode means: checks to run. ``` - scons platform=<platform> target=debug/release_debug/release +scons platform=<platform> target=debug/release_debug/release ``` This flag appends the `.debug` suffix (for debug), or `.tools` (for debug @@ -168,7 +168,7 @@ else. - **default**: Build for the architecture that matches the host platform. ``` - scons platform=<platform> bits=default/32/64 +scons platform=<platform> bits=default/32/64 ``` This flag appends `.32` or `.64` suffixes to resulting binaries when @@ -189,7 +189,7 @@ For instance, it's possible to provide both relative, absolute, and user directory paths containing such modules: ``` - scons custom_modules="../modules,/abs/path/to/modules,~/src/pandemonium_modules" +scons custom_modules="../modules,/abs/path/to/modules,~/src/pandemonium_modules" ``` Note: @@ -232,12 +232,12 @@ The default `custom.py` file can be created at the root of the Pandemonium Engin source to initialize any SCons build options passed via the command line: ``` - # custom.py +# custom.py - optimize = "size" - module_mono_enabled = "yes" - use_llvm = "yes" - extra_suffix = "game_title" +optimize = "size" +module_mono_enabled = "yes" +use_llvm = "yes" +extra_suffix = "game_title" ``` You can also disable some of the builtin modules before compiling, saving some @@ -253,7 +253,7 @@ Another custom file can be specified explicitly with the `profile` command line option, both overriding the default build configuration: ``` - scons profile=path/to/custom.py +scons profile=path/to/custom.py ``` Note: Build options set from the file can be overridden by the command line options. @@ -261,15 +261,15 @@ Note: Build options set from the file can be overridden by the command line opti It's also possible to override the options conditionally: ``` - # custom.py +# custom.py - import version +import version - # Override options specific for Pandemonium 3.x and 4.x versions. - if version.major == 3: - pass - elif version.major == 4: - pass +# Override options specific for Pandemonium 3.x and 4.x versions. +if version.major == 3: + pass +elif version.major == 4: + pass ``` #### Using the SCONSFLAGS @@ -304,22 +304,22 @@ will notice that most files are optimized binaries or packages for each platform: ``` - android_debug.apk - android_release.apk - webassembly_debug.zip - webassembly_release.zip - linux_server_32 - linux_server_64 - linux_x11_32_debug - linux_x11_32_release - linux_x11_64_debug - linux_x11_64_release - osx.zip - version.txt - windows_32_debug.exe - windows_32_release.exe - windows_64_debug.exe - windows_64_release.exe +android_debug.apk +android_release.apk +webassembly_debug.zip +webassembly_release.zip +linux_server_32 +linux_server_64 +linux_x11_32_debug +linux_x11_32_release +linux_x11_64_debug +linux_x11_64_release +osx.zip +version.txt +windows_32_debug.exe +windows_32_release.exe +windows_64_debug.exe +windows_64_release.exe ``` To create those yourself, follow the instructions detailed for each diff --git a/05_engine_development/01_compiling/03_compiling_for_windows.md b/05_engine_development/01_compiling/03_compiling_for_windows.md index 99d0722..730115b 100644 --- a/05_engine_development/01_compiling/03_compiling_for_windows.md +++ b/05_engine_development/01_compiling/03_compiling_for_windows.md @@ -24,7 +24,7 @@ For compiling under Windows, the following is required: If you have `Scoop ( https://scoop.sh/ )` installed, you can easily install MinGW and other dependencies using the following command: ``` - scoop install gcc python scons make +scoop install gcc python scons make ``` ### Note: @@ -32,8 +32,8 @@ If you have `Scoop ( https://scoop.sh/ )` installed, you can easily install MinG If you have `MSYS2 ( https://www.msys2.org/ )` installed, you can easily install MinGW and other dependencies using the following command: ``` - pacman -S mingw-w64-x86_64-python3-pip mingw-w64-x86_64-gcc \ - mingw-w64-i686-python3-pip mingw-w64-i686-gcc make +pacman -S mingw-w64-x86_64-python3-pip mingw-w64-x86_64-gcc \ + mingw-w64-i686-python3-pip mingw-w64-i686-gcc make ``` For each MSYS2 MinGW subsystem, you should then run `pip3 install scons` in its shell. @@ -49,7 +49,7 @@ For a general overview of SCons usage for Pandemonium, see `doc_introduction_to_ To install SCons, open the command prompt and run the following command: ``` - python -m pip install scons +python -m pip install scons ``` If you are prompted with the message @@ -123,13 +123,13 @@ After opening a command prompt, change to the root directory of the engine source code (using `cd`) and type: ``` - C:\pandemonium> scons platform=windows +C:\pandemonium> scons platform=windows ``` You can specify a number of CPU threads to use to speed up the build: ``` - C:\pandemonium> scons -j6 platform=windows +C:\pandemonium> scons -j6 platform=windows ``` In general, it is OK to have at least as many threads compiling Pandemonium as you @@ -174,7 +174,7 @@ You can create a Visual Studio solution via SCons by running SCons with the `vsproj=yes` parameter, like this: ``` - scons p=windows vsproj=yes +scons p=windows vsproj=yes ``` You will be able to open Pandemonium's source in a Visual Studio solution now, @@ -222,8 +222,8 @@ Before attempting the compilation, SCons will check for the following binaries in your `PATH` environment variable: ``` - i686-w64-mingw32-gcc - x86_64-w64-mingw32-gcc +i686-w64-mingw32-gcc +x86_64-w64-mingw32-gcc ``` If the binaries are not located in the `PATH` (e.g. `/usr/bin`), @@ -231,8 +231,8 @@ you can define the following environment variables to give a hint to the build system: ``` - export MINGW32_PREFIX="/path/to/i686-w64-mingw32-" - export MINGW64_PREFIX="/path/to/x86_64-w64-mingw32-" +export MINGW32_PREFIX="/path/to/i686-w64-mingw32-" +export MINGW64_PREFIX="/path/to/x86_64-w64-mingw32-" ``` To make sure you are doing things correctly, executing the following in @@ -240,8 +240,8 @@ the shell should result in a working compiler (the version output may differ based on your system): ``` - ${MINGW32_PREFIX}gcc --version - # i686-w64-mingw32-gcc (GCC) 6.1.0 20160427 (Mageia MinGW 6.1.0-1.mga6) +${MINGW32_PREFIX}gcc --version +# i686-w64-mingw32-gcc (GCC) 6.1.0 20160427 (Mageia MinGW 6.1.0-1.mga6) ``` ### Troubleshooting @@ -254,19 +254,19 @@ You can change that configuration following those instructions, for 64-bit: ``` - sudo update-alternatives --config x86_64-w64-mingw32-gcc - - sudo update-alternatives --config x86_64-w64-mingw32-g++ - +sudo update-alternatives --config x86_64-w64-mingw32-gcc + +sudo update-alternatives --config x86_64-w64-mingw32-g++ + ``` And for 32-bit: ``` - sudo update-alternatives --config i686-w64-mingw32-gcc - - sudo update-alternatives --config i686-w64-mingw32-g++ - +sudo update-alternatives --config i686-w64-mingw32-gcc + +sudo update-alternatives --config i686-w64-mingw32-g++ + ``` ### Creating Windows export templates @@ -275,10 +275,10 @@ Windows export templates are created by compiling Pandemonium without the editor with the following flags: ``` - C:\pandemonium> scons platform=windows tools=no target=release_debug bits=32 - C:\pandemonium> scons platform=windows tools=no target=release bits=32 - C:\pandemonium> scons platform=windows tools=no target=release_debug bits=64 - C:\pandemonium> scons platform=windows tools=no target=release bits=64 +C:\pandemonium> scons platform=windows tools=no target=release_debug bits=32 +C:\pandemonium> scons platform=windows tools=no target=release bits=32 +C:\pandemonium> scons platform=windows tools=no target=release_debug bits=64 +C:\pandemonium> scons platform=windows tools=no target=release bits=64 ``` If you plan on replacing the standard export templates, copy these to the @@ -286,16 +286,16 @@ following location, replacing `( version )` with the version identifier (such as `3.1.1.stable` or `3.2.dev`): ``` - %USERPROFILE%\AppData\Roaming\Pandemonium\templates\\ +%USERPROFILE%\AppData\Roaming\Pandemonium\templates\\ ``` With the following names: ``` - windows_32_debug.exe - windows_32_release.exe - windows_64_debug.exe - windows_64_release.exe +windows_32_debug.exe +windows_32_release.exe +windows_64_debug.exe +windows_64_release.exe ``` However, if you are using custom modules or custom engine code, you diff --git a/05_engine_development/01_compiling/04_compiling_for_x11.md b/05_engine_development/01_compiling/04_compiling_for_x11.md index 4994f84..dfb3ef7 100644 --- a/05_engine_development/01_compiling/04_compiling_for_x11.md +++ b/05_engine_development/01_compiling/04_compiling_for_x11.md @@ -114,7 +114,7 @@ sudo eopkg install -c system.devel scons libxcursor-devel libxinerama-devel libx Start a terminal, go to the root dir of the engine source code and type: ``` - scons -j8 platform=x11 +scons -j8 platform=x11 ``` A good rule of thumb for the `-j` (*jobs*) flag, is to have at least as many @@ -129,7 +129,7 @@ manager. Note: If you wish to compile using Clang rather than GCC, use this command: ``` - scons platform=x11 use_llvm=yes +scons platform=x11 use_llvm=yes ``` Using Clang appears to be a requirement for OpenBSD, otherwise fonts would not build. @@ -154,19 +154,19 @@ To compile a *headless* build which provides editor functionality to export projects in an automated manner, use: ``` - scons -j8 platform=server tools=yes target=release_debug +scons -j8 platform=server tools=yes target=release_debug ``` To compile a debug *server* build which can be used with `remote debugging tools ( doc_command_line_tutorial )`, use: ``` - scons -j8 platform=server tools=no target=release_debug +scons -j8 platform=server tools=no target=release_debug ``` To compile a *server* build which is optimized to run dedicated game servers, use: ``` - scons -j8 platform=server tools=no target=release +scons -j8 platform=server tools=no target=release ``` ## Building export templates @@ -182,15 +182,15 @@ To build X11 (Linux, \*BSD) export templates, run the build system with the foll - (32 bits) ``` - scons platform=x11 tools=no target=release bits=32 - scons platform=x11 tools=no target=release_debug bits=32 +scons platform=x11 tools=no target=release bits=32 +scons platform=x11 tools=no target=release_debug bits=32 ``` - (64 bits) ``` - scons platform=x11 tools=no target=release bits=64 - scons platform=x11 tools=no target=release_debug bits=64 +scons platform=x11 tools=no target=release bits=64 +scons platform=x11 tools=no target=release_debug bits=64 ``` Note that cross-compiling for the opposite bits (64/32) as your host @@ -199,16 +199,16 @@ platform is not always straight-forward and might need a chroot environment. To create standard export templates, the resulting files must be copied to: ``` - $HOME/.local/share/pandemonium/templates/[gd-version]/ +$HOME/.local/share/pandemonium/templates/[gd-version]/ ``` and named like this (even for \*BSD which is seen as "Linux X11" by Pandemonium): ``` - linux_x11_32_debug - linux_x11_32_release - linux_x11_64_debug - linux_x11_64_release +linux_x11_32_debug +linux_x11_32_release +linux_x11_64_debug +linux_x11_64_release ``` However, if you are writing your custom modules or custom C++ code, you might instead want to configure your @@ -230,7 +230,7 @@ To do so, install Clang and the `lld` package from your distribution's package m then use the following SCons command: ``` - scons platform=x11 use_llvm=yes use_lld=yes +scons platform=x11 use_llvm=yes use_lld=yes ``` After the build is completed, a new binary with a `.llvm` suffix will be created in the `bin/` folder. @@ -254,7 +254,7 @@ to get even faster builds. script to a location in your `PATH` environment variable: ``` - ln -s ~/.local/opt/pyston/bin/scons ~/.local/bin/pyston-scons +ln -s ~/.local/opt/pyston/bin/scons ~/.local/bin/pyston-scons ``` - Instead of running `scons ( build arguments )`, run `pyston-scons ( build arguments )` diff --git a/05_engine_development/01_compiling/05_compiling_for_osx.md b/05_engine_development/01_compiling/05_compiling_for_osx.md index 4f87490..70ef8de 100644 --- a/05_engine_development/01_compiling/05_compiling_for_osx.md +++ b/05_engine_development/01_compiling/05_compiling_for_osx.md @@ -19,7 +19,7 @@ For compiling under macOS, the following is required: If you have `Homebrew ( https://brew.sh/ )` installed, you can easily install SCons and yasm using the following command: ``` - brew install scons yasm +brew install scons yasm ``` Installing Homebrew will also fetch the Command Line Tools for Xcode automatically if you don't have them already. @@ -28,7 +28,7 @@ Similarly, if you have `MacPorts ( https://www.macports.org/ )` installed, you c SCons and yasm using the following command: ``` - sudo port install scons yasm +sudo port install scons yasm ``` To get the Pandemonium source code for compiling, see `doc_getting_source`. @@ -42,19 +42,19 @@ Start a terminal, go to the root directory of the engine source code. To compile for Intel (x86-64) powered Macs, use ``` - scons platform=osx arch=x86_64 --jobs=$(sysctl -n hw.logicalcpu) +scons platform=osx arch=x86_64 --jobs=$(sysctl -n hw.logicalcpu) ``` To compile for Apple Silicon (ARM64) powered Macs, use: ``` - scons platform=osx arch=arm64 --jobs=$(sysctl -n hw.logicalcpu) +scons platform=osx arch=arm64 --jobs=$(sysctl -n hw.logicalcpu) ``` To support both architectures in a single "Universal 2" binary, run the above two commands and then use `lipo` to bundle them together: ``` - lipo -create bin/pandemonium.osx.tools.x86_64 bin/pandemonium.osx.tools.arm64 -output bin/pandemonium.osx.tools.universal +lipo -create bin/pandemonium.osx.tools.x86_64 bin/pandemonium.osx.tools.arm64 -output bin/pandemonium.osx.tools.universal ``` If all goes well, the resulting binary executable will be placed in the `bin/` subdirectory. This executable @@ -68,10 +68,10 @@ template located in `misc/dist/osx_tools.app`. Typically, for an optimized editor binary built with `target=release_debug`: ``` - cp -r misc/dist/osx_tools.app ./Pandemonium.app - mkdir -p Pandemonium.app/Contents/MacOS - cp bin/pandemonium.osx.opt.tools.universal Pandemonium.app/Contents/MacOS/Pandemonium - chmod +x Pandemonium.app/Contents/MacOS/Pandemonium +cp -r misc/dist/osx_tools.app ./Pandemonium.app +mkdir -p Pandemonium.app/Contents/MacOS +cp bin/pandemonium.osx.opt.tools.universal Pandemonium.app/Contents/MacOS/Pandemonium +chmod +x Pandemonium.app/Contents/MacOS/Pandemonium ``` ## Compiling a headless/server build @@ -80,21 +80,21 @@ To compile a *headless* build which provides editor functionality to export projects in an automated manner, use: ``` - scons platform=server tools=yes target=release_debug --jobs=$(sysctl -n hw.logicalcpu) +scons platform=server tools=yes target=release_debug --jobs=$(sysctl -n hw.logicalcpu) ``` To compile a debug *server* build which can be used with `remote debugging tools ( doc_command_line_tutorial )`, use: ``` - scons platform=server tools=no target=release_debug --jobs=$(sysctl -n hw.logicalcpu) +scons platform=server tools=no target=release_debug --jobs=$(sysctl -n hw.logicalcpu) ``` To compile a release *server* build which is optimized to run dedicated game servers, use: ``` - scons platform=server tools=no target=release --jobs=$(sysctl -n hw.logicalcpu) +scons platform=server tools=no target=release --jobs=$(sysctl -n hw.logicalcpu) ``` ## Building export templates @@ -110,23 +110,23 @@ of those two architectures by leaving out the `lipo` step below. - For Intel x86_64: ``` - scons platform=osx tools=no target=release arch=x86_64 --jobs=$(sysctl -n hw.logicalcpu) - scons platform=osx tools=no target=release_debug arch=x86_64 --jobs=$(sysctl -n hw.logicalcpu) +scons platform=osx tools=no target=release arch=x86_64 --jobs=$(sysctl -n hw.logicalcpu) +scons platform=osx tools=no target=release_debug arch=x86_64 --jobs=$(sysctl -n hw.logicalcpu) ``` - For ARM64 (Apple M1): ``` - scons platform=osx tools=no target=release arch=arm64 --jobs=$(sysctl -n hw.logicalcpu) - scons platform=osx tools=no target=release_debug arch=arm64 --jobs=$(sysctl -n hw.logicalcpu) +scons platform=osx tools=no target=release arch=arm64 --jobs=$(sysctl -n hw.logicalcpu) +scons platform=osx tools=no target=release_debug arch=arm64 --jobs=$(sysctl -n hw.logicalcpu) ``` To support both architectures in a single "Universal 2" binary, run the above two commands blocks and then use `lipo` to bundle them together: ``` - lipo -create bin/pandemonium.osx.opt.x86_64 bin/pandemonium.osx.opt.arm64 -output bin/pandemonium.osx.opt.universal - lipo -create bin/pandemonium.osx.opt.debug.x86_64 bin/pandemonium.osx.opt.debug.arm64 -output bin/pandemonium.osx.opt.debug.universal +lipo -create bin/pandemonium.osx.opt.x86_64 bin/pandemonium.osx.opt.arm64 -output bin/pandemonium.osx.opt.universal +lipo -create bin/pandemonium.osx.opt.debug.x86_64 bin/pandemonium.osx.opt.debug.arm64 -output bin/pandemonium.osx.opt.debug.universal ``` To create an `.app` bundle like in the official builds, you need to use the @@ -137,18 +137,18 @@ with the following commands (assuming a universal build, otherwise replace the `.universal` extension with the one of your arch-specific binaries): ``` - cp -r misc/dist/osx_template.app . - mkdir -p osx_template.app/Contents/MacOS - cp bin/pandemonium.osx.opt.universal osx_template.app/Contents/MacOS/pandemonium_osx_release.64 - cp bin/pandemonium.osx.opt.debug.universal osx_template.app/Contents/MacOS/pandemonium_osx_debug.64 - chmod +x osx_template.app/Contents/MacOS/pandemonium_osx* +cp -r misc/dist/osx_template.app . +mkdir -p osx_template.app/Contents/MacOS +cp bin/pandemonium.osx.opt.universal osx_template.app/Contents/MacOS/pandemonium_osx_release.64 +cp bin/pandemonium.osx.opt.debug.universal osx_template.app/Contents/MacOS/pandemonium_osx_debug.64 +chmod +x osx_template.app/Contents/MacOS/pandemonium_osx* ``` You can then zip the `osx_template.app` folder to reproduce the `osx.zip` template from the official Pandemonium distribution: ``` - zip -q -9 -r osx.zip osx_template.app +zip -q -9 -r osx.zip osx_template.app ``` ## Cross-compiling for macOS from Linux @@ -163,7 +163,7 @@ somewhere on your machine (or download a ZIP file and extract it somewhere), e.g.: ``` - git clone --depth=1 https://github.com/tpoechtrager/osxcross.git "$HOME/osxcross" +git clone --depth=1 https://github.com/tpoechtrager/osxcross.git "$HOME/osxcross" ``` 1. Follow the instructions to package the SDK: @@ -176,17 +176,17 @@ the OSXCross installation (the same place where you cloned the repository/extracted the zip), e.g.: ``` - export OSXCROSS_ROOT="$HOME/osxcross" +export OSXCROSS_ROOT="$HOME/osxcross" ``` Now you can compile with SCons like you normally would: ``` - scons platform=osx +scons platform=osx ``` If you have an OSXCross SDK version different from the one expected by the SCons buildsystem, you can specify a custom one with the `osxcross_sdk` argument: ``` - scons platform=osx osxcross_sdk=darwin15 +scons platform=osx osxcross_sdk=darwin15 ``` \ No newline at end of file diff --git a/05_engine_development/01_compiling/06_compiling_for_android.md b/05_engine_development/01_compiling/06_compiling_for_android.md index 1fe5ab5..5fb7b25 100644 --- a/05_engine_development/01_compiling/06_compiling_for_android.md +++ b/05_engine_development/01_compiling/06_compiling_for_android.md @@ -50,13 +50,13 @@ For a general overview of SCons usage for Pandemonium, see `doc_introduction_to_ path to the Android SDK, then answering all the prompts with `y`: ``` - tools/bin/sdkmanager --sdk_root=( android_sdk_path> --licenses +tools/bin/sdkmanager --sdk_root=( android_sdk_path> --licenses ``` - Complete setup by running the following command where `android_sdk_path` is the path to the Android SDK. ``` - tools/bin/sdkmanager --sdk_root=( android_sdk_path> "platform-tools" "build-tools;30.0.3" "platforms;android-29" "cmdline-tools;latest" "cmake;3.10.2.4988404" +tools/bin/sdkmanager --sdk_root=( android_sdk_path> "platform-tools" "build-tools;30.0.3" "platforms;android-29" "cmdline-tools;latest" "cmake;3.10.2.4988404" ``` To set the environment variable on Windows, press :kbd:`Windows + R`, type "control system", then click @@ -79,13 +79,13 @@ root directory with the following arguments: - Release template (used when exporting with **Debugging Enabled** unchecked) ``` - scons platform=android target=release android_arch=armv7 - scons platform=android target=release android_arch=arm64v8 - cd platform/android/java - # On Windows - .\gradlew generatePandemoniumTemplates - # On Linux and macOS - ./gradlew generatePandemoniumTemplates +scons platform=android target=release android_arch=armv7 +scons platform=android target=release android_arch=arm64v8 +cd platform/android/java +# On Windows +.\gradlew generatePandemoniumTemplates +# On Linux and macOS +./gradlew generatePandemoniumTemplates ``` The resulting APK will be located at `bin/android_release.apk`. @@ -93,13 +93,13 @@ The resulting APK will be located at `bin/android_release.apk`. - Debug template (used when exporting with **Debugging Enabled** checked) ``` - scons platform=android target=release_debug android_arch=armv7 - scons platform=android target=release_debug android_arch=arm64v8 - cd platform/android/java - # On Windows - .\gradlew generatePandemoniumTemplates - # On Linux and macOS - ./gradlew generatePandemoniumTemplates +scons platform=android target=release_debug android_arch=armv7 +scons platform=android target=release_debug android_arch=arm64v8 +cd platform/android/java +# On Windows +.\gradlew generatePandemoniumTemplates +# On Linux and macOS +./gradlew generatePandemoniumTemplates ``` The resulting APK will be located at `bin/android_debug.apk`. @@ -112,15 +112,15 @@ command a third and fourth time with the `android_arch=x86`, and example, for the release template: ``` - scons platform=android target=release android_arch=armv7 - scons platform=android target=release android_arch=arm64v8 - scons platform=android target=release android_arch=x86 - scons platform=android target=release android_arch=x86_64 - cd platform/android/java - # On Windows - .\gradlew generatePandemoniumTemplates - # On Linux and macOS - ./gradlew generatePandemoniumTemplates +scons platform=android target=release android_arch=armv7 +scons platform=android target=release android_arch=arm64v8 +scons platform=android target=release android_arch=x86 +scons platform=android target=release android_arch=x86_64 +cd platform/android/java +# On Windows +.\gradlew generatePandemoniumTemplates +# On Linux and macOS +./gradlew generatePandemoniumTemplates ``` This will create a fat binary that works on all platforms. @@ -133,11 +133,11 @@ the APK. You can use the following commands to remove the generated export templates: ``` - cd platform/android/java - # On Windows - .\gradlew cleanPandemoniumTemplates - # On Linux and macOS - ./gradlew cleanPandemoniumTemplates +cd platform/android/java +# On Windows +.\gradlew cleanPandemoniumTemplates +# On Linux and macOS +./gradlew cleanPandemoniumTemplates ``` ## Using the export templates @@ -198,7 +198,7 @@ If so: If it still fails, open a command line and run `logcat ( https://developer.android.com/studio/command-line/logcat )`: ``` - adb logcat +adb logcat ``` Then check the output while the application is installed; diff --git a/05_engine_development/01_compiling/07_compiling_for_ios.md b/05_engine_development/01_compiling/07_compiling_for_ios.md index 4204268..0284a10 100644 --- a/05_engine_development/01_compiling/07_compiling_for_ios.md +++ b/05_engine_development/01_compiling/07_compiling_for_ios.md @@ -19,13 +19,13 @@ For a general overview of SCons usage for Pandemonium, see `doc_introduction_to_ Open a Terminal, go to the root dir of the engine source code and type: ``` - $ scons p=iphone target=debug +$ scons p=iphone target=debug ``` for a debug build, or: ``` - $ scons p=iphone target=release +$ scons p=iphone target=release ``` for a release build (check `platform/iphone/detect.py` for the compiler @@ -34,7 +34,7 @@ flags used for each configuration). Alternatively, you can run ``` - $ scons p=iphone arch=x86_64 target=debug +$ scons p=iphone arch=x86_64 target=debug ``` for a Simulator executable. @@ -46,22 +46,22 @@ It can be done in three steps: first compile the 32-bit version, then compile th All those steps can be performed with following commands: ``` - $ scons p=iphone tools=no target=release arch=arm - $ scons p=iphone tools=no target=release arch=arm64 - $ lipo -create bin/libpandemonium.iphone.opt.arm.a bin/libpandemonium.iphone.opt.arm64.a -output bin/libpandemonium.iphone.release.fat.a - $ lipo -create bin/libpandemonium_camera_module.iphone.opt.arm.a bin/libpandemonium_camera_module.iphone.opt.arm64.a -output bin/libpandemonium_camera_module.iphone.release.fat.a - $ lipo -create bin/libpandemonium_arkit_module.iphone.opt.arm.a bin/libpandemonium_arkit_module.iphone.opt.arm64.a -output bin/libpandemonium_arkit_module.iphone.release.fat.a +$ scons p=iphone tools=no target=release arch=arm +$ scons p=iphone tools=no target=release arch=arm64 +$ lipo -create bin/libpandemonium.iphone.opt.arm.a bin/libpandemonium.iphone.opt.arm64.a -output bin/libpandemonium.iphone.release.fat.a +$ lipo -create bin/libpandemonium_camera_module.iphone.opt.arm.a bin/libpandemonium_camera_module.iphone.opt.arm64.a -output bin/libpandemonium_camera_module.iphone.release.fat.a +$ lipo -create bin/libpandemonium_arkit_module.iphone.opt.arm.a bin/libpandemonium_arkit_module.iphone.opt.arm64.a -output bin/libpandemonium_arkit_module.iphone.release.fat.a ``` If you also want to provide a simulator build (reduces the chance of any linker errors with dependencies), you'll need to build and lipo the `x86_64` architecture as well. ``` - $ scons p=iphone tools=no target=release arch=arm - $ scons p=iphone tools=no target=release arch=arm64 - $ scons p=iphone tools=no target=release arch=x86_64 - $ lipo -create bin/libpandemonium.iphone.opt.arm.a bin/libpandemonium.iphone.opt.arm64.a bin/libpandemonium.iphone.opt.x86_64.a -output bin/libpandemonium.iphone.release.fat.a - $ lipo -create bin/libpandemonium_camera_module.iphone.opt.arm.a bin/libpandemonium_camera_module.iphone.opt.arm64.a bin/libpandemonium_camera_module.iphone.opt.x86_64.a -output bin/libpandemonium_camera_module.iphone.release.fat.a - $ lipo -create bin/libpandemonium_arkit_module.iphone.opt.arm.a bin/libpandemonium_arkit_module.iphone.opt.arm64.a bin/libpandemonium_arkit_module.iphone.opt.x86_64.a -output bin/libpandemonium_arkit_module.iphone.release.fat.a +$ scons p=iphone tools=no target=release arch=arm +$ scons p=iphone tools=no target=release arch=arm64 +$ scons p=iphone tools=no target=release arch=x86_64 +$ lipo -create bin/libpandemonium.iphone.opt.arm.a bin/libpandemonium.iphone.opt.arm64.a bin/libpandemonium.iphone.opt.x86_64.a -output bin/libpandemonium.iphone.release.fat.a +$ lipo -create bin/libpandemonium_camera_module.iphone.opt.arm.a bin/libpandemonium_camera_module.iphone.opt.arm64.a bin/libpandemonium_camera_module.iphone.opt.x86_64.a -output bin/libpandemonium_camera_module.iphone.release.fat.a +$ lipo -create bin/libpandemonium_arkit_module.iphone.opt.arm.a bin/libpandemonium_arkit_module.iphone.opt.arm64.a bin/libpandemonium_arkit_module.iphone.opt.x86_64.a -output bin/libpandemonium_arkit_module.iphone.release.fat.a ``` ## Run diff --git a/05_engine_development/01_compiling/08_cross_compiling_for_ios_on_linux.md b/05_engine_development/01_compiling/08_cross_compiling_for_ios_on_linux.md index f59f79a..0a50de5 100644 --- a/05_engine_development/01_compiling/08_cross_compiling_for_ios_on_linux.md +++ b/05_engine_development/01_compiling/08_cross_compiling_for_ios_on_linux.md @@ -48,18 +48,18 @@ described here and cross-compiling the binary. Clone the repository on your machine: ``` - $ git clone https://github.com/darlinghq/darling-dmg.git +$ git clone https://github.com/darlinghq/darling-dmg.git ``` Build it: ``` - $ cd darling-dmg - $ mkdir build - $ cd build - $ cmake .. -DCMAKE_BUILD_TYPE=Release - $ make -j 4 # The number is the amount of cores your processor has, for faster build - $ cd ../.. +$ cd darling-dmg +$ mkdir build +$ cd build +$ cmake .. -DCMAKE_BUILD_TYPE=Release +$ make -j 4 # The number is the amount of cores your processor has, for faster build +$ cd ../.. ``` ### Preparing the SDK @@ -67,26 +67,26 @@ Build it: Mount the XCode image: ``` - $ mkdir xcode - $ ./darling-dmg/build/darling-dmg /path/to/Xcode_7.1.1.dmg xcode - [...] - Everything looks OK, disk mounted +$ mkdir xcode +$ ./darling-dmg/build/darling-dmg /path/to/Xcode_7.1.1.dmg xcode +[...] +Everything looks OK, disk mounted ``` Extract the iOS SDK: ``` - $ mkdir -p iPhoneSDK/iPhoneOS9.1.sdk - $ cp -r xcode/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/* iPhoneSDK/iPhoneOS9.1.sdk - $ cp -r xcode/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/* iPhoneSDK/iPhoneOS9.1.sdk/usr/include/c++ - $ fusermount -u xcode # unmount the image +$ mkdir -p iPhoneSDK/iPhoneOS9.1.sdk +$ cp -r xcode/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/* iPhoneSDK/iPhoneOS9.1.sdk +$ cp -r xcode/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/* iPhoneSDK/iPhoneOS9.1.sdk/usr/include/c++ +$ fusermount -u xcode # unmount the image ``` Pack the SDK: ``` - $ cd iPhoneSDK - $ tar -cf - * | xz -9 -c - > iPhoneOS9.1.sdk.tar.xz +$ cd iPhoneSDK +$ tar -cf - * | xz -9 -c - > iPhoneOS9.1.sdk.tar.xz ``` ### Toolchain @@ -94,9 +94,9 @@ Pack the SDK: Build cctools: ``` - $ git clone https://github.com/tpoechtrager/cctools-port.git - $ cd cctools-port/usage_examples/ios_toolchain - $ ./build.sh /path/iPhoneOS9.1.sdk.tar.xz arm64 +$ git clone https://github.com/tpoechtrager/cctools-port.git +$ cd cctools-port/usage_examples/ios_toolchain +$ ./build.sh /path/iPhoneOS9.1.sdk.tar.xz arm64 ``` Copy the tools to a nicer place. Note that the SCons scripts for @@ -105,8 +105,8 @@ for the toolchain binaries, so you must copy to such subdirectory, akin to the following commands: ``` - $ mkdir -p /home/user/iostoolchain/usr - $ cp -r target/bin /home/user/iostoolchain/usr/ +$ mkdir -p /home/user/iostoolchain/usr +$ cp -r target/bin /home/user/iostoolchain/usr/ ``` Now you should have the iOS toolchain binaries in @@ -123,15 +123,15 @@ For the iPhone platform to be detected, you need the `OSXCROSS_IOS` environment variable defined to anything. ``` - $ export OSXCROSS_IOS=anything +$ export OSXCROSS_IOS=anything ``` Now you can compile for iPhone using SCons like the standard Pandemonium way, with some additional arguments to provide the correct paths: ``` - $ scons -j 4 platform=iphone arch=arm target=release_debug IPHONESDK="/path/to/iPhoneSDK" IPHONEPATH="/path/to/iostoolchain" ios_triple="arm-apple-darwin11-" - $ scons -j 4 platform=iphone arch=arm64 target=release_debug IPHONESDK="/path/to/iPhoneSDK" IPHONEPATH="/path/to/iostoolchain" ios_triple="arm-apple-darwin11-" +$ scons -j 4 platform=iphone arch=arm target=release_debug IPHONESDK="/path/to/iPhoneSDK" IPHONEPATH="/path/to/iostoolchain" ios_triple="arm-apple-darwin11-" +$ scons -j 4 platform=iphone arch=arm64 target=release_debug IPHONESDK="/path/to/iPhoneSDK" IPHONEPATH="/path/to/iostoolchain" ios_triple="arm-apple-darwin11-" ``` ### Producing fat binaries @@ -142,9 +142,9 @@ Apple requires a fat binary with both architectures (`armv7` and you are in the root Pandemonium source directory: ``` - $ /path/to/iostoolchain/usr/bin/arm-apple-darwin11-lipo -create bin/libpandemonium.iphone.opt.debug.arm.a bin/libpandemonium.iphone.opt.debug.arm64.a -output bin/libpandemonium.iphone.debug.fat.a - $ /path/to/iostoolchain/usr/bin/arm-apple-darwin11-lipo -create bin/libpandemonium_camera_module.iphone.opt.debug.arm.a bin/libpandemonium_camera_module.iphone.opt.debug.arm64.a -output bin/libpandemonium_camera_module.iphone.debug.fat.a - $ /path/to/iostoolchain/usr/bin/arm-apple-darwin11-lipo -create bin/libpandemonium_arkit_module.iphone.opt.debug.arm.a bin/libpandemonium_arkit_module.iphone.opt.debug.arm64.a -output bin/libpandemonium_arkit_module.iphone.debug.fat.a +$ /path/to/iostoolchain/usr/bin/arm-apple-darwin11-lipo -create bin/libpandemonium.iphone.opt.debug.arm.a bin/libpandemonium.iphone.opt.debug.arm64.a -output bin/libpandemonium.iphone.debug.fat.a +$ /path/to/iostoolchain/usr/bin/arm-apple-darwin11-lipo -create bin/libpandemonium_camera_module.iphone.opt.debug.arm.a bin/libpandemonium_camera_module.iphone.opt.debug.arm64.a -output bin/libpandemonium_camera_module.iphone.debug.fat.a +$ /path/to/iostoolchain/usr/bin/arm-apple-darwin11-lipo -create bin/libpandemonium_arkit_module.iphone.opt.debug.arm.a bin/libpandemonium_arkit_module.iphone.opt.debug.arm64.a -output bin/libpandemonium_arkit_module.iphone.debug.fat.a ``` Then you will have iOS fat binaries in `bin` directory. diff --git a/05_engine_development/01_compiling/09_compiling_for_uwp.md b/05_engine_development/01_compiling/09_compiling_for_uwp.md index c0f8b37..fde2280 100644 --- a/05_engine_development/01_compiling/09_compiling_for_uwp.md +++ b/05_engine_development/01_compiling/09_compiling_for_uwp.md @@ -53,7 +53,7 @@ required DLLs for the selected architecture. Once you're set, run the SCons command similarly to the other platforms: ``` - C:\pandemonium>scons platform=uwp +C:\pandemonium>scons platform=uwp ``` ## Creating UWP export templates @@ -66,8 +66,8 @@ Open the command prompt for one architecture and run SCons twice (once for each target): ``` - C:\pandemonium>scons platform=uwp target=release_debug - C:\pandemonium>scons platform=uwp target=release +C:\pandemonium>scons platform=uwp target=release_debug +C:\pandemonium>scons platform=uwp target=release ``` Repeat for the other architectures. @@ -86,12 +86,12 @@ Add the files in the `uwp_template` folder to a ZIP. Rename the resulting Zip according to the target/architecture of the template: ``` - uwp_x86_debug.zip - uwp_x86_release.zip - uwp_x64_debug.zip - uwp_x64_release.zip - uwp_arm_debug.zip - uwp_arm_release.zip +uwp_x86_debug.zip +uwp_x86_release.zip +uwp_x64_debug.zip +uwp_x64_release.zip +uwp_arm_debug.zip +uwp_arm_release.zip ``` Move those templates to the `[versionstring]\templates` folder in Pandemonium diff --git a/05_engine_development/01_compiling/10_compiling_for_web.md b/05_engine_development/01_compiling/10_compiling_for_web.md index a6a1b55..8a8c804 100644 --- a/05_engine_development/01_compiling/10_compiling_for_web.md +++ b/05_engine_development/01_compiling/10_compiling_for_web.md @@ -28,8 +28,8 @@ Then instruct SCons to build the JavaScript platform. Specify `target` as either `release` for a release build or `release_debug` for a debug build: ``` - scons platform=javascript tools=no target=release - scons platform=javascript tools=no target=release_debug +scons platform=javascript tools=no target=release +scons platform=javascript tools=no target=release_debug ``` By default, the `JavaScript singleton ( doc_javascript_eval )` will be built @@ -38,8 +38,8 @@ enabled. Since `eval()` calls can be a security concern, the `javascript_eval` option can be used to build without the singleton: ``` - scons platform=javascript tools=no target=release javascript_eval=no - scons platform=javascript tools=no target=release_debug javascript_eval=no +scons platform=javascript tools=no target=release javascript_eval=no +scons platform=javascript tools=no target=release_debug javascript_eval=no ``` The engine will now be compiled to WebAssembly by Emscripten. Once finished, @@ -51,13 +51,13 @@ Finally, rename the zip archive to `webassembly_release.zip` for the release template: ``` - mv bin/pandemonium.javascript.opt.zip bin/webassembly_release.zip +mv bin/pandemonium.javascript.opt.zip bin/webassembly_release.zip ``` And `webassembly_debug.zip` for the debug template: ``` - mv bin/pandemonium.javascript.opt.debug.zip bin/webassembly_debug.zip +mv bin/pandemonium.javascript.opt.debug.zip bin/webassembly_debug.zip ``` ## Threads @@ -69,8 +69,8 @@ performance and compatibility reasons. See the You can build the export templates using the option `threads_enabled=yes` to enable threads support: ``` - scons platform=javascript tools=no threads_enabled=yes target=release - scons platform=javascript tools=no threads_enabled=yes target=release_debug +scons platform=javascript tools=no threads_enabled=yes target=release +scons platform=javascript tools=no threads_enabled=yes target=release_debug ``` Once finished, the resulting file will be placed in the `bin` subdirectory. @@ -79,13 +79,13 @@ Its name will have the `.threads` suffix. Finally, rename the zip archives to `webassembly_release_threads.zip` for the release template: ``` - mv bin/pandemonium.javascript.opt.threads.zip bin/webassembly_threads_release.zip +mv bin/pandemonium.javascript.opt.threads.zip bin/webassembly_threads_release.zip ``` And `webassembly_debug_threads.zip` for the debug template: ``` - mv bin/pandemonium.javascript.opt.debug.threads.zip bin/webassembly_threads_debug.zip +mv bin/pandemonium.javascript.opt.debug.threads.zip bin/webassembly_threads_debug.zip ``` ## Building the Editor @@ -96,7 +96,7 @@ browser. The editor version requires threads support and is not recommended over the native build. You can build the editor with: ``` - scons platform=javascript tools=yes threads_enabled=yes target=release_debug +scons platform=javascript tools=yes threads_enabled=yes target=release_debug ``` Once finished, the resulting file will be placed in the `bin` subdirectory. diff --git a/05_engine_development/01_compiling/11_optimizing_for_size.md b/05_engine_development/01_compiling/11_optimizing_for_size.md index 5e919c9..58ca794 100644 --- a/05_engine_development/01_compiling/11_optimizing_for_size.md +++ b/05_engine_development/01_compiling/11_optimizing_for_size.md @@ -26,7 +26,7 @@ strip debug symbols from binaries by installing the `strip` package from your distribution then running: ``` - strip path/to/pandemonium.binary +strip path/to/pandemonium.binary ``` On Windows, `strip.exe` is included in most MinGW toolchain setups. @@ -54,7 +54,7 @@ Pandemonium 3.1 onwards allows compiling using size optimizations (instead of sp To enable this, set the `optimize` flag to `size`: ``` - scons p=windows target=release tools=no optimize=size +scons p=windows target=release tools=no optimize=size ``` Some platforms such as WebAssembly already use this mode by default. @@ -71,7 +71,7 @@ template functions and unused code. It can currently be used with the GCC and MSVC compilers: ``` - scons p=windows target=release tools=no use_lto=yes +scons p=windows target=release tools=no use_lto=yes ``` Linking becomes much slower and more RAM-consuming with this option, @@ -91,7 +91,7 @@ so it should be used only for release builds: For 2D games, having the whole 3D engine available usually makes no sense. Because of this, there is a build flag to disable it: ``` - scons p=windows target=release tools=no disable_3d=yes +scons p=windows target=release tools=no disable_3d=yes ``` Tools must be disabled in order to use this flag, as the editor is not designed @@ -108,7 +108,7 @@ Most small games don't require complex GUI controls such as Tree, ItemList, TextEdit or GraphEdit. They can be disabled using a build flag: ``` - scons p=windows target=release tools=no disable_advanced_gui=yes +scons p=windows target=release tools=no disable_advanced_gui=yes ``` This is everything that will be disabled: @@ -146,7 +146,7 @@ A lot of Pandemonium's functions are offered as modules. You can see a list of modules with the following command: ``` - scons --help +scons --help ``` The list of modules that can be disabled will appear, together with all @@ -154,7 +154,7 @@ build options. If you are working on a simple 2D game, you could disable a lot of them: ``` - scons p=windows target=release tools=no module_arkit_enabled=no module_assimp_enabled=no module_bmp_enabled=no module_bullet_enabled=no module_camera_enabled=no module_csg_enabled=no module_dds_enabled=no module_enet_enabled=no module_etc_enabled=no module_gdnative_enabled=no module_gridmap_enabled=no module_hdr_enabled=no module_jsonrpc_enabled=no module_mbedtls_enabled=no module_mobile_vr_enabled=no module_opensimplex_enabled=no module_opus_enabled=no module_pvr_enabled=no module_recast_enabled=no module_regex_enabled=no module_squish_enabled=no module_svg_enabled=no module_tga_enabled=no module_theora_enabled=no module_tinyexr_enabled=no module_upnp_enabled=no module_vhacd_enabled=no module_vorbis_enabled=no module_webm_enabled=no module_webrtc_enabled=no module_websocket_enabled=no module_xatlas_unwrap_enabled=no +scons p=windows target=release tools=no module_arkit_enabled=no module_assimp_enabled=no module_bmp_enabled=no module_bullet_enabled=no module_camera_enabled=no module_csg_enabled=no module_dds_enabled=no module_enet_enabled=no module_etc_enabled=no module_gdnative_enabled=no module_gridmap_enabled=no module_hdr_enabled=no module_jsonrpc_enabled=no module_mbedtls_enabled=no module_mobile_vr_enabled=no module_opensimplex_enabled=no module_opus_enabled=no module_pvr_enabled=no module_recast_enabled=no module_regex_enabled=no module_squish_enabled=no module_svg_enabled=no module_tga_enabled=no module_theora_enabled=no module_tinyexr_enabled=no module_upnp_enabled=no module_vhacd_enabled=no module_vorbis_enabled=no module_webm_enabled=no module_webrtc_enabled=no module_websocket_enabled=no module_xatlas_unwrap_enabled=no ``` If this proves not to work for your use case, you should review the list of @@ -167,40 +167,40 @@ Alternatively, you can supply a list of disabled modules by creating following: ``` - # custom.py +# custom.py - module_arkit_enabled = "no" - module_assimp_enabled = "no" - module_bmp_enabled = "no" - module_bullet_enabled = "no" - module_camera_enabled = "no" - module_csg_enabled = "no" - module_dds_enabled = "no" - module_enet_enabled = "no" - module_etc_enabled = "no" - module_gdnative_enabled = "no" - module_gridmap_enabled = "no" - module_hdr_enabled = "no" - module_jsonrpc_enabled = "no" - module_mbedtls_enabled = "no" - module_mobile_vr_enabled = "no" - module_opensimplex_enabled = "no" - module_opus_enabled = "no" - module_pvr_enabled = "no" - module_recast_enabled = "no" - module_regex_enabled = "no" - module_squish_enabled = "no" - module_svg_enabled = "no" - module_tga_enabled = "no" - module_theora_enabled = "no" - module_tinyexr_enabled = "no" - module_upnp_enabled = "no" - module_vhacd_enabled = "no" - module_vorbis_enabled = "no" - module_webm_enabled = "no" - module_webrtc_enabled = "no" - module_websocket_enabled = "no" - module_xatlas_unwrap_enabled = "no" +module_arkit_enabled = "no" +module_assimp_enabled = "no" +module_bmp_enabled = "no" +module_bullet_enabled = "no" +module_camera_enabled = "no" +module_csg_enabled = "no" +module_dds_enabled = "no" +module_enet_enabled = "no" +module_etc_enabled = "no" +module_gdnative_enabled = "no" +module_gridmap_enabled = "no" +module_hdr_enabled = "no" +module_jsonrpc_enabled = "no" +module_mbedtls_enabled = "no" +module_mobile_vr_enabled = "no" +module_opensimplex_enabled = "no" +module_opus_enabled = "no" +module_pvr_enabled = "no" +module_recast_enabled = "no" +module_regex_enabled = "no" +module_squish_enabled = "no" +module_svg_enabled = "no" +module_tga_enabled = "no" +module_theora_enabled = "no" +module_tinyexr_enabled = "no" +module_upnp_enabled = "no" +module_vhacd_enabled = "no" +module_vorbis_enabled = "no" +module_webm_enabled = "no" +module_webrtc_enabled = "no" +module_websocket_enabled = "no" +module_xatlas_unwrap_enabled = "no" ``` See also: `doc_overriding_build_options`. diff --git a/05_engine_development/01_compiling/12_compiling_with_script_encryption_key.md b/05_engine_development/01_compiling/12_compiling_with_script_encryption_key.md index 2b568de..e01dc9b 100644 --- a/05_engine_development/01_compiling/12_compiling_with_script_encryption_key.md +++ b/05_engine_development/01_compiling/12_compiling_with_script_encryption_key.md @@ -28,14 +28,14 @@ PCK encryption. `OpenSSL ( https://www.openssl.org/ )` command-line tools: ``` - openssl rand -hex 32 > pandemonium.gdkey +openssl rand -hex 32 > pandemonium.gdkey ``` The output in `pandemonium.gdkey` should be similar to: ``` - # NOTE: Do not use the key below! Generate your own key instead. - aeb1bc56aaf580cc31784e9c41551e9ed976ecba10d315db591e749f3f64890f +# NOTE: Do not use the key below! Generate your own key instead. +aeb1bc56aaf580cc31784e9c41551e9ed976ecba10d315db591e749f3f64890f ``` You can generate the key without redirecting the output to a file, but @@ -73,24 +73,24 @@ your Pandemonium build. Pandemonium is encrypting the scripts during export, but them at runtime. ``` - ERROR: open_and_parse: Condition "String::md5(md5.digest) != String::md5(md5d)" is true. Returning: ERR_FILE_CORRUPT - At: core/io/file_access_encrypted.cpp:103 - ERROR: load_byte_code: Condition ' err ' is true. returned: err - At: modules/gdscript/gdscript.cpp:755 - ERROR: load: Condition ' err != OK ' is true. returned: RES() - At: modules/gdscript/gdscript.cpp:2135 - ERROR: Failed loading resource: res://Node2D.gde - At: core/io/resource_loader.cpp:279 - ERROR: poll: res://Node2D.tscn:3 - Parse Error: [ext_resource] referenced nonexistent resource at: res://Node2D.gd - At: scene/resources/scene_format_text.cpp:439 - ERROR: load: Condition ' err != OK ' is true. returned: RES() - At: core/io/resource_loader.cpp:202 - ERROR: Failed loading resource: res://Node2D.tscn - At: core/io/resource_loader.cpp:279 - ERROR: Failed loading scene: res://Node2D.tscn - At: main/main.cpp:1727 - WARNING: cleanup: ObjectDB Instances still exist! - At: core/object.cpp:2081 - ERROR: clear: Resources Still in use at Exit! - At: core/resource.cpp:425 +ERROR: open_and_parse: Condition "String::md5(md5.digest) != String::md5(md5d)" is true. Returning: ERR_FILE_CORRUPT + At: core/io/file_access_encrypted.cpp:103 +ERROR: load_byte_code: Condition ' err ' is true. returned: err + At: modules/gdscript/gdscript.cpp:755 +ERROR: load: Condition ' err != OK ' is true. returned: RES() + At: modules/gdscript/gdscript.cpp:2135 +ERROR: Failed loading resource: res://Node2D.gde + At: core/io/resource_loader.cpp:279 +ERROR: poll: res://Node2D.tscn:3 - Parse Error: [ext_resource] referenced nonexistent resource at: res://Node2D.gd + At: scene/resources/scene_format_text.cpp:439 +ERROR: load: Condition ' err != OK ' is true. returned: RES() + At: core/io/resource_loader.cpp:202 +ERROR: Failed loading resource: res://Node2D.tscn + At: core/io/resource_loader.cpp:279 +ERROR: Failed loading scene: res://Node2D.tscn + At: main/main.cpp:1727 +WARNING: cleanup: ObjectDB Instances still exist! + At: core/object.cpp:2081 +ERROR: clear: Resources Still in use at Exit! + At: core/resource.cpp:425 ``` \ No newline at end of file diff --git a/05_engine_development/02_cpp/01_introduction_to_pandemonium_development.md b/05_engine_development/02_cpp/01_introduction_to_pandemonium_development.md index c6916d2..ac31f9c 100644 --- a/05_engine_development/02_cpp/01_introduction_to_pandemonium_development.md +++ b/05_engine_development/02_cpp/01_introduction_to_pandemonium_development.md @@ -24,14 +24,14 @@ passing the `-e` argument to Pandemonium Engine's binary from within your project's folder. Typically: ``` - $ cd ~/myproject - $ gdb pandemonium - > run -e +$ cd ~/myproject +$ gdb pandemonium +> run -e ``` Or: ``` - $ gdb pandemonium - > run -e --path ~/myproject +$ gdb pandemonium +> run -e --path ~/myproject ``` diff --git a/05_engine_development/02_cpp/02_common_engine_methods_and_macros.md b/05_engine_development/02_cpp/02_common_engine_methods_and_macros.md index 2613d89..1c115d8 100644 --- a/05_engine_development/02_cpp/02_common_engine_methods_and_macros.md +++ b/05_engine_development/02_cpp/02_common_engine_methods_and_macros.md @@ -10,21 +10,21 @@ but it can also be useful for those writing custom C++ modules. ``` - // Prints a message to standard output. - print_line("Message"); +// Prints a message to standard output. +print_line("Message"); - // Prints a message to standard output, but only when the engine - // is started with the `--verbose` command line argument. - print_verbose("Message"); +// Prints a message to standard output, but only when the engine +// is started with the `--verbose` command line argument. +print_verbose("Message"); - // Prints a formatted error or warning message with a trace. - ERR_PRINT("Message"); - WARN_PRINT("Message"); +// Prints a formatted error or warning message with a trace. +ERR_PRINT("Message"); +WARN_PRINT("Message"); - // Prints an error or warning message only once per session. - // This can be used to avoid spamming the console output. - ERR_PRINT_ONCE("Message"); - WARN_PRINT_ONCE("Message"); +// Prints an error or warning message only once per session. +// This can be used to avoid spamming the console output. +ERR_PRINT_ONCE("Message"); +WARN_PRINT_ONCE("Message"); ``` If you need to add placeholders in your messages, use format strings as @@ -36,14 +36,14 @@ The `vformat()` function returns a formatted `String`. It behaves in a way similar to C's `sprintf()`: ``` - vformat("My name is %s.", "Godette"); - vformat("%d bugs on the wall!", 1234); - vformat("Pi is approximately %f.", 3.1416); +vformat("My name is %s.", "Godette"); +vformat("%d bugs on the wall!", 1234); +vformat("Pi is approximately %f.", 3.1416); - // Converts the resulting String into a `const char *`. - // You may need to do this if passing the result as an argument - // to a method that expects a `const char *` instead of a String. - vformat("My name is %s.", "Godette").c_str(); +// Converts the resulting String into a `const char *`. +// You may need to do this if passing the result as an argument +// to a method that expects a `const char *` instead of a String. +vformat("My name is %s.", "Godette").c_str(); ``` In most cases, try to use `vformat()` instead of string concatenation as it @@ -54,11 +54,11 @@ makes for more readable code. This is mainly useful when printing numbers directly. ``` - // Prints "42" using integer-to-string conversion. - print_line(itos(42)); +// Prints "42" using integer-to-string conversion. +print_line(itos(42)); - // Prints "123.45" using real-to-string conversion. - print_line(rtos(123.45)); +// Prints "123.45" using real-to-string conversion. +print_line(rtos(123.45)); ``` ## Internationalize a string @@ -74,18 +74,18 @@ There are two types of internationalization in Pandemonium's codebase: translation shouldn't be used in editor-only code. ``` - // Returns the translated string that matches the user's locale settings. - // Translations are located in `editor/translations`. - // The localization template is generated automatically; don't modify it. - TTR("Exit the editor?"); +// Returns the translated string that matches the user's locale settings. +// Translations are located in `editor/translations`. +// The localization template is generated automatically; don't modify it. +TTR("Exit the editor?"); ``` To insert placeholders in localizable strings, wrap the localization macro in a `vformat()` call as follows: ``` - String file_path = "example.txt"; - vformat(TTR("Couldn't open \"%s\" for reading."), file_path); +String file_path = "example.txt"; +vformat(TTR("Couldn't open \"%s\" for reading."), file_path); ``` Note: @@ -102,12 +102,12 @@ Pandemonium provides macros for clamping a value with a lower bound (`MAX`), an upper bound (`MIN`) or both (`CLAMP`): ``` - int a = 3; - int b = 5; +int a = 3; +int b = 5; - MAX(b, 6); // 6 - MIN(2, a); // 2 - CLAMP(a, 10, 30); // 10 +MAX(b, 6); // 6 +MIN(2, a); // 2 +CLAMP(a, 10, 30); // 10 ``` This works with any type that can be compared to other values (like `int` and @@ -119,12 +119,12 @@ If you want to benchmark a piece of code but don't know how to use a profiler, use this snippet: ``` - uint64_t begin = OS::get_singleton()->get_ticks_usec(); +uint64_t begin = OS::get_singleton()->get_ticks_usec(); - // Your code here... +// Your code here... - uint64_t end = OS::get_singleton()->get_ticks_usec(); - print_line(vformat("Snippet took %d microseconds", end - begin)); +uint64_t end = OS::get_singleton()->get_ticks_usec(); +print_line(vformat("Snippet took %d microseconds", end - begin)); ``` This will print the time spent between the `begin` declaration and the `end` @@ -142,23 +142,23 @@ Note: There are four macros available for this: ``` - // Returns the specified project setting's value, - // defaulting to `false` if it doesn't exist. - GLOBAL_DEF("section/subsection/value", false); +// Returns the specified project setting's value, +// defaulting to `false` if it doesn't exist. +GLOBAL_DEF("section/subsection/value", false); - // Returns the specified editor setting's value, - // defaulting to "Untitled" if it doesn't exist. - EDITOR_DEF("section/subsection/value", "Untitled"); +// Returns the specified editor setting's value, +// defaulting to "Untitled" if it doesn't exist. +EDITOR_DEF("section/subsection/value", "Untitled"); ``` If a default value has been specified elsewhere, don't specify it again to avoid repetition: ``` - // Returns the value of the project setting. - GLOBAL_GET("section/subsection/value"); - // Returns the value of the editor setting. - EDITOR_GET("section/subsection/value"); +// Returns the value of the project setting. +GLOBAL_GET("section/subsection/value"); +// Returns the value of the editor setting. +EDITOR_GET("section/subsection/value"); ``` It's recommended to use `GLOBAL_DEF`/`EDITOR_DEF` only once per setting and @@ -192,41 +192,41 @@ Note: well. ``` - // Conditionally prints an error message and returns from the function. - // Use this in methods which don't return a value. - ERR_FAIL_COND_MSG(!mesh.is_valid(), vformat("Couldn't load mesh at: %s", path)); +// Conditionally prints an error message and returns from the function. +// Use this in methods which don't return a value. +ERR_FAIL_COND_MSG(!mesh.is_valid(), vformat("Couldn't load mesh at: %s", path)); - // Conditionally prints an error message and returns `0` from the function. - // Use this in methods which must return a value. - ERR_FAIL_COND_V_MSG(rect.x < 0 || rect.y < 0, 0, - "Couldn't calculate the rectangle's area."); +// Conditionally prints an error message and returns `0` from the function. +// Use this in methods which must return a value. +ERR_FAIL_COND_V_MSG(rect.x < 0 || rect.y < 0, 0, + "Couldn't calculate the rectangle's area."); - // Prints an error message if `index` is < 0 or >= `SomeEnum::QUALITY_MAX`, - // then returns from the function. - ERR_FAIL_INDEX_MSG(index, SomeEnum::QUALITY_MAX, - vformat("Invalid quality: %d. See SomeEnum for allowed values.", index)); +// Prints an error message if `index` is < 0 or >= `SomeEnum::QUALITY_MAX`, +// then returns from the function. +ERR_FAIL_INDEX_MSG(index, SomeEnum::QUALITY_MAX, + vformat("Invalid quality: %d. See SomeEnum for allowed values.", index)); - // Prints an error message if `index` is < 0 >= `some_array.size()`, - // then returns `-1` from the function. - ERR_FAIL_INDEX_V_MSG(index, some_array.size(), -1, - vformat("Item %d is out of bounds.", index)); +// Prints an error message if `index` is < 0 >= `some_array.size()`, +// then returns `-1` from the function. +ERR_FAIL_INDEX_V_MSG(index, some_array.size(), -1, + vformat("Item %d is out of bounds.", index)); - // Unconditionally prints an error message and returns from the function. - // Only use this if you need to perform complex error checking. - if (!complex_error_checking_routine()) { - ERR_FAIL_MSG("Couldn't reload the filesystem cache."); - } +// Unconditionally prints an error message and returns from the function. +// Only use this if you need to perform complex error checking. +if (!complex_error_checking_routine()) { + ERR_FAIL_MSG("Couldn't reload the filesystem cache."); +} - // Unconditionally prints an error message and returns `false` from the function. - // Only use this if you need to perform complex error checking. - if (!complex_error_checking_routine()) { - ERR_FAIL_V_MSG(false, "Couldn't parse the input arguments."); - } +// Unconditionally prints an error message and returns `false` from the function. +// Only use this if you need to perform complex error checking. +if (!complex_error_checking_routine()) { + ERR_FAIL_V_MSG(false, "Couldn't parse the input arguments."); +} - // Crashes the engine. This should generally never be used - // except for testing crash handling code. Pandemonium's philosophy - // is to never crash, both in the editor and in exported projects. - CRASH_NOW_MSG("Can't predict the future! Aborting."); +// Crashes the engine. This should generally never be used +// except for testing crash handling code. Pandemonium's philosophy +// is to never crash, both in the editor and in exported projects. +CRASH_NOW_MSG("Can't predict the future! Aborting."); ``` See also: diff --git a/05_engine_development/02_cpp/03_core_types.md b/05_engine_development/02_cpp/03_core_types.md index cf8715f..ba4e422 100644 --- a/05_engine_development/02_cpp/03_core_types.md +++ b/05_engine_development/02_cpp/03_core_types.md @@ -75,9 +75,9 @@ should not be used. Instead, a few other ones are provided. For C-style allocation, Pandemonium provides a few macros: ``` - memalloc() - memrealloc() - memfree() +memalloc() +memrealloc() +memfree() ``` These are equivalent to the usual malloc, realloc, free of the standard C @@ -86,11 +86,11 @@ library. For C++-style allocation, special macros are provided: ``` - memnew( Class / Class(args) ) - memdelete( instance ) +memnew( Class / Class(args) ) +memdelete( instance ) - memnew_arr( Class , amount ) - memdelete_arr( pointer to array ) +memnew_arr( Class , amount ) +memdelete_arr( pointer to array ) ``` which are equivalent to new, delete, new[] and delete[]. @@ -103,19 +103,19 @@ standard vector class, and is very similar to vector in the C++ standard library To create a PoolVector buffer, use this: ``` - PoolVector data; +PoolVector data; ``` PoolVector can be accessed using the [] operator and a few helpers exist for this: ``` - PoolVector::Read r = data.read() - int someint = r[4] +PoolVector::Read r = data.read() +int someint = r[4] ``` ``` - PoolVector::Write w = data.write() - w[4] = 22; +PoolVector::Write w = data.write() +w[4] = 22; ``` These operations allow fast read/write from PoolVectors and keep it @@ -143,9 +143,9 @@ debug symbols and code. List, Set and Map can be iterated using pointers, like this: ``` - for(List::Element *E=somelist.front();E;E=E->next()) { - print_line(E->get()); // print the element - } +for(List::Element *E=somelist.front();E;E=E->next()) { + print_line(E->get()); // print the element +} ``` The Vector<> class also has a few nice features: diff --git a/05_engine_development/02_cpp/05_object_class.md b/05_engine_development/02_cpp/05_object_class.md index 9ca5b19..fc4613f 100644 --- a/05_engine_development/02_cpp/05_object_class.md +++ b/05_engine_development/02_cpp/05_object_class.md @@ -14,19 +14,19 @@ editable properties, and declaring them is a matter of using a single macro like this. ``` - class CustomObject : public Object { +class CustomObject : public Object { - GDCLASS(CustomObject, Object); // this is required to inherit - }; + GDCLASS(CustomObject, Object); // this is required to inherit +}; ``` This makes Objects gain a lot of functionality, like for example ``` - obj = memnew(CustomObject); - print_line("Object class: ", obj->get_class()); // print object class +obj = memnew(CustomObject); +print_line("Object class: ", obj->get_class()); // print object class - obj2 = Object::cast_to(obj); // converting between classes, this also works without RTTI enabled. +obj2 = Object::cast_to(obj); // converting between classes, this also works without RTTI enabled. ``` ### References: @@ -42,7 +42,7 @@ their methods properties and integer constants. Classes are registered by calling: ``` - ClassDB::register_class() +ClassDB::register_class() ``` Registering it will allow the class to be instanced by scripts, code, or @@ -51,7 +51,7 @@ creating them again when deserializing. Registering as virtual is the same but it can't be instanced. ``` - ClassDB::register_virtual_class() +ClassDB::register_virtual_class() ``` Object-derived classes can override the static function @@ -65,13 +65,13 @@ Inside `bind_methods`, there are a couple of things that can be done. Registering functions is one: ``` - ClassDB::bind_method(D_METHOD("methodname", "arg1name", "arg2name"), &MyCustomMethod); +ClassDB::bind_method(D_METHOD("methodname", "arg1name", "arg2name"), &MyCustomMethod); ``` Default values for arguments can be passed in reverse order: ``` - ClassDB::bind_method(D_METHOD("methodname", "arg1name", "arg2name"), &MyCustomType::method, DEFVAL(-1)); // default value for arg2name +ClassDB::bind_method(D_METHOD("methodname", "arg1name", "arg2name"), &MyCustomType::method, DEFVAL(-1)); // default value for arg2name ``` `D_METHOD` is a macro that converts "methodname" to a StringName for more @@ -94,24 +94,24 @@ string passing the name can be passed for brevity. Classes often have enums such as: ``` - enum SomeMode { - MODE_FIRST, - MODE_SECOND - }; +enum SomeMode { + MODE_FIRST, + MODE_SECOND +}; ``` For these to work when binding to methods, the enum must be declared convertible to int, for this a macro is provided: ``` - VARIANT_ENUM_CAST(MyClass::SomeMode); // now functions that take SomeMode can be bound. +VARIANT_ENUM_CAST(MyClass::SomeMode); // now functions that take SomeMode can be bound. ``` The constants can also be bound inside `bind_methods`, by using: ``` - BIND_CONSTANT(MODE_FIRST); - BIND_CONSTANT(MODE_SECOND); +BIND_CONSTANT(MODE_FIRST); +BIND_CONSTANT(MODE_SECOND); ``` ## Properties (set/get) @@ -125,13 +125,13 @@ Properties are usually defined by the PropertyInfo() class. Usually constructed as: ``` - PropertyInfo(type, name, hint, hint_string, usage_flags) +PropertyInfo(type, name, hint, hint_string, usage_flags) ``` For example: ``` - PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "0,49,1", PROPERTY_USAGE_EDITOR) +PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "0,49,1", PROPERTY_USAGE_EDITOR) ``` This is an integer property, named "amount", hint is a range, range goes @@ -141,7 +141,7 @@ from 0 to 49 in steps of 1 (integers). It is only usable for the editor Another example: ``` - PropertyInfo(Variant::STRING, "modes", PROPERTY_HINT_ENUM, "Enabled,Disabled,Turbo") +PropertyInfo(Variant::STRING, "modes", PROPERTY_HINT_ENUM, "Enabled,Disabled,Turbo") ``` This is a string property, can take any string but the editor will only @@ -161,7 +161,7 @@ From `bind_methods()`, properties can be created and bound as long as set/get functions exist. Example: ``` - ADD_PROPERTY(PropertyInfo(Variant::INT, "amount"), "set_amount", "get_amount") +ADD_PROPERTY(PropertyInfo(Variant::INT, "amount"), "set_amount", "get_amount") ``` This creates the property using the setter and the getter. @@ -179,10 +179,10 @@ every override and the previous ones are not invalidated (multilevel call). ``` - protected: - void _get_property_list(List *r_props) const; // return list of properties - bool _get(const StringName &p_property, Variant &r_value) const; // return true if property was found - bool _set(const StringName &p_property, const Variant &p_value); // return true if property was found +protected: + void _get_property_list(List *r_props) const; // return list of properties + bool _get(const StringName &p_property, Variant &r_value) const; // return true if property was found + bool _set(const StringName &p_property, const Variant &p_value); // return true if property was found ``` This is also a little less efficient since `p_property` must be @@ -194,10 +194,10 @@ Pandemonium provides dynamic casting between Object-derived classes, for example: ``` - void somefunc(Object *some_obj) { +void somefunc(Object *some_obj) { - Button *button = Object::cast_to