Inledning
Vi har i tidigare del skapat en paddel som vi kan röra på. Vi ska nu i denna del, steg för steg, lägga till bollen/kulan, få bollen att röra på sig och studsa mot väggarna, fixa kollisionsdetektering mellan bollen och paddeln, samt introducera gamestates.
Vårt spel kommer att befinna sig i olika "lägen" (states) beroende på vad som händer. Vi måste kunna styra olika funktioner beroende på läget som spelet befinner sig i. Ett läge är såklart när vi håller på att spela och bollen ska röra sig, ett annat läge kan vara när vi missar bollen och måste börja om, eller när spelet är slut och vi kanske ska mata in vårt namn eftersom vi nått en toppnotering poängmässigt (highscore).
Lägga till bollen
Vi skapar en enkel boll i lämpligt grafikprogram. Bollen som vi kommer att använda oss av är har storleken 12x12 pixlar och finns tillgänglig via länken ball.zip nedan. Det går givetvis att göra en mindre eller större boll själv men då måste en del kod justeras.
Bilden (OBS förstorad här) ger en bild av hur bollen ser ut. Observera den rosa färgen, RGB(255,0,255), som tolkas som genomskinlig i XNA.
Vi lägger kopierar filen ball.png som återfinns i zip-filen till vår Content-mapp i projektmappen precis som vi gjorde med paddeln (player.png).
Variabler
Nu är det dags att rita ut bollen på skärmen. Vi kommer att lägga till tre nya globala variabler strax under de vi skapade tidigare.
Texture2D player_gfx; //Grafik till paddeln (från del1)
Vector2 player_position; //Position för paddeln (från del1)
int game_state;
Texture2D ball_gfx; //grafik till bollen
Vector2 ball_position; //Position för bollen
Vi har lagt till gamestate av typen int som kommer att användas som en enkel räknare för att hålla koll på i vilket läge som spelet befinner sig i. Sedan lägger vi till ball_gfx av typen Texture2D och ball_position av typen Vector2, som används för grafiken till bollen och bollens position.
game_state = 0;
ball_gfx = Content.Load<Texture2D>("ball");
ball_position = new Vector2(230, 558);
Vi sätter game_state till noll och bestämmer därmed att värdet noll är det läge som spelet ska köras i då det startar. Vi laddar även in grafiken till bollen och sätter bollens startposition till strax ovanför mitten på paddelns startposition.
spriteBatch.Draw(ball_gfx, ball_position, Color.White);
Precis efter raden som ritar ut paddeln så lägger vi till ovanstående rad som ritar ut bollen. Tillägget måste vara "innanför" spriteBatch.Begin(); och spriteBatch.End();
if (game_state == 0)
{
ball_position.X = player_position.X + 30;
ball_position.Y = player_position.Y - 12;
}
Vi lägger till följande if-sats som kollar ifall game_state är satt till noll. Är så fallet så kommer vi att "klistra" fast bollen vid paddeln genom att ge bollen samma position som paddeln, fast något förskjutet. Paddelns position i sin tur beror ju på musens rörelse.
Kör vi vårt projekt så borde det se ut såhär:
Få bollen att röra på sig och studsa mot väggarna
Nu är det dags att skapa lite action och få bollen att röra på sig och studsa mot väggarna. Vi börjar med att lägga till en global variabel.
Vector2 ball_speed;
Vi lägger till en variabel vid namn ball_speed av typen Vector2 som vi ska använda för att ange bollens hastighet och riktning.
if (game_state == 0)
{
ball_position.X = player_position.X + 30; //Tillagt sedan innan
ball_position.Y = player_position.Y - 12; //Tillagt sedan innan
if (mouse.LeftButton == ButtonState.Pressed)
{
ball_speed = new Vector2(1, -3);
game_state = 1;
}
}
if (game_state == 1)
{
ball_position = ball_position + ball_speed;
if (ball_position.X <= 0) //Kontrollerar kollision med vänsterkanten
{
ball_position.X = 0;
ball_speed.X = -ball_speed.X;
}
if (ball_position.X >= 788) //Kontrollerar kollision med högerkanten
{
ball_position.X = 788;
ball_speed.X = -ball_speed.X;
}
if (ball_position.Y <= 0) //Kontrollerar kollision med övre kanten
{
ball_position.Y = 0;
ball_speed.Y = -ball_speed.Y;
}
}
Vi lägger till en if-sats som kollar ifall vi klickar på vänster musknapp, mouse.LeftButton == ButtonState.Pressed. Stämmer detta så sätts game_state till ett vilket få bli läget då spelet är igång. Vi tilldelar även variabeln ball_speed värdet (1 , -3). Det får bli utgångshastigheten och riktningen för bollen då spelet startas. Vi kan finjustera detta värde vid behov.
Enkel förklaring av en vektor
Som vi nämnt tidigare så använder vi en vektor för att bestämma position samt hastighet och riktning för ett objekt. Vi ska titta lite på hur det fungerar (i alla fall försöka förklara vad som händer).
Skärmen är ett enda stort koordinatsystem. I vårt fall 800 x 600 pixlar stort "rutmönster". Där översta vänstra pixeln är punkt (0 , 0). X-axeln fungerar precis som "vanligt" och går från vänster till höger men Y-axeln går uppifrån och nedåt (eftersom origo 0,0 är övre vänstra hörnet) vilket kan vara smått förvirrande.
Tittar vi på värdet (1,-3) som ges till vektorn ball_speed så betyder det att om vi adderar bollens position med denna vektor så ökar X-värdet med 1 (röd pil) och Y-värdet minskas med 3 (grön pil). Bollens riktning blir alltså gula pilen och hastigheten blir den gula pilens längd. Det betyder att vi kan multiplicera X- och Y-värdet för ball_speed och på så sätt öka (eller minska) bollens hastighet.
Återgår vi till koden så kollar vi ifall game_state är lika med 1 och är den det så uppdaterar vi bollens position (ball_position) genom att addera vektorn ball_speed. Då adderas både X- och Y-värdet till vektorn för bollens position.
För att göra så att bollen studsar mot skärmens kanter lägger vi till tre if-satser som helt enkelt kollar ifall bollens position är utanför skärmen, antigen uppåt, höger eller åt vänster (vi väntar med nedre delen). Är bollen utanför sidokanterna så "speglar" vi vektorn genom att ändra riktningen för X-värdet på vektorn, ball_speed.X = -ball_speed.X. Går bollen mot ovankanten på skärmen så gör vi likadant fast för Y-axeln. Värdena är anpassade efter bollens storlek (12x12 pixlar).
Vi har nu fått bollen att röra på sig och studsa mot väggen. Passerar bollen nedre kanten på skärmen så händer ingenting och vi måste starta om spelet.
Fixa kollisionen mellan bollen och paddeln
Vi ska nu fixa kollisionskontroll mellan bollen och paddeln och vidtaga lämpliga åtgärder ifall detta inträffar. Själva kollisionskontrollen gör vi enklast med rektanglar som beskrivits i tidigare artiklar här. Det går till så att vi ritar ut osynliga rektanglar kring bollen och paddeln varje gång Update() körs och kollar ifall de skär (överlappar) varandra.
När vi konstaterat att en kollision inträffar så kan vi inte bara spegla bollens vektor så som vi gör vid kollision mot kanterna eftersom bollen skulle studsa likadant hela tiden i så fall.
För att lösa detta så kollar vi vart bollen kolliderar på paddeln och ändrar bollens bana lite olika beroende på detta. I och med detta så har spelaren möjlighet att styra bollen något. De äldre spelen såsom Arkanoid gör likadant.
Bilden försöker illustrera vad som händer. Vi delar in paddeln i 5 olika zoner och beroende på vart bollen träffar (detta kontrollerar vi enkelt genom att jämföra paddelns position i X-led i förhållande till bollens position i X-led) samt vilken riktning bollen har i X-led så ändrar vi lite olika på bollens riktning. Dock gäller samma regler för vissa zoner (därav färgerna på bilden).
Träffar bollen paddeln i kanterna (markerat rött) så sätts en fast riktning på bollen oavsett varifrån (i X-led) som billen kommer ifrån. Träffar de orangefärgade zonerna så speglas vektorn i Y-led men i X-led beroende från vilket håll bollen kommer. Träffar bollen mitten (markerat grönt) så speglas vektorn i Y-led.
Vi lägger till följande till Update(), OBS koden ska vara inne i, if (game_state == 1)
Rectangle ballBox = new Rectangle((int)ball_position.X, (int)ball_position.Y, 12, 12);
Rectangle playerBox = new Rectangle((int)player_position.X, (int)player_position.Y, 90, 17);
if (playerBox.Intersects(ballBox) && ball_speed.Y > 0) //Kontrollerar kollision med spelaren
{
if (ball_position.X + 6 >= player_position.X && ball_position.X + 6 <= (player_position.X + 15)) ball_speed = new Vector2(-2.5F,-1.5F);
if (ball_position.X + 6 >= player_position.X + 16 && ball_position.X + 6 <= (player_position.X + 31))
{
if (ball_speed.X > 0) ball_speed.X = -ball_speed.X;
ball_speed.Y = -ball_speed.Y;
}
if (ball_position.X + 6 >= player_position.X + 59 && ball_position.X + 6 <= player_position.X + 74)
{
if (ball_speed.X < 0) ball_speed.X = -ball_speed.X;
ball_speed.Y = -ball_speed.Y;
}
if (ball_position.X + 6 >= player_position.X + 75 && ball_position.X + 6 <= player_position.X + 90) ball_speed = new Vector2(2.5F,-1.5F);
if (ball_position.X + 6 >= player_position.X + 32 && ball_position.X + 6 <= player_position.X + 58) ball_speed.Y = -ball_speed.Y;
}
Vi börjar med att skapa en rektangel kring bollen och en kring paddeln. Sedan kan vi enkelt med en inbyggd funktion kontrollera ifall bollens rektangel och paddelns rektangel kolliderar (överlappar) if (playerBox.Intersects(ballBox) && ball_speed.Y > 0), vi lägger även till villkoret att bollen måste vara på väg nedåt (ball_speed.Y>0).
Efter detta bör vi ha något i stil med detta: