【发布时间】:2013-12-13 13:34:25
【问题描述】:
这是我的第一篇文章,如果我犯了任何错误等,敬请见谅。 如果在帖子末尾,您需要更多信息,那么我会很乐意发布更多信息。
我从事基于 2D 瓷砖的游戏已经有一段时间了,最近我尝试了一种不同的方法来渲染瓷砖。但是现在我在渲染图块时遇到了性能问题。
游戏将其世界分成 16*256 块,每个块都有自己的 RenderTarget2D (16*256) 来保存图块数据。我已将渲染目标称为块缓冲区。
public void updateChunks()
{
int loadMinX = (int)( ( game.camera.location.X - game.GraphicsDevice.Viewport.Width / 2 ) / Tile.tileWidth / Chunk.chunkWidth );
int loadMaxX = (int)( ( game.camera.location.X + game.GraphicsDevice.Viewport.Width / 2 ) / Tile.tileWidth / Chunk.chunkWidth );
int minX = (int)( game.player.location.X / ( Chunk.chunkWidth * Tile.tileWidth ) ) - (int)Math.Floor( chunkCount / 2f );
int maxX = (int)( game.player.location.X / ( Chunk.chunkWidth * Tile.tileWidth ) ) + (int)Math.Floor( chunkCount / 2f );
Chunk currentChunk;
int cursorChunk = game.cursor.chunkLocation;
for( int c = minX; c <= maxX; c++ )
{
if( chunkBuffer.ContainsKey( c ) )
{
currentChunk = chunkBuffer[c];
if( c >= loadMinX && c <= loadMaxX )
{
currentChunk.bufferBuilt = false;
}
if( currentChunk.highlighted == true )
{
currentChunk.highlighted = false;
currentChunk.bufferBuilt = false;
}
if( chunkBuffer.ContainsKey( cursorChunk ) && chunkBuffer[cursorChunk] == currentChunk )
{
currentChunk.highlighted = true;
currentChunk.bufferBuilt = false;
}
if( currentChunk.bufferBuilt == false )
{
RebuildChunk( currentChunk );
}
}
}
game.tilesLoaded = chunkBuffer.Count * Chunk.chunkWidth * Chunk.chunkHeight;
}
每个tick都会调用此方法,并决定需要重建/重绘哪些块缓冲区。目前,视口中的区块会在每个刻度上重建,鼠标所在的区块也会重建(因为图块会变为突出显示的颜色。)
块存储在字典中,加载的块通过循环访问块字典的 minX -> minY 值,如 updateChunks() 方法所示。
public void RebuildChunk( Chunk chunk )
{
BuildChunk( chunk );
chunk.bufferBuilt = true;
}
private void BuildChunk( Chunk chunk )
{
int chunkWidth = Chunk.chunkWidth;
int chunkHeight = Chunk.chunkHeight;
int chunkLocation = chunk.id * chunkWidth;
int loadMinX = (int)( ( game.camera.location.X - game.GraphicsDevice.Viewport.Width / 2 ) / Tile.tileWidth ) - 2;
int loadMaxX = (int)( ( game.camera.location.X + game.GraphicsDevice.Viewport.Width / 2 ) / Tile.tileWidth ) + 2;
int loadMinY = (int)( ( game.camera.location.Y - game.GraphicsDevice.Viewport.Height / 2 ) / Tile.tileHeight ) - 2;
int loadMaxY = (int)( ( game.camera.location.Y + game.GraphicsDevice.Viewport.Height / 2 ) / Tile.tileHeight ) + 2;
game.GraphicsDevice.SetRenderTarget( chunk.buffer );
game.GraphicsDevice.Clear( Color.CornflowerBlue );
game.spriteBatch.Begin( SpriteSortMode.Immediate, BlendState.NonPremultiplied );
for( int x = 0; x < chunkWidth; x++ )
{
for( int y = 0; y < chunkHeight; y++ )
{
if( x + chunkLocation > loadMinX && x + chunkLocation < loadMaxX && y > loadMinY && y < loadMaxY )
{
if( chunk.tiles[x, y].type == 0 && chunk.tiles[x, y].lightSource == false )
{
chunk.tiles[x, y].lightSource = true;
chunk.tiles[x, y].lightComponent = new Light( game, new Point( chunkLocation + x, y ), 1.0f, 6 );
}
if( chunk.tiles[x, y].lightSource == true )
{
if( chunk.tiles[x, y].lightComponent == null )
{
chunk.tiles[x, y].lightSource = false;
}
else
{
ApplyLighting( chunk, x, y, chunk.tiles[x, y].lightComponent );
}
}
float brightness = chunk.tiles[x, y].brightness;
if( chunk.tiles[x, y].type == 0 )
{
if( chunk.highlighted == true && game.cursor.tileLocation.X == x && game.cursor.tileLocation.Y == y )
{
game.spriteBatch.Draw( game.gameContent.airTexture, new Rectangle( x * Tile.tileWidth, y * Tile.tileHeight, Tile.tileWidth, Tile.tileHeight ), new Color( 0.5f, 0.5f, 0.5f ) );
}
else
{
game.spriteBatch.Draw( game.gameContent.airTexture, new Rectangle( x * Tile.tileWidth, y * Tile.tileHeight, Tile.tileWidth, Tile.tileHeight ), new Color( brightness, brightness, brightness ) );
}
}
else if( chunk.tiles[x, y].type == 1 )
{
if( chunk.highlighted == true && game.cursor.tileLocation.X == x && game.cursor.tileLocation.Y == y )
{
game.spriteBatch.Draw( game.gameContent.dirtTexture, new Rectangle( x * Tile.tileWidth, y * Tile.tileHeight, Tile.tileWidth, Tile.tileHeight ), new Color( 0.5f, 0.5f, 0.5f ) );
}
else
{
game.spriteBatch.Draw( game.gameContent.dirtTexture, new Rectangle( x * Tile.tileWidth, y * Tile.tileHeight, Tile.tileWidth, Tile.tileHeight ), new Color( brightness, brightness, brightness ) );
}
}
}
}
}
game.spriteBatch.End();
game.GraphicsDevice.SetRenderTarget( null );
}
RebuildChunk 负责再次构建块并重置已构建标志,因此 CPU 时间不会浪费在重建尚未编辑或不在视口中的块。
BuildChunk 是将图块绘制到块缓冲区的位置。渲染目标被交换到块缓冲区,然后循环遍历块块。
if( x + chunkLocation > loadMinX && x + chunkLocation < loadMaxX && y > loadMinY && y < loadMaxY )
此行检查图块是否在视口中。 如果是,则检查瓷砖类型是什么,然后为该瓷砖绘制相应的纹理。那里也有照明逻辑,但它不会影响我的问题。
我的主要渲染逻辑是:
protected override void Draw( GameTime gameTime )
{
frameCounter++;
drawScene( gameTime );
GraphicsDevice.Clear( Color.White );
spriteBatch.Begin( SpriteSortMode.Immediate, BlendState.NonPremultiplied );
spriteBatch.Draw( white, new Rectangle( 0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height ), Color.CornflowerBlue );
spriteBatch.Draw( worldBuffer, new Rectangle( 0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height ), Color.White );
spriteBatch.End();
debugUI.Draw( gameTime );
base.Draw( gameTime );
}
private void drawScene( GameTime gameTime )
{
int loadMinX = (int)( ( camera.location.X - GraphicsDevice.Viewport.Width / 2 ) / Tile.tileWidth / Chunk.chunkWidth );
int loadMaxX = (int)( ( camera.location.X + GraphicsDevice.Viewport.Width / 2 ) / Tile.tileWidth / Chunk.chunkWidth );
int minX = (int)( player.location.X / ( Chunk.chunkWidth * Tile.tileWidth ) ) - (int)Math.Floor( worldManager.chunkCount / 2f );
int maxX = (int)( player.location.X / ( Chunk.chunkWidth * Tile.tileWidth ) ) + (int)Math.Floor( worldManager.chunkCount / 2f );
GraphicsDevice.SetRenderTarget( worldBuffer );
GraphicsDevice.Clear( Color.CornflowerBlue );
spriteBatch.Begin( SpriteSortMode.Immediate, BlendState.NonPremultiplied, SamplerState.PointClamp, null, null, null, camera.GetViewTransformation( GraphicsDevice ) );
for( int c = minX; c <= maxX; c++ )
{
if( c >= loadMinX && c <= loadMaxX )
{
if( worldManager.chunkBuffer.ContainsKey( c ) )
{
Chunk currentChunk = worldManager.chunkBuffer[c];
if( currentChunk.bufferBuilt == true )
{
spriteBatch.Draw( currentChunk.buffer, new Rectangle( c * Chunk.chunkWidth * Tile.tileWidth, 0, Chunk.chunkWidth * Tile.tileWidth, Chunk.chunkHeight * Tile.tileHeight ), Color.White );
}
}
}
}
spriteBatch.Draw( player.material, new Rectangle( (int)player.location.X, (int)player.location.Y, 16, 32 ), Color.White );
spriteBatch.End();
GraphicsDevice.SetRenderTarget( null );
}
最后,在我的输入管理器中,我有以下内容:
if( mouseState.LeftButton == ButtonState.Pressed )
{
game.worldManager.RemoveTile( game.cursor.chunkLocation, game.cursor.tileLocation );
}
调用:
public void RemoveTile( int chunkLocation, Point location )
{
if( chunkBuffer.ContainsKey( chunkLocation ) && location.X >= 0 && location.X < Chunk.chunkWidth && location.Y >= 0 && location.Y < Chunk.chunkHeight )
{
if( chunkBuffer[chunkLocation].tiles[location.X, location.Y].type != 0 )
chunkBuffer[chunkLocation].tiles[location.X, location.Y].type = 0;
}
}
这只是我改变不同瓷砖状态的测试方法,在这种情况下,它将类型从污垢变为空气以模拟去除瓷砖。
现在解决问题。当游戏第一次运行时它很好并且保持在60fps。我将我的角色向右移动以用图块填充屏幕宽度,然后我向下挖掘以用图块填充屏幕高度。
http://i.imgur.com/NBJ8qRj.png
http://i.imgur.com/N9i8asW.png (注意:第二张图片中的灯光是关闭的)
游戏在第一张图片中仍然运行良好,但是当我开始在第二张图片中移除玩家周围的瓷砖时,一段时间后帧率从 60 下降到 10 以下并下降到 2-3 fps 左右永远保持这个速度并且即使我已经停止点击/对游戏做任何事情也不会恢复。
问题出现在全屏分辨率下,性能分析表明在 BuildChunk() 方法内的绘制调用期间使用了大量 cpu 时间:
else
{
game.spriteBatch.Draw( game.gameContent.dirtTexture, new Rectangle( x * Tile.tileWidth, y * Tile.tileHeight, Tile.tileWidth, Tile.tileHeight ), new Color( brightness, brightness, brightness ) );
}
那么这可能是由不断交换要绘制的纹理引起的吗? IE;从绘制 airTexture 交换到 dirtTexture 等?因为我发现如果我将 airTexture 也更改为dirtyTexture,问题就会消失。
如果我在我移除的所有图块下方进一步挖掘,fps 会恢复到 60,所以我的猜测是它与在重建阶段绘制纹理有关。
如果有人可以查看此代码并指出其中的任何缺陷,将不胜感激,并且可能的解决方案会很棒。
另外,如果有人在这种情况下使用瓷砖并提供您的建议将是非常宝贵的!
【问题讨论】:
-
下次尽量只贴相关代码,这个问题很大,不知道有多少人会看。