Como desenvolver outro jogo de plataformas usando o Unity. Outro tutorial

Olá Habr!


Outro artigo está esperando por você, que mostrará como eu me propus a programar o jogo, com base na tradução do artigo no Habr chamado Level Design Patterns para jogos 2D .


O artigo tem muito texto (regular e fonte) e muitas fotos.


Antes de começar meu primeiro artigo, vamos conhecê-lo. Meu nome é Denis. Trabalho como administrador de sistemas com uma experiência total de 7 anos. Não cabe a você dizer que um administrador de sistema é um tipo de equipe de TI que é implantada com cuidado uma vez e depois contempla a cintilação de vários caracteres em um monitor. Com o tempo, cheguei à conclusão de que era hora de expandir os limites do conhecimento e mudar para a programação. Sem entrar em detalhes, tentei criar projetos em C ++ e Python. Mas, após um ano de estudo, cheguei à conclusão de que programar meu aplicativo e software de sistema não é meu. Por várias razões.


Depois de pensar mais fundo, me perguntei: o que eu realmente gosto de fazer com diferentes tipos de equipamentos de computação? Minha pergunta para mim mesma me jogou longe na infância, ou seja, nas horas felizes passadas para PS1, PS2, Railroad Tycoon 3 para PC ... Bem, você entende. Video game!


Pelo número de vários materiais de treinamento, a escolha recaiu sobre o Unity (não reinventar a roda?). Depois de um mês lendo e vendo vários materiais, decidi lançar o primeiro jogo infantil muito simples no mercado de brinquedos. Para superar o medo, por assim dizer. Afinal, liberar aplicativos no mercado de jogos não é assustador, certo?


Depois de alguns meses, lancei o jogo de plataformas já mais complexo. Depois houve uma pausa (afinal, o trabalho deve ser trabalhado, afinal).


Cerca de duas semanas atrás, vi uma tradução do artigo em um hub chamado Level Design Patterns para jogos 2D ( https://habr.com/en/post/456152/ ) e pensei comigo mesmo - por que não? O artigo possui uma tabela simples e clara com uma lista do que deve estar no jogo para que seja interessante. Copiei a tabela para mim no OneNote e marquei cada padrão com a tag Cases (que pode ser marcada como concluída).


O que eu quero obter como resultado? Sua crítica. Como eu gostaria de dizer para mim mesmo - se você quiser aprender a nadar, mergulhe com a cabeça. Se você acha que fiz algo bem, escreva-me sobre isso em um comentário. Se você acha que fiz algo errado, escreva duplamente.


Iniciarei meu longo programa para programar outro jogo de plataformas.


Avatar


Uma entidade controlada pelos jogadores dentro de um jogo. Por exemplo, Mario e Luigi em Super Mario Bros (Nintendo, 1985).


Existem várias subtarefas que precisam ser implementadas para dar vida ao herói. Ou seja:


•   ( ) •      •      •       •        

Para implementar a animação, precisamos converter um único sprite em um multi sprite. Isso é feito incrivelmente simples. Adicione o sprite à pasta do projeto e encontre-o no Explorer Explorer Unity Explorer. Em seguida, clicar no sprite na janela do inspetor altera o valor da propriedade SptiteMode de Único para Múltiplo .


imagem


Clique em Aplicar e , em seguida, SpriteEditor .


Dentro da janela do Editor de Sprite , você precisa selecionar cada quadro da animação futura com o mouse, como mostra a figura abaixo.


Além disso, o Unity oferece a capacidade de destacar automaticamente os limites dos objetos no sprite. Para fazer isso, na janela do Editor de Sprite , você deve clicar no botão Fatia . No menu suspenso, você deve ter Tipo => Automático, Pivô => Centro . Tudo que você precisa fazer é clicar no botão Fatia . Depois disso, todos os objetos dentro do sprite serão selecionados automaticamente.



Vamos fazer esta operação para todas as outras animações. Em seguida, você precisará configurar os estados da animação e sua alternância. Isso é feito em duas etapas. A primeira ação, código do programa.
Crie um objeto de jogo vazio. Para fazer isso, clique com o botão direito do mouse na guia Hierarquia e selecione Criar vazio no menu suspenso.



Um objeto de jogo vazio, criado no palco, por padrão, possui apenas um componente - Transform . Este componente determina a posição do objeto no palco, o ângulo de inclinação e sua escala.


Você pode encontrar a palavra transformar em dois significados diferentes:


  • Transform é uma classe. Como essa é uma classe, o Transform descreve uma implementação de software de quais coordenadas esse objeto será localizado e de quais dimensões ele será.
  • transform é uma instância de uma classe. Ou seja, você pode consultar um objeto específico e alterar sua posição ou escala na cena. Por exemplo, mais adiante no código, haverá uma linha:
     transform.position = new Vector2 (transform.position.x + dirX, transform.position.y + dirY);. 


    Essa linha será responsável pelo movimento de Lucas no palco.

Para criar seu próprio componente, clique no botão Adicionar componente na guia do inspetor de objetos. Em seguida, uma caixa de pesquisa aparece entre os componentes padrão. Basta começar a digitar o nome do script futuro (ou componente já implementado); se não houver nomes adequados, o Unity oferecerá a você a criação de um novo componente. Eu chamei esse componente de HeroScript.cs .



Primeiro, descrevemos os campos que armazenam informações sobre o componente visual e físico de Lucas:


Título de spoiler
 private Animator animator; //      . private Rigidbody2D rb2d; //rb     

A seguir, os campos que responderão ao movimento do personagem:


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

Um começo foi feito, excelente. A seguir, uma enumeração será descrita e uma propriedade será gravada, responsável pela alteração do estado da animação. Essa enumeração deve ser escrita fora da classe:


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

Implementamos uma propriedade que receberá e definirá um novo estado de animação:


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

A parte do software está concluída. Agora, temos uma enumeração e uma propriedade que serão associadas à troca da animação. Em seguida, o segundo passo. No editor do Unity, você precisa vincular o estado da animação e indicar em quais valores int eles precisam ser alterados.


Para fazer isso, você deve associar os multi sprites criados anteriormente a um objeto de jogo vazio. Tudo o que você precisa fazer é selecionar os quadros no Unity Explorer e arrastá-los para um objeto de jogo vazio, ao qual corrigimos o script anteriormente.


imagem


Faça isso com cada animação subseqüente. Além disso, no explorer com animações, você encontrará a aparência de um objeto com um diagrama de blocos e um botão Play . Clicar duas vezes nele abrirá a guia Animador . Dentro, você verá vários blocos com animações e, inicialmente, apenas os estados de Entrada e o primeiro conjunto de animações que foram conectados estão conectados. AnyState e outras animações serão exibidas como quadrados cinza regulares. Para vincular tudo, você precisa clicar no status do AnyState e selecionar o único menu suspenso Make Transaction e vinculá-lo ao bloco cinza. Esta operação deve ser realizada para cada condição. No final, você deve obter algo parecido com o que vê na imagem abaixo.


imagem


Em seguida, você deve indicar explicitamente o que exatamente deve ser o Estado para iniciar a animação necessária. Preste atenção na captura de tela, a parte esquerda. Guia Parâmetros . Uma variável do tipo int State é criada nela. Em seguida, preste atenção no lado direito. Antes de tudo, a partir da transição da animação, você precisa desmarcar a caixa de seleção Pode a transação para si mesmo . Esta operação salvará você de transições de animação estranhas e às vezes completamente incompreensíveis para si e para a seção Condições , onde indicamos que essa transição de animação recebeu o valor 3 da variável State . Depois disso, o Unity saberá qual animação executar.
Tudo é feito para o movimento dos personagens animados. Vamos seguir em frente.


O próximo passo é ensinar Lucas a se movimentar pelo palco. Isso é totalmente programação. Para mover o personagem pela cena, você precisará de botões, clicando nos quais Lucas vai e volta. Para fazer isso, na guia Assets Store , precisamos importar ativos padrão, mas não todos, apenas alguns componentes adicionais, a saber:


• CrossPlatformInput
Editor
• Meio ambiente


Após a importação do ativo, a janela principal do Unity deve ser modificada e uma guia adicional Entrada móvel será exibida. Nós o ativamos.


Vamos criar novos elementos de interface do usuário no palco - botões de controle. Crie 4 botões em cada direção. Para cima, para baixo, para frente e para trás. No componente Imagem, atribuímos aos botões uma imagem que corresponderá à imagem, o que significa a capacidade de se mover. Deve ser algo como a captura de tela abaixo:



Para cada botão, adicione um componente AxisTouchButton . Este script possui apenas 4 campos. O campo axisName significa qual nome responder quando chamado. O campo axisValue é responsável pela direção na qual Lucas se moverá. O campo responseSpeed é responsável pela rapidez com que Lucas desenvolverá sua velocidade. O campo returnToCentreSpeed ​​é responsável pela rapidez com que o botão retorna ao centro. Para o botão Avançar, deixe como está. Para o botão Voltar, altere o valor de axisValue para -1, para que Lucas volte. Para os botões Para cima e Para baixo, altere o nome do eixo para Vertical . Para o botão Acima axisValue, defina o valor como 1, para Down -1.


Em seguida, modifique HeroScript.cs . Adicionar espaço para nome à diretiva using


 using UnityStandardAssets.CrossPlatformInput; //    . 

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

No método Start padrão, adicione o seguinte código:


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

Criamos um método que será responsável por mover o herói:


Título 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 você pode ver, tudo é simples. Os campos dirX e DirY registram informações sobre a direção do Eixo ( horizontal e vertical ) multiplicada pela velocidade (que precisará ser especificada no editor) e multiplicada pelo tempo de viagem do último quadro.
transform.position grava a nova posição no componente Transform do nosso objeto de jogo.


No lado prático da questão, você pode executar a cena e ver como Lucas cai no abismo, já que não há objetos embaixo dela que possam impedir isso. Lucas está sempre na animação Idle e não se vira quando o direcionamos de volta. Para isso, o script precisa ser modificado. Crie um método que determine em qual direção Lucas está olhando:


Título 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; 

Essa parte do código também não é difícil. O método descreve que, se dirX > 0 (se formos para a direita), giraremos o sprite nessa direção e iniciaremos a animação de caminhada. Se for menor que 0, gire Lucas 180 graus e inicie a animação. Se o dirX for zero, Lucas estará de pé e você precisará iniciar a animação de espera.


Por que é preferível usar uma operação com Scale neste caso do que flipX = true ? No futuro, descreverei a capacidade de pegar qualquer objeto na mão e, naturalmente, Lucas pode se virar segurando algo em suas mãos. Se eu usasse a reflexão usual, o objeto que eu seguraria em minhas mãos permaneceria no lado direito (por exemplo) quando Lucas olha para a esquerda e vice-versa. Aumentar o zoom moverá o objeto que prende Lucas na mesma direção em que Lucas se virou.


Colocamos a função CheckWhereToFace () na função Update () , para seu monitoramento quadro a quadro.


Ótimo. Os 2 primeiros pontos de 5 são completados. Vamos seguir para as necessidades de Lucas. Digamos que Lucas terá três tipos de necessidades que devem ser atendidas para permanecer vivo. Este é o padrão de vida, o nível de fome e o nível de sede. Você precisa criar para isso um painel simples e compreensível com um indicador de cada item. Para criar esse painel, clique com o botão direito do mouse e selecione UI => Painel .


Vamos marcá-lo aproximadamente como mostrado abaixo


imagem


O painel consiste em três imagens (Imagem) de cada necessidade (esquerda). À direita está o próprio painel. Na primeira camada (colocaremos dessa maneira), há um indicador de cor (Imagem) que não possui transparência, um objeto de Imagem é copiado sob ela, que é transparente. Esta imagem é meio transparente ao original. Além disso, a imagem, que não possui transparência, possui a propriedade Tipo de imagem = preenchida . Esse recurso nos permitirá simular uma diminuição na plenitude da escala de necessidades.


imagem


imagem


Defina novas variáveis ​​estáticas:


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

Nesse caso, eu uso campos estáticos. Isso é feito para que esses campos sejam exclusivos para toda a classe. Além disso, isso nos permitirá acessar diretamente esses campos pelo nome da classe. Escrevemos algumas funções simples:


Título 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; } 

Em seguida, escrevemos um método que coleta informações sobre o desejo de comer e beber:


Título 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()); } 

A função Needs () é colocada na função Update () e cada quadro é chamado. Assim, nas linhas


 if (fEat(_Eat) < 0) 

é chamada uma função que passa como parâmetro o quanto deve ser retirado das variáveis Eat e Water . Se o resultado da função não for 0, Lucas ainda não morreu de sede ou fome. Se Lucas morre de fome ou de ferimentos mortais, então fazemos rotinas


 StartCoroutine(ifDie()); 

que inicia a animação da morte e reinicia o nível:


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

Ladrilho rígido


Um objeto de jogo que não permite que o jogador passe por ele. Exemplo: gênero em Super Mario Bros (Nintendo, 1985).


Para realizar a terra e impedir que Lucas caia nela, você precisa conectar os componentes BoxCollider2D e Rigidbody2D a Lucas. Além disso, você precisa de um sprite da terra no qual o componente BoxCollider2D estará localizado . O componente BoxCollider2D implementa coletores e seu comportamento de colisão. Nesta fase, não precisamos de nada além de impedir o fracasso de Lucas no subsolo. Tudo o que podemos editar opcionalmente são as bordas do colisor. No meu caso, o sprite no solo tem uma superfície de grama e, para que não pareça que a grama é capaz de suportar o peso de Lucas, editarei as bordas do componente.



Agora, um emocionante processo de marcação de nível. Por conveniência, você pode exportar este cubo de terra para uma casa pré-fabricada. Uma pré-fabricada é um contêiner de um objeto de jogo, mediante modificação, na qual você pode aplicar automaticamente alterações a todos os objetos de jogo criados a partir dessa pré-fabricada. Em seguida, clone esta pré-fabricada com CTRL + D (depois de selecioná-la na guia hierarquia) e coloque-a no palco.


imagem


Ecrã


A parte do nível / mundo do jogo que está atualmente visível para o jogador.


Configure uma câmera que siga o player prestes a exibir parte da cena. Em seguida, haverá um script muito simples de implementar:


Título 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; } 

No campo objectToFollow do tipo GameObject , um objeto será designado para ser monitorado e, no campo speed, a velocidade na qual é necessário mover-se suavemente atrás do GameObject atribuído.


As informações sobre a velocidade do movimento desde o último quadro são registradas no campo de interpolação. A seguir, será utilizado o método Lerp, que garantirá o movimento suave da câmera atrás de Lucas quando ela se mover pelos eixos X e U. Infelizmente, não consigo explicar o funcionamento da linha


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

em termos de matemática. Portanto, direi mais simples: esse método aumentará o tempo de execução de qualquer ação. No nosso caso, esse é o movimento da câmera atrás do objeto.


Perigo


Título de spoiler

Entidades que impedem o jogador de completar sua tarefa. Exemplo: picos de 1001 picos (Nicalis e 8bits Fanatics, 2014).


Vamos começar adicionando algo que não apenas impedirá Lukas de passar pelo estágio até o fim, mas afetará o número de suas vidas e a capacidade de morrer (por exemplo, implementaremos o quinto subproblema para implementar a história de vida de Lukas - o Herói pode ser morto ou pode morrer).
Nesse caso, espalhamos os espinhos no palco que estarão escondidos atrás da vegetação e apenas a atenção do jogador ajudará a passar.


Crie um GameObject vazio e conecte os componentes SpriteRenderer e PolygonCollider2D a ele. No componente SpriteRenderer , conectamos o sprite do botão farpado ou qualquer outro objeto, conforme desejado. Além disso, atribua tag = Thorn ao pico.


Em seguida, no GameObject de Lucas , criamos um script que será responsável pelo que acontecerá com ele se Lucas colidir com outros jogadores. No meu caso, chamei de ColliderReaction.cs


Título 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; } } 

A essência do script é tão simples quanto 2x2. Quando uma tag Thorn colide com um objeto de jogo, a instrução Switch se compara aos candidatos que especificamos. No nosso caso, por enquanto, é Thorn . Primeiro, Lucas vomita, e depois nos voltamos para uma variável estática e pegamos 5 unidades de vida de Lucas. Olhando para o futuro, posso dizer que faz sentido descrever a mesma coisa para um conflito com inimigos:


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

Em seguida, proponho matar dois coelhos com uma cajadada.


Item e regra coletados.


Um objeto de jogo que os jogadores podem pegar.


Propomos a regra de que, se Lucas quiser ir entre as ilhas e subir, você precisará coletar uma árvore para construir pontes e escadas.
De acordo com os métodos já passados, criaremos uma árvore e escadas.


Nós conectaremos um script à árvore que será responsável por quantos logs você pode bater se começar a cortá-lo. Como apenas a animação do ataque foi proposta no conjunto de sprites, a usaremos quando cortarmos a árvore (custos de produção).
O script que está na árvore:


Título de spoiler
 [SerializeField] private Transform inst; //     [SerializeField] private GameObject FireWoodPref; //   [SerializeField] private int fireWood; //        

Quando o nível começa, escrevemos um valor aleatório no fireWood :


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

Descreve um método com um parâmetro que será responsável por quantos logs cairão em um único golpe:


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

Um método que criará clones de log no palco.
private void InstantiateFireWood ():


Título de spoiler
  { Instantiate(FireWoodPref, inst.position, inst.rotation); } 


Vamos criar um log e conectar um script com o seguinte código:


Título 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; } } 

Em seguida, também criaremos uma classe que será responsável pelo inventário.


Primeiro, verifique se há espaço na bolsa. Caso contrário, o erro e o log permanecerão, se houver espaço, reabasteceremos o inventário por uma unidade e destruiremos o log.
Em seguida, você precisa fazer algo com esses recursos. Como mencionado acima, oferecemos ao jogador a oportunidade de construir pontes e escadas.


Para criar uma ponte, precisamos de 2 prefabs com a metade esquerda e direita da ponte. BoxCollider2D . , , - , .


:


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

:


Título 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 :


Título 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.


imagem


Trees .


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


, . , - .


:


Título 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, , .


imagem


, .


Título 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; 

, .


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

, , , , :


Título de spoiler
 public int Damage(int x) { Health = Health - x; return Health; } 

, , :


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

0, .


, :


Título de spoiler
 if (bGreenMonster) { fGreenMonster(); } if (bSunFlower) { fSunFlower(); } 

, .


imagem


.


, ?


, .


:



:


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

, :


Título 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; 

, .
, :


Título 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!


, - :


imagem


, — .
, . , , , .


2 , .


!


.


https://opengameart.org/ , :


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


All Articles