mirror of
https://github.com/godotengine/godot-docs.git
synced 2026-01-04 14:11:02 +03:00
Removing trailing whitespace
With `sed -i $(rg -l '[[:blank:]]*$' -g'!classes') -e 's/[[:blank:]]*$//g'`
This commit is contained in:
@@ -61,12 +61,12 @@ or:
|
||||
make SPHINXBUILD=~/.local/bin/sphinx-build html
|
||||
```
|
||||
|
||||
The compilation might take some time as the `classes/` folder contains many files to parse.
|
||||
The compilation might take some time as the `classes/` folder contains many files to parse.
|
||||
You can then test the changes live by opening `_build/html/index.html` in your favorite browser.
|
||||
|
||||
### Building with Sphinx on Windows
|
||||
|
||||
On Windows, you need to:
|
||||
On Windows, you need to:
|
||||
* Download the Python installer [here](https://www.python.org/downloads/).
|
||||
* Install Python. Don't forget to check the "Add Python to PATH" box.
|
||||
* Use the above `pip` commands.
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
<link rel="alternate" hreflang="pt-br" href="http://docs.godotengine.org/pt-br/" />
|
||||
<link rel="alternate" hreflang="uk" href="http://docs.godotengine.org/uk/" />
|
||||
<link rel="alternate" hreflang="zh-cn" href="http://docs.godotengine.org/zh-cn/" />
|
||||
<link rel="alternate" hreflang="x-default" href="http://docs.godotengine.org/" />
|
||||
<link rel="alternate" hreflang="x-default" href="http://docs.godotengine.org/" />
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
|
||||
@@ -56,7 +56,6 @@ Active:
|
||||
::
|
||||
|
||||
The dog bit the man.
|
||||
|
||||
|
||||
**Don't** use the passive voice:
|
||||
|
||||
@@ -400,10 +399,10 @@ and set the anisotropic filter quality to 16x in Project Settings
|
||||
|
||||
Screenshot size should not exceed 1920x1080.
|
||||
|
||||
When you need to highlight an area of the editor to show something, like a
|
||||
When you need to highlight an area of the editor to show something, like a
|
||||
button or option, use a 2 pixel thick outline without a bevel.
|
||||
|
||||
Before you add or replace any images in the documentation, they should be run through
|
||||
a png compressor to save size. The built in lossless compressor in programs like Krita
|
||||
or Photoshop should be done. However you should also use a lossy one, such as `pngquant <https://pngquant.org/>`_
|
||||
where almost no image quality is lost during compression.
|
||||
where almost no image quality is lost during compression.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Contribute to the Class Reference
|
||||
=================================
|
||||
|
||||
Godot ships with many nodes and singletons to help you develop your games in GDscript. Each is a class, documented in the :ref:`class reference <toc-class-ref>`. This reference is essential for anyone learning the engine: it is available both online and in the engine.
|
||||
Godot ships with many nodes and singletons to help you develop your games in GDscript. Each is a class, documented in the :ref:`class reference <toc-class-ref>`. This reference is essential for anyone learning the engine: it is available both online and in the engine.
|
||||
|
||||
But it's incomplete. Some methods, variables and signals lack descriptions. Others changed with recent releases and need updates. The developers can't write the entire reference on their own. Godot needs you, and all of us, to contribute.
|
||||
|
||||
@@ -20,7 +20,7 @@ The class reference lies in the following XML files, in Godot's GitHub repositor
|
||||
|
||||
There are 5 steps to update the class reference (full guide below):
|
||||
|
||||
1. Fork `Godot's repository <https://github.com/godotengine/godot>`_
|
||||
1. Fork `Godot's repository <https://github.com/godotengine/godot>`_
|
||||
2. Clone your fork on your computer
|
||||
3. Edit the class file in ``doc/classes/`` to write documentation
|
||||
4. Commit your changes and push them to your fork
|
||||
@@ -174,7 +174,7 @@ Our job is to add the missing text between these marks:
|
||||
- <member></member>
|
||||
- <signal></signal>
|
||||
|
||||
Write in a clear and simple language. Always follow the :ref:`writing guidelines <doc_docs_writing_guidelines>` to keep your descriptions short and easy to read. **Do not leave empty lines** in the descriptions: each line in the XML file will result in a new paragraph.
|
||||
Write in a clear and simple language. Always follow the :ref:`writing guidelines <doc_docs_writing_guidelines>` to keep your descriptions short and easy to read. **Do not leave empty lines** in the descriptions: each line in the XML file will result in a new paragraph.
|
||||
|
||||
Here's how a class looks like in XML:
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ The aim is to not focus on the modelling techniques (there are plenty of tutoria
|
||||
Start with a tree
|
||||
-----------------
|
||||
|
||||
I took this tree from SketchFab:
|
||||
I took this tree from SketchFab:
|
||||
|
||||
.. image:: img/tree_base.png
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ for a Simulator executable.
|
||||
|
||||
Additionally since some time Apple requires 64 bit version of application binary when you are uploading to iStore.
|
||||
The best way to provide these is to create a bundle in which there are both 32bit and 64 binaries, so every device will be able to run the game.
|
||||
It can be done in three steps, first compile 32 bit version, then compile 64 bit version and then use ``lipo`` to bundle them into one fat binary, all those steps can be performed with following commands:
|
||||
It can be done in three steps, first compile 32 bit version, then compile 64 bit version and then use ``lipo`` to bundle them into one fat binary, all those steps can be performed with following commands:
|
||||
|
||||
::
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ Distro-specific oneliners
|
||||
+---------------+------------------------------------------------------------------------------------------------------------+
|
||||
| **OpenBSD** | :: |
|
||||
| | |
|
||||
| | pkg_add python scons png llvm yasm |
|
||||
| | pkg_add python scons png llvm yasm |
|
||||
+---------------+------------------------------------------------------------------------------------------------------------+
|
||||
| **openSUSE** | :: |
|
||||
| | |
|
||||
|
||||
@@ -46,7 +46,7 @@ The contents of the ``._sc_`` file (when not empty) are read with the ConfigFile
|
||||
format as ``project.godot``, etc). So far it can contain a list of pre-loaded project in this
|
||||
format:
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
[init_projects]
|
||||
list=["demos/2d/platformer", "demos/2d/isometric"]
|
||||
|
||||
@@ -104,7 +104,7 @@ memnew/memdelete also use a little C++ magic and notify Objects right
|
||||
after they are created, and right before they are deleted.
|
||||
|
||||
For dynamic memory, the PoolVector<> template is provided. PoolVector is a
|
||||
standard vector class, and is very similar to vector in the C++ standard library.
|
||||
standard vector class, and is very similar to vector in the C++ standard library.
|
||||
To create a PoolVector buffer, use this:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
TSCN file format
|
||||
================
|
||||
|
||||
A :code:`.tscn` File format is the "Text SCeNe" file format and represents
|
||||
a single scene-tree inside Godot. TSCN files have the advantage of being
|
||||
nearly human-readable and easy for version control systems to manage. During
|
||||
import the TSCN files are compiled into binary :code:`.scn` files stored
|
||||
A :code:`.tscn` File format is the "Text SCeNe" file format and represents
|
||||
a single scene-tree inside Godot. TSCN files have the advantage of being
|
||||
nearly human-readable and easy for version control systems to manage. During
|
||||
import the TSCN files are compiled into binary :code:`.scn` files stored
|
||||
inside the .import folder. This reduces the data size and speed up loading.
|
||||
|
||||
The :code:`.escn` file format is identical to the TSCN file format, but is used to
|
||||
The :code:`.escn` file format is identical to the TSCN file format, but is used to
|
||||
indicate to Godot that the file has been exported from another program and
|
||||
should not be edited by the user from within Godot.
|
||||
|
||||
@@ -21,17 +21,17 @@ File structure
|
||||
There are five main sections inside the TSCN File:
|
||||
|
||||
0. File Descriptor
|
||||
1. External resources
|
||||
1. External resources
|
||||
2. Internal resources
|
||||
3. Nodes
|
||||
4. Connections
|
||||
|
||||
The file descriptor looks like :code:`[gd_scene load_steps=1 format=2]` And
|
||||
should be the first entry in the file. The load_steps parameter should (in
|
||||
theory) be the number of resources within the file, though in practice its
|
||||
The file descriptor looks like :code:`[gd_scene load_steps=1 format=2]` And
|
||||
should be the first entry in the file. The load_steps parameter should (in
|
||||
theory) be the number of resources within the file, though in practice its
|
||||
value seems not to matter.
|
||||
|
||||
These sections should appear in order, but it can be hard to distinguish
|
||||
These sections should appear in order, but it can be hard to distinguish
|
||||
them. The only difference between them is the first element in the heading
|
||||
for all of the items in the section.
|
||||
For example, the heading of all external resources should start with
|
||||
@@ -48,9 +48,9 @@ Where resource_type is one of:
|
||||
- sub_resource
|
||||
- node
|
||||
- connection
|
||||
|
||||
Underneath every heading comes zero or more :code:`key = value` pairs. The
|
||||
values can be complex datatypes such as arrays, transformations, colors, and
|
||||
|
||||
Underneath every heading comes zero or more :code:`key = value` pairs. The
|
||||
values can be complex datatypes such as arrays, transformations, colors, and
|
||||
so on. For example, a spatial node looks like:
|
||||
|
||||
::
|
||||
@@ -73,11 +73,11 @@ Other valid keywords include:
|
||||
- index (if two nodes have the same name)
|
||||
- groups
|
||||
|
||||
The first node in the file should not have the :code:`parent=Path/To/Node`
|
||||
entry in it's heading, and it is the scene root. All scene files should have
|
||||
exactly one scene root. It it does not, Godot will fail to import the file.
|
||||
The parent path of other nodes should be absolute, but without the scene
|
||||
root's name. If it is a direct child of the scene root, it should be
|
||||
The first node in the file should not have the :code:`parent=Path/To/Node`
|
||||
entry in it's heading, and it is the scene root. All scene files should have
|
||||
exactly one scene root. It it does not, Godot will fail to import the file.
|
||||
The parent path of other nodes should be absolute, but without the scene
|
||||
root's name. If it is a direct child of the scene root, it should be
|
||||
:code:`"."`. Here is an example scene tree (but without any node content).
|
||||
|
||||
::
|
||||
@@ -86,9 +86,9 @@ root's name. If it is a direct child of the scene root, it should be
|
||||
[node name="Arm" parent="." type="Spatial"] ; Parented to the scene root
|
||||
[node name="Hand" parent="Arm" type="Spatial"]
|
||||
[node name="Finger" parent="Arm/Hand" type="Spatial"]
|
||||
|
||||
Similar to the internal resource, the document for each node is currently
|
||||
incomplete. Fortunately it is easy to find out because you can simply
|
||||
|
||||
Similar to the internal resource, the document for each node is currently
|
||||
incomplete. Fortunately it is easy to find out because you can simply
|
||||
save a file with that node in it. Some example nodes are:
|
||||
|
||||
::
|
||||
@@ -154,7 +154,7 @@ Skeleton
|
||||
|
||||
Skeleton node inherits Spatial node, besides that it may have a list
|
||||
of bones described in key, value pair in the format :code:`bones/Id/Attribute=Value`,
|
||||
attributes of bone consists of
|
||||
attributes of bone consists of
|
||||
|
||||
- name
|
||||
- parent
|
||||
@@ -172,7 +172,7 @@ attributes of bone consists of
|
||||
|
||||
4) :code:`pose` is the pose matrix use :code:`rest` as basis
|
||||
|
||||
5) :code:`bound_children` is a list of NodePath() points to
|
||||
5) :code:`bound_children` is a list of NodePath() points to
|
||||
BoneAttachments belong to this bone
|
||||
|
||||
An example of a skeleton node with two bones:
|
||||
@@ -199,7 +199,7 @@ BoneAttachment
|
||||
|
||||
BoneAttachment node is an intermediate node to describe some node being parented
|
||||
to a single bone in Skeleton node. The BoneAttachment has a :code:`bone_name=NameOfBone`,
|
||||
and the corresponding bone being the parent has the BoneAttachment node
|
||||
and the corresponding bone being the parent has the BoneAttachment node
|
||||
in its :code:`bound_children` list.
|
||||
|
||||
An example of one MeshInstance parented to a bone in Skeleton:
|
||||
@@ -253,13 +253,13 @@ node will have an accompanying ArrayMesh resource. The ArrayMesh resource
|
||||
may be either internal or external to the TSCN file.
|
||||
|
||||
References to the resources are handled by id numbers in the resources heading.
|
||||
External resources and internal resource are referred to with
|
||||
:code:`ExtResource(id)` and :code:`SubResource(id)`. Because there have
|
||||
External resources and internal resource are referred to with
|
||||
:code:`ExtResource(id)` and :code:`SubResource(id)`. Because there have
|
||||
different methods to refer to internal and external resource, you can have
|
||||
the same ID for both an internal and external resource.
|
||||
|
||||
For example, to refer to the resource
|
||||
:code:`[ext_resource id=3 type="PackedScene" path=....]` you would use
|
||||
:code:`[ext_resource id=3 type="PackedScene" path=....]` you would use
|
||||
:code:`ExtResource(3)`
|
||||
|
||||
External resources
|
||||
@@ -272,10 +272,10 @@ itself. An external resource consists of:
|
||||
- A type
|
||||
- An ID
|
||||
|
||||
Godot always generates absolute paths relative to the resource directory and
|
||||
thus prefixed with :code:`res://`, but paths relative to the TSCN file's
|
||||
location are also valid.
|
||||
|
||||
Godot always generates absolute paths relative to the resource directory and
|
||||
thus prefixed with :code:`res://`, but paths relative to the TSCN file's
|
||||
location are also valid.
|
||||
|
||||
Some example external resources are:
|
||||
|
||||
::
|
||||
@@ -286,10 +286,10 @@ Some example external resources are:
|
||||
Internal resources
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A TSCN file can contain meshes, materials and other data, and these are
|
||||
A TSCN file can contain meshes, materials and other data, and these are
|
||||
contained in the internal resources section of the file. The heading
|
||||
for an internal resource looks similar to those of external resources, but
|
||||
does not have a path. Internal resources also have :code:`key=value` pairs
|
||||
does not have a path. Internal resources also have :code:`key=value` pairs
|
||||
under each heading. For example, a capsule collision shape looks like:
|
||||
|
||||
::
|
||||
@@ -298,10 +298,10 @@ under each heading. For example, a capsule collision shape looks like:
|
||||
|
||||
radius = 0.5
|
||||
height = 3.0
|
||||
|
||||
Some internal resource contain links to other internal resources (such as a
|
||||
mesh having a material). In this case, the referring resource must appear
|
||||
before the reference to it. Thus, in the internal resources section of the
|
||||
|
||||
Some internal resource contain links to other internal resources (such as a
|
||||
mesh having a material). In this case, the referring resource must appear
|
||||
before the reference to it. Thus, in the internal resources section of the
|
||||
file, order does matter.
|
||||
|
||||
Unfortunately, documentation on the formats for these subresources is not
|
||||
@@ -322,7 +322,7 @@ TSCN support two format of surface,
|
||||
- arrays
|
||||
- morph_arrays
|
||||
|
||||
i) :code:`primitive` is an enumerate variable, :code:`primitive=4` which is
|
||||
i) :code:`primitive` is an enumerate variable, :code:`primitive=4` which is
|
||||
PRIMITIVE_TRIANGLES is frequently used.
|
||||
|
||||
ii) :code:`arrays` as the name suggests is an array of array, it contains:
|
||||
@@ -336,7 +336,7 @@ TSCN support two format of surface,
|
||||
7) Bone weight array
|
||||
8) Vertex index array
|
||||
|
||||
iii) :code:`morph_arrays` is an array of morph, each morph is exactly an
|
||||
iii) :code:`morph_arrays` is an array of morph, each morph is exactly an
|
||||
:code:`arrays` without vertex index array.
|
||||
|
||||
An example of ArrayMesh:
|
||||
@@ -384,15 +384,15 @@ it includes:
|
||||
- imported
|
||||
- enabled
|
||||
|
||||
1) The :code:`type` must be put as the first attribute of each track.
|
||||
1) The :code:`type` must be put as the first attribute of each track.
|
||||
The value of :code:`type` can be:
|
||||
|
||||
- 'transform'
|
||||
- 'value'
|
||||
- 'method'
|
||||
|
||||
2) The :code:`path` has the format :code:`NodePath(Path/To/Node:Attribute)`.
|
||||
It is the path from animation root node (property of AnimationPlayer) to the
|
||||
2) The :code:`path` has the format :code:`NodePath(Path/To/Node:Attribute)`.
|
||||
It is the path from animation root node (property of AnimationPlayer) to the
|
||||
animated node or attribute.
|
||||
|
||||
3) The :code:`interp` is the method to interpolate frames from the keyframes.
|
||||
@@ -402,12 +402,12 @@ it includes:
|
||||
- 1 (linear)
|
||||
- 2 (cubic)
|
||||
|
||||
4) The :code:`keys` is the keyframes, it appears as a PoolRealArray()
|
||||
4) The :code:`keys` is the keyframes, it appears as a PoolRealArray()
|
||||
but have different structure for track with different type
|
||||
|
||||
- A transform track use every 12 real number in the :code:`keys` to describte a keyframe.
|
||||
- A transform track use every 12 real number in the :code:`keys` to describte a keyframe.
|
||||
The first number is the timestamp, the second number is the transition (default 1.0
|
||||
in transform track), followed by a three number translation vector, followed by
|
||||
in transform track), followed by a three number translation vector, followed by
|
||||
four number rotation quaternion (x,y,z,w) and finally a three number scale vector.
|
||||
|
||||
::
|
||||
|
||||
@@ -5,7 +5,7 @@ Using an external text editor
|
||||
|
||||
While Godot has an inbuilt text editor, some developers have a tendency to
|
||||
want to use a text editor they are familiar with. Godot provides this
|
||||
option via the options under
|
||||
option via the options under
|
||||
``Editor -> Editor Settings -> Text Editor -> External``
|
||||
|
||||
.. image:: img/editor_settings.png
|
||||
|
||||
@@ -140,7 +140,7 @@ It is possible to bind values when establishing a connection by passing an objec
|
||||
{
|
||||
var plusButton = (Button)GetNode("PlusButton");
|
||||
var minusButton = (Button)GetNode("MinusButton");
|
||||
|
||||
|
||||
plusButton.Connect("pressed", this, "ModifyValue", new object[] { 1 });
|
||||
minusButton.Connect("pressed", this, "ModifyValue", new object[] { -1 });
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ This tutorial aims to be a quick reference for how to use GDScript more
|
||||
efficiently. It focuses on common cases specific to the language, but
|
||||
also covers a lot of information on dynamically typed languages.
|
||||
|
||||
It's meant to be especially useful for programmers with little or no previous
|
||||
It's meant to be especially useful for programmers with little or no previous
|
||||
experience with dynamically typed languages.
|
||||
|
||||
Dynamic nature
|
||||
@@ -147,7 +147,7 @@ too. Some Examples:
|
||||
|
||||
SomeClass instance = new SomeClass(); // Created as reference
|
||||
use_class(instance); // Passed as reference
|
||||
// Garbage collector will get rid of it when not in
|
||||
// Garbage collector will get rid of it when not in
|
||||
// use and freeze your game randomly for a second
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ easily with dictionaries. Here's a simple battleship game example:
|
||||
if pos in board: # Something at that pos
|
||||
if board[pos] == SHIP: # There was a ship! hit it
|
||||
board[pos] = SHIP_HIT
|
||||
else:
|
||||
else:
|
||||
print("Already hit here!") # Hey dude you already hit here
|
||||
else: # Nothing, mark as water
|
||||
board[pos] = WATER_HIT
|
||||
|
||||
@@ -25,7 +25,7 @@ Examine this concrete GDScript example:
|
||||
|
||||
# Define a format string with placeholder '%s'
|
||||
var format_string = "We're waiting for %s."
|
||||
|
||||
|
||||
# Using the '%' operator, the placeholder is replaced with the desired value
|
||||
var actual_string = format_string % "Godot"
|
||||
|
||||
@@ -78,7 +78,7 @@ 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."
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Getting started with Visual Scripting
|
||||
=====================================
|
||||
|
||||
As with everything in Godot, we prioritize a good experience over copying or integrating third party solutions
|
||||
As with everything in Godot, we prioritize a good experience over copying or integrating third party solutions
|
||||
which might not fit nicely in the current workflow. This led us to write our own version of how we believe
|
||||
this feature would work best with the engine.
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ VisualScript
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
:name: toc-learn-scripting-visual_script
|
||||
|
||||
|
||||
what_is_visual_scripting
|
||||
getting_started
|
||||
nodes_purposes
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Nodes and terminology
|
||||
=====================
|
||||
|
||||
Before continuing, it must be noted that the *Node* terminology needs to be used with care.
|
||||
Before continuing, it must be noted that the *Node* terminology needs to be used with care.
|
||||
When referring to *Visual Script Nodes* (or generally *Nodes*) this text will refer to the little boxes you connect with lines, which are part of a graph.
|
||||
When referring to *Scene Nodes*, it is implied that the elements that make up a Scene are being referred, which are part of a tree. Their naming is similar but their function is different.
|
||||
When referring to *Node* here, it will be implied that a *Visual Script Node* is referred to unless indicated otherwise.
|
||||
@@ -15,7 +15,7 @@ Node properties
|
||||
---------------
|
||||
|
||||
Like in most visual scripting implementations, each node has editable properties. In Godot, though, we try to avoid
|
||||
bloating the nodes with editable controls for the sake of readability.
|
||||
bloating the nodes with editable controls for the sake of readability.
|
||||
|
||||
Nodes still display the required information as text, but editing is done via the *Inspector*. To edit them,
|
||||
select any node and edit its properties in the *Inspector*.
|
||||
@@ -24,29 +24,29 @@ select any node and edit its properties in the *Inspector*.
|
||||
Ports and connections
|
||||
---------------------
|
||||
|
||||
Programming in Godot Visual Scripting is done via *Nodes* and *Port Connections* inside each function.
|
||||
Programming in Godot Visual Scripting is done via *Nodes* and *Port Connections* inside each function.
|
||||
|
||||
|
||||
Ports
|
||||
~~~~~
|
||||
|
||||
Nodes in Godot Visual Scripting have *Ports*. These are endpoints that appear to the
|
||||
Nodes in Godot Visual Scripting have *Ports*. These are endpoints that appear to the
|
||||
left and right of nodes and which can be used to make *Connections*:
|
||||
There are two types of *Ports*: *Sequence* and *Data*.
|
||||
|
||||
.. image:: img/visual_script17.png
|
||||
|
||||
|
||||
*Sequence Ports* indicate the order in which operations are executed.
|
||||
Typically when a *Node* is done processing, it will go to the next node from one of the ports at the right.
|
||||
If nothing is connected the function may end, or another output *Sequence Port* might be tried (this depends on the node).
|
||||
*Sequence Ports* indicate the order in which operations are executed.
|
||||
Typically when a *Node* is done processing, it will go to the next node from one of the ports at the right.
|
||||
If nothing is connected the function may end, or another output *Sequence Port* might be tried (this depends on the node).
|
||||
Thanks to this, you can follow the logic flow within a function by following the white lines.
|
||||
Not every *Node* has *Sequence Ports*. In fact, most do not.
|
||||
|
||||
*Data Ports* ports contain typed values. Types can be any regular Godot types,
|
||||
such as a boolean, an integer, a string, a Vector3, an array, any Object or Scene Node, etc.
|
||||
A *Data Port* on the right side of a node is considered an output, while,
|
||||
a port on the left side is an input. Connecting them allows information to flow to the next node.
|
||||
*Data Ports* ports contain typed values. Types can be any regular Godot types,
|
||||
such as a boolean, an integer, a string, a Vector3, an array, any Object or Scene Node, etc.
|
||||
A *Data Port* on the right side of a node is considered an output, while,
|
||||
a port on the left side is an input. Connecting them allows information to flow to the next node.
|
||||
|
||||
Not all *Data Port* types are compatible and will allow connections, though.
|
||||
Pay special attention to colors and icons, as each type has a different representation:
|
||||
@@ -57,19 +57,19 @@ Pay special attention to colors and icons, as each type has a different represen
|
||||
Connections
|
||||
~~~~~~~~~~~
|
||||
|
||||
Connecting is a relatively simple process. Drag an *Output Port* towards an *Input Port*.
|
||||
Connecting is a relatively simple process. Drag an *Output Port* towards an *Input Port*.
|
||||
|
||||
.. image:: img/visual_script_connect.gif
|
||||
|
||||
|
||||
Disconnecting takes a bit more practice. Disconnecting in *Data Ports* happens by
|
||||
Disconnecting takes a bit more practice. Disconnecting in *Data Ports* happens by
|
||||
dragging the *Input* away, while for *Sequence Ports*, this happens by dragging the *Output* away.
|
||||
|
||||
.. image:: img/visual_script_disconnect.gif
|
||||
|
||||
|
||||
This may seem strange at the beginning, but it happens because *Data Ports* are 1:N
|
||||
(A single output port can connect to many inputs), while *Sequence Ports* are N:1
|
||||
This may seem strange at the beginning, but it happens because *Data Ports* are 1:N
|
||||
(A single output port can connect to many inputs), while *Sequence Ports* are N:1
|
||||
(Many sequence outputs can be connected to a single input).
|
||||
|
||||
Connecting to empty space (drag to connect but unpress over empty space) is also context sensitive, it will supply
|
||||
@@ -86,7 +86,7 @@ While, for data, a contextual set/get/call menu will open:
|
||||
Adding nodes
|
||||
------------
|
||||
|
||||
Finally! We got to the fun part! But, before explaining in more detail what each type of node does,
|
||||
Finally! We got to the fun part! But, before explaining in more detail what each type of node does,
|
||||
let's take a short look at how nodes are most commonly added and dealt with.
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ Accessing scene nodes
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
One of the most common tasks is accessing Scene Tree Nodes (again, not to mistake with *Visual Script Nodes*).
|
||||
Dragging from the Scene Tree and dropping into the canvas will ask you to *call a method* (sometimes referred to as *member function*) on this node.
|
||||
Dragging from the Scene Tree and dropping into the canvas will ask you to *call a method* (sometimes referred to as *member function*) on this node.
|
||||
|
||||
.. image:: img/visual_script19.png
|
||||
|
||||
@@ -210,7 +210,7 @@ a brief overview of node types follows:
|
||||
Constants
|
||||
~~~~~~~~~
|
||||
|
||||
Constant nodes are nodes that provide values that, while not changing over time, can be useful as reference values.
|
||||
Constant nodes are nodes that provide values that, while not changing over time, can be useful as reference values.
|
||||
Most of the time they are integer or float.
|
||||
|
||||
.. image:: img/visual_script36.png
|
||||
@@ -273,14 +273,14 @@ As it can be seen above, there are two nodes available: A simple getter, and a s
|
||||
Scene Node
|
||||
^^^^^^^^^^
|
||||
|
||||
This is just a reference to a node in the tree, but it's easier to use this node by dragging the actual node
|
||||
This is just a reference to a node in the tree, but it's easier to use this node by dragging the actual node
|
||||
from the scene tree to the canvas (this will create it and configure it).
|
||||
|
||||
|
||||
Self
|
||||
^^^^
|
||||
|
||||
In some rare occasions, it may be desired to pass this Scene Node as argument.
|
||||
In some rare occasions, it may be desired to pass this Scene Node as argument.
|
||||
It can be used to call functions and set/get properties, or drag nodes (or event the node itself that has the script) from the Scene Tree to the canvas for this.
|
||||
|
||||
|
||||
@@ -342,7 +342,7 @@ Some data types in Godot (ie, arrays, dictionaries) are iterable. This means tha
|
||||
for each element that it has.
|
||||
|
||||
The Iterator node goes through all elements and, for each of them, it goes via the "each" sequence port,
|
||||
making the element available in the "elem" data port.
|
||||
making the element available in the "elem" data port.
|
||||
|
||||
When done, it goes via the "exit" sequence port.
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ Notice the text [perspective] under the toolbar, it is a button that opens a lis
|
||||
|
||||
|image20|
|
||||
|
||||
.. note:: Read :ref:`doc_introduction_to_3d` for more detail about **3D workspace**.
|
||||
.. note:: Read :ref:`doc_introduction_to_3d` for more detail about **3D workspace**.
|
||||
|
||||
The **Script** workspace is a complete code editor with a debugger, rich
|
||||
auto-completion, and built-in code reference. Press F3 to access it, and
|
||||
|
||||
@@ -70,11 +70,11 @@ F#, Boo or ClojureCLR. In practice however, C# is the only officially supported
|
||||
GDNative / C++
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Finally, one of our brightest additions for the 3.0 release:
|
||||
Finally, one of our brightest additions for the 3.0 release:
|
||||
GDNative allows scripting in C++ without needing to recompile (or even
|
||||
restart) Godot.
|
||||
|
||||
Any C++ version can be used, and mixing compiler brands and versions for the
|
||||
Any C++ version can be used, and mixing compiler brands and versions for the
|
||||
generated shared libraries works perfectly, thanks to our use of an internal C
|
||||
API Bridge.
|
||||
|
||||
@@ -182,7 +182,7 @@ signals in your own scripts.
|
||||
|
||||
In this step, we'll connect the "pressed" signal to a custom function. Forming
|
||||
connections is the first part and defining the custom function is the second part.
|
||||
For the first part, Godot provides two ways to create connections: through a
|
||||
For the first part, Godot provides two ways to create connections: through a
|
||||
visual interface the editor provides or through code.
|
||||
|
||||
While we will use the code method for the remainder of this tutorial series, let's
|
||||
@@ -199,7 +199,7 @@ button in the bottom right, you'll open up the connection creation dialogue.
|
||||
.. image:: img/connect_dialogue.png
|
||||
|
||||
In the bottom-left are the key things you need to create a connection: a node
|
||||
which implements the method you want to trigger (represented here as a
|
||||
which implements the method you want to trigger (represented here as a
|
||||
NodePath) and the name of the method to trigger.
|
||||
|
||||
The top-left section displays a list of your scene's nodes with the emitting
|
||||
@@ -246,7 +246,7 @@ Next, write a function which will be called when the button is pressed:
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
func _on_Button_pressed():
|
||||
func _on_Button_pressed():
|
||||
get_node("Label").text = "HELLO!"
|
||||
|
||||
.. code-tab:: csharp
|
||||
@@ -323,7 +323,7 @@ Why, hello there! Congratulations on scripting your first scene.
|
||||
|
||||
# Not for this case,
|
||||
# but just in case.
|
||||
get_node("Label/Button")
|
||||
get_node("Label/Button")
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
@@ -335,10 +335,10 @@ Also, remember that nodes are referenced by name, not by type.
|
||||
|
||||
.. note::
|
||||
|
||||
The right-hand panel of the connect dialogue is for binding specific
|
||||
values to the connected function's parameters. You can add and remove
|
||||
The right-hand panel of the connect dialogue is for binding specific
|
||||
values to the connected function's parameters. You can add and remove
|
||||
values of different types.
|
||||
|
||||
The code approach also enables this with a 4th ``Array`` parameter that
|
||||
is empty by default. Feel free to read up on the ``Object.connect``
|
||||
is empty by default. Feel free to read up on the ``Object.connect``
|
||||
method for more information.
|
||||
|
||||
@@ -7,9 +7,9 @@ Animation supported:
|
||||
- light animation
|
||||
- camera animation
|
||||
|
||||
Multiple Actions For Single Object
|
||||
Multiple Actions For Single Object
|
||||
----------------------------------
|
||||
In most games, one objects would have several animations to switch between.
|
||||
In most games, one objects would have several animations to switch between.
|
||||
This addon have a support for exporting multiple actions all at once into
|
||||
a single AnimationPlayer and makes it easy to switch actions.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Godot-Blender-Exporter
|
||||
======================
|
||||
|
||||
.. note:: This chapter relates to the blender plugin called "Godot-Blender-Exporter",
|
||||
.. note:: This chapter relates to the blender plugin called "Godot-Blender-Exporter",
|
||||
which can be downloaded here: https://github.com/godotengine/godot-blender-exporter
|
||||
|
||||
Details on exporting
|
||||
|
||||
@@ -4,7 +4,7 @@ Mesh
|
||||
Modifiers
|
||||
---------
|
||||
There is an exporting option :code:`Apply Modifiers` to
|
||||
control whether mesh modifiers are applied to the exported mesh.
|
||||
control whether mesh modifiers are applied to the exported mesh.
|
||||
|
||||
|
||||
Shape Key
|
||||
|
||||
@@ -7,9 +7,9 @@ Rest Bone
|
||||
---------
|
||||
|
||||
Armature object in Blender is exported as a Skeleton node along with
|
||||
rest position (transform in Godot) of bones.
|
||||
rest position (transform in Godot) of bones.
|
||||
|
||||
.. warning::
|
||||
.. warning::
|
||||
The three check boxes :code:`Inherit Rotation`, :code:`Inherit Scale`,
|
||||
:code:`Local Location` (colored in red) must be ticked when building
|
||||
armature in Blender, so that the exported bone transform would be
|
||||
|
||||
@@ -9,7 +9,7 @@ Why importing?
|
||||
Raw audio data in general is large and undesired. Godot provides two main
|
||||
options to import your audio data: WAV and OGG Vorbis.
|
||||
|
||||
Each has different advantages.
|
||||
Each has different advantages.
|
||||
* Wav files use raw data or light compression, require small amount of CPU to play back (hundreds of simultaneous voices in this format are fine), but take up significant space.
|
||||
* Ogg Vorbis files use a stronger compression that results in much smaller file size, but uses significantly more processor to play back.
|
||||
|
||||
@@ -49,10 +49,10 @@ As you can see above, sound effects become huge with reverb added.
|
||||
Trimming
|
||||
~~~~~~~~
|
||||
|
||||
One issue that happens often is that the waveform are exported with long
|
||||
One issue that happens often is that the waveform are exported with long
|
||||
silences at the beginning and at the end. These are inserted by
|
||||
DAWs when saving to a waveform, increase their size unnecessarily and
|
||||
add latency to the moment they are played back.
|
||||
add latency to the moment they are played back.
|
||||
|
||||
Importing as WAV with the Trimming option enabled solves
|
||||
this.
|
||||
|
||||
@@ -8,7 +8,7 @@ Why importing them?
|
||||
|
||||
In Godot 3+, image files are no longer native resources and they must be imported.
|
||||
The reason behind this is the large amount of configuration parameters that
|
||||
image files can be imported with.
|
||||
image files can be imported with.
|
||||
|
||||
This small tutorial will explain what these parameters are and how to best
|
||||
make use of them.
|
||||
@@ -69,7 +69,7 @@ advantages and disadvantages ( |good| = Best, |bad| =Worst ):
|
||||
HDR Mode
|
||||
~~~~~~~~
|
||||
|
||||
Godot supports high dynamic range textures (as .HDR or .EXR). These are mostly useful as high dynamic range equirectancular panorama skys (the internet
|
||||
Godot supports high dynamic range textures (as .HDR or .EXR). These are mostly useful as high dynamic range equirectancular panorama skys (the internet
|
||||
has plenty of if you look for them), which replace Cubemaps in Godot 2.x. Modern PCs support the BC6H VRAM format, but there are still plenty that do not.
|
||||
|
||||
If you want Godot to ensure full compatibility in for kind of textures, enable the "Force RGBE" option.
|
||||
@@ -108,7 +108,7 @@ When pixels become smaller than the screen, mipmaps kick in. This helps reduce t
|
||||
* Texture width and height must be powers of 2
|
||||
* Repeat must be enabled
|
||||
|
||||
Keep in mind the above when making phone games and applications, want to aim for full compatibility, and need mipmaps.
|
||||
Keep in mind the above when making phone games and applications, want to aim for full compatibility, and need mipmaps.
|
||||
|
||||
When doing 3D, mipmap should be turned on as this also improves performance (smaller versions of the texture are used for objects further away).
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ When dealing with 3D assets, Godot has a flexible and configurable importer.
|
||||
Godot works with *scenes*. This means that the entire scene being worked on in your favorite 3D DCC will be
|
||||
transferred as close as possible.
|
||||
|
||||
Godot supports the following 3D *scene file fomats*:
|
||||
Godot supports the following 3D *scene file fomats*:
|
||||
|
||||
* DAE (Collada), which is currently the most mature workflow.
|
||||
* GLTF 2.0. Both text and binary formats are supported. Godot has full support for it, but the format is new and gaining traction.
|
||||
@@ -25,7 +25,7 @@ Why not FBX?
|
||||
Most game engines use the FBX format for importing 3D scenes, which is
|
||||
definitely one of the most standardized in the industry. However, this
|
||||
format requires the use of a closed library from Autodesk which is
|
||||
distributed with a more restrictive licensing terms than Godot.
|
||||
distributed with a more restrictive licensing terms than Godot.
|
||||
|
||||
The plan is, sometime in the future, to offer a binary plug-in using GDNative.
|
||||
|
||||
@@ -53,7 +53,7 @@ Exporting ESCN files from Blender
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The most powerful one, called `godot-blender-exporter
|
||||
<https://github.com/godotengine/godot-blender-exporter>`__.
|
||||
<https://github.com/godotengine/godot-blender-exporter>`__.
|
||||
It uses .escn files which is kind of another name of .tscn file(Godot scene file),
|
||||
it keeps as much information as possible from a Blender scene.
|
||||
|
||||
@@ -99,7 +99,7 @@ Allows setting a specific name to the generated root node.
|
||||
Custom Script
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
A special script to process the whole scene after import can be provided.
|
||||
A special script to process the whole scene after import can be provided.
|
||||
This is great for post processing, changing materials, doing funny stuff
|
||||
with the geometry etc.
|
||||
|
||||
@@ -123,12 +123,12 @@ Storage
|
||||
|
||||
By default, Godot imports a single scene. This option allows specifying
|
||||
that nodes below the root will each be a separate scene and instanced
|
||||
into the imported one.
|
||||
into the imported one.
|
||||
|
||||
Of course, instancing such imported scenes in other places manually works too.
|
||||
|
||||
|
||||
Materials
|
||||
Materials
|
||||
~~~~~~~~~
|
||||
|
||||
Location
|
||||
@@ -262,7 +262,7 @@ following dialog will appear:
|
||||
|
||||
.. image:: img/scene_import4.png
|
||||
|
||||
In inherited scenes, the only limitations for modifications are:
|
||||
In inherited scenes, the only limitations for modifications are:
|
||||
|
||||
* Nodes can't be removed (but can be added anywhere).
|
||||
* Sub-Resources can't be edited (save them externally as described above for this)
|
||||
@@ -295,7 +295,7 @@ Option "-col" will work only for Mesh nodes. If it is detected, a child
|
||||
static collision node will be added, using the same geometry as the mesh.
|
||||
|
||||
However, it is often the case that the visual geometry is too complex or
|
||||
too un-smooth for collisions, which ends up not working well.
|
||||
too un-smooth for collisions, which ends up not working well.
|
||||
|
||||
To solve this, the "-colonly" modifier exists, which will remove the mesh upon
|
||||
import and create a :ref:`class_staticbody` collision instead.
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
Exporting for iOS
|
||||
=================
|
||||
|
||||
These are the steps to load a Godot project in Xcode. This allows you to
|
||||
build and deploy to an iOS device, build a release for the App Store, and
|
||||
These are the steps to load a Godot project in Xcode. This allows you to
|
||||
build and deploy to an iOS device, build a release for the App Store, and
|
||||
do everything else you can normally do with Xcode.
|
||||
|
||||
Requirements
|
||||
@@ -17,10 +17,10 @@ Requirements
|
||||
Export a Godot project to Xcode
|
||||
-------------------------------
|
||||
|
||||
In the Godot editor, open the **Export** window from the **Project** menu. When the
|
||||
Export window opens, click **Add..** and select **iOS**.
|
||||
In the Godot editor, open the **Export** window from the **Project** menu. When the
|
||||
Export window opens, click **Add..** and select **iOS**.
|
||||
|
||||
The following export options are required. Leaving any blank with cause the
|
||||
The following export options are required. Leaving any blank with cause the
|
||||
exporter to throw an error:
|
||||
|
||||
* In the **Application** category
|
||||
@@ -31,42 +31,42 @@ exporter to throw an error:
|
||||
|
||||
After you click **Export Project**, there are still two important options left:
|
||||
|
||||
* **Path** is an empty folder that will contain the exported Xcode project files.
|
||||
* **File** will be the name of the Xcode project and several project specific files and directories.
|
||||
* **Path** is an empty folder that will contain the exported Xcode project files.
|
||||
* **File** will be the name of the Xcode project and several project specific files and directories.
|
||||
|
||||
.. image:: img/ios_export_file.png
|
||||
|
||||
.. note:: This tutorial uses **exported_xcode_project_name**, but you will use your
|
||||
project's name. When you see **exported_xcode_project_name**
|
||||
in the following steps, replace it with the name you used instead.
|
||||
project's name. When you see **exported_xcode_project_name**
|
||||
in the following steps, replace it with the name you used instead.
|
||||
|
||||
When the export completes, the output folder should look like this:
|
||||
|
||||
.. image:: img/ios_export_output.png
|
||||
|
||||
Opening **exported_xcode_project_name.xcodeproj** lets you build and deploy
|
||||
like any other iOS app.
|
||||
Opening **exported_xcode_project_name.xcodeproj** lets you build and deploy
|
||||
like any other iOS app.
|
||||
|
||||
|
||||
Active development considerations
|
||||
---------------------------------
|
||||
|
||||
The above method creates an exported project that you can build for
|
||||
release, but you have to re-export every time you make a change in Godot.
|
||||
release, but you have to re-export every time you make a change in Godot.
|
||||
|
||||
While developing, you can speed this process up by linking your
|
||||
Godot project files directly into your app.
|
||||
Godot project files directly into your app.
|
||||
|
||||
In the following example:
|
||||
|
||||
* **exported_xcode_project_name** is the name of the exported iOS application (as above).
|
||||
* **godot_project_to_export** is the name of the Godot project.
|
||||
* **exported_xcode_project_name** is the name of the exported iOS application (as above).
|
||||
* **godot_project_to_export** is the name of the Godot project.
|
||||
|
||||
Steps to link an Godot project folder to Xcode
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
1. Start from an exported iOS project (follow the steps above).
|
||||
2. In Finder, drag the Godot project folder into the Xcode file browser.
|
||||
2. In Finder, drag the Godot project folder into the Xcode file browser.
|
||||
|
||||
.. image:: img/ios_export_add_dir.png
|
||||
|
||||
@@ -75,19 +75,19 @@ you will be able to continue to edit your Godot project in its current location.
|
||||
|
||||
.. image:: img/ios_export_file_ref.png
|
||||
|
||||
4. See the **godot_project_to_export** folder in the Xcode file browser.
|
||||
4. See the **godot_project_to_export** folder in the Xcode file browser.
|
||||
5. Delete **exported_xcode_project_name.pck** from the Xcode project.
|
||||
|
||||
.. image:: img/ios_export_delete_pck.png
|
||||
|
||||
6. Open **exported_xcode_project_name-Info.plist** and add a string property named
|
||||
**godot_path** (this is the real key name) with a value **godot_project_to_export**
|
||||
**godot_path** (this is the real key name) with a value **godot_project_to_export**
|
||||
(this is the name of your project)
|
||||
|
||||
.. image:: img/ios_export_set_path.png
|
||||
|
||||
That's it! You can now edit your project in the Godot editor and build it
|
||||
in Xcode when you want to run it on a device.
|
||||
in Xcode when you want to run it on a device.
|
||||
|
||||
Services for iOS
|
||||
----------------
|
||||
|
||||
@@ -28,7 +28,7 @@ As described in `UWP documentation <https://docs.microsoft.com/en-us/windows/uwp
|
||||
- available memory is 5GB
|
||||
- 4 exclusive CPU cores and 2 shared CPU cores
|
||||
- exclusive access to GPU power (100%)
|
||||
|
||||
|
||||
- Exceeding these memory limitations will cause allocation failures and the application will crash.
|
||||
|
||||
Creating a signing certificate
|
||||
|
||||
@@ -5,7 +5,7 @@ Export
|
||||
:maxdepth: 1
|
||||
:name: toc-learn-workflow-export
|
||||
|
||||
|
||||
|
||||
exporting_projects
|
||||
feature_tags
|
||||
exporting_for_pc
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Introduction
|
||||
------------
|
||||
|
||||
This tutorial explains how the 2D lighting works in the
|
||||
This tutorial explains how the 2D lighting works in the
|
||||
`lights and shadows <https://github.com/godotengine/godot-demo-projects/tree/master/2d/lights_and_shadows>`_ demo project.
|
||||
It begins with a brief description of the resources used final demo and then describes how
|
||||
to make a scene like the demo step by step.
|
||||
@@ -14,31 +14,31 @@ to make a scene like the demo step by step.
|
||||
.. image:: img/light_shadow_main.png
|
||||
|
||||
All the resources for this tutorial can be found in the `official demo repository <https://github.com/godotengine/godot-demo-projects>`_
|
||||
on github. I suggest you download it before starting. Alternatively,
|
||||
it can be downloaded from the Project Manager. Launch Godot and in the top
|
||||
on github. I suggest you download it before starting. Alternatively,
|
||||
it can be downloaded from the Project Manager. Launch Godot and in the top
|
||||
bar select "Templates" and search for "2D Lights and Shadows Demo".
|
||||
|
||||
Setup
|
||||
-----
|
||||
|
||||
For this demo we use four textures: two for the lights, one for the shadow casters,
|
||||
For this demo we use four textures: two for the lights, one for the shadow casters,
|
||||
and one for the background. I've included links to them all here if you want to download them
|
||||
separately from the demo.
|
||||
|
||||
The first is the background image (`bg.png <https://raw.githubusercontent.com/godotengine/godot-demo-projects/master/2d/lights_and_shadows/bg.png>`_)
|
||||
The first is the background image (`bg.png <https://raw.githubusercontent.com/godotengine/godot-demo-projects/master/2d/lights_and_shadows/bg.png>`_)
|
||||
used in the demo. You do not necessarily need a background, but we use one for the demo.
|
||||
|
||||
The second is a plain black image (`caster.png <https://raw.githubusercontent.com/godotengine/godot-demo-projects/master/2d/lights_and_shadows/caster.png>`_)
|
||||
to use as our shadow caster object. For a top down game this could be a wall or any
|
||||
The second is a plain black image (`caster.png <https://raw.githubusercontent.com/godotengine/godot-demo-projects/master/2d/lights_and_shadows/caster.png>`_)
|
||||
to use as our shadow caster object. For a top down game this could be a wall or any
|
||||
other object that casts a shadow.
|
||||
|
||||
Next is the light itself (`light.png <https://raw.githubusercontent.com/godotengine/godot-demo-projects/master/2d/lights_and_shadows/light.png>`_).
|
||||
If you click the link you will notice how large it is. The image you use
|
||||
for a light should cover the area you want your light to cover. This image is
|
||||
Next is the light itself (`light.png <https://raw.githubusercontent.com/godotengine/godot-demo-projects/master/2d/lights_and_shadows/light.png>`_).
|
||||
If you click the link you will notice how large it is. The image you use
|
||||
for a light should cover the area you want your light to cover. This image is
|
||||
1024x1024 pixels, so you should use it to cover 1024x1024 pixels in your game.
|
||||
|
||||
Lastly, we have the spotlight image (`spot.png <https://raw.githubusercontent.com/godotengine/godot-demo-projects/master/2d/lights_and_shadows/spot.png>`_).
|
||||
The demo uses a blob to show where the light is and the larger light
|
||||
Lastly, we have the spotlight image (`spot.png <https://raw.githubusercontent.com/godotengine/godot-demo-projects/master/2d/lights_and_shadows/spot.png>`_).
|
||||
The demo uses a blob to show where the light is and the larger light
|
||||
image to show the effect of the light upon the rest of the scene.
|
||||
|
||||
|
||||
@@ -51,24 +51,24 @@ The demo uses four different nodes:
|
||||
* :ref:`Light2D <class_Light2D>`
|
||||
* :ref:`LightOccluder2D <class_LightOccluder2D>`
|
||||
|
||||
:ref:`CanvasModulate<class_CanvasModulate>` is used to darken the scene.
|
||||
:ref:`CanvasModulate<class_CanvasModulate>` is used to darken the scene.
|
||||
|
||||
:ref:`Sprites <class_Sprite>` are used to display the textures for the light blobs, the
|
||||
:ref:`Sprites <class_Sprite>` are used to display the textures for the light blobs, the
|
||||
background, and for the shadow casters.
|
||||
|
||||
:ref:`Light2Ds <class_Light2D>` are used to light the scene. The way a light typically works
|
||||
:ref:`Light2Ds <class_Light2D>` are used to light the scene. The way a light typically works
|
||||
is by adding a selected texture over the rest of the scene to simulate lighting. But it can be
|
||||
used in other ways, for example masking out parts of the scene.
|
||||
|
||||
:ref:`LightOccluder2Ds <class_LightOccluder2D>` are used to tell the shader which parts of
|
||||
the scene cast shadows. The shadows appear only on areas covered by the :ref:`Light2D <class_Light2D>` and
|
||||
:ref:`LightOccluder2Ds <class_LightOccluder2D>` are used to tell the shader which parts of
|
||||
the scene cast shadows. The shadows appear only on areas covered by the :ref:`Light2D <class_Light2D>` and
|
||||
their direction is based on the center of the :ref:`Light <class_Light2D>`.
|
||||
|
||||
Lights
|
||||
------
|
||||
|
||||
:ref:`Lights <class_Light2D>` cover the entire extent of their respective Texture. They use additive
|
||||
blending to add the color of their texture to the scene.
|
||||
:ref:`Lights <class_Light2D>` cover the entire extent of their respective Texture. They use additive
|
||||
blending to add the color of their texture to the scene.
|
||||
|
||||
.. image:: img/light_shadow_light.png
|
||||
|
||||
@@ -81,12 +81,12 @@ blending to add the color of their texture to the scene.
|
||||
``Mix`` mixes the color of the light with the underlying scene. The resulting brightness is
|
||||
halfway between the color of the light and the color underneath.
|
||||
|
||||
``Mask`` is used to mask out areas that are covered by the light. Masked out areas are hidden or revealed based on
|
||||
``Mask`` is used to mask out areas that are covered by the light. Masked out areas are hidden or revealed based on
|
||||
the color of the light.
|
||||
|
||||
For the demo the lights have two components, the :ref:`Light <class_Light2D>` itself (which
|
||||
is the effect of the light), and a :ref:`Sprite <class_Sprite>` blob which is an image showing the
|
||||
location of the light source. A child :ref:`Sprite <class_Sprite>` is not necessary to make a
|
||||
For the demo the lights have two components, the :ref:`Light <class_Light2D>` itself (which
|
||||
is the effect of the light), and a :ref:`Sprite <class_Sprite>` blob which is an image showing the
|
||||
location of the light source. A child :ref:`Sprite <class_Sprite>` is not necessary to make a
|
||||
:ref:`Light <class_Light2D>` work.
|
||||
|
||||
.. image:: img/light_shadow_light_blob.png
|
||||
@@ -96,82 +96,82 @@ Shadows
|
||||
|
||||
Shadows are made by intersecting a :ref:`Light <class_Light2D>` with a :ref:`LightOccluder2D <class_LightOccluder2D>`.
|
||||
|
||||
By default shadows are turned off. To turn them on click on the :ref:`Light <class_Light2D>`
|
||||
By default shadows are turned off. To turn them on click on the :ref:`Light <class_Light2D>`
|
||||
and under the Shadows section check ``Enabled``.
|
||||
|
||||
In the demo we are using a :ref:`Sprite <class_Sprite>` with a Texture on it to make the "Shadow Casters",
|
||||
but in reality all you need is a couple of :ref:`LightOccluder2Ds <class_LightOccluder2D>`. By itself
|
||||
the :ref:`LightOccluder2D <class_LightOccluder2D>` looks like a dark spot and in this demo the :ref:`Sprite <class_Sprite>` is
|
||||
In the demo we are using a :ref:`Sprite <class_Sprite>` with a Texture on it to make the "Shadow Casters",
|
||||
but in reality all you need is a couple of :ref:`LightOccluder2Ds <class_LightOccluder2D>`. By itself
|
||||
the :ref:`LightOccluder2D <class_LightOccluder2D>` looks like a dark spot and in this demo the :ref:`Sprite <class_Sprite>` is
|
||||
just a black square.
|
||||
|
||||
Step by step
|
||||
------------
|
||||
|
||||
Now that we have covered the basics of the nodes being used, we can now walk step by step through
|
||||
Now that we have covered the basics of the nodes being used, we can now walk step by step through
|
||||
the process of making a scene like the one found in the demo.
|
||||
|
||||
First add a :ref:`Sprite <class_Sprite>` and set its texture to the `background image <https://raw.githubusercontent.com/godotengine/godot-demo-projects/master/2d/lights_and_shadows/bg.png>`_. For your game this can be any
|
||||
background you choose. For this style of shadow it is most likely to be a floor texture.
|
||||
First add a :ref:`Sprite <class_Sprite>` and set its texture to the `background image <https://raw.githubusercontent.com/godotengine/godot-demo-projects/master/2d/lights_and_shadows/bg.png>`_. For your game this can be any
|
||||
background you choose. For this style of shadow it is most likely to be a floor texture.
|
||||
|
||||
.. image:: img/light_shadow_background.png
|
||||
|
||||
Next create three :ref:`Light2D's <class_Light2D>` and set their textures to the `light image <https://raw.githubusercontent.com/godotengine/godot-demo-projects/master/2d/lights_and_shadows/light.png>`_. You can alter their
|
||||
color in the top section. By default shadows are turned off and the ``mode`` is set to ``add``. This
|
||||
Next create three :ref:`Light2D's <class_Light2D>` and set their textures to the `light image <https://raw.githubusercontent.com/godotengine/godot-demo-projects/master/2d/lights_and_shadows/light.png>`_. You can alter their
|
||||
color in the top section. By default shadows are turned off and the ``mode`` is set to ``add``. This
|
||||
means that each light adds its own color to whatever is underneath.
|
||||
|
||||
.. image:: img/light_shadow_all_lights_no_blob.png
|
||||
|
||||
Next add a child :ref:`Sprite <class_Sprite>` to each of the :ref:`Light <class_Light2D>` nodes, and set
|
||||
the :ref:`Sprite's <class_Sprite>` texture to the `blob image <https://raw.githubusercontent.com/godotengine/godot-demo-projects/master/2d/lights_and_shadows/spot.png>`_. Each of these
|
||||
should stay centered on the :ref:`Light <class_Light2D>` node. The blob is the image of the light
|
||||
itself while the :ref:`Light <class_Light2D>` shows the effect that the light has on the scene. The
|
||||
:ref:`LightOccluder2D's <class_LightOccluder2D>` will treat the position of the light as the center of the :ref:`Light <class_Light2D>`
|
||||
Next add a child :ref:`Sprite <class_Sprite>` to each of the :ref:`Light <class_Light2D>` nodes, and set
|
||||
the :ref:`Sprite's <class_Sprite>` texture to the `blob image <https://raw.githubusercontent.com/godotengine/godot-demo-projects/master/2d/lights_and_shadows/spot.png>`_. Each of these
|
||||
should stay centered on the :ref:`Light <class_Light2D>` node. The blob is the image of the light
|
||||
itself while the :ref:`Light <class_Light2D>` shows the effect that the light has on the scene. The
|
||||
:ref:`LightOccluder2D's <class_LightOccluder2D>` will treat the position of the light as the center of the :ref:`Light <class_Light2D>`
|
||||
node which is why we want the blob to be centered on its parent :ref:`Light <class_Light2D>`.
|
||||
|
||||
.. image:: img/light_shadow_all_lights.png
|
||||
|
||||
.. note:: At the time of writing, 3.0 is the stable release version. The 3.1 development branch contains
|
||||
many changes to the animation system, so the animations in the demo will not be covered here.
|
||||
.. note:: At the time of writing, 3.0 is the stable release version. The 3.1 development branch contains
|
||||
many changes to the animation system, so the animations in the demo will not be covered here.
|
||||
See :ref:Introduction to 2D Animation. <doc_introduction_2d> for more information.
|
||||
|
||||
Right now the scene should look too bright. This is because all three lights are adding color to the scene.
|
||||
Right now the scene should look too bright. This is because all three lights are adding color to the scene.
|
||||
This is why the demo uses a :ref:`CanvasModulate <class_CanvasModulate>` in the scene. The
|
||||
:ref:`CanvasModulate <class_CanvasModulate>` multiples the entire viewport by a specific color.
|
||||
|
||||
Add a :ref:`CanvasModulate <class_CanvasModulate>` to the scene and set its color to ``rgb(70, 70, 70)``.
|
||||
Add a :ref:`CanvasModulate <class_CanvasModulate>` to the scene and set its color to ``rgb(70, 70, 70)``.
|
||||
This will make the scene sufficiently dark to see the effects of the lights distinctly.
|
||||
|
||||
.. image:: img/light_shadow_ambient.png
|
||||
|
||||
Now we add the shadow casters.
|
||||
|
||||
The demo uses a :ref:`Node <class_Node2D>` named "casters" to organize the shadow casters. Add a
|
||||
:ref:`Node2D <class_Node2D>` to the scene. It will be used to group all the shadow casters together.
|
||||
The demo uses a :ref:`Node <class_Node2D>` named "casters" to organize the shadow casters. Add a
|
||||
:ref:`Node2D <class_Node2D>` to the scene. It will be used to group all the shadow casters together.
|
||||
This way we can show and hide them all at the same time.
|
||||
|
||||
Each shadow caster is made of a :ref:`Sprite <class_Sprite>`, with a :ref:`LightOccluder2D <class_LightOccluder2D>`
|
||||
child. For the demo the :ref:`Sprite <class_Sprite>` has a texture
|
||||
set to the `caster image <https://raw.githubusercontent.com/godotengine/godot-demo-projects/master/2d/lights_and_shadows/caster.png>`_ and nothing else. The child :ref:`LightOccluder2D <class_LightOccluder2D>` is where all the magic happens. In a
|
||||
game the :ref:`Sprite <class_Sprite>` could be more than a black box; it could be an image of whatever object is casting
|
||||
Each shadow caster is made of a :ref:`Sprite <class_Sprite>`, with a :ref:`LightOccluder2D <class_LightOccluder2D>`
|
||||
child. For the demo the :ref:`Sprite <class_Sprite>` has a texture
|
||||
set to the `caster image <https://raw.githubusercontent.com/godotengine/godot-demo-projects/master/2d/lights_and_shadows/caster.png>`_ and nothing else. The child :ref:`LightOccluder2D <class_LightOccluder2D>` is where all the magic happens. In a
|
||||
game the :ref:`Sprite <class_Sprite>` could be more than a black box; it could be an image of whatever object is casting
|
||||
the shadow: a wall, a magical chest, or anything else.
|
||||
|
||||
.. image:: img/light_shadow_sprites.png
|
||||
|
||||
:ref:`LightOccluder2Ds <class_LightOccluder2D>` tell the game what shape the occluder has. They hold
|
||||
an :ref:`OccluderPolygon2D <class_OccluderPolygon2D>` which is a container
|
||||
for a polygon and some other information. For this demo, since our wall is a square, we
|
||||
:ref:`LightOccluder2Ds <class_LightOccluder2D>` tell the game what shape the occluder has. They hold
|
||||
an :ref:`OccluderPolygon2D <class_OccluderPolygon2D>` which is a container
|
||||
for a polygon and some other information. For this demo, since our wall is a square, we
|
||||
set ``Polygon`` to a square. The other default settings are fine.
|
||||
|
||||
The first setting, ``Closed`` can be either ``on`` or ``off``. A closed polygon occludes light
|
||||
The first setting, ``Closed`` can be either ``on`` or ``off``. A closed polygon occludes light
|
||||
coming from all directions. An open polygon only occludes light from one direction
|
||||
|
||||
``Cull Mode`` lets you select which direction gets culled. The default is ``Disabled``, meaning the occluder
|
||||
will cast a shadow no matter which side the light is on. The other two settings ``Clockwise`` and
|
||||
``Counter-Clockwise`` refer to the winding order of the vertices of the polygon. The winding order
|
||||
is used to determine which side of the line is inside the polygon. Only outward facing lines cast shadows.
|
||||
``Cull Mode`` lets you select which direction gets culled. The default is ``Disabled``, meaning the occluder
|
||||
will cast a shadow no matter which side the light is on. The other two settings ``Clockwise`` and
|
||||
``Counter-Clockwise`` refer to the winding order of the vertices of the polygon. The winding order
|
||||
is used to determine which side of the line is inside the polygon. Only outward facing lines cast shadows.
|
||||
|
||||
To illustrate the difference, here is an image of a :ref:`LightOccluder2D <class_LightOccluder2D>` with ``Closed``
|
||||
set to ``off`` in the corresponding :ref:`OccluderPolygon2D <class_OccluderPolygon2D>` so that the
|
||||
set to ``off`` in the corresponding :ref:`OccluderPolygon2D <class_OccluderPolygon2D>` so that the
|
||||
lines of the polygon can be seen:
|
||||
|
||||
.. image:: img/light_shadow_cull_disabled.png
|
||||
@@ -188,18 +188,18 @@ lines of the polygon can be seen:
|
||||
If ``Closed`` was set to ``on`` there would be an additional vertical line on the
|
||||
left which would cast a shadow as well.
|
||||
|
||||
When you have added the :ref:`LightOccluder2Ds <class_LightOccluder2D>` the shadows still won't
|
||||
appear. You need to go back into the :ref:`Light2Ds <class_Light2D>` and under the Shadow
|
||||
When you have added the :ref:`LightOccluder2Ds <class_LightOccluder2D>` the shadows still won't
|
||||
appear. You need to go back into the :ref:`Light2Ds <class_Light2D>` and under the Shadow
|
||||
section set ``Enable`` to ``on``. This turns on shadows with hard edges like in the image below.
|
||||
|
||||
.. image:: img/light_shadow_filter0_pcf0.png
|
||||
|
||||
To give the shadows that nice soft edge look we set the variables ``filter``, ``filter smooth``, and
|
||||
``gradient length``. Godot supports `Percentage Closer Filtering <https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch11.html>`_
|
||||
(PCF) which takes multiple samples of the shadow map around a pixel and blurs them to create
|
||||
a smooth shadow effect. The higher the number of samples the smoother the shadow will
|
||||
look but the slower it will run. That is why Godot provides 3-13 samples by default and allows you to choose.
|
||||
The demo uses PCF7.
|
||||
To give the shadows that nice soft edge look we set the variables ``filter``, ``filter smooth``, and
|
||||
``gradient length``. Godot supports `Percentage Closer Filtering <https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch11.html>`_
|
||||
(PCF) which takes multiple samples of the shadow map around a pixel and blurs them to create
|
||||
a smooth shadow effect. The higher the number of samples the smoother the shadow will
|
||||
look but the slower it will run. That is why Godot provides 3-13 samples by default and allows you to choose.
|
||||
The demo uses PCF7.
|
||||
|
||||
.. image:: img/light_shadow_normal.png
|
||||
|
||||
@@ -208,27 +208,27 @@ The demo uses PCF7.
|
||||
|
||||
.. image:: img/light_shadow_pcf13.png
|
||||
|
||||
.. note:: ``filter`` is set to ``PCF13``. Notice how the shadow becomes wider, this is because the
|
||||
.. note:: ``filter`` is set to ``PCF13``. Notice how the shadow becomes wider, this is because the
|
||||
distance between samples is based on the variable ``filter smooth``.
|
||||
|
||||
In order to make use of filtering you need to set the ``filter smooth`` variable.
|
||||
This dictates how far apart the samples are. If you want the soft area to extend quite far you can increase
|
||||
the size of ``filter smooth``. However with low samples and a large filter smooth you can see lines
|
||||
In order to make use of filtering you need to set the ``filter smooth`` variable.
|
||||
This dictates how far apart the samples are. If you want the soft area to extend quite far you can increase
|
||||
the size of ``filter smooth``. However with low samples and a large filter smooth you can see lines
|
||||
forming between the samples.
|
||||
|
||||
.. image:: img/light_shadow_filter30.png
|
||||
|
||||
.. note:: ``filter smooth` is set to ``30``.
|
||||
|
||||
The different :ref:`Light <class_Light2D>` nodes in the demo use different values for filter smooth.
|
||||
The different :ref:`Light <class_Light2D>` nodes in the demo use different values for filter smooth.
|
||||
Play around with it and see what you like.
|
||||
|
||||
.. image:: img/light_shadow_filter0.png
|
||||
|
||||
.. note:: ``filter smooth`` is set to ``0``.
|
||||
|
||||
Lastly there is the variable ``gradient length``. For some smooth shadows it is preferable to not have the
|
||||
shadow start immediately on the object as this produces a hard edge. The gradient length variable creates
|
||||
Lastly there is the variable ``gradient length``. For some smooth shadows it is preferable to not have the
|
||||
shadow start immediately on the object as this produces a hard edge. The gradient length variable creates
|
||||
a smooth gradient to begin the shadow to reduce the effect of the hard edge.
|
||||
|
||||
.. image:: img/light_shadow_grad0.png
|
||||
@@ -240,5 +240,5 @@ a smooth gradient to begin the shadow to reduce the effect of the hard edge.
|
||||
.. note:: ``gradient length`` is set to ``10``.
|
||||
|
||||
You will need to play around with settings a bit to find ones that suit your project. There is no right solution
|
||||
for everyone, which is why Godot provides so much flexibility. Just keep in mind that the higher ``filter``
|
||||
for everyone, which is why Godot provides so much flexibility. Just keep in mind that the higher ``filter``
|
||||
set the more expensive the shadows will be.
|
||||
|
||||
@@ -123,7 +123,7 @@ while up/down moves it forward or backward in whatever direction it's facing.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
|
||||
extends KinematicBody2D
|
||||
|
||||
export (int) var speed = 200
|
||||
@@ -148,7 +148,7 @@ while up/down moves it forward or backward in whatever direction it's facing.
|
||||
get_input()
|
||||
rotation += rotation_dir * rotation_speed * delta
|
||||
move_and_slide(velocity)
|
||||
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
using Godot;
|
||||
@@ -192,7 +192,7 @@ while up/down moves it forward or backward in whatever direction it's facing.
|
||||
MoveAndSlide(velocity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Here we've added two new variables to track our rotation direction and speed.
|
||||
Again, pressing both keys at once will cancel out and result in no rotation.
|
||||
The rotation is applied directly to the body's ``rotation`` property.
|
||||
@@ -213,7 +213,7 @@ is set by the mouse position instead of the keyboard. The character will always
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
|
||||
extends KinematicBody2D
|
||||
|
||||
export (int) var speed = 200
|
||||
@@ -271,7 +271,7 @@ could get the same effect by setting the angle like this:
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
|
||||
rotation = get_global_mouse_position().angle_to_point(position)
|
||||
|
||||
.. code-tab:: csharp
|
||||
@@ -289,7 +289,7 @@ on the screen will cause the player to move to the target location.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
|
||||
extends KinematicBody2D
|
||||
|
||||
export (int) var speed = 200
|
||||
@@ -306,7 +306,7 @@ on the screen will cause the player to move to the target location.
|
||||
# rotation = velocity.angle()
|
||||
if (target - position).length() > 5:
|
||||
move_and_slide(velocity)
|
||||
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
using Godot;
|
||||
@@ -337,19 +337,19 @@ on the screen will cause the player to move to the target location.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Note the ``length()`` check we make prior to movement. Without this test,
|
||||
the body would "jitter" upon reaching the target position, as it moves
|
||||
slightly past the position and tries to move back, only to move too far and
|
||||
repeat.
|
||||
repeat.
|
||||
|
||||
Uncommenting the ``rotation`` line will also turn the body to point in its
|
||||
direction of motion if you prefer.
|
||||
|
||||
.. tip:: This technique can also be used as the basis of a "following" character.
|
||||
The ``target`` position can be that of any object you want to move to.
|
||||
|
||||
|
||||
Summary
|
||||
-------
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ In this part we're going to add grenades to the player, give the player the abil
|
||||
|
||||
.. note:: You are assumed to have finished :ref:`doc_fps_tutorial_part_four` before moving on to this part of the tutorial.
|
||||
The finished project from :ref:`doc_fps_tutorial_part_four` will be the starting project for part 5
|
||||
|
||||
|
||||
Let's get started!
|
||||
|
||||
Adding grenades
|
||||
@@ -34,7 +34,7 @@ coordinates instead of local coordinates, so we have ``Local Coords`` unchecked
|
||||
Let's write the code needed for the grenade. Select ``Grenade`` and make a new script called ``Grenade.gd``. Add the following:
|
||||
|
||||
::
|
||||
|
||||
|
||||
extends RigidBody
|
||||
|
||||
const GRENADE_DAMAGE = 60
|
||||
@@ -55,35 +55,35 @@ Let's write the code needed for the grenade. Select ``Grenade`` and make a new s
|
||||
grenade_mesh = $Grenade
|
||||
blast_area = $Blast_Area
|
||||
explosion_particles = $Explosion
|
||||
|
||||
|
||||
explosion_particles.emitting = false
|
||||
explosion_particles.one_shot = true
|
||||
|
||||
func _process(delta):
|
||||
|
||||
|
||||
if grenade_timer < GRENADE_TIME:
|
||||
grenade_timer += delta
|
||||
return
|
||||
else:
|
||||
if explosion_wait_timer <= 0:
|
||||
explosion_particles.emitting = true
|
||||
|
||||
|
||||
grenade_mesh.visible = false
|
||||
rigid_shape.disabled = true
|
||||
|
||||
|
||||
mode = RigidBody.MODE_STATIC
|
||||
|
||||
|
||||
var bodies = blast_area.get_overlapping_bodies()
|
||||
for body in bodies:
|
||||
if body.has_method("bullet_hit"):
|
||||
body.bullet_hit(GRENADE_DAMAGE, body.global_transform.looking_at(global_transform.origin, Vector3(0,1,0)) )
|
||||
|
||||
|
||||
# This would be the perfect place to play a sound!
|
||||
|
||||
|
||||
|
||||
|
||||
if explosion_wait_timer < EXPLOSION_WAIT_TIME:
|
||||
explosion_wait_timer += delta
|
||||
|
||||
|
||||
if explosion_wait_timer >= EXPLOSION_WAIT_TIME:
|
||||
queue_free()
|
||||
|
||||
@@ -93,7 +93,7 @@ Let's go over what's happening, starting with the class variables:
|
||||
* ``GRENADE_TIME``: The amount of time the grenade takes (in seconds) to explode once it's created/thrown.
|
||||
* ``grenade_timer``: A variable for tracking how long the grenade has been created/thrown.
|
||||
* ``EXPLOSION_WAIT_TIME``: The amount of time needed (in seconds) to wait before we destroy the grenade scene after the explosion
|
||||
* ``explosion_wait_timer``: A variable for tracking how much time has passed since the grenade exploded.
|
||||
* ``explosion_wait_timer``: A variable for tracking how much time has passed since the grenade exploded.
|
||||
* ``rigid_shape``: The :ref:`CollisionShape <class_CollisionShape>` for the grenade's :ref:`RigidBody <class_RigidBody>`.
|
||||
* ``grenade_mesh``: The :ref:`MeshInstance <class_MeshInstance>` for the grenade.
|
||||
* ``blast_area``: The blast :ref:`Area <class_Area>` used to damage things when the grenade explodes.
|
||||
@@ -154,7 +154,7 @@ the environment and needs to stick to something.
|
||||
Select ``Sticky_Grenade`` and make a new script called ``Sticky_Grenade.gd``. Add the following:
|
||||
|
||||
::
|
||||
|
||||
|
||||
extends RigidBody
|
||||
|
||||
const GRENADE_DAMAGE = 40
|
||||
@@ -180,67 +180,67 @@ Select ``Sticky_Grenade`` and make a new script called ``Sticky_Grenade.gd``. Ad
|
||||
grenade_mesh = $Sticky_Grenade
|
||||
blast_area = $Blast_Area
|
||||
explosion_particles = $Explosion
|
||||
|
||||
|
||||
explosion_particles.emitting = false
|
||||
explosion_particles.one_shot = true
|
||||
|
||||
|
||||
$Sticky_Area.connect("body_entered", self, "collided_with_body")
|
||||
|
||||
|
||||
func collided_with_body(body):
|
||||
|
||||
|
||||
if body == self:
|
||||
return
|
||||
|
||||
|
||||
if player_body != null:
|
||||
if body == player_body:
|
||||
return
|
||||
|
||||
|
||||
if attached == false:
|
||||
attached = true
|
||||
attach_point = Spatial.new()
|
||||
body.add_child(attach_point)
|
||||
attach_point.global_transform.origin = global_transform.origin
|
||||
|
||||
|
||||
rigid_shape.disabled = true
|
||||
|
||||
|
||||
mode = RigidBody.MODE_STATIC
|
||||
|
||||
|
||||
func _process(delta):
|
||||
|
||||
|
||||
if attached == true:
|
||||
if attach_point != null:
|
||||
global_transform.origin = attach_point.global_transform.origin
|
||||
|
||||
|
||||
if grenade_timer < GRENADE_TIME:
|
||||
grenade_timer += delta
|
||||
return
|
||||
else:
|
||||
if explosion_wait_timer <= 0:
|
||||
explosion_particles.emitting = true
|
||||
|
||||
|
||||
grenade_mesh.visible = false
|
||||
rigid_shape.disabled = true
|
||||
|
||||
|
||||
mode = RigidBody.MODE_STATIC
|
||||
|
||||
|
||||
var bodies = blast_area.get_overlapping_bodies()
|
||||
for body in bodies:
|
||||
if body.has_method("bullet_hit"):
|
||||
body.bullet_hit(GRENADE_DAMAGE, body.global_transform.looking_at(global_transform.origin, Vector3(0,1,0)) )
|
||||
|
||||
|
||||
# This would be the perfect place to play a sound!
|
||||
|
||||
|
||||
|
||||
|
||||
if explosion_wait_timer < EXPLOSION_WAIT_TIME:
|
||||
explosion_wait_timer += delta
|
||||
|
||||
|
||||
if explosion_wait_timer >= EXPLOSION_WAIT_TIME:
|
||||
if attach_point != null:
|
||||
attach_point.queue_free()
|
||||
queue_free()
|
||||
|
||||
|
||||
The code above is almost identical to the code for ``Grenade.gd``, so let's just go over what's changed.
|
||||
|
||||
First, we have a few more class variables:
|
||||
@@ -309,7 +309,7 @@ the rotation of ``Grenade_Toss_Pos``, you can change the angle the grenades are
|
||||
Okay, now let's start making the grenades work with the player. Add the following class variables to ``Player.gd``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
var grenade_amounts = {"Grenade":2, "Sticky Grenade":2}
|
||||
var current_grenade = "Grenade"
|
||||
var grenade_scene = preload("res://Grenade.tscn")
|
||||
@@ -321,7 +321,7 @@ Okay, now let's start making the grenades work with the player. Add the followin
|
||||
* ``grenade_scene``: The grenade scene we worked on earlier.
|
||||
* ``sticky_grenade_scene``: The sticky grenade scene we worked on earlier.
|
||||
* ``GRENADE_THROW_FORCE``: The force at which the player will throw the grenades at.
|
||||
|
||||
|
||||
Most of these variables are similar to how we have our weapons set up.
|
||||
|
||||
.. tip:: While it's possible to make a more modular grenade system, I found it was not worth the additional complexity for just two grenades.
|
||||
@@ -332,7 +332,7 @@ ______
|
||||
Now we need to add some code in ``_process_input`` Add the following to ``_process_input``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
# ----------------------------------
|
||||
# Changing and throwing grenades
|
||||
|
||||
@@ -345,7 +345,7 @@ Now we need to add some code in ``_process_input`` Add the following to ``_proce
|
||||
if Input.is_action_just_pressed("fire_grenade"):
|
||||
if grenade_amounts[current_grenade] > 0:
|
||||
grenade_amounts[current_grenade] -= 1
|
||||
|
||||
|
||||
var grenade_clone
|
||||
if current_grenade == "Grenade":
|
||||
grenade_clone = grenade_scene.instance()
|
||||
@@ -353,12 +353,12 @@ Now we need to add some code in ``_process_input`` Add the following to ``_proce
|
||||
grenade_clone = sticky_grenade_scene.instance()
|
||||
# Sticky grenades will stick to the player if we do not pass ourselves
|
||||
grenade_clone.player_body = self
|
||||
|
||||
|
||||
get_tree().root.add_child(grenade_clone)
|
||||
grenade_clone.global_transform = $Rotation_Helper/Grenade_Toss_Pos.global_transform
|
||||
grenade_clone.apply_impulse(Vector3(0,0,0), grenade_clone.global_transform.basis.z * GRENADE_THROW_FORCE)
|
||||
# ----------------------------------
|
||||
|
||||
|
||||
Let's go over what's happening here.
|
||||
|
||||
First, we check to see if the ``change_grenade`` action has just been pressed. If it has, we then check to see which grenade the player is
|
||||
@@ -383,7 +383,7 @@ We still need a way to show the player how many grenades are left, and we should
|
||||
First, let's change some of the code in ``Player.gd`` to show how many grenades are left. Change ``process_UI`` to the following:
|
||||
|
||||
::
|
||||
|
||||
|
||||
func process_UI(delta):
|
||||
if current_weapon_name == "UNARMED" or current_weapon_name == "KNIFE":
|
||||
# First line: Health, second line: Grenades
|
||||
@@ -401,7 +401,7 @@ Now we'll show how many grenades the player has left in the UI.
|
||||
While we're still in ``Player.gd``, let's add a function to add grenades to the player. Add the following function to ``Player.gd``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
func add_grenade(additional_grenade):
|
||||
grenade_amounts[current_grenade] += additional_grenade
|
||||
grenade_amounts[current_grenade] = clamp(grenade_amounts[current_grenade], 0, 4)
|
||||
@@ -410,7 +410,7 @@ Now we can add a grenade using ``add_grenade``, and it will automatically be cla
|
||||
|
||||
.. tip:: You can change the ``4`` to a constant if you want. You'd need to make a new global constant, something like ``MAX_GRENADES``, and
|
||||
then change the clamp from ``clamp(grenade_amounts[current_grenade], 0, 4)`` to ``clamp(grenade_amounts[current_grenade], 0, MAX_GRENADES)``
|
||||
|
||||
|
||||
If you do not want to limit how many grenades the player can carry, remove the line that clamps the grenades altogether!
|
||||
|
||||
Now we have a function to add grenades, let's open up ``AmmoPickup.gd`` and use it!
|
||||
@@ -418,13 +418,13 @@ Now we have a function to add grenades, let's open up ``AmmoPickup.gd`` and use
|
||||
Open up ``AmmoPickup.gd`` and go to the ``trigger_body_entered`` function. Change it to the following:
|
||||
|
||||
::
|
||||
|
||||
|
||||
func trigger_body_entered(body):
|
||||
if body.has_method("add_ammo"):
|
||||
body.add_ammo(AMMO_AMOUNTS[kit_size])
|
||||
respawn_timer = RESPAWN_TIME
|
||||
kit_size_change_values(kit_size, false)
|
||||
|
||||
|
||||
if body.has_method("add_grenade"):
|
||||
body.add_grenade(GRENADE_AMOUNTS[kit_size])
|
||||
respawn_timer = RESPAWN_TIME
|
||||
@@ -436,9 +436,9 @@ You may have noticed we are using a new constant we have not defined yet, ``GREN
|
||||
to ``AmmoPickup.gd`` with the other class variables:
|
||||
|
||||
::
|
||||
|
||||
|
||||
const GRENADE_AMOUNTS = [2, 0]
|
||||
|
||||
|
||||
* ``GRENADE_AMOUNTS``: The amount of grenades each pick up contains.
|
||||
|
||||
Notice how the second element in ``GRENADE_AMOUNTS`` is ``0``. This is so the small ammo pick up does not give the player
|
||||
@@ -457,7 +457,7 @@ Next let's give the player the ability to pick up and throw :ref:`RigidBody <cla
|
||||
Open up ``Player.gd`` and add the following class variables:
|
||||
|
||||
::
|
||||
|
||||
|
||||
var grabbed_object = null
|
||||
const OBJECT_THROW_FORCE = 120
|
||||
const OBJECT_GRAB_DISTANCE = 7
|
||||
@@ -471,35 +471,35 @@ Open up ``Player.gd`` and add the following class variables:
|
||||
With that done, all we need to do is add some code to ``process_input``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
# ----------------------------------
|
||||
# Grabbing and throwing objects
|
||||
|
||||
if Input.is_action_just_pressed("fire") and current_weapon_name == "UNARMED":
|
||||
if grabbed_object == null:
|
||||
var state = get_world().direct_space_state
|
||||
|
||||
|
||||
var center_position = get_viewport().size/2
|
||||
var ray_from = camera.project_ray_origin(center_position)
|
||||
var ray_to = ray_from + camera.project_ray_normal(center_position) * OBJECT_GRAB_RAY_DISTANCE
|
||||
|
||||
|
||||
var ray_result = state.intersect_ray(ray_from, ray_to, [self, $Rotation_Helper/Gun_Fire_Points/Knife_Point/Area])
|
||||
if ray_result != null:
|
||||
if ray_result["collider"] is RigidBody:
|
||||
grabbed_object = ray_result["collider"]
|
||||
grabbed_object.mode = RigidBody.MODE_STATIC
|
||||
|
||||
|
||||
grabbed_object.collision_layer = 0
|
||||
grabbed_object.collision_mask = 0
|
||||
|
||||
|
||||
else:
|
||||
grabbed_object.mode = RigidBody.MODE_RIGID
|
||||
|
||||
|
||||
grabbed_object.apply_impulse(Vector3(0,0,0), -camera.global_transform.basis.z.normalized() * OBJECT_THROW_FORCE)
|
||||
|
||||
|
||||
grabbed_object.collision_layer = 1
|
||||
grabbed_object.collision_mask = 1
|
||||
|
||||
|
||||
grabbed_object = null
|
||||
|
||||
if grabbed_object != null:
|
||||
@@ -545,10 +545,10 @@ We first set the :ref:`RigidBody <class_RigidBody>` we are holding mode to ``MOD
|
||||
|
||||
.. note:: This is making a rather large assumption that all the rigid bodies will be using ``MODE_RIGID``. While that is the case for this tutorial series,
|
||||
that may not be the case in other projects.
|
||||
|
||||
|
||||
If you have :ref:`RigidBody <class_RigidBody>`'s with different modes, you may need to store the mode of the :ref:`RigidBody <class_RigidBody>` you
|
||||
have picked up into a class variable so you can change it back to the mode it was in before you picked it up.
|
||||
|
||||
|
||||
Then we apply an impulse to send it flying forward. We send it flying in the direction the camera is facing, using the force we set in the ``OBJECT_THROW_FORCE`` variable.
|
||||
|
||||
We then set the grabbed :ref:`RigidBody <class_RigidBody>`'s collision layer and mask to ``1``, so it can collide with anything on layer ``1`` again.
|
||||
@@ -573,7 +573,7 @@ Before we test this, we need to change something in ``_physics_process``. While
|
||||
want the player to be able to change weapons or reload, so change ``_physics_process`` to the following:
|
||||
|
||||
::
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
process_input(delta)
|
||||
process_view_input(delta)
|
||||
@@ -587,7 +587,7 @@ want the player to be able to change weapons or reload, so change ``_physics_pro
|
||||
process_UI(delta)
|
||||
|
||||
Now the player cannot change weapons or reload while holding an object.
|
||||
|
||||
|
||||
Now you can grab and throw RigidBody nodes while you're in the ``UNARMED`` state! Go give it a try!
|
||||
|
||||
Adding a turret
|
||||
@@ -615,7 +615,7 @@ Now that we've looked at how the scene is set up, lets start writing the code fo
|
||||
Add the following to ``Turret.gd``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
extends Spatial
|
||||
|
||||
export (bool) var use_raycast = false
|
||||
@@ -656,46 +656,46 @@ Add the following to ``Turret.gd``:
|
||||
var bullet_scene = preload("Bullet_Scene.tscn")
|
||||
|
||||
func _ready():
|
||||
|
||||
|
||||
$Vision_Area.connect("body_entered", self, "body_entered_vision")
|
||||
$Vision_Area.connect("body_exited", self, "body_exited_vision")
|
||||
|
||||
|
||||
node_turret_head = $Head
|
||||
node_raycast = $Head/Ray_Cast
|
||||
node_flash_one = $Head/Flash
|
||||
node_flash_two = $Head/Flash_2
|
||||
|
||||
|
||||
node_raycast.add_exception(self)
|
||||
node_raycast.add_exception($Base/Static_Body)
|
||||
node_raycast.add_exception($Head/Static_Body)
|
||||
node_raycast.add_exception($Vision_Area)
|
||||
|
||||
|
||||
node_flash_one.visible = false
|
||||
node_flash_two.visible = false
|
||||
|
||||
|
||||
smoke_particles = $Smoke
|
||||
smoke_particles.emitting = false
|
||||
|
||||
|
||||
turret_health = MAX_TURRET_HEALTH
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
|
||||
|
||||
if is_active == true:
|
||||
|
||||
|
||||
if flash_timer > 0:
|
||||
flash_timer -= delta
|
||||
|
||||
|
||||
if flash_timer <= 0:
|
||||
node_flash_one.visible = false
|
||||
node_flash_two.visible = false
|
||||
|
||||
|
||||
if current_target != null:
|
||||
|
||||
|
||||
node_turret_head.look_at(current_target.global_transform.origin + Vector3(0, PLAYER_HEIGHT, 0), Vector3(0, 1, 0))
|
||||
|
||||
|
||||
if turret_health > 0:
|
||||
|
||||
|
||||
if ammo_in_turret > 0:
|
||||
if fire_timer > 0:
|
||||
fire_timer -= delta
|
||||
@@ -706,7 +706,7 @@ Add the following to ``Turret.gd``:
|
||||
ammo_reload_timer -= delta
|
||||
else:
|
||||
ammo_in_turret = AMMO_IN_FULL_TURRET
|
||||
|
||||
|
||||
if turret_health <= 0:
|
||||
if destroyed_timer > 0:
|
||||
destroyed_timer -= delta
|
||||
@@ -716,40 +716,40 @@ Add the following to ``Turret.gd``:
|
||||
|
||||
|
||||
func fire_bullet():
|
||||
|
||||
|
||||
if use_raycast == true:
|
||||
node_raycast.look_at(current_target.global_transform.origin + Vector3(0, PLAYER_HEIGHT, 0), Vector3(0,1,0))
|
||||
|
||||
|
||||
node_raycast.force_raycast_update()
|
||||
|
||||
|
||||
if node_raycast.is_colliding():
|
||||
var body = node_raycast.get_collider()
|
||||
if body.has_method("bullet_hit"):
|
||||
body.bullet_hit(TURRET_DAMAGE_RAYCAST, node_raycast.get_collision_point())
|
||||
|
||||
|
||||
ammo_in_turret -= 1
|
||||
|
||||
|
||||
else:
|
||||
var clone = bullet_scene.instance()
|
||||
var scene_root = get_tree().root.get_children()[0]
|
||||
scene_root.add_child(clone)
|
||||
|
||||
|
||||
clone.global_transform = $Head/Barrel_End.global_transform
|
||||
clone.scale = Vector3(8, 8, 8)
|
||||
clone.BULLET_DAMAGE = TURRET_DAMAGE_BULLET
|
||||
clone.BULLET_SPEED = 60
|
||||
|
||||
|
||||
ammo_in_turret -= 1
|
||||
|
||||
|
||||
node_flash_one.visible = true
|
||||
node_flash_two.visible = true
|
||||
|
||||
|
||||
flash_timer = FLASH_TIME
|
||||
fire_timer = FIRE_TIME
|
||||
|
||||
|
||||
if ammo_in_turret <= 0:
|
||||
ammo_reload_timer = AMMO_RELOAD_TIME
|
||||
|
||||
|
||||
|
||||
func body_entered_vision(body):
|
||||
if current_target == null:
|
||||
@@ -763,7 +763,7 @@ Add the following to ``Turret.gd``:
|
||||
if body == current_target:
|
||||
current_target = null
|
||||
is_active = false
|
||||
|
||||
|
||||
flash_timer = 0
|
||||
fire_timer = 0
|
||||
node_flash_one.visible = false
|
||||
@@ -772,7 +772,7 @@ Add the following to ``Turret.gd``:
|
||||
|
||||
func bullet_hit(damage, bullet_hit_pos):
|
||||
turret_health -= damage
|
||||
|
||||
|
||||
if turret_health <= 0:
|
||||
smoke_particles.emitting = true
|
||||
destroyed_timer = DESTROYED_TIME
|
||||
@@ -916,7 +916,7 @@ select one of the :ref:`StaticBody <class_StaticBody>` nodes from either ``Base`
|
||||
Add the following code to ``TurretBodies.gd``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
extends StaticBody
|
||||
|
||||
export (NodePath) var path_to_turret_root
|
||||
@@ -927,7 +927,7 @@ Add the following code to ``TurretBodies.gd``:
|
||||
func bullet_hit(damage, bullet_hit_pos):
|
||||
if path_to_turret_root != null:
|
||||
get_node(path_to_turret_root).bullet_hit(damage, bullet_hit_pos)
|
||||
|
||||
|
||||
All this code does is call ``bullet_hit`` on whatever node ``path_to_turret_root`` leads to. Go back to the editor and assign the :ref:`NodePath <class_NodePath>`
|
||||
to the ``Turret`` node.
|
||||
|
||||
@@ -941,7 +941,7 @@ The last thing we need to do is add a way for the player to be hurt. Since all o
|
||||
Open ``Player.gd`` and add the following:
|
||||
|
||||
::
|
||||
|
||||
|
||||
func bullet_hit(damage, bullet_hit_pos):
|
||||
health -= damage
|
||||
|
||||
|
||||
@@ -41,14 +41,14 @@ Feel free to use whatever button layout you want. Make sure that the device sele
|
||||
|
||||
Once you are happy with the input, close the project settings and save.
|
||||
|
||||
______
|
||||
______
|
||||
|
||||
Now let's open up ``Player.gd`` and add joypad input.
|
||||
|
||||
First, we need to define a few new class variables. Add the following class variables to ``Player.gd``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
# You may need to adjust depending on the sensitivity of your joypad
|
||||
var JOYPAD_SENSITIVITY = 2
|
||||
const JOYPAD_DEADZONE = 0.15
|
||||
@@ -60,23 +60,23 @@ Let's go over what each of these do:
|
||||
|
||||
.. note:: Many joypads jitter around a certain point. To counter this, we ignore any movement in a
|
||||
with a radius of JOYPAD_DEADZONE. If we did not ignore said movement, the camera would jitter.
|
||||
|
||||
|
||||
Also, we are defining ``JOYPAD_SENSITIVITY`` as a variable instead of a constant because we'll later be changing it.
|
||||
|
||||
Now we are ready to start handling joypad input!
|
||||
Now we are ready to start handling joypad input!
|
||||
|
||||
______
|
||||
|
||||
|
||||
In ``process_input`` add the following code, just before ``input_movement_vector = input_movement_vector.normalized()``:
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript Xbox Controller
|
||||
|
||||
|
||||
# Add joypad input, if there is a joypad
|
||||
if Input.get_connected_joypads().size() > 0:
|
||||
|
||||
|
||||
var joypad_vec = Vector2(0, 0)
|
||||
|
||||
|
||||
if OS.get_name() == "Windows":
|
||||
joypad_vec = Vector2(Input.get_joy_axis(0, 0), -Input.get_joy_axis(0, 1))
|
||||
elif OS.get_name() == "X11":
|
||||
@@ -95,9 +95,9 @@ In ``process_input`` add the following code, just before ``input_movement_vector
|
||||
|
||||
# Add joypad input, if there is a joypad
|
||||
if Input.get_connected_joypads().size() > 0:
|
||||
|
||||
|
||||
var joypad_vec = Vector2(0, 0)
|
||||
|
||||
|
||||
if OS.get_name() == "Windows" or OS.get_name() == "X11":
|
||||
joypad_vec = Vector2(Input.get_joy_axis(0, 0), -Input.get_joy_axis(0, 1))
|
||||
elif OS.get_name() == "OSX":
|
||||
@@ -127,7 +127,7 @@ If it is, we set ``joypad_vec`` to an empty Vector2. If it is not, we use a scal
|
||||
|
||||
.. note:: You can find a great article explaining all about how to handle joypad/controller dead zones here:
|
||||
http://www.third-helix.com/2013/04/12/doing-thumbstick-dead-zones-right.html
|
||||
|
||||
|
||||
We're using a translated version of the scaled radial dead zone code provided in that article.
|
||||
The article is a great read, and I highly suggest giving it a look!
|
||||
|
||||
@@ -135,16 +135,16 @@ Finally, we add ``joypad_vec`` to ``input_movement_vector``.
|
||||
|
||||
.. tip:: Remember how we normalize ``input_movement_vector``? This is why! If we did not normalize ``input_movement_vector``, the player could
|
||||
move faster if the player pushes in the same direction with both the keyboard and the joypad!
|
||||
|
||||
|
||||
______
|
||||
|
||||
Make a new function called ``process_view_input`` and add the following:
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript Xbox Controller
|
||||
|
||||
|
||||
func process_view_input(delta):
|
||||
|
||||
|
||||
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
|
||||
return
|
||||
|
||||
@@ -179,9 +179,9 @@ Make a new function called ``process_view_input`` and add the following:
|
||||
# ----------------------------------
|
||||
|
||||
.. code-tab:: gdscript Playstation Controller
|
||||
|
||||
|
||||
func process_view_input(delta):
|
||||
|
||||
|
||||
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
|
||||
return
|
||||
|
||||
@@ -242,10 +242,10 @@ The last thing we need to do is add ``process_view_input`` to ``_physics_process
|
||||
Once ``process_view_input`` is added to ``_physics_process``, you should be able to play using a joypad!
|
||||
|
||||
.. note:: I decided not to use the joypad triggers for firing because we'd then have to do some more axis managing, and because I prefer to use a shoulder buttons to fire.
|
||||
|
||||
|
||||
If you want to use the triggers for firing, you will need to change how firing works in ``process_input``. You need to get the axis values for the triggers,
|
||||
and check if it's over a certain value, say ``0.8`` for example. If it is, you add the same code as when the ``fire`` action was pressed.
|
||||
|
||||
|
||||
Adding mouse scroll wheel input
|
||||
-------------------------------
|
||||
|
||||
@@ -254,7 +254,7 @@ Let's add one more input related feature before we start working on the pick ups
|
||||
Open up ``Player.gd`` and add the following class variables:
|
||||
|
||||
::
|
||||
|
||||
|
||||
var mouse_scroll_value = 0
|
||||
const MOUSE_SENSITIVITY_SCROLL_WHEEL = 0.08
|
||||
|
||||
@@ -268,16 +268,16 @@ ______
|
||||
Now let's add the following to ``_input``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
if event is InputEventMouseButton and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
||||
if event.button_index == BUTTON_WHEEL_UP or event.button_index == BUTTON_WHEEL_DOWN:
|
||||
if event.button_index == BUTTON_WHEEL_UP:
|
||||
mouse_scroll_value += MOUSE_SENSITIVITY_SCROLL_WHEEL
|
||||
elif event.button_index == BUTTON_WHEEL_DOWN:
|
||||
mouse_scroll_value -= MOUSE_SENSITIVITY_SCROLL_WHEEL
|
||||
|
||||
|
||||
mouse_scroll_value = clamp(mouse_scroll_value, 0, WEAPON_NUMBER_TO_NAME.size()-1)
|
||||
|
||||
|
||||
if changing_weapon == false:
|
||||
if reloading_weapon == false:
|
||||
var round_mouse_scroll_value = int(round(mouse_scroll_value))
|
||||
@@ -286,7 +286,7 @@ Now let's add the following to ``_input``:
|
||||
changing_weapon = true
|
||||
mouse_scroll_value = round_mouse_scroll_value
|
||||
|
||||
|
||||
|
||||
Let's go over what's happening here:
|
||||
|
||||
First we check if the event is a ``InputEventMouseButton`` event and that the mouse mode is ``MOUSE_MODE_CAPTURED``.
|
||||
@@ -315,9 +315,9 @@ ______
|
||||
One more thing we need to change is in ``process_input``. In the code for changing weapons, add the following right after the line ``changing_weapon = true``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
mouse_scroll_value = weapon_change_number
|
||||
|
||||
|
||||
Now the scroll value will be changed with the keyboard input. If we did not change this, the scroll value will be out of sync. If the scroll wheel is out of
|
||||
sync, scrolling forwards or backwards would not transition to the next/last weapon, but rather the next/last weapon the scroll wheel changed to.
|
||||
|
||||
@@ -346,7 +346,7 @@ The last thing to note is how we have a :ref:`AnimationPlayer <class_AnimationPl
|
||||
Select ``Health_Pickup`` and add a new script called ``Health_Pickup.gd``. Add the following:
|
||||
|
||||
::
|
||||
|
||||
|
||||
extends Spatial
|
||||
|
||||
export (int, "full size", "small") var kit_size = 0 setget kit_size_change
|
||||
@@ -360,11 +360,11 @@ Select ``Health_Pickup`` and add a new script called ``Health_Pickup.gd``. Add t
|
||||
var is_ready = false
|
||||
|
||||
func _ready():
|
||||
|
||||
|
||||
$Holder/Health_Pickup_Trigger.connect("body_entered", self, "trigger_body_entered")
|
||||
|
||||
|
||||
is_ready = true
|
||||
|
||||
|
||||
kit_size_change_values(0, false)
|
||||
kit_size_change_values(1, false)
|
||||
kit_size_change_values(kit_size, true)
|
||||
@@ -373,7 +373,7 @@ Select ``Health_Pickup`` and add a new script called ``Health_Pickup.gd``. Add t
|
||||
func _physics_process(delta):
|
||||
if respawn_timer > 0:
|
||||
respawn_timer -= delta
|
||||
|
||||
|
||||
if respawn_timer <= 0:
|
||||
kit_size_change_values(kit_size, true)
|
||||
|
||||
@@ -457,7 +457,7 @@ We get the collision shape for the node corresponding to ``size`` and disable it
|
||||
.. note:: Why are we using ``!enable`` instead of ``enable``? This is so when we say we want to enable the node, we can pass in ``true``, but since
|
||||
:ref:`CollisionShape <class_CollisionShape>` uses disabled instead of enabled, we need to flip it. By flipping it, we can enable the collision shape
|
||||
and make the mesh visible when ``true`` is passed in.
|
||||
|
||||
|
||||
We then get the correct :ref:`Spatial <class_Spatial>` node holding the mesh and set its visibility to ``enable``.
|
||||
|
||||
This function may be a little confusing, try to think of it like this: We're enabling/disabling the proper nodes for ``size`` using ``enabled``. This is so we cannot pick up
|
||||
@@ -480,15 +480,15 @@ The last thing we need to do before the player can use this health pick up is ad
|
||||
Open up ``Player.gd`` and add the following class variable:
|
||||
|
||||
::
|
||||
|
||||
|
||||
const MAX_HEALTH = 150
|
||||
|
||||
|
||||
* ``MAX_HEALTH``: The maximum amount of health a player can have.
|
||||
|
||||
Now we need to add the ``add_health`` function to the player. Add the following to ``Player.gd``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
func add_health(additional_health):
|
||||
health += additional_health
|
||||
health = clamp(health, 0, MAX_HEALTH)
|
||||
@@ -515,35 +515,35 @@ for the difference in mesh sizes.
|
||||
Select ``Ammo_Pickup`` and add a new script called ``Ammo_Pickup.gd``. Add the following:
|
||||
|
||||
::
|
||||
|
||||
|
||||
extends Spatial
|
||||
|
||||
export (int, "full size", "small") var kit_size = 0 setget kit_size_change
|
||||
|
||||
# 0 = full size pickup, 1 = small pickup
|
||||
const AMMO_AMOUNTS = [4, 1]
|
||||
|
||||
|
||||
const RESPAWN_TIME = 20
|
||||
var respawn_timer = 0
|
||||
|
||||
var is_ready = false
|
||||
|
||||
func _ready():
|
||||
|
||||
|
||||
$Holder/Ammo_Pickup_Trigger.connect("body_entered", self, "trigger_body_entered")
|
||||
|
||||
|
||||
is_ready = true
|
||||
|
||||
|
||||
kit_size_change_values(0, false)
|
||||
kit_size_change_values(1, false)
|
||||
|
||||
|
||||
kit_size_change_values(kit_size, true)
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
if respawn_timer > 0:
|
||||
respawn_timer -= delta
|
||||
|
||||
|
||||
if respawn_timer <= 0:
|
||||
kit_size_change_values(kit_size, true)
|
||||
|
||||
@@ -552,7 +552,7 @@ Select ``Ammo_Pickup`` and add a new script called ``Ammo_Pickup.gd``. Add the f
|
||||
if is_ready:
|
||||
kit_size_change_values(kit_size, false)
|
||||
kit_size = value
|
||||
|
||||
|
||||
kit_size_change_values(kit_size, true)
|
||||
else:
|
||||
kit_size = value
|
||||
@@ -588,7 +588,7 @@ _______
|
||||
All we need to do for making the ammo pick ups work is to add a new function to the player. Open ``Player.gd`` and add the following function:
|
||||
|
||||
::
|
||||
|
||||
|
||||
func add_ammo(additional_ammo):
|
||||
if (current_weapon_name != "UNARMED"):
|
||||
if (weapons[current_weapon_name].CAN_REFILL == true):
|
||||
@@ -637,7 +637,7 @@ Alright, now switch back to ``Target.tscn``, select the ``Target`` :ref:`StaticB
|
||||
Add the following code to ``Target.gd``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
extends StaticBody
|
||||
|
||||
const TARGET_HEALTH = 40
|
||||
@@ -662,12 +662,12 @@ Add the following code to ``Target.gd``:
|
||||
func _physics_process(delta):
|
||||
if target_respawn_timer > 0:
|
||||
target_respawn_timer -= delta
|
||||
|
||||
|
||||
if target_respawn_timer <= 0:
|
||||
|
||||
|
||||
for child in broken_target_holder.get_children():
|
||||
child.queue_free()
|
||||
|
||||
|
||||
target_collision_shape.disabled = false
|
||||
visible = true
|
||||
current_health = TARGET_HEALTH
|
||||
@@ -675,20 +675,20 @@ Add the following code to ``Target.gd``:
|
||||
|
||||
func bullet_hit(damage, bullet_transform):
|
||||
current_health -= damage
|
||||
|
||||
|
||||
if current_health <= 0:
|
||||
var clone = destroyed_target.instance()
|
||||
broken_target_holder.add_child(clone)
|
||||
|
||||
|
||||
for rigid in clone.get_children():
|
||||
if rigid is RigidBody:
|
||||
var center_in_rigid_space = broken_target_holder.global_transform.origin - rigid.global_transform.origin
|
||||
var direction = (rigid.transform.origin - center_in_rigid_space).normalized()
|
||||
# Apply the impulse with some additional force (I find 12 works nicely)
|
||||
rigid.apply_impulse(center_in_rigid_space, direction * 12 * damage)
|
||||
|
||||
|
||||
target_respawn_timer = TARGET_RESPAWN_TIME
|
||||
|
||||
|
||||
target_collision_shape.disabled = true
|
||||
visible = false
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ the ``X`` axis.
|
||||
.. note:: If we did not use ``Rotation_helper`` then we'd likely have cases where we are rotating
|
||||
both the ``X`` and ``Y`` axes at the same time. This can lead to undesirable results, as we then
|
||||
could rotate on all three axes in some cases.
|
||||
|
||||
|
||||
See :ref:`using transforms <doc_using_transforms>` for more information
|
||||
|
||||
_________
|
||||
@@ -148,34 +148,34 @@ Add the following code to ``Player.gd``:
|
||||
const ACCEL = 4.5
|
||||
|
||||
var dir = Vector3()
|
||||
|
||||
|
||||
const DEACCEL= 16
|
||||
const MAX_SLOPE_ANGLE = 40
|
||||
|
||||
|
||||
var camera
|
||||
var rotation_helper
|
||||
|
||||
|
||||
var MOUSE_SENSITIVITY = 0.05
|
||||
|
||||
|
||||
func _ready():
|
||||
camera = $Rotation_Helper/Camera
|
||||
rotation_helper = $Rotation_Helper
|
||||
|
||||
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
process_input(delta)
|
||||
process_movement(delta)
|
||||
|
||||
|
||||
func process_input(delta):
|
||||
|
||||
|
||||
# ----------------------------------
|
||||
# Walking
|
||||
dir = Vector3()
|
||||
var cam_xform = camera.get_global_transform()
|
||||
|
||||
|
||||
var input_movement_vector = Vector2()
|
||||
|
||||
|
||||
if Input.is_action_pressed("movement_forward"):
|
||||
input_movement_vector.y += 1
|
||||
if Input.is_action_pressed("movement_backward"):
|
||||
@@ -184,20 +184,20 @@ Add the following code to ``Player.gd``:
|
||||
input_movement_vector.x -= 1
|
||||
if Input.is_action_pressed("movement_right"):
|
||||
input_movement_vector.x += 1
|
||||
|
||||
|
||||
input_movement_vector = input_movement_vector.normalized()
|
||||
|
||||
|
||||
dir += -cam_xform.basis.z.normalized() * input_movement_vector.y
|
||||
dir += cam_xform.basis.x.normalized() * input_movement_vector.x
|
||||
# ----------------------------------
|
||||
|
||||
|
||||
# ----------------------------------
|
||||
# Jumping
|
||||
if is_on_floor():
|
||||
if Input.is_action_just_pressed("movement_jump"):
|
||||
vel.y = JUMP_SPEED
|
||||
# ----------------------------------
|
||||
|
||||
|
||||
# ----------------------------------
|
||||
# Capturing/Freeing the cursor
|
||||
if Input.is_action_just_pressed("ui_cancel"):
|
||||
@@ -206,35 +206,35 @@ Add the following code to ``Player.gd``:
|
||||
else:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
# ----------------------------------
|
||||
|
||||
|
||||
func process_movement(delta):
|
||||
dir.y = 0
|
||||
dir = dir.normalized()
|
||||
|
||||
|
||||
vel.y += delta*GRAVITY
|
||||
|
||||
|
||||
var hvel = vel
|
||||
hvel.y = 0
|
||||
|
||||
|
||||
var target = dir
|
||||
target *= MAX_SPEED
|
||||
|
||||
|
||||
var accel
|
||||
if dir.dot(hvel) > 0:
|
||||
accel = ACCEL
|
||||
else:
|
||||
accel = DEACCEL
|
||||
|
||||
|
||||
hvel = hvel.linear_interpolate(target, accel*delta)
|
||||
vel.x = hvel.x
|
||||
vel.z = hvel.z
|
||||
vel = move_and_slide(vel,Vector3(0,1,0), 0.05, 4, deg2rad(MAX_SLOPE_ANGLE))
|
||||
|
||||
|
||||
func _input(event):
|
||||
if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
||||
rotation_helper.rotate_x(deg2rad(event.relative.y * MOUSE_SENSITIVITY))
|
||||
self.rotate_y(deg2rad(event.relative.x * MOUSE_SENSITIVITY * -1))
|
||||
|
||||
|
||||
var camera_rot = rotation_helper.rotation_degrees
|
||||
camera_rot.x = clamp(camera_rot.x, -70, 70)
|
||||
rotation_helper.rotation_degrees = camera_rot
|
||||
@@ -243,9 +243,9 @@ This is a lot of code, so let's break it down function by function:
|
||||
|
||||
.. tip:: While copy and pasting code is ill advised, as you can learn a lot from manually typing the code in, you can
|
||||
copy and paste the code from this page directly into the script editor.
|
||||
|
||||
|
||||
If you do this, all of the code copied will be using spaces instead of tabs.
|
||||
|
||||
|
||||
To convert the spaces to tabs in the script editor, click the "edit" menu and select "Convert Indent To Tabs".
|
||||
This will convert all of the spaces into tabs. You can select "Convert Indent To Spaces" to convert t back into spaces.
|
||||
|
||||
@@ -275,7 +275,7 @@ increasing ``JUMP_SPEED`` you can get a more 'floaty' feeling character.
|
||||
Feel free to experiment!
|
||||
|
||||
.. note:: You may have noticed that ``MOUSE_SENSITIVITY`` is written in all caps like the other constants, but ``MOUSE_SENSITIVITY`` is not a constant.
|
||||
|
||||
|
||||
The reason behind this is we want to treat it like a constant variable (a variable that cannot change) throughout our script, but we want to be
|
||||
able to change the value later when we add customizable settings. So, in an effort to remind ourselves to treat it like a constant, it's named in all caps.
|
||||
|
||||
@@ -543,7 +543,7 @@ First we need a few more class variables in our player script:
|
||||
const MAX_SPRINT_SPEED = 30
|
||||
const SPRINT_ACCEL = 18
|
||||
var is_sprinting = false
|
||||
|
||||
|
||||
var flashlight
|
||||
|
||||
All of the sprinting variables work exactly the same as the non sprinting variables with
|
||||
@@ -555,7 +555,7 @@ we will be using to hold the player's flash light node.
|
||||
Now we need to add a few lines of code, starting in ``_ready``. Add the following to ``_ready``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
flashlight = $Rotation_Helper/Flashlight
|
||||
|
||||
This gets the flash light node and assigns it to the ``flashlight`` variable.
|
||||
@@ -565,7 +565,7 @@ _________
|
||||
Now we need to change some of the code in ``process_input``. Add the following somewhere in ``process_input``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
# ----------------------------------
|
||||
# Sprinting
|
||||
if Input.is_action_pressed("movement_sprint"):
|
||||
@@ -573,7 +573,7 @@ Now we need to change some of the code in ``process_input``. Add the following s
|
||||
else:
|
||||
is_sprinting = false
|
||||
# ----------------------------------
|
||||
|
||||
|
||||
# ----------------------------------
|
||||
# Turning the flashlight on/off
|
||||
if Input.is_action_just_pressed("flashlight"):
|
||||
@@ -597,26 +597,26 @@ _________
|
||||
Now we need to change a couple things in ``process_movement``. First, replace ``target *= MAX_SPEED`` with the following:
|
||||
|
||||
::
|
||||
|
||||
|
||||
if is_sprinting:
|
||||
target *= MAX_SPRINT_SPEED
|
||||
else:
|
||||
target *= MAX_SPEED
|
||||
|
||||
Now instead of always multiplying ``target`` by ``MAX_SPEED``, we first check to see if the player is sprinting or not.
|
||||
If the player is sprinting, we instead multiply ``target`` by ``MAX_SPRINT_SPEED``.
|
||||
If the player is sprinting, we instead multiply ``target`` by ``MAX_SPRINT_SPEED``.
|
||||
|
||||
Now all that's left is changing the acceleration when sprinting. Change ``accel = ACCEL`` to the following:
|
||||
|
||||
::
|
||||
|
||||
|
||||
if is_sprinting:
|
||||
accel = SPRINT_ACCEL
|
||||
else:
|
||||
accel = ACCEL
|
||||
|
||||
|
||||
Now when the player is sprinting we'll use ``SPRINT_ACCEL`` instead of ``ACCEL``, which will accelerate the player faster.
|
||||
Now when the player is sprinting we'll use ``SPRINT_ACCEL`` instead of ``ACCEL``, which will accelerate the player faster.
|
||||
|
||||
_________
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ This is the last part of the FPS tutorial, by the end of this you will have a so
|
||||
|
||||
.. note:: You are assumed to have finished :ref:`doc_fps_tutorial_part_five` before moving on to this part of the tutorial.
|
||||
The finished project from :ref:`doc_fps_tutorial_part_four` will be the starting project for part 6
|
||||
|
||||
|
||||
Let's get started!
|
||||
|
||||
Adding the main menu
|
||||
@@ -50,24 +50,24 @@ Select ``Main_Menu`` (the root node) and create a new script called ``Main_Menu.
|
||||
start_menu = $Start_Menu
|
||||
level_select_menu = $Level_Select_Menu
|
||||
options_menu = $Options_Menu
|
||||
|
||||
|
||||
$Start_Menu/Button_Start.connect("pressed", self, "start_menu_button_pressed", ["start"])
|
||||
$Start_Menu/Button_Open_Godot.connect("pressed", self, "start_menu_button_pressed", ["open_godot"])
|
||||
$Start_Menu/Button_Options.connect("pressed", self, "start_menu_button_pressed", ["options"])
|
||||
$Start_Menu/Button_Quit.connect("pressed", self, "start_menu_button_pressed", ["quit"])
|
||||
|
||||
|
||||
$Level_Select_Menu/Button_Back.connect("pressed", self, "level_select_menu_button_pressed", ["back"])
|
||||
$Level_Select_Menu/Button_Level_Testing_Area.connect("pressed", self, "level_select_menu_button_pressed", ["testing_scene"])
|
||||
$Level_Select_Menu/Button_Level_Space.connect("pressed", self, "level_select_menu_button_pressed", ["space_level"])
|
||||
$Level_Select_Menu/Button_Level_Ruins.connect("pressed", self, "level_select_menu_button_pressed", ["ruins_level"])
|
||||
|
||||
|
||||
$Options_Menu/Button_Back.connect("pressed", self, "options_menu_button_pressed", ["back"])
|
||||
$Options_Menu/Button_Fullscreen.connect("pressed", self, "options_menu_button_pressed", ["fullscreen"])
|
||||
$Options_Menu/Check_Button_VSync.connect("pressed", self, "options_menu_button_pressed", ["vsync"])
|
||||
$Options_Menu/Check_Button_Debug.connect("pressed", self, "options_menu_button_pressed", ["debug"])
|
||||
|
||||
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
|
||||
|
||||
var globals = get_node("/root/Globals")
|
||||
$Options_Menu/HSlider_Mouse_Sensitivity.value = globals.mouse_sensitivity
|
||||
$Options_Menu/HSlider_Joypad_Sensitivity.value = globals.joypad_sensitivity
|
||||
@@ -162,7 +162,7 @@ In ``level_select_menu_button_pressed``, we check to see which button is pressed
|
||||
|
||||
If the ``back`` button has been pressed, we change the currently visible panels to return to the main menu.
|
||||
|
||||
If one of the scene changing buttons are pressed, we fist call ``set_mouse_and_joypad_sensitivity`` so the singleton (``Globals.gd``) has the values from the :ref:`HSlider
|
||||
If one of the scene changing buttons are pressed, we fist call ``set_mouse_and_joypad_sensitivity`` so the singleton (``Globals.gd``) has the values from the :ref:`HSlider
|
||||
<class_HSlider>` nodes.
|
||||
Then we tell the singleton to change nodes using its ``load_new_scene`` function, passing in the file path of the scene the player has selected.
|
||||
|
||||
@@ -303,12 +303,12 @@ Open up ``Globals.gd`` and add the following class variables:
|
||||
|
||||
# ------------------------------------
|
||||
# All of the GUI/UI related variables
|
||||
|
||||
|
||||
var canvas_layer = null
|
||||
|
||||
|
||||
const DEBUG_DISPLAY_SCENE = preload("res://Debug_Display.tscn")
|
||||
var debug_display = null
|
||||
|
||||
|
||||
# ------------------------------------
|
||||
|
||||
* ``canvas_layer``: A canvas layer so the GUI/UI created in ``Globals.gd`` is always drawn on top.
|
||||
@@ -336,7 +336,7 @@ This is because nodes will not be freed/destroyed when you change scene, meaning
|
||||
instancing/spawning lots of nodes and you are not freeing them.
|
||||
|
||||
______
|
||||
|
||||
|
||||
Now we need to add ``set_debug_display`` to ``Globals.gd``:
|
||||
|
||||
::
|
||||
@@ -350,7 +350,7 @@ Now we need to add ``set_debug_display`` to ``Globals.gd``:
|
||||
if debug_display == null:
|
||||
debug_display = DEBUG_DISPLAY_SCENE.instance()
|
||||
canvas_layer.add_child(debug_display)
|
||||
|
||||
|
||||
Let's go over what's happening.
|
||||
|
||||
First we check to see if ``Globals.gd`` is trying to turn on the debug display, or turn it off.
|
||||
@@ -409,16 +409,16 @@ Add the following to ``_process``:
|
||||
if Input.is_action_just_pressed("ui_cancel"):
|
||||
if popup == null:
|
||||
popup = POPUP_SCENE.instance()
|
||||
|
||||
|
||||
popup.get_node("Button_quit").connect("pressed", self, "popup_quit")
|
||||
popup.connect("popup_hide", self, "popup_closed")
|
||||
popup.get_node("Button_resume").connect("pressed", self, "popup_closed")
|
||||
|
||||
|
||||
canvas_layer.add_child(popup)
|
||||
popup.popup_centered()
|
||||
|
||||
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
|
||||
|
||||
get_tree().paused = true
|
||||
|
||||
Let's go over what's happening here.
|
||||
@@ -458,9 +458,9 @@ Add the following to ``Globals.gd``:
|
||||
if popup != null:
|
||||
popup.queue_free()
|
||||
popup = null
|
||||
|
||||
|
||||
``popup_closed`` will resume the game and destroy the pop up if there is one.
|
||||
|
||||
|
||||
``popup_quit`` is similar, but we're also making sure the mouse is visible and changing scenes to the title screen.
|
||||
|
||||
Add the following to ``Globals.gd``:
|
||||
@@ -477,7 +477,7 @@ Add the following to ``Globals.gd``:
|
||||
popup = null
|
||||
|
||||
load_new_scene(MAIN_MENU_PATH)
|
||||
|
||||
|
||||
``popup_quit`` will resume the game, set the mouse mode to ``MOUSE_MODE_VISIBLE`` to ensure the mouse is visible in the main menu, destroy
|
||||
the pop up if there is one, and change scenes to the main menu.
|
||||
|
||||
@@ -530,7 +530,7 @@ Open up ``Player.gd`` and add the following class variables:
|
||||
const RESPAWN_TIME = 4
|
||||
var dead_time = 0
|
||||
var is_dead = false
|
||||
|
||||
|
||||
var globals
|
||||
|
||||
* ``RESPAWN_TIME``: The amount of time (in seconds) it takes to respawn.
|
||||
@@ -546,15 +546,15 @@ We now need to add a couple lines to ``_ready``, so we can use ``Globals.gd`` in
|
||||
|
||||
globals = get_node("/root/Globals")
|
||||
global_transform.origin = globals.get_respawn_position()
|
||||
|
||||
|
||||
|
||||
Now we're getting the ``Globals.gd`` singleton and assigning it to ``globals``. We also set the player's global position
|
||||
by setting the origin in the player's global :ref:`Transform <class_Transform>` to the position returned by ``globals.get_respawn_position``.
|
||||
|
||||
.. note:: Don't worry, we will be adding ``get_respawn_position`` further below!
|
||||
|
||||
|
||||
______
|
||||
|
||||
|
||||
Next we need to make a few changes to ``physics_process``. Change ``physics_processing`` to the following:
|
||||
|
||||
::
|
||||
@@ -586,58 +586,58 @@ Let's add ``process_respawn``. Add the following to ``Player.gd``:
|
||||
::
|
||||
|
||||
func process_respawn(delta):
|
||||
|
||||
|
||||
# If we just died
|
||||
if health <= 0 and !is_dead:
|
||||
$Body_CollisionShape.disabled = true
|
||||
$Feet_CollisionShape.disabled = true
|
||||
|
||||
|
||||
changing_weapon = true
|
||||
changing_weapon_name = "UNARMED"
|
||||
|
||||
|
||||
$HUD/Death_Screen.visible = true
|
||||
|
||||
|
||||
$HUD/Panel.visible = false
|
||||
$HUD/Crosshair.visible = false
|
||||
|
||||
|
||||
dead_time = RESPAWN_TIME
|
||||
is_dead = true
|
||||
|
||||
|
||||
if grabbed_object != null:
|
||||
grabbed_object.mode = RigidBody.MODE_RIGID
|
||||
grabbed_object.apply_impulse(Vector3(0,0,0), -camera.global_transform.basis.z.normalized() * OBJECT_THROW_FORCE / 2)
|
||||
|
||||
|
||||
grabbed_object.collision_layer = 1
|
||||
grabbed_object.collision_mask = 1
|
||||
|
||||
|
||||
grabbed_object = null
|
||||
|
||||
|
||||
if is_dead:
|
||||
dead_time -= delta
|
||||
|
||||
|
||||
var dead_time_pretty = str(dead_time).left(3)
|
||||
$HUD/Death_Screen/Label.text = "You died\n" + dead_time_pretty + " seconds till respawn"
|
||||
|
||||
|
||||
if dead_time <= 0:
|
||||
global_transform.origin = globals.get_respawn_position()
|
||||
|
||||
|
||||
$Body_CollisionShape.disabled = false
|
||||
$Feet_CollisionShape.disabled = false
|
||||
|
||||
|
||||
$HUD/Death_Screen.visible = false
|
||||
|
||||
|
||||
$HUD/Panel.visible = true
|
||||
$HUD/Crosshair.visible = true
|
||||
|
||||
|
||||
for weapon in weapons:
|
||||
var weapon_node = weapons[weapon]
|
||||
if weapon_node != null:
|
||||
weapon_node.reset_weapon()
|
||||
|
||||
|
||||
health = 100
|
||||
grenade_amounts = {"Grenade":2, "Sticky Grenade":2}
|
||||
current_grenade = "Grenade"
|
||||
|
||||
|
||||
is_dead = false
|
||||
|
||||
Let's go through what this function is doing.
|
||||
@@ -792,7 +792,7 @@ Now all we need is a way to set the respawn points. Open up ``Ruins_Level.tscn``
|
||||
func _ready():
|
||||
var globals = get_node("/root/Globals")
|
||||
globals.respawn_points = get_children()
|
||||
|
||||
|
||||
Now when a node with ``Respawn_Point_Setter.gd`` has its ``_ready`` function called, all of the children
|
||||
nodes of the node with ``Respawn_Point_Setter.gd``, ``Spawn_Points`` in the case of ``Ruins_Level.tscn``, will be added
|
||||
to ``respawn_points`` in ``Globals.gd``.
|
||||
@@ -806,7 +806,7 @@ Now when the player dies, they will respawn after waiting ``4`` seconds!
|
||||
|
||||
.. note:: No spawn points are already set up for any of the levels besides ``Ruins_Level.tscn``!
|
||||
Adding spawn points to ``Space_Level.tscn`` is left as an exercise for the reader.
|
||||
|
||||
|
||||
Writing a sound system we can use anywhere
|
||||
------------------------------------------
|
||||
|
||||
@@ -826,7 +826,7 @@ First, open up ``SimpleAudioPlayer.gd`` and change it to the following:
|
||||
audio_node = $Audio_Stream_Player
|
||||
audio_node.connect("finished", self, "sound_finished")
|
||||
audio_node.stop()
|
||||
|
||||
|
||||
globals = get_node("/root/Globals")
|
||||
|
||||
|
||||
@@ -836,13 +836,13 @@ First, open up ``SimpleAudioPlayer.gd`` and change it to the following:
|
||||
globals.created_audio.remove(globals.created_audio.find(self))
|
||||
queue_free()
|
||||
return
|
||||
|
||||
|
||||
audio_node.stream = audio_stream
|
||||
|
||||
|
||||
# If you are using a AudioPlayer3D, then uncomment these lines to set the position.
|
||||
#if position != null:
|
||||
# audio_node.global_transform.origin = position
|
||||
|
||||
|
||||
audio_node.play(0.0)
|
||||
|
||||
|
||||
@@ -854,7 +854,7 @@ First, open up ``SimpleAudioPlayer.gd`` and change it to the following:
|
||||
audio_node.stop()
|
||||
queue_free()
|
||||
|
||||
|
||||
|
||||
There are several changes from the old version, first and foremost being we are no longer storing the sound files in ``SimpleAudioPlayer.gd`` anymore.
|
||||
This is much better for performance since we're no longer loading each audio clip when we create a sound, but instead we are forcing an audio stream to be passed
|
||||
in to ``play_sound``.
|
||||
@@ -909,17 +909,17 @@ Lets go over these global variables.
|
||||
|
||||
.. note:: If you want to add additional audio, you need to add it to ``audio_clips``. No audio files are provided in this tutorial,
|
||||
so you will have to provide your own.
|
||||
|
||||
|
||||
One site I'd recommend is **GameSounds.xyz**.
|
||||
I'm using the Gamemaster audio gun sound pack included in the Sonniss' GDC Game Audio bundle for 2017.
|
||||
The tracks I've used (with some minor editing) are as follows:
|
||||
|
||||
|
||||
* gun_revolver_pistol_shot_04,
|
||||
* gun_semi_auto_rifle_cock_02,
|
||||
* gun_submachine_auto_shot_00_automatic_preview_01
|
||||
|
||||
______
|
||||
|
||||
|
||||
Now we need to add a new function called ``play_sound`` to ``Globals.gd``:
|
||||
|
||||
::
|
||||
@@ -928,12 +928,12 @@ Now we need to add a new function called ``play_sound`` to ``Globals.gd``:
|
||||
if audio_clips.has(sound_name):
|
||||
var new_audio = SIMPLE_AUDIO_PLAYER_SCENE.instance()
|
||||
new_audio.should_loop = loop_sound
|
||||
|
||||
|
||||
add_child(new_audio)
|
||||
created_audio.append(new_audio)
|
||||
|
||||
|
||||
new_audio.play_sound(audio_clips[sound_name], sound_position)
|
||||
|
||||
|
||||
else:
|
||||
print ("ERROR: cannot play sound that does not exist in audio_clips!")
|
||||
|
||||
@@ -961,7 +961,7 @@ Add the following to ``load_new_scene``:
|
||||
if (sound != null):
|
||||
sound.queue_free()
|
||||
created_audio.clear()
|
||||
|
||||
|
||||
Now before ``Globals.gd`` changes scenes, it goes through each simple audio player in ``created_sounds`` and frees/destroys them. Once ``Globals.gd`` has gone through
|
||||
all of the sounds in ``created_audio``, we clear ``created_audio`` so it no longer holds any references to any (noew freed/destroyed) simple audio players.
|
||||
|
||||
@@ -1010,11 +1010,11 @@ At this point you have a good base to build more complicated FPS games.
|
||||
|
||||
Other than that, the source is exactly the same, just with helpful comments explaining what
|
||||
each part does.
|
||||
|
||||
|
||||
.. tip:: The finished project source is hosted on Github as well: https://github.com/TwistedTwigleg/Godot_FPS_Tutorial
|
||||
|
||||
|
||||
**Please note that the code in Github may or may not be in sync with the tutorial in the documentation**.
|
||||
|
||||
|
||||
The code in the documentation is likely better managed and/or more up to date.
|
||||
If you are unsure on which to use, use the project(s) provided in the documentation as they are maintained by the Godot community.
|
||||
|
||||
@@ -1024,7 +1024,7 @@ All assets provided in the started assets (unless otherwise noted) were **origin
|
||||
All original assets provided for this tutorial are released under the ``MIT`` license.
|
||||
|
||||
Feel free to use these assets however you want! All original assets belong to the Godot community, with the other assets belonging to those listed below:
|
||||
|
||||
|
||||
The skybox is created by **StumpyStrust** and can be found at OpenGameArt.org. https://opengameart.org/content/space-skyboxes-0
|
||||
. The skybox is licensed under the ``CC0`` license.
|
||||
|
||||
|
||||
@@ -40,34 +40,34 @@ Follow the instructions below for either (or both) of the scenes you want to use
|
||||
.. code-tab:: gdscript Space_Level.tscn
|
||||
|
||||
Expand "Other_Objects" and then expand "Physics_Objects".
|
||||
|
||||
|
||||
Expand one of the "Barrel_Group" nodes and then select "Barrel_Rigid_Body" and open it using
|
||||
the "Open in Editor" button.
|
||||
This will bring you to the "Barrel_Rigid_Body" scene. From there select the root node and
|
||||
scroll the inspector down to the bottom.
|
||||
Select the drop down arrow under the "Node" tab, and then select "Load". Navigate to
|
||||
"RigidBody_hit_test.gd" and select "Open".
|
||||
|
||||
|
||||
Return back to "Space_Level.tscn".
|
||||
|
||||
|
||||
Expand one of the "Box_Group" nodes and then select "Crate_Rigid_Body" and open it using the
|
||||
"Open in Editor" button.
|
||||
This will bring you to the "Crate_Rigid_Body" scene. From there select the root node and
|
||||
scroll the inspector down to the bottom.
|
||||
Select the drop down arrow under the "Node" tab, and then select "Load". Navigate to
|
||||
"RigidBody_hit_test.gd" and select "Open".
|
||||
|
||||
|
||||
Return back to "Space_Level.tscn".
|
||||
|
||||
|
||||
|
||||
|
||||
.. code-tab:: gdscript Ruins_Level.tscn
|
||||
|
||||
Expand "Misc_Objects" and then expand "Physics_Objects".
|
||||
|
||||
|
||||
Select all of the "Stone_Cube" RigidBodies and then in the inspector scroll down to the bottom.
|
||||
Select the drop down arrow under the "Node" tab, and then select "Load". Navigate to
|
||||
"RigidBody_hit_test.gd" and select "Open".
|
||||
|
||||
|
||||
Return back to "Ruins_Level.tscn".
|
||||
|
||||
Now you can fire at all of the rigid bodies in either level and they will react to bullets hitting them!
|
||||
@@ -82,7 +82,7 @@ First we need to define a few variables in each of our weapon scripts.
|
||||
Open up ``Weapon_Pistol.gd`` and add the following class variables:
|
||||
|
||||
::
|
||||
|
||||
|
||||
var ammo_in_weapon = 10
|
||||
var spare_ammo = 20
|
||||
const AMMO_IN_MAG = 10
|
||||
@@ -104,14 +104,14 @@ Now we need to add ammo for both the rifle and the knife.
|
||||
|
||||
.. note:: You may be wondering why we are adding ammo for the knife given it does not consume any ammunition.
|
||||
The reason we want to add ammo to the knife is so we have a consistent interface for all of our weapons.
|
||||
|
||||
|
||||
If we did not add ammo variables for the knife, we would have to add checks for the knife. By adding the ammo
|
||||
variables to the knife, we don't need to worry about whether or all our weapons have the same variables.
|
||||
|
||||
Add the following class variables to ``Weapon_Rifle.gd``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
var ammo_in_weapon = 50
|
||||
var spare_ammo = 100
|
||||
const AMMO_IN_MAG = 50
|
||||
@@ -122,7 +122,7 @@ the player loses ammo regardless of whether the player hit something or not.
|
||||
Now all that's left is the knife. Add the following to ``Weapon_Knife.gd``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
var ammo_in_weapon = 1
|
||||
var spare_ammo = 1
|
||||
const AMMO_IN_MAG = 1
|
||||
@@ -136,7 +136,7 @@ Now all we need to do is change a one thing in ``Player.gd``.
|
||||
All we need to change how we're firing the weapons in ``process_input``. Change the code for firing weapons to the following:
|
||||
|
||||
::
|
||||
|
||||
|
||||
# ----------------------------------
|
||||
# Firing the weapons
|
||||
if Input.is_action_pressed("fire"):
|
||||
@@ -147,7 +147,7 @@ All we need to change how we're firing the weapons in ``process_input``. Change
|
||||
if animation_manager.current_state == current_weapon.IDLE_ANIM_NAME:
|
||||
animation_manager.set_animation(current_weapon.FIRE_ANIM_NAME)
|
||||
# ----------------------------------
|
||||
|
||||
|
||||
Now the weapons have a limited amount of ammo, and will stop firing when the player runs out.
|
||||
|
||||
_______
|
||||
@@ -159,7 +159,7 @@ First, add ``process_UI(delta)`` to ``_physics_process``.
|
||||
Now add the following to ``Player.gd``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
func process_UI(delta):
|
||||
if current_weapon_name == "UNARMED" or current_weapon_name == "KNIFE":
|
||||
UI_status_label.text = "HEALTH: " + str(health)
|
||||
@@ -191,10 +191,10 @@ For reloading we need to add a few more variables and a function to every weapon
|
||||
Open up ``Weapon_Pistol.gd`` and add the following class variables:
|
||||
|
||||
::
|
||||
|
||||
|
||||
const CAN_RELOAD = true
|
||||
const CAN_REFILL = true
|
||||
|
||||
|
||||
const RELOADING_ANIM_NAME = "Pistol_reload"
|
||||
|
||||
* ``CAN_RELOAD``: A boolean to track whether this weapon has the ability to reload
|
||||
@@ -204,32 +204,32 @@ Open up ``Weapon_Pistol.gd`` and add the following class variables:
|
||||
Now we need to add a function for handling reloading. Add the following function to ``Weapon_Pistol.gd``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
func reload_weapon():
|
||||
var can_reload = false
|
||||
|
||||
|
||||
if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
|
||||
can_reload = true
|
||||
|
||||
|
||||
if spare_ammo <= 0 or ammo_in_weapon == AMMO_IN_MAG:
|
||||
can_reload = false
|
||||
|
||||
|
||||
if can_reload == true:
|
||||
var ammo_needed = AMMO_IN_MAG - ammo_in_weapon
|
||||
|
||||
|
||||
if spare_ammo >= ammo_needed:
|
||||
spare_ammo -= ammo_needed
|
||||
ammo_in_weapon = AMMO_IN_MAG
|
||||
else:
|
||||
ammo_in_weapon += spare_ammo
|
||||
spare_ammo = 0
|
||||
|
||||
|
||||
player_node.animation_manager.set_animation(RELOADING_ANIM_NAME)
|
||||
|
||||
|
||||
return true
|
||||
|
||||
|
||||
return false
|
||||
|
||||
|
||||
Let's go over what's happening:
|
||||
|
||||
First we define a variable to see whether or not this weapon can reload.
|
||||
@@ -255,41 +255,41 @@ _______
|
||||
Now we need to add reloading to the rifle. Open up ``Weapon_Rifle.gd`` and add the following class variables:
|
||||
|
||||
::
|
||||
|
||||
|
||||
const CAN_RELOAD = true
|
||||
const CAN_REFILL = true
|
||||
|
||||
|
||||
const RELOADING_ANIM_NAME = "Rifle_reload"
|
||||
|
||||
|
||||
These variables are exactly the same as the pistol, just with ``RELOADING_ANIM_NAME`` changed to the rifle's reloading animation.
|
||||
|
||||
Now we need to add ``reload_weapon`` to ``Weapon_Rifle.gd``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
func reload_weapon():
|
||||
var can_reload = false
|
||||
|
||||
|
||||
if player_node.animation_manager.current_state == IDLE_ANIM_NAME:
|
||||
can_reload = true
|
||||
|
||||
|
||||
if spare_ammo <= 0 or ammo_in_weapon == AMMO_IN_MAG:
|
||||
can_reload = false
|
||||
|
||||
|
||||
if can_reload == true:
|
||||
var ammo_needed = AMMO_IN_MAG - ammo_in_weapon
|
||||
|
||||
|
||||
if spare_ammo >= ammo_needed:
|
||||
spare_ammo -= ammo_needed
|
||||
ammo_in_weapon = AMMO_IN_MAG
|
||||
else:
|
||||
ammo_in_weapon += spare_ammo
|
||||
spare_ammo = 0
|
||||
|
||||
|
||||
player_node.animation_manager.set_animation(RELOADING_ANIM_NAME)
|
||||
|
||||
|
||||
return true
|
||||
|
||||
|
||||
return false
|
||||
|
||||
This code is exactly the same as the pistol.
|
||||
@@ -299,7 +299,7 @@ _______
|
||||
The last bit we need to do for the weapons is add 'reloading' to the knife. Add the following class variables to ``Weapon_Knife.gd``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
const CAN_RELOAD = false
|
||||
const CAN_REFILL = false
|
||||
|
||||
@@ -311,7 +311,7 @@ has no reloading animation.
|
||||
Now we need to add ``reloading_weapon``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
func reload_weapon():
|
||||
return false
|
||||
|
||||
@@ -323,9 +323,9 @@ Adding reloading to the player
|
||||
Now we need to add a few things to ``Player.gd``. First we need to define a new class variable:
|
||||
|
||||
::
|
||||
|
||||
|
||||
var reloading_weapon = false
|
||||
|
||||
|
||||
* ``reloading_weapon``: A variable to track whether or not the player is currently trying to reload.
|
||||
|
||||
|
||||
@@ -334,7 +334,7 @@ Next we need to add another function call to ``_physics_process``.
|
||||
Add ``process_reloading(delta)`` to ``_physics_process``. Now ``_physics_process`` should look something like this:
|
||||
|
||||
::
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
process_input(delta)
|
||||
process_movement(delta)
|
||||
@@ -345,7 +345,7 @@ Add ``process_reloading(delta)`` to ``_physics_process``. Now ``_physics_process
|
||||
Now we need to add ``process_reloading``. Add the following function to ``Player.gd``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
func process_reloading(delta):
|
||||
if reloading_weapon == true:
|
||||
var current_weapon = weapons[current_weapon_name]
|
||||
@@ -371,7 +371,7 @@ Before we can let the player reload, we need to change a few things in ``process
|
||||
The first thing we need to change is in the code for changing weapons. We need to add a additional check (``if reloading_weapon == false:``) to see if the player is reloading:
|
||||
|
||||
::
|
||||
|
||||
|
||||
if changing_weapon == false:
|
||||
# New line of code here!
|
||||
if reloading_weapon == false:
|
||||
@@ -384,7 +384,7 @@ This makes it so the player cannot change weapons if the player is reloading.
|
||||
Now we need to add the code to trigger a reload when the player pushes the ``reload`` action. Add the following code to ``process_input``:
|
||||
|
||||
::
|
||||
|
||||
|
||||
# ----------------------------------
|
||||
# Reloading
|
||||
if reloading_weapon == false:
|
||||
@@ -429,7 +429,7 @@ reloading.
|
||||
Let's change our firing code in ``process_input`` so it reloads when trying to fire an empty weapon:
|
||||
|
||||
::
|
||||
|
||||
|
||||
# ----------------------------------
|
||||
# Firing the weapons
|
||||
if Input.is_action_pressed("fire"):
|
||||
@@ -448,11 +448,11 @@ Now we check to make sure the player is not reloading before we fire the weapon,
|
||||
we set ``reloading_weapon`` to ``true`` if the player tries to fire.
|
||||
|
||||
This will make it where the player will try to reload when the player tries to fire a empty weapon.
|
||||
|
||||
|
||||
_______
|
||||
|
||||
|
||||
With that done, the player can now reload! Give it a try! Now you can fire all of the spare ammo for each weapon.
|
||||
|
||||
|
||||
Adding sounds
|
||||
-------------
|
||||
|
||||
@@ -497,12 +497,12 @@ and insert the following code:
|
||||
|
||||
|
||||
func play_sound(sound_name, position=null):
|
||||
|
||||
|
||||
if audio_pistol_shot == null or audio_rifle_shot == null or audio_gun_cock == null:
|
||||
print ("Audio not set!")
|
||||
queue_free()
|
||||
return
|
||||
|
||||
|
||||
if sound_name == "Pistol_shot":
|
||||
audio_node.stream = audio_pistol_shot
|
||||
elif sound_name == "Rifle_shot":
|
||||
@@ -566,7 +566,7 @@ to save on resources.
|
||||
Because we cannot play looping sounds, certain effects, like footstep sounds, are harder to accomplish
|
||||
because we then have to keep track of whether or not there is a sound effect and whether or not we
|
||||
need to continue playing it.
|
||||
|
||||
|
||||
One of the biggest flaws with this system is we can only play sounds from ``Player.gd``.
|
||||
Ideally we'd like to be able to play sounds from any script at any time.
|
||||
|
||||
@@ -625,7 +625,7 @@ Open up ``Weapon_Pistol.gd``.
|
||||
Now, we want to make a noise when the player fires the pistol, so add the following to the end of the ``fire_weapon`` function:
|
||||
|
||||
::
|
||||
|
||||
|
||||
player_node.create_sound("Pistol_shot", self.global_transform.origin)
|
||||
|
||||
Now when the player fires the pistol, we'll play the ``Pistol_shot`` sound.
|
||||
@@ -634,7 +634,7 @@ To make a sound when the player reloads, we need to add the following right unde
|
||||
``reload_weapon`` function:
|
||||
|
||||
::
|
||||
|
||||
|
||||
player_node.create_sound("Gun_cock", player_node.camera.global_transform.origin)
|
||||
|
||||
Now when the player reloads, we'll play the ``Gun_cock`` sound.
|
||||
@@ -647,7 +647,7 @@ Open up ``Weapon_Rifle.gd``.
|
||||
To play sounds when the rifle is fired, add the following to the end of the ``fire_weapon`` function:
|
||||
|
||||
::
|
||||
|
||||
|
||||
player_node.create_sound("Rifle_shot", ray.global_transform.origin)
|
||||
|
||||
Now when the player fires the rifle, we'll play the ``Rifle_shot`` sound.
|
||||
@@ -656,7 +656,7 @@ To make a sound when the player reloads, we need to add the following right unde
|
||||
``reload_weapon`` function:
|
||||
|
||||
::
|
||||
|
||||
|
||||
player_node.create_sound("Gun_cock", player_node.camera.global_transform.origin)
|
||||
|
||||
Now when the player reloads, we'll play the ``Gun_cock`` sound.
|
||||
|
||||
@@ -1205,7 +1205,7 @@ Now we need to add this code:
|
||||
|
||||
func bullet_hit(damage, bullet_global_trans):
|
||||
var direction_vect = bullet_global_trans.basis.z.normalized() * BASE_BULLET_BOOST;
|
||||
|
||||
|
||||
apply_impulse((bullet_global_trans.origin - global_transform.origin).normalized(), direction_vect * damage)
|
||||
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ You can hide the gizmos in the 3D view of the editor through this menu:
|
||||
.. image:: img/tuto_3d6_1.png
|
||||
|
||||
To hide a specific type of gizmos, you can toggle them off in the "View" menu.
|
||||
|
||||
|
||||
.. image:: img/tuto_3d6_2.png
|
||||
|
||||
Default environment
|
||||
|
||||
@@ -8,7 +8,7 @@ Introduction
|
||||
|
||||
:ref:`Gridmaps <class_GridMap>` are a tool for creating 3D
|
||||
game levels, similar to the way :ref:`TileMap <doc_using_tilemaps>`
|
||||
works in 2D. You start with a predefined collection of 3D meshes (a
|
||||
works in 2D. You start with a predefined collection of 3D meshes (a
|
||||
:ref:`class_MeshLibrary`) that can be placed on a grid,
|
||||
as if you were building a level with an unlimited amount of Lego blocks.
|
||||
|
||||
@@ -34,7 +34,7 @@ scene to see an example of how to set up the mesh library.
|
||||
.. image:: img/gridmap_meshlibrary1.png
|
||||
|
||||
As you can see, this scene has a :ref:`class_Spatial` node as its root, and
|
||||
a number of :ref:`class_MeshInstance` node children.
|
||||
a number of :ref:`class_MeshInstance` node children.
|
||||
|
||||
If you don't need any physics in your scene, then you're done. However, in most
|
||||
cases you'll want to assign collision bodies to the meshes.
|
||||
@@ -42,7 +42,7 @@ cases you'll want to assign collision bodies to the meshes.
|
||||
Collisions
|
||||
----------
|
||||
|
||||
You can manually assign a :ref:`class_StaticBody` and
|
||||
You can manually assign a :ref:`class_StaticBody` and
|
||||
:ref:`class_CollisionShape` to each mesh. Alternatively, you can use the "Mesh" menu
|
||||
to automatically create the collision body based on the mesh data.
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ A default basis (unmodified) is akin to:
|
||||
GD.Print(identityBasis.y); // prints: (0, 1, 0)
|
||||
GD.Print(identityBasis.z); // prints: (0, 0, 1)
|
||||
|
||||
// The Identity basis is equivalent to:
|
||||
// The Identity basis is equivalent to:
|
||||
var basis = new Basis(Vector3.Right, Vector3.Up, Vector3.Back);
|
||||
GD.Print(basis); // prints: ((1, 0, 0), (0, 1, 0), (0, 0, 1))
|
||||
|
||||
@@ -189,7 +189,7 @@ To rotate relative to object space (the node's own transform) use the following:
|
||||
rotate_object_local(Vector3(1, 0, 0), PI)
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
|
||||
// Rotate locally
|
||||
RotateObjectLocal(Vector3.Right, Mathf.Pi);
|
||||
|
||||
@@ -341,7 +341,7 @@ Example of looking around, FPS style:
|
||||
// modify accumulated mouse rotation
|
||||
_rotationX += mouseMotion.Relative.x * LookAroundSpeed;
|
||||
_rotationY += mouseMotion.Relative.y * LookAroundSpeed;
|
||||
|
||||
|
||||
// reset rotation
|
||||
Transform transform = Transform;
|
||||
transform.basis = Basis.Identity;
|
||||
|
||||
@@ -6,13 +6,13 @@ Vertex displacement with shaders
|
||||
Introduction
|
||||
------------
|
||||
|
||||
This tutorial will teach you how to displace the vertices of
|
||||
a :ref:`Plane Mesh<class_PlaneMesh>` inside a shader. Vertex displacement can be used
|
||||
This tutorial will teach you how to displace the vertices of
|
||||
a :ref:`Plane Mesh<class_PlaneMesh>` inside a shader. Vertex displacement can be used
|
||||
for a wide variety of effects, but most commonly it is used
|
||||
as a quick way to turn a flat plane into a simple terrain. Typically
|
||||
this is done using a heightmap, but in order to keep everything self
|
||||
contained, in this tutorial we will use noise in a shader. At the end
|
||||
of this tutorial we will have a deformed plane that looks like a
|
||||
of this tutorial we will have a deformed plane that looks like a
|
||||
miniature terrain complete with dynamic lighting.
|
||||
|
||||
By reading this tutorial you should gain a basic understanding of:
|
||||
@@ -28,18 +28,18 @@ The plane mesh
|
||||
--------------
|
||||
|
||||
First, add a :ref:`Spatial<class_Spatial>` node to the scene to act as the root. Next, add a :ref:`MeshInstance<class_MeshInstance>`
|
||||
as a child.
|
||||
as a child.
|
||||
|
||||
.. image:: img/vertex_displacement_new_mesh.png
|
||||
|
||||
Select the newly created :ref:`MeshInstance<class_MeshInstance>`. Then click on the button that says "null"
|
||||
Select the newly created :ref:`MeshInstance<class_MeshInstance>`. Then click on the button that says "null"
|
||||
next to the :ref:`Mesh<class_MeshInstance>` in the Inspector. This will bring up a list of :ref:`PrimitiveMeshes<class_PrimitiveMesh>`.
|
||||
Select "New PlaneMesh".
|
||||
|
||||
.. image:: img/vertex_displacement_planemesh.png
|
||||
|
||||
The button will change into a small image of a plane. Click on it to enter into
|
||||
the Inspector for the :ref:`Plane Mesh<class_MeshInstance>`.
|
||||
the Inspector for the :ref:`Plane Mesh<class_MeshInstance>`.
|
||||
|
||||
Then, in the viewport, click in the upper left corner where it says [Perspective].
|
||||
A menu will appear. In the middle of the menu are options for how to display the scene.
|
||||
@@ -96,7 +96,7 @@ the ``render_mode`` the plane would appear blue because it would pick up the sky
|
||||
|
||||
Next we will define a vertex shader. The vertex shader determines where the vertices of your
|
||||
:ref:`Mesh<class_MeshInstance>` appear in the final scene. We will be using it to offset the height of each vertex and
|
||||
make our flat plane appear like a little terrain.
|
||||
make our flat plane appear like a little terrain.
|
||||
|
||||
We define the vertex shader like so:
|
||||
|
||||
@@ -106,7 +106,7 @@ We define the vertex shader like so:
|
||||
|
||||
}
|
||||
|
||||
With nothing in the ``vertex`` function Godot will use its default vertex shader. We can easily
|
||||
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:
|
||||
|
||||
::
|
||||
@@ -114,14 +114,14 @@ start to make changes by adding a single line:
|
||||
void vertex() {
|
||||
VERTEX.y += cos(VERTEX.x) * sin(VERTEX.z);
|
||||
}
|
||||
|
||||
|
||||
Adding this line you should get an image like the one below.
|
||||
|
||||
.. image:: img/vertex_displacement_cos.png
|
||||
|
||||
Okay, lets unpack this. The ``y`` value of the ``VERTEX`` is being increased. And we are passing
|
||||
the ``x`` and ``z`` components of the ``VERTEX`` as arguments to ``cos`` and ``sin`` this gives us
|
||||
a wave like appearance across the ``x`` and ``z`` axis.
|
||||
a wave like appearance across the ``x`` and ``z`` axis.
|
||||
|
||||
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.
|
||||
@@ -134,7 +134,7 @@ hills. We do so by scaling the inputs to the ``cos`` and ``sin`` functions.
|
||||
|
||||
.. image:: img/vertex_displacement_cos_scaled.png
|
||||
|
||||
This looks better, but it is still too spiky. This is because ``cos`` and ``sin`` output values between ``-1`` and ``1``,
|
||||
This looks better, but it is still too spiky. This is because ``cos`` and ``sin`` output values between ``-1`` and ``1``,
|
||||
so the range of the output is much too high. We correct this by multiplying the result by ``0.5`` to reduce the size.
|
||||
|
||||
::
|
||||
@@ -151,12 +151,12 @@ Noise
|
||||
-----
|
||||
|
||||
Noise is a very popular tool for procedural generation. Think of it as similar to the cosine function
|
||||
where you have repeating hills except with noise each hill has a different height. Understanding
|
||||
where you have repeating hills except with noise each hill has a different height. Understanding
|
||||
noise is not necessary for this tutorial. There is nothing wrong with simply copying and pasting
|
||||
the code below.
|
||||
|
||||
The first function we use to generate the noise is the ``hash`` function. It gives the random height
|
||||
for each of the hill tops.
|
||||
for each of the hill tops.
|
||||
|
||||
::
|
||||
|
||||
@@ -165,8 +165,8 @@ for each of the hill tops.
|
||||
}
|
||||
|
||||
You will find similar functions to this all over the internet. It is lovingly referred to as the
|
||||
'one-liner hash function'. It works well for simple noise, but there are many better alternatives
|
||||
floating around as well. For this tutorial it will work fine.
|
||||
'one-liner hash function'. It works well for simple noise, but there are many better alternatives
|
||||
floating around as well. For this tutorial it will work fine.
|
||||
|
||||
Next we define the ``noise`` function. It smoothly interpolates between the random heights.
|
||||
Again, if this code seems daunting, do not worry, just copy paste and move on with the tutorial.
|
||||
@@ -178,7 +178,7 @@ Again, if this code seems daunting, do not worry, just copy paste and move on wi
|
||||
vec2 f = fract(x);
|
||||
f = f * f * (3.0 - 2.0 * f);
|
||||
vec2 a = vec2(1.0, 0.0);
|
||||
return mix(mix(hash(p + a.yy), hash(p + a.xy), f.x),
|
||||
return mix(mix(hash(p + a.yy), hash(p + a.xy), f.x),
|
||||
mix(hash(p + a.yx), hash(p + a.xx), f.x), f.y);
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ we increase the frequency each level, decrease the amplitude, and calculate a ne
|
||||
return height;
|
||||
}
|
||||
|
||||
We can now use this noise function in place of ``cos`` and ``sin`` in the previous section.
|
||||
We can now use this noise function in place of ``cos`` and ``sin`` in the previous section.
|
||||
|
||||
::
|
||||
|
||||
@@ -211,7 +211,7 @@ We can now use this noise function in place of ``cos`` and ``sin`` in the previo
|
||||
.. image:: img/vertex_displacement_noise1.png
|
||||
|
||||
With the noise function in place we already have something that looks kind of cool.
|
||||
There is a lot of detail, it kind of looks hilly or mountainous.
|
||||
There is a lot of detail, it kind of looks hilly or mountainous.
|
||||
|
||||
Fragment shader
|
||||
---------------
|
||||
@@ -220,14 +220,14 @@ The difference between a vertex shader and a fragment shader is that the vertex
|
||||
runs per vertex and sets properties such as ``VERTEX`` (position) and ``NORMAL``, while
|
||||
the fragment shader runs per pixel and, most importantly, sets the ``ALBEDO`` color of the :ref:`Mesh<class_MeshInstance>`.
|
||||
|
||||
Now lets look at the :ref:`Mesh<class_MeshInstance>` with a regular shader instead of the wireframe. Set the
|
||||
Now lets look at the :ref:`Mesh<class_MeshInstance>` with a regular shader instead of the wireframe. Set the
|
||||
viewport back to 'Display Normal'.
|
||||
|
||||
.. image:: img/vertex_displacement_noise2.png
|
||||
|
||||
The :ref:`Mesh<class_MeshInstance>` appears completely white because the fragment shader is coloring each pixel white,
|
||||
but if every pixel is white we lose detail on the :ref:`Mesh<class_MeshInstance>`. So lets color each pixel based
|
||||
on the height calculated in the vertex shader. We do so by setting the ``COLOR`` variable
|
||||
on the height calculated in the vertex shader. We do so by setting the ``COLOR`` variable
|
||||
in the vertex shader. And by setting the ``ALBEDO`` in the fragment shader to the calculated
|
||||
``COLOR`` variable.
|
||||
|
||||
@@ -250,7 +250,7 @@ Uniforms
|
||||
--------
|
||||
|
||||
Uniform variables allow you to pass data from the game into the shader. They can
|
||||
be very useful for controlling shader effects. Uniforms can be almost any
|
||||
be very useful for controlling shader effects. Uniforms can be almost any
|
||||
datatype that can be used in the shader. To use a uniform you declare it in
|
||||
your :ref:`Shader<class_Shader>` using the keyword ``uniform``.
|
||||
|
||||
@@ -262,16 +262,16 @@ Lets make a uniform that changes the height of the terrain.
|
||||
|
||||
|
||||
Godot lets you initialize a uniform with a value, here ``height_scale`` is set to
|
||||
``0.5``. You can set uniforms from gdscript by calling the function ``set_shader_param``
|
||||
``0.5``. You can set uniforms from gdscript by calling the function ``set_shader_param``
|
||||
on the material corresponding to the shader. The value passed from gdscript takes
|
||||
precedence over the value used to initialize it in the shader.
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
material.set_shader_param("height_scale", 0.5)
|
||||
|
||||
Remember that the string passed into ``set_shader_param`` must match the name
|
||||
of the uniform variable in the :ref:`Shader<class_Shader>`. You can use the uniform variable anywhere
|
||||
of the uniform variable in the :ref:`Shader<class_Shader>`. You can use the uniform variable anywhere
|
||||
inside your :ref:`Shader<class_Shader>`. Here, we will use it to set the height value instead
|
||||
of arbitrarily multiplying by ``0.5``.
|
||||
|
||||
@@ -313,15 +313,15 @@ at the beginning of this tutorial, lets remove that.
|
||||
It looks slightly better now, you can see the light affecting the terrain, and it has
|
||||
turned blue as a result of the sky. The problem is the light is affecting the terrain
|
||||
as if it were a flat plane. This is because the light shader uses the normals of the
|
||||
:ref:`Mesh<class_MeshInstance>` to calculate light. The normals are stored in the :ref:`Mesh<class_MeshInstance>`, but we are changing
|
||||
:ref:`Mesh<class_MeshInstance>` to calculate light. The normals are stored in the :ref:`Mesh<class_MeshInstance>`, but we are changing
|
||||
the shape of the :ref:`Mesh<class_MeshInstance>` in the shader so the normals are no longer correct. To fix this
|
||||
we need to recalculate the normals in the shader. Godot makes this easy for us, all we
|
||||
have to do is calculate the new normal and set ``NORMAL`` to that value in the vertex shader.
|
||||
With ``NORMAL`` set Godot will do all the difficult lighting calculations for us.
|
||||
|
||||
To calculate the normal from noise we are going to use a technique called 'central differences'.
|
||||
This is used a lot, especially in places like shadertoy, to calculate normals in shaders.
|
||||
What we will do is calculate the noise at four points surrounding the vertex in the ``x`` and ``z`` directions and then calculate
|
||||
This is used a lot, especially in places like shadertoy, to calculate normals in shaders.
|
||||
What we will do is calculate the noise at four points surrounding the vertex in the ``x`` and ``z`` directions and then calculate
|
||||
the slope at the vertex from that. After all a normal is just an indicator of the slope of the
|
||||
noise.
|
||||
|
||||
@@ -333,15 +333,15 @@ We calculate the normal with one line in the vertex shader.
|
||||
vec3 normal = normalize(vec3(fbm(VERTEX.xz - e) - fbm(VERTEX.xz + e), 2.0 * e.x, fbm(VERTEX.xz - e.yx) - fbm(VERTEX.xz + e.yx)));
|
||||
NORMAL = normal;
|
||||
|
||||
The variable ``e`` just makes it easier to add and subtract the right value from the ``VERTEX``.
|
||||
Setting ``e`` to a lower number will increase the level of detail of the normal.
|
||||
The variable ``e`` just makes it easier to add and subtract the right value from the ``VERTEX``.
|
||||
Setting ``e`` to a lower number will increase the level of detail of the normal.
|
||||
|
||||
With ``NORMAL`` calculated the terrain now looks like:
|
||||
|
||||
.. image:: img/vertex_displacement_normal.png
|
||||
|
||||
This still does not look how we want it to. The issue here is that the noise changes
|
||||
faster than the vertices do. So when we calculate the normal at the point of the
|
||||
This still does not look how we want it to. The issue here is that the noise changes
|
||||
faster than the vertices do. So when we calculate the normal at the point of the
|
||||
``VERTEX`` it does not align with what we see in the final :ref:`Mesh<class_MeshInstance>`. In order to fix
|
||||
this we add more vertices. The below image is made with a :ref:`Mesh<class_MeshInstance>` with ``subdivision`` set
|
||||
to ``100``.
|
||||
@@ -363,6 +363,6 @@ shaders in Godot. As a further exercise try changing the ``height_scale`` from g
|
||||
try using different :ref:`Primitive Meshes<class_PrimitiveMesh>`, and try making your
|
||||
own functions to calculate ``height``.
|
||||
|
||||
For further information on how to use shaders in Godot
|
||||
For further information on how to use shaders in Godot
|
||||
you should check out the :ref:`doc_shading_language` page.
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ create a child sprite and load the torso, later accommodate it properly:
|
||||
.. image:: img/tuto_cutout3.png
|
||||
|
||||
This looks good. Let's see if our hierarchy works as a skeleton by
|
||||
rotating the torso. We can do this be pressing ``E`` to enter rotate mode,
|
||||
rotating the torso. We can do this be pressing ``E`` to enter rotate mode,
|
||||
and dragging with the left mouse button. To exit rotate mode hit ``ESC``.
|
||||
|
||||
.. image:: img/tutovec_torso1.gif
|
||||
@@ -121,7 +121,7 @@ simple! Or maybe not:
|
||||
|
||||
.. image:: img/tuto_cutout7.png
|
||||
|
||||
Right. In 2D, parent nodes appear below children nodes. Well, this sucks.
|
||||
Right. In 2D, parent nodes appear below children nodes. Well, this sucks.
|
||||
But how can this problem be solved? We want the left arm to appear behind
|
||||
the hip and the torso. For this, we can move the nodes behind the hip
|
||||
(note that you can bypass this by setting the Node2D Z property, but then you
|
||||
@@ -229,8 +229,8 @@ can be selected and animated.
|
||||
Finally, create endpoints in all meaningful extremities and connect the
|
||||
whole skeleton with bones up to the hip.
|
||||
|
||||
You may notice when connecting the hip and torso, that an extra bone is created.
|
||||
To fix this, select the root and hip node, open the Skeleton menu, click ``clear bones``.
|
||||
You may notice when connecting the hip and torso, that an extra bone is created.
|
||||
To fix this, select the root and hip node, open the Skeleton menu, click ``clear bones``.
|
||||
|
||||
.. image:: img/tuto_cutout15_2.png
|
||||
|
||||
@@ -354,9 +354,9 @@ Batch setting transition curves
|
||||
When creating complex animations and inserting many keyframes,
|
||||
editing the individual keyframe curves for each can become an endless
|
||||
task. For this, the Animation Editor has a small menu where changing
|
||||
animation curves is easy. First select the appropriate keys. Next click on the
|
||||
pencil icon in the bottom right of the animation panel, this will open the
|
||||
transition editor. Now click on one of the curve options most appropriate for
|
||||
animation curves is easy. First select the appropriate keys. Next click on the
|
||||
pencil icon in the bottom right of the animation panel, this will open the
|
||||
transition editor. Now click on one of the curve options most appropriate for
|
||||
your animation.
|
||||
|
||||
.. image:: img/tuto_cutout24.png
|
||||
|
||||
@@ -25,37 +25,37 @@ While it may look like a lot (and there is more as you scroll down), each field
|
||||
described in terms of what you should put in. We will nonetheless go over what
|
||||
is required in the submission form here as well.
|
||||
|
||||
* **Asset Name**:
|
||||
The name of your asset. Should be a unique, descriptive title of
|
||||
* **Asset Name**:
|
||||
The name of your asset. Should be a unique, descriptive title of
|
||||
what your asset is.
|
||||
* **Category**:
|
||||
* **Category**:
|
||||
The category that your asset belongs to, and will be shown in
|
||||
search results. The category is split into **Addons** and **Projects**.
|
||||
search results. The category is split into **Addons** and **Projects**.
|
||||
In-editor, assets of the Project type (Templates, Demos, Projects) only show
|
||||
up when viewing the AssetLib from the Project Manager, while assets of the
|
||||
Addon type will only be visible from inside a project.
|
||||
* **Godot version**:
|
||||
Addon type will only be visible from inside a project.
|
||||
* **Godot version**:
|
||||
The version of the engine that the asset works with.
|
||||
Currently it's not possible to have a single asset entry contain downloads for
|
||||
multiple engine versions, so you may need to re-submit the asset multiple times,
|
||||
with an entry for each Godot version it supports. This is particularly important
|
||||
when dealing with major versions of the engine, such as Godot 2.x and Godot 3.x.
|
||||
* **Version**:
|
||||
* **Version**:
|
||||
The version number of the asset. While you are free to choose
|
||||
and use any versioning scheme that you like, you may want to look into
|
||||
something such as `SemVer <https://semver.org>`_ if you want your asset's
|
||||
versioning scheme to be clear and consistent. Note that there is also an
|
||||
internal version number, incremented every time the asset download URL is
|
||||
changed or updated.
|
||||
* **Repository host**:
|
||||
changed or updated.
|
||||
* **Repository host**:
|
||||
Assets uploaded to the AssetLib are not hosted on it
|
||||
directly. Instead, they point to repositories hosted on third-party Git providers
|
||||
such as GitHub, GitLab or Bitbucket. This is where you choose which provider
|
||||
your asset uses, so the site can compute the final download link.
|
||||
* **Repository URL**:
|
||||
* **Repository URL**:
|
||||
The URL to your asset's files/webpage. This will vary
|
||||
based on your choice of provider, but it should look similar to `https://github.com/<user>/<project>`.
|
||||
* **Issues URL**:
|
||||
* **Issues URL**:
|
||||
The URL to your asset's issue tracker. Again, this will differ
|
||||
from repository host to repository host, but will likely look similar to
|
||||
`https://github.com/<user>/<project>/issues`. You may leave this field empty
|
||||
@@ -71,7 +71,7 @@ is required in the submission form here as well.
|
||||
* **License**:
|
||||
The license under which you are distributing the asset. The list
|
||||
includes a variety of free and open-source software licenses, such as GPL
|
||||
(v2 and v3), MIT, BSD and Boost Software License. You can visit `OpenSource.org <https://opensource.org>`_
|
||||
(v2 and v3), MIT, BSD and Boost Software License. You can visit `OpenSource.org <https://opensource.org>`_
|
||||
for a detailed description of each of the listed licenses.
|
||||
* **Description**:
|
||||
Finally, you can use the Description field for a textual
|
||||
@@ -83,7 +83,7 @@ You may also include up to three video and/or image previews, which will be show
|
||||
at the bottom of the asset page. Use the "Enable" checkbox on each of the preview
|
||||
submission boxes to enable them.
|
||||
|
||||
* **Type**:
|
||||
* **Type**:
|
||||
Either an image, or a video.
|
||||
* **Image/YouTube URL**:
|
||||
Either a link to the image, or to a video, hosted on YouTube.
|
||||
@@ -97,7 +97,7 @@ which you can visit on the AssetLib `here <https://godotengine.org/asset-library
|
||||
take up to a few days for your addon to be accepted (or rejected), so please
|
||||
be patient! You will be informed when your asset is reviewed. If it was rejected,
|
||||
you will be told why that may have been, and you will be able to submit it again
|
||||
with the appropriate changes.
|
||||
with the appropriate changes.
|
||||
You may have some luck accelerating the approval process by messaging the
|
||||
moderators/assetlib reviewers on IRC (the #godotengine-atelier channel on Freenode),
|
||||
or the official Discord server.
|
||||
|
||||
@@ -73,7 +73,7 @@ Now let's take a look at what an asset's page looks like and what it contains.
|
||||
Registering and logging in
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In order to upload assets to the AssetLib, you need to be logged in, and to do
|
||||
In order to upload assets to the AssetLib, you need to be logged in, and to do
|
||||
that, you need a registered user account. In the future, this may also give you
|
||||
access to other features, such as commenting on or rating the existing assets.
|
||||
You do *not* need to be logged in to browse and download the assets.
|
||||
@@ -119,13 +119,13 @@ like, with some differences:
|
||||
|
||||
|image8|
|
||||
|
||||
Similarly to the web version of the AssetLib, here you can search
|
||||
Similarly to the web version of the AssetLib, here you can search
|
||||
for assets by category, name, and sort them by factors such as name or edit date.
|
||||
|
||||
Notably, you can only fetch assets for the current version of Godot you are running.
|
||||
Also, you can only download Projects, Demos and Templates from the Project Manager
|
||||
view of the AssetLib, while Addons (tools, scripts, materials etc.) can only be
|
||||
downloaded from the in-project AssetLib.
|
||||
downloaded from the in-project AssetLib.
|
||||
In addition, unlike when using the web frontend, the search results are updated
|
||||
in real-time (you do not have to press Search after every change to your search
|
||||
query for the changes to take place).
|
||||
@@ -155,7 +155,7 @@ Here you can see a list of all the files that will be installed. You can tick of
|
||||
any of the files that you do not wish to install, and Godot will also inform you
|
||||
about any problems with files that it cannot install. These files will be shown
|
||||
in red, and hovering over them will show you a message stating why it cannot be
|
||||
installed.
|
||||
installed.
|
||||
|
||||
|image12|
|
||||
|
||||
|
||||
@@ -7,4 +7,4 @@ Audio
|
||||
|
||||
audio_buses
|
||||
audio_streams
|
||||
|
||||
|
||||
|
||||
@@ -90,13 +90,13 @@ Listing the video memory usage of the running game.
|
||||
Misc
|
||||
++++
|
||||
|
||||
Miscellaneous options for debug.
|
||||
Miscellaneous options for debug.
|
||||
|
||||
|
||||
Remote in Scene dock
|
||||
--------------------
|
||||
|
||||
When running your game, a bar will occur at the top of the ``Scene`` dock. You can switch to ``Remote`` and inspect or change the nodes' parameters in the running game.
|
||||
When running your game, a bar will occur at the top of the ``Scene`` dock. You can switch to ``Remote`` and inspect or change the nodes' parameters in the running game.
|
||||
|
||||
.. image:: img/overview_remote.png
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ exists. Example
|
||||
{
|
||||
DrawSelected()
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
DrawNormal();
|
||||
}
|
||||
@@ -83,7 +83,7 @@ for example:
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
func get_minimum_size():
|
||||
func get_minimum_size():
|
||||
return Vector2(30, 30)
|
||||
|
||||
.. code-tab:: csharp
|
||||
@@ -207,7 +207,7 @@ exists, but can be checked with the _notification callback:
|
||||
{
|
||||
case NotificationMouseEnter:
|
||||
// mouse entered the area of this control
|
||||
break;
|
||||
break;
|
||||
|
||||
case NotificationMouseExit:
|
||||
// mouse exited the area of this control
|
||||
|
||||
@@ -80,7 +80,7 @@ An example usage:
|
||||
|
||||
var t = new Theme();
|
||||
t.SetColor("fontColor", "Label", new Color(1.0f, 1.0f, 1.0f));
|
||||
|
||||
|
||||
var l = new Label();
|
||||
l.SetTheme(t);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ You might want to change the appearance of the mouse cursor in your game in orde
|
||||
1. Using project settings
|
||||
2. Using a script
|
||||
|
||||
Using project settings is a simpler but more limited way to customize the mouse cursor. The second way is more customizable but involves scripting.
|
||||
Using project settings is a simpler but more limited way to customize the mouse cursor. The second way is more customizable but involves scripting.
|
||||
|
||||
Using project settings
|
||||
----------------------
|
||||
@@ -35,12 +35,12 @@ Create a Node and attach the following script.
|
||||
# Load the custom images for the mouse cursor
|
||||
var arrow = load("res://arrow.png")
|
||||
var beam = load("res://beam.png")
|
||||
|
||||
|
||||
func _ready():
|
||||
# Changes only the arrow shape of the cursor
|
||||
# This is similar to changing it in the project settings
|
||||
Input.set_custom_mouse_cursor(arrow)
|
||||
|
||||
|
||||
# Changes a specific shape of the cursor (here the IBeam shape)
|
||||
Input.set_custom_mouse_cursor(beam, Input.CURSOR_IBEAM)
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ received input, in order:
|
||||
1. First of all, the standard :ref:`Node._input() <class_Node__input>` function
|
||||
will be called in any node that overrides it (and hasn't disabled input processing with :ref:`Node.set_process_input() <class_Node_set_process_input>`).
|
||||
If any function consumes the event, it can call :ref:`SceneTree.set_input_as_handled() <class_SceneTree_set_input_as_handled>`, and the event will
|
||||
not spread any more. This ensures that you can filter all events of interest, even before the GUI.
|
||||
not spread any more. This ensures that you can filter all events of interest, even before the GUI.
|
||||
For gameplay input, :ref:`Node._unhandled_input() <class_Node__unhandled_input>` is generally a better fit, because it allows the GUI to intercept the events.
|
||||
2. Second, it will try to feed the input to the GUI, and see if any
|
||||
control can receive it. If so, the :ref:`Control <class_Control>` will be called via the
|
||||
@@ -90,7 +90,7 @@ received input, in order:
|
||||
of mouse events via :ref:`Control._gui_input() <class_Control__gui_input>`
|
||||
callback, and whether these events are propagated further.
|
||||
3. If so far no one consumed the event, the unhandled input callback
|
||||
will be called if overridden (and not disabled with
|
||||
will be called if overridden (and not disabled with
|
||||
:ref:`Node.set_process_unhandled_input() <class_Node_set_process_unhandled_input>`).
|
||||
If any function consumes the event, it can call :ref:`SceneTree.set_input_as_handled() <class_SceneTree_set_input_as_handled>`, and the
|
||||
event will not spread any more. The unhandled input callback is ideal for full-screen gameplay events, so they are not received when a GUI is active.
|
||||
@@ -150,7 +150,7 @@ There are several specialised types of InputEvent, described in the table below:
|
||||
| | | information. (only available on mobile |
|
||||
| | | devices) |
|
||||
+-------------------------------------------------------------------+--------------------+-----------------------------------------+
|
||||
| :ref:`InputEventScreenDrag <class_InputEventScreenDrag>` | SCREEN_DRAG | Contains multi-touch drag information. |
|
||||
| :ref:`InputEventScreenDrag <class_InputEventScreenDrag>` | SCREEN_DRAG | Contains multi-touch drag information. |
|
||||
| | | (only available on mobile devices) |
|
||||
+-------------------------------------------------------------------+--------------------+-----------------------------------------+
|
||||
| :ref:`InputEventAction <class_InputEventAction>` | SCREEN_ACTION | Contains a generic action. These events |
|
||||
@@ -185,7 +185,7 @@ The Input singleton has a method for this:
|
||||
|
||||
var ev = InputEventAction.new()
|
||||
# set as move_left, pressed
|
||||
ev.set_as_action("move_left", true)
|
||||
ev.set_as_action("move_left", true)
|
||||
# feedback
|
||||
Input.parse_input_event(ev)
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ progress bar or loading screen, etc.
|
||||
# start your "loading..." animation
|
||||
get_node("animation").play("loading")
|
||||
|
||||
wait_frames = 1
|
||||
wait_frames = 1
|
||||
|
||||
``_process`` is where the loader is polled. ``poll`` is called, and then
|
||||
we deal with the return value from that call. ``OK`` means keep polling,
|
||||
|
||||
@@ -5,7 +5,7 @@ Encrypting save games
|
||||
|
||||
Why?
|
||||
----
|
||||
|
||||
|
||||
Because the world today is not the world of yesterday. A capitalist
|
||||
oligarchy runs the world and forces us to consume in order to keep the
|
||||
gears of this rotten society on track. As such, the biggest market for
|
||||
@@ -33,7 +33,7 @@ How?
|
||||
----
|
||||
|
||||
The class :ref:`File <class_File>` can open a file at a
|
||||
location and read/write data (integers, strings and variants).
|
||||
location and read/write data (integers, strings and variants).
|
||||
It also supports encryption.
|
||||
To create an encrypted file, a passphrase must be provided, like this:
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ that hasn't been translated, rotated or scaled.
|
||||
var m = Transform2D()
|
||||
print(m)
|
||||
# prints: ((1, 0), (0, 1), (0, 0))
|
||||
|
||||
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
@@ -442,7 +442,7 @@ If the matrix is orthonormal, then:
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
|
||||
# if m is orthonormal, then
|
||||
pos = mi.xform(pos)
|
||||
# is the same is
|
||||
|
||||
@@ -158,7 +158,7 @@ the velocity to the current position.
|
||||
|
||||
.. image:: img/vector_movement1.png
|
||||
|
||||
.. tip:: Velocity measures the **change** in position per unit of time. The
|
||||
.. tip:: Velocity measures the **change** in position per unit of time. The
|
||||
new position is found by adding velocity to the previous position.
|
||||
|
||||
- Pointing toward a target
|
||||
@@ -189,14 +189,14 @@ by its magnitude:
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
var a = Vector2(2, 4)
|
||||
var m = sqrt(a.x*a.x + a.y*a.y) # get magnitude "m" using the Pythagorean theorem
|
||||
var m = sqrt(a.x*a.x + a.y*a.y) # get magnitude "m" using the Pythagorean theorem
|
||||
a.x /= m
|
||||
a.y /= m
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
var a = new Vector2(2, 4);
|
||||
var m = Mathf.Sqrt(a.x*a.x + a.y*a.y); // get magnitude "m" using the Pythagorean theorem
|
||||
var m = Mathf.Sqrt(a.x*a.x + a.y*a.y); // get magnitude "m" using the Pythagorean theorem
|
||||
a.x /= m;
|
||||
a.y /= m;
|
||||
|
||||
@@ -249,7 +249,7 @@ to handle this. Here is a GDScript example of the diagram above using a
|
||||
move_and_collide(reflect)
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
|
||||
// KinematicCollision2D contains information about the collision
|
||||
KinematicCollision2D collision = MoveAndCollide(_velocity * delta);
|
||||
if (collision != null)
|
||||
@@ -270,13 +270,13 @@ direction, a scalar value has only magnitude.
|
||||
The formula for dot product takes two common forms:
|
||||
|
||||
.. math::
|
||||
|
||||
|
||||
A \cdot B = \left \| A \right \|\left \| B \right \|\cos \Theta
|
||||
|
||||
and
|
||||
|
||||
.. math::
|
||||
|
||||
|
||||
A \cdot B = A_{x}B_{x} + A_{y}B_{y}
|
||||
|
||||
However, in most cases it is easiest to use the built-in method. Note that
|
||||
|
||||
@@ -261,7 +261,7 @@ Code should be something like this:
|
||||
break; // with one that fails, it's enough
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Pretty cool, huh? But this gets much better! With a little more effort,
|
||||
similar logic will let us know when two convex polygons are overlapping
|
||||
too. This is called the Separating Axis Theorem (or SAT) and most
|
||||
@@ -328,7 +328,7 @@ Code should be something like this:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (allOut)
|
||||
{
|
||||
// a separating plane was found
|
||||
@@ -353,7 +353,7 @@ Code should be something like this:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (allOut)
|
||||
{
|
||||
overlapping = false;
|
||||
@@ -481,7 +481,7 @@ So the final algorithm is something like:
|
||||
print("Polygons collided!")
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
|
||||
var overlapping = true;
|
||||
|
||||
foreach (Plane plane in planesOfA)
|
||||
@@ -520,7 +520,7 @@ So the final algorithm is something like:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (allOut)
|
||||
{
|
||||
overlapping = false;
|
||||
|
||||
@@ -47,10 +47,10 @@ Here is how a ``_process()`` function might look like for you:
|
||||
func _process(delta):
|
||||
if Engine.editor_hint:
|
||||
# Code to execute in editor.
|
||||
|
||||
|
||||
if not Engine.editor_hint:
|
||||
# Code to execute in game.
|
||||
|
||||
|
||||
# Code to execute both in editor and in game.
|
||||
|
||||
.. note:: Modifications in editor are permanent. In our case, when we remove the script, the node will keep its rotation direction. Be careful so that you don't make unwanted modifications.
|
||||
|
||||
@@ -63,10 +63,10 @@ This object is not meant to be created directly, but is designed so that several
|
||||
|
||||
This object extends from :ref:`PacketPeer <class_PacketPeer>`, so it inherits all the useful methods for serializing, sending and receiving data. On top of that, it adds methods to set a peer, transfer mode, etc. It also includes signals that will let you know when peers connect or disconnect.
|
||||
|
||||
This class interface can abstract most types of network layers, topologies and libraries. By default Godot
|
||||
This class interface can abstract most types of network layers, topologies and libraries. By default Godot
|
||||
provides an implementation based on ENet (:ref:`NetworkedMultiplayerEnet <class_NetworkedMultiplayerENet>`), but this could be used to implement mobile APIs (for adhoc WiFi, Bluetooth) or custom device/console-specific networking APIs.
|
||||
|
||||
For most common cases, using this object directly is discouraged, as Godot provides even higher level networking facilities.
|
||||
For most common cases, using this object directly is discouraged, as Godot provides even higher level networking facilities.
|
||||
Yet it is made available in case a game has specific needs for a lower level API.
|
||||
|
||||
Initializing the network
|
||||
@@ -118,7 +118,7 @@ Terminating the networking feature:
|
||||
Managing connections
|
||||
--------------------
|
||||
|
||||
Some games accept connections at any time, others during the lobby phase. Godot can be requested to no longer accept
|
||||
Some games accept connections at any time, others during the lobby phase. Godot can be requested to no longer accept
|
||||
connections at any point (see `set_refuse_new_network_connections(bool)` and related methods on :ref:`SceneTree <class_SceneTree>`). To manage who connects, Godot provides the following signals in SceneTree:
|
||||
|
||||
Server and Clients:
|
||||
@@ -138,7 +138,7 @@ Clients:
|
||||
- `connection_failed`
|
||||
- `server_disconnected`
|
||||
|
||||
Again, all these functions are mainly useful for lobby management or for adding/removing players on the fly.
|
||||
Again, all these functions are mainly useful for lobby management or for adding/removing players on the fly.
|
||||
For these tasks the server clearly has to work as a server and you have do tasks manually such as sending a newly connected
|
||||
player information about other already connected players (e.g. their names, stats, etc).
|
||||
|
||||
@@ -168,7 +168,7 @@ Functions can be called in two fashions:
|
||||
- Reliable: the function call will arrive no matter what, but may take longer because it will be re-transmitted in case of failure.
|
||||
- Unreliable: if the function call does not arrive, it will not be re-transmitted, but if it arrives it will do it quickly.
|
||||
|
||||
In most cases, reliable is desired. Unreliable is mostly useful when synchronizing object positions (sync must happen constantly,
|
||||
In most cases, reliable is desired. Unreliable is mostly useful when synchronizing object positions (sync must happen constantly,
|
||||
and if a packet is lost, it's not that bad because a new one will eventually arrive and it would likely be outdated because the object moved further in the meantime, even if it was resent reliably).
|
||||
|
||||
There is also the `get_rpc_sender_id` function in `SceneTree` which can be used to check which peer (or peer ID) sent a RPC call.
|
||||
@@ -232,7 +232,7 @@ You might have noticed already something different, which is the usage of the `r
|
||||
::
|
||||
|
||||
remote func register_player(id, info):
|
||||
|
||||
|
||||
This keyword has two main uses. The first is to let Godot know that this function can be called from RPC. If no keywords are added
|
||||
Godot will block any attempts to call functions for security. This makes security work a lot easier (so a client can't call a function
|
||||
to delete a file on another client's system).
|
||||
@@ -252,7 +252,7 @@ The others will be explained further down.
|
||||
Note that you could also use the `get_rpc_sender_id` function on `SceneTree` to check which peer actually made the RPC call to `register_player`.
|
||||
|
||||
With this, lobby management should be more or less explained. Once you have your game going you will most likely want to add some
|
||||
extra security to make sure clients don't do anything funny (just validate the info they send from time to time, or before
|
||||
extra security to make sure clients don't do anything funny (just validate the info they send from time to time, or before
|
||||
game start). For the sake of simplicity and because each game will share different information, this is not shown here.
|
||||
|
||||
Starting the game
|
||||
@@ -264,13 +264,13 @@ special in itself, but we'll explain a few nice tricks that can be done at this
|
||||
Player scenes
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
In most games, each player will likely have its own scene. Remember that this is a multiplayer game, so in every peer
|
||||
In most games, each player will likely have its own scene. Remember that this is a multiplayer game, so in every peer
|
||||
you need to instance **one scene for each player connected to it**. For a 4 player game, each peer needs to instance 4 player nodes.
|
||||
|
||||
So, how to name such nodes? In Godot nodes need to have an unique name. It must also be relatively easy for a player to tell which
|
||||
nodes represent 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
|
||||
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:
|
||||
|
||||
::
|
||||
|
||||
@@ -69,7 +69,7 @@ or lose precision if the frame rate is too high or too low.
|
||||
|
||||
public class PhysicsScript : KinematicBody2D
|
||||
{
|
||||
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
{
|
||||
}
|
||||
@@ -85,9 +85,9 @@ for the character. Use the robot sprite and create a scene like this:
|
||||
|
||||
.. image:: img/kbscene.png
|
||||
|
||||
You'll notice that there's a warning icon next to our CollisionShape2D node,
|
||||
that's because we haven't defined a shape for it. Create a new CircleShape2D
|
||||
in the shape property of CollisionShape2D. Click on <CircleShape2D> to go to the
|
||||
You'll notice that there's a warning icon next to our CollisionShape2D node,
|
||||
that's because we haven't defined a shape for it. Create a new CircleShape2D
|
||||
in the shape property of CollisionShape2D. Click on <CircleShape2D> to go to the
|
||||
options for it, and set the radius to 30:
|
||||
|
||||
.. image:: img/kbradius.png
|
||||
@@ -121,21 +121,21 @@ collision happens, it stops right at the moment of the collision.
|
||||
So, let's move our sprite downwards until it hits the floor:
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends KinematicBody2D
|
||||
|
||||
func _physics_process(delta):
|
||||
move_and_collide(Vector2(0, 1)) # Move down 1 pixel per physics frame
|
||||
|
||||
.. code-tab:: csharp
|
||||
.. code-tab:: csharp
|
||||
|
||||
using Godot;
|
||||
using System;
|
||||
|
||||
public class PhysicsScript : KinematicBody2D
|
||||
{
|
||||
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
{
|
||||
// Move down 1 pixel per physics frame
|
||||
@@ -150,7 +150,7 @@ The next step will be adding gravity to the mix, this way it behaves a
|
||||
little more like an actual game character:
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends KinematicBody2D
|
||||
|
||||
@@ -163,7 +163,7 @@ little more like an actual game character:
|
||||
var motion = velocity * delta
|
||||
move_and_collide(motion)
|
||||
|
||||
.. code-tab:: csharp
|
||||
.. code-tab:: csharp
|
||||
|
||||
using Godot;
|
||||
using System;
|
||||
@@ -207,14 +207,14 @@ This adds simple walking support by pressing left and right:
|
||||
velocity.x = WALK_SPEED
|
||||
else:
|
||||
velocity.x = 0
|
||||
|
||||
|
||||
# We don't need to multiply velocity by delta because MoveAndSlide already takes delta time into account.
|
||||
|
||||
# The second parameter of move_and_slide is the normal pointing up.
|
||||
# In the case of a 2d platformer, in Godot upward is negative y, which translates to -1 as a normal.
|
||||
move_and_slide(velocity, Vector2(0, -1))
|
||||
|
||||
.. code-tab:: csharp
|
||||
.. code-tab:: csharp
|
||||
|
||||
using Godot;
|
||||
using System;
|
||||
@@ -246,7 +246,7 @@ This adds simple walking support by pressing left and right:
|
||||
// We don't need to multiply velocity by delta because MoveAndSlide already takes delta time into account.
|
||||
|
||||
// The second parameter of MoveAndSlide is the normal pointing up.
|
||||
// In the case of a 2d platformer, in Godot upward is negative y, which translates to -1 as a normal.
|
||||
// In the case of a 2d platformer, in Godot upward is negative y, which translates to -1 as a normal.
|
||||
MoveAndSlide(velocity, new Vector2(0, -1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ and to detect contact with other objects.
|
||||
|
||||
.. note:: In order to detect collisions, at least one ``Shape2D`` must be
|
||||
assigned to the object.
|
||||
|
||||
|
||||
The most common way to assign a shape is by adding a :ref:`CollisionShape2D <class_CollisionShape2D>`
|
||||
or :ref:`CollisionPolygon2D <class_CollisionPolygon2D>` as a child of the object.
|
||||
These nodes allow you to draw the shape directly in the editor workspace.
|
||||
@@ -85,7 +85,7 @@ In order to avoid this inaccuracy, any code that needs to access a body's proper
|
||||
be run in the :ref:`Node._physics_process() <class_Node__physics_process>`
|
||||
callback, which is called before each physics step at a constant frame rate
|
||||
(60 times per second by default).
|
||||
|
||||
|
||||
Collision Layers and Masks
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -183,7 +183,7 @@ that is overriding the global physics properties.
|
||||
When a rigid body is at rest and hasn't moved for a time, it goes to sleep.
|
||||
A sleeping body acts like a static body, and its forces are not calculated by
|
||||
the physics engine. The body will wake up when forces are applied, either by
|
||||
a collision or via code.
|
||||
a collision or via code.
|
||||
|
||||
Rigid body modes
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -318,9 +318,9 @@ occurred:
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends KinematicBody2D
|
||||
|
||||
|
||||
var velocity = Vector2(250, 250)
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
var collision_info = move_and_collide(velocity * delta)
|
||||
if collision_info:
|
||||
@@ -348,9 +348,9 @@ Or to bounce off of the colliding object:
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends KinematicBody2D
|
||||
|
||||
|
||||
var velocity = Vector2(250, 250)
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
var collision_info = move_and_collide(velocity * delta)
|
||||
if collision_info:
|
||||
|
||||
@@ -29,16 +29,16 @@ When starting our game, we will need to get the item details from Google such as
|
||||
|
||||
#First listen to the sku details update callback
|
||||
IAP.connect("sku_details_complete",self,"sku_details_complete")
|
||||
|
||||
|
||||
#Then ask google the details for these items
|
||||
IAP.sku_details_query(["pid1","pid2"]) #pid1 and pid2 are our product ids entered in Googleplay dashboard
|
||||
|
||||
|
||||
|
||||
|
||||
#This will be called when sku details are retrieved successfully
|
||||
func sku_details_complete():
|
||||
print(IAP.sku_details) #This will print the details as JSON format, refer the format in iap.gd
|
||||
print(IAP.sku_details["pid1"].price) #print formatted localized price
|
||||
|
||||
|
||||
We can use the IAP details to display the title, price and/or description on our shop scene.
|
||||
|
||||
Check if user purchased an item
|
||||
@@ -89,7 +89,7 @@ We can also implement other signals for the purchase flow and improve the user e
|
||||
Consumables and Non-Consumables
|
||||
-------------------------------
|
||||
|
||||
There are two types of products - consumables and non-consumables.
|
||||
There are two types of products - consumables and non-consumables.
|
||||
**Consumables** are purchased and used, for eg: healing potions which can be purchased again and again.
|
||||
**Non-consumables** are one time purchases, for eg: Level packs.
|
||||
|
||||
@@ -108,7 +108,7 @@ If our game has only consumables, we don't have to do this. We can set it to con
|
||||
::
|
||||
|
||||
IAP.set_auto_consume(true)
|
||||
|
||||
|
||||
If our game has only non-consumables, we can
|
||||
|
||||
::
|
||||
|
||||
@@ -311,7 +311,7 @@ that the import wasn't successful.
|
||||
return ERR_PARSE_ERROR
|
||||
|
||||
var color
|
||||
if options.use_red_anyway:
|
||||
if options.use_red_anyway:
|
||||
color = Color8(255, 0, 0)
|
||||
else:
|
||||
color = Color8(int(channels[0]), int(channels[1]), int(channels[2]))
|
||||
|
||||
@@ -7,7 +7,7 @@ bits = ARGUMENTS.get("bits", 64)
|
||||
|
||||
final_lib_path = 'demo/bin/'
|
||||
|
||||
# This makes sure to keep the session environment variables on windows,
|
||||
# This makes sure to keep the session environment variables on windows,
|
||||
# that way you can run scons in a vs 2017 prompt and it will find all the required tools
|
||||
env = Environment()
|
||||
if platform == "windows":
|
||||
|
||||
@@ -38,7 +38,7 @@ See also :ref:`Compiling <toc-devel-compiling>` as the build tools are identical
|
||||
to the ones you need to compile Godot from source.
|
||||
|
||||
You can download these repositories from GitHub or let Git
|
||||
do the work for you.
|
||||
do the work for you.
|
||||
Note that these repositories now have different branches for different versions of Godot. GDNative modules written for an earlier version of Godot will work in newer versions (with the exception of one breaking change in ARVR interfaces between 3.0 and 3.1) but not vise versa so make sure you download the correct branch.
|
||||
|
||||
If you are versioning your project using Git,
|
||||
@@ -69,7 +69,7 @@ Do make sure you clone recursive to pull in both repositories:
|
||||
|
||||
.. note:: The ``-b 3.0`` I've added as an example to show how to select a specific branch for a specific version of Godot.
|
||||
Also ``godot-cpp`` now includes ``godot_headers`` as a nested submodule, if you've manually downloaded them please make sure to place ``godot_headers`` inside of the ``godot-cpp`` folder.
|
||||
|
||||
|
||||
You don't have to do it this way but I've found it easiest to manage. If you decide to just download the repositories or just clone them into your folder, makes sure to keep the folder layout the same as I've setup here as much of the code we'll be showcasing here assumes the project has this layout.
|
||||
|
||||
If you cloned the example from the link specified in
|
||||
|
||||
@@ -22,18 +22,18 @@ Accordingly, most features available in GLSL are available in Godot's shading la
|
||||
Shader Programs
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
In GLSL each shader uses a separate program. You have one program for the vertex shader and one
|
||||
for the fragment shader. In Godot you have a single shader that contains a ``vertex`` and/or a
|
||||
``fragment`` function. If you only choose to write one, Godot will supply the other.
|
||||
In GLSL each shader uses a separate program. You have one program for the vertex shader and one
|
||||
for the fragment shader. In Godot you have a single shader that contains a ``vertex`` and/or a
|
||||
``fragment`` function. If you only choose to write one, Godot will supply the other.
|
||||
|
||||
Godot allows uniform variables and functions to be shared by defining the fragment and vertex
|
||||
shaders in one file. In GLSL the vertex and fragment programs cannot share variables except
|
||||
Godot allows uniform variables and functions to be shared by defining the fragment and vertex
|
||||
shaders in one file. In GLSL the vertex and fragment programs cannot share variables except
|
||||
when varyings are used.
|
||||
|
||||
Vertex attributes
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
In GLSL you can pass in per-vertex information using attributes. In GLSL you have the flexibility to
|
||||
In GLSL you can pass in per-vertex information using attributes. In GLSL you have the flexibility to
|
||||
pass in as much or as little as you want. In Godot you have a set number of input attributes
|
||||
including, ``VERTEX`` (position), ``COLOR``, ``UV``, ``UV2``, ``NORMAL``. For a complete list
|
||||
see the :ref:`Shading language reference <doc_shading_language>`.
|
||||
@@ -41,17 +41,17 @@ see the :ref:`Shading language reference <doc_shading_language>`.
|
||||
gl_Position
|
||||
^^^^^^^^^^^
|
||||
|
||||
``gl_Position`` receives the final position of a vertex specified in the vertex shader.
|
||||
It is specified by the user in clip space. Typically in GLSL the model space vertex position
|
||||
is passed in using a vertex attribute called ``position`` and you handle the
|
||||
conversion from model space to clip space manually.
|
||||
``gl_Position`` receives the final position of a vertex specified in the vertex shader.
|
||||
It is specified by the user in clip space. Typically in GLSL the model space vertex position
|
||||
is passed in using a vertex attribute called ``position`` and you handle the
|
||||
conversion from model space to clip space manually.
|
||||
|
||||
In Godot ``VERTEX`` specifies the vertex position in model space at the beginning of the ``vertex``
|
||||
function. Godot also handles the final conversion to clip space after the user-defined ``vertex``
|
||||
function is run. If you want to skip the conversion from model to view space, you can set the
|
||||
``render_mode`` to ``skip_vertex_transform``. If you want to skip all transforms, set
|
||||
``render_mode`` to ``skip_vertex_transform`` and set the ``PROJECTION_MATRIX`` to ``mat4(1.0)``
|
||||
in order to nullify the final transform from view space to clip space.
|
||||
function. Godot also handles the final conversion to clip space after the user-defined ``vertex``
|
||||
function is run. If you want to skip the conversion from model to view space, you can set the
|
||||
``render_mode`` to ``skip_vertex_transform``. If you want to skip all transforms, set
|
||||
``render_mode`` to ``skip_vertex_transform`` and set the ``PROJECTION_MATRIX`` to ``mat4(1.0)``
|
||||
in order to nullify the final transform from view space to clip space.
|
||||
|
||||
Varyings
|
||||
^^^^^^^^
|
||||
@@ -63,15 +63,15 @@ out of the vertex shader is defined with ``out`` in the vertex shader and ``in``
|
||||
Main
|
||||
^^^^
|
||||
|
||||
In GLSL each shader program looks like a self-contained C-style program. Accordingly, the main entry point
|
||||
is ``main``. If you are copying a vertex shader, rename ``main`` to ``vertex`` and if you are copying a
|
||||
In GLSL each shader program looks like a self-contained C-style program. Accordingly, the main entry point
|
||||
is ``main``. If you are copying a vertex shader, rename ``main`` to ``vertex`` and if you are copying a
|
||||
fragment shader, rename ``main`` to ``fragment``.
|
||||
|
||||
Constants
|
||||
^^^^^^^^^
|
||||
|
||||
Godot currently does not support constants. You can fake the functionality by using a uniform initialized
|
||||
to the value, but you will not benefit from the increased speed from using a constant.
|
||||
to the value, but you will not benefit from the increased speed from using a constant.
|
||||
|
||||
Macros
|
||||
^^^^^^
|
||||
@@ -79,14 +79,14 @@ Macros
|
||||
In keeping with its similarity to C, GLSL lets you use macros. Commonly ``#define`` is used to define
|
||||
constants or small functions. There is no straightforward way to translate defines to Godot's shading language.
|
||||
If it is a function that is defined, then replace with a function, and if it is a constant then replace with
|
||||
a uniform. For other macros (``#if``, ``#ifdef``, etc.) there is no equivalent because they run during the
|
||||
a uniform. For other macros (``#if``, ``#ifdef``, etc.) there is no equivalent because they run during the
|
||||
pre-processing stage of compilation.
|
||||
|
||||
Variables
|
||||
^^^^^^^^^
|
||||
|
||||
GLSL has many built in variables that are hard-coded. These variables are not uniforms, so they
|
||||
are not editable from the main program.
|
||||
are not editable from the main program.
|
||||
|
||||
+---------------------+---------+------------------------+-----------------------------------------------------+
|
||||
|Variable |Type |Equivalent |Description |
|
||||
@@ -102,20 +102,20 @@ are not editable from the main program.
|
||||
|gl_PointCoord |vec2 |POINT_COORD |Position on point when drawing Point primitives. |
|
||||
+---------------------+---------+------------------------+-----------------------------------------------------+
|
||||
|gl_FrontFacing |bool |FRONT_FACING |True if front face of primitive. |
|
||||
+---------------------+---------+------------------------+-----------------------------------------------------+
|
||||
+---------------------+---------+------------------------+-----------------------------------------------------+
|
||||
|
||||
.. _glsl_coordinates:
|
||||
|
||||
Coordinates
|
||||
^^^^^^^^^^^
|
||||
|
||||
``gl_FragCoord`` in GLSL and ``FRAGCOORD`` in the Godot shading language use the same coordinate system.
|
||||
``gl_FragCoord`` in GLSL and ``FRAGCOORD`` in the Godot shading language use the same coordinate system.
|
||||
If using UV in Godot, the y-coordinate will be flipped upside down.
|
||||
|
||||
Precision
|
||||
^^^^^^^^^
|
||||
|
||||
In GLSL you can define the precision of a given type (float or int) at the top of the shader with the
|
||||
In GLSL you can define the precision of a given type (float or int) at the top of the shader with the
|
||||
``precision`` keyword. In Godot you can set the precision of individual variables as you need by placing
|
||||
precision qualifiers ``lowp``, ``mediump``, and ``highp`` before the type when defining the variable. For
|
||||
more information see the :ref:`Shading Language <doc_shading_language>` reference.
|
||||
@@ -123,7 +123,7 @@ more information see the :ref:`Shading Language <doc_shading_language>` referenc
|
||||
Shadertoy
|
||||
---------
|
||||
|
||||
`Shadertoy <https://www.shadertoy.com>`_ is a website that makes it easy to write fragment shaders and
|
||||
`Shadertoy <https://www.shadertoy.com>`_ is a website that makes it easy to write fragment shaders and
|
||||
create `pure magic <https://www.shadertoy.com/view/4tjGRh>`_.
|
||||
|
||||
Shadertoy does not give the user full control over the shader. It only allows the user to write a
|
||||
@@ -139,18 +139,18 @@ has the regular types, including `Constants`_ and macros.
|
||||
mainImage
|
||||
^^^^^^^^^
|
||||
The main point of entry to a Shadertoy shader is the ``mainImage`` function. ``mainImage`` has two
|
||||
parameters, ``fragColor`` and ``fragCoord`` which correspond to ``COLOR`` and ``FRAGCOORD`` in Godot
|
||||
parameters, ``fragColor`` and ``fragCoord`` which correspond to ``COLOR`` and ``FRAGCOORD`` in Godot
|
||||
respectively. These parameters are handled automatically in Godot, so you do not need to include them
|
||||
as parameters yourself. Anything in the ``mainImage`` function should be copied into the ``fragment``
|
||||
function when porting to Godot.
|
||||
|
||||
Variables
|
||||
^^^^^^^^^
|
||||
In order to make writing fragment shaders straightforward and easy, Shadertoy handles passing a lot
|
||||
of helpful information from the main program into the fragment shader for you. A few of these
|
||||
have no equivalents in Godot because Godot has chosen not to make them available by default.
|
||||
This is okay because Godot gives you the ability to make your own uniforms. For variables whose
|
||||
equivalents are listed as "Provide with Uniform", the user is responsible for creating that
|
||||
In order to make writing fragment shaders straightforward and easy, Shadertoy handles passing a lot
|
||||
of helpful information from the main program into the fragment shader for you. A few of these
|
||||
have no equivalents in Godot because Godot has chosen not to make them available by default.
|
||||
This is okay because Godot gives you the ability to make your own uniforms. For variables whose
|
||||
equivalents are listed as "Provide with Uniform", the user is responsible for creating that
|
||||
uniform themself. The description gives the reader a hint about what they can pass in as a substitute.
|
||||
|
||||
+---------------------+---------+------------------------+-----------------------------------------------------+
|
||||
@@ -176,7 +176,7 @@ uniform themself. The description gives the reader a hint about what they can pa
|
||||
+---------------------+---------+------------------------+-----------------------------------------------------+
|
||||
|iChannelResolution[4]|vec3 |1.0 / TEXTURE_PIXEL_SIZE|Resolution of particular texture. |
|
||||
+---------------------+---------+------------------------+-----------------------------------------------------+
|
||||
|iChanneli |Sampler2D|TEXTURE |Godot provides only one built in, user can make more.|
|
||||
|iChanneli |Sampler2D|TEXTURE |Godot provides only one built in, user can make more.|
|
||||
+---------------------+---------+------------------------+-----------------------------------------------------+
|
||||
|
||||
Coordinates
|
||||
@@ -188,11 +188,11 @@ Coordinates
|
||||
The Book of Shaders
|
||||
-------------------
|
||||
|
||||
Similar to Shadertoy, `The Book of Shaders <https://thebookofshaders.com>`_ provides access to a fragment
|
||||
shader in the web browser for the user to interact with. The user is restricted to writing fragment
|
||||
shader code with a set list of uniforms passed in and with no ability to add additional uniforms.
|
||||
Similar to Shadertoy, `The Book of Shaders <https://thebookofshaders.com>`_ provides access to a fragment
|
||||
shader in the web browser for the user to interact with. The user is restricted to writing fragment
|
||||
shader code with a set list of uniforms passed in and with no ability to add additional uniforms.
|
||||
|
||||
For further help on porting shaders to various frameworks generally, The Book of Shaders provides
|
||||
For further help on porting shaders to various frameworks generally, The Book of Shaders provides
|
||||
a `page <https://thebookofshaders.com/04>`_ on running shaders in various frameworks.
|
||||
|
||||
Types
|
||||
@@ -210,8 +210,8 @@ a Book of Shaders ``main`` function should be copied into Godot's ``fragment`` f
|
||||
Variables
|
||||
^^^^^^^^^
|
||||
|
||||
The Book of Shaders sticks closer to plain GLSL than Shadertoy does. It also implements fewer uniforms than
|
||||
Shadertoy.
|
||||
The Book of Shaders sticks closer to plain GLSL than Shadertoy does. It also implements fewer uniforms than
|
||||
Shadertoy.
|
||||
|
||||
+---------------------+---------+------------------------+-----------------------------------------------------+
|
||||
|Variable |Type |Equivalent |Description |
|
||||
|
||||
@@ -9,7 +9,7 @@ Introduction
|
||||
For the most common cases, Godot provides ready to use materials for
|
||||
most types of shaders, such as SpatialMaterial, CanvasItemMaterial and
|
||||
ParticlesMaterial (@TODO link to tutorials/classes). They are flexible implementations that cover most
|
||||
use cases.
|
||||
use cases.
|
||||
|
||||
Shader materials allow writing a custom shader directly, for maximum flexibility.
|
||||
Examples of this are:
|
||||
|
||||
@@ -81,7 +81,7 @@ demonstrate the effect of different stretch modes. A single sprite, also
|
||||
directly at the target resolution. 3D will be largely unaffected,
|
||||
while in 2D there is no longer a 1:1 correspondence between sprite
|
||||
pixels and screen pixels, which may result in scaling artifacts.
|
||||
|
||||
|
||||
This is a good option if your 2D artwork has a sufficiently high
|
||||
resolution and does not require pixel-perfect rendering. Consider
|
||||
enabling texture filtering and mipmapping on your 2D textures and
|
||||
@@ -138,7 +138,7 @@ to the region outside the blue frame you see in the 2D editor.
|
||||
taller than the base resolution, the viewport will be grown in the
|
||||
vertical direction (and more content will be visible to the bottom).
|
||||
You can also think of this as "Expand Vertically".
|
||||
|
||||
|
||||
This is usually the best option for creating GUIs or HUDs that scale,
|
||||
so some controls can be anchored to the bottom
|
||||
(:ref:`doc_size_and_anchors`).
|
||||
@@ -151,7 +151,7 @@ to the region outside the blue frame you see in the 2D editor.
|
||||
screen is wider than the base resolution, the viewport will be grown
|
||||
in the horizontal direction (and more content will be visible to the
|
||||
right). You can also think of this as "Expand Horizontally".
|
||||
|
||||
|
||||
This is usually the best option for 2D games that scroll horizontally
|
||||
(like runners or platformers).
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ of making a procedural planet like the one below:
|
||||
.. note:: This tutorial does not cover how to code a dynamic atmosphere like the one this planet has.
|
||||
|
||||
This tutorial assumes you are familiar with how to set up a basic scene including:
|
||||
a :ref:`Camera <class_Camera>`, a :ref:`light source <class_OmniLight>`, a
|
||||
:ref:`Mesh Instance <class_MeshInstance>` with a :ref:`Primitive Mesh <class_PrimitiveMesh>`,
|
||||
and applying a :ref:`Spatial Material <class_SpatialMaterial>` to the mesh. The focus will be on using
|
||||
a :ref:`Camera <class_Camera>`, a :ref:`light source <class_OmniLight>`, a
|
||||
:ref:`Mesh Instance <class_MeshInstance>` with a :ref:`Primitive Mesh <class_PrimitiveMesh>`,
|
||||
and applying a :ref:`Spatial Material <class_SpatialMaterial>` to the mesh. The focus will be on using
|
||||
the :ref:`Viewport <class_Viewport>` to dynamically create textures that can be applied to the mesh.
|
||||
|
||||
During the course of this tutorial will cover the following topics:
|
||||
@@ -32,15 +32,15 @@ Setting up the Viewport
|
||||
|
||||
First, add a :ref:`Viewport <class_Viewport>` to the scene.
|
||||
|
||||
Next, set the size of the :ref:`Viewport <class_Viewport>` to ``(1024, 512)``. The
|
||||
:ref:`Viewport <class_Viewport>` can actually be any size so long as the width is double the height.
|
||||
The width needs to be double the height so that the image will accurately map onto the
|
||||
Next, set the size of the :ref:`Viewport <class_Viewport>` to ``(1024, 512)``. The
|
||||
:ref:`Viewport <class_Viewport>` can actually be any size so long as the width is double the height.
|
||||
The width needs to be double the height so that the image will accurately map onto the
|
||||
sphere as we will be using equirectangular projection, but more on that later.
|
||||
|
||||
.. image:: img/planet_new_viewport.png
|
||||
|
||||
Next, disable HDR and disable 3D. We don't need HDR because our planets surface will not be especially
|
||||
bright so values between ``0`` and ``1`` will be fine. And we will be using a :ref:`ColorRect <class_ColorRect>`
|
||||
bright so values between ``0`` and ``1`` will be fine. And we will be using a :ref:`ColorRect <class_ColorRect>`
|
||||
to render the surface, so we don't need 3D either.
|
||||
|
||||
Select the Viewport and add a :ref:`ColorRect <class_ColorRect>` as a child.
|
||||
@@ -73,12 +73,12 @@ apply to the sphere.
|
||||
Applying the texture
|
||||
--------------------
|
||||
|
||||
Now we go into the :ref:`Mesh Instance <class_MeshInstance>` and add a :ref:`Spatial Material <class_SpatialMaterial>`
|
||||
to it. No need for a special :ref:`Shader Material <class_ShaderMaterial>` (although that would be a good idea
|
||||
Now we go into the :ref:`Mesh Instance <class_MeshInstance>` and add a :ref:`Spatial Material <class_SpatialMaterial>`
|
||||
to it. No need for a special :ref:`Shader Material <class_ShaderMaterial>` (although that would be a good idea
|
||||
for more advanced effects, like the atmosphere in the example above).
|
||||
|
||||
Open the newly created :ref:`Spatial Material <class_SpatialMaterial>` and scroll down to the "Albedo" section
|
||||
and click beside the "Texture" property to add an Albedo Texture. Here we will apply the texture we made.
|
||||
and click beside the "Texture" property to add an Albedo Texture. Here we will apply the texture we made.
|
||||
Choose "New ViewportTexture"
|
||||
|
||||
.. image:: img/planet_new_viewport_texture.png
|
||||
@@ -92,16 +92,16 @@ Your sphere should now be colored in with the colors we rendered to the Viewport
|
||||
.. image:: img/planet_seam.png
|
||||
|
||||
Notice the ugly seam that forms where the texture wraps around? This is because we are picking
|
||||
a color based on UV coordinates and UV coordinates do not wrap around the texture. This is a classic
|
||||
problem in 2D map projection. Gamedevs often have a 2-dimensional map they want to project
|
||||
onto a sphere but when it wraps around it has large seams. There is an elegant work around for this
|
||||
a color based on UV coordinates and UV coordinates do not wrap around the texture. This is a classic
|
||||
problem in 2D map projection. Gamedevs often have a 2-dimensional map they want to project
|
||||
onto a sphere but when it wraps around it has large seams. There is an elegant work around for this
|
||||
problem that we will illustrate in the next section.
|
||||
|
||||
Making the planet texture
|
||||
-------------------------
|
||||
|
||||
So now when we render to our :ref:`Viewport <class_Viewport>` it appears magically on the sphere. But there is an ugly
|
||||
seam created by our texture coordinates. So how do we get a range of coordinates that wrap around
|
||||
seam created by our texture coordinates. So how do we get a range of coordinates that wrap around
|
||||
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. Lets apply them to the texture and see what happens
|
||||
|
||||
@@ -111,10 +111,10 @@ the sphere in a nice way? One solution is to use a function that repeats on the
|
||||
|
||||
.. image:: img/planet_sincos.png
|
||||
|
||||
Not too bad. If you look around you can see that the seam has now disappeared, but in its place we
|
||||
Not too bad. If you look around you can see that the seam has now disappeared, but in its place we
|
||||
have pinching at the poles. This pinching is due to the way Godot maps textures to spheres in its
|
||||
:ref:`Spatial Material <class_SpatialMaterial>`. It uses a projection technique called equirectangular
|
||||
projection. Which translates a spherical map onto a 2D plane.
|
||||
:ref:`Spatial Material <class_SpatialMaterial>`. It uses a projection technique called equirectangular
|
||||
projection. Which translates a spherical map onto a 2D plane.
|
||||
|
||||
.. note:: If you are interested in a little extra information on the technique, we will be converting from
|
||||
spherical coordinates into Cartesian coordinates. Spherical coordinates map the longitude and
|
||||
@@ -123,8 +123,8 @@ projection. Which translates a spherical map onto a 2D plane.
|
||||
|
||||
For each pixel we will calculate its 3D position on the sphere. From that we will use
|
||||
3D noise to determine a color value. By calculating the noise in 3D we solve the problem
|
||||
of the pinching at the poles. To understand why, picture the noise being calculated across the
|
||||
surface of the sphere instead of across the 2D plane. When you calculate across the
|
||||
of the pinching at the poles. To understand why, picture the noise being calculated across the
|
||||
surface of the sphere instead of across the 2D plane. When you calculate across the
|
||||
surface of the sphere you never hit an edge, and hence you never create a seam or
|
||||
a pinch point on the pole. The following code converts the ``UVs`` into Cartesion
|
||||
coordinates.
|
||||
@@ -216,13 +216,13 @@ Coloring the planet
|
||||
|
||||
Now to make the planet colors. There are many ways to do this, if you look on `Shadertoy <https://www.shadertoy.com>`_
|
||||
you will find all kinds of ways of mapping colors to procedural planet terrain. For now
|
||||
we will stick with a simple gradient between water and land.
|
||||
we will stick with a simple gradient between water and land.
|
||||
|
||||
To make a gradient in glsl we use the ``mix`` function. ``mix`` takes two values to interpolate
|
||||
between and a third parameter to choose how much to interpolate between them, in essence
|
||||
it *mixes* the two values together. In other APIs this function is often called ``lerp``.
|
||||
Although, ``lerp`` is typically reserved for mixing two floats together, ``mix`` can take any
|
||||
values whether it be floats or vector types.
|
||||
values whether it be floats or vector types.
|
||||
|
||||
::
|
||||
|
||||
@@ -231,7 +231,7 @@ values whether it be floats or vector types.
|
||||
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.x * 0.5 + 0.5``.
|
||||
``n.x`` smoothly varies between ``-1`` and ``1``. So we map it into the ``0-1`` range that ``mix`` expects.
|
||||
Now you can see that the colors change between blue and red.
|
||||
Now you can see that the colors change between blue and red.
|
||||
|
||||
.. image:: img/planet_noise_color.png
|
||||
|
||||
@@ -252,7 +252,7 @@ and it returns ``1`` whenever ``n.x`` is above ``0``.
|
||||
|
||||
One more thing to make this a little more planet-y. The land shouldn't be so blobby lets make the edges
|
||||
a little rougher. A trick that is often used in shaders to make rough looking terrain with noise is
|
||||
to layer levels of noise over one another at various frequencies. We use one layer to make the
|
||||
to layer levels of noise over one another at various frequencies. We use one layer to make the
|
||||
overall blobby structure of the continents. Then another layer breaks up the edges a bit, and then
|
||||
another, and so on. What we will do is calculate ``n`` with four lines of shader code
|
||||
instead of just one. ``n`` becomes:
|
||||
@@ -277,7 +277,7 @@ Making an ocean
|
||||
|
||||
One final thing to make this look more like a planet. The ocean and the land reflect light differently.
|
||||
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.
|
||||
into the ``alpha`` channel of our output ``COLOR`` and using it as a Roughness map.
|
||||
|
||||
::
|
||||
|
||||
@@ -286,24 +286,24 @@ into the ``alpha`` channel of our output ``COLOR`` and using it as a Roughness m
|
||||
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.
|
||||
|
||||
And then in the material under the "Metallic" section make sure ``Metallic`` is set to ``0`` and
|
||||
``Specular`` is set to ``1``. The reason for this is the water reflects light really well, but
|
||||
And then in the material under the "Metallic" section make sure ``Metallic`` is set to ``0`` and
|
||||
``Specular`` is set to ``1``. The reason for this is the water reflects light really well, but
|
||||
isn't metallic. These values are not physically accurate, but they are good enough for this demo.
|
||||
|
||||
Next under the "Roughness" section set ``Roughness`` to ``1`` and set the roughness texture to a
|
||||
:ref:`Viewport Texture <class_ViewportTexture>` pointing to our planet texture :ref:`Viewport <class_Viewport>`.
|
||||
Finally set the ``Texture Channel`` to ``Alpha``. This instructs the renderer to use the ``alpha``
|
||||
Next under the "Roughness" section set ``Roughness`` to ``1`` and set the roughness texture to a
|
||||
:ref:`Viewport Texture <class_ViewportTexture>` pointing to our planet texture :ref:`Viewport <class_Viewport>`.
|
||||
Finally set the ``Texture Channel`` to ``Alpha``. This instructs the renderer to use the ``alpha``
|
||||
channel of our output ``COLOR`` as the ``Roughness`` value.
|
||||
|
||||
.. image:: img/planet_ocean.png
|
||||
|
||||
You'll notice that very little changes except that the planet is no longer reflecting the sky.
|
||||
You'll notice that very little changes except that the planet is no longer reflecting the sky.
|
||||
This is happening because by default when something is rendered with an
|
||||
alpha value it gets drawn as a transparent object over the background. And since the default background
|
||||
of the :ref:`Viewport <class_Viewport>` is opaque, the ``alpha`` channel of the
|
||||
:ref:`Viewport Texture <class_ViewportTexture>` is ``1`` resulting in the planet texture being
|
||||
drawn with slightly fainter colors and a ``Roughness`` value of ``1`` everywhere. To correct this we
|
||||
go into the :ref:`Viewport <class_Viewport>` and set "Transparent Bg" to on. Since we are now
|
||||
of the :ref:`Viewport <class_Viewport>` is opaque, the ``alpha`` channel of the
|
||||
:ref:`Viewport Texture <class_ViewportTexture>` is ``1`` resulting in the planet texture being
|
||||
drawn with slightly fainter colors and a ``Roughness`` value of ``1`` everywhere. To correct this we
|
||||
go into the :ref:`Viewport <class_Viewport>` and set "Transparent Bg" to on. Since we are now
|
||||
rendering one transparent object on top of another we want to enable ``blend_premul_alpha``:
|
||||
|
||||
::
|
||||
@@ -311,7 +311,7 @@ rendering one transparent object on top of another we want to enable ``blend_pre
|
||||
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
|
||||
when blending one transparent color on top of another, even if the background has an ``alpha`` of ``0`` (as it
|
||||
does in this case), you end up with weird color bleed issues. Setting ``blend_premul_alpha`` fixes that.
|
||||
|
||||
Now the planet should look like it is reflecting light on the ocean but not the land. If you haven't done
|
||||
|
||||
@@ -8,30 +8,30 @@ Introduction
|
||||
|
||||
Think of :ref:`Viewports <class_Viewport>` as a screen that the game is projected onto. In order
|
||||
to see the game we need to have a surface to draw it on, this surface is
|
||||
the Root :ref:`Viewport <class_Viewport>`.
|
||||
the Root :ref:`Viewport <class_Viewport>`.
|
||||
|
||||
.. image:: img/viewportnode.png
|
||||
|
||||
|
||||
:ref:`Viewports <class_Viewport>` can also be added to the scene so that there
|
||||
are multiple surfaces to draw on. When we are drawing to a :ref:`Viewport <class_Viewport>`
|
||||
that is not the Root we call it a render target. We can access the contents
|
||||
of a render target by accessing its corresponding :ref:`texture <class_ViewportTexture>`.
|
||||
are multiple surfaces to draw on. When we are drawing to a :ref:`Viewport <class_Viewport>`
|
||||
that is not the Root we call it a render target. We can access the contents
|
||||
of a render target by accessing its corresponding :ref:`texture <class_ViewportTexture>`.
|
||||
By using a :ref:`Viewport <class_Viewport>` as a render target
|
||||
we can either render multiple scenes simultaneously or we can render to
|
||||
a :ref:`texture <class_ViewportTexture>` which is applied to an object in the scene, for example a dynamic
|
||||
a :ref:`texture <class_ViewportTexture>` which is applied to an object in the scene, for example a dynamic
|
||||
skybox.
|
||||
|
||||
:ref:`Viewports <class_Viewport>` have a variety of use cases including:
|
||||
:ref:`Viewports <class_Viewport>` have a variety of use cases including:
|
||||
|
||||
- Rendering 3D objects within a 2D game
|
||||
- Rendering 2D elements in a 3D game
|
||||
- Rendering dynamic textures
|
||||
- Generating procedural textures at runtime
|
||||
- Rendering multiple cameras in the same scene
|
||||
|
||||
What all these use cases have in common is that you are given the ability to
|
||||
draw objects to a texture as if it were another screen and then you can choose
|
||||
- Rendering multiple cameras in the same scene
|
||||
|
||||
What all these use cases have in common is that you are given the ability to
|
||||
draw objects to a texture as if it were another screen and then you can choose
|
||||
what to do with the resulting texture.
|
||||
|
||||
Input
|
||||
@@ -40,8 +40,8 @@ Input
|
||||
:ref:`Viewports <class_Viewport>` are also responsible for delivering properly adjusted and
|
||||
scaled input events to all their children nodes. Typically input is received by the
|
||||
nearest :ref:`Viewport <class_Viewport>` in the tree, but you can set :ref:`Viewports <class_Viewport>` to not receive input by checking
|
||||
'Disable Input' to 'on', this will allow the next nearest :ref:`Viewport <class_Viewport>` in the tree to capture
|
||||
the input.
|
||||
'Disable Input' to 'on', this will allow the next nearest :ref:`Viewport <class_Viewport>` in the tree to capture
|
||||
the input.
|
||||
|
||||
.. image:: img/input.png
|
||||
|
||||
@@ -67,7 +67,7 @@ following hierarchy:
|
||||
.. image:: img/cameras.png
|
||||
|
||||
CameraA will display on the Root :ref:`Viewport <class_Viewport>` and it will draw MeshA. CameraB
|
||||
will be captured by the :ref:`Viewport <class_Viewport>` Node along with MeshB. Even though MeshB is in the scene
|
||||
will be captured by the :ref:`Viewport <class_Viewport>` Node along with MeshB. Even though MeshB is in the scene
|
||||
hierarchy, it will still not be drawn to the Root :ref:`Viewport <class_Viewport>`. Similarly MeshA will not
|
||||
be visible from the :ref:`Viewport <class_Viewport>` node because :ref:`Viewport <class_Viewport>` nodes only
|
||||
capture nodes below them in the hierarchy.
|
||||
@@ -83,11 +83,11 @@ or make it the current camera by calling:
|
||||
Scale & stretching
|
||||
------------------
|
||||
|
||||
:ref:`Viewports <class_Viewport>` have a "size" property which represents the size of the :ref:`Viewport <class_Viewport>`
|
||||
in pixels. For :ref:`Viewports <class_Viewport>` which are children of :ref:`ViewportContainers <class_viewportcontainer>`,
|
||||
:ref:`Viewports <class_Viewport>` have a "size" property which represents the size of the :ref:`Viewport <class_Viewport>`
|
||||
in pixels. For :ref:`Viewports <class_Viewport>` which are children of :ref:`ViewportContainers <class_viewportcontainer>`,
|
||||
these values are overridden, but for all others this sets their resolution.
|
||||
|
||||
It is also possible to scale the 2D content and make the :ref:`Viewport <class_Viewport>` resolution
|
||||
It is also possible to scale the 2D content and make the :ref:`Viewport <class_Viewport>` resolution
|
||||
different than the one specified in size, by calling:
|
||||
|
||||
::
|
||||
@@ -199,7 +199,7 @@ their albedo.
|
||||
|
||||
Overdraw draws the meshes semi-transparent with an additive blend so you can see how the meshes overlap.
|
||||
|
||||
.. image:: img/overdraw.png
|
||||
.. image:: img/overdraw.png
|
||||
|
||||
*The same scene with Debug Draw set to Overdraw*
|
||||
|
||||
@@ -227,12 +227,12 @@ and then selecting the :ref:`Viewport <class_Viewport>` you want to use.
|
||||
|
||||
.. image:: img/texturepath.png
|
||||
|
||||
Every frame the :ref:`Viewport <class_Viewport>`'s texture is cleared away with the default clear color (or a transparent
|
||||
Every frame the :ref:`Viewport <class_Viewport>`'s texture is cleared away with the default clear color (or a transparent
|
||||
color if Transparent BG is set to true). This can be changed by setting Clear Mode to Never or Next Frame.
|
||||
As the name implies, Never means the texture will never be cleared while next frame will
|
||||
clear the texture on the next frame and then set itself to Never.
|
||||
|
||||
By default, re-rendering of the :ref:`Viewport <class_Viewport>` happens when the
|
||||
By default, re-rendering of the :ref:`Viewport <class_Viewport>` happens when the
|
||||
:ref:`Viewport <class_Viewport>`'s :ref:`ViewportTexture <class_ViewportTexture>` has been drawn in a frame. If visible, it will be
|
||||
rendered, otherwise it will not. This behavior can be changed to manual
|
||||
rendering (once), or always render, no matter if visible or not. This flexibility
|
||||
|
||||
@@ -18,7 +18,7 @@ You can use the function :ref:`get_interfaces <class_ARVRServer_get_interfaces>`
|
||||
To enable an interface you execute the following code:
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
var arvr_interface = ARVRServer.find_interface("Native mobile")
|
||||
if arvr_interface and arvr_interface.initialize():
|
||||
|
||||
Reference in New Issue
Block a user