The primary enemies in our game that Patchman faces are robotic drones, programmed to protect and guard the Sheeple. Why are they gaurding them? The Sheeple are their energy source, obviously!
While there are a number of high-level psychological tactics that are used to keep the Sheeple in line, without resorting to force, we won't be discussing them here.
The Drones were programmed over five major iterations, each of which successively added more abstraction and overall robustness. For example, in the first iteration, the Drones were only able to target Patchman, and alert other Drones. After a round of abstraction, the Drones are able to target any object, such as a decoy blow-up doll of Patchman, and alert other Drones to that blow-up doll's presence, and shoot at it violently until it is successfully eliminated. Drones REALLY don't like blow-up dolls, but if they see a moving Patchman they'll figure out what's what and go after the moving target instead.
This is all predicated on the ye-old basic desires system. Desires increase in rank depending on various factors, and the top desire becomes the dominant goal. Whereas the Sheeple have three levels of goal planning, the Drones have only two levels: goal and action. The reason being is that the Drones really aren't that complex compared to the Sheeple, even though the Sheeple *seem* to be doing less-complex behaviors. Hunting, pursuit, patrolling, investigating, and offensive things of that nature are not rocket science. It's hard work but easy plans. Vice versa for the Sheeple, who have easy work but hard plans, deciding whether to watching the Repeater in order to get Symbols for the Vending machine and ultimately solve their hunger problem.
What follows below is some sample Lua code, that has been heavily pruned so that it might fit in this article and be more readable as pseudo-code:
-- AI --------------------------------------
--------------------------------------------
function drone:ai()
-- =====================================
-- A) KNOWLEDGE / INIT
-- =====================================
local x,y = drone.body:getWorldCenter()
local know = drone.know or {}
-- =====================================
-- A1) TARGETS
-- =====================================
drone.visual = false
-- 1 -> Patchman Target
drone:look_for_patchman()
-- 2 -> Alert Target
if know['alert'] then drone:alert_target() end
-- 3 -> Interests Target
if not drone.visual and not drone.short_circuit then drone:look_for_interests(200) end
-- 4 -> Area Targets
if not drone.know['visual'] then drone:look_in_area() end
-- 5 -> remove dead targets
for object,target in pairs(drone.targets) do end
-- look for top available targets
local top_target = false
local top_target_value = 0
for object,target in pairs(drone.targets) do
local value = target['value']
if target['visual'] or target['alert'] then
if value > top_target_value then top_target = object; top_target_value = value end
end
end
local new_target = (top_target ~= drone.current_target)
-- =====================================
-- B) DESIRES
-- =====================================
local desires = {}
-- basic patrol desire
desires[500] = {'patrol'}
-- targets
for object,target in pairs(drone.targets) do
desires[target['value']] = {'pursuit',object}
end
-- =====================================
-- B2) GOAL CHECK -> ADDITIONAL DESIRES
-- =====================================
if drone.goal == 'track' then
if drone.current_target then
if drone.travel then
desires[900] = {'track', drone.current_target}
else
desires[900] = {'hunt', drone.current_target}
end
end
end
-- =====================================
-- B3) TOP DESIRE -> GOAL
-- =====================================
for value,data in pairs(desires) do
if value > top_desire_value then top_desire = desire; top_desire_value = value end
end
local goal = top_desire
local new_goal = (goal~=prev_goal) or drone.goal_fail or drone.goal_success
local prev_plan = drone.plan
if new_goal then
drone.plan = false
end
-- =====================================
-- C) GOAL -> PLAN
-- =====================================
-- PURSUIT: follow and attack visible target
if goal == 'pursuit' then
-- PATROL: move around waypoints
elseif goal == 'patrol' then
-- TRACK: go to lost target prediction coords
elseif goal == 'track' then
-- INVESTIGATE: go to alert location
elseif goal == 'investigate' then
-- HUNT: search area
elseif goal == 'hunt' then
-- ENERGY: go to charge station, wait to charge, recharge
elseif goal == 'energy' then
-- TWIRL: twirl around
elseif goal == 'twirl' then
-- RANDOM: random movement when stuck
elseif goal == 'random' then
end
-- finish
drone.goal = goal
-- =====================================
-- D) ATTACK
-- =====================================
drone.attack = false
if drone.current_target then
local target = drone.targets[drone.current_target]
if target['visual'] then drone.attack = true end
end
end
--------------------------------------------
-- STEP ------------------------------------
--------------------------------------------
function drone:step()
Map:CalculateIsometricPriority(self)
local x, y = drone.body:getWorldCenter()
-- =====================================
-- A0) MODIFIERS
-- =====================================
if drone.electrical_damage > 0 then
drone.electrical_damage = drone.electrical_damage - 1
end
-- =====================================
-- PLAN -> ACTION
-- =====================================
if drone.plan == 'follow' then
-- STATION: turn to face target
elseif drone.plan == 'station' then
-- TWIRL: turn around
elseif drone.plan == 'twirl' then
end
-- =====================================
-- TRAVEL
-- =====================================
if drone.travel and not drone.travel_fail then
end
-- =====================================
-- E) AVOID
-- =====================================
if drone.travel then
end
-- =====================================
-- ATTACK
-- =====================================
if drone.attack then
end
end
The ":ai()" routine is called only on one object per-frame in a round-robin fashion to avoid the simulation slowing down. If a faster platform is detected, then more ai routines will be called every frame. The ":step()" routine is called once-per-frame for all objects.
We hope you learned *something* about game development here, if not, maybe inspired you to see that it is, in some ways, easier and more accessible than you might expect. AI can be very easy to imagine but very difficult to implement because debugging AI is a tough problem when it fails to work as you want it to.
If you want access to the full source code, let's make it happen. We're including a smash goal tier in our crowdfunding campaign to totally opensource our engine and beyond that, creative commons the entire game!
Stay tuned because it's happening soon!
We will launch a Kickstarter this month (February 2015).
Please subscribe, follow, like, and share: