Last Update: 2023-07-06
Creating Custom Creatures
This guide focuses on creating custom creatures for Don't Starve Together. Custom creatures can add new challenges, pets, allies, or unique monsters to your mod.
Overview of Creature Creation
Creating a custom creature involves several key components:
- Prefab Definition: The basic entity structure
- Visual Assets: Animations, textures, and effects
- Components: Health, combat, locomotion, etc.
- AI Behavior: Brain and behavior tree implementation
- State Graph: Animation states and transitions
- Network Code: Client-server synchronization
Basic Creature Structure
Here's a skeleton structure for a basic creature:
-- In scripts/prefabs/custom_creature.lua
local assets = {
Asset("ANIM", "anim/custom_creature.zip"),
}
local prefabs = {
"meat",
"monster_meat",
}
local function fn()
local inst = CreateEntity()
-- Basic setup
inst.entity:AddTransform()
inst.entity:AddAnimState()
inst.entity:AddSoundEmitter()
inst.entity:AddDynamicShadow()
inst.entity:AddNetwork()
-- Entity properties
MakeCharacterPhysics(inst, 50, .5)
-- Animation setup
inst.AnimState:SetBank("custom_creature")
inst.AnimState:SetBuild("custom_creature")
inst.AnimState:PlayAnimation("idle")
inst:AddTag("monster")
inst:AddTag("hostile")
inst:AddTag("custom_creature")
inst.entity:SetPristine()
if not TheWorld.ismastersim then
return inst
end
-- Components
inst:AddComponent("health")
inst.components.health:SetMaxHealth(100)
inst:AddComponent("combat")
inst.components.combat:SetDefaultDamage(20)
inst.components.combat:SetAttackPeriod(2)
inst.components.combat:SetRange(2, 3)
inst:AddComponent("lootdropper")
inst.components.lootdropper:SetLoot({"meat", "monster_meat"})
inst:AddComponent("locomotor")
inst.components.locomotor.walkspeed = 4
inst.components.locomotor.runspeed = 6
inst:AddComponent("knownlocations")
inst:AddComponent("sleeper")
inst:AddComponent("inspectable")
-- Brain & State Graph
inst:SetBrain(require("brains/custom_creature_brain"))
inst:SetStateGraph("SGcustom_creature")
return inst
end
return Prefab("custom_creature", fn, assets, prefabs)
Creating Visual Assets
Visual assets are crucial for your creature. You'll need:
Animation Files
- Create a .scml file in Spriter (or another animation tool)
- Export to Don't Starve's animation format
- Place in your mod's
anim/
directory
-- Register animations in your prefab
inst.AnimState:SetBank("custom_creature") -- Bank name
inst.AnimState:SetBuild("custom_creature") -- Build name
inst.AnimState:PlayAnimation("idle") -- Animation name
Animation States
Common animation states to implement:
idle
: Default standing animationwalk
,run
: Movement animationshit
: Reaction to being attackedattack
: Attack animationsleep
: Sleeping animationdeath
: Death animation
Adding Components
Essential components for creatures:
Health Component
inst:AddComponent("health")
inst.components.health:SetMaxHealth(150)
inst.components.health.murdersound = "dontstarve/creatures/myfx/death"
Combat Component
inst:AddComponent("combat")
inst.components.combat:SetDefaultDamage(20)
inst.components.combat:SetAttackPeriod(3)
inst.components.combat:SetRange(3)
inst.components.combat:SetRetargetFunction(3, RetargetFunction)
Locomotor Component
inst:AddComponent("locomotor")
inst.components.locomotor.walkspeed = 4
inst.components.locomotor.runspeed = 7
Loot Dropper Component
inst:AddComponent("lootdropper")
inst.components.lootdropper:AddRandomLoot("meat", 3)
inst.components.lootdropper:AddRandomLoot("monster_meat", 1)
inst.components.lootdropper.numrandomloot = 1
Creating AI with a Brain
The brain controls how your creature makes decisions:
-- In scripts/brains/custom_creature_brain.lua
require "behaviours/wander"
require "behaviours/chaseandattack"
require "behaviours/panic"
require "behaviours/runaway"
local CustomCreatureBrain = Class(Brain, function(self, inst)
Brain._ctor(self, inst)
end)
function CustomCreatureBrain:OnStart()
local root = PriorityNode(
{
-- Run away when health is low
WhileNode(function() return self.inst.components.health.takingfiredamage end, "OnFire",
Panic(self.inst)),
-- Attack nearby targets
ChaseAndAttack(self.inst, 20, 30),
-- Return to home area
WhileNode(function()
return self.inst.components.knownlocations:GetLocation("home") ~= nil
end, "HasHome",
Wander(self.inst, function()
return self.inst.components.knownlocations:GetLocation("home")
end, 20)),
-- Wander randomly
Wander(self.inst, function()
return self.inst:GetPosition()
end, 20)
}, .25)
self.bt = BT(self.inst, root)
end
return CustomCreatureBrain
Creating a State Graph
State graphs define animation states and transitions:
-- In scripts/stategraphs/SGcustom_creature.lua
require("stategraphs/commonstates")
local events = {
EventHandler("attacked", function(inst)
if not inst.components.health:IsDead() and not
inst.sg:HasStateTag("attack") then
inst.sg:GoToState("hit")
end
end),
EventHandler("death", function(inst) inst.sg:GoToState("death") end),
EventHandler("doattack", function(inst)
if not inst.components.health:IsDead() then
inst.sg:GoToState("attack")
end
end),
}
local states = {
State{
name = "idle",
tags = {"idle", "canrotate"},
onenter = function(inst)
inst.AnimState:PlayAnimation("idle", true)
end,
},
State{
name = "walk",
tags = {"moving", "canrotate"},
onenter = function(inst)
inst.AnimState:PlayAnimation("walk", true)
inst.components.locomotor:WalkForward()
end,
},
State{
name = "attack",
tags = {"attack", "busy"},
onenter = function(inst)
inst.components.locomotor:StopMoving()
inst.AnimState:PlayAnimation("attack")
end,
timeline = {
TimeEvent(15*FRAMES, function(inst)
inst.components.combat:DoAttack()
inst.SoundEmitter:PlaySound("dontstarve/creatures/myfx/attack")
end),
},
events = {
EventHandler("animover", function(inst) inst.sg:GoToState("idle") end),
},
},
State{
name = "hit",
tags = {"busy"},
onenter = function(inst)
inst.AnimState:PlayAnimation("hit")
inst.SoundEmitter:PlaySound("dontstarve/creatures/myfx/hurt")
inst.components.locomotor:StopMoving()
end,
events = {
EventHandler("animover", function(inst) inst.sg:GoToState("idle") end),
},
},
State{
name = "death",
tags = {"busy"},
onenter = function(inst)
inst.AnimState:PlayAnimation("death")
inst.SoundEmitter:PlaySound("dontstarve/creatures/myfx/death")
inst.components.locomotor:StopMoving()
RemovePhysicsColliders(inst)
end,
},
}
CommonStates.AddSleepStates(states)
CommonStates.AddFrozenStates(states)
return StateGraph("custom_creature", states, events, "idle")
Adding Sounds
Sound effects enhance your creature's presence:
-- In your prefab function
inst.SoundEmitter:PlaySound("dontstarve/creatures/myfx/idle")
-- In your stategraph
TimeEvent(15*FRAMES, function(inst)
inst.SoundEmitter:PlaySound("dontstarve/creatures/myfx/attack")
end),
Special Abilities and Effects
You can add special abilities to make your creature unique:
Area of Effect Attack
-- In combat component setup
inst.components.combat:SetAreaDamage(3, 0.8) -- 3 unit radius, 80% damage
Environmental Effects
-- Create fire or other effects when attacking
local function OnAttack(inst, target)
if math.random() < 0.3 then -- 30% chance
local fx = SpawnPrefab("firering_fx")
fx.Transform:SetPosition(target:GetPosition():Get())
end
end
inst.components.combat:SetOnHitOtherFn(OnAttack)
Spawning Minions
local function SpawnMinions(inst, count)
count = count or 2
for i = 1, count do
local angle = math.random() * 2 * PI
local offset = Vector3(math.cos(angle), 0, math.sin(angle)) * 2
local pos = inst:GetPosition() + offset
local minion = SpawnPrefab("custom_minion")
minion.Transform:SetPosition(pos:Get())
-- Link minion to parent
minion.parent = inst
end
end
Network Synchronization
For proper client-server synchronization:
-- Network variables for client/server communication
inst.num_minions = net_smallbyte(inst.GUID, "custom_creature.num_minions", "num_minions_dirty")
-- Handling in the instance setup
if not TheWorld.ismastersim then
-- Client-side code
inst:ListenForEvent("num_minions_dirty", function()
-- Update client-side visuals based on num_minions
end)
return inst
else
-- Server-side code
inst.SpawnMinions = SpawnMinions
-- Update the network variable when minions change
inst.UpdateMinionCount = function(inst)
local count = 0
-- Count current minions
for k,v in pairs(Ents) do
if v.parent == inst and v.prefab == "custom_minion" and not v:IsInLimbo() then
count = count + 1
end
end
inst.num_minions:set(count)
end
end
Making Your Creature Spawnable
Add your creature to the world through spawners:
-- In modmain.lua
AddPrefabPostInit("world", function(inst)
if inst.ismastersim then
-- Add creature to monster spawner
if inst.components.monstertracker then
inst.components.monstertracker:AddSpawn({
prefab = "custom_creature",
basechance = 0.1,
chancemult = {
day = 0.5, -- Less common during day
dusk = 1.0,
night = 2.0, -- More common at night
},
biases = {
forest = 1.0,
rocky = 0.8,
savanna = 0.2,
},
range = {min=2, max=5}, -- How many spawn at once
})
end
end
end)
Custom Creature Examples
Basic Enemy Example
local function fn()
local inst = CreateEntity()
-- Basic setup
inst.entity:AddTransform()
inst.entity:AddAnimState()
inst.entity:AddSoundEmitter()
inst.entity:AddDynamicShadow()
inst.entity:AddNetwork()
-- Physics setup
MakeCharacterPhysics(inst, 50, .5)
-- Animation setup
inst.AnimState:SetBank("spider")
inst.AnimState:SetBuild("custom_spider")
inst.AnimState:PlayAnimation("idle")
inst:AddTag("monster")
inst:AddTag("hostile")
inst:AddTag("scarytoprey")
inst.entity:SetPristine()
if not TheWorld.ismastersim then
return inst
end
-- Components
inst:AddComponent("health")
inst.components.health:SetMaxHealth(75)
inst:AddComponent("combat")
inst.components.combat:SetDefaultDamage(10)
inst.components.combat:SetAttackPeriod(3)
inst.components.combat:SetRange(2)
inst:AddComponent("lootdropper")
inst.components.lootdropper:AddRandomLoot("monstermeat", 1)
inst.components.lootdropper:AddRandomLoot("silk", 0.5)
inst.components.lootdropper.numrandomloot = 1
inst:AddComponent("locomotor")
inst.components.locomotor.walkspeed = 3
inst.components.locomotor.runspeed = 5
inst:AddComponent("sleeper")
inst:AddComponent("inspectable")
-- AI and State Graph
inst:SetBrain(require("brains/spiderbrain"))
inst:SetStateGraph("SGspider")
return inst
end
Friendly Creature Example
local function fn()
local inst = CreateEntity()
-- Basic setup
inst.entity:AddTransform()
inst.entity:AddAnimState()
inst.entity:AddSoundEmitter()
inst.entity:AddDynamicShadow()
inst.entity:AddNetwork()
-- Physics setup
MakeCharacterPhysics(inst, 10, .25)
-- Animation setup
inst.AnimState:SetBank("rabbit")
inst.AnimState:SetBuild("friendly_bunny")
inst.AnimState:PlayAnimation("idle")
inst:AddTag("animal")
inst:AddTag("prey")
inst:AddTag("companion")
inst.entity:SetPristine()
if not TheWorld.ismastersim then
return inst
end
-- Components
inst:AddComponent("health")
inst.components.health:SetMaxHealth(25)
inst:AddComponent("combat")
inst.components.combat:SetDefaultDamage(0)
inst:AddComponent("lootdropper")
inst.components.lootdropper:AddRandomLoot("smallmeat", 1)
inst:AddComponent("locomotor")
inst.components.locomotor.walkspeed = 5
inst.components.locomotor.runspeed = 8
inst:AddComponent("follower")
inst:AddComponent("inspectable")
-- Add pettable behavior
inst:AddComponent("petleash")
inst.components.petleash:SetOnSpawnFn(function(pet, owner)
pet.components.follower:SetLeader(owner)
end)
-- AI and State Graph
inst:SetBrain(require("brains/friendlybunnybrain"))
inst:SetStateGraph("SGfriendlybunny")
return inst
end
See also
- Custom AI - For more details on AI systems
- Custom Stategraphs and Animations - For animation systems
- Custom Components - For creature components
By following this guide, you can create unique creatures that add new gameplay experiences to your Don't Starve Together mod, from simple critters to complex boss monsters.