Don't Starve,好腳本,好歡樂

最近玩了shank系列的開發公司新出的遊戲饑荒(Don't Starve),容量很小,200MB左右,因此能夠歸類爲小遊戲。。可是遊戲性倒是至關的高,遊戲中各物件的交互出奇的豐富和複雜,我相信該遊戲90%的創意和亮點就在於這豐富的可交互性中(想一想神做輻射系列吧,我大學那會玩輻射2但是玩的的不亦樂乎!)。node

 

 

 

 

這麼棒的gameplay製做,咱們須要把功勞歸到開發組策劃和程序的完美配合上。他們爲何能夠作得這麼好捏?我發現能夠說是腳本系統完美應用的結果。在遊戲目錄data\script下,就是遊戲的所有lua腳本,可閱讀可修改,有Components,有StateGraph,有Behaviours tree,至關的有可參考性!dom

 

 

 

首先我發現data\script\prefabs目錄下是全部對象(物品,角色)的基本配置(組成動畫,掉落物品,component組裝,behavior賦予)。好比我們看二師兄pigman的腳本文件。。編輯器

 

 

local assets =
{
    Asset("ANIM", "anim/ds_pig_basic.zip"),
    Asset("ANIM", "anim/ds_pig_actions.zip"),
    Asset("ANIM", "anim/ds_pig_attacks.zip"),
    Asset("ANIM", "anim/pig_build.zip"),
    Asset("ANIM", "anim/pigspotted_build.zip"),
    Asset("ANIM", "anim/pig_guard_build.zip"),
    Asset("ANIM", "anim/werepig_build.zip"),
    Asset("ANIM", "anim/werepig_basic.zip"),
    Asset("ANIM", "anim/werepig_actions.zip"),
    Asset("SOUND", "sound/pig.fsb"),
}

local prefabs =
{
    "meat",
    "monstermeat",
    "poop",
    "tophat",
    "strawhat",
    "pigskin",
}

local function SetWerePig(inst)
    inst:AddTag("werepig")
    inst:RemoveTag("guard")
    local brain = require "brains/werepigbrain"
    inst:SetBrain(brain)
    inst:SetStateGraph("SGwerepig")
    inst.AnimState:SetBuild("werepig_build")

    inst.components.sleeper:SetResistance(3)
    
    inst.components.combat:SetDefaultDamage(TUNING.WEREPIG_DAMAGE)
    inst.components.combat:SetAttackPeriod(TUNING.WEREPIG_ATTACK_PERIOD)
    inst.components.locomotor.runspeed = TUNING.WEREPIG_RUN_SPEED 
    inst.components.locomotor.walkspeed = TUNING.WEREPIG_WALK_SPEED 
    
    inst.components.sleeper:SetSleepTest(WerepigSleepTest)
    inst.components.sleeper:SetWakeTest(WerepigWakeTest)
    
    inst.components.lootdropper:SetLoot({"meat","meat", "pigskin"})
    inst.components.lootdropper.numrandomloot = 0
    
    inst.components.health:SetMaxHealth(TUNING.WEREPIG_HEALTH)
    inst.components.combat:SetTarget(nil)
    inst.components.combat:SetRetargetFunction(3, WerepigRetargetFn)
    inst.components.combat:SetKeepTargetFunction(WerepigKeepTargetFn)
    
    inst.components.trader:Disable()
    inst.components.follower:SetLeader(nil)
    inst.components.talker:IgnoreAll()
    inst.Label:Enable(false)
    inst.Label:SetText("")
end


local function common()
    local inst = CreateEntity()
    local trans = inst.entity:AddTransform()
    local anim = inst.entity:AddAnimState()
    local sound = inst.entity:AddSoundEmitter()
    local shadow = inst.entity:AddDynamicShadow()
    shadow:SetSize( 1.5, .75 )
    inst.Transform:SetFourFaced()

    inst.entity:AddLightWatcher()
    inst.entity:AddLabel()

    inst.Label:SetFontSize(24)
    inst.Label:SetFont(TALKINGFONT)
    inst.Label:SetPos(0,3.8,0)
    --inst.Label:SetColour(180/255, 50/255, 50/255)
    inst.Label:Enable(false)

    MakeCharacterPhysics(inst, 50, .5)
    
    inst:AddComponent("locomotor") -- locomotor must be constructed before the stategraph
    inst.components.locomotor.runspeed = TUNING.PIG_RUN_SPEED --5
    inst.components.locomotor.walkspeed = TUNING.PIG_WALK_SPEED --3

    inst:AddTag("character")
    inst:AddTag("pig")
    inst:AddTag("scarytoprey")
    anim:SetBank("pigman")
    anim:PlayAnimation("idle_loop")
    anim:Hide("hat")

    ------------------------------------------
    inst:AddComponent("eater")
    inst.components.eater:SetOmnivore()
    inst.components.eater:SetCanEatHorrible()
    inst.components.eater.strongstomach = true -- can eat monster meat!
    inst.components.eater:SetOnEatFn(OnEat)
    ------------------------------------------
    inst:AddComponent("combat")
    inst.components.combat.hiteffectsymbol = "pig_torso"

    MakeMediumBurnableCharacter(inst, "pig_torso")

    inst:AddComponent("named")
    inst.components.named.possiblenames = STRINGS.PIGNAMES
    inst.components.named:PickNewName()
    
    ------------------------------------------
    inst:AddComponent("werebeast")
    inst.components.werebeast:SetOnWereFn(SetWerePig)
    inst.components.werebeast:SetTriggerLimit(4)
    
    ------------------------------------------
    inst:AddComponent("follower")
    inst.components.follower.maxfollowtime = TUNING.PIG_LOYALTY_MAXTIME
    ------------------------------------------
    inst:AddComponent("health")

    ------------------------------------------

    inst:AddComponent("inventory")
    
    ------------------------------------------

    inst:AddComponent("lootdropper")

    ------------------------------------------

    inst:AddComponent("knownlocations")
    inst:AddComponent("talker")
    inst.components.talker.ontalk = ontalk
    

    ------------------------------------------

    inst:AddComponent("trader")
    inst.components.trader:SetAcceptTest(ShouldAcceptItem)
    inst.components.trader.onaccept = OnGetItemFromPlayer
    inst.components.trader.onrefuse = OnRefuseItem
    
    ------------------------------------------

    inst:AddComponent("sanityaura")
    inst.components.sanityaura.aurafn = CalcSanityAura


    ------------------------------------------

    inst:AddComponent("sleeper")
    
    ------------------------------------------
    MakeMediumFreezableCharacter(inst, "pig_torso")
    
    ------------------------------------------


    inst:AddComponent("inspectable")
    inst.components.inspectable.getstatus = function(inst)
        if inst:HasTag("werepig") then
            return "WEREPIG"
        elseif inst:HasTag("guard") then
            return "GUARD"
        elseif inst.components.follower.leader ~= nil then
            return "FOLLOWER"
        end
    end
    ------------------------------------------
   
    
    
    inst.OnSave = function(inst, data)
        data.build = inst.build
    end        
    
    inst.OnLoad = function(inst, data)    
        if data then
            inst.build = data.build or builds[1]
            if not inst.components.werebeast:IsInWereState() then
                inst.AnimState:SetBuild(inst.build)
            end
        end
    end           
    
    inst:ListenForEvent("attacked", OnAttacked)    
    inst:ListenForEvent("newcombattarget", OnNewTarget)
    
    return inst
end

local function normal()
    local inst = common()
    inst.build = builds[math.random(#builds)]
    inst.AnimState:SetBuild(inst.build)
    SetNormalPig(inst)
    return inst
end

local function guard()
    local inst = common()
    inst.build = guardbuilds[math.random(#guardbuilds)]
    inst.AnimState:SetBuild(inst.build)
    SetGuardPig(inst)
    return inst
end

return Prefab( "common/characters/pigman", normal, assets, prefabs),
       Prefab("common/character/pigguard", guard, assets, prefabs) 

 

 

主要值得注意的有幾個地方:ide

  1. SetStateGraph()
  2. SetBrain()
  3. AddComponent() 

我閱讀了這麼一下子,這3個東西就是一個遊戲對象的重要組成部分。它們的腳本分別位於工具

data\script\stategraphs,data\script\brains,data\script\components目錄下。oop

 

StateGraph性能

看這個名字我猜想這個部分應該是處理狀態機的吧。二師兄的狀態腳本爲SGpig.lua。裏面定義了一些狀態如:funnyidle,death,frozen等。還能夠參考data\script下的stategraph.lua文件。學習

 

Brain動畫

幾乎每一個角色都有相應的這個腳本。這個brain對象就是對該角色behavior tree的一個封裝。好比二師兄的pigbrain.lua文件。。咱們從最上面的require部分能夠得知,二師兄能夠有這些behavior: wander, follow, runaway, panic, ChaseAndAttack, findlight等。那麼咱們至少能夠得知,該遊戲看來是將behavior tree這部分腳本化了,值得學習喲。ui

behavior方面的腳本主要就是data\script\behaviourtree.lua文件和data\script\behaviours整個目錄了。前者定義了行爲樹類和它的各類五花八門的功能node,序列節點,條件節點,選擇節點,優先級節點,並行節點,decorate節點等。後者是一些定義好的behavior。

 

Component

基於組件的entity系統。既然邏輯都腳本化了,組件模塊確定也要隨之腳本化。一來提供開放接口在邏輯腳本中調用,二來方便策劃擴展新的組件。

咱們看到,二師兄是由這麼些組件搭建成的:eater, combat, health, trader, sleeper等等。全部組件都在data\script\Component目錄下,至關多,一共167個文件!想知道二師兄爲何戰鬥跑位這麼風騷嗎?戰鬥邏輯就在combat.lua這個組件裏。

 

 

從該遊戲身上,咱們要認識到,一個好遊戲不是憑空而來的,它的每一個亮點都對應了開發人員背後的思考和汗水。僅僅從撈錢出發,把玩家當SB的中國式特點(極品裝備,一刷就爆;我不斷的尋找,油膩的師姐在哪裏!)的開發套路是不可能作出真正的好遊戲的!應用腳本系統,把角色怪物配置,狀態邏輯,交互邏輯等XXXX邏輯相關的部分腳本化,我以爲在技術上不是特別特別難的事情(策劃要給力。。),只要堅持下去作,必定能帶來不少好處,讓項目良性發展。

  • 配合工具,就像星際2的一站式銀河編輯器同樣,讓策劃能進行獨立設計和擴展,解救客戶端程序於無盡的瑣碎小事中。
  • 我以爲將邏輯代碼腳本化後,使得整個客戶端的代碼整潔性能大幅度提高,必定不能小瞧破窗戶理論帶來的危害。。邏輯這個髒亂差的模塊統一在腳本里管理起來就好多了。我總感受本身有很嚴重的代碼潔癖。
  • 腳本化後,有了對外開放的API集,提供MOD功能也方便了。
相關文章
相關標籤/搜索