mirror of
https://github.com/Relintai/pandemonium_engine_docs.git
synced 2025-01-21 15:07:22 +01:00
More block cleanups.
This commit is contained in:
parent
8e5258c966
commit
469fb55c5e
@ -28,9 +28,9 @@ Usage is generally as follows
|
||||
Obtaining a ResourceInteractiveLoader
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
```
|
||||
Ref( ResourceInteractiveLoader> ResourceLoader::load_interactive(String p_path);
|
||||
```
|
||||
|
||||
This method will give you a ResourceInteractiveLoader that you will use
|
||||
to manage the load operation.
|
||||
@ -38,9 +38,9 @@ to manage the load operation.
|
||||
Polling
|
||||
~~~~~~~
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
```
|
||||
Error ResourceInteractiveLoader::poll();
|
||||
```
|
||||
|
||||
Use this method to advance the progress of the load. Each call to
|
||||
`poll` will load the next stage of your resource. Keep in mind that
|
||||
@ -55,10 +55,10 @@ Load progress (optional)
|
||||
|
||||
To query the progress of the load, use the following methods:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
```
|
||||
int ResourceInteractiveLoader::get_stage_count() const;
|
||||
int ResourceInteractiveLoader::get_stage() const;
|
||||
```
|
||||
|
||||
`get_stage_count` returns the total number of stages to load.
|
||||
`get_stage` returns the current stage being loaded.
|
||||
@ -66,9 +66,9 @@ To query the progress of the load, use the following methods:
|
||||
Forcing completion (optional)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
```
|
||||
Error ResourceInteractiveLoader::wait();
|
||||
```
|
||||
|
||||
Use this method if you need to load the entire resource in the current
|
||||
frame, without any more steps.
|
||||
@ -76,9 +76,9 @@ frame, without any more steps.
|
||||
Obtaining the resource
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Ref( Resource> ResourceInteractiveLoader::get_resource();
|
||||
```
|
||||
Ref<Resource> ResourceInteractiveLoader::get_resource();
|
||||
```
|
||||
|
||||
If everything goes well, use this method to retrieve your loaded
|
||||
resource.
|
||||
@ -92,8 +92,7 @@ context of the `doc_singletons_autoload` example.
|
||||
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
|
||||
@ -103,6 +102,7 @@ with the main scene of the game:
|
||||
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
|
||||
needs to be switched. It requests an interactive loader, and calls
|
||||
@ -110,8 +110,7 @@ needs to be switched. It requests an interactive loader, and calls
|
||||
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.
|
||||
@ -125,6 +124,7 @@ progress bar or loading screen.
|
||||
get_node("animation").play("loading")
|
||||
|
||||
wait_frames = 1
|
||||
```
|
||||
|
||||
`process` is where the loader is polled. `poll` is called, and then
|
||||
we deal with the return value from that call. `OK` means keep polling,
|
||||
@ -138,8 +138,7 @@ to cram more than one call to `poll` in one frame; some might take way
|
||||
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
|
||||
@ -168,6 +167,7 @@ precise control over the timings.
|
||||
show_error()
|
||||
loader = null
|
||||
break
|
||||
```
|
||||
|
||||
Some extra helper functions. `update_progress` updates a progress bar,
|
||||
or can also update a paused animation (the animation represents the
|
||||
@ -176,8 +176,7 @@ newly loaded scene on the tree. Because it's a scene being loaded,
|
||||
`instance()` needs to be called on the resource obtained from the
|
||||
loader.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func update_progress():
|
||||
var progress = float(loader.get_stage()) / loader.get_stage_count()
|
||||
# Update your progress bar?
|
||||
@ -194,6 +193,7 @@ loader.
|
||||
func set_new_scene(scene_resource):
|
||||
current_scene = scene_resource.instance()
|
||||
get_node("/root").add_child(current_scene)
|
||||
```
|
||||
|
||||
Using multiple threads
|
||||
----------------------
|
||||
@ -224,34 +224,34 @@ Example class
|
||||
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()
|
||||
```
|
||||
|
||||
Call after you instance the class to start the thread.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
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)
|
||||
```
|
||||
|
||||
Remove a resource from the queue, discarding any loading done.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func is_ready(path)
|
||||
```
|
||||
|
||||
Returns `true` if a resource is fully loaded and ready to be retrieved.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func get_progress(path)
|
||||
```
|
||||
|
||||
Get the progress of a resource. Returns -1 if there was an error (for example if the
|
||||
resource is not in the queue), or a number between 0.0 and 1.0 with the
|
||||
@ -259,9 +259,9 @@ progress of the load. Use mostly for cosmetic purposes (updating
|
||||
progress bars, etc), use `is_ready` to find out if a resource is
|
||||
actually ready.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func get_resource(path)
|
||||
```
|
||||
|
||||
Returns the fully loaded resource, or `null` on error. If the resource is
|
||||
not fully loaded (`is_ready` returns `false`), it will block your thread
|
||||
@ -271,8 +271,7 @@ 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()
|
||||
@ -301,6 +300,7 @@ Example:
|
||||
|
||||
# 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
|
||||
scenarios. If you run into any issues, ask for help in one of
|
||||
|
@ -20,10 +20,12 @@ little-endian-encoded. All packets have a 4-byte header representing an
|
||||
integer, specifying the type of data.
|
||||
|
||||
The lowest value two bytes are used to determine the type, while the highest value
|
||||
two bytes contain flags::
|
||||
two bytes contain flags
|
||||
|
||||
```
|
||||
base_type = val & 0xFFFF;
|
||||
flags = val >> 16;
|
||||
```
|
||||
|
||||
+--------+--------------------------+
|
||||
| Type | Value |
|
||||
|
@ -11,15 +11,15 @@ 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
|
||||
```
|
||||
|
||||
And often simplified to:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
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.
|
||||
|
||||
|
@ -280,8 +280,9 @@ could get the same fruit three or more times in a row.
|
||||
|
||||
You can accomplish this using the *shuffle bag* pattern. It works by removing an
|
||||
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::
|
||||
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 = []
|
||||
@ -307,6 +308,7 @@ ends up empty. When that happens, you reinitialize it to its default value::
|
||||
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
|
||||
row. Once we picked a fruit, it will no longer be a possible return value unless
|
||||
|
@ -84,37 +84,37 @@ 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
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
Get the previously set network peer:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
get_tree().get_network_peer()
|
||||
```
|
||||
|
||||
Checking whether the tree is initialized as a server or client:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
get_tree().is_network_server()
|
||||
```
|
||||
|
||||
Terminating the networking feature:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
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.)
|
||||
|
||||
@ -189,8 +189,7 @@ Back to lobby
|
||||
|
||||
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.
|
||||
|
||||
extends Node
|
||||
@ -232,12 +231,13 @@ Let's get back to the lobby. Imagine that each player that connects to the serve
|
||||
player_info[id] = info
|
||||
|
||||
# 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):
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
@ -260,10 +260,10 @@ 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
|
||||
```
|
||||
|
||||
Tip:
|
||||
You can also use `SceneTree.get_rpc_sender_id()` to have more advanced rules on how an rpc can be called.
|
||||
@ -292,8 +292,7 @@ node represents each player ID.
|
||||
The solution is to simply name the *root nodes of the instanced player scenes as their network ID*. This way, they will be the same in
|
||||
every peer and RPC will work great! Here is an example:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
remote func pre_configure_game():
|
||||
var selfPeerID = get_tree().get_network_unique_id()
|
||||
|
||||
@ -317,7 +316,7 @@ every peer and RPC will work great! Here is an example:
|
||||
# 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:
|
||||
Depending on when you execute pre_configure_game(), you may need to change any calls to `add_child()`
|
||||
@ -329,16 +328,15 @@ Synchronizing game start
|
||||
Setting up players might take different amounts of time for every peer due to lag, different hardware, or other reasons.
|
||||
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)
|
||||
```
|
||||
|
||||
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()
|
||||
@ -358,7 +356,7 @@ When the server gets the OK from all the peers, it can tell them to start, as fo
|
||||
get_tree().set_pause(false)
|
||||
# Game starts now!
|
||||
|
||||
|
||||
```
|
||||
|
||||
Synchronizing the game
|
||||
----------------------
|
||||
@ -380,8 +378,7 @@ 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()
|
||||
@ -396,7 +393,7 @@ If you have paid attention to the previous example, it's possible you noticed th
|
||||
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.
|
||||
|
||||
@ -416,16 +413,15 @@ 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)
|
||||
```
|
||||
|
||||
Example player code:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
puppet func stun():
|
||||
stunned = true
|
||||
|
||||
@ -438,6 +434,7 @@ Example player code:
|
||||
# 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).
|
||||
The bomb knows the bodies (player nodes) in the area, so it checks that they contain an `exploded` method before calling it.
|
||||
@ -472,9 +469,9 @@ any player in the bomb area get stunned on the screens of all the peers.
|
||||
Note that you could also send the `stun()` message only to a specific player by using `rpc_id(<id>, "exploded", bomb_owner)`.
|
||||
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
|
||||
```
|
||||
|
||||
Exporting for dedicated servers
|
||||
-------------------------------
|
||||
|
@ -61,9 +61,9 @@ and it will work.
|
||||
If you are using Linux, you can use the supplied certs file, generally
|
||||
located in:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
```
|
||||
/etc/ssl/certs/ca-certificates.crt
|
||||
```
|
||||
|
||||
This file allows HTTPS connections to virtually any website (i.e.,
|
||||
Google, Microsoft, etc.).
|
||||
|
@ -52,8 +52,7 @@ Minimal connection example
|
||||
This example will show you how to create a WebRTC connection between two peers in the same application.
|
||||
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
|
||||
|
||||
# Create the two peers
|
||||
@ -96,21 +95,21 @@ This is not very useful in real life, but will give you a good overview of how a
|
||||
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
|
||||
```
|
||||
|
||||
Local signaling example
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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
|
||||
|
||||
@ -146,14 +145,14 @@ This example expands on the previous one, separating the peers in two different
|
||||
|
||||
func send_message(message):
|
||||
channel.put_packet(message.to_utf8())
|
||||
```
|
||||
|
||||
And now for the local signaling server:
|
||||
|
||||
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
|
||||
|
||||
@ -183,11 +182,11 @@ Note:
|
||||
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
|
||||
|
||||
@ -204,13 +203,14 @@ Then you can use it like this:
|
||||
# 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
|
||||
```
|
||||
|
||||
Remote signaling with WebSocket
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -33,7 +33,7 @@ Minimal client example
|
||||
|
||||
This example will show you how to create a WebSocket connection to a remote server, and how to send and receive data.
|
||||
|
||||
::
|
||||
```
|
||||
|
||||
extends Node
|
||||
|
||||
@ -83,20 +83,22 @@ This example will show you how to create a WebSocket connection to a remote serv
|
||||
# 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
|
||||
```
|
||||
|
||||
Minimal server example
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This example will show you how to create a WebSocket server that listens for remote connections, and how to send and receive data.
|
||||
|
||||
::
|
||||
```
|
||||
|
||||
extends Node
|
||||
|
||||
@ -149,13 +151,15 @@ This example will show you how to create a WebSocket server that listens for rem
|
||||
# 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
|
||||
```
|
||||
|
||||
Advanced chat demo
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
@ -113,11 +113,13 @@ objects are rendered from back to front, consider 3 objects `A`, `B` and
|
||||
|
||||
![](img/overlap1.png)
|
||||
|
||||
In painter's order they are ordered::
|
||||
In painter's order they are ordered:
|
||||
|
||||
```
|
||||
A - wood
|
||||
B - grass
|
||||
C - wood
|
||||
```
|
||||
|
||||
Because of the texture changes, they can't be batched and will be rendered in 3
|
||||
draw calls.
|
||||
@ -140,11 +142,12 @@ looking ahead to decide whether items can be reordered. The number of items to
|
||||
lookahead for reordering can be set in project settings (see below), in order to
|
||||
balance the costs and benefits in your project.
|
||||
|
||||
::
|
||||
```
|
||||
|
||||
A - wood
|
||||
C - wood
|
||||
B - grass
|
||||
```
|
||||
|
||||
Since the texture only changes once, we can render the above in only 2 draw
|
||||
calls.
|
||||
@ -161,7 +164,7 @@ lights, they would be drawn as follows, each line being a draw call:
|
||||
|
||||
![](img/lights_overlap.png)
|
||||
|
||||
::
|
||||
```
|
||||
|
||||
A
|
||||
A - light 1
|
||||
@ -171,6 +174,7 @@ lights, they would be drawn as follows, each line being a draw call:
|
||||
B - light 1
|
||||
B - light 2
|
||||
B - light 3
|
||||
```
|
||||
|
||||
That is a lot of draw calls: 8 for only 2 sprites. Now, consider we are drawing
|
||||
1,000 sprites. The number of draw calls quickly becomes astronomical and
|
||||
@ -185,12 +189,13 @@ so the drawing process is as follows:
|
||||
|
||||
![](img/lights_separate.png)
|
||||
|
||||
::
|
||||
```
|
||||
|
||||
AB
|
||||
AB - light 1
|
||||
AB - light 2
|
||||
AB - light 3
|
||||
```
|
||||
|
||||
|
||||
That is only 4 draw calls. Not bad, as that is a 2× reduction. However, consider
|
||||
@ -413,8 +418,7 @@ as intended, and help you fix these situations to get the best possible performa
|
||||
Reading a diagnostic
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
```
|
||||
canvas_begin FRAME 2604
|
||||
items
|
||||
joined_item 1 refs
|
||||
@ -434,6 +438,7 @@ Reading a diagnostic
|
||||
batch D 0-0
|
||||
batch R 0-2560 [0 - 144] {158 193 0 104 } MULTI
|
||||
canvas_end
|
||||
```
|
||||
|
||||
|
||||
This is a typical diagnostic.
|
||||
@ -455,8 +460,9 @@ Default batches
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
The second number following default batches is the number of commands in the
|
||||
batch, and it is followed by a brief summary of the contents::
|
||||
batch, and it is followed by a brief summary of the contents:
|
||||
|
||||
```
|
||||
l - line
|
||||
PL - polyline
|
||||
r - rect
|
||||
@ -469,6 +475,7 @@ batch, and it is followed by a brief summary of the contents::
|
||||
c - circle
|
||||
t - transform
|
||||
CI - clip_ignore
|
||||
```
|
||||
|
||||
You may see "dummy" default batches containing no commands; you can ignore those.
|
||||
|
||||
@ -544,10 +551,12 @@ value to the power of 4.
|
||||
|
||||
For example, on a screen size of 1920×1080, there are 2,073,600 pixels.
|
||||
|
||||
At a threshold of 1,000 pixels, the proportion would be::
|
||||
At a threshold of 1,000 pixels, the proportion would be:
|
||||
|
||||
```
|
||||
1000 / 2073600 = 0.00048225
|
||||
0.00048225 ^ (1/4) = 0.14819
|
||||
```
|
||||
|
||||
So a `scissor_area_threshold
|
||||
( ProjectSettings_property_rendering/batching/lights/scissor_area_threshold )`
|
||||
@ -555,10 +564,12 @@ of `0.15` would be a reasonable value to try.
|
||||
|
||||
Going the other way, for instance with a `scissor_area_threshold
|
||||
( ProjectSettings_property_rendering/batching/lights/scissor_area_threshold )`
|
||||
of `0.5`::
|
||||
of `0.5`:
|
||||
|
||||
```
|
||||
0.5 ^ 4 = 0.0625
|
||||
0.0625 * 2073600 = 129600 pixels
|
||||
```
|
||||
|
||||
If the number of pixels saved is greater than this threshold, the scissor is
|
||||
activated.
|
||||
|
@ -105,8 +105,7 @@ using a profiler, is to manually time the function or area under test.
|
||||
The specifics vary depending on the language, but in GDScript, you would do
|
||||
the following:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var time_start = OS.get_ticks_usec()
|
||||
|
||||
# Your function you want to time
|
||||
@ -114,6 +113,7 @@ the following:
|
||||
|
||||
var time_end = OS.get_ticks_usec()
|
||||
print("update_enemies() took %d microseconds" % time_end - time_start)
|
||||
```
|
||||
|
||||
When manually timing functions, it is usually a good idea to run the function
|
||||
many times (1,000 or more times), instead of just once (unless it is a very slow
|
||||
|
@ -244,17 +244,19 @@ The proverb *"a chain is only as strong as its weakest link"* applies directly t
|
||||
performance optimization. If your project is spending 90% of the time in
|
||||
function `A`, then optimizing `A` can have a massive effect on performance.
|
||||
|
||||
.. code-block:: none
|
||||
```
|
||||
|
||||
A: 9 ms
|
||||
Everything else: 1 ms
|
||||
Total frame time: 10 ms
|
||||
```
|
||||
|
||||
.. code-block:: none
|
||||
```
|
||||
|
||||
A: 1 ms
|
||||
Everything else: 1ms
|
||||
Total frame time: 2 ms
|
||||
```
|
||||
|
||||
In this example, improving this bottleneck `A` by a factor of 9× decreases
|
||||
overall frame time by 5× while increasing frames per second by 5×.
|
||||
@ -262,17 +264,19 @@ overall frame time by 5× while increasing frames per second by 5×.
|
||||
However, if something else is running slowly and also bottlenecking your
|
||||
project, then the same improvement can lead to less dramatic gains:
|
||||
|
||||
.. code-block:: none
|
||||
```
|
||||
|
||||
A: 9 ms
|
||||
Everything else: 50 ms
|
||||
Total frame time: 59 ms
|
||||
```
|
||||
|
||||
.. code-block:: none
|
||||
```
|
||||
|
||||
A: 1 ms
|
||||
Everything else: 50 ms
|
||||
Total frame time: 51 ms
|
||||
```
|
||||
|
||||
In this example, even though we have hugely optimized function `A`,
|
||||
the actual gain in terms of frame rate is quite small.
|
||||
@ -281,17 +285,19 @@ In games, things become even more complicated because the CPU and GPU run
|
||||
independently of one another. Your total frame time is determined by the slower
|
||||
of the two.
|
||||
|
||||
.. code-block:: none
|
||||
```
|
||||
|
||||
CPU: 9 ms
|
||||
GPU: 50 ms
|
||||
Total frame time: 50 ms
|
||||
```
|
||||
|
||||
.. code-block:: none
|
||||
```
|
||||
|
||||
CPU: 1 ms
|
||||
GPU: 50 ms
|
||||
Total frame time: 50 ms
|
||||
```
|
||||
|
||||
In this example, we optimized the CPU hugely again, but the frame time didn't
|
||||
improve because we are GPU-bottlenecked.
|
||||
|
@ -23,21 +23,21 @@ Scene tree
|
||||
|
||||
Interacting with the active scene tree is **NOT** thread-safe. Make sure to use mutexes when sending data between threads. If you want to call functions from a thread, the *call_deferred* function may be used:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# Unsafe:
|
||||
node.add_child(child_node)
|
||||
# Safe:
|
||||
node.call_deferred("add_child", child_node)
|
||||
```
|
||||
|
||||
However, creating scene chunks (nodes in tree arrangement) outside the active tree is fine. This way, parts of a scene can be built or instantiated in a thread, then added in the main thread:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var enemy_scene = load("res://enemy_scene.scn")
|
||||
var enemy = enemy_scene.instance()
|
||||
enemy.add_child(weapon) # Set a weapon.
|
||||
world.call_deferred("add_child", enemy)
|
||||
```
|
||||
|
||||
Still, this is only really useful if you have **one** thread loading data.
|
||||
Attempting to load or create scene chunks from multiple threads may work, but you risk
|
||||
|
@ -53,19 +53,19 @@ world orientation.
|
||||
|
||||
In order to control the speed of the animation, we will start by defining our own time variable using `TIME`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
//time_scale is a uniform float
|
||||
float time = TIME * time_scale;
|
||||
```
|
||||
|
||||
The first motion we will implement is the side to side motion. It can be made by offsetting `VERTEX.x` by
|
||||
`cos` of `TIME`. Each time the mesh is rendered, all the vertices will move to the side by the amount
|
||||
of `cos(time)`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
//side_to_side is a uniform float
|
||||
VERTEX.x += cos(time) * side_to_side;
|
||||
```
|
||||
|
||||
The resulting animation should look something like this:
|
||||
|
||||
@ -76,18 +76,18 @@ rotation matrix for it to rotate around the center of the fish.
|
||||
|
||||
We construct a rotation matrix like so:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
//angle is scaled by 0.1 so that the fish only pivots and doesn't rotate all the way around
|
||||
//pivot is a uniform float
|
||||
float pivot_angle = cos(time) * 0.1 * pivot;
|
||||
mat2 rotation_matrix = mat2(vec2(cos(pivot_angle), -sin(pivot_angle)), vec2(sin(pivot_angle), cos(pivot_angle)));
|
||||
```
|
||||
|
||||
And then we apply it in the `x` and `z` axes by multiplying it by `VERTEX.xz`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
VERTEX.xz = rotation_matrix * VERTEX.xz;
|
||||
```
|
||||
|
||||
With only the pivot applied you should see something like this:
|
||||
|
||||
@ -96,18 +96,18 @@ With only the pivot applied you should see something like this:
|
||||
The next two motions need to pan down the spine of the fish. For that, we need a new variable, `body`.
|
||||
`body` is a float that is `0` at the tail of the fish and `1` at its head.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
float body = (VERTEX.z + 1.0) / 2.0; //for a fish centered at (0, 0) with a length of 2
|
||||
```
|
||||
|
||||
The next motion is a cosine wave that moves down the length of the fish. To make
|
||||
it move along the spine of the fish, we offset the input to `cos` by the position
|
||||
along the spine, which is the variable we defined above, `body`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
//wave is a uniform float
|
||||
VERTEX.x += cos(time + body) * wave;
|
||||
```
|
||||
|
||||
This looks very similar to the side to side motion we defined above, but in this one, by
|
||||
using `body` to offset `cos` each vertex along the spine has a different position in
|
||||
@ -118,18 +118,18 @@ the wave making it look like a wave is moving along the fish.
|
||||
The last motion is the twist, which is a panning roll along the spine. Similarly to the pivot,
|
||||
we first construct a rotation matrix.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
//twist is a uniform float
|
||||
float twist_angle = cos(time + body) * 0.3 * twist;
|
||||
mat2 twist_matrix = mat2(vec2(cos(twist_angle), -sin(twist_angle)), vec2(sin(twist_angle), cos(twist_angle)));
|
||||
```
|
||||
|
||||
We apply the rotation in the `xy` axes so that the fish appears to roll around its spine. For
|
||||
this to work, the fish's spine needs to be centered on the `z` axis.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
VERTEX.xy = twist_matrix * VERTEX.xy;
|
||||
```
|
||||
|
||||
Here is the fish with twist applied:
|
||||
|
||||
@ -145,10 +145,10 @@ panning motions to the back half of the fish. To do this, we create a new variab
|
||||
`mask` is a float that goes from `0` at the front of the fish to `1` at the end using
|
||||
`smoothstep` to control the point at which the transition from `0` to `1` happens.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
//mask_black and mask_white are uniforms
|
||||
float mask = smoothstep(mask_black, mask_white, 1.0 - body);
|
||||
```
|
||||
|
||||
Below is an image of the fish with `mask` used as `COLOR`:
|
||||
|
||||
@ -156,10 +156,10 @@ Below is an image of the fish with `mask` used as `COLOR`:
|
||||
|
||||
For the wave, we multiply the motion by `mask` which will limit it to the back half.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
//wave motion with mask
|
||||
VERTEX.x += cos(time + body) * mask * wave;
|
||||
```
|
||||
|
||||
In order to apply the mask to the twist, we use `mix`. `mix` allows us to mix the
|
||||
vertex position between a fully rotated vertex and one that is not rotated. We need to
|
||||
@ -167,10 +167,10 @@ use `mix` instead of multiplying `mask` by the rotated `VERTEX` because we are n
|
||||
adding the motion to the `VERTEX` we are replacing the `VERTEX` with the rotated
|
||||
version. If we multiplied that by `mask`, we would shrink the fish.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
//twist motion with mask
|
||||
VERTEX.xy = mix(VERTEX.xy, twist_matrix * VERTEX.xy, mask);
|
||||
```
|
||||
|
||||
Putting the four motions together gives us the final animation.
|
||||
|
||||
@ -218,12 +218,12 @@ and is described in the `MultiMeshInstance tutorial ( doc_using_multi_mesh_insta
|
||||
The second is to loop over all the instances and set their transforms in code. Below, we use GDScript
|
||||
to loop over all the instances and set their transform to a random position.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
for i in range($School.multimesh.instance_count):
|
||||
var position = Transform()
|
||||
position = position.translated(Vector3(randf() * 100 - 50, randf() * 50 - 25, randf() * 50 - 25))
|
||||
$School.multimesh.set_instance_transform(i, position)
|
||||
```
|
||||
|
||||
Running this script will place the fish in random positions in a box around the position of the
|
||||
MultiMeshInstance.
|
||||
@ -244,26 +244,25 @@ swim cycle, we only need to offset `time`.
|
||||
|
||||
We do that by adding the per-instance custom value `INSTANCE_CUSTOM` to `time`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
float time = (TIME * time_scale) + (6.28318 * INSTANCE_CUSTOM.x);
|
||||
|
||||
Next, we need to pass a value into `INSTANCE_CUSTOM`. We do that by adding one line into
|
||||
the `for` loop from above. In the `for` loop we assign each instance a set of four
|
||||
random floats to use.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
$School.multimesh.set_instance_custom_data(i, Color(randf(), randf(), randf(), randf()))
|
||||
```
|
||||
|
||||
Now the fish all have unique positions in the swim cycle. You can give them a little more
|
||||
individuality by using `INSTANCE_CUSTOM` to make them swim faster or slower by multiplying
|
||||
by `TIME`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
//set speed from 50% - 150% of regular speed
|
||||
float time = (TIME * (0.5 + INSTANCE_CUSTOM.y) * time_scale) + (6.28318 * INSTANCE_CUSTOM.x);
|
||||
```
|
||||
|
||||
You can even experiment with changing the per-instance color the same way you changed the per-instance
|
||||
custom value.
|
||||
|
@ -21,14 +21,13 @@ First create a Particles node. Then, under "Draw Passes" set the Particle's "Dra
|
||||
|
||||
Set the `shader_type` to `particles`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type particles
|
||||
```
|
||||
|
||||
Then add the following two functions:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
float rand_from_seed(in uint seed) {
|
||||
int k;
|
||||
int s = int(seed);
|
||||
@ -48,6 +47,7 @@ Then add the following two functions:
|
||||
x = (x >> uint(16)) ^ x;
|
||||
return x;
|
||||
}
|
||||
```
|
||||
|
||||
These functions come from the default `ParticlesMaterial`.
|
||||
They are used to generate a random number from each particle's `RANDOM_SEED`.
|
||||
@ -66,8 +66,7 @@ built-in variable `RESTART` which becomes `true` for one frame when the particle
|
||||
|
||||
From a high level, this looks like:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void vertex() {
|
||||
if (RESTART) {
|
||||
//Initialization code goes here
|
||||
@ -75,34 +74,35 @@ From a high level, this looks like:
|
||||
//per-frame code goes here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Next, we need to generate 4 random numbers: 3 to create a random position and one for the random
|
||||
offset of the swim cycle.
|
||||
|
||||
First, generate 4 seeds inside the `RESTART` block using the `hash` function provided above:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
uint alt_seed1 = hash(NUMBER + uint(1) + RANDOM_SEED);
|
||||
uint alt_seed2 = hash(NUMBER + uint(27) + RANDOM_SEED);
|
||||
uint alt_seed3 = hash(NUMBER + uint(43) + RANDOM_SEED);
|
||||
uint alt_seed4 = hash(NUMBER + uint(111) + RANDOM_SEED);
|
||||
```
|
||||
|
||||
Then, use those seeds to generate random numbers using `rand_from_seed`:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
CUSTOM.x = rand_from_seed(alt_seed1);
|
||||
vec3 position = vec3(rand_from_seed(alt_seed2) * 2.0 - 1.0,
|
||||
rand_from_seed(alt_seed3) * 2.0 - 1.0,
|
||||
rand_from_seed(alt_seed4) * 2.0 - 1.0);
|
||||
```
|
||||
|
||||
Finally, assign `position` to `TRANSFORM[3].xyz`, which is the part of the transform that holds
|
||||
the position information.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
TRANSFORM[3].xyz = position * 20.0;
|
||||
```
|
||||
|
||||
Remember, all this code so far goes inside the `RESTART` block.
|
||||
|
||||
@ -113,27 +113,27 @@ or by writing to `VELOCITY`.
|
||||
|
||||
Let's transform the fish by setting their `VELOCITY`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
VELOCITY.z = 10.0;
|
||||
```
|
||||
|
||||
This is the most basic way to set `VELOCITY` every particle (or fish) will have the same velocity.
|
||||
|
||||
Just by setting `VELOCITY` you can make the fish swim however you want. For example, try the code
|
||||
below.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
VELOCITY.z = cos(TIME + CUSTOM.x * 6.28) * 4.0 + 6.0;
|
||||
```
|
||||
|
||||
This will give each fish a unique speed between `2` and `10`.
|
||||
|
||||
If you used `CUSTOM.y` in the last tutorial, you can also set the speed of the swim animation based
|
||||
on the `VELOCITY`. Just use `CUSTOM.y`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
CUSTOM.y = VELOCITY.z * 0.1;
|
||||
```
|
||||
|
||||
This code gives you the following behavior:
|
||||
|
||||
|
@ -60,8 +60,7 @@ Example manual Camera script
|
||||
|
||||
Here is an example of a simple fixed Camera which follows an interpolated target:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
```
|
||||
extends Camera
|
||||
|
||||
# Node that the camera will follow
|
||||
@ -88,6 +87,7 @@ Here is an example of a simple fixed Camera which follows an interpolated target
|
||||
|
||||
# Fixed camera position, but it will follow the target
|
||||
look_at(_target_pos, Vector3(0, 1, 0))
|
||||
```
|
||||
|
||||
Mouse look
|
||||
^^^^^^^^^^
|
||||
|
@ -69,10 +69,10 @@ 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:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
```
|
||||
fraction = 0.02 / 0.10
|
||||
fraction = 0.2
|
||||
```
|
||||
|
||||
This is called the **physics interpolation fraction**, and is handily calculated for you by Godot. It can be retrieved on any frame by calling `Engine.get_physics_interpolation_fraction( Engine_method_get_physics_interpolation_fraction )`.
|
||||
|
||||
@ -81,17 +81,17 @@ Calculating the interpolated position
|
||||
|
||||
Once we have the interpolation fraction, we can insert it into a standard linear interpolation equation. The X coordinate would thus be:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
```
|
||||
x_interpolated = x_prev + ((x_curr - x_prev) * 0.2)
|
||||
```
|
||||
|
||||
So substituting our `x_prev` as 10, and `x_curr` as 30:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
```
|
||||
x_interpolated = 10 + ((30 - 10) * 0.2)
|
||||
x_interpolated = 10 + 4
|
||||
x_interpolated = 14
|
||||
```
|
||||
|
||||
Let's break that down:
|
||||
|
||||
|
@ -162,8 +162,9 @@ can use binary, hexadecimal, or decimal notation for layer masks, depending
|
||||
on your preference.
|
||||
|
||||
The code equivalent of the above example where layers 1, 3 and 4 were enabled
|
||||
would be as follows::
|
||||
would be as follows:
|
||||
|
||||
```
|
||||
# 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.
|
||||
@ -178,6 +179,7 @@ would be as follows::
|
||||
# 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)
|
||||
```
|
||||
|
||||
|
||||
Area2D
|
||||
|
@ -106,8 +106,7 @@ gdscript GDScript
|
||||
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
|
||||
@ -117,6 +116,7 @@ data:
|
||||
shape: int # shape index of collider
|
||||
metadata: Variant() # metadata of collider
|
||||
}
|
||||
```
|
||||
|
||||
The data is similar in 3D space, using Vector3 coordinates.
|
||||
|
||||
|
@ -46,8 +46,7 @@ Getting started
|
||||
To use the `GodotGooglePlayBilling` API you first have to get the `GodotGooglePlayBilling`
|
||||
singleton and start the connection:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var payment
|
||||
|
||||
func _ready():
|
||||
@ -71,6 +70,7 @@ singleton and start the connection:
|
||||
payment.startConnection()
|
||||
else:
|
||||
print("Android IAP support is not enabled. Make sure you have enabled 'Custom Build' and the GodotGooglePlayBilling 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.
|
||||
|
||||
@ -82,14 +82,14 @@ 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_sku_details_query_completed(sku_details):
|
||||
for available_sku in sku_details:
|
||||
print(available_sku)
|
||||
```
|
||||
|
||||
|
||||
Purchase an item
|
||||
@ -99,14 +99,13 @@ To initiate the purchase flow for an item, call `purchase`.
|
||||
You **must** query the SKU details for an item before you can
|
||||
initiate the purchase flow for it.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
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
|
||||
@ -115,6 +114,7 @@ Then, wait for the `on_purchases_updated` callback and handle the purchase resul
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
Check if the user purchased an item
|
||||
@ -126,8 +126,7 @@ 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:
|
||||
@ -136,6 +135,7 @@ Full example:
|
||||
if !purchase.is_acknowledged:
|
||||
payment.acknowledgePurchase(purchase.purchase_token)
|
||||
# Or wait for the _on_purchase_acknowledged callback before giving the user what they bought
|
||||
```
|
||||
|
||||
|
||||
Consumables
|
||||
@ -147,8 +147,7 @@ Call `queryPurchases` to get the purchase token. Calling `consumePurchase` autom
|
||||
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:
|
||||
@ -156,6 +155,7 @@ Consuming a product allows the user to purchase it again, and removes it from ap
|
||||
# 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
|
||||
*************
|
||||
|
@ -68,11 +68,13 @@ The instructions below assumes that you're using Android Studio.
|
||||
|
||||
- Open the plugin `AndroidManifest.xml` file.
|
||||
- Add the `<application></application )` tag if it's missing.
|
||||
- In the `<application )` tag, add a `<meta-data )` tag setup as follow::
|
||||
- In the `<application )` tag, add a `<meta-data )` tag setup as follow:
|
||||
|
||||
```
|
||||
<meta-data
|
||||
android:name="org.godotengine.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.
|
||||
|
||||
@ -85,8 +87,9 @@ The instructions below assumes that you're using Android Studio.
|
||||
6. Create a Godot Android Plugin configuration file to help the system detect and load your plugin:
|
||||
|
||||
- The configuration file extension must be `gdap` (e.g.: `MyPlugin.gdap`).
|
||||
- The configuration file format is as follow::
|
||||
|
||||
- The configuration file format is as follow:
|
||||
|
||||
```
|
||||
[config]
|
||||
|
||||
name="MyPlugin"
|
||||
@ -98,6 +101,7 @@ The instructions below assumes that you're using Android Studio.
|
||||
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,11 +133,11 @@ The Godot editor will automatically parse all `.gdap` files in the `res://androi
|
||||
|
||||
From your script:
|
||||
|
||||
.. code::
|
||||
|
||||
```
|
||||
if Engine.has_singleton("MyPlugin"):
|
||||
var singleton = Engine.get_singleton("MyPlugin")
|
||||
print(singleton.myPluginFunction("World"))
|
||||
```
|
||||
|
||||
|
||||
Bundling GDNative resources
|
||||
|
@ -22,8 +22,7 @@ The default HTML page is available in the Godot Engine repository at
|
||||
`/misc/dist/html/full-size.html ( https://github.com/godotengine/godot/blob/master/misc/dist/html/full-size.html )`
|
||||
but the following template can be used as a much simpler example:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
```
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@ -39,6 +38,7 @@ but the following template can be used as a much simpler example:
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Setup
|
||||
-----
|
||||
@ -88,8 +88,7 @@ However, in the simplest case all you need to do is to create an instance of the
|
||||
class with the exported configuration, and then call the :js:meth:`engine.startGame <Engine.prototype.startGame )` method
|
||||
optionally overriding any :js:attr:`EngineConfig` parameters.
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
```
|
||||
const engine = new Engine($GODOT_CONFIG);
|
||||
engine.startGame({
|
||||
/* optional override configuration, eg. */
|
||||
@ -97,6 +96,7 @@ optionally overriding any :js:attr:`EngineConfig` parameters.
|
||||
// canvasResizePolicy: 0,
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
This snippet of code automatically loads and initializes the engine before starting the game.
|
||||
It uses the given configuration to to load the engine. The :js:meth:`engine.startGame <Engine.prototype.startGame )`
|
||||
@ -113,8 +113,7 @@ 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.
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
```
|
||||
const myWasm = 'mygame.wasm';
|
||||
const myPck = 'mygame.pck';
|
||||
const engine = new Engine();
|
||||
@ -129,6 +128,7 @@ This process is a bit more complex, but gives you full control over the engine s
|
||||
}).then(() => {
|
||||
console.log('Engine has started!');
|
||||
});
|
||||
```
|
||||
|
||||
To load the engine manually the :js:meth:`Engine.load` static method must be called. As
|
||||
this method is static, multiple engine instances can be spawned if the share the same `wasm`.
|
||||
@ -161,10 +161,10 @@ By default, the first canvas element on the page is used for rendering. To use a
|
||||
element the :js:attr:`canvas` override option can be used. It requires a reference to the DOM
|
||||
element itself.
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
```
|
||||
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`
|
||||
override option.
|
||||
@ -173,12 +173,12 @@ If your game takes some time to load, it may be useful to display a custom loadi
|
||||
the progress. This can be achieved with the :js:attr:`onProgress` callback option, which
|
||||
allows to set up a callback function that will be called regularly as the engine loads new bytes.
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
```
|
||||
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.
|
||||
|
||||
@ -197,8 +197,7 @@ behavior can be customized by setting your own functions to handle messages.
|
||||
Use the :js:attr:`onPrint` override option to set a callback function for the output stream,
|
||||
and the :js:attr:`onPrintError` override option to set a callback function for the error stream.
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
```
|
||||
function print(text) {
|
||||
console.log(text);
|
||||
}
|
||||
@ -206,6 +205,7 @@ and the :js:attr:`onPrintError` override option to set a callback function for t
|
||||
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
|
||||
finished product.
|
||||
|
@ -20,11 +20,13 @@ An iOS plugin requires a `.gdip` configuration file, a binary file which can be
|
||||
|
||||
![](img/ios_export_preset_plugins_section.png)
|
||||
|
||||
When a plugin is active, you can access it in your using `Engine.get_singleton()`::
|
||||
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())
|
||||
```
|
||||
|
||||
Creating an iOS plugin
|
||||
----------------------
|
||||
@ -59,24 +61,25 @@ 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 Godot. For instance:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
-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.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
6. Create a Godot iOS Plugin configuration file to help the system detect and load your plugin:
|
||||
|
||||
- The configuration file extension must be `gdip` (e.g.: `MyPlugin.gdip`).
|
||||
|
||||
- The configuration file format is as follow::
|
||||
- The configuration file format is as follow:
|
||||
|
||||
```
|
||||
[config]
|
||||
name="MyPlugin"
|
||||
binary="MyPlugin.a"
|
||||
@ -106,6 +109,7 @@ To build an iOS plugin:
|
||||
</array>
|
||||
"
|
||||
StringPlistKeyToInput:string_input="Type something"
|
||||
```
|
||||
|
||||
The `config` section and fields are required and defined as follow:
|
||||
|
||||
|
@ -19,8 +19,7 @@ returns a registered singleton.
|
||||
|
||||
Here's an example of how to do this in GDScript:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var in_app_store
|
||||
var game_center
|
||||
|
||||
@ -34,6 +33,7 @@ Here's an example of how to do this in GDScript:
|
||||
game_center = Engine.get_singleton("GameCenter")
|
||||
else:
|
||||
print("iOS Game Center plugin is not available on this platform.")
|
||||
```
|
||||
|
||||
|
||||
Asynchronous methods
|
||||
@ -42,9 +42,9 @@ Asynchronous methods
|
||||
When requesting an asynchronous operation, the method will look like
|
||||
this:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
Error purchase(Variant params);
|
||||
```
|
||||
|
||||
The parameter will usually be a Dictionary, with the information
|
||||
necessary to make the request, and the call will have two phases. First,
|
||||
@ -54,8 +54,7 @@ locally (no internet connection, API incorrectly configured, etc). If
|
||||
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:
|
||||
@ -72,6 +71,7 @@ the 'pending events' queue. Example:
|
||||
show_success(event.product_id)
|
||||
else:
|
||||
show_error()
|
||||
```
|
||||
|
||||
Remember that when a call returns OK, the API will *always* produce an
|
||||
event through the pending_event interface, even if it's an error, or a
|
||||
@ -97,20 +97,20 @@ 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)
|
||||
```
|
||||
|
||||
and the pending events interface:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
int get_pending_event_count()
|
||||
Variant pop_pending_event()
|
||||
```
|
||||
|
||||
`purchase`
|
||||
~~~~~~~~~~~~
|
||||
@ -125,9 +125,9 @@ Parameters
|
||||
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" })
|
||||
```
|
||||
|
||||
Response event
|
||||
^^^^^^^^^^^^^^
|
||||
@ -136,23 +136,23 @@ The response event will be a dictionary with the following fields:
|
||||
|
||||
On error:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
{
|
||||
"type": "purchase",
|
||||
"result": "error",
|
||||
"product_id": "the product ID requested",
|
||||
}
|
||||
```
|
||||
|
||||
On success:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
{
|
||||
"type": "purchase",
|
||||
"result": "ok",
|
||||
"product_id": "the product ID requested",
|
||||
}
|
||||
```
|
||||
|
||||
`request_product_info`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -165,17 +165,16 @@ Parameters
|
||||
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"] })
|
||||
```
|
||||
|
||||
Response event
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
The response event will be a dictionary with the following fields:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
{
|
||||
"type": "product_info",
|
||||
"result": "ok",
|
||||
@ -186,6 +185,7 @@ The response event will be a dictionary with the following fields:
|
||||
"prices": [ list of valid product prices ],
|
||||
"localized_prices": [ list of valid product localized prices ],
|
||||
}
|
||||
```
|
||||
|
||||
`restore_purchases`
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -198,13 +198,13 @@ Response event
|
||||
|
||||
The response events will be dictionaries with the following fields:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
{
|
||||
"type": "restore",
|
||||
"result": "ok",
|
||||
"product_id": "product ID of restored purchase",
|
||||
}
|
||||
```
|
||||
|
||||
`set_auto_finish_transaction`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -218,9 +218,9 @@ Parameters
|
||||
Takes a boolean as a parameter which specifies if purchases should be
|
||||
automatically finalized. Example:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
in_app_store.set_auto_finish_transaction(true)
|
||||
```
|
||||
|
||||
`finish_transaction`
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -235,9 +235,9 @@ Parameters
|
||||
Takes a string `product_id` as an argument. `product_id` specifies what product to
|
||||
finalize the purchase on. Example:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
in_app_store.finish_transaction("my_product1")
|
||||
```
|
||||
|
||||
Game Center
|
||||
-----------
|
||||
@ -247,8 +247,7 @@ Implemented in `Godot iOS GameCenter plugin ( https://github.com/godotengine/god
|
||||
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)
|
||||
@ -258,13 +257,14 @@ has the following methods:
|
||||
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()
|
||||
```
|
||||
|
||||
`authenticate`
|
||||
~~~~~~~~~~~~~~~~
|
||||
@ -278,24 +278,24 @@ 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,
|
||||
}
|
||||
```
|
||||
|
||||
On success:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
{
|
||||
"type": "authentication",
|
||||
"result": "ok",
|
||||
"player_id": the value from GKLocalPlayer::playerID,
|
||||
}
|
||||
```
|
||||
|
||||
`post_score`
|
||||
~~~~~~~~~~~~~~
|
||||
@ -312,9 +312,9 @@ Takes a dictionary as a parameter, with two fields:
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var result = game_center.post_score({ "score": 100, "category": "my_leaderboard", })
|
||||
```
|
||||
|
||||
Response event
|
||||
^^^^^^^^^^^^^^
|
||||
@ -323,23 +323,23 @@ 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,
|
||||
}
|
||||
```
|
||||
|
||||
On success:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
{
|
||||
"type": "post_score",
|
||||
"result": "ok",
|
||||
}
|
||||
```
|
||||
|
||||
`award_achievement`
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -359,9 +359,9 @@ Takes a Dictionary as a parameter, with 3 fields:
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var result = award_achievement({ "name": "hard_mode_completed", "progress": 6.1 })
|
||||
```
|
||||
|
||||
Response event
|
||||
^^^^^^^^^^^^^^
|
||||
@ -370,22 +370,22 @@ 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,
|
||||
}
|
||||
```
|
||||
|
||||
On success:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
{
|
||||
"type": "award_achievement",
|
||||
"result": "ok",
|
||||
}
|
||||
```
|
||||
|
||||
`reset_achievements`
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -399,22 +399,22 @@ 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,
|
||||
}
|
||||
```
|
||||
|
||||
On success:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
{
|
||||
"type": "reset_achievements",
|
||||
"result": "ok",
|
||||
}
|
||||
```
|
||||
|
||||
`request_achievements`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -429,24 +429,24 @@ The response event will be a dictionary with the following fields:
|
||||
|
||||
On error:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
{
|
||||
"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 ],
|
||||
}
|
||||
```
|
||||
|
||||
`request_achievement_descriptions`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -461,18 +461,17 @@ 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,
|
||||
}
|
||||
```
|
||||
|
||||
On success:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
{
|
||||
"type": "achievement_descriptions",
|
||||
"result": "ok",
|
||||
@ -484,6 +483,7 @@ On success:
|
||||
"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`
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
@ -506,10 +506,10 @@ 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" })
|
||||
```
|
||||
|
||||
Response event
|
||||
^^^^^^^^^^^^^^
|
||||
@ -518,12 +518,12 @@ The response event will be a dictionary with the following fields:
|
||||
|
||||
On close:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
{
|
||||
"type": "show_game_center",
|
||||
"result": "ok",
|
||||
}
|
||||
```
|
||||
|
||||
Multi-platform games
|
||||
--------------------
|
||||
@ -536,8 +536,7 @@ you need inside a conditional block, you need to also define them as
|
||||
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
|
||||
|
||||
func post_score(score):
|
||||
@ -555,3 +554,4 @@ of how to work around this in a class:
|
||||
if Globals.has_singleton("GameCenter"):
|
||||
GameCenter = Globals.get_singleton("GameCenter")
|
||||
# connect your timer here to the "check_events" function
|
||||
```
|
||||
|
@ -23,9 +23,10 @@ color, and the resulting color will be used as the albedo (main color) of the
|
||||
imported material. In this example it will contain the pure blue color
|
||||
(zero red, zero green, and full blue):
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
```
|
||||
0,0,255
|
||||
```
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
@ -33,8 +34,7 @@ Configuration
|
||||
First we need a generic plugin that will handle the initialization and
|
||||
destruction of our import plugin. Let's add the `plugin.cfg` file first:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
```
|
||||
[plugin]
|
||||
|
||||
name="Silly Material Importer"
|
||||
@ -42,12 +42,12 @@ destruction of our import plugin. Let's add the `plugin.cfg` file first:
|
||||
author="Yours Truly"
|
||||
version="1.0"
|
||||
script="material_import.gd"
|
||||
```
|
||||
|
||||
Then we need the `material_import.gd` file to add and remove the import plugin
|
||||
when needed:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# material_import.gd
|
||||
tool
|
||||
extends EditorPlugin
|
||||
@ -64,6 +64,7 @@ when needed:
|
||||
func _exit_tree():
|
||||
remove_import_plugin(import_plugin)
|
||||
import_plugin = null
|
||||
```
|
||||
|
||||
When this plugin is activated, it will create a new instance of the import
|
||||
plugin (which we'll soon make) and add it to the editor using the
|
||||
@ -88,8 +89,7 @@ with files.
|
||||
|
||||
Let's begin to code our plugin, one method at time:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# import_plugin.gd
|
||||
tool
|
||||
extends EditorImportPlugin
|
||||
@ -97,6 +97,7 @@ Let's begin to code our plugin, one method at time:
|
||||
|
||||
func get_importer_name():
|
||||
return "demos.sillymaterial"
|
||||
```
|
||||
|
||||
The first method is the
|
||||
`get_importer_name()( EditorImportPlugin_method_get_importer_name )`. This is a
|
||||
@ -104,10 +105,10 @@ unique name for your plugin that is used by Godot to know which import was used
|
||||
in a certain file. When the files needs to be reimported, the editor will know
|
||||
which plugin to call.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func get_visible_name():
|
||||
return "Silly Material"
|
||||
```
|
||||
|
||||
The `get_visible_name()( EditorImportPlugin_method_get_visible_name )` method is
|
||||
responsible for returning the name of the type it imports and it will be shown to the
|
||||
@ -117,10 +118,10 @@ You should choose this name as a continuation to "Import as", e.g. *"Import as
|
||||
Silly Material"*. You can name it whatever you want but we recommend a
|
||||
descriptive name for your plugin.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func get_recognized_extensions():
|
||||
return ["mtxt"]
|
||||
```
|
||||
|
||||
Godot's import system detects file types by their extension. In the
|
||||
`get_recognized_extensions()( EditorImportPlugin_method_get_recognized_extensions )`
|
||||
@ -134,10 +135,10 @@ Tip:
|
||||
for the game and should not be imported. You have to be careful when
|
||||
importing to validate the data. Never expect the file to be well-formed.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func get_save_extension():
|
||||
return "material"
|
||||
```
|
||||
|
||||
The imported files are saved in the `.import` folder at the project's root.
|
||||
Their extension should match the type of resource you are importing, but since
|
||||
@ -150,10 +151,10 @@ resource types. If you are importing a scene, you can use `scn`. Generic
|
||||
resources can use the `res` extension. However, this is not enforced in any
|
||||
way by the engine.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func get_resource_type():
|
||||
return "SpatialMaterial"
|
||||
```
|
||||
|
||||
The imported resource has a specific type, so the editor can know which property
|
||||
slot it belongs to. This allows drag and drop from the FileSystem dock to a
|
||||
@ -180,8 +181,7 @@ shows how the options will appear in the editor:
|
||||
Since there might be many presets and they are identified with a number, it's a
|
||||
good practice to use an enum so you can refer to them using names.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
tool
|
||||
extends EditorImportPlugin
|
||||
|
||||
@ -190,28 +190,29 @@ good practice to use an enum so you can refer to them using names.
|
||||
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Now that the enum is defined, let's keep looking at the methods of an import
|
||||
plugin:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func get_preset_count():
|
||||
return Presets.size()
|
||||
```
|
||||
|
||||
The `get_preset_count()` method
|
||||
returns the amount of presets that this plugins defines. We only have one preset
|
||||
now, but we can make this method future-proof by returning the size of our
|
||||
`Presets` enumeration.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func get_preset_name(preset):
|
||||
match preset:
|
||||
Presets.DEFAULT:
|
||||
return "Default"
|
||||
_:
|
||||
return "Unknown"
|
||||
```
|
||||
|
||||
|
||||
Here we have the
|
||||
@ -227,8 +228,7 @@ count you defined, it's always better to be on the safe side.
|
||||
If you have only one preset you could simply return its name directly, but if
|
||||
you do this you have to be careful when you add more presets.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func get_import_options(preset):
|
||||
match preset:
|
||||
Presets.DEFAULT:
|
||||
@ -238,6 +238,7 @@ you do this you have to be careful when you add more presets.
|
||||
}]
|
||||
_:
|
||||
return []
|
||||
```
|
||||
|
||||
This is the method which defines the available options.
|
||||
`get_import_options()` returns
|
||||
@ -273,10 +274,10 @@ Warning:
|
||||
have to return an array even it's empty, otherwise you can get
|
||||
errors.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func get_option_visibility(option, options):
|
||||
return true
|
||||
```
|
||||
|
||||
For the
|
||||
`get_option_visibility()`
|
||||
@ -293,8 +294,7 @@ The heavy part of the process, responsible for converting the files into
|
||||
resources, is covered by the `import()`
|
||||
method. Our sample code is a bit long, so let's split in a few parts:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func import(source_file, save_path, options, r_platform_variants, r_gen_files):
|
||||
var file = File.new()
|
||||
var err = file.open(source_file, File.READ)
|
||||
@ -304,6 +304,7 @@ method. Our sample code is a bit long, so let's split in a few parts:
|
||||
var line = file.get_line()
|
||||
|
||||
file.close()
|
||||
```
|
||||
|
||||
The first part of our import method opens and reads the source file. We use the
|
||||
`File` class to do that, passing the `source_file`
|
||||
@ -312,8 +313,7 @@ parameter which is provided by the editor.
|
||||
If there's an error when opening the file, we return it to let the editor know
|
||||
that the import wasn't successful.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var channels = line.split(",")
|
||||
if channels.size() != 3:
|
||||
return ERR_PARSE_ERROR
|
||||
@ -323,6 +323,7 @@ that the import wasn't successful.
|
||||
color = Color8(255, 0, 0)
|
||||
else:
|
||||
color = Color8(int(channels[0]), int(channels[1]), int(channels[2]))
|
||||
```
|
||||
|
||||
This code takes the line of the file it read before and splits it in pieces
|
||||
that are separated by a comma. If there are more or less than the three values,
|
||||
@ -332,18 +333,18 @@ Then it creates a new `Color` variable and sets its values
|
||||
according to the input file. If the `use_red_anyway` option is enabled, then
|
||||
it sets the color as a pure red instead.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var material = SpatialMaterial.new()
|
||||
material.albedo_color = color
|
||||
```
|
||||
|
||||
This part makes a new `SpatialMaterial` that is the
|
||||
imported resource. We create a new instance of it and then set its albedo color
|
||||
as the value we got before.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
return ResourceSaver.save("%s.%s" % [save_path, get_save_extension()], material)
|
||||
```
|
||||
|
||||
This is the last part and quite an important one, because here we save the made
|
||||
resource to the disk. The path of the saved file is generated and informed by
|
||||
@ -376,10 +377,10 @@ editor can know that you did.
|
||||
For example, let's say we save a different material for a mobile platform. We
|
||||
would need to do something like the following:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
r_platform_variants.push_back("mobile")
|
||||
return ResourceSaver.save("%s.%s.%s" % [save_path, "mobile", get_save_extension()], mobile_material)
|
||||
```
|
||||
|
||||
The `r_gen_files` argument is meant for extra files that are generated during
|
||||
your import process and need to be kept. The editor will look at it to
|
||||
@ -390,8 +391,7 @@ This is also an array and should be filled with full paths of the files you
|
||||
save. As an example, let's create another material for the next pass and save it
|
||||
in a different file:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var next_pass = SpatialMaterial.new()
|
||||
next_pass.albedo_color = color.inverted()
|
||||
var next_pass_path = "%s.next_pass.%s" % [save_path, get_save_extension()]
|
||||
@ -400,6 +400,7 @@ in a different file:
|
||||
if err != OK:
|
||||
return err
|
||||
r_gen_files.push_back(next_pass_path)
|
||||
```
|
||||
|
||||
Trying the plugin
|
||||
-----------------
|
||||
|
@ -26,8 +26,7 @@ The plugin script will come with `enter_tree()` and `exit_tree()`
|
||||
methods, but for a main screen plugin we need to add a few extra methods.
|
||||
Add five extra methods such that the script looks like this:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
tool
|
||||
extends EditorPlugin
|
||||
|
||||
@ -54,6 +53,7 @@ Add five extra methods such that the script looks like this:
|
||||
|
||||
func get_plugin_icon():
|
||||
return get_editor_interface().get_base_control().get_icon("Node", "EditorIcons")
|
||||
```
|
||||
|
||||
The important part in this script is the `has_main_screen()` function,
|
||||
which is overloaded so it returns `true`. This function is automatically
|
||||
@ -75,14 +75,14 @@ Next, let's add a button to our example main screen plugin.
|
||||
Add a `Button` node, and set the text to "Print Hello" or similar.
|
||||
Add a script to the button like this:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
tool
|
||||
extends Button
|
||||
|
||||
|
||||
func _on_PrintHello_pressed():
|
||||
print("Hello from the main screen plugin!")
|
||||
```
|
||||
|
||||
Then connect the "pressed" signal to itself. If you need help with signals,
|
||||
see the `doc_signals` article.
|
||||
@ -96,8 +96,7 @@ We need to update the `main_screen_plugin.gd` script so the plugin
|
||||
instances our main panel scene and places it where it needs to be.
|
||||
Here is the full plugin script:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
tool
|
||||
extends EditorPlugin
|
||||
|
||||
@ -136,6 +135,7 @@ Here is the full plugin script:
|
||||
func get_plugin_icon():
|
||||
# Must return some kind of Texture for the icon.
|
||||
return get_editor_interface().get_base_control().get_icon("Node", "EditorIcons")
|
||||
```
|
||||
|
||||
A couple of specific lines were added. `MainPanel` is a constant that holds
|
||||
a reference to the scene, and we instance it into `main_panel_instance`.
|
||||
|
@ -330,8 +330,7 @@ an autoload.
|
||||
|
||||
Use the following code to register a singleton from an editor plugin:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
tool
|
||||
extends EditorPlugin
|
||||
|
||||
@ -346,3 +345,4 @@ Use the following code to register a singleton from an editor plugin:
|
||||
|
||||
func _exit_tree():
|
||||
remove_autoload_singleton(AUTOLOAD_NAME)
|
||||
```
|
||||
|
@ -27,18 +27,17 @@ the gizmo can be hidden or not.
|
||||
|
||||
This would be a basic setup:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# MyCustomGizmoPlugin.gd
|
||||
extends EditorSpatialGizmoPlugin
|
||||
|
||||
|
||||
func get_name():
|
||||
return "CustomNode"
|
||||
```
|
||||
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# MyCustomEditorPlugin.gd
|
||||
tool
|
||||
extends EditorPlugin
|
||||
@ -55,6 +54,7 @@ This would be a basic setup:
|
||||
|
||||
func _exit_tree():
|
||||
remove_spatial_gizmo_plugin(gizmo_plugin)
|
||||
```
|
||||
|
||||
|
||||
For simple gizmos, just inheriting `EditorSpatialGizmoPlugin`
|
||||
@ -68,8 +68,7 @@ Simple approach
|
||||
The first step is to, in our custom gizmo plugin, override the `has_gizmo()( EditorSpatialGizmoPlugin_method_has_gizmo )`
|
||||
method so that it returns `true` when the spatial parameter is of our target type.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# ...
|
||||
|
||||
|
||||
@ -78,12 +77,12 @@ method so that it returns `true` when the spatial parameter is of our target typ
|
||||
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
Then we can override methods like `redraw()( EditorSpatialGizmoPlugin_method_redraw )`
|
||||
or all the handle related ones.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# ...
|
||||
|
||||
|
||||
@ -112,6 +111,7 @@ or all the handle related ones.
|
||||
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
Note that we created a material in the `init` method, and retrieved it in the `redraw`
|
||||
method using `get_material()( EditorSpatialGizmoPlugin_method_get_material )`. This
|
||||
@ -120,8 +120,7 @@ method retrieves one of the material's variants depending on the state of the gi
|
||||
|
||||
So the final plugin would look somewhat like this:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
extends EditorSpatialGizmoPlugin
|
||||
|
||||
|
||||
@ -158,6 +157,7 @@ So the final plugin would look somewhat like this:
|
||||
|
||||
# You should implement the rest of handle-related callbacks
|
||||
# (get_handle_name(), get_handle_value(), commit_handle()...).
|
||||
```
|
||||
|
||||
Note that we just added some handles in the redraw method, but we still need to implement
|
||||
the rest of handle-related callbacks in `EditorSpatialGizmoPlugin`
|
||||
@ -174,8 +174,7 @@ In these cases all we need to do is, in our new gizmo plugin, override
|
||||
`create_gizmo()( EditorSpatialGizmoPlugin_method_create_gizmo )`, so it returns our custom gizmo implementation
|
||||
for the Spatial nodes we want to target.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# MyCustomGizmoPlugin.gd
|
||||
extends EditorSpatialGizmoPlugin
|
||||
|
||||
@ -194,12 +193,12 @@ for the Spatial nodes we want to target.
|
||||
return MyCustomGizmo.new()
|
||||
else:
|
||||
return null
|
||||
```
|
||||
|
||||
This way all the gizmo logic and drawing methods can be implemented in a new class extending
|
||||
`EditorSpatialGizmo( EditorSpatialGizmo )`, like so:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# MyCustomGizmo.gd
|
||||
extends EditorSpatialGizmo
|
||||
|
||||
@ -232,6 +231,7 @@ This way all the gizmo logic and drawing methods can be implemented in a new cla
|
||||
|
||||
# You should implement the rest of handle-related callbacks
|
||||
# (get_handle_name(), get_handle_value(), commit_handle()...).
|
||||
```
|
||||
|
||||
Note that we just added some handles in the redraw method, but we still need to implement
|
||||
the rest of handle-related callbacks in `EditorSpatialGizmo( EditorSpatialGizmo )`
|
||||
|
@ -30,8 +30,7 @@ Don't forget to change its mode to "CanvasItem" (if you are using a Sprite):
|
||||
Create a script which derives from `VisualShaderNodeCustom`. This is
|
||||
all you need to initialize your plugin.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# PerlinNoise3D.gd
|
||||
tool
|
||||
extends VisualShaderNodeCustom
|
||||
@ -189,6 +188,7 @@ all you need to initialize your plugin.
|
||||
|
||||
func _get_code(input_vars, output_vars, mode, type):
|
||||
return output_vars[0] + " = cnoise(vec3((%s.xy + %s.xy) * %s, %s)) * 0.5 + 0.5;" % [input_vars[0], input_vars[1], input_vars[2], input_vars[3]]
|
||||
```
|
||||
|
||||
Save it and open the Visual Shader. You should see your new node type within the member's dialog (if you can't see your new node, try restarting the editor):
|
||||
|
||||
|
@ -399,9 +399,11 @@ If the game has a very high base resolution (e.g. 3840×2160), aliasing might
|
||||
appear when downsampling to something considerably lower like 1280×720.
|
||||
Aliasing can be made less visible by shrinking all images by a factor of 2
|
||||
upon loading. This can be done by calling the method below before
|
||||
the game data is loaded::
|
||||
the game data is loaded:
|
||||
|
||||
```
|
||||
VisualServer.texture_set_shrink_all_x2_on_set_data(true)
|
||||
```
|
||||
|
||||
Alternatively, you can also enable mipmaps on all your 2D textures. However,
|
||||
enabling mipmaps will increase memory usage which may be problematic on low-end
|
||||
|
@ -76,9 +76,9 @@ There can only be one active camera per `Viewport`, so if there is more
|
||||
than one, make sure that the desired one has the "current" property set,
|
||||
or make it the current camera by calling:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
camera.make_current()
|
||||
```
|
||||
|
||||
By default, cameras will render all objects in their world. In 3D, cameras can use their
|
||||
`cull_mask` property combined with the
|
||||
@ -95,10 +95,10 @@ these values are overridden, but for all others, this sets their resolution.
|
||||
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.
|
||||
```
|
||||
|
||||
The root `Viewport` uses this for the stretch options in the project
|
||||
settings. For more information on scaling and stretching visit the `Multiple Resolutions Tutorial ( doc_multiple_resolutions )`
|
||||
@ -136,8 +136,7 @@ It is possible to query a capture of the `Viewport` contents. For the root
|
||||
`Viewport`, this is effectively a screen capture. This is done with the
|
||||
following code:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# Retrieve the captured Image using get_data().
|
||||
var img = get_viewport().get_texture().get_data()
|
||||
# Flip on the Y axis.
|
||||
@ -148,16 +147,17 @@ following code:
|
||||
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,
|
||||
you will get an empty texture because there is nothing to get as texture. You can deal with
|
||||
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.
|
||||
```
|
||||
|
||||
Viewport Container
|
||||
------------------
|
||||
@ -224,11 +224,11 @@ When rendering to a `Viewport`, whatever is inside will not be
|
||||
visible in the scene editor. To display the contents, you have to draw the `Viewport's` somewhere.
|
||||
This can be requested via code using (for example):
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# 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"
|
||||
|
||||
|
@ -46,11 +46,11 @@ Using C# from GDScript doesn't need much work. Once loaded
|
||||
(see `doc_gdscript_classes_as_resources`), the script can be instantiated
|
||||
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
|
||||
```
|
||||
|
||||
Warning:
|
||||
|
||||
@ -71,10 +71,10 @@ Instantiating GDScript nodes from C#
|
||||
From the C# side, everything work the same way. Once loaded, the GDScript can
|
||||
be instantiated with `GDScript.New()`.
|
||||
|
||||
.. code-block:: csharp
|
||||
|
||||
```
|
||||
GDScript MyGDScript = (GDScript) GD.Load("res://path_to_gd_file.gd");
|
||||
Object myGDScriptNode = (Godot.Object) MyGDScript.New(); // This is a Godot.Object
|
||||
```
|
||||
|
||||
Here we are using an `Object`, but you can use type conversion like
|
||||
explained in `doc_c_sharp_features_type_conversion_and_casting`.
|
||||
@ -88,14 +88,14 @@ Accessing C# fields from GDScript
|
||||
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.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
|
||||
attribute. However, trying to set a value on a property that does not define
|
||||
@ -108,14 +108,14 @@ As C# is statically typed, accessing GDScript from C# is a bit more
|
||||
convoluted, you will have to use `Object.Get()`
|
||||
and `Object.Set()`. The first argument is the name of the field you want to access.
|
||||
|
||||
.. code-block:: csharp
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
Keep in mind that when setting a field value you should only use types the
|
||||
GDScript side knows about.
|
||||
@ -132,8 +132,7 @@ marshalling process will do its best to cast the arguments to match
|
||||
function signatures.
|
||||
If that's impossible, you'll see the following error: `Invalid call. Nonexistent function `FunctionName```.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
my_csharp_node.PrintNodeName(self) # myGDScriptNode
|
||||
# my_csharp_node.PrintNodeName() # This line will fail.
|
||||
|
||||
@ -141,6 +140,7 @@ If that's impossible, you'll see the following error: `Invalid call. Nonexistent
|
||||
|
||||
my_csharp_node.PrintArray(["a", "b", "c"]) # a, b, c
|
||||
my_csharp_node.PrintArray([1, 2, 3]) # 1, 2, 3
|
||||
```
|
||||
|
||||
Calling GDScript methods from C#
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -150,8 +150,7 @@ To call GDScript methods from C# you'll need to use
|
||||
name of the method you want to call. The following arguments will be passed
|
||||
to said method.
|
||||
|
||||
.. code-block:: csharp
|
||||
|
||||
```
|
||||
myGDScriptNode.Call("print_node_name", this); // my_csharp_node
|
||||
// myGDScriptNode.Call("print_node_name"); // This line will fail silently and won't error out.
|
||||
|
||||
@ -164,6 +163,7 @@ to said method.
|
||||
myGDScriptNode.Call("print_array", (object)arr); // a, b, c
|
||||
myGDScriptNode.Call("print_array", (object)new int[] { 1, 2, 3 }); // 1, 2, 3
|
||||
// Note how the type of each array entry does not matter as long as it can be handled by the marshaller
|
||||
```
|
||||
|
||||
Warning:
|
||||
|
||||
|
@ -22,12 +22,14 @@ Note:
|
||||
Basic usage
|
||||
-----------
|
||||
|
||||
To evaluate a mathematical expression, use::
|
||||
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
|
||||
```
|
||||
|
||||
The following operators are available:
|
||||
|
||||
@ -55,8 +57,9 @@ applies. Use parentheses to override the order of operations if needed.
|
||||
All the Variant types supported in Godot can be used: integers, floating-point
|
||||
numbers, strings, arrays, dictionaries, colors, vectors, …
|
||||
|
||||
Arrays and dictionaries can be indexed like in GDScript::
|
||||
Arrays and dictionaries can be indexed like in GDScript:
|
||||
|
||||
```
|
||||
# Returns 1.
|
||||
[1, 2][0]
|
||||
|
||||
@ -70,14 +73,16 @@ Arrays and dictionaries can be indexed like in GDScript::
|
||||
Vector3(5, 6, 7)[2]
|
||||
Vector3(5, 6, 7)["z"]
|
||||
Vector3(5, 6, 7).z
|
||||
```
|
||||
|
||||
Passing variables to an expression
|
||||
----------------------------------
|
||||
|
||||
You can pass variables to an expression. These variables will then
|
||||
become available in the expression's "context" and will be substituted when used
|
||||
in the expression::
|
||||
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.
|
||||
@ -86,6 +91,7 @@ in the expression::
|
||||
# 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,
|
||||
even if you only define one variable. Also, variable names are **case-sensitive**.
|
||||
@ -98,8 +104,9 @@ expression has no base instance associated to it.
|
||||
|
||||
When calling `Expression.execute()`,
|
||||
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::
|
||||
instance such as `self`, another script instance or even a singleton:
|
||||
|
||||
```
|
||||
func double(number):
|
||||
return number * 2
|
||||
|
||||
@ -116,6 +123,7 @@ instance such as `self`, another script instance or even a singleton::
|
||||
# as the base instance.
|
||||
result = expression.execute([], self)
|
||||
print(result) # 20
|
||||
```
|
||||
|
||||
Associating a base instance allows doing the following:
|
||||
|
||||
@ -135,8 +143,9 @@ Warning:
|
||||
Example script
|
||||
--------------
|
||||
|
||||
The script below demonstrates what the Expression class is capable of::
|
||||
The script below demonstrates what the Expression class is capable of:
|
||||
|
||||
```
|
||||
const DAYS_IN_YEAR = 365
|
||||
var script_member_variable = 1000
|
||||
|
||||
@ -179,9 +188,11 @@ The script below demonstrates what the Expression class is capable of::
|
||||
|
||||
# The method's return value is also the expression's return value.
|
||||
return 0
|
||||
```
|
||||
|
||||
The output from the script will be::
|
||||
The output from the script will be:
|
||||
|
||||
```
|
||||
4
|
||||
160
|
||||
1.570796
|
||||
@ -196,6 +207,7 @@ The output from the script will be::
|
||||
You called 'call_me()' in the expression text.
|
||||
Argument passed: some string
|
||||
0
|
||||
```
|
||||
|
||||
Built-in functions
|
||||
------------------
|
||||
|
@ -27,13 +27,14 @@ with SVN, Git, Mercurial, Perforce, etc.
|
||||
|
||||
Example of file system contents:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
```
|
||||
/project.godot
|
||||
/enemy/enemy.tscn
|
||||
/enemy/enemy.gd
|
||||
/enemy/enemysprite.png)
|
||||
/player/player.gd
|
||||
```
|
||||
|
||||
project.godot
|
||||
-------------
|
||||
|
@ -57,19 +57,19 @@ assignment. Example:
|
||||
|
||||
Static:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
```
|
||||
int a; // Value uninitialized.
|
||||
a = 5; // This is valid.
|
||||
a = "Hi!"; // This is invalid.
|
||||
```
|
||||
|
||||
Dynamic:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var a # 'null' by default.
|
||||
a = 5 # Valid, 'a' becomes an integer.
|
||||
a = "Hi!" # Valid, 'a' changed to a string.
|
||||
```
|
||||
|
||||
As function arguments:
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -79,8 +79,7 @@ different arguments, for example:
|
||||
|
||||
Static:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
```
|
||||
void print_value(int value) {
|
||||
|
||||
printf("value is %i\n", value);
|
||||
@ -90,11 +89,11 @@ Static:
|
||||
|
||||
print_value(55); // Valid.
|
||||
print_value("Hello"); // Invalid.
|
||||
```
|
||||
|
||||
Dynamic:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func print_value(value):
|
||||
print(value)
|
||||
|
||||
@ -102,6 +101,7 @@ Dynamic:
|
||||
|
||||
print_value(55) # Valid.
|
||||
print_value("Hello") # Valid.
|
||||
```
|
||||
|
||||
Pointers & referencing:
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -119,8 +119,7 @@ too. Some Examples:
|
||||
|
||||
- C++:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
```
|
||||
void use_class(SomeClass *instance) {
|
||||
|
||||
instance->use();
|
||||
@ -132,11 +131,11 @@ too. Some Examples:
|
||||
use_class(instance); // Passed as pointer.
|
||||
delete instance; // Otherwise it will leak memory.
|
||||
}
|
||||
```
|
||||
|
||||
- Java:
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
```
|
||||
@Override
|
||||
public final void use_class(SomeClass instance) {
|
||||
|
||||
@ -150,11 +149,11 @@ too. Some Examples:
|
||||
// Garbage collector will get rid of it when not in
|
||||
// use and freeze your game randomly for a second.
|
||||
}
|
||||
```
|
||||
|
||||
- GDScript:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func use_class(instance): # Does not care about class type
|
||||
instance.use() # Will work with any class that has a ".use()" method.
|
||||
|
||||
@ -162,6 +161,7 @@ too. Some Examples:
|
||||
var instance = SomeClass.new() # Created as reference.
|
||||
use_class(instance) # Passed as reference.
|
||||
# Will be unreferenced and deleted.
|
||||
```
|
||||
|
||||
In GDScript, only base types (int, float, String and PoolArray types)
|
||||
are passed by value to functions (value is copied). Everything else
|
||||
@ -175,8 +175,9 @@ Note:
|
||||
|
||||
A value is **passed by value** when it is copied every time it's specified
|
||||
as a function parameter. One consequence of this is that the function cannot
|
||||
modify the parameter in a way that is visible from outside the function::
|
||||
|
||||
modify the parameter in a way that is visible from outside the function:
|
||||
|
||||
```
|
||||
func greet(text):
|
||||
text = "Hello " + text
|
||||
|
||||
@ -189,14 +190,16 @@ Note:
|
||||
greet(example)
|
||||
|
||||
print(example) # Godot
|
||||
```
|
||||
|
||||
A value is **passed by reference** when it is *not* copied every time it's
|
||||
specified as a function parameter. This allows modifying a function
|
||||
parameter within a function body (and having the modified value accessible
|
||||
outside the function). The downside is that the data passed as a function
|
||||
parameter is no longer guaranteed to be immutable, which can cause
|
||||
difficult-to-track bugs if not done carefully::
|
||||
difficult-to-track bugs if not done carefully:
|
||||
|
||||
```
|
||||
func greet(text):
|
||||
text.push_front("Hello")
|
||||
|
||||
@ -210,6 +213,7 @@ Note:
|
||||
greet(example)
|
||||
|
||||
print(example) # [Hello, Godot] (Array with 2 String elements)
|
||||
```
|
||||
|
||||
Compared to passing by value, passing by reference can perform better when
|
||||
using large objects since copying large objects in memory can be slow.
|
||||
@ -225,8 +229,7 @@ Arrays in dynamically typed languages can contain many different mixed
|
||||
datatypes inside and are always dynamic (can be resized at any time).
|
||||
Compare for example arrays in statically typed languages:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
```
|
||||
int *array = new int[4]; // Create array.
|
||||
array[0] = 10; // Initialize manually.
|
||||
array[1] = 20; // Can't mix types.
|
||||
@ -247,33 +250,34 @@ Compare for example arrays in statically typed languages:
|
||||
array.resize(3); // Can be resized.
|
||||
use_array(array); // Passed reference or value.
|
||||
// Freed when stack ends.
|
||||
```
|
||||
|
||||
And in GDScript:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var array = [10, "hello", 40, 60] # Simple, and can mix types.
|
||||
array.resize(3) # Can be resized.
|
||||
use_array(array) # Passed as reference.
|
||||
# Freed when no longer in use.
|
||||
```
|
||||
|
||||
In dynamically typed languages, arrays can also double as other
|
||||
datatypes, such as lists:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var array = []
|
||||
array.append(4)
|
||||
array.append(5)
|
||||
array.pop_front()
|
||||
```
|
||||
|
||||
Or unordered sets:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var a = 20
|
||||
if a in [10, 20, 30]:
|
||||
print("We have a winner!")
|
||||
```
|
||||
|
||||
Dictionaries
|
||||
------------
|
||||
@ -292,25 +296,24 @@ will go as far as implementing arrays as dictionaries.
|
||||
|
||||
Example of Dictionary:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var d = {"name": "John", "age": 22} # Simple syntax.
|
||||
print("Name: ", d["name"], " Age: ", d["age"])
|
||||
```
|
||||
|
||||
Dictionaries are also dynamic, keys can be added or removed at any point
|
||||
at little cost:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
d["mother"] = "Rebecca" # Addition.
|
||||
d["age"] = 11 # Modification.
|
||||
d.erase("name") # Removal.
|
||||
```
|
||||
|
||||
In most cases, two-dimensional arrays can often be implemented more
|
||||
easily with dictionaries. Here's a simple battleship game example:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# Battleship Game
|
||||
|
||||
const SHIP = 0
|
||||
@ -338,14 +341,14 @@ easily with dictionaries. Here's a simple battleship game example:
|
||||
missile(Vector2(1, 1))
|
||||
missile(Vector2(5, 8))
|
||||
missile(Vector2(2, 3))
|
||||
```
|
||||
|
||||
Dictionaries can also be used as data markup or quick structures. While
|
||||
GDScript's dictionaries resemble python dictionaries, it also supports Lua
|
||||
style syntax and indexing, which makes it useful for writing initial
|
||||
states and quick structs:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# Same example, lua-style support.
|
||||
# This syntax is a lot more readable and usable.
|
||||
# Like any GDScript identifier, keys written in this form cannot start
|
||||
@ -362,14 +365,14 @@ states and quick structs:
|
||||
|
||||
d["mother"] = "Rebecca"
|
||||
d.mother = "Caroline" # This would work too to create a new key.
|
||||
```
|
||||
|
||||
For & while
|
||||
-----------
|
||||
|
||||
Iterating in some statically typed languages can be quite complex:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
```
|
||||
const char* strings = new const char*[50];
|
||||
|
||||
[..]
|
||||
@ -385,51 +388,51 @@ Iterating in some statically typed languages can be quite complex:
|
||||
|
||||
std::cout << *it << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
This is usually greatly simplified in dynamically typed languages:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
for s in strings:
|
||||
print(s)
|
||||
```
|
||||
|
||||
Container datatypes (arrays and dictionaries) are iterable. Dictionaries
|
||||
allow iterating the keys:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
for key in dict:
|
||||
print(key, " -> ", dict[key])
|
||||
```
|
||||
|
||||
Iterating with indices is also possible:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
for i in range(strings.size()):
|
||||
print(strings[i])
|
||||
```
|
||||
|
||||
The range() function can take 3 arguments:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
range(n) # Will go from 0 to n-1.
|
||||
range(b, n) # Will go from b to n-1.
|
||||
range(b, n, s) # Will go from b to n-1, in steps of s.
|
||||
```
|
||||
|
||||
Some statically typed programming language examples:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
```
|
||||
for (int i = 0; i < 10; i++) {}
|
||||
|
||||
for (int i = 5; i < 10; i++) {}
|
||||
|
||||
for (int i = 5; i < 10; i += 2) {}
|
||||
```
|
||||
|
||||
Translate to:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
for i in range(10):
|
||||
pass
|
||||
|
||||
@ -438,32 +441,33 @@ Translate to:
|
||||
|
||||
for i in range(5, 10, 2):
|
||||
pass
|
||||
```
|
||||
|
||||
And backwards looping is done through a negative counter:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
for (int i = 10; i > 0; i--) {}
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
for i in range(10, 0, -1):
|
||||
pass
|
||||
```
|
||||
|
||||
While
|
||||
-----
|
||||
|
||||
while() loops are the same everywhere:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var i = 0
|
||||
|
||||
while i < strings.size():
|
||||
print(strings[i])
|
||||
i += 1
|
||||
```
|
||||
|
||||
Custom iterators
|
||||
----------------
|
||||
@ -471,8 +475,7 @@ You can create custom iterators in case the default ones don't quite meet your
|
||||
needs by overriding the Variant class's `iter_init`, `iter_next`, and `iter_get`
|
||||
functions in your script. An example implementation of a forward iterator follows:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
class ForwardIterator:
|
||||
var start
|
||||
var current
|
||||
@ -498,14 +501,15 @@ functions in your script. An example implementation of a forward iterator follow
|
||||
|
||||
func _iter_get(arg):
|
||||
return current
|
||||
```
|
||||
|
||||
And it can be used like any other iterator:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var itr = ForwardIterator.new(0, 6, 2)
|
||||
for i in itr:
|
||||
print(i) # Will print 0, 2, and 4.
|
||||
```
|
||||
|
||||
Make sure to reset the state of the iterator in `iter_init`, otherwise nested
|
||||
for-loops that use custom iterators will not work as expected.
|
||||
@ -522,12 +526,12 @@ As an example, imagine a situation where a big rock is falling down a
|
||||
tunnel, smashing everything on its way. The code for the rock, in a
|
||||
statically typed language would be something like:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
```
|
||||
void BigRollingRock::on_object_hit(Smashable *entity) {
|
||||
|
||||
entity->smash();
|
||||
}
|
||||
```
|
||||
|
||||
This way, everything that can be smashed by a rock would have to
|
||||
inherit Smashable. If a character, enemy, piece of furniture, small rock
|
||||
@ -541,10 +545,10 @@ With dynamically typed languages, this is not a problem. Duck typing
|
||||
makes sure you only have to define a `smash()` function where required
|
||||
and that's it. No need to consider inheritance, base classes, etc.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func _on_object_hit(object):
|
||||
object.smash()
|
||||
```
|
||||
|
||||
And that's it. If the object that hit the big rock has a smash() method,
|
||||
it will be called. No need for inheritance or polymorphism. Dynamically
|
||||
@ -566,11 +570,11 @@ Some dynamically typed languages simply ignore a method call when it
|
||||
doesn't exist, but GDScript is stricter, so checking if the function
|
||||
exists is desirable:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func _on_object_hit(object):
|
||||
if object.has_method("smash"):
|
||||
object.smash()
|
||||
```
|
||||
|
||||
Then, simply define that method and anything the rock touches can be
|
||||
smashed.
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -9,11 +9,13 @@ Introduction to exports
|
||||
In Godot, class members can be exported. This means their value gets saved along
|
||||
with the resource (such as the `scene`) they're
|
||||
attached to. They will also be available for editing in the property editor.
|
||||
Exporting is done by using the `export` keyword::
|
||||
Exporting is done by using the `export` keyword:
|
||||
|
||||
```
|
||||
extends Button
|
||||
|
||||
export var number = 5 # Value will be saved and visible in the property editor.
|
||||
```
|
||||
|
||||
An exported variable must be initialized to a constant expression or have an
|
||||
export hint in the form of an argument to the `export` keyword (see the
|
||||
@ -36,8 +38,7 @@ Note:
|
||||
Examples
|
||||
--------
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# If the exported value assigns a constant or constant expression,
|
||||
# the type will be inferred and used in the editor.
|
||||
|
||||
@ -136,6 +137,7 @@ Examples
|
||||
export(AnimationNode) var resource
|
||||
# The drop-down menu will be limited to AnimationNode and all
|
||||
# its inherited classes.
|
||||
```
|
||||
|
||||
It must be noted that even if the script is not being run while in the
|
||||
editor, the exported properties are still editable. This can be used
|
||||
@ -146,22 +148,26 @@ Exporting bit flags
|
||||
|
||||
Integers used as bit flags can store multiple `true`/`false` (boolean)
|
||||
values in one property. By using the export hint `int, FLAGS, ...`, they
|
||||
can be set from the editor::
|
||||
can be set from the editor:
|
||||
|
||||
```
|
||||
# Set any of the given flags from the editor.
|
||||
export(int, FLAGS, "Fire", "Water", "Earth", "Wind") var spell_elements = 0
|
||||
```
|
||||
|
||||
You must provide a string description for each flag. In this example, `Fire`
|
||||
has value 1, `Water` has value 2, `Earth` has value 4 and `Wind`
|
||||
corresponds to value 8. Usually, constants should be defined accordingly (e.g.
|
||||
`const ELEMENT_WIND = 8` and so on).
|
||||
|
||||
Export hints are also provided for the physics and render layers defined in the project settings::
|
||||
Export hints are also provided for the physics and render layers defined in the project settings:
|
||||
|
||||
```
|
||||
export(int, LAYERS_2D_PHYSICS) var layers_2d_physics
|
||||
export(int, LAYERS_2D_RENDER) var layers_2d_render
|
||||
export(int, LAYERS_3D_PHYSICS) var layers_3d_physics
|
||||
export(int, LAYERS_3D_RENDER) var layers_3d_render
|
||||
```
|
||||
|
||||
Using bit flags requires some understanding of bitwise operations.
|
||||
If in doubt, use boolean variables instead.
|
||||
@ -175,8 +181,7 @@ If the exported array specifies a type which inherits from Resource, the array
|
||||
values can be set in the inspector by dragging and dropping multiple files
|
||||
from the FileSystem dock at once.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# Default value must be a constant expression.
|
||||
|
||||
export var a = [1, 2, 3]
|
||||
@ -207,6 +212,7 @@ from the FileSystem dock at once.
|
||||
# be exported.
|
||||
|
||||
var c = [a, 2, 3]
|
||||
```
|
||||
|
||||
Setting exported variables from a tool script
|
||||
---------------------------------------------
|
||||
@ -245,8 +251,7 @@ Properties
|
||||
To understand how to better use the sections below, you should understand
|
||||
how to make properties with advanced exports.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func _get_property_list():
|
||||
var properties = []
|
||||
# Same as "export(int) var my_property"
|
||||
@ -255,6 +260,7 @@ how to make properties with advanced exports.
|
||||
type = TYPE_INT
|
||||
})
|
||||
return properties
|
||||
```
|
||||
|
||||
* The `get_property_list()` function gets called by the inspector. You
|
||||
can override it for more advanced exports. You must return an `Array`
|
||||
@ -276,8 +282,7 @@ property or else you may need to override the
|
||||
`set()` and
|
||||
`get()` methods. Attaching
|
||||
a variable to to a property also gives you the ability to give it a default state.
|
||||
::
|
||||
|
||||
```
|
||||
|
||||
# This variable is determined by the function below.
|
||||
# This variable acts just like a regular gdscript export.
|
||||
@ -291,6 +296,7 @@ a variable to to a property also gives you the ability to give it a default stat
|
||||
type = TYPE_INT
|
||||
})
|
||||
return properties
|
||||
```
|
||||
|
||||
Adding default values for properties
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -301,8 +307,7 @@ To define default values for advanced exports, you need to override the `propert
|
||||
|
||||
* The `property_get_revert()` method takes the name of a property and must return the default value for that property.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func _get_property_list():
|
||||
var properties = []
|
||||
properties.append({
|
||||
@ -319,6 +324,7 @@ To define default values for advanced exports, you need to override the `propert
|
||||
func property_get_revert(property):
|
||||
if property == "my_property":
|
||||
return 5
|
||||
```
|
||||
|
||||
Adding script categories
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -326,8 +332,7 @@ Adding script categories
|
||||
For better visual distinguishing of properties, a special script category can be
|
||||
embedded into the inspector to act as a separator. `Script Variables` is one
|
||||
example of a built-in category.
|
||||
::
|
||||
|
||||
```
|
||||
func _get_property_list():
|
||||
var properties = []
|
||||
properties.append({
|
||||
@ -342,6 +347,7 @@ example of a built-in category.
|
||||
type = TYPE_BOOL
|
||||
})
|
||||
return properties
|
||||
```
|
||||
|
||||
* `name` is the name of a category to be added to the inspector;
|
||||
|
||||
@ -356,8 +362,7 @@ Grouping properties
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A list of properties with similar names can be grouped.
|
||||
::
|
||||
|
||||
```
|
||||
func _get_property_list():
|
||||
var properties = []
|
||||
properties.append({
|
||||
@ -380,6 +385,7 @@ A list of properties with similar names can be grouped.
|
||||
type = TYPE_COLOR
|
||||
})
|
||||
return properties
|
||||
```
|
||||
|
||||
* `name` is the name of a group which is going to be displayed as collapsible
|
||||
list of properties;
|
||||
|
@ -21,8 +21,7 @@ Usage in GDScript
|
||||
|
||||
Examine this concrete GDScript example:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# Define a format string with placeholder '%s'
|
||||
var format_string = "We're waiting for %s."
|
||||
|
||||
@ -31,6 +30,7 @@ Examine this concrete GDScript example:
|
||||
|
||||
print(actual_string)
|
||||
# Output: "We're waiting for Godot."
|
||||
```
|
||||
|
||||
Placeholders always start with a `%`, but the next character or characters,
|
||||
the *format specifier*, determines how the given value is converted to a
|
||||
@ -52,8 +52,7 @@ matters when the index or mixed style of Array is used.
|
||||
|
||||
A quick example in GDScript:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# Define a format string
|
||||
var format_string = "We're waiting for {str}"
|
||||
|
||||
@ -62,6 +61,7 @@ A quick example in GDScript:
|
||||
|
||||
print(actual_string)
|
||||
# Output: "We're waiting for Godot"
|
||||
```
|
||||
|
||||
There are other `format specifiers`, but they are only applicable when using
|
||||
the `%` operator.
|
||||
@ -74,13 +74,13 @@ Format strings may contain multiple placeholders. In such a case, the values
|
||||
are handed in the form of an array, one value per placeholder (unless using a
|
||||
format specifier with `*`, see `dynamic padding`):
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var format_string = "%s was reluctant to learn %s, but now he enjoys it."
|
||||
var actual_string = format_string % ["Estragon", "GDScript"]
|
||||
|
||||
print(actual_string)
|
||||
# Output: "Estragon was reluctant to learn GDScript, but now he enjoys it."
|
||||
```
|
||||
|
||||
Note the values are inserted in order. Remember all placeholders must be
|
||||
replaced at once, so there must be an appropriate number of values.
|
||||
@ -158,40 +158,40 @@ used.
|
||||
|
||||
To pad a string to a minimum length, add an integer to the specifier:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
print("%10d" % 12345)
|
||||
# output: " 12345"
|
||||
# 5 leading spaces for a total length of 10
|
||||
```
|
||||
|
||||
If the integer starts with `0`, integral values are padded with zeroes
|
||||
instead of white space:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
print("%010d" % 12345)
|
||||
# output: "0000012345"
|
||||
```
|
||||
|
||||
Precision can be specified for real numbers by adding a `.` (*dot*) with an
|
||||
integer following it. With no integer after `.`, a precision of 0 is used,
|
||||
rounding to integral value. The integer to use for padding must appear before
|
||||
the dot.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# Pad to minimum length of 10, round to 3 decimal places
|
||||
print("%10.3f" % 10000.5555)
|
||||
# Output: " 10000.556"
|
||||
# 1 leading space
|
||||
```
|
||||
|
||||
The `-` character will cause padding to the right rather than the left,
|
||||
useful for right text alignment:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
print("%-10d" % 12345678)
|
||||
# Output: "12345678 "
|
||||
# 2 trailing spaces
|
||||
```
|
||||
|
||||
|
||||
Dynamic padding
|
||||
@ -202,21 +202,21 @@ without modifying the format string. It is used in place of an integer in the
|
||||
format specifier. The values for padding and precision are then passed when
|
||||
formatting:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var format_string = "%*.*f"
|
||||
# Pad to length of 7, round to 3 decimal places:
|
||||
print(format_string % [7, 3, 8.8888])
|
||||
# Output: " 8.889"
|
||||
# 2 leading spaces
|
||||
```
|
||||
|
||||
It is still possible to pad with zeroes in integer placeholders by adding `0`
|
||||
before `*`:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
print("%0*d" % [2, 3])
|
||||
# Output: "03"
|
||||
```
|
||||
|
||||
|
||||
Escape sequence
|
||||
@ -225,11 +225,11 @@ Escape sequence
|
||||
To insert a literal `%` character into a format string, it must be escaped to
|
||||
avoid reading it as a placeholder. This is done by doubling the character:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var health = 56
|
||||
print("Remaining health: %d%%" % health)
|
||||
# Output: "Remaining health: 56%"
|
||||
```
|
||||
|
||||
|
||||
Format method examples
|
||||
|
@ -25,8 +25,7 @@ Note:
|
||||
|
||||
Here is a complete class example based on these guidelines:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
class_name StateMachine
|
||||
extends Node
|
||||
# Hierarchical State machine for the player.
|
||||
@ -88,6 +87,7 @@ Here is a complete class example based on these guidelines:
|
||||
func _on_state_changed(previous, new):
|
||||
print("state changed")
|
||||
emit_signal("state_changed")
|
||||
```
|
||||
|
||||
.. _formatting:
|
||||
|
||||
@ -109,47 +109,46 @@ Each indent level should be one greater than the block containing it.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
for i in range(10):
|
||||
print("hello")
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
for i in range(10):
|
||||
print("hello")
|
||||
|
||||
for i in range(10):
|
||||
print("hello")
|
||||
```
|
||||
|
||||
Use 2 indent levels to distinguish continuation lines from
|
||||
regular code blocks.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
effect.interpolate_property(sprite, "transform/scale",
|
||||
sprite.get_scale(), Vector2(2.0, 2.0), 0.3,
|
||||
Tween.TRANS_QUAD, Tween.EASE_OUT)
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
effect.interpolate_property(sprite, "transform/scale",
|
||||
sprite.get_scale(), Vector2(2.0, 2.0), 0.3,
|
||||
Tween.TRANS_QUAD, Tween.EASE_OUT)
|
||||
```
|
||||
|
||||
Exceptions to this rule are arrays, dictionaries, and enums. Use a single
|
||||
indentation level to distinguish continuation lines:
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var party = [
|
||||
"Godot",
|
||||
"Godette",
|
||||
@ -168,11 +167,11 @@ indentation level to distinguish continuation lines:
|
||||
TILE_SPIKE,
|
||||
TILE_TELEPORT,
|
||||
}
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var party = [
|
||||
"Godot",
|
||||
"Godette",
|
||||
@ -191,6 +190,7 @@ indentation level to distinguish continuation lines:
|
||||
TILE_SPIKE,
|
||||
TILE_TELEPORT,
|
||||
}
|
||||
```
|
||||
|
||||
Trailing comma
|
||||
~~~~~~~~~~~~~~
|
||||
@ -201,47 +201,46 @@ line doesn't need to be modified when adding new elements.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
enum Tiles {
|
||||
TILE_BRICK,
|
||||
TILE_FLOOR,
|
||||
TILE_SPIKE,
|
||||
TILE_TELEPORT,
|
||||
}
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
enum Tiles {
|
||||
TILE_BRICK,
|
||||
TILE_FLOOR,
|
||||
TILE_SPIKE,
|
||||
TILE_TELEPORT
|
||||
}
|
||||
```
|
||||
|
||||
Trailing commas are unnecessary in single-line lists, so don't add them in this case.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT,}
|
||||
```
|
||||
|
||||
Blank lines
|
||||
~~~~~~~~~~~
|
||||
|
||||
Surround functions and class definitions with two blank lines:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func heal(amount):
|
||||
health += amount
|
||||
health = min(health, max_health)
|
||||
@ -252,6 +251,7 @@ Surround functions and class definitions with two blank lines:
|
||||
health -= amount
|
||||
health = max(0, health)
|
||||
emit_signal("health_changed", health)
|
||||
```
|
||||
|
||||
Use one blank line inside functions to separate logical sections.
|
||||
|
||||
@ -276,27 +276,27 @@ not even with a single line conditional statement.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
if position.x > width:
|
||||
position.x = 0
|
||||
|
||||
if flag:
|
||||
print("flagged")
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
if position.x > width: position.x = 0
|
||||
|
||||
if flag: print("flagged")
|
||||
```
|
||||
|
||||
The only exception to that rule is the ternary operator:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
next_state = "fall" if not is_on_floor() else "idle"
|
||||
```
|
||||
|
||||
Format multiline statements for readability
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -317,8 +317,7 @@ end of the previous line.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var angle_degrees = 135
|
||||
var quadrant = (
|
||||
"northeast" if angle_degrees <= 90
|
||||
@ -333,17 +332,18 @@ end of the previous line.
|
||||
and position.y > 300 and position.y < 400
|
||||
):
|
||||
pass
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var angle_degrees = 135
|
||||
var quadrant = "northeast" if angle_degrees <= 90 else "southeast" if angle_degrees <= 180 else "southwest" if angle_degrees <= 270 else "northwest"
|
||||
|
||||
var position = Vector2(250, 350)
|
||||
if position.x > 200 and position.x < 400 and position.y > 300 and position.y < 400:
|
||||
pass
|
||||
```
|
||||
|
||||
Avoid unnecessary parentheses
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -354,17 +354,17 @@ they only reduce readability.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
if is_colliding():
|
||||
queue_free()
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
if (is_colliding()):
|
||||
queue_free()
|
||||
```
|
||||
|
||||
Boolean operators
|
||||
~~~~~~~~~~~~~~~~~
|
||||
@ -379,17 +379,17 @@ This can make long expressions easier to read.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
if (foo and bar) or baz:
|
||||
print("condition is true")
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
if foo && bar || baz:
|
||||
print("condition is true")
|
||||
```
|
||||
|
||||
Comment spacing
|
||||
~~~~~~~~~~~~~~~
|
||||
@ -399,17 +399,17 @@ This helps differentiate text comments from disabled code.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# This is a comment.
|
||||
#print("This is disabled code")
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
#This is a comment.
|
||||
# print("This is disabled code")
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
@ -426,31 +426,31 @@ in dictionary references and function calls.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
position.x = 5
|
||||
position.y = target_position.y + 10
|
||||
dict["key"] = 5
|
||||
my_array = [4, 5, 6]
|
||||
print("foo")
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
position.x=5
|
||||
position.y = mpos.y+10
|
||||
dict ["key"] = 5
|
||||
myarray = [4,5,6]
|
||||
print ("foo")
|
||||
```
|
||||
|
||||
Don't use spaces to align expressions vertically:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
x = 100
|
||||
y = 100
|
||||
velocity = 500
|
||||
```
|
||||
|
||||
Quotes
|
||||
~~~~~~
|
||||
@ -458,8 +458,7 @@ Quotes
|
||||
Use double quotes unless single quotes make it possible to escape fewer
|
||||
characters in a given string. See the examples below:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# Normal string.
|
||||
print("hello world")
|
||||
|
||||
@ -471,6 +470,7 @@ characters in a given string. See the examples below:
|
||||
|
||||
# Both quote styles would require 2 escapes; prefer double quotes if it's a tie.
|
||||
print("'hello' \"world\"")
|
||||
```
|
||||
|
||||
Numbers
|
||||
~~~~~~~
|
||||
@ -479,45 +479,51 @@ Don't omit the leading or trailing zero in floating-point numbers. Otherwise,
|
||||
this makes them less readable and harder to distinguish from integers at a
|
||||
glance.
|
||||
|
||||
**Good**::
|
||||
|
||||
**Good**:
|
||||
```
|
||||
var float_number = 0.234
|
||||
var other_float_number = 13.0
|
||||
```
|
||||
|
||||
**Bad**::
|
||||
|
||||
**Bad**:
|
||||
```
|
||||
var float_number = .234
|
||||
var other_float_number = 13.
|
||||
```
|
||||
|
||||
Use lowercase for letters in hexadecimal numbers, as their lower height makes
|
||||
the number easier to read.
|
||||
|
||||
**Good**::
|
||||
|
||||
**Good**:
|
||||
```
|
||||
var hex_number = 0xfb8c0b
|
||||
```
|
||||
|
||||
**Bad**::
|
||||
|
||||
**Bad**:
|
||||
```
|
||||
var hex_number = 0xFB8C0B
|
||||
```
|
||||
|
||||
Take advantage of GDScript's underscores in literals to make large numbers more
|
||||
readable.
|
||||
|
||||
**Good**::
|
||||
|
||||
**Good**:
|
||||
```
|
||||
var large_number = 1_234_567_890
|
||||
var large_hex_number = 0xffff_f8f8_0000
|
||||
var large_bin_number = 0b1101_0010_1010
|
||||
# Numbers lower than 1000000 generally don't need separators.
|
||||
var small_number = 12345
|
||||
```
|
||||
|
||||
**Bad**::
|
||||
|
||||
**Bad**:
|
||||
```
|
||||
var large_number = 1234567890
|
||||
var large_hex_number = 0xfffff8f80000
|
||||
var large_bin_number = 0b110100101010
|
||||
# Numbers lower than 1000000 generally don't need separators.
|
||||
var small_number = 12_345
|
||||
```
|
||||
|
||||
.. _naming_conventions:
|
||||
|
||||
@ -532,17 +538,18 @@ File names
|
||||
~~~~~~~~~~
|
||||
|
||||
Use snake_case for file names. For named classes, convert the PascalCase class
|
||||
name to snake_case::
|
||||
|
||||
name to snake_case:
|
||||
```
|
||||
# This file should be saved as `weapon.gd`.
|
||||
class_name Weapon
|
||||
extends Node
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# This file should be saved as `yaml_parser.gd`.
|
||||
class_name YAMLParser
|
||||
extends Object
|
||||
```
|
||||
|
||||
This is consistent with how C++ files are named in Godot's source code. This
|
||||
also avoids case sensitivity issues that can crop up when exporting a project
|
||||
@ -553,43 +560,43 @@ Classes and nodes
|
||||
|
||||
Use PascalCase for class and node names:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
extends KinematicBody
|
||||
```
|
||||
|
||||
Also use PascalCase when loading a class into a constant or a variable:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
const Weapon = preload("res://weapon.gd")
|
||||
```
|
||||
|
||||
Functions and variables
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Use snake\_case to name functions and variables:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var particle_effect
|
||||
func load_level():
|
||||
```
|
||||
|
||||
Prepend a single underscore (\_) to virtual methods functions the user must
|
||||
override, private functions, and private variables:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var _counter = 0
|
||||
func _recalculate_path():
|
||||
```
|
||||
|
||||
Signals
|
||||
~~~~~~~
|
||||
|
||||
Use the past tense to name signals:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
signal door_opened
|
||||
signal score_changed
|
||||
```
|
||||
|
||||
Constants and enums
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
@ -597,21 +604,21 @@ Constants and enums
|
||||
Write constants with CONSTANT\_CASE, that is to say in all caps with an
|
||||
underscore (\_) to separate words:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
const MAX_SPEED = 200
|
||||
```
|
||||
|
||||
Use PascalCase for enum *names* and CONSTANT\_CASE for their members, as they
|
||||
are constants:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
enum Element {
|
||||
EARTH,
|
||||
WATER,
|
||||
AIR,
|
||||
FIRE,
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Code order
|
||||
@ -622,8 +629,7 @@ This first section focuses on code order. For formatting, see
|
||||
|
||||
We suggest to organize GDScript code this way:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
01. tool
|
||||
02. class_name
|
||||
03. extends
|
||||
@ -642,6 +648,7 @@ We suggest to organize GDScript code this way:
|
||||
14. remaining built-in virtual methods
|
||||
15. public methods
|
||||
16. private methods
|
||||
```
|
||||
|
||||
We optimized the order to make it easy to read the code from top to bottom, to
|
||||
help developers reading the code for the first time understand how it works, and
|
||||
@ -672,12 +679,12 @@ Following that, you should have the class's optional docstring as comments. You
|
||||
can use that to explain the role of your class to your teammates, how it works,
|
||||
and how other developers should use it, for example.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
class_name MyNode
|
||||
extends Node
|
||||
# A brief description of the class's role and functionality.
|
||||
# Longer description.
|
||||
```
|
||||
|
||||
Signals and properties
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -691,8 +698,7 @@ properties.
|
||||
Then, write constants, exported variables, public, private, and onready
|
||||
variables, in that order.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
signal spawn_player(position)
|
||||
|
||||
enum Jobs {KNIGHT, WIZARD, ROGUE, HEALER, SHAMAN}
|
||||
@ -709,6 +715,7 @@ variables, in that order.
|
||||
|
||||
onready var sword = get_node("Sword")
|
||||
onready var gun = get_node("Gun")
|
||||
```
|
||||
|
||||
|
||||
Note:
|
||||
@ -752,8 +759,7 @@ interactions with the game engine.
|
||||
The rest of the class's interface, public and private methods, come after that,
|
||||
in that order.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func _init():
|
||||
add_to_group("state_machine")
|
||||
|
||||
@ -783,6 +789,7 @@ in that order.
|
||||
func _on_state_changed(previous, new):
|
||||
print("state changed")
|
||||
emit_signal("state_changed")
|
||||
```
|
||||
|
||||
|
||||
Static typing
|
||||
@ -795,24 +802,24 @@ Declared types
|
||||
|
||||
To declare a variable's type, use `<variable>: <type )`:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var health: int = 0
|
||||
```
|
||||
|
||||
To declare the return type of a function, use `-> <type )`:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func heal(amount: int) -> void:
|
||||
```
|
||||
|
||||
Inferred types
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
In most cases you can let the compiler infer the type, using `:=`:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var health := 0 # The compiler will use the int type.
|
||||
```
|
||||
|
||||
However, in a few cases when context is missing, the compiler falls back to
|
||||
the function's return type. For example, `get_node()` cannot infer a type
|
||||
@ -821,26 +828,26 @@ should set the type explicitly.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
onready var health_bar: ProgressBar = get_node("UI/LifeBar")
|
||||
```
|
||||
|
||||
Alternatively, you can use the `as` keyword to cast the return type, and
|
||||
that type will be used to infer the type of the var.
|
||||
|
||||
.. rst-class:: code-example-good
|
||||
|
||||
::
|
||||
.. rst-class: code-example-good
|
||||
|
||||
```
|
||||
onready var health_bar := get_node("UI/LifeBar") as ProgressBar
|
||||
# health_bar will be typed as ProgressBar
|
||||
```
|
||||
|
||||
This option is also considered more `type-safe( doc_gdscript_static_typing_safe_lines )` than the first.
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# The compiler can't infer the exact type and will use Node
|
||||
# instead of ProgressBar.
|
||||
onready var health_bar := get_node("UI/LifeBar")
|
||||
```
|
||||
|
@ -32,8 +32,7 @@ node, then an `Inventory`. To add items to the inventory, the people
|
||||
who work with your code should always pass an `Item` to the
|
||||
`Inventory.add` method. With types, you can enforce this:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# In 'Item.gd'.
|
||||
class_name Item
|
||||
# In 'Inventory.gd'.
|
||||
@ -46,6 +45,7 @@ who work with your code should always pass an `Item` to the
|
||||
item = _instance_item_from_db(reference)
|
||||
|
||||
item.amount += amount
|
||||
```
|
||||
|
||||
Another significant advantage of typed GDScript is the new **warning
|
||||
system**. From version 3.1, Godot gives you warnings about your code as
|
||||
@ -89,19 +89,19 @@ To define the type of a variable or a constant, write a colon after the
|
||||
variable's name, followed by its type. E.g. `var health: int`. This
|
||||
forces the variable's type to always stay the same:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var damage: float = 10.5
|
||||
const MOVE_SPEED: float = 50.0
|
||||
```
|
||||
|
||||
Godot will try to infer types if you write a colon, but you omit the
|
||||
type:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var life_points := 4
|
||||
var damage := 10.5
|
||||
var motion := Vector2()
|
||||
```
|
||||
|
||||
Currently you can use three types of… types:
|
||||
|
||||
@ -123,26 +123,26 @@ You can use any class, including your custom classes, as types. There
|
||||
are two ways to use them in scripts. The first method is to preload the
|
||||
script you want to use as a type in a constant:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
const Rifle = preload("res://player/weapons/Rifle.gd")
|
||||
var my_rifle: Rifle
|
||||
```
|
||||
|
||||
The second method is to use the `name` keyword when you create.
|
||||
For the example above, your Rifle.gd would look like this:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
extends Node2D
|
||||
class_name Rifle
|
||||
```
|
||||
|
||||
If you use `name`, Godot registers the Rifle type globally in
|
||||
the editor, and you can use it anywhere, without having to preload it
|
||||
into a constant:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var my_rifle: Rifle
|
||||
```
|
||||
|
||||
Variable casting
|
||||
~~~~~~~~~~~~~~~~
|
||||
@ -162,14 +162,14 @@ casting keyword, and using the colon `:` again to force the variable
|
||||
to use this type. This forces the variable to stick to the
|
||||
`PlayerController` type:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func _on_body_entered(body: PhysicsBody2D) -> void:
|
||||
var player := body as PlayerController
|
||||
if not player:
|
||||
return
|
||||
|
||||
player.damage()
|
||||
```
|
||||
|
||||
As we're dealing with a custom type, if the `body` doesn't extend
|
||||
`PlayerController`, the `player`\ variable will be set to `null`.
|
||||
@ -221,24 +221,23 @@ Define the return type of a function with the arrow ->
|
||||
To define the return type of a function, write a dash and a right angle
|
||||
bracket `- )` after its declaration, followed by the return type:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func _process(delta: float) -> void:
|
||||
pass
|
||||
```
|
||||
|
||||
The type `void` means the function does not return anything. You can
|
||||
use any type, as with variables:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func hit(damage: float) -> bool:
|
||||
health_points -= damage
|
||||
return health_points <= 0
|
||||
```
|
||||
|
||||
You can also use your own nodes as return types:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# Inventory.gd
|
||||
|
||||
# Adds an item to the inventory and returns it.
|
||||
@ -249,6 +248,7 @@ You can also use your own nodes as return types:
|
||||
|
||||
item.amount += amount
|
||||
return item
|
||||
```
|
||||
|
||||
Typed or dynamic: stick to one style
|
||||
------------------------------------
|
||||
@ -263,8 +263,7 @@ Typed code takes a little more writing, but you get the benefits we
|
||||
discussed above. Here's an example of the same, empty script, in a
|
||||
dynamic style:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
extends Node
|
||||
|
||||
|
||||
@ -274,11 +273,11 @@ dynamic style:
|
||||
|
||||
func _process(delta):
|
||||
pass
|
||||
```
|
||||
|
||||
And with static typing:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
extends Node
|
||||
|
||||
|
||||
@ -288,33 +287,34 @@ And with static typing:
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
pass
|
||||
```
|
||||
|
||||
As you can see, you can also use types with the engine's virtual
|
||||
methods. Signal callbacks, like any methods, can also use types. Here's
|
||||
a `body_entered` signal in a dynamic style:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func _on_Area2D_body_entered(body):
|
||||
pass
|
||||
```
|
||||
|
||||
And the same callback, with type hints:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func _on_area_entered(area: CollisionObject2D) -> void:
|
||||
pass
|
||||
```
|
||||
|
||||
You're free to replace, e.g. the `CollisionObject2D`, with your own type,
|
||||
to cast parameters automatically:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
func _on_area_entered(bullet: Bullet) -> void:
|
||||
if not bullet:
|
||||
return
|
||||
|
||||
take_damage(bullet.damage)
|
||||
```
|
||||
|
||||
The `bullet` variable could hold any `CollisionObject2D` here, but
|
||||
we make sure it is our `Bullet`, a node we created for our project. If
|
||||
@ -338,32 +338,31 @@ use type hints. All the examples below **will trigger errors**.
|
||||
|
||||
You can't use Enums as types:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
enum MoveDirection {UP, DOWN, LEFT, RIGHT}
|
||||
var current_direction: MoveDirection
|
||||
```
|
||||
|
||||
You can't specify the type of individual members in an array. This will
|
||||
give you an error:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var enemies: Array = [$Goblin: Enemy, $Zombie: Enemy]
|
||||
```
|
||||
|
||||
You can't force the assignment of types in a `for` loop, as each
|
||||
element the `for` keyword loops over already has a different type. So you
|
||||
**cannot** write:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var names = ["John", "Marta", "Samantha", "Jimmy"]
|
||||
for name: String in names:
|
||||
pass
|
||||
```
|
||||
|
||||
Two scripts can't depend on each other in a cyclic fashion:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# Player.gd
|
||||
|
||||
extends Area2D
|
||||
@ -371,9 +370,9 @@ Two scripts can't depend on each other in a cyclic fashion:
|
||||
|
||||
|
||||
var rifle: Rifle
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# Rifle.gd
|
||||
|
||||
extends Area2D
|
||||
@ -381,6 +380,7 @@ Two scripts can't depend on each other in a cyclic fashion:
|
||||
|
||||
|
||||
var player: Player
|
||||
```
|
||||
|
||||
Summary
|
||||
-------
|
||||
|
@ -79,17 +79,17 @@ You can use two shorthands to shorten your code in GDScript. Firstly, putting th
|
||||
`onready` keyword before a member variable makes it initialize right before
|
||||
the `ready()` callback.
|
||||
|
||||
.. code-block:: gdscript
|
||||
|
||||
```
|
||||
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.
|
||||
|
||||
.. code-block:: gdscript
|
||||
|
||||
```
|
||||
onready var sprite = $Sprite
|
||||
onready var tween = $ShieldBar/Tween
|
||||
```
|
||||
|
||||
Creating nodes
|
||||
--------------
|
||||
|
@ -233,22 +233,6 @@ Note:
|
||||
|
||||
func _init():
|
||||
print(data)
|
||||
.. code-tab:: csharp
|
||||
|
||||
using System;
|
||||
using Godot;
|
||||
|
||||
public class BotStatsTable : Resource
|
||||
{
|
||||
private Godot.Dictionary<String, BotStats> _stats = new Godot.Dictionary<String, BotStats>();
|
||||
|
||||
public BotStatsTable()
|
||||
{
|
||||
_stats["GodotBot"] = new BotStats(10); // Creates instance with 10 health.
|
||||
_stats["DifferentBot"] = new BotStats(20); // A different one with 20 health.
|
||||
GD.Print(_stats);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Instead of just inlining the Dictionary values, one could also, alternatively...
|
||||
|
@ -49,13 +49,13 @@ so we need to nullify the effects of Godot's transformations. We do this by sett
|
||||
`POSITION` built-in to our desired position. `POSITION` bypasses the built-in transformations
|
||||
and sets the vertex position directly.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type spatial;
|
||||
|
||||
void vertex() {
|
||||
POSITION = vec4(VERTEX, 1.0);
|
||||
}
|
||||
```
|
||||
|
||||
Even with this vertex shader, the quad keeps disappearing. This is due to frustum
|
||||
culling, which is done on the CPU. Frustum culling uses the camera matrix and the
|
||||
@ -78,9 +78,9 @@ Depth texture
|
||||
To read from the depth texture, perform a texture lookup using `texture()` and
|
||||
the uniform variable `DEPTH_TEXTURE`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x;
|
||||
```
|
||||
|
||||
Note:
|
||||
Similar to accessing the screen texture, accessing the depth texture is only
|
||||
@ -102,25 +102,25 @@ coordinates (NDC). NDC run from `-1` to `1`, similar to clip space coordinates.
|
||||
Reconstruct the NDC using `SCREEN_UV` for the `x` and `y` axis, and
|
||||
the depth value for `z`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x;
|
||||
vec3 ndc = vec3(SCREEN_UV, depth) * 2.0 - 1.0;
|
||||
}
|
||||
```
|
||||
|
||||
Convert NDC to view space by multiplying the NDC by `INV_PROJECTION_MATRIX`.
|
||||
Recall that view space gives positions relative to the camera, so the `z` value will give us
|
||||
the distance to the point.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
...
|
||||
vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
|
||||
view.xyz /= view.w;
|
||||
float linear_depth = -view.z;
|
||||
}
|
||||
```
|
||||
|
||||
Because the camera is facing the negative `z` direction, the position will have a negative `z` value.
|
||||
In order to get a usable depth value, we have to negate `view.z`.
|
||||
@ -129,8 +129,7 @@ The world position can be constructed from the depth buffer using the following
|
||||
that the `CAMERA_MATRIX` is needed to transform the position from view space into world space, so
|
||||
it needs to be passed to the fragment shader with a varying.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
varying mat4 CAMERA;
|
||||
|
||||
void vertex() {
|
||||
@ -142,6 +141,7 @@ it needs to be passed to the fragment shader with a varying.
|
||||
vec4 world = CAMERA * INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
|
||||
vec3 world_position = world.xyz / world.w;
|
||||
}
|
||||
```
|
||||
|
||||
An optimization
|
||||
---------------
|
||||
@ -157,8 +157,7 @@ vertices, normals, colors, etc.
|
||||
|
||||
Now, attach a script to the MeshInstance and use the following code:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
extends MeshInstance
|
||||
|
||||
func _ready():
|
||||
@ -176,6 +175,7 @@ Now, attach a script to the MeshInstance and use the following code:
|
||||
|
||||
# Create mesh from mesh_array:
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, mesh_array)
|
||||
```
|
||||
|
||||
Note:
|
||||
The triangle is specified in normalized device coordinates. Recall, NDC run
|
||||
|
@ -59,24 +59,23 @@ Note:
|
||||
need to create your own uniform in the shader and pass the `Viewport` texture in
|
||||
manually, like so:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
// Inside the Shader.
|
||||
uniform sampler2D ViewportTexture;
|
||||
```
|
||||
|
||||
And you can pass the texture into the shader from GDScript like so:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# In GDScript.
|
||||
func _ready():
|
||||
$Sprite.material.set_shader_param("ViewportTexture", $Viewport.get_texture())
|
||||
```
|
||||
|
||||
Copy the following code to your shader. The above code is a single pass edge detection filter, a
|
||||
`Sobel filter ( https://en.wikipedia.org/wiki/Sobel_operator )`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type canvas_item;
|
||||
|
||||
void fragment() {
|
||||
@ -91,6 +90,7 @@ Copy the following code to your shader. The above code is a single pass edge det
|
||||
col += texture(TEXTURE, UV + vec2(SCREEN_PIXEL_SIZE.x, -SCREEN_PIXEL_SIZE.y)).xyz;
|
||||
COLOR.xyz = col;
|
||||
}
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
@ -134,8 +134,7 @@ As an example, you could write a full screen Gaussian blur effect by attaching t
|
||||
to each of the `ViewportContainers`. The order in which you apply the shaders
|
||||
does not matter:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type canvas_item;
|
||||
|
||||
// Blurs the screen in the X-direction.
|
||||
@ -151,9 +150,9 @@ does not matter:
|
||||
col += texture(TEXTURE, UV + vec2(4.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.05;
|
||||
COLOR.xyz = col;
|
||||
}
|
||||
```
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type canvas_item;
|
||||
|
||||
// Blurs the screen in the Y-direction.
|
||||
@ -169,6 +168,7 @@ does not matter:
|
||||
col += texture(TEXTURE, UV + vec2(0.0, 4.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.05;
|
||||
COLOR.xyz = col;
|
||||
}
|
||||
```
|
||||
|
||||
Using the above code, you should end up with a full screen blur effect like below.
|
||||
|
||||
|
@ -23,20 +23,22 @@ working with shaders, you need to code and think differently from other
|
||||
programming languages.
|
||||
|
||||
Suppose you want to update all the pixels in a texture to a given color. In
|
||||
GDScript, your code would use `for` loops::
|
||||
GDScript, your code would use `for` loops:
|
||||
|
||||
```
|
||||
for x in range(width):
|
||||
for y in range(height):
|
||||
set_color(x, y, some_color)
|
||||
```
|
||||
|
||||
Your code is already part of a loop in a shader, so the corresponding code would
|
||||
look like this.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
COLOR = some_color;
|
||||
}
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
@ -80,9 +82,9 @@ support different render modes, built-in variables, and processing functions.
|
||||
|
||||
In Godot, all shaders need to specify their type in the first line, like so:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type spatial;
|
||||
```
|
||||
|
||||
Here are the available types:
|
||||
|
||||
@ -96,10 +98,10 @@ Render modes
|
||||
Shaders have optional render modes you can specify on the second line, after the
|
||||
shader type, like so:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type spatial;
|
||||
render_mode unshaded, cull_disabled;
|
||||
```
|
||||
|
||||
Render modes alter the way Godot applies the shader. For example, the
|
||||
`unshaded` mode makes the engine skip the built-in light processor function.
|
||||
|
@ -36,23 +36,22 @@ Write a custom shader for the leaves
|
||||
|
||||
This is a simple example of a shader for leaves:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type spatial;
|
||||
render_mode depth_draw_alpha_prepass, cull_disabled, world_vertex_coords;
|
||||
```
|
||||
|
||||
This is a spatial shader. There is no front/back culling (so leaves can be seen from both sides), and alpha prepass is used, so there are less depth artifacts that result from using transparency (and leaves cast shadow). Finally, for the sway effect, world coordinates are recommended, so the tree can be duplicated, moved, etc. and it will still work together with other trees.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
uniform sampler2D texture_albedo : hint_albedo;
|
||||
uniform vec4 transmission : hint_color;
|
||||
```
|
||||
|
||||
Here, the texture is read, as well as a transmission color, which is used to add some back-lighting to the leaves, simulating subsurface scattering.
|
||||
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
uniform float sway_speed = 1.0;
|
||||
uniform float sway_strength = 0.05;
|
||||
uniform float sway_phase_len = 8.0;
|
||||
@ -63,14 +62,14 @@ Here, the texture is read, as well as a transmission color, which is used to add
|
||||
VERTEX.y += sin(VERTEX.y * sway_phase_len + TIME * sway_speed * 1.12412) * strength;
|
||||
VERTEX.z += sin(VERTEX.z * sway_phase_len * 0.9123 + TIME * sway_speed * 1.3123) * strength;
|
||||
}
|
||||
```
|
||||
|
||||
This is the code to create the sway of the leaves. It's basic (just uses a sinewave multiplying by the time and axis position, but works well). Notice that the strength is multiplied by the color. Every axis uses a different small near 1.0 multiplication factor so axes don't appear in sync.
|
||||
|
||||
|
||||
Finally, all that's left is the fragment shader:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
vec4 albedo_tex = texture(texture_albedo, UV);
|
||||
ALBEDO = albedo_tex.rgb;
|
||||
@ -79,6 +78,7 @@ Finally, all that's left is the fragment shader:
|
||||
ROUGHNESS = 1.0;
|
||||
TRANSMISSION = transmission.rgb;
|
||||
}
|
||||
```
|
||||
|
||||
And this is pretty much it.
|
||||
|
||||
|
@ -25,11 +25,11 @@ It takes as argument the UV of the screen and returns a vec3 RGB with the color.
|
||||
special built-in varying: SCREEN_UV can be used to obtain the UV for
|
||||
the current fragment. As a result, this simple canvas_item fragment shader:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
COLOR = textureLod(SCREEN_TEXTURE, SCREEN_UV, 0.0);
|
||||
}
|
||||
```
|
||||
|
||||
results in an invisible object, because it just shows what lies behind.
|
||||
|
||||
@ -53,8 +53,7 @@ special demo for *Screen Space Shaders*, that you can download to see
|
||||
and learn. One example is a simple shader to adjust brightness, contrast
|
||||
and saturation:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type canvas_item;
|
||||
|
||||
uniform float brightness = 1.0;
|
||||
@ -70,6 +69,7 @@ and saturation:
|
||||
|
||||
COLOR.rgb = c;
|
||||
}
|
||||
```
|
||||
|
||||
Behind the scenes
|
||||
~~~~~~~~~~~~~~~~~
|
||||
@ -150,10 +150,10 @@ converted via the inverse projection matrix.
|
||||
|
||||
The following code retrieves the 3D position below the pixel being drawn:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
float depth = textureLod(DEPTH_TEXTURE, SCREEN_UV, 0.0).r;
|
||||
vec4 upos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
|
||||
vec3 pixel_position = upos.xyz / upos.w;
|
||||
}
|
||||
```
|
||||
|
@ -68,8 +68,7 @@ If not written to, these values will not be modified and be passed through as th
|
||||
The user can disable the built-in modelview transform (projection will still happen later) and do
|
||||
it manually with the following code:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type canvas_item;
|
||||
render_mode skip_vertex_transform;
|
||||
|
||||
@ -77,6 +76,7 @@ it manually with the following code:
|
||||
|
||||
VERTEX = (EXTRA_MATRIX * (WORLD_MATRIX * vec4(VERTEX, 0.0, 1.0))).xy;
|
||||
}
|
||||
```
|
||||
|
||||
Note:
|
||||
`WORLD_MATRIX` is actually a modelview matrix. It takes input in local space and transforms it
|
||||
@ -84,21 +84,21 @@ Note:
|
||||
|
||||
In order to get the world space coordinates of a vertex, you have to pass in a custom uniform like so:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
material.set_shader_param("global_transform", get_global_transform())
|
||||
```
|
||||
|
||||
|
||||
Then, in your vertex shader:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
uniform mat4 global_transform;
|
||||
varying vec2 world_position;
|
||||
|
||||
void vertex(){
|
||||
world_position = (global_transform * vec4(VERTEX, 0.0, 1.0)).xy;
|
||||
}
|
||||
```
|
||||
|
||||
`world_position` can then be used in either the vertex or fragment functions.
|
||||
|
||||
@ -149,18 +149,18 @@ when a custom fragment function is attached to these nodes, the texture lookup n
|
||||
manually. Godot does not provide the texture color in the `COLOR` built-in variable; to read
|
||||
the texture color for such nodes, use:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
COLOR = texture(TEXTURE, UV);
|
||||
```
|
||||
|
||||
This differs from the behavior of the built-in normal map. If a normal map is attached, Godot uses
|
||||
it by default and assigns its value to the built-in `NORMAL` variable. If you are using a normal
|
||||
map meant for use in 3D, it will appear inverted. In order to use it in your shader, you must assign
|
||||
it to the `NORMALMAP` property. Godot will handle converting it for use in 2D and overwriting `NORMAL`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
NORMALMAP = texture(NORMAL_TEXTURE, UV).rgb;
|
||||
```
|
||||
|
||||
+----------------------------------+----------------------------------------------------------------+
|
||||
| Built-in | Description |
|
||||
|
@ -89,19 +89,19 @@ Casting of types of different size is also not allowed. Conversion must be done
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
float a = 2; // invalid
|
||||
float a = 2.0; // valid
|
||||
float a = float(2); // valid
|
||||
```
|
||||
|
||||
Default integer constants are signed, so casting is always needed to convert to unsigned:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
int a = 2; // valid
|
||||
uint a = 2; // invalid
|
||||
uint a = uint(2); // valid
|
||||
```
|
||||
|
||||
Members
|
||||
~~~~~~~
|
||||
@ -119,8 +119,7 @@ Constructing
|
||||
|
||||
Construction of vector types must always pass:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
// The required amount of scalars
|
||||
vec4 a = vec4(0.0, 1.0, 2.0, 3.0);
|
||||
// Complementary vectors and/or scalars
|
||||
@ -128,16 +127,17 @@ Construction of vector types must always pass:
|
||||
vec4 a = vec4(vec3(0.0, 1.0, 2.0), 3.0);
|
||||
// A single scalar for the whole vector
|
||||
vec4 a = vec4(0.0);
|
||||
```
|
||||
|
||||
Construction of matrix types requires vectors of the same dimension as the matrix. You can
|
||||
also build a diagonal matrix using `matx(float)` syntax. Accordingly, `mat4(1.0)` is
|
||||
an identity matrix.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
mat2 m2 = mat2(vec2(1.0, 0.0), vec2(0.0, 1.0));
|
||||
mat3 m3 = mat3(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0));
|
||||
mat4 identity = mat4(1.0);
|
||||
```
|
||||
|
||||
Matrices can also be built from a matrix of another dimension.
|
||||
There are two rules :
|
||||
@ -145,11 +145,11 @@ If a larger matrix is constructed from a smaller matrix, the additional rows and
|
||||
set to the values they would have in an identity matrix. If a smaller matrix is constructed
|
||||
from a larger matrix, the top, left submatrix of the larger matrix is used.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
mat3 basis = mat3(WORLD_MATRIX);
|
||||
mat4 m4 = mat4(basis);
|
||||
mat2 m2 = mat2(m4);
|
||||
```
|
||||
|
||||
Swizzling
|
||||
~~~~~~~~~
|
||||
@ -157,8 +157,7 @@ Swizzling
|
||||
It is possible to obtain any combination of components in any order, as long as the result
|
||||
is another vector type (or scalar). This is easier shown than explained:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
vec4 a = vec4(0.0, 1.0, 2.0, 3.0);
|
||||
vec3 b = a.rgb; // Creates a vec3 with vec4 components.
|
||||
vec3 b = a.ggg; // Also valid; creates a vec3 and fills it with a single vec4 component.
|
||||
@ -169,17 +168,18 @@ is another vector type (or scalar). This is easier shown than explained:
|
||||
vec3 c = b.xrt; // Invalid, mixing different styles is forbidden.
|
||||
b.rrr = a.rgb; // Invalid, assignment with duplication.
|
||||
b.bgr = a.rgb; // Valid assignment. "b"'s "blue" component will be "a"'s "red" and vice versa.
|
||||
```
|
||||
|
||||
Precision
|
||||
~~~~~~~~~
|
||||
|
||||
It is possible to add precision modifiers to datatypes; use them for uniforms, variables, arguments and varyings:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
lowp vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // low precision, usually 8 bits per component mapped to 0-1
|
||||
mediump vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // medium precision, usually 16 bits or half float
|
||||
highp vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // high precision, uses full float or integer range (default)
|
||||
```
|
||||
|
||||
|
||||
Using lower precision for some operations can speed up the math involved (at the cost of less precision).
|
||||
@ -204,16 +204,15 @@ Local arrays
|
||||
Local arrays are declared in functions. They can use all of the allowed datatypes, except samplers.
|
||||
The array declaration follows a C-style syntax: `[const] + [precision] + typename + identifier + [array size]`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
float arr[3];
|
||||
}
|
||||
```
|
||||
|
||||
They can be initialized at the beginning like:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
float float_arr[3] = float[3] (1.0, 0.5, 0.0); // first constructor
|
||||
|
||||
int int_arr[3] = int[] (2, 1, 0); // second constructor
|
||||
@ -221,35 +220,36 @@ They can be initialized at the beginning like:
|
||||
vec2 vec2_arr[3] = { vec2(1.0, 1.0), vec2(0.5, 0.5), vec2(0.0, 0.0) }; // third constructor
|
||||
|
||||
bool bool_arr[] = { true, true, false }; // fourth constructor - size is defined automatically from the element count
|
||||
```
|
||||
|
||||
You can declare multiple arrays (even with different sizes) in one expression:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
float a[3] = float[3] (1.0, 0.5, 0.0),
|
||||
b[2] = { 1.0, 0.5 },
|
||||
c[] = { 0.7 },
|
||||
d = 0.0,
|
||||
e[5];
|
||||
```
|
||||
|
||||
To access an array element, use the indexing syntax:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
float arr[3];
|
||||
|
||||
arr[0] = 1.0; // setter
|
||||
|
||||
COLOR.r = arr[0]; // getter
|
||||
```
|
||||
|
||||
Arrays also have a built-in function `.length()` (not to be confused with the built-in `length()` function). It doesn't accept any parameters and will return the array's size.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
float arr[] = { 0.0, 1.0, 0.5, -1.0 };
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
@ -261,38 +261,38 @@ Constants
|
||||
|
||||
Use the `const` keyword before the variable declaration to make that variable immutable, which means that it cannot be modified. All basic types, except samplers can be declared as constants. Accessing and using a constant value is slightly faster than using a uniform. Constants must be initialized at their declaration.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
const vec2 a = vec2(0.0, 1.0);
|
||||
vec2 b;
|
||||
|
||||
a = b; // invalid
|
||||
b = a; // valid
|
||||
```
|
||||
|
||||
Constants cannot be modified and additionally cannot have hints, but multiple of them (if they have the same type) can be declared in a single expression e.g
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
const vec2 V1 = vec2(1, 1), V2 = vec2(2, 2);
|
||||
```
|
||||
|
||||
Similar to variables, arrays can also be declared with `const`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
const float arr[] = { 1.0, 0.5, 0.0 };
|
||||
|
||||
arr[0] = 1.0; // invalid
|
||||
|
||||
COLOR.r = arr[0]; // valid
|
||||
```
|
||||
|
||||
Constants can be declared both globally (outside of any function) or locally (inside a function).
|
||||
Global constants are useful when you want to have access to a value throughout your shader that does not need to be modified. Like uniforms, global constants are shared between all shader stages, but they are not accessible outside of the shader.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type spatial;
|
||||
|
||||
const float PI = 3.14159265358979323846;
|
||||
```
|
||||
|
||||
Operators
|
||||
---------
|
||||
@ -332,8 +332,7 @@ Flow control
|
||||
|
||||
Godot Shading language supports the most common types of flow control:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
// if and else
|
||||
if (cond) {
|
||||
|
||||
@ -369,6 +368,7 @@ Godot Shading language supports the most common types of flow control:
|
||||
do {
|
||||
|
||||
} while(true);
|
||||
```
|
||||
|
||||
Keep in mind that, in modern GPUs, an infinite loop can exist and can freeze your application (including editor).
|
||||
Godot can't protect you from this, so be careful not to make this mistake!
|
||||
@ -389,8 +389,7 @@ Functions
|
||||
|
||||
It is possible to define functions in a Godot shader. They use the following syntax:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
ret_type func_name(args) {
|
||||
return ret_type; // if returning a value
|
||||
}
|
||||
@ -400,6 +399,7 @@ It is possible to define functions in a Godot shader. They use the following syn
|
||||
int sum2(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
You can only use functions that have been defined above (higher in the editor) the function from which you are calling
|
||||
@ -413,11 +413,11 @@ Function arguments can have special qualifiers:
|
||||
|
||||
Example below:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void sum2(int a, int b, inout int result) {
|
||||
result = a + b;
|
||||
}
|
||||
```
|
||||
|
||||
Varyings
|
||||
~~~~~~~~
|
||||
@ -426,8 +426,7 @@ To send data from the vertex to the fragment (or light) processor function, *var
|
||||
used. They are set for every primitive vertex in the *vertex processor*, and the
|
||||
value is interpolated for every pixel in the *fragment processor*.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type spatial;
|
||||
|
||||
varying vec3 some_color;
|
||||
@ -443,11 +442,11 @@ value is interpolated for every pixel in the *fragment processor*.
|
||||
void light() {
|
||||
DIFFUSE_LIGHT = some_color * 100; // optionally
|
||||
}
|
||||
```
|
||||
|
||||
Varying can also be an array:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type spatial;
|
||||
|
||||
varying float var_arr[3];
|
||||
@ -460,11 +459,11 @@ Varying can also be an array:
|
||||
void fragment() {
|
||||
ALBEDO = vec3(var_arr[0], var_arr[1], var_arr[2]); // red color
|
||||
}
|
||||
```
|
||||
|
||||
It's also possible to send data from *fragment* to *light* processors using *varying* keyword. To do so you can assign it in the *fragment* and later use it in the *light* function.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type spatial;
|
||||
|
||||
varying vec3 some_light;
|
||||
@ -476,11 +475,11 @@ It's also possible to send data from *fragment* to *light* processors using *var
|
||||
void light() {
|
||||
DIFFUSE_LIGHT = some_light;
|
||||
}
|
||||
```
|
||||
|
||||
Note that varying may not be assigned in custom functions or a *light processor* function like:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type spatial;
|
||||
|
||||
varying float test;
|
||||
@ -496,6 +495,7 @@ Note that varying may not be assigned in custom functions or a *light processor*
|
||||
void light() {
|
||||
test = 0.0; // Error too.
|
||||
}
|
||||
```
|
||||
|
||||
This limitation was introduced to prevent incorrect usage before initialization.
|
||||
|
||||
@ -505,8 +505,7 @@ Interpolation qualifiers
|
||||
Certain values are interpolated during the shading pipeline. You can modify how these interpolations
|
||||
are done by using *interpolation qualifiers*.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type spatial;
|
||||
|
||||
varying flat vec3 our_color;
|
||||
@ -518,6 +517,7 @@ are done by using *interpolation qualifiers*.
|
||||
void fragment() {
|
||||
ALBEDO = our_color;
|
||||
}
|
||||
```
|
||||
|
||||
There are two possible interpolation qualifiers:
|
||||
|
||||
@ -537,17 +537,17 @@ Passing values to shaders is possible. These are global to the whole shader and
|
||||
When a shader is later assigned to a material, the uniforms will appear as editable parameters in it.
|
||||
Uniforms can't be written from within the shader.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type spatial;
|
||||
|
||||
uniform float some_value;
|
||||
```
|
||||
|
||||
You can set uniforms in the editor in the material. Or you can set them through GDScript:
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
material.set_shader_param("some_value", some_value)
|
||||
```
|
||||
|
||||
Note:
|
||||
The first argument to `set_shader_param` is the name of the uniform in the shader. It
|
||||
@ -557,13 +557,13 @@ Any GLSL type except for *void* can be a uniform. Additionally, Godot provides
|
||||
optional shader hints to make the compiler understand for what the uniform is
|
||||
used, and how the editor should allow users to modify it.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type spatial;
|
||||
|
||||
uniform vec4 color : hint_color;
|
||||
uniform float amount : hint_range(0, 1);
|
||||
uniform vec4 other_color : hint_color = vec4(1.0);
|
||||
```
|
||||
|
||||
It's important to understand that textures that are supplied as color require hints for proper sRGB->linear conversion (i.e. `hint_albedo`), as Godot's 3D engine renders in linear color space.
|
||||
|
||||
@ -618,12 +618,12 @@ Note:
|
||||
|
||||
Uniforms can also be assigned default values:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type spatial;
|
||||
|
||||
uniform vec4 some_vector = vec4(0.0);
|
||||
uniform vec4 some_color : hint_color = vec4(1.0);
|
||||
```
|
||||
|
||||
Built-in variables
|
||||
------------------
|
||||
|
@ -108,8 +108,7 @@ They can optionally be presented in world space by using the *world_vertex_coord
|
||||
Users can disable the built-in modelview transform (projection will still happen later) and do
|
||||
it manually with the following code:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type spatial;
|
||||
render_mode skip_vertex_transform;
|
||||
|
||||
@ -118,6 +117,7 @@ it manually with the following code:
|
||||
NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);
|
||||
// same as above for binormal and tangent, if normal mapping is used
|
||||
}
|
||||
```
|
||||
|
||||
Other built-ins, such as UV, UV2 and COLOR, are also passed through to the fragment function if not modified.
|
||||
|
||||
@ -319,11 +319,11 @@ each light type.
|
||||
|
||||
Below is an example of a custom light function using a Lambertian lighting model:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void light() {
|
||||
DIFFUSE_LIGHT += clamp(dot(NORMAL, LIGHT), 0.0, 1.0) * ATTENUATION * ALBEDO;
|
||||
}
|
||||
```
|
||||
|
||||
If you want the lights to add together, add the light contribution to `DIFFUSE_LIGHT` using `+=`, rather than overwriting it.
|
||||
|
||||
|
@ -26,8 +26,7 @@ Note:
|
||||
|
||||
Here is a complete shader example based on these guidelines:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type canvas_item;
|
||||
// Screen-space shader to adjust a 2D scene's brightness, contrast
|
||||
// and saturation. Taken from
|
||||
@ -46,6 +45,7 @@ Here is a complete shader example based on these guidelines:
|
||||
|
||||
COLOR.rgb = c;
|
||||
}
|
||||
```
|
||||
|
||||
Formatting
|
||||
----------
|
||||
@ -65,38 +65,38 @@ Each indent level should be one tab greater than the block containing it.
|
||||
|
||||
**Good**:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
COLOR = vec3(1.0, 1.0, 1.0);
|
||||
}
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
COLOR = vec3(1.0, 1.0, 1.0);
|
||||
}
|
||||
```
|
||||
|
||||
Use 2 indent levels to distinguish continuation lines from
|
||||
regular code blocks.
|
||||
|
||||
**Good**:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
vec2 st = vec2(
|
||||
atan(NORMAL.x, NORMAL.z),
|
||||
acos(NORMAL.y));
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
vec2 st = vec2(
|
||||
atan(NORMAL.x, NORMAL.z),
|
||||
acos(NORMAL.y));
|
||||
```
|
||||
|
||||
|
||||
Line breaks and blank lines
|
||||
@ -111,31 +111,30 @@ an `if` statement or similar.
|
||||
|
||||
**Good**:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
if (true) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment()
|
||||
{
|
||||
if (true)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Blank lines
|
||||
~~~~~~~~~~~
|
||||
|
||||
Surround function definitions with one (and only one) blank line:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void do_something() {
|
||||
// ...
|
||||
}
|
||||
@ -143,6 +142,7 @@ Surround function definitions with one (and only one) blank line:
|
||||
void fragment() {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Use one (and only one) blank line inside functions to separate logical sections.
|
||||
|
||||
@ -162,29 +162,29 @@ Never combine multiple statements on a single line.
|
||||
|
||||
**Good**:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
ALBEDO = vec3(1.0);
|
||||
EMISSION = vec3(1.0);
|
||||
}
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
ALBEDO = vec3(1.0); EMISSION = vec3(1.0);
|
||||
}
|
||||
```
|
||||
|
||||
The only exception to that rule is the ternary operator:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
bool should_be_white = true;
|
||||
ALBEDO = should_be_white ? vec3(1.0) : vec3(0.0);
|
||||
}
|
||||
```
|
||||
|
||||
Comment spacing
|
||||
~~~~~~~~~~~~~~~
|
||||
@ -194,23 +194,23 @@ This helps differentiate text comments from disabled code.
|
||||
|
||||
**Good**:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
// This is a comment.
|
||||
//return;
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
//This is a comment.
|
||||
// return;
|
||||
```
|
||||
|
||||
Don't use multiline comment syntax if your comment can fit on a single line:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
/* This is another comment. */
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
@ -227,26 +227,26 @@ in function calls.
|
||||
|
||||
**Good**:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
COLOR.r = 5.0;
|
||||
COLOR.r = COLOR.g + 0.1;
|
||||
COLOR.b = some_function(1.0, 2.0);
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
COLOR.r=5.0;
|
||||
COLOR.r = COLOR.g+0.1;
|
||||
COLOR.b = some_function (1.0,2.0);
|
||||
```
|
||||
|
||||
Don't use spaces to align expressions vertically:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
ALBEDO.r = 1.0;
|
||||
EMISSION.r = 1.0;
|
||||
```
|
||||
|
||||
Floating-point numbers
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -257,19 +257,19 @@ distinguishing numbers greater than 1 from those lower than 1.
|
||||
|
||||
**Good**:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
ALBEDO.rgb = vec3(5.0, 0.1, 0.2);
|
||||
}
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
ALBEDO.rgb = vec3(5., .1, .2);
|
||||
}
|
||||
```
|
||||
|
||||
Accessing vector members
|
||||
------------------------
|
||||
@ -281,15 +281,15 @@ understand what the underlying data represents.
|
||||
|
||||
**Good**:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
COLOR.rgb = vec3(5.0, 0.1, 0.2);
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
COLOR.xyz = vec3(5.0, 0.1, 0.2);
|
||||
```
|
||||
|
||||
Naming conventions
|
||||
------------------
|
||||
@ -303,11 +303,11 @@ Functions and variables
|
||||
|
||||
Use snake\_case to name functions and variables:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void some_function() {
|
||||
float some_variable = 0.5;
|
||||
}
|
||||
```
|
||||
|
||||
Constants
|
||||
~~~~~~~~~
|
||||
@ -315,17 +315,16 @@ Constants
|
||||
Write constants with CONSTANT\_CASE, that is to say in all caps with an
|
||||
underscore (\_) to separate words:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
const float GOLDEN_RATIO = 1.618;
|
||||
```
|
||||
|
||||
Code order
|
||||
----------
|
||||
|
||||
We suggest to organize shader code this way:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
01. shader type declaration
|
||||
02. render mode declaration
|
||||
03. // docstring
|
||||
@ -338,6 +337,7 @@ We suggest to organize shader code this way:
|
||||
08. vertex() function
|
||||
09. fragment() function
|
||||
10. light() function
|
||||
```
|
||||
|
||||
We optimized the order to make it easy to read the code from top to bottom, to
|
||||
help developers reading the code for the first time understand how it works, and
|
||||
|
@ -59,13 +59,13 @@ Note:
|
||||
|
||||
ColorRect > CanvasItem > Material > Material > click / Edit > ShaderMaterial > Shader > `New Shader` > click / Edit:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type canvas_item;
|
||||
|
||||
void fragment() {
|
||||
COLOR = vec4(UV.x, UV.y, 0.5, 1.0);
|
||||
}
|
||||
```
|
||||
|
||||
The above code renders a gradient like the one below.
|
||||
|
||||
@ -113,9 +113,9 @@ seam created by our texture coordinates. So how do we get a range of coordinates
|
||||
the sphere in a nice way? One solution is to use a function that repeats on the domain of our texture.
|
||||
`sin` and `cos` are two such functions. Let's apply them to the texture and see what happens.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
COLOR.xyz = vec3(sin(UV.x * 3.14159 * 4.0) * cos(UV.y * 3.14159 * 4.0) * 0.5 + 0.5);
|
||||
```
|
||||
|
||||
![](img/planet_sincos.png)
|
||||
|
||||
@ -138,8 +138,7 @@ surface of the sphere, you never hit an edge, and hence you never create a seam
|
||||
a pinch point on the pole. The following code converts the `UVs` into Cartesian
|
||||
coordinates.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
float theta = UV.y * 3.14159;
|
||||
float phi = UV.x * 3.14159 * 2.0;
|
||||
vec3 unit = vec3(0.0, 0.0, 0.0);
|
||||
@ -148,6 +147,7 @@ coordinates.
|
||||
unit.y = cos(theta) * -1.0;
|
||||
unit.z = cos(phi) * sin(theta);
|
||||
unit = normalize(unit);
|
||||
```
|
||||
|
||||
And if we use `unit` as an output `COLOR` value, we get:
|
||||
|
||||
@ -156,8 +156,7 @@ And if we use `unit` as an output `COLOR` value, we get:
|
||||
Now that we can calculate the 3D position of the surface of the sphere, we can use 3D noise
|
||||
to make the planet. We will be using this noise function directly from a `Shadertoy ( https://www.shadertoy.com/view/Xsl3Dl )`:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
vec3 hash(vec3 p) {
|
||||
p = vec3(dot(p, vec3(127.1, 311.7, 74.7)),
|
||||
dot(p, vec3(269.5, 183.3, 246.1)),
|
||||
@ -180,16 +179,17 @@ to make the planet. We will be using this noise function directly from a `Shader
|
||||
mix(dot(hash(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)),
|
||||
dot(hash(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)), u.x), u.y), u.z );
|
||||
}
|
||||
```
|
||||
|
||||
Note:
|
||||
All credit goes to the author, Inigo Quilez. It is published under the `MIT` licence.
|
||||
|
||||
Now to use `noise`, add the following to the `fragment` function:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
float n = noise(unit * 5.0);
|
||||
COLOR.xyz = vec3(n * 0.5 + 0.5);
|
||||
```
|
||||
|
||||
![](img/planet_noise.png)
|
||||
|
||||
@ -211,9 +211,9 @@ it *mixes* the two values together. In other APIs, this function is often called
|
||||
However, `lerp` is typically reserved for mixing two floats together; `mix` can take any
|
||||
values whether it be floats or vector types.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), n * 0.5 + 0.5);
|
||||
```
|
||||
|
||||
The first color is blue for the ocean. The second color is a kind of reddish color (because
|
||||
all alien planets need red terrain). And finally, they are mixed together by `n * 0.5 + 0.5`.
|
||||
@ -226,9 +226,9 @@ That is a little more blurry than we want. Planets typically have a relatively c
|
||||
land and sea. In order to do that, we will change the last term to `smoothstep(-0.1, 0.0, n)`.
|
||||
And thus the whole line becomes:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), smoothstep(-0.1, 0.0, n));
|
||||
```
|
||||
|
||||
What `smoothstep` does is return `0` if the third argument is below the first and `1` if the
|
||||
third argument is larger than the second and smoothly blends between `0` and `1` if the third number
|
||||
@ -244,12 +244,12 @@ overall blobby structure of the continents. Then another layer breaks up the edg
|
||||
another, and so on. What we will do is calculate `n` with four lines of shader code
|
||||
instead of just one. `n` becomes:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
float n = noise(unit * 5.0) * 0.5;
|
||||
n += noise(unit * 10.0) * 0.25;
|
||||
n += noise(unit * 20.0) * 0.125;
|
||||
n += noise(unit * 40.0) * 0.0625;
|
||||
```
|
||||
|
||||
And now the planet looks like:
|
||||
|
||||
@ -266,9 +266,9 @@ One final thing to make this look more like a planet. The ocean and the land ref
|
||||
So we want the ocean to shine a little more than the land. We can do this by passing a fourth value
|
||||
into the `alpha` channel of our output `COLOR` and using it as a Roughness map.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
COLOR.a = 0.3 + 0.7 * smoothstep(-0.1, 0.0, n);
|
||||
```
|
||||
|
||||
This line returns `0.3` for water and `1.0` for land. This means that the land is going to be quite
|
||||
rough, while the water will be quite smooth.
|
||||
@ -293,9 +293,9 @@ drawn with slightly fainter colors and a `Roughness` value of `1` everywhere. To
|
||||
go into the `Viewport` and enable the "Transparent Bg" property. Since we are now
|
||||
rendering one transparent object on top of another, we want to enable `blend_premul_alpha`:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
render_mode blend_premul_alpha;
|
||||
```
|
||||
|
||||
This pre-multiplies the colors by the `alpha` value and then blends them correctly together. Typically,
|
||||
when blending one transparent color on top of another, even if the background has an `alpha` of `0` (as it
|
||||
|
@ -63,9 +63,9 @@ Your first CanvasItem shader
|
||||
In Godot, all shaders start with a line specifying what type of shader they are.
|
||||
It uses the following format:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type canvas_item;
|
||||
```
|
||||
|
||||
Because we are writing a CanvasItem shader, we specify `canvas_item` in the
|
||||
first line. All our code will go beneath this declaration.
|
||||
@ -96,11 +96,11 @@ shorthand for constructing a vector with 4 numbers. For more information about
|
||||
vectors see the `Vector math tutorial ( doc_vector_math )` `COLOR` is both
|
||||
an input variable to the fragment function and the final output from it.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment(){
|
||||
COLOR = vec4(0.4, 0.6, 0.9, 1.0);
|
||||
}
|
||||
```
|
||||
|
||||
![](img/blue-box.png)
|
||||
|
||||
@ -121,11 +121,11 @@ other functions or to assign values to `COLOR` directly.
|
||||
|
||||
![](img/iconuv.png)
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
COLOR = vec4(UV, 0.5, 1.0);
|
||||
}
|
||||
```
|
||||
|
||||
![](img/UV.png)
|
||||
|
||||
@ -135,12 +135,12 @@ Using `TEXTURE` built-in
|
||||
When you want to adjust a color in a Sprite you cannot just adjust the color
|
||||
from the texture manually like in the code below.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment(){
|
||||
//this shader will result in an all white rectangle
|
||||
COLOR.b = 1.0;
|
||||
}
|
||||
```
|
||||
|
||||
The default fragment function reads from a texture and displays it. When you
|
||||
overwrite the default fragment function, you lose that functionality, so you
|
||||
@ -149,12 +149,12 @@ function. Certain nodes, like Sprites, have a dedicated texture variable that
|
||||
can be accessed in the shader using `TEXTURE`. Use it together with `UV` and
|
||||
`texture` to draw the Sprite.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment(){
|
||||
COLOR = texture(TEXTURE, UV); //read from texture
|
||||
COLOR.b = 1.0; //set blue channel to 1.0
|
||||
}
|
||||
```
|
||||
|
||||
![](img/blue-tex.png)
|
||||
|
||||
@ -166,23 +166,23 @@ the entire shader.
|
||||
|
||||
You can use uniforms by defining them at the top of your shader like so:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
uniform float size;
|
||||
```
|
||||
|
||||
For more information about usage see the `Shading Language doc
|
||||
( doc_shading_language )`.
|
||||
|
||||
Add a uniform to change the amount of blue in our Sprite.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
uniform float blue = 1.0; // you can assign a default value to uniforms
|
||||
|
||||
void fragment(){
|
||||
COLOR = texture(TEXTURE, UV); //read from texture
|
||||
COLOR.b = blue;
|
||||
}
|
||||
```
|
||||
|
||||
Now you can change the amount of blue in the Sprite from the editor. Look back
|
||||
at the Inspector under where you created your shader. You should see a section
|
||||
@ -197,10 +197,10 @@ You can change uniforms from code using the function `set_shader_param()`
|
||||
which is called on the node's material resource. With a Sprite node, the
|
||||
following code can be used to set the `blue` uniform.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
var blue_value = 1.0
|
||||
material.set_shader_param("blue", blue_value)
|
||||
```
|
||||
|
||||
Note that the name of the uniform is a string. The string must match exactly
|
||||
with how it is written in the shader, including spelling and case.
|
||||
@ -221,21 +221,21 @@ viewport, or parent nodes).
|
||||
|
||||
You can offset the vertices by directly adding to `VERTEX`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void vertex() {
|
||||
VERTEX += vec2(10.0, 0.0);
|
||||
}
|
||||
```
|
||||
|
||||
Combined with the `TIME` built-in variable, this can be used for simple
|
||||
animation.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void vertex() {
|
||||
// Animate Sprite moving in big circle around its location
|
||||
VERTEX += vec2(cos(TIME)*100.0, sin(TIME)*100.0);
|
||||
}
|
||||
```
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
|
@ -107,9 +107,9 @@ shaders on the fly. The first thing Godot shaders need is a declaration of what
|
||||
type of shader they are. We set the variable `shader_type` to `spatial`
|
||||
because this is a spatial shader.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type spatial;
|
||||
```
|
||||
|
||||
Next we will define the `vertex()` function. The `vertex()` function
|
||||
determines where the vertices of your `Mesh( MeshInstance )` appear in
|
||||
@ -118,20 +118,20 @@ make our flat plane appear like a little terrain.
|
||||
|
||||
We define the vertex shader like so:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void vertex() {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
With nothing in the `vertex()` function, Godot will use its default vertex
|
||||
shader. We can easily start to make changes by adding a single line:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void vertex() {
|
||||
VERTEX.y += cos(VERTEX.x) * sin(VERTEX.z);
|
||||
}
|
||||
```
|
||||
|
||||
Adding this line, you should get an image like the one below.
|
||||
|
||||
@ -146,11 +146,11 @@ What we want to achieve is the look of little hills; after all. `cos` and
|
||||
`sin` already look kind of like hills. We do so by scaling the inputs to the
|
||||
`cos` and `sin` functions.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void vertex() {
|
||||
VERTEX.y += cos(VERTEX.x * 4.0) * sin(VERTEX.z * 4.0);
|
||||
}
|
||||
```
|
||||
|
||||
![](img/cos4.png)
|
||||
|
||||
@ -170,9 +170,9 @@ generating a noise texture that can be accessed from a shader.
|
||||
To access a texture in a shader add the following code near the top of your
|
||||
shader, outside the `vertex()` function.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
uniform sampler2D noise;
|
||||
```
|
||||
|
||||
This will allow you to send a noise texture to the shader. Now look in the
|
||||
inspector under your material. You should see a section called "Shader Params".
|
||||
@ -200,10 +200,10 @@ a` channels at the position. Since the noise texture is grayscale, all of the
|
||||
values are the same, so we can use any one of the channels as the height. In
|
||||
this case we'll use the `r`, or `x` channel.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
float height = texture(noise, VERTEX.xz / 2.0 + 0.5).x;
|
||||
VERTEX.y += height;
|
||||
```
|
||||
|
||||
Note: `xyzw` is the same as `rgba` in GLSL, so instead of `texture().x`
|
||||
above, we could use `texture().r`. See the `OpenGL documentation
|
||||
@ -228,9 +228,9 @@ that can be used in the shader. To use a uniform, you declare it in your
|
||||
|
||||
Let's make a uniform that changes the height of the terrain.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
uniform float height_scale = 0.5;
|
||||
```
|
||||
|
||||
|
||||
Godot lets you initialize a uniform with a value; here, `height_scale` is set
|
||||
@ -239,10 +239,10 @@ to `0.5`. You can set uniforms from GDScript by calling the function
|
||||
passed from GDScript takes precedence over the value used to initialize it in
|
||||
the shader.
|
||||
|
||||
::
|
||||
|
||||
```
|
||||
# called from the MeshInstance
|
||||
mesh.material.set_shader_param("height_scale", 0.5)
|
||||
```
|
||||
|
||||
Note:
|
||||
Changing uniforms in Spatial-based nodes is different from
|
||||
@ -257,9 +257,9 @@ of the uniform variable in the `Shader( Shader )`. You can use the
|
||||
uniform variable anywhere inside your `Shader( Shader )`. Here, we will
|
||||
use it to set the height value instead of arbitrarily multiplying by `0.5`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
VERTEX.y += height * height_scale;
|
||||
```
|
||||
|
||||
Now it looks much better.
|
||||
|
||||
@ -302,9 +302,9 @@ tutorial, for now we will read normals from a texture.
|
||||
Instead we will rely on the NoiseTexture again to calculate normals for us. We
|
||||
do that by passing in a second noise texture.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
uniform sampler2D normalmap;
|
||||
```
|
||||
|
||||
Set this second uniform texture to another NoiseTexture with another
|
||||
OpenSimplexNoise. But this time, check off "As Normalmap".
|
||||
@ -315,10 +315,10 @@ Now, because this is a normalmap and not a per-vertex normal, we are going to
|
||||
assign it in the `fragment()` function. The `fragment()` function will be
|
||||
explained in more detail in the next part of this tutorial.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
}
|
||||
```
|
||||
|
||||
When we have normals that correspond to a specific vertex we set `NORMAL`, but
|
||||
if you have a normalmap that comes from a texture, set the normal using
|
||||
@ -333,8 +333,7 @@ that with varyings.
|
||||
Above the `vertex()` define a `vec2` called `tex_position`. And inside the
|
||||
`vertex()` function assign `VERTEX.xz` to `tex_position`.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
varying vec2 tex_position;
|
||||
|
||||
void vertex() {
|
||||
@ -343,14 +342,15 @@ Above the `vertex()` define a `vec2` called `tex_position`. And inside the
|
||||
float height = texture(noise, tex_position).x;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
And now we can access `tex_position` from the `fragment()` function.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
void fragment() {
|
||||
NORMALMAP = texture(normalmap, tex_position).xyz;
|
||||
}
|
||||
```
|
||||
|
||||
With the normals in place the light now reacts to the height of the mesh
|
||||
dynamically.
|
||||
@ -364,8 +364,7 @@ We can even drag the light around and the lighting will update automatically.
|
||||
Here is the full code for this tutorial. You can see it is not very long as
|
||||
Godot handles most of the difficult stuff for you.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
```
|
||||
shader_type spatial;
|
||||
|
||||
uniform float height_scale = 0.5;
|
||||
@ -383,6 +382,7 @@ Godot handles most of the difficult stuff for you.
|
||||
void fragment() {
|
||||
NORMALMAP = texture(normalmap, tex_position).xyz;
|
||||
}
|
||||
```
|
||||
|
||||
That is everything for this part. Hopefully, you now understand the basics of
|
||||
vertex shaders in Godot. In the next part of this tutorial we will write a
|
||||
|
Loading…
Reference in New Issue
Block a user