• Don't Starve,好脚本,好欢乐


    最近玩了shank系列的开发公司新出的游戏饥荒(Don't Starve),容量很小,200MB左右,所以可以归类为小游戏。。但是游戏性却是相当的高,游戏中各物件的交互出奇的丰富和复杂,我相信该游戏90%的创意和亮点就在于这丰富的可交互性中(想想神作辐射系列吧,我大学那会玩辐射2可是玩的的不亦乐乎!)。

     

    这么棒的gameplay制作,我们需要把功劳归到开发组策划和程序的完美配合上。他们为什么可以做得这么好捏?我发现可以说是脚本系统完美应用的结果。在游戏目录datascript下,就是游戏的全部lua脚本,可阅读可修改,有Components,有StateGraph,有Behaviours tree,相当的有可参考性!

     

    首先我发现datascriptprefabs目录下是所有对象(物品,角色)的基本配置(组成动画,掉落物品,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) 

    主要值得注意的有几个地方:

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

    我阅读了这么一会儿,这3个东西就是一个游戏对象的重要组成部分。它们的脚本分别位于

    datascriptstategraphs,datascriptrains,datascriptcomponents目录下。

    StateGraph

    看这个名字我猜测这个部分应该是处理状态机的吧。二师兄的状态脚本为SGpig.lua。里面定义了一些状态如:funnyidle,death,frozen等。还可以参考datascript下的stategraph.lua文件。

    Brain

    几乎每个角色都有相应的这个脚本。这个brain对象就是对该角色behavior tree的一个封装。比如二师兄的pigbrain.lua文件。。我们从最上面的require部分可以得知,二师兄可以有这些behavior: wander, follow, runaway, panic, ChaseAndAttack, findlight等。那么我们至少可以得知,该游戏看来是将behavior tree这部分脚本化了,值得学习哟。

    behavior方面的脚本主要就是datascriptehaviourtree.lua文件和datascriptbehaviours整个目录了。前者定义了行为树类和它的各种五花八门的功能node,序列节点,条件节点,选择节点,优先级节点,并行节点,decorate节点等。后者是一些定义好的behavior。

    Component

    基于组件的entity系统。既然逻辑都脚本化了,组件模块肯定也要随之脚本化。一来提供开放接口在逻辑脚本中调用,二来方便策划扩展新的组件。

    我们看到,二师兄是由这么些组件搭建成的:eater, combat, health, trader, sleeper等等。所有组件都在datascriptComponent目录下,相当多,一共167个文件!想知道二师兄为什么战斗跑位这么风骚吗?战斗逻辑就在combat.lua这个组件里。

    从该游戏身上,我们要认识到,一个好游戏不是凭空而来的,它的每个亮点都对应了开发人员背后的思考和汗水。仅仅从捞钱出发,把玩家当SB的中国式特色(极品装备,一刷就爆;我不断的寻找,油腻的师姐在哪里!)的开发套路是不可能做出真正的好游戏的!应用脚本系统,把角色怪物配置,状态逻辑,交互逻辑等XXXX逻辑相关的部分脚本化,我觉得在技术上不是特别特别难的事情(策划要给力。。),只要坚持下去做,一定能带来很多好处,让项目良性发展。

    • 配合工具,就像星际2的一站式银河编辑器一样,让策划能进行独立设计和扩展,解救客户端程序于无尽的琐碎小事中。
    • 我觉得将逻辑代码脚本化后,使得整个客户端的代码整洁性能大幅度提升,一定不能小瞧破窗户理论带来的危害。。逻辑这个脏乱差的模块统一在脚本里管理起来就好多了。我总感觉自己有很严重的代码洁癖。
    • 脚本化后,有了对外开放的API集,提供MOD功能也方便了。
  • 相关阅读:
    【单调栈】求一个数组第一个比他小的数的位置
    【双向bfs】2017多校训练十 HDU 6171 Admiral
    【归并排序求逆序对个数】【树状数组求逆序对个数】
    【单调队列优化dp】uestc 594 我要长高
    【单调队列优化dp】HDU 3401 Trade
    【单调队列+尺取】HDU 3530 Subsequence
    linux下备份目录文件及目录
    浅谈 Python 的 with 语句
    SQLAlchemy中scoped_session
    pycharm自定义代码片段
  • 原文地址:https://www.cnblogs.com/mavaL/p/3279582.html
Copyright © 2020-2023  润新知