最近玩了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
我閱讀了這麼一下子,這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邏輯相關的部分腳本化,我以爲在技術上不是特別特別難的事情(策劃要給力。。),只要堅持下去作,必定能帶來不少好處,讓項目良性發展。