MonoGame 中的低帧率

Low framerate in MonoGame

我正在用 MonoGame 编写一个 2D 益智游戏。我刚刚将我的第一个移动精灵添加到程序中,发现我的速度大约为 10fps,但我一点也不知道为什么。我不知道我是否可以提供足够的信息来在这里获得帮助,但我认为值得一试。作为参考,移动物体是一个球。

主要更新例程:

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

    MouseState newState = Mouse.GetState();

    if (newState.LeftButton == ButtonState.Pressed && oldState.LeftButton == ButtonState.Released)
    {
        MouseLeftClicked(newState.X, newState.Y);
    }

    oldState = newState;

    if (gameState == GameState.Playing)
    {
        try
        {
            foreach (Ball ball in ballList)
            {
                ball.Update(gameTime);
            }
        }
        catch(NullReferenceException)
        {
        }
    }
    // TODO: Add your update logic here

    base.Update(gameTime);
}

球更新例程:

public void Update(GameTime gameTime)
{
    this.pos += this.motion * (float)gameTime.ElapsedGameTime.TotalSeconds;
}

主要绘图例程:

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

    // TODO: Add your drawing code here
    spriteBatch.Begin();

    for (int x = 0; x < 8; x++)
    {
        for (int y = 0; y < 6; y++)
        {
            if (gameGrid[x, y] != null)
            {
                spriteBatch.Draw(backgroundTile, gameGrid[x, y].rect, Color.White);
            }
        }
    }


    //Draw menu
    if (gameState == GameState.StartMenu)
    {
        spriteBatch.Draw(startButton, orbPosition, Color.White);
    }

    //Draw game while playing
    if (gameState == GameState.Playing)
    {
        for (int x = 0; x < 8; x++)
        {
            for (int y = 0; y < 6; y++)
            {
                try
                {
                    gameGrid[x, y].pipe.Draw(spriteBatch);
                }
                catch (NullReferenceException)
                {
                    continue;
                }
            }
        }
        foreach (Wheel wheel in wheelList)
        {
            wheel.Draw(spriteBatch);
        }

        foreach (Ball ball in ballList)
        {
            ball.Draw(spriteBatch);
        }
    }

    spriteBatch.End();

    base.Draw(gameTime);
}

抽球例程:

public void Draw(SpriteBatch spriteBatch)
{
    int sprite = (int)color;

    Rectangle sourceRect = new Rectangle(sprite * spriteSize, 0, spriteSize, spriteSize);
    Rectangle ballRect = new Rectangle((int)this.pos.X, (int)this.pos.Y, spriteSize * scale, spriteSize * scale);

    //spriteBatch.Begin();

    spriteBatch.Draw(this.texture, ballRect, sourceRect, Color.White);

    //spriteBatch.End();
}

我测试了你的代码,它在提供纹理时没有延迟。我必须对您的代码稍作修改才能使其正常工作,因为您省略了其中的某些部分。我想省略的部分可能是负责任的,但不太可能。我不知道你在什么样的机器上测试这个,但是,我会提供一些建议,以解决你在本地遇到的问题。

编写高性能代码时,忘掉你所知道的关于 'object oriented behaviour' 的一切,并思考数据。面向数据的设计就是将属于一起的数据粘贴成大块并一次处理它们。这要快得多。在很多情况下,对于游戏设计来说,有一个就有很多。利用它来发挥你的优势并传递整个数组并直接在现场对其进行操作。

尽可能避免嵌套异常和迭代循环。异常在发生时代价高昂,只有在非常异常极不寻常情况确实发生时才应使用它们,并且您希望通过抛出异常来强制代码的使用者处理它来处理这种 'edge' 情况。

 for (int x = 0; x < 8; x++)
    {
        for (int y = 0; y < 6; y++)
        {
            try
            {
                gameGrid[x, y].pipe.Draw(spriteBatch);
            }
            catch (NullReferenceException)
            {
                continue;
            }
        }
    }

捕获嵌套在两个 for 循环中的空引用异常可能不是一个好主意。如果您需要从其中一个抛出,并且允许您绘制下一个,请考虑如果您希望保持代码原样,为什么需要抛出。如果它在那里捕获 gameGrid 或管道中的空项,请考虑构造函数应始终将项目置于有效状态并且列表应始终为 'complete'。如果一个元素不再存在,它就不应该再出现在列表中。否则,如果一次失败意味着全部失败,则将 try 块移到外面。这个比较常见。

分析您的应用程序是一种机制,可以帮助您找到比预期慢的地方,有时甚至 为什么。 visual studio 中提供了有关如何执行此操作的参考。 Beginners Guide to Performance Profiling as well as Walkthrough: Profiling Applications.

也就是说,其中 none 会使您的应用程序减慢到您描述的那种程度。因此,我建议您编辑您的问题,并包括可能是原因的代码的其他相关部分。我在下面附上了一个示例,该示例是根据您的小示例构建的,并进行了一些小的修改,以便以更加极端的方式模拟您的环境。这个例子绘制了 100 个矩形,每个矩形都是 50x50,它们都像您的应用程序一样重新缩放和移动。这些是 100 个图块,如果这对您来说很慢,如果您使用 visual studio 或尝试从 Mono Game's official website 或您最新的显卡驱动程序获取最新的二进制文件,您绝对应该查看上面的分析器主题.

/// <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";
    }

    public IList<Ball> Balls { get; private set; }

    /// <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);
        var rand = new Random();
        Balls = new List<Ball>(5);
        for(var iii = 0; iii < 100; ++iii)
            Balls.Add(new Ball(GraphicsDevice, new Vector2(rand.Next(50, 500), rand.Next(50, 500))));
    }

    /// <summary>
    /// UnloadContent will be called once per game and is the place to unload
    /// all content.
    /// </summary>
    protected override void UnloadContent()
    {
    }

    /// <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();

        foreach (var ball in Balls)
            ball.Update(gameTime);

        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);

        spriteBatch.Begin();

        foreach (var ball in Balls)
            ball.Draw(spriteBatch);

        spriteBatch.End();

        base.Draw(gameTime);
    }
}

public class Ball
{
    public Texture2D Texture { get; }
    public Vector2 Position { get; private set; }
    public double Scale { get; private set; }

    public Ball(GraphicsDevice gd, Vector2 initialPosition)
    {
        Texture = new Texture2D(gd, 50, 50);
        Position = initialPosition;
        var data = new Color[100*100];
        for (var iii = 0; iii < data.Length; ++iii) data[iii] = Color.YellowGreen;
        Texture.SetData(data);
    }

    private bool _increaseScale, _increaseX, _increaseY;

    public void Update(GameTime gameTime)
    {
        if (Scale < 1)
            _increaseScale = true;
        else if (Scale > 4)
            _increaseScale = false;

        if (Position.X < 50)
            _increaseX = true;
        else if (Position.X > 500)
            _increaseX = false;

        if (Position.Y < 50)
            _increaseY = true;
        else if (Position.Y > 500)
            _increaseY = false;

        Scale += (_increaseScale ? 1.5 : -1.5) * gameTime.ElapsedGameTime.TotalSeconds;
        Position += new Vector2((float)((_increaseX ? 100 : -100)*gameTime.ElapsedGameTime.TotalSeconds), (float)((_increaseY ? 100 : -100)*gameTime.ElapsedGameTime.TotalSeconds));
    }

    public void Draw(SpriteBatch spriteBatch)
    {
        var source = new Rectangle(0, 0, Texture.Height, Texture.Width);
        var dest = new Rectangle((int)Position.X, (int)Position.Y, Texture.Width * (int)Scale, Texture.Height* (int)Scale);
        spriteBatch.Draw(Texture, dest, source, Color.White);
    }
}