Xtreme Visual Basic Talk

Xtreme Visual Basic Talk (http://www.xtremevbtalk.com/)
-   .NET Game Programming (http://www.xtremevbtalk.com/-net-game-programming/)
-   -   RPG Dynamic Bitmap Class (http://www.xtremevbtalk.com/-net-game-programming/326173-rpg-dynamic-bitmap-class.html)

ephestion 07-09-2013 08:40 AM

RPG Dynamic Bitmap Class
 
I have some basic knowledge of BitBlt methods but I am trying to learn some of VB.Net approaches to Graphics manipulations.

I don't want to BitBlt or use XNA or DirectX API calls etc.

I found a nice class module called DynamicBitmap here:
http://www.vbforums.com/showthread.p...nd-pathfinding

The module is very good and works flawlessly so far. It even has some pathfinding and other things in it. The part I need for the moment is a simple yet effective way to move around a map.

I have tried various methods but seem to always leave artifacts in the screen of the player image as a trail or in other experiements the map underneath the player's image is rubbed out. I have also run into a problem where the picture box loses it's image/bitmap when some action is performed.

So is there a very simple lean project/solution that demonstrates how to move around a map in VB.Net. The class I am using is DynamicBitmap, and would prefer an example using that class module. It has alphablending for fog of war, which works well, transparent management and is really very good for my purposes. But for the life of me I just cant get a character to move around the map.

Please take a look at the linked class module or visit a tutorial site here to understand:
http://www.visual-basic-tutorials.com/PicRendering3.php

I want to use this module but have no idea how to get it to work for moving a character around the map and doing graphical manipulations.

If I get over this hurdle I will be able to focus on more important things. Any help is appreciated.

ephestion 07-12-2013 06:16 AM

Sorted it out partially.

With this class module I am able to store and restore the grid background. I do this just before applying the sprite changes. Because I am drawing to the form and then updating a picture box, I seem to be getting a buffering without need to apply masks.

Code:

Case Keys.Down
                'DownKey = True
                gPlayerWalking = True
                piX = (aGrid.TileColumn(gPlayerLoc) * gTileWidth) - gTileWidth
                piY = (aGrid.TileRow(gPlayerLoc) * gTileHeight) - gTileHeight + 1
                gPlayerDest = gPlayerLoc + 30
                gPlayerLoc = gPlayerLoc + 30
                'Restore the Grid as it was initially saved
                aGrid.ImageRestore()
                'Update the sprite, Fog and redraw screen
                DrawSprite(2)
                DrawFOG()
                DrawScreen()
                'Set the picture box to use the image in buffer.
                picMap.Image = aGrid.Bitmap

Because I am using a Grid class all the drawing and stuff is based on a Tile count left to right. eg 0,0 px position is actually a rectangle at point 0,0 but called Tile number 1. The Grid I use is 30 tiles wide and 30 tiles high, so the Global variable gPlayerLoc needs to add 30 (30 tiles from current position) to drop to the next row of tiles.

This is all good and working. But now I want to animate the sprite. I want to press key down for example, then the sprite does goes through some walking frames and also moves 1 pixel at a time to the required destination. I know with BiBlt API it is easily done, but what I want to explore VB.Net managed code without getting into full blown DirectX or XNA programming.

How can I animate an object in VB.Net using its Graphics managed code? But at the same time make use of my Grid system. I want the Sprite to move to the next Grid in animated fashion but when they arrive to destination to update the Grid. Best example I can think of is like the way the Civilization games work. You press a key, the sprite animates and moves to the required location and stops. I don't want to stack calls when the key is pressed either ie some kind of lock while moving. Otherwise if a player keeps the button pressed and the move hasn't finished, they may likely make another move based on some Windows que.

If this forum is dead, can someone please tell me since it was about a week since my last post and I had no reply.

Thanks.

This is the sample code that plugs into a normal Windows Forms Application Form.
Quote:

Code:

Public Class Form1



    Dim aTileSheet As New DynamicBitmap
    Dim aGrid As New DynamicBitmap
    Dim aSprite As New DynamicBitmap
    Dim aBlackSquare As New DynamicBitmap
    Dim gPlayerLoc As Integer
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        gPlayerLoc = 1
        aGrid.CreateBitmap(960, 960)
        aSprite.CreateBitmap(19, 32)
        aSprite.LoadBitmap("C:\temp\tilesets\sprite.png", 19, 32)
        aTileSheet.LoadBitmap("C:\temp\tilesets\test5.bmp", 32, 32)
        aSprite.MakeTransparent()
        aBlackSquare.CreateBitmap(32, 32)
        aBlackSquare.Clear(Color.Black)
        aBlackSquare.AlphaBlend()


        aGrid.CreateBitmap(960, 960)
        aGrid.CreateGrid(32, 32, 30, 30)
        For i As Integer = 1 To aGrid.TileCount
            aGrid.DrawOnSurface(aTileSheet.Bitmap, aTileSheet.Rectangle(1), aGrid.Rectangle(i))
        Next

        If aGrid.Exists Then MessageBox.Show(aGrid.TileCount)
        aGrid.ImageStore()

    End Sub



    Private Sub Form1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown
        Dim intX As Integer
        Dim intY As Integer
        aGrid.ImageRestore()
        intX = aGrid.TileColumn(gPlayerLoc)
        intY = aGrid.TileRow(gPlayerLoc)
        gPlayerLoc = gPlayerLoc + 30
        aGrid.FillTile(aGrid.Tile(e.X, e.Y), Color.Red, True)
        PictureBox1.Image = aGrid.Bitmap
        DrawSprite()
        DrawScreen()




    End Sub





    Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint

        DrawScreen()

    End Sub



    Public Sub DrawScreen()

        Dim G As Graphics = Me.CreateGraphics
        G.DrawImage(aGrid.Bitmap, New Point(0, 0))

    End Sub


    Sub DrawSprite()
        aGrid.DrawOnSurface(aSprite.Bitmap, aSprite.Rectangle(5), aGrid.Rectangle(gPlayerLoc), False, True)
    End Sub
End Class



passel 07-12-2013 05:17 PM

The grid class isn't setup to move sprites by pixel.
Instead of using the aGrid.DrawOnSurface to draw the sprite, you should probably just draw it on the form after you drawn the aGrid.bitmap.
If you do it that way you don't have to worry about saving and restoring aGrid.bitmap using the built in functions because you won't be drawing on it.
You would have to calculate the pixel offsets and animation frame, of course, but you would remove the DrawSprite call from the MouseDown event, and the DrawScreen would look like this
Code:

    Public Sub DrawScreen()

        Dim G As Graphics = Me.CreateGraphics
        G.DrawImage(aGrid.Bitmap, New Point(0, 0))
        G.DrawImage(aSprite.Bitmap,Sprite.X, Sprite.Y)  'draw at the pixel location you determine
    End Sub

Actually doing "Dim G as Graphics = Me.CreateGraphics" as you are here is a bad thing to do. I know that was the example the author gave, but it is not good.

You should just invalidate the form when you want to redraw the screen and use the Graphics object provided in the Paint event.
Code:

    Private Sub Form1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown
        Dim intX As Integer
        Dim intY As Integer
        aGrid.ImageRestore()
        intX = aGrid.TileColumn(gPlayerLoc)
        intY = aGrid.TileRow(gPlayerLoc)
        gPlayerLoc = gPlayerLoc + 30
        aGrid.FillTile(aGrid.Tile(e.X, e.Y), Color.Red, True)
        PictureBox1.Image = aGrid.Bitmap
        Me.Invalidate()    'have the form redraw itself
    End Sub

    Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
        DrawScreen(e.Graphics)  'pass the provided Graphics object to the draw routine
    End Sub

    Public Sub DrawScreen(G as Graphics)
        G.DrawImage(aGrid.Bitmap, New Point(0, 0))
        G.DrawImage(aSprite.Bitmap,Sprite.X, Sprite.Y)  'draw at the pixel location you determine
    End Sub

You probably don't need the picturebox either. Just set the "DoubleBuffered" property to "True" for the Form in the IDE.

Possibly the attachment in this post might be of interest, but it is neither a tile based example nor dealing with animation so is not directly relevant.
This post has some simple two phase animation, but still no tile map.

ephestion 07-14-2013 11:25 PM

Thanks for your help.

I placed all updates to the Graphics object in DrawScreen(G) procedure and passed all the picturebox paint events to the DrawScreen(G). I updated my code to invalidate the picturebox instead of the form. Me.picMap.invalidate()

It actually works much faster than declaring a separate graphics object every time to draw the screen. I also kept the Me.Invalidate by using some conditionals to control whether I want the form redrawn or just the picture box.

I also took your advice to handle pixel by pixel movement to the required position and added a a short loop with a wait command.

Code:

Select Case iDirection
                Case 1 'up
                    DrawFOG()
                    Do While iY > iMoveToY
                        nSpriteX = iX
                        nSpriteY = iY
                        For X = pFrame To pFrame + 2
                            nCurrentSprite = X
                            Me.picMap.Invalidate()
                            Wait(60)
                        Next X
                        iY = iY - CType(dY / 9, Integer)
                    Loop
                    'Me.picMap.Invalidate()
                Case 2 'down
                    DrawFOG()
                    Do While iY < iMoveToY
                        nSpriteX = iX
                        nSpriteY = iY
                        For X = pFrame To pFrame + 2
                            nCurrentSprite = X
                            Me.picMap.Invalidate()
                            Wait(60)
                        Next X
                        iY = iY + CType(dY / 9, Integer)
                    Loop
                    'Me.picMap.Invalidate()
                Case 3 'left
                    DrawFOG()
                    Do While iX > iMoveToX
                        nSpriteX = iX
                        nSpriteY = iY
                        For X = pFrame To pFrame + 2
                            nCurrentSprite = X
                            Me.picMap.Invalidate()
                            Wait(60)
                        Next X
                        iX = iX - CType(dX / 9, Integer)
                    Loop
                    'Me.picMap.Invalidate()
                Case 4 'right
                    DrawFOG()
                    Do While iX < iMoveToX
                        nSpriteX = iX
                        nSpriteY = iY
                        For X = pFrame To pFrame + 2
                            nCurrentSprite = X
                            Me.picMap.Invalidate()
                            Wait(60)
                        Next X
                        iX = iX + CType(dX / 9, Integer)
                    Loop
                    'Me.picMap.Invalidate()
                Case Else
                    'MapRegion(nGridCurrent).DrawOnSurface(aSprite.Bitmap, aSprite.Rectangle(pFrame), MapRegion(nGridCurrent).Rectangle(gPlayerLoc), False, False)
            End Select

The sleep event is defined as a Sub called Wait

Code:

Public Sub Wait(ByVal interval As Integer)
        Dim stopW As New Stopwatch
        stopW.Start()
        Do While stopW.ElapsedMilliseconds < interval
            ' Allows your UI to remain responsive
            Application.DoEvents()
        Loop
        stopW.Stop()
    End Sub

Only problem I have at the moment is that when the Key-up event fires it sets a variable gPlayerWalking to True if the movement is valid. It sets some variable like moveTo in Pixels. The moveFrom is set in KeyDown event.

It draws the sprite and animates fine but if I press the key again while it is making the move from one tile area to another it fires the Sprite Drawing again. In the Wait Sub I have to set Application.DoEvents() otherwise nothing is visually displayed until the sprite moves to it's destination. Is there a way to check state or force one procedure to end before accepting key strokes? It seems I have a problem with the asynchronous nature of .Net.

Basically I want a toggle to accept keystrokes only after all required procedures have finished.

passel 07-15-2013 01:57 PM

It would be best to have an overall "game loop" triggered by some periodic timer that would look at "state" variable to determine what it should be doing each time through the loop, rather then multiple inline loops with delays that can be re-entered, but if you don't want to take the time to restructure that now, you can prevent processing the keys when your're busy in a loop by setting a "busy" flag so that you don't process additional keys.
Code:

Static Busy as Boolean

If Not Busy Then    'If I'm not currently processing a key
  Busy = True     
  Select Case iDirection
                Case 1 'up
                    DrawFOG()
                    Do While iY > iMoveToY
                        nSpriteX = iX
                        nSpriteY = iY
                        For X = pFrame To pFrame + 2
                            nCurrentSprite = X
                            Me.picMap.Invalidate()
                            Wait(60)
                        Next X
                        iY = iY - CType(dY / 9, Integer)
                    Loop
                    'Me.picMap.Invalidate()
                Case 2 'down
                    DrawFOG()
                    Do While iY < iMoveToY
                        nSpriteX = iX
                        nSpriteY = iY
                        For X = pFrame To pFrame + 2
                            nCurrentSprite = X
                            Me.picMap.Invalidate()
                            Wait(60)
                        Next X
                        iY = iY + CType(dY / 9, Integer)
                    Loop
                    'Me.picMap.Invalidate()
                Case 3 'left
                    DrawFOG()
                    Do While iX > iMoveToX
                        nSpriteX = iX
                        nSpriteY = iY
                        For X = pFrame To pFrame + 2
                            nCurrentSprite = X
                            Me.picMap.Invalidate()
                            Wait(60)
                        Next X
                        iX = iX - CType(dX / 9, Integer)
                    Loop
                    'Me.picMap.Invalidate()
                Case 4 'right
                    DrawFOG()
                    Do While iX < iMoveToX
                        nSpriteX = iX
                        nSpriteY = iY
                        For X = pFrame To pFrame + 2
                            nCurrentSprite = X
                            Me.picMap.Invalidate()
                            Wait(60)
                        Next X
                        iX = iX + CType(dX / 9, Integer)
                    Loop
                    'Me.picMap.Invalidate()
                Case Else
                    'MapRegion(nGridCurrent).DrawOnSurface(aSprite.Bitmap, aSprite.Rectangle(pFrame), MapRegion(nGridCurrent).Rectangle(gPlayerLoc), False, False)
            End Select
  Busy = False  'I'm done processing the key
End If

Now, if a key is pressed while the first hasn't reached the end of the If..EndIf block, the code will skip over the block and not process the key.

ephestion 07-15-2013 06:34 PM

Thanks. You confirmed that it should work.

I had placed a similar code since posting and it was still bugged. When a user kept pressing the arrow keys repeatedly and fast. So I added this:

Code:

If Not Busy And iDirection > 0 Then

When my movement is finished it would set Direction to 0. But for some weird *** reason despite adding the busy argument to key up and down event:

Code:

If Not Busy Then
            Select Case e.KeyCode
                Case Keys.Up

Which should have blocked all keystrokes while busy the stupid thing would fire the Busy Event again.

Thanks it was frustrating because I wasn't sure if you could lock the keys without an API lock. But this seems good enough and is working.


All times are GMT -6. The time now is 04:53 AM.

Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Search Engine Optimisation provided by DragonByte SEO v2.0.15 (Lite) - vBulletin Mods & Addons Copyright © 2017 DragonByte Technologies Ltd.
All site content is protected by the Digital Millenium Act of 1998. Copyright©2001-2011 MAS Media Inc. and Extreme Visual Basic Forum. All rights reserved.
You may not copy or reproduce any portion of this site without written consent.