ThirdBlog-Top

Thursday, July 2, 2015

NextBot NPC Creation


Nextbot is what the AI in Team Fortress 2 and Left 4 Dead use. This tutorial will go over the steps for a simple AI that will search for enemies (you) and chase them until they die or are too far away. It will also do some random other stuff when there are not any enemies.

Lets get started

First create the .lua file for your entity. The one I made for this tutorial is in "addons/Nextbot_tut/lua/entities" and is named "simple_nextbot.lua". Now open that file so you can start adding the code.

The code

The basic stuff we need for entities

Start off with defining the base entity to use and making it spawnable. Pretty much the same as any other entity so far Here we set the model and define some variables we will use later.

AddCSLuaFile()

ENT.Base                                             = "base_nextbot"
ENT.Spawnable                = true

function ENT:Initialize()

                self:SetModel( "models/hunter.mdl" )

                self.LoseTargetDist         = 2000   -- How far the enemy has to be before we lose them
                self.SearchRadius            = 1000   -- How far to search for enemies

end
Enemy related stuff

This adds some useful functions for enemy related stuff. An NPC/bot isn't complete if it can't target stuff, right? These include a function to check if there is still an enemy or if it got away and a function to search for enemies. I've added all sorts of comments so you know exactly what they do.

----------------------------------------------------
-- ENT:Get/SetEnemy()
-- Simple functions used in keeping our enemy saved
----------------------------------------------------
function ENT:SetEnemy( ent )
                self.Enemy = ent
end
function ENT:GetEnemy()
                return self.Enemy
end

----------------------------------------------------
-- ENT:HaveEnemy()
-- Returns true if we have an enemy
----------------------------------------------------
function ENT:HaveEnemy()
                -- If our current enemy is valid
                if ( self:GetEnemy() and IsValid( self:GetEnemy() ) ) then
                                -- If the enemy is too far
                                if ( self:GetRangeTo( self:GetEnemy():GetPos() ) > self.LoseTargetDist ) then
                                                -- If the enemy is lost then call FindEnemy() to look for a new one
                                                -- FindEnemy() will return true if an enemy is found, making this function return true
                                                return self:FindEnemy()
                                -- If the enemy is dead( we have to check if its a player before we use Alive() )
                                elseif ( self:GetEnemy():IsPlayer() and !self:GetEnemy():Alive() ) then
                                                return self:FindEnemy()                               -- Return false if the search finds nothing
                                end
                                -- The enemy is neither too far nor too dead so we can return true
                                return true
                else
                                -- The enemy isn't valid so lets look for a new one
                                return self:FindEnemy()
                end
end

----------------------------------------------------
-- ENT:FindEnemy()
-- Returns true and sets our enemy if we find one
----------------------------------------------------
function ENT:FindEnemy()
                -- Search around us for entities
                -- This can be done any way you want eg. ents.FindInCone() to replicate eyesight
                local _ents = ents.FindInSphere( self:GetPos(), self.SearchRadius )
                -- Here we loop through every entity the above search finds and see if it's the one we want
                for k, v in pairs( _ents ) do
                                if ( v:IsPlayer() ) then
                                                -- We found one so lets set it as our enemy and return true
                                                self:SetEnemy( v )
                                                return true
                                end
                end
                -- We found nothing so we will set our enemy as nil ( nothing ) and return false
                self:SetEnemy( nil )
                return false
end
Coroutines


Now that the bot can find enemies we need to get it to actually do something other then just having an enemy. This next part is where most of our AI will be set up. The function is a coroutine, or pretty much a giant looping section of code, except you can pause it for a period of time using coroutine.wait( time ). Coroutines allow you to do things in a timed order, letting you pause the function so we can make the bot face the player or play an animation. And since its all inside a while true loop, it will run for as long as the bot exists. So after your ai has finished running everything it can do, it will go back and do it again. Here is an example of a very simple bot.

No comments:

Post a Comment