Justice, the awesome NPC system

This is practically pre-release software, I recommend making heavy modifications before you use this in a full fledged game, which you should be doing anyway!

Justice, in its current form, is a script (which I consider awesome) created to add basic NPCs to your game quickly and easily. It is designed to be hacked on rather than be used without modification. It features automatic NPC generation and makes it easy to designate areas for your NPCs to target. Justice can handle hundreds of NPCs. I have tested it with 1000 with no issues at all (except for an obvious performance hit) on my local machine. Each NPC has its own coroutine to control its movement.

Here it is running 20 NPCs in Studio. The markers that indicate where the NPCs are going only show up in Studio as a debugging aid.


And here is a more extreme example. 500 NPCs.

(I do not recommend running so many NPCs at once, it does cause issues as demonstrated in the example.)
And, just for fun, here’s the run where I did 1000 NPCs simultaneously.

I have discovered the reason why player servers are capped to 200. All in the name of 5 FPS science!

NPCs will automatically wander to areas that are tagged as pathfindable. I recommend using Tag Editor for convenience of marking areas as pathfindable.

Configuring Justice is easy, and the configuration file is kept minimalistic:

---------------------------
-- Justice Configuration -- 
---------------------------

return {
	count = 20, -- The amount of NPCs you want to generate
	movementTimeout = 3, -- How long the NPCs can be stuck for before they give up and go to another target
	maxDawdlingTime = 5,  -- How long the NPCs will dawdle in an area before they select a new target
	canCollideWithNPCs = false, -- If NPCs can collide with each other
	canCollideWithPlayers = false, -- If NPCs can collide with players
	discriminators = { "male", "female" }, -- How NPCs will be unique (e.g. gender, race, etc.)
	origin = CFrame.new(0, 0, 0), -- The spawnpoint of the NPCs
	logging = true, -- If logging should be enabled.
	nameGenerator = function(i) -- The function used to generate names for the NPCs.
		return "Intern " .. i
	end,
}

NPC generation is a bit more in depth. The Assets script contains everything you need to generate unique NPCs. The default assets provided are what I like to call the ‘office’ asset pack of sorts. The original purpose of this script wasn’t to be released to the public, but to be kept private for an office block to provide more life to the place. There’s some trivia for you.

Discriminators are used to make more unique NPCs. In the default configuration, the discriminators are configured as genders. This, in turn, makes it so only certain assets can be applied to an NPC (e.g. female faces can only go onto an NPC that has a discriminator of female). You can see this used a little bit in the default Assets file, where various things are reserved for various discriminators. There is a special all discriminator which, regardless of what the NPC’s discriminator is, can be selected. This is to prevent duplication of assets over and over again just so you can have them for all of your NPCs.

If you look closely, you can see that under accessory, there is a -1. This isn’t a typo, this is a feature of Justice where if this is selected, it skips adding an accessory to an NPC. This also works for hair too.

You will need to drag the Template out of the script into the workspace to set where the generated NPCs will spawn.

As a reminder, this is meant to be a hackable script. It’s not meant to be a one size fits all ordeal, so please modify it to your needs.

Download: Justice.rbxm (21.7 KB)

History

Build 4: Justice.rbxm (21.7 KB)
Build 3: Justice.rbxm (21.6 KB)
Build 2: Justice.rbxm (21.4 KB)
Build 1: Justice.rbxm (21.0 KB)

This isn’t particularly the cleanest thing I’ve written, but it does its job. I’ll probably make it prettier if people actually use it.

The character, Justice, is from Helltaker, by vanripper. The art is his as well.

218 Likes

Pretty nice system.
Would def try/play around w/ this in the future…

Also is it possible to have the npcs move in random directions(ie. simulator type npcs)

6 Likes

Sorta, if you just have one area tagged as pathfindable it’ll move randomly within that one area.

7 Likes

This seems really nice. Just one question, would it be possible to have a few NPCs spawn with a certain uniform?

Also, what about having the NPCs walk to a certain point at certain times (an NPC crosses the road only when the “WALK” sign is on)?

3 Likes

Sure! In the config, set a discriminator called, oh I don’t know, myCoolUniqueUniformForCertainNPCs. In the Assets file, you can go to the clothing section and add that discriminator. Although, you will need to add that discriminator to other asset categories as well! This system is still being refined and I’m not too sure how I want it done yet so this will most likely change in the future.

Seems like something that would fit in the scope of the project, something along the lines of using CollectionService to assign a tag to an NPC to allow it to begin moving again once so and so condition has been met. For now, you can implement this yourself. The method for controlling NPCs is creatively called controlNpc. Just check at the start of the loop (around line 70) for your condition and if it isn’t set, skip the iteration of the loop or do a repeat wait() until condition statement.

4 Likes

Just another question: Would this work for R15/Rthro? (pls dont hate on me for actually appreciating Rthro)?

2 Likes

Support for R15/Rthro is planned later. For now this is R6 models only.

4 Likes

https://developer.roblox.com/en-us/api-reference/function/BasePart/SetNetworkOwnershipAuto
Hopefully this works.

Edit-
I misunderstood the point of this function.

2 Likes

Maybe you should add a Client Sided render radius for them. So if the player is in a certain part region or region3 it would render the npc’s. The system works well though.

3 Likes

That unfortunately doesn’t let me force it to be server-side. That lets the game engine decide who owns the NPC so it’s essentially default behaviour. However, that function is new to me so thank you for introducing me to that!

3 Likes

For animations or for letting them show up? Either way, seems like an interesting idea!

3 Likes

It looks cool, though might wanna make it a model.

2 Likes

I purposely held off of putting this in the toolbox until it was ready for an actual release. For now this is very much still a work in progress.

3 Likes

I love the Helltaker reference. How did you get it to run smoothly without lag?

3 Likes

I thought the function would allow for a Boolean argument, however that is not the case and it simply reverts to default behavior, but there doesn’t seem to be a way to force it to always be server side.

2 Likes

For letting the npc’s show up visually. So you could have a large amount of them in the game at once.

Or you could just make the npc’s client sided in general.

1 Like

Painfully.

	coroutine.resume(
		coroutine.create(
			function()
				while wait() do
					pcall(function() npc.PrimaryPart:SetNetworkOwner(nil) end) -- forcefully and constantly override network ownership of the npc
					-- to be honest this actually sucks but it's the best we can do at the moment, there's no way of just 
					-- preventing automatic ownership changes iirc
				end
			end
		)
	)
1 Like

I’ll probably implement a Level of Detail like system where you can see the NPC in full, then it has no animation, and then it just doesn’t render at all if it’s too far away.

In the meantime, I’ll probably work out how to do occlusion to hide NPCs from players if they can’t see them.

3 Likes

Also, why does the download have the notepad app??? Or is that just me?

1 Like

I feel like a more efficient way would be:

coroutine.resume(
	coroutine.create(
		function()
			while wait() do
			    if npc.PrimaryPart:GetNetworkOwner() ~= nil then
                    npc.PrimaryPart:SetNetworkOwner(nil)
                end
			end
		end
	)
)

Or even using Heartbeat for something like this, because while wait() sucks.

2 Likes