Creación de prototipos de un juego móvil, dónde comenzar y cómo hacerlo. Parte 3 (final)

En la primera parte hablé sobre por qué hacer prototipos y, en general, dónde comenzar
- Parte 1

En la segunda parte, repase un poco las clases clave
y arquitectura - Parte 2

Y la tercera parte: en realidad tendrá una pequeña discusión, analizaremos cómo funcionan los modificadores y saldremos al campo de juego (esto no es difícil, pero hay matices). Y un poco sobre arquitectura, lo intentaré sin tedio.

Las capturas de pantalla sobre el cohete son generalmente aburridas, por lo que sugiero ver un video de otro prototipo, que se ensambló en 2 semanas junto con los gráficos, y fue abandonado debido al hecho de que en el género de plataformas no hay forma de evitarlo. Esta, por cierto, es una de las ideas clave en torno al prototipo: para ensamblar, ver si esto es una mierda, ¿es necesario? Y honestamente, tira la canasta si las respuestas no te parecen convincentes. Pero! Esto no se aplica a proyectos creativos, a veces la creatividad sucede por el bien de la creatividad).

Entonces vidosik, debes mirar los textos ordenados por nivel, y no la jugabilidad):



Pequeña digresión


En el último artículo, recibí el código de revisión y estoy agradecido por esto; la crítica ayuda a desarrollarse incluso si no está de acuerdo con él.

Pero quiero implementar para la arquitectura y la sintaxis con respecto a los prototipos:
  1. No importa cuán genial seas, no puedes prever que no será hipotecado, y puede haber mucha hipoteca de lo que no se necesita. Por lo tanto, en cualquier caso, será necesaria una refactorización o expansión. Si no puede describir los beneficios específicos del código / enfoque, es mejor no gastar mucho tiempo en este código.
  2. Por qué, en mi opinión, OOP / Event Model / Composition es más fácil para los prototipos que ECS, Unity COOP, DI FrameWorks, Reactive Frameworks, etc. Menos garabatos, todas las conexiones son visibles en el código, porque la tarea principal del prototipo es responder a la pregunta principal: ¿es posible jugarla y varias secundarias? ¿Cuál es mejor para el juego, esto o aquello? Por lo tanto, debe implementar la funcionalidad necesaria lo más rápido posible. ¿Por qué introducir un marco en un proyecto pequeño, prescribir toda la basura para implementar tres entidades de juego? Cada uno de los cuales es en realidad una clase de 50-100 líneas. La arquitectura debe pensarse como parte de las tareas prototipo y como parte de una posible extensión a alfa, pero la segunda se necesita más en la cabeza que en el código para que el código no se grabe más tarde


Sobre modificadores:


Y finalmente sobre el modificador en sí:
Aquí y antes, llamo a los modificadores los campos de fuerza que toca el cohete y que afectan su trayectoria, en el prototipo hay dos tipos de ellos: aceleración y desviación.

Clase modificador
public class PushSideModificator : MonoBehaviour { [SerializeField] TypeOfForce typeOfForce = TypeOfForce.Push; [SerializeField] private float force; [SerializeField] DropPanelConfig dropPanelConfig; private float boundsOfCollider; private void OnTriggerEnter(Collider other) { boundsOfCollider = other.bounds.extents.x; GlobalEventAggregator.EventAggregator.Invoke(new SpaceForces { TypeOfForce = typeOfForce, Force = force, ColliderBound = boundsOfCollider, CenterOfObject = transform.position, IsAdded = true }); } private void OnTriggerExit(Collider other) { GlobalEventAggregator.EventAggregator.Invoke(new SpaceForces { CenterOfObject = transform.position, IsAdded = false }); } } 


Esta es una clase muy simple cuya tarea es pasar dos eventos:
  1. El golpe del jugador en el campo (que en realidad es un disparador de unidad física) y todo el contexto necesario: el tipo de modificador, su posición, el tamaño del colisionador, la fuerza del modificador, etc. En este más el evento del agregador, puede transmitir cualquier contexto a las partes interesadas. En este sentido, este es un modelo de cohete que procesa el modificador.
  2. El segundo evento: el jugador dejó el campo. Para eliminar su influencia en la trayectoria del jugador.

¿Para qué es el modelo de evento? ¿Quién más podría necesitar este evento?
En el proyecto actual esto no está implementado, pero:
  1. Actuación de voz (recibió un evento en el que alguien ingresó al campo; reproducimos el sonido correspondiente, alguien salió, de manera similar)
  2. Marcadores de UI, digamos que para cada campo eliminaremos algo de combustible del cohete, debe aparecer información sobre herramientas que ingresamos al campo y perdimos combustible, bueno, o ganamos puntos por cada golpe en el campo, hay muchas opciones en las que la interfaz está interesada en que el jugador ingrese el campo
  3. Especial efectos: cuando se golpea en un tipo diferente de campo, se pueden superponer diferentes efectos, tanto en el cohete como en el espacio alrededor del cohete / campo. Especial Los efectos pueden ser manejados por una entidad / controlador separado, que también se suscribirá a los eventos de los modificadores.
  4. Bueno, este es un mínimo de código, no se necesitan localizadores de servicios, agregación, dependencias, etc.


Base de juego


En este prototipo, la esencia de la jugabilidad es colocar modificadores en el campo de juego, ajustando la trayectoria de vuelo del cohete, volar alrededor de obstáculos y golpear el punto / planeta de destino. Para hacer esto, tenemos un panel a la derecha, en el que se encuentran los iconos modificadores.



Clase de panel
  [RequireComponent (typeof(CanvasGroup))] public class DragAndDropModifiersPanel : MonoBehaviour { [SerializeField] private DropModifiersIcon iconPrfb; [SerializeField] private DropPanelConfig config; private CanvasGroup canvasGroup; private void Awake() { GlobalEventAggregator.EventAggregator.AddListener<ButtonStartPressed>(this, RocketStarted); canvasGroup = GetComponent<CanvasGroup>(); } private void RocketStarted(ButtonStartPressed obj) { canvasGroup.DOFade(0, 1); (canvasGroup.transform as RectTransform).DOAnchorPosX(100, 1); } private void Start() { for (var x = 0; x< 3; x++) { var mod = config.GetModifierByType(TypeOfForce.Push); var go = Instantiate(iconPrfb, transform); go.Init(mod); } for (var x = 0; x< 1; x++) { var mod = config.GetModifierByType(TypeOfForce.AddSpeed); var go = Instantiate(iconPrfb, transform); go.Init(mod); } } } 



Preguntas anticipadas:
 for (var x = 0; x< 3; x++) for (var x = 0; x< 1; x++) 


3 y 1: los llamados números mágicos que simplemente se toman de la cabeza y se insertan en el código, esto debe evitarse, pero ¿por qué están aquí? El principio por el cual se forma el panel derecho aún no se ha determinado, y se decidió simplemente probar el prototipo con tantos modificadores en el prototipo.

¿Cómo hacerlo bien? - al menos colóquelo en campos serializables y configure las cantidades requeridas a través del inspector. ¿Por qué soy demasiado vago y deberías hacerlo? Aquí debemos proceder del panorama general, para la formación del número requerido de modificadores, una entidad y una configuración separadas seguirán siendo responsables, por lo que aquí fui demasiado vago esperando mucha refactorización en el futuro. Pero mejor no vago! )

Acerca de las configuraciones: cuando aparecieron las primeras conferencias sobre ScriptableObject, me gustó la idea de almacenar datos como un activo. Obtiene los datos necesarios donde los necesita, sin estar vinculado a una instancia de copia única. Luego hubo una conferencia sobre un enfoque para el desarrollo de juegos que involucraba ScriptableObject, donde solían almacenar configuraciones de instancia. En realidad, los ajustes preestablecidos / ajustes de algo guardado en el activo son la configuración.

Considere la clase de configuración:
Clase config
  [CreateAssetMenu(fileName = "DropModifiersPanel", menuName = "Configs/DropModifier", order = 2)] public class DropPanelConfig : ScriptableObject { [SerializeField] private ModifierBluePrintSimple[] modifierBluePrintSimples; public DropModifier GetModifierByType(TypeOfForce typeOfModifiers) { return modifierBluePrintSimples.FirstOrDefault(x => x.GetValue.TypeOfModifier == typeOfModifiers).GetValue; } } [System.Serializable] public class DropModifier { public TypeOfForce TypeOfModifier; public Sprite Icon; public GameObject Modifier; public Material Material; } 



¿Cuál es la esencia de su trabajo? Almacena una clase de datos modificadora personalizada.
  public class DropModifier { public TypeOfForce TypeOfModifier; public Sprite Icon; public GameObject Modifier; public Material Material; } 


El tipo de modificador es necesario para la identificación, un icono para la interfaz, el modo de juego del objeto de juego del modificador, el material aquí para que se pueda cambiar durante la configuración. Es posible que los modificadores ya estén ubicados en el campo de juego, y digamos que el diseñador del juego cambia su tipo, ahora le da aceleración, el modificador se inicia desde la configuración y actualiza todos los campos, incluido el material, de acuerdo con este tipo de modificador.

Trabajar con la configuración es muy simple: recurrimos a la configuración de los datos para un tipo específico, obtenemos estos datos, datos íntimos y posibles configuraciones de estos datos.

¿Dónde está el beneficio?
El beneficio es una gran flexibilidad, por ejemplo, si desea cambiar el material y el ícono en el modificador de aceleración, o digamos que reemplace todo el proyecto del juego. En lugar de reescribir y reenviar a los campos del inspector, simplemente cambiamos estos datos en una configuración y listo, todo se actualizará con nosotros, en todas las escenas / niveles / paneles.

¿Y si hay varios datos para el modificador del acelerador en la configuración?
En el prototipo, puede rastrearlo manualmente para que los datos no se dupliquen; en un borrador de trabajo, necesita pruebas y validación de datos.

Del icono al campo de juego


Modificador Icon Class
 public class DropModifiersIcon : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler { [SerializeField] private Image icon; [Header("       ")] [SerializeField] private RectTransform canvas; private CanvasGroup canvasGroup; private DropModifier currentModifier; private Vector3 startPoint; private Vector3 outV3; private GameObject currentDraggedObj; private void Start() { canvasGroup = GetComponent<CanvasGroup>(); startPoint = transform.position; canvas = GetComponentInParent<Canvas>().transform as RectTransform; } public void Init(DropModifier dropModifier) { icon.sprite = dropModifier.Icon; currentModifier = dropModifier; } public void OnBeginDrag(PointerEventData eventData) { BlockRaycast(false); currentDraggedObj = Instantiate(currentModifier.Modifier, WorldSpaceCoord(), Quaternion.identity); GlobalEventAggregator.EventAggregator.Invoke(new ImOnDragEvent { IsDragging = true }); } private void BlockRaycast(bool state) { canvasGroup.blocksRaycasts = state; } public void OnDrag(PointerEventData eventData) { Vector2 outV2; RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas, Input.mousePosition, null, out outV2); transform.position = canvas.transform.TransformPoint(outV2); if (currentDraggedObj != null) currentDraggedObj.transform.position = WorldSpaceCoord(); } private Vector3 WorldSpaceCoord() { RectTransformUtility.ScreenPointToWorldPointInRectangle(canvas, Input.mousePosition, Camera.main, out outV3); return outV3; } public void OnEndDrag(PointerEventData eventData) { GlobalEventAggregator.EventAggregator.Invoke(new ImOnDragEvent { IsDragging = false }); if (eventData.pointerCurrentRaycast.gameObject != null && eventData.pointerCurrentRaycast.gameObject.layer == 5) { Destroy(currentDraggedObj); transform.SetAsLastSibling(); canvasGroup.blocksRaycasts = true; } else Destroy(gameObject); } } public struct ImOnDragEvent { public bool IsDragging; } 



¿Qué está pasando aquí?
Tomamos el icono del panel, debajo creamos un blog del juego del modificador en sí. Y en realidad estamos configurando la coordenada desde el clic / carretilla hasta el espacio del juego, por lo que movemos el modificador en el espacio del juego junto con el ícono en la interfaz de usuario, por cierto te aconsejo que leas sobre RectTransformUtility, esta es una gran clase auxiliar en la que hay muchas características para la interfaz.

Digamos que cambiamos de opinión acerca de poner un modificador y devolverlo al panel,
  if (eventData.pointerCurrentRaycast.gameObject != null && eventData.pointerCurrentRaycast.gameObject.layer == 5) 

Este código nos permite comprender qué hay debajo del clic. ¿Por qué la comprobación de capas también se indica aquí? ¿Y por qué otra vez es el número mágico 5? Como recordamos de la segunda parte, utilizamos el gráfico de rastrillo no solo para la interfaz de usuario, sino también para el botón que está en la escena, también si agregamos la funcionalidad para eliminar modificadores ya colocados en el campo o moverlos, entonces también caerán en el gráfico de rastrillo, por lo tanto También hay una comprobación adicional para pertenecer a la capa de interfaz de usuario. Esta es la capa predeterminada, y su orden no cambia, por lo que el número 5 aquí generalmente no es un número mágico.

Como resultado, resulta que si liberamos el icono sobre el panel, volverá al panel, si sobre el campo de juego el modificador permanece en el campo, el icono se eliminará.

Se dedicó 1 día hábil al código prototipo. Además de un poco de alboroto en archivos y gráficos. En general, se descubrió que la jugabilidad era adecuada, a pesar de la gran cantidad de preguntas sobre el arte y los chips de diseño del juego. Misión completada.

Conclusiones y recomendaciones.


  1. Establecer una arquitectura mínima, pero no obstante arquitectura
  2. Sigue los principios básicos, pero sin fanatismo)
  3. Elige soluciones simples
  4. Entre versatilidad y velocidad: es mejor elegir una velocidad para el prototipo
  5. Para proyectos grandes / medianos, implica que es mejor reescribir el proyecto desde cero. Por ejemplo, ahora la tendencia en Unity es DOTS, tiene que escribir muchos componentes y sistemas, es malo para tiradas cortas, pierde tiempo y tiradas largas: cuando todos los componentes y sistemas están registrados, comienza la ganancia de tiempo. No creo que sea genial dedicar mucho tiempo a la arquitectura de tendencias y descubrir qué es el prototipo de mierda

Prototipos exitosos para todos.

Source: https://habr.com/ru/post/453748/


All Articles