Tag: Monogame

  • Fullskärm med exakt virtuell bildupplösning

    Fullskärm med exakt virtuell bildupplösning

    Om du vill bygga ett spel med Monogame som 1) körs i fullsärmsläge och 2) har en låg virtuell upplösning, typ 320 x 200 pixlar, så finns en enkel lösning. Istället för att rendera spelet på din backbuffer, så renderar du till en render target som är just 320 x 200 pixlar stor. För att åstadkomma detta, skapa en ny property i ditt spel, av typen RenderTarget2D.

    private RenderTarget2D renderTarget { get; set; }

    I konstruktorn, givet att vi befinner oss i release-kompileringen, skapar man en backbuffer som är lika stor som skärmen.

                graphics = new GraphicsDeviceManager(this);
    #if DEBUG
                ScreenWidth = 640;
                ScreenHeight = 400;
                graphics.PreferredBackBufferWidth = ScreenWidth;
                graphics.PreferredBackBufferHeight = ScreenHeight;
                graphics.IsFullScreen = false;
    #else
                ScreenWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
                ScreenHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
                graphics.PreferredBackBufferWidth = ScreenWidth;
                graphics.PreferredBackBufferHeight = ScreenHeight;
                graphics.IsFullScreen = true;
    #endif
                Content.RootDirectory = "Content";

    Funktionen Initialize är en lämplig plats att skapa RenderTarget2D-objektet. Det är storleken på den som styr den virtuella upplösningen.

    renderTarget = new RenderTarget2D(GraphicsDevice, 320, 200);

    Slutligen renderas spelet mot RenderTarget2D-objektet. Funktionen Draw skulle kunna se ut så här:

    protected override void Draw(GameTime gameTime)
    {
        graphics.GraphicsDevice.SetRenderTarget(renderTarget);
        graphics.GraphicsDevice.Clear(Color.Black);
    
        //Rita spelet här!
        spriteBatch.Begin();
        spriteBatch.Draw(hej, new Vector2(0, 0), Color.White);
        spriteBatch.Draw(hej, new Vector2(310, 190), Color.White);
        spriteBatch.End();
    
        graphics.GraphicsDevice.SetRenderTarget(null);
        spriteBatch.Begin(samplerState: SamplerState.PointClamp);
        spriteBatch.Draw(renderTarget, new Rectangle(0, 0, ScreenWidth, ScreenHeight), new Rectangle(0, 0, 320, 200), Color.White);
        spriteBatch.End();
        base.Draw(gameTime);
    }

    Notera det sista anropet på spriteBatch.Draw, den tredje sista raden. Den skalar upp spelet till den riktiga back bufferten. Om man vill ha rektangulära pixlar, ska det hanteras där. Denna exempelkod tar inte hänsyn till olika bildförhållanden.

    Så här ser hela koden ut:

    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    
    namespace Game1
    {
        public class Game1 : Game
        {
            private RenderTarget2D renderTarget { get; set; }
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            private int ScreenWidth { get; }
            private int ScreenHeight { get; }
            private Texture2D hej;
    
            public Game1()
            {
                graphics = new GraphicsDeviceManager(this);
    #if DEBUG
                ScreenWidth = 640;
                ScreenHeight = 400;
                graphics.PreferredBackBufferWidth = ScreenWidth;
                graphics.PreferredBackBufferHeight = ScreenHeight;
                graphics.IsFullScreen = false;
    #else
                ScreenWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
                ScreenHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
                graphics.PreferredBackBufferWidth = ScreenWidth;
                graphics.PreferredBackBufferHeight = ScreenHeight;
                graphics.IsFullScreen = true;
    #endif
                Content.RootDirectory = "Content";
            }
            protected override void Initialize()
            {
                renderTarget = new RenderTarget2D(GraphicsDevice, 320, 200);
                base.Initialize();
            }
            protected override void LoadContent()
            {
                spriteBatch = new SpriteBatch(GraphicsDevice);
                hej = Content.Load<Texture2D>("hej");
            }
            protected override void Update(GameTime gameTime)
            {
                if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                    Exit();
                base.Update(gameTime);
            }
            protected override void Draw(GameTime gameTime)
            {
                graphics.GraphicsDevice.SetRenderTarget(renderTarget);
                graphics.GraphicsDevice.Clear(Color.Black);
    
                //Rita spelet här!
                spriteBatch.Begin();
                spriteBatch.Draw(hej, new Vector2(0, 0), Color.White);
                spriteBatch.Draw(hej, new Vector2(310, 190), Color.White);
                spriteBatch.End();
    
                graphics.GraphicsDevice.SetRenderTarget(null);
                spriteBatch.Begin(samplerState: SamplerState.PointClamp);
                spriteBatch.Draw(renderTarget, new Rectangle(0, 0, ScreenWidth, ScreenHeight), new Rectangle(0, 0, 320, 200), Color.White);
                spriteBatch.End();
                base.Draw(gameTime);
            }
        }
    }
    

    Jag utvecklar detta på min arbetsgivares blogg.

  • Skala upp Monogame

    Skala upp Monogame

    När man bygger retrospel med en upplösning på omkring 320 x 200 pixlar, vill man gärna skala upp spelet så att sprites och andra objekt inte blir så små på en modern skärm. För den som programmerar Monogame finns flera lösningar. Jag tycker att den enklaste metoden är att använda ett transform matrix. När jag debuggar mitt spel, tycker jag att en uppskalning på 3 x 3 är rätt lämplig, vilket innebär att ett fönster på 960 x 600 pixlar är lämpligt. Detta är spelets konstruktor, spelet har kompletterats med en medlemsvariabel vid namn transformMatrix av typen Matrix.

    graphics.PreferredBackBufferWidth = 960;
    graphics.PreferredBackBufferHeight = 600;
    graphics.IsFullScreen = false;
    transformMatrix = Matrix.CreateScale(960 / 320, 600 / 200, 1.0f);

    I skarpt läge låter jag skärmupplösningen styra skalningen, eftersom jag vill att spelet ska köras i fullskärm.

    var width = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
    var height = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
    graphics.PreferredBackBufferWidth = width;
    graphics.PreferredBackBufferHeight = height;
    graphics.IsFullScreen = true;
    transformMatrix = Matrix.CreateScale(width / 320, height / 200, 1.0f);
    

    För att effekten ska slå igenom, ska Matrix-objektet skickas med till SpriteBatch-objektets Begin-metod. För att undvika oönskad kantutjämning använder jag en sampler state som heter PointClamp.

    spriteBatch.Begin(transformMatrix: transformMatrix, samplerState: SamplerState.PointClamp);

    Ett exempelspel skulle kunna se ut så här:

    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    
    namespace Game1
    {
        public class Game1 : Game
        {
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            Matrix transformMatrix;
            Texture2D testtexture { get; set; }
            private int x = 0;
            private int y = 0;
            public Game1()
            {
                graphics = new GraphicsDeviceManager(this);
    #if DEBUG
                graphics.PreferredBackBufferWidth = 960;
                graphics.PreferredBackBufferHeight = 600;
                graphics.IsFullScreen = false;
                transformMatrix = Matrix.CreateScale(960 / 320, 600 / 200, 1.0f);
    #else
                var width = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
                var height = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
                graphics.PreferredBackBufferWidth = width;
                graphics.PreferredBackBufferHeight = height;
                graphics.IsFullScreen = true;
                transformMatrix = Matrix.CreateScale(width / 320, height / 200, 1.0f);
    #endif
                Content.RootDirectory = "Content";
            }
            protected override void LoadContent()
            {
                spriteBatch = new SpriteBatch(GraphicsDevice);
                testtexture = Content.Load<Texture2D>("my_folder");
            }
            protected override void Update(GameTime gameTime)
            {
                var state = Keyboard.GetState();
                if (state.IsKeyDown(Keys.Escape))
                    Exit();
                if (state.IsKeyDown(Keys.Left))
                    x--;
                else if (state.IsKeyDown(Keys.Right))
                    x++;
                if (state.IsKeyDown(Keys.Up))
                    y--;
                else if (state.IsKeyDown(Keys.Down))
                    y++;
                base.Update(gameTime);
            }
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
                spriteBatch.Begin(transformMatrix: transformMatrix, samplerState: SamplerState.PointClamp);
                spriteBatch.Draw(testtexture, new Vector2(x, y), Color.White);
                spriteBatch.End();
                base.Draw(gameTime);
            }
        }
    }
    

    Ett problem som man bör känna till, är att om bildförhållandet mellan den virtuella upplösningen och den fysiska upplösningen inte är samma, så kommer fler pixlar än önskat att synas. När man kör i fönster, så kan man styra detta så att bildförhållandet är detsamma. 960 x 600 har samma bildförhållande som 320 x 200. Men i fullskärmsläge bör man ha detta i åtanke.

    Jag utvecklar detta på min arbetsgivares blogg.