Para aquellos que se perdieron la primera parte -
Parte 1Siguiente parte -
Parte 3Si alguien est谩 interesado en leer sobre el agregador utilizado por el evento,
aqu铆 est谩 , pero esto no es necesario.
Entonces, comenzamos a recolectar todo en un mont贸n
CoheteClase de cohete 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; } } } }
驴Qu茅 necesitamos para que despegue un cohete? En el espacio de juego, necesitamos un planeta condicional con el que comenzamos, un bot贸n de inicio y un cohete. 驴Qu茅 debe hacer un cohete?
- Espera el comienzo
- Volar
- Ser afectado por modificadores
- Para parar
Es decir, tenemos un comportamiento / estado diferente del cohete, dependiendo del estado actual, el cohete deber铆a proporcionar un comportamiento diferente. En programaci贸n, nos enfrentamos constantemente a una situaci贸n en la que un objeto puede tener muchos comportamientos radicalmente diferentes.
Para el comportamiento complejo de objetos, es mejor usar patrones de comportamiento, por ejemplo, un patr贸n de estado. Para los simples, los programadores novatos a menudo usan mucho si es que lo hacen. Recomiendo usar switch y enum. En primer lugar, esta es una divisi贸n m谩s clara de la l贸gica en etapas espec铆ficas, gracias a esto sabremos exactamente en qu茅 estado nos encontramos ahora, y lo que est谩 sucediendo, hay menos oportunidades para convertir el c贸digo en un fideo de docenas de excepciones.
C贸mo funcionaPrimero comenzamos enumeraci贸n con los estados que necesitamos:
public enum RocketState { WAITFORSTART = 0, MOVE = 1, STOP = 2, COMPLETESTOP = 3, }
En la clase padre tenemos un campo:
protected RocketState rocketState;
Por defecto, se le asigna el primer valor. Enum establece los valores predeterminados, pero para los datos que los dise帽adores de juegos pueden cambiar desde arriba o configurar, configuro manualmente los valores, 驴para qu茅? Para poder agregar otro valor al inam en cualquier lugar y no violar los datos almacenados. Tambi茅n te aconsejo que estudies enum.
Siguiente:Definimos el comportamiento en s铆 en una actualizaci贸n, dependiendo del valor del campo 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; } }
Descifrar茅 lo que est谩 sucediendo:- Cuando esperamos, simplemente giramos el cohete hacia el cursor del mouse, estableciendo as铆 la trayectoria inicial
- El segundo estado: volamos, aceleramos el cohete en la direcci贸n correcta y actualizamos el modelo modificador para la aparici贸n de objetos que afectan la trayectoria
- El tercer estado es cuando el equipo llega a nosotros para detenerse, aqu铆 resolvemos todo para que el cohete se detenga y se traduzca en el estado: nos detuvimos por completo.
- El 煤ltimo estado es que no estamos haciendo nada.
La conveniencia del patr贸n actual: todo es muy f谩cil de expandir y ajustar, pero hay uno excepto el eslab贸n d茅bil: es cuando podemos tener un estado que combina varios otros estados. Aqu铆, ya sea una bandera inam, con una complicaci贸n de procesamiento, o ya cambiar a patrones m谩s "pesados".
Descubrimos el cohete. El siguiente paso es un objeto simple pero divertido: el bot贸n de inicio.
Bot贸n de inicio
Se requiere la siguiente funcionalidad de ella: hizo clic, notific贸 que hicieron clic en ella.
Clase de bot贸n de inicio 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 { }
Seg煤n el dise帽o del juego, este es un objeto 3D en el escenario, se supone que el bot贸n est谩 integrado en el dise帽o del planeta inicial. Bueno, est谩 bien, hay un matiz: 驴c贸mo rastrear un clic en un objeto en una escena?
Si buscamos en Google, encontraremos un mont贸n de m茅todos OnMouse, entre los cuales habr谩 un clic. Parecer铆a una opci贸n f谩cil, pero es muy malo, comenzando con el hecho de que a menudo funciona de manera torcida (hay muchos matices para el seguimiento de clics), "querido", y termina con el hecho de que no da esa cantidad de bollos que est谩n en UnityEngine.EventSystems.
Al final, recomiendo usar UnityEngine.EventSystems y las interfaces IPointerDownHandler, IPointerClickHandler. En sus m茅todos, nos damos cuenta de la reacci贸n al presionar, pero hay varios matices.
- Un EventSystem debe estar presente en la escena, este es un objeto / clase / componente de la unidad, generalmente creado cuando creamos el lienzo para la interfaz, pero tambi茅n puede crearlo usted mismo.
- F铆sica RayCaster debe estar presente en la c谩mara (esto es para 3D, para gr谩ficos 2D hay un racaster separado)
- Debe haber un colisionador en la instalaci贸n
En el proyecto, se ve as铆:
Ahora el objeto sigue el clic y este m茅todo se llama:
public void OnPointerDown(PointerEventData eventData) { ButtonStartPressed(); } private void ButtonStartPressed() { if (isTriggered) return; isTriggered = true; GlobalEventAggregator.EventAggregator.Invoke(new ButtonStartPressed()); Debug.Log(""); }
驴Qu茅 est谩 pasando aqu铆?Tenemos un campo booleano en el que rastreamos si se presiona el bot贸n o no (esto es protecci贸n contra la presi贸n repetida para que no tengamos un script de inicio cada vez).
A continuaci贸n, llamamos al evento: se presiona el bot贸n, al que se suscribe la clase de cohete, y se pone el cohete en un estado de movimiento.
Saltando un poco hacia adelante, 驴por qu茅 est谩 aqu铆 y all谩 para los eventos? Esta es una programaci贸n orientada a eventos. En primer lugar, un modelo de evento es m谩s barato que el procesamiento continuo de datos para descubrir sus cambios. En segundo lugar, esta es la conexi贸n m谩s d茅bil, no necesitamos saber en el cohete que hay un bot贸n, que alguien lo presion贸, y as铆 sucesivamente, solo sabemos que hay un evento para comenzar, lo recibimos y estamos actuando. Adem谩s, este evento es interesante no solo para el cohete, por ejemplo, un panel con modificadores est谩 firmado para el mismo evento, est谩 oculto al comienzo del cohete. Adem谩s, este evento puede ser de inter茅s para el controlador de entrada, y la entrada del usuario no puede procesarse o procesarse de manera diferente despu茅s del lanzamiento del cohete.
驴Por qu茅 a muchos programadores no les gusta el paradigma del evento? Debido a que una gran cantidad de eventos y suscripciones a estos eventos convierten f谩cilmente el c贸digo en fideos, en los que no est谩 del todo claro d贸nde comenzar y si terminar谩 en alg煤n lugar, sin mencionar el hecho de que tambi茅n necesita monitorear su cancelaci贸n / suscripci贸n y mantener vivos todos los objetos.
Y es por eso que para la implementaci贸n de eventos utilizo mi agregador de eventos, que de hecho no transmite eventos, sino contenedores de datos a trav茅s de eventos, y las clases se suscriben a los datos que les interesan. Adem谩s, el agregador mismo monitorea objetos vivos y arroja objetos muertos de los suscriptores. Gracias a la transferencia del contenedor, la inyecci贸n tambi茅n es posible; puede pasar un enlace a la clase de inter茅s para nosotros. Por el contenedor, puede rastrear f谩cilmente qui茅n procesa y env铆a estos datos. Para la creaci贸n de prototipos es una gran cosa.
Rotaci贸n de cohetes para determinar la ruta de inicio

Seg煤n el dise帽o del juego, el cohete deber铆a poder girar alrededor del planeta para determinar la trayectoria inicial, pero no m谩s de un 谩ngulo. La rotaci贸n se lleva a cabo con el tacto: el cohete simplemente sigue al dedo y siempre se dirige al lugar donde tocamos la pantalla. Por cierto, es solo el prototipo que hizo posible determinar que este es un punto d茅bil y que hay muchos episodios asociados con la administraci贸n que bordean esta funcionalidad.
Pero en orden:- Necesitamos que el cohete gire en relaci贸n con el planeta en la direcci贸n de la carretilla
- Necesitamos sujetar el 谩ngulo de rotaci贸n
En cuanto a la rotaci贸n relativa al planeta: puede girar astutamente alrededor del eje y calcular el eje de rotaci贸n, o simplemente puede crear un objeto con un maniqu铆 centrado dentro del planeta, mover el cohete all铆 y rotar silenciosamente el maniqu铆 alrededor del eje Z, el maniqu铆 tendr谩 una clase que determinar谩 el comportamiento del objeto. El cohete girar谩 con 茅l. El objeto que llam茅 RocketHolder. Lo descubrimos.
Ahora sobre las restricciones para girar y girar en la direcci贸n de la carretilla:
clase 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); } }
A pesar de que el juego es en teor铆a 3D, pero toda la l贸gica y el juego son en realidad 2D. Y solo necesitamos apretar el cohete alrededor del eje Z en la direcci贸n del lugar de presi贸n. Al final del m茅todo, fijamos el grado de rotaci贸n por el valor especificado en el inspector. En el m茅todo Despertar, puede ver la implementaci贸n m谩s correcta de una inyecci贸n de clase a trav茅s de un agregador.
Controlador de entrada
Una de las clases m谩s importantes, es 茅l quien recopila y procesa el comportamiento del usuario. Al presionar teclas de acceso r谩pido, botones de gamepad, teclados, etc. Tengo una entrada bastante simple en el prototipo, de hecho, solo necesita saber 3 cosas:
- 驴Hay un clic y sus coordenadas?
- 驴Hay un deslizamiento vertical y cu谩nto deslizar?
- 驴Opero con interfaz / modificadores?
clase 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; }
Todo est谩 en la frente y sin problemas, lo interesante puede ser la implementaci贸n primitiva de propiedad reactiva: cuando reci茅n comenzaba a programar, siempre fue interesante c贸mo descubrir que los datos han cambiado, sin una ventilaci贸n constante de los datos. Bueno, ya est谩.
Se ve as铆:
clase 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); } } } }
Nos suscribimos a OnChange y nos contraemos si solo ha cambiado el valor.
Con respecto a la creaci贸n de prototipos y la arquitectura: los consejos son los mismos, propiedades y m茅todos p煤blicos, todos los datos solo deben cambiarse localmente. Cualquier procesamiento y c谩lculo: sumar de acuerdo con m茅todos separados. Como resultado, siempre puede cambiar la implementaci贸n / los c谩lculos, y esto no afectar谩 a los usuarios externos de la clase. Eso es todo por ahora, en la tercera parte final: sobre modificadores e interfaz (arrastrar y soltar). Y planeo poner el proyecto en git para que pueda ver / sentir. Si tiene preguntas sobre la creaci贸n de prototipos, pregunte, intentar茅 responderlas claramente.