65.9K
CodeProject 正在变化。 阅读更多。
Home

Dungeon of Despair:一个使用 Conscript 的脚本化演示游戏

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (52投票s)

2007 年 7 月 23 日

CPOL

8分钟阅读

viewsIcon

95729

downloadIcon

1885

本文介绍了一个使用Conscript的脚本驱动演示游戏,Conscript是一个在早期文章中介绍过的脚本引擎。

Screenshot - DOD01.jpg

Screenshot - DOD11.jpg

文中插入的截图来自Lockdown [Alex Varanese, 2002, Game Scripting Mastery]。

引言

本文介绍了“绝望地牢”(DOD),一个完整的游戏应用程序,旨在演示在早期文章中介绍的Conscript脚本引擎。该游戏使用GDI+渲染图形,并作为一个无边框的Windows应用程序进行开发。

灵感

DOD的灵感来源于Lockdown,一款由Alex Varanese在其著作Game Scripting Mastery中为Xtremescript脚本引擎开发的演示游戏。Lockdown的截图已在上述截图上方嵌入。在Lockdown中,你是一名被困在某个空间站的机器人,必须找到四把颜色编码的钥匙,并将它们放入中央控制室以解锁空间站并逃生。玩家一次只能看到空间站的一个房间,每个房间通过门通往一个或多个房间。保护钥匙和控制室的是具有不同智能水平的敌方机器人,其中一些不仅会在接触时伤害玩家,甚至在某些情况下会向玩家开火。玩家可以反击,并且可以休息以恢复机器人的生命值。如果玩家失去所有生命值或空间站被解锁,游戏结束。Lockdown通过实现环境效果(如闪烁的灯光)和敌方机器人AI作为脚本来展示脚本的应用。

DOD的游戏机制与Lockdown非常相似,但背景设定灵感来源于经典的2D RPG游戏。在DOD中,玩家是一名被困在魔法地牢中的法师,只能通过激活魔法传送门才能逃脱。而传送门只有在找到四瓶元素之瓶并将其放置在传送门上才能激活。然而,玩家必须面对许多敌人,敌人的类型决定了其整体智能水平和威胁等级。玩家在接触敌人或被敌方法术击中时会失去生命值。生命值显示在屏幕左上角的彩色生命值计量表中。屏幕右上角的四个图标代表玩家收集的元素之瓶的库存。

Screenshot - DOD02.jpg

Screenshot - DOD07.jpg

脚本的应用

与Lockdown一样,DOD也是一个足够复杂的游戏应用程序,值得使用脚本,但其范围仍然局限于说明性示例。DOD主要使用Conscript脚本来实现敌方AI。没有专门用于环境效果的脚本。但是,地牢的布局及其内容是由脚本定义的复杂数据结构来确定的。游戏还演示了一个简单的脚本驱动的过场动画系统,通过进入每个房间会触发一个可选的过场动画脚本函数,以帮助玩家沉浸在游戏中。

美术

背景图是通过处理大量的(经过大量)照片生成的。角色精灵图和对话面部集合是使用在线RPG精灵图生成器生成的,可以在这里找到。

游戏说明

要运行游戏,请编译附加的解决方案并执行生成的二进制文件。执行后,游戏应用程序会显示一个组合标题/菜单屏幕,用户可以在其中选择开始游戏会话或退出应用程序。菜单项选择可以通过光标键和空格键或回车键执行。一旦开始游戏会话,用户可以使用光标键移动玩家,并通过空格键发射法术。在过场动画期间,玩家控制将暂时禁用。用户可以通过按Esc键随时退出游戏会话并返回标题屏幕。游戏结束屏幕可以通过按空格键或回车键退出。

背景

嵌入式脚本引擎已被证明是游戏制作中的宝贵工具。它们使游戏设计师能够在不干扰游戏程序员的情况下构建游戏内容,而程序员可以专注于游戏开发的其它更技术性的方面。另一个好处是,生产周转时间大大缩短,因为设计师可以在不重新构建游戏应用程序的情况下更改游戏内容。脚本引擎也是“可修改”游戏的基石,这些游戏鼓励社区通过用户创建的内容来延长游戏的生命周期。

实现

游戏应用程序遵循面向对象的(OO)设计范式,其中游戏的主要组件由相互交互的对象表示。该应用程序使用无边框的主窗口,使用GDI+渲染图形,并通过键盘事件读取玩家输入。游戏的主要状态——即标题屏幕、游戏会话、游戏结束屏幕等——是通过继承自通用GameState类的游戏状态类来实现的。应用程序维护一个当前活动状态类的引用,该类负责处理自身的行为。每个状态还可以请求转换为新状态,例如从标题屏幕开始新游戏或结束游戏会话并显示游戏结束屏幕。

地牢表示

游戏世界由一个Dungeon类表示,它由一系列相互连接的房间组成,每个房间由一个Room类表示。每个房间通过一个Doors类实例链接到一个或多个其他房间,该实例维护四个主要方向的房间链接。附加的房间属性提供了装饰性特征,以增加房间的多样性,例如房间周围的闪烁火把或一个圆形基座,通常放置在交叉房间中。其中一个房间包含元素传送门,这由Dungeon类中的一个房间引用表示。类似引用也用于起始房间以及四个元素之瓶在地牢中的放置位置。

Screenshot - DOD09.jpg

地牢数据在Conscript脚本Dungeon.cns中定义,它是一个多级关联数组,其结构类似于DungeonRoomDoors实例的层次结构。该脚本在切换到游戏会话状态时加载并执行。结果结构从脚本的变量字典中提取,并进行处理以构建地牢布局。以下是地牢布局脚本的摘录。

var s_dungeon;

function main()
{
    var rooms = {};

    // Room 00

    rooms.R00 = {};
    rooms.R00.Pedestal = true;
    rooms.R00.Doors = {};
    rooms.R00.Doors.East = "R10";
    rooms.R00.Doors.South = "R01";
    rooms.R00.TorchNE = true;
    rooms.R00.TorchSW = true;
    rooms.R00.Enemies = {};
    rooms.R00.Enemies.Enemy0 = {};
    rooms.R00.Enemies.Enemy0.Type = "Wizard";
    rooms.R00.Enemies.Enemy0.Quantity = 1;
    rooms.R00.Enemies.Enemy1 = {};
    rooms.R00.Enemies.Enemy1.Type = "Pig";
    rooms.R00.Enemies.Enemy1.Quantity = 1;
    rooms.R00.Enemies.Enemy2 = {};
    rooms.R00.Enemies.Enemy2.Type = "Lizard";
    rooms.R00.Enemies.Enemy2.Quantity = 1;

    // Room 10

    rooms.R10 = {};
    rooms.R10.Doors = {};
    rooms.R10.Doors.West = "R00";
    rooms.R10.Doors.East = "R20";
    rooms.R10.TorchNE = true;
    rooms.R10.TorchSE = true;
    rooms.R10.Enemies = {};
    rooms.R10.Enemies.Enemy0 = {};
    rooms.R10.Enemies.Enemy0.Type = "Wizard";
        rooms.R10.Enemies.Enemy0.Quantity = 1;
    rooms.R10.Enemies.Enemy1 = {};
    rooms.R10.Enemies.Enemy1.Type = "Pig";
    rooms.R10.Enemies.Enemy1.Quantity = 2;

    // more rooms here..

    // :

    // :


    // dungeon top-level array

    s_dungeon = {};

    // uncomment for infinite health

    //s_dungeon.Cheat = true;


    s_dungeon.Rooms = rooms;

    // define starting room and portal room

    s_dungeon.StartingRoom = "R34";
    s_dungeon.PortalRoom = "R30";

    // define vial rooms

    s_dungeon.RedVialRoom    = "R20";
    s_dungeon.GreenVialRoom  = "R40";
    s_dungeon.YellowVialRoom = "R44";
    s_dungeon.BlueVialRoom   = "R24";
}

实体

玩家、敌人和弹体都继承自一个通用的Entity类,该类提供诸如放置和移动之类的基本服务。玩家由一个Player类表示,其中包含玩家生命值以及拥有元素之瓶的标志。每个敌方实体由一个Enemy类表示,该类包含敌方生命值、一个引用了适当AI脚本的ScriptContext实例以及一个用于敌人移动速率的速度属性。

Screenshot - DOD04.jpg

Screenshot - DOD05.jpg

每个敌人脚本都设计为与应用程序的其余部分并行执行,因此包含一个主循环,在其中感知环境并采取适当的行动。为了驱动与脚本上下文相关联的敌人实例,注册了许多宿主函数,并将敌人实例分配为函数处理程序。以下是敌方AI宿主函数的列表。

int GetRandom(int iMin, int iMax)
返回给定范围内的随机整数
Move(int iX, int iY)
将敌人移动到给定坐标
SetDirection(int iDirection)
将敌人朝向给定方向
SetAutomaticDirection(bool bFlag)
根据速度启用/禁用自动方向
{"X":int, "Y":int} GetPosition()
获取敌人的坐标
{"X":int, "Y":int} GetPlayerPosition()
获取玩家的坐标
CastSpell()
以敌人面向的方向施放法术

过场动画

每当玩家进入一个房间时,游戏应用程序会检查是否有过场动画可用,如果有,则暂时从用户那里收回玩家的控制权,同时播放过场动画。过场动画实现为脚本文件中定义的脚本函数Cutscenes.cns。应用程序假设函数的名称格式为Cutscene_RNN,其中RNN是房间标识符,并据此定位函数。过场动画函数由一系列宿主函数调用组成,用于控制场景中的角色交互。以下脚本函数定义了中心房间R32的过场动画。这个特别的场景鼓励玩家探索北边的出口。

// centre cut-scene

function Cutscene_R32()
{
    // do scene only first time round

    if (s_canSeeLight != null) return;
    s_canSeeLight = true;

    // disable player control

    Player_SetActive(false);

    // move uo to room centre and talk

    Player_MoveTo(320, 160);
    while(Player_Moving()) yield;
    Player_Say("I can see strange lights coming beyond this doorway!");
    while(TextWindow_IsActive()) yield;

    // move closer to door and talk some more

    Player_MoveTo(320, 170);
    Player_Say("Maybe I should have a look...");

    // return control to player

    Player_SetActive(true);
}

Screenshot - DOD03.jpg

Screenshot - DOD06.jpg

为了支持脚本化的过场动画,游戏应用程序定义了以下宿主函数:

Player_SetActive(bool bFlag)
启用/禁用玩家控制
Player_SetPosition(int iX, int iY)
设置玩家的位置
Player_MoveTo(int iX, int iY)
请求玩家移动到指定位置
bool Player_Moving()
如果玩家正在移动,则返回true
Player_Say(String strMessage)
让玩家在聊天窗口中说出某句话
bool Player_HasAllVials()
如果玩家收集齐了所有四瓶元素之瓶,则返回true
Boss_SetActive(bool bFlag)
显示/隐藏敌方首领
Boss_SetPosition(int iX, int iY)
设置首领的位置
Boss_MoveTo(int iX, int iY)
请求敌方首领移动到指定位置
bool Boss_Moving()
如果敌方首领正在移动,则返回true
Boss_Say(String strMessage)
让首领在聊天窗口中说出某句话
Boss_SpawnEnemy(String strType)
生成指定类型的敌人
bool TextWindow_IsActive()
如果聊天窗口仍处于打开状态,则返回true

关注点

从概念上讲,DOD是一个非常简单的游戏,本可以轻松地在不使用嵌入式脚本的情况下开发。然而,得益于脚本,不仅可以改变地牢布局,还可以改变敌人的AI行为和游戏整体的情节线。将更多权力交给脚本系统会增加游戏的“可修改性”。在下一篇文章中,我将介绍一个完整的Conscript IDE应用程序,它将以更复杂的方式演示Conscript API的使用,例如提供调试功能和实现宿主函数模块插件机制。

Screenshot - DOD08.jpg

Screenshot - DOD10.jpg

相关文章

  • EezeeScript:用于 .NET 的简单可嵌入脚本语言
  • Conscript:一个可嵌入的、用于.NET的编译型脚本语言
  • Conscript IDE:一个为Conscript脚本语言实现的集成开发环境(IDE)。

历史

  • 2007年7月20日 - 发布第一个版本
© . All rights reserved.