How to Make an RPG

In this tutorial, you’ll learn how to use pre-built frameworks to create your own reusable RPG engine. By the time you’re done, you’ll have the groundwork to create the RPG of your dreams! By .

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Warp Speed Ahead!

Now that you have an exit defined and another room for the user to enter, you need to add some code to check when the hero hits this spot, and transition to a new map.

First, since the exits layer is an object group, you need to define a CCTMXObjectGroup in order to access it. Back in Xcode, open GameLayer.m and in the private interface at the top of the file, add the following line:

@property (nonatomic, strong) CCTMXObjectGroup *exitGroup;

Next add the following line to the bottom of the loadMapNamed method:

self.exitGroup = [self.tileMap objectGroupNamed:@"exits"];    

This gives you a handy reference to the new exits object layer that you created in the Tile Map. Now you just need to detect when the user steps on those exits.

Add the following code to the heroIsDoneWalking method:

// 1
NSArray *exits = self.exitGroup.objects;
for(NSDictionary *exit in exits)
{
    // 2
    CGRect exitRect = CGRectMake([exit[@"x"] floatValue], [exit[@"y"] floatValue],
                                 [exit[@"width"] floatValue], [exit[@"height"] floatValue]);
    // 3
    if(CGRectContainsPoint(exitRect, self.hero.position))
    {
        // 4
        NSString *name = exit[@"destination"];
        CGPoint heroPoint = CGPointMake([exit[@"startx"] floatValue] * self.tileSize + (self.tileSize/2), [exit[@"starty"] floatValue] * self.tileSize + (self.tileSize/2));

        self.hero.position = heroPoint;
        [self loadMapNamed:name];
        return;
    }
}

Here’s what you are doing:

  1. Enumerate through the exits in the exit group.
  2. Convert the exit’s frame (the rectangle you drew) to a CGRect.
  3. Check to see if the player is standing in the exit.
  4. If so, fetch the name of the room and the new position for the player. Load the new room and update the player’s position.

Build and run the project. You should now be able to walk out of the door and into the town. Congratulations, you’re no longer a recluse!

Forever Alone No More!

When I first started developing games, the idea of NPCs boggled my mind. How did developers build so many characters capable of such complex interactions, particularly when it came to questing?

Well, I’ve come up with a rather simple and robust method for doing just that.

You see, when you are writing gameplay logic like this, it’s often easier to do so in a lightweight scripting language than a strongly typed language like Objective-C. It makes your code much faster to write and tweak, which is critical when you’re changing things a lot like you will when creating NPCs, quests, and so on.

So in this part I am going to show you how to integrate a scripting language called Lua into your game. As I mentioned earlier, Lua is ideal for this purpose, and is really easy to integrate into Xcode projects.

Here’s how it will work. You’ll add NPCs to the tile map inside of Tiled and give each of them a name. When your character approaches an NPC, the game will invoke the NPC’s Lua code. You’ll be building a simple bridge so that your Lua code can call upon your Objective-C code to do things like store key/value pairs, display chat boxes, and so on.

With this system in place, you can easily script an entire game with minimal changes to your engine.

All About Lua

Before you begin, let’s take a moment to discuss how the Lua integration will work at a high level.

Integrating LUA into an Xcode project

Lua is a very high level scriping language made to run in just about any environment. It only has one complex type (called a table) which can be used to build high level data structures such as arrays, hashes, objects, etc. You can learn more about Lua on their official website.

Lua ships as a bunch of .c and .h files for it’s runtime that get compiled directly into your project. In order to get Objective-C and Lua talking, you are going to be using an open source class called LuaObjBridge. Although it’s no longer maintained, it will work perfectly for your purposes.

What this class does is makes use of Objective-C’s reflection ability to dynamically “link up” objective-c classes/methods/properties to the Lua runtime. This gives you the ability to call upon them using a specific syntax (to be discussed later).

For your game, you are going to be scripting the entire storyline (which is pretty shallow) and all of the interaction between the player and the NPCs.

Bring On the NPCs

Now that you have a basic understanding of how the Lua integration will work and why you’re using Lua, let’s start adding some NPCs into the game!

To do this, first open room.tmx inside of Tiled.

Find the soldier tileset and right-click on one of the soldier tiles. Select Tile Properties.

Add a new key/value pair with “name” and “soldier”:

Create a new tile layer called npc. Now you can add the soldier to the layer like a stamp in three steps:

  1. Click on the npc layer to ensure it’s selected.
  2. Select the soldier tile to which you added the name property.
  3. Then click somewhere in the room to place the soldier. Make sure you put him somewhere the player will be able to access!

Having the NPC in the game is a good first step, but there are still some issues. For one, the user can pass right through them. Start by preventing the user from standing on top of the NPC.

Head back to Xcode and open GameLayer.m. Add the following line to the private class interface at the top of the file:

@property (nonatomic, strong) CCTMXLayer *npcLayer;

Then add the following line at the bottom of the loadMapNamed method:

self.npcLayer = [self.tileMap layerNamed:@"npc"];

Finally, add the following code after the //Check walls section in the setPlayerPosition method:

tileGid = [self.npcLayer tileGIDAt:tileCoord];
if(tileGid)
{
    NSDictionary *properties = [self.tileMap propertiesForGID:tileGid];
    NSString *name = [properties objectForKey:@"name"];
    // TODO: Interact with NPC
    return;
}

This code simply prevents the user from walking into the NPC. Build and run and you should see that the player stops when approaching the soldier.

You shall not pass!

Managing NPCs With Lua

Now that the NPCs are in place, they need to do more than stand around and block the player character. You’ll handle all the interaction through a new class called NPCManager. The interactivity is where Lua scripting comes into play.

The starter project already includes Lua and a Lua-ObjC bridge that allows Lua scripts to use your native Objective-C objects and call methods on them. Here are the steps I took to add them to the project in case you are adventurous and want to add Lua to your existing project:

  1. Download Lua 5.1 here and extract it. Newer versions of Lua don’t work with the bridge.
  2. Copy the src folder into your project’s folder and drag all of the files into your project (don’t check to have XCode copy them in).
  3. Delete the following files: lua.c, luac.c, print.c, Makefile . Leaving these in will cause issues.
  4. Download LuaObjCBridge from here.
  5. Drag LuaObjCBridge.h and LuaObjCBridge.m (in the trunk folder) into your project and let XCode copy them in.

Aren’t you glad I did all that for you? ;)

Add a new class to the project called NPCManager and make it a subclass of NSObject. Replace the contents of NPCManager.h with the following:

#import <Foundation/Foundation.h>
#import "cocos2d.h"

@class GameLayer;

@interface NPCManager : NSObject
@property(nonatomic, strong) NSMutableDictionary *npcs;

- (id) initWithGameLayer:(GameLayer *)layer;
- (void) interactWithNPCNamed:(NSString *) npcName;
- (void)loadNPCsForTileMap:(CCTMXTiledMap *) map named:(NSString *) name;
@end

I will explain each of these methods as you implement them. Now open up NPCManager.m and replace the contents with the following code:

// 1
#import "cocos2d.h"
#import "NPCManager.h"
#import "LuaObjCBridge.h"
#import "GameLayer.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

// 2
@interface NPCManager()
@property(nonatomic) lua_State *luaState;
@property(nonatomic, strong) GameLayer *gameLayer;
@end

@implementation NPCManager

- (id)initWithGameLayer:(GameLayer *)layer
{
    if (self = [super init]) {
        self.npcs = [@{} mutableCopy];
        self.gameLayer = layer;

        // 3
        self.luaState = lua_objc_init();

        // 4
        lua_pushstring(self.luaState, "game");
        lua_objc_pushid(self.luaState, self.gameLayer);
        lua_settable(self.luaState, LUA_GLOBALSINDEX);
    }
    return self;
}

@end

Here’s what’s going on in the code above:

  1. These are all of the includes you need to make the manager work. Don’t worry about the nerdy C headers.
  2. You’ll use the luaState property to interact with the Lua system; gameLayer is your familiar GameLayer class.
  3. Initialize the Lua system.
  4. Send the Game object into Lua by adding it to the top of the Lua stack. The lua runtime uses a stack to manage variables, objects, and method calls. To run something, it must first get pushed on to the stack. So first, you push a string called “game” telling Lua that “game” is what you are going to call the object you are about to put on the stack. Then, you push the game object itself. Remember, the bridge handles all of the heavy lifting behind the scenes. The last call just tells Lua to make the game object global so you can access it from any Lua file.

Now comes the tricky part. Before you can interact with the Lua system, you need a way to call upon it. Add the following two methods to your NPCManager class:

/**
 * Executes Lua code and prints results to the console.
 */
- (void) runLua:(NSString *) luaCode
{
    char buffer[256] = {0};
    int out_pipe[2];
    int saved_stdout;

    // Set up pipes for output
    saved_stdout = dup(STDOUT_FILENO);
    pipe(out_pipe);
    fcntl(out_pipe[0], F_SETFL, O_NONBLOCK);
    dup2(out_pipe[1], STDOUT_FILENO);
    close(out_pipe[1]);

    // Run Lua
    luaL_loadstring(self.luaState, [luaCode UTF8String]);
    int status = lua_pcall(self.luaState, 0, LUA_MULTRET, 0);

    // Report errors if there are any
    report_errors(self.luaState, status);

    // Grab the output
    read(out_pipe[0], buffer, 255);
    dup2(saved_stdout, STDOUT_FILENO);

    // Print the output to the log
    NSString *output = [NSString stringWithFormat:@"%@",
      [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding]];
    if(output && [output length] > 2)
    {
        NSLog(@"Lua: %@",output);
    }
}

/**
 * Reports Lua errors to the console
 */
void report_errors(lua_State *L, int status)
{
    if ( status!=0 ) {
        const char *error = lua_tostring(L, -1);
        NSLog(@"Lua Error: %s",error);
        lua_pop(L, 1); // remove error message
    }
}

This tutorial won’t go over this code in too much detail. If you’re dying to know more about it, you can reach out to me on Twitter.

Just know and trust that it will run a string of Lua code that is passed in to the runLua: method. For example:

[self runLua:@"print(\"Hello Lua\")"];

The code above will print the text “Hello Lua” to the console.

Now that Lua is hooked up, you’ve got three more methods to implement in this class. Add the following methods to NPCManager.m:

/**
 * Loads all NPCs on a given tile map.  Initialized empty Lua table to hold
 * NPCs in Lua.
 */
- (void)loadNPCsForTileMap:(CCTMXTiledMap *) map named:(NSString *) name
{
    // Reset NPCs for the current map
    [self runLua:@"npcs = {}"];

    [self loadLuaFilesForMap:map layerName:@"npc" named:name];    
}

/**
 * For a given layer on a tile map, this method tries to load files of the format:
 * [MapName]-[NPCName].lua
 *
 * Lua files are responsible for initializing themselves and adding themselves to the
 * global npcs table.
 *
 * All Lua objects in the npcs table must have an interact method that will be invoked when
 * the player interacts with them.
 */
- (void) loadLuaFilesForMap:(CCTMXTiledMap *) map layerName:(NSString *) layerName named:(NSString *) name
{
    NSFileManager *manager = [NSFileManager defaultManager];
    CCTMXLayer *layer = [map layerNamed:layerName];

    // Enumerate the layer
    for(int i = 0; i < layer.layerSize.width; i++)
    {
        for(int j = 0; j < layer.layerSize.height; j++)
        {
            CGPoint tileCoord = CGPointMake(j,i);
            int tileGid = [layer tileGIDAt:tileCoord];

            // Check to see if there is an NPC at this location
            if(tileGid)
            {             
                // Fetch the name of the NPC
                NSDictionary *properties = [map propertiesForGID:tileGid];
                NSString *npcName = [properties objectForKey:@"name"];

                // Resolve the path to the NPCs Lua file
                NSString *roomName = [name stringByReplacingOccurrencesOfString:@".tmx" withString:@""];
                NSString *npcFilename = [NSString stringWithFormat:@"%@-%@.lua",roomName,npcName];
                NSString *path = [[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"npc"] stringByAppendingPathComponent:npcFilename];

                // If the NPC has a Lua file, initialize it.
                if([manager fileExistsAtPath:path])
                {
                    NSError *error = nil;
                    NSString *lua = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
                    if(!error)
                    {
                        [self runLua:lua];
                    }
                    else
                    {
                        NSLog(@"Error loading NPC: %@",error);
                    }
                }
                else
                {
                    NSLog(@"Warning: No Lua file for npc %@ at path %@",npcName,path);
                }
            }
        }
    }

}

The code above is pretty well commented, so I won't go into further explanation. The gist of it is, you pass a tile map and the name of the tile map into loadNPCsForTileMap:named:, which loads Lua files inside a folder called npc in the bundle path.

Note that all NPCs must follow the naming convention [:map_name]-[:npc_name].lua. You’ll return to this idea in a bit.

Also note the line "[self runLua:@"npcs = {}"];" . This sets up a global table in the Lua system to hold all of the NPC objects that will be loaded from files.

The final method you need to implement is the one that your GameLayer class will call to interact with the NPC. Add this method to NPCManager.m:

- (void) interactWithNPCNamed:(NSString *) npcName
{
    NSString *luaCode = [NSString stringWithFormat:@"npcs[\"%@\"]:interact()",npcName];
    [self runLua:luaCode];
}

Now back up a minute before considering what this method does. When an NPC is loaded into the system, they get added to a global Lua table called (you guessed it) npc.

Lua tables are like dictionaries. So if you have an NPC named soldier, it would look like this to Lua:

    npc["soldier"]:interact()

This code looks up a soldier Lua object in the global NPC table and calls the interact method on it. Of course, this assumes that your soldier object has an interact method – rest assured you’ll get to that soon!

With that explanation in mind, you can see what the interactWithNPCNamed: method does. It looks up the NPC by name in the Lua table and calls the NPC’s interact method.

You’ll have to wait just a bit longer to see the results of all this work, but don’t lose heart!