Datu struktūras //
un AI
C# List un Dictionary, ienaidnieku AI ar NavigationAgent2D un A* algoritmu. Rezultāts - Top-down šautene ar gudru AI.
6 stundas - C# kolekcijas, AI un top-down šautene
List un Array
List<T> dinamiskam bultām sarakstam, Add/RemoveAt, iterācija ar foreach.
Dictionary un Godot Dictionary
Dictionary<TKey, TValue> un Godot.Collections.Dictionary - atslēga→vērtība, spēles stāvokļa glabāšana.
State machines AI uzvedībai
PATROL → CHASE → ATTACK stāvokļu mašīna, enum stāvokļi, pārejas nosacījumi.
Pathfinding ar A*
Godot NavigationAgent2D, A* algoritms, GetNextPathPosition(), navigācijas karte.
Šaušanas mehānika un object pooling
Bullet object pool - pārlieto objektus nevis veido jaunus, ieroču sistēma ar List<Bullet>.
Noslēguma projekts: Top-down šautene
Ienaidnieki ar A* pathfinding, ieroču sistēma, viļņu pārvaldnieks, highscore ar Dictionary.
4. tēmas špikeris - datu struktūras un AI
Saraksti, vārdnīcas, stāvokļu mašīna, pathfinding un object pooling. Katrs bloks atbilst vienai stundai.
4.1 Saraksti: List pret Godot Array
using Godot;
using Godot.Collections;
using System.Collections.Generic;
// Python: enemies = [] / enemies.append(e) / len(enemies)
List<Enemy> enemies = new();
enemies.Add(enemy); // pievieno beigās
enemies.Count; // garums
enemies.RemoveAt(1); // izņem otro
enemies[10]; // met kļūdu, ja nav
foreach (Enemy enemy in enemies) { enemy.UpdateEnemy(); } // Python: for e in enemies
// NEDZĒS cikla laikā! Vispirms savāc, tad dzēs:
List<Enemy> toRemove = new();
foreach (Enemy enemy in enemies) if (!enemy.Alive) toRemove.Add(enemy);
foreach (Enemy enemy in toRemove) enemies.Remove(enemy);
// Godot Array - dažādi tipi vienā sarakstā, vajadzīgs saziņai ar editoru
Array bullets = new(); bullets.Add(42); bullets.Add("teksts");
List<T> - iekšējam C# kodam (ātrs, tipa-drošs). Array - kad dati jānodod Godot.
4.2 Vārdnīcas: Dictionary pret Godot Dictionary
using Godot;
using Godot.Collections;
using System.Collections.Generic;
// Python: inventars = {} / inventars["zobens"] = 5
Dictionary<string, int> inventory = new();
inventory["Health Potion"] = 5;
// DROŠI:
int GetAmount(string name)
{
if (inventory.TryGetValue(name, out int amount)) return amount;
return 0;
}
foreach ((string name, int count) in inventory) // Python: for k, v in d.items()
GD.Print(name, " x", count);
// Dictionary<TKey,TValue> statiski tipots, ātra meklēšana
// Godot Dictionary Godot tips: dažādi vērtību tipi, der saglabāšanai
Godot.Collections.Dictionary save = new();
save["level"] = 5;
save["name"] = "Anna";
Ja atslēga var nebūt vārdnīcā, lieto TryGetValue, nevis tiešu kvadrātiekavu lasīšanu.
4.3 Stāvokļu mašīna: viens stāvoklis vienlaikus
public enum EnemyState { Idle, Patrol, Chase, Attack, Dead }
public partial class Enemy : CharacterBody2D
{
private EnemyState state = EnemyState.Idle;
private double stateTime = 0.0;
private Player player;
private void ChangeState(EnemyState nextState)
{
state = nextState;
stateTime = 0.0;
}
public override void _PhysicsProcess(double delta)
{
stateTime += delta;
double distance = Position.DistanceTo(player.Position);
// Slieksnis ATPAKAĻ ir LIELĀKS nekā uz priekšu -> nemirgo uz robežas
if (state != EnemyState.Dead)
{
if (distance < 50) ChangeState(EnemyState.Attack);
else if (distance < 300) ChangeState(EnemyState.Chase);
else if (distance > 400) ChangeState(EnemyState.Patrol);
}
switch (state)
{
case EnemyState.Patrol: Patrol(); break;
case EnemyState.Chase: Chase(); break;
case EnemyState.Attack: Attack(); break;
case EnemyState.Dead: break; // gala stāvoklis
}
}
}
DEAD ir gala stāvoklis - no tā nav nevienas izejošas pārejas.
4.4 Pathfinding: apiet sienu, nevis spiesties tajā
// Scēnā: NavigationRegion2D -> NavigationPolygon -> Bake
// Ienaidniekam bērns: NavigationAgent2D
private void Chase()
{
NavigationAgent2D agent = GetNode<NavigationAgent2D>("NavigationAgent2D");
agent.TargetPosition = player.Position;
Vector2 next = agent.GetNextPathPosition();
Vector2 dir = (next - Position).Normalized();
Velocity = dir * speed;
MoveAndSlide();
}
// A* algoritms, ko Godot izmanto iekšēji:
// g = cik tālu jau esmu gājis
// h = taisnais attālums līdz mērķim (novērtējums)
// f = g + h -> vienmēr izvēlas soli ar mazāko f
Bez navigācijas zonas ienaidnieks spiežas sienā. Ar to - apiet apkārt.
4.5 Object pooling: neveido un nedzēs, bet lieto atkārtoti
public class BulletPool
{
private readonly List<Bullet> pool = new();
private const int PoolSize = 50;
public void Initialize(Node parent)
{
for (int i = 0; i < PoolSize; i++)
{
Bullet bullet = new Bullet();
bullet.Visible = false;
bullet.SetProcess(false);
parent.AddChild(bullet);
pool.Add(bullet);
}
}
public Bullet GetBullet()
{
foreach (Bullet bullet in pool)
if (!bullet.Visible) return bullet; // atkārtoti lieto neaktīvo
return null; // visi aizņemti
}
}
// Bez pooling: 200 šāvieni = 200 Instantiate + 200 QueueFree -> FPS krīt
// Ar pooling: mezglu skaits scēnā paliek nemainīgs
Ja GetBullet() atgriež null, pārbaudi to - citādi programma avarēs.