【问题标题】:Choosing a scripting language for game and implementing it为游戏选择脚本语言并实现它
【发布时间】:2013-01-15 06:39:15
【问题描述】:

我目前正在用 C++ 开发一款 3D 动作/RPG 游戏,在选择一种脚本语言来对游戏的 AI 进行编程时,我想要一些建议。我的团队来自模组制作背景,事实上我们仍在完成游戏哥特式模组的工作。在那个游戏中(我们也从中获得了灵感)使用了语言 DAEDALUS(由游戏制作者 Piranha Bytes 创建)。 Here is a full description of said language.

需要注意的主要一点是它使用实例多于类。游戏引擎已关闭,因此只能猜测该语言的内部实现,但我在脚本语言中寻找的主要内容(理想情况下会非常相似,但最好也比 DAEDALUS 更强大)是事实实际上存在 3 个类的“分离”——即类、实例和(实例的实例?)。

我认为如果我提供一个例子会更容易理解我想要什么。拿一个普通的NPC。首先,您定义了一个类(我理解)它反映了引擎内部的(类或结构):

CLASS C_NPC 
{
    VAR INT     id                              ;       //  absolute ID des NPCs
    VAR STRING  name            [5]             ;       //  Namen des NPC
    VAR STRING  slot                            ;           
    VAR INT     npcType                         ;           
    VAR INT     flags                           ;           
    VAR INT     attribute       [ATR_INDEX_MAX] ;           
    VAR INT     protection      [PROT_INDEX_MAX];           
    VAR INT     damage          [DAM_INDEX_MAX] ;           
    VAR INT     damagetype                      ;
    VAR INT     guild,level                     ;           

    VAR FUNC    mission         [MAX_MISSIONS]  ;           
    var INT     fight_tactic                    ;           
    VAR INT     weapon                          ;           

    VAR INT     voice                           ;           
    VAR INT     voicePitch                      ;           
    VAR INT     bodymass                        ;           

    VAR FUNC    daily_routine                   ;       //  Tagesablauf
    VAR FUNC    start_aistate                   ;       //  Zustandsgesteuert

    // **********************                   
    // Spawn                                    
    // **********************                   
    VAR STRING  spawnPoint                      ;       //  Beim Tod, wo respawnen ?
    VAR INT     spawnDelay                      ;       //  Mit Delay in (Echtzeit)-Sekunden

    // **********************                   
    // SENSES                                   
    // **********************                   
    VAR INT     senses                          ;       //  Sinne
    VAR INT     senses_range                    ;       //  Reichweite der Sinne in cm

    // **********************                   
    // Feel free to use                         
    // **********************                   
    VAR INT     aivar           [50]            ;                       
    VAR STRING  wp                              ;           

    // **********************                   
    // Experience dependant                     
    // **********************                   
    VAR INT     exp                             ;       // EXerience Points
    VAR INT     exp_next                        ;       // EXerience Points needed to advance to next level
    VAR INT     lp                              ;       // Learn Points     
};

然后,您还可以定义原型(设置一些默认值)。但是你实际上如何定义一个 NPC 是这样的:

instance BAU_900_Ricelord (Npc_Default) //Inherit from prototype Npc_Default
{
    //-------- primary data --------

    name        =   "Ryzowy Ksiaze";
    npctype     =   NPCTYPE_GUARD;  
    guild       =   GIL_BAU;      
    level       =   10;
    voice       =   12;
    id          =   900; 

    //-------- abilities --------
    attribute[ATR_STRENGTH]     = 50;
    attribute[ATR_DEXTERITY]    = 10;
    attribute[ATR_MANA_MAX]     = 0;
    attribute[ATR_MANA]         = 0;
    attribute[ATR_HITPOINTS_MAX]= 170;
    attribute[ATR_HITPOINTS]    = 170;

    //-------- visuals --------
    //              animations
    Mdl_SetVisual       (self,"HUMANS.MDS");
    Mdl_ApplyOverlayMds (self,"Humans_Arrogance.mds");
    Mdl_ApplyOverlayMds (self,"HUMANS_DZIDA.MDS");
    //          body mesh     ,bdytex,skin,head mesh     ,headtex,teethtex,ruestung 
    Mdl_SetVisualBody (self,"Hum_Body_CookSmith",1,1,"Hum_Head_FatBald",91 ,  0,-1);

    B_Scale (self); 
    Mdl_SetModelFatness(self,2);

    fight_tactic    =   FAI_HUMAN_STRONG;

    //-------- Talente --------                                    
    Npc_SetTalentSkill  (self,NPC_TALENT_1H,1); 


    //-------- inventory --------                                    

        CreateInvItems (self, ItFoRice,10);
        CreateInvItem (self, ItFoWine);
        CreateInvItems(self, ItMiNugget,40);
        EquipItem  (self, Heerscherstab); 

        EquipItem  (self, MOD_AMULETTDESREISLORDS); 

        CreateInvItem (self, ItMi_Alchemy_Moleratlubric_01);
        //CreateInvItem (self,ItKey_RB_01);

        EquipItem (self, Ring_des_Lebens);

    //-------------Daily Routine-------------
    daily_routine = Rtn_start_900;

};

FUNC VOID Rtn_start_900 ()
{
    TA_Boss         (07,00,20,00,"NC_RICELORD");
    TA_SitAround    (20,00,24,00,"NC_RICELORD_SIT");
    TA_Sleep        (24,00,07,00,"NC_RICEBUNKER_10");
};

如您所见,实例声明更像是一个构造函数,从内部设置值和调用函数。如果不是因为另一件事,这仍然不会造成太大的问题:此实例的多个副本。例如,您可以生成多个BAU_900_Ricelord,每个BAU_900_Ricelord 都会跟踪自己的 AI 状态、生命值等。

现在我认为实例在引擎内部表示为整数(甚至可能是 NPC 的 id),因为无论何时(在脚本内部)您使用表达式 BAU_900_Ricelord 它只能分配给一个 int 变量,大多数在 NPC 上运行的函数都采用该 int 值。但是,要直接修改其生命值等,您必须执行类似var C_NPC npc = GetNPC(Bau_900_Ricelord); npc.attribute[ATR_HITPOINTS] = 10; 的操作,即获取代表它的实际 C_NPC 对象。

最后回顾一下 - 是否有可能在您知道的任何脚本语言中获得这种行为,或者我是否坚持必须自己制作?或者也许有更好的方式来代表 NPC 及其行为。对我来说,编写脚本的理想语言是 C#,因为我只是喜欢这种语言,但不知何故,我怀疑尝试在 C# 中实现类似的行为是否可行或确实可行。

非常感谢

【问题讨论】:

  • C# 被编译为 CLR,因此您可能不应该将它用于“脚本”,这通常是解释的。
  • 请原谅我的命名约定,在这种情况下,我的意思是在不属于引擎代码但在引擎代码之外的任何内容中编写脚本,我实际上认为如果确实编译了脚本会更好(即DAEDALUS 也是如此)

标签: scripting


【解决方案1】:

C# 可以用作脚本语言。

除了c#,lua作为一种游戏脚本语言非常流行

您的 npc 示例可以解决如下:

  • 创建基类 npc
  • 继承 npc 类以创建具有特定特征/行为的自定义 npc
  • 创建继承类的实例

【讨论】:

  • 是的,但是我不想继承 NPC 类,因为每个 NPC 都是同一个 NPC 类的副本,它只有不同的值。我想我要做的就是说:(伪伪代码)NPC npc = new NPC(); npc.name = "Random Guy"; NpcMgr.AddNpc(npc, "RndGuy"); 然后在多个实例中生成他?嗯,也许这只是一个好的管理代码的例子,但我仍然觉得这样做是多余的(所有的npc. 并将其存储在一个变量中,然后再将其复制到管理器......)@987654323 @ 等似乎更容易......
  • 你可以在你的 npc 类中创建静态函数来返回一个预填充的 npc。例如。静态 npc CreateBadGuy() { npc baddy = new npc(); baddy.name = "坏人"; baddy.score = 100;等等等等。 }
  • 我猜,但事实是,会有数百个这样的 NPC,而且我确实需要一种方法来明确识别它们的对话脚本,例如 Dialogue RndGuy_Hello { name = "Hi, what's up?; npc_id = 1; conidtion = RndGuy_Hello_Cond; talk = RndGuy_Hello_Talk;} bool RndGuy_Hello_Cond() {return canWeTalkToRndGuy;} void RndGuy_Hello_Talk() {self.Say("Hi, what's up?"); other.Say("Get lost!");}
  • 我想我真正需要的是一种“覆盖”每个对象的构造函数的方法,难道说反射不可能吗?用lua可以实现吗?
  • 您所描述的是经典的类继承。基类 npc 有一个称为talk() 的虚拟方法。 rndguy 类继承 npc 并实现方法talk。在您的主循环中,您只有一堆 npc。您可以潜在地在所有 npc 上调用 hello 方法。只有当你碰巧有一个 rndguy 实例时, hello 方法才会真正做一些事情。
【解决方案2】:

我认为,虽然您希望该语言支持 3 个级别的类/实例,但如果您实际上只有 1 个级别,并且从那里构建所有内容,它会有所帮助。

静态语言通常有 2 个级别——“类”,在编译时定义,“实例”,在运行时从类创建。这很有意义,因为预先知道你想要多少个类是很实际的,而确切地知道有多少实例通常是不切实际的。类成为千篇一律,实例成为 cookie。

但归根结底,抛开类和实例完全不同的概念,并认为类是一种事物,它 (a) 允许您创建新事物,并且 (b) 充当这些新事物应该如何使用的中心参考行为。这些事物反过来也可以创造新事物,等等。

在 C++ 和 Java 等语言中,我们通常执行 (a) 通过分配具有一组由类构造函数定义的属性的内存块,以及 (b) 通过维护对类方法的一些引用(例如,通过v-table)。但是您可以只复制一个对象,包括其中的所有方法引用,其中之一将是该对象“实例”的构造函数。这是prototype-based programming,它提供了一个非常简单的继承模型,因为您的“类”与任何其他对象一样,只是它们提供了一种创建新对象的方法,这些对象被视为实例。

世界上最流行的脚本语言——可能是世界上最流行的语言——恰好为您提供原型继承。 Javascript 就是我所指的,它是嵌入游戏的一个很好的选择,它的好处是被开发人员广为人知并且在今天得到了非常积极的工作。网上有很多教程,但涵盖各种继承方法的教程是here。可能需要一段时间才能适应,尤其是如果您来自传统的 OO 背景,并且有明确的类/实例划分,但值得了解其中的差异并自己评估优缺点。

【讨论】:

  • 太棒了!这正是我一直在寻找的!谢谢!
【解决方案3】:

我会使用 Javascript。我自己正在开发一个游戏引擎,它实现了 Chrome 中使用的 V8 javascript 引擎。它非常容易实现,而且 Javascript 是一种非常强大的语言,完全基于原型。

【讨论】:

  • 谢谢!我永远不会有这个想法!我去看看
  • 欢迎在这里查看我的引擎的代码; github.com/qard/jsgame 目前基本上只是直接封装了OpenGL、GLU、GLUT和OpenAL。它将每个库存储在单独的全局对象中,因此不会过多地污染全局。
猜你喜欢
  • 1970-01-01
  • 2010-09-10
  • 1970-01-01
  • 2011-10-07
  • 2015-12-20
  • 2011-11-18
  • 1970-01-01
  • 2010-10-12
  • 1970-01-01
相关资源
最近更新 更多