ebSkola

4.5 Šaušanas mehānika un object pooling

Stundas uzdevums: Implementēt efektīvu lādiņu (bullet) sistēmu ar object pooling tehniku.

SR 2.4.19. Algoritmu efektivitāte SR 2.4.16. Datu struktūras

80 min plāns: Teorija un paraugs (~10 min) · 1. uzdevums (~20 min) - izšauj pirmo lādiņu · 2. uzdevums (~25 min) - izveido lādiņu krātuvi · 3. uzdevums (~20 min) - salīdzini abas pieejas ar mērījumu · Iesniegšana (~5 min).

Pirms sāc: atver savu projektu. Šodien uztaisīsi šaušanu divos veidos un pats izmērīsi, kurš no tiem ir ātrāks.

Teorija: Object pooling

Object pooling ir tehnika, kas atkārtoti izmanto objektus, nevis tos nepārtraukti veido un dzēš. Kritisks veiktspējai šautenēs, kur var būt 100+ lādiņu vienlaikus.

using Godot;
using System.Collections.Generic;

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();
            parent.AddChild(bullet);
            bullet.Visible = false;
            bullet.SetActive(false);
            pool.Add(bullet);
        }
    }

    public Bullet GetBullet()
    {
        foreach (Bullet bullet in pool)
        {
            if (!bullet.IsActive)
            {
                bullet.SetActive(true);
                bullet.Visible = true;
                return bullet;
            }
        }
        return null;  // pool tukšs
    }

    public void ReturnBullet(Bullet bullet)
    {
        bullet.SetActive(false);
        bullet.Visible = false;
    }
}

Kā šo izmantot projektā

Atceries: ar redzamu efektu editorā nepietiek. Paskaidro, kura C# klase glabā stāvokli, kura metode to maina un kā Godot node struktūra izmanto šo kodu.

Pārbaudi: C# kodā pamatoti izvēlēti List, Dictionary vai HashSet, AI stāvokļi ir skaidri, un sarežģītākas sistēmas var profilēt vai atkļūdot.

public class AiDataCheckpoint
{
    public string Lesson = "4.5 Šaušanas mehānika un object pooling";
    public bool UsesCSharp = true;
    public bool ChoosesContainer = true;
    public bool SeparatesAiState = true;
    public bool HandlesRemovalSafely = true;
}

Praktiskie uzdevumi

1. uzdevums -

Izšauj pirmo lādiņu

Beigās spēlētājs varēs šaut ar peles klikšķi.

  1. Izveido scēnu bullet.tscn ar Area2D sakni un sadursmes formu.
  2. Izveido C# klasi Bullet, kas kustas taisni ar delta.
  3. Pievieno tai laiku, pēc kura lādiņš izdzēšas ar QueueFree();.
  4. Izveido Input Map darbību shoot ar peles kreiso pogu.
  5. Ielādē spēlētāja klasē scēnu ar [Export] public PackedScene BulletScene { get; set; } vai GD.Load<PackedScene>("res://bullet.tscn").
  6. Izveido jaunu lādiņu ar Bullet bullet = BulletScene.Instantiate<Bullet>(); un pievieno to scēnai.
  7. Būvē un izšauj dažus lādiņus.

Gatavs, kad: ar katru peles klikšķi parādās lādiņš, kas lido prom un pēc laika pazūd.

2. uzdevums -

Izveido lādiņu krātuvi

Beigās lādiņi tiks izmantoti atkārtoti, nevis veidoti no jauna.

  1. Izveido klasi BulletPool ar lauku private readonly List<Bullet> pool = new();.
  2. Uzraksti metodi public void Initialize(Node parent).
  3. Izveido tajā ciklā 50 lādiņus ar Bullet bullet = BulletScene.Instantiate<Bullet>(); un paslēp tos.
  4. Uzraksti metodi public Bullet GetBullet(), kas atgriež pirmo neaktīvo.
  5. Atgriez null, ja visi ir aizņemti, un apstrādā šo gadījumu.
  6. Nomaini šaušanas kodu tā, lai tas ņemtu lādiņu no krātuves.
  7. Atgriez lādiņu krātuvē, kad tā laiks beidzas, nevis dzēs to.

Gatavs, kad: šaujot lādiņi parādās kā iepriekš, bet scēnas kokā mezglu skaits vairs neaug.

3. uzdevums -

Salīdzini abas pieejas ar mērījumu

Beigās tev būs skaitļos pierādīts, kāpēc krātuve ir vērtīga.

  1. Izšauj 200 lādiņus ar pirmo pieeju un pieraksti FPS rādījumu.
  2. Ieslēdz FPS rādītāju ar Debug -> Visible Collision Shapes un profilēšanas paneli.
  3. Izšauj 200 lādiņus ar krātuves pieeju un pieraksti FPS.
  4. Saskaiti, cik mezglu abos gadījumos ir scēnas kokā.
  5. Pieraksti, kura pieeja radīja vairāk mezglu.
  6. Pieraksti, kāpēc objekta izveide un dzēšana maksā vairāk nekā tā paslēpšana.
  7. Izvēlies, kuru pieeju atstāsi savā spēlē, un pamato.

Gatavs, kad: ar krātuvi mezglu skaits paliek nemainīgs, un tu vari nosaukt abu pieeju FPS atšķirību.

Papildu uzdevums - Pievieno šaušanas ātruma ierobežojumu

Ja pamatdarbs ir gatavs, neļauj šaut bezgalīgi ātri.

  1. Pievieno lauku double cooldown = 0.0;.
  2. Samazini to katrā kadrā ar delta.
  3. Atļauj šaut tikai tad, kad tas ir mazāks vai vienāds ar nulli.
  4. Iestati to uz 0.2 pēc katra šāviena.
  5. Pārbaudi, ka turot peles pogu, lādiņi nāk vienmērīgi.

Gatavs, kad: turot peles pogu, lādiņi izšaujas ar vienmērīgu atstarpi, nevis visi uzreiz.

Ko sagatavo

Stundas mērķis: Tavā spēlē ir strādājoša šaušana, kas izmanto lādiņu krātuvi (object pooling), un tu ar mērījumu vari pierādīt, ar ko tā ir labāka par lādiņu veidošanu un dzēšanu katru reizi.

GitHub krātuvē jābūt:

  1. Faili Bullet.cs un BulletPool.cs.
  2. Ekrānuzņēmums ar scēnas koku pēc 200 izšautiem lādiņiem, izmantojot krātuvi.
  3. README.md: abu pieeju FPS un mezglu skaits un viens teikums, kāpēc krātuve ir ātrāka.

Kā iesniedz: Veic commit un push. Skolotāja norādītajā vietā ievieto tikai GitHub krātuves saiti; visiem prasītajiem failiem, README.md pierakstiem un pierādījumiem jābūt pašā krātuvē.

Biežākās kļūdas

  • Pool tukšs: POOL_SIZE pārāk mazs vai bullets nepareizi atgriežas.
  • Lag at start: Pool initialization pārāk lēns - sadali laikā vai async.
  • Bullets pazūd: Lifetime un visibility nepareizi sinhronizēti.

Godot ekrānuzņēmumi

Spēles aina: spēlētājs centrā, 15+ lādiņu trajektorijas dažādos virzienos, vairāki ienaidnieki ekrāna malās.
Spēles aina: spēlētājs centrā, 15+ lādiņu trajektorijas dažādos virzienos, vairāki ienaidnieki ekrāna malās.

Koda piemērs (paplašināts)

using Godot;

public partial class Bullet : Area2D
{
    private Vector2 velocity;
    private float lifetime = 0.0f;
    private int damage = 10;

    public bool IsActive { get; private set; } = false;

    public override void _PhysicsProcess(double delta)
    {
        if (!IsActive) return;
        Position += velocity * (float)delta;
        lifetime -= (float)delta;
        if (lifetime <= 0) Deactivate();
    }

    public void Fire(Vector2 pos, Vector2 vel, float life = 2.0f)
    {
        Position = pos;
        velocity = vel;
        lifetime = life;
        SetActive(true);
        Visible = true;
    }

    public void Deactivate()
    {
        SetActive(false);
        Visible = false;
    }

    public void SetActive(bool active)
    {
        IsActive = active;
        SetPhysicsProcess(active);
    }
}

public partial class Player : CharacterBody2D
{
    private BulletPool pool;
    private Vector2 mousePos;

    private void Shoot()
    {
        Bullet bullet = pool.GetBullet();
        if (bullet != null)
        {
            Vector2 dir = (mousePos - Position).Normalized();
            bullet.Fire(Position, dir * 800.0f);
        }
    }
}
Spēlētājs šauj 5 lādiņus/sek, ekrānā līdz 30 lādiņiem vienlaikus, FPS stabils 60.