Utilisation d'InputEvent

Qu'est-ce que c'est ?

La gestion des entrées est généralement complexe, quel que soit le système d'exploitation ou la plate-forme. Pour faciliter un peu les choses, un type spécial intégré est fourni, InputEvent. Ce type de données peut être configuré pour contenir plusieurs types d'événements d'entrée. Les événements d'entrée voyagent à travers le moteur et peuvent être reçus à plusieurs endroits, selon le but recherché.

Voici un exemple rapide, la fermeture de votre jeu si la touche quitter est appuyée :

func _unhandled_input(event):
    if event is InputEventKey:
        if event.pressed and event.scancode == KEY_ESCAPE:
            get_tree().quit()

Cependant, il est plus propre et plus flexible d'utiliser la fonction fournie InputMap, qui vous permet de définir des actions d'entrée et de leur assigner différentes touches. De cette façon, vous pouvez définir plusieurs touches pour la même action (par exemple, la touche d'Échaptr et le bouton start sur une manette de jeu). Vous pouvez alors plus facilement changer cette cartographie dans les paramètres du projet sans mettre à jour votre code, et même construire une fonction de cartographie de touche par-dessus pour permettre à votre jeu de changer la cartographie de touche pendant l'exécution !

Vous pouvez configurer votre InputMap sous Project > Project Settings > Input Map et utiliser ces actions comme ceci :

func _process(delta):
    if Input.is_action_pressed("ui_right"):
        # Move right.

Comment ça marche ?

Chaque événement d'entrée provient de l'utilisateur/joueur (bien qu'il soit possible de générer un InputEvent et de le renvoyer au moteur, ce qui est utile pour les gestes). L'objet OS pour chaque plate-forme lira les événements du périphérique, puis les enverra à MainLoop. Comme SceneTree est l'implémentation par défaut de MainLoop, les événements lui sont envoyés. Godot fournit une fonction pour obtenir l'objet SceneTree courant : get_tree().

Mais SceneTree ne sait pas quoi faire de l'événement, donc il le donnera aux viewports, en commençant par la "racine" Viewport (le premier nœud de l'arbre de scènes). Viewport fait beaucoup de choses avec les données reçues, dans l'ordre :

../../_images/input_event_flow.png
  1. Tout d'abord, la fonction standard Node._input() sera appelée dans tout nœud qui la surcharge (et qui n'a pas désactivé le traitement des entrées avec Node.set_process_input()). Si une fonction consomme l'événement, elle peut appeler SceneTree.set_input_as_handled(), et l'événement ne se répandra plus. Cela vous permet de filtrer tous les événements d'intérêt, même avant l'interface graphique. Pour l'entrée du gameplay, Node._unhandled_input() est généralement plus adapté, car il permet à l'interface graphique d'intercepter les événements.

  2. Deuxièmement, il va essayer d'alimenter l'entrée de l'interface graphique, et voir si un contrôle peut la recevoir. Si c'est le cas, le Control sera appelé via la fonction virtuelle Control._gui_input() et le signal "gui_input" sera émis (cette fonction est réimplémentable par script en héritant d'elle). Si le contrôle veut "consommer" l'événement, il appellera Control.accept_event() et l'événement ne sera plus diffusé. Utilisez la propriété Control.mouse_filter pour contrôler si un Control est notifié des événements de souris via le callback Control._gui_input(), et si ces événements sont propagés plus loin.

  3. Si jusqu'à présent personne n'a consommé l'événement, le rappel d'entrée non géré sera appelé s'il est surchargé (et non désactivé avec Node.set_process_unhandled_input()). Si une fonction consomme l'événement, elle peut appeler SceneTree.set_input_as_handled(), et l'événement ne se répandra plus. Le rappel d'entrée non géré est idéal pour les événements de gameplay en plein écran, de sorte qu'ils ne sont pas reçus lorsqu'une interface graphique est active.

  4. Si personne n'a voulu de l'événement jusqu'à présent, et qu'une Camera est assignée au Viewport avec Object Picking activé, un rayon vers le monde physique (dans la direction du rayon à partir du clic) sera lancé. (Pour le viewport racine, cela peut également être activé dans Project Settings) Si ce rayon touche un objet, il appellera la fonction CollisionObject._input_event() dans l'objet physique concerné (les corps reçoivent ce rappel par défaut, mais pas les areas. Cela peut être configuré via les propriétés d' Area).

  5. Enfin, si l'événement n'a pas été traité, il sera transmis au prochain Viewport dans l'arbre, sinon il sera ignoré.

Lorsque vous envoyez des événements à tous les nœuds qui écoute d'une scène, la fenêtre de visualisation le fait dans l'ordre inverse de la profondeur : Commençant par le nœud en bas de l'arbre de scène, et se terminant au nœud racine :

../../_images/input_event_scene_flow.png

Les événements de l'interface graphique remontent également dans l'arbre des scènes mais, comme ces événements ciblent des Controls spécifiques, seuls les ancêtres directs du nœud de Control ciblé reçoivent l'événement.

Conformément au design de Godot basée sur les nœuds, cela permet à des nœuds enfants spécialisés de gérer et de consommer des événements particuliers, tandis que leurs ancêtres, et finalement la racine de la scène, peuvent fournir un comportement plus généralisé si nécessaire.

Anatomie d'un InputEvent

InputEvent est juste un type intégré de base, il ne représente rien et contient seulement quelques informations de base, comme l'ID de l'événement (qui est incrémenté pour chaque événement), l'index du périphérique, etc.

Il existe plusieurs types spécialisés d'InputEvent, décrits dans le tableau ci-dessous :

Évènement

Indice de type

Description

InputEvent

NONE

Évènement d'entrée vide.

InputEventKey

CLÉ

Contient un code d'analyse et une valeur Unicode, ainsi que des modificateurs.

InputEventMouseButton

MOUSE_BUTTON

Contient des informations de clic, telles que bouton, modificateurs, etc.

InputEventMouseMotion

MOUSE_MOTION

Contient l'information du mouvement, comme les positions relatives et absolues ainsi que la vitesse.

InputEventJoypadMotion

JOYSTICK_MOTION

Contient l'information de l(axe analogique du Joystick/Joypad.

InputEventJoypadButton

JOYSTICK_BUTTON

Contient des informations sur les boutons de la manette/du joystick.

InputEventScreenTouch

SCREEN_TOUCH

Contient des informations de pression/relâchement multi-touche. (uniquement disponible sur les appareils mobiles)

InputEventScreenDrag

SCREEN_DRAG

Contient des informations de glissement multi-touche. (uniquement disponible sur les appareils mobiles)

InputEventAction

SCREEN_ACTION

Contient une action générique. Ces événements sont souvent générés par le programmeur en tant que retour d'information. (plus d'informations ci-dessous)

Actions

Un InputEvent peut ou non représenter une action prédéfinie. Les actions sont utiles car elles permettent d'abstraire le périphérique d'entrée lors de la programmation de la logique du jeu. Cela permet :

  • Le même code pour travailler sur différents appareils avec différentes entrées (par exemple, clavier sur PC, Joypad sur console).

  • Entrée à reconfigurer au moment de l'exécution.

Les actions peuvent être créées à partir du menu Paramètres du projet dans l'onglet Actions.

Tout événement a les méthodes InputEvent.is_action(), InputEvent.is_pressed() et InputEvent.

Alternativement, il peut être souhaité de fournir au jeu un retour avec une action à partir du code de jeu (un bon exemple de cela est la détection des gestes). Le singleton Input a une méthode pour cela : Input.parse_input_event(). Vous l'utiliseriez normalement comme ceci :

var ev = InputEventAction.new()
# Set as move_left, pressed.
ev.action = "move_left"
ev.pressed = true
# Feedback.
Input.parse_input_event(ev)

InputMap

Il est souvent souhaitable de personnaliser et de re-cartographier les entrées à partir du code. Si tout votre workflow dépend d'actions, le singleton InputMap est idéal pour réaffecter ou créer différentes actions à l'exécution. Ce singleton n'est pas sauvegardé (doit être modifié manuellement) et son état est lancé à partir des paramètres du projet (project.godot). Ainsi, tout système dynamique de ce type doit stocker les paramètres de la manière la plus appropriée pour le programmeur.