|
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.aHere'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
|