Please help with classes for npcs

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
User avatar
Imaculata
Party member
Posts: 102
Joined: Mon Aug 10, 2015 2:51 pm

Please help with classes for npcs

Post by Imaculata »

I've been going through the tutorials on classes, and it is just an incomprehensible mess. I cannot make any sense of it. Even if I copy the code given in examples literally, errors pop up, and I'm unable to figure out what the correct syntax it. So instead I figured I'd simply ask the forum directly. Perhaps some of you can even come up with a far better way to do this.

I'm creating a simple isometric party game, that uses bump. I want to populate my party world, with party people. So I need to come up with a quick and easy way to create npc's with a standard template (including their name, x-y-location, gender, etc), and with an easy way to look them up again.

I don't know how classes work, please help me understand it.

I have a separate script called "npcfunctions.lua" that is loaded by "main.lua". During the building of the level, it calls the following function from "npcfunctions.lua":

Code: Select all

local npclist = {} --Stores all npc's

function spawnnpcs(numberofnpcs,partyzones)
	npclist = {}
	npclist.__index = npclist
  for n=1,numberofnpcs do
	local npcspawnloc = partyzones[(love.math.random(1,#partyzones))]
	print("Creating npc at "..npcspawnloc[1]..", "..npcspawnloc[2])
	
	npclist = class(function(npc,npcspawnloc) --This is where things go wrong
		--This is where I want to add all the properties of my npc's
              	npc.x, npc.y = npcspawnloc[1], npcspawnloc[2]
         end)
  end
  
  print("Added "..#npclist.." npc's to the world.")
end
But when I run it, it says: ERROR /scripts/npcfunctions.lua:10: Attempt to call global 'class' (A nil value)
What is going wrong here? How do I fix it? And how do I make sure that when I add my npc's to bump's world, I can find them again? In other words, how do I give them unique object names that allow me to look up individual objects? Am I just doing this all wrong?
User avatar
Doctory
Party member
Posts: 441
Joined: Fri Dec 27, 2013 4:53 pm

Re: Please help with classes for npcs

Post by Doctory »

Could you please provide a .love file?
User avatar
deströyer
Prole
Posts: 32
Joined: Thu Jun 27, 2013 7:59 pm
Contact:

Re: Please help with classes for npcs

Post by deströyer »

class() isn't a keyword in Lua - you're probably following a tutorial that requires some additional code.

Here's a basic way to get a class working. Read the whole thing, and try running it on its own if you can:

Code: Select all

--	basic class stuff
npc = {}
npc.__index = npc
npc.New = function( )
	local newNPC = { }
	setmetatable( newNPC, npc )
	return newNPC
end

--	functions
npc.TakeDamage = function( self, damage )
	self.hp = self.hp - damage
	if self.hp < 1 then
		print( "NPC died" )
	end
end

--	test
Imaculata = npc:New( )
Imaculata.hp = 10
Imaculata:TakeDamage( 10 )
basically, npc is the class. You can add new functionality to the class (like movement, checking positions, attacking, drawing, etc.) by just cramming more functions on to it.

The trick here is using __index and setmetatable.
setmetatable basically says: "Hey newNPC! If the code asks you to do something, and you don't know what that is, check with npc to see if it knows what to do about it."
Also, take note of npc.TakeDamage - the first parameter in the function is "self", which means that when you later on do Imaculata:TakeDamage(10), the code will damage the Imaculata-table, not the npc-table.
User avatar
Imaculata
Party member
Posts: 102
Joined: Mon Aug 10, 2015 2:51 pm

Re: Please help with classes for npcs

Post by Imaculata »

Thanks, this helps a lot!

I've now got a basic npc system working. It first creates a list of tiles where it is allowed to spawn npc's. I've marked areas in the level as so called "party zones". And my festive npc will be spawning in these areas. This allows me to create hotspots where a bunch of npc's group together.

Code: Select all

function spawnnpcs(numberofnpcs,partyzones)
	npclist = {}
	npclist.__index = npclist
	npc.size = {32, 48} --Same size as player
	--Create all npc anims (same as player's anims)
	npc.anims = {}
	npc.anims[0] = {"idleup",1,npc.size,{0,0},{1}}
	npc.anims[1] = {"idleright",1,npc.size,{0,0},{5}}
	npc.anims[2] = {"idledown",1,npc.size,{0,0},{9}}
	npc.anims[3] = {"idleleft",1,npc.size,{0,0},{13}}
	npc.anims[4] = {"walkup",4,npc.size,{0,0},{1,2,3,4}}
	npc.anims[5] = {"walkright",4,npc.size,{0,0},{5,6,7,8}}
	npc.anims[6] = {"walkdown",4,npc.size,{0,0},{9,10,11,12}}
	npc.anims[7] = {"walkleft",4,npc.size,{0,0},{13,14,15,16}}
	--Spawn all npc's for the level
	for n=1,numberofnpcs do
		local npcspawnloc = partyzones[(love.math.random(1,#partyzones))]
		--print("Creating npc at "..npcspawnloc[1]..", "..npcspawnloc[2])
		npclist[#npclist+1] = npc:initialize(npcspawnloc[1]*tileSize, npcspawnloc[2]*tileSize) --Add reference of npc to list
		addNpc("npc"..(#npclist+1), npcspawnloc[1]*tileSize, npcspawnloc[2]*tileSize, npc.size[1], npc.size[2]) --Add npc to npc bump world
	end
  
	print("Added "..#npclist.." npc's to the world.")
end
As you can see, it also defines a default animation list for npc's in general, and then spawns all npc's at once. It also adds a reference to each npc to a separate table.

Should the npc's all be stored in a separate table, like I am doing now? Or is it better to store the npc's along with their functions inside bump's world? Because I will need to use bump to find out which objects are currently on screen, and then collect the data belonging to that npc from the other table. I'm not entirely sure how to do this back and forth, and it seems cumbersome.

Next, here is the code that defines the npc 'class':

Code: Select all

function npc:initialize(x, y)
  self.x, self.y = x, y
  local rgender = love.math.random(0,1)
  if (rgender == 0) then self.gender = "male" else self.gender = "female" end
  self.race = love.math.random(1,5)
  self.name = getNpcName(self.gender,self.race)
  self.interests = love.math.random(1,24)
  self.action = "idle"
  self.direction = "down"
  self.tile = {math.floor((self.x+(mapX/tileSize)+(self.size[1]/2)+(tileSize/2))/tileSize),math.floor((self.y+(mapY/tileSize)+self.size[2]+(tileSize/2))/tileSize)}
  --print("Added "..self.name..", who is a "..self.race.." "..self.gender..".")
  return self.name
end


The initialize code defines all the attributes of the npc, such as their gender, their name (randomly selected from a huge list, based on their gender) and their race (determines what sprite to use for their body). I'll be adding a lot more to this list as the game develops.

Lastly, npcadd will add the npc to a separate bump world, called npcworld. I don't want the player to collide with the over 50 npc's that populate the level. I don't want the npc's to cause a blockade, so I made a separate collision world for them. They won't be bumping with the level I think, depending on how I code their movement.

Code: Select all

function addNpc(npcname, npcx, npcy, npcsizex, npcsizey)
npcworld:add(npcname, npcx+(npcsizex/4), (npcy+npcsizey-tileSize/2), 16,8) -- x,y,w,h
end
But I have another question. Is it a good idea to have a separate collision world for the npc's? Because I don't want the player to collide with the npc's, and I don't want the npc's to collide with the player, nor with each other. However, I will be doing some line of sight checks and touch events based on the npc's. But I am not sure how to go about doing this. For example, when an npc touches a door, I want the door to open, and when they no longer touch it, the door should close. I have not made the door object yet, but I am trying to figure out how to best do this. I suppose I could add the doors to the npcworld, but the player will be opening and closing doors too.... so do I add the doors to both? Or should everything just be inside one collision world?
User avatar
Imaculata
Party member
Posts: 102
Joined: Mon Aug 10, 2015 2:51 pm

Re: Please help with classes for npcs

Post by Imaculata »

deströyer wrote: Also, take note of npc.TakeDamage - the first parameter in the function is "self", which means that when you later on do Imaculata:TakeDamage(10), the code will damage the Imaculata-table, not the npc-table.
First of all, I'm still going through your code, and fiddling with it to see if I can get it to work with my code. So what I posted before is now outdated, and in the process of being updated with what you posted. But I do have some questions:

So how would you go about retrieving information from a specific npc once you've added them all? What would the code for that look like? Because I'm really struggling to wrap my head around that part.

As I understand it, my code now creates several npc's which are stored in a meta table, I think (I hope I'm doing this correctly). But now I need to figure out how to retrieve properties from them, such as their x-y location, their gender and their race.
User avatar
Imaculata
Party member
Posts: 102
Joined: Mon Aug 10, 2015 2:51 pm

Re: Please help with classes for npcs

Post by Imaculata »

Imaculata = npc:New( )
Imaculata.hp = 10
Imaculata:TakeDamage( 10 )
This area confuses me. As I understand it, you are creating a new npc class that is stored in the value "Imaculata". And then you change the ".hp" property of "Imaculata" to 10. You then call the function "TakeDamage" within "Imaculata", along with the value 10 (the amount of damage to take). So far I follow. But that is just a single npc.

How do you create 50 Imaculata's, and all tell them all apart? Do you store them all in a table? How do you give them unique names, and how are you able to retrieve the individual information from one of them?

Currently I have this, but I'm still unsure how to retrieve values from individual npc's.

Code: Select all

function spawnnpcs(numberofnpcs,partyzones)
	--Starting values
	npc.size = {32, 48} --Same size as player
	--Create all npc anims (same as player's anims)
	npc.anims = {}
	npc.anims[0] = {"idleup",1,npc.size,{0,0},{1}}
	npc.anims[1] = {"idleright",1,npc.size,{0,0},{5}}
	npc.anims[2] = {"idledown",1,npc.size,{0,0},{9}}
	npc.anims[3] = {"idleleft",1,npc.size,{0,0},{13}}
	npc.anims[4] = {"walkup",4,npc.size,{0,0},{1,2,3,4}}
	npc.anims[5] = {"walkright",4,npc.size,{0,0},{5,6,7,8}}
	npc.anims[6] = {"walkdown",4,npc.size,{0,0},{9,10,11,12}}
	npc.anims[7] = {"walkleft",4,npc.size,{0,0},{13,14,15,16}}
	
	--Create npc class here
	npclist = {}
	npclist.__index = npclist
	npclist.New = function(npcx,npcy)
		local newNPC = { }
		newNPC.x, newNPC.y = npcx, npcy
		local rgender = love.math.random(0,1)
		if (rgender == 0) then newNPC.gender = "male" else newNPC.gender = "female" end
		newNPC.race = love.math.random(1,5)
		newNPC.name = getNpcName(newNPC.gender,newNPC.race)
		newNPC.interests = love.math.random(1,24)
		newNPC.action = "idle"
		newNPC.direction = "down"
		newNPC.tile = {math.floor((newNPC.x+(mapX/tileSize)+(tileSize/2))/tileSize),math.floor((newNPC.y+(mapY/tileSize)-tileSize+npc.size[2]+(tileSize/2))/tileSize)}
		setmetatable( newNPC, npclist )
		return newNPC
	end
	
	--Spawn all npc's for the level
	numberofnpcs = 3
	for n=1,numberofnpcs do
		local pickedpartyzone = love.math.random(1,#partyzones)
		local npcspawnloc = partyzones[pickedpartyzone]
		--print("Spawning npc at: "..npcspawnloc[1]..", "..npcspawnloc[2])
		table.remove(partyzones,pickedpartyzone)
		--print("Creating npc at "..npcspawnloc[1]..", "..npcspawnloc[2])
		npcspawnloc[1],npcspawnloc[2] = ((npcspawnloc[1]*tileSize)-tileSize+8),((npcspawnloc[2]*tileSize)-16-(tileSize))
		npclist[#npclist+1] = npclist.New(npcspawnloc[1], npcspawnloc[2]) --Add reference of npc to list
		addNpc("npc"..(#npclist+1), npcspawnloc[1], npcspawnloc[2], npc.size[1], npc.size[2]) --Add npc to npc bump world
	end
  
	print("Added "..#npclist.." npc's to the world.")
end
User avatar
pgimeno
Party member
Posts: 3551
Joined: Sun Oct 18, 2015 2:58 pm

Re: Please help with classes for npcs

Post by pgimeno »

In deströyer's example, Imaculata is an instance, which means means that it has data separate from all other instances, as opposed to a class (npc in this case), which is like a template having the elements that will be common to all newly-created instances, and default values for existing instances. If you call npc.new() 50 times and store them in a sequence, you'll have 50 separate instances. For example:

Code: Select all

npcs = {}
for i = 1, 50 do
  npcs[i] = npc.new()
end
Then you access each npc's hp as npcs[1].hp, npcs[3].hp and so on.

By the way, more properly it should be written npc.new() rather than npc:new() but that's nitpicking because the argument is ignored.
User avatar
Imaculata
Party member
Posts: 102
Joined: Mon Aug 10, 2015 2:51 pm

Re: Please help with classes for npcs

Post by Imaculata »

Ah, thanks a lot. This clears up a lot of confusion. I'll try and get this working over the weekend, and update this thread as things progress. Thanks a lot for all your input and clear explanations.
User avatar
Imaculata
Party member
Posts: 102
Joined: Mon Aug 10, 2015 2:51 pm

Re: Please help with classes for npcs

Post by Imaculata »

Alright, I've hit my first roadblock. So I succeeded in making a list of npc's, and I now add them to bump's world. According to the documentation of bump, it's collision system should also work just fine as an object repository. And that's all well and nice, but I'm unsure how to collect the properties of the npc's once they've been added to bump's world.

My code currently look like this:

Code: Select all

function spawnnpcs(numberofnpcs,partyzones)
	--Starting values
	npc.size = {32, 48} --Same size as player
	--Create all npc anims (same as player's anims)
	npc.anims = {}
	npc.anims[0] = {"idleup",1,npc.size,{0,0},{1}}
	npc.anims[1] = {"idleright",1,npc.size,{0,0},{5}}
	npc.anims[2] = {"idledown",1,npc.size,{0,0},{9}}
	npc.anims[3] = {"idleleft",1,npc.size,{0,0},{13}}
	npc.anims[4] = {"walkup",4,npc.size,{0,0},{1,2,3,4}}
	npc.anims[5] = {"walkright",4,npc.size,{0,0},{5,6,7,8}}
	npc.anims[6] = {"walkdown",4,npc.size,{0,0},{9,10,11,12}}
	npc.anims[7] = {"walkleft",4,npc.size,{0,0},{13,14,15,16}}
	
	--Create npc class here
	npclist = {}
	npclist.__index = npclist
	npclist.New = function(npcx,npcy)
		local newNPC = { }
		newNPC.x, newNPC.y = npcx, npcy
		local rgender = love.math.random(0,1)
		if (rgender == 0) then newNPC.gender = "male" else newNPC.gender = "female" end
		newNPC.race = love.math.random(1,5)
		newNPC.name = getNpcName(newNPC.gender,newNPC.race)
		newNPC.interests = love.math.random(1,24)
		newNPC.action = "idle"
		newNPC.direction = "down"
		newNPC.tile = {math.floor((newNPC.x+(mapX/tileSize)+(tileSize/2))/tileSize),math.floor((newNPC.y+(mapY/tileSize)-tileSize+npc.size[2]+(tileSize/2))/tileSize)}
		setmetatable( newNPC, npclist )
		return newNPC
	end
	
	npclist.GetName = function( self )
	return self.name --<==Test to see if we can call a function and retrieve a piece of data
	end
	
	--Spawn all npc's for the level
	for n=1,numberofnpcs do
		local pickedpartyzone = love.math.random(1,#partyzones)
		local npcspawnloc = partyzones[pickedpartyzone]
		table.remove(partyzones,pickedpartyzone)
		npcspawnloc[1],npcspawnloc[2] = ((npcspawnloc[1]*tileSize)-tileSize+8),((npcspawnloc[2]*tileSize)-16-(tileSize))

		addNpc((npclist.New(npcspawnloc[1], npcspawnloc[2])), npcspawnloc[1], npcspawnloc[2], npc.size[1], npc.size[2]) --Add npc to npc bump world
	end
  
	print("Added "..numberofnpcs.." npc's to the world.")
end
As you can see, the last lines in the code call the function "addNpc", which takes our block of npc data, and directly adds it to bump's world with the following bit of code:

Code: Select all

function addNpc(npctoadd, npcx, npcy, npcsizex, npcsizey)
npcworld:add(npctoadd, npcx+(npcsizex/4), (npcy+npcsizey-tileSize/2), 16,8) -- x,y,w,h
end
But how do I retrieve the data of the npc's? I added a bit of code to append a function to the npc that would allow me to retrieve their name:

Code: Select all

	
	npclist.GetName = function( self )
	return self.name --<==Test to see if we can call a function and retrieve a piece of data
	end
-But I do not know what the syntax would need to be to call this function. These are my first steps in working with bump, and its documentation isn't very helpful with these sorts of issues, nor are the examples games.
User avatar
pgimeno
Party member
Posts: 3551
Joined: Sun Oct 18, 2015 2:58 pm

Re: Please help with classes for npcs

Post by pgimeno »

I haven't used bump, but after a quick look at the docs, it seems to me that you could use world:getItems(). However, I think that's a weird approach. Rather than just adding the NPCs to the world directly and retrieving them with the above, you could store them in your own array; that at least gives you control over the order they appear in the array (though order is probably not important here).

npclist is a weird name for the class, since it's the base type of the NPCs and not really a list of them. That name is more suitable for... well, a list of NPCs :)

Choosing good names for your variables will help you in the long term, because they won't mislead you into thinking they mean something different when you use them later. That can be and has been the source of numerous bugs.
Post Reply

Who is online

Users browsing this forum: slime and 70 guests