Gw Temp

Menu

Tutorial - 'RPG BASICS Part III: Basic Actor Engines' by Kuro

An item about Gamemaker posted on

Blurb

Making a bais actor engine in GameMaker!

Body

Rpg Basics in Game Maker: Basic Actor Engines


A WARINING: This tutorial is very long so take it very slowly and re-read anything you don't understand right away. Also, if you still don't get something, or you need help with anything programming-related, just send an e-mail to krealm2k@yahoo.com. I'll help you out ASAP. On with the tutorial:

It didn't take long for me to realize that making separate objects for each hero and NPC was a bad idea. So to make things right, I'm back to explain how to create an 'Actor' engine in Game Maker. 'Actors' are the term I use for player objects. They include the Heroes and NPC's in your game. Let me start by outlining what we're here to accomplish.

1) We want to create a system that will allow us to create Players and NPC's alike from ONE object. (Just ONE object, no more, no less)
2) Verily, since we are only using one object, all of our Players and NPCs will have to follow the same protocols. (Rules, like walking into walls and generating events)
3) We need to implement what I call a 'Sprite Collection.' It'll be very useful for extending your Actor.
4) We also want each Actor to have basic parameters for extending to Battle Events and other Systems. (HP, MP, PWR, DEF, etc)
5) We finally need to make a basic Party System so we can keep track of who we've collected so far (Who is controllable opposed to NPCs).


Now that we have a game plan, let's tackle each problem separately.


Part I: The one object to rule them all

If you think its impossible to create a Game Maker game using only one object for every Hero and NPC, you're just not thinking hard enough. Open up Game Maker and create a new object and call it actor. Add in three events, Create, Step, and Draw. In the Create event, drag in a code snippet and type/copy this in. BTW, if you copy/paste the code in, it may turn to a bold font. Press F12 twice to fix the syntax highlighting.

// No keyboard response from this actor
self.action = false; // Determines if we can run keyboard/ai routines
self.ai = false; // Determines if this actor is user-controlled

// Clear the keys array to 0
for(i=0;i<10;i+=1){
self.keys = false;
}

Next drag in another code snippet to the Draw event and type/copy this.

// Draw the sprite if it exists
if(self.sprite_index != -1){
draw_sprite(self.sprite_index,self.image_single,self.x,self.y);
}

Finally, drag in a code snippet to the Step event, and add this:

// See if we can get input
if(self.action){
if(!self.ai){
for(i=0;i<4i+=1){
self.keys = keyboard_check(global.player_keys[self.pID,i]);
}

ProcessPlayerInput(self.id);
}else{
// It is computer-controlled
ProcessComputerAI(self.id);
}
}

if(global.action){
ProcessPlayerState(self.id);
}


That's all for the actor object, for now at least. Now before we go and write those functions listed above, we need to define some Constants.

Part II: Constants and Sprite Collections

Constants in Game Maker are usually overlooked, at least I never used them. But constants can be a great asset when designing games. Constants are what they are, a bunch of definite values pre-defined in your game. The best way for me to explain why they're so useful is through example.

Let's say you're making an overhead-rpg with a character that can face up to four directions. Now instead of defining the walk animation inside the hero's own object, we do it the way above with only one 'actor' object. But how do we define the sprites without directly implementing them in the object? Simple. We use a Sprite Collection - A simple 2-D array that can hold reference values to our sprites. We 'load' the array to the object when it is created and fill out the values through a pre-defined function. But after we fill out the array, how do we know what sprites are located where? Constants! By using constants, we can make the array uniform for all the actors we create during the game. Now if you're not getting this, here's all of the above in code form:

// Script-> LoadSprites()
// argument0-> Object ID (the instance id of an object)
// argument1-> Character ID (this decides what sprite set we'll load

switch(argument1) {
case player_Alex: // player_Alex is a user-defined constant value equal to 0
argument0.mask_index = alex_mask;
argument0.sprites[facing_north, actor_stand] = alex_north_stand; // facing_north is a user-defined constant equal to 0
argument0.sprites[facing_north, actor_walk] = alex_north_walk; // actor_stand & actor_walk are u-d constants equal to 0 & 1 respectively

argument0.sprites[facing_west, actor_stand] = alex_west_stand; // facing_west is a user-defined constant equal to 1
argument0.sprites[facing_west, actor_walk] = alex_west_walk; // the values after the = sign are sprites defined beforehand

argument0.sprites[facing_east, actor_stand] = alex_east_stand; // facing_east is a user-defined constant equal to 2
argument0.sprites[facing_east, actor_walk] = alex_east_walk;

argument0.sprites[facing_south, actor_stand] = alex_south_stand;// facing_south is a user-defined constant equal to 3
argument0.sprites[facing_south, actor_walk] = alex_south_walk;
break;
case player_Zack: // player_Zack is a user-defined constant value equal to 1
argument0.mask_index = zack_mask;
argument0.sprites[facing_north, actor_stand] = zack_north_stand; // facing_north is a user-defined constant equal to 0
argument0.sprites[facing_north, actor_walk] = zack_north_walk; // actor_stand & actor_walk are u-d constants equal to 0 & 1 respectively

argument0.sprites[facing_west, actor_stand] = zack_west_stand; // facing_west is a user-defined constant equal to 1
argument0.sprites[facing_west, actor_walk] = alex_west_walk; // the values after the = sign are sprites defined beforehand

argument0.sprites[facing_east, actor_stand] = zack_east_stand; // facing_east is a user-defined constant equal to 2
argument0.sprites[facing_east, actor_walk] = zack_east_walk;

argument0.sprites[facing_south, actor_stand] = zack_south_stand;// facing_south is a user-defined constant equal to 3
argument0.sprites[facing_south, actor_walk] = zack_south_walk;
break;
}


And there you go. If its confusing, uh... sorry? Seriously, if you don't understand this, stop reading and either PM me on the forums or email me at krealm2k@yahoo.com. Its a pretty simple concept. What's even better about it is that this is exactly how the LoadSprites() function will look later on, only you'll have more 'case' statements that what's listed if you have more characters in your game. Creating characters will be much faster this way as well. Moving on, I want to talk a bit about how we handle sprites.

Part II.I: Sprites: masks and origin points

You probably already know this, but sprite masks are simple, invisible sprites designed to handle collisions instead of the actual sprite itself. Masks are important because often, your sprite will be changing all the time and the bounding box for collision detection will change as well. The mask will keep your collision detection perfect. Check the Game Maker Help to see more on Masks.

Now if you've never messed around with the origin points, you should more often. They're quite useful when they're set correctly. I'm not going to bore you with the theory of sprite origins so here's my suggestion when you create your sprites: When adding/copying/pasting your sprites into the sprite editor, make sure you bottom-align all of your sprites, i.e. that the sprite frame touches the bottom of the sprite box. When you're done with editing that particular sprite, set the Y origin to the sprite height - 1. So if the sprite is 50px high, set the sprite height to 49. Now the X sprite origin is a bit trickier. You need to find the point in the sprite where it would look symmetrical if you 'folded the sprite in half.' Use your best estimation on this. Usually you can just set the X origin to half the sprite width, but when working with bigger sprites, you may not have that symmetry at the middle point, especially if the sprite is an action pose.

Lastly, if you use the above method for all of your sprites, then make sure you modify your 'sprite mask' sprite accordingly. This is entirely up to you, but I like to take my character's standing sprite and duplicate it. Then I open it in the sprite editor and delete all of the frames and add an empty one. (Shift+A). After that, I resize the canvas to 85% of the original width and 60% of the original height. And of course, I define the X and Y origins the way mentioned above.

Now let's move on to actually creating actors.

Part III: CreateActor()

The CreateActor() function is where we actually create the instance for a character, whether they're an NPC or PC. Here's the basic layout to which we'll add to later.

// Script-> CreateActor()
// argument0-> Character ID, for use with the LoadSprite function
// argument1-> Party ID (if the value is 0, then its an NPC, if its 1, its a PC)
// argument2-> AI trigger (do we let the computer control the movement?

if(argument2 == 0){
i = GetNextNPC()
if(i != -1) {
global.npc = instance_create(0,0,actor);
global.npc.ai = argument2;
global.npc.cID = argument0;
global.npc.pID = i;

LoadSprites(global.npc);
LoadParams(global.npc);
}
}else{
i = GetNextParty();
if(i != -1) {
global.party = instance_create(0,0,actor);
global.party.ai = argument2;
global.party.cID = argument0;
global.party.pID = i;

LoadSprites(global.party);
LoadParams(global.party);
}
}

That's the basic template we'll use. The LoadParams() function is pretty similar to the LoadSprites() function, but it loads the characters base stats. This is what it could look like:

// Script-> LoadParams()
// argument0-> Object ID
// argument1-> Character ID

if(instance_exists(argument0)){ // Make sure we're not trying to access something that doesn't exist
switch(argument1){
case player_Alex:
with(argument0){ // param_lvl,param_hp,param_maxhp all user-defined constants
parameters[param_lvl] = 1;
parameters[param_hp] = 20;
parameters[param_maxhp] = 20;
...
}
}
}
}

And so on. The reason I put all of the characters stats in an array is for fast editing. Just think about it, if we had separate variables for each parameter, we'd either have a lot of functions, or one big function that had a bunch of if-else-if or case statements. By using the above, we make stat-editing much more efficient, as seen below:

// Script-> SetParam()
// argument0-> Object ID
// argument1-> Param #
// argument2-> Param Value
// argument3-> Relative (Whether argument2 will be absolute or relative)

if(instance_exists(argument0)){
if(argument3){
argument0.parameters[argument1] += argument2;
}else{
argument0.parameters[argument1] = argument2;
}
}

We could also write a function to 'Get' a specified parameter, but I'll leave that task to you.


That's the basic premise of CreateActor(). Now I've been going on about this for quite a while, and I know that I'm not even up to the halfway point right now. (Its about 40% done really.) If you've actually been doing this stuff while you're reading it, save your file because this is the stopping point.

The final 60% of this tutorial will be out sometime during the middle of November. If you have any questions, feel free to PM me on the forums or email me at krealm2k@yahoo.com I'm out.

- Kuro, Master of the Anti-Light -