diff options
Diffstat (limited to 'scripts/actors')
| -rw-r--r-- | scripts/actors/AI/BaseAI.cs | 19 | ||||
| -rw-r--r-- | scripts/actors/AI/HostileEnemyAI.cs | 26 | ||||
| -rw-r--r-- | scripts/actors/Actor.cs | 63 | ||||
| -rw-r--r-- | scripts/actors/ActorDefinition.cs | 9 | ||||
| -rw-r--r-- | scripts/actors/Enemy.cs | 20 | ||||
| -rw-r--r-- | scripts/actors/EnemyDefinition.cs | 3 | ||||
| -rw-r--r-- | scripts/actors/Player.cs | 3 | ||||
| -rw-r--r-- | scripts/actors/actions/Action.cs | 24 | ||||
| -rw-r--r-- | scripts/actors/actions/BumpAction.cs | 10 | ||||
| -rw-r--r-- | scripts/actors/actions/DirectionalAction.cs | 15 | ||||
| -rw-r--r-- | scripts/actors/actions/MeleeAction.cs | 12 | ||||
| -rw-r--r-- | scripts/actors/actions/MovementAction.cs | 9 |
12 files changed, 200 insertions, 13 deletions
diff --git a/scripts/actors/AI/BaseAI.cs b/scripts/actors/AI/BaseAI.cs index f9b5387..23cdcf6 100644 --- a/scripts/actors/AI/BaseAI.cs +++ b/scripts/actors/AI/BaseAI.cs @@ -1,17 +1,36 @@ using Godot; +/// <summary> +/// base para as IAs do jogo. +/// </summary> public abstract partial class BaseAI : Node { + /// <summary> + /// Corpo controlado pela IA. + /// O corpo é a marionete da alma. + /// </summary> protected Actor body; public override void _Ready() { base._Ready(); + // Por padrão, a IA é filha do nó de seu corpo. body = GetParent<Actor>(); } + /// <summary> + /// Computa um único turno para o ator controlado. + /// </summary> public abstract void Perform(); + /// <summary> + /// Utiliza o pathfinder do mapa para obter um caminho + /// da posição atual do ator para um destino qualquer. + /// </summary> + /// <param name="destination">Destino</param> + /// <returns>Vetor com vetores, passo a passo para chegar no destino.</returns> public Godot.Collections.Array<Vector2> GetPathTo(Vector2I destination) { + // Arrays do Godot são muito mais confortáveis de manipular, então + // eu converto o Array do C# em um array do Godot antes de retornar o caminho. Godot.Collections.Array<Vector2> list = []; Vector2[] path = body.Map_Data.Pathfinder.GetPointPath(body.GridPosition, destination); list.AddRange(path); diff --git a/scripts/actors/AI/HostileEnemyAI.cs b/scripts/actors/AI/HostileEnemyAI.cs index 061295f..3989004 100644 --- a/scripts/actors/AI/HostileEnemyAI.cs +++ b/scripts/actors/AI/HostileEnemyAI.cs @@ -1,37 +1,59 @@ using Godot; +/// <summary> +/// Uma IA simples. Sempre tentará atacar o jogador com ataques corpo a corpo. +/// </summary> public partial class HostileEnemyAI : BaseAI { + /// <summary> + /// Caminho até a última posição conhecida do jogador. + /// </summary> private Godot.Collections.Array<Vector2> path = []; public override void Perform() { + // O alvo da IA sempre é o jogador. Player target = body.Map_Data.Player; + // Vetor que parte do inimigo até o jogador. Vector2I offset = target.GridPosition - body.GridPosition; + // Distância entre o inimigo e o jogador. Leva em consideração somente + // um dos eixos. int distance = int.Max(int.Abs(offset.X), int.Abs(offset.Y)); + // A ação executada no turno pode ser de ataque ou de movimento. Action action; + // Só faz sentido atacar o jogador se o inimigo estiver visível. if (body.Map_Data.GetTile(body.GridPosition).IsInView) { + // Se estiver do lado do jogador, ataque. if (distance <= 1) { action = new MeleeAction(body, offset); action.Perform(); + // Executada a ação, acabamos nosso turno aqui. return; } + + // Se o inimigo estiver visível para o jogador, + // consideramos que ele também consiga ver o jogador. + // Logo, atualizamos o caminho para a posição atual do jogador. path = GetPathTo(target.GridPosition); - GD.Print($"Arno Breker: {path}"); + // O primeiro passo é a posição atual do inimigo, podemos remover. path.RemoveAt(0); } + // Se existir um caminho conhecido para o jogador. if (path.Count > 0) { + // Pegamos o próximo passo para o destino. Vector2I destination = (Vector2I) path[0]; - GD.Print(destination); + // Se tiver um outro ator no caminho, paramos o nosso turno aqui. if (body.Map_Data.GetBlockingActorAtPosition(destination) != null) { return; } + // Caso o contrário, criamos uma nova ação de movimentação e a executamos. action = new MovementAction(body, destination - body.GridPosition); action.Perform(); + // Podemos remover o passo do caminho. path.RemoveAt(0); } } diff --git a/scripts/actors/Actor.cs b/scripts/actors/Actor.cs index 199c301..9257bdc 100644 --- a/scripts/actors/Actor.cs +++ b/scripts/actors/Actor.cs @@ -1,39 +1,76 @@ using Godot; +/// <summary> +/// A classe de ator define um personagem no jogo. +/// </summary> [GlobalClass] public abstract partial class Actor : Sprite2D { + /// <summary> + /// A definição do ator possui caracterísitcas padrões que definem + /// o ator em questão. + /// </summary> protected ActorDefinition definition; + /// <summary> + /// É conveniente ter acesso ao mapa dentro do ator. Isto porque suas ações são feitas dentro + /// do mapa, então é necessário ter acesso à algumas informações. + /// </summary> public MapData Map_Data { get; set; } + private Vector2I gridPosition = Vector2I.Zero; + /// <summary> + /// Posição do ator no mapa do jogo. Diferentemente de Position, GridPosition tem como formato + /// os tiles do mapa. + /// </summary> public Vector2I GridPosition { set { gridPosition = value; + // O sistema de coordenadas do Godot é em pixels, mas faz mais sentido para o jogo utilizar coordenadas em tiles. + // Esta propriedade converte um sistema para o outro automaticamente. Position = Grid.GridToWorld(value); } get => gridPosition; } + /// <summary> + /// Se o ator bloqueia movimento (não pode oculpar a mesma célula de outro ator.) + /// </summary> public bool BlocksMovement { get => definition.blocksMovement; } + /// <summary> + /// Nome do ator. + /// </summary> public string ActorName { get => definition.name; } private int hp; + /// <summary> + /// HP máximo do ator. + /// </summary> public int MaxHp { get; private set; } + /// <summary> + /// HP atual do ator. + /// </summary> public int Hp { get => hp; set { + // Esta propriedade impede que o HP seja maior que o máximo. hp = int.Clamp(value, 0, MaxHp); } } private int mp; + /// <summary> + /// Máximo de mana do ator. + /// </summary> public int MaxMp { get; private set; } + /// <summary> + /// Mana atual do ator. + /// </summary> public int Mp { get => mp; set { @@ -41,22 +78,42 @@ public abstract partial class Actor : Sprite2D } } + /// <summary> + /// Estatística de ataque + /// </summary> public int Atk { get; private set; } + /// <summary> + /// Estatística de defesa. + /// </summary> public int Def { get; private set; } + /// <summary> + /// Estatística mental. + /// </summary> public int Men { get; private set; } public override void _Ready() { base._Ready(); + // Quando o ator for carregado completamente, atualizamos sua posição para refletir + // sua posição real. GridPosition = Grid.WorldToGrid(Position); } + /// <summary> + /// Move o ator para uma localização. Veja MovementAction. + /// </summary> + /// <param name="offset">Vetor que parte da posição do ator até o seu destino.</param> public void Walk(Vector2I offset) { + // Cada ator tem um peso no sistema de pathfinding. + // Sempre que ele se mover, removemos seu peso da posição antiga Map_Data.UnregisterBlockingActor(this); GridPosition += offset; + // E colocamos na próxima. Map_Data.RegisterBlockingActor(this); + // Este peso influencia o algoritmo de pathfinding. + // Atores evitam caminhos bloqueados. por outros atores. } public Actor(Vector2I initialPosition, MapData map, ActorDefinition definition) { @@ -67,6 +124,12 @@ public abstract partial class Actor : Sprite2D SetDefinition(definition); } + /// <summary> + /// Aplica uma definição de NPC para o ator. + /// Se o ator for um boneco de barro, este método é como um + /// sopro de vida. + /// </summary> + /// <param name="definition">A definição do ator.</param> public virtual void SetDefinition(ActorDefinition definition) { this.definition = definition; Texture = definition.texture; diff --git a/scripts/actors/ActorDefinition.cs b/scripts/actors/ActorDefinition.cs index 5c0ece9..72eb745 100644 --- a/scripts/actors/ActorDefinition.cs +++ b/scripts/actors/ActorDefinition.cs @@ -1,19 +1,26 @@ using Godot; -using System; + +/// <summary> +/// Define de forma genérica as características de um ator. +/// </summary> [GlobalClass] public partial class ActorDefinition : Resource { [ExportCategory("Visuals")] + // Nome do ator. [Export] public string name = "unnamed"; + // Seu sprite. [Export] public Texture2D texture; [ExportCategory("Mechanics")] + // Se o ator bloqueia movimento. [Export] public bool blocksMovement = true; + // Estatísticas padrão do ator. [ExportCategory("Stats")] [Export] public int Hp; diff --git a/scripts/actors/Enemy.cs b/scripts/actors/Enemy.cs index 6c111b1..03fe309 100644 --- a/scripts/actors/Enemy.cs +++ b/scripts/actors/Enemy.cs @@ -1,16 +1,29 @@ using Godot; using System; +/// <summary> +/// Enum das diferentes IAs disponíveis. +/// </summary> public enum AIType { None, DefaultHostile }; +/// <summary> +/// Um inimigo é uma espécie de ator que é +/// hostil ao jogador. Inimigos são controlados por IA. +/// </summary> public partial class Enemy : Actor { + /// <summary> + /// A alma do ator. Gera ações que são executadas todo turno. + /// </summary> public BaseAI Soul { get; private set; } + /// <summary> + /// Enquanto a alma controlar o corpo, o ser continua vivo. + /// </summary> public bool IsAlive { get => Soul != null; } public Enemy(Vector2I initialPosition, MapData map, EnemyDefinition definition) : base(initialPosition, map, definition) @@ -18,10 +31,17 @@ public partial class Enemy : Actor SetDefinition(definition); } + /// <summary> + /// Além de definir as características gerais de um ator, + /// também define qual IA utilizar. + /// </summary> + /// <param name="definition">Definição do inimigo.</param> public void SetDefinition(EnemyDefinition definition) { + // Definimos as características do ator. base.SetDefinition(definition); + // Definimos qual IA utilizar. switch(definition.AI) { case AIType.None: break; diff --git a/scripts/actors/EnemyDefinition.cs b/scripts/actors/EnemyDefinition.cs index 21d4ca0..e372e3a 100644 --- a/scripts/actors/EnemyDefinition.cs +++ b/scripts/actors/EnemyDefinition.cs @@ -1,5 +1,8 @@ using Godot; +/// <summary> +/// Além das configurações do ator, também possui qual IA utilizar. +/// </summary> [GlobalClass] public partial class EnemyDefinition : ActorDefinition { [ExportCategory("AI")] diff --git a/scripts/actors/Player.cs b/scripts/actors/Player.cs index c15db21..324e67a 100644 --- a/scripts/actors/Player.cs +++ b/scripts/actors/Player.cs @@ -1,6 +1,9 @@ using Godot; using System; +/// <summary> +/// Classe do jogador. Por enquanto não é diferente do Ator, mas isso pode mudar. +/// </summary> [GlobalClass] public partial class Player : Actor { diff --git a/scripts/actors/actions/Action.cs b/scripts/actors/actions/Action.cs index bb5f6f1..0320dd8 100644 --- a/scripts/actors/actions/Action.cs +++ b/scripts/actors/actions/Action.cs @@ -1,16 +1,36 @@ using Godot; -using System; -using System.Data; + +/// <summary> +/// <c>Action</c> representa uma ação no jogo efetuada por um ator. +/// Ações são geradas pelo jogador e pela IA, elas regem os atores do jogo. +/// </summary> public abstract partial class Action : RefCounted { + /// <summary> + /// O ator que realiza a ação. + /// </summary> protected Actor actor; public Action(Actor actor) { this.actor = actor; } + /// <summary> + /// Método que executa a ação. Subclasses da ação devem implementar este método. + /// <example> + /// Exemplo: + /// <code> + /// Action action = new Action(actor); + /// /* . . . */ + /// action.Perform(); + /// </code> + /// </example> + /// </summary> public abstract void Perform(); + /// <summary> + /// É conveniente ter acesso ao mapa dentro de uma ação. + /// </summary> protected MapData Map_Data { get => actor.Map_Data; } diff --git a/scripts/actors/actions/BumpAction.cs b/scripts/actors/actions/BumpAction.cs index 5958b11..def721b 100644 --- a/scripts/actors/actions/BumpAction.cs +++ b/scripts/actors/actions/BumpAction.cs @@ -1,6 +1,10 @@ using Godot; -using System; +/// <summary> +/// Ação de "Esbarramento", utilizada principalmente pelo jogador. +/// Esta ação direcionada tentará andar para o destino, se houver um +/// ator no caminho, uma ação de ataque é gerada no lugar. +/// </summary> public partial class BumpAction : DirectionalAction { public BumpAction(Actor actor, Vector2I offset) : base(actor, offset) @@ -11,14 +15,18 @@ public partial class BumpAction : DirectionalAction { Vector2I destination = actor.GridPosition + Offset; + // Declaramos uma ação genérica. Action action; + // Se houver um ator no destino, crie uma ação de ataque. if (GetBlockingActorAtPosition(destination) != null) { action = new MeleeAction(actor, Offset); } else { + // Mas se não houver, crie uma ação de movimento. action = new MovementAction(actor, Offset); } + // Executa a ação. action.Perform(); } } diff --git a/scripts/actors/actions/DirectionalAction.cs b/scripts/actors/actions/DirectionalAction.cs index d5199f9..8c3c68f 100644 --- a/scripts/actors/actions/DirectionalAction.cs +++ b/scripts/actors/actions/DirectionalAction.cs @@ -1,14 +1,27 @@ using Godot; -using System; +/// <summary> +/// Ação direcionada. Esta ação é acompanhada com um vetor que representa uma +/// distância tendo como ponto de partida o ator. +/// </summary> public abstract partial class DirectionalAction : Action { + /// <summary> + /// Direção/distância do ator da ação. + /// Seu significado depende da ação que implementará esta classe. + /// </summary> public Vector2I Offset { get; private set; } public DirectionalAction(Actor actor, Vector2I offset) : base(actor) { Offset = offset; } + /// <summary> + /// É conveniente ter acesso à função para obter atores em uma determinada posição. + /// Este método expõe o método de mesmo nome do mapa. + /// </summary> + /// <param name="pos">Posição para verificar</param> + /// <returns>O ator naquela posição, nulo se não houver.</returns> protected Actor GetBlockingActorAtPosition(Vector2I pos) { return Map_Data.GetBlockingActorAtPosition(pos); } diff --git a/scripts/actors/actions/MeleeAction.cs b/scripts/actors/actions/MeleeAction.cs index c6d0960..cf40f1d 100644 --- a/scripts/actors/actions/MeleeAction.cs +++ b/scripts/actors/actions/MeleeAction.cs @@ -1,21 +1,27 @@ using Godot; -using System; -using System.Net.NetworkInformation; +/// <summary> +/// Ação de ataque físico. Uma ação direcionada que ataca um alvo. +/// </summary> public partial class MeleeAction : DirectionalAction { public MeleeAction(Actor actor, Vector2I offset) : base(actor, offset) { } + /// <summary> + /// Ataca o ator na direção da ação. + /// </summary> public override void Perform() { Vector2I destination = actor.GridPosition + Offset; - + // Eu te disse que este método seria útil. Actor target = GetBlockingActorAtPosition(destination); + // Se não houver um ator na direção, não podemos continuar. if (target == null) return; + // TODO: Implementar ataque. GD.Print($"Você tenta socar {target.ActorName}, mas como não sobra nada para o beta, você ainda não tem um método de ataque."); } } diff --git a/scripts/actors/actions/MovementAction.cs b/scripts/actors/actions/MovementAction.cs index 704dd4f..f86d542 100644 --- a/scripts/actors/actions/MovementAction.cs +++ b/scripts/actors/actions/MovementAction.cs @@ -1,6 +1,8 @@ using Godot; -using System; +/// <summary> +/// Ação de movimento. Movimenta o ator para a direção de seu Offset. +/// </summary> public partial class MovementAction : DirectionalAction { public MovementAction(Actor actor, Vector2I offset) : base(actor, offset) @@ -11,12 +13,13 @@ public partial class MovementAction : DirectionalAction { Vector2I finalDestination = actor.GridPosition + Offset; + // Não anda se o destino for um tile sólido. if (!Map_Data.IsTileWalkable(finalDestination)) return; + // Não anda se o destino for oculpado por um ator. + // Na maioria dos casos, essa condição nunca é verdadeira. if (GetBlockingActorAtPosition(finalDestination) != null) return; - GD.Print("What?"); - actor.Walk(Offset); } } |
