C#-WinForms ||用于旋转的图形的多个实例

C#-WinForms || Multiple instances of Graphics for rotation

我目前正在从事一个学校项目,该项目基本上是一个涉及 GDI-Winforms 动画的游戏。 我选择了我的游戏,就像越狱一样,你试图逃离监狱,而拿着手电筒的守卫在房间里走来走去。

所以,我有代表守卫的 Guard class 并且有 A Path 可以走。此外,防护罩可以 90 度旋转。角度。 (动画到一定程度) 当我旋转守卫时,我实际上旋转了通过 Form_Paint 事件传递的 Graphics 对象:

    void Game_Paint(object sender, PaintEventArgs e)
    {
        e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
        ply.Draw(e.Graphics); // Draws the player

        grd.Draw(e.Graphics); // Draws the guard
        e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path

    }

当我只对付一名后卫时,效果很好。顺利,并做了他应该做的事情。 当我试图再增加 1 名警卫时,他们开始惊慌失措。很快我发现这是因为我派两个守卫 去绘制同一个 Graphics 实例。这也意味着,他们都在旋转它。 假设一个旋转-90,另一个旋转,但是他的 angle class 成员变量不会是-90,那么一切都会崩溃。

图形旋转方法(坐在守卫里面class):

 public Graphics RotateGuard(Graphics g, Point pivot, float angle) // returns the rotated Graphics object
    {
        if (!float.IsNaN(angle))
        {
            using (Matrix m = new Matrix())
            {
                m.RotateAt(angle, pivot);
                g.Transform = m;
            }
        }
        return g;
    }

接下来我做的是给他们每个人 this.CreateGraphics()

void Game_Paint(object sender, PaintEventArgs e)
    {
        e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
        ply.Draw(e.Graphics); // Draws the player
        foreach (Guard grd in this.guards )
        {
            grd.Draw(this.CreateGraphics()); // Draws the guard
            e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
        }        

    }

然后一切正常。唯一的问题是,GPU 处理起来似乎真的很繁重。它比他应该绘制的每 5 帧少绘制一次守卫。

我用谷歌搜索,但除了人们所说的 "There is no need to clone a Graphics object" 之外找不到任何东西,但我仍然想不出任何更好的工作解决方案。

如何才能很好地解决这个问题? 提前致谢。

使用旋转的 Graphics 工具对象绘制后,只需调用 e.Graphics.ResetTransform()

如果您进行了一些设置,您可能还想查看 Graphics.Save()Graphics.Restore() 以 return 到..它可以保存几个状态,完成后他们把他们带回来。非常好,至少如果你记下自己在做什么。

当然,您 可以 通过以相反的顺序执行反向调用来撤消 Translation/Rotation,但其他方法更简单,只适合您的情况.

请注意 Graphics 包含 任何图形,它是一个 工具,用于绘制 到关联的 Bitmap 或在控件的表面上..

最后:永远,永远不要使用CreateGraphics!!!它的结果是非持久性,你很少需要它..

一种有效的方法是维护一个整体变换,并为每个要绘制的对象创建一个矩阵。在绘制之前,您将当前变换与对象的变换相乘。之后您在下一步绘制之前重置变换。

void Game_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

    var g = e.Graphics;
    g.ResetTransform();

    g.MultiplyTransform (playerMatrix);
    ply.Draw(e.Graphics); // Draws the player
    g.ResetTransform();

    foreach (Guard grd in this.guards )
    {
        g.MultiplyTransform (grd.Matrix);
        grd.Draw(this.CreateGraphics()); // Draws the guard
        e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path

        g.ResetTransform();
    }        

}

这些概念类似于 Direct3D 等 3D 图形中的处理方式;新华社; OpenGL 和 Unity3D。

CreateGraphics() 真是个坏主意。您应该对 PaintEventArgs 传递的 Graphics 进行所有绘制。所以你的初始代码就好了。但是,您需要做的是确保每个在其 Draw 方法中接收到 Graphics 的对象在完成其工作后保持不变。这可以通过像这样使用 Graphics.Save and Graphics.Restore

来实现
class Guard
{
    public void Draw(Graphics g)
    {
        var state = g.Save();
        try
        {
            // The actual drawing code
        }
        finally
        {
            g.Restore(state);
        }
    }
}

我弄清楚了它是什么,然后我使用了@Ivan 所做的。

 void Game_Paint(object sender, PaintEventArgs e)
    {
        SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
        var saved = e.Graphics.Save();
        ply.Draw(e.Graphics); // Draws the player
        foreach (Guard grd in this.guards )
        {
            grd.Draw(e.Graphics); // Draws the guard

            e.Graphics.Restore(saved);
            e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
        }



    }

我所要做的就是没有使用 this.DoubleBuffered,而是使用了 SetStyle(ControlStyles.OptimizedDoubleBuffer, true);,起初我认为两者的作用相同,但显然不是。 然后,我保存了当前的图形状态并重新绘制。

非常感谢大家!