找回密码
 点一下
楼主: 疯人¢衰人

StarCraft2 地图编辑器 Galaxy教程

[复制链接]
 楼主| 发表于 2012-2-13 20:24:15 | 显示全部楼层
4.2 Galaxy下的函数
       上一节提到了触发,那么,触发是由什么构成的呢?是由基本语句直接构成的么?
       并非如此,虽然在GUI界面中你能够看到完整的触发,但是如果你打开地图的脚本文件,如代码1-1,你会发现,Galaxy脚本中,并没有直观触发的结构。你能看到的内容,除了声明就是函数。

       函数是什么?
       函数过程中的这些语句用于完成某些有意义的工作——通常是处理文本,控制输入或计算数值。通过在程序代码中引入函数名称和所需的参数,可在该程序中执行(或称调用)该函数。
       类似过程,不过函数一般都有一个返回值。它们都可在自己结构里面调用自己,称为递归。
——度娘

        度娘的解释很渣,实际上编程语言中的函数较为类似数学的函数——给定参数返回结果。只是编程语言中的函数更为灵活,例如,可以没有输入的参数,可以不返回结果,而且在执行过程中还可以实现输出结果以外的功能。
        总而言之,函数就是一段打包了的代码。这些代码往往需要重复使用,或者有着特殊的含义,我们把它包装起来,成为一个整体,就成为了函数。

        那么,定义函数的语法结构是什么?
       类型标识符    函数名形式参数表
        {
               声明部分;
               语句部分;
               return    返回值;
       }

        函数标识符就是Galaxy中的变量类型,另外还有一个特殊的无返回值函数标识符“void”。当函数的返回类型不为“void”时,必须有返回值,即有“return 返回值;”语句。
        函数名的要求与变量名基本相同。

        关于参数表,如果没有参数,留空即可,如果有多个参数,其格式如下:
        (参数1的变量类型    参数1的变量名,    参数2的变量类型    参数2的变量名,    ……

        注意多行参数以英文“,”分割。如果将参数的变量名留空,不会有语法错误,只是无法使用参数。
       参数可以使用对应变量类型的常量、变量、表达式、函数。

        返回值语句return返回的值要与函数的类型标识符对应,否则会产生语法错误。如果函数的类型为空(void)那么可以使用“Return;”语句返回,或者省略。如果类型非空,那么函数的最后一个语句必须为“return 返回值;”,否则语法错误。

        我们可以看一个实际的函数。

代码 4-1 隐藏或显示单位动作
[codes=galaxy]void libNtve_gf_ShowHideUnit (unit lp_unit, bool lp_showHide) {
    // Implementation
    if ((lp_showHide == true)) {
        UnitSetState(lp_unit, c_unitStateHidden, false);
    }
    else {
        UnitSetState(lp_unit, c_unitStateHidden, true);
    }
} [/codes]

        代码4-1就是显示或隐藏单位这个动作的实际函数。这是一个官方函数,出自NativeLib.galaxy。这个函数的类型标识符为void,代表这是一个没有返回值的函数。函数名为libNtve_gf_ShowHideUnit,一共有两个参数,分别为单位类型的lp_unit和布尔值类型的lp_showHide.

        知道了函数结构,下一步我们就需要知道怎么调用函数。
        执行函数的语法:
        函数名   参数表

        请注意:函数需要在调用前定义。否则会有语法错误。
        那么,有没有办法在调用还没有定义的函数?
        有,那就是函数的声明。不知道大家注意了没有,这节我提到的都是函数的“定义”,并且都加粗字体。实际上就是为了区别函数的“定义”和函数的“声明”。
        函数的“定义”及语法都在前面说明过,函数的声明又是什么呢?我们先来看看函数声明的语法:
        类型标识符    函数名参数表);

        注意最后有个英文“;”存在。

        函数的声明类似变量声明——声明后即可使用。如你所见,函数的声明并没有写函数体,只是说明了函数的返回类型、函数的名称及函数的参数。具体应用请看代码5-2.

代码 4-2 函数的声明与定义实例
[codes=galaxy]        void a();
        void a()
        {
                return;
        }[/codes]

        此外,有一点需要说明一下,一个有返回值的函数可以在不使用其结果的条件下使用,如:

代码 4-3 不使用返回值时函数调用
[codes=galaxy]        int a()
        {
                return 0;
}

a();[/codes]

        函数不能嵌套定义或声明,一个函数必须是全局的,但函数可以嵌套调用,甚至可以再一个语句中嵌套使用函数,如:

代码 4-4 函数的嵌套调用
[codes=galaxy]            MaxI(RandomInt(0,100),RandomInt(0,100));[/codes]

        我们在调用比较整数大小函数MaxI()中使用随机整数函数RandomInt()的返回值作为参数。

        另外还要说一下一种特殊的函数调用状态——递归。递归是指在调用一个函数的过程中,直接或间接的调用该函数本身的状态。如:

代码 4-5 递归示例
[codes=galaxy]        int a()
        {
                return a();
        }[/codes]

        当然不会有人使用代码4-5,因为那明显是一个死循环。其实递归也可以看做循环,部分递归结构可以用循环结构替代。至于选择递归还是循环,请依据自己的代码内容决定。一般来说,因为递归涉及函数调用,在效率上及代码可读性上不如循环,所以一般来说,我们使用循环较多。
        代码4-5为直接调用,间接嵌套调用需要函数的声明。如

代码 4-6 间接调用
[codes=galaxy]        void a();
        void b();
        
        void a()
        {
                b();
        }
        void b()
        {
                a();
        }[/codes]
回复

使用道具 举报

 楼主| 发表于 2012-2-13 20:29:56 | 显示全部楼层
4.3 全局变量与局域变量以及函数参数类别
        看到这节的标题,你也许会问,不是在讲函数么,怎么又讲回变量?关于变量的内容,直接写到第二章不好吗?
        全局变量和局域变量的差别在于变量的可用范围。全局变量的范围是整个脚本(static 标志的略有差别,具体内容在后面叙述),而局域变量的范围是其声明的函数。至于参数,参数也是局部变量,其有效范围为其对应的函数。

代码 4-7 全局变量和局部变量的有效范围
[codes=galaxy]int i = 0;

void a()
{
        TriggerDebugOutput(1, IntToText(i) , true);
}

void b()
{
        int i = 1;
        int j = 0;
        TriggerDebugOutput(1, IntToText(i) , true);
}

void c()
{
int j = 1;
        TriggerDebugOutput(1, IntToText(j) , true);
}
void d(int i)
{
        TriggerDebugOutput(1, IntToText(i) , true);
}

void e(int i)
{
int i = 1;
        TriggerDebugOutput(1, IntToText(i) , true);
} [/codes]

        TriggerDebugOutput()函数的作用是将文本输出到Debug窗口以及游戏。
        那么当我们调用a(),b(),c(),d(),e()。输出的结果是什么呢?有兴趣的话,你可以自己测试下。

        函数a()中的变量i使用的是全局变量,也就是0;b()中使用了局部变量,尽管它与全局变量的变量名相同,但是在函数b()中它还是1,即局部变量如果跟全局变量重名,那么有效的值是局部变量,无论读取还是赋值,都不影响全局变量;c()中使用的是变量j,这个变量的同名变量在b()中已经声明,但是c()的显示值是1,即局部变量和不同函数的局部变量同名时,相互之间不影响,他们的有效值都是当前函数;函数d()中的i是参数,函数的显示结果为输入的值,这点应该没有什么疑问吧;最后是函数e(),你猜结果会是什么呢?结果是什么也没有,因为参数不能与局域变量重名,否则编译报错。

        此外,要注意一下几点:
        与函数调用相同,全局变量可以在任意处声明,但需要在声明后才可使用。局域变量必须在函数内最先声明,然后才是执行语句。
        某函数内的局域变量或参数的变量名不能与所在函数的函数名相同。全局变量的变量名不能跟任意函数的函数名相同。否则编译时报错。

        需要特殊说明前置标志“static”。这个标志只能在全局变量或者函数前使用,作用是表明声明、定义的变量与函数的有效范围为当前文件。如果你导入脚本文件,使用include加载,那么导入文件中的全局变量和函数都可以使用,加上“static”标志的除外。
        这个标志多用于多人合作编写时,避免变量名、函数名冲突。一般情况下我们极少使用。

        介绍变量的有效范围之后,就不得不说参数了。因为一个函数是封装的,其内部局域变量无法用于函数间传递数据,那么能够在函数间传递数据的就只有全局变量和参数(指使用变量范围,关于数据传递的其他方法会在后面介绍)。全局变量在某些情况下回出现一些问题,比如重复调用有等待的延时执行函数,会因为全局变量的值的变化产生bug。所以调用数据一般都使用参数(这是必然的,不然要参数做什么)。

        那么不知道大家考虑没有考虑过这样一个问题,当某个变量作为参数输入到某个函数中时,如果在这个函数内修改这个参数的值,那么原来参数的值是否会改变呢?

代码 4-8 实参与形参
[codes=galaxy]void a(int b)
{
        b = 1;
}
int c()
{
        int d = 0;
        a(d);
        return d;
}[/codes]

        如代码4-8中,函数c的返回值是多少?0还是1?其真正的结果是0。
        我们称变量d(调用函数时的参数变量)这样的参数是实际参数(实参);称参数b(函数声明的参数)为形式参数(形参)。
        而我们调用函数时,被调用的函数的形参相当于一个新声明的局部变量。它与实际参数并无直接关系,无论你对其做任何赋值上的修改,都不会对实参有所影响。
        但是,这里还有一点例外的情况。我们在节2.1的最后提到基础变量类型和复合变量类型,其中二者的差别就是这一点例外。
代码 4-9 复合变量的情况
[codes=galaxy]void a(unit b)
{
UnitRemove(b);
b = null;
}
unit c()
{
    unit d = UnitFromId(1);
    a(d);
    return d;
}[/codes]
        类似代码4-8,函数c()的输出依旧是“UnitFromId(1);”,不过有一点,UnitRemove()函数的执行有效。即,函数c()的输出值的单位被删除了。
        这里你也许没看懂。但是实际上这并不复杂。当基础变量作为实参时,形参相当于一个实参的复制品。无论你对其值做任何修改,都无法影响到实参自身。但是如果使用复合变量做实参,对应的形参依旧是个复制品,但是复制的内容只有引用而不包括复合数据本身。所以当你对形参做部分改变(非引用改变,如赋值)将会影响到实参。
4-2.png
图 4-2 基础变量与复合变量作为参数


        以上存储关系为个人猜测,未实际验证,仅作为理解实参形参关系的辅助。
回复

使用道具 举报

 楼主| 发表于 2012-2-13 20:56:38 | 显示全部楼层
4.4 Galaxy下的触发与SC2地图的脚本结构
        节4.2中我们说到,在地图脚本文件MapScript.galaxy中,无法明显的看到触发结构,那么Galaxy下并没有GUI中触发结构么?
        非也。让我们来看代码4-10.
代码 4-10 MapScript.galaxy
[codes=galaxy]
//==================================================================================================
//
// Generated Map Script
//
// Name:   libGAWH_cf_CircumcircleCenterOfCircle
// Author: WhimsyDuke
//
//==================================================================================================
include "TriggerLibs/NativeLib"

//--------------------------------------------------------------------------------------------------
// Library Initialization
//--------------------------------------------------------------------------------------------------
void InitLibs () {
    libNtve_InitLib();
}

//--------------------------------------------------------------------------------------------------
// Trigger Variables
//--------------------------------------------------------------------------------------------------
trigger gt_Init;

//--------------------------------------------------------------------------------------------------
// Custom Script: Script
//--------------------------------------------------------------------------------------------------
point libGAWH_cf_CircumcircleCenterOfCircle(point lp_PointA, point lp_PointB, point lp_PointC)
{
    fixed [2] lv_AngleOfPerpendicularBisector;
    fixed [2] lv_b;
    fixed [6] lv_X;
    fixed [6] lv_Y;

    lv_X[2] = PointGetX(lp_PointA);
    lv_X[3] = PointGetX(lp_PointB);
    lv_X[4] = PointGetX(lp_PointC);
    lv_X[0] = (lv_X[2] + lv_X[3]) / 2;
    lv_X[1] = (lv_X[2] + lv_X[4]) / 2;
    lv_Y[2] = PointGetY(lp_PointA);
    lv_Y[3] = PointGetY(lp_PointB);
    lv_Y[4] = PointGetY(lp_PointC);
    lv_Y[0] = (lv_Y[2] + lv_Y[3]) / 2;
    lv_Y[1] = (lv_Y[2] + lv_Y[4]) / 2;

    lv_AngleOfPerpendicularBisector[0] = -(lv_X[2] - lv_X[3]) / (lv_Y[2] - lv_Y[3]);
    lv_AngleOfPerpendicularBisector[1] = -(lv_X[2] - lv_X[4]) / (lv_Y[2] - lv_Y[4]);

    lv_b[0] = lv_Y[0] - lv_AngleOfPerpendicularBisector[0] * lv_X[0];
    lv_b[1] = lv_Y[1] - lv_AngleOfPerpendicularBisector[1] * lv_X[1];

    lv_X[5] = (lv_b[0] - lv_b[1]) / (lv_AngleOfPerpendicularBisector[1] - lv_AngleOfPerpendicularBisector[0]);
    lv_Y[5] = lv_AngleOfPerpendicularBisector[0] * lv_X[5] + lv_b[0];
    MaxI(RandomInt(0,100),RandomInt(0,100));

    return Point(lv_X[5], lv_Y[5]);
}

//--------------------------------------------------------------------------------------------------
// Custom Script Initialization
//--------------------------------------------------------------------------------------------------
void InitCustomScript () {
}

//--------------------------------------------------------------------------------------------------
// Trigger: Init
//--------------------------------------------------------------------------------------------------
bool gt_Init_Func (bool testConds, bool runActions) {
    // Variable Declarations
    point lv_a;
    point lv_b;
    point lv_c;
    point lv_center;

    // Variable Initialization
    lv_a = RegionRandomPoint(RegionPlayableMap());
    lv_b = RegionRandomPoint(RegionPlayableMap());
    lv_c = RegionRandomPoint(RegionPlayableMap());
    lv_center = RegionRandomPoint(RegionPlayableMap());

    // Actions
    if (!runActions) {
        return true;
    }

    lv_center = libGAWH_cf_CircumcircleCenterOfCircle(lv_a,lv_b,lv_c);
    TriggerDebugOutput(1, (FixedToTextAdvanced(DistanceBetweenPoints(lv_center, lv_a), c_formatNumberStyleNormal, true, 2, 2)), true);
    TriggerDebugOutput(1, (FixedToTextAdvanced(DistanceBetweenPoints(lv_center, lv_b), c_formatNumberStyleNormal, true, 2, 2)), true);
    TriggerDebugOutput(1, (FixedToTextAdvanced(DistanceBetweenPoints(lv_center, lv_c), c_formatNumberStyleNormal, true, 2, 2)), true);
    return true;
}

//--------------------------------------------------------------------------------------------------
void gt_Init_Init () {
    gt_Init = TriggerCreate("gt_Init_Func");
    TriggerAddEventKeyPressed(gt_Init, c_playerAny, c_key1, true, c_keyModifierStateIgnore, c_keyModifierStateIgnore, c_keyModifierStateIgnore);
}

//--------------------------------------------------------------------------------------------------
// Trigger Initialization
//--------------------------------------------------------------------------------------------------
void InitTriggers () {
    gt_Init_Init();
}

//--------------------------------------------------------------------------------------------------
// Map Initialization
//--------------------------------------------------------------------------------------------------
void InitMap () {
    InitLibs();
    InitCustomScript();
    InitTriggers();
}
[/codes]
4-3.png
图 4-3 代码4-10的GUI截图

        果然直接看不出来……怎么办呢?看一段代码,除非已经了解需要寻找的地方,否则都是按照执行、调用顺序来看。那么。代码4-10中的脚本入口在哪呢?(脚本入口就是初始执行的函数)。
        很不错的一点,GUI生成的脚本都有详细的注释。我们看最后一个函数前的注释:
//--------------------------------------------------------------------------------------------------
// Map Initialization
//--------------------------------------------------------------------------------------------------

        明显的写着地图初始化,确实,最后一个函数InitMap()也就是整个脚本的主函数。所谓主函数,就是主要函数。在Galaxy下,指地图加载过程中首先执行的函数。其函数名为InitMap ()固定不变,任何地图的MapScript.galaxy文件中都必须有这样一个函数,否则地图无法加载。

       InitMap()函数调用了三个函数,分别为:
        1、InitLibs();这个函数是库的初始化函数,它调用了的libNtve_InitLib()函数是库NativeLib.galaxy的初始化函数。这里要多提一句,大家注意到include "TriggerLibs/NativeLib"这个语句了么?这句的作用就是预载NativeLib.galaxy作为头文件,然后我们就可以调用NativeLib.galaxy中的函数了。关于预载的详细内容,将会在后面详细讲解。

        2、InitCustomScript();这个是自定义脚本的预载。我们添加的全局自定义脚本(如图4-3中的Script)是可以添加初始化函数的,添加位置在自定义脚本编写框下方,如图4-4
4-4.png
图4-4 全局自定义脚本


        3、InitTriggers();这个函数就是触发器的初始化了。我们来看看其内容是什么:
void InitTriggers () {
    gt_Init_Init();
}

        现在我们只看到gt_Init_Init()这一个函数,这个函数所属的注释范围的内容是:
代码 4-11 触发Init
[codes=galaxy] //--------------------------------------------------------------------------------------------------
// Trigger: Init
//--------------------------------------------------------------------------------------------------
bool gt_Init_Func (bool testConds, bool runActions) {
    // Variable Declarations
    point lv_a;
    point lv_b;
    point lv_c;
    point lv_center;

    // Variable Initialization
    lv_a = RegionRandomPoint(RegionPlayableMap());
    lv_b = RegionRandomPoint(RegionPlayableMap());
    lv_c = RegionRandomPoint(RegionPlayableMap());
    lv_center = RegionRandomPoint(RegionPlayableMap());

    // Actions
    if (!runActions) {
        return true;
    }

    lv_center = libGAWH_cf_CircumcircleCenterOfCircle(lv_a,lv_b,lv_c);
    TriggerDebugOutput(1, (FixedToTextAdvanced(DistanceBetweenPoints(lv_center, lv_a), c_formatNumberStyleNormal, true, 2, 2)), true);
    TriggerDebugOutput(1, (FixedToTextAdvanced(DistanceBetweenPoints(lv_center, lv_b), c_formatNumberStyleNormal, true, 2, 2)), true);
    TriggerDebugOutput(1, (FixedToTextAdvanced(DistanceBetweenPoints(lv_center, lv_c), c_formatNumberStyleNormal, true, 2, 2)), true);
    return true;
}

//--------------------------------------------------------------------------------------------------
void gt_Init_Init () {
    gt_Init = TriggerCreate("gt_Init_Func");
    TriggerAddEventKeyPressed(gt_Init, c_playerAny, c_key1, true, c_keyModifierStateIgnore, c_keyModifierStateIgnore, c_keyModifierStateIgnore);
}
[/codes]

        看明白了么?如果你有J的经验,你能看出这里整个是一个触发,由注释可见,这个触发就是图4-1中的Init这个触发,而void gt_Init_Init ()正是这个触发的注册函数。

确实如此。所谓触发,从代码上来看,由两个函数和一个变量构成。对应名称如下。
记录触发的变量:
        trigger    gt_触发名

        触发注册函数:
        void    gt_Init_触发名()

        触发条件动作函数:
bool gt_触发名_Func (bool testConds, bool runActions)

        当地图加载时,SC2通过主函数InitMap ()调用触发初始化函数InitTriggers()。每一个有效的触发都在这里初始化——创建触发并注册事件(如果有)。当触发因某注册事件触发后,SC2就会调用其对应触发条件动作函数。

        当然,如果我们完全自主编写一个地图的全部脚本,除了脚本主函数需要固定函数名为InitMap (),不必以此顺序执行。并且也可以在一个触发注册函数注册全部触发。但是需要注意一点,作为触发条件动作函数的函数必须为bool函数,并且使用指定的(bool testConds和bool runActions)参数,否则无法编译。

        触发也可以写在全局自定义脚本中。请看代码4-10,自定义代码Script也在文件中。注释“// Custom Script: Script”之后的就是。
回复

使用道具 举报

 楼主| 发表于 2012-2-13 21:09:50 | 显示全部楼层
4.5 函数的数据存储及函数间的数据传输
        数据存储,一般来说使用变量即可,多个连续的数据,则采用数组。更为复杂的数据也可以采用多维数组存储,相比J下8192个元素上限的数组系统,Galaxy的数组无疑强大很多,但是仍然有个致命的缺陷,那就是数组无法动态声明,即我们必须在编写脚本时确定一个函数的大小。
        如我们需要将某个区域内的全部单位放入一个单位数组(不是单位组,是一个数组,变量类型为单位),那么我们要将这个数组的大小设置为多少呢?因为区域内的单位数量不确定,所以我们很有可能因超出数组范围而导致bug产生。当然你也可以使用一个很大的整数来声明数组,如“int[9999]这种”。但这会占用过多内存,导致游戏过慢。

        那么,解决方案是什么呢?
        解决方案是DataTable。DataTable是数据表(zhCN下SE的翻译,对应的数据集文件指bank)的名称。它很类似WE中的HashTable(简称HT)或GameCache(简称GC)。不过DataTable采用的Key是一个字符串,并非HT的两个整数或GC的两个字符串。相比之下,DataTable的执行效率相当的高,完全不像GC那样缓慢。
        因此,解决数组大小问题的方法就是用DataTable替代数组使用。

代码 4-12 DataTable相关函数
[codes=galaxy]
//--------------------------------------------------------------------------------------------------
// Data Table
// - Data tables provide named storage for any script type.
//   Table access may be either global or thread-local.
//--------------------------------------------------------------------------------------------------
// Types
const int c_dataTypeUnknown             = -1;
const int c_dataTypeAbilCmd             =  0;
const int c_dataTypeActor               =  1;
const int c_dataTypeActorScope          =  2;
const int c_dataTypeAIFilter            =  3;
const int c_dataTypeBank                =  4;
const int c_dataTypeBool                =  5;
const int c_dataTypeByte                =  6;
const int c_dataTypeCameraInfo          =  7;
const int c_dataTypeCinematic           =  8;
const int c_dataTypeColor               =  9;
const int c_dataTypeControl             = 10;
const int c_dataTypeConversation        = 11;
const int c_dataTypeDialog              = 12;
const int c_dataTypeDoodad              = 13;
const int c_dataTypeFixed               = 14;
const int c_dataTypeInt                 = 15;
const int c_dataTypeMarker              = 16;
const int c_dataTypeObjective           = 17;
const int c_dataTypeOrder               = 18;
const int c_dataTypePing                = 19;
const int c_dataTypePlanet              = 20;
const int c_dataTypePlayerGroup         = 21;
const int c_dataTypePoint               = 22;
const int c_dataTypePortrait            = 23;
const int c_dataTypeRegion              = 24;
const int c_dataTypeReply               = 25;
const int c_dataTypeRevealer            = 26;
const int c_dataTypeSound               = 27;
const int c_dataTypeSoundLink           = 28;
const int c_dataTypeString              = 29;
const int c_dataTypeText                = 30;
const int c_dataTypeTimer               = 31;
const int c_dataTypeTransmission        = 32;
const int c_dataTypeTransmissionSource  = 33;
const int c_dataTypeTrigger             = 34;
const int c_dataTypeUnit                = 35;
const int c_dataTypeUnitFilter          = 36;
const int c_dataTypeUnitGroup           = 37;
const int c_dataTypeUnitRef             = 38;
const int c_dataTypeWave                = 39;
const int c_dataTypeWaveInfo            = 40;
const int c_dataTypeWaveTarget          = 41;

// General functionality
native void     DataTableClear (bool global);
native int      DataTableValueCount (bool global);
native string   DataTableValueName (bool global, int index);
native bool     DataTableValueExists (bool global, string name);
native int      DataTableValueType (bool global, string name);
native void     DataTableValueRemove (bool global, string name);

// Type-specific value set/get
// - c_dataTypeAbilCmd
native void         DataTableSetAbilCmd (bool global, string name, abilcmd val);
native abilcmd      DataTableGetAbilCmd (bool global, string name);

// - c_dataTypeActor
native void         DataTableSetActor (bool global, string name, actor val);
native actor        DataTableGetActor (bool global, string name);

// - c_dataTypeActorScope
native void         DataTableSetActorScope (bool global, string name, actorscope val);
native actorscope   DataTableGetActorScope (bool global, string name);

// - c_dataTypeAIFilter
native void         DataTableSetAIFilter (bool global, string name, aifilter val);
native aifilter     DataTableGetAIFilter (bool global, string name);

// - c_dataTypeBank
native void         DataTableSetBank (bool global, string name, bank val);
native bank         DataTableGetBank (bool global, string name);

// - c_dataTypeBool
native void         DataTableSetBool (bool global, string name, bool val);
native bool         DataTableGetBool (bool global, string name);

// - c_dataTypeByte
native void         DataTableSetByte (bool global, string name, byte val);
native byte         DataTableGetByte (bool global, string name);

// - c_dataTypeCameraInfo
native void         DataTableSetCameraInfo (bool global, string name, camerainfo val);
native camerainfo   DataTableGetCameraInfo (bool global, string name);

// - c_dataTypeCinematic
native void         DataTableSetCinematic (bool global, string name, int val);
native int          DataTableGetCinematic (bool global, string name);

// - c_dataTypeColor
native void         DataTableSetColor (bool global, string name, color val);
native color        DataTableGetColor (bool global, string name);

// - c_dataTypeControl
native void         DataTableSetControl (bool global, string name, int val);
native int          DataTableGetControl (bool global, string name);

// - c_dataTypeConversation
native void         DataTableSetConversation (bool global, string name, int val);
native int          DataTableGetConversation (bool global, string name);

// - c_dataTypeDialog
native void         DataTableSetDialog (bool global, string name, int val);
native int          DataTableGetDialog (bool global, string name);

// - c_dataTypeDoodad
native void         DataTableSetDoodad (bool global, string name, doodad val);
native doodad       DataTableGetDoodad (bool global, string name);

// - c_dataTypeFixed
native void         DataTableSetFixed (bool global, string name, fixed val);
native fixed        DataTableGetFixed (bool global, string name);

// - c_dataTypeInt
native void         DataTableSetInt (bool global, string name, int val);
native int          DataTableGetInt (bool global, string name);

// - c_dataTypeMarker
native void         DataTableSetMarker (bool global, string name, marker val);
native marker       DataTableGetMarker (bool global, string name);

// - c_dataTypeObjective
native void         DataTableSetObjective (bool global, string name, int val);
native int          DataTableGetObjective (bool global, string name);

// - c_dataTypeOrder
native void         DataTableSetOrder (bool global, string name, order val);
native order        DataTableGetOrder (bool global, string name);

// - c_dataTypePing
native void         DataTableSetPing (bool global, string name, int val);
native int          DataTableGetPing (bool global, string name);

// - c_dataTypePlanet
native void         DataTableSetPlanet (bool global, string name, int val);
native int          DataTableGetPlanet (bool global, string name);

// - c_dataTypePlayerGroup
native void         DataTableSetPlayerGroup (bool global, string name, playergroup val);
native playergroup  DataTableGetPlayerGroup (bool global, string name);

// - c_dataTypePoint
native void         DataTableSetPoint (bool global, string name, point val);
native point        DataTableGetPoint (bool global, string name);

// - c_dataTypePortrait
native void         DataTableSetPortrait (bool global, string name, int val);
native int          DataTableGetPortrait (bool global, string name);

// - c_dataTypeRegion
native void         DataTableSetRegion (bool global, string name, region val);
native region       DataTableGetRegion (bool global, string name);

// - c_dataTypeReply
native void         DataTableSetReply (bool global, string name, int val);
native int          DataTableGetReply (bool global, string name);

// - c_dataTypeRevealer
native void         DataTableSetRevealer (bool global, string name, revealer val);
native revealer     DataTableGetRevealer (bool global, string name);

// - c_dataTypeSound
native void         DataTableSetSound (bool global, string name, sound val);
native sound        DataTableGetSound (bool global, string name);

// - c_dataTypeSoundLink
native void         DataTableSetSoundLink (bool global, string name, soundlink val);
native soundlink    DataTableGetSoundLink (bool global, string name);

// - c_dataTypeString
native void         DataTableSetString (bool global, string name, string val);
native string       DataTableGetString (bool global, string name);

// - c_dataTypeText
native void         DataTableSetText (bool global, string name, text val);
native text         DataTableGetText (bool global, string name);

// - c_dataTypeTimer
native void         DataTableSetTimer (bool global, string name, timer val);
native timer        DataTableGetTimer (bool global, string name);

// - c_dataTypeTransmission
native void         DataTableSetTransmission (bool global, string name, int val);
native int          DataTableGetTransmission (bool global, string name);

// - c_dataTypeTransmissionSource
native void                 DataTableSetTransmissionSource (bool global, string name, transmissionsource val);
native transmissionsource   DataTableGetTransmissionSource (bool global, string name);

// - c_dataTypeTrigger
native void         DataTableSetTrigger (bool global, string name, trigger val);
native trigger      DataTableGetTrigger (bool global, string name);

// - c_dataTypeUnit
native void         DataTableSetUnit (bool global, string name, unit val);
native unit         DataTableGetUnit (bool global, string name);

// - c_dataTypeUnitFilter
native void         DataTableSetUnitFilter (bool global, string name, unitfilter val);
native unitfilter   DataTableGetUnitFilter (bool global, string name);

// - c_dataTypeUnitGroup
native void         DataTableSetUnitGroup (bool global, string name, unitgroup val);
native unitgroup    DataTableGetUnitGroup (bool global, string name);

// - c_dataTypeUnitRef
native void         DataTableSetUnitRef (bool global, string name, unitref val);
native unitref      DataTableGetUnitRef (bool global, string name);

// - c_dataTypeWave
native void         DataTableSetWave (bool global, string name, wave val);
native wave         DataTableGetWave (bool global, string name);

// - c_dataTypeWaveInfo
native void         DataTableSetWaveInfo (bool global, string name, waveinfo val);
native waveinfo     DataTableGetWaveInfo (bool global, string name);

// - c_dataTypeWaveTarget
native void         DataTableSetWaveTarget (bool global, string name, wavetarget val);
native wavetarget   DataTableGetWaveTarget (bool global, string name);
[/codes]

        对应每一个变量类型,都有两个函数。分别为:
1、存储数据。
        native    void    DataTableSet变量类型标识符 (bool    global, string    name,    对应变量类型    对应变量类型的存储值);

2、读取数据。
native    void    DataTableGet变量类型标识符 (bool    global,    string    name);

除此之外,还有六个通用函数:


// General functionality

native void     DataTableClear (bool global);

native int      DataTableValueCount (bool global);

native string   DataTableValueName (bool global, int index);

native bool     DataTableValueExists (bool global, string
name);

native int      DataTableValueType (bool global, string
name);

native void     DataTableValueRemove (bool global, string
name);

    作用分别为:清理全部数据(分局域和全局范围);存储总值计数;根据存储的顺序序号获取对应的Key;判断对应Key的值是否存在,对应Key存储的变量类型;删除Key对应的值。

        这里的参数global是控制这个DataTable存储数据的有效范围的,类似全局变量与局域变量。

        如果我们需要使用一个数组名为I的整数数组,我们可以这样使用

代码 4-12 用DataTable实现数组
[codes=galaxy]
//Const
const string libGAWH_gv_cf_ArraySystemFirstName = "ArraySystem:";

//Function
void libGAWH_cf_ArraySystemSetInt(string lp_ArrayName, int lp_Index, int lp_ArrayData)
{
    DataTableSetInt(true, libGAWH_gv_cf_ArraySystemFirstName + lp_ArrayName + IntToString(lp_Index), lp_ArrayData);   
}

int libGAWH_cf_ArraySystemGetInt(string lp_ArrayName, int lp_Index)
{
    return DataTableGetInt(true, libGAWH_gv_cf_ArraySystemFirstName + lp_ArrayName + IntToString(lp_Index));   
}
[/codes]

        实际上用这样的函数来处理有点多此一举。直接使用DataTable函数处理也许更好些。

        数据存储可以这样解决。下面就是数据传输。

        函数间的数据传输,实际上并不难。简单来说,需要传入某函数的数据作为参数即可,需要返回到调用函数的,用返回值即可。
        那么,会有什么问题?
        问题很多,如函数的多值返回。
        所谓多值返回,就是指一个函数返回一个以上的值。而因为函数的return语句只能带一个值,而函数本身会在执行到return语句处退出,所以使用返回值我们只能返回一个值。
        当然,聪明的你会想出用复合变量来返回多个值的办法,如用point返回两个fixed。这确实是不错的想法,但是实际中应用范围太窄。
那么用结构体?不幸的是,结构体不能作为函数返回类型。另外,Galaxy也不支持数组作为参数——不是数组元素,而是一个数组本身作为参数——除非我们写一堆参数,然后把数组元素逐一作为实际参数调用。这样的话,仍然是不方便。
那么解决方案是什么?依旧是DataTable,将需要返回的值存储到DataTable中,然后返回DataTable的Key(string变量类型的参数name)即可。然后在调用函数中使用Key与数据序号即可。

       Handle值存储结构:
        Galaxy下并没有Handle这一类型变量。Handle是WAR3的脚本语言中使用的一种综合变量类型。当然,我们没必要理解它是什么,我们只要知道它的存储方式即可,在实际应用中,我们会用到。
        当然这种数据的存储结构也许有其正是名称。这里我暂时称其为Handle存储结构。

        这种存储方式说来也简单,它只是一种可循环使用Index(序号)分配方式,具体规则如下:
        1、有一个记录使用Index最大值的变量,从0开始递增,在一次游戏过程中,不减少或重置。
        2、有一个记录释放(不在使用的)Index值的数组(栈结构),此数组内Index值无序,每当释放一个Index时入栈。
        3、当从系统获取一个Index时,优先判断释放Index数组(栈)是否为空(可用元素数量是否为0)。若非空,读取数组最后一个Index值,并将数组元素数量记录减一(相当于出栈);若为空,给出记录使用Index最大值的变量的值,并将此值加一。

关于栈,这里做下说明:

基本概念
  栈,是硬件。主要作用表现为一种数据结构,是只能在某一端插入和删除的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
  栈是允许在同一端进行插入和删除操作的特殊线性表。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。栈也称为后进先出表。
  栈可以用来在函数调用的时候存储断点,做递归时要用到栈!
  以上定义是在经典计算机科学中的解释。
  在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。在i386机器中,栈顶由称为esp的寄存器进行定位。压栈的操作使得栈顶的地址减小,弹出的操作使得栈顶的地址增大。
  栈在程序的运行中有着举足轻重的作用。最重要的是栈保存了一个函数调用时所需要的维护信息,这常常称之为堆栈帧或者活动记录。堆栈帧一般包含如下几方面的信息:
  1. 函数的返回地址和参数
  2. 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。
quote1.png

栈的模型
基本算法
  1.进栈(PUSH)算法
  ①若TOP≥n时,则给出溢出信息,作出错处理(进栈前首先检查栈是否已满,满则溢出;不满则作②);
  ②置TOP=TOP+1(栈指针加1,指向进栈地址);
  ③S(TOP)=X,结束(X为新进栈的元素);
  2.退栈(POP)算法
  ①若TOP≤0,则给出下溢信息,作出错处理(退栈前先检查是否已为空栈, 空则下溢;不空则作②);
  ②X=S(TOP),(退栈后的元素赋给X):
③TOP=TOP-1,结束(栈指针减1,指向栈顶)。
——度娘

        Handle存储结构的实例代码这里也给出,大家自己参照规则分析吧,就不做解说了。
代码 4-13 Handle存储结构
[codes=galaxy]
//IndexGetSystem
//Const
const string libGAWH_gv_is_NameIndexSystemFirstName = "IndexSystem:";
const string libGAWH_gv_is_NameIndexSystemDeleteNum = ":DeleteNum:";
const string libGAWH_gv_is_NameIndexSystemDeleteIndex = ":DeleteIndex:";
const string libGAWH_gv_is_NameIndexSystemMaxNum = ":Max:";
const string libGAWH_gv_is_NameIndexSystemLastIndex = ":LastIndex:";

//Functions
int libGAWH_is_IndexSystemGetIndex(string lp_IndexName)
{
    int lv_DeleteNum = DataTableGetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemDeleteNum);
    int lv_MaxNum = 0;
    int lv_LastIndex = 0;

    if (lv_DeleteNum == 0)
    {
        lv_MaxNum = DataTableGetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemMaxNum);
        DataTableSetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemMaxNum, lv_MaxNum + 1);
        DataTableSetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemLastIndex, lv_MaxNum);
        return lv_MaxNum;
    }
    else
    {
        lv_DeleteNum =lv_DeleteNum - 1;
        DataTableSetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemDeleteNum, lv_DeleteNum);
        lv_LastIndex = DataTableGetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemDeleteIndex + IntToString(lv_DeleteNum));
        DataTableSetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemLastIndex, lv_LastIndex);
        return lv_LastIndex;
    }
}

void libGAWH_is_IndexSystemDeleteIndex(string lp_IndexName, int lp_Index)
{
    int lv_DeleteNum = DataTableGetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemDeleteNum);

    DataTableSetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemDeleteNum, lv_DeleteNum + 1);
    DataTableSetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemDeleteIndex + IntToString(lv_DeleteNum), lp_Index);
}
[/codes]

        至此,Galaxy的基础知识讲解完毕。想必大家对于整个Galaxy有了大体了解。很有可能你看到这里还是什么都不会,那么请先记住大体的语法结构,变量类别即可,从下一章开始,请打开SC2,我们准备开始实际动手了。
回复

使用道具 举报

 楼主| 发表于 2012-2-13 21:13:17 | 显示全部楼层
五.算法

        算法是什么?
        算法是描述如何在脚本中实现某种效果的过程说明。它的意义在于,它告知SC2如何执行,以此实现编写者所要达到的效果。它并不仅仅指脚本中的计算,还包括其他实现效果采用的步骤。
        当然,为实现某种效果,我们有很多种算法可以采用,那么编写脚本时采用哪种算法和如何更好的用Galaxy描述算法就是SEer的首要问题。采用的算法的优劣以及实现算法的代码的执行效率是最直观的体现一个SEer水平的标志。而这种能力是需要在长期编写Galaxy脚本或使用其他编程语言的过程中培养的。
        因此从某种角度来说,对于一个使用过其他编程语言的SEer来说,Galaxy是较为简单的,如果你仅仅使用过Jass,那么,也许你仍然需要学习很多内容。
        下面是度娘对于算法的解说。
        算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度时间复杂度来衡量。
  一个算法应该具有以下七个重要的特征:
        算法可以使用自然语言、伪代码、流程图等多种不同的方法来描述。
1、有穷性(Finiteness)
        算法的有穷性是指算法必须能在执行有限个步骤之后终止
2、确切性(Definiteness)
        算法的每一步骤必须有确切的定义;
3、输入项(Input)
        一个算法有0个或多个输入,以刻画运算对象的初始情况,所谓0个输入是指算法本身定出了初始条件;
4、输出项(Output)
        一个算法有一个或多个输出,以反映对输入数据加工后的结果。没有输出的算法是毫无意义的;
5、可行性(Effectiveness)
        算法中执行的任何计算步都是可以被分解为基本的可执行的操作步,即每个计算步都可以在有限时间内完成(也称之为有效性);
6、 高效性(High efficiency)
        执行速度快,占用资源少;
7、 健壮性(Robustness)
        对数据响应正确。
——度娘

        我们使用Galaxy或者GUI,不可避免的要和算法打交道。然而学习算法更多的是靠自己体悟和积淀。所以大家在看这篇教程时,不仅要记住语法格式,更要逐渐的掌握如何选择、编写、实现自己需要的算法。将算法与你需要制作的效果联系起来。
        授人以鱼不如授人以渔,然而授人以渔比授人以鱼的难度大很多。我会尽可能的将代码编写的思路写出来,作为参考。也许我的思路不适合每一个人,希望能起到抛砖引玉的效果。
回复

使用道具 举报

 楼主| 发表于 2012-2-13 21:16:51 | 显示全部楼层
5.1 通过一个程序来讲解算法
代码 5-1 两条射线构成的角度
[codes=galaxy]
fixed libGAWH_cf_AngleRemainderDeal(fixed lp_AngleEnd, fixed lp_AngleStart)
{
    fixed lv_AngleRemainder = lp_AngleEnd - lp_AngleStart;
    if (lv_AngleRemainder > 180)
    {
        return 360 - lv_AngleRemainder;
    }

    if (lv_AngleRemainder < -180)         
    {
        return lv_AngleRemainder + 360;
    }

    return lv_AngleRemainder;
}
[/codes]

       代码5-1的内容是通过两个射线的方向,取得两条射线构成的角度(小于180的角度)。请仔细思考下整个程序,你会发现,它其实很简单。

       直接计算两条射线的角度差。
fixed lv_AngleRemainder = lp_AngleEnd - lp_AngleStart;
//(注,这里隐含两个参数的取值范围为(-180,180])

       对计算出的差值进行整理。

代码 5-2 整理计算结果
[codes=galaxy]
if (lv_AngleRemainder > 180)
    {
        return 360 - lv_AngleRemainder;
    }

    if (lv_AngleRemainder < -180)         
    {
        return lv_AngleRemainder + 360;
    }
[/codes]

        那么这个算法是怎么考虑出来的?这并不难,请大家跟随我的思路,考虑我们应该采用怎样的计算才能获得需要的结果。

        首先,我们来考虑我们已知的参数和需要的结果。

       我们现在知道的参数是两条射线的角度,lp_AngleEnd、lp_AngleStart。这两个值的取值范围是(-180,180],因为一般来说,这个角度值是通过反三角函数Atan获取的,所以我们没必要做验证参数是否符合要求。
       直接计算角度差lp_AngleEnd-lp_AngleStart,得到的值范围是(-360,360)。但是这个范围明显不符合我们的需求,其主要原因是对应同一个结果,会有两个不同计算值。如-181°与179°在直角坐标系中完全是同一个角度。于是我们需要一个算法将结果范围整理到(-180,180]。
       当lp_AngleEnd-lp_AngleStart的计算结果在范围(-180,180]时我们无需处理。需要处理的范围就分成了两段。
       当lp_AngleEnd-lp_AngleStart的计算结果在范围(-360,-180]时,我们应该怎么处理呢?

5-1.png
图 5-1

       如图5-1,-223°可以换为137°。由此可知,若lp_AngleEnd-lp_AngleStar的值为X,那么实际结果的计算方法为360°+X。

当lp_AngleEnd-lp_AngleStart的计算结果在范围(180,360)时,我们应该怎么处理呢?
5-2.png
图 5-2

        如图2-2,221°可以换为-139°。由此可知,若lp_AngleEnd-lp_AngleStar的值为X,那么实际结果的计算方法为X-360°。

       依照以上内容,将想好的算法用Galaxy描述即可。
回复

使用道具 举报

 楼主| 发表于 2012-2-13 21:19:34 | 显示全部楼层
5.2结构化的算法
        算法不是简单就能说明的内容。并且不同的编程语言应用的算法基本上相同的。大家可以看一些其他编程语言的算法书,了解一些基本算法,如冒泡排序等等。

       算法是实现你所需要效果的手段,我们可以采取的手段并不唯一。当我们想不出实现效果的算法时,也可将思路逆转,寻找能够曲线救国的方法。

        要知道算法并没有“最好”的,只有“最符合要求”的,我们很多时候都要在效果和效率中抉择,这种抉择的依据是算法本身的复杂度,执行效率等等,每个人的选择方式不同,编程习惯不同,选择的算法也不同。

        但是不论怎样的算法,都是由算法有三种基本结构组成的,算法的三种结构如下:
        ⑴顺序结构。
顺序结构的程序设计是最简单的,只要按照解决问题的顺序写出相应的语句就行,它的执行顺序是自上而下,依次执行。
例如;a = 3,b = 5,现交换a,b的值,这个问题就好像交换两个杯子水,这当然要用到第三个杯子,假如第三个杯子是c,那么正确的程序为: c = a; a = b; b = c; 执行结果是a = 5,b = c = 3如果改变其顺序,写成:a = b; c = a; b = c; 则执行结果就变成a = b = c = 5,不能达到预期的目的,初学者最容易犯这种错误。 顺序结构可以独立使用构成一个简单的完整程序,常见的输入、计算,输出三部曲的程序就是顺序结构,例如计算圆的面积,其程序的语句顺序就是输入圆的半径r,计算s = 3.14159*r*r,输出圆的面积s。不过大多数情况下顺序结构都是作为程序的一部分,与其它结构一起构成一个复杂的程序,例如分支结构中的复合语句、循环结构中的循环体等。
——度娘
        ⑵选择结构。

选择程序结构用于判断给定的条件,根据判断的结果判断某些条件,根据判断的结果来控制程序的流程。使用选择结构语句时,要用逻辑表达式来描述条件 。

  当然各种程序对选择结构语法不一样。例如:
  C语言的选择结构为:
  if(条件表达式1)
  {语句块1;}
  else if(条件表达式2)
  {语句块2;}
  else if(条件表达式3)
  {语句块3;}
  .
  .
  .
  else
  {语句块n;}
  VB 中的选择结构为:
  If(条件表达式1) then
  语句块1
  ElseIf(条件表达式2) then
  语句块2
  .
  .
  .
  Else
  语句块n
  End If
  C语言中switch 语句为:
  switch(变量或表达式)
  {
  case 常量表达式1:
  语句块1;
  break;
  case 常量表达式2:
  语句块2;
  break;
  …….
  case 常量表达式n:
  语句块n;
  break;
  default: 语句块n+1
  break;
  }
  VB语言中Select Case(相当于C的switch)语句为:
  Select Case 变量或表达式
  Case 表达式列表1
  语句块1
  Case 常量表达式2
  语句块2
  …….
  Case 常量表达式n:
  语句块n
  [Case Else
  语句块n+1]
  End Select
  }
  条件表达式可以分为两类:
  关系表达式和逻辑表达式
  条件表达式的取值为逻辑值(也称布尔值):
  真(True) 和假(False)
C用非0表示真,0表示假
——度娘
⑶        循环结构。

循环结构可以减少源程序重复书写的工作量,用来描述重复执行某段算法的问题,这是程序设计中最能发挥计算机特长的程序结构 。  
结构简介
  循环结构可以看成是一个条件判断语句和一个向回转向语句的组合。另外,循环结构的三个要素:循环变量、循环体和循环终止条件. ,循环结构在程序框图中是利用判断框来表示,判断框内写上条件,两个出口分别对应着条件成立和条件不成立时所执行的不同指令,其中一个要指向循环体,然后再从循环体回到判断框的入口处.
——度娘

       一个算法完全可以依据这三种基本结构分解开来,反过来,也就是说我们完全可以通过选择三种基本结构来构成我们需要的算法。节3.3―3.5就是依次对应顺序、选择、循环结构算法的语法结构。

        在实际应用中,我们首先要决定的是使用否循环结构。因为循环结构涉及的方面较广,临时添加需要做大量修改。如果我们确定需要使用循环,那么我们优先考虑的是我们需要一个什么样的循环,采用什么样的循环条件来控制循环。

        选择结构往往是在编写中添加,当我们需要根据不同的选择处理问题时,我们就添加选择结构的算法。

        其他内容只要按照顺结构继续写下去即可。

        如果我们实现的内容复杂,我们需要将其肢解,分步分块处理,特别是一些常用的功能,可以单独编写函数,这样我们能够更加明确的选择算法,编写出的算法也容易理解(可读性好)。
回复

使用道具 举报

 楼主| 发表于 2012-2-13 21:30:14 | 显示全部楼层
5.3 计算相关算法讲解
        一张地图中Galaxy脚本只做两件事:计算和对象处理。这两者之间的联系也有两种:将计算结果赋值给对象的某个属性和依据计算结果不同选择如何处理对象。
        一般来说,越复杂的代码,需要的计算越多,特别是做地图上的位置,轨迹有关处理时,需要复杂的解析几何运算,所以学会如何设计和应用运算相关的算法,是十分必要的。

        既然是计算相关,那么首先你需要有足够的数学水平来推导公式。关于数学水平的相关问题,不在此教程范围之内,如果你的数学水平不足以完成你需要的算法,那么想办法降低条件或修改条件。编程也需要逆转思维,放开思维的。

        这里,我以已知平面内三角形三顶点,求外接圆圆心为例,讲解如何实现运算相关的算法。
看到这个实例,你也许会问,这又不是做数学题,编写这个做什么?其实这是实际问题的抽象。例如如果我们做一个技能,需要确定与某三个单位距离相等的点,在那里创建效果之类的情况时,就需要设计这样一个函数了。

        首先,这是一个解析几何的问题,我们需要画图并计算,这里给出示意图,具体计算过程就不一一写出。
5-3.png
图 5-3 平面内三角形三顶点,求外接圆圆心示意图


        已知点为A、B、C,所求外接圆圆心为O,OD与OE分别为三角形ABC两边AB与AC的垂直平分线,外切圆圆心就是其交点。我们的算法也是依照通过求取OD与OE的直线方程,计算交点来编写的。

        因为Galaxy中point变量的两个坐标值都是fixed类型的,所以我们可以直接声明两个fixed数组来存储对应点的X轴、Y轴坐标。另外,因为我们确定O点坐标需要获取OD与OE的直线方程“Y = K × X + b”,所以还需要两个数组变量,分别存储K(采用变量名为AngleOfPerpendicularBisector)和b。

代码 5-3 所需变量的声明
[codes=galaxy]
    fixed [2] lv_AngleOfPerpendicularBisector;
fixed [2] lv_b;
fixed [6] lv_X;
fixed [6] lv_Y;
[/codes]

        声明完变量后,我们就来写运算过程。(很多情况下,我们在编写过程中逐步添加修改变量,所以并非一次性的全部将变量声明完成。)
        对于直线OD,其直线方程我们可以通过点D坐标和OD直线方向来获取。D点坐标很容易求:X0 = (X2 + X3) / 2,Y0 = (Y2 + Y3) / 2。
        因为OD垂直AB,所以其直线方程中的K0与AB的直线方程中的K的关系为K0 = - (1 / K)。而K可以通过(Y2 – Y3) / (X2 – X3)来求。直线方程中的b通过带入D点坐标就可以求,b = Y0 – K0× X0。
        至于OE也是如此。
        综合以上内容我们可以编写出运算过程:

代码 5-4 运算过程
[codes=galaxy]
    lv_X[2] = PointGetX(lp_PointA);
    lv_X[3] = PointGetX(lp_PointB);
    lv_X[4] = PointGetX(lp_PointC);
    lv_X[0] = (lv_X[2] + lv_X[3]) / 2;
    lv_X[1] = (lv_X[2] + lv_X[4]) / 2;
    lv_Y[2] = PointGetY(lp_PointA);
    lv_Y[3] = PointGetY(lp_PointB);
    lv_Y[4] = PointGetY(lp_PointC);
    lv_Y[0] = (lv_Y[2] + lv_Y[3]) / 2;
    lv_Y[1] = (lv_Y[2] + lv_Y[4]) / 2;

    lv_AngleOfPerpendicularBisector[0] = -(lv_X[2] - lv_X[3]) / (lv_Y[2] - lv_Y[3]);
lv_AngleOfPerpendicularBisector[1] = -(lv_X[2] - lv_X[4])  / (lv_Y[2] - lv_Y[4]);
lv_b[0] = lv_Y[0] - lv_AngleOfPerpendicularBisector[0] * lv_X[0];
lv_b[1] = lv_Y[1] - lv_AngleOfPerpendicularBisector[1] * lv_X[1];
[/codes]

其中因为输入的是lp_PointA、lp_PointB、lp_PointC三个点,所以为了减少运算消耗,我们将其赋值给fixed类型变量X和Y(数组元素)。
最后就是解这个二元一次方程即可。分别得到O点坐标的X5和Y5。

代码 5-5 解方程
[codes=galaxy]
    lv_X[5] = (lv_b[0] - lv_b[1]) / (lv_AngleOfPerpendicularBisector[1] - lv_AngleOfPerpendicularBisector[0]);
lv_Y[5] = lv_AngleOfPerpendicularBisector[0] * lv_X[5] + lv_b[0];
[/codes]

这样,计算步骤就写完了,不过因为我们的已知条件是三个point类型的点,返回也是。最终函数如代码5-6。

代码 5-6平面内三角形三顶点,求外接圆圆心函数
[codes=galaxy]
point libGAWH_cf_CircumcircleCenterOfCircle(point lp_PointA, point lp_PointB, point lp_PointC)
{
    fixed [2] lv_AngleOfPerpendicularBisector;
    fixed [2] lv_b;
    fixed [6] lv_X;
    fixed [6] lv_Y;

    lv_X[2] = PointGetX(lp_PointA);
    lv_X[3] = PointGetX(lp_PointB);
    lv_X[4] = PointGetX(lp_PointC);
    lv_X[0] = (lv_X[2] + lv_X[3]) / 2;
    lv_X[1] = (lv_X[2] + lv_X[4]) / 2;
    lv_Y[2] = PointGetY(lp_PointA);
    lv_Y[3] = PointGetY(lp_PointB);
    lv_Y[4] = PointGetY(lp_PointC);
    lv_Y[0] = (lv_Y[2] + lv_Y[3]) / 2;
    lv_Y[1] = (lv_Y[2] + lv_Y[4]) / 2;

    lv_AngleOfPerpendicularBisector[0] = -(lv_X[2] - lv_X[3]) / (lv_Y[2] - lv_Y[3]);
    lv_AngleOfPerpendicularBisector[1] = -(lv_X[2] - lv_X[4]) / (lv_Y[2] - lv_Y[4]);

    lv_b[0] = lv_Y[0] - lv_AngleOfPerpendicularBisector[0] * lv_X[0];
    lv_b[1] = lv_Y[1] - lv_AngleOfPerpendicularBisector[1] * lv_X[1];

    lv_X[5] = (lv_b[0] - lv_b[1]) / (lv_AngleOfPerpendicularBisector[1] - lv_AngleOfPerpendicularBisector[0]);
    lv_Y[5] = lv_AngleOfPerpendicularBisector[0] * lv_X[5] + lv_b[0];

    return Point(lv_X[5], lv_Y[5]);
}
[/codes]

        注意到了么,代码5-6其实就是节4.4中代码4-10 MapScript.galaxy中的自定义脚本。对应的实际地图也就是测试这个函数的地图。测试方法很简单:随机生成3个点,输入到函数中去,求出外接圆圆心,然后分别计算这三点的距离,显示出来即可。
        注意因为运算误差的关系,实际结果会有微小差别。

        经过验证后,这个函数没有问题,那么我们的函数就编写好了。
回复

使用道具 举报

 楼主| 发表于 2012-2-13 21:31:01 | 显示全部楼层
5.4 面向对象算法讲解
        这一节的范畴很大,就不举实例讲解了。需要做什么,大家在GUI下寻找对应动作即可。然后按ctrl + F11,看对应的函数。这是很方便的寻找函数的方法。否则你也可以在natives.galaxy中寻找。

        natives.galaxy是需要看的,还有其他一些官方基础脚本文件。这些文件的位置在Mods\\Core.SC2Mod\\Base.SC2Data\\TriggerLibs或Mods\\Core.SC2Mod\\Base.SC2Data\\TriggerLibs\\GameData文件夹中,当然最新版本文件会在Versions文件夹下的最新更新补丁mpq文件中。
       (你需要使用MPQ Editor(下载地址:http://www.zezula.net/en/mpq/download.html)来打开MPQ文件。)

        我们当然也可以通过打开官方战役地图等来学习如何编写面向对象的算法。
        关于面向对象的算法,大家还是多用,慢慢积攒经验吧。
回复

使用道具 举报

 楼主| 发表于 2012-2-13 21:37:06 | 显示全部楼层
5.5 综合应用
        现在,我们用一个实例来讲解整体应用——当你面对一个有着种种要求的系统,改如何着手编写脚本。因为需要完成的内容不同,具体思考方式也不同,编程习惯也因人而异,这里仅以我自己完成的一个系统来抛砖引玉。

        我所要完成的内容是,任意多边形区域生成。有个内容可以看如下帖子:
        http://bbs.islga.org/read-htm-tid-346762.html
        http://bbs.islga.org/read-htm-tid-348761.html
        http://bbs.islga.org/read-htm-tid-354509.html
        http://bbs.islga.org/read-htm-tid-373495.html

        注:此教程内函数不同于我已经发布那个版本,算是一个升级版本吧。在效率上还是已发布版本较高,但是需要预先设定最大顶点数。此外,因采用简化运算,此系统需要保证输入的顶点次序为边的连接次序,且任意两边不能有顶点外交点。

        遇到这样一个需要编写的内容,首先要考虑的是输入输出的问题。按照之前的描述,我们可以确定,我们的输入是一系列点,总数未知;输出为一个复合区域。此外,我们要求这个系统是局域化的(即,同时多次调用不会互相影响)。
        从输入条件来看,因为是总数未知的点,我们不能使用参数来输入点,参考节4.5中所述,我们可以采用DataTable来存储这一系列点。
从输出条件来看,我们的主要函数的类型需要为region。
最后,因为此系统为局域化系统,我们需要将所有数据局域化,也就是将DataTable存储数据的Key在不同调用中区分开。这里我们可以使用节4.5中提到的Handle存储结构。

考虑到此,我们来依次编写整个系统。
        1、我们需要一个Index分配函数,用来获取多边形区域的序号,并有一个与之对应的销毁函数。暂定内容如下:

代码 5-7 Index分配函数-1
[codes=galaxy]
int libGAWH_prc_PolygonInitialization()
{     string lv_PolygonName = libGAWH_gv_prc_PolygonRegion + IntToString(lv_PolygonIndex);
     DataTableSetInt(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex, 0);     
return lv_PolygonIndex;
}  
void libGAWH_prc_PolygonRemove(int lp_PolygonIndex)
{
    libGAWH_is_IndexSystemDeleteIndex(libGAWH_gv_prc_PolygonIndexName, lp_PolygonIndex);
}
[/codes]

        其中存储在Key为“lv_PolygonName + libGAWH_gv_prc_PolygonVertex”的是此多边形的总点数,这里是将其数据清零。

        2、因为不能用参数依次输入顶点,我们还需要一个顶点输入函数:

代码 5-8 顶点输入函数-1
[codes=galaxy]
void libGAWH_prc_PolygonRegionAddVertex(int lp_PolygonIndex, point lp_Vertex)
{
    string lv_PolygonName = libGAWH_gv_prc_PolygonRegion + IntToString(lp_PolygonIndex);
    int lv_MaxVertexIndex = DataTableGetInt(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex);
    fixed lv_X = PointGetX(lp_Vertex);
    fixed lv_Y = PointGetY(lp_Vertex);
    DataTableSetPoint(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_MaxVertexIndex), lp_Vertex);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_MaxVertexIndex) + libGAWH_gv_prc_PolygonVertexX, lv_X);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_MaxVertexIndex) + libGAWH_gv_prc_PolygonVertexY, lv_Y);
}
[/codes]

        这里我们分别存储了点和对应的X、Y轴坐标。其中点为备用。

        3、下面就是这个多边形创建函数了。在编写函数之前,我们需要先想好如何构成这个多边形区域。前面提到的几个主题中,我提到了相关算法,这里再解说一下:
        这种算法的理论依据是拓补几何,相关数学原理请自己问度娘。这里直接给出答案:以平面内闭合曲线内部的任意一点为端点,向任意方向做射线,这条射线与闭合曲线的交点数必然为奇数。而多边形的边可以看做是某种闭合曲线,这样,我们可以很容易的区分多边形内部和外部的点。
另外,我们输出结果是一系列的区域集合,一般来说我们都是采用矩形区域。那么我们完全可以将整个多边形看作一幅图片,而这些多边形就是其中的像素。因此,我们可以知道,我们生成的区域与对应多边形的相似度完全由我们作为像素的矩形区域的大小决定,因而我们的多边形创建函数中,除了多边形序号这个参数外,还应该有一个构成区域大小的参数。那么我们可以确定,这个函数的整体框架。

代码 5-9 多边形区域生成函数框架
[codes=galaxy]
region libGAWH_prc_PolygonRegionCreate(int lp_PolygonIndex, fixed lp_RegionPrecision)
{     
region lv_PolygonRegion = RegionEmpty();
return lv_PolygonRegion;
}
[/codes]

        4、这些像素区域的位置是由一个个点确定的,我们采用区域中心来确定,这样,我们就把问题转换成为在多边形区域内找到一系列点的问题了。这些点不能在整个地图区域寻找,这样无效运算太大,因此我们需要用一个区域来确定多边形的范围。因此我们修改顶点输入函数为:

代码 5-10 顶点输入函数-2
[codes=galaxy]
void libGAWH_prc_PolygonRegionAddVertex(int lp_PolygonIndex, point lp_Vertex)
{
    string lv_PolygonName = libGAWH_gv_prc_PolygonRegion + IntToString(lp_PolygonIndex);
    int lv_MaxVertexIndex = DataTableGetInt(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex);
    fixed lv_X = PointGetX(lp_Vertex);
    fixed lv_Y = PointGetY(lp_Vertex);
    fixed lv_MaxX = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMax + libGAWH_gv_prc_PolygonVertexX);
    fixed lv_MinX = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMin + libGAWH_gv_prc_PolygonVertexX);
    fixed lv_MaxY = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMax + libGAWH_gv_prc_PolygonVertexY);
    fixed lv_MinY = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMin + libGAWH_gv_prc_PolygonVertexY);

    DataTableSetPoint(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_MaxVertexIndex), lp_Vertex);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_MaxVertexIndex) + libGAWH_gv_prc_PolygonVertexX, lv_X);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_MaxVertexIndex) + libGAWH_gv_prc_PolygonVertexY, lv_Y);

    if (lv_X > lv_MaxX)
    {
        DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMax + libGAWH_gv_prc_PolygonVertexX, lv_X);
    }
    if (lv_X < lv_MinX)
    {
        DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMin + libGAWH_gv_prc_PolygonVertexX, lv_X);
    }
    if (lv_Y > lv_MaxY)
    {
        DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMax + libGAWH_gv_prc_PolygonVertexY, lv_Y);
    }
    if (lv_Y < lv_MinY)
    {
        DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMin + libGAWH_gv_prc_PolygonVertexY, lv_Y);
    }

DataTableSetInt(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex, lv_MaxVertexIndex + 1);
}
[/codes]

        算法很简单,每次添加顶点时比较当前最大最小X、Y轴坐标即可。
        除此函数外,Index分配函数也要做些修改,以保证重复使用相同Index时不会产生bug。

代码 5-11 Index分配函数-2
[codes=galaxy]
int libGAWH_prc_PolygonInitialization()
{
    int lv_PolygonIndex = libGAWH_is_IndexSystemGetIndex(libGAWH_gv_prc_PolygonIndexName);
    string lv_PolygonName = libGAWH_gv_prc_PolygonRegion + IntToString(lv_PolygonIndex);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMin + libGAWH_gv_prc_PolygonVertexX, 99999);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMin + libGAWH_gv_prc_PolygonVertexY, 99999);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMax + libGAWH_gv_prc_PolygonVertexX, -1);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMax + libGAWH_gv_prc_PolygonVertexY, -1);
    DataTableSetInt(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex, 0);
    return lv_PolygonIndex;
}
[/codes]

        5、如果给定顶点小于3,那么我们返回的区域为空——请注意这一点。

        6、系统编写到这里,开始进入核心部分。现在的首要问题是,我们如何确定这条射线的方向,才能减少我们的计算量,然后,如何用这条射线来寻找多边形内部的点。
        关于射线方向,一般来说,采用垂直坐标轴的方向是最便于计算的。我们可以很方便的获取这条直线上的点。那么怎么利用这个射线呢?选一个点,然后用射线验证么?
        当然不能如此,这样的计算量太大。我们逆转思维,不通过射线验证,而是先确定一条直线(方向差180°的两条射线),然后在直线上选取复合条件的点呢?
        我们来仔细考虑一下这个方法。我们首先做一条垂直X轴方向的直线,如果直线通过多边形,那么这个直线会与多边形的部分边相交。通过运算可以获得这些交点的坐标,然后以这些点的Y轴坐标排序,获得一系列点P0、P1、P2……。那么,P0和P1点之间的点就在多边形内部,P1和P2之间的点在多边形外部。如图5-2。依照这种方法,依次画出间隔为参数lp_RegionPrecision(像素区域边长)的直线,我们就能确定每一个像素区域的位置。
5-4.png
图 5-4 直线与多边形部分边交点


        7、算法的整体是一个循环,循环内容是没一条垂直于X轴直线,循环初始值是多边形所在区域的X坐标最小值,循环结束值是多边形所在区域的X坐标最大值,循环增量是参数lp_RegionPrecision(像素区域边长)。
我们据此此写出主体循环结构:

代码 5-12 之前描述部分内容及主体循环结构
[codes=galaxy]
    string lv_PolygonName = libGAWH_gv_prc_PolygonRegion + IntToString(lp_PolygonIndex);
    int lv_MaxVertexIndex = DataTableGetInt(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex);
    fixed [4] lv_SideValue;
    fixed [2] lv_FirstPoint;
    fixed lv_HalfRegionPrecision = lp_RegionPrecision / 2;  
    //Vertex number Check
    if (lv_MaxVertexIndex < 3)
    {
        return lv_PolygonRegion;
    }

    lv_SideValue[0] = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMax + libGAWH_gv_prc_PolygonVertexX);
    lv_SideValue[1] = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMin + libGAWH_gv_prc_PolygonVertexX);
    lv_SideValue[2] = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMax + libGAWH_gv_prc_PolygonVertexY);
    lv_SideValue[3] = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMin + libGAWH_gv_prc_PolygonVertexY);

    lv_LoopX = lv_SideValue[1];
    lv_LoopX += (((lv_SideValue[0] - lv_SideValue[1]) % lp_RegionPrecision) / 2);

    while (lv_LoopX < lv_SideValue[0])
    {
        lv_LoopX += lv_HalfRegionPrecision;
                //Codes
        lv_LoopX += lv_HalfRegionPrecision;
    }
[/codes]

其中“lv_LoopX += (((lv_SideValue[0] - lv_SideValue[1]) % lp_RegionPrecision) / 2);”语句的作用是将像素区域居中。两次“lv_LoopX += lv_HalfRegionPrecision;”也是相同作用。

        8、接下来就是对于每一条画出的直线的处理。首先我们要找出每条直线与哪些边相交。看图5-4,与直线OO`相交的边有什么规律呢?
        AB、CD、FG、HI,这是四条相交的边,发现没有,这四条边都满足同一个条件,即两个端点一个在直线左侧、一个在直线右侧。那么,我们用什么样的算法判断呢?
        因为已知输入的顶点是有序的,相临两点构成一个边,那么我们对照当前判断的直线的X轴坐标值,比较给定参数中相临两个点的X坐标值与lv_LoopX的关系即可,即(lv_LoopX < MaxF(lv_Side[0], lv_Side[2]) && lv_LoopX > MinF(lv_Side[0], lv_Side[2]))。

        然后,我们还要计算一下直线与边交点的坐标,并将Y轴坐标值存入DataTable。
        其计算公式很简单:“(lv_LoopX - lv_Side[0]) / (lv_Side[0] - lv_Side[2]) * (lv_Side[1] - lv_Side[3]) + lv_Side[1]”

代码 5-13 求取交点
[codes=galaxy]
        lv_I = 1;
        lv_SideIntersectNum = 0;
        lv_Side[2] = lv_FirstPoint[0];
        lv_Side[3] = lv_FirstPoint[1];
        while (lv_I <= lv_MaxVertexIndex)
        {
            lv_Side[0] = lv_Side[2];
            lv_Side[1] = lv_Side[3];

            lv_Side[2] = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_I) + libGAWH_gv_prc_PolygonVertexX);
            lv_Side[3] = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_I) + libGAWH_gv_prc_PolygonVertexY);

            if (lv_LoopX < MaxF(lv_Side[0], lv_Side[2]) && lv_LoopX > MinF(lv_Side[0], lv_Side[2]))
            {
                DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexY + IntToString(lv_SideIntersectNum), (lv_LoopX - lv_Side[0]) / (lv_Side[0] - lv_Side[2]) * (lv_Side[1] - lv_Side[3]) + lv_Side[1]);
                lv_SideIntersectNum += 1;               
            }

            lv_I += 1;
        }
[/codes]

        为了便于循环判断,我们在主循环前做一些补充赋值。

代码 5-14 求取交点的补充赋值
[codes=galaxy]
    lv_FirstPoint[0] = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(0) + libGAWH_gv_prc_PolygonVertexX);
    lv_FirstPoint[1] = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(0) + libGAWH_gv_prc_PolygonVertexY);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_MaxVertexIndex) + libGAWH_gv_prc_PolygonVertexX, lv_FirstPoint[0]);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_MaxVertexIndex) + libGAWH_gv_prc_PolygonVertexY, lv_FirstPoint[1]); [/codes]

        主要内容是将第一点存入最后一点之后。这样我们不需特殊处理最后一点。

        9、下一步的任务是将这些点按照Y轴坐标排序,为减少循环,采用交换最小值的方式, (如没有交点则不进行):

代码 5-15 交点排序
[codes=galaxy]
            lv_I = 0;
            while (lv_I < lv_SideIntersectNum)
            {
                lv_J = lv_I;
                lv_TempMinY = 9999;
                while (lv_J < lv_SideIntersectNum)
                {
                    lv_TempY = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexY + IntToString(lv_J));
                    if (lv_TempY < lv_TempMinY)
                    {
                        lv_TempMinY = lv_TempY;
                        lv_TempMinYIndex = lv_J;
                    }
                    lv_J += 1;
                }
                DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexY + IntToString(lv_TempMinYIndex), DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexY + IntToString(lv_I)));
                DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexY + IntToString(lv_I), lv_TempMinY);
                lv_I += 1;
            }
        }
[/codes]

        10、最后就是按照次序,在序号为n和n+1(n为偶数)之间,依据参数lp_RegionPrecision添加区域了。

        完整代码如下:

代码 5-16任意多边形区域生成系统
[codes=galaxy]
//IndexGetSystem
//Const
const string libGAWH_gv_is_NameIndexSystemFirstName = "IndexSystem:";
const string libGAWH_gv_is_NameIndexSystemDeleteNum = ":DeleteNum:";
const string libGAWH_gv_is_NameIndexSystemDeleteIndex = ":DeleteIndex:";
const string libGAWH_gv_is_NameIndexSystemMaxNum = ":Max:";
const string libGAWH_gv_is_NameIndexSystemLastIndex = ":LastIndex:";

//Functions
int libGAWH_is_IndexSystemGetIndex(string lp_IndexName)
{
    int lv_DeleteNum = DataTableGetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemDeleteNum);
    int lv_MaxNum = 0;
    int lv_LastIndex = 0;

    if (lv_DeleteNum == 0)
    {
        lv_MaxNum = DataTableGetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemMaxNum);
        DataTableSetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemMaxNum, lv_MaxNum + 1);
        DataTableSetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemLastIndex, lv_MaxNum);
        return lv_MaxNum;
    }
    else
    {
        lv_DeleteNum =lv_DeleteNum - 1;
        DataTableSetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemDeleteNum, lv_DeleteNum);
        lv_LastIndex = DataTableGetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemDeleteIndex + IntToString(lv_DeleteNum));
        DataTableSetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemLastIndex, lv_LastIndex);
        return lv_LastIndex;
    }
}

void libGAWH_is_IndexSystemDeleteIndex(string lp_IndexName, int lp_Index)
{
    int lv_DeleteNum = DataTableGetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemDeleteNum);

    DataTableSetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemDeleteNum, lv_DeleteNum + 1);
    DataTableSetInt(true, libGAWH_gv_is_NameIndexSystemFirstName + lp_IndexName + libGAWH_gv_is_NameIndexSystemDeleteIndex + IntToString(lv_DeleteNum), lp_Index);
}

//Polygon Region Create
//Const
const string libGAWH_gv_prc_PolygonIndexName = "PolygonIndex:";
const string libGAWH_gv_prc_PolygonRegion = "PolygonRegion:";
const string libGAWH_gv_prc_PolygonVertex = ":VerTex:";
const string libGAWH_gv_prc_PolygonVertexX = ":X:";
const string libGAWH_gv_prc_PolygonVertexY = ":Y:";
const string libGAWH_gv_prc_PolygonVertexMax = ":Max:";
const string libGAWH_gv_prc_PolygonVertexMin = ":Min:";
const string libGAWH_gv_prc_PolygonSide = ":Side:";


//Function
int libGAWH_prc_PolygonInitialization()
{
    int lv_PolygonIndex = libGAWH_is_IndexSystemGetIndex(libGAWH_gv_prc_PolygonIndexName);
    string lv_PolygonName = libGAWH_gv_prc_PolygonRegion + IntToString(lv_PolygonIndex);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMin + libGAWH_gv_prc_PolygonVertexX, 99999);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMin + libGAWH_gv_prc_PolygonVertexY, 99999);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMax + libGAWH_gv_prc_PolygonVertexX, -1);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMax + libGAWH_gv_prc_PolygonVertexY, -1);
    DataTableSetInt(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex, 0);
    return lv_PolygonIndex;
}

void libGAWH_prc_PolygonRemove(int lp_PolygonIndex)
{
    libGAWH_is_IndexSystemDeleteIndex(libGAWH_gv_prc_PolygonIndexName, lp_PolygonIndex);
}

void libGAWH_prc_PolygonRegionAddVertex(int lp_PolygonIndex, point lp_Vertex)
{
    string lv_PolygonName = libGAWH_gv_prc_PolygonRegion + IntToString(lp_PolygonIndex);
    int lv_MaxVertexIndex = DataTableGetInt(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex);
    fixed lv_X = PointGetX(lp_Vertex);
    fixed lv_Y = PointGetY(lp_Vertex);
    fixed lv_MaxX = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMax + libGAWH_gv_prc_PolygonVertexX);
    fixed lv_MinX = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMin + libGAWH_gv_prc_PolygonVertexX);
    fixed lv_MaxY = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMax + libGAWH_gv_prc_PolygonVertexY);
    fixed lv_MinY = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMin + libGAWH_gv_prc_PolygonVertexY);

    DataTableSetPoint(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_MaxVertexIndex), lp_Vertex);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_MaxVertexIndex) + libGAWH_gv_prc_PolygonVertexX, lv_X);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_MaxVertexIndex) + libGAWH_gv_prc_PolygonVertexY, lv_Y);

    if (lv_X > lv_MaxX)
    {
        DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMax + libGAWH_gv_prc_PolygonVertexX, lv_X);
    }
    if (lv_X < lv_MinX)
    {
        DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMin + libGAWH_gv_prc_PolygonVertexX, lv_X);
    }
    if (lv_Y > lv_MaxY)
    {
        DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMax + libGAWH_gv_prc_PolygonVertexY, lv_Y);
    }
    if (lv_Y < lv_MinY)
    {
        DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMin + libGAWH_gv_prc_PolygonVertexY, lv_Y);
    }

    DataTableSetInt(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex, lv_MaxVertexIndex + 1);

region libGAWH_prc_PolygonRegionCreate(int lp_PolygonIndex, fixed lp_RegionPrecision)
{
    string lv_PolygonName = libGAWH_gv_prc_PolygonRegion + IntToString(lp_PolygonIndex);
    region lv_PolygonRegion = RegionEmpty();
    int lv_MaxVertexIndex = DataTableGetInt(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex);
    int lv_I = 0;
    int lv_J = 0;
    int lv_SideIntersectNum = 0;
    fixed lv_LoopX = 0;
    fixed lv_LoopY = 0;
    fixed [4] lv_SideValue;
    fixed lv_HalfRegionPrecision = lp_RegionPrecision / 2;
    fixed [4] lv_Side;
    fixed [2] lv_FirstPoint;
    fixed lv_TempMinY = 0;
    int lv_TempMinYIndex = 0;
    fixed lv_TempY = 0;
    fixed lv_TempNextY = 0;

    //Vertex number Check
    if (lv_MaxVertexIndex < 3)
    {
        return lv_PolygonRegion;
    }

    lv_SideValue[0] = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMax + libGAWH_gv_prc_PolygonVertexX);
    lv_SideValue[1] = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMin + libGAWH_gv_prc_PolygonVertexX);
    lv_SideValue[2] = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMax + libGAWH_gv_prc_PolygonVertexY);
    lv_SideValue[3] = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexMin + libGAWH_gv_prc_PolygonVertexY);

    lv_FirstPoint[0] = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(0) + libGAWH_gv_prc_PolygonVertexX);
    lv_FirstPoint[1] = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(0) + libGAWH_gv_prc_PolygonVertexY);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_MaxVertexIndex) + libGAWH_gv_prc_PolygonVertexX, lv_FirstPoint[0]);
    DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_MaxVertexIndex) + libGAWH_gv_prc_PolygonVertexY, lv_FirstPoint[1]);

    lv_LoopX = lv_SideValue[1];
    lv_LoopX += (((lv_SideValue[0] - lv_SideValue[1]) % lp_RegionPrecision) / 2);

    while (lv_LoopX < lv_SideValue[0])
    {
        lv_LoopX += lv_HalfRegionPrecision;

        lv_I = 1;
        lv_SideIntersectNum = 0;
        lv_Side[2] = lv_FirstPoint[0];
        lv_Side[3] = lv_FirstPoint[1];
        while (lv_I <= lv_MaxVertexIndex)
        {
            lv_Side[0] = lv_Side[2];
            lv_Side[1] = lv_Side[3];

            lv_Side[2] = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_I) + libGAWH_gv_prc_PolygonVertexX);
            lv_Side[3] = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertex + IntToString(lv_I) + libGAWH_gv_prc_PolygonVertexY);

            if (lv_LoopX < MaxF(lv_Side[0], lv_Side[2]) && lv_LoopX > MinF(lv_Side[0], lv_Side[2]))
            {
                DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexY + IntToString(lv_SideIntersectNum), (lv_LoopX - lv_Side[0]) / (lv_Side[0] - lv_Side[2]) * (lv_Side[1] - lv_Side[3]) + lv_Side[1]);
                lv_SideIntersectNum += 1;               
            }

            lv_I += 1;
        }

        if (lv_SideIntersectNum != 0)
        {
            lv_I = 0;
            while (lv_I < lv_SideIntersectNum)
            {
                lv_J = lv_I;
                lv_TempMinY = 9999;
                while (lv_J < lv_SideIntersectNum)
                {
                    lv_TempY = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexY + IntToString(lv_J));
                    if (lv_TempY < lv_TempMinY)
                    {
                        lv_TempMinY = lv_TempY;
                        lv_TempMinYIndex = lv_J;
                    }
                    lv_J += 1;
                }
                DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexY + IntToString(lv_TempMinYIndex), DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexY + IntToString(lv_I)));
                DataTableSetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexY + IntToString(lv_I), lv_TempMinY);
                lv_I += 1;
            }

            lv_LoopY = -1;
            lv_TempNextY = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexY + IntToString(lv_SideIntersectNum));
            while (true)
            {
                if(lv_LoopY < lv_TempNextY)
                {
                    if (lv_SideIntersectNum <= 0)
                    {
                        break;
                    }

                    lv_SideIntersectNum -= 2;
                    lv_LoopY = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexY + IntToString(lv_SideIntersectNum + 1));
                    lv_TempNextY = DataTableGetFixed(true, lv_PolygonName + libGAWH_gv_prc_PolygonVertexY + IntToString(lv_SideIntersectNum));
                    lv_LoopY -= (((lv_LoopY - lv_TempNextY) % lp_RegionPrecision) / 2);
                }

                RegionAddRect(lv_PolygonRegion, true, lv_LoopX - lv_HalfRegionPrecision, lv_LoopX + lv_HalfRegionPrecision, lv_LoopY, lv_LoopY + lp_RegionPrecision);
                lv_LoopY -= lp_RegionPrecision;
            }        
        }

        lv_LoopX += lv_HalfRegionPrecision;
    }
    return lv_PolygonRegion;
}
[/codes]

        PS:关于区域叠加,有如下注意事项:
  • 区域叠加256*256测试通过,即65536块区域叠加。
  • 负区域叠加后效果无法取消。
  • 可以用添加区域到空区域的方式复制区域。
  • 正负叠加的复杂区域内的随机点获取成功概率很高。共计32次测试,测试失败报错“經32嘗試,無法找到'未知地區'地區內的隨機點”。


        此例中面向对象部分较少。在某些系统中则会需要很多面向对象的算法,该如何做呢?
        不论是怎样的系统,或者单一函数,在编写之前,我们首先要想好输入输出条件。实际上很多时候,只要略微修改输入输出条件,便能将你的算法的执行效率提高很多。
        一个系统也好,一个函数也好,其本身应该是封装的、独立的,输入输出更应该是标准化的。例如之前的例子里,所有的DataTable的存储Key都用常量存储,而且即使是不同的系统,Key的结构也是近似的。这样,当你制作新的内容时,你可以方便的利用自己以前写好的函数。
        运算和对象之间的联系其实要比想象中简单的多。当你确定输入输出条件后,下一步就是选择对应的对象处理函数,如上例中的添加区域函数“RegionAddRect()”,当你确定好这一步,剩下就是考虑对应每种情况应有的运算结果。

        在编写过程中,如果不是确定必然只会单次使用,一般都尽量保证局域化。若是仅区别不同玩家局域化的系统,可以用长度16的数组来代替DataTable,因为尽管DataTable的执行效率很高,但是过多次的连接字符串也会影响效率。
        说起效率,如果你有足够水平,那么请在编写过程中就保证执行效率,从每一个细节做起。当你好不在意效率的将函数编写完,运行时突然发现执行起来卡的时候,你会相当郁闷的。要知道,优化一个函数比重写还要困难。
回复

使用道具 举报

 楼主| 发表于 2012-2-13 21:40:56 | 显示全部楼层
5.6 Debug方法
        Debug的水平决定你的成品率和开发周期。关于这方面的讲解很难。原因同样是因人而异。
        一般来说我们都是采用验证关键值的方式来Debug。不在循环中的值可以赋值给全局变量,然后在Debug窗口中读取。通用的方法是用TriggerDebugOutput()这个函数来输出基础变量类型及Text等类型的值。
        SE中不支持步进,但我们可以自己编写步进脚本。

代码 5-17 步进脚本
[codes=galaxy]         
bool Next = true;
void BreakOut(string ShowText)
{
    TriggerDebugOutput(1, StringToText(ShowText), false);
    while(Next)
    {
        Wait(0.01,c_timeGame);
    }
    Next = true;
}
[/codes]

        使用时在需要打断处调用函数“BreakOut();”即可设置断点,输入Text参数为打断时输出信息,用于输出Debug数据。
通过触发设置全局变量Next = false;就可以使函数继续执行。
回复

使用道具 举报

 楼主| 发表于 2012-2-13 21:43:40 | 显示全部楼层
5.7 GUI下的自定义函数、事件、条件、动作以及库的制作
        SE支持自定义函数、事件、条件、动作。如图5-5、5-6。
5-5.png
图 5-5 可新建内容


5-6.png
图 5-6 新建库


事件、条件、动作、函数的自定义实质上都是相同的内容,如图5-7。
5-7.png
图5-7 事件、条件、动作、函数


        关于图5-7中每个选项选项的含义,大家参照基本库中的使用与自己测试来了解好了。个人能力有限,暂不做逐一说明。

        使用自定义内容(不包括自定义脚本),可以使用几个宏,其内容如下:

#AUTOVAR用来创建随机不重名的变量。第二个参数代表类型,可以忽略。类型可以由别的东西决定,比如函数的参数。
#SUBFUNCS用来代表sub-function里的内容。具体哪一行由关键字决定
#IFHAVESUBFUNCS判断是否有填写sub-function
#CUSTOM用户所输入的自定义代码
#PARAM用来取函数的参数
#SMARTBREAK就是,当外部有循环结构的时候写break,反之不写。
#DEFRETURN代表外部函数的默认返回值

        可参看:http://bbs.islga.org/read-htm-tid-177329.html中头目的说明。详细使用方式及其他未说明宏可自行在基本库中寻找。

代码 5-18 一个使用宏的自定义脚本实例
[codes=galaxy]
    #AUTOVAR(I,int) = 0;
    #AUTOVAR(TotalSquaresNum,int) = 0;
    #AUTOVAR(NameLabyrinthNum,string) = "";
    #AUTOVAR(NameLabyrinthNum) = libGAWH_gv_lc_NameLabyrinthFirstName + IntToString(#PARAM(LabyrinthNum));
    #AUTOVAR(TotalSquaresNum) = DataTableGetInt(true, #AUTOVAR(NameLabyrinthNum) + libGAWH_gv_lc_NameLabyrinthTotalSquaresNum);
    while(#AUTOVAR(I) <= #AUTOVAR(TotalSquaresNum))
    {
        #PARAM(SquarePoint) = DataTableGetPoint(true, ((#AUTOVAR(NameLabyrinthNum) + libGAWH_gv_lc_NameLabyrinthTotalSquaresNum) + IntToString(#AUTOVAR(I))) + libGAWH_gv_lc_NameLabyrinthSquarePoint);        
        #SUBFUNCS(LabyrinthSquaresCreateFunction)
        #AUTOVAR(I) = #AUTOVAR(I) + 1;
    }
[/codes]

       对应的函数设置见图5-8。
5-8.png
图 5-8 函数设置


        下面来对代码5-18做一下解析。
        #AUTOVAR(I,int) = 0;是声明一个整数局域变量I。

#AUTOVAR(TotalSquaresNum,int) = 0;是声明一个整数局域变量TotalSquaresNum。

#AUTOVAR(NameLabyrinthNum,string) = "";是声明一个字符串局域变量NameLabyrinthNum。

#AUTOVAR(NameLabyrinthNum) = libGAWH_gv_lc_NameLabyrinthFirstName + IntToString(#PARAM(LabyrinthNum));对字符串变量NameLabyrinthNum赋值,其值为libGAWH_gv_lc_NameLabyrinthFirstName加上整数参数LabyrinthNum转换为字符串后的值。

#AUTOVAR(TotalSquaresNum) = DataTableGetInt(true, #AUTOVAR(NameLabyrinthNum) + libGAWH_gv_lc_NameLabyrinthTotalSquaresNum);对整数局域变量TotalSquaresNum赋值,其值为DataTable中Key为NameLabyrinthNum加上libGAWH_gv_lc_NameLabyrinthTotalSquaresNum对应项的整数存储值。

while(#AUTOVAR(I) <= #AUTOVAR(TotalSquaresNum))循环,循环条件为局域变量I小于等于整数局域变量TotalSquaresNum。

#PARAM(SquarePoint) = DataTableGetPoint(true, ((#AUTOVAR(NameLabyrinthNum) + libGAWH_gv_lc_NameLabyrinthTotalSquaresNum) + IntToString(#AUTOVAR(I))) + libGAWH_gv_lc_NameLabyrinthSquarePoint);对参数SquarePoint赋值,值为DataTable中Key为指定值(略)对应的点(point)类型值。

#SUBFUNCS(LabyrinthSquaresCreateFunction)执行循环动作中的代码。LabyrinthSquaresCreateFunction为图5-8“迷宫格创建动作”下添加的代码。此内容将会在使用此函数时添加。

#AUTOVAR(I) = #AUTOVAR(I) + 1;整数变量I自增1.

        大家可以看出,实际上宏的使用与Galaxy的原本语法完全相同,仅#SUBFUNCS宏的使用方法较为特别。相信大家能够很轻松的学会宏的使用。
回复

使用道具 举报

 楼主| 发表于 2012-2-13 21:46:59 | 显示全部楼层
5.8 良好的编程习惯
       这章内容仅供参考,你可以自己确立自己的编程习惯。

        不论哪种编程语言的教程都会提到一点,那就是良好的编程习惯,这种好的习惯也许并不能影响你编写内容的执行效果,但是它会增加你的代码的可读性。使你在修改或应用以前编写的代码时能够更清晰、快捷的回忆起你当时的设计。如果你分享了自己的代码,同样也便于大家阅读。

        良好的编程习惯包括三个方面:
        1、注释。
        2、规范的变量名及函数名。
        3、明确的缩进及空行。
        4、常量。

        我们依次说明。

        一段复杂的代码经常包含复杂的逻辑关系及处理步骤,当我们阅读这样一段代码时,往往会无从下手,或者对其中的内容不解。编程过程中,在函数关键点写下注释是相当好的习惯。这样不仅仅便于其他人阅读你的代码,也便于你自己对代码的修改、完善。
        因为Galaxy不支持中文注释,所以大家还是采用英文注释吧。或者干脆拼音?

        关于变量名及函数名,这个就是更为细节的内容。类似注释,它也是增加代码的可读性的。如果你有加密脚本的意图,除非你是手动在编写的同时加密,否则还是建议你使用规范化的变量名及函数名。
        这种规范的命名规则有那么几种,学编程的同学应该了解。具体大家可以自己问度娘,大家也可以根据自己的习惯来处理,或者参考Blizzard的官方脚本中的命名方法。

        我自己的命名规则如下:
        局域变量采用lv_变量名,如lv_I;
        参数使用lp_参数名,如lp_Index;
        全局变量采用库名_gv_功能名缩写_变量名的方式,如libGAWH_gv_cf_ArraySystemFirstName;
        函数名采用库名_功能名缩写_函数名的方式,如libGAWH_cf_ArraySystemSetInt。

        明确的缩进和空行便于你确定“{}”的配对,以及选择与循环语句嵌套时的逻辑关系。如果你使用Galaxy++ Editor,软件会自动处理。如果你直接使用SE编程,那么请自己注意添加空格。这里我的语法格式不同于官方文件中的,个人感觉我这样的配对方式能容易体现出“{}”的配对。

        一些重复使用的值,例如用于区分DataTable存储空间的Key的前置名等,最好使用常量,这样方便你在遇到冲突时的修改。
回复

使用道具 举报

发表于 2012-2-13 22:17:05 | 显示全部楼层
辛苦了
回复

使用道具 举报

发表于 2012-2-13 22:38:38 | 显示全部楼层
终于有教程了,支持,辛苦了,爪机无力下载…
回复

使用道具 举报

发表于 2012-2-13 23:23:46 | 显示全部楼层
感谢~~~~拜读~~~~
回复

使用道具 举报

发表于 2012-2-13 23:50:47 | 显示全部楼层
好辛苦的样子
回复

使用道具 举报

发表于 2012-2-14 02:52:27 | 显示全部楼层
求数据编辑器教程。
其实也不用教程,把那些坑爹翻译修正了就行。
回复

使用道具 举报

发表于 2012-2-14 02:55:05 | 显示全部楼层
遇到具体属性不明白的可以来问啊。

数据编辑器的属性太多,写一个单独的教程是很不现实的。那必须得是一部字典阿
回复

使用道具 举报

发表于 2012-2-14 08:44:02 | 显示全部楼层
辛苦了呢~加油~加油~
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 点一下

本版积分规则

Archiver|移动端|小黑屋|地精研究院

GMT+8, 2024-11-22 22:27 , Processed in 0.177255 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表