mirror of
https://github.com/Relintai/pandemonium_engine_docs.git
synced 2025-01-31 15:49:23 +01:00
Ran it.
This commit is contained in:
parent
87d7f0c1be
commit
80c317b0f5
@ -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
|
||||
|
@ -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
|
||||
```
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
```
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
```
|
||||
|
||||
|
@ -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()
|
||||
```
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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))
|
||||
```
|
||||
|
@ -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?
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
```
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
```
|
||||
|
||||
|
||||
|
@ -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!
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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`.
|
||||
|
@ -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)
|
||||
```
|
@ -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)
|
||||
```
|
@ -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
|
||||
|
@ -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()
|
||||
```
|
@ -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
|
||||
|
@ -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()
|
||||
```
|
||||
|
@ -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.
|
||||
```
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
```
|
@ -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
|
||||
|
@ -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**.
|
||||
|
@ -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
|
||||
```
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
```
|
||||
|
@ -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
|
||||
|
@ -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<Resource> ResourceInteractiveLoader::get_resource();
|
||||
Ref<Resource> 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
|
||||
|
@ -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
|
||||
|
@ -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 |
|
||||
|
@ -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()
|
||||
```
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
```
|
||||
|
@ -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.,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
```
|
||||
|
@ -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)]
|
||||
```
|
||||
|
||||
|
||||
|
@ -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))
|
||||
```
|
||||
|
||||
|
||||
|
@ -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)
|
||||
```
|
||||
|
||||
|
||||
|
@ -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!
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
```
|
||||
|
@ -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)
|
||||
```
|
||||
|
@ -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()
|
||||
```
|
||||
|
@ -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
|
||||
```
|
||||
|
@ -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)
|
||||
```
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
```
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
||||
```
|
||||
<meta-data
|
||||
android:name="org.pandemoniumengine.plugin.v1.[PluginName]"
|
||||
android:value="[plugin.init.ClassFullName]" />
|
||||
<meta-data
|
||||
android:name="org.pandemoniumengine.plugin.v1.[PluginName]"
|
||||
android:value="[plugin.init.ClassFullName]" />
|
||||
```
|
||||
|
||||
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"))
|
||||
```
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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="
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
"
|
||||
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="
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
"
|
||||
StringPlistKeyToInput:string_input="Type something"
|
||||
```
|
||||
|
||||
The `config` section and fields are required and defined as follow:
|
||||
|
@ -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
|
||||
```
|
||||
|
@ -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:
|
||||
|
||||
```
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My Template</title>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script src="$PANDEMONIUM_URL"></script>
|
||||
<script>
|
||||
var engine = new Engine($PANDEMONIUM_CONFIG);
|
||||
engine.startGame();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My Template</title>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script src="$PANDEMONIUM_URL"></script>
|
||||
<script>
|
||||
var engine = new Engine($PANDEMONIUM_CONFIG);
|
||||
engine.startGame();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## 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
|
||||
|
@ -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"
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
```
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user