|
Intro
Welcome to the first tutorial in the RPG Programming Series. Instead
of making one big tutorial for our RPG, we'll simply break it up
into several stages to make things easier. Since this is the first
stage, we won't worry about story design, level design..etc. For
now, we'll focus mainly on getting our basic game to function. Since
this series will be rather large, I ask that you please bear with me
as I release these tutorials, it may take some time. Furthermore,
since there will be massive amounts of code - any code previously
explained in another tutorial will not be elaborated on here. Please
be sure that you have learned and "mastered" the tutorials in
Section I. Please remember to read tutorials in Section I and II,
that's a prerequisite for this series.
Theory
Before we make this game, we
need to plan it out. This section will focus on and give you ideas
about how to approach various subjects.
First of all, when we make our game, we're obviously going to have
levels. Let's say the character finishes level I, and he goes on to
level II. Now wait, how will we redraw level 2? Do we do something
like:
If Level1 Then
<Draw objects on Level1>
ElseIf Level2 Then
<Draw objects on Level2>
End If
No! That's preposterous. The best
thing to do is change some variables around <-- yeah that sounded
vague. Let me elaborate. We're going to learn a concept called
Scripting, which we'll touch on later - but we'll pseudocode it
out right now.
Instead of using the method above, what we can do is change the
variables loaded. For example, there'll be a variable which will
store the map name, when the new level loads we'll just feed in a
different map name. We'll have to change the character's position in
the world, so the map text file (see Reading and Writing Text
files) will also hold the character's default position.
Another way we can approach this
is to have a textfile for each level (A level is different
from a map). The Level text file will hold:
a) The path to the map text file
b) Default character location
When we load in a new level, we'll just say something like
MainGameClass.CurrentLevel = "level2.txt". It will begin reading the
file. It stores the path tot he map text file in a variable, and
default character location into a variable. Then it will read the
map text file, and thus a new array of tiles (see Tile Based
Collision Detection) will be stored into some MainGameClass.Tiles(,)
variable. Form1_Paint will still be rendering the tiles in
MainGameClass.Tiles(,) ... but the only difference is, a new set of
tiles will have been loaded. The character's position will be set to
a new position (the value stored in Default Character Location).
Woah, that took a while to explain.
In any case, if you don't understand - don't worry. It's just
theory. Some guys learn better when you tell them what's coming
first ;). Others like to see the code, and then they'll understand.
So if you don't understand I'm guessing you'll be the latter people.
Anyways, I'm just giving you a
heads up on what's coming. Now it's time to get started!
Getting the images
Please note: The
images are not mine. My friend has a program called RPG
Maker, and he gave me these images to use.
First things first, our RPG
won't be very .. how should I say it... big on graphics. It's
up to you to get some more images if you want :). For now, we'll
makeshift with these 3 map images.
Create a new project called RPG Engine. Save these images into your
bin folder in the tiles\basis folder.
(1.jpg)

(2.jpg)

(3.jpg)
Now for the character images:
-------------------------------------
There's an error on 8k.com which prevents you guys from downloading
any zip files..etc, after searching through their
help support, I managed to find a 'quick fix'.
To download any of my files, visit this URL
http://www.nocache.vbprogramming.8k.com/files/alex.zip
then click alex.zip. If that doesn't work then you can always try
the normal way:
http://www.vbprogramming.8k.com/files/alex.zip
--------------------------------------
Extract those images and save it in
your bin folder under the Sprites\Alex folder.
Now you should have images in these folders:
bin\sprites\Alex\ (12 images in this folder)
bin\tiles\basis (3 images in this folder)
Take a quick look at the files and how they're arranged. Now let's
get coding!
Setting up our clsSprite class
Remember, we've already covered most of this.
Instead of throwing all the sprite code into Form1, let's make it
cleaner by adding a clsSprite class to store the position/frame/direction..etc
of the sprite.
Create a new class called clsSprite, and add the following
variables:
'Stores
the position (x,y) of the sprite. Sometimes these values will be
decimals, so use PointF
Public Position
As PointF
'Stores the
folder in which the Sprite images are located in
Public Folder As String
'Stores the
current frame (1 2 or 3) of the sprite
Public Frame As Integer
What we're going
to do is have an array of images for each direction/frame. Declare
the following constants:
'Stores
the number of Directions each sprite can have
Public Const NumberOfDirections As Integer = 4
'Stores the
number of Frames each sprite can have
Public Const NumberOfFrames As Integer = 3
Now
declare the following bitmap:
'Holds all
the images for the sprite
Public
SpriteImage(NumberOfDirections, NumberOfFrames) As Bitmap
Now wait, let's
fix bad habits before we continue. We know that arrays begin with 0.
So SpriteImage(4,3) really holds 5 * 4 = 20 images, not 4 * 3 = 12
images. The extra 8 images will hog up memory! So, simply change
that line to:
Public
SpriteImage(NumberOfDirections - 1, NumberOfFrames - 1) As Bitmap
Cool, simple
enough so far. Now we have to store the direction. Rather than
saying to ourselves, "0 is up, 1 is down"...etc, a better way is to
use an Enum (short for Enumeration):
'Stores
the current direction of the sprite
Public Enum Dir
Up = 0
Down = 1
Left = 2
Right = 3
End Enum
'Create a copy
for our sprite
Public Direction As Dir
What exactly is
an enum? It's just another way to refer to a number. Instead of
remember what direction corresponds to whatever number, it's much
easier to use Enum. It's sort of like "Renaming" a number, so to
speak. Let's say we push right. We'd simply say MySprite.Direction =
Direction.Right instead of saying:
dim direction as integer and mysprite.direction = 3. It's a much
easier way to do this. If you don't understand this, you'll figure
out more as we go along.
Now, our New
sub will load the sprite images into the array. In this game, all of
our sprites will be stored in bin\Sprites\. Each individual sprite
will be stored in bin\Sprites\MySpriteName\... For example, our Alex
sprite is stored in bin\Sprites\Alex\. So, to find out where the
sprites are, we just need the sprite name! So declare:
Public
SpriteName As String
Storing our images to the
SpriteImage array
Let's keep moving. I think we're done with variables for now, let's
create the New sub. This sub will store our images
'Constructor.
Public Sub New(ByVal NameOfSprite As String)
SpriteName = NameOfSprite
End Sub
Now wait, when
we store our images, we can simply say <Don't type this in, just
look at the following code>:
'Sprites\Alex\right1.GIF
SpriteImage(Dir.Right, 0) = New Bitmap("Sprites\" & NameOfSprite &
"\right1.GIF")
SpriteImage(Dir.Right, 1) = New Bitmap("Sprites\" & NameOfSprite &
"\right2.GIF")
SpriteImage(Dir.Right, 2) = New Bitmap("Sprites\" & NameOfSprite &
"\right3.GIF")
Yeah, we could say that and go through it for every direction, which
would lead to 12 different directions. Instead of doing it this
way, why not brush up our programming skills and doing it the
harder (but shorter) way :D. First of all, I want you to notice how
when the FrameNumber (2nd dimension element in SpriteImage) is 0,
the actual image is right1. I .. erm .. messed up. I should have
named my images right0.GIF, right1.GIF, right2.GIF ... that was my
fault. So the image is really the FrameNumber + 1.
Ok, now here's how to do this, here's some pseudocode:
-Loop through every direction and frame number
-Set each bitmap.
Heh, doesn't sound hard as it looks! Here's the "Loop through every
direction and frame number" part.
Dim
CurrentDirection As Integer
Dim CurrentFrame As Integer
For CurrentDirection = 0 To NumberOfDirections - 1
For CurrentFrame = 0 To NumberOfFrames - 1
Next
Next
The next part is a bit tricky, normally you'd expect to do something
like (this is inside the loop remember) :
SpriteImage(CurrentDirection,
CurrentFrame) = New Bitmap("Sprites\" & NameOfSprite & "\" &
CurrentDirection & CurrentFrame + 1 & ".GIF")
But if you
really look at it, that translates to [Pretending CurrentDirection
and CurrentFrame are both 0]:
Sprites\alex\01.GIF
That's not what we want, we want it to say:
Sprites\alex\up1.GIF
So our problem right now is "Translating" our CurrentDirection into
text from numbers. Pretty simple - Create a function called
ReturnDirection:
'ReturnDirection returns a String from an Integer value provided by
the argument
Public Function
ReturnDirection(ByVal CurrentDirection As Integer) As String
If CurrentDirection = Dir.Up Then Return "up"
If CurrentDirection = Dir.Down Then Return "down"
If CurrentDirection = Dir.Left Then Return "left"
If CurrentDirection = Dir.Right Then Return "right"
End Function
To simplify it,
take the first statement for example.. it translates into: If
CurrentDirection = 0 Then Return "up" . Understand? Now let's fix
our line in the New sub:
SpriteImage(CurrentDirection, CurrentFrame) = New Bitmap("Sprites\"
& NameOfSprite & "\" & ReturnDirection(CurrentDirection) &
CurrentFrame + 1 & ".GIF")
Just a quick refresher, now our New sub should look like this:
Public Sub
New(ByVal NameOfSprite As String)
'Store the SpriteName (the sprites are stored in bin\Sprites\SpriteName)
SpriteName = NameOfSprite
Dim CurrentDirection As Integer
Dim CurrentFrame As Integer
'Loop through every Direction
For CurrentDirection = 0 To NumberOfDirections - 1
For CurrentFrame =
0 To NumberOfFrames - 1
'Set each bitmap (all 12)
SpriteImage(CurrentDirection, CurrentFrame) = New Bitmap("Sprites\"
& NameOfSprite & "\" & ReturnDirection(CurrentDirection) &
CurrentFrame + 1 & ".GIF")
'These aren't 2
different lines, the page isn't wide enough to fit the entire line.
Next
Next
End Sub
Heh, I think I
just took something that was simple (and long) and made it a
bit more complicated (and shorter). Hey at least it gave you a quick
refresher on Return values!
Now let's test out our class! Go back to form1 and in your globals:
'The alex sprite
has the name of.. alex, and is stored in the \Sprites\alex folder
Dim alex As New
clsSprite("alex")
Now go to Paint:
e.Graphics.DrawImage(alex.SpriteImage(alex.Direction, alex.Frame),
alex.Position)
As you can see,
we've effectively minimized the amount of code in Form1 because we
used classes. Run the app, you'll see that we forgot to set
transparency. So go back to clsSprite, in the New sub
right after
SpriteImage(CurrentDirection, CurrentFrame) = New Bitmap(etc......):
SpriteImage(CurrentDirection,
CurrentFrame).MakeTransparent(Color.Lime)
There now it's
transparent!
Movement
Well, again this is pretty easy since there's
been several tutorials on animation. We're going to do Tile Based
animation. The majority of our code will be in clsSprite. Since
we'll use OOP Practices, you'll see a lot less code in Form1 (thus,
avoiding clutter). First, in the clsSprite globals:
Dim InMotion as Boolean
This variable will check if the sprite is
currently InMotion. We don't want him to change direction when he's
already moving, so we use this as a "Safeguard" so that he can't
move in mid-motion.
Now I'm
going to do something which may seem absolutely random to you.
However, in the long run, it will make sense. Create a new class
called GameClass. And in GameClass:
'The form
in which we will draw to.
Public Shared GameForm As Form
Public Sub New(ByVal frm As Form)
'Set the form
that we'll draw to
GameForm = frm
End Sub
Now go
back to form1, and simply type in (in Globals)
Dim Game
As New GameClass(Me)
The reason why we're doing this is to store the form in which
we're going to draw to in the GameClass so that it can be used by
other classes (which is the reason why it is Shared). If you
remember from my other tutorials, Shared means that any class can
access that object without instantiating that class.. quick example:
If you had classA and classB, and you wanted to access GameForm,
you'd simply refer to it as: GameClass.GameForm (you can do the same
with any class).
Now,
remember we've already done this before, so the following code
(hopefully) shouldn't be a big surprise to you. Take a look at
the code to move the sprite up (in clsSprite)
Public Sub
MoveUp()
'If the sprite
is currently not moving Then move him up
If Not InMotion Then
'Now he's in motion.
InMotion = True
'Loop Variable
Dim x As Single
'Set his direction
Direction = Dir.Up
'Loop 30
times.
For x = 1 To 30
'Move
him one unit up, 30 times.
Position.Y -= 1
'Let your
application do other events (ex: Check for keypress and refresh the
form)
'in this loop.
Application.DoEvents()
'Refresh the form
GameClass.GameForm.Invalidate()
Next
'Now he's done moving.
InMotion = False
End If
End Sub
Wow, that code
took quite a while to format :). Anyways, I'm sure you understand
this, it simply moves the character one unit, 30 times (meaning 30
units). The reason I did the whole GameClass thing was to be able to
refresh the Form from the loop itself.
We're going
to make several changes to these Move subs, so I won't paste
all 4 right now, I'll paste them when we're done making the changes.
For now, assume we've created all 4 (or create them yourself if you
wish), and go to form1_keydown. This shouldn't come to a surprise to
you at all (besides the fact that you probably don't have a MoveDown,
MoveLeft, and MoveRight subs yet - if you don't, just comment those
lines out):
'Select
case is just another way of writing a bunch of If statements. Pretty
self explanatory
Select Case e.KeyCode
Case Keys.Up
alex.MoveUp()
Case Keys.Down
alex.MoveDown()
Case Keys.Left
alex.MoveLeft()
Case Keys.Right
alex.MoveRight()
End Select
'Display his
position
Me.Text = "Current Position: " & alex.Position.ToString()
Oh and, go to
form1_load, I hope you remember seeing these:
Me.SetStyle(ControlStyles.DoubleBuffer, True)
Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
Me.SetStyle(ControlStyles.UserPaint, True)
The last one might come as a surprise to you. Earlier, I thought
that DoubleBuffer and AllPaintinginWMPaint is enough to reduce the
flicker, but some information (given by a fellow vbProgrammer)
indicated to me that you need UserPaint as well.
Now run the project (heh, I mean those of you who did the MoveDown
MoveLeft and MoveRight subs :)). I'm sure most of you don't have it,
but don't worry about that, I'll give you the code for those subs
soon. By the way, since you only have a MoveUp sub, why not just
change the guy's default position to 120,0 to test out his movement.
To accomplish this, just change the line to:
Public
Position As PointF = new PointF(0,60)
Controlled Movement
First thing
you'll notice is (especially if you have a good computer): HE MOVES
TOO FAST! One thing I should tell you right from the start,
GDI+ is slow. The moment you start adding in objects, you won't need
to slow down the guy, he'll go really slow! But anyways, we need to adapt our game to
newer/older computers. First of all, (we'll implement this later),
we'll do a check to see how many frames per second you get. And
based on that, we'll figure out how fast the character must move.
Now, listen
up :P. We need to plan ahead. So before moving on to animation,
let's fix problems before they pop up again! First, let's experiment
with incrementing his position by 0.1 each time:
See the 30
in the loop (For x = 1 to 30)? Change that to 300. See the
Position.Y -= 1? Change the 1 to a 0.1 & now you're all set. Try
moving the character
Now
you'll see that he moves normally (or way too slow for some of you
guys with slower computers, or even too fast for the guys with fast
computers!). Don't worry about the differences yet.
Take a look at your position being displayed when you move up. When
you move up once, your position should be (30,0), but for
some odd reason it shows up as 30.0000000000042 or something
like that. It's really strange, I have no idea why .NET messed up my
number like that. It probably
doesn't like my processor (hyperthreaded). These [very] tiny changes
can certainly screw up your number when it comes to collision
detection. To counter that problem, I simply rounded the number to 0
decimal places (aka, whole numbers). So in the MoveUp sub, before
InMotion = False, I typed in:
Position.Y
= Math.Round(Position.Y, 0)
As I said
before, '0.1' is NOT going to work once we start adding in objects.
You'll think he's going slow for some reason :). What we need to do
is figure out a formula to relate the Loop Variable (right now it's
300) to the Increment variable (it's 0.1 right now).
Well it's pretty simple, first: Change the 300 back to 30 and the
0.1 back to 1. When we changed the 30 to a 300, we multiplied by 10.
When we changed the 1 to a 0.1, notice how we multiply and divide by
the same number? That number will be our Multiplier:
'This number
will be the multiplier and will be adjusted according to the
computer's speed. The higher it is, the slower your game will be.
The default value is 1.
Public Multiplier as Single = 1
Now, go to the MoveUp sub:
For x = 1 To 30
* Multiplier
Position.Y -= 1 / Multiplier
If you don't
understand this, go back to the '.1' example. Our multiplier was 10.
For now, play around with the default multiplier value until you get
what's best for your computer. Hint:
Set your form's WindowStyle property to Maximized
because we're going to
do a FullScreen game. The fact that your form is maximized slows
down your character considerably :). Why? Because your form is busy
drawing the gray for the background color (when you do Me.Invalidate,
the form redraws it's background color as well!) - and since you've
maximized it, there's a lot more background to draw.
I've found a
little trick around this: Normally, to select an Event, you'd go to
Base Class Events (or Form Events for those of you with VS.NET 2003)
and select your event from the drop down menu. This time,
go to (Overrides) instead of Base Class Events and select
OnPaintBackground.
It'll (obviously) automatically create a sub for you - leave it
empty. We haven't learned about On events yet (ex
OnPaintBackground). What this event USED to do (before you overrided
it) was paint the form background color. Now that you've overridden
it, you're basically telling the form: "Hey, I'll give you the code
for painting the background, not you". And since you're leaving it
blank, the form background shows up as black (no, it is not filling
the color as black, it's just... the .. blank color).
Therefore
you don't have a background anymore, and your application should
speed up (a bit). The other thing that's making it slow is your form
size, but don't worry about that.
Anyways,
back to multiplier. Set the multipler to what you want. By the way,
setting the multiplier to a very small number isn't going to help.
For example if you set the multiplier to 1/100:
For x = 1 to 30 * 1/100........... aka For x = 1 to .3 ..... that
woudn't work out :). 1/30 won't help either cuz he'd jump from 1
tile to the next. So please, don't go crazy with multiplier values.
I'd say 1/ 5 is the absolute smallest you can go (using 1/5 will
only move him 6 times!!). Ye who hath the exalted AlienWare -
don't brag :D, your computer wont take a multiplier greater than
like... 2 when the game's done ;).
Also
remember, later we're going to implement an FPS (Frames per Second)
counter. We'll adjust the multiplier according to the FPS.
Anyways, for
now, on FullScreen, my computer likes a multiplier of 1 and 2. He
moves kind of fast, but you won't be saying that when we render the
map.
Animation
Let's get to animation! (This is the last
thing I'll speak about before giving you the MoveDown MoveLeft and
MoveRight subs).
Animation is
rather easy since we've already set up most of it! We know
that all we have to do is increment the frames and be sure that the
frames don't go higher than 2 and less than 0. So, let's
create an Animate sub!
Public Sub
Animate()
'Make him move
Frame += 1
'Don't let it go too high!
If Frame = 3 Then Frame = 0
End Sub
Now go back to your move up sub. Right after you change Position.Y,
type in:
Me.Animate()
Me.Animate() is
the same as typing in Animate(). So anyways, run the project and the
guy should animate.
Here are the
rest of your subs (I've reduced the font, the code takes up a lot of
space).
*****UNFORMATTED CODE (Paste into .NET to see the formatting)*****
Public Sub MoveUp()
'If the sprite is currently not moving Then move him up
If Not InMotion Then
'Now he's in motion.
InMotion = True
'Loop Variable
Dim x As Single
'Set his direction
Direction = Dir.Up
'Loop 30 times controlled by the multiplier
For x = 1 To 30 * Multiplier
'Move him one unit up, 30 times controlled by the multiplier
Position.Y -= 1 / Multiplier
'Animate the sprite!
Me.Animate()
'Let your application do other events (ex: Check for keypress and
refresh the form)
'in this loop.
Application.DoEvents()
'Refresh the form
GameClass.GameForm.Invalidate()
Next
'Round his decimal place
Position.Y = Math.Round(Position.Y, 0)
'Now he's done moving.
InMotion = False
End If
End Sub
Public Sub MoveDown()
'If the sprite is currently not moving Then move him up
If Not InMotion Then
'Now he's in motion.
InMotion = True
'Loop Variable
Dim x As Single
'Set his direction
Direction = Dir.Down
'Loop 30 times controlled by the multiplier
For x = 1 To 30 * Multiplier
'Move him one unit up, 30 times controlled by the multiplier
Position.Y += 1 / Multiplier
'Animate the sprite!
Me.Animate()
'Let your application do other events (ex: Check for keypress and
refresh the form)
'in this loop.
Application.DoEvents()
'Refresh the form
GameClass.GameForm.Invalidate()
Next
'Round his decimal place
Position.Y = Math.Round(Position.Y, 0)
'Now he's done moving.
InMotion = False
End If
End Sub
Public Sub MoveLeft()
'If the sprite is currently not moving Then move him up
If Not InMotion Then
'Now he's in motion.
InMotion = True
'Loop Variable
Dim x As Single
'Set his direction
Direction = Dir.Left
'Loop 30 times controlled by the multiplier
For x = 1 To 30 * Multiplier
'Move him one unit up, 30 times controlled by the multiplier
Position.X -= 1 / Multiplier
'Animate the sprite!
Me.Animate()
'Let your application do other events (ex: Check for keypress and
refresh the form)
'in this loop.
Application.DoEvents()
'Refresh the form
GameClass.GameForm.Invalidate()
Next
'Round his decimal place
Position.X = Math.Round(Position.X, 0)
'Now he's done moving.
InMotion = False
End If
End Sub
Public Sub MoveRight()
'If the sprite is currently not moving Then move him up
If Not InMotion Then
'Now he's in motion.
InMotion = True
'Loop Variable
Dim x As Single
'Set his direction
Direction = Dir.Right
'Loop 30 times controlled by the multiplier
For x = 1 To 30 * Multiplier
'Move him one unit up, 30 times controlled by the multiplier
Position.X += 1 / Multiplier
'Animate the sprite!
Me.Animate()
'Let your application do other events (ex: Check for keypress and
refresh the form)
'in this loop.
Application.DoEvents()
'Refresh the form
GameClass.GameForm.Invalidate()
Next
'Round his decimal place
Position.X = Math.Round(Position.X, 0)
'Now he's done moving.
InMotion = False
End If
End Sub
Wow, we're done with movement and animation. This tutorial is
getting quite long... and this part almost took me 2 hours to type
lol. Anyways, I'll stop rambling. I was thinking of doing Loading
the Map and Collision Detection in another tutorial, but nah - we'll
do Loading the map now and then Collision in the next one.
Loading the map
Please be sure you've read Reading and Writing
text files. In clsSprite:
Public ReadOnly Property CurrentTile()
As Point
Get
Return New Point(Position.X / 30, Position.Y /
30)
End Get
End Property
We've done this, pretty self explanatory. The function will return
the tile which the character is on.
----------------------
Now, we need to make our application more "OOP", even though it
already is. Our clsSprite class should be able to access our clsMap
class (which we'll create) and vice versa. What we're going to
do is declare the clsSprite inside GameClass instead of Form1 so
that clsMap can access it:
Public
Shared alex As New clsSprite("alex")
Remember, shared
means all the other classes can access it. Now, go back to form1 and
delete the line that declares alex as a clsSprite. Obviously a
bunch of errors will come up:
1) Hit control +
F.
2) Click the Replace button
3) In the Find textbox, type in alex
4) In the Replace textbox, type in Game.alex
5) Be sure that the option "Search In: Current Document" is checked
instead of "Search: All Open Documents"
5) Hit 'Replace All'
Not that hard, we're just putting alex in gameclass so that clsMap
can access it.
----------------------
Here's the map:
22,16
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1
3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2
1,1,1,1,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2
1,1,1,1,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
1,1,1,1,2,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
3,1,1,1,1,1,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,2,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3
Copy and paste
everything in black into notepad and save it under \bin\maps\lightworld.map
Just to give you a refresher from the Reading and Writing text files
tutorial, the first 2 numbers are the width and the height of the
map. The rest of the numbers correspond to each bitmap (ex: 2
corresponds to "2.jpg" and so on and so forth).
Here's clsMap
*****UNFORMATTED
CODE (Paste into .NET to see the formatting)*****
Public
Class clsMap
'The thing which will read our map
Dim SR As System.IO.StreamReader
'Size of the map
Public Width As Integer
Public Height As Integer
'Will store all the tiles
Public Tiles(,) As Bitmap
Public Sub New(ByVal MapName As String, ByVal tileset As String)
ReadMap(MapName, tileset)
End Sub
Public Sub ReadMap(ByVal MapName As String, ByVal tileset As String)
'Read a certain map file
SR = New System.IO.StreamReader("maps\" & MapName)
Dim ln As String
'This will store the width/height of the map, seperated by commas.
ln = SR.ReadLine()
'This will store the width and the height individually
Dim str() As String
'Split ln from it's commas
str = ln.Split(",")
'Store the width and the height
Width = str(0) - 1
Height = str(1) - 1
'Now that we know the height and the width:
'Redim Tiles according to height and the width of the map
ReDim Tiles(Width, Height)
'Stores the current row/column being read
Dim CurrentRow As Integer
Dim CurrentColumn As Integer
Dim Line() As String
For CurrentRow = 0 To Height
'Read each Line
ln = SR.ReadLine()
Line = ln.Split(",")
For CurrentColumn = 0 To Width
'Read each character (Go across the file)
Tiles(CurrentColumn, CurrentRow) = New Bitmap("tiles\" & tileset &
"\" & Line(CurrentColumn) & ".jpg")
Next
Next
End Sub
End Class
Back in GameClass:
'Declare
the map. The tiles for this map are found in the basis folder, and
the map
'file is found in lightworld.map
Public Shared map As New clsMap("lightworld.map", "basis")
In form1:
Dim x, y
As Integer
In form1.Paint (do this before you render the character):
For x = 0 To
Game.map.Width
For y = 0 To Game.map.Height
e.Graphics.DrawImage(Game.map.Tiles(x, y), x *
30, y * 30)
Next
Next
Run it, you
should see the map being rendered (woohoo). Yeah... that's pretty
much it for this tutorial.
Next up: Collision Detection, Scrolling, and Changing Screen
Resolution (something new... yay!)
Have a nice day!
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
|