Bang! Bang! They shoot in a space! Lidinators v0.6
Sveiki! Tuvojas ziemas saulgrieži, un Ziemassvētku noskaņā nolēmu iemācīt Lidinatora raķetēm “pikoties”. Liekot lietā visu savu zīmēšanas talantu, izveidoju grafikas gariņu, kuru izmantot.
Nākošais solis ir izveidot klasi, kas atspoguļos lodes īpašības un uzvedību. Absolūtais minimums ir no SpriteComponent (kas ir SpriteComponent, var izlasīt 5ajā sērijā) mantota klase, kas konstruktorā norāda attēlošanai izmantoto klasi. Sperot vēl vienu soli uz priekšu, izveidoju konstruktoru, kuram kā papildus parametri ir kuģa koordinātes un leņķis. Šie mainīgie tiks izmantoti, lai noteiktu lodes sākumpozīciju un virzienu. No kuģa klases Update() metodes nokopēju koordināšu izmaiņu daļu (jā, zinu, ka tā darīt ir slikti, bet pamazām jau sāku saprast, kā to nāksies refaktorēt). Visu šo darbību rezultātā ieguvu Bullet klasi:
class Bullet : SpriteComponent
{
private float speed;
public Bullet(Game game)
: this(game, new Vector2(), 0)
{
}
public Bullet(Game game, Vector2 location, float angle) : base(game)
{
this.textureAsset = "bullet_sprite";
this.location = location;
this.angle = angle;
this.speed = 20;
}
/// <summary>
/// Allows the game component to perform any initialization it needs to before starting
/// to run. This is where it can query for any required services and load content.
/// </summary>
public override void Initialize()
{
// TODO: Add your initialization code here
base.Initialize();
}
/// <summary>
/// Allows the game component to update itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
public override void Update(GameTime gameTime)
{
// TODO: Add your update code here
location.X += speed * (float)Math.Sin(angle / 180 * Math.PI);
location.Y -= speed * (float)Math.Cos(angle / 180 * Math.PI);
base.Update(gameTime);
}
Ship klase ir jāpapildina ar jauniem mainīgajiem – šaušanas taustiņu un izšauto šāviņu kolekciju, kā arī Update() metodē jāpievieno izšaušanas loģika.
if (currentState.IsKeyDown(FireKey))
{
var bullet = new Bullet(this.Game, this.location, this.angle);
this.bullets.Add(bullet);
this.Game.Components.Add(bullet);
}
Sākumam ar to pietiek un šobrīd jau var papriecāties par šaujošiem kuģiem.
Tomēr šai relizācijai ir viens būtisks trūkums, t.i., tā neaizvāc lodes, kuras ir izlidojušas ārpus ekrāna robežām. Lai to izdarītu, nāksies nedaudz piestrādāt gan Bullet, gan Ship klasēs. Kuģa klasei izveidoju papildus metodi, kas veic lodes izvākšanu no kuģa ložu kolekcijas un spēles komponentu kolekcijas.
internal void RemoveBullet(Bullet bullet)
{
this.bullets.Remove(bullet);
Game.Components.Remove(bullet);
}
Ložu klasei papildināju konstruktoru, lai tam tiktu nodots kuģis, kurš izšāvā lodi, kā arī Update() metodi ar lodes iznīcināšanas loģiku.
if (Math.Abs(location.X) > Game.Window.ClientBounds.Width / 2
|| Math.Abs(location.Y) > Game.Window.ClientBounds.Height / 2)
{
// remove bullet if out of screen bounds
parent.RemoveBullet(this);
this.Dispose();
return;
}
Lai būtu interesantāk, kuģa izšaušanas mehānismu var papildināt ar maksimālā ložu skaita ierobežojumu.
if (currentState.IsKeyDown(FireKey) && this.bullets.Count < 11)
{
var bullet = new Bullet(this.Game, this, this.location, this.angle);
this.bullets.Add(bullet);
this.Game.Components.Add(bullet);
}
Tagad atkal var paspēlēties un paskatīties, kas no visa ir sanācis. Šodienai vēl ir atlicis tikai izveidot tādu “sīkumu”, kā iespēju iznīcināt otru kuģi. Lai to realizētu pēc iespējas universālāk, papildināsim SpriteComponent klasi, pievienojot dažus mainīgos:
Destroyable – norāda, vai objekts ir iznīcināms
Damage – norāda, cik lielus bojājumus objekts nodara otram objektam pie sadursmes
Health – norāda, cik daudz “dzīvības” objektam ir atlicis
Savukārt objektu konstruktoros inicializēsim šos mainīgos ar atbilstošajām vērtībām.
Lai veiktu sadursmju noteikšanu Game klases Update() metode ir jāpapildina ar dubultu ciklu, kurā pārbauda, vai Components kolekcijā esošie objekti saduras un sadursmes gadījumā atbilstoši izmaina to stāvokli. Jāpiebilst, ka objektu kopu atlasīšanai lieliski noderēja LINQ-to-objects.
var gameObjects = from component in this.Components
where component is SpriteComponent
select component;
foreach (SpriteComponent gameObject in gameObjects)
{
var otherObjects = from component in this.Components
where component is SpriteComponent
&& component != gameObject
select component;
foreach (SpriteComponent otherObject in otherObjects)
{
BoundingSphere sphere = gameObject.GetBoundingSphere();
if (sphere.Intersects(otherObject.GetBoundingSphere())
&& otherObject.Destroyable)
{
otherObject.Health -= gameObject.Damage;
}
}
}
Iznīcinātie objekti ir korekti jāaizvāc, tādēļ SpriteComponent papildināsim ir virtuālo metodi objektu aizvākšanai.
internal virtual void Destroy()
{
this.Game.Components.Remove(this);
this.Dispose();
}
Bullet klasei šo operāciju ir jāpārraksta, lai lodi izvāktu no kuģa izšauto ložu kolekcijas.
internal override void Destroy()
{
this.parent.RemoveBullet(this);
base.Destroy();
}
Un vēlreiz jāpapildina Game.Update() metode, lai veiktu objektu iznīcināšanu:
var destroyedObjects = new List<SpriteComponent>();
foreach (SpriteComponent gameObject in gameObjects)
{
if (gameObject.Health <= 0)
{
destroyedObjects.Add(gameObject);
}
}
foreach (SpriteComponent item in destroyedObjects)
{
item.Destroy();
}
destroyedObjects = null;
Ja tagad mēģinās spēlēt Lidinatoru, tad varēs novērot dīvainību – izšautās lodes uzreiz pazūd, efektīvi pašiznīcinot kuģi. Iemesls tam ir izšauto ložu inicializācija, kur tās pārklājas ar kuģi. Lai no šīs problēmas izvairītos ir jāizmaina Bullet konstruktors.
public Bullet(Game game, Ship parent, Vector2 location, float angle) : base(game)
{
this.textureAsset = "bullet_sprite";
this.parent = parent;
this.location = location;
this.angle = angle;
this.speed = 20;
this.Destroyable = false;
this.Health = 1;
this.Damage = 10;
float shipRadius = parent.GetBoundingSphere().Radius;
this.location.X -= shipRadius * 1.25f;
this.location.Y -= shipRadius * 1.25f;
this.location.X += (shipRadius * 2) * (float)Math.Sin(this.AngleInRads);
this.location.Y -= (shipRadius * 2) * (float)Math.Cos(this.AngleInRads);
}
Jāatzīst, ka šis koda fragments nav labas programmēšanas paraugs, bet tas darbojas :)