Entity Creation and Messaging in a VB.NET Text-Based Game

The player can't be alone in the world. Obviously, he's going to have to have some non-human company. Let's put some non-human characters into the game. In this ninth and final part of our series teaching VB.NET through the creation of a text-based game, we're also going to create a messaging system.

Contributed by
Rating: 4 stars4 stars4 stars4 stars4 stars / 3
July 22, 2008
Rate this Article:
MEH MEH++


SEARCH ASP FREE
TOOLS YOU CAN USE

advertisement

Drawing Other Entities

The non-human characters will be derived from Entity, just as the player character is, because they're going to share some of the same properties. They are going to have names, heatlh, attack, defense, a character symbol, a position on the map, and so forth.

Let's go ahead and implement an entity who will pace back and forth. This entity is completely pointless as far as gameplay is concerned, but implementing him will help us with non-human entity functionality because we have to deal with drawing him and moving him around.

Create a class called PacingManEntity (in the file PacingManEntity.vb) to house our new entity:


Public Class PacingManEntity


End Class

The first thing we need to do with this new class is inherit from Entity and then set up an appropriate constructor, which will call Entity's constructor and pass the appropriate values:

Public Class PacingManEntity

 Inherits Entity


 Sub New(ByVal x As Integer, ByVal y As Integer)

 MyBase.New("Pacing Man", "P", ConsoleColor.Red, _

x, y, 10, 1, 1)

 End Sub

End Class

As you can see above, his name is Pacing Man, and his symbol is a red “P.” His starting location is, of course, not hard-coded into the class. Rather, the starting location is accepted as a parameter in the constructor.

We have an entity, and now we need to draw him. Drawing is done the exact same way as with the player—through DrawEntity. However, we need a way to draw all of the non-player entities at once. The best way to do this is to store the entities in a collection and then create a new method that will loop through the collection and call DrawEntity for each entity. Plus, with a collection, we can keep track of all the entities.

We'll use a List(Of T) to store the entities because that is what's most appropriate for the situation. Create the collection as a field of the Game module, right under the player and map definitions:

Dim entities As New List(Of Entity)

Notice how we also instantiate the collection.

Next, we need to add a PacingManEntity to the collection. Put the following line at about the location that we create the Adventurer object (the line where we create the PacingMan and add it to the entities collection needs to be before the line where we draw the entities):

entities.Add(New PacingManEntity(10, 10))

Now we need a method that will loop through entities and draw everything. We'll call this procedure DrawEntities, and it will use a simple For Each loop to get the job done:

Sub DrawEntities()

 For Each toBeDrawn As Entity In entities

DrawEntity(toBeDrawn)

 Next

End Sub

Next, simply call the new procedure at around the location that the player is drawn through DrawEntity:

DrawEntities()

Run the program, and you should see a PacingManEntity somewhere off to the bottom right of the player.

Implementing Movement

The next step is to add some life to PacingManEntity. Each time the player makes a move, PacingManEntity (and other entities, for that matter) needs to make a move as well. The easiest way to implement this is to add a Move method to Entity. In Entity, this method will be blank. Then, entities can override this method with their own behaviors. After each move of the player, the Move method will be called for each entity, allowing each entity to perform some sort of action.

The first step, then, is to add a Move procedure to the Entity class. Since the intent here is to have the method be overridden by subclasses, we need to modify it with the Overridable keyword:

Public Overridable Sub Move()


End Sub

In the PacingManEntity class, we need to override this procedure and provide functionality. Let's make a Pacing Man pace back and forth, from right to left. He will reverse direction whenever he hits an obstruction. This will require a way to keep track of what direction a PacingManEntity object is currently going. So, we'll need to first add a field to the PacingManEntity class. A Boolean called goingRight should do the job. If it's set to True, then the entity is moving to the right. If it's set to False, then the entity is moving to the left. Let's set it to True initially, causing him to move right from the start:

Private goingRight As Boolean = True

Now it's time to override Move. In order to override a procedure, the Overrides keyword is used:

Public Overrides Sub Move()

 MyBase.Move()

End Sub

Visual Studio will automatically call the parent class's method first. In our case, the parent class's method has nothing in it, though.

Remember, we need to change directions if there is an obstruction in the way (if TryMove returns False). However, since a Pacing Man only moves from right to left, if there are obstructions in both directions, then we'll have him simply forfeit his turn. In this situation, he can wait until the next turn to see if one of the obstructions has cleared. Here are the contents of PacingManEntity's Move procedure:

' Determine the change in X based on direction

Dim changeX As Integer = 1

If Not goingRight Then

changeX = -1

End If


' Move, or else change direction and move, or else quit

If Not TryMove(Me, changeX, 0) Then

changeX *= -1

 goingRight = Not goingRight

 TryMove(Me, changeX, 0)

End If

One of the things you should notice is the Me keyword. The Me keyword in Visual Basic refers to the current object. It's equivalent to the “this” or “self” operator in other languages.

Now we just need to call this new method from within Main. Entities should only move if the player has made a move. So, after this:


If Not playerMoved Then

    Continue While

End If


Place this:


For Each toMove As Entity In entities

    toMove.Move()

Next


This will loop over all of the entities and give each an opportunity to make a move.

Run the program. Each time the player makes a move, the pacing entity should make a move. When the entity hits a wall, he should reverse direction.

Checking for Entity Collision

You may, however, have noticed a problem with TryMove. If the entity trying to move faces a wall, then TryMove returns False as it should since no move can be made. However, if the entity trying to move faces another entity, then TryMove will return True.

You can test this out by getting in the way of the PacingManEntity. He'll walk right through you. Clearly, this is not what we want, but, thankfully, this can be easily fixed by modifying TryMove. TryMove simply needs to loop through all of the entities to determine if the destination tile is inhabited by another entity, or if the player inhabits the destination tile.

All of this is easy, though. We simply need to compare the X and Y properties of each entity to the x- and y-coordinates of the destination tile. Here's the entire TryMove method, rewritten:

Function TryMove(ByVal toBeMoved As Entity, ByVal xChange As Integer, ByVal ychange As Integer) As Boolean

 Dim x As Integer = toBeMoved.X + xChange

 Dim y As Integer = toBeMoved.Y + ychange

 For Each gameEntity As Entity In entities

 If gameEntity.X = x And gameEntity.Y = y Then

 Return False

 End If

 Next

 If map(x, y).Passable = True _

 And (player.X <> x Or player.Y <> y) Then

RedrawTile(toBeMoved.X, toBeMoved.Y)

toBeMoved.X = x

toBeMoved.Y = y

DrawEntity(toBeMoved)

 Return True

 End If

 Return False

End Function

Now TryMove works properly, checking not only the Passable property of the destination tile but also the location of the player and all of the other entities. The player and the PacingManEntity should no longer walk over each other. Instead, they should stop, blocked.

Implementing a Message System

When the player bumps into another entity, it might be a good idea to display a short message to the user telling him that someone is in the way. Besides, a messaging system would be nice for other purposes as well. So, let's create one.

First, we need something in which to store our messages. A collection will do the job. However, I'd like to introduce a new kind of collection especially suited for the purpose: the Queue(Of T).

Picture a line (a queue) of people waiting to buy a movie ticket. The first person who gets into the line is the first person to be removed from the line, and the last person who gets into the line is the last person to be removed from the line. A Queue(Of T) works just like this. It's a first-in-first out collection.

Unlike other collections, it has no index associated with it. Instead, we enqueue and dequeue elements. The first element to be enqueued is the first element to be dequeued. This works for our purposes because we have limited screen space to work with it, and we'll need to remove the oldest messages (the first ones to be added) when we run out of room.

Let's go ahead and create a Queue(Of T) as a field of the Game module:

Dim messages As New Queue(Of String)

We don't want to add messages directly to the the collection because we want to be able to limit the size of it. If it gets too large, we need to remove some messages. Speaking of size, let's define a ReadOnly field containing the maximum number of messages in messages at a time:

ReadOnly MessageLimit As Integer = 10

Next, we need to create a method that will add messages. This method needs to check the size of messages at first, and if the number of messages has reached MessageLimit, then an element needs to be dequeued and discarded using the Dequeue method. Then, if the message is too long, it needs to be split up into multiple messages. That way, a message doesn't automatically run to the next line and mess up everything (it will be written over or will run past the allowed height of the message area, possibly causing the screen to scroll). The message then needs to be enqueued using the Enqueue method, and all of the messages need to be redrawn. Here's WriteMessage:

Sub WriteMessage(ByVal message As String)


 ' Is it too big? If so, split it up.

 If message.Length > 70 Then

WriteMessage(message.Substring(0, 70))

WriteMessage(message.Substring(70))


 Else


 ' Do we have too many messages?

 If messages.Count = MessageLimit Then

messages.Dequeue()

 End If


 ' Add it and draw the messages

messages.Enqueue(message)

DrawMessages()

 End If

End Sub

Now we need to create a method that will draw the messages out to the screen, below the map and user statistics, with one space of padding to the left. This involves two things. First, we need to erase the old messages by writing blank spaces over them. Second, we need to actually write out the current messages. Here's DrawMessages:

Sub DrawMessages()


 ' Erase old messages by writing blank spaces over them

 ' Write out ten spaces at a time to minimize flickering

 For y As Integer = 1 To MessageLimit

Console.SetCursorPosition(1, 20 + y)

 For x As Integer = 1 To 7

 Console.Write(" ")

 Next

 Next


Console.SetCursorPosition(0, 21)

 For Each message As String In messages

Console.CursorLeft = 1

Console.WriteLine(message)

 Next

End Sub

We now have a working messaging system. You can test it out by writing a welcome message before the game's While loop:

WriteMessage("Welcome to VB Quest!")

Let's modify TryMove to alert the player if he's trying to walk into another entity. This only involves rewriting the For Each loop to check to see if its the player trying to move. If it is, then we need to write a message:

For Each gameEntity As Entity In entities

 If gameEntity.X = x And gameEntity.Y = y Then

 If toBeMoved Is player Then

 WriteMessage(gameEntity.Name & " is in the way.")

 End If

 Return False

 End If

Next

Notice the Is operator and the & (concatenation) operator. The Is operator checks to see if two variables point to the same instance. Here, we're checking to see if the entity to be moved (toBeMoved) is actually the player. The & operator concatenates too strings. The + operator will also work, but it's not generally recommended when concatenating to strings because it's not exclusive to strings as & is.

blog comments powered by Disqus
VISUAL BASIC.NET ARTICLES

- Basic Form Properties and Modality in VB.NET
- Multiple Document Interfaces in Visual Basic
- Visual Basic for Beginners
- ASP.NET Image to PDF with VB.Net
- MySQL in ASP.NET: Mono using VB.NET
- AsyncFileUpload File Type and File Size Vali...
- Visual Studio: Adding Functionality and Style
- Clocks and Countdowns
- User-defined Functions using Visual Basic Ap...
- Understanding Object Binding in VBA
- Mastering the Message Box
- Testing a Windows Forms Application
- Using Visual Basic.NET Features to Code a Wi...
- Correcting Code in a Windows Forms Applicati...
- Write Readable Code and Comments for Windows...

ASP Web Hosting ASP.Net Web Hosting Windows Web Hosting
ASP Free Forums 
 RSS  Tutorials RSS
 RSS  Forums RSS
 RSS  All Feeds
Site Map 
Request Media Kit
Write For Us Get Paid 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Privacy Policy 
Support 


© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 7 - Follow our Sitemap
Most Popular Topics
All ASP.Net Tutorials