1

我正在用 C# 编写一个简单的 C# 游戏 Knights Tour 程序,这是学习所有 C# 的艰难方法。我有一个棋盘和一个骑士片,骑士是一个带有骑士图片的定制面板。

我试图做的是允许用户在运行时单击并拖动骑士块控件(正是您可以在设计时移动控件以放置它的方式),但无论出于何种原因,我得到了一些非常不希望的结果。

    private void KTmain_Load(object sender, EventArgs e)
    {
        boolKnightmoves = false;
    }

    private void kpcKnight_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        switch (e.Button)
        {
            case MouseButtons.Left:
                boolKnightmoves = true;

                intCurMouseX = e.X;
                intCurMouseY = e.Y;

                break;
            case MouseButtons.Right:

            case MouseButtons.Middle:

            case MouseButtons.XButton1:

            case MouseButtons.XButton2:

            case MouseButtons.None:
            default:
                boolKnightmoves = false;
                break;
        }
    }

    private void kpcKnight_MouseUp(object sender, MouseEventArgs e)
    {
        switch (e.Button)
        {
            case MouseButtons.Left:
                boolKnightmoves = false;
                break;
            case MouseButtons.Right:

            case MouseButtons.Middle:

            case MouseButtons.XButton1:

            case MouseButtons.XButton2:

            case MouseButtons.None:
            default:
                boolKnightmoves = false;
                break;
        }
    }

    private void kpcKnight_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        if (boolKnightmoves)
        {                
            txtTest.Text = e.X + ", " + e.Y;
            txtTest.Text += Environment.NewLine + kpcKnight.Location;

            int i = e.X == intCurMouseX ? 0 : e.X > intCurMouseX ? 1 : -1;
            int j = e.Y == intCurMouseY ? 0 : e.Y > intCurMouseY ? 1 : -1;

            txtTest.Text += Environment.NewLine + i.ToString() + ", " + j.ToString();

            kpcKnight.Location = new Point(
                 kpcKnight.Location.X + i,
                 kpcKnight.Location.Y + j);//e.Y == intCurMouseY ? 0 : e.Y > intCurMouseY ? 1 : -1);
                                            //e.X == intCurMouseX ? 0 : e.X > intCurMouseX ? 1 : -1,
            intCurMouseX = e.X;
            intCurMouseY = e.Y;
        }
    }

    private void kpcKnight_MouseLeave(object sender, EventArgs e)
    {
        boolKnightmoves = false;
    }

    private void kpcKnight_LocationChanged(object sender, EventArgs e)
    {
        kpcKnight.Refresh();
    }

我看不出这段代码不会做同样事情的真正原因,但我显然遗漏了一些东西。当我点击骑士并移动它时,它的移动速度与鼠标的速度不同,它的移动速度要慢得多。当它移动到你看不到的地方时,它也会消失。

我如何使骑士棋子以与表单设计器中相同的方式移动,以使棋子在棋盘上移动是有意义的?

任何帮助将不胜感激。


我稍微更新了代码,它似乎确实有所帮助,但是它的动画方面仍然很不稳定,并且面板在移动和放置时会拾取一些背景。

它是如何在表单设计器中如此顺利地完成的?

    private void kpcKnight_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        if (boolKnightmoves)
        {                
            txtTest.Text = e.X + ", " + e.Y;
            txtTest.Text += Environment.NewLine + kpcKnight.Location;

            int x = kpcKnight.Location.X + e.X - intCurMouseX;
            int y = kpcKnight.Location.Y + e.Y - intCurMouseY;

            kpcKnight.Location = new Point(x, y);
            kpcKnight.Refresh();
            /*
            int i = e.X == intCurMouseX ? 0 : e.X > intCurMouseX ? 1 : -1;
            int j = e.Y == intCurMouseY ? 0 : e.Y > intCurMouseY ? 1 : -1;

            txtTest.Text += Environment.NewLine + i.ToString() + ", " + j.ToString();

            kpcKnight.Location = new Point(
                 kpcKnight.Location.X + i,
                 kpcKnight.Location.Y + j);//e.Y == intCurMouseY ? 0 : e.Y > intCurMouseY ? 1 : -1);
                                            //e.X == intCurMouseX ? 0 : e.X > intCurMouseX ? 1 : -1,
            intCurMouseX = e.X;
            intCurMouseY = e.Y;*/
        }
    }
4

4 回答 4

2

为什么不在 Mouse_Move 方法中将 Knights 位置设置为与鼠标位置相同?

就像是:

kpcKnight.Location = new Point(e.X, e.Y)

显然,您可以通过了解 Knight 最初点击的位置并根据该增量平滑移动而不会出现初始抖动,从而使其移动得更好。

于 2013-05-03T19:53:33.283 回答
2

它的绘制速度非常缓慢,因为您在每次鼠标移动时都在移动面板。这意味着表单需要多次重绘自身,而完整的表单重绘成本很高。

基本解决方案是 - 不要经常更改面板的位置。

我看到了两种方法。

  1. 第一种方法很简单,但可能看起来很生涩。不要画出每一个鼠标移动。每 5 个或您设置的任意数字绘制一次。基本上保持一个在鼠标按下时设置为 0 的计数器,每次移动鼠标时,检查是否 ++counter % n == 0。如果是,请进行实际绘图。只要确保在鼠标上画,也一样。或者,同样有效的是,仅当鼠标移动到 x 或 y 距离前一个位置一定数量的点时才绘制。

  2. 第二个想法更复杂,但应该是你能做的最快、最不生涩的事情。鼠标移动时不要移动面板。相反,使面板消失,并将光标设置为显示骑士的自定义光标。在鼠标向上时,重置光标,移动面板并使其可见。这应该尽可能快。

编辑

我将在这里进入隐喻的领域,只是为了了解一些事情。动画是 C# 的一个方面,但它不是它的特性之一。(即,您可以完成它,但它并没有太多让您轻松完成这些事情,而且这不是一个简单的关键功能。)所以...比喻。

想一想您在屏幕上放置的控件,以使您的棋盘和骑士成为一堆挤在停车场的汽车。你所做的就是从直升机高处看停车场。每次移动组件时,您要告诉运行时执行的操作是将汽车完全推离停车场,然后用起重机将它们放置在新位置。当我说“完整的表单重绘很昂贵”时,这就是我所说的范围。

相反,您想从直升机上做的是感知汽车正在神奇地改变位置。与其拥有推土机和起重机,只需将您的直升机视野空白,拍摄您想看到的快照,然后一点一点地更改快照,直到它看起来像您想要的那样。这就是第二个建议 - 不要总是强制表单重新计算每个组件的外观。相反,将动画放在表单上方,仅在完成后更改表单。

您要搜索的关键字是“gdi+”(.NET 图形包)和动画。MouseMove 不会受到伤害,并且 Paint 是您可能需要执行动画的事件。您可以找到很多地方,尽管How to draw rectangle on MouseDown/Move c#可能是一个好的开始。

编辑#2

我有最后一个建议。除了您制作的任何动画外,您还可以使用它。从功能上讲,它本身就可以满足您的要求,但它可能不是您想要的。跟踪鼠标,并修改鼠标悬停在任何面板上的背景图像。在这种情况下,您需要查看ImageList, 和简单的属性,例如您的控件BackgroundImage. 即使您确实有更好的动画效果,这也可能很好。您可以轻松地使用它来显示“骑士不能移动到这里”或“骑士已经移动到这里”。由于它是在更改您的组件而不是移动您的组件,因此这样做非常便宜,并且可以轻松跟上您的鼠标移动。暗示你想要的动作可能就足够了,它会教你比动画和 GDI+ 渲染更重要和更常用的 winform 方面。

于 2013-05-03T19:56:52.287 回答
1

所有你需要的是:

    private int intCurMouseX, intCurMouseY;

    private void kpcKnight_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        if (e.Button == System.Windows.Forms.MouseButtons.Left)
        {
                intCurMouseX = e.X;
                intCurMouseY = e.Y;
        }
    }

    private void kpcKnight_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        if (e.Button == System.Windows.Forms.MouseButtons.Left)
        {
            kpcKnight.Location = new Point(
                kpcKnight.Location.X + (e.X - intCurMouseX),
                kpcKnight.Location.Y + (e.Y - intCurMouseY));
        }
    }
于 2013-05-03T21:03:23.863 回答
0

.Net 中的透明度有点用词不当。背景只是成为父容器的颜色。当您的控件与每个控件重叠时,就像在棋盘上拖动棋子时可能出现的情况一样,您将看到棋子的“背景”,因为控件是矩形的。一种选择是实际剪辑 PictureBox,使其成为不规则形状。这可以通过从 GraphicsPath() 创建 Region() 然后将其分配给 PictureBox 的 Region() 属性来完成。一种简单的方法是使用图像左上角的任何颜色并将其用作“蒙版”颜色。接下来遍历整个图像,仅将像素不是遮罩颜色的位置添加到 GraphicsPath()。这只需在分配 Image() 后使用 PictureBox 完成一次。同样,这种方法要求图像的“背景”(您不想保留的部分)都是相同的颜色,并且这种颜色不会作为您想要保留的图像的一部分出现在任何地方。这是一个例子:

    private void Form1_Load(object sender, EventArgs e)
    {
        // perform this for all your PictureBox pieces:
        this.ClipPictureBoxPiece(this.kpcKnight);
        // ...
        // ...
    }

    private void ClipPictureBoxPiece(PictureBox pb)
    {
        if (pb != null && pb.Image != null)
        {
            System.Drawing.Drawing2D.GraphicsPath gp = new System.Drawing.Drawing2D.GraphicsPath();
            using (Bitmap bmp = new Bitmap(pb.Image))
            {
                Color mask = bmp.GetPixel(0, 0);
                for (int x = 0; x < bmp.Width; x++)
                {
                    for (int y = 0; y < bmp.Height; y++)
                    {
                        if (!bmp.GetPixel(x, y).Equals(mask))
                        {
                            gp.AddRectangle(new Rectangle(x, y, 1, 1));
                        }
                    }
                }
            }
            pb.Region = new Region(gp);
        }
    }
于 2013-05-04T15:05:38.367 回答