Navigacija
Lista poslednjih: 16, 32, 64, 128 poruka.

vbSokoban: Hajde da napravimo igru!

[es] :: Visual Basic 6 :: vbSokoban: Hajde da napravimo igru!

Strane: 1 2

[ Pregleda: 4217 | Odgovora: 37 ]

Postavi temu Odgovori

Autor

Pretraga teme: Traži
Markiranje Štampanje RSS

Aleksandar Ružičić
Developer, Haragei Creative Solutions
Beograd - Čačak

Moderator
Član broj: 26939
Poruke: 1778
*.yu1.net.

Jabber: krcko@haragei.com
Sajt: krcko.haragei.org


Profil

icon vbSokoban: Hajde da napravimo igru!02.11.2006. u 22:38

vbSokoban: Hajde da napravimo igru!

VAŽNO: ovo nije još jedan od pokušaja da se na forumu okupi grupa ljudi koja bi
zajedno učestvovala u izradi nekog projekta! Već je ovo jedan mali tutorijal namenjen
ljudima koji bi želeli da nauče kako se prave igre u Visual Basicu.

[Ovu poruku je menjao krckoorascic dana 03.11.2006. u 00:24 GMT+1]
Čovekova dostignuća prevazilaze njegovu maštu.
02.11.2006. u 22:38 

Aleksandar Ružičić
Developer, Haragei Creative Solutions
Beograd - Čačak

Moderator
Član broj: 26939
Poruke: 1778
*.yu1.net.

Jabber: krcko@haragei.com
Sajt: krcko.haragei.org


Profil

icon Re: vbSokoban: Hajde da napravimo igru!02.11.2006. u 22:39
Uvod:
Kao što rekoh ovo je tutorijal o pravljenju igara. Zajedno ćemo napraviti jednu verziju
popularne Sokoban igre, ja ću je zvati vbSokoban (ukoliko imate neko originalnije ime,
možete tako nazvati svoju kreaciju). Pošto možda ima nekoga među vama, čitaocima, koji
nikad nisu igrali neku verziju Sokobana (ili su igrali a nisu znali da je to ustvari
samo jedna od kopija ove igre) ukratko ću pojasniti o čemu se radi u ovoj igri:

Vi se nalazite u jednom lavirintu iz koga nema izlaska i vaš zadatak je da kutije koje
se nalaze u tom lavirintu složite na označena mesta. Iako ovo zvuči jednostavno i dosadno
upravo je suprotno: komplikovano i zanimljivo! Pravila su da se kutija može gurati samo u
smeru "od sebe" tj ne možete vući kutiju krećući se u nazad, možete gurati samo kutiju koja
ispred sebe (u smeru u kome je gurate) nema ni zid ni neku drugu kutiju. Upravo su ova
pravila i posebno oblikovani nivoi (izgledi lavirnta i raspored kutija i ciljnih mesta)
ono zbog čega je ova igra zanimljiva i zbog čega postoji ogroman broj klonova i rimejkova
ove igre.

Dakle sada kada znamo šta ćemo da pravimo neko može da postavi pitanje zašto sam baš odabrao
Sokoban za ovaj tutorial. Odgovor je da je Sokoban veoma lak za programiranje i samim tim
pogodan za prve korake ka svetu programiranja igara. Lakše je iskodirati Sokoban nego, recimo
Pong, iz razloga što kod Ponga moramo da vodimo računa i o uglu odbijanja loptice i o ubrzanju
koje loptica dobija kada udari u ivicu reketa i sl. Kod Sokobana nema ni fizike ni matematike
(sem sabiranja i oduzimanja ) već samo obično kretanje (gore, dole, levo, desno), i kod Ponga
bi morali da imamo petlju (tzv "Game Loop") u kojoj bi se odigravala logika igre (proračuni
kretanja lopte i reketa) i iscrtavali frejmovi, kod Sokobana nema takve dinamičnosti već svu
logiku koju treba da primenimo (a to je samo da proverimo mogućnost kretanja u željenom pravcu i
pomeranja kutija) izvršavamo samo kada korisnik pritisne jedan od mogućih tastera, a za to nam
ne treba petlja već KeyDown događaj.

To bi bilo to o tome zašto pravimo Sokoban, a sada da vidimo kako ćemo da ga napravimo.

Pre nego što se počne sa izradom bilo kog programa, prvo mora da postoji algoritam,
odnosno prvo se problem dobro analizira i napravi se neki plan (algoritam) koji će
se posle pretočiti u pisani program. Važno je da shvatite da je svrha programiranja
dobro razumevanje problema i nalaženje pogodnog rešenja, a samo programiranje (tj
pisanje koda) je najlakši deo u celoj toj stvari.

Mi nećemo crtati algoritam (ono "početak->ulaz->obrada->izlaz->kraj") ali ćemo napraviti
analizu "problema" koji je pred nama.
Dakle, znamo šta je Sokoban i šta treba da napravimo, ono što naš Sokoban treba da ima je:

1. "Mehanizam" za učitavanje nivoa iz fajlova
2. Mehanizam za iscrtavanje nivoa (lavirnita, kutija i igrača)
3. Mehanizam za upravljanje igračem (tu spada i pomeranje kutija)

ok, nema mnogo tačaka

sada da se spremimo za rešavanje problema, tačku po tačku.
Čovekova dostignuća prevazilaze njegovu maštu.
02.11.2006. u 22:39 

Aleksandar Ružičić
Developer, Haragei Creative Solutions
Beograd - Čačak

Moderator
Član broj: 26939
Poruke: 1778
*.yu1.net.

Jabber: krcko@haragei.com
Sajt: krcko.haragei.org


Profil

icon Re: vbSokoban: Hajde da napravimo igru!02.11.2006. u 22:43
Priprema:
Prvo što treba da razmislimo to je kako ćemo da posmatramo nivo, tj lavirint i sve u njemu.
Najjednostavnije (i najlogičnije) je da lavirint posmatramo kao dvodimenzionalnu matricu, ili
tabelu sa redovima i kolonama. Sada se postavlja pitanje na koji način ćemo da čuvamo podatke o
nivou, odgovor sledi iz predhodne rečenice: matrica (matrica je višedimenzionalni niz, u našem
slučaju to je niz sa dve dimenzije), da bi znali šta da čuvamo u toj matrici moramo se prvo
podsetiti koji su sve "objekti" u pitanju:

imamo:
- zid, to je polje na koje igrač ne može da stane niti kutija može da se nalazi na tom polju
- prazno polje, tuda se zapravo igrač kreće i gura kutije
- ciljno mesto, tj tamo gde treba da se stavi kutija, ovo je ustvari isto kao prazno polje samo što ima neku oznaku
- kutiju, to je kutija koja se nalazi na praznom polju
- postavljenu kutiju, to je kutija na ciljnom mestu
- i na kraju imamo igrača, koji se kreće po praznim (i ciljnim) poljima

znači svako polje u našoj matrici će imati jednu od vrednosti koja označava šta se nalazi na tom polju:
- ZID
- POLJE
- CILJ
- KUTIJA
- ZGODITAK (ovo je kutija na ciljnom mestu )

u matrici nećemo čuvati podatak o tome gde se nalazi igrač jer je mnogo lakše da to
čuvamo u posebnoj promenljivoj, iz razloga što bi pre pomeranja igrača morali prvo
da tražimo u matrici njegovu trenutnu poziciju, a u slučaju da to čuvamo u posebnoj
promenljivoj uvek imamo podatke o njegovoj poziciji...

ok pre nego što konačno počnemo sa programiranjem moramo da imamo grafiku sa kojom ćemo da
radimo, sličice koje nam trebaju su:

- zid
- ciljno polje
- kutija
- kutija na ciljnom polju
- igrač

evo šta sam ja nacrtao:


(vbsokslicice.zip)

znam, odvratno je ali ja nisam mogao bolje Ukoliko vi imate malo većeg talenta od mene
možete koristiti vaše sličice, samo pazite da su sve veličine 40x40 pixela.
Čovekova dostignuća prevazilaze njegovu maštu.
Prikačeni fajlovi
02.11.2006. u 22:43 

Aleksandar Ružičić
Developer, Haragei Creative Solutions
Beograd - Čačak

Moderator
Član broj: 26939
Poruke: 1778
*.yu1.net.

Jabber: krcko@haragei.com
Sajt: krcko.haragei.org


Profil

icon Re: vbSokoban: Hajde da napravimo igru!02.11.2006. u 22:47
Izrada:
Konačno da počnemo sa pravljenjem igre!

Pokrenite VB i izaberite Standard EXE projekat. Form1 preimenujte u frmGame i podesite
joj ScaleMode property na 3 - Pixel.

Dodajte jedan PictureBox i nazovite ga picLevel. Podesite mu sledeća svojstva:
Appearance: 0 - Flat
AutoRedraw: True
BackColor: &H0042734A&
Enabled: False
ScaleMode: 3 - Pixel

u picLevel ćemo da isrtavamo naš nivo, i on bi trebao da bude uvek na sredini forme
(to ćemo sledeće da uradimo).
Napomena: ukoliko koristite vaše sličice, BackColor postavite na hex vrednost boje koja
je vama pozadina. Samo obratite pažnju na to da VB boje zapisuje u BGR obliku a ne u RGB
(kao photoshop recimo) tj ako je vaša boja pozadine u photoshopu FFCCAA u vb-u će to biti
&H00AACCFF& (a ne &H00FFCCAA&)!

Sada da centriramo picLevel na sredinu forme: kliknite dva puta na formu da bi vam se otvorio
Code View, u gornjem desnom comboboxu (gde sada piše Load) izaberite Resize i otkucajte
sledeće:
Code:

Private Sub Form_Resize()

    picLevel.Move (Me.ScaleWidth - picLevel.Width) / 2, _
                  (Me.ScaleHeight - picLevel.Height) / 2

End Sub

Sada pokrenite program (F5) i menjajte veličinu forme, videćete da je naš picLevel uvek na
sredini.

Sada ćemo da dodamo sličice na našu formu. Kopirajte picLevel i pritisnite Ctrl+V, kada vas
VB pita da li želite da napravite control array odgovorite mu sa No. Promenite ime Picture1
u picImage i podesite AutoSize na True, Visible na False, a za Picture postavite cilj.bmp (ili
vašu sličicu), kopirajte picImage i pritisnite Ctrl+V, sada odgovorite vb-u da hoćete da
napravite control array. Za picImage(1) postavite igrac.bmp kao Picture. Opet pritisnite Ctrl+V
(ali predhodno kliknite na formu, tj deselektujte picImage(1)) i sada za Picture postavite kutija.bmp
ponovite postupak za zgoditak.bmp i zid.bmp. Sada bi trebalo da imate 5 picImage objekata.

sada nam još treba tzv bafer (buffer) u koji ćemo da crtamo nivo sličicu-po-sličicu pa tek ceo frejm
kopiramo u picLevel (tako da nema tzv "flicker" efekta) iz bafera. Selektujte picLevel i kopirajte ga,
pritisnite Ctrl+V i odgovorite sa No, preimenujte Picture1 u picBuffer i podesite mu Visible na False.

evo kako meni izgleda forma:




ok, sada nam trebaju metode za crtanje, u ovu svrhu ćemo koristiti BitBlt API funkciju i to će biti
jedina API funkcija koju ćemo koristiti u našem projektu!

ona izgleda ovako:
Code:

Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, _
                                     ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long

BitBlt (BitBlock) funkcija služi za kopiranje dela (ili cele) slike sa jednog mesta na drugo, i nju ćemo koristiti da iz picImage objekata
kopiramo sličice na određena mesta u picBuffer, kada iscrtamo sve (ceo frejm) onda ćemo sliku iz picBuffer-a prekopirati u picLevel.

Dodajte u projekat novi Modul (Project->Add Module) i nazovite ga modDraw, u njega ćemo da stavimo sav kod koji ima veze sa crtanjem.

za početak prepišite sledeći kod u modDraw:
Code:

Option Explicit

Private Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long

Public Const CILJ       As Byte = 0
Public Const SMAJLI     As Byte = 1
Public Const KUTIJA     As Byte = 2
Public Const ZGODITAK   As Byte = 3
Public Const ZID        As Byte = 4

Public Const POLJE      As Byte = 5

Dakle prvo smo deklarisali BitBlt funkciju, zatim smo naveli neke konstante, ono što je
važno to je da se vrednost konstanti CILJ, SMAJLI, KUTIJA, ZGODITAK i ZID poklapaju sa
indexima picImage objekata tj picImage(0) ima sliku cilja, picImage(1) sliku igraca, itd...

Sada da napišemo funkciju koja će određenu sliku da nacrta na određenom mestu u baferu:
Code:

' ova funkcija crta odredjenu slicicu na odredjeno mesto u bafer
Public Sub DrawImage(ImageId As Byte, X As Long, Y As Long)

    ' crtamo sliku u bafer
    BitBlt frmGame.picBuffer.hDC, X, Y, 40, 40, _
           frmGame.picImage(ImageId).hDC, 0, 0, vbSrcCopy

End Sub

Dakle funkcija drawImage prima tri parametra, to su id sličice (CILJ, SMAJLI, KUTIJA,
ZGODITAK ili ZID), x i y pozicija na koju treba da "nalepimo" sličicu.
Za kopiranje sličice iz picImage-a u picBuffer koristimo BitBlt funkciju, prvi parametar
koji prosleđujemo BitBlt funkciji je hDC (Device Context Handle) onog PictureBox-a na koji
želimo da crtamo, to je picBuffer u našem slučaju. Zatim prosleđujemo informacije o
poziciji i veličini bloka (BitBlock transfer) koji želimo da isrtamo na taj DC, to su
X, Y, 40 i 40 (40 i 40 su W i H bloka koji kopiramo), zatim prosleđujemo hDC objekta sa kojeg
kopiramo, to je određeni picImage (ImageId određuje koji picImage je u pitanju), kada prosledimo
hDC izvora (picImage-a) moramo da odredimo sa koje pozicije će se uzimati blok, u našem slučaju
to je 0, 0 (gornji levi ugao), a pošto je naš picImage 40x40 pixela ceo njegov sadržaj ćemo
prekopirati u bafer, i poslednji (ali ne i najneznačajniji) parametar je tip operacije koji
će se izvršiti nad pixelima u blokovima (blok na koji kopiramo i blok koji kopiramo), pošto
mi treba samo da prekopiramo sadržaj sa jednog mesta na drugo, koristimo vbSrcCopy konstantu koja
samo kopira blok sa izvornog DC-a na odredišni (ne menja pixele). BitBlt ima još dosta korisnih
operacija ali to nije tema ovog tutorijala tako da neću o tome govoriti (ako vas interesuje koje
su to još konstante možete u vb-u pritisnuti F2 i otkucati rasteropconstants)

Ok da bi videli da li radi ova naša drawImage funkcija moramo da napišemo još jednu koja će da
kopira ceo sadržaj bafera na picLevel, tj na ekran.
Analogno predhodnoj funkciji:
Code:

' ova funkcija kopira sadrzaj bafera na ekran
Public Sub Render()

    ' preslikavamo ceo bafer na ekran
    BitBlt frmGame.picLevel.hDC, 0, 0, frmGame.picLevel.ScaleWidth, _
           frmGame.picLevel.ScaleHeight, frmGame.picBuffer.hDC, 0, 0, vbSrcCopy

    frmGame.picBuffer.Cls ' cistimo sadrzaj bafera

     ' osvezavamo nas picturebox da bi se izmene pokazale na ekranu
    frmGame.picLevel.Refresh

End Sub

Dakle opet kopiramo blok sa jednog mesta (picBuffer) na drugo (picLevel) razlika je samo što je taj
blok sada veličine bafera odnosno nivoa (picBuffer i picLevel moraju imati identične dimenzije).
Kada prekopiramo sadržaj bafera na picLevel moramo očistiti sadržaj bafera (picBuffer.Cls) i osvežiti
sadržaj picLevel-a (picLevel.Refresh) da bi se novi frejm prikazao na ekranu (picLevel.Refresh je neophodan
iz razloga što je AutoRedraw property podešen na True).

Ok sada da testiramo kod! Otvorite kod frmGame-a i u desnom comboboxu (obratite pažnju da je u levom
selektovano Form) izaberite KeyDown događaj. Unesite sledeći kod:
Code:

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)

    DrawImage SMAJLI, 50, 50
    Render

End Sub

Pokrenite program i pritisnite bilo koji taster, pojaviće se smajli! Woo-hoo! Odlično za početak, zar ne?





Hmm, mislim da do sada nisam pominjao čuvanje fajlova! To je trebalo prvo da uradimo
Elem sad je dobar trenutak da sačuvamo sve što smo do sada uradili (valjalo bi da vam Ctrl+S postane "navika" )
Ovo je sav kod koji do sada imamo:

frmGame:
Code:

Option Explicit
'


Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)

    DrawImage SMAJLI, 50, 50
    Render

End Sub
'

Private Sub Form_Resize()

    picLevel.Move (Me.ScaleWidth - picLevel.Width) / 2, _
                  (Me.ScaleHeight - picLevel.Height) / 2

End Sub
'


modGame:
Code:

Option Explicit

Private Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long

Public Const CILJ       As Byte = 0
Public Const SMAJLI     As Byte = 1
Public Const KUTIJA     As Byte = 2
Public Const ZGODITAK   As Byte = 3
Public Const ZID        As Byte = 4

Public Const POLJE      As Byte = 5
'

' ova funkcija crta odredjenu slicicu na odredjeno mesto u bafer
Public Sub DrawImage(ImageId As Byte, X As Long, Y As Long)

    ' crtamo sliku u bafer
    BitBlt frmGame.picBuffer.hDC, X, Y, 40, 40, _
           frmGame.picImage(ImageId).hDC, 0, 0, vbSrcCopy

End Sub
'

' ova funkcija kopira sadrzaj bafera na ekran
Public Sub Render()

    ' preslikavamo ceo bafer na ekran
    BitBlt frmGame.picLevel.hDC, 0, 0, frmGame.picLevel.ScaleWidth, _
           frmGame.picLevel.ScaleHeight, frmGame.picBuffer.hDC, 0, 0, vbSrcCopy

    frmGame.picBuffer.Cls ' cistimo sadrzaj bafera

     ' osvezavamo nas picturebox da bi se izmene pokazale na ekranu
    frmGame.picLevel.Refresh

End Sub
'

Fajlove organizujte "po želji", ja volim da imam ovakvu strukturu fajlova:
Code:

[<ime projekta>]
    |
    +--- [src]
          |
          +--- [frm]        <-- ovde čuvam forme (*.frm)
          |
          +--- [bas]        <-- ovde čuvam module (*.bas)
          |
          +--- <ime projekta>.vbp  <-- projekat (*.vbp),  nalazi se u src

ali to je nebitno i stvar je ukusa, čuvajte fajlove kako se vama sviđa.
Čovekova dostignuća prevazilaze njegovu maštu.
Prikačeni fajlovi
02.11.2006. u 22:47 

Aleksandar Ružičić
Developer, Haragei Creative Solutions
Beograd - Čačak

Moderator
Član broj: 26939
Poruke: 1778
*.yu1.net.

Jabber: krcko@haragei.com
Sajt: krcko.haragei.org


Profil

icon Re: vbSokoban: Hajde da napravimo igru!02.11.2006. u 22:51
Pred nama je učitavanje nivoa iz fajlova i njihovo iscrtavanje!
Pre nego što se pozabavimo problematikom učitavanja fajlova moramo da vidimo kakve
ćemo to fajlove da koristimo, tj kakvog formata:

Pošto već postoji ogroman broj sokoban klonova i rimejkova, na internetu se
ustalio standardan format fajlova za opisivanje nivoa.
To su obični text fajlovi sa promenjenom ekstenzijom (u našem slučaju
ekstenzija će biti .sok). Nivo se opisuje pomoću sledećih karaktera:

Code:

<space> - prazno polje
      $ - kutija
      . - polje na koje treba postaviti kutiju ("tačka")
      * - kutija na jednoj od tačaka
      @ - početna pozicija igrača
      + - početna pozicija igrača na jednoj od tačaka
      # - zid


Primer:
Code:

######
#@   #####
# $ $  ..#
##########


Nivoi (fajlovi) se čitaju liniju po liniju i proces učitavanja se prekida
kada se naiđe na liniju koja sadrži bar jedan nedozvoljeni karakter
(nedozvoljeni karakteri su svi osim onih 7 gore navedenih). Što znači da
nivo mora da počne u prvoj liniji fajla! Isto tako znači da posle nivoa (ispod)
možete da stavite šta god hoćete; komentar, uputstva i td...

Napravite novi fajl i sačuvajte ga kao 01.sok, ubacite ovo u njega:
Code:

###########
#.#       #
#*#   #   #
#         #
#    ##$$##
###### +  #
     ######

Nivo:  01.sok
Autor: krcko

01.sok ubacite u levels folder koji se nalazi u istom folderu kao i vaš projekat (*.vbp)

Ovaj fajl ćemo koristiti za testiranje.

Pre nego što učitamo fajl moramo da vidimo gde ćemo da čuvamo učitane i obrađene podatke,
dodajte još jedan modul u projekat i nazovite ga modGame.
Prekucajte sledeće u njega:
Code:

Option Explicit

Public Type Pozicija

    X       As Long
    Y       As Long

End Type

Public nivo()       As Byte         ' matrica koja sadrzi podatke o nivou
Public igrac        As Pozicija     ' trenutna pozicija igraca
Public ukupnoKutija As Long         ' broj kutija u nivou
Public postavljenih As Long         ' broj kutija koje su postavljene
Public polja        As Long         ' broj ciljnih polja
Public duzina       As Long         ' velicina matrice "po horizontali"
Public sirina       As Long         ' velicina matrice po vertikali

Pored svake promenljive stoji komentar tako da se vidi za šta koja služi.

Ok evo funkcije za učitavanje nivoa:
Code:

' ova funkcija ucitava nivo iz fajla (vraca false ako dodje do greske)
Public Function UcitajNivo(fajl As String) As Boolean

    UcitajNivo = False

    ' brisemo predhodni nivo
    ReDim nivo(0, 0)
    duzina = 0
    sirina = 0

    ' pomeramo igraca van tabele (pomaze nam da ispitamo ispravnost nivoa)
    igrac.X = -1
    igrac.Y = -1

    ' resetujemo broj kutija i polja (i broj postavljenih kutija)
    ukupnoKutija = 0
    polja = 0 ' polja na koja treba staviti kutije
    postavljenih = 0

    Dim sok()   As String
    Dim X       As Long
    Dim Y       As Long

    sok = UcitajFajl(fajl) ' iscitavamo iz fajla sve sto mozemo da prepoznamo

    ' proveravamo da li je ispravan fajl, matrica ce imati 0 elementata ako nije
    If UBound(sok, 1) > 0 And UBound(sok, 2) > 0 Then

        ' ispravan je fajl
        duzina = UBound(sok, 1)
        sirina = UBound(sok, 2)

        ReDim nivo(1 To duzina, 1 To sirina)

        For X = 1 To duzina
            For Y = 1 To sirina

                Select Case sok(X, Y)

                    Case " ", "" ' prazno polje
                        nivo(X, Y) = POLJE

                    Case "#" ' zid
                        nivo(X, Y) = ZID

                    Case "$" ' kutija
                        nivo(X, Y) = KUTIJA

                        ukupnoKutija = ukupnoKutija + 1


                    Case "@" ' igrac
                        nivo(X, Y) = POLJE

                        If igrac.X > -1 Then Exit Function ' dupliran igrac

                        ' pozicioniramo igraca
                        igrac.X = X
                        igrac.Y = Y

                    Case "." ' mesto na koje treba postaviti kutiju ("tacka")
                        nivo(X, Y) = CILJ
                        polja = polja + 1

                    Case "*" ' kutija na jednoj od "tacaka"
                        nivo(X, Y) = ZGODITAK ' :)
                        polja = polja + 1
                        ukupnoKutija = ukupnoKutija + 1
                        postavljenih = postavljenih + 1

                    Case "+" ' igrac na jednoj od "tacaka"
                        nivo(X, Y) = CILJ

                        If igrac.X > -1 Then Exit Function ' dupliran igrac

                        igrac.X = X
                        igrac.Y = Y

                        polja = polja + 1

                End Select

            Next
        Next

        ' proveravamo validnost ucitanih podataka
        If (ukupnoKutija = polja) And igrac.X > -1 Then UcitajNivo = True

    End If

End Function

i potrebna UcitajFajl funkcija:
Code:

' ova funkcija "izvlaci" iz fajla sve sto moze da prepozna kao
' validni "level info", tj cita samo one karaktere koji su predvidjeni za
' opis nivoa (citanje se prekida kada se naidje na liniju koja sadrzi bar
' jedan neodgovarajuci karakter)
Private Function UcitajFajl(fajl As String) As String()

    ' ova funkcija vraca dvodimenzionalni niz (matricu)
    Dim ret()   As String

    Dim fn      As Integer

    Dim X       As Long
    Dim Y       As Long
    Dim W       As Long

    Dim line    As String


    ReDim ret(0 To 0, 0 To 0) ' isto sto i ReDim ret(0, 0) ali lepse :)

    fn = FreeFile
    Open fajl For Input As #fn

        While Not EOF(fn)

            Line Input #fn, line

            line = RTrim(line)

            If Not ispravniPodaci(line) Then

                UcitajFajl = ret
                Exit Function

            End If

             ' ova linija je duza od svih predhodnih
            If Len(line) > W Then W = Len(line)

            ' dodajemo novi red u matricu
            Y = UBound(ret, 2) + 1

            ReDimMatrix ret, W, Y

            ' i popunjavamo ga:
            For X = 1 To Len(line)

                ret(X, Y) = Mid(line, X, 1)

            Next

        Wend

    Close #fn

    UcitajFajl = ret

End Function

i funkcije potrebne za UcitajFajl:
Code:

' ova funkcija vraca true ako se u liniji nalaze samo podrzani karakteri
Private Function ispravniPodaci(linija As String) As Boolean

    Const dozvoljeniKarakteri = "# @.$+*"

    Dim i           As Integer
    Dim karakter    As String

    ispravniPodaci = False

    If Len(linija) = 0 Then Exit Function

    For i = 1 To Len(linija)

        karakter = Mid(linija, i, 1)

        If InStr(dozvoljeniKarakteri, karakter) < 1 Then Exit Function

    Next

    ispravniPodaci = True

End Function
'

' jedan od nacina da se zaobidje vb-ova ogranicenost po pitanju menjanja
' velicine (2D) matrice sa cuvanjem podataka (ReDim Preserve)
Private Sub ReDimMatrix(ByRef source() As String, W As Long, H As Long)

    Dim ret()   As String
    Dim X       As Long
    Dim Y       As Long

    ReDim ret(W, H)

    For X = 1 To UBound(source, 1)
        For Y = 1 To UBound(source, 2)
            ret(X, Y) = source(X, Y)
        Next
    Next

    source = ret

End Sub

Uh, ovde ima mnogo koda! Hajdemo polako od početka:

Funckija UcitajNivo prima jedan parametar a to je putanja do fajla koji treba da se učita,
ukoliko je fajl ispravan funkcija će da vrati True. Prvo što UcitajNivo radi je brisanje
(resetovanje) podataka o predhodnom nivou, zatim poziva UcitajFajl funkciju koja sadržaj
fajla koji može da prepozna kao validan vraća u obliku dvodimenzinalne matrice string tipa.
Recimo da imamo ovakav fajl:
Code:

######
#@   #####
# $ $  ..#
##########

UcitajFajl će za ovaj fajl da vrati sledeću matricu (predstavljena kao tabela):
Code:

  1   2   3   4   5   6   7   8   9   10
+---+---+---+---+---+---+---+---+---+---+
| # | # | # | # | # | # |   |   |   |   | 1
+---+---+---+---+---+---+---+---+---+---+
| # | @ |   |   |   | # | # | # | # | # | 2
+---+---+---+---+---+---+---+---+---+---+
| # |   | $ |   | $ |   |   | . | . | # | 3
+---+---+---+---+---+---+---+---+---+---+
| # | # | # | # | # | # | # | # | # | # | 4
+---+---+---+---+---+---+---+---+---+---+

dakle to radi UcitajFajl, a kako to radi o tome ćemo kasnije.

Kada dobije rezultat od UcitajFajl, UcitajNivo funkcija prvo proverava ispravnost podataka
koje je vratila UcitajFajl, tj proverava UBound (Upper Bound, gornja granica) obe dimenzije
i ukoliko je bar jedna manja od 1 vraća false.
Zatim podešava promenljive duzina i sirina, i koristeći njih redimenzioniše (širi) niz nivo u
koji ćemo da stavimo podatke o nivou. U dvostrukoj For..Next petlji se pomoću Select Case
komande podešavaju svi parametri nivoa. Pri tome se vodi računa da se igrač ne definiše dva
puta.
Po izlasku iz obe petlje (kada su svi podaci uspešno parsirani) vrši se finalna provera
ispravnosti nivoa, tj proverava se da broj kutija odgovara broju ciljnih mesta (tačaka) i da
je igrač postavljen (ukoliko se u fajlu nije nalazio ni @ ni + karakter, koji označava igrača
onda će igrac.X biti jednak -1).

Već smo objasnili šta radi UcitajFajl, sada da vidimo kako to radi:
prvo se resetuje ret matrica na (0,0) onda se fajl otvori For Input i isčitava se
liniju po liniju pomoću While petlje i Line Input komande. Liniju iz fajla učitavamo u line
promenljivu. Odmah po učitavanju linije (posle Line Input) uklanjamo sve razmake sa desne strane
tj pozivamo RTrim (ne koristimo Trim jer ne želimo da diramo razmake na početku linije) za slučaj
da je neko ostavio jedan (ili više) razmak viška na kraju linije.
Zatim se proverava ispravnost linije, tj poziva se ispravniPodaci funkcija koja će da vrati True
samo ako se u liniji nalaze samo dozvoljeni karakteri, ukoliko je bar jedan karakter nedozvoljen
izlazi se iz funkcije i vraća se ono što je već učitano.
Posle provere (to znači da linija sadrži ispravne podatke) se upoređuje dužina linije sa predhodno
najdužom linijom (dužina najduže linije se čuva u W promenljivoj), i ukoliko je trenutna linija
duža od svih predhodnih onda se podešava W promenljiva. Zatim se dodaje novi red u matricu, to
radimo pomoću Ubound (da utvrdimo trenutni broj redova, da bi uvećali taj broj za 1) i ReDimMatrix
funkcije (više o njoj nešto kasnije). Kada smo dodali red ostalo je samo da ga popunimo sa karakterima
iz line promenljive.

ispravniPodaci je veoma jednostavna funkcija i njen zadatak je da potvrdi ispravnost prosleđene joj
linije. To radi tako što proverava svaki karakter linije upoređujući ga sa dozvoljenim karakterima.

ReDimMatrix je "workaround" funkcija za nemogućnost VB-a da promeni veličinu matrice
(višedimenzionalnih nizova) a da pri tom sačuva podatke koji se u njoj nalaze (ReDim Preserve).
Sa ReDim Preserve možemo samo da menjamo veličinu poslednje dimenzije u matrici, tj ako imate
ovakvu matricu:
Code:

Dim matrica() As Byte

ReDim matrica(2, 6)

dakle matrica koja ima 3 kolone (0 To 2) i 7 redova (0 To 6), ako želimo da promenimo broj
kolona:
Code:

ReDim Preserve matrica(3, 6)

dobićemo grešku, ali zato ako hoćemo da promenimo broj redova:
Code:

ReDim Preserve matrica(2, 25)

neće biti problema. Dakle ReDimMatrix nam omogućava da zaobiđemo ovaj nedostatak, i to radi na
veoma jednostavan način, prvo proširi ret matricu (koju ćemo da vratimo) do željene veličine
i zatim kroz dvostruku For..Next petlju iskopira podatke iz source matrice u ret.

Ok to bi bilo to! Pre nego što proverimo kako ovaj kod radi sačuvajte modGame!
Evo šta se trenutno nalazi u njemu (u modGame modulu):
Code:

Option Explicit

Public Type Pozicija

    X       As Long
    Y       As Long

End Type

Public nivo()       As Byte         ' matrica koja sadrzi podatke o nivou
Public igrac        As Pozicija     ' trenutna pozicija igraca
Public ukupnoKutija As Long         ' broj kutija u nivou
Public postavljenih As Long         ' broj kutija koje su postavljene
Public polja        As Long         ' broj ciljnih polja
Public duzina       As Long         ' velicina matrice "po horizontali"
Public sirina       As Long         ' velicina matrice po vertikali
'

' ova funkcija ucitava nivo iz fajla (vraca false ako dodje do greske)
Public Function UcitajNivo(fajl As String) As Boolean

    UcitajNivo = False

    ' brisemo predhodni nivo
    ReDim nivo(0, 0)
    duzina = 0
    sirina = 0

    ' pomeramo igraca van tabele (pomaze nam da ispitamo ispravnost nivoa)
    igrac.X = -1
    igrac.Y = -1

    ' resetujemo broj kutija i polja (i broj postavljenih kutija)
    ukupnoKutija = 0
    polja = 0 ' polja na koja treba staviti kutije
    postavljenih = 0

    Dim sok()   As String
    Dim X       As Long
    Dim Y       As Long

    sok = UcitajFajl(fajl) ' iscitavamo iz fajla sve sto mozemo da prepoznamo

    ' proveravamo da li je ispravan fajl, matrica ce imati 0 elementata ako nije
    If UBound(sok, 1) > 0 And UBound(sok, 2) > 0 Then

        ' ispravan je fajl
        duzina = UBound(sok, 1)
        sirina = UBound(sok, 2)

        ReDim nivo(1 To duzina, 1 To sirina)

        For X = 1 To duzina
            For Y = 1 To sirina

                Select Case sok(X, Y)

                    Case " ", "" ' prazno polje
                        nivo(X, Y) = POLJE

                    Case "#" ' zid
                        nivo(X, Y) = ZID

                    Case "$" ' kutija
                        nivo(X, Y) = KUTIJA

                        ukupnoKutija = ukupnoKutija + 1


                    Case "@" ' igrac
                        nivo(X, Y) = POLJE

                        If igrac.X > -1 Then Exit Function ' dupliran igrac

                        ' pozicioniramo igraca
                        igrac.X = X
                        igrac.Y = Y

                    Case "." ' mesto na koje treba postaviti kutiju ("tacka")
                        nivo(X, Y) = CILJ
                        polja = polja + 1

                    Case "*" ' kutija na jednoj od "tacaka"
                        nivo(X, Y) = ZGODITAK ' :)
                        polja = polja + 1
                        ukupnoKutija = ukupnoKutija + 1
                        postavljenih = postavljenih + 1

                    Case "+" ' igrac na jednoj od "tacaka"
                        nivo(X, Y) = CILJ

                        If igrac.X > -1 Then Exit Function ' dupliran igrac

                        igrac.X = X
                        igrac.Y = Y

                        polja = polja + 1

                End Select

            Next
        Next

        ' proveravamo validnost ucitanih podataka
        If (ukupnoKutija = polja) And igrac.X > -1 Then UcitajNivo = True

    End If

End Function
'

' ova funkcija "izvlaci" iz fajla sve sto moze da prepozna kao
' validni "level info", tj cita samo one karaktere koji su predvidjeni za
' opis nivoa (citanje se prekida kada se naidje na liniju koja sadrzi bar
' jedan neodgovarajuci karakter)
Private Function UcitajFajl(fajl As String) As String()

    ' ova funkcija vraca dvodimenzionalni niz (matricu)
    Dim ret()   As String

    Dim fn      As Integer

    Dim X       As Long
    Dim Y       As Long
    Dim W       As Long

    Dim line    As String


    ReDim ret(0 To 0, 0 To 0) ' isto sto i ReDim ret(0, 0) ali lepse :)

    fn = FreeFile
    Open fajl For Input As #fn

        While Not EOF(fn)

            Line Input #fn, line

            line = RTrim(line)

            If Not ispravniPodaci(line) Then

                UcitajFajl = ret
                Exit Function

            End If

             ' ova linija je duza od svih predhodnih
            If Len(line) > W Then W = Len(line)

            ' dodajemo novi red u matricu
            Y = UBound(ret, 2) + 1

            ReDimMatrix ret, W, Y

            ' i popunjavamo ga:
            For X = 1 To Len(line)

                ret(X, Y) = Mid(line, X, 1)

            Next

        Wend

    Close #fn

    UcitajFajl = ret

End Function
'

' ova funkcija vraca true ako se u liniji nalaze samo podrzani karakteri
Private Function ispravniPodaci(linija As String) As Boolean

    Const dozvoljeniKarakteri = "# @.$+*"

    Dim i           As Integer
    Dim karakter    As String

    ispravniPodaci = False

    If Len(linija) = 0 Then Exit Function

    For i = 1 To Len(linija)

        karakter = Mid(linija, i, 1)

        If InStr(dozvoljeniKarakteri, karakter) < 1 Then Exit Function

    Next

    ispravniPodaci = True

End Function
'

' jedan od nacina da se zaobidje vb-ova ogranicenost po pitanju menjanja
' velicine (2D) matrice sa cuvanjem podataka (ReDim Preserve)
Private Sub ReDimMatrix(ByRef source() As String, W As Long, H As Long)

    Dim ret()   As String
    Dim X       As Long
    Dim Y       As Long

    ReDim ret(W, H)

    For X = 1 To UBound(source, 1)
        For Y = 1 To UBound(source, 2)
            ret(X, Y) = source(X, Y)
        Next
    Next

    source = ret

End Sub
'

Čovekova dostignuća prevazilaze njegovu maštu.
02.11.2006. u 22:51 

Aleksandar Ružičić
Developer, Haragei Creative Solutions
Beograd - Čačak

Moderator
Član broj: 26939
Poruke: 1778
*.yu1.net.

Jabber: krcko@haragei.com
Sajt: krcko.haragei.org


Profil

icon Re: vbSokoban: Hajde da napravimo igru!02.11.2006. u 22:52
Sada otvorite CodeView frmGame-a i izaberite Load iz desnog comboboxa (u levom je Form selektovano)
Unesite sledeći kod:
Code:

Private Sub Form_Load()

    Debug.Print UcitajNivo("E:\Projects\vbSokoban\src\levels\01.sok")

End Sub

E:\Projects\vbSokoban\src\levels\01.sok zamenite sa apsolutnom putanjom do vašeg 01.sok fajla.

Pokrenite program i u Immediate prozoru će vam se ispisati True, ništa više... ali to je znak da
je nivo (01.sok) uspešno učitan! Pa dobro, učitan je... ali gde je? E sada ćemo da odgovorimo
na to pitanje.
Dodajte sledeći kod u modDraw:
Code:

' ova funkcija crta trenutni izgled nivoa i poziciju igraca
Public Sub DrawFrame()

    Dim X   As Long
    Dim Y   As Long

    For X = 1 To duzina
        For Y = 1 To sirina

            If nivo(X, Y) <> POLJE Then ' prazna polja necemo da crtamo :)

                DrawImage nivo(X, Y), (X - 1) * 40, (Y - 1) * 40

            End If

        Next
    Next

    ' crtamo igraca
    DrawImage SMAJLI, (igrac.X - 1) * 40, (igrac.Y - 1) * 40

    Render ' prikazujemo novi frejm

End Sub

DrawFrame funkcija (kao što ste i pretpostavili) crta frejm na ekran. Pomoću dvostruke For..Next petlje
se iscrtava nivo (pri tome vodimo računa da ne crtamo prazna polja), ali ono što vam možda nije jasno to
je pozicioniranje te sličice, tj ovo (X - 1) * 40 i (Y - 1) * 40. Naša matrica je bazirana na osnovi 1
(1 based) tj njene granice (bounds) su od 1 do duzina za prvu dimenziju i od 1 do sirina za drugu
dimenziju i zbog toga moramo oduzeti 1 od vrednosti X i Y pre nego što ih pomnožimo sa 40 (40 je dužina
i širina jednog polja u pixelima). Kada izađemo iz petlji iscrtamo igrača i na kraju prikažemo frejm
na ekranu.
Ok, hajde da vidimo kako to radi. Otvorite frmGame i dodajte poziv ka DrawFrame u Form_Load funkciji.
Form_Load treba da vam izgleda ovako:
Code:

Private Sub Form_Load()

    UcitajNivo "E:\Projects\vbSokoban\src\levels\01.sok"

    DrawFrame

End Sub

(uklonio sam Debug.Print iz razloga što smo se već uverili u ispravnost funkcije za učitavanje)

Pokrenite program, dobićete nešto slično ovome:



dakle izgleda da se sve lepo iscrtava, samo što se ne vidi ceo nivo.
Otvorite modGame i dodajte sledeću funkciju:
Code:

Public Sub PodesiVelicinuTable()

    ' sirimo picLevel i picBuffer u zavisnosti od velicine nivoa
    frmGame.picLevel.Width = 40 * duzina
    frmGame.picLevel.Height = 40 * sirina

    frmGame.picBuffer.Width = 40 * duzina
    frmGame.picBuffer.Height = 40 * sirina

    Call frmGame.Form_Resize ' centriramo picLevel

End Sub

dakle kao što vidite, PodesiVelicinuTable podešava veličinu picLevel-a i picBuffer-a, i ona bi
trebala da se poziva po uspešnom učitavanju nivoa, za početak dodajte je u Form_Load (pre poziva
DrawFrame funkciji):
Code:

Private Sub Form_Load()

    UcitajNivo "E:\Projects\vbSokoban\src\levels\01.sok"

    PodesiVelicinuTable

    DrawFrame

End Sub

Pre nego što pokrenete program morate napraviti jednu malu izmenu u kodu frmGame-a.
U PodesiVelicinuTable pozivamo Form_Resize koji je deklarisan kao Private Sub, ukoliko
pokrenete sada program dobićete "Method or data member not found" grešku. Da bi rešili
ovo samo promenite Private u Public kod Form_Resize, dakle evo kako treba da vam izgleda ceo
kod u frmGame:
Code:

Option Explicit
'


Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)

    'DrawImage SMAJLI, 50, 50
    'Render

End Sub
'

Private Sub Form_Load()

    UcitajNivo "E:\Projects\vbSokoban\src\levels\01.sok"

    PodesiVelicinuTable

    DrawFrame

End Sub
'

Public Sub Form_Resize()

    picLevel.Move (Me.ScaleWidth - picLevel.Width) / 2, _
                  (Me.ScaleHeight - picLevel.Height) / 2

End Sub

(ja sam komentovao kod u Form_KeyDown jer nam više ne treba, ali nemojte ga brisati još uvek)

Pokrenite program i... voila:



Super izgleda, zar ne?

Samo što se meni ne sviđa ova "default" boja pozadine prozora (tj forme), ja ću da je promenim
(BackColor frmGame-a) u neku drugu, recimo u &H00808080& (depresivno sivu ), vi odaberite boju
koja se vama sviđa (ili nemojte da menjate boju uopšte, na vama je da odlučite)...
Čovekova dostignuća prevazilaze njegovu maštu.
Prikačeni fajlovi
02.11.2006. u 22:52 

Aleksandar Ružičić
Developer, Haragei Creative Solutions
Beograd - Čačak

Moderator
Član broj: 26939
Poruke: 1778
*.yu1.net.

Jabber: krcko@haragei.com
Sajt: krcko.haragei.org


Profil

icon Re: vbSokoban: Hajde da napravimo igru!02.11.2006. u 22:56
Sada je pred nama najteži deo izrade ove igre: kretanje igrača i pomeranje kutija.
Ali videćete da je to samo "gomila" If..Then blokova, za početak dodajte sledeći kod u Form_KeyDown:
Code:

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)

    Select Case KeyCode

        Case vbKeyLeft
            Debug.Print "Levo Hugo, levo!"

        Case vbKeyRight
            Debug.Print "Desno Hugo, desno!"

        Case vbKeyUp
            Debug.Print "Gore Hugo, gore!"

        Case vbKeyDown
            Debug.Print "Dole Hugo, dole!"

        Case vbKeyR
            Debug.Print "reset"

        Case vbKeyZ
            If Shift = vbCtrlMask Then Debug.Print "undo"

        Case Else
            Exit Sub ' nije jedan od podrzanih tastera

    End Select

End Sub

Pokrenite program i pritiskajte "podržane" tastere (to su "strelice", R i Ctrl+Z kombinacija), dobićete
odgovarajuće poruke. Dakle na taj način ćemo da znamo koji taster je korisnik pritisnuo.
Krenućemo sa tasterima za kretanje (R i Ctrl+Z ćemo nešto kasnije implementirati):
Code:

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)

    Dim dX  As Integer
    Dim dY  As Integer

    Select Case KeyCode

        Case vbKeyLeft
            dX = -1

        Case vbKeyRight
            dX = 1

        Case vbKeyUp
            dY = -1

        Case vbKeyDown
            dY = 1

        Case vbKeyR
            Debug.Print "reset"

        Case vbKeyZ
            If Shift = vbCtrlMask Then Debug.Print "undo"

        Case Else
            Exit Sub ' nije jedan od podrzanih tastera

    End Select

    Dim newX    As Long
    Dim newY    As Long

    newX = igrac.X + dX
    newY = igrac.Y + dY

End Sub

Uveli smo prvo dve nove promenljive dX i dY koje predstavljaju pomeranje (d kao delta) po
horizontali odnosno po vertikali. Kada je dX -1 onda se pomeramo u levo, kada je 1 onda u desno
a kada je dX jednako 0 onda nema pomeranja po x osi, analogno važi i za dY.
Kada dodelimo vrednost promenljivama dX i dY (njihova vrednost zavisi od pritisnutog tastera)
uvodimo još dve promenljive: newX i newY koje predstavljaju koordinate polja na koje bi trebalo
da pomerimo igrača ("željeno polje").
Sada treba da proverimo da li je željeno polje (newX, newY) unutar dimenzija lavirinta:
Code:

If (newX > 0 And newX <= duzina) And (newY > 0 And newY <= sirina) Then

End If

zatim proverimo da li se na željenom mestu nalazi zid (jer kretanje po zidovima nije dozvoljeno):
Code:

If nivo(newX, newY) <> ZID Then

End If

zatim proveravamo da li se na željenom polju nalazi kutija:
Code:

If (nivo(newX, newY) = KUTIJA) Or (nivo(newX, newY) = ZGODITAK) Then

Else

End If

Ukoliko nema kutije (Else) pomeramo igrača jer je željeno polje prazno.

Dakle evo kako do sada izgleda kada se sve sklopi:
Code:

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)

    Dim dX  As Integer
    Dim dY  As Integer

    Select Case KeyCode

        Case vbKeyLeft
            dX = -1

        Case vbKeyRight
            dX = 1

        Case vbKeyUp
            dY = -1

        Case vbKeyDown
            dY = 1

        Case vbKeyR
            Debug.Print "reset"

        Case vbKeyZ
            If Shift = vbCtrlMask Then Debug.Print "undo"

        Case Else
            Exit Sub ' nije jedan od podrzanih tastera

    End Select

    Dim newX    As Long
    Dim newY    As Long

    newX = igrac.X + dX
    newY = igrac.Y + dY

    If (newX > 0 And newX <= duzina) And (newY > 0 And newY <= sirina) Then

        If nivo(newX, newY) <> ZID Then

            If (nivo(newX, newY) = KUTIJA) Or (nivo(newX, newY) = ZGODITAK) Then

        &