NPC and Player Class Structure?

Problem

I have a class system where both “Zombie” and “Human” extend their super class “User”. This is fine for player character’s however I want to implement NPCs.

Structure

super User
Zombie extends User
Human extends User

Options

NPCs and Players will be able to do the same things, however, the logic is just a bit different. NPCs have the whole movement system to implement as well.

From what I can tell I have two options:

  1. Upon constructor the new User Object, give the object a “Type” property and use that property to determine what functions get run
function User.new(Player, type)
    ... -- assume everything has been completed here
    self.Type = type

    if self.Type == "Player" then
         -- do some things
    else
         -- do other things
    end

    return self
end
  1. Create another class called NPC controller that runs the NPC along with the same for the player. (could be integrated with the first solution)

Question

I like this option but I still want to see if I can find any others. What would you guys do?

You cannot inherit classes with metatables, however, you can use a function for __index that checks the superclass and subclass (i.e. return metaIndex[key] or superMetaIndex[key]), or make a copy of the superclass metatable and add your new functions that way.

I already have the “inheriting” system down, this is merely a structure question.

Oh, then that really depends on the purpose of it. An example is: you have a shape class and you could either give it a function to represent its form or give it a type for it to choose a form. If they are supposed to share the exact same functionality then use the same “class.” Having different functionality based on a “type” of the same class is usually only necessary when the same operations are going to be performed regardless, but it is done differently on one object compared to another

1 Like

Does your structure allow for interfaces?

local user = {}
user.__index = user

function user.new()

    local self = {}
    setmetatable(self, user)

    return self
end

return user
local player = {}
player.__index = player

function player.new()

    local self =user.new()
    setmetatable(player, user)
    setmetatable(self, player)

    return self
end

return player
local npc= {}
npc.__index = npc

function npc.new()

    local self =user.new()
    setmetatable(npc, user)
    setmetatable(self, npc)

    return self
end

return npc

Put all shared methods in the user class, then unique to each player and npc in their respective classes. This is how I’d do it :smiley:

2 Likes

Why not go the Roblox route and have a Humanoid(-like) class?

You’d have your Humanoid-like class, then under that you can have Zombie or whatever.

I’m sure it would be good to define a standardized way for you to control the humanoids. That way, NPCs can use AI/logic/whatever, and you can use user input as well. Just two different types of controllers, but any controller can be used with any humanoid. Sound simple enough?

If you need interactable NPCs, maybe do it the way Avorion does it and have everything have an interactions list (defined in the base, humanoid-like class). Players can have it be empty (or maybe you can have functionality for other players, inspect/trade/etc?) and NPCs can have their chat dialogs or whatever you need.

It’s easy to extend this into a very flexible system, and it should be pretty easy to maintain too.

I hope I’ve inspired you to make something great :)

1 Like

Not currently.

I only have basic java knowledge that is just short of interfaces.

That is a good method!

It solves my issue of doing the if statements before start up.

There’s a better way to do this instead of just constantly wrapping the module whenever you need to create a new instance of an object. Instead of creating a blank table, use setmetatable.

Any superclass should be constructing a new table. Any extended class should be using the return of setmetatable on a blank table and the superclass table.

local user = {}
user.__index = user

local player = setmetatable({}, user)
player.__index = player

local npc = setmetatable({}, user)
npc.__index = user

Your constructor methods then work as normal where you create an object and then wrap that in the current class table.

function user.new()
    return setmetatable({}, user)
end

function player.new()
    return setmetatable({}, player)
end

-- etc

You can nest metatables which is what this does. When an object of a class does not have a given indice, it looks to its class metatable. When the class table does not have an indice, it looks to its superclass metatable.

1 Like