How To: NPCs and loops

From PGE Wiki
Jump to: navigation, search

This tutorial is about the basics of working with NPCs and interacting with NPC objects and their properties during runtime.

Getting NPCs

By ID/Section

The NPC Class contains various ways to modify NPCs through interaction with SMBX's NPC objects. Often when dealing with NPCs, we don't target a specific NPC, but a group of NPCs of a certain ID.

To do so, we use NPC.get(int or table ids, int or table sections).

This function takes two arguments:

 (1) The ID or a table of IDs of the NPCs we want to target (these are the same numerical IDs SMBX assigns them). Leaving this blank will have the function return NPCs of all IDs (However, to specify a section, this argument must be filled in.)
 (2) The section number or a table of section numbers of the sections we want to look for NPCs in. This argument is optional and if not included, the function will return NPCs of the given ID(s) from all sections. Often, this will be player.section (the ID of the section the player is in). Remember that sections start at 0!

This function returns a table of all of the objects of NPCs that fit the criteria.

For example:

function onTick()
	local goombas = NPC.get(1); --all SMB3 goombas (ID 1)

	local moreGoombas = NPC.get({1, 2}); --all SMB3 goombas (IDs 1 and 2)

	local section0Goombas = NPC.get(1, 0); --all SMB3 goombas (ID 1) in section 0 (the first section)

	local currentSectionGoombas = NPC.get({1, 2}, player.section); --all SMB3 goombas (IDs 1 and 2) from the section the player is in
end

If we wanted to get the first SMB3 goomba (ID 1) in the level (remembering that unlike a lot of other programming languages Lua tables have indices by default starting at 1):

function onTick()
	local goombas = NPC.get(1);
	local firstGoomba = goombas[1]; --the first element in the table of all SMB3 goombas (ID 1)
end

Or even just:

function onTick()
	local firstGoomba = NPC.get(1)[1]; --the first element in the table of all SMB3 goombas (ID 1)
end

Finding NPCs by Position

Another useful function to be aware of is NPC.getIntersecting(number x1, number y1, number x2, number y2). If we're looking for NPCs in a specific location, we can target them based on whether or not they collide with a box formed by the arguments of NPC.getIntersecting. x1 and y1 are the x and y-coordinates of the top left corner of the box, while x2 and y2 are the x and y-coordinates of the bottom right corner, as shown below.

https://i.imgur.com/ZyFIb1V.png

Like NPC.get, NPC.getIntersecting returns a table of all NPCs that are colliding with the box.

function onTick()
	local intersectingNPCs = NPC.getIntersecting(-200000, -200608, -199200, -200000); --all NPCs within a box formed by the coordinates (-200000, -200608), for the top left, and (-199200, -200000), for the bottom right

	local moreIntersectingNPCs = NPC.getIntersecting(-150000, -150000, -150000 + 32, -150000 + 64); --all NPCs within a box with top left corner at (-150000, -150000) and width 32 and height 64
end

Interacting with NPCs

We can see that NPCs all have a lot of different properties that they inherit from the NPC Class (see: Instance functions). All NPCs have a bunch of functions (methods) and fields that we can use. Some fields are read-only (marked "ro") which means we can read them but we cannot set values to them.

If we wanted to set a value to make our first goomba stop moving:

function onTick()
	local firstGoomba = NPC.get(1)[1];
	firstGoomba.dontMove = true; --set the value true to the NPC object's "dontMove" field
end

Each of our NPC objects is also essentially a table. When we access the value of dontMove from an NPC, we are looking at the index "dontMove" of the NPC and its corresponding value.

Therefore, the following is another equally valid way to write the code above:

function onTick()
	local firstGoomba = NPC.get(1)[1];
	firstGoomba["dontMove"] = true; --set the value true to the NPC object's "dontMove" field
end

We use the former when dealing with objects as it is quicker to write, instead of the lengthier means of writing the same thing in the second example.

We can also call a method on our first goomba to change it into a coin:

function onTick()
	local firstGoomba = NPC.get(1)[1];
	firstGoomba:toCoin(); --call the method "toCoin" on the NPC object
end

Multiple NPCs

When we want to interact with multiple NPCs on the same tick, we use a for loop to iterate over a table of NPCs objects. The code inside the for loop will run with each element (which has a key, value pair representing its index in the table and its value) from the table. We use the ipairs function as our iterator when dealing with any of LunaLua's get and getIntersecting functions across all classes.

The general structure of this type of for loop is:

for key, value in ipairs(table) do
	--loop code
end

In the for loop our variables "key" and "value" are automatically localized to the scope of the loop. That means that outside of the loop code, these variables don't exist unless they are defined otherwise. We can name these two variables whatever we choose. Often they will be seen as "k" and "v" to shorten it, "_" and "v" usually when the index of the element in the table doesn't matter to the code, or some other names that best suit the situation.

A more specific example to an NPC.get call:

for index, goomba in ipairs(NPC.get(1)) do
	--loop code
end

This code loops over the table returned by NPC.get(1) for every element in the table which has its own key, value pair. We can call these variables whatever we like. In this case, the key is called "index" and the value is "goomba". It runs the code inside the for loop for each element in the array.

 index = 1, goomba = NPC.get(1)[1] --> run loop code
 index = 2, goomba = NPC.get(1)[2] --> run loop code
 ...
 index = #NPC.get(1), goomba = NPC.get(1)[#NPC.get(1)] --> run loop code

Remember that the hash operator (#) tells us the length of the table. If the length of the table is #NPC.get(1) then the last element in the table will have an index of #NPC.get(1).

Just like when we changed the value of "dontMove" for our first goomba earlier, we can also change the properties of NPC objects in our for loops, in exactly the same way! In our current for loop, the variable holding our NPC object is "goomba". If we want to make all goombas not move by setting each of their "dontMove" fields to true:

for index, goomba in ipairs(NPC.get(1)) do
	goomba.dontMove = true;
end

The code iterates over all goombas in the table returned by NPC.get(1):

 index = 1, goomba = NPC.get(1)[1] --> goomba.dontMove = true;
 index = 2, goomba = NPC.get(1)[2] --> goomba.dontMove = true;
 ...
 index = #NPC.get(1), goomba = NPC.get(1)[#NPC.get(1)] --> goomba.dontMove = true;

It runs the loop code on each of the objects it iterates over, one at a time.

Again, we can also call methods on the NPCs we loop over, such as the very useful "kill" function.

for index, goomba in ipairs(NPC.get(1)) do
	goomba:kill();
end

This would kill all of our goombas. It's good to be aware that there are different harm types that we can use when calling the "harm" and "kill" methods on NPCs. You can find them here.