MonoAsteroids – Del 1

Inledning

Vi ska i denna serie använda MonoGame för att konstruera ett komplett spel. Du kommer att kunna följa serien som nybörjare trots att vi stundtals blandar in lite mer avancerade begrepp såsom deisgn och arv.

Artikeln är mer ett stöd till den videogenomgång som mer steg för steg går igenom alla moment. Nedan är en kort trailer som visar lite hur spelet kommer att fungera. Det tillkommer fler "features" än de som syns i trailern i kommande delar.

Innan vi börjar så bör du ha Visual Studio installerat samt tillägget MonoGame. Mer info om installationen av MonoGame hittar du i artikeln MonoGame - Snabbstart .

Game Content

Efter tillåtelse från Kenney Vleugels kan vi erbjuda snygga sprites till vårt projekt. Kenney har väldigt mycket grafik och ljud som man kan använda till en rad olika projekt. Kan man så bör man donera en slant till honom och samtidigt få ett professionellt paket med 18000+ filer!

Här nedan hittar du den zip-fil med grafik och ljud som du behöver för att kunna följa artikeln. Ladda hem den och packa sedan upp innehållet på t.ex. skrivbordet.

Som tack för att vi får använda grafiken så länkar vi till Kenneys egen trailer för sitt "Kenney Game Assets".

Historia

Asteroids kom ut i sin originalversion första gången 1979. Spelet var företaget Ataris svar på Space Invaders, ett annat populärt spel som kom 1978. Spelet byggde på vector-grafik, en teknik som bygger på skarpa linjer. Spelet blev en succé och är idag en klassiker.

bild

Konceptet är enkelt. Du styr ett rymdskepp som kan skjuta asteroider. Asteroiderna blir fler och fler och kommer med högre hastighet allteftersom du klarar olika nivåer.

Ett nytt MonoGame Windows Project

Vi börjar med att skapa ett nytt MonoGame-projekt av typen MonoGame Windows Project. Du väljer i menyn "File" och "New Project". Projekttypen MonoGame Windows Project finns i trädet "MonoGame".

bild

Välj namn på ditt projekt. Du kan kalla det "MonoAsteroids" kort och gott.

Exempel: Standard MonoGame Windows Project

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

namespace Game1
{
    /// <summary>
    /// This is the main type for your game.
    /// </summary>
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

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

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// game-specific content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            // TODO: Add your update logic here

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here

            base.Draw(gameTime);
        }
    }
}

Vi rekommenderar att du först ser videogenomgången men vi kommer att ytterligare belysa några saker här. Projektet ovan innehåller några olika metoder som är värda att omnämnas.

  • Game1(), här kan du ändra inställningar på skärmen via graphics, t.ex. upplösning och fullskärmsläge etc.
  • Initialize(), initiering av variabler kan du placera här.
  • LoadContent(), all inladdning från Content gör du här.
  • Update(GameTime gameTime), typisk för förflyttning och kollisionsberäkningar. All logik i spelet bör placeras här.
  • Draw(GameTime gameTime), all uppritning sköts härifrån.

De mesta centrala delarna är just Update och Draw. Dessa metoder körs hela tiden så ofta som det bara går och utgör själva hjärtat av spelet. Draw är som regel låst till att försöka köra 60 gånger per sekund (60 fps). Metoden Update kan köras mer än en gång mellan varje Draw.

Videogenomgång

Kod

Som tidigare nämnts så är texten i artikeln här mest ett stöd för videogenomgången. Följ först videogenomgången sedan kan du jämföra din kod med den färdiga som listas under detta avsnitt.

Undvik klipp och klistra! Du lär dig mer att skriva koden steg för steg när du följer videogenomgången.

IGameObject.cs

using Microsoft.Xna.Framework;

namespace MonoAsteroids
{
    interface IGameObject
    {
        bool IsDead { get; set; }
        Vector2 Position { get; set; }
        float Radius { get; set; }
        Vector2 Speed { get; set; }
        float Rotation { get; set; }
    }
}
Globals.cs

using Microsoft.Xna.Framework;

namespace MonoAsteroids
{
    class Globals
    {
        public static int ScreenWidth = 1280;
        public static int ScreenHeight = 720;

        public static Rectangle GameArea
        {
            get
            {
                return new Rectangle(-80, -80, ScreenWidth + 160, ScreenHeight + 160);
            }
        }
    }
}
Player.cs

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

namespace MonoAsteroids
{
    class Player : DrawableGameComponent, IGameObject
    {
        public bool IsDead { get; set; }
        public Vector2 Position { get; set; }
        public float Radius { get; set; }
        public Vector2 Speed { get; set; }
        public float Rotation { get; set; }

        private Texture2D playerTexture;

        public Player(Game game) : base(game)
        {
            Position = new Vector2(Globals.ScreenWidth / 2, Globals.ScreenHeight / 2);
        }

        protected override void LoadContent()
        {
            playerTexture = Game.Content.Load<Texture2D>("player");

            base.LoadContent();
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(playerTexture, Position, null, Color.White, Rotation + MathHelper.PiOver2,
                new Vector2(playerTexture.Width / 2, playerTexture.Height / 2), 1.0f, SpriteEffects.None, 0f);
        }

        public override void Update(GameTime gameTime)
        {
            Position += Speed;

            if (Position.X < Globals.GameArea.Left)
                Position = new Vector2(Globals.GameArea.Right, Position.Y);
            if(Position.X > Globals.GameArea.Right)
                Position = new Vector2(Globals.GameArea.Left, Position.Y);
            if (Position.Y < Globals.GameArea.Top)
                Position = new Vector2(Position.X, Globals.GameArea.Bottom );
            if (Position.Y > Globals.GameArea.Bottom)
                Position = new Vector2(Position.X, Globals.GameArea.Top);

            base.Update(gameTime);
        }

        public void Accelerate()
        {
            Speed += new Vector2((float)Math.Cos(Rotation), 
                (float)Math.Sin(Rotation))* 0.20f;

            if(Speed.LengthSquared() > 25)
                Speed = Vector2.Normalize(Speed) * 5;
        }
    }
}
MonoAsteroids.cs

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

namespace MonoAsteroids
{
    public class MonoAsteroids : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Texture2D backgroundTexture;
        Player player;
        KeyboardState previousKbState;

        public MonoAsteroids()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredBackBufferHeight = Globals.ScreenHeight;
            graphics.PreferredBackBufferWidth = Globals.ScreenWidth;

            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            player = new Player(this);
            Components.Add(player);

            base.Initialize();
        }

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            backgroundTexture = Content.Load<Texture2D>("background");
        }

        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            KeyboardState state = Keyboard.GetState();

            if (state.IsKeyDown(Keys.Up))
                player.Accelerate();
            if (state.IsKeyDown(Keys.Left))
                player.Rotation -= 0.05f;
            else if (state.IsKeyDown(Keys.Right))
                player.Rotation += 0.05f;

            player.Update(gameTime);
            previousKbState = state;

            base.Update(gameTime);
        }

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

            spriteBatch.Begin();

            for (int y = 0; y < Globals.ScreenHeight; y += backgroundTexture.Width)
            {
                for (int x = 0; x < Globals.ScreenWidth; x += backgroundTexture.Width)
                {
                    spriteBatch.Draw(backgroundTexture, new Vector2(x, y), Color.White);
                }
            }
            player.Draw(spriteBatch);
            spriteBatch.End();
            base.Draw(gameTime);
        }
    }
}

Lämna ett svar

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

Scroll to top