Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / GDI+

WestWorld - Part 4

0.00/5 (No votes)
20 Jul 2015CPOL4 min read 10.8K   138  
A demonstration of Finite State Machines (FSM) where agents inhabit an Old West style gold mining town called West World.

Note: This is Part 4 of a CodeProject Article Series entitled WestWorld. To get more background information for this article and for Parts 1-3, follow this link for WestWorld.

WestWorld Screen Capture

Introduction

A practical example of how to create agents that utilize finite state machines. In this article, I will attempt to explain how to implement FSM code using WinForms and GDI+. We are going to look at a game environment where agents inhabit an Old West-style gold mining town named West World. There are two inhabitants of WestWorld — a gold miner named Miner Bob and his Wife, Amanda.

  • WestWorld1 - Basics of FSM
  • WestWorld2 - FSM with two agents (Miner & Wife)
  • WestWorld3 - Includes FSM with Messaging between Agents
  • WestWorld4 (this article) - Introduces WindowsForms GDI+ using RogueSharp

Background

The aim of this tutorial is to show you how to create a simple game, from scratch, without the help of higher level APIs like XNA or DirectX which automate half the process for you. All we will be using is a Windows Form and GDI+ to do some basic drawing, and for a little extra convenience, a few of the Form's events.

Map Generation

WestWorld uses a modified VB.NET version of RogueSharp. Map Generation generates a randomly created unique map. Your map may appear different than the map shown.

RogueSharp is a free library written in C# to help roguelike developers get a head start on their game. RogueSharp provides many utility functions for dealing with map generation, field-of-view calculations, path finding, random number generation and more. It also contains strategies for making room and cave-like maps.

Most interactions with RogueSharp is based around the concept of a Map which is a rectangular grid of Cells.

Each Cell in a Map has the following properties:

  • IsTransparent: true if visibility extends through the Cell
  • IsWalkable: true if a the Cell may be traversed by the player
  • IsExplored: true if the player has ever had line-of-sight to the Cell
  • IsInFov: true if the Cell is currently in the player's field-of-view

To instantiate a new Map, you can use its constructor which takes a width and height and will create a new map of those dimensions with the properties of all Cells set to false.

Notice that the ToString() operator is overridden on the Map class to provide a simple visual representation of the map. An optional bool parameter can be provided to ToString() to indicate if you want to use the field-of-view or not. If the parameter is not given, it defaults to false.

The symbols used are as follows:

  • %: Cell is not in field-of-view
  • .: Cell is transparent, walkable, and in field-of-view
  • s: Cell is walkable and in field-of-view (but not transparent)
  • o: Cell is transparent and in field-of-view (but not walkable)
  • #: Cell is in field-of-view (but not transparent or walkable)

A more interesting way to create a map is to use the Map class' static method Create which takes an IMapCreationStrategy. Some simple classes implementing IMapCreationStrategy are provided with RogueSharp but this is easily extended by creating your own class that implements the strategy.

For the original version of RogueSharp, a NuGet package is available at https://www.nuget.org/packages/RogueSharp.

Sprite Rendering

To animate WestWorld, we simply draw an image, and then move to the next frame, then we draw the next image in the sequence. So, we need to keep track of what frame of the animation we're on, how many frames are in the animation, the width and height of an individual sprite, and the direction in which to turn. To animate our images, we draw the image at (0, 32), and then on the next frame, draw the image at (33, 65), and repeat.

VB
    Dim sizeOfSprites As Integer = 32
    Dim scale As Double = 0.5F
    Dim rectPosition As New System.Drawing.Rectangle_
                   (New Point(X * sizeOfSprites, Y * sizeOfSprites)...
    buffer.DrawImage(_Image, rectPosition, imageX, imageY, _Width, _Height, GraphicsUnit.Pixel)

The image is too large to fit on the screen. Therefore, let's just scale all of our sprites down to 1/2 size, 16 x 16 pixels. Using the DrawImage method on the Bitmap class allows us to set the scale of the sprite to draw based upon its current position.

Map Drawing

To actually see our map, we’re going to need to add some code to the Draw() method in World.vb. We set a variable corresponding to the size of the sprites we are using for our floor and wall Cells. If you used the graphics provided, these are 64 pixels square. This value will be used to calculate where to draw each sprite. Next, we call the GetAllCells() method on our newly created _map. This returns an IEnumerable<cell> of all Cells in the map that we can iterate through using a For Each loop. As we iterate through each Cell, we check the IsWalkable property. This will determine if we should draw a floor. We calculate the position of where we should draw the sprite by looking at the corresponding Cell’s X and Y properties and multiplying it by the variable we set. This is where we make the actual calls to the Draw method on our Bitmap buffer variable and provide either the floor or wall texture and the position we calculated earlier.

VB
Public Sub Draw(ByVal g As Graphics)
    Dim sizeOfSprites As Integer = 64
    Dim scale As Double = 0.5F
    Dim multiplier As Integer = CInt(sizeOfSprites * scale)

    Dim myBrush As Brush = New SolidBrush(Color.Yellow)

   For Each Cell As Cell In _map.GetAllCells
        Dim Text As String = String.Format("X:{0}, Y:{1}", Cell.X, Cell.Y)

        If Cell.IsWalkable And Cell.IsTransparent Then

            Dim position As New System.Drawing.Point(CInt(Cell.X * multiplier), CInt(Cell.Y * multiplier))
            g.DrawImage(_Grass, position)
        Else

            Dim position As New System.Drawing.Point(CInt(Cell.X * multiplier), CInt(Cell.Y * multiplier))
            g.DrawImage(_Wall, position)

       End If
   Next

History

Credits

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)