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);
}
}
我正在用 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);
}
}