Game Maker 8 Enemy Ai Game

Click here to see this page in full context

In my first tutorial, I explain how to get the enemy AI following the player, making it face the player, and climbing up stairs. If there is something wrong. Chase behaviour for a platformer enemy / character. Part of a series of videos coming on different enemy behaviours for platformers etc. Find these videos useful? Consider supporting on Patreon: Download the project files: More GameMaker tutorials at: I believe in accessible education, this video’s content and the included GameMaker source files are and always.

Script assets are essentially a collection of one or more user defined functions or variables that you write yourself as snippets of code in the Script Editor. The functions you define in a script can resolve expressions, return values or do anything else that the GameMaker Language permits, just like the built in runtime functions.

Script functions should generally be used if you have a block of code that you use in more than one place or object, or when you want a block of code to be used across multiple objects in a modular fashion. Using scripts to define functions means that you can change the function just once when required and the change will be 'picked up' by every object that has a call to the function.

Scripts can also be very handy from an organizational point of view, as they permit you to create groups of functions that belong to a certain category - for example, you might have several functions all related to collisions between instances in your game, so you would create a 'Collision_Functions' script and store all these functions together in it.

Creating Functions

When creating a script with functions in it, the functions must be created using the following formats:

function <name>( <parameter1>, <parameter2>, etc... )
{

<statement1>;
<statement1>;
...
}

or:

<name> = function( <parameter1>, <parameter2>, etc... )
{
<statement1>;
<statement1>;
...
}

In general, however, you would use the first form for script functions as it will define the function as specifically being a script function, meaning that it will be global in scope, be assigned a script index, and not require the global prefix to identify it since the compiler will recognise it as a script function. Using the second form will instead be generating a global scope method variable, and as such it will not be recognised as a script function by the IDE and will require the use of the global prefix when being referenced in your code.

NOTE: You can check this by using both forms in a script and then calling the runtime function typeof() on each of them. One will be classed as a 'number' - since it returns a script index ID - and the other will be classed as a 'method'.

Arguments

You can define your own parameters/arguments for the function, which will be available to the function as local variables and can be used in any manner:

function move(spd, dir)
{
speed = spd;
direction = dir;
}

This function takes two arguments and applies their values to the instance's speed and direction variables. It can now be called like any runtime function and arguments can be passed into it:

var _mouse_dir = point_direction(x, y, mouse_x, mouse_y);
move(4, _mouse_dir);

Note that if an argument is not given to a function, its value will be undefined. You can use this to define optional arguments, and check whether an argument is passed in or not by checking if it is equal to undefined. However, you can also define a default value for an argument which will be used instead of undefined when it is not passed in.

You can assign such a default value to a parameter using the equal (=) sign, making it an optional variable:

function move(spd, dir = 90)
{
speed = spd;
direction = dir;
}

If the dir argument is not passed in when calling the above function, then its value will default to 90, moving the instance in an upward direction.

The default value of an optional variable can be an expression, so for example, you can use variables and call functions while defining an optional variable. Note that such an expression will only be executed if its optional argument is not provided in the function call. See the following example of a logging function:

function log(text = 'Log', object = object_index, time = date_datetime_string(date_current_datetime()))
{
var _string = '[' + string(time) + '] ';
_string += object_get_name(object) + ': ';
_string += text;
show_debug_message(_string);
}

This function takes three arguments, where the first argument defaults to a string constant, the second argument defaults to an instance variable (in the scope of the calling instance) and the third argument defaults to an expression calling a function to retrieve the current date and time. This function can now be called with up to three arguments, as seen in the following example:

log();
// Prints: [09-Jun-21 12:34:37 PM] Object1: Log
log('Player Shot', obj_player, 10);
// Prints: [10] obj_player: Player Shot

JSDoc

We also recommend that you add comments to define the properties of the function (see the section on JSDoc Comments for more details), such that a simple script would look like this:

/// @function log(message);
/// @param {string} message The message to show
/// @description Show a message whenever the function is called.
function log(message)
{
show_debug_message(message);
}

Additional functions for the script can be added using the same format as shown above, one after the other within the script asset.

Return Value

Functions in scripts can also return a value, just as runtime functions can, and as such they can be used in expressions. For this you would use the return statement:

return <expression>

It should be noted that the execution of the function ends at the return statement, meaning that any code which comes after return has been called will not be run. Here is a short example function from a function called 'sqr_calc' which calculates the square of whatever value is passed to it, and in case the given value is not a real number, it uses return to end the function early so the actual calculation never runs:

/// @function sqr_calc(val);
/// @param {real} val The value to calculate the square of
/// @description Calculate the square of the given value
function sqr_calc(val)
{
if !is_real(val)
{
return 0;
}
return (val * val);
}

Note that if you create a script function with no return value then in your code check for one, you will get the value undefined by default.

To call a script function from within a piece of code, just use it the same way as when calling any runtime function - that is, write the function name with the parameter values in parentheses. So, the above script would be called like this:

if keyboard_check_pressed(vk_enter)
{
val = scr_sqr(amount);
}

NOTE: When using your own functions from scripts in the code editor, you can press or click the middle mouse button on the function name to open the script that contains it for editing directly.

Script Names vs. Function Names

It is important to understand that script names are independent of the functions that they contain, so you can name your scripts in a more 'common sense' way, ie: all your AI functions could go in a script 'Enemy_AI' (following the standard asset naming conventions of alpha-numeric characters and the under-bar '_' only). However, you can still call scripts in your game - and you can name scripts the same as a function that you define in them - which can give rise to a issues due to the way that GameMaker Studio 2 stores asset references. To give an example consider this code, called from an instance:

function indirectCall(func, arg)
{
func(arg);
}
indirectCall(myscript, arg);

The above code is attempting to call a script called 'myscript' within a method, which in this case will fail. This is because the in-line function is actually using the index for the script asset and not actually calling the script - eg: if the script index resolves to '4', essentially the function is calling 4(arg);, which makes no sense. The code should instead be structured in one of the following two ways:

function indirectCall(func, arg)
{
func(arg);
}
indirectCall(method(undefined, myscript), arg);
// OR
function indirectCall(func, arg)
{
script_execute(func, arg);
}
indirectCall(myscript, arg);

Game Maker 8 Enemy Ai Game Programming

This is important to note, especially when working with legacy projects where scripts contain one single function, and the function is named the same as the script. However, you really should never do this, and your scripts should be named independently of the functions they contain.

Script Scope

This leads us to the final and most important thing to know about scripts and the functions they contain: scripts are parsed on a global level and will be compiled at the very start of the game. This means that technically all functions in a script are 'unbound' method variables, and any variables declared outside of a function in the script will be considered global variables. For example, consider this script:

function Foo()
{
// Do something
}
blah = 10;
function Bar()
{
// Do something else
}

In the above case, not only have we defined the functions Foo and Bar but also the variable blah and all of them are considered to have been created in the global scope. The functions don't need the global keyword to be recognized as the compiler understands that these functions are part of the script, but if you wanted to access blah then you would need to do:

val = global.blah;

That said, we recommend that you always explicitly type global variables when creating them in scripts to prevent any issues later. Scripts are also an ideal place to define any Macros or Enums (constants), as adding them to a script outside of a function also means that they will be created for use before the game code actually starts running. Below is an example of a script that is creating different global scope values for a game:

/// Initialise All Global Scope Values And Constants
global.player_score = 0;
global.player_hp = 100;
global.pause = false;
global.music = true;
global.sound = true;
enum rainbowcolors
{
red,
orange,
yellow,
green,
blue,
indigo,
violet
}
#macro weapon_num 3
#macro weapon_gun 0
#macro weapon_bomb 1
#macro weapon_knife 2

Programming

Gamemaker Studio 2 Enemy Ai

Note how all these constants are set up outside of any function call, meaning they will be initialised before everything else and at a global scope. This means that if you want to use a script to initialise variables on an instance scope then you must wrap them in a function, for example:

/// @function init_enemy();
/// @description Initialise enemy instance vars
function init_enemy()
{
hp = 100;
dmg = 5;
mana = 50;
}

So, scripts can be used to generate macros, enums and global variables before the game starts so they are ready for use at any time, and they can also be used to create 'unbound' methods (user-defined functions) that can be used in your game like GML runtime functions.

One final thing to note about script functions is that if you are developing for Web (ie: targeting HTML5), then there is an additional function protocol that you can use when adding functions to scripts, which is to prefix a function name with gmcallback_, for example:

gmcallback_create_button

Using the above function name would mean that the function gmcallback_create_button() will not be obfuscated and so can be used in JavaScript extensions and other areas of your game, for example, when using the clickable_* functions.

Static Variables

Functions can also make use of static variables, which maintain their values throughout every function call. Please read this page for more information.

© Copyright YoYo Games Ltd. 2021 All Rights Reserved

Game Maker 8 Enemy Ai Gameplay

Written in April 2018 by Nathan Ranney, the founder of game development studio Gutter Arcade.

In this post, I will show you some simple AI concepts and how to set up different behaviors for your games.

We are going to work with a couple of simple behaviors, which should give you a decent idea about how to structure more complex behaviors and enemies. The enemy we will be working with will have three different states which will determine its actions. First, the idle state. The enemy will be sitting still, doing nothing, until one of the other states are triggered. The second state, chase, is pretty much what it sounds like. The enemy will chase our player around for a short time before transitioning to the last state, the shoot state, where the enemy will stop moving and fire a shot at the players current position. Start by creating a new script and name it enum_init.

enum_init script

Game Maker 8 Enemy Ai Games

If you are unfamiliar with enums, I highly recommend checking out my blog post on state machines.

Create a new enemy object, throw a sprite on there, and add the create, step, and draw event. Add the following code.

oEnemy create event

The state variable is used to manage the current state of the enemy, and the behavior based on the state. actionDur is used within a given state to determine how long that state will last, or to trigger something within the state. Speed is the movement speed of the enemy. Last but not least, distanceTrigger is used to set the enemy to the chase state based on how close it is to the player object.

Game Maker 8 Enemy Ai Gameplay

oEnemy draw event

This event is somewhat optional. Really all we are doing here is drawing the distanceTrigger around the enemy object. This is essentially debug information so you can see the radius around the enemy where it is checking for the player object.

States and Behaviors

In this section, we will define the behaviors for our enemy based on the state it is in. By default our enemy starts in the idle state, so let's set that up first. Open the step event and add the following.

oEnemey step event

The idle state is very simple. The enemy checks around itself for the oPlayer object, and once the player object is within range, it switches to the chase state. To achieve this we are using the distance_to_object function that is built-in to GameMaker. actionDur is set to zero in this event to make sure it is always zero when entering different states. Now we need to add the chase state. Add the following switch case below the idle case.

oEnemy step event

Slightly more complex than idle, but still pretty simple. First, we set a check to stop chasing the player if the player object gets too far away from the enemy. This is done by checking to see if the player object is farther away than the distanceTrigger plus 10, so a slightly larger radius than the basic distanceTrigger.

The speed is set to one, and the direction (also a built-in GameMaker variable) is set to wherever the player object is at the time. Speed and direction work together by default. If speed is not zero, and direction is set, the object will move in the direction it is pointed at the rate of the speed variable.

Finally we use actionDur to switch to the shoot state. actionDur counts up every frame until it reaches 120, at which point the state changes to shoot and actionDur is reset back to zero. In my game, which runs at 60 fps, the enemy shoots after chasing the player for two seconds. For the shoot state, add this final switch case.

Game Maker 8 Enemy Ai Game

oEnemy step event

This assumes you already have a oBullet object created. If you don't have that, create one and add speed = 2 to the create event.

Game Maker 8 Enemy Ai Gamer

To ensure the enemy only creates one bullet, we check to see if actionDur is set to zero, meaning it hasn't counted up yet, and create a bullet. The bullet direction is set to the same direction of the enemy that is creating it. We zero out the enemy speed so that it stops moving to shoot. This is mostly for debug purposes so we can tell for sure that the enemy has entered this state. Finally we count up the actionDur just as we did in the chase event. Once actionDur reaches 60 (one second if your game runs at 60 fps) the enemy resets to the idle state.

Resources