Free Web Hosting Provider - Web Hosting - E-commerce - High Speed Internet - Free Web Page
Search the Web

vbProgramming | Tutorials | Games | RPG Programming III : Loading NPCs
    vbProgramming 
Tutorials -
RPG Programming Series:
Stage III. Loading NPCs
 
     

vbProgramming Home :: vbProgramming Forums :: Tutorials :: Contact :: Links 

 

 

This is the third tutorial in the RPG Programming Series. You'll learn how to load Non-Playable Characters (NPCs) into your game. You'll also learn how to check for collision for the NPCs and you'll start creating a class which displays game dialogue.

Intro
Welcome to the third tutorial in the RPG Programming Series. (Neat short intro huh?)

Fixing a few bugs
Before we get started, there was an error on the previous tutorial in the clsCollision class. Please fix the problem right now by applying the following changes:
In clsCollision.Allow, in Case clsSprite.Direction.Down, change

   If GameClass.alex.TilePos.Y = GameClass.map.Height / 30  Then Return False
   to
   If GameClass.alex.TilePos.Y = GameClass.map.Height Then Return False

And in Case clsSprite.Direction.Right, change:

   If GameClass.alex.TilePos.X = GameClass.map.Width / 30 Then Return False

   If GameClass.alex.TilePos.X = GameClass.map.Width Then Return False

The reason you needed to make this change was because GameClass.map.width is the number of tiles going horizontally and not the number of pixels going horizontally(which would equal GameClass.map.width * 30), same with the Height.

Our clsNPC class
Well, I guess I'll start you off with a bit of code for your NPC.

<This is the first time ever (!) that FrontPage automatically did the code for me! Usually this never works. I guess I just have to paste this into Word and then paste it into frontpage. This is awesome. But it is to make half the code black (The black shows up as green as it is default) and un-double space it all>

Public Class clsNPC
   
'Store his position
   
Public Position As Point
   
'Store his 4 images (Up, Down, Left, Right)
    Public SpriteImage(3) As Bitmap
   
'Store his folder where his images are located
    Public Folder As String
    'Our NPC will have a direction, so that he can turn when the character talks to them.
    Public Direction As clsSprite.Dir

   
Public Sub New(ByVal ImageFolder As String, ByVal Pos As Point, ByVal defaultdirection As clsSprite.Dir)
       
'Store his position
        Position = Pos
       
'Store the variable
        Folder = ImageFolder
       
'Set his default direction
        Direction = defaultdirection
       
'Load the images
        Load(ImageFolder)
   
End Sub 

    Public Sub Load(ByVal ImageFolder As String)
       
'Up = 0
        'Down = 1
        'Left = 2
        'Right = 3
 

       
'Store his images
        SpriteImage(0) = New Bitmap("NPCs\" & ImageFolder & "\up.bmp")
        SpriteImage(1) =
New Bitmap("NPCs\" & ImageFolder & "\down.bmp")
        SpriteImage(2) =
New Bitmap("NPCs\" & ImageFolder & "\left.bmp")
        SpriteImage(3) =
New Bitmap("NPCs\" & ImageFolder & "\right.bmp")

        'Make Lime his transparent color
        SpriteImage(0).MakeTransparent(Color.Lime)
        SpriteImage(1).MakeTransparent(Color.Lime)
        SpriteImage(2).MakeTransparent(Color.Lime)
        SpriteImage(3).MakeTransparent(Color.Lime)
   
End Sub 

    Public ReadOnly Property CurrentImage() As Bitmap
       
Get
            Return SpriteImage(Direction)
       
End Get
    End Property 

End Class

There's your formatted code for the day (Syntax highlighters usually screw up the rest of the page, but this works).  It's pretty basic. It's just like a sprite actually, only with (a lot) less features.

Our images
Now check out the following NPC images below. Create a new folder, within your bin folder, called NPCs, and within that folder create another folder called poom.
In bin\NPCs\poom, save these following images:
down.bmp
up.bmp
left.bmp
right.bmp

Note, these images are not mine. They were given to me by a friend of mine who has another program called RPG Maker which came with this image. If the author of this image would like this removed, please email me.

I want to avoid using RPG Maker images as much as possible (since they're not mine), so please make this small adjustment. For these tutorials, I will only use these 4 images for NPCs. Find your own if you wish to make your own RPG. And no, I'm not an artist; I can't make sprites. Thank you.

Some more changes...and then rendering
NPCs will take up space on the screen (obviously). You need to check for collision when dealing with NPCs too (again, quite obvious). What we'll do is simply tell the game to NOT let Alex walk to the place where the NPC is.

So all you have to do is (in clsNPC)

    Public Sub UpdateMap()
        GameClass.map.Passable(Position.X, Position.Y) =
False
    End Sub

Simple enough. Now, there's one last change we'll make to our NPC class before we leave it alone and say goodbye. In a different tutorial, I'm going to be working with dialogues (so you can talk to these characters, and we'll need a file that will contain all that the NPC needs to say. All I want you to do is change the constructor arguments for the NPC (the "new" sub arguments) to this:

    Public Sub New(ByVal ImageFolder As String, ByVal TilePos As Point, ByVal defaultdirection As clsSprite.Dir, ByVal DialogueLoc As String)

We'll work with this later, in a different tutorial.

Now it's time to render the NPC. You know the drill.

In Gameclass,
 Public Shared poom As New clsNPC("poom", New Point(5, 5), clsSprite.Dir.Left, "poom.dlg")
In form1_paint (Before DrawString "Press Escape to Quit"):
e.Graphics.DrawImage(Game.poom.CurrentImage, New Point((Game.poom.Position.X * 30) - Game.alex.Position.X + 300, (Game.poom.Position.Y * 30) - Game.alex.Position.Y + 240))

You know what the +300 and the +240 are for, see the previous tutorial. Everything else is pretty basic, you draw the NPC in his position, and subtract the offset (Game.Alex.Position) to scroll him. (You might have forgotten how we got this, again see the previous tutorial, we even do the same thing for rendering the map)

Run it and it should work.

Talking to the NPC
NPCs are there to talk to. So we should... talk to them.
Here's a clsEvent class, with a method "CheckForEvents()" which is called whenever you hit Space:

'This class has a very basic purpose: Checking for events
'Examples of events are: Talking to NPCs, Picking up items... that sort of thing.

Public
Class clsEvent
    Public Sub CheckForEvents()
        Select Case GameClass.alex.Direction
            Case clsSprite.Dir.Up
                If GameClass.alex.TilePos.X = GameClass.poom.Position.X And GameClass.alex.TilePos.Y - 1 = GameClass.poom.Position.Y Then
                    MessageBox.Show("You talked to me!!!")
                    GameClass.poom.Direction = clsSprite.Dir.Down

                End If
            Case clsSprite.Dir.Down
                If GameClass.alex.TilePos.X = GameClass.poom.Position.X And GameClass.alex.TilePos.Y + 1 = GameClass.poom.Position.Y Then
                    MessageBox.Show("You talked to me!!!")
                    GameClass.poom.Direction = clsSprite.Dir.Up

                End If
            Case clsSprite.Dir.Left
                If GameClass.alex.TilePos.X - 1 = GameClass.poom.Position.X And GameClass.alex.TilePos.Y = GameClass.poom.Position.Y Then
                    MessageBox.Show("You talked to me!!!")
                    GameClass.poom.Direction = clsSprite.Dir.Right

                End If
            Case clsSprite.Dir.Right
                If GameClass.alex.TilePos.X + 1 = GameClass.poom.Position.X And GameClass.alex.TilePos.Y = GameClass.poom.Position.Y Then
                    MessageBox.Show("You talked to me!!!")
                    GameClass.poom.Direction = clsSprite.Dir.Left

                End If
        End Select
    End Sub

End
Class

Don't get overwhelmed at first sight. Read this carefully and you'll see how this works. Take the first case, Case clsSprite.Dir.Up. This means that the sprite is facing Up and we need to check if poom is above him.
If GameClass.alex.TilePos.X = GameClass.poom.Position.X And GameClass.alex.TilePos.Y - 1 = GameClass.poom.Position.Y Then

Examine it closely. First of all, if the character is below poom, then their X positions have to be equal. If the character is below poom, then his Y position has to be below Poom's position. Hence the
GameClass.alex.TilePos.Y - 1 = GameClass.poom.Position.Y. So the entire If statement can be pseudocoded as "If poom is above me and I'm facing up... Then... say 'You talked to me!!!' and make me face down".

The same goes for the rest of the directions. In gameclass, Public Shared Events as New clsEvents()
Now go to form1_keydown and add another case:
Case Keys.Space
   Game.Events.CheckForEvents()

Now run it and talk to the character. He should speak in a message box saying "You talked to me!!!" and face in the right direction.

Talking to the NPC.... without messageboxes
Alright! Alright! I know you hate the stupid MessageBox.  Let's get rid of it and make a clsText class.

In an RPG, text is displayed in a box on the bottom of the screen. I’ve created (with my uber-leet paint skills) a little box. The size is 480x160 and I’ve calculated that in order for it to be the on the center of the screen, on the bottom, it needs to be in position (80, 320). You can calculate that by knowing that:

To keep it on the centered (left-right): 

The distance between the left edge of the box and the left edge of the screen must be the same as the distance between the right edge of the box and the right edge of the screen.

The box’s width 480, and the screen width is 640.

640-480 = 160.
Keeping the box’s x position on 80 – The distance between the left edge of the box (80) and the left edge of the screen (0) is 80. The distance between the right edge of the box (left + width = 80 + 480 = 560) and the right edge of the screen (640) is 80. Since they are the same, the box is centered in the middle (talking about left’s and right’s here).

To keep the box on the bottom of the screen:

The bottom of the box must touch the bottom of the screen. Very simple.
Box height is 160. Bottom of the screen is 480. 480-160 = 320. A Y position of 320 would keep the box touching the bottom of the screen.a

Here's the uber-leet box (all hail the pentium guy <-- that's me):
...........................

 Now that you're done laughing at me.....
...... Create a new folder in your bin folder called DialogueImage, and save this as default.bmp inside that folder.

Comment out the MessageBox lines in clsEvent. However before proceeding, we need to make a few changes

Making some changes....
We're going to make a few changes before making the text class.
I don’t want to give away too much from the next Event-Driven programming tutorial, but please make this adjustment now so that we won’t have to go back and change a lot later.

Add a Paint sub to GameClass:
Public Sub Paint(ByVal e As PaintEventArgs)
 Dim x as integer
 Dim y as integer
End Sub

In form1_paint, cut out all your code (Control + X) and replace it with Game.Paint(e). Now paste that code into GameClass.Paint
You'll see a whole bunch of errors saying "Cannot find 'game' " For example,
e.Graphics.DrawImage(Game.map.Tiles(x, y) <Etc>) has this error.
Replace the word "Game" with "Me" and it will work.

Go to the Misc section and read up on the AddHandler tutorial and find out what it's about.

In the constructor of GameClass (at the end):
        AddHandler GameForm.Paint, AddressOf Me.Paint

If you've read the AddHandler tutorial, you'll know that this simply means "Whenever GameForm.Paint is called, Me.Paint (aka Gameclass.paint) is called".
You'll get an error by the way. If you read the addhandler tutorial you would know how to fix this. All you have to do is change the GameClass.Paint sub's argument to:
Public Sub Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs)

Simple enough.  It will work as usual (sorry for the messiness in this section, I'm sure I thoroughly confused you). Just run it, it will work as usual. The only difference being that the code to render the game is in GameClass and not in form1.

...Back on subject
Now let's get back on subject.

What we ideally need to do now is create a clsText class which will handle dialogues, conversations……..etc. However this will be time consuming.. so we’ll have an entire tutorial on the clsText class, and dialogues later. For now our goal is to get the basic thing working, and then we can worry about the details.

Create a basic clsText class:

Public Class clsText
    Public Shared Text As String
    Public Shared Position As Point = New Point(140, 395)

End
Class

I told you it would be basic. Anyways, for now I figured 140,395 to be the center of the text box (remember that crappy dialogue box?)

In clsEvent delete any MessageBox statements and replace them with:
clsText.Text = "Umm... who are you? Why are you here?"
Gameclass.gameform.invalidate()


We have to invalidate because the character changes direction (Be sure you invalidate after changing direction).
There should be 4 of these lines within your clsEvent class.

In gameclass.Paint, right at the end:

        If Not clsText.Text Is Nothing Then
            'Draw the dialogue box
            e.Graphics.DrawImage(New Bitmap("DialogueImage\default.bmp"), New Point(80, 320))
            'Draw the text
            e.Graphics.DrawString(clsText.Text, New Font("Courier New", 12, FontStyle.Bold), Brushes.Black, clsText.Position.X, clsText.Position.Y)
        End If

So now it draws the text (only if the clsText.Text has something in it).  Run it, talk to poom. Now walk away.... the dialogue doesn't disappear! We don't want him talking to poom forever!

STOP TALKING TO ME!
Unfortunately, there's no way around this. Poom just wont shut up. Sorry guys, there's no way to NOT make poom talk. You wasted your time with this tutorial....

... not really.

Fixing this problem is actually quite easy. In clsSprite,
    Public InConversation As Boolean
In clsEvent, after you invalidate (4 times, in the whole class), add GameClass.alex.InConversation = True (4 of them in the whole class). Change clsEvent like so:

 

'This class has a very basic purpose: Checking for events
'Examples of events are: Talking to NPCs, Picking up items... that sort of thing.

Public
Class clsEvent
    Public Sub CheckForEvents()
        If Not GameClass.alex.InConversation Then
            Select Case GameClass.alex.Direction
                Case clsSprite.Dir.Up
                    If GameClass.alex.TilePos.X = GameClass.poom.Position.X And GameClass.alex.TilePos.Y - 1 = GameClass.poom.Position.Y
Then
                        GameClass.poom.Direction = clsSprite.Dir.Down
                        clsText.Text = "Umm... who are you? Why are you here?"
                        GameClass.GameForm.Invalidate()
                        GameClass.alex.InConversation = True
                    End If
                Case clsSprite.Dir.Down
                    If GameClass.alex.TilePos.X = GameClass.poom.Position.X And GameClass.alex.TilePos.Y + 1 = GameClass.poom.Position.Y
Then
                        GameClass.poom.Direction = clsSprite.Dir.Up
                        clsText.Text = "Umm... who are you? Why are you here?"
                        GameClass.GameForm.Invalidate()
                        GameClass.alex.InConversation = True
                    End If
                Case clsSprite.Dir.Left
                    If GameClass.alex.TilePos.X - 1 = GameClass.poom.Position.X And GameClass.alex.TilePos.Y = GameClass.poom.Position.Y
Then
                        GameClass.poom.Direction = clsSprite.Dir.Right
                        clsText.Text = "Umm... who are you? Why are you here?"
                        GameClass.GameForm.Invalidate()
                        GameClass.alex.InConversation = True
                    End If
                Case clsSprite.Dir.Right
                    If GameClass.alex.TilePos.X + 1 = GameClass.poom.Position.X And GameClass.alex.TilePos.Y = GameClass.poom.Position.Y
Then
                        GameClass.poom.Direction = clsSprite.Dir.Left
                        clsText.Text = "Umm... who are you? Why are you here?"
                        GameClass.GameForm.Invalidate()
                        GameClass.alex.InConversation = True
                   End If
            End Select

 
        ElseIf GameClass.alex.InConversation Then
            'Since in this tutorial, we're not going to do much with text,
            'We'll just say that text is nothing
            clsText.Text = Nothing
            GameClass.GameForm.Invalidate()
            GameClass.alex.InConversation = False

 
            'In a future tutorial, we'll say something like
            ' "If there's more conversation remaining then advance the conversation "
            ' "Else, end conversation
        End If
    End Sub

End
Class

There's too much green to change to black :). So all I've basically done is:
If alex is in conversation then End the conversation
If he's not in conversation then start one.

Well now run it, talk to the guy (hit space), read what you have to read, and hit space again. Whee it disappears. But now go back and do it again. Talk to him, but then walk away. Uhoh. We don’t want the character moving away in the middle of a conversation.

Now go to form1_KeyDown.

 

        Select Case e.KeyCode
          If Not alex.InConversation Then
            Case Keys.Up
                Game.alex.MoveUp()
            Case Keys.Down
                Game.alex.MoveDown()
            Case Keys.Left
                Game.alex.MoveLeft()
            Case Keys.Right
                Game.alex.MoveRight()
          End if
            Case Keys.Space
                Game.Events.CheckForEvents()
            Case Keys.Escape
                Me.Close()

        End Select


Unfortunately you can't do that, however. So to get around that, break the select case into 2 parts: One part which can happen during a conversation and one part which can't happen during a conversation.

        If Not GameClass.alex.InConversation Then
            Select Case e.KeyCode
                Case Keys.Up
                    Game.alex.MoveUp()
                Case Keys.Down
                    Game.alex.MoveDown()
                Case Keys.Left
                    Game.alex.MoveLeft()
                Case Keys.Right
                    Game.alex.MoveRight()
            End Select
        End If


        Select Case e.KeyCode
            Case Keys.Space
                Game.Events.CheckForEvents()
            Case Keys.Escape
                Me.Close()

        End Select

The first part can't happen during a conversation, meaning that movement can't happen in the middle of a conversation.
The second part can happen during a conversation. You can hit space to advance the conversation (not implemented yet) or if you're already at the end of a conversation, you can hit space to END a conversation.

Sneaky way to get around it huh?

That’s pretty much it for this tutorial. Wait around for the next tutorial and for the clsText tutorial (that one will be a beast. We’ll have to handle “writing” text, character by character, splitting text automatically so it will fit in the box, and conversations with choices (so if someone answers “Yes’ the character has to say something, if they answer No, they have to say something else…..etc!).

Next up - event driven programming

 


 

The Source Code for this tutorial is located here:

You can also locate this by logging in to vbProgramming Forums and going to:
Tutorials > Tutorial Source Code > Source Code