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 3 of 4 of this article. Click here to view the first page.

Start Your Script Engine!

Well, you have made it through that section: you’ve earned enough experience points for a level-up! Even better, how about a scriptable RPG engine? ;]

It's time to learn how to use the NPCManager in the GameLayer. Open GameLayer.m and add the following import to the top of the file:

#import "NPCManager.h"

Add the following property to the private class declaration:

@property(nonatomic, strong) NPCManager *npcManager;

Add the following line to the init method, at the top of the if block:

self.npcManager = [[NPCManager alloc] initWithGameLayer:self];

Here's where the connection takes place. At the end of the loadMapNamed method, add the following line:

[self.npcManager loadNPCsForTileMap:self.tileMap named:name];

This loads the set of NPCs for the newly loaded tile map. When the player changes rooms, the new NPCs get loaded with the new room.

You can start to see the need for the NPC naming convention here. You might have the soldier in your room as well as in the town, but they will interact with the player quite differently, so they would be scripted in separate files named room-soldier.lua and town-soldier.lua.

The final step is to invoke the Lua code of the NPC with which you wish the player to interact. In the setPlayerPosition: method, replace the //TODO Interact with NPC comment with the following line:

[self.npcManager interactWithNPCNamed:name];

When the player touches to move toward an NPC, the NPC’s name is looked up in its list of properties. This name is passed to the interactWithNPCNamed: method in order to invoke the NPC’s interact Lua method.

Writing Some Lua (Finally)

Now that you have full support for the Lua system, it’s time to actually write some Lua.

Right-click on the npc folder in the Xcode Project Navigator and click New File.... Choose the iOS\Other\Empty template. Name the file room-soldier.lua and make sure to save it into the npc folder inside of your project directory!

Add the following code to the new empty room-soldier.lua file:

-- 1
soldier = {}
-- 2
function soldier:new(game)  
    local object = { 
        game = game,
    }
    setmetatable(object, { __index = soldier })

    return object
end 
-- 3
function soldier:interact()
    print "Hello soldier!"
end

-- 4
soldier = soldier:new(game)	
npcs["soldier"] = soldier

Here’s what's going on:

  1. Lua only has one complex data type and that's a table. So it seems natural to use a table to create an "object".
  2. This is the constructor for the soldier class. You can read up on metatables here to learn more about what's going on. It's just some boilerplate stuff.
  3. This is your interact method, the main way that the player interacts with the NPC.
  4. Finally, you instantiate a new soldier object and add it to the global NPC table you set up earlier. Note that the constructor takes a game variable, but where did that come from? Remember that when you initialized Lua, you put your GameLayer object on the Lua stack with the name of game. Since it's global, you can just go ahead and pass it in here.

This is the moment you’ve been waiting for – a build and run! So do it! Then walk over to the soldier to interact with him. Once you do, you should see the following printed to the console:

Lua: Hello soldier!

Hooray! You have just interacted with your first NPC via Lua!

Getting Chatty

Your current game is great, as long as you don't mind playing a game with Xcode's debug window open. :]

You’re going to change that now by building a chat box that the NPC can call upon to “talk” to the player.

Here’s a screenshot of what you’re going to build:

Start by adding a new class called ChatBox and make it a subclass of CCNode. Replace the contents of ChatBox.h with the following code:

#import "cocos2d.h"

@interface ChatBox : CCNode

- (id) initWithNPC:(NSString *) npc text:(NSString *) text;
- (void) advanceTextOrHide;

@end

The first method takes an NPC name and the text to display. The second method either advances the text or hides it – more on that momentarily.

Next add the implementation by replacing the contents of ChatBox.m with the following code:

#import "ChatBox.h"

@interface ChatBox ()<CCTargetedTouchDelegate>
@property(nonatomic, strong) NSString *npc;
@property(nonatomic, strong) NSMutableArray *textArray;
@property(nonatomic, strong) CCLabelTTF *label;
@end

@implementation ChatBox

- (id) initWithNPC:(NSString *)npc text:(NSString *)text
{
    if (self = [super init])
    {
        self.npc = npc;
        // 1
        self.textArray = [[text componentsSeparatedByString:@""] mutableCopy];
        // 2
        CCSprite *backroundSprite = [CCSprite spriteWithFile:@"chat-box.png"];
        [backroundSprite.texture setAliasTexParameters];
        backroundSprite.scale = 8;
        backroundSprite.position = ccp(0,240);
        backroundSprite.anchorPoint = ccp(0,0);
        [self addChild:backroundSprite z:0];
        // 3
        self.label = [CCLabelTTF labelWithString:@"" dimensions:CGSizeMake(460, 60) hAlignment:UITextAlignmentLeft lineBreakMode:UILineBreakModeWordWrap fontName:@"Helvetica" fontSize:24];
        self.label.color = ccWHITE;
        self.label.anchorPoint = ccp(0, 0);
        self.label.position = ccp(10,250);

        [self addChild:self.label z:1];
    }

    return self;
}
@end

Here’s a breakdown of the above code:

  1. When NPCs in a game have something to say, the text usually takes up more than one line. Here you employ a strategy to solve this by splitting up the string on newline (\\n) characters. This way, the UI shows only one segment of the string at a time and advances the text to the next line when the user taps on the chat box. When the array becomes empty, the chat box hides itself.
  2. Draws the background of the chat box.
  3. Draws the text label.

Now add the following method to facilitate the text advancement process:

- (void) advanceTextOrHide
{
    // 1
    if(self.textArray.count == 0)
    {
        [self setVisible:NO];
        [self.parent removeChild:self cleanup:YES];
        return;
    }

    // 2
    NSString *text = self.textArray[0];
    [self.textArray removeObjectAtIndex:0];

    // 3
    NSString *message = [NSString stringWithFormat:@"%@: %@",[self.npc uppercaseString], text];
    [self.label setString:message];
}

The code above does the following:

  1. If the text array is empty, you hide the chat box.
  2. Here you pop the next chat string off of the stack.
  3. You display the new chat string.

Now you need to allow the NPC to create a new chat box and send it string messages. You can do this inside of the GameLayer class since your NPC has access to all of its methods.

Add an additional import at the top of GameLayer.m:

#import "ChatBox.h"

Add the following property to the private interface:

@property(nonatomic, strong) ChatBox *chatbox;

Add the following method:

- (void) npc: (NSString *)npc say:(NSString *) text
{
    self.chatbox = [[ChatBox alloc] initWithNPC:(NSString *)npc text:text];    
    [self.parent addChild:self.chatbox];
    [self.chatbox advanceTextOrHide];
}

An NPC can call this method to instantiate a chat box and display it. Here’s an example of how the NPC would call it:

self.game:npc_say("soldier","Please, save the princess!")
-- equivalent to [gameLayer npc:@"soldier" say:@"Please, save the princess!"];

Let's break this down:

  • self: You are assuming the NPC is calling this from inside their interact method. This is just a self reference inside of the class.
  • game: A reference to the game that was passed in on initialization.
  • npc_say: This is where things get interesting. The Lua bridge uses Objective-C reflection to generate these sorts of Lua methods to map to your class methods. Anywhere there is a space and another argument in your Objective-C method, you replace it with an underscore in Lua. Finally, all parameters are passed in (in order) at the end.

Now you need to add touch interaction so that the player can advance the text or hide the chat box. Add the following lines to the beginning of ccTouchesEnded:withEvent:

if(self.chatbox && self.chatbox.visible)
{
    [self.chatbox advanceTextOrHide];
    return;
}

The final step is to have your soldier NPC call upon this method. Open room-soldier.lua and replace the print line in the interact method with the following:

self.game:npc_say("soldier","Please, save the princess!")

Build and run!

Your NPC can talk!

You can now repeat the process of adding NPCs to the map (inside any room/tilemap) and writing their corresponding Lua files.

One could argue that at this stage, you have enough knowledge to make a complete (though very basic) RPG game. But don’t head off to the tavern just yet – you’re about to go a few steps further to add more complex interactivity.