O Herói
Parte da seção Arquitetura de jogos no Unity do Journey de Unity da Coddy. Lição 41 de 50.
Este capítulo cria Arena Survivor, o último jogo da jornada. Ondas de inimigos invadem uma arena murada e você luta contra eles com uma espada: soldados rasos que avançam em sua direção e orbes que aguardam e depois avançam rapidamente. Uma onda derrotada deixa um aprimoramento, uma HUD mostra sua vida e a onda atual e, quando sua vida acaba, a partida termina e sua melhor partida é salva. O jogo usa as ferramentas desta seção quando elas se encaixam: um Enemy abstrato com dois tipos, IDamageable para tudo que pode sofrer dano, as ondas como um recurso ScriptableObject, itens coletáveis por trás de uma interface, uma HUD que apenas escuta eventos C# e um salvamento em JSON. Todos os doze scripts estão no projeto desde o início. Apenas Hero já tem código, e cada lição acrescenta mais.
A espada do herói atinge o que está à sua frente, então o golpe precisa verificar se algo está "à frente". Vector3.Dot(a, b) multiplica as partes correspondentes de dois vetores e as soma: a.x * b.x + a.y * b.y + a.z * b.z. Para duas direções de comprimento 1, esse é o cosseno do ângulo entre elas: 1 quando apontam na mesma direção, 0.866 a 30 graus, 0.707 a 45, 0 em ângulos retos e -1 quando apontam em direções opostas. Assim, "dentro de 30 graus de onde estou virado" se torna "um produto escalar de pelo menos 0.866", sem precisar calcular nenhum ângulo. Este robô sentinela dá o alarme quando o jogador está a até 10 unidades e dentro do seu campo de visão de 30 graus:
public Transform player;
public float range = 10f;
void Update()
{
Vector3 toPlayer = player.position - transform.position;
toPlayer.y = 0f;
bool near = toPlayer.magnitude <= range;
bool inView = Vector3.Dot(transform.forward, toPlayer.normalized) >= 0.866f;
if (near && inView)
{
Debug.Log("Alarm!");
}
}As duas direções precisam ter comprimento 1. transform.forward sempre tem esse comprimento, mas toPlayer tem o comprimento da distância, então é normalizado primeiro; caso contrário, um jogador duas vezes mais distante produziria um produto escalar duas vezes maior. O valor y é definido como 0 antes disso, então um jogador em uma passarela mais alta conta pela posição que ocupa no mapa, não pela altura. O sinal distingue a frente e os fundos: um produto escalar acima de 0 indica qualquer ponto na metade dianteira, e abaixo de 0, qualquer ponto atrás.
Desafio
MédioInicie Arena Survivor com a espada do herói. O movimento é escrito assim: a ação Move faz o herói andar com SimpleMove e o vira para ficar de frente para a direção em que anda. Cinco bonecos de treino com a tag Enemy ficam ao redor dele. No Inspector, defina reach (2) e swingCooldown (0.5) para Hero, além do golpe que aparece durante um balanço da espada.
- A barra de espaço ou um clique com o botão esquerdo do mouse executa um golpe, no máximo uma vez a cada
swingCooldownsegundos: um pressionamento antes do fim do tempo de recarga não faz nada. Leia as teclas comKeyboard.currenteMouse.currente chameSwing(). - Em
Swing(), depois que o golpe aparecer, destrua todos os objetos com a tagEnemyque estejam dentro dereache à frente do herói. Estar ao alcance significa que qualquer colisor encontrado porPhysics.OverlapSphereao redor do herói, com raio dereach, está ao alcance. Estar à frente significa que o produto escalar entre a direção frontal do herói e a direção até o objeto no plano do chão seja de pelo menos 0.5: até 60 graus para qualquer lado.
Deixe os outros scripts como estão. Pressione Play, caminhe até os bonecos e desfira um golpe; depois, Check.
Experimente você mesmo
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
public class Hero : MonoBehaviour
{
[SerializeField] private float speed = 5f;
[SerializeField] private float reach = 2f;
[SerializeField] private float swingCooldown = 0.5f;
[SerializeField] private GameObject slash;
private CharacterController controller;
private InputAction moveAction;
void Start()
{
controller = GetComponent<CharacterController>();
moveAction = InputSystem.actions.FindAction("Move");
}
void Update()
{
Vector2 input = moveAction.ReadValue<Vector2>();
Vector3 move = new Vector3(input.x, 0f, input.y);
controller.SimpleMove(move * speed);
if (move != Vector3.zero)
{
transform.rotation = Quaternion.LookRotation(move);
}
// Pressionar Space ou clicar com o botão esquerdo executa um golpe, no máximo uma vez a cada swingCooldown segundos
}
void Swing()
{
StartCoroutine(ShowSlash());
// destrua tudo com a tag Enemy que estiver ao alcance e à frente do herói
}
IEnumerator ShowSlash()
{
slash.SetActive(true);
yield return new WaitForSeconds(0.1f);
slash.SetActive(false);
}
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Arquitetura de jogos no Unity
1Componentes, não hierarquias
Componentes pequenosUm script-base para inimigosComponentes abstratosComponentes trabalhando juntosRevisão: Torre modular4Dados Serializados
Classes SerializáveisListas no InspectorEnums no InspectorRevisão: Tabela de Loot7Máquinas de Estados
Estados como ClassesUma Máquina de EstadosTransiçõesInteligências dos InimigosRevisão: Estados da Porta10Projeto: Sobrevivente da Arena
O HeróiTipos de Inimigos5ScriptableObjects
Assets de dadosAtributos das armasConfigurações compartilhadasOndas como dadosRevisão: banco de dados de itens