Наведите курсор мыши между двумя частями головоломки.

Я создаю игру-головоломку в winforms, и я хочу распознать наведение мыши на любую часть и переместить ее с помощью мыши. Проблема в том, что когда я прикасаюсь к прозрачной части кусочка головоломки, я хочу проверить, есть ли за ним какой-нибудь кусочек, и если да, то начать движение мыши этого другого кусочка, понял?

Я также могу распознать, было ли наведение мыши над изображением или это произошло в прозрачной части кусочка головоломки. Моя проблема состоит в том, чтобы найти лучший способ передать событие мыши на часть позади.

Спасибо заранее.

ОБНОВЛЕНИЕ 1

Ниже приведен класс для кусочка головоломки:

class Peça : DrawingArea
{
    private Point _Offset = Point.Empty;
    public Image imagem
    {
        get;
        set;
    }


    protected override void OnDraw()
    {
        Rectangle location = new Rectangle(0, 0, imagem.Width, imagem.Height);
        this.graphics.DrawImage(imagem, location);
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        if (_Offset != Point.Empty)
        {
            Point newlocation = this.Location;
            newlocation.X += e.X - _Offset.X;
            newlocation.Y += e.Y - _Offset.Y;
            this.Location = newlocation;
        }
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        _Offset = Point.Empty;
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        Down(e);
        //Console.WriteLine(color.ToString());
    }

    public void Down(MouseEventArgs e)
    {
        Bitmap b = new Bitmap(imagem);
        Color? color = null;
        try
        {
            color = b.GetPixel(e.X, e.Y);
            if (color.Value.A != 0 && color != null)
            {
                if (e.Button == MouseButtons.Left)
                {
                    _Offset = new Point(e.X, e.Y);
                    this.BringToFront();
                }
            }
        }
        catch {
             }
    }
} 

Следующий код — это моя DrawingArea (Panel), которую я создаю для работы с прозрачностью:

abstract public class DrawingArea : Panel
{
    protected Graphics graphics;
    abstract protected void OnDraw();

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT

            return cp;
        }
    }

    public DrawingArea()
    {

    }

    protected override void OnPaintBackground(PaintEventArgs pevent)
    {

    }

    protected override void OnPaint(PaintEventArgs e)
    {

        this.graphics = e.Graphics;


        this.graphics.TextRenderingHint =
            System.Drawing.Text.TextRenderingHint.AntiAlias;
        this.graphics.InterpolationMode =
            System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
        this.graphics.PixelOffsetMode =
            System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
        this.graphics.SmoothingMode =
            System.Drawing.Drawing2D.SmoothingMode.HighQuality;


        OnDraw();
    } 
}

И вы также можете увидеть мой код формы:

 public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams handleParam = base.CreateParams;
            handleParam.ExStyle |= 0x02000000;       
            return handleParam;
        }
    }
}

Это мои части, и когда я касаюсь прозрачного пространства в первой части, я хочу взять вторую и переместить ее на мышке вместо того, чтобы ничего не делать...

Это выглядит так:

введите здесь описание изображения

Извините за мой плохой английский.

ОБНОВЛЕНИЕ 2

Я думаю, что подобрался очень близко к разгадке, но сейчас происходит что-то странное, когда я касаюсь части за другой, она исчезает... Что я делаю не так?

НЕКОТОРЫЕ ОБНОВЛЕНИЯ КОДА

Класс изделия:

class Peça : DrawingArea
{
    private Point _Offset = Point.Empty;
    public Boolean movable = false;
    public Image imagem
    {
        get;
        set;
    }

    protected override void OnDraw()
    {
        Rectangle location = new Rectangle(0, 0, imagem.Width, imagem.Height);
        this.graphics.DrawImage(imagem, location);
    }

    public void Move(MouseEventArgs e)
    {
        if (_Offset != Point.Empty)
        {
            Point newlocation = this.Location;
            newlocation.X += e.X - _Offset.X;
            newlocation.Y += e.Y - _Offset.Y;
            this.Location = newlocation;
        }
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        _Offset = Point.Empty;
        movable = false;
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        Down(e);
        //Console.WriteLine(color.ToString());
    }

    public Boolean Down(MouseEventArgs e, bool propaga=true)
    {
        Form parentForm = (this.Parent as Form);
        Bitmap b = new Bitmap(imagem);
        Color? color = null;
        Boolean flag = false;
        try
        {
            color = b.GetPixel(e.X, e.Y);
            if (color.Value.A != 0 && color != null)
            {
                if (e.Button == MouseButtons.Left)
                {
                    _Offset = new Point(e.X, e.Y);
                    this.BringToFront();
                    flag = true;
                    movable = true;
                }
            }
            else
            {
                if(propaga)
                (this.Parent as Form1).propagaEvento(this, e);
                flag = false;

            }
            return flag; 
        }
        catch {
            return flag; }
    }
}

Форма1:

public partial class Form1 : Form
{
    private List<Peça> peças;
    private Point _Offset = Point.Empty;
    public Form1()
    {
        InitializeComponent();

        peças = new List<Peça>();
        SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
        criaListaPecas();
        associaEventosPecas();      
    }

    private void associaEventosPecas()
    {
        foreach (Peça p in peças)
        {
            p.MouseMove += Form1_MouseMove;
        }
    }

    private void criaListaPecas()
    {
        peças.Clear();
        foreach (Control p in this.Controls)
        {
            if (p.GetType() == typeof(Peça))
                peças.Add((Peça)p);
        }
        Console.WriteLine(peças[0].Name);
        Console.WriteLine(peças[1].Name);
        Console.WriteLine(peças[2].Name);
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams handleParam = base.CreateParams;
            handleParam.ExStyle |= 0x02000000;       
            return handleParam;
        }
    }

    private void Form1_MouseMove(object sender, MouseEventArgs e)
    {
        label1.Text = e.Location.ToString();
        gereMovimento(e);
    }

    private void gereMovimento(MouseEventArgs e)
    {
        foreach (Peça p in peças)
        {
            if (p.movable)
            {
                p.Move(e);
            }
        }
    }

    internal void propagaEvento(Peça peça, MouseEventArgs e)
    {
        foreach (Peça p in peças)
        {
            if (p != peça)
            {
                if (p.Down(e, false))
                    break;
            }
        }
    }
}

Заранее спасибо еще раз :)


person Jorge Oliveira    schedule 13.05.2014    source источник
comment
Мы понятия не имеем, о чем вы говорите, если вы не покажете свой код. Что такое puzzle Control? Drawing? или что?   -  person Sriram Sakthivel    schedule 13.05.2014
comment
Это называется проверка попадания. Имея координаты мыши, вы должны проверить, какая часть будет перетаскиваться прочь. Ваша проблема не только в прозрачной части, но и в z-порядке. Я бы рекомендовал вам не использовать для этого элементы управления winform, а иметь модель, в которой вы храните и обновляете данные о фрагментах (местоположение, z-порядок, прозрачность, вращение?) и выполняете тест на попадание.   -  person Sinatr    schedule 13.05.2014
comment
@SriramSakthivel проверьте мои обновления кода к сообщению, а также мое изображение в раскрывающемся списке. Вы понимаете, что я пытаюсь сделать?   -  person Jorge Oliveira    schedule 13.05.2014
comment
@Sinatr, я собираюсь попробовать хит-тест. Когда вы говорите модель, вы имеете в виду класс, который позаботится обо всех данных, верно?   -  person Jorge Oliveira    schedule 13.05.2014
comment
@HighCore большое спасибо за совет. я знаю, что winforms не лучшая платформа для игр. Тем не менее, я являюсь частью проекта, который реализует что-то вроде крошечной ОС, работающей под Windows, и имеет множество функций, некоторые из которых являются играми. Что вы подразумеваете под winforms, на самом деле не поддерживающей прозрачность?   -  person Jorge Oliveira    schedule 13.05.2014
comment
Да. И у вас есть небольшая ошибка в вашем программном обеспечении, попробуйте подобрать что-нибудь в месте 0;0. Лучше иметь выделенную переменную bool для состояния (например, isDragging, isNotMoving и т. д.), чем использовать специальные значения (Point.Empty).   -  person Sinatr    schedule 13.05.2014
comment
@Sinatr, у вас есть какой-нибудь лучший пример для winforms?   -  person Jorge Oliveira    schedule 13.05.2014
comment
Нет, и я не рекомендую использовать для этого winforms (что означает использование коллекции элементов управления).   -  person Sinatr    schedule 13.05.2014


Ответы (3)


Части могут быть представлены как:

public class Piece
{
    public Point Location {get; set;}
    public int Z {get; set;}
    public int ID {get; set;} // to be bound to control or a control itself?
    public Image Image {get; set;} // texture?
    public DockStyle PlusArea {get; set;}
    public DockStyle MinusArea {get; set;}  // can be None
    ...

    public bool HitTest(Point point)
    {
        // assuming all of same size
        if((new Rectangle(Location, new Size(...)).Contains(point))
        {
            switch(MinusArea)
            {
                case Top:
                    if((new Rectangle(...)).Contains(point))
                        return false;
                ...
            }
        }
        switch(MinusArea)
        {
            case Top:
                if((new Rectangle(...)).Contains(point))
                    return true;
            ...
        }
        return false;
    }

Тогда головоломка

public class Puzzle
{
    public List<Piece> Pieces {get; set;}

    public void Draw(Graphics graphics)
    {
        // draw all pictures with respect to z order
    }

    public Piece HitTest(Point point)
    {
        ... // hittest all pieces, return highest z-order or null
    }
}

Это не полное решение, но должно дать вам представление.

В основном:

  • В случае с мышью вы вызываете Figure.HitTest(), чтобы фигура начала двигаться (это то, что вам нужно).
  • Вы передаете все под контроль владельца, вызывая Figure.Draw().
  • Очевидно, что операции перетаскивания вызывают Invalidate().
  • У вас может быть специальный флаг, чтобы указать, что фигура перетаскивается, и рисовать ее по-разному (с тенями, немного смещенными, чтобы имитировать ее натягивание на другие части и т. д.).
  • Каждая фигура представлена ​​в виде прямоугольника и PlusArea или MinusArea (не знаю, как их лучше назвать, это лишняя или отсутствующая площадь соединителей кусков), это упрощение, можно улучшить.
person Sinatr    schedule 13.05.2014
comment
проверьте мое решение... правильно ли я все делаю? Или у меня будут проблемы? - person Jorge Oliveira; 14.05.2014

В общем, держите список всех элементов управления вашей головоломки, отсортированных сверху вниз. Когда вы получаете событие «нажатие мыши» на одной части, проверьте прозрачность в этой точке, если она не прозрачна, обработайте событие на этой части. Если он прозрачен, перенаправьте событие на следующую часть в вашем списке (прямой вызов обработчика события, вероятно, самый простой способ). Продолжайте делать это, пока не найдете непрозрачную точку или не закончатся кусочки.

ОБНОВЛЕНИЕ Вот ссылка на проект, показывающий пример того, как это сделать в чистом GDI. https://drive.google.com/file/d/0B42fIyGTLNv3WlJwNGVRN2txTGs/edit?usp=sharing

person Bradley Uffner    schedule 13.05.2014
comment
Спасибо. Не могли бы вы объяснить лучше, как я могу отсортировать свой список по индексу представления моих элементов управления и оставить его отсортированным сверху вниз? - person Jorge Oliveira; 13.05.2014
comment
Класс, который вы используете для представления своих частей, должен иметь свойство zIndex или что-то еще, указывающее его позицию в стеке. Просто добавьте их в новый список и отсортируйте список по этому свойству. Всякий раз, когда вы добавляете, удаляете или перемещаете элемент в этом списке, вы должны прибегать к нему. - person Bradley Uffner; 13.05.2014
comment
Не могли бы вы проверить мое ОБНОВЛЕНИЕ 2? Я следую тому, что вы предложили, но что-то идет не так... Думаю, я довольно близок к решению... - person Jorge Oliveira; 13.05.2014
comment
Я точно не знаю, что там происходит, но это может быть связано с тем, как мышь захватывается отдельными элементами управления. Из-за этого попытка использовать реальные элементы управления для частей головоломки может оказаться очень сложной. Возможно, вам также придется перенаправить события MouseMove и MouseUP, так как они будут перехвачены элементом управления, который первоначально вызвал MouseDown. Возможно, вам повезет больше, если вы воспользуетесь чистым GDI для одного большого элемента управления, чтобы рисовать изображения и обрабатывать все события мыши. Таким образом, вам не нужно беспокоиться о том, что органы управления будут бороться за то, кому принадлежит мышь. - person Bradley Uffner; 13.05.2014
comment
Я добавил пример проекта, чтобы вы могли посмотреть, как это сделать с помощью чистого GDI, если вы хотите пойти по этому пути. - person Bradley Uffner; 13.05.2014
comment
Ребята, проверьте мое решение... правильно ли я делаю? Или у меня будут проблемы? - person Jorge Oliveira; 14.05.2014
comment
Вероятно, вам следует использовать цикл для просмотра каждой из частей головоломки вместо того, чтобы вручную проверять каждую по индексу. Текущий способ не очень масштабируем, если у вас много частей. - person Bradley Uffner; 14.05.2014

РЕШЕНО :)

я разобрался... вот код кому как надо (сделал прямо сейчас, так что код еще не чистый):

Класс изделия:

class Peça : DrawingArea
{
    private Point _Offset = Point.Empty;
    public Boolean movable = false;

    public Image imagem
    {
        get;
        set;
    }

    protected override void OnDraw()
    {
        Rectangle location = new Rectangle(0, 0, imagem.Width, imagem.Height);
        this.graphics.DrawImage(imagem, location);
    }

    public Boolean Down(Point e, bool propaga = true)
    {
        Bitmap b = new Bitmap(imagem);
        Color? color = null;
        Boolean flag = false;
        try
        {
            color = b.GetPixel(e.X, e.Y);
            if (color.Value.A != 0 && color != null)
            {
               flag = true;
            }
            else
            {
                flag = false;
            }
            return flag;
        }
        catch
        {
            return flag;
        }
    }
}

Форма1:

public partial class Form1 : Form
{
    private List<Peça> peças;
    private Point _Offset = Point.Empty;
    private Peça peça1, peça2, peça3, peça4;
    private bool canMove;
    private Peça atual;
    private bool other=false;
    public Form1()
    {
        FormBorderStyle = FormBorderStyle.None;
        WindowState = FormWindowState.Maximized;
        InitializeComponent();

        atual = new Peça();

        peça1 = new Peça();
        peça2 = new Peça();
        peça3 = new Peça();
        peça4 = new Peça();
        peça1.imagem = Properties.Resources._4p1_1;
        peça2.imagem = Properties.Resources._4p1_2;
        peça3.imagem = Properties.Resources._4p1_3;
        peça4.imagem = Properties.Resources._4p1_4;

        peças = new List<Peça>();

        peça1.Name = "peça1";
        peça2.Name = "peça2";
        peça3.Name = "peça3";
        peça4.Name = "peça4";

        this.Controls.Add(peça1);
        this.Controls.Add(peça2);
        this.Controls.Add(peça3);
        this.Controls.Add(peça4);

        criaListaPecas();

        foreach (Peça p in peças)
        {
            p.Size = new Size(p.imagem.Width, p.imagem.Height);
        }

        SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);

        associaEventosPecas();
        canMove = false;
    }

    private void associaEventosPecas()
    {
        foreach (Peça p in peças)
        {
            p.MouseMove += Form1_MouseMove;
            p.MouseDown += Form1_MouseDown;
            p.MouseUp += Form1_MouseUp;
        }
    }

    private void criaListaPecas()
    {
        peças.Clear();
        foreach (Control p in this.Controls)
        {
            if (p.GetType() == typeof(Peça))
                peças.Add((Peça)p);
        }
        Console.WriteLine(peças[0].Name);
        Console.WriteLine(peças[1].Name);
        Console.WriteLine(peças[2].Name);
        Console.WriteLine(peças[3].Name);
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams handleParam = base.CreateParams;
            handleParam.ExStyle |= 0x02000000;
            return handleParam;
        }
    }

    private void Form1_MouseMove(object sender, MouseEventArgs e)
    {
        if (sender.GetType().Equals(typeof(Peça)))
        {
            label1.Text = new Point(e.Location.X + (sender as Peça).Location.X, e.Location.Y + (sender as Peça).Location.Y).ToString();
        }
        else
        label1.Text = e.Location.ToString();
        gereMovimento(sender, e);
    }

    private void gereMovimento(object sender, MouseEventArgs e)
    {
        if (canMove)
        {
            if (other)
            {
                Point p = atual.PointToClient(new Point(e.X + (sender as Peça).Location.X, e.Y + (sender as Peça).Location.Y));

                Point newlocation = atual.Location;
                newlocation.X += p.X - _Offset.X;
                newlocation.Y += p.Y - _Offset.Y;
                atual.Location = newlocation;
            }
            else
            {
                Point newlocation = atual.Location;
                newlocation.X += e.X - _Offset.X;
                newlocation.Y += e.Y - _Offset.Y;
                atual.Location = newlocation;
            }
        }
    }

    private void Form1_MouseDown(object sender, MouseEventArgs e)
    {
        if (sender.GetType().Equals(typeof(Peça)) && e.Button == MouseButtons.Left)
        {
            atual = sender as Peça;
            atual.BringToFront();
            criaListaPecas();
            if (atual.Down(e.Location))
            {
                _Offset = new Point(e.X, e.Y);
                canMove = true;
                other = false;
            }
            else
            {
                Console.WriteLine(peças[1].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y)));
                Console.WriteLine(atual.Location);
                Point p = new Point(); 
                if (peças[1].ClientRectangle.Contains(peças[1].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y)))
                    && peças[1].Down(peças[1].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y))))
                {
                    p = peças[1].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y));
                    atual = peças[1];
                    atual.BringToFront();
                    criaListaPecas();
                    _Offset = p;
                    canMove = true;
                    other = true;
                }
                else if (peças[2].ClientRectangle.Contains(peças[2].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y)))
                    && peças[2].Down(peças[2].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y))))
                {
                    p = peças[2].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y));
                    atual = peças[2];
                    atual.BringToFront();
                    criaListaPecas();
                    _Offset = p;
                    canMove = true;
                    other = true;
                }
                else if (peças[3].ClientRectangle.Contains(peças[3].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y)))
                    && peças[3].Down(peças[3].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y))))
                {
                    p = peças[3].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y));
                    atual = peças[3];
                    atual.BringToFront();
                    criaListaPecas();
                    _Offset = p;
                    canMove = true;
                    other = true;
                }
            }
        }
    }

    private void Form1_MouseUp(object sender, MouseEventArgs e)
    {
        canMove = false;
    }
}

Приносим извинения за повторяющийся и запутанный код, но, как я уже сказал, я сделал это несколько секунд назад и еще не чистил код;)

person Jorge Oliveira    schedule 14.05.2014