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

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

首先我发现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() 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 = inst.components.health:SetMaxHealth(TUNING.WEREPIG_HEALTH)
inst.components.combat:SetTarget(nil)
inst.components.combat:SetRetargetFunction(, 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, . )
inst.Transform:SetFourFaced() inst.entity:AddLightWatcher()
inst.entity:AddLabel() inst.Label:SetFontSize()
inst.Label:SetFont(TALKINGFONT)
inst.Label:SetPos(,3.8,)
--inst.Label:SetColour(180/255, 50/255, 50/255)
inst.Label:Enable(false) MakeCharacterPhysics(inst, , .) inst:AddComponent("locomotor") -- locomotor must be constructed before the stategraph
inst.components.locomotor.runspeed = TUNING.PIG_RUN_SPEED --
inst.components.locomotor.walkspeed = TUNING.PIG_WALK_SPEED -- 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() ------------------------------------------
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[]
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个东西就是一个游戏对象的重要组成部分。它们的脚本分别位于

data\script\stategraphs,data\script\brains,data\script\components目录下。

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这部分脚本化了,值得学习哟。

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功能也方便了。

Don't Starve,好脚本,好欢乐的更多相关文章

  1. Gradle Android最新自动化编译脚本教程

    转自:http://blog.csdn.net/changemyself/article/details/39927381 一.前言 Gradle 是以 Groovy 语言为基础,面向Java应用为主 ...

  2. Gradle Android它自己的编译脚本教程的最新举措(提供demo源代码)

    一.前言 Gradle 是以 Groovy 语言为基础,面向Java应用为主.基于DSL(领域特定语言)语法的自己主动化构建工具. 上面这句话我认为写得非常官方,大家仅仅需知道Gradle能够用来an ...

  3. 源码:自己用Python写的iOS项目自动打包脚本

    http://www.cocoachina.com/ios/20160307/15501.html 什么?又要测试包! 做iOS开发几年了,每天除了码代码,改Bug之外,最让我烦恼的莫过于测试的妹子跑 ...

  4. 游戏案例|Service Mesh 在欢乐游戏的应用演变和实践

    作者 陈智伟,腾讯 12 级后台专家工程师,现负责欢乐游戏工作室公共后台技术研发以及团队管理工作.在微服务分布式架构以及游戏后台运维研发有丰富的经验. 前言 欢乐游戏工作室后台是分布式微服务架构,目前 ...

  5. Apache执行Python脚本

    由于经常需要到服务器上执行些命令,有些命令懒得敲,就准备写点脚本直接浏览器调用就好了,比如这样: 因为线上有现成的Apache,就直接放它里面了,当然访问安全要设置,我似乎别的随笔里写了安全问题,这里 ...

  6. SQL Server镜像自动生成脚本

    SQL Server镜像自动生成脚本 镜像的搭建非常繁琐,花了一点时间写了这个脚本,方便大家搭建镜像 执行完这个镜像脚本之后,最好在每台机器都绑定一下hosts文件,不然的话,镜像可能会不work 1 ...

  7. 分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间)

    分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间) 很多时候我们都需要计算数据库中各个表的数据量和每行记录所占用空间 这里共享一个脚本 CREATE TABLE #tab ...

  8. 探真无阻塞加载javascript脚本技术,我们会发现很多意想不到的秘密

    下面的图片是我使用firefox和chrome浏览百度首页时候记录的http请求 下面是firefox: 下面是chrome: 在浏览百度首页前我都将浏览器的缓存全部清理掉,让这个场景最接近第一次访问 ...

  9. 第一个shell脚本

    打开文本编辑器,新建一个文件,扩展名为sh(sh代表shell),扩展名并不影响脚本执行,见名知意就好. #!/bin/bash echo "Hello World !" &quo ...

随机推荐

  1. leetcode—sudoku solver

    1.题目描述 Write a program to solve a Sudoku puzzle by filling the empty cells. Empty cells are indicate ...

  2. Hadoop2.2.0 手动切换HA环境搭建

    ssh-copy-id -i hadoop5含义: 节点hadoop4上执行ssh-copy-id -i hadoop5的含义是把hadoop4上的公钥id_rsa.pub的内容追加到hadoop5的 ...

  3. iOS 获取通讯录权限的时机

    建议将获取通讯录权限的代码放到 -(void)viewDidAppear:(BOOL)animated 或 -(void)viewWillAppear:(BOOL)animated 假如放在 view ...

  4. SCAU 10893 Spiral

    10893 Spiral 时间限制:1000MS  内存限制:65535K 题型: 编程题   语言: 无限制 Description Given an odd number n, we can ar ...

  5. oracle 去掉空格

    trim(value) 去掉左右空格 ltrim(value) 去掉左空格 rtrim(value) 去掉右空格

  6. IIS7 503错误 Service Unavailable

    把相应的Application Pools的process model的Identity属性设置成“LocalSystem”就OK了

  7. Scene View Navigation

    [Scene View Navigation] Hold the right mouse button to enter Flythrough mode. This turns your mouse ...

  8. USB -- BULK_ONLY和UFI协议

    2 BULK_ONLY和UFI协议 Bulk—Only协议是USB组织针对大容量存储设备制定的一种块存储类协议,目前已经普遍应用于各种移动存储设备. USB设备分为5大类,即显示器.通信设备.音频设备 ...

  9. unigui MessageDlg方法调用例子

    procedure TfrmEmployee.btnDeleteClick(Sender: TObject);var aBool: Boolean;begin inherited; MessageDl ...

  10. TQImport3XLS.Map

    property Map: TStrings; 设置数据集字段和Excel单元格之间定义映射属性以下列方式: FieldName=CellRange 导入单独的单元格 Field1=A1 Field1 ...