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.
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.
- Izveido scēnu
bullet.tscnarArea2Dsakni un sadursmes formu. - Izveido C# klasi
Bullet, kas kustas taisni ardelta. - Pievieno tai laiku, pēc kura lādiņš izdzēšas ar
QueueFree();. - Izveido Input Map darbību
shootar peles kreiso pogu. - Ielādē spēlētāja klasē scēnu ar
[Export] public PackedScene BulletScene { get; set; }vaiGD.Load<PackedScene>("res://bullet.tscn"). - Izveido jaunu lādiņu ar
Bullet bullet = BulletScene.Instantiate<Bullet>();un pievieno to scēnai. - 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.
- Izveido klasi
BulletPoolar laukuprivate readonly List<Bullet> pool = new();. - Uzraksti metodi
public void Initialize(Node parent). - Izveido tajā ciklā 50 lādiņus ar
Bullet bullet = BulletScene.Instantiate<Bullet>();un paslēp tos. - Uzraksti metodi
public Bullet GetBullet(), kas atgriež pirmo neaktīvo. - Atgriez
null, ja visi ir aizņemti, un apstrādā šo gadījumu. - Nomaini šaušanas kodu tā, lai tas ņemtu lādiņu no krātuves.
- 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.
- Izšauj 200 lādiņus ar pirmo pieeju un pieraksti FPS rādījumu.
- Ieslēdz FPS rādītāju ar Debug -> Visible Collision Shapes un profilēšanas paneli.
- Izšauj 200 lādiņus ar krātuves pieeju un pieraksti FPS.
- Saskaiti, cik mezglu abos gadījumos ir scēnas kokā.
- Pieraksti, kura pieeja radīja vairāk mezglu.
- Pieraksti, kāpēc objekta izveide un dzēšana maksā vairāk nekā tā paslēpšana.
- 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.
- Pievieno lauku
double cooldown = 0.0;. - Samazini to katrā kadrā ar
delta. - Atļauj šaut tikai tad, kad tas ir mazāks vai vienāds ar nulli.
- Iestati to uz 0.2 pēc katra šāviena.
- 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:
- Faili
Bullet.csunBulletPool.cs. - Ekrānuzņēmums ar scēnas koku pēc 200 izšautiem lādiņiem, izmantojot krātuvi.
- 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
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);
}
}
}