Pamācības

Snake 2 stundās

Snake 2 stundās

Elviss Strazdiņš, 13.02.2008

Izstrādātājs: elvman

Lejupielādēt: šeit

Ļoti bieži no cilvēkiem nākas dzirdēt tekstus līdzīgus šim: "kā tu taisi tās spēles? Kā tas notiek? Tas noteikti ir ļoti sarežģīti." Tādēļ izdomāju beidzot cilvēkiem parādīt kā tas notiek un, ja saprot, ko dara, tad tas nemaz nav tik sarežģīti. Šī spēle arī labi kalpos topošajiem spēļu izstrādātājiem, kas īsti nezina, kur ķerties klāt.

Kad ķēros klāt pie plānošanas, biju izdomājis, kādu softu lietošu spēles izstrādē. Un tas ir: izstrādes valoda un vide: Visual C++ 2008 Express (www.microsoft.com/express/vc/), spēles grafiskā un ievades daļa: SDL (http://www.libsdl.org/), grafikas apstrāde: GIMP (http://www.gimp.org/). Speciāli izmeklēju rīkus, kas ir bez maksas, lai pierādītu, ka spēles var veidot arī bez maksas.

Spēles plānošana (15 min)

Sākumā definēju spēles mērķi: vadot čūsku apēst pēc iespējas vairāk peļu. Pēc tam izplānoju kādas klases būs spēlē. Tās būtu: Application (pati galvenā klase), AppState (application state, jeb režīms, kādā atrodas aplikācija t.i. izvēlne, vai spēle), MainMenu (izvēlne) un Game (spēle). Tad sīkāk izplānoju, tieši kā darbosies spēle. Pašas čūskas princips ir šāds: čūskai ir virziens, kurā tā pārvietojas. Pārvietojas čūska pa noteiktiem laika intervāliem (atkarībā no līmeņa) un virzienā, ko pēdējo ir norādījis spēlētājs. Kad pienāk laiks čūskai pārvietoties par vienu vienību uz priekšu, tās pēdējā šuna tiek izdzēsta, bet pirms pirmās šūnas tiek pievienota jauna (čūskas iešanas virzienā). Grafiski tas izskatās šādi:

 

Spēles grūtibas pakāpe līdz ar katru noķerto peli aug, jo cūška paliek garāka un ātrāka.

Grafiskā daļa (15 minūtes)

Spēlei bija vajadzīgas 2 pogas (New Game un Exit), čūskas āda un pele. Šādi GIMP izskatījās pogu rediģēšana (ar GIMP nekad mūžā neesmu strādājis, taču nebija tik sarežģīti, kā daudzi mēdz runāt):

Šeit pārveidoju internetā atrastās peles attēlu:

Spēles kods (1 stunda 30 minūtes)

Šī arī bija daļa, kur nācās visvairāk pasvīts. Tā pat kā ar GIMP, arī ar SDL nekad nebiju strādājis, tādēļ paralēli spēles izstrādei nācās iemācīties, kā darboties ar SDL. Tātad sāku ar jauna projekta izveidi (Microsoft Visual C++ tas ir File/New/Project), izvēlējos empty project, nosaucu par Snake un sāku kodēt. Sāku ar visu klašu izveidošanu (labais taustinš uz projekta un Add/Class/C++ class), un tās ir Application, AppState, Game un MainMenu. Pievienoju failus main.cpp un main.h (Add/New Item), kurā atrodas aplikācijas galvenā funkcija (main) 

int SDL_main(int argc, char* argv[])
{
    gApp = new Application;
   
    gApp->Go();

    delete gApp;

    return 0;
}

un globālās definīcijas: 

extern Application*    gApp;
extern Game*        gGame;
extern MainMenu*    gMainMenu;
extern SDL_Surface*    gDisplay;

Pašā sākumā sadefinēju vajadzīgās funkcijas (OnEvent, OnInit, OnDraw un OnUpdate) un izveidoju Application klasi. Init funcijā tiek inicializēts SDL un izveidots jauns logs (SDL_Init) un inicializēta MainMenu klase:

if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
    return false;

gDisplay = SDL_SetVideoMode(600, 600, 32, SDL_HWSURFACE | SDL_DOUBLEBUF);
SDL_WM_SetCaption("Snake", NULL);

if (!gDisplay)
    return false;

gMainMenu = new MainMenu();
if (!gMainMenu->OnInit())
    return false;

mCurrentAppState = gMainMenu;

Rezultāts:

Pēc tam ķēros klāt pie galvenās izvēlnes. Ievades sistēma ir diezgan vienkārša: ja lietotājs ar peli uzklikšķina pogas reģionā, tad tiek aktivizēta attiecīgā komanda (Exit vai New Game). Kods izskatās šādi:

if (Event.button.x>200 && Event.button.x<200+200 &&
    Event.button.y>100 && Event.button.y<100+100)
    if (!gApp->NewGame())
        gApp->Quit();

if (Event.button.x>200 && Event.button.x<200+200 &&
    Event.button.y>250 && Event.button.y<250+100)
    gApp->Quit();

Attēli ar SDL tiek ielādēti un zīmēti šādi:

//ielāde

mNewGame = SDL_LoadBMP("media/newgame.bmp");

mExit = SDL_LoadBMP("media/exit.bmp");

//zīmēšana

SDL_Rect rect;
rect.x=200;
rect.y=100;
rect.w=200;
rect.h=100;

SDL_BlitSurface(mNewGame, NULL, gDisplay, &rect);

Kad pabeidzu galveno izvēlni, ķēros klāt pie sarežģītākā, pašas spēles. Game klase izmantos šādas struktūras:

enum DIRECTION
{
    UP = 0,
    DOWN,
    LEFT,
    RIGHT
};

struct Mouse
{
    unsigned int PosX;
    unsigned int PosY;
};

struct Node
{
    unsigned int PosX;
    unsigned int PosY;
};

Struktūra Mouse un Node ir vienādas, taču es tās izdalīju atsevišķi, lai nejauktu galvu. Mouse apzīmē peles pozīciju, bet Node - čūskas posma pozīciju. DIRECTION tiek izmantots, lai apzīmētu čūskas kustības virzienu. Manā spēlē čūska tiek apzīmēta ar vector (vektors, kas sastāv no čūskas posmiem). Kad čūska pārvietojas, tās pēdējais posms tiek likvidēts un priekšā tiek pievienots jauns posms:

if (mGrowing)
    mGrowing=false;
else
    mSnake.erase(mSnake.begin()); //idzeesham asti

Node node;

switch(mDirection)
{
case UP:
    node.PosX=mSnake.back().PosX;
    node.PosY=mSnake.back().PosY-1;
    break;
case RIGHT:
    node.PosX=mSnake.back().PosX+1;
    node.PosY=mSnake.back().PosY;
    break;
case DOWN:
    node.PosX=mSnake.back().PosX;
    node.PosY=mSnake.back().PosY+1;
    break;
case LEFT:
    node.PosX=mSnake.back().PosX-1;
    node.PosY=mSnake.back().PosY;
    break;
}

mSnake.push_back(node);

mGrowing mainīgais norādā, vai čūska nav apēdusi mušu un tā neaug. Respektīvi, ja čūska aug, tad tajā gājienā viņai netiek likvidēts pēdējais posms.

Lietotāja ievade tiek apstrādāta šādi:

switch(Event.type)
{
case SDL_KEYDOWN:
    {
        switch(Event.key.keysym.sym)
        {
        case (SDLK_UP):
            if (mDirection != DOWN) mNewDirection = UP;
            break;
        case (SDLK_RIGHT):
            if (mDirection != LEFT) mNewDirection = RIGHT;
            break;
        case (SDLK_DOWN):
            if (mDirection != UP) mNewDirection = DOWN;
            break;
        case (SDLK_LEFT):
            if (mDirection != RIGHT) mNewDirection = LEFT;
            break;
        }
        break;
    }
}

Tiek izmantoti divi mainīgie: mDirection un mNewDirection lai saglabātu virzienu, kurā čūska kustās tekošajā gājienā un virziens, kurā čūskai būs jākustas nākošajā. Tas vajadzīgs tādēļ, lai lietotājs nevarētu uzsākt kustību, kas ir tieši retējs iepriekšējam kustības virzienam.

Spēlētājs zaudē, ja čūska ieskrien pati sevī, vai laukuma malās, Tas tiek pārbaudīts šādi:

if (node.PosX<0 || node.PosX>=12 || //ja ieskrienam siena
    node.PosY<0 || node.PosY>=12)
    gApp->Loose();

for(std::vector::iterator i = mSnake.begin(); i!=mSnake.end()-1; i++) //parbaudam, vai neieskrienam sevi
{
    if (node.PosX==(*i).PosX && node.PosY==(*i).PosY)
    {
        gApp->Loose();
        break;
    }
}

Noskaidrot, vai čūska ir apēdusi peli nav sarežģīti (vienkāršā pozīciju salīdzināšana), bet sarežģītāk ir novietot tā, lai tā neatrastos zem čūskas. Kods:

void Game::PlaceMouse()
{
    bool placed=false;

    while(!placed) //cikls, lai pele neparaditos zem cuskas
    {
        mMouse.PosX=rand()%12;
        mMouse.PosY=rand()%12;

        placed=true;

        for(std::vector::iterator i = mSnake.begin(); i!=mSnake.end(); i++)
        {
            if (mMouse.PosX==(*i).PosX && mMouse.PosY==(*i).PosY) //vai pele atrdoas zem cuskas
            {
                placed=false;
                break;
            }
        }
    }
}

Tas arī viss! Manuprāt, nebija sarežģīti, bet jautri gan. Domāju šī pamācība noderēs jaunajiem izstrādātājiem, kas grasās piedalītās piemēram MiniGC sacensībās, kurās šāda veida spēles arī ir vairākums un kopumā visiem jaunajiem izstrādātājiem.

Ja ir kādi jautājumi iebildumi, vai ieteikumi, tad rakstiet tos komentāros, vai sūtiet man uz e-pastu: elvman[at]inbox[punkts]lv.

Paldies par uzmanību!

Snake 2 stundās image 1

Līdzīgi raksti:

Autorizācija

Lietotājs

Parole


Reģistrēties Aizmirsu paroli