Mulla on nyt vireillä kaiken kaikkiaan kolme peliä.Tämä johon tarvitsen hiukan neuvoa on ainoa toimintapeli.Eli arkanoid-klooni.Kuinka olisi viisainta järjestää törmäys mailan ja pallon kanssa?Käyttääkkö värejä mailassa ja kun pallo osuu siihen väriin niin laskettasiin X-erotus mailan ja pallon sijainneista jonka mukaan sitten singotaan pallo takaisin radalle?
Vaiko sitten ihan vertailemalla sijainteja otettaisiin selvää törmäyksestä?
Ja muita törmäyksiä ajatellen ajattelin että pallossa olisi neljä törmäysspottia.Ylhäällä,alhaalla,vasemmalla ja oikealla. Olisiko siinä järkeä?
Ajattelin että jos pallon nopeus on vaikka 6 niin tehdään silmukka 0-6 ja siirretään palloa pixeli kerrallaan ja tehdään törmäystarkistuksia mutta graafisesti peli päivittyisi vasta silmukan jälkeen.....Kertokaapa onko tässä jutussa päätä tahi häntää......:D
Turhaan sitä tarkistusta mistään pikseleistä on katsoa. Pidät jossain tallessa mailan sijainnin ja pallon sijainnin niin eiköhän se hoidu jo sillä?
Väännetään nyt vielä rautalangasta: jos pallon y-sijainti on sama kuin mailan y-sijainti ja pallon x-sijainti on mailan x-sijainnin ja mailan pituuden välillä, niin pallo osuu mailaan.
Aika simppeliä. Ja kun mailasta ja pallosta tulee monivärisiä niin se olisi pixelitasolla mennyt aika monimutkaiseksi. Kiitos punppis.
DumTom kirjoitti:
Vaiko sitten ihan vertailemalla sijainteja otettaisiin selvää törmäyksestä?
Yksinkertaistettuna kyllä.
DumTom kirjoitti:
Ja muita törmäyksiä ajatellen ajattelin että pallossa olisi neljä törmäysspottia.
Yksi keskellä oleva riittää mainiosti. Siitä pallon säteen päässä olevat pisteet aiheuttavat törmäyksen. Mailasta sen sijaan olisi hyvä tietää mihin sivuun pallo osuu.
DumTom kirjoitti:
Ajattelin että jos pallon nopeus on vaikka 6 niin tehdään silmukka 0-6 ja siirretään palloa pixeli kerrallaan ja tehdään törmäystarkistuksia mutta graafisesti peli päivittyisi vasta silmukan jälkeen.....Kertokaapa onko tässä jutussa päätä tahi häntää......:D
On siinä vähän ajatusta. Törmäys havaitaan, jos mailan ja pallon nopeudet ovat pienemmät kuin niiden halkaisijat. Eli pikseli kerrallaan liikuttelu ei ole tarpeen, vaan voit laskea liikkuvien objektien nopeudesta ja koosta sopivan askelvälin tarkastukselle.
Veikkaan, ettei +25fps:n (nykykoneilla tuo fps-arvo on jo useita satoja) nopeudessa kukaan edes huomaa, vaikka törmäyksen vain feikkaisi katsomalla vain, että onko pallon y-arvo siirtynyt viime kierroksella mailan yläpuolelta alapuolelle, ja jos on, niin onko pallon x-arvo mailan molempien sivulla olevien "reunojen" välissä.
Mielenkiintoisia heittoja. Sain juuri tehtyä seinistä törmäilyn.
Jos maila on vaikka 64 pixeliä leveä niin se täytyy jakaa kahteen 32 pixelin osioon ja uusi lentokulma lasketaan 0-32 ja 32-64 kohtien avulla.
Olisiko kellään vinkkiä miten tällä menetelmällä saataisiin normaalin näköisiä ratoja? Siniä,cosinia vai tangettia vai mitä ydinfysiikkaa. Latasin yhden lähdekoodin jostain ikivanhasta kloonista mutta en oikein sisäistänyt ajatusta.Tällä hetkellä joudun keksimään kaavan itse ja ainakaan vielä en ole löytänyt aitoa tunnelmaa.....
Mitä tarkoitat normaalin näköisellä? Jos tarkoitat Arkanoidin näköistä, niin muistaakseni siinä vain yksinkertaisesti säädetään kulmaa sen mukaan mihin kohtaan mailaa pallo osuu. Eli jos pallo osuu keskelle mailaa, niin se räväytetään suoraan peiliheijastuksena matkaan. Jos se osuu vasemalle, niin kallistetaan heijastuskulmaa vasemmalle. Ja vastaavasti sama oikealle.
Lähtökulman laskemiseen tarvitset siis tulokulman ja osumakohdan. Molemmat helppoja laskea jopa ilman raketti-insinöörin pätevyyttä. Kaikki tavittava matematiikka löytyy Putkan matikkaoppaasta.
Edit. Itseasiassa Arkanoidissa kulma taitaakin riippua ainoastaan osumakohdasta. Eli pallo lyödään suoraan osumakohdan määrittämässä kulmassa, ilman että tulokulmalla olisi siihen mitään vaikutusta. Mutta kokeile eri kaavoilla ja testaa mikä tuntuu parhaalta.
Sain ladattua aidon arkanoid ykkösen. Pitää siitä apinoida tota hommaa. Näyttää tosiaan siltä että lähtökulma riippuu vain osumakohdasta. Keskikohdasta tosin lähtee peiliheijastuma. tulokulma+=360;while(tulokulma>359) tulokulma-=360; noinko se peiliheijastuma lasketaan?
DumTom kirjoitti:
tulokulma+=360;while(tulokulma>359) tulokulma-=360; noinko se peiliheijastuma lasketaan?
Tuolla rajoitetaan luku välille 0-360.
DumTom kirjoitti:
tulokulma+=360;while(tulokulma>359) tulokulma-=360; noinko se peiliheijastuma lasketaan?
Peiliheijastuksen laskeminen opetetaan matematiikkaoppaassa: https://www.ohjelmointiputka.net/oppaat/opas.
suunta=suunta+180;while(suunta>359) suunta-=360;
Tolla kaavalla mielestäni toimii peiliheijastus ilman vektorimatematiikkaa,tietty välikulmia ei tule vaan 0-359.????
Kaavasi ei tee peiliheijastusta, vaan kimpoaa pallon takaisin siihen suuntaan mistä se on tullutkin. Se lienee kuitenkin, mitä hait takaakin?
Kyllä vain sillä näin arkanoidissa. Peiliheijastuma on sitten suunta+=360;while(suunta>359) suunta-=360;
DumTom kirjoitti:
Peiliheijastuma on sitten suunta+=360;while(suunta>359) suunta-=360;
Tuohan ei tee yhtään mitään. Lisäät 360 ja otat sen samantien pois.
Muistaakseni kulmilla laskettuna peiliheijastuma tuli näin:
2 * N - S + 180
Jossa
N = Törmäys pinnan normaalikulma.
S = Pallon vanha kulkusuunta.
Laitetaan nyt vielä versio vektoreillakin, tämä freepascalia:
function Reflect(const rayDir, wallNormal: TVector2f): TVector2f; var temp: single; begin temp:=2*(rayDir.x*wallNormal.x+rayDir.y*wallNormal.y); result.x:=rayDir.x-wallNormal.x*temp; result.y:=rayDir.y-wallNormal.y*temp; end;
Tuota saattaa tarvita siinä vaiheessa kun pallon pitää kimpoilla pelialueen palikoista. Pelaajan mailalla on ihan eri kaava.
Kuinka selvitän wallNormalin helposti? Törmäysvaihtoehtoja on alhaalta,ylhäältä ja sivuilta. Käytän koko pallon sädettä törmäystarkistuksessa. Laskin arvot ja tallensin ne lookup taulukkoon.
Matematiikkaoppaassa kerrotaan, miten muodostetaan normaalivektori. Kaksiulotteisessa tapauksessa (seinän suunta)vektorin (a,b) eräs normaalivektori on (-b,a).
Kolmiulotteisessa tapauksessa tarvitset kaksi tason erisuuntaista vektoria, jolloin niiden ristitulona saat niitä molempia vastaan kohtisuoran vektorin joka on samalla tason normaalivektori.
En ymmärtänyt. Palikka on suorakaiteen muotoinen ja siinä on kaksi eri suunnassa olevaa seinää eli ei radiaaneina 270 ylös ja 0 vaakataso.
Ei huvita opiskella vektoreita.Kaikki muu törmäily toimii ilman vektoreita.
Ja miksi palikoista kimpoilu olisi erilaista kuin muu törmäily?
Yrittäkää kertoa se ilman oppaita.Kiitos etukäteen.
lainaus:
eri suunnassa olevaa seinää eli ei radiaaneina 270 ylös ja 0 vaakataso
Tarkoitat varmaan asteina. Toisaalta voisit piirtää kuvan...
Olet ilmeisesti kaksiulotteisessa avaruudessa. Kuvaamassasi tilanteessa toisen seinän normaali on i ja toisen j (tässä siis lihavointi tarkoittaa samaa kuin vektorimerkki eli se viiva ylhäällä. ja i on siis x-koordinaatin suuntainen yksikkövektori ja j y:n suuntainen yksikkövektori)
Jos et halua vektorina, niin suorina ne on: y=0 ja x=0
Peiliheijastus pelkillä kulmilla tulee pikaisesti ajatellen kaavasta 180 miinus kulma plus tarvittaessa 180 riippuen siitä, osoittaako kulma 0 pois törmäyspinnasta vai pinnan suuntaisesti.
Kiitos metabolix. Ton tajuan ja mulla on keino tai ajatus jolla toteutan tuon......
Kyselen taas lisää kun homma etenee.
Ajattelin luoda ruudun kokoisen lookup taulukoston.
Eli jokaisen palikkapaikan ääriviivat tallennettaisiin muistiin ja sitten vaikka 8kpl 45 asteen välein olevia tarkistuspisteitä palloon.
Näin tietäisi mikä palikka on kyseessä BrickNumber[800][600] ja tarkistuspisteen mukaan sitten jompaakumpaa metabolixin kimpoilukaavoista...
Vai välähtäisikö jollain parempaa tapaa ottaa palikkaan osumisesta tieto?
Tuo tapa toimii kyllä, mutta jos tiedät palikoiden leveyden ja korkeuden, riittää tallentaa vain palikoiden koordinaatit. Voit sitten laskea palikan koordinaattien ja koon avulla, onko tarkistuspiste palikan sisällä.
Olisi myös mahdollista laskea pikselintarkasti törmäävätkö pallo ja palikka, mutta tuo tapa riittänee tarpeisiisi. :)
Tässäpä tämän hetkinen pallon liikutus ja törmäilykoodi.
Välillä tulee aika yllättäviä kimpoamisia.
Pitäisikö palikan reunaan osuminen katsoa palikan koordinaateista eikä käyttämistäni pallon vasen ja oikea hotspoteista?
void MoveBalls(void) { static char notnow=0; static short maila64[64]={10,10,10, 9,9,9, 8,8,8, 7,7,7, 6,6,6, 5,5,5, 4,4,4 ,3,3,3 ,2,2,2,2, 1,1,1,1,1,1,1,1,1,1,1, 11,11,11,11, 12,12,12, 13,13,13, 14,14,14, 15,15,15, 16,16,16 ,17,17,17 ,18,18,18}; for (short s=0;s<6;s++) // NOPEUS { for (short t=0;t<50;t++) { if (B[t].GetOn()==1) { if (B[t].GetLocked()==0) { if ((short)B[t].GetY()>592) // POIS ALAREUNASTA { AlustaPallotJaMaila(); UpdateGame(1); return; } float vx=B[t].GetX(),vy=B[t].GetY(); B[t].SetEX(vx);B[t].SetEY(vy);// ENNEN TÖRMÄYSTÄ OLEVAT KOORDINAATIT short suu=B[t].GetSuunta(); float ksuu=deg2rad(suu); vx+=cos(ksuu); vy+=sin(ksuu); B[t].SetX(vx); B[t].SetY(vy); CheckPalikkaHits(t); if (vy<32) { suu=360-suu; B[t].SetSuunta(suu); B[t].SetX(B[t].GetEX()); B[t].SetY(B[t].GetEY()); G.SetFU(5); } if (vx<32 || vx>762) { if (vx>762) G.SetFR(5);else G.SetFL(5); suu=180-suu;if (suu<0) suu+=360; if (suu==180) suu=190; if (suu==0) suu=350; B[t].SetSuunta(suu); B[t].SetX(B[t].GetEX()); B[t].SetY(B[t].GetEY()); } if (vy>M.GetY()-1 && vy<M.GetY()+8 && vx>M.GetX()-1 && vx<M.GetX()+M.GetLeveys()+1) { B[t].SetX(B[t].GetEX()); B[t].SetY(B[t].GetEY()); short kohta=B[t].GetX()-M.GetX(); if (M.GetMagnet()==1) {B[0].SetXero(kohta);B[t].SetLocked(2500);notnow=1;} KOHTA=kohta; if (maila64[kohta]!=1) { suu=suu+180;while(suu>359) suu-=360; B[t].SetSuunta(suu); short AngleNumber=maila64[kohta]; PKOHTA=AngleNumber; switch(AngleNumber) { case 2: suu= 265; break; case 3: suu=259; break; case 4: suu=252; break; case 5: suu=245; break; case 6: suu=237; break; case 7: suu=229; break; case 8: suu=221; break; case 9: suu=213; break; case 10: suu=205; break; case 11: suu=279; break; case 12: suu=287; break; case 13: suu=295; break; case 14: suu=303; break; case 15: suu=311; break; case 16:; suu=319; break; case 17: suu=327; break; case 18: suu=335; } //suu=360-suu; B[t].SetSuunta(suu); } if (maila64[kohta]==1) { suu=suu+180;while(suu>359) suu-=360; B[t].SetSuunta(suu); } } } } if (notnow==0 && B[t].GetLocked()>0) {B[t].SetY(555);B[t].SetX(M.GetX()+B[t].GetXero());B[t].DecLocked();} } } notnow=0; SUUNTA=B[0].GetSuunta(); } void CheckPalikkaHits(short t) { for (t=0;t<4;t++) // neljä osumakohtaa pallossa { char kt; short bx=B[t].GetX(),by=B[t].GetY(); switch(t) { case 0: by-=5; kt=0; break; case 1: bx+=5; kt=1; break; case 2: by+=5; kt=0; break; case 3: bx-=5; kt=1; break; } short gx,gy,x=0,y=0; for (y=0;y<15;y++) { for (x=0;x<15;x++) { short tiili=A.GetTile(x,y); if (tiili>0) { gy=26*y+34; gx=48*x+40; if (bx>gx-1 && bx<gx+47 && by>gy-1 && by<gy+25) { A.SetTile(x,y,0); short suu=B[t].GetSuunta(); if (kt==0) suu=180-suu+180; //if (kt==0) suu=360-suu; if (kt==1) suu=180-suu; while(suu<0) suu+=360; while(suu>359) suu-=360; B[t].SetSuunta(suu); B[t].SetX(B[t].GetEX()); B[t].SetY(B[t].GetEY()); goto ulos; } } } } } ulos:; }
Tossa pelattava versio.
DumTom kirjoitti:
Pitäisikö palikan reunaan osuminen katsoa palikan koordinaateista eikä käyttämistäni pallon vasen ja oikea hotspoteista?
Itse lähtisin ainakin kokeilemaan siten, että palikalla on 8 normaalivektoria. Yksi jokaiselle sivulle ja yksi jokaiselle kulmalle. Sitten pseudona itse kimpoaminen:
Jos Etäisyys(pallo, palikka) < pallon r: // onko törmäys alareunaan? Jos pallon y < palikan y JA pallon x > palikan vasen x JA pallon x < palikan oikea x: // pallo kimpoaa takaisin alaspäin pallon Vy <- (-1 * pallon Vy) // onko törmäys yläreunaan? Jos pallon y > palikan y JA pallon x > palikan vasen x JA pallon x < palikan oikea x: // pallo kimpoaa takaisin ylöspäin pallon Vy <- (-1 * pallon Vy) // Huom. edellä olevat ehdot voi yhdistää. Tässä pseudossa ne ovat erillisinä, jotta idea tulisi paremmin selväksi. // vasen sivu, oikea sivu, vasen alakulma, jne...
Tuossa siis pallon y ja pallon x tarkoittavat pallon keskipisteen koordinaatteja.
Osallistutaanpas nyt vielä hiukan myöhässä tähänkin keskusteluun...
Joko olet saanut jotain täysin toimivaa aikaiseksi?
Itse muuten unohtaisin nuo törmäyspisteet pallossa ja toteuttaisin varmaankin tuon törmäystarkistuksen laskemalla kahden suoran leikkauspisteen Cramerin säännön mukaan determinanttien avulla.
Toisen suoran saat muodostettua pallon sijaintipisteestä sekä liikesuunnasta ja toinen suora tulee tietysti palikan vertailtavasta sivusta.
Cramerin säännön hienous on se, että saat suoraan tiedon, kohtaavatko suorat, ovatko ne samasuuntaisia tai peräti sama suora. Nyt voit törmäys pisteen etäisyyden ja pallon liikenopeuden perusteella päätellä tapahtuuko törmäys.
Tuli pakottava tarve sanoa DumTomille: Korjaa koodisi, siinä on paljon rumia kohtia, jotka vaikeuttavat koodin lukemista.
case 2: suu= 265; break; case 3: suu=259; break; case 4: suu=252;
Voisi korjata muotoon, näin esimerkkinä:
case 2: suu = 265; break; case 3: suu = 259; break; jne.
Jotta muistaisit sitten ylihuomennakin, mitä siinä oikein tapahtuu.
Jokotai kirjoitti:
Tuli pakottava tarve sanoa DumTomille: Korjaa koodisi, siinä on paljon rumia kohtia, jotka vaikeuttavat koodin lukemista.
Totta, mutta tuli pakottava tarve sanoa, että kyseiseen kohtaan parempi korjaus olisi taulukoida nuo suunnat ja korvata koko switch-case sillä:
suu = suunta_taulukko[AngleNumber];
Tai mieluummin korvata kaavalla jos mahdollista.
(Mod. yhdisti keskustelut, miksi teit uuden?)
Muutan kyllä switch caset suuntataulukoksi kunhan kerkeän eli kohta.
Lisäsin 4 tarkistuspistettä eli koillinen,kaakko,lounas ja luode pisteet.
Hieman asettui omituiset kimpoilut. Määräsin myös kaikki kimpoilut yhdelle kaavalle eli suu=180-suu+180.
Ei minusta kovin häiritseviä kimpoiluja enää.
Välillä toki takoo pari riviä kerralla mutta olisiko se paha asia,ei se näytä luonnottomalta...
Kuitenkin haluisin nähdä C kielisenä koodina tuon Jalskin Cramerin idean....
Olisiko se mahdollista?
Mieluiten kommentoituna.
Jatkan nyt pelin tekoa eli alan lisätä lisäominaisuuksia ja sitten tietty luon kenttäeditorin jota vain minä käytän tai siis pyydän kaveriani tekemään sata kenttää. Jokaisesta ruudusta pääsee arkanoid 2:en tapaisesti ulos joko oikealta tai vasemmalta joten läpipelattavaa ei ole kuin 50 kenttää.
Kenties virittelen jotain intermissioneita joissa pallolla pitää tuhota joku iso önni tai osua tiettyihin paikkoihin intermission kentän koloissa.
Ehkä voisi myös ajatella jonkun intermission tehtävän olevan kuusnepan international karaten pallojen torjuminen toteutettuna mailalla ja pallolla.
Lisäominaisuuksia minulla on mielessä kauhea kasa ja suurimman osan olen aikoinaan toteuttanut tahmeassa dos-versiossa jossa mailaa ei siirretty hiiren pallon x-liikehdinnän mukaan vaan maila oli aina kyseisen hiirikoordinaatin kohdalla. Tämä teki ohjauksesta sangen omituisen ja vei kaiken pelattavuuden siitä versiosta vaikka efektit ovatkin jo siinä näyttäviä....
Käytän toistaiseksi aika palikkamaista grafiikkaa ja annan sitten graafikon piirtää "päälle"...Itse en mikään suuri pixeligraafikko ole.
No nyt muuttamaan ne switch caset taulukkoon....:)
DumTom kirjoitti:
suu=180-suu+180
Kaavasi on kyllä laskutaidon riemuvoitto. Eikö tullut mieleen, että saman asian voisi kirjoittaa myös helpommassa muodossa suu = 360 - suu
?
Mutta ei tuolla kovin järkevää kimpoamista saa aikaan kuin yhdessä erikoistapauksessa, joten jos käytät tuota kaikkiin eri kimpoamisiin, niistä suurin osa menee varmasti pieleen.
En jaksa alkaa C-kielistä SDL-demoa väsäämään, kun en edes SDL:ää tunne.
Infernon mukana on Limbolla toteutettu esimerkki, jossa on törmäystarkistukseen käytetty kahden suoran leikkauspisteen laskemista Cramerin säännön mukaan kuvailemallani tavalla.
Tuo kyseinen demo on nimeltään bounce ja sen lähdekoodi löytyy: appl/wm/bounce.b
Ohjelmassa kiinnostavia kohtia voisivat olla:
makeunit()
boing()
intersect()
animproc()
Tällä koodilla toimii tosin palikan kulmiin osumat puuttuvat mutta se on helppo lisätä....
Kiitos kaikille avusta.
Heittäkää kritiikkiä....
class Ball { private: float x,y; short suunta,locked,xero; short ex,ey; // ennen törmäystä float spd; char on; public: float GetX(void) {return x;} float GetY(void) {return y;} void SetX(float a) {x=a;} void SetY(float a) {y=a;} short GetEX(void) {return ex;} short GetEY(void) {return ey;} void SetEX(short a) {ex=a;} void SetEY(short a) {ey=a;} short GetSuunta(void) {return suunta;} void SetSuunta(short a) {suunta=a;} short GetLocked(void) {return locked;} void SetLocked(short a) {locked=a;} void DecLocked(void) {if (locked>0) locked--;} char GetOn(void) {return on;} void SetOn(char a) {on=a;} short GetXero(void) {return xero;} void SetXero(short a) {xero=a;} }; Ball B[50]; void MoveBalls(void) { static char notnow=0; static short maila64[64]={10,10,10, 9,9,9, 8,8,8, 7,7,7, 6,6,6, 5,5,5, 4,4,4 ,3,3,3 ,2,2,2,2, 1,1,1,1,1,1,1,1,11,11,11, 12,12,12,12, 13,13,13, 14,14,14, 15,15,15, 16,16,16, 17,17,17 ,18,18,18 ,19,19,19}; static short kulmat1[20]={0,0,265,259,252,245,237,229,221,213,205,279,287,295,303,311,319,327,335,341}; Hit=0; for (short s=0;s<6;s++) // NOPEUS { for (short t=0;t<50;t++) { if (B[t].GetOn()==1) { if (B[t].GetLocked()==0) { if ((short)B[t].GetY()>592) // POIS ALAREUNASTA { AlustaPallotJaMaila(); UpdateGame(1); return; } float vx=B[t].GetX(),vy=B[t].GetY(); //B[t].SetEX(vx);B[t].SetEY(vy); short suu=B[t].GetSuunta(); float ksuu=deg2rad(suu); vx+=cos(ksuu); vy+=sin(ksuu); B[t].SetX(vx); B[t].SetY(vy); char ok=1; ok=CheckLeftHits(t); if (ok==0) Hit=1; if (ok==1) ok=CheckRightHits(t); if (ok==0) Hit=1; if (ok==1) CheckPalikkaHits(t); if (ok==0) Hit=1; //CheckRightHits(t); //CheckPalikkaHits(t); if (vy<32+5) { suu=360-suu; B[t].SetSuunta(suu); B[t].SetX(B[t].GetEX()); B[t].SetY(B[t].GetEY()); G.SetFU(5); Hit=1; } if (vx<32+5 || vx>762) { if (vx>762) G.SetFR(5);else G.SetFL(5); suu=180-suu;if (suu<0) suu+=360; if (suu==180) suu=190; if (suu==0) suu=350; B[t].SetSuunta(suu); B[t].SetX(B[t].GetEX()); B[t].SetY(B[t].GetEY()); Hit=1; } if (vy>M.GetY()-1 && vy<M.GetY()+8 && vx>M.GetX()-1 && vx<M.GetX()+M.GetLeveys()+1) { B[t].SetX(B[t].GetEX()); B[t].SetY(B[t].GetEY()); short kohta=B[t].GetX()-M.GetX(); if (M.GetMagnet()==1) {B[0].SetXero(kohta);B[t].SetLocked(2500);notnow=1;} KOHTA=kohta; short ma=maila64[kohta]; if (ma>1) { Hit=1; suu=suu+180;while(suu>359) suu-=360; B[t].SetSuunta(suu); short AngleNumber=maila64[kohta]; PKOHTA=AngleNumber; suu=kulmat1[AngleNumber]; //suu=360-suu; B[t].SetSuunta(suu); } if (ma==1) { Hit=1; suu=suu+180;while(suu>359) suu-=360; B[t].SetSuunta(suu); } } } } if (notnow==0 && B[t].GetLocked()>0) {B[t].SetY(548);B[t].SetX(M.GetX()+B[t].GetXero());B[t].DecLocked();Hit=0;} if (Hit==0) { float vx=B[t].GetX(),vy=B[t].GetY(); B[t].SetEX(vx);B[t].SetEY(vy); } } } notnow=0; SUUNTA=B[0].GetSuunta(); } char CheckLeftHits(short t) { // VASEN PUOLI for (t=0;t<8;t++) { short bx=B[t].GetX(),by=B[t].GetY(); switch(t) { case 0: by-=5; break; case 1: by+=5; break; case 2: bx+=5; break; case 3: bx-=5; break; case 4: bx+=3; by-=3; break; case 5: bx-=3; by+=3; break; case 6: bx+=3; by+=3; break; case 7: bx-=3; by-=3; break; } short gx,gy,x=0,y=0; for (y=0;y<15;y++) { for (x=0;x<15;x++) { short tiili=A.GetTile(x,y); if (tiili>0) { //gy=26*y+34; //gx=48*x+40; gy=A.GetTileGyCoordinates(x,y); gx=A.GetTileGxCoordinates(x,y); if (bx>gx-1 && bx<gx+1 && by>gy+1 && by<gy+24) { SCORE+=5; A.SetTile(x,y,0); NewRulla(gx,gy,ArvoRulla()); if (M.GetHeavy()==0) { short suu=B[t].GetSuunta(); //suu=suu+45; short ero=suu-180; if (ero>=0) { suu=suu-2*ero; } else { //suu=suu+2*abs(ero); suu=180-suu; } //suu=360-suu; //if (kt==1) suu-=90; //if (kt==2) suu+=90; while(suu<0) suu+=360; while(suu>359) suu-=360; B[t].SetSuunta(suu); B[t].SetX(B[t].GetEX()); B[t].SetY(B[t].GetEY()); return 0; } } } } } } return 1; } char CheckRightHits(short t) { for (t=0;t<8;t++) { short bx=B[t].GetX(),by=B[t].GetY(); switch(t) { case 0: by-=5; break; case 1: by+=5; break; case 2: bx+=5; break; case 3: bx-=5; break; case 4: bx+=3; by-=3; break; case 5: bx-=3; by+=3; break; case 6: bx+=3; by+=3; break; case 7: bx-=3; by-=3; break; } short gx,gy,x=0,y=0; for (y=0;y<15;y++) { for (x=0;x<15;x++) { short tiili=A.GetTile(x,y); if (tiili>0) { //gy=26*y+34; //gx=48*x+40; gy=A.GetTileGyCoordinates(x,y); gx=A.GetTileGxCoordinates(x,y)+45; if (bx>gx-1 && bx<gx+1 && by>gy+1 && by<gy+24) { SCORE+=5; A.SetTile(x,y,0); NewRulla(gx,gy,ArvoRulla()); if (M.GetHeavy()==0) { short suu=B[t].GetSuunta(); /* if (suu<90) { short ero=suu; suu=suu-ero*2; } else { short ero=360-suu; suu=suu+ero*2; } */ suu=180-suu; //suu=suu-45; //suu=360-suu; //if (kt==1) suu-=90; //if (kt==2) suu+=90; while(suu<0) suu+=360; while(suu>359) suu-=360; B[t].SetSuunta(suu); B[t].SetX(B[t].GetEX()); B[t].SetY(B[t].GetEY()); return 0; }}}}} } return 1; } void CheckPalikkaHits(short t) { // YLÄ JA ALAPUOLI for (t=0;t<8;t++) { char kt; short bx=B[t].GetX(),by=B[t].GetY(); switch(t) { case 0: by-=5; kt=0; break; case 1: by+=5; kt=0; break; case 2: bx+=5; kt=2; break; case 3: bx-=5; kt=1; break; case 4: bx+=3; by-=3; kt=2; break; case 5: bx-=3; by+=3; kt=1; break; case 6: bx+=3; by+=3; kt=2; break; case 7: bx-=3; by-=3; kt=1; break; } short gx,gy,x=0,y=0; for (y=0;y<15;y++) { for (x=0;x<15;x++) { short tiili=A.GetTile(x,y); if (tiili>0) { //gy=26*y+34; //gx=48*x+40; gy=A.GetTileGyCoordinates(x,y); gx=A.GetTileGxCoordinates(x,y); if (bx>gx && bx<gx+46 && by>gy-1 && by<gy+25) { SCORE+=5; A.SetTile(x,y,0); NewRulla(gx,gy,ArvoRulla()); if (M.GetHeavy()==0) { short suu=B[t].GetSuunta(); suu=360-suu; //if (kt==1) suu-=90; //if (kt==2) suu+=90; while(suu<0) suu+=360; while(suu>359) suu-=360; B[t].SetSuunta(suu); B[t].SetX(B[t].GetEX()); B[t].SetY(B[t].GetEY()); return; } } } } } } }
Kritiikki numero yksi: Koodi on niin kammottavan rumaa, ettei sitä jaksa lukea. Sisennykset menevät, miten sattuu, ja nimissä on suomea ja englantia sekaisin. Erityisen huonoja ovat "A" ja funktioiden paikallisten muuttujien nimet kuten "suu" ja "bx", joiden ymmärtäminen vaatii perehtymistä koodiin.
Kritiikki numero kaksi: Annat funktioille täysin turhan parametrin t, jota käytät sitten paikallisena muuttujana. Tämä on selvästi väärin ja antaa aika huonon kuvan taidoistasi.
Kritiikki numero kolme: Funktioissa on tarpeettoman monta tasoa sisennystä. Koodia helpottaisi, jos käyttäisit järkevästi esimerkiksi continue-sanaa.
Kritiikki numero neljä: Koodissasi on paljon toistuvia osia, jotka kannattaisi laittaa funktioihin. Esimerkiksi suuntakulman normalisointi on tällainen. Toisaalta tuossa pelissä voisi olla helpompaa käyttää kulman sijaan pelkkää liikevektoria, sillä saattaisit hahmottaa kimpoamisetkin paremmin.
En jaksa kritisoida enempää. Mitään hyvää sanottavaa tuosta sotkusta ei tule näin äkkiseltään mieleen.
Kiitos Metabolix tosta t:een lähettämisen ja sen käyttämisen heti silmukkamuuttujana huomaamisesta.
Ihmettelin kun peli bugitti kun pallojen määrä alkoi nousta.
t ei ole turha parametri vaan kertoo pallon numeron.
Tosi typerä bugi joka on ollut pitkään.
Muusta kritiikistä en ota pulttia,mulle riittää että ite tiedän suurinpiirtein miten homma menee......:)
Continueta en oikein ole älynnyt käyttää...
Enpä tähän hätään muista edes miten sitä käytetään.
Ehkä hion koodin hienommaksi?????
Mulla on aina ollu tapana sotkea suomea ja englantia...
Itsekouluttaunut kun olen....
Mutta nyt hävis se hiton bugi!
DumTom kirjoitti:
Mulle riittää että ite tiedän suurinpiirtein miten homma menee.
Kokemuksesta voin kertoa, että parin kuukauden kuluttua et välttämättä enää tiedä itsekään, miten homma menee.
DumTom kirjoitti:
t ei ole turha parametri vaan kertoo pallon numeron.
Jos parametrin nimi olisi ollut esimerkiksi ball_number, tätä ei varmaankaan olisi tapahtunut, vai olisiko? Hyvä tapa on myös määritellä silmukoiden indeksimuuttujat vain silmukan alussa, jos ei ole syytä tehdä jotain muuta. Voisi myös olla järkevää välittää funktiolle viittaus palloon eikä indeksiä.
Itseäni jäi kiinnostamaan tuo Cramerin sääntöön perustuva törmäyksentunnistus ja toteutinkin yksinkertaisen Arkanoid-kloonin, joka käyttää kyseistä systeemiä. Olin kirjoittanut jo pitkän sepustuksen siitä, mihin tarkistus perustuu, mutta välilehtiä selatessa vahingossa suljin väärän. Nyt saatte lukea jostain muualta, mitä Cramerin sääntö sanoo ja miten ne suorat muodostetaan jos ette sitä valmiiksi osaa. Idean jalski kuitenkin selitti jo: muodostetaan kaksi suoraa, joista toinen on pallon keskipisteen kautta kulkeva pallon liikesuunnan suuntainen suora ja toinen on palikan sivun kautta kulkeva suora, ja lasketaan niiden leikkauspiste. Leikkauspisteen ratkaisemisessa käytetään Cramerin sääntöä. Sen jälkeen tarkistetaan, onko törmäys mahdollinen. Itse toteutin sen niin, että tarkistan, onko leikkauspiste palikan sivun määräämällä janalla ja onko leikkauspiste alle säteen päässä pallon keskipisteestä. Jalskin ehdottama systeemi on kyllä parempi.
Yhtälöryhmän muodostin ottamalla kaksi suoraa ja muokkaamalla ne tällaiseen muotoon:
y = k1*x + b1 <=> y - k1*x = b1
y = k2*x + b2 <=> y - k2*x = b2
Muodostamalla yhtälöryhmästä matriisin ja laskemalla determinantin sain:
det A = 1*(-k2) + 1*(-k1) = -k2 + k1
Sen jälkeen vain sijoitetaan ratkaisuvektori b A:han i:nen sarakkeen paikalle ja saadaan matriisi Ai. Nyt Cramerin säännön mukaan ratkaistavan vektorin x:n i:s jäsen on xi = det Ai / det A.
Leikkauspisteen tallennan Intersection structiin, joka myös tallentaa sen, leikkaavatko suorat ja sen, ovatko suorat samat.
struct Intersection { bool intersects; bool sameLine; float x,y; };
Tässä on varsinainen leikkauspisteen ratkaisukoodi (kommentteja on vähän nihkeästi).
Intersection SolveIntersection::solve(float x11, float y11, float x12, float y12, float x21, float y21, float x22, float y22) { Intersection inter; inter.x = 0; inter.y = 0; float k1, k2; // First deal with special cases (when at least one of the lines is vertical) if( x11 == x12 && x21 == x22) { inter.intersects = false; inter.sameLine = true; return inter; } else if (x11 == x12) { k2 = (y22 - y21) / (x22 - x21); inter.x = x11; inter.y = k2*x11 + (y21 - k2*x21); inter.intersects = true; return inter; } else if(x21 == x22) { k1 = (y12 - y11) / (x12 - x11); inter.x = x21; inter.y = k1*x21 + (y11 - k1*x11); inter.intersects = true; return inter; } // Calculate the slopes of the lines k1 = (y12 - y11) / (x12 - x11); k2 = (y22 - y21) / (x22 - x21); // k1 == k2 <=> det A = 0 // which means that the matrix is singular and Cramer's rule can't be used if(k1 == k2) { inter.intersects = false; if((y21 - k2*x21) == (y11 - k1*x11)) { inter.sameLine = true; } else { inter.sameLine = false; } return inter; } // Calculate the actual intersection. I solved the problem on paper and just // wrote the final formula here. inter.x = (-k1*x11 + y11 + k2*x21 -y21) / (-k1+k2); inter.y = ( k1*(k2*x21-y21) - k2*(k1*x11-y11) ) / (-k1 + k2); inter.intersects = true; return inter; }
Kritiikkiä saa antaa. Etenkin jos keksitte tavan, jolla poikkeustapaukset pystyisi käsittelemään nätimmin.
EDIT:
Piti vielä kirjoittaa siitä peiliheijastuksesta, kun siitäkin oli tässä ketjussa puhetta.
Peiliheijastuksen laskeminen vektoreilla matematiikka-oppaassa esitetyllä kaavalla on helppoa. Funktiosta tulee myös nätti, koska ei tarvitse käsitellä erikoistapauksia. :)
Omassa toteutuksessani tein oman vektori structin ja oman funktion pistetulolle (engl. dot product).
struct Vect { float x1; float x2; }; float dot(Vect x, Vect y) { return x.x1*y.x1 + x.x2*y.x2; }
Itse peiliheijastuksen laskevassa funktiossa vain ensin muodostetaan pinnan suuntainen vektori ja muodostetaan sen normaalivektori. Sen jälkeen normaalivektori normitetaan ja sovelletaan oppaassa annettua kaavaa. Sen jälkeen vektori palautetaan.
Vect CollisionEngine::reflection(Vect in, float surfX1, float surfY1, float surfX2, float surfY2 ) { // Calculate the surface vector Vect surf; surf.x1 = surfX2 - surfX1; surf.x2 = surfY2 - surfY1; // Calculate the surface vector's normal vector Vect surfNorm; surfNorm.x1 = surf.x2; surfNorm.x2 = -surf.x1; // Normalize the surfNorm vector (divide surfNorm with it's own lenght) surfNorm.x1 = surfNorm.x1/sqrt(surfNorm.x1*surfNorm.x1 + surfNorm.x2*surfNorm.x2); surfNorm.x2 = surfNorm.x2/sqrt(surfNorm.x1*surfNorm.x1 + surfNorm.x2*surfNorm.x2); // Calculate the final vector using the formula // v2 = x - 2 * (v . N) * N, which in this context means // out = in - 2 * dot(in, surfNorm) * surfNorm Vect out; out.x1 = in.x1 - 2*(dot(in, surfNorm))*surfNorm.x1; out.x2 = in.x2 - 2*(dot(in, surfNorm))*surfNorm.x2; return out; }
Jos käyttää pelissä muuttujaa kulma, joka tallentaa sen hetkisen liikesuunnan, niin vektorin voi muodostaa sinin ja cosinin avulla muistaakseni näin (kulma on radiaaneina):
Vect vektori; vektori.x1 = cos(kulma) vektori.x2 = sin(kulma)
Virheistä saa (ja pitääkin) huomauttaa. Kritiikkiä saa antaa.
/* Tuntematonta pseudosekasotkua */ void TutkiTormaykset { // Ratkaistaan ensin missä pallo on tile-kartan suhteen palaX = (int)(palloX / palanLeveys) palaY = (int)(palloY / palanKorkeus) int temp, etaisyys = 9999 Piste lahin, nykyinen // Tutkitaan palloa lähinnä olevat 9 ruutua. // Jos oikein optimoi niin pääsee neljällä tai kuudellakin for j = palaY-1 to palaY+1 { for i = palaX-1 to palaX+1 { if OnkoSeina(i,j) { temp = EtsiLahinPiste(i, j, palloX, palloY, *nykyinen) if temp<etaisyys { etaisyys = temp lahin = nykyinen } } } } // Jos ei törmää niin mennään pois if etaisyys == 9999 return // Seinän normaalivektori on pallon sijainnin ja lähimmän seinän pisteen erotus // Tällöin kulmat ja suorat seinät otetaan huomioon muitta mutkitta pallonSuunta = Reflect(pallonSuunta, Normalisoi(Vektori(palloX-lahin.x, palloY-lahin.Y)) ) // Törmäyksen sattuessa pallon sijainti pitänee muutta myös // joten teen sen helpomman kautta. Vanhat koordinaatit päivitetään // aina kaikkien laskujen jälkeen palloX = vanhaPalloX palloY = vanhaPalloY } // Laskee palloa lähimmän pisteen suorakulmiosta Piste EtsiLahinPiste(i, j, palloX, palloY, *nykyinen) { if palloX <= i*palanLeveys nykyinen.x = i*palanLeveys else if palloX >= (i+1)*palanLeveys-1 nykyinen.x = (i+1)*palanLeveys-1 else nykyinen.x = palloX if palloY <= i*palanKorkeus nykyinen.y = i*palanKorkeus else if palloY >= (i+1)*palanKorkeus-1 nykyinen.y = (i+1)*palanKorkeus-1 else nykyinen.y = palloY } // Tää ei ole nyt pseudoa enää, sori :D function Reflect(const rayDir, wallNormal: TVector2f): TVector2f; var temp: single; begin temp:=2*(rayDir.x*wallNormal.x+rayDir.y*wallNormal.y); result.x:=rayDir.x-wallNormal.x*temp; result.y:=rayDir.y-wallNormal.y*temp; end;
Enpäs annakaan tämän vielä kuolla. ;)
Tuo User137:n ehdotus on kyllä elegantti, mutta kun koitin toteuttaa sitä, törmäsin ongelmaan. Nimittäin pallo voi päätyä laatikon sisälle, jos pallon nopeus on suurempi kuin säde. Siihen pitäisi keksiä jokin hyvä tapa ennakoida tuollaiset tilanteet. Ratkaisin ongelman nopeasti suurentamalla tarkasteltavaa ympyrää nopeuden mukaan, mutta se ei ole kovin hyvä ratkaisu. Se saa pallon käyttäytymään kuin se olisi isompi kuin se oikeasti onkaan.
Voit myös jakaa pallon nopeusvektorin pienempiin osiin, eli käytännössä liikuttaa sitä hitaammin mutta monta kertaa yhden framen aikana. Tai yksinkertaisin tapa asettaa pallon nopeudelle maksimi niin ettei se liiku kerralla törmäyskohtien yli.
User137 kirjoitti:
Tai yksinkertaisin tapa asettaa pallon nopeudelle maksimi niin ettei se liiku kerralla törmäyskohtien yli.
Tällä tavalla tämänkin simulaation, missä elämme, ohjelmoija ratkaisi törmäystarkistuongelman. Siksi maailmankaikkeudessa ei pääse valon nopeutta lujempaa.
Sinullahan on kuitenkin suunta ja nopeus tiedossa, jolloin voit jakaa aikayksikössä kuljetun reitin vaikka pallon säteen välein sijaitseviin pisteisiin. Sitten käyt nämä pisteet järjestyksessä läpi. Eli vähän sama idea kuin tuo User137 ensimmäinen ehdotus.
Grez kirjoitti:
Tällä tavalla tämänkin simulaation, missä elämme, ohjelmoija ratkaisi törmäystarkistuongelman. Siksi maailmankaikkeudessa ei pääse valon nopeutta lujempaa.
Paitsi jos tuntee säännöt ja osaa hakkeroida ne, jolloin sitä voi käyttää hyväksi vaikka törmäystarkistuksen kiertoon ja hypätä seinän läpi. :)
User137 kirjoitti:
Voit myös jakaa pallon nopeusvektorin pienempiin osiin, eli käytännössä liikuttaa sitä hitaammin mutta monta kertaa yhden framen aikana. Tai yksinkertaisin tapa asettaa pallon nopeudelle maksimi niin ettei se liiku kerralla törmäyskohtien yli.
Tuo ensimmäinen ehdotus kuulostaa ihan hyvältä, mutta Torgon versio kuulostaa vähän skaalautuvammalta. Kiitos kuitenkin ehdotuksesta.
Nopeuden rajoittaminen taas ei oikein toimi, koska sitä joutuisi rajoittamaan turhan paljon.
Torgo kirjoitti:
Sinullahan on kuitenkin suunta ja nopeus tiedossa, jolloin voit jakaa aikayksikössä kuljetun reitin vaikka pallon säteen välein sijaitseviin pisteisiin. Sitten käyt nämä pisteet järjestyksessä läpi. Eli vähän sama idea kuin tuo User137 ensimmäinen ehdotus.
Jotain tuon tapaista ideaa pyörittelinkin päässä, tosin hitusen matemaattisemmassa muodossa, mikä sai sen tuntumaan vähän hankalammalta. :P Ajattelin jotain sen tyylistä, että muodostan jonkinlaisen funktion pallon etäisyydestä mailaan ja ratkaisen milloin se etäisyys on säteen mittainen. Jakamalla matkan osiin siitä tulee helpompi toteuttaa ja tarkkuus riittää hyvin tähän sovellukseen. Kiitokset tästä.
Torgo kirjoitti:
Grez kirjoitti:
Tällä tavalla tämänkin simulaation, missä elämme, ohjelmoija ratkaisi törmäystarkistuongelman. Siksi maailmankaikkeudessa ei pääse valon nopeutta lujempaa.
Paitsi jos tuntee säännöt ja osaa hakkeroida ne, jolloin sitä voi käyttää hyväksi vaikka törmäystarkistuksen kiertoon ja hypätä seinän läpi. :)
Disassembleri vain käyntiin ja bugia metsästämään. Neolta voisi ehkä kysäistä vähän vinkkiäkin. Sitten vain pankkeja putsaamaan seinän läpi. :)
Aihe on jo aika vanha, joten et voi enää vastata siihen.