Animation In Visual Basic .NET: Interacting with Objects

In previous Animation articles we've covered many different methods and aspects of the animation. This article looks at interacting with objects on a playing field. As usual the article will use a game as a base for the code. This time we will work on a simple Solitaire game base, and as usual I will only be doing the animation and leave it up to you to complete the rest of the game.

When you think about Solitaire, you don't necessarily think of animation. However there is an aspect of animation that is very heavily used in Solitaire — Object interaction. We will be looking at interaction with several objects, with some that may be overlapping each other.

Not long ago there was a thread on CodeGuru where someone was looking for a method to detect mouse movement over different areas of an image, clickable Hotspots, so to speak. There are many ways to achieve the same results, and the method shown here might not be ideal for all applications, but is a good tool to have in your box of tricks.

The Graphics for Our Animation Example

Let's start with the graphics. Firstly let me say that, yes, I am aware that there is a DLL with all the card graphics in it that you can use, however I decided to use image files so that you can replace them with any that you choose. There are 60 odd images, including all the playing cards, two jokers and several Backings. So the first task is to load the images.

    Private Cards() As Image
    Private Backs() As Image
    Private CardSize As Size

    Private Sub BuildCards()
        ReDim Cards(53)
        ReDim Backs(1)
        Dim Tmp As Integer
        Dim Path As String = Application.StartupPath
        For Tmp = 0 To 53
            Cards(Tmp) = New Bitmap(Path & "\card\" & (Tmp + 1).ToString & ".png")
        Next
        For Tmp = 0 To 1
            Backs(Tmp) = New Bitmap(Path & "\card\b" & (Tmp + 1).ToString & "fv.png")
        Next
        'Lets use one of the cards to get the size..
        CardSize = Cards(0).Size
    End Sub

But now there is additional information that is needed. We want the draw order of the cards, so that one card can be put on top of another. We also want to know if the card is not shown, or turned over, and then we want the position of the card. And all of this can be initialized with the card loading, so our above sub can be expanded a little.

    Private Cards() As Image
    Private Backs() As Image
    Private CardPos() As Point
    Private CardShow() As ShowMode
    Private DrawOrder() As Integer
    Private CardSize As Size
    Private MyBrushs() As SolidBrush

    Private Enum ShowMode
        Hidden
        Show
        Back
    End Enum

    Private Sub BuildCards()
        ReDim Cards(53)
        ReDim Backs(1)
        ReDim CardShow(53)
        ReDim CardPos(53)
        ReDim MyBrushs(53)
        ReDim DrawOrder(53)
        Dim Tmp As Integer
        Dim Path As String = Application.StartupPath
        For Tmp = 0 To 53
            Cards(Tmp) = New Bitmap(Path & "\card\" & (Tmp + 1).ToString & ".png")
            MyBrushs(Tmp) = New SolidBrush(Color.FromArgb(&HFF000000 Or (Tmp + 1)))
            DrawOrder(Tmp) = Tmp
        Next
        For Tmp = 0 To 1
            Backs(Tmp) = New Bitmap(Path & "\card\b" & (Tmp + 1).ToString & "fv.png")
        Next
        'Lets use one of the cards to get the size..
        CardSize = Cards(0).Size
    End Sub

At the same time, we are also setting up a color brush for our hotspots. For each unique card we have a unique colored Hotspot image, I used RGB colors 1-54 for the 54 unique cards and RGB 0 for the board.

The Board

Now we have the cards in memory, you can now start building the game board. We will be using the Standard Green that most people associate with card games for the background. So we have our Form, and I normally like to place a picturebox on the form, however you can use the form's image too. So our game board is this image, and we will use it to build our Buffer. Buffers is explained in full on previous articles so I'm not going into in here

    Private Buffer As Image
    Private BufferG As Graphics
    Private HotSpots As Image
    Private HotSpotsG As Graphics
    Private Boardsize As Rectangle

    Private Sub InitBuffer()
        Buffer = New Bitmap(Board.Width, Board.Height)
        BufferG = System.Drawing.Graphics.FromImage(Buffer)
        Boardsize.Size = Buffer.Size
        HotSpots = New Bitmap(Board.Width, Board.Height)
        HotSpotsG = System.Drawing.Graphics.FromImage(HotSpots)
    End Sub

As you may have noticed, I'm initializing two Buffers, The second one will not be drawn but used for the hotspot detection. When a card is drawn on the board's buffer, the Hotspot image will be drawn in the same position in the second buffer. Of course now we need some code to draw these images.

    Private Sub DoDisplay()
        BuildBuffer()
        ShowCards()
        Board.Image = Buffer
    End Sub

    Private Sub BuildBuffer()
        BufferG.FillRectangle(Brushes.Green, Boardsize)
        HotSpotsG.FillRectangle(BackBrush, Boardsize)
    End Sub

    Private Sub ShowCards()
        Dim Tmp As Integer
        Dim Order As Integer
        For Tmp = 0 To 53
            Order = DrawOrder(Tmp)
            Select Case CardShow(Order)
                Case ShowMode.Show
                    BufferG.DrawImage(Cards(Order), CardPos(Order))
                    HotSpotsG.FillRectangle(MyBrushs(Order), CardPos(Order).X, _
                                            CardPos(Order).Y, CardSize.Width, _
                                            CardSize.Height)
                Case ShowMode.Back
                    BufferG.DrawImage(Backs(0), CardPos(Order).X, CardPos(Order).Y, _
                                      CardSize.Width, CardSize.Height)
                    HotSpotsG.FillRectangle(MyBrushs(Order), CardPos(Order).X, _
                                            CardPos(Order).Y, CardSize.Width, _
                                            CardSize.Height)
                Case ShowMode.Hidden
                    If MouseCard - 1 = Order Then
                        BufferG.DrawImage(Cards(Order), CardPos(Order))
                    End If
            End Select

        Next
    End Sub
 

Here I've split the key codes into several different subs so that it's easier to see what's doing what.

  • BuildBuffer simply paints our initial background, so we have a clean image to place cards on.
  • ShowCards paints each card on the buffer in the order listed in the Cardshow Array, with bottom card first.
  • Dodisplay pulls it all together, and places the buffer image on the form.

Animation In Visual Basic .NET: Interacting with Objects

Finding the Cards

Now we have all of the cards displayed on the board, we setup our hotspots for picking up the card we are 'Hovering' over. We need code that accepts co-ordinates and returns the color of the pixel at that spot. The following code does just that.

Private Function GetHotSpot(ByVal HotImage As Image, ByVal Location As Point) As Integer
     Dim Xloc As Integer
     Dim Yloc As Integer
     Xloc = (Location.X)
     Yloc = (Location.Y)
     ' This function does not like values outside the image limits..
     GetHotSpot = (CType(HotImage, Bitmap).GetPixel(Xloc, Yloc).ToArgb) And &HFFFFFF
End Function

But now we need the location of our mouse, for this we use theMouseMove event.

Private Hover As Integer

Private Sub Board_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
                Handles Board.MouseMove
    Dim loc As Point
    loc.X = e.X
    loc.Y = e.Y
    'Make sure we dont move outside the playing field...
    'Some functions dont like it..
    If e.X < 0 Or e.Y < 0 Or e.X >= Board.Width Or e.Y >= Board.Height Then
        'Gone out of bounds , Do nothing else
        Exit Sub
    End If
    Hover = GetHotSpot(HotSpots, loc)
End Sub

NOTE: There are some situations where mousemove will trigger even if it is not on the form; we need to make allowances for this. I simply check if we're in limits and exit out the sub if not.

Next we want to know if the user has clicked on the card they are hovering over. If so, we want to pick the card up and move it with the mouse. Using the MouseDown Event we detect when the user has pressed down on the Button and MouseUp event when the user releases the button.

Private MouseCard As Integer
Private MouseOffset As Point

Private Sub Board_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
          Handles Board.MouseDown
    If e.Button = Windows.Forms.MouseButtons.Left Then
        If Hover <> 0 Then
            Demo = False
            MouseCard = Hover
            CardShow(Hover - 1) = ShowMode.Hidden
            MouseOffset.X = CardPos(Hover - 1).X - (e.X)
            MouseOffset.Y = CardPos(Hover - 1).Y - (e.Y)
        End If
    End If
End Sub

Private Sub Board_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
           Handles Board.MouseUp
    If MouseCard <> 0 Then
        CardShow(MouseCard - 1) = ShowMode.Show
        CardPos(MouseCard - 1).X = MouseOffset.X + (e.X)
        CardPos(MouseCard - 1).Y = MouseOffset.Y + (e.Y)
    End If
    MouseCard = 0
    DoDisplay()
End Sub
 

If the mouse is over one of the cards, and the user presses and holds the left click button, we essentially need to allow the card to float with the mouse. So we transfer the card info into Mouse variables, find the offset position (that is the position that the mouse is sitting on the card) so that when we move the mouse the card can move with it and it looks like the card is stuck to the mouse. Once the button is released we then drop the card back onto the board and clear the mouse variables.

Of course we need to adjust the MouseMove event above to allow us to redraw the card as it moves around.

Private Sub Board_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Board.MouseMove
    Dim loc As Point
    loc.X = e.X
    loc.Y = e.Y
    If e.X < 0 Or e.Y < 0 Or e.X >= Board.Width Or e.Y >= Board.Height Then
        Exit Sub
    End If
    Hover = GetHotSpot(HotSpots, loc)
    TextBox1.Text = Hover
    If MouseCard &tl;> 0 Then
        CardPos(MouseCard - 1).X = MouseOffset.X + (e.X)
        CardPos(MouseCard - 1).Y = MouseOffset.Y + (e.Y)
        DoDisplay()
    End If

Final Thought

The project download is slightly different to the code posted here, as I've added several additional clicks and features, and specific workarounds.

Internet Explorer: Mousemove event is triggered for every pixel that the mouse moves over the image. If the code had to be used as above, the movement is actually very sketchy, and inconsistent, as the event can trigger up to 10 times per 16ms tick or more. And therein is the problem. We trying to repaint and draw the board faster than we can render it. So as a workaround I used a timer to give me a ~40 ms tick, and only on those ticks do I re-render the board. This smoothed out the Card movement nicely.

Something else that's in the code is that right clicking on a card will flip it over and show you the back.

Taking the code further, you could add code to snap cards into specific positions on the board, or make special hotspots on the board to place cards on. Currently there are no game rules written into the code, as that was not the scope of this article, however, if you have a wild enough imagination you can develop this simple card board into any type of card solitaire you want.



About the Author

Richard Newcombe

Richard Newcombe has been involved in computers since the time of the Commodore 64. Today, he has excelled in programming, and designs. Richard is in his mid 30's and, if or when you looking for him look no further than his computer. Always willing to help and give advice where he can in regard to computer related subjects. At present he is working as a .NET 2008 Software Developer for Syncrony Web Services, South Africa.

Related Articles

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: November 20, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Are you wanting to target two or more platforms such as iOS, Android, and/or Windows? You are not alone. 90% of enterprises today are targeting two or more platforms. Attend this eSeminar to discover how mobile app developers can rely on one IDE to create applications across platforms and approaches (web, native, and/or hybrid), saving time, money, and effort and introducing apps to market faster. You'll learn the trade-offs for gaining long …

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

Most Popular Programming Stories

More for Developers

RSS Feeds