C#游戏编程:保存或计算不变数据是否更好?

时间:2011-07-24 16:44:53

标签: c# xna-4.0

我正在用C#XNA框架编写一个游戏,有些东西一直困扰着我。我永远不确定是否有利于程序保存数据(也就是说,将其存储在局部变量中)或每帧计算它。

虽然我已经决定连续计算改变每一帧的数据(例如玩家的位置,自动精灵的位置等等),但我不确定是否应该保存一块像窗口宽度的数据或每帧计算它。我担心的是,计算一块数据,无论它有多小,每秒六十次都会产生相当多的开销,所以到目前为止我一直坚持节省大量资料。

以下是我想要了解的一个例子:

//  Scenario 1: Save the data

//  This class represents an entry within a menu.
public class MenuEntry
{
    //  The constructor takes a SpriteFont variable to calculate the size of 
    //  the given string (which is the text used to represent this entry in a 
    //  menu) and save that data for later access
    public MenuEntry(string entryText, SpriteFont font)
    {
        this.entryText = entryText;

        //  Initializes a local value of the font to lessen the burden of method calls.
        SpriteFont font = menuScreen.GameInstance.MenuFont;

        //  Initialize the bounding rectangle for this 'MenuEntry' object.
        entryRectangle = new Rectangle();

        //  Based on the text given, adjust the values of the rectangle.
        entryRectangle.Height = font.LineSpacing;
        entryRectangle.Width = (int)font.MeasureString(entryText).X;
    }

    //  Object methods

    //  This method simply sets the location of the 'hitbox' of this MenuEntry based
    //  on the given position.
    public void SetLocation(int x, int y)
    {
        entryRectangle.X = x;
        entryRectangle.Y = y;
    }

    //  Object fields

    //  Rectangle that represents the 'hitbox' for this MenuEntry.  In other
    //  words, the place on the screen over which the mouse can hover to cause
    //  this MenuEntry to be the selected item on the menu
    private Rectangle entryRectangle;

    //  The 'entryText' is the string representation of this MenuEntry object in
    //  a menu.
    private string entryText;
}

//  Scenario 2: Calculate per frame

//  This class also represents an entry within a menu.
public class MenuEntry
{
    //  The constructor simply initializes the value of the text field
    public MenuEntry(string entryText)
    {
        this.entryText = entryText;
    }

    //  Object methods

    //  Instead of saving the data, these methods allow this MenuEntry
    //  to calculate its 'hitbox' whenever this method is called, 
    //  returning the width and height variables associated with the
    //  text representation of this MenuEntry object.
    public virtual int GetWidth(SpriteFont font)
    {
        return (int)font.MeasureString(Text).X;
    }

    public virtual int GetHeight(SpriteFont font)
    {
        return font.LineSpacing;
    }

    //  Object fields

    //  The MenuEntry still must know where it's located, but this component
    //  is less memory intensive than a 'Rectangle' object (correct me if
    //  I'm wrong on this one)
    private Vector2 position;

    //  This property allows for the controlling menu to place and replace the
    //  MenuEntry, though this value must be calculated each frame even though
    //  the position may be the same for each frame.
    public Vector2 Position
    {
        get { return position; }
        set { position = value; }
    }

    //  The 'entryText' is the string representation of this MenuEntry object in
    //  a menu.
    private string entryText;
}

//  Example in menu:

public class Menu
{
    //  For the sake of simplicity, the constructor simply receives a list of entries
    //  and initializes them to a local variable.  If we're using Scenario 1, the  
    //  positions for each rectangle must be set.
    public class Menu(SpriteFont menuFont, List<MenuEntry> menuEntries)
    {
        this.menuFont = menuFont;
        this.menuEntries = menuEntries;

        #if Scenario 1

        Vector2 position = new Vector2(0f, 175f);

        for(int i = 0; i < menuEntries.Count; i++)
        {
            //  Initialize a local reference to the current MenuEntry.
            MenuEntry menuEntry = menuEntries[i];

            //  Adjust this entry toward the center of the screen.  Assume it
            //  has access to a static viewport variable.
            position.X = Game.Viewport.Width / 2 - menuEntry.GetWidth(this) / 2;

            //  Set the entry's position to the calculated position.  There must be
            //  casts as these are floating-point values.
            menuEntry.SetLocation((int)position.X, (int)position.Y);

            //  Move down for the next entry the size of this entry
            position.Y += menuEntry.GetHeight(this);
        }

        #endif
    }

    //  This method is called by the main update method once per frame.
    public void Update(GameTime gameTime)
    {
        #if Scenario 1

        //  Nothing, as the MenuEntry items don't need to move and their positions
        //  have already been set.

        #elif Scenario 2

        Vector2 position = new Vector2(0f, 175f);

        for(int i = 0; i < menuEntries.Count; i++)
        {
            //  Initialize a local reference to the current MenuEntry.
            MenuEntry menuEntry = menuEntries[i];

            //  Adjust this entry toward the center of the screen.  Assume it
            //  has access to a static viewport variable.
            position.X = Game.Viewport.Width / 2 - menuEntry.GetWidth(this) / 2;

            //  Set the entry's position
            menuEntry.Position = position;

            //  Move down for the next entry the size of this entry
            position.Y += menuEntry.GetHeight(this);
        }

        #endif
    }

    //  Object fields

    //  The list of MenuEntry items associated with this Menu.
    private List<MenuEntry> menuEntries;

    //  The font used for this Menu.
    private SpriteFont menuFont;
}

虽然这个例子有点长,但我觉得它充分地包含了我的问题。那么,将更多数据保存到对象(在示例中,保存矩形结构)或者每帧计算这些数据更好吗?请注意,我所说的数据在帧之间不会改变。

4 个答案:

答案 0 :(得分:6)

nothing 每秒60次,用于简单计算。您的计算机每秒可以进行数百万次简单计算。

选择产生更易于维护的代码的那个。由于窗口可以调整大小,我强烈建议在每个帧上重新计算基于窗口的内容,如宽高比,投影矩阵等。

此类性能问题仅与经常执行的代码相关。说&gt;每秒100万次。

对于罕见的执行,只有昂贵的调用,例如IO。

所以我在这个问题上与svick有关:Profile找到瓶颈然后优化它。对于所有其他代码,请使用最清晰的最简单版本。

答案 1 :(得分:2)

更多内存使用或性能降低。这就是它归结为。在你的情况下哪个更重要?

答案 2 :(得分:2)

为最简单的阅读和理解代码做任何事情。如果您有实际的性能问题,您应该开始担心优化。

处理菜单布局之类的代码从不需要优化 - 当然在微优化级别,如:某些数据类型有多大,方法调用的开销,以及是否需要缓存结果。

当您进行100+对象物理模拟时,开始担心这一点。或10000+物体粒子系统。或100多边形碰撞检测程序。

将这种努力投入10+入门菜单 insane ,这使得很难提供任何实际的性能建议。

在许多情况下,缓存数据实际上可以降低性能,因为CPU可以在从内存中读取和写入数据所花费的时间内完成一些相当复杂的计算。代码中最重要的是SpriteFont.MeasureString - 甚至可能在你的情况下“足够快”。

提及内存访问模式也是值得的 - 如果您没有大量数据项,每个项目的大小并不重要(例如:Rectangle vs {{1}因为内存被提取到CPU缓存中的块比它大得多。

我无法强调一点就是优化这个想法是多么糟糕!只需编写好的,易于阅读的代码。

就个人而言,我会更专注于使用CLR Profiler来确保你在绘制更新周期中没有分配内存(导致垃圾收集器间歇性运行),而不是担心低级CPU和内存 - 访问时间的东西。

答案 3 :(得分:0)

我认为你没有太多选择,每一帧都应该更新所有移动物体。