Cómo desarrollar otro juego de plataformas con Unity. Otro tutorial

Hola Habr!


Otro artículo te está esperando debajo del corte, que te informará sobre cómo me puse el objetivo de programar el juego, basado en la traducción del artículo sobre Habr llamado Level Design Patterns para juegos 2D .


El artículo tiene mucho texto (tanto regular como fuente) y muchas imágenes.


Antes de comenzar mi primer artículo, vamos a conocerte. Me llamo Denis Trabajo como administrador de sistemas con una experiencia total de 7 años. No me corresponde decirte que un administrador del sistema es un tipo de técnico de TI que despliega diligentemente una vez y luego contempla el parpadeo de varios caracteres en un monitor. Con el tiempo, llegué a la conclusión de que era hora de ampliar los límites del conocimiento y pasar a la programación. Sin entrar en detalles, intenté hacer proyectos en C ++ y Python. Pero después de un año de estudio, llegué a la conclusión de que programar mi aplicación y el software del sistema no son míos. Por varias razones


Después de pensar más profundamente, me hice la pregunta: ¿qué es lo que realmente me gusta hacer con los diferentes tipos de equipos informáticos? Mi pregunta a mí mismo me arrojó lejos en la infancia, es decir, en las horas felices que pasé para PS1, PS2, Railroad Tycoon 3 para PC ... Bueno, ya entiendes. Videojuego!


Por la cantidad de diversos materiales de capacitación, la elección recayó en Unity (¿no reinventar la rueda?). Después de un mes de leer y ver varios materiales, decidí lanzar el primer juego infantil muy simple al mercado de juegos. Para superar el miedo, por así decirlo. Después de todo, lanzar aplicaciones en el mercado de juegos no da miedo, ¿verdad?


Después de un par de meses, lancé el juego de plataformas ya más complejo. Luego hubo un descanso (después de todo, el trabajo debe ser trabajado, después de todo).


Hace unas dos semanas, vi una traducción de un artículo en un centro llamado Level Design Patterns para juegos 2D ( https://habr.com/en/post/456152/ ) y pensé para mí mismo: ¿por qué no? El artículo tiene una tabla simple y clara con una lista de lo que debería estar en el juego para que sea interesante. Copié la tabla para mí en OneNote y marqué cada patrón con la etiqueta Casos (que se puede marcar como completada).


¿Qué quiero obtener como resultado? Tu critica Como me gusta decirme a mí mismo: si quieres aprender a nadar, bucea con la cabeza. Si crees que hice algo bien, escríbeme sobre esto en un comentario. Si crees que hice algo mal, escríbelo doblemente.


Comenzaré mi largo programa para programar otro juego de plataformas.


Avatar


Una entidad controlada por jugadores dentro de un juego. Por ejemplo, Mario y Luigi en Super Mario Bros (Nintendo, 1985).


Hay varias subtareas que deben implementarse para darle vida al héroe. A saber:


•   ( ) •      •      •       •        

Para implementar la animación, necesitamos convertir un solo sprite en un sprite múltiple. Esto se hace increíblemente simple. Agregue el sprite a la carpeta del proyecto y encuéntrelo en Explorer Explorer Unity Explorer. Luego, al hacer clic en el sprite en la ventana del inspector, cambia el valor de la propiedad SptiteMode de Single a Multiple .


imagen


Haga clic en Aplicar , luego SpriteEditor .


Dentro de la ventana del Editor de Sprite , debe seleccionar cada cuadro de la futura animación con el mouse como se muestra en la figura a continuación.


Además, Unity proporciona la capacidad de resaltar automáticamente los límites de los objetos dentro del sprite. Para hacer esto, en la ventana Sprite Editor , debe hacer clic en el botón Slice . En el menú desplegable, debe tener Type => Automatic, Pivot => Center . Todo lo que tienes que hacer es hacer clic en el botón Slice . Después de eso, todos los objetos dentro del sprite se seleccionarán automáticamente.



Hagamos esta operación para todas las demás animaciones. A continuación, deberá configurar los estados de animación y su cambio. Esto se hace en dos pasos. La primera acción, código de programa.
Crea un objeto de juego vacío. Para hacer esto, haga clic con el botón derecho en la pestaña Jerarquía y seleccione Crear vacío en el menú desplegable.



Un objeto de juego vacío, que se crea en el escenario, por defecto tiene solo un componente: Transformar . Este componente determina la posición del objeto en el escenario, el ángulo de inclinación y su escala.


Puede encontrar la palabra transformar en dos significados diferentes:


  • Transformar es una clase. Como se trata de una clase, Transform describe una implementación de software de qué coordenadas se ubicará este objeto y qué dimensiones tendrá.
  • transform es una instancia de una clase. Es decir, puede hacer referencia a un objeto específico y cambiar su posición o escala en la escena. Por ejemplo, más adelante en el código habrá una línea:
     transform.position = new Vector2 (transform.position.x + dirX, transform.position.y + dirY);. 


    Esta línea será responsable del movimiento de Lucas en el escenario.

Para crear su propio componente, haga clic en el botón Agregar componente en la pestaña del inspector de objetos. A continuación, aparece un cuadro de búsqueda entre los componentes estándar. Es suficiente comenzar a escribir el nombre de la secuencia de comandos futura (o componente ya implementado), si no hay nombres adecuados, Unity le ofrecerá crear un nuevo componente. Llamé a este componente HeroScript.cs .



Primero, describimos los campos que almacenarán información sobre el componente visual y físico de Lucas:


Encabezado de spoiler
 private Animator animator; //      . private Rigidbody2D rb2d; //rb     

A continuación, los campos que responderán al movimiento del personaje:


Encabezado de spoiler
 /*  ,     */ Vector3 localScale; bool facingRight = true; [SerializeField] private float dirX, dirY; //    [Range(1f, 20f)] [SerializeField] private float moveSpeed; //    private SpriteRenderer sprite; //   SpriteRenderer /*   ,     */ 

Se ha comenzado, excelente. A continuación, se describirá una enumeración y se escribirá una propiedad que será responsable de cambiar el estado de la animación. Esta enumeración debe escribirse fuera de la clase:


Encabezado de spoiler
 /* *      . *         *    . */ public enum CharState { idle, //   0 Run, //   1 Walk, //   2 Die //   3 } 

Implementamos una propiedad que recibirá y establecerá un nuevo estado de animación:


Encabezado de spoiler
 public CharState State { get {//    CharState    animator       int return (CharState)animator.GetInteger("State"); } set { //    animator     int    State       ,      int. animator.SetInteger("State", (int)value); } } 

La parte del software está terminada. Ahora tenemos una enumeración y una propiedad que se asociará con el cambio de animación. A continuación, el segundo paso. En el editor de Unity, debe vincular el estado de la animación e indicar a qué valores int deben cambiarse.


Para hacer esto, debe asociar los sprites múltiples creados previamente con un objeto de juego vacío. Todo lo que necesita hacer es seleccionar los cuadros en el Explorador de unidades y arrastrarlos a un objeto de juego vacío, al que previamente arreglamos el guión.


imagen


Haga esto con cada animación posterior. Además, en el explorador con animaciones encontrará la apariencia de un objeto con un diagrama de bloques y un botón Reproducir . Al hacer doble clic en él, se abrirá la pestaña Animador . En el interior, verá varios bloques con animaciones e, inicialmente, solo se conectan los estados de Entrada y el primer conjunto de animación que estaban conectados. AnyState y otras animaciones se mostrarán como cuadrados grises regulares. Para vincular todo, debe hacer clic en el estado de AnyState y seleccionar el único menú desplegable Realizar transacción y vincularlo al bloque gris. Esta operación debe hacerse para cada condición. Al final, debería obtener algo como lo que ve en la captura de pantalla a continuación.


imagen


A continuación, debe indicar explícitamente qué estado debe ser exactamente para comenzar la animación necesaria. Presta atención a la captura de pantalla, es decir, su parte izquierda. Parámetros de la pestaña. Se crea una variable de tipo int State . Luego, preste atención al lado derecho. En primer lugar, desde la transición de la animación, debe desmarcar la casilla de verificación Can Transaction To Self . Esta operación lo salvará de transiciones de animación extrañas ya veces completamente incomprensibles para sí mismo y la sección Condiciones , donde indicamos que a esta transición de animación se le asignó el valor 3 de la variable Estado . Después de eso, Unity sabrá qué animación ejecutar.
Todo se hace para el movimiento del personaje animado. Sigamos adelante.


El siguiente paso es enseñarle a Lucas a moverse por el escenario. Esto es completamente programación. Para mover al personaje por la escena, necesitará botones, haciendo clic en el cual Lucas irá de un lado a otro. Para hacer esto, en la pestaña Almacén de activos , necesitamos importar Activos estándar, pero no todos, solo algunos componentes adicionales, a saber:


• CrossPlatformInput
• Editor
• Medio ambiente


Después de importar el activo, se debe modificar la ventana principal de Unity y aparecerá una pestaña Entrada móvil adicional. Lo activamos


Creemos nuevos elementos de IU en el escenario: botones de control. Crea 4 botones en cada dirección. Arriba, abajo, adelante y atrás. En el componente Imagen, asignamos a los botones una imagen que corresponderá a la imagen, lo que significa la capacidad de moverse. Debería parecerse a la siguiente captura de pantalla:



Para cada botón, agregue un componente AxisTouchButton . Este script tiene solo 4 campos. El campo axisName significa a qué nombre responder cuando se llama. El campo axisValue es responsable de la dirección en la que se moverá Lucas. El campo ResponseSpeed es responsable de la rapidez con que Lucas desarrollará su velocidad. El campo returnToCentreSpeed ​​es responsable de la rapidez con que el botón vuelve al centro. Para el botón Adelante, déjelo como está. Para el botón Atrás, cambie el valor de axisValue a -1 para que Lucas retroceda. Para los botones Arriba y Abajo, cambie el axisName a Vertical . Para el botón AxialValue, establezca el valor en 1, para Abajo -1.


A continuación, modifique HeroScript.cs . Agregar espacio de nombres a la directiva using


 using UnityStandardAssets.CrossPlatformInput; //    . 

Encabezado de spoiler
        : /*  ,     */ Vector3 localScale; //   bool facingRight = true; [SerializeField] private float dirX, dirY; //    [Range(1f, 20f)] [SerializeField] private float moveSpeed; //    private SpriteRenderer sprite; //   SpriteRenderer /*   ,     */ 

En el método de inicio estándar, agregue el siguiente código:


Encabezado de spoiler
  void Start() { localScale = transform.localScale; animator = GetComponent<Animator>(); //     . sprite = GetComponent<SpriteRenderer>(); //  SpriteRenderer rb = GetComponent<Rigidbody2D>(); State = CharState.idle; } 

Creamos un método que se encargará de mover al héroe:


Encabezado de spoiler
 public void MoveHero() { dirX = CrossPlatformInputManager.GetAxis ("Horizontal") * moveSpeed * Time.deltaTime; dirY = CrossPlatformInputManager.GetAxis ("Vertical") * moveSpeed * Time.deltaTime; transform.position = new Vector2 (transform.position.x + dirX, transform.position.y + dirY); } 

Como puede ver, todo es simple. Los campos dirX y DirY registran información sobre la dirección del Eje ( horizontal y vertical ) multiplicada por la velocidad (que deberá especificarse en el editor) y multiplicada por el tiempo de viaje desde el último cuadro.
transform.position escribe la nueva posición en el componente Transformar de nuestro objeto de juego.


En el lado práctico del problema, puede ejecutar la escena y ver cómo Lucas cae al abismo, ya que no hay objetos debajo que puedan evitarlo. Lucas siempre está en la animación Idle y no se da vuelta cuando lo dirigimos de regreso. Para esto, el script necesita ser modificado. Cree un método que determine en qué dirección mira Lucas:


Encabezado de spoiler
 void CheckWhereToFace () { if (dirX > 0) { facingRight = true; State = CharState.Walk; } if (dirX < 0) { facingRight = false; State = CharState.Walk; } if (dirX == 0) { State = CharState.idle; } if (dirY < 0) { State = CharState.Walk; } if (dirY > 0) { State = CharState.Walk; } if (((facingRight) && (localScale.x < 0)) || ((!facingRight) && (localScale.x > 0))) localScale.x *= -1; transform.localScale = localScale; 

Esta parte del código tampoco es difícil. El método describe que si dirX > 0 (si vamos a la derecha), rotamos el sprite en esta dirección y comenzamos la animación de la caminata. Si es menor que 0, gire Lucas 180 grados y comience la animación. Si dirX es cero, entonces Lucas está de pie y debe comenzar la animación de espera.


¿Por qué es preferible usar una operación con Scale en este caso que usar flipX = true ? En el futuro, describiré la capacidad de tomar cualquier objeto en la mano y, naturalmente, Lucas puede darse la vuelta sosteniendo algo en sus manos. Si usara el reflejo habitual, el objeto que tendría en mis manos permanecería en el lado derecho (por ejemplo) cuando Lucas mira a la izquierda y viceversa. Al acercar, se moverá el objeto que mantiene a Lucas en la misma dirección en la que Lucas mismo giró.


Colocamos la función CheckWhereToFace () en la función Update () , para su monitoreo cuadro por cuadro.


Genial Se completan los primeros 2 puntos de 5. Pasemos a las necesidades de Lucas. Digamos que Lucas tendrá 3 tipos de necesidades que deben cumplirse para mantenerse con vida. Este es el nivel de vida, el nivel de hambre y el nivel de sed. Debe crear para esto un panel simple y comprensible con un indicador de cada elemento. Para crear dicho panel, haga clic derecho y seleccione UI => Panel .


Vamos a marcarlo aproximadamente como se muestra a continuación


imagen


El panel consta de tres imágenes (Imagen) de cada necesidad (izquierda). A la derecha está el panel en sí. En la primera capa (lo pondremos de esta manera), hay un indicador de color (Imagen) que no tiene transparencia, un objeto de Imagen se copia debajo, que es transparente. Esta imagen es medio transparente al original. Además, Imagen, que no tiene transparencia, tiene la propiedad Tipo de imagen = Relleno . Esta característica nos permitirá simular una disminución en la plenitud de la escala de necesidades.


imagen


imagen


Definir nuevas variables estáticas:


Encabezado de spoiler
 /*  ,     */ [SerializeField] public static float Health = 100, Eat = 100, Water = 100, _Eat = 0.05f, _Water = 0.1f; //        .    _  . /*   ,     */ /*  ,      */ [SerializeField] Image iHealt, iEat, iWater; //      /*   ,      */ 

En este caso, uso campos estáticos. Esto se hace para que estos campos sean únicos para toda la clase. Además, esto nos permitirá acceder directamente a estos campos por nombre de clase. Escribimos algunas funciones simples:


Encabezado de spoiler
 private float fEat(float x) { Eat = Eat - x * Time.deltaTime; iEat.fillAmount = Eat / 100f; // ,        return Eat; } private float fWater(float x) { Water = Water - x * Time.deltaTime; iWater.fillAmount = Water / 100; return Water; } 

Luego, escribimos un método que recopila información sobre el deseo de comer y beber:


Encabezado de spoiler
 private void Needs() { if (fEat(_Eat) < 0) { Debug.Log(Eat); } else if (fEat(0) == 0) { StartCoroutine(ifDie()); } if (fWater(_Water) < 0) { Debug.Log(Water); } else if (fWater(0) == 0) { StartCoroutine(ifDie()); } 

La función Needs () se coloca en la función Update () y se llama a cada cuadro. En consecuencia, en las líneas


 if (fEat(_Eat) < 0) 

Se llama una función que pasa como parámetro cuánto se debe tomar de las variables Eat y Water . Si el resultado de la función no es 0, entonces Lucas aún no ha muerto de sed o hambre. Si Lucas muere de hambre o heridas mortales, entonces hacemos corutina


 StartCoroutine(ifDie()); 

que comienza la animación de la muerte y reinicia el nivel:


Encabezado de spoiler
 IEnumerator ifDie() { State = CharState.Die; yield return new WaitForSeconds(2); SceneManager.LoadScene("WoodDay", LoadSceneMode.Single); } 

Azulejo duro


Un objeto del juego que no permite que el jugador lo atraviese. Ejemplo: género en Super Mario Bros (Nintendo, 1985).


Para realizar la tierra y evitar que Lucas caiga a través de ella, debes conectar los componentes BoxCollider2D y Rigidbody2D a Lucas. Además, necesita un sprite de la tierra en el que se ubicará el componente BoxCollider2D . El componente BoxCollider2D implementa colisionadores y su comportamiento de colisión. En esta etapa, no necesitamos nada más que evitar el fracaso de Lucas underground. Todo lo que podemos editar opcionalmente son los bordes del colisionador. En mi caso, el sprite de tierra tiene una superficie de hierba y para que no parezca que la hierba pueda soportar el peso de Lucas, editaré los bordes del componente.



Ahora, un emocionante proceso de marcado de nivel. Para mayor comodidad, puede exportar este cubo de tierra a una casa prefabricada. Un prefabricado es un contenedor de un objeto de juego, tras la modificación del cual puede aplicar automáticamente cambios a todos los objetos de juego creados a partir de este prefabricado. Luego, clone este prefab con CTRL + D (después de seleccionarlo en la pestaña de jerarquía) y colóquelo en el escenario.


imagen


Pantalla


La parte del nivel / mundo del juego que actualmente es visible para el jugador.


Configure una cámara que siga al jugador a punto de mostrar parte de la escena. A continuación, habrá un script muy simple para implementar:


Encabezado de spoiler
 public GameObject objectToFollow; public float speed = 2.0f; void Update () { CamFoll(); } private void CamFoll() { float interpolation = speed * Time.deltaTime; Vector3 position = this.transform.position; position.y = Mathf.Lerp(this.transform.position.y, objectToFollow.transform.position.y, interpolation); position.x = Mathf.Lerp(this.transform.position.x, objectToFollow.transform.position.x, interpolation); this.transform.position = position; } 

En el campo objectToFollow del tipo GameObject , se asignará un objeto para ser monitoreado, y en el campo de velocidad, la velocidad a la que es necesario moverse suavemente detrás del GameObject asignado.


La información sobre la velocidad de movimiento desde el último cuadro se registra en el campo de interpolación. A continuación, se utilizará el método Lerp, que garantizará un movimiento suave de la cámara detrás de Lucas cuando se mueva a lo largo de los ejes X y U. Desafortunadamente, no puedo explicar el funcionamiento de la línea.


 position.y = Mathf.Lerp(this.transform.position.y, objectToFollow.transform.position.y, interpolation); 

en términos de matemática. Por lo tanto, diré más simple: este método alargará el tiempo de ejecución de cualquier acción. En nuestro caso, esto está moviendo la cámara detrás del objeto.


Peligro


Encabezado de spoiler

Entidades que impiden al jugador completar su tarea. Ejemplo: picos de 1001 picos (Nicalis y 8bits Fanatics, 2014).


Comencemos agregando algo que no solo evitará que Lukas pase por el escenario hasta el final, sino que afectará el número de sus vidas y la capacidad de morir (por ejemplo, implementaremos el quinto subproblema para implementar la historia de vida de Lukas: el Héroe puede ser asesinado o puede morir).
En este caso, extendimos los picos en el escenario que estarán ocultos detrás de la vegetación y solo la atención del jugador ayudará a pasar.


Cree un GameObject vacío y conecte los componentes SpriteRenderer y PolygonCollider2D . En el componente SpriteRenderer , conectamos el sprite del botón de púas o cualquier otro objeto como lo desee. Además, asigne tag = Thorn a la espiga.


A continuación, en Lucas GameObject, creamos un guión que será responsable de lo que le sucederá si Lucas choca con otros colisionadores. En mi caso, lo llamé ColliderReaction.cs


Encabezado de spoiler
 private Rigidbody2D rb2d; void Start() { rb2d = GetComponent<Rigidbody2D>(); } public void OnTriggerEnter2D(Collider2D collision) { switch (collision.gameObject.tag) { case "Thorn": { rb2d.AddForce(transform.up * 4, ForceMode2D.Impulse); HeroScript.Health = HeroScript.Health - 5; } break; } } 

La esencia del guión es tan simple como 2x2. Cuando una etiqueta Thorn colisiona con un objeto del juego, la instrucción Switch se compara con los candidatos que especificamos. En nuestro caso, por ahora, es Thorn . Primero, Lucas vomita, y luego pasamos a una variable estática y tomamos 5 unidades de vida de Lucas. Mirando hacia el futuro, puedo decir que tiene sentido describir lo mismo para un conflicto con enemigos:


Encabezado de spoiler
 case "Enemy": { rb2d.AddForce(transform.up * 2, ForceMode2D.Impulse); HeroScript.Health = HeroScript.Health - 10; } break; 

A continuación, propongo matar dos pájaros de un tiro.


Artículo recogido y regla.


Un objeto del juego que los jugadores pueden recoger.


Proponemos la regla de que si Lucas quiere ir entre las islas y subir, entonces debe recoger un árbol para construir puentes y escaleras.
Según los métodos ya aprobados, crearemos un árbol y escaleras.


Conectaremos un script al árbol que será responsable de la cantidad de registros que puede eliminar si comienza a cortarlo. Dado que solo se propuso la animación del ataque en el conjunto de sprites, la usaremos cuando cortemos el árbol (costos de producción).
El script que está en el árbol:


Encabezado de spoiler
 [SerializeField] private Transform inst; //     [SerializeField] private GameObject FireWoodPref; //   [SerializeField] private int fireWood; //        

Cuando comienza el nivel, escribimos un valor aleatorio en fireWood :


Encabezado de spoiler
 void Awake() { fireWood = Random.Range(4,10); } 

Describe un método con un parámetro que será responsable de cuántos registros caerán de un golpe:


Encabezado de spoiler
 public int fireWoodCounter(int x) { for (int i = 0; i < fireWood; i++) { fireWood = fireWood - x; InstantiateFireWood(); } return fireWood; } 

Un método que creará clones de registro en el escenario.
vacío privado InstantiateFireWood ():


Encabezado de spoiler
  { Instantiate(FireWoodPref, inst.position, inst.rotation); } 


Creemos un registro y conectemos un script con el siguiente código:


Encabezado de spoiler
 public void OnTriggerEnter2D(Collider2D collision) { switch (collision.gameObject.tag) { case "Player": { if (InventoryOnHero.woodCount > 10) { Debug.Log("   !"); } else { InventoryOnHero.woodCount = InventoryOnHero.woodCount + 1; Destroy(this.gameObject); } } break; } } 

A continuación, también crearemos una clase que será responsable del inventario.


Primero, verifique si hay espacio en la bolsa. Si no, entonces el error y el registro siguen mintiendo, si hay espacio, entonces reponemos el inventario por una unidad y destruimos el registro.
A continuación, debe hacer algo con estos recursos. Como se mencionó anteriormente, ofrecemos al jugador la oportunidad de construir puentes y escaleras.


Para crear un puente, necesitamos 2 prefabricados con la mitad izquierda y derecha del puente. BoxCollider2D . , , - , .


:


Encabezado de spoiler
 [SerializeField] private Transform inst1, inst2; //        [SerializeField] private GameObject bridgePref1, bridgePref2; //   [SerializeField] private int BridgeCount; //   ,   .    

:


Encabezado de spoiler
 public void BuildBridge() { if (InventoryOnHero.woodCount == 0) { Debug.LogWarning (" !"); } if (InventoryOnHero.woodCount > 0) { BridgeCount = BridgeCount - 1; InventoryOnHero.woodCount = InventoryOnHero.woodCount - 1; } switch (BridgeCount) { case 5: Inst1(); break; case 0: Inst2(); break; default: Debug.LogWarning("-      "); break; } } 

, , . , 10 , 12 8.


, , , , . , 1 , 1 . , 5, , . 0, . , .


.


, ColliderReaction.cs :


Encabezado de spoiler
 void OnTriggerStay2D(Collider2D collision) { switch (collision.gameObject.tag) { case "Ladder": { rb2d.gravityScale = 0; } break; } } void OnTriggerExit2D(Collider2D collision) { switch (collision.gameObject.tag) { case "Ladder": { rb2d.gravityScale = 1; } break; } } 

OnTriggerStay2D , . , 0. , . OnTriggerExit2D , .



, .


19 , . , , , , , .


GO, SpriteRenderer , BoxCollider2D , Rigidbody2D . , — , . , ru.stackoverflow.com.


imagen


Trees .


, . , -, , Raycast 2 (4 ). , , , ( ). ( ), . , . , , . , . , , ( , ).


, . , - .


:


Encabezado de spoiler
 [SerializeField] private GameObject area; private bool m1 = true, m2; // m  move private void fGreenMonster() { float dist = Vector3.Distance(greenMonster.transform.position, area.transform.position); Debug.Log(dist); if (m1) { if (dist < 3f) { transform.position += new Vector3(speed,0,0) * Time.deltaTime; SR.flipX = true; } else { m1 = false; m2 = true; } } if (m2) { if(dist >= 1f) { transform.position += new Vector3(-speed,0,0) * Time.deltaTime; SR.flipX = false; } else { m2 = false; m1 = true; } } } 

Update() , . , 3 , . 3, , .


imagen


, .


Encabezado de spoiler
 private void fSunFlower() { canBullet = canBullet - minus * Time.deltaTime; if (canBullet <= 0 && SR.flipX == false) { GameObject newArrow = Instantiate(sunFlowerBullet) as GameObject; newArrow.transform.position = transform.position; Rigidbody2D rb = newArrow.GetComponent<Rigidbody2D>(); rb.velocity = sunFlowerTrans.transform.forward * -sunFlowerBulletSpeed; canBullet = 2; } if (canBullet <= 0 && SR.flipX == true) { GameObject newArrow = Instantiate(sunFlowerBullet) as GameObject; newArrow.transform.position = transform.position; Rigidbody2D rb = newArrow.GetComponent<Rigidbody2D>(); rb.velocity = sunFlowerTrans.transform.forward * sunFlowerBulletSpeed; canBullet = 2; } 

 canBullet = canBullet - minus * Time.deltaTime; 

, .


Encabezado de spoiler
 if (canBullet <= 0 && SR.flipX == false) { GameObject newArrow = Instantiate(sunFlowerBullet) } 

, , , , :


Encabezado de spoiler
 public int Damage(int x) { Health = Health - x; return Health; } 

, , :


Encabezado de spoiler
 public void ifDie() { if (Damage(0) <= 0) { Destroy(this.gameObject); } } 

0, .


, :


Encabezado de spoiler
 if (bGreenMonster) { fGreenMonster(); } if (bSunFlower) { fSunFlower(); } 

, .


imagen


.


, ?


, .


:



:


Encabezado de spoiler
 [SerializeField] private Transform Hero; //         [SerializeField] private float distWhatHeroSee; //   [SerializeField] private LayerMask Tree, BridgeBuild, LadderBuild ,drinkingWater, lEnemy; //   

, :


Encabezado de spoiler
 private void AttackBtn() { if (CrossPlatformInputManager.GetButtonDown("Attack")) { GameObject.Find("Hero").GetComponent<HeroScript>().State = CharState.AttackA; Collider2D[] Trees = Physics2D.OverlapCircleAll(Hero.position, distWhatHeroSee, Tree); for (int i = 0; i < Trees.Length; i++) { Trees[i].GetComponent<TreeControl>().fireWoodCounter(1); Debug.Log("Trees Collider"); HeroScript.Water = HeroScript.Water - 0.7f; } // BB  BridgeBuild Collider2D[] BB = Physics2D.OverlapCircleAll(Hero.position, distWhatHeroSee, BridgeBuild); for (int i = 0; i < BB.Length; i++) { BB[i].GetComponent<BridgeBuilding>().BuildBridge(); HeroScript.Water = HeroScript.Water - 0.17f; } 

 GameObject.Find("Hero").GetComponent<HeroScript>().State = CharState.AttackA; 

, .
, :


Encabezado de spoiler
 Collider2D[] Trees = Physics2D.OverlapCircleAll(Hero.position, distWhatHeroSee, Tree); for (int i = 0; i < Trees.Length; i++) { Trees[i].GetComponent<TreeControl>().fireWoodCounter(1); Debug.Log("Trees Collider"); HeroScript.Water = HeroScript.Water - 0.7f; } 

Trees , . , , , . .
, . Simple as that!


, - :


imagen


, — .
, . , , , .


2 , .


Buena suerte


.


https://opengameart.org/ , :


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


All Articles