Envie Artigos e Concorra a Prêmios
 
Artigos
Article List
Implementando a funcionalidade Drag and Drop em jogos com XNA
Funcionalidade muito utilizada nos mais variados estilos de jogos
Enviado por Luciano José em 22/3/2008 12:21:39

 

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:
 
  1. Clica, geralmente, com o botão esquerdo do mouse em cima de um item, por exemplo;
  2. Pressiona o botão;
  3. Arrasta o item para o local desejado;
  4. 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ção

Essa 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:
 
  • MyMouse
  • Item
  • Game1
 
 

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:

 
  1. 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(...).
 
  1. O método Item.DoDragDrop levanta o evento Item.DragEnter
 
  1. 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.
 
  1. Quando o usuário(player) deixa de pressionar o botão esquerdo do mouse, o evento Item.DragDrop é levantado.
 
  1. 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
 

Imagem do 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:
 
  1. Clicando em cima do inventário e arrastando-o para outro local;
 
  1. 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:
 
  1. A propriedade AllowDrop está “setada” para false.
  2. 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!

 


Sobre o Autor

???
Não Definido
Não Definido
Ocorreu um erro.
Ocorreu um erro.


Clique para avaliar:

Comentários
" Obrigado aeee Jalf. Nao tem como eu testar isso: eu nao tenho 1 joystick de Xbox e nao tenho um Xbox 360 pra testar...

Pelo que sei através de leitura..., Mouse não é suportado no Xbox 360, mas acredito que seja possivel ter um comportamento semelhante utilizando algumas daquelas alavancas do joystick do Xbox.

Quando eu digo que Mouse nao é suportado no Xbox 360, eu digo baseado neste link:

http://msdn2.microsoft.com/pt-br/library/bb203903(en-us).aspx

"
Enviado por lucianoJose em 23/3/2008 8:10:28:
 
" Excelente artigo Luciano, esse realmente é um tópico que é pouco visto, mas e isso no XBox? Você já testou para ver se isso funciona?"
Enviado por Jose Antonio Farias em 22/3/2008 17:23:24:
 

Adicione seu Comentário
PrêmiosMinimizar

Envie um artigo para o Sharpgames, colabore com a comunidade e fique famoso! Clique aqui e saiba mais.

Logos do XBox 360, XNA e Games For Windows
Copyright 2010 por SharpgamesPolítica de Privacidade  |  Termos de Uso