Pour ceux qui ont raté la première partie -
Partie 1Partie suivante -
Partie 3Si quelqu'un est intéressé à lire sur l'agrégateur utilisé par l'événement, alors vous
êtes ici , mais ce n'est pas nécessaire.
Donc, nous commençons à tout rassembler en tas
Fusée:Classe de fusée de baseusing DG.Tweening; using GlobalEventAggregator; using UnityEngine; namespace PlayerRocket { public class Rocket : PlayerRocketBase { [SerializeField] private float pathorrectionTime = 10; private Vector3 movingUp = new Vector3(0, 1, 0); protected override void StartEventReact(ButtonStartPressed buttonStartPressed) { transform.SetParent(null); rocketState = RocketState.MOVE; transform.DORotate(Vector3.zero, pathorrectionTime); } protected override void Start() { base.Start(); EventAggregator.Invoke(new RegisterUser { playerHelper = this }); if (rocketState == RocketState.WAITFORSTART) return; RocketBehaviour(); } private void FixedUpdate() { RocketBehaviour(); } private void RocketBehaviour() { switch (rocketState) { case RocketState.WAITFORSTART: if (inputController.OnTouch && !inputController.OnDrag) rocketHolder.RotateHolder(inputController.worldMousePos); break; case RocketState.MOVE: rigidbody.AddRelativeForce(Vector3.up*(config.Speed*Time.deltaTime)); forceModel.AddModificator(); break; case RocketState.STOP: Debug.Log(" "); rigidbody.velocity = Vector3.zero; rigidbody.drag = 50; rocketState = RocketState.COMPLETESTOP; break; case RocketState.COMPLETESTOP: break; default: rocketState = RocketState.COMPLETESTOP; break; } } } }
De quoi avons-nous besoin pour qu'une fusée décolle? Dans l'espace de jeu, nous avons besoin d'une planète conditionnelle avec laquelle nous commençons, d'un bouton de démarrage et d'une fusée. Que devrait faire une fusée?
- Attendez le début
- Voler
- Soyez affecté par les modificateurs
- Arrêter
Autrement dit, nous avons un comportement / état différent de la fusée, selon l'état actuel, la fusée devrait fournir un comportement différent. En programmation, nous sommes constamment confrontés à une situation où un objet peut avoir de nombreux comportements radicalement différents.
Pour un comportement complexe d'objets - il est préférable d'utiliser des modèles de comportement, par exemple, un modèle d'état. Pour les plus simples, les programmeurs novices en utilisent souvent beaucoup sinon. Je recommande d'utiliser switch et enum. Premièrement, il s'agit d'une division plus claire de la logique en étapes spécifiques.Grâce à cela, nous saurons exactement dans quel état nous sommes maintenant et ce qui se passe, il y a moins d'occasions de transformer le code en une nouille de dizaines d'exceptions.
Comment ça marche:Commençons d'abord par énumérer les états dont nous avons besoin:
public enum RocketState { WAITFORSTART = 0, MOVE = 1, STOP = 2, COMPLETESTOP = 3, }
Dans la classe parent, nous avons un champ -
protected RocketState rocketState;
Par défaut, la première valeur lui est affectée. Enum lui-même définit les valeurs par défaut, mais pour les données qui peuvent être modifiées par le haut ou configurées par les concepteurs de jeux - j'ai défini manuellement les valeurs, pour quoi faire? Afin de pouvoir ajouter une autre valeur à l'inam n'importe où et ne pas violer les données stockées. Je vous conseille également d'étudier l'énumération des drapeaux.
Suivant:Nous définissons le comportement lui-même dans une mise à jour, en fonction de la valeur du champ rocketState
private void FixedUpdate() { RocketBehaviour(); } private void RocketBehaviour() { switch (rocketState) { case RocketState.WAITFORSTART: if (inputController.OnTouch && !inputController.OnDrag) rocketHolder.RotateHolder(inputController.worldMousePos); break; case RocketState.MOVE: rigidbody.AddRelativeForce(Vector3.up*(config.Speed*Time.deltaTime)); forceModel.AddModificator(); break; case RocketState.STOP: Debug.Log(" "); rigidbody.velocity = Vector3.zero; rigidbody.drag = 50; rocketState = RocketState.COMPLETESTOP; break; case RocketState.COMPLETESTOP: break; default: rocketState = RocketState.COMPLETESTOP; break; } }
Je vais déchiffrer ce qui se passe:- Lorsque nous attendons, nous tournons simplement la fusée vers le curseur de la souris, définissant ainsi la trajectoire initiale
- Le deuxième état - nous volons, accélérons la fusée dans la bonne direction et mettons à jour le modèle de modification pour l'apparition d'objets affectant la trajectoire
- Le troisième état est lorsque l'équipe arrive à nous pour nous arrêter, ici nous travaillons tout pour que la fusée s'arrête et se traduise dans l'état - nous nous sommes complètement arrêtés.
- Le dernier état est que nous ne faisons rien.
La commodité du modèle actuel - tout est très facilement extensible et réglable, mais il y a une chose mais un maillon faible - c'est quand nous pouvons avoir un état qui combine un certain nombre d'autres états. Ici, soit un drapeau inam, avec une complication de traitement, soit déjà basculer vers des motifs plus "lourds".
Nous avons compris la fusée. La prochaine étape est un objet simple mais amusant - le bouton de démarrage.
Bouton Démarrer
La fonctionnalité suivante est requise de sa part - elle a cliqué, elle a informé qu'ils avaient cliqué sur elle.
Classe de bouton de démarrage using UnityEngine; using UnityEngine.EventSystems; public class StartButton : MonoBehaviour, IPointerDownHandler { private bool isTriggered; private void ButtonStartPressed() { if (isTriggered) return; isTriggered = true; GlobalEventAggregator.EventAggregator.Invoke(new ButtonStartPressed()); Debug.Log(""); } public void OnPointerDown(PointerEventData eventData) { ButtonStartPressed(); } } public struct ButtonStartPressed { }
Selon la conception du jeu, il s'agit d'un objet 3D sur la scène, le bouton est censé être intégré dans la conception de la planète de départ. Eh bien, ok, il y a une nuance - comment suivre un clic sur un objet dans une scène?
Si nous google, nous trouverons un tas de méthodes OnMouse, parmi lesquelles il y aura un clic. Cela semblerait être un choix facile, mais il est tout simplement très mauvais, à commencer par le fait qu'il fonctionne souvent de manière tordue (il existe de nombreuses nuances pour le suivi des clics), "cher", se terminant par le fait qu'il ne donne pas cette tonne de petits pains qui sont dans UnityEngine.EventSystems.
Au final, je recommande d'utiliser UnityEngine.EventSystems et les interfaces IPointerDownHandler, IPointerClickHandler. Dans leurs méthodes, nous réalisons la réaction au pressage, mais il y a plusieurs nuances.
- Un EventSystem doit être présent dans la scène, il s'agit d'un objet / classe / composant de l'unité, généralement créé lorsque nous créons le canevas pour l'interface, mais vous pouvez également le créer vous-même.
- Physics RayCaster doit être présent sur la caméra (c'est pour la 3D, pour les graphiques 2D il y a un racaster séparé)
- Il doit y avoir un collisionneur dans l'installation
Dans le projet, cela ressemble à ceci:
Maintenant, l'objet suit le clic et cette méthode est appelée:
public void OnPointerDown(PointerEventData eventData) { ButtonStartPressed(); } private void ButtonStartPressed() { if (isTriggered) return; isTriggered = true; GlobalEventAggregator.EventAggregator.Invoke(new ButtonStartPressed()); Debug.Log(""); }
Que se passe-t-il ici:Nous avons un champ booléen dans lequel nous suivons si le bouton a été enfoncé ou non (c'est une protection contre les pressions répétées afin que nous n'ayons pas un script de démarrage exécuté à chaque fois).
Ensuite, nous appelons l'événement - le bouton est pressé, auquel la classe de fusée est abonnée, et met la fusée en état de mouvement.
Avancer un peu - pourquoi est-ce ici et là pour les événements? Il s'agit d'une programmation événementielle. Tout d'abord, un modèle d'événement est moins cher qu'un traitement de données en continu pour connaître leurs évolutions. Deuxièmement, c'est la connexion la plus faible, nous n'avons pas besoin de savoir sur la fusée qu'il y a un bouton, que quelqu'un l'a appuyé, et ainsi de suite, nous savons juste qu'il y a un événement pour commencer, nous l'avons reçu et agissons. De plus, cet événement est intéressant non seulement pour la fusée, par exemple, un panneau avec des modificateurs est signé pour le même événement, il est caché au début de la fusée. En outre, cet événement peut intéresser le contrôleur d'entrée - et l'entrée utilisateur ne peut pas être traitée ou traitée différemment après le lancement de la fusée.
Pourquoi beaucoup de programmeurs n'aiment-ils pas le paradigme des événements? Parce qu'une tonne d'événements et d'abonnements à ces événements transforment facilement le code en nouilles, dans lesquelles il n'est pas du tout clair par où commencer et s'il se terminera quelque part, sans parler du fait que vous devez également suivre la désinscription / abonnement et garder tous les objets en vie.
Et c'est pourquoi pour l'implémentation d'événements, j'utilise mon agrégateur d'événements, qui en fait ne transmet pas d'événements, mais des conteneurs de données via des événements, et les classes souscrivent aux données qui les intéressent. De plus, l'agrégateur lui-même surveille les objets vivants et jette les objets morts hors des abonnés. Grâce au transfert du conteneur, l'injection est également possible; vous pouvez nous transmettre un lien vers la classe d'intérêt. Par le conteneur, vous pouvez facilement suivre qui traite et envoie ces données. Car le prototypage est une bonne chose.
Rotation de la fusée pour déterminer la trajectoire de départ

Selon la conception du jeu, la fusée devrait pouvoir tourner autour de la planète pour déterminer la trajectoire initiale, mais pas plus d'un certain angle. La rotation est effectuée par le toucher - la fusée suit simplement le doigt et est toujours dirigée vers l'endroit où nous avons poussé l'écran. Soit dit en passant, c'est juste le prototype qui a permis de déterminer qu'il s'agit d'un point faible et de nombreux épisodes associés à la gestion sont à la limite de cette fonctionnalité.
Mais dans l'ordre:- Il faut que la fusée tourne par rapport à la planète en direction de la brouette
- Nous devons serrer l'angle de rotation
En ce qui concerne la rotation par rapport à la planète - vous pouvez tourner sournoisement autour de l'axe et calculer l'axe de rotation, ou vous pouvez simplement créer un objet avec un mannequin centré à l'intérieur de la planète, y déplacer la fusée et faire tourner doucement le mannequin autour de l'axe Z, le mannequin aura une classe qui déterminera le comportement de l'objet. La fusée tournera avec. L'objet que j'ai appelé RocketHolder. Nous l'avons compris.
Maintenant, à propos des restrictions sur le virage et le virage vers la brouette:
classe RocketHolder using UnityEngine; public class RocketHolder : MonoBehaviour { [SerializeField] private float clampAngle = 45; private void Awake() { GlobalEventAggregator.EventAggregator.AddListener(this, (InjectEvent<RocketHolder> obj) => obj.inject(this)); } private float ClampAngle(float angle, float from, float to) { if (angle < 0f) angle = 360 + angle; if (angle > 180f) return Mathf.Max(angle, 360 + from); return Mathf.Min(angle, to); } private Vector3 ClampRotationVectorZ (Vector3 rotation ) { return new Vector3(rotation.x, rotation.y, ClampAngle(rotation.z, -clampAngle, clampAngle)); } public void RotateHolder(Vector3 targetPosition) { var diff = targetPosition - transform.position; diff.Normalize(); float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg; transform.rotation = Quaternion.Euler(0f, 0f, rot_z - 90); transform.eulerAngles = ClampRotationVectorZ(transform.rotation.eulerAngles); } }
Malgré le fait que le jeu soit en théorie 3D, mais toute la logique et le gameplay sont en réalité 2D. Et il suffit de resserrer la fusée autour de l'axe Z en direction du lieu de pressage. À la fin de la méthode, nous fixons le degré de rotation par la valeur spécifiée dans l'inspecteur. Dans la méthode Awake, vous pouvez voir l'implémentation la plus correcte d'une injection de classe via un agrégateur.
Inputcontroller
L'une des classes les plus importantes, c'est lui qui collecte et traite le comportement des utilisateurs. Appui sur les touches de raccourci, les boutons de la manette de jeu, les claviers, etc. J'ai une entrée assez simple dans le prototype, en fait vous devez savoir seulement 3 choses:
- Y a-t-il un clic et ses coordonnées
- Y a-t-il un balayage vertical et combien de balayage
- Dois-je fonctionner avec une interface / des modificateurs
classe InputController using System; using UnityEngine; using UnityEngine.EventSystems; public class InputController : MonoBehaviour { public const float DirectionRange = 10; private Vector3 clickedPosition; [Header(" ")] [SerializeField] private float afterThisDistanceWeGonnaDoSwipe = 0.5f; [Header(" ")] [SerializeField] private float speedOfVerticalScroll = 2; public ReactiveValue<float> ReactiveVerticalScroll { get; private set; } public Vector3 worldMousePos => Camera.main.ScreenToWorldPoint(Input.mousePosition); public bool OnTouch { get; private set; } public bool OnDrag { get; private set; }
Tout est dans le front et sans problèmes, intéressante peut être l'implémentation primitive du propriétaire réactif - quand je commençais à peine à programmer, il était toujours intéressant de savoir comment les données avaient changé, sans ventilation constante des données. Eh bien, c'est tout.
Cela ressemble à ceci:
class ReactiveValue public class ReactiveValue<T> where T: struct { private T currentState; public Action<T> OnChange; public T CurrentValue { get => currentState; set { if (value.Equals(currentState)) return; else { currentState = value; OnChange?.Invoke(currentState); } } } }
Nous souscrivons à OnChange, et nous tremblons si seulement la valeur a changé.
En ce qui concerne le prototypage et l'architecture - les conseils sont les mêmes, propriétés et méthodes publiques uniquement, toutes les données ne doivent être modifiées que localement. Tout traitement et calcul - additionnez selon des méthodes distinctes. Par conséquent, vous pouvez toujours modifier l'implémentation / les calculs, et cela n'affectera pas les utilisateurs externes de la classe. C'est tout pour l'instant, dans la troisième partie finale - sur les modificateurs et l'interface (glisser-déposer). Et je prévois de mettre le projet sur git pour que je puisse voir / sentir. Si vous avez des questions sur le prototypage - posez-les, j'essaierai d'y répondre clairement.