O que é Drag-and-Drop? Drag and Drop é arrastar e soltar. Essa funcionalidade é muito usada em aplicações Windows Forms. Em jogos, não é diferente, existem muitos jogos que utilizam esse tipo de funcionalidade. Observe o fluxo dessa funcionalidade aplicada em um jogo: - Clica, geralmente, com o botão esquerdo do mouse em cima de um item, por exemplo;
- Pressiona o botão;
- Arrasta o item para o local desejado;
- Solta o item em algum local permitido; é muito comum esse local ser um inventário de um personagem.
Implementando Drag-and-Drop com XNA – Demonstração(Parte 1)Em aplicações Windows Forms, essa funcionalidade é implementada principalmente baseada em Eventos. Você pode optar por não implementar essa funcionalidade utilizando Eventos, entretanto, o uso deles, facilita e deixa mais simples o processo de construção e manuseio dessa funcionalidade. DemonstraçãoEssa funcionalidade será aplicada em um ambiente 2D(Sprites). Será estabelecido um contrato para os Sprites que precisem ser arrastados(dragged) e/ou soltados(dropped). Observe o contrato(Interface) que será estabelecido: /// /// Interface utilizada por objetos que tanto podem ser arrastados(dragged) /// ou soltados(dropped) /// public interface IDragDrop { /// /// Evento levantado no momento em que a operação Drag-and-Drop for iniciada. /// Evento levantado dentro do método DoDragDrop. /// event EventHandler DragEnter; /// /// Evento levantado quando o sprite, que está sendo arrastado /// pelo sprite condutor, for solto. /// event EventHandler DragDrop; /// /// Propriedade utilizada pelos sprites que receberão os sprites arrastados. /// Esta propriedade encapsulará o campo allowDrop; /// Se esse campo for false, indica que o sprite(um inventário, /// por exemplo) que implementa esta interface, /// rejeita a tentativa de atribuição do sprite(Item) ao Inventário. /// Se o campo for true, o processo é permitido. /// bool AllowDrop { get; set; } bool IsBeingDragged { get; set; } /// /// Inicia o processo de Drag-and-Drop. /// Esse metodo será invocado quando o usuario clicar com o botão /// esquerdo do mouse. /// ///Sprite condutor responsavel por efetuar o processo de Drag-and-Drop void DoDragDrop(Sprite _sprite); } Inicialmente essa demonstração será baseada em arrastar e soltar um Item em qualquer local da tela. Observe o Item utilizado: O mouse será representado pelo Sprite: Conhecidos os elementos iniciais da demonstração, observe o diagrama da demonstração abaixo: A classe abstrata Sprite será a classe mãe(base class) para o Item(maça) e o Mouse. Abaixo serão apresentadas as 2 classes mostradas no diagrama, juntamente com a classe Game1. Logo após será apresentado um fluxo envolvendo o processo de Drag-and-Drop entre as 3 classes: A classe MyMouse Para a construção da classe MyMouse, tenha em mente o seguinte propósito dessa classe para a aplicação(jogo): Queremos saber se o usuário(player) apertou o botão esquerdo do Mouse. O ato de DragAndDrop, é iniciado quando o player aperta o botão esquerdo do Mouse. Observe a classe MyMouse: public class MyMouse:Sprite { /// /// Guarda o estado atual do Mouse /// MouseState mouseState; /// /// Guarda o ultimo estado do Mouse. Este campo é necessario para sabermos /// quantas vezes o usuario clicou. Se esse campo auxiliar não é utilizado, quando /// você for checar no metodo Update, se o usuario clicou, o resultado obtido será /// de um clique efetuado a cada metodo Update executado, repercutindo em resultados /// nao condizentes com a realidade. /// MouseState lastState; /// /// Se o usuario apertou o botao esquerdo do Mouse, esse campo será true, senão clicou /// será false. /// private bool pressed; public bool Pressed { get { return pressed; } } /// /// Este campo é true, se o botao esquerdo do Mouse permanece pressionado(apertado). /// Servirá para sabermos quando o item foi largado(dropped) e também, /// se o item acompanha a trajetoria do mouse. Tais utilidades podem ser verificadas /// no metodo Upadate do Item ///
private bool isPressing; … public override void Update(GameTime gameTime) { //Estado Atual do Mouse mouseState = Mouse.GetState(); //item sendo conduzido pelo sprite condutor atribuído ao Mouse this.Rectangle.X = mouseState.X; this.Rectangle.Y = mouseState.Y; if (mouseState.LeftButton == ButtonState.Pressed && lastState.LeftButton != ButtonState.Pressed) { //Se o botao esquerdo do mouse, do estado atual do mouse, //estiver pressionado, e se o botao esquerdo do Mouse, //do ultimo estado do mouse, nao foi pressionado, dai //entao, temos 1 pressionamento no botao esquerdo do Mouse. pressed = true; } else { /*Imagine a seguinte situação: mantenha o botao esquerdo do mouse * pessionado. Agora imagine que há +- 1 segundo atrás, ambos os * estados(atual e ultimo) do Mouse, apresentavam o botao * esquerdo do mouse pressionado, ou seja, você nao apertou o botao do Mouse. * A ação de apertar é diferente da ação de manter pressionado. * Esse é um dos motivos para precisarmos guardar o ultimo estado do Mouse. * */ pressed = false; } if (mouseState.LeftButton == ButtonState.Pressed && lastState.LeftButton == ButtonState.Pressed) { isPressing = true; } else { isPressing = false; } //ultimo estado do Mouse; isso será util para a próxima //passagem no metodo Update lastState = mouseState; } } A classe Item: public class Item : Sprite, IDragDrop { bool allowDrop; private bool isBeingDragged; /// /// Sprite responsavel por conduzir o item ao longo do processo de DragAndDrop. /// Sprite conductor; #region IDraggable Members /// /// Evento levantado no momento em que a operação Drag-and-Drop /// for iniciada e é levantado dentro do método DoDragDrop. /// public event EventHandler DragEnter; /// /// Evento levantado quando o sprite, que está sendo arrastado, for solto. /// public event EventHandler DragDrop; /// /// Esta propriedade nao será util para este item, entretanto ela poderia /// servir se eu quisesse permitir que outro objeto(sprite) fosse solto em cima /// desse item. /// /// Imagine o cenario a seguir: um item para recuperar "life", e vc queira misturar /// o item de "mana" com o de life. Para permitir que o outro item possa ser /// solto neste item, a fim de efetuar a mistura das porções, /// esta propriedade deve retornar true. /// public bool AllowDrop { get { return allowDrop; } set { this.allowDrop = value; } } /// /// Indica se o item está sendo arrastado ou nao. /// public bool IsBeingDragged { get { return isBeingDragged; } set { isBeingDragged = value; } } /// /// Inicia o processo de Drag-and-Drop. /// Esse metodo será invocado quando o usuario clicar com o botão /// esquerdo do mouse. /// ///Sprite condutor responsavel por efetuar o processo de Drag-and-Drop public void DoDragDrop(Sprite _sprite) { this.conductor = _sprite; this.DragEnter(this, EventArgs.Empty); } #endregion public Item() { this.DragEnter += new EventHandler(Item_DragEnter); this.DragDrop += new EventHandler(Item_DragDrop); } void Item_DragEnter(object sender, EventArgs e) { this.IsBeingDragged = true; } void Item_DragDrop(object sender, EventArgs e) { this.IsBeingDragged = false; } public override void Update(GameTime gameTime) { if (this.IsBeingDragged) { //como o item está sendo arrastado pelo condutor, entao, o item //recebe a posição do condutor this.Rectangle.X = this.conductor.Rectangle.X; this.Rectangle.Y = this.conductor.Rectangle.Y; // deixa o item centralizado ao ponteiro do Mouse this.Rectangle.X -= this.Rectangle.Width / 2; this.Rectangle.Y -= this.Rectangle.Height / 2; if (((MyMouse)this.conductor).IsPressing == false) { this.DragDrop(this, EventArgs.Empty); } } } A classe Game1: public class Game1 : Microsoft.Xna.Framework.Game { ... Item item1; MyMouse myMouse; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } . . . protected override void Update(GameTime gameTime) { this.myMouse.Update(gameTime); this.item1.Update(gameTime); if (item1.Rectangle.Contains(myMouse.MouseState.X, myMouse.MouseState.Y) && this.myMouse.Pressed) { // se o usuario apertou o botao esquerdo do mouse e no momento do clique, // os pontos X e Y estão contidos no retangulo(Rectangle) que envolve // o sprite, entao, uma chamada ao metodo DoDragDrop é feita. this.item1.DoDragDrop(this.myMouse); } base.Update(gameTime); }
protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); this.spriteBatch.Begin(); this.item1.Draw(this.spriteBatch); this.myMouse.Draw(this.spriteBatch); this.spriteBatch.End(); base.Draw(gameTime); } } Algumas imagens do protótipo: Fluxo: - Na classe Game1, no metodo Update, o processo de Drag and Drop é assionado quando o player clica em cima do item através do metodo Item.DoDragDrop(...).
- O método Item.DoDragDrop levanta o evento Item.DragEnter
- Na classe Item existe um metodo que trata o invocamento do metodo Item.DragEnter.Nesse metodo, a propriedade IsBeingDragged recebe true; isso informa que o item está sendo arrastado, daí haverá um tratamento no metodo Update da propria classe Item para esta mudança de estado.
- Quando o usuário(player) deixa de pressionar o botão esquerdo do mouse, o evento Item.DragDrop é levantado.
- Na classe Item existe um metodo que trata o invocamento do metodo Item. DragDrop. Nesse metodo, a propriedade IsBeingDragged recebe false; isso informa que o player deixou de pressionar o mouse(liberou o botão esquerdo do mouse), neste momento, é finalizado todo o processo de Drag-and-Drop e também, o mouse não mais pode controlar ou conduzir o item.
Resumo do Fluxo de forma Gráfica: 
Download da demonstração(Parte 1): draganddropcomxna-parte1.zip 122852 bytes Extendendo a Demonstração feita acima - Demonstração(Parte 2) Para esta demonstração, haverá interação entre 3 objetos: - MyMouse – o mesmo utilizado na demonstração passada
- Item(Maçã)
- Inventário

Agora, não mais será permitido arrastar e soltar a maçã para qualquer local da tela. A maçã será apenas arrastada e solta dentro do inventário. No momento em que a maçã é solta no inventário, a propriedade AllowDrop é verificada – se ela for true, a maçã é colocada dentro do inventário, caso seja false, a maçã é rejeitada pelo inventário e volta ao seu local de origem. Propriedade AllowDrop do Inventário Se AllowDrop é true, logo após a maça encaixada no inventário. Para esta demonstração, existem duas formas da Maçã sair do inventário: - Clicando em cima do inventário e arrastando-o para outro local;
- Apertando o botão direito do mouse.
Diagrama Observe o diagrama abaixo: A classe Item A classe Item, em relação a demonstração(Parte 1), sofre apenas uma modificação - O acréscimo da propriedade PositionAtMomentDragEnter. No momento em que o evento DragEnter é invocado, positionAtMomentDragEnter guarda a posiçãodo item. Guardar a posição do item no momento inicial do processo de Drag and Drop será util para as duas situações abaixo: - A propriedade AllowDrop está “setada” para false.
- O usuário(player) soltar o item fora do inventário.
Observe como fica o metodo que trata o evento DragEnter: void Item_DragEnter(object sender, EventArgs e) { this.positionAtMomentDragEnter = this.Rectangle; this.IsBeingDragged = true; } A classe Inventário A classe inventário vai se comportar semelhante ao item da demonstração(parte 1) anterior. Diferente do item utilizado na demonstração(Parte 1) que não utilizava a propriedade AllowDrop, agora, o inventário fará uso dela. A classe Game1 Na demonstração Parte 2, é nesta classe, aonde maiores modificações foram efetuadas. Veja como ficou o metodo Initialize: protected override void Initialize() { this.item1 = new Item(); this.inventario1 = new Inventario(); this.myMouse = new MyMouse(); this.inventario1.AllowDrop = true; ... //Quando o item for solto, o metodo item1_DragDrop, vai tratar //o comportamento tanto do item como do Inventário this.item1.DragDrop += new EventHandler(item1_DragDrop); } Logo após o metodo Initialize, temos o metodo item1_DragDrop: /// /// //Quando o item for solto, o metodo item1_DragDrop, vai tratar //o comportamento tanto do item como do Inventário /// ///Quem levantou o evento. Neste caso, foi o item. void item1_DragDrop(object sender, EventArgs e) { Item item = (Item)sender; if (inventario1.Rectangle.Contains(myMouse.MouseState.X, myMouse.MouseState.Y) == true) { if (inventario1.AllowDrop == true) { //os valores 15 e 8, foram escolhidos aleatoriamente para a maçã //ficar visualmente "encaixada" no inventário this.item1.Rectangle.X = this.inventario1.Rectangle.X + 15; this.item1.Rectangle.Y = this.inventario1.Rectangle.Y + 8; } else { //No topico "A classe Item", a situação 1 está relacionada com este bloco de codigo this.inventario1.Color = new Color(255, 0, 0, 200); this.item1.Rectangle = item1.PositionAtMomentDragEnter; } } else { //No topico "A classe Item", a situação 2 está relacionada com este bloco de codigo this.item1.Rectangle = item1.PositionAtMomentDragEnter; } } Método Update protected override void Update(GameTime gameTime) { ... this.inventario1.Update(gameTime); if (myMouse.MouseState.RightButton == ButtonState.Pressed) { //No topico "Propriedade AllowDrop do Inventário", a 2º forma de //sair do inventário está relacionada com este bloco de codigo /*a cor do inventário precisa ser "resetada" no caso de uma tentativa * de Drag And Drop, com a maça para o inventário, * efetuada sem sucesso(AllowDrop sendo FALSE). */ this.inventario1.Color = Color.White; item1.Rectangle.X = 380; item1.Rectangle.Y = 250; } … if (inventario1.Rectangle.Contains(myMouse.MouseState.X, myMouse.MouseState.Y) && this.myMouse.Pressed) { // se o usuario apertou o botao esquerdo do mouse e no momento do clique, // os pontos X e Y estão contidos no retangulo(Rectangle) que envolve // o sprite, entao, uma chamada ao metodo DoDragDrop é feita. this.inventario1.DoDragDrop(this.myMouse); } base.Update(gameTime); } Observe o if que envolve o metodo DoDragDrop do inventário, ele ficou semelhante ao if do Item. Algumas Imagens do protótipo: Download da demonstração(Parte 2): draganddropcomxna-parte2.zip 149679 bytes
Todas as imagens utilizadas neste artigo, foram criadas ou editadas(com excessão dos diagramas que são gerados pelo Visual Studio) com a ferramenta Microsoft Expression Design 2 Beta. Dúvidas, Críticas e Sugestões são bem vindas. Obrigado! |