Search code examples
c#textscrollmonogame

MonoGame - RenderTarget2D drawn below sprites and in the wrong location


This seems like a simple thing but still doesn't work. So I'm trying to draw a scrollable log window (not scrolling yet) by using a RenderTarget2D.

Render target bugs

There are several bugs:

  • The text should be drawn on a black background, now it seems transparent
  • The text should be drawn in the black box below the game area, now it's drawn in the top left corner
  • The text appears now on the background color only, it should be drawn over the other sprites

What's wrong with my code? The relevant parts are below.

    private GraphicsDevice graphicsDevice;
    private RenderTarget2D logRenderTarget;

    private bool redrawLogFlag;

    public void Init(ContentManager content, GraphicsDevice graphicsDevice)
    {
        this.graphicsDevice = graphicsDevice;
        logRenderTarget = new RenderTarget2D(
            graphicsDevice,
            10,
            10
        );
        Log.LogWritten += SetRedrawLogFlag;
    }

    private void DrawUiComponents(SpriteBatch spriteBatch)
    {
        spriteBatch.Draw(Scavenger.AssetManager.TextureMap["game_area"], Vector2.Zero, Color.White);
        spriteBatch.Draw(Scavenger.AssetManager.TextureMap["log_area"], new Vector2(0, Constants.GAME_AREA_HEIGHT), Color.White);
        spriteBatch.Draw(Scavenger.AssetManager.TextureMap["stats_area"], new Vector2(Constants.GAME_AREA_WIDTH, 0), Color.White);
        spriteBatch.Draw(Scavenger.AssetManager.TextureMap["help_info"], new Vector2(Constants.GAME_AREA_WIDTH, Constants.SCREEN_HEIGHT - 52), Color.White);

        DrawStats(spriteBatch);
        TryRedrawLog(spriteBatch);
        WriteLog(spriteBatch);
    }

    private void SetRedrawLogFlag(object sender, EventArgs e)
    {
        redrawLogFlag = true;
    }

    private void TryRedrawLog(SpriteBatch spriteBatch)
    {
        if(redrawLogFlag)
        {
            redrawLogFlag = false;

            logRenderTarget = new RenderTarget2D(
                this.graphicsDevice,
                Constants.GAME_AREA_WIDTH,
                Log.Height
            );

            this.graphicsDevice.SetRenderTarget(logRenderTarget);
            this.graphicsDevice.Clear(Color.Black);

            for (int i = 0; i < Log.Count; i++)
            {
                spriteBatch.DrawString(Scavenger.AssetManager.Font12, 
                    Log.Entries[i], 
                    new Vector2(Constants.LOG_MARGIN, Constants.LOG_MARGIN + i * Scavenger.AssetManager.Font12.LineSpacing), 
                    Color.GreenYellow
                );
            }

            this.graphicsDevice.SetRenderTarget(null);
        }
    }

    private void WriteLog(SpriteBatch spriteBatch)
    {
        spriteBatch.Draw(logRenderTarget,
            new Rectangle(0, Constants.GAME_AREA_HEIGHT, Constants.GAME_AREA_WIDTH, Constants.LOG_HEIGHT),
            new Rectangle(0, 0, Constants.GAME_AREA_WIDTH, Constants.LOG_HEIGHT),
            Color.White
        );
    }

Solution

  • I decided to switch to using a Viewport. It seems, however, that MonoGame uses the last set-up Viewport before the call to spriteBatch.End(). It also seems that it is the same with the ScissorRectangle, so I could have made it also work. I have added the ScissorRectangle-specific code here in comments. Here's my new class:

    public class LogWriter
    {
        private GraphicsDevice graphicsDevice;
    
        private Viewport defaultViewport, logViewport;
        //private Rectangle scissor, scissorBackup;
    
        public void Init(GraphicsDevice graphicsDevice)
        {
            this.graphicsDevice = graphicsDevice;
    
            defaultViewport = this.graphicsDevice.Viewport;
            logViewport = new Viewport(0, Constants.GAME_AREA_HEIGHT, Constants.GAME_AREA_WIDTH, Constants.LOG_HEIGHT);
    
            //scissor = new Rectangle(0, Constants.GAME_AREA_HEIGHT, Constants.GAME_AREA_WIDTH, Constants.LOG_HEIGHT);
            //scissorBackup = this.graphicsDevice.ScissorRectangle;
        }
    
        public void WriteLog(SpriteBatch spriteBatch)
        {
            this.graphicsDevice.Viewport = logViewport;
            //this.graphicsDevice.ScissorRectangle = scissor;
    
            spriteBatch.Begin();
            //spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, new RasterizerState() { ScissorTestEnable = true });
    
            for (int i = 0; i < Log.Count; i++)
            {
                spriteBatch.DrawString(Scavenger.AssetManager.Font12,
                    Log.Entries[i],
                    new Vector2(
                        Constants.LOG_MARGIN,
                        Constants.LOG_MARGIN - 100 + i * Scavenger.AssetManager.Font12.LineSpacing),
                    Color.GreenYellow
                );
            }
    
            spriteBatch.End();
    
            this.graphicsDevice.Viewport = defaultViewport;
            //this.graphicsDevice.ScissorRectangle = scissorBackup;
        }
    }
    

    Init() is called in the LoadContent() function. WriteLog() is called in the Draw() function after End() has been called on the spriteBatch after drawing all the other things.