Space Race – Del 1

Inledning

Det är rekommenderat att du som läsare har gått igenom artikelserien asteroids och har lite koll på grunderna i XNA då denna artikelserie kommer att hoppa över en del "basic" steg.

Vi tänkte erbjuda en fördjupning inom 2D-spelens underbara värld och samtidigt gå in på några saker som kan hjälpa dig lösa problem med dina egna spel:

  • Avancerad per-pixel kollisionsdetektering med roterande spites.
  • Mer fysik för att hantera själva kollisionerna, med bl.a. hastighetsändringar och rotation vid kollision.
  • Mätare för liv, tid etc.
  • Bättre hantering av keyboard input.
  • Hantering av sk. states i spelet, t.ex. när är banan avklarad, vill du starta om etc etc.

Spelidé

Du ska med tangenterna (W, A & D) styra en liten raket från start till mål. Det kommar att bli ungefär som den gamla klassikern lunar lander. Du kan rotera och kollidera.

bild

Du har även sköldar och kan tåla lite kollision med omgivningen samt en begränsad mängd bränsle. Spelet ska även gå på tid. Bilden ovan visar i detalj hur det ska gå till.

Banorna kommer att bestå av bilder i .PNG format. Har du inget ritprogram så kan vi tipsa om Paint.NET som både är gratis, kraftfullt och skrivet i .NET. Banans pixlar kommer att bestämma var vi kan åka. Vissa sorters pixlar kommer att ha speciell betydelse:

  • #000000, svart. Kollision med skada.
  • #FF0000, röd. Du kommer att starta ovanför denna punkt.
  • #00FF00, grön. Kollision utan skada. Startplatta.
  • #0000FF, blå. Kollision utan skada. Målplatta.

Att raketen ser ut som Tintins i "Månen tur och retur" är en ren tillfällighet.

Starta projektet

Börja med att skapa ett nytt XNA projekt av typen "Windows Game". Döp det gärna till SpaceRace.

Skapa och lägg till en ny klass som du kallar för SpaceShip.cs.

Ladda hem och lägg till all Content som finns nedan. Zip-filen innehåller några banor, samt grafiken till skeppet.

SpaceShip.cs

Vi börjar med att samla vissa egenskaper som beskriver vårt skepp. Det vi kommer att behöva i klassen är Position, Speed (hastighet), TurnRate (rotationshastighet), Rotation och Accelerating. Utöver detta så ska SpaceShip känna till grafiken för skeppet i normaltillstånd och när det accelererar.

Engenskaperna som är fetmarkerade gör vi properties av i klassen. Properties fungerar som variabler men vi har en chans kontrollera ögonblicket då variabeln ändras. Till dessa properties använder vi privata variabler med namnen _position, _speed etc. På så vis exponerar vi bara properties och inga direkta variabler.

Koden för spaceship.cs blir i första steget:

SpaceShip.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace SpaceRace
{
    class SpaceShip
    {
        Texture2D _gfx, _gfx_acc;
        bool _accelerating;
        float _turnRate, _rotation;
        Vector2 _position, _speed;

        #region Properties

        public Vector2 Position
        {
            get { return _position; }
            set { _position = value; }
        }

        public Vector2 Speed
        {
            get { return _speed; }
            set { _speed = value; }
        }

        public float TurnRate
        {
            get { return _turnRate; }
            set { _turnRate = value; }
        }

        public float Rotation
        {
            get { return _rotation; }
            set { _rotation = value; }
        }

        public bool Accelerating
        {
            get { return _accelerating; }
            set { _accelerating = value; }
        }

        #endregion

        public SpaceShip(Texture2D ship_normal, Texture2D ship_acc)
        {
            this._gfx = ship_normal;
            this._gfx_acc = ship_acc;
        }

        public void Update(GameTime gameTime)
        {
        }

        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            if (_accelerating)
                spriteBatch.Draw(_gfx_acc, _position, null, Color.White, _rotation,
                    new Vector2(_gfx.Width / 2, _gfx.Height / 2), 1.0f, SpriteEffects.None, 0);
            else
                spriteBatch.Draw(_gfx, _position, null, Color.White, _rotation,
                    new Vector2(_gfx.Width / 2, _gfx.Height / 2), 1.0f, SpriteEffects.None, 0);
        }
    }
}

Vi har passat på att lägga till en konstruktor som tar emot två Texture2D objekt. På så vis har vi löst problemet med att skeppet ska känna till grafiken som ska användas.

Vi har lagt till metoderna Draw och Update så att skeppet kan sköta den delen också. Uppritningen i Draw fungerar på samma sätt som i asteroids, dvs. att om vi accelererar ritas en annan grafik ut.

Positionen för skeppet, Position, kommer att räknas från centrum av grafiken i "ship.png" (skeppets normaltillstånd).

Vi har gjort en region i koden med #region och #endregion för våra properties. Tanken med det är att du ska kunna "fälla ihop" ett större stycke kod och slippa se den. Detta främst för att öka läsligheten och att hitta lättare.

Game1.cs

I vårt spel behöver vi ladda in grafik, skapa ett skepp och se till att det ritas ut. Allt detta är ganska standard. Värt att nämna är dock att vi skickar vår spriteBatch till skeppet vid uppritning. Koden blir:

SpaceShip.cs

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace SpaceRace
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Texture2D level;
        Texture2D ship_normal, ship_acc;
        SpaceShip ship;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);

            level = Content.Load<Texture2D>("Level01");
            ship_normal = Content.Load<Texture2D>("ship");
            ship_acc = Content.Load<Texture2D>("ship_acc");
            ship = new SpaceShip(ship_normal, ship_acc);

        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();


            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            spriteBatch.Begin();

            //Rita banan
            spriteBatch.Draw(level, Vector2.Zero, Color.White);
            //Rita skeppet
            ship.Draw(gameTime, spriteBatch);

            spriteBatch.End();
            base.Draw(gameTime);
        }
    }
}

Detta var de första stegen.. Du kan testa att köra spelet och se att det finns en liten raket uppe i vänstra hörnet (0, 0)

Varde fysik

..Lär någon ha sagt i tidernas begynnelse. Vi börjar med att införa prev_ks och gravity som variabler i Game1.cs

Game1.cs - tillägg

        Vector2 gravity = new Vector2(0, 0.01f);
        KeyboardState prev_ks = new KeyboardState();

Gravitationen kommer att dra vårt skepp nedåt och är en del av fysiken. prev_ks kommer att innehålla föregående keyboard status. Med hjälp av denna ska vi fixa till bättre styrning. Vi vill t.ex. kunna hålla inne både A (vänster) och D (höger) samtidigt men att den senast intryckta tangenten ska gälla.

Vidare kommer vi låta skeppet uppdatera sig och flytta sin position i hastighetens riktning. Gravitationen kommer hela tiden dra hastigheten nedåt men och vi gasar med W kommer hastigheten att ökas i den riktning skeppet pekar. För att lösa detta måste vi kunna rotera accelerationsriktningen från motorerna i den riktning skeppet pekar.

Det sista där kan låta jobbigt men vi ska använda oss av en del inbyggda funktioner i XNA. Vi uppdaterar Update i Game1.cs med följande:

Game1.cs - tillägg i Update

            //Läs aktuell keyboard status
            KeyboardState ks = Keyboard.GetState();

            //Tryckte vi ned några knappar?
            if ((ks.IsKeyDown(Keys.A) && !prev_ks.IsKeyDown(Keys.A))
                || (ks.IsKeyDown(Keys.A) && !ks.IsKeyDown(Keys.D)))
                ship.TurnRate = -0.03f;
            else if ((ks.IsKeyDown(Keys.D) && !prev_ks.IsKeyDown(Keys.D))
                || (ks.IsKeyDown(Keys.D) && !ks.IsKeyDown(Keys.A)))
                ship.TurnRate = 0.03f;
            else if ((!ks.IsKeyDown(Keys.D) && prev_ks.IsKeyDown(Keys.D)) ||
                (!ks.IsKeyDown(Keys.A) && prev_ks.IsKeyDown(Keys.A)))
                ship.TurnRate = 0;

            if (ks.IsKeyDown(Keys.W))
                ship.Speed += gravity + 
					Vector2.Transform(new Vector2(0, -0.03f), Matrix.CreateRotationZ(ship.Rotation));
            else
                ship.Speed += gravity;

            ship.Accelerating = ks.IsKeyDown(Keys.W);
            //Låt skeppet uppdatera sig
            ship.Update(gameTime);
            
            //Spara undan keyboard status, som blir föregående status nästa vända
            prev_ks = ks;

För att styra rotationsriktningen på skeppet ändrar vi TurnRate. Som du ser så blev logiken för att avgöra åt vilket håll vi ska rotera en aning bökig. I skutet avslutar vi med att spara keyboard state till nästa omgång. På så sätt kan vi avgöra när en tangent trycks ned.

Vinkeln 0 kommer att motsvara skeppets startläge, dvs rakt uppåt. Således är kraften från motorerna riktade uppåt (-0.03f) när vi trycker på W. Riktningen för denna kraft roteras med hälp av en rotationmatrix, Matrix.CreateRotationZ. Z-axeln kommer att vara den axel som pekar in i skärmen och vi roterar med den vinkel som skeppet har (Ship.Rotation).

Nu återstår bara 2 rader i SpaceShip.cs innan vi kan testköra.

SpaceShip.cs - tillägg i Update

            _rotation += _turnRate;
            _position += _speed;

Visst är det fint med Vector2? Det blir enkelt och snyggt att lägga ihop två vektorer.

Avslutning

Det var allt i denna första del. Mycket "basic" blev det men finesserna kommer längre fram. Du ska kunna köra ditt spel nu och testa fysiken. Om inte så kan du ladda hem projektet nedan.

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *

Scroll to top