sobota, 13 listopada 2010

Podsumowanie pracy

Z racji tego, iż za dwa dni kończy się konkurs "Daj się poznać" w którym udział bierze ten projekt, chciałem dzisiaj podsumować okres ponad 2 miesięcy pracy nad nim i podzielić się spostrzeżeniami  na jego temat.
Otóż w silniku przez ten okres udało mi się stworzyć mnóstwo klas, które będą stanowić podstawę do dalszej pracy nad silnikiem. W skrócie:
- można wyświetlać obraz(wczytany z pliku lub generowany) i tekst na ekranie;
- można rysować prymitywy;
- można używać klawiatury, myszki;
- dostępny jest wielowyjściowy logger(obecnie wyjscia do pliku i na konsolę dos);
- dostępny timer stałokrokowy;
- animacje z atlasu i manager animacji;
- dodano podstawową obsługę dźwięku(można załadować muzykę z pliku i odtwarzać ją); - i inne drobnostki.
- integracja z Box2D (obecnie mało rozwinięta: można renderować świat, klasa IActor - która łączy się z Body i zarządza nim)
- klasa Aplikacji(CApp) - pozwala zaoszczędzić czas i skrócić pisany kod, bo ma już pętle główną i inne metody których nie musimy pisać sami.
-IGame - interfejs gry klasa główna naszej gry powinna dziedziczyć i wykorzystać metody w nim zawarte.

To na razie tyle, obecnie po zakończeniu konkursu będę się chyba zajmował dokumentacją tego wszystkiego, bo ją strasznie zaniedbałem i skupiłem się tylko na implementacji. Poza tym udział w takim konkursie był super, nauczyłem się paru nowych rzeczy od uczestników. Oby takie konkursy były organizowane częściej.

Teraz jest głosowanie na najlepsze projekty w konkursie jeśli komuś przydały się informacje znajdujące  się na blogu może oddać głos na mój projekt: http://dajsiepoznac.devmedia.pl/ 

IActor

IActor to klasa reprezentująca jak sama nazwa wskazuje aktora. Jest abstrakcyjną klasą bazową po której dziedziczy nasza klasa aktora, w której dodajemy np. zdrowie postaci. Poza tym reprezentuje graficzną formę, zawiera CSprite. Klasa jest zintegrowana z Boxem i została stworzona właśnie do Renderowania ciał(b2Body) na ekranie. Poza tym zawiera kilka flag do zarządzania tym ciałem(czy ma zostać zniszczone, czy ma być rysowane, czy "żyje").




#pragma once
#include
#include
#include
#include "CSprite.h"
namespace DarkStorm2D
{
class DLLDARKSTORM2D IActor
{
protected:
b2Body* m_Body;
GraphicsCore::CSprite* m_Sprite;
unsigned int ID;

enum ActorFlags
{
Alive,
Destroy,
Visible,
ActorFlagsCount
};
std::bitset m_Flags;
public:
IActor()
{
m_Body = 0;
m_Sprite = 0;
m_Flags.reset();
}

~IActor()
{
m_Body = 0;
m_Sprite = 0;
m_Flags.reset();
}
void Render(float ScaleFactory)
{
if ( m_Flags[Alive]&& !m_Flags[Destroy])
{
float rot = m_Body->GetAngle();
b2Vec2 pos = m_Body->GetPosition();
//if (rot)
//m_Sprite->Render(pos.x/ScaleFactory-(float)m_Sprite->GetWidth()/2.0f, pos.y/ScaleFactory-(float)m_Sprite->GetHeight()/2.0f);
//else
m_Sprite->Render(pos.x/ScaleFactory-(float)m_Sprite->GetWidth()/2.0f, pos.y/ScaleFactory-(float)m_Sprite->GetHeight()/2.0f);
}
}

GraphicsCore::CSprite* GetSprite()
{
return m_Sprite;
}
bool IsAlive()
{
return m_Flags[Alive];
}

bool IsDestroy()
{
return m_Flags[Destroy];
}

bool IsVisible()
{
return m_Flags[Visible];
}
};
};


Na razie nie jest aż tak funkcjonalna ale to się zmieni. Dzięki niej można wyrenderować świat Boxa za pomocą jednej komend(klasy CWorld - Render() // iteruje ona przez wszystkie body i renderuje ich aktorów). To na razie tyle o aktorach.

sobota, 6 listopada 2010

CApp - Podstawa Frameworka

O klasie CApp wspomniałem w jednym z wcześniejszych postów, ale nie przedstawiałem jej jeszcze dokładnie. Otóż, będzie ona stanowić podstawę frameworka, jest to jedna z głównych klas w silniku. W założeniu, tworzymy jej instancje w głównej funkcji( main ), oto jak by to wyglądało:




#include "MyGame.h"

#include "CApp"




using namespace DarkStorm2D::SystemCore;

int main()

{

MyGame Game;

CApp App(&Game);

if (App.Init())

{

App.Run();

App.Deinit();

}

return 0;

}


Dosyć prosto wygląda :).
MyGame to klasa naszej gry(dziedziczy po IGame.
Do konstruktora aplikacji przekazujemy naszą grę zgodną z interfejsem IGame, drugim parametrem, którego tu nie podaję, bo domyślna wartość(60) w zupełności wystarczy, to żądana liczba klatek na sekundę.
Potem inicjujemy Grę, następnie jeśli się to powiodło Możemy ją uruchomić(W metodzie Run() znajduje się pętla główna). Na koniec Zwalniamy wszelkie zasoby i kończymy. I to by było na tyle o CApp, jeśli ktoś miałby jakieś sugestie, uwagi niech napisze w komentarzach.

Box2D - Wielka bitwa

Od ponad tygodnia rozplanowuje klasy pomocnicze do obsługi fizyki w silniku. Głównie to będą jakieś wrappery(nakładki) na Boxa, które trochę ułatwią zarządzanie nim. Jeszcze dokładnie nie opanowałem tego pudełka, ale większość już wiem, internet w tej sprawie trochę mi pomógł, ale mogłoby być więcej tutków do tego. Niedługo spróbuję napisać jakieś małe demko z fizyką.

sobota, 30 października 2010

Box2D - Drugie Starcie

Druga próba "dopasowania" Boxa do silnika okazała się sukcesem. Po kilku poprawkach udało mi się wreszcie ruszyć coś na ekranie. Trochę trwało szukanie przyczyn złego dopasowania tego co widzimy na ekranie z tym co
robi box( obraz nie pokrywał się z rzeczywistością, złe pozycje obrazu(różnica 15px)), przyczyną był brak rzutowania na float, wysokości i szerokości podczas definiowania b2Polygona(metoda SetAsBox). Teraz wszystko działa idealnie. Oczywiście były to testy, teraz muszę zrobić kilka klas pomocniczych do tego i może zabiorę się za jakieś demko prezentujące troszeczkę możliwości silnika:)

czwartek, 28 października 2010

Box2D - Pierwsze Starcie

Dzisiaj bawiłem się pudełkiem, czyli silnikiem fizycznym 2D o nazwie Box2D, na razie tylko testowo, ale zanim zacząłem cokolwiek z nim robić musiałem je sobie zbudować( CMAKE i te rzeczy), potem napisałem kilka funkcji: do inicjalizacji świata Boxa, tworzenia Body, rysowania go i itp. Potem próbowałem poruszać obiektem ale coś było nie tak i postanowiłem, rozwiązać to jutro, bo dzisiaj kupiłem nową grę i chciałbym ją przetestować.
Ogólnie to ten Box jest fajny, ale mam problem z tutkami(na stronie głównej box2d jest słaba instrukcja, poza tym widziałem tylko tutki związane z wersją flashową boxa, nic pod c++ :( A może słabo szukałem ??

środa, 27 października 2010

Input Core - Klawiatura i mysz

Napisałem niedawno klasy odpowiedzialne za mysz i klawiaturę, są bardzo proste:
Najpierw klawiatura:




class CKeyboard
{
private:
bool m_Key[512]; // true - wciśnięty, false - nie
public:
CKeyboard()
{
Clear();
}

~CKeyboard()
{
Clear();
}

void Update(int Key, bool Action = true)
{
m_Key[Key] = Action;
}

void Clear()
{
memset(m_Key,0,512*sizeof(bool));
}

bool IsKeyDown(int Key)
{
return m_Key[Key];
}
bool IsKeyUp(int Key)
{
return !(m_Key[Key]);
}

bool operator [] (int Key)
{
return m_Key[Key];
}
};



Teraz klasa myszy:


class CMouse
{
private:
struct{
int x;
int y;
int z;

int dx;
int dy;
int dz;

unsigned int button;
} m_MouseState;

GraphicsCore::ImagePtr m_ImageCursor;

bool m_IsCursorVisible;
protected:
public:
enum EButton{NONE,LEFT, RIGHT, MIDDLE};

CMouse()
{
Clear();
m_IsCursorVisible = true;
}

~CMouse()
{
Clear();
m_IsCursorVisible = true;
al_show_mouse_cursor(al_get_current_display());
}

void Clear()
{
memset(&m_MouseState,0,sizeof(m_MouseState));
}

void Update(ALLEGRO_EVENT& Event)
{
switch (Event.type)
{
case ALLEGRO_EVENT_MOUSE_AXES:

m_MouseState.x = Event.mouse.x;
m_MouseState.y = Event.mouse.y;
m_MouseState.z = Event.mouse.z;

m_MouseState.dx = Event.mouse.dx;
m_MouseState.dy = Event.mouse.dy;
m_MouseState.dz = Event.mouse.dz;
break;

case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN:

m_MouseState.x = Event.mouse.x;
m_MouseState.y = Event.mouse.y;
m_MouseState.z = Event.mouse.z;
m_MouseState.button = Event.mouse.button;
break;

case ALLEGRO_EVENT_MOUSE_BUTTON_UP:

m_MouseState.x = Event.mouse.x;
m_MouseState.y = Event.mouse.y;
m_MouseState.z = Event.mouse.z;
m_MouseState.button = 0;
}
}

unsigned int GetButton()
{
return m_MouseState.button; 
}

void GetXY(int* x, int* y)
{
*x = m_MouseState.x;
*y = m_MouseState.y;
}

void GetZ(int* z)
{
*z = m_MouseState.z;
}

void GetDXDY(int* dx, int* dy)
{
*dx = m_MouseState.dx;
*dy = m_MouseState.dy;
}

void GetDZ(int* dz)
{
*dz = m_MouseState.dz;
}

void VisibleSystemCursor(bool Visible)
{
if (Visible == true)
al_show_mouse_cursor(al_get_current_display());
else al_hide_mouse_cursor(al_get_current_display());
m_IsCursorVisible = Visible;
}

void DrawCursor()
{
m_ImageCursor->Draw(m_MouseState.x,m_MouseState.y);
}

void SetCursorImage(GraphicsCore::ImagePtr& CursorImage)
{
m_ImageCursor = CursorImage;
}
};


W przyszłości będzie oczywiście gamepad oraz obsługa w stylu PS Move(z zastosowaniem kamerki internetowej)

sobota, 23 października 2010

Interfejs GRY

Obecnie pracuję nad klasą, która będzie szkieletowym interfejsem gry. Co to takiego? Otóż będzie to klasa, po której będziemy dziedziczyli podstawowe metody(wirtualne), potem naszą klasę podamy jako parametr do konstruktora klasy CApp(klasa aplikacji- reprezentuje aplikacje to w niej jest pętla główna). Wróćmy jednak do IGame, obecnie ma kilka metod wirtualnych:
- void Update(double step) // aktualizacja gry, parametr krok czasowy;
- void Render() // odrysowanie przebiegu gry;
- bool Init() // inicjalizacja gry;
- bool Uninit() // po zakończeniu gry;
... // jeszcze jakieś można wymyślić.

Ogólnie ta klasa w połączeniu z CApp i innymi  ma pomóc przy tworzeniu gry(nie musimy pisać na przykład pętli głównej od początku itp.)

środa, 20 października 2010

Co już jest w silniku ??

W tym poście chce podsumować te kilka tygodni pracy i pokazać co jeszcze zostało do zrobienia, oraz jak wygląda obecnie silnik(podział na części).

Zacznijmy od podsumowania:
- można wyświetlać obraz(wczytany z pliku lub generowany) i tekst na ekranie;
- jest manager obrazów;
- dostęp do czcionek z plików .ttf;
- można rysować prymitywy;
- można używać klawiatury, myszki;
- dostępny jest wielowyjściowy logger(obecnie wyjscia do pliku i na konsolę dos);
- dostępny timer stałokrokowy;
- animacje z atlasu i manager animacji;
- Sprity(zbiór animacji, graficzna reprezentacjia aktora);
- podstawowa obsługa dźwięku(można załadować muzykę z pliku i odtwarzać ją); 
- i inne drobnostki.

Ogólny zarys silnika:
Podział na 12 podsystemów(w silniku nazywanych rdzeniami), każdy rdzeń odpowiada za odpowiednią funkcjonalność:

  • Graphics Core - odpowiada za to co zobaczymy na ekranie; 
  • Input Core - odpowiada za sterowanie w grze;
  • Sound Core - odpowiada za dźwięk i jego efekty;
  • Physic Core - odpowiada za fizykę oraz systemy cząsteczkowe;
  • System Core - odpowiada za aplikacje, posiada klasy do obsługi maszyny stanów;
  • GUI Core - przyciski, etykiety, okna, czyli GUI i jego obsługa;
  • Effect Core - będą tam szablony różnych efektów np. blur i jego odmiany;
  • Utility Core - Posiada klasy pomocnicze, Manager Zasobów oraz klasy obsługujące VFS i Archiwa( Zip, RAR, itp.);
  • Network Core - odpowiada za komunikacje sieciową
  • Script Core - odpowiada za przetwarzanie skryptów pisanych w specjalnie do tego celu stworzonym języku DarkScript;
  • Artificial Intelligence Core - odpowiada za sztuczną inteligencję w grze;
  • Game Core - odpowiada za całą grę, posiada klasy wyższego poziomu(czytaj: frameworka) które ułatwiają programowanie gier(zawiera klasy reprezentujące poszczególne elementy różnych typów gier).

sobota, 16 października 2010

Co w trawie piszczy ??

W silniku pojawiła się podstawowa obsługa dźwięku. Wreszcie można coś usłyszeć. To na razie tylko tak testowa i atrapowa klasa:



class CSound : public IResource
{
protected:
ALLEGRO_SAMPLE* m_Sound;
ALLEGRO_SAMPLE_ID m_SoundID;
public:
enum EPlayMode {
ONCE = ALLEGRO_PLAYMODE_ONCE,
LOOP = ALLEGRO_PLAYMODE_LOOP,
BIDIR = ALLEGRO_PLAYMODE_BIDIR
};
enum EPanMode {
NONE = -1000,
CENTER = 0,
LEFT = -1,
RIGHT = 1,
};

CSound()
{
m_Type = IResource::RES_TYPE_SOUND;
m_Sound = 0;
memset(&m_SoundID,0,sizeof(m_SoundID));
}
~CSound()
{
Stop();
if (m_Sound != 0)
al_destroy_sample(m_Sound);
}

const CError& LoadFromFile(const std::string & Filename)
{
if (m_Sound == 0)
m_Sound = al_load_sample(Filename.c_str());
if (m_Sound != 0)
{
SetFilename(Filename);
return CError();
}
return CError(CError::TYPE_SYSTEM_ERROR,CError::FILE_LOAD);
}
const CError& Reload()
{
al_destroy_sample(m_Sound);
return LoadFromFile(m_Filename);
}

bool Play(float Gain, float Pan, float Speed = 1.0, int Loop = ONCE)
{
return al_play_sample(m_Sound,Gain,Pan,Speed,Loop,&m_SoundID);
}

void Stop()
{
al_stop_sample(&m_SoundID);
}
};


Jak widać tylko podstawowa funkcjonalność, ale dodam jeszcze miksery i efekty to będzie git. Obsługuje formaty takie jak: .wav, .flac, .ogg, .it, .mod, .s3m, .xm.
Grunt że działa !!!

Angielski is Good !!!

Dzisiaj o nazewnictwie, dlaczego używam i wielu innych programistów języka angielskiego w pracy? Używam go dlatego, że jest wygodniejszy niż ojczysty, w większości przypadków dużo krótszy, a oto przykład:
metdy typu Get i Set, po polsku to Pobierz, Zmien // prawda, że po angielsku łatwiej zapisać i lepiej wygląda.
Ogólnie, rzecz biorąc nasze ogonki nie mogą być wykorzystywane w programowaniu, więc te wyrazy mają orty i zdarzyło mi się kilka razy zapomnieć co dany wyraz oznacza. Może drugi przykład na potwierdzenie tezy, że angielski jest prostszy w zapisie:
int Speed;
int Predkosc;
// Widać różnice ??
Oczywiście są wyjątki od tej reguły np.
int Mass;
int Masa
// ...
Projekty pisane po angielsku są dużo bardziej czytelniejsze niż te po polsku(mimo że w głowie mamy automat korekcji błędów i sami dodajemy ogonki). Jeszcze jeden powód dla którego warto stosować angielskie słownictwo, otóż na świecie jest dużo więcej osób posługujących się angielskim niż polskim i gdyby jakiś anglik chciał skorzystać z naszej pracy to nawet Trans Googla nie pomoże:)

Czekam na uwagi i opinie do tematu.

piątek, 15 października 2010

Duchy na ekranie:)

Prace nad silnikiem powoli idą do przodu:)
Dzisiaj pragnę przedstawić, a właściwie zapoznać z duchami. Chyba każdy wie kim, a raczej czym jest duch - folklorze ludowym i według spirytystów istota inteligentna, żyjąca po śmierci fizycznej człowieka, bytująca w świecie pozamaterialnym, tak tym jest właśnie duch. Ale w grafice jest to po prostu dwuwymiarowy obrazek, który wyświetla się na ekranie i fachową nazwą dla niego jest Sprite. W moim silniku, tworzy go klasa o oryginalnej nazwie "CSprite" . Oto jej kod:



//! Klasa zawiera zbiór animacji.
class DLLDARKSTORM2D CSprite : public IResource
{
protected:
AnimationManagerPtr m_Animations;
std::string m_CurrentAnimationName;
AnimationPtr m_CurrentAnimation;
public:
CSprite();
~CSprite();

bool Create(const  std::string & CurrentAnimationName,AnimationManagerPtr & Animations);
void Update();

CError& LoadFromFile(const std::string & Filename);
const CError& Reload()
{
// mała atrapa
m_Animations->ReloadAll();
return CError();
}

bool SetAnimation(const std::string & AnimationName)
{
m_CurrentAnimationName = AnimationName;
m_CurrentAnimation = m_Animations->GetResource(AnimationName);
return true;
}
const std::string & GetCurrentAnimationName() const
{
return m_CurrentAnimationName;
}

void Render(float x, float y)
{
m_CurrentAnimation->Render(x,y);
}
};




JAK widać nic nadzwyczajnego, po prostu zbiór animacji. Jedną rzeczą na jaką trzeba zwrócić uwagę jest fakt że nie ma on określonej pozycji i wymiarów(to tylko część wizualna), jest to spowodowane filozofią projektową silnika - pozycja i wymiary będą w wyższej klasie grupującej tą i obiektu fizycznego, zdradzę może jej nazwę: CActor.


Może taka mała uwaga odnośnie nazewnictwa klas mojego projektu, a dokładniej przedrostków w nazwach: 
1. C - od klasy "normalnej"
2. I  - od interface, czyli klasa interfejsu(wirtualna)
3. S - od Singleton
4. może jeszcze FO od factory objects fabryka obiektów(o tym wspomnę w najbliższym poście).

Dokumentacja źródłem sukcesów w życiu ...

Dokumentacja jest tak jak w nagłówku źródłem sukcesów. Jest wręcz nie zastąpiona jako pomoc w rozumieniu kodu(choć są wyjątki od tej reguły). Dokumentacja pozwala zrozumieć biblioteki i różne inne projekty(samodzielnie). Czasami jest to dalekie od ideałów, gdy jest chaotyczna, niekompletna, pisana trudnym językiem, itd.. Faktem jest, że jej brak nie jest zbyt korzystny, ani dla twórcy(twórców) projektu, ani potencjalnego użytkownika(można wtedy konsultować się z autorem i dowiadywać się wszystkiego(oczywiście takie rozwiązanie przy dużej liczbie użytkowników, jest bardzo frustrujące dla wszystkich)). Wracając do tematu, czym jest właściwie dokumentacja kodu projektu? Otóż to wszelkie informacje o projekcie, opis kodu, itd.. Prostym sposobem dokumentowania są, uwaga: komentarze:) - to dzięki nim możliwa jest analiza kodu do którego powróciło się po kilku miesiącach. Lepszą formą opisu kodu w większych projektach są generatory dokumentacji, które za pomocą usystematyzowanych komentarzy jak sama nazwa wskazuje generują nam gotową dokumentację kodu w przejrzystej strony html lub innej przyjemnej dla oka formie. Godnym polecenia jest tutaj: Doxygen - darmowy, obsługuje wiele języków, posiada program(Doxywizard), który prowadzi nas przez proces tworzenia dokumentacji, posiada graficzny interfejs. Mały tutek jest na tej stronie: http://wierzba.miks.uj.edu.pl/~gurgul/doxygen/

I teraz zdanie podsumowujące: Dokumentacja jest bardzo ważnym elementem, który wpływa na wiele innych czynników, związanych mniej lub bardziej z projektem(np. popularności, która przy mało przejrzystej dokumentacji będzie prawdopodobnie bardzo niska).

Proszę  o uwagi , opinie związane z tematem dokumentacji. Chętnie dowiem się jak sprawa stoi u innych.

poniedziałek, 11 października 2010

GUI - koncepcja odbioru zdarzeń myszki


Wczoraj trochę(nawet więcej) poszedłem myślami do przodu, a mianowicie do projektowania GUI(graficznego interfejsu użytkownika). Na początek rozważałem zdarzenia myszki i żeby optymalnie je obsługiwać wymyśliłem to:

- jeśli byśmy sprawdzali kolizje z myszką na każdej kontrolce, to zajęłoby to dużo czasu(wyobraź sobie z 100 kontrolek ), a przecież nie musimy sprawdzać wszystkich. Można, więc wykorzystać drzewo czwórkowe, które posłuży do eliminacji kontrolek, których i tak zdarzenie nie będzie dotyczyć. Zanim omówię, jak wykorzystać to w praktyce, trzeba wyjaśnić czym jest to drzewo, otóż wyobraźmy sobie kwadratową(dla uproszczenia)kartkę papieru, teraz dzielimy ją  na cztery równe części:

Proszę nic nie sugerować, kolory nie są tak istotne :)
Teraz każdą z tych części dzielimy na cztery równe:


Możemy dalej dzielić te komórki, aż dojdziemy do pojedynczych pikseli,które z reguły są nie podzielne:). Do GUI możemy pozostać na tym poziomie podziału. Teraz skoro już wiemy jak wygląda takie drzewo możemy przejść dalej:
- pierwszy poziom podziału to cztery(a jaka inna liczba powinna być w drzewie czwórkowym??) komórki, możemy, więc stworzyć tablicę Tab1[4][4] - jej typem będzie lista kontrolek które będą znajdowały się w danej komórce. My sprawdzamy hit testem w której komórce jest kursor myszy(najpierw na pierwszym poziomie, potem na drugim, te dane przekazujemy do tabeli i przechodzimy po liście(sprawdzamy kolizje)). Oczywiście tą tabelę musimy wygenerować przed wykorzystaniem. Takie rozwiązanie problemu z myszką powinno zaowocować wydajną pracą GUI(nie musimy sprawdzać wszystkich kontrolek, jeśli jest to nie potrzebne). 
Jeśli coś byłoby nie jasne to śmiało można pisać komentarze. A może ktoś rozwiązałby ten problem inaczej. Proszę o opinie na temat tego rozwiązania.

sobota, 9 października 2010

Animacja

Ostatnio tworzyłem klasy odpowiedzialne za animacje. Które są zawarte w jednym obrazie(tzw. atlas), każda klatka ma określony wymiar , oto przykład:

Jak widać ma 4 klatki(każda o wymiarach 64x64). Dobra ale jak narysować odpowiednią ??
Otóż potrzebna nam jest klasa, która będzie zawierać numer komórki(w poziomie i pionie):

class CellID
{
public:
uint X;
uint Y;
};


Teraz szablon komórki(zawiera obraz z którego pobieramy dane, wysokość i szerokość jednej komórki), dzięki jednej z jej metod możemy pobrać interesującą nas klatkę animacji(wystarczy że podamy numer w poziomie i pionie):

class CTemplateSet
{
protected:
             // wskaźnik na obraz
GraphicsCore::ImagePtr m_Image;
            //  szerokość komórki
uint m_CellWidth;
           // jej wyskość
uint m_CellHeight;
public:
CTemplateSet()
{
m_Image = 0;
m_CellWidth = 0;
m_CellHeight = 0;
}
~CTemplateSet()
{
m_Image = 0;
m_CellWidth = 0;
m_CellHeight = 0;
}

void Create(GraphicsCore::ImagePtr& Image, uint CellWidth, uint CellHeight)
{
m_Image = Image;
m_CellWidth = CellWidth;
m_CellHeight = CellHeight;
}
                // zwraca nam współrzędne wybranej klatki
void GetRect(CellID Cell, CRect* Rect)
{
Rect->m_Top = Cell.Y* m_CellHeight;
Rect->m_Left = Cell.X* m_CellWidth;

Rect->m_Right = Cell.X* m_CellWidth + m_CellWidth;
Rect->m_Bottom = Cell.Y* m_CellHeight + m_CellHeight;
}
uint GetCellWidth() {return m_CellWidth;}
uint GetCellHeight() {return m_CellHeight;}
GraphicsCore::ImagePtr& GetImage() {return m_Image;}
};




Teraz czas na klasę animacji, 

class  CAnimation
{
protected:
CTemplateSet m_TemplateSet; // szablon katki
uint m_Frames; // liczba klatek
uint m_CurrentFrame; // obecna klatka
CellID* m_FramesID; // tablica numerów(w poziomie i pionie) naszych klatek
public:
CAnimation();
~CAnimation();

void Create(CTemplateSet & TemplateSet, uint Frames, CellID* FramesID);
void Update(); // zmienia klatkę na następną
void Render(float x, float y);
void SetCurrentFrame(uint Frame);
uint GetCurrentFrame();
};

-------------------------------------------------------------------------------------------------------------
// anim.cpp

#include "CAnimation.h"

CAnimation::CAnimation()
{
m_Frames = 0;
m_CurrentFrame = 0;
m_FramesID = 0;
}

CAnimation::~CAnimation()
{
m_Frames = 0;
m_CurrentFrame = 0;
if ( m_FramesID != 0)
{
delete [] m_FramesID;
m_FramesID = 0;
}
}

void CAnimation::Create(CTemplateSet & TemplateSet, uint Frames, CellID* FramesID)
{
m_TemplateSet.Create(TemplateSet.GetImage(), TemplateSet.GetCellWidth(),TemplateSet.GetCellHeight());

m_FramesID = new CellID [Frames];

memcpy(m_FramesID,FramesID,sizeof(CellID)*Frames);
m_Frames = Frames;
m_CurrentFrame = 0;
}

void CAnimation::Update()
{
m_CurrentFrame++;
if (m_CurrentFrame>= m_Frames)
m_CurrentFrame = 0;
}

void CAnimation::Render(float X, float Y)
{
CRect Rect;
m_TemplateSet.GetRect(m_FramesID[m_CurrentFrame],&Rect);
m_TemplateSet.GetImage()->DrawRect(X,Y,&Rect);
}

void CAnimation::SetCurrentFrame(uint Frame)
{
m_CurrentFrame = Frame;
}

uint CAnimation::GetCurrentFrame()
{
return m_CurrentFrame;
}



Dzięki tym klasą można tworzyć i wyświetlać animacje.
Można rozszerzyć klasę animacji o interwał czasowy zmiany klatki(klatka zmieni się po upływie określonego czasu np. po 0.5 sekundy)

piątek, 8 października 2010

Kosmici atakują [::) - Intrusive Smart Pointer

Nie pisałem jeszcze o tym, ale wydaje mi się że to ważne. Otóż w większej części silnika(szczególnie odnosi się to do różnych zasobów(np. obrazy, dźwięki, animacje, itp.)) korzystam z tzw. inwazyjnych inteligentnych wskaźników - nie są to jednak zielone ludki z kosmosu, ale bardzo pomocne i pożyteczne wskazówki. Wszystkie smart pointery mają jeden cel zabezpieczyć program przed wyciekami pamięci. 


W projekcie wykorzystuje ich wersję z BOOST(boost::intrusive_ptr), ogólnie założenie inwazyjnych wskaźników jest takie: 
- w klasie która ma korzystać z dobrodziejstwa tegoż wskaźnika musi być pole(zmienna, nasz intruz), która będzie licznikiem referencji(liczba wskaźników które wskazują dane miejsce w pamięci w którym znajduje się obiekt)
- trzeba też zdefiniować funkcje które będą operowały na liczniku(dodającą i odejmującą referencje)
- na koniec musimy stworzyć szablonowe funkcje o nazwach: intrusive_ptr_add_ref i intrusive_ptr_release.
Kod klasy Licznika referencji: 





#pragma once
#include


//! Klasa licznika referencji potrzebna do inteligentnego wskaźnika.
/*! 
 * Od tej klasy powinna dziedziczyć klasa, która chce korzystać z mechanizmu SmartPointer'a.
 */
class CRefCounter 
{
//! Licznik referencji.
uint m_References;


public:
//! Konstruktor zeruje licznik.
CRefCounter() : m_References(0) {}
virtual ~CRefCounter() {}


//! Dodaje referencje do wskaźnika
void AddRef()
{
++m_References;
}
//! Odejmuje referencje od wskaźnika
int Release()
{
--m_References;
return m_References;
}


        //! zwraca liczbę referencji
int GetRef()
{
return m_References;
}
};


// z tego korzysta boost przy dodawaniu referencji
template void intrusive_ptr_add_ref(T * p)
{
p->AddRef();
}
// z tego korzysta boost przy odejmowaniu referencji
template void intrusive_ptr_release(T * p)
{
if (p->Release() <= 0)
delete p;
}


Aby skorzystać teraz z Smart pointera musimy odziedziczyć po tej klasie(patrz powyżej) klasę która ma z niego korzystać :

CMyClass :public CRefCounter 
{
public:
    CMyClass() {cout << "Konstruktor CMyClass" << endl;}
    CMyClass() {cout << "Destruktor CMyClass" << endl;}
};

typedef boost::intrusive_ptr CMyClassPtr;

int main()
{
    CMyClassPtr p1(new CMyClass); ref = 1 // komunikat konstruktora
    CMyClassPtr p2(p1); // ref = 2
    CMyClassPtr p3 = p2;  // ref = 3

    // powinny być trzy trójki
    cout << p1->GetRef() << " " << p2->GetRef() << " " << p3->GetRef() << endl;
    
//komunikat destruktora
     cin.get();
    return 0;
}

Jak widać Inwazyjny Smart nie jest trudny w zastosowaniu, jest też wydajny i wygodny. 

Jeśli gdzieś zrobiłem błąd, albo coś jest niejasne to piszcie w komentarzach.