December 2008 - Posts

Veco gadu aizvadot
Nākas atzīt, ka iepriekšējā ierakstā esmu samelojies un tas nebija pēdejais šā gada ieraksts. Sakarā ar vispārējo jaungada apņemšanos psihozi esmu nolēmis, ka arī man ir kas jāapsola saviem lasītājiem, vienlaikus nedaudz iepazīstinot ar nākošā gada plāniem.
  • Galvenais, ko es esmu apņēmies, ir tas, ka nākošgad jums nodošu vismaz 100 ierakstus, t.i., ceru, ka varēšu uzrakstīt vismaz divus ierakstus nedēļā. Vai iznāks, neesmu pārliecināts, bet centīšos.
  • Šobrīd karstākā no manām tēmām ir draugiem.lv pastnieks. Nākošā gada sākumā taisos nodot jūsu rīcībā nedaudz uzlabotu versiju, kurā būs ņemti vērā pēdējie jūsu ieteikumi. Tāpat grasos nodot atklātībā arī pastnieka pirmkodu, bet tas varētu būt kaut kad februārī, kad būšu pietiekami sakopis XAML kodu.
  • Arī lidinatora seriāls netiks aizmirsts, jo katram gribās kādu atslodzi no ikdienas un pelēko biznesa aplikāciju rakstīšanas. Galu galā mēs visi kaut nedaudz esam bērni un spēlēties mums patīk.
  • Noteikti, nākošgad mēģināšu apgūt ko jaunu. Un tikpat noteikti dalīšos ar jums, par jaunuzzinātajām lietām.
  • Varbūt beidzot saņemšos un nopietni pieiešu TDD (bet bez reliģiska fanātisma). Tad jūs sagaida raksti par testiem un testējamu kodu :)
  • Vēl vēlos pastāstīt šādas tādas lietas par atvērtā koda produktu izmantošanu .NET bāzētos risinājumos. Reizēm ir vērts zināt par alternatīvām un prast tās izmantot.
Šim gadam solījumi pietiks, tiksimies jaunajā gadā.


Laimīgo Jauno gadu!
Draugiem.lv pastnieks 1.5

Visticamākais, šis ar mans pēdējais raksts, nu jau strauji aizejošajā 2008. gadā. Lai gan šajā laikā lielākā daļa “interneta tautas” savu uzmanību velta Īstās Dzīves™ plašumiem, esmu nolēmis neturēt sveci zem pūra un piedāvāt jums jaunu versiju draugiem.lv pastniekam.

Tā kā gandrīz visas sūdzības, ko saņēmu iepriekšējā raksta komentāros bija dēļ neesošas nepieciešamās .NET platformas versijas , tad lietotāju ērtībai ir izveidots instalācijas fails, kurš pārbauda vai ir datorā ir nepieciešamā .NET versija. Vajadzības gadījumā instalācija to lejupielādē un instalē.

Lielākais uzlabojums ir klāt nākušās sadaļas, kas informē par lietotāja un viņu draugu aktivitātēm

draugiem_pastnieks_7

Tiem, kas šo programmu lieto (vai lietos) bieži, vajadzētu patikt “autopieteikšanās” iespējai, t.i., programma spēj atcerēties iepriekšējo lietotāju. Lietotāja dati ir pietiekami droši saglabāti, lai par to drošumu nevajadzētu uztraukties, vismaz tik ilgi, kamēr kāds cits nevar lietot datoru ar Jūsu Windows lietotāja kontu.

draugiem_pastnieks_6

Vēl ir daudzi sīki uzlabojumi (kurus jau esmu paspējis piemirst), un, droši vien, arī jaunas kļūdas :)

Lai gan vēl neesmu gatavs dalīties ar pastnieka pirmkodu (galvenokārt dēļ tā drausmīgās kvalitātes), izstrādātājiem varētu noderēt manis sarakstītais draugiem.lv API “iesaiņojums”, kuru izmanto arī šī programma.

Un beidzot pats galvenais - instalācija

Draugiem.lv API .net wrapper jauna versija

Esmu sagatavojis noslīpētāku versiju draugiem.lv .NET API “iesaiņojumam”. Sīkāka informācija un lejupielādes ir atrodamas wrappera info lapā.

Ceru, ka noderēs!

Draugiem.lv API .NET iesaiņojums (wrapper)
Šajā lapā ir informācija par draugiem.lv API “iesaiņojumu” (wrapper) .NET valodām. Lai gan bibliotēka ir sarakstīta C#, to ir iespējams izmantot arī ar citām .NET platformas valodām. Draugiem.lv .NET API versija 1.1 Izmaiņas: Visa bibliotēka...
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.

bullet_sprite 

 

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.

lidinators_b

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 :)

Neiztrūkstošais pirmkods

Jauna SmallBasic versija

Esmu jau jums stāstījis par Microsoft DevLabs veidoto SmallBasic. Vakar šai sākuma līmeņa universālajai valodai ir iznākusi jauna versija. Pilno izmaiņu sarakstu varat apskatīt SmallBasic blogā.

Joprojām nākas vilties, ka darbības ar masīviem nav izveidotas “normāli”, t.i., pie masīva elementiem var tikt tikai izmantojot Array klases metodes.

Atliek tikai gaidīt jaunu versiju grāmatai par Pēci un Maiju :)

Datu aizsargāšana izmantojot ProtectedData

Šis ir viens no mini ierakstiem, kuros es pastāstīšu par to, ko esmu uzzinājis, izstrādājot draugiem.lv pastnieku. Neslēpšu, ka šobrīd strādāju pie pastnieka nākošā laidiena, kurā cenšos nogludināt pamanītās nepilnības, kā arī nedaudz papildināt.

Viena no prasībām bija automātiskās pieteikšanās iespējām. Kā izrādās, tas nemaz nav tik sarežģīti, nepieciešams tikai iegaumēt lietotāja e-pastu un saņemto API atslēgu. Arī pati vērtību saglabāšana ir vienkārša pateicoties .NET iebūvētajam programmu iestatījumu mehānismam, kas ļauj saglabāt iestatījumus windows lietotāja profilā. Tomēr negribējās šos datus saglabāt parastā tekstā. Sākotnēji likās, ka tos varētu šifrēt ar kādu no zināmajiem šifrēšanas algoritmiem, bet arī tur ir savas problēmas, piemēram, kur glabāt šifram izmantotās atslēgas.

Tad man ienāca prātā, ka sistēmā vajadzētu būt iebūvētam šifrēšanas mehānismam, jo lietotāja privātos datus tomēr nākas diezgan bieži saglabāt. Tā kā no gūgles neko izdabūt neizdevās, nolēmu uzdot jautājumu StackOverflow.com, kur man ieteica DPAPI un ProtectedData klasi. Jāatzīst, ka tik ātru un precīzu atbildi nebiju gaidījis. DPAPI priekšrocība (un arī trūkums) ir tas, ka atslēgas ir piesaistītas lietotāja windows kontam un datus nav iespējams atšifrēt “neielogojoties” ar tā paša lietotāja akreditācijas datiem, kurš ir veicis šifrēšanu.

Arī ProtectedData pielietošana ir vienkārša. Šī klase satur divas metodes – Protect šifrēšanai un Unprotect atšifrēšanai. Abām metodēm ieejā tiek padots baitu masīvs, kurš tiek šifrēts/atšifrēts, papildus “sāls” un enumerators, kāda tipa atslēgu (lietotāja vai datora) izmantot šifrēšanai. Tā kā es datus glabāju simbolu virknēs, tad visa izmantošanas maģija ir simbolu virkņu pārveidošanā uz baitu masīviem un otrādi. Šeit gan ir jāatcerās, ka sašifrētie dati ne vienmēr var veiksmīgi tikt pārveidoti uz simbolu virkni, tāpēc labāk tos ir glabāt Base64 kodējumā.

Jau pēc pirmajiem šifrēšanas un atšifrēšanas mēģinājumiem, sapratu, ka arī šīm metodēm vajadzētu izveidot “iesaiņojumu”, ko nolēmu izdarīt, izmantojot paplašinājuma metodes. Gala rezultāts sanāca vienkāršs un elegants (tiesa, neizmantojot papildus “sāls” datus):

using System;
using System.Security.Cryptography;
using System.Text;

namespace DraugiemDesktop
{
    /// <summary>
    /// Contains extension methods for string encryption/decription
    /// using <see cref="ProtectedData"/> methods.
    /// </summary>
    public static class ProtectionExtensions
    {
        /// <summary>
        /// Encrypts the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>Base64 string with encrypted value</returns>
        public static string Encrypt(this string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return string.Empty;
            }

            byte[] protectedValue = ProtectedData.Protect(
                Encoding.UTF8.GetBytes(value), 
                null, 
                DataProtectionScope.CurrentUser);
            return Convert.ToBase64String(protectedValue);
        }

        /// <summary>
        /// Decrypts the specified value.
        /// </summary>
        /// <param name="value">The value (Base64 encoded).</param>
        /// <returns>Decrypted value as string</returns>
        public static string Decrypt(this string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return string.Empty;
            }

            byte[] plainData = ProtectedData.Unprotect(
                Convert.FromBase64String(value),
                null,
                DataProtectionScope.CurrentUser);
            return new string(Encoding.UTF8.GetChars(plainData));
        }
    }
}

Un lietošana ir vienkārša:

                this.txtEmail.Text = Settings.Default.Email.Decrypt();
                Settings.Default.Email = this.txtEmail.Text.Encrypt();
Draugiem.lv pastnieks.

Informācija: Šī ir veca programmas versija. Jaunāko versiju var atrast šeit

Pagājušo nedēļu sanāca dažus vakarus ziedot, lai uzrakstītu nelielu draugiem.lv API programmiņu. Iepazīstieties ar draugiem.lv pastnieku, kurš dzīvo “blakus pulkstenim”  un prot parādīt paziņojumus par ienākošajām draugiem.lv vēstulēm.

pastnieks_1

pastnieks_2

 

Lai darbinātu pastnieku, ir nepieciešams .NET 3.5

Par visiem negludumiem darbā ar pastnieku varat ziņot šeit pat, komentāros.

Draudzīgās saskarnes iesaiņojums.

Šovakar radās nedaudz brīva laika, lai ko uzrakstītu šim blokam. Patiesībā, radās ne tikai laiks, bet arī iemesls uzrakstīšanai. Iemesls gan nav pārāk svaigs, tomēr Latvijas blokosfēras mērogā pietiekami ievērojams – draugiem.lv piedāvā savu saskarni dažādu lietojumu programmēšanai. Tā kā Lielā Lāča komentāros jau ir sākuši pieteikties “wrapperu rakstīšanai populārākajām valodām”, tad mana pašapziņa ciestu, ja es kādam citam ļautu rakstīt .NET iesaiņojumu!

Pagaidām saskarnes iespējas ir ļoti vienkāršas, tāpēc darbiņš ir no JFDI sērijas un pie viena būs iespēja papraktizēties LINQ to XML lietošanā. Pēc dokumentācijas pirmais solis, kas jāveic, lai varētu sākt izmantot API, ir savas lietojumprogrammas reģistrācija draugiem.lv portālā.

draugiem_lv_1

Pēc reģistrācijas ir pieejama API atslēga, kuru tālāk nāksies izmantot visos lietojuma veiktajos pieprasījumos.

draugiem_lv_2

Kopš sāku rakstīt šo rakstu ir pagājušas pāris stundas un man ir izdevies pabeigt sākotnējo versiju draugiem.lv saskarnes iesaiņojumam. Tā kā ir pusdivi naktī, tad es vairs nejūtu sevī spēku novest kodu līdz pirmās versijas relīzei :) Ņemiet pievienoto pirmkodu, skatieties piemēru (ļoti nesmuki uzrakstīto) un mēģiniet lietot to, ko es saucu par draugiem.lv saskarnes iesaiņojuma pirmo beta versiju. Gaidīšu komentārus un ieteikumus, kā arī tuvākajā laikā centīšos nopulēt kodu, lai varētu izlaist pirmo versiju.

Neaizmirstiet nomainīt AppKey :)

Viens kuģis labi, divi vēl labāk. Lidinators v0.5

Kā minēju iepriekšējā “Lidinatora” sērijas rakstā, viena no tuvākajā laikā īstenojamajām lietām ir vairākspēlētāju režīms, kuru tad iesākšu realizēt šajā sērijā.

Kopš iepriekšējā raksta esmu nedaudz pārstrādājis kodu, izveidojot bāzes komponentu SpriteComponent, kurā ir iznestas LoadContent(), Draw() un GetBoundingSphere() metodes, kas bija kopīgas gan planētām, gan kuģim.  Līdz ar to, klasēs, kuras reprezentē minētos objektus ir palicis tikai stāvokļa izmaiņas kods. Detalizētāk to varēs apskatīt raksta beigās pievienotajā pirmkodā.

Atgriežoties pie vairākspēlētāju režīma varu pavēstīt priecīgu ziņu – pamatā kods jau uztur iespēju izmantot vairākus kuģus vienlaicīgi. Vienīgi viņi sāks vienā un tajā pašā punktā un vienādi reaģēs uz nospiestajiem taustiņiem un tādēļ nebūs atšķirami. Tātad, ir trīs risināmi jautājumi:

  1. Kuģa sākuma koordinātes
  2. Dažādi vadības taustiņi
  3. Kuģu atšķirības zīmes

Pirmo punktu var atrisināt, papildinot kuģa konstruktora metodi, piedāvājot iespēju norādīt sākumpunkta koordinātes.

        public Ship(Game game, Vector2 startLocation): base(game)
{
this.location = startLocation;
this.textureAsset = "ship_sprite";
}

public Ship(Game game)
:
this(game, new Vector2())
{
}

Kā redzams, ir atstāts arī vecais konstruktors, kurš inicializē kuģi ekrāna centrā. Papildināsim spēles klases konstruktoru, lai redzētu, kā izskatās vairāki kuģi uz viena ekrāna.

            ships = new List<Ship>();
for (int i = 0; i < 4; i++)
{
var shipLocation = new Vector2(
(
i + 1) * Window.ClientBounds.Width / 5 - Window.ClientBounds.Width / 2,
0);
var ship = new Ship(this, shipLocation);
ships.Add(ship);
Components.Add(ship);
}

Lai varētu paspēlēties, pagaidām atkal ir jāizkomentē sadursmju noteikšana. Toties, spriežot pēc attēla, ne vienai vien lidotāju vienībai skauž cik sinhroni šie puši prot lidot.

lidinators_8

Problēmu saraksts ir saīsināts par vienu punktu. Turpināšu ar trešo punktu, t.i. kuģu atšķiršanu. Ar šo punktu ne tikai tāpēc, ka to ir vieglāk realizēt, bet arī tādēļ, ka esmu ieplānojis to izmantot pārveidotajā sadursmju noteikšanā.

Lai atšķirtu kuģus, iekrāsosim tos dažādos toņos. Savukārt, lai varētu kuģus iekrāsot, nepieciešams papildināt bāzes komponentu ar īpašību toņa glabāšanai un jaunu konstruktoru, kuram var nodot toni.

        public Color Tint {get; set;}

public SpriteComponent(Game game, Color tint)
:
base(game)
{
this.Tint = tint;
}

Bāzes objektu papildināt nākas divu iemeslu pēc. Pirmkārt, tajā ir realizēta gariņa zīmēšana. Otrkārt, nākotnē varētu būt vēlme zīmēt tonētas planētas. Savukārt tonis ir publisks tikai tāpēc, lai pie sadursmju noteikšanas varētu mainīt fona krāsu uz vainīgā kuģa krāsu. 

Lai redzētu izmaiņas, nepieciešams izmainīt zīmēšanas metodi, un Color.White vietā izmantot iepriekš norādīto toņa vērtību. Jāņem vērā, ka norādītais tonis tiek lietots kā maska, tādēļ vēlams izmantot ļoti gaišas krāsas, lai saglabātu vismaz daļēju oriģinālo krāsojumu.

            spriteBatch.Draw(
sprite,
center + location - spriteCenter,
null,
Tint,
angle * (float)Math.PI / 180,
spriteCenter,
1,
SpriteEffects.None,
0);

Un atkal jāmaina kuģu inicializācija, tagad jau izmantojot krāsas:

            ships = new List<Ship>();
var colors = new[]
{
Color.White,
new Color(255,164,164),
new Color(164,255,164),
new Color(164,164,255)
};

for (int i = 0; i < 4; i++)
{
var shipLocation = new Vector2(
(
i + 1) * Window.ClientBounds.Width / 5 - Window.ClientBounds.Width / 2,
0);
var ship = new Ship(this, shipLocation, colors[i]);
ships.Add(ship);
Components.Add(ship);
}

Un sadursmju noteikšana:

            backgroundColor = Color.CornflowerBlue;
foreach (var ship in this.ships)
{
BoundingSphere shipSphere = ship.GetBoundingSphere();
foreach (var planet in planets)
{
if (shipSphere.Intersects(planet.GetBoundingSphere()))
{
backgroundColor = ship.Tint;
}
}
}

Domāju, ka ir pienācis brīdis, kad var papriecāties par rezultātu.

lidinators_9

Šobrīd no sākotnējā saraksta ir atlicis tikai viens punkts, dažādu vadības taustiņu definēšana. Vienkāršākajā variantā nepieciešams kuģa objektam nodefinēt četras īpašības, kur katra atbilst vienai no darbībām un atbilstoši izmainīt kuģa Update() metodes pārbaudes. Tas izskatās šādi:

        public Keys LeftKey { get; set; }
public Keys RightKey { get; set; }
public Keys DownKey { get; set; }
public Keys UpKey { get; set; }

tad ir nedaudz izlaista koda un pārbaudes bloks ir mainīts pēc šāda principa:

            if (currentState.IsKeyDown(UpKey))
{
speed += 3;
}

Atkal izmainam daudz cietušo kuģu inicializācijas bloku:

            ships = new List<Ship>();
var colors = new[]
{
Color.White,
new Color(255,164,164),
new Color(164,255,164),
new Color(164,164,255)
};

var shipLocation = new Vector2(
Window.ClientBounds.Width / (-4),
0);
var ship = new Ship(this, shipLocation, colors[0])
{
UpKey = Keys.Up,
DownKey = Keys.Down,
LeftKey = Keys.Left,
RightKey = Keys.Right
};
ships.Add(ship);
Components.Add(ship);

shipLocation = new Vector2(Window.ClientBounds.Width / 4, 0);
ship = new Ship(this, shipLocation, colors[2])
{
UpKey = Keys.I,
DownKey = Keys.K,
LeftKey = Keys.J,
RightKey = Keys.L
};
ships.Add(ship);
Components.Add(ship);

Un divspēlētāju “Lidinators” ir gatavs. Tagad divreiz labāks par to pašu cenu! :)

lidinators_A

Iesaiņojums. Daudz iesaiņojuma. Lidinators v0.4

Gandrīz nedēļa pagājusi, kopš pēdējās “Lidinatora” sērijas, kuras beigas vēl nav tik drīz paredzamas. Šodienas sērijā Kamila raudot pastāsta Himēnai, ka Vasko taču ir Bernabes draugs un, tā kā viņš ir Ursulas mīļākais, tad būtu varējis iekļūt pilī un nozagt vēstuli. esmu paredzējis nedaudz satīrīt esošo pirmkodu, tajā pašā laikā apskatot klāt jaunu XNA apgabalu.

Ja paskatās uz spēles Update() un Draw() metodēm, kļūst skaidrs, ka šādi turpinot neko daudz papildināt nevarēs. Pareizāk sakot – varēs, bet kods kļūs pavisam nepārskatāms, jo katrs jaunais objekts, visticamāk, “uzpūtīs” šīs metodes. Savukārt, manā galvā esošā vīzija par to, kā vajadzētu izskatīties pabeigtam “Lidinatoram”, nepārprotami liecina, ka papildināt vajadzēs un viena no tuvākajā laikā realizējamajām lietām ir vairākspēlētāju režīms. Tāpēc sākšu ar kuģa “iesaiņošanu” atsevišķā klasē.

Tā kā XNA platforma ir paredzēta, lai atvieglotu spēļu izstrādi tiem, kam tas ir tikai hobijs, tad šeit jau ir iepriekš padomāts par darba vienkāršošanu. Tā vietā, lai izstrādātājs sāktu no tukšas vietas veidot savu klašu hierarhiju, tiek piedāvāti bāzes objekti GameComponent un DrawableGameComponent. Kāpēc izmantot šos komponentus? Kaut vai tādēļ, ka kopā ar Game klases GameComponentCollection saraksta palīdzību var automātiski izpildīt komponentes stāvokļa atjaunošanas vai zīmēšanas (tikai DrawableGameComponent) metodes, nerakstot papildus kodu.

Tātad, pievienojam jaunu sastāvdaļu projektam, izvēloties jaunu XNA komponentu

add component logs

un mūsu rīcībā ir komponenta skelets ar sagatavēm Initialize() un Update() metodēm:

/// <summary>
///
This is a game component that implements IUpdateable.
/// </summary>
public class Ship : Microsoft.Xna.Framework.GameComponent
{
public Ship(Game game)
:
base(game)
{
// TODO: Construct any child components here
}

/// <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

base.Update(gameTime);
}
}

Tā bija uzdevuma vieglākā daļa. Tagad jāsāk pārnest loģika no spēles pamatklases uz šejieni. Pirmkārt, visi mainīgie, kurā glabājās informācija par kuģa stāvokli:

        Vector2 location;
Texture2D sprite;
float speed, rotationSpeed, angle;
const float MaxSpeed = 10;
const float MaxRotationSpeed = 3;

Inicializācijas metodē norādam kuģa sākumstāvokli:

        public override void Initialize()
{
location = new Vector2(0, 0);
base.Initialize();
}

Savukārt Update() metode saņem visu kuģa pārvietošanas kodu, kuru šeit neievietošu.

Kā jau nedaudz augstāk rakstīju, objektiem, kuri tiek attēloti uz ekrāna, nepieciešams, lai to “sencis” būtu DrawableGameComponent. Diemžēl, XNA nepiedāvā sagatavi šādam objektam, tādēļ nepieciešams izmainīt klases defīnīciju, lai tā mantotos no pareizā tipa, kā arī pievienot Draw() metodi (pēc Game klases ģīmja un līdzības)

 

        public override void Draw(GameTime gameTime)
{
Vector2 center = new Vector2(
Game.Window.ClientBounds.Width / 2,
Game.Window.ClientBounds.Height / 2);
Vector2 shipCenter = new Vector2(sprite.Width / 2, sprite.Height / 2);

spriteBatch.Begin();
spriteBatch.Draw(
sprite,
center + location - shipCenter,
null,
Color.White,
angle * (float)Math.PI / 180,
shipCenter,
1,
SpriteEffects.None,
0);
spriteBatch.End();
base.Draw(gameTime);
}

Šajā posmā nācās atrisināt vēl divas problēmas. Pirmā – nav pieejams SpriteBatch, ar kura palīdzību zīmēt, otrā – nav ielādēta gariņa tekstūra. Šeit izdevās nošaut divus zaķus ar vienu šāvienu, t.i. abas problēmas atrisinājās ar LoadContent() metodes pievienošanu:

        protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
sprite = Game.Content.Load<Texture2D>("ship_sprite");
base.LoadContent();
}

Žēl, jo es jau biju paspējis izdomāt viltīgu mehānismu ar tekstūru ielādēšanu spēles pamatklasē un tekstūru kolekcijas nodošanu objektiem. Eh…

Ja tagad papildina spēles konstruktoru ar kuģa objekta izveidi un tā ievietošanu spēles sastāvdaļu kolekcijā, tad var palaist spēli un pārliecināties, ka tā būtiski nav mainījusies. Tik vien, kā nāksies izkomentēt (vai pārrakstīt) sadursmju noteikšanu. Turpinot uzkopšanas darbus, izveidoju līdzīgu klasi planētai. Šajā brīdī tapa skaidrs, ka sava bāzes klase, kas būtu mantota no DrawableGameComponent, būtu noderējusi, jo vairākas metodes abām klasēm ir praktiski vienādas. Tomēr šo soli atstāšu vēlākam laikam.

Šobrīd kods joprojām darbojas bez sadursmju noteikšanas, tādēļ papildināsim abas klases (kuģim un planētai), lai varētu piekļūt to ierobežojošajām BoundingSphere lodēm. Šī ir kārtējā metode, kas dublējas abām klasēm:

        public BoundingSphere GetBoundingSphere()
{
return new BoundingSphere(
new Vector3(location, 0),
(
float)(sprite.Width / 2.5));
}

Beidzot ir pienācis laiks parādīt, kādi tad tagad ir spēles pamatklases lauki un konstruktors:

        GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Ship ship;
Planet planet;
bool crashed;

public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
ship = new Ship(this);
planet = new Planet(this);
Components.Add(planet);
Components.Add(ship);
}

Update() ir vienkāršojusies līdz:

        protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();

crashed = ship.GetBoundingSphere().Intersects(
planet.GetBoundingSphere());

base.Update(gameTime);
}

Tagad spēle darbojas tāpat, kā bija pirms tika uzsākta pārveidošana. Kāda no tā jēga, ja koda ir palicis vairāk un tas ir pat sarežģītāks? Viens no ieguvumiem – ar pavisam nelieliem pārveidojumiem varam uz ekrāna izvietot vairākas planētas. Ir tikai nedaudz jāizmaina konstruktors:

        public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
ship = new Ship(this);
planets = new List<Planet>();
for (int i = 0; i < 3; i++)
{
var planet = new Planet(this);
planets.Add(planet);
Components.Add(planet);
}

Components.Add(ship);
}

un sadursmju noteikšana:

            crashed = false;
BoundingSphere shipSphere = ship.GetBoundingSphere();
foreach (var planet in planets)
{
if (shipSphere.Intersects(planet.GetBoundingSphere()))
{
crashed = true;
}
}

base.Update(gameTime);

Un spēlētāja rīcībā ir vairākas planētas.

lidinators_7

Līdzīgā veidā var pievienot vairākus spēlētājus, lai īstenotu daudzspēlētāju režīmu. Tas gan būs nedaudz sarežģītāk, jo būs nepieciešams definēt dažādus vadības taustiņus katram spēlētājam, bet par to kādā no nākošajām sērijām.