Up to date
This page is up to date for Godot 4.3.
If you still find outdated information, please open an issue.
Dessin personnalisé en 2D
Introduction
Godot a des nœuds pour dessiner des sprites, des polygones, des particules, du texte, et beaucoup d'autres besoins communs de développement de jeux. Cependant, si vous avez besoin de quelque chose de spécifique non couvert avec les nœuds standards, vous pouvez faire que n'importe quel nœud 2D (par exemple, basé sur Control ou Node2D) dessine sur l'écran en utilisant des commandes personnalisées.
Le dessin personnalisé dans un nœud 2D est vraiment utile. Voici quelques cas d'utilisation :
Dessiner des formes ou une logique que les nœuds existants ne peuvent pas faire, comme une image avec des traînées ou un polygone animé spécial.
Dessiner un grand nombre d'objets simples, comme une grille ou une planche pour un jeu 2d. Le dessin personnalisé évite l'utilisation d'un grand nombre de nœuds, réduisant éventuellement l'utilisation de la mémoire et améliorant les performances.
Création d'un contrôle d'interface utilisateur personnalisé. Il existe de nombreux contrôles disponibles, mais si vous avez des besoins inhabituels, vous aurez probablement besoin d'un contrôle personnalisé.
Dessin
Ajoutez un script à n'importe quel nœud dérivé de CanvasItem, comme Control ou Node2D. Puis remplacez la fonction _draw().
extends Node2D
func _draw():
pass # Your draw commands here.
using Godot;
public partial class MyNode2D : Node2D
{
public override void _Draw()
{
// Your draw commands here.
}
}
Les commandes de dessin sont décrites dans la référence de classe CanvasItem. Elles sont nombreuses et nous allons voir quelques exemples de celles-ci en dessous.
Mise à jour
La fonction _draw n'est appelée qu'une fois, puis les commandes de dessin sont mises en mémoire tampon et mémorisées, de sorte que les appels ultérieurs sont inutiles.
Si redessiner est nécessaire parce qu'une variable ou autre chose a changé, appelez CanvasItem.queue_redraw dans ce même nœud et un nouvel appel _draw() se produira.
Voici un exemple un peu plus complexe, où nous avons une variable de texture qui peut être modifiée à tout moment, et à l'aide d'un setter, il force un redessin de la texture lorsqu'elle est modifiée :
extends Node2D
@export var texture : Texture2D:
set(value):
texture = value
queue_redraw()
func _draw():
draw_texture(texture, Vector2())
using Godot;
public partial class MyNode2D : Node2D
{
private Texture2D _texture;
[Export]
public Texture2D Texture
{
get
{
return _texture;
}
set
{
_texture = value;
QueueRedraw();
}
}
public override void _Draw()
{
DrawTexture(_texture, new Vector2());
}
}
Pour le voir en action, vous pouvez définir la texture en l'icône Godot sur l'éditeur en glissant-déposant l’icône par défaut icon.svg de l'onglet FileSystem à la propriété Texture sur l'onglet Inspector. En changeant la valeur de la propriété Texture pendant que le script précédent s'exécute, la texture change aussi automatiquement.
Dans certains cas, il peut être souhaitable de dessiner chaque image. Pour cela, appelez simplement queue_redraw, comme ceci :
extends Node2D
func _draw():
pass # Your draw commands here.
func _process(_delta):
queue_redraw()
using Godot;
public partial class MyNode2D : Node2D
{
public override void _Draw()
{
// Your draw commands here.
}
public override void _Process(double delta)
{
QueueRedraw();
}
}
Coordinates and line width alignment
L'API de dessin utilise le système de coordonnées du CanvasItem, pas nécessairement les coordonnées de pixels. Cela signifie que _draw() utilise l'espace de coordonnées créé après avoir appliqué la transformation du CanvasItem. En outre, vous pouvez appliquer une transformation personnalisée en plus de celle-ci en utilisant draw_set_transform ou draw_set_transform_matrix.
Lors de l'utilisation de draw_line, vous devriez considérer la largeur de la ligne. Lorsqu'il s'agit d'une largeur impaire, la position des points de début et de fin devrait être décalée de 0.5 pour garder la ligne au centre, comme montré ci-dessous.
func _draw():
draw_line(Vector2(1.5, 1.0), Vector2(1.5, 4.0), Color.GREEN, 1.0)
draw_line(Vector2(4.0, 1.0), Vector2(4.0, 4.0), Color.GREEN, 2.0)
draw_line(Vector2(7.5, 1.0), Vector2(7.5, 4.0), Color.GREEN, 3.0)
public override void _Draw()
{
DrawLine(new Vector2(1.5f, 1.0f), new Vector2(1.5f, 4.0f), Colors.Green, 1.0f);
DrawLine(new Vector2(4.0f, 1.0f), new Vector2(4.0f, 4.0f), Colors.Green, 2.0f);
DrawLine(new Vector2(7.5f, 1.0f), new Vector2(7.5f, 4.0f), Colors.Green, 3.0f);
}
Il en va de même pour la méthode draw_rect avec filled = false.
func _draw():
draw_rect(Rect2(1.0, 1.0, 3.0, 3.0), Color.GREEN)
draw_rect(Rect2(5.5, 1.5, 2.0, 2.0), Color.GREEN, false, 1.0)
draw_rect(Rect2(9.0, 1.0, 5.0, 5.0), Color.GREEN)
draw_rect(Rect2(16.0, 2.0, 3.0, 3.0), Color.GREEN, false, 2.0)
public override void _Draw()
{
DrawRect(new Rect2(1.0f, 1.0f, 3.0f, 3.0f), Colors.Green);
DrawRect(new Rect2(5.5f, 1.5f, 2.0f, 2.0f), Colors.Green, false, 1.0f);
DrawRect(new Rect2(9.0f, 1.0f, 5.0f, 5.0f), Colors.Green);
DrawRect(new Rect2(16.0f, 2.0f, 3.0f, 3.0f), Colors.Green, false, 2.0f);
}
Dessin avec anticrénelage
Godot offre des paramètres de méthode dans draw_line pour activer l'antialiasing, mais certaines méthodes de dessin personnalisé n'offrent pas ce paramètre antialiased.
Pour les méthodes de dessin personnalisées qui ne fournissent pas un paramètre antialiased, vous pouvez activer la MSAA 2D, qui affecte le rendu dans l'ensemble de la fenêtre d'affichage. Cela fournit un anticrénelage de haute qualité, mais un coût de performance plus élevé et seulement sur des éléments spécifiques. Voir Anticrénelage 2D pour plus d'informations.
Voici une comparaison d'une ligne de largeur minimale (width=-1) tracée avec antialiased=false, antialiased=true, et antialiased=false avec la MSAA 2D 2x, 4x et 8x activés.
Outils
Dessiner vos propres nœuds peut également être désiré lors de leur exécution dans l'éditeur. Cela peut être utilisé comme aperçu ou visualisation de certaines fonctionnalités ou comportements.
Pour ce faire, vous pouvez utiliser l'annotation tool aussi bien avec GDScript qu'avec C#. Consultez l'exemple ci-dessous et Exécuter le code dans l'éditeur pour plus d'informations.
Exemple 1 : Dessiner un forme personnalisée
Nous allons maintenant utiliser la fonctionnalité de dessin personnalisé du moteur Godot pour dessiner quelque chose pour lequel Godot ne fournit pas de fonctions. Nous allons recréer le logo de Godot mais avec du code, seulement en utilisant des fonctions de dessin.
Vous devrez coder une fonction pour réaliser cela et dessiner le logo vous-même.
Note
The following instructions use a fixed set of coordinates that could be too small for high resolution screens (larger than 1080p). If that is your case, and the drawing is too small consider increasing your window scale in the project setting Display > Window > Stretch > Scale to adjust the project to a higher resolution (a 2 or 4 scale tends to work well).
Dessiner une forme de polygone personnalisée
Bien qu'il y ait un nœud dédié à dessiner des polygones personnalisés ( Polygon2D), nous utiliserons dans ce cas exclusivement des fonctions de dessin de plus bas niveau pour les combiner sur le même nœud et être en mesure de créer des formes plus complexes plus tard.
D'abord, nous définirons un ensemble de points -ou coordonnées X et Y - qui formeront la base de notre forme :
extends Node2D
var coords_head : Array = [
[ 22.952, 83.271 ], [ 28.385, 98.623 ],
[ 53.168, 107.647 ], [ 72.998, 107.647 ],
[ 99.546, 98.623 ], [ 105.048, 83.271 ],
[ 105.029, 55.237 ], [ 110.740, 47.082 ],
[ 102.364, 36.104 ], [ 94.050, 40.940 ],
[ 85.189, 34.445 ], [ 85.963, 24.194 ],
[ 73.507, 19.930 ], [ 68.883, 28.936 ],
[ 59.118, 28.936 ], [ 54.494, 19.930 ],
[ 42.039, 24.194 ], [ 42.814, 34.445 ],
[ 33.951, 40.940 ], [ 25.637, 36.104 ],
[ 17.262, 47.082 ], [ 22.973, 55.237 ]
]
using Godot;
public partial class MyNode2D : Node2D
{
private float[,] _coordsHead =
{
{ 22.952f, 83.271f }, { 28.385f, 98.623f },
{ 53.168f, 107.647f }, { 72.998f, 107.647f },
{ 99.546f, 98.623f }, { 105.048f, 83.271f },
{ 105.029f, 55.237f }, { 110.740f, 47.082f },
{ 102.364f, 36.104f }, { 94.050f, 40.940f },
{ 85.189f, 34.445f }, { 85.963f, 24.194f },
{ 73.507f, 19.930f }, { 68.883f, 28.936f },
{ 59.118f, 28.936f }, { 54.494f, 19.930f },
{ 42.039f, 24.194f }, { 42.814f, 34.445f },
{ 33.951f, 40.940f }, { 25.637f, 36.104f },
{ 17.262f, 47.082f }, { 22.973f, 55.237f }
};
}
Ce format, bien que compact, n'est pas celui que Godot comprend pour dessiner un polygone. Dans un scénario différent, nous pourrions devoir charger ces coordonnées à partir d'un fichier ou calculer les positions pendant que l'application fonctionne, et alors une transformation pourrait être nécessaire.
Pour transformer ces coordonnées dans le bon format, nous allons créer une nouvelle méthode float_array_to_Vector2Array(). Ensuite, nous redéfinirons la fonction _ready(), que Godot n'appellera qu'une seule fois - au début de l'exécution - pour charger ces coordonnées dans une variable :
var head : PackedVector2Array
func float_array_to_Vector2Array(coords : Array) -> PackedVector2Array:
# Convert the array of floats into a PackedVector2Array.
var array : PackedVector2Array = []
for coord in coords:
array.append(Vector2(coord[0], coord[1]))
return array
func _ready():
head = float_array_to_Vector2Array(coords_head);
private Vector2[] _head;
private Vector2[] FloatArrayToVector2Array(float[,] coords)
{
// Convert the array of floats into an array of Vector2.
int size = coords.GetUpperBound(0);
Vector2[] array = new Vector2[size + 1];
for (int i = 0; i <= size; i++)
{
array[i] = new Vector2(coords[i, 0], coords[i, 1]);
}
return array;
}
public override void _Ready()
{
_head = FloatArrayToVector2Array(_coordsHead);
}
Pour enfin dessiner notre première forme, nous utiliserons la méthode draw_polygon et y passons les points (comme un tableau de coordonnées Vector2) et sa couleur, comme ceci :
func _draw():
# We are going to paint with this color.
var godot_blue : Color = Color("478cbf")
# We pass the PackedVector2Array to draw the shape.
draw_polygon(head, [ godot_blue ])
public override void _Draw()
{
// We are going to paint with this color.
Color godotBlue = new Color("478cbf");
// We pass the array of Vector2 to draw the shape.
DrawPolygon(_head, new Color[]{ godotBlue });
}
Quand de l'exécution, vous devriez voir quelque chose comme ça :
Notez que la partie inférieure du logo semble segmentée - c'est parce qu'une faible quantité de points ont été utilisés pour définir cette partie. Pour simuler une courbe lisse, nous pourrions ajouter plus de points à notre tableau, ou peut-être utiliser une fonction mathématique pour interpoler une courbe et créer une forme lisse à partir du code (voir example 2).
Les polygones vont toujours connecter leur dernier point défini à leur premier afin d'avoir une forme fermée.
Dessiner des lignes connectées
Dessiner une séquence de lignes connectées qui ne se rejoignent pas pour former un polygone est très semblable à la méthode précédente. Nous utiliserons un ensemble de lignes connectées pour dessiner la bouche du logo de Godot.
D'abord, nous définirons la liste des coordonnées qui forment la forme de la bouche, comme ceci :
var coords_mouth = [
[ 22.817, 81.100 ], [ 38.522, 82.740 ],
[ 39.001, 90.887 ], [ 54.465, 92.204 ],
[ 55.641, 84.260 ], [ 72.418, 84.177 ],
[ 73.629, 92.158 ], [ 88.895, 90.923 ],
[ 89.556, 82.673 ], [ 105.005, 81.100 ]
]
private float[,] _coordsMouth =
{
{ 22.817f, 81.100f }, { 38.522f, 82.740f },
{ 39.001f, 90.887f }, { 54.465f, 92.204f },
{ 55.641f, 84.260f }, { 72.418f, 84.177f },
{ 73.629f, 92.158f }, { 88.895f, 90.923f },
{ 89.556f, 82.673f }, { 105.005f, 81.100f }
};
Nous allons charger ces coordonnées dans une variable et définir une variable supplémentaire pour une épaisseur de ligne configurable :
var mouth : PackedVector2Array
var _mouth_width : float = 4.4
func _ready():
head = float_array_to_Vector2Array(coords_head);
mouth = float_array_to_Vector2Array(coords_mouth);
private Vector2[] _mouth;
private float _mouthWidth = 4.4f;
public override void _Ready()
{
_head = FloatArrayToVector2Array(_coordsHead);
_mouth = FloatArrayToVector2Array(_coordsMouth);
}
Et finalement nous utiliserons la méthode draw_polyline afin de dessiner la ligne, comme ceci :
func _draw():
# We will use white to draw the line.
var white : Color = Color.WHITE
var godot_blue : Color = Color("478cbf")
draw_polygon(head, [ godot_blue ])
# We draw the while line on top of the previous shape.
draw_polyline(mouth, white, _mouth_width)
public override void _Draw()
{
// We will use white to draw the line.
Color white = Colors.White;
Color godotBlue = new Color("478cbf");
DrawPolygon(_head, new Color[]{ godotBlue });
// We draw the while line on top of the previous shape.
DrawPolyline(_mouth, white, _mouthWidth);
}
Vous devriez obtenir la sortie suivante :
Unlike draw_polygon(), polylines can only have a single unique color
for all its points (the second argument). This method has 2 additional
arguments: the width of the line (which is as small as possible by default)
and enabling or disabling the antialiasing (it is disabled by default).
L'ordre des appels de _draw est important- comme avec les positions des Node dans la hiérarchie en arbre, les différentes formes seront dessinées de haut en bas, entraînant les dernières formes à cacher les précédentes si elles se chevauchent. Dans ce cas, nous voulons que la bouche soit dessinée sur la tête, donc nous l'avons mis après.
Remarquez comment nous pouvons définir les couleurs de différentes manières, soit avec un code hexadécimal ou un nom de couleur prédéfini. Regardez la classe Color pour d'autres constantes et de moyens de définir des Colors.
Dessiner des cercles
Pour créer les yeux, nous allons ajouter 4 appels supplémentaires pour dessiner les formes des yeux, dans différentes tailles, couleurs et positions.
Pour dessiner un cercle, vous le positionnez en fonction de son centre à l'aide de la méthode draw_circle. Le premier paramètre est un Vector2 avec les coordonnées de son centre, le second est son rayon, et le troisième est sa couleur :
func _draw():
var white : Color = Color.WHITE
var godot_blue : Color = Color("478cbf")
var grey : Color = Color("414042")
draw_polygon(head, [ godot_blue ])
draw_polyline(mouth, white, _mouth_width)
# Four circles for the 2 eyes: 2 white, 2 grey.
draw_circle(Vector2(42.479, 65.4825), 9.3905, white)
draw_circle(Vector2(85.524, 65.4825), 9.3905, white)
draw_circle(Vector2(43.423, 65.92), 6.246, grey)
draw_circle(Vector2(84.626, 66.008), 6.246, grey)
public override void _Draw()
{
Color white = Colors.White;
Color godotBlue = new Color("478cbf");
Color grey = new Color("414042");
DrawPolygon(_head, new Color[]{ godotBlue });
DrawPolyline(_mouth, white, _mouthWidth);
// Four circles for the 2 eyes: 2 white, 2 grey.
DrawCircle(new Vector2(42.479f, 65.4825f), 9.3905f, white);
DrawCircle(new Vector2(85.524f, 65.4825f), 9.3905f, white);
DrawCircle(new Vector2(43.423f, 65.92f), 6.246f, grey);
DrawCircle(new Vector2(84.626f, 66.008f), 6.246f, grey);
}
Lors de l'exécution, vous devriez avoir quelque chose comme ça :
Pour les arcs de cercle non remplis (portions d'une forme de cercle entre certains angles arbitraires), vous pouvez utiliser la méthode draw_arc.
Dessiner des lignes
Pour dessiner la forme finale (le nez) nous utiliserons une ligne pour l'approximer.
draw_line peut être utilisé pour dessiner un seul segment en fournissant ses coordonnées de début et de fin comme arguments, comme ceci :
func _draw():
var white : Color = Color.WHITE
var godot_blue : Color = Color("478cbf")
var grey : Color = Color("414042")
draw_polygon(head, [ godot_blue ])
draw_polyline(mouth, white, _mouth_width)
draw_circle(Vector2(42.479, 65.4825), 9.3905, white)
draw_circle(Vector2(85.524, 65.4825), 9.3905, white)
draw_circle(Vector2(43.423, 65.92), 6.246, grey)
draw_circle(Vector2(84.626, 66.008), 6.246, grey)
# Draw a short but thick white vertical line for the nose.
draw_line(Vector2(64.273, 60.564), Vector2(64.273, 74.349), white, 5.8)
public override void _Draw()
{
Color white = Colors.White;
Color godotBlue = new Color("478cbf");
Color grey = new Color("414042");
DrawPolygon(_head, new Color[]{ godotBlue });
DrawPolyline(_mouth, white, _mouthWidth);
DrawCircle(new Vector2(42.479f, 65.4825f), 9.3905f, white);
DrawCircle(new Vector2(85.524f, 65.4825f), 9.3905f, white);
DrawCircle(new Vector2(43.423f, 65.92f), 6.246f, grey);
DrawCircle(new Vector2(84.626f, 66.008f), 6.246f, grey);
// Draw a short but thick white vertical line for the nose.
DrawLine(new Vector2(64.273f, 60.564f), new Vector2(64.273f, 74.349f),
white, 5.8f);
}
Vous devriez maintenant être capable de voir la forme suivante à l'écran :
Notez que si de multiples lignes non connectées vont être dessinées en même temps, vous pouvez obtenir une meilleure performance en les dessinant tous en un seul appel, en utilisant la méthode draw_multiline.
Dessiner du texte
While using the Label Node is the most common way to add text to your application, the low level _draw function includes functionality to add text to your custom Node drawing. We will use it to add the name "GODOT" under the robot head.
Nous utiliserons la méthode draw_string pour le faire, comme ceci :
var default_font : Font = ThemeDB.fallback_font;
func _draw():
var white : Color = Color.WHITE
var godot_blue : Color = Color("478cbf")
var grey : Color = Color("414042")
draw_polygon(head, [ godot_blue ])
draw_polyline(mouth, white, _mouth_width)
draw_circle(Vector2(42.479, 65.4825), 9.3905, white)
draw_circle(Vector2(85.524, 65.4825), 9.3905, white)
draw_circle(Vector2(43.423, 65.92), 6.246, grey)
draw_circle(Vector2(84.626, 66.008), 6.246, grey)
draw_line(Vector2(64.273, 60.564), Vector2(64.273, 74.349), white, 5.8)
# Draw GODOT text below the logo with the default font, size 22.
draw_string(default_font, Vector2(20, 130), "GODOT",
HORIZONTAL_ALIGNMENT_CENTER, 90, 22)
private Font _defaultFont = ThemeDB.FallbackFont;
public override void _Draw()
{
Color white = Colors.White;
Color godotBlue = new Color("478cbf");
Color grey = new Color("414042");
DrawPolygon(_head, new Color[]{ godotBlue });
DrawPolyline(_mouth, white, _mouthWidth);
DrawCircle(new Vector2(42.479f, 65.4825f), 9.3905f, white);
DrawCircle(new Vector2(85.524f, 65.4825f), 9.3905f, white);
DrawCircle(new Vector2(43.423f, 65.92f), 6.246f, grey);
DrawCircle(new Vector2(84.626f, 66.008f), 6.246f, grey);
DrawLine(new Vector2(64.273f, 60.564f), new Vector2(64.273f, 74.349f),
white, 5.8f);
// Draw GODOT text below the logo with the default font, size 22.
DrawString(_defaultFont, new Vector2(20f, 130f), "GODOT",
HorizontalAlignment.Center, 90, 22);
}
Ici, nous chargeons d'abord dans la variable defaultFont la police par défaut configurée (une police personnalisée peut être définie à la place), puis nous passons les paramètres suivants : police, position, texte, alignement horizontal, largeur et taille de police.
Vous devriez voir ce qui suit sur votre écran :
Des paramètres supplémentaires ainsi que d'autres méthodes liées au texte et aux caractères peuvent être trouvés sur la référence de classe de CanvasItem.
Afficher le dessin lors de l'édition
Alors que le code est jusqu'à présent capable de dessiner le logo sur une fenêtre d'exécution, il ne s'affichera pas sur la vue 2D de l'éditeur. Dans certains cas, vous pouvez souhaiter également montrer votre contrôle ou Node2D personnalisé dans l'éditeur, pour le positionner et l'élargir correctement, comme la plupart des autres nœuds font.
Pour afficher le logo directement dans l'éditeur (sans l'exécuter), vous pouvez utiliser l'annotation @tool pour demander que le dessin personnalisé du nœud apparaisse également pendant l'édition, comme ceci :
@tool
extends Node2D
using Godot;
[Tool]
public partial class MyNode2D : Node2D
Vous devrez sauvegarder votre scène, reconstruire votre projet (uniquement pour C#) et recharger manuellement la scène actuelle via l'option de menu Scene > Reload Saved Scene pour actualiser le nœud actuel dans la vue 2D la première fois que vous ajoutez ou supprimez l'annotation @tool.
Animation
Si nous voulions faire en sorte que la forme personnalisée change au moment de l'exécution, nous pourrions modifier les méthodes appelées ou leurs arguments au moment de l'exécution, ou appliquer une transformation.
Par exemple, si nous voulons que la forme personnalisée que nous venons de concevoir tourne, nous pourrions ajouter la variable et le code suivants aux méthodes _ready et _process :
extends Node2D
@export var rotation_speed : float = 1 # In radians per second.
func _ready():
rotation = 0
...
func _process(delta: float):
rotation -= rotation_speed * delta
[Export]
public float RotationSpeed { get; set; } = 1.0f; // In radians per second.
public override void _Ready()
{
Rotation = 0;
...
}
public override void _Process(double delta)
{
Rotation -= RotationSpeed * (float)delta;
}
Le problème avec le code ci-dessus est que, puisque nous avons créé les points approximativement sur un rectangle commençant à partir du coin supérieur gauche, la coordonnée (0, 0) et s'étendant vers la droite et vers le bas, nous voyons que la rotation est effectuée en utilisant le coin supérieur gauche comme pivot. Un changement de transformation de position sur le nœud ne nous aidera pas ici, car la transformation de rotation est appliquée en premier.
Bien que nous pourrions réécrire toutes les coordonnées des points pour être centré autour de (0, 0), y compris les coordonnées négatives, ce serait beaucoup de travail.
Une façon possible de contourner ce problème est d'utiliser la méthode de bas niveau draw_set_transform pour résoudre ce problème, en translatant tous les points dans le référentiel du CanvasItem, puis en le replaçant à sa place d'origine avec une transformation de noeud standard, soit dans l'éditeur, soit dans le code, comme ceci :
func _ready():
rotation = 0
position = Vector2(60, 60)
...
func _draw():
draw_set_transform(Vector2(-60, -60))
...
public override void _Ready()
{
Rotation = 0;
Position = new Vector2(60, 60);
...
}
public override void _Draw()
{
DrawSetTransform(new Vector2(-60.0f, -60.0f));
...
}
Voilà le résultat, tournant autour d'un pivot placé en (60, 60) :
Si ce que nous voulions animer était une propriété à l'intérieur de l'appel _draw(), nous devons nous souvenir d'appeler queue_redraw() pour forcer un rafraîchissement, car sinon il ne serait pas mis à jour sur l'écran.
Par exemple, c'est ainsi que nous pouvons faire que le robot ait l'air d'ouvrir et fermer sa bouche, en changeant la largeur de la ligne de sa bouche en suivant un courbe sinusoïdale (sin) :
var _mouth_width : float = 4.4
var _max_width : float = 7
var _time : float = 0
func _process(delta : float):
_time += delta
_mouth_width = abs(sin(_time) * _max_width)
queue_redraw()
func _draw():
...
draw_polyline(mouth, white, _mouth_width)
...
private float _mouthWidth = 4.4f;
private float _maxWidth = 7f;
private float _time = 0f;
public override void _Process(double delta)
{
_time += (float)delta;
_mouthWidth = Mathf.Abs(Mathf.Sin(_time) * _maxWidth);
QueueRedraw();
}
public override void _Draw()
{
...
DrawPolyline(_mouth, white, _mouthWidth);
...
}
Il ressemblera quelque peu à ceci lors de l’exécution :
Veuillez noter que _mouth_width est une propriété définie par l'utilisateur comme n'importe quelle autre et qu'elle ou tout autre propriété utilisée comme argument de dessin peut être animée en utilisant des méthodes standard de plus haut niveau comme un Tween ou un nœud AnimationPlayer. La seule différence est qu'un appel de queue_redraw() est nécessaire pour appliquer ces changements afin qu'ils soient affichés à l'écran.
Exemple 2 : dessiner une ligne dynamique
L'exemple précédent était utile pour apprendre à dessiner et modifier des nœuds avec des formes et des animations personnalisées. Cela pourrait avoir certains avantages, comme l'utilisation de coordonnées et de vecteurs exacts pour le dessin, plutôt que des bitmaps, ce qui signifie qu'ils s'adapteront bien lorsqu'ils seront transformés à l'écran. Dans certains cas, des résultats similaires pourraient être obtenus en composant des fonctionnalités de plus haut niveau avec des nœuds tels que sprites ou AnimatedSprites chargeant des ressources SVG (qui sont également des images définies avec des vecteurs) et le nœud AnimationPlayer.
Dans d'autres cas, ce ne sera pas possible parce que nous ne saurons pas ce que la représentation graphique résultante sera avant d'exécuter le code. Ici, nous verrons comment dessiner une ligne dynamique dont les coordonnées ne sont pas connues à l'avance, et sont affectées par l'entrée de l'utilisateur.
Dessiner une ligne droite entre 2 points
Supposons que nous voulions dessiner une ligne droite entre deux points : le premier sera fixé dans le coin supérieur gauche (0, 0) et le second sera défini par la position du curseur à l'écran.
Nous pourrions dessiner une ligne dynamique entre ces deux points comme ceci :
extends Node2D
var point1 : Vector2 = Vector2(0, 0)
var width : int = 10
var color : Color = Color.GREEN
var _point2 : Vector2
func _process(_delta):
var mouse_position = get_viewport().get_mouse_position()
if mouse_position != _point2:
_point2 = mouse_position
queue_redraw()
func _draw():
draw_line(point1, _point2, color, width)
using Godot;
using System;
public partial class MyNode2DLine : Node2D
{
public Vector2 Point1 { get; set; } = new Vector2(0f, 0f);
public int Width { get; set; } = 10;
public Color Color { get; set; } = Colors.Green;
private Vector2 _point2;
public override void _Process(double delta)
{
Vector2 mousePosition = GetViewport().GetMousePosition();
if (mousePosition != _point2)
{
_point2 = mousePosition;
QueueRedraw();
}
}
public override void _Draw()
{
DrawLine(Point1, _point2, Color, Width);
}
}
Dans cet exemple, nous obtenons la position de la souris dans la vue par défaut à chaque frame avec la méthode get_mouse_position. Si la position a changé depuis la dernière requête draw (une petite optimisation pour éviter de redessiner à chaque frame), nous planifierons un nouveau draw. Notre méthode _draw() ne contient qu'une seule ligne : draw d'une ligne verte d'une largeur de 10 pixels entre le coin supérieur gauche et la position obtenue.
La largeur, la couleur et la position du point de départ peuvent être configurées avec les propriétés correspondantes.
Il devrait ressembler à ça une fois exécuté :
Dessiner un arc entre 2 points
L'exemple ci-dessus fonctionne, mais nous pourrions vouloir relier ces deux points avec une forme ou une fonction différente, autre qu'une ligne droite.
Essayons maintenant de créer un arc (une portion de circonférence) entre les deux points.
Exporter le point de départ de la ligne, les segments, la largeur, la couleur et l'anticrénelage nous permettra de modifier ces propriétés très facilement directement depuis le panneau de l'inspecteur de l'éditeur :
extends Node2D
@export var point1 : Vector2 = Vector2(0, 0)
@export_range(1, 1000) var segments : int = 100
@export var width : int = 10
@export var color : Color = Color.GREEN
@export var antialiasing : bool = false
var _point2 : Vector2
using Godot;
using System;
public partial class MyNode2DLine : Node2D
{
[Export]
public Vector2 Point1 { get; set; } = new Vector2(0f, 0f);
[Export]
public float Length { get; set; } = 350f;
[Export(PropertyHint.Range, "1,1000,")]
public int Segments { get; set; } = 100;
[Export]
public int Width { get; set; } = 10;
[Export]
public Color Color { get; set; } = Colors.Green;
[Export]
public bool AntiAliasing { get; set; } = false;
private Vector2 _point2;
}
Pour dessiner l'arc, nous pouvons utiliser la méthode :ref:draw_arc<class_CanvasItem_method_draw_arc>. Il existe de nombreux arcs qui passent par deux points, donc pour cet exemple, nous choisirons le demi-cercle dont le centre se trouve au point médian entre les deux points initiaux.
Le calcul de cet arc de cercle sera plus complexe que dans le cas de la ligne :
func _draw():
# Calculate the arc parameters.
var center : Vector2 = Vector2((_point2.x - point1.x) / 2,
(_point2.y - point1.y) / 2)
var radius : float = point1.distance_to(_point2) / 2
var start_angle : float = (_point2 - point1).angle()
var end_angle : float = (point1 - _point2).angle()
if end_angle < 0: # end_angle is likely negative, normalize it.
end_angle += TAU
# Finally, draw the arc.
draw_arc(center, radius, start_angle, end_angle, segments, color,
width, antialiasing)
public override void _Draw()
{
// Calculate the arc parameters.
Vector2 center = new Vector2((_point2.X - Point1.X) / 2.0f,
(_point2.Y - Point1.Y) / 2.0f);
float radius = Point1.DistanceTo(_point2) / 2.0f;
float startAngle = (_point2 - Point1).Angle();
float endAngle = (Point1 - _point2).Angle();
if (endAngle < 0.0f) // endAngle is likely negative, normalize it.
{
endAngle += Mathf.Tau;
}
// Finally, draw the arc.
DrawArc(center, radius, startAngle, endAngle, Segments, Color,
Width, AntiAliasing);
}
Le centre du demi-cercle sera le point médian entre les deux points. Le rayon sera la moitié de la distance entre les deux points. Les angles de début et de fin seront les angles du vecteur de point1 à point2 et vice-versa. Notez que nous avons dû normaliser le end_angle dans des valeurs positives parce que si end_angle est inférieur à start_angle, l'arc sera dessiné en sens inverse des aiguilles d'une montre, ce que nous ne voulons pas dans ce cas (l'arc serait à l'envers).
Le résultat devrait être quelque chose comme ça, avec l'arc descendant entre les points :
N'hésitez pas à jouer avec les paramètres de l'inspecteur pour obtenir différents résultats : changer la couleur, la largeur, l'anticrénelage, et augmenter le nombre de segments pour améliorer le lissage de la courbe, au coût de moins bonnes performances.