|
system39运算的优先顺序:
system39运算的特殊规则: op1 + op2 两数相加如果大于65535,那么,结果为65535 system39运算的数组改进: 数组的引用改为数组名加[]来表示 []之中可以是立即数,比如1,2,3…… 也可以是表达式,比如2+3,3*6…… 也可以是另一个数组中的某个元素,ARRAY[D01+D02[4*D03]是合法的。 system39开发提示: SG命令播放midi的时候,如果指定的midi已经在播放中,那么该命令将不作任何操作。 SX命令(音乐渲染)的第五个属性如果设为1,那么在渲染结束前,不要进行任何音乐相关操作,如: SX 2,1,5000,0,1: 这个命令是让midi声音的音量在5秒内越变越小,最后消失,但, S系命令中,SP,ST等用于播放wave的命令,不要和小文字命令诸如wavPlay, 常变量的定义,搞不清楚到底应该叫什么了,意思是system3x中变量的定义基本是使用时付值即可, const word 変量名 = 初期値 ,変量名 = 初期値……: 例如:const word wX = 16, wY = 32: 这样以后用到wX或者16都是一样的,常变量一旦付值,其值将不可改变,上边是数字型变量, const string 変量名 = 初期値 ,変量名 = 初期値……: (例) 关于pragma autodeclvar [on/off]: 上记命令的作用是在给变量付值的时候是否检测所付值变量为新变量,默认值是on,这也就是上边说的system3x中变量的定义基本是使用时付值即可的原因,但如果你确信所编的adv里没有新变量, !变量:0!
pragma autodeclvar off: 关于pragma forbidcmd,这要配合system39c.ini来使用,在system39c.ini中可以指定不可使用的命令, 启动选项 格式: -D=フォルダ名 这是在包含dll文件的情况下设置搜索路径,如: -D=c:\dll BY AFIC |
||||||||||||||||||||||
技术Tech分类
[GAMEDEV] 游戏开发 BACKUP1
算法一:A*寻路初探
From GameDev.net
译者序:很久以前就知道了A*算法,但是从未认真读过相关的文章,也没有看过代码,只是脑子里有个模糊的概念。这次决定从头开始,研究一下这个被人推崇备至的简单方法,作为学习人工智能的开始。
这篇文章非常知名,国内应该有不少人翻译过它,我没有查找,觉得翻译本身也是对自身英文水平的锻炼。经过努力,终于完成了文档,也明白的A*算法的原理。毫无疑问,作者用形象的描述,简洁诙谐的语言由浅入深的讲述了这一神奇的算法,相信每个读过的人都会对此有所认识(如果没有,那就是偶的翻译太差了–b)。
原文链接:http://www.gamedev.net/reference/articles/article2003.asp
以下是翻译的正文。(由于本人使用ultraedit编辑,所以没有对原文中的各种链接加以处理(除了图表),也是为了避免未经许可链接的嫌疑,有兴趣的读者可以参考原文。
会者不难,A*(念作A星)算法对初学者来说的确有些难度。
这篇文章并不试图对这个话题作权威的陈述。取而代之的是,它只是描述算法的原理,使你可以在进一步的阅读中理解其他相关的资料。
最后,这篇文章没有程序细节。你尽可以用任意的计算机程序语言实现它。如你所愿,我在文章的末尾包含了一个指向例子程序的链接。 压缩包包括C++和Blitz Basic两个语言的版本,如果你只是想看看它的运行效果,里面还包含了可执行文件。
我们正在提高自己。让我们从头开始。。。
序:搜索区域
假设有人想从A点移动到一墙之隔的B点,如下图,绿色的是起点A,红色是终点B,蓝色方块是中间的墙。

[图1]
你首先注意到,搜索区域被我们划分成了方形网格。像这样,简化搜索区域,是寻路的第一步。这一方法把搜索区域简化成了一个二维数组。数组的每一个元素是网格的一个方块,方块被标记为可通过的和不可通过的。路径被描述为从A到B我们经过的方块的集合。一旦路径被找到,我们的人就从一个方格的中心走向另一个,直到到达目的地。
这些中点被称为“节点”。当你阅读其他的寻路资料时,你将经常会看到人们讨论节点。为什么不把他们描述为方格呢?因为有可能你的路径被分割成其他不是方格的结构。他们完全可以是矩形,六角形,或者其他任意形状。节点能够被放置在形状的任意位置-可以在中心,或者沿着边界,或其他什么地方。我们使用这种系统,无论如何,因为它是最简单的。
开始搜索
正如我们处理上图网格的方法,一旦搜索区域被转化为容易处理的节点,下一步就是去引导一次找到最短路径的搜索。在A*寻路算法中,我们通过从点A开始,检查相邻方格的方式,向外扩展直到找到目标。
我们做如下操作开始搜索:
1,从点A开始,并且把它作为待处理点存入一个“开启列表”。开启列表就像一张购物清单。尽管现在列表里只有一个元素,但以后就会多起来。你的路径可能会通过它包含的方格,也可能不会。基本上,这是一个待检查方格的列表。
2,寻找起点周围所有可到达或者可通过的方格,跳过有墙,水,或其他无法通过地形的方格。也把他们加入开启列表。为所有这些方格保存点A作为“父方格”。当我们想描述路径的时候,父方格的资料是十分重要的。后面会解释它的具体用途。
3,从开启列表中删除点A,把它加入到一个“关闭列表”,列表中保存所有不需要再次检查的方格。
在这一点,你应该形成如图的结构。在图中,暗绿色方格是你起始方格的中心。它被用浅蓝色描边,以表示它被加入到关闭列表中了。所有的相邻格现在都在开启列表中,它们被用浅绿色描边。每个方格都有一个灰色指针反指他们的父方格,也就是开始的方格。

[图2]
接着,我们选择开启列表中的临近方格,大致重复前面的过程,如下。但是,哪个方格是我们要选择的呢?是那个F值最低的。
路径评分
选择路径中经过哪个方格的关键是下面这个等式:
F = G + H
这里:
* G = 从起点A,沿着产生的路径,移动到网格上指定方格的移动耗费。
* H = 从网格上那个方格移动到终点B的预估移动耗费。这经常被称为启发式的,可能会让你有点迷惑。这样叫的原因是因为它只是个猜测。我们没办法事先知道路径的长度,因为路上可能存在各种障碍(墙,水,等等)。虽然本文只提供了一种计算H的方法,但是你可以在网上找到很多其他的方法。
我们的路径是通过反复遍历开启列表并且选择具有最低F值的方格来生成的。文章将对这个过程做更详细的描述。首先,我们更深入的看看如何计算这个方程。
正如上面所说,G表示沿路径从起点到当前点的移动耗费。在这个例子里,我们令水平或者垂直移动的耗费为10,对角线方向耗费为14。我们取这些值是因为沿对角线的距离是沿水平或垂直移动耗费的的根号2(别怕),或者约1.414倍。为了简化,我们用10和14近似。比例基本正确,同时我们避免了求根运算和小数。这不是只因为我们怕麻烦或者不喜欢数学。使用这样的整数对计算机来说也更快捷。你不就就会发现,如果你不使用这些简化方法,寻路会变得很慢。
既然我们在计算沿特定路径通往某个方格的G值,求值的方法就是取它父节点的G值,然后依照它相对父节点是对角线方向或者直角方向(非对角线),分别增加14和10。例子中这个方法的需求会变得更多,因为我们从起点方格以外获取了不止一个方格。
H值可以用不同的方法估算。我们这里使用的方法被称为曼哈顿方法,它计算从当前格到目的格之间水平和垂直的方格的数量总和,忽略对角线方向。然后把结果乘以10。这被成为曼哈顿方法是因为它看起来像计算城市中从一个地方到另外一个地方的街区数,在那里你不能沿对角线方向穿过街区。很重要的一点,我们忽略了一切障碍物。这是对剩余距离的一个估算,而非实际值,这也是这一方法被称为启发式的原因。想知道更多?你可以在这里找到方程和额外的注解。
F的值是G和H的和。第一步搜索的结果可以在下面的图表中看到。F,G和H的评分被写在每个方格里。正如在紧挨起始格右侧的方格所表示的,F被打印在左上角,G在左下角,H则在右下角。

[图3]
现在我们来看看这些方格。写字母的方格里,G = 10。这是因为它只在水平方向偏离起始格一个格距。紧邻起始格的上方,下方和左边的方格的G值都等于10。对角线方向的G值是14。
H值通过求解到红色目标格的曼哈顿距离得到,其中只在水平和垂直方向移动,并且忽略中间的墙。用这种方法,起点右侧紧邻的方格离红色方格有3格距离,H值就是30。这块方格上方的方格有4格距离(记住,只能在水平和垂直方向移动),H值是40。你大致应该知道如何计算其他方格的H值了~。
每个格子的F值,还是简单的由G和H相加得到
继续搜索
为了继续搜索,我们简单的从开启列表中选择F值最低的方格。然后,对选中的方格做如下处理:
4,把它从开启列表中删除,然后添加到关闭列表中。
5,检查所有相邻格子。跳过那些已经在关闭列表中的或者不可通过的(有墙,水的地形,或者其他无法通过的地形),把他们添加进开启列表,如果他们还不在里面的话。把选中的方格作为新的方格的父节点。
6,如果某个相邻格已经在开启列表里了,检查现在的这条路径是否更好。换句话说,检查如果我们用新的路径到达它的话,G值是否会更低一些。如果不是,那就什么都不做。
另一方面,如果新的G值更低,那就把相邻方格的父节点改为目前选中的方格(在上面的图表中,把箭头的方向改为指向这个方格)。最后,重新计算F和G的值。如果这看起来不够清晰,你可以看下面的图示。
好了,让我们看看它是怎么运作的。我们最初的9格方格中,在起点被切换到关闭列表中后,还剩8格留在开启列表中。这里面,F值最低的那个是起始格右侧紧邻的格子,它的F值是40。因此我们选择这一格作为下一个要处理的方格。在紧随的图中,它被用蓝色突出显示。

[图4]
首先,我们把它从开启列表中取出,放入关闭列表(这就是他被蓝色突出显示的原因)。然后我们检查相邻的格子。哦,右侧的格子是墙,所以我们略过。左侧的格子是起始格。它在关闭列表里,所以我们也跳过它。
其他4格已经在开启列表里了,于是我们检查G值来判定,如果通过这一格到达那里,路径是否更好。我们来看选中格子下面的方格。它的G值是14。如果我们从当前格移动到那里,G值就会等于20(到达当前格的G值是10,移动到上面的格子将使得G值增加10)。因为G值20大于14,所以这不是更好的路径。如果你看图,就能理解。与其通过先水平移动一格,再垂直移动一格,还不如直接沿对角线方向移动一格来得简单。
当我们对已经存在于开启列表中的4个临近格重复这一过程的时候,我们发现没有一条路径可以通过使用当前格子得到改善,所以我们不做任何改变。既然我们已经检查过了所有邻近格,那么就可以移动到下一格了。
于是我们检索开启列表,现在里面只有7格了,我们仍然选择其中F值最低的。有趣的是,这次,有两个格子的数值都是54。我们如何选择?这并不麻烦。从速度上考虑,选择最后添加进列表的格子会更快捷。这种导致了寻路过程中,在靠近目标的时候,优先使用新找到的格子的偏好。但这无关紧要。(对相同数值的不同对待,导致不同版本的A*算法找到等长的不同路径。)
那我们就选择起始格右下方的格子,如图。

[图5]
这次,当我们检查相邻格的时候,发现右侧是墙,于是略过。上面一格也被略过。我们也略过了墙下面的格子。为什么呢?因为你不能在不穿越墙角的情况下直接到达那个格子。你的确需要先往下走然后到达那一格,按部就班的走过那个拐角。(注解:穿越拐角的规则是可选的。它取决于你的节点是如何放置的。)
这样一来,就剩下了其他5格。当前格下面的另外两个格子目前不在开启列表中,于是我们添加他们,并且把当前格指定为他们的父节点。其余3格,两个已经在开启列表中(起始格,和当前格上方的格子,在表格中蓝色高亮显示),于是我们略过它们。最后一格,在当前格的左侧,将被检查通过这条路径,G值是否更低。不必担心,我们已经准备好检查开启列表中的下一格了。
我们重复这个过程,知道目标格被添加进开启列表,就如在下面的图中所看到的。

[图6]
注意,起始格下方格子的父节点已经和前面不同的。之前它的G值是28,并且指向右上方的格子。现在它的G值是20,指向它上方的格子。这在寻路过程中的某处发生,当应用新路径时,G值经过检查变得低了-于是父节点被重新指定,G和F值被重新计算。尽管这一变化在这个例子中并不重要,在很多场合,这种变化会导致寻路结果的巨大变化。
那么,我们怎么确定这条路径呢?很简单,从红色的目标格开始,按箭头的方向朝父节点移动。这最终会引导你回到起始格,这就是你的路径!看起来应该像图中那样。从起始格A移动到目标格B只是简单的从每个格子(节点)的中点沿路径移动到下一个,直到你到达目标点。就这么简单。

[图7]
A*方法总结
好,现在你已经看完了整个说明,让我们把每一步的操作写在一起:
1,把起始格添加到开启列表。
2,重复如下的工作:
a) 寻找开启列表中F值最低的格子。我们称它为当前格。
b) 把它切换到关闭列表。
c) 对相邻的8格中的每一个?
* 如果它不可通过或者已经在关闭列表中,略过它。反之如下。
* 如果它不在开启列表中,把它添加进去。把当前格作为这一格的父节点。记录这一格的F,G,和H值。
* 如果它已经在开启列表中,用G值为参考检查新的路径是否更好。更低的G值意味着更好的路径。如果是这样,就把这一格的父节点改成当前格,并且重新计算这一格的G和F值。如果你保持你的开启列表按F值排序,改变之后你可能需要重新对开启列表排序。
d) 停止,当你
* 把目标格添加进了开启列表,这时候路径被找到,或者
* 没有找到目标格,开启列表已经空了。这时候,路径不存在。
3.保存路径。从目标格开始,沿着每一格的父节点移动直到回到起始格。这就是你的路径。
题外话
离题一下,见谅,值得一提的是,当你在网上或者相关论坛看到关于A*的不同的探讨,你有时会看到一些被当作A*算法的代码而实际上他们不是。要使用A*,你必须包含上面讨论的所有元素--特定的开启和关闭列表,用F,G和H作路径评价。有很多其他的寻路算法,但他们并不是A*,A*被认为是他们当中最好的。Bryan Stout在这篇文章后面的参考文档中论述了一部分,包括他们的一些优点和缺点。有时候特定的场合其他算法会更好,但你必须很明确你在作什么。好了,够多的了。回到文章。
实现的注解
现在你已经明白了基本原理,写你的程序的时候还得考虑一些额外的东西。下面这些材料中的一些引用了我用C++和Blitz Basic写的程序,但对其他语言写的代码同样有效。
1,维护开启列表:这是A*寻路算法最重要的组成部分。每次你访问开启列表,你都需要寻找F值最低的方格。有几种不同的方法实现这一点。你可以把路径元素随意保存,当需要寻找F值最低的元素的时候,遍历开启列表。这很简单,但是太慢了,尤其是对长路径来说。这可以通过维护一格排好序的列表来改善,每次寻找F值最低的方格只需要选取列表的首元素。当我自己实现的时候,这种方法是我的首选。
在小地图。这种方法工作的很好,但它并不是最快的解决方案。更苛求速度的A*程序员使用叫做“binary heap”的方法,这也是我在代码中使用的方法。凭我的经验,这种方法在大多数场合会快2~3倍,并且在长路经上速度呈几何级数提升(10倍以上速度)。如果你想了解更多关于binary heap的内容,查阅我的文章,Using Binary Heaps in A* Pathfinding。
2,其他单位:如果你恰好看了我的例子代码,你会发现它完全忽略了其他单位。我的寻路者事实上可以相互穿越。取决于具体的游戏,这也许可以,也许不行。如果你打算考虑其他单位,希望他们能互相绕过,我建议在寻路算法中忽略其他单位,写一些新的代码作碰撞检测。当碰撞发生,你可以生成一条新路径或者使用一些标准的移动规则(比如总是向右,等等)直到路上没有了障碍,然后再生成新路径。为什么在最初的路径计算中不考虑其他单位呢?那是因为其他单位会移动,当你到达他们原来的位置的时候,他们可能已经离开了。这有可能会导致奇怪的结果,一个单位突然转向,躲避一个已经不在那里的单位,并且会撞到计算完路径后,冲进它的路径中的单位。
然而,在寻路算法中忽略其他对象,意味着你必须编写单独的碰撞检测代码。这因游戏而异,所以我把这个决定权留给你。参考文献列表中,Bryan Stout的文章值得研究,里面有一些可能的解决方案(像鲁棒追踪,等等)。
3,一些速度方面的提示:当你开发你自己的A*程序,或者改写我的,你会发现寻路占据了大量的CPU时间,尤其是在大地图上有大量对象在寻路的时候。如果你阅读过网上的其他材料,你会明白,即使是开发了星际争霸或帝国时代的专家,这也无可奈何。如果你觉得寻路太过缓慢,这里有一些建议也许有效:
* 使用更小的地图或者更少的寻路者。
* 不要同时给多个对象寻路。取而代之的是把他们加入一个队列,把寻路过程分散在几个游戏周期中。如果你的游戏以40周期每秒的速度运行,没人能察觉。但是他们会发觉游戏速度突然变慢,当大量寻路者计算自己路径的时候。
* 尽量使用更大的地图网格。这降低了寻路中搜索的总网格数。如果你有志气,你可以设计两个或者更多寻路系统以便使用在不同场合,取决于路径的长度。这也正是专业人士的做法,用大的区域计算长的路径,然后在接近目标的时候切换到使用小格子/区域的精细寻路。如果你对这个观点感兴趣,查阅我的文章Two-Tiered A* Pathfinding。
* 使用路径点系统计算长路径,或者预先计算好路径并加入到游戏中。
* 预处理你的地图,表明地图中哪些区域是不可到达的。我把这些区域称作“孤岛”。事实上,他们可以是岛屿或其他被墙壁包围等无法到达的任意区域。A*的下限是,当你告诉它要寻找通往那些区域的路径时,它会搜索整个地图,直到所有可到达的方格/节点都被通过开启列表和关闭列表的计算。这会浪费大量的CPU时间。可以通过预先确定这些区域(比如通过flood-fill或类似的方法)来避免这种情况的发生,用某些种类的数组记录这些信息,在开始寻路前检查它。在我Blitz版本的代码中,我建立了一个地图预处理器来作这个工作。它也标明了寻路算法可以忽略的死端,这进一步提高了寻路速度。
4,不同的地形损耗:在这个教程和我附带的程序中,地形只有两种-可通过的和不可通过的。但是你可能会需要一些可通过的地形,但是移动耗费更高-沼泽,小山,地牢的楼梯,等等。这些都是可通过但是比平坦的开阔地移动耗费更高的地形。类似的,道路应该比自然地形移动耗费更低。
这个问题很容易解决,只要在计算任何地形的G值的时候增加地形损耗就可以了。简单的给它增加一些额外的损耗就可以了。由于A*算法已经按照寻找最低耗费的路径来设计,所以很容易处理这种情况。在我提供的这个简单的例子里,地形只有可通过和不可通过两种,A*会找到最短,最直接的路径。但是在地形耗费不同的场合,耗费最低的路径也许会包含很长的移动距离-就像沿着路绕过沼泽而不是直接穿过它。
一种需额外考虑的情况是被专家称之为“influence mapping”的东西(暂译为影响映射图)。就像上面描述的不同地形耗费一样,你可以创建一格额外的分数系统,并把它应用到寻路的AI中。假设你有一张有大批寻路者的地图,他们都要通过某个山区。每次电脑生成一条通过那个关口的路径,它就会变得更拥挤。如果你愿意,你可以创建一个影响映射图对有大量屠杀事件的格子施以不利影响。这会让计算机更倾向安全些的路径,并且帮助它避免总是仅仅因为路径短(但可能更危险)而持续把队伍和寻路者送到某一特定路径。
5,处理未知区域:你是否玩过这样的PC游戏,电脑总是知道哪条路是正确的,即使它还没有侦察过地图?对于游戏,寻路太好会显得不真实。幸运的是,这是一格可以轻易解决的问题。
答案就是为每个不同的玩家和电脑(每个玩家,而不是每个单位--那样的话会耗费大量的内存)创建一个独立的“knownWalkability”数组,每个数组包含玩家已经探索过的区域,以及被当作可通过区域的其他区域,直到被证实。用这种方法,单位会在路的死端徘徊并且导致错误的选择直到他们在周围找到路。一旦地图被探索了,寻路就像往常那样进行。
6,平滑路径:尽管A*提供了最短,最低代价的路径,它无法自动提供看起来平滑的路径。看一下我们的例子最终形成的路径(在图7)。最初的一步是起始格的右下方,如果这一步是直接往下的话,路径不是会更平滑一些吗?
有几种方法来解决这个问题。当计算路径的时候可以对改变方向的格子施加不利影响,对G值增加额外的数值。也可以换种方法,你可以在路径计算完之后沿着它跑一遍,找那些用相邻格替换会让路径看起来更平滑的地方。想知道完整的结果,查看Toward More Realistic Pathfinding,一篇(免费,但是需要注册)Marco Pinter发表在Gamasutra.com的文章
7,非方形搜索区域:在我们的例子里,我们使用简单的2D方形图。你可以不使用这种方式。你可以使用不规则形状的区域。想想冒险棋的游戏,和游戏中那些国家。你可以设计一个像那样的寻路关卡。为此,你可能需要建立一个国家相邻关系的表格,和从一个国家移动到另一个的G值。你也需要估算H值的方法。其他的事情就和例子中完全一样了。当你需要向开启列表中添加新元素的时候,不需使用相邻的格子,取而代之的是从表格中寻找相邻的国家。
类似的,你可以为一张确定的地形图创建路径点系统,路径点一般是路上,或者地牢通道的转折点。作为游戏设计者,你可以预设这些路径点。两个路径点被认为是相邻的如果他们之间的直线上没有障碍的话。在冒险棋的例子里,你可以保存这些相邻信息在某个表格里,当需要在开启列表中添加元素的时候使用它。然后你就可以记录关联的G值(可能使用两点间的直线距离),H值(可以使用到目标点的直线距离),其他都按原先的做就可以了。
另一个在非方形区域搜索RPG地图的例子,查看我的文章Two-Tiered A* Pathfinding。
进一步的阅读
好,现在你对一些进一步的观点有了初步认识。这时,我建议你研究我的源代码。包里面包含两个版本,一个是用C++写的,另一个用Blitz Basic。顺便说一句,两个版本都注释详尽,容易阅读,这里是链接。
* 例子代码:A* Pathfinder (2D) Version 1.71
如果你既不用C++也不用Blitz Basic,在C++版本里有两个小的可执行文件。Blitz Basic可以在从Blitz Basic网站免费下载的litz Basic 3D(不是Blitz Plus)演示版上运行。Ben O'Neill提供一个联机演示可以在这里找到。
你也该看看以下的网页。读了这篇教程后,他们应该变得容易理解多了。
* Amit的 A* 页面:这是由Amit Patel制作,被广泛引用的页面,如果你没有事先读这篇文章,可能会有点难以理解。值得一看。尤其要看Amit关于这个问题的自己的看法。
* Smart Moves:智能寻路:Bryan Stout发表在Gamasutra.com的这篇文章需要注册才能阅读。注册是免费的而且比起这篇文章和网站的其他资源,是非常物有所值的。Bryan用Delphi写的程序帮助我学习A*,也是我的A*代码的灵感之源。它还描述了A*的几种变化。
* 地形分析:这是一格高阶,但是有趣的话题,Dave Pottinge撰写,Ensemble Studios的专家。这家伙参与了帝国时代和君王时代的开发。别指望看懂这里所有的东西,但是这是篇有趣的文章也许会让你产生自己的想法。它包含一些对mip-mapping,influence mapping以及其他一些高级AI/寻路观点。对”flood filling”的讨论使我有了我自己的“死端”和“孤岛”的代码的灵感,这些包含在我Blitz版本的代码中。
其他一些值得一看的网站:
* aiGuru: Pathfinding
* Game AI Resource: Pathfinding
* GameDev.net: Pathfinding
好了,这就是全部。如果你刚好写一个运用这些观点的程序,我想见识见识。你可以这样联系我:
现在,好运!
操作必须使用一个可更新的查询
操作必须使用一个可更新的查询
出错提示:
操作必须使用一个可更新的查询。
win2k win2003 系统
问题应该是服务器数据库目录的写入权限没有设置好。
数据库目录 属性 安全 EVERYONE ……给他写入权限就OK了。
winXP系统
XP操作系统安装好,文件夹选项里面默认使用简单共享(推荐),把这个选项去掉,
再在文件夹上右键点击,就会出现安全这个选项卡,
原来默认的没有,然后在安全选项卡里面可以设置用户的写入权限了
右击数据库>安全>everyone>将需要的勾上
问题表现是提示:操作必须使用一个可更新的查询。
意思就是系统不让更新数据库了,就是对数据库没有写入的权限了。
在2000或XP下的NTFS格式的文件,都有权限设置的,用鼠标右键点文件或文件夹选属性,架设你的网站是在webroot的文件夹里,你就会
看到
这样的界面,你可以看看允许项有没有打钩。
上面的Everyone就是所有人的意思,你可以上这个用户组可以完全控制
那样就不会再有那个错误了。
[ALICESOFT]system3.x的游戏制作方法[上] BY AFIC
system3.x的游戏制作方法[上]
用system作游戏主要分为两个部分:
1制作资料文件
2编写剧本
一般alicesoft的游戏都是把各种资料放在一起,比如图片放在一起做成*ga.ald,midi做成*ma.ald等等
下面介绍一下制作方法。
首先要有开发环境,我能找到的最新版是system3.8,在seein青的alice cd里。
直接拷贝不能使用,需要用写字版编辑*.hed文件,我的设置如下:
我是在g盘建立一个文件夹,比如名字叫aaa
这个文件夹下再建立五个子文件夹:
1是cg工具,用来制作*ga.ald
必要工具包括:
alicecg.exe
nl4.exe
dir.hed
编辑dir.hed
#NL
pms\link.hed
pms\
result\gamega.ald (生成的文件名)
AUTO
#TRIP
BMP\CG_SIZE.CLP
BMP\
PMS\
AUTO
写成这样即可,也可以自己定义……
再在本文件夹(cg工具)下建立三个文件夹
1是bmp
在这里放上
clip.exe
2是pms
在这里建一个
link.hed
3是
result
说明一下,把bmp文件放入bmp文件夹,拖动cg工具文件夹下的dir.hed到
alicecg.exe上释放,就会让你对你所选的bmp文件裁减成你想要的部分。
想要原图,直接按回车就可以了,想裁减的话,用方向键和 空白+方向键
可以改变框选范围。这样就在pms文件夹下生成了pms文件,这时,编辑pms
文件夹下的link.hed,他的写法是
数字,文件名
比如
1,LEFT.PMS
1,RIGHT.PMS
2,RANCE.PMS
2,SILL.PMS
3,MAP1.PMS
这样写,然后拖动cg工具文件夹下的dir.hed到alicecg.exe上释放就在result文件夹下生成了gamega.ald,包含left,right.pms,和gamegb.ald还有gamegc.ald
也就是说数字是指定生成在第几个文件,数字最小是1。
这里生成五个文件和生成一个文件在调用时完全一样。
文件名可以指定的文件有*.bmp和*.pms也就是说你直接在link.hed里写1,left.bmp也可以,
只不过pms是压缩过的,容量小一些。好像还有其他支持的文件格式,比如jpg,我没有试过……
你要记住的是,如果你用bmp生成*ga.ald的话,那么一般要拖动两回dir.hed文件,而且,
以后如果要加文件,也是要拖动两次,加文件后要记得再编辑pms文件夹下的link.hed,
这时(添加过bmp文件并拖拽……)在pms文件夹下会多出一个文件,里边记载了你新增加了哪些文件。
至此,制作*ga.ald的介绍完了。
在前边提到的aaa文件夹下的另一个子文件夹叫做
MIDI工具
里边包括
nl4.exe
dir.hed
origial (文件夹)
dist (文件夹)
dir.hed的写法如下:
#NL
G:\aaa\MIDI工具\origial\link.hed
G:\aaa\MIDI工具\origial\
G:\aaa\MIDI工具\dist\gamema.ald
AUTO
这样,再在origial文件夹下建一个叫link.hed的文件,写法和cg工具的那个完全一样。
都是 数字,文件名 的格式。以后你只要把游戏中用到的midi文件
(必须是windows标准midi文件,88音源什么的不行)放到origial文件夹下,
拖动dir.hed到nl4.exe上就可以在dist文件夹下得到名为gamema.ald的成果了。
alicesoft的背景音乐只能是midi或音轨。
在前边提到的aaa文件夹下还有一个文件夹叫做WAVE工具
它和midi工具唯一的不同是
它的dir.hed的写法是
#NL
origial\link.hed
origial\
dist\gamewa.ald
AUTO
只改了生成文件的文件名,其他完全和MIDI工具一样(子文件夹什么的,当然也有那个link.hed)
在前边提到的aaa文件夹下还有一个文件夹叫做手册,里边放的是找到的各种资料 ,
一般各种说明文件都放在这里就好了。
在前边提到的aaa文件夹下还有最重要的一个文件夹,叫做 开发环境
开发环境下必要的文件是:
Sys38c.exe
Nl4.exe
Sys38ide
Dir.hed
这里dir.hed的写法如下:
#CANT
source\gamecomp.hed
source\
obj\
AUTO END
#NL
obj\gamelink.hed
obj\
exe\gamesa.ald
AUTO
以及三个子文件夹
exe
source
obj
这里是用来制作*sa.ald的,也就是写剧本(编程)的地方
在exe文件夹下放上system38.exe以及system38.ini以及别的地方制作好的*.ald文件。
在source文件夹下
随便建立一个
扩展名为adv的文件,这里举例为
Initial.adv
和gamecomp.hed
该文件的写法是:
#SYSTEM35
Initial.adv
在obj文件夹下建立gamelink.hed
其他就不用管了。
调试程序的过程是这样的:
运行开发环境文件夹下Sys38ide.exe,上边有七个按钮,从左到右是
编译,link,重新编译并执行,直接执行成果,,代debug的执行,未知……,环境设定。
一般按第三个按钮。
至此,完成了制作的70%其他就是编程了,编程就是编写Initial.adv
至于如何编写,我们下次再说……
BY AFIC
[ALICESOFT]SYSTEM3.X的游戏制作方法[中] BY AFIC
SYSTEM3.X的游戏制作方法[中]
再介绍一下如何编写adv文件。由于我看的时间也不多,所以只写一些简单的,其他的在手册里全有。
!var:exp!
var代表变量,exp代表表达式,下同。
这是赋值表达式,意思是将表达式的值赋给变量。
另外,如果需要新定义变量,则直接把一个随便的数赋给新变量就完成定义并赋初值,变量声明必须在使用之
前,可以在程序体的任何位置。system3.x系统中,有一些系统变量,一个是RND(大写,system3.x大小写敏感)
这个用于返回系统函数的返回值,一般在程序的第一行定义并赋初值0。还有的系统变量是从D01到D20的一组变量
也应该在程序开始声明,就算你不用也最好声明……
所以,system系统程序开头都是这样的:
!RND:0!
!D01:0!!D02:0!!D03:0!!D04:0!!D05:0!!D06:0!!D07:0!!D08:0!!D09:0!!D10:0!
!D11:0!!D12:0!!D13:0!!D14:0!!D15:0!!D16:0!!D17:0!!D18:0!!D19:0!!D20:0!
变量的类型吗?system的变量是无符号整形的吧,变量中数不可以超过655535
{exp:命令群}
这个是if语句,表达式成立则执行命令群的命令,否则跳出大括号,复杂的if then 语句的例子是:
{ D01 = 1:’あ’}
else if { D01 = 2:’い’}
else {‘う’}
‘文字’ 的意思是在message窗口上显示指定的文字。
上里的效果就是D01分别等于1,2,3时,对应显示あ,い,う(一时是あ……)
*mmmm:
定义标号,mmmm是标号的名字,名字尽量不用特殊符号如*,:等,用中文有时可以。别忘了最后的冒号。
而且,程序内(每个adv文件)标号不能重名。
@mmmm:
相当于goto语句,执行的操作是跳转到mmmm指定的标号。
¥mmmm:
这个预设标号,推荐不要用,先定义一个标号mmmm(*mmmm:),从别处跳到这个标号时可以预先进行一些操作,
最好不要用,要用的话,函数可以完全代替它。
&#mmmm:
这个是在不同的adv文件之间跳转,mmmm指定adv文件名,千万不要指定路径,直接写文件名就可以了。
%0:
跳转返回,对应&#mmmm:
< var,start,end,sign,step:
>
这个是for循环,start代表初值end代表终值sign为0每次循环变量递减,为1递增step是指定每次递增的值是多少。
<@ exp:命令群
>
这个是while循环。表达式和@符号之间应有空格,只要表达式成立就不断作命令群(也可以是一条命令)的命令,
否则跳出循环。
WW x_size,y_size,color:
dib做成,其实就是设定整个工作范围,这和可视范围是不同的,这里只是指定工作范围的大小,
一般x_size,y_size是可视范围(屏幕上能看见的范围)的整数倍。color = 颜色数
(8=256色,16=65536色,24=1677万色)
WV start_x,start_y,size_x,size_y:
可视范围的设定,定义的是左上角坐标和窗口的长宽。
现在的情况,一般定义窗口的都写成这样:
WW 640,1440,24:
WV 0,0,640,480:
B1,num,X1,Y1,X2,Y2,V:
设定选择肢窗口的坐标,num是窗口号(见紧跟着的B2),x1,、y1是左上角坐标。
x2,y2是场合宽,最多可以同时制定100个窗口(没必要的,定义好的窗口可以用本命令再次指定)
v是如果从子函数跳出 是否把它清除出内存,选1,清,0是不清,因为选择肢窗口某一项被选择后自动关闭
,所以通常选1。
B2,num,W,C1,C2,C3,C4:
这里是指定当前要使用哪个选择肢窗口,通过窗口号(num)指定,w是有无边框,0是没有,1是有。
其他的变量没用,全部写0。
ZS size:
设置选择肢窗口字体,size = 16,24,32,48,64 可用,数越大,字越大……
ZF sw:
设定选择肢窗口是否可变。
sw = 0 選択肢纵向可变(初期値)
sw = 1 選択肢纵向固定
sw = 2 選択肢の横幅固定(初期値)
sw = 3 選択肢の横向取最长的文字的长度
$label$文字列$
定义选择肢,label是标号,在文件中应该有这个标号,对应如果别人选了这项所执行的命令群。
文字列就是在选择肢窗口上显示的字。
]
这个命令就是打开选择肢窗口,预先定义好的选择肢会出现在窗口中。
在它后边也可以加命令,对应右键,esc等取消操作。下边举个例子:
*choice_1: (定义一个标号)
$A01$見る$ (定义三个选择肢)
$A02$聞く$
$A03$話す$
]@choice_1: (打开选择肢窗口,如果按右键泽跳转到开头)
*A01: (选择見る后执行的命令)
’1 was selected’A
@choice_end_1: (見る的命令执行完了,防止执行别的选择肢的命令)
*A02: (选择聞く后执行的命令)
’2 was selected’A
@choice_end_1:
*A03: (选择話す后执行的命令)
’3 was selected’A ;
@choice_end_1:
当然之前要定义选择肢窗口,和定义当前要使用哪个窗口。例如:
B1,1,450,20,172,240,1:
B2,1,1,0,0,0,0:
是在右上角定义一个选择肢窗口。
B3,num,X1,Y1,X2,Y2,V:
这是定义信息窗口,就是显示文字的窗口的位置,x1,y1,x2,y2和前边一样,num是窗口号,
同样只能同时定义100个(1-100),v和B1命令的作用也一样,只不过这里一般选0,不清内存。
也就是说一个子函数打开了一个信息窗口,如果这里选1,则返回主函数,窗口就将被清除。0则是不清除。
其余的B命令都是取窗口坐标的,没什么用,先不介绍。
B4,num,W,C1,C2,N,M:
指定现在使用的message窗口,num是窗口号,w是有无边框,0无1有。
c1,c2没用。N是1清空窗口,0不清。M是0正常,为1的时候为窗口重新显示。
ZM size:
设置message窗口字体,size = 16,24,32,48,64 可用,数越大,字越大……
A
这可能是所有命令中,少数几个结尾不用加冒号的命令之一,意思是先等待按任意健,
如果有键按下(鼠标也算),则清空message窗口的内容。以后如果在显示信息,则从message窗口的左上角显示。
R
换行,不等待有健按下。
IK 0:
等待任何键按下,所以,实现换行,等待有健按下可以写:
‘something’R
IK 0:
‘is nothing…’
而且可以实现确定按下的是哪个键甚至手柄上的哪个按钮:
keyboard mouse joypad
RND = 1 8,↑ ↑
RND = 2 2,↓ ↓
RND = 4 4,← ←
RND = 8 6,→ →
RND = 16 enter 左クリック Aボタン
RND = 32 space 右クリック Bボタン
RND = 64 ESC Cボタン
RND = 128 TAB Dボタン
如果同时按下了多个键,则系统变量RND存储的是它们的逻辑和。
仔细看一下,8个键分别对应8个2进制位,可以用对每一位的测试(逻辑运算)
判断按下的是哪个键。注意,以上的键按下才有效,其他键,程序不往下执行,
如果想具体知道到底是mouse 左クリック还是keyboard enter也有命令,需要的话,我再说明。
还有一个关于光标形状的,我想可能对我们涌出也不大,就没写。
IM cursol_x,cursol_y:
取得鼠标的坐标,当然是按下键以后取得才有意义……
另外RND=16是左键,=32是右键。
IZ start_x,start_y:
鼠标指针移动到指定位置。
H fig,num:
在message窗口上输出数字,fig是表示几位,0是全表示。num是要表示的数字或者变量。 (全角)
HH fig,num:
半角,其他和上边的一样。
J0,x,y:
指定下一幅cg要显示的坐标,显示后,坐标清除。
J1,x,y:
指定下一幅cg要显示的坐标(相对坐标),显示后,坐标清除。
J2,x,y:
指定下一幅cg要显示的坐标,显示后,坐标不清除。
J3,x,y:
指定下一幅cg要显示的坐标(相对坐标),显示后,坐标不清除。
J4:
清除J2,J3设定的坐标。
G num:
显示指定的cg,num是cg号,cg号是如何的来的呢?还记得当时制作*ga.ald的时候,
在pms文件夹下有一个link,hed文件么?比如它是这样的:
1,LEFT.PMS
1,RIGHT.PMS
1,RANCE.PMS
那么如果你要调用rance.pms很简单,
G 3:
就可以了,就是这里的顺序。当然别忘了提前指定要显示的坐标。
G num,c:
这样也可以,那个c的作用是图片上某种颜色视为透明,这样你把图片中不想要的部分涂成一种颜色,
比如黑色,用这个命令指定那种颜色为透明,黑色是0。就可以实现任意形状的图片等。
GX cg_no,shadow_no:
这是更高明的透明方法,仅限于图片是16bitPMS(24的好像也行?)
shadow_no是过滤图片,仅限于256色のPMS
过滤图片用trip.exe作,我还没用过……
**func_name,var1,var2,…:
自定义函数,和定义标号差不多,只不过多代了一些参数。不代参数的函数可以写成:
**func_name :
注意func_name和:之间一定有空格。
~0,cali:
cali设定返回值。
函数结尾返回用
%0:
~func_name,para1,para2,…:
调用函数。
~~变量:
取得刚才调用的函数的返回值。
CB start_x,start_y,lengs_x,lengs_y,color:
画一个矩形。
CF start_x,start_y,lengs_x,lengs_y,color:
实心矩形。
CC source_x,source_y,source_lengs_x,source_lengs_y,destin_x,destin_y:
指定矩形范围的全部东西拷贝到另一个矩形范围,分别指定源矩形的左上角点以及尺寸和目标矩形的左上角点。
CD sorce_x,sorce_y,sorce_lengs_x,sorce_lengs_y,destin_x,destin_y,effect_number,option,wait_flag,color:
也是矩形拷贝,前边的变量都一样。
effect_number是指定特效,这个命令好像只有五个特效,未确定。
option为0是默认值,为数字则是拷贝到指定的窗口中。
wait_flag为1是玩家可以按健不看特效,为0则不能取消。
color透明色。
CE sorce_x,sorce_y,sorce_lengs_x,sorce_lengs_y,destin_x,destin_y,effect_number,
option,wait_flag:
专门的特效拷贝,
effect_number如下:
1 パンイン 上→下
2 パンイン 下→上
3 1ラインとばし上下重ね合わせ
4 1ラインとばし左右重ね合わせ
5 市松模様っぽいの
6 64ドットブロック 外→内
7 外→内
8 内→外
9 じわじわ4×4(4段)
10 縮小→標準ズーム表示(名称変更 99/03/25)
11 すだれ落ち
12 左→右
13 右→左
14 上→下
15 下→上
16 縦ライン:広がり
17 横ライン;広がり
18 縦ライン:内→外
19 縦ライン:外→内
20 横ライン:内→外
21 横ライン:外→内
22 じわじわ2×2(2段)
23 じわじわ2×2(4段)
24 モザイク
25 円形広がり、内→外
26 円形広がり、外→内
27 高精度フェードイン[16/24bit専用]
28 高精度ホワイトイン[16/24bit専用]
29 高精度フェードアウト[16/24bit専用]
30 高精度ホワイトアウト[16/24bit専用]
31 クロスフェード[16/24bit専用]
32 クロスフェードモザイク[16/24bit専用]
33 すだれ上がり
34 すだれ上→中←下
35 上→下クロスフェード[16/24bit専用]
36 下→上クロスフェード[16/24bit専用]
37 左→右クロスフェード[16/24bit専用]
38 右→左クロスフェード[16/24bit専用]
39 上下重ね合わせクロスフェード[16/24bit専用]
40 左右重ね合わせクロスフェード[16/24bit専用]
41 縦ライン内→外クロスフェード[16/24bit専用]
42 横ライン内→外クロスフェード[16/24bit専用]
43 ズームイン
座標は(0,0)固定,必ず全画面処理をする
使用座標パラメータは
sorce_x, sorce_y, sorce_lengs_x, sorce_lengs_y のみ
全画面からパラメータで示されたサイズへズームインする
44 五芒星(内→外)
45 五芒星(外→内)
46 六芒星(内→外)
47 六芒星(外→内)
48 すだれ左→右
49 すだれ右→左
50 風車
51 風車180°
52 風車360°
ライン是line
クロス是cross
フェード是fade(总之就是渲染的那个)
モザイク是马赛克。
还有一些应该看的懂吧,其实,试一下是最好的办法。
手册里还写了关于特效所用的时间的设定等,在マニュアル(手册)项的CE時間補足里。
CM sorce_x,sorce_y,sorce_lengs_x,sorce_lengs_y,
destin_x,destin_y,destin_lengs_x,destin_lengs_y,mirror_switch:
这个是代扩大缩小反转效果的拷贝,同时指定了原和目的矩形的左上角点以及尺寸。
mirror_switch是一个两位数,每位只能取0或1
第一位为1则上下反转
第二位为1则左右反转
为0则不反转
CP start_x,start_y,color:
指定颜色(256色)画一个点(ペイント)
CS sorce_x,sorce_y,sorce_lengs_x,sorce_lengs_y,destin_x,destin_y,splite:
代透明色的拷贝,CD好像也行呀?如果是只用代透明色的拷贝,还是使这个,以提高编码效率。
DC page_no,size,save_flag:
在内存中开出一片空间,
page_no 空间号码 (1~256)
size 大小(字节)(1~65536)
save_flag 1是以后存进度命令会把这个范围的数据存进记录,0则不存。
DS poin_var,data_var,位置,page_no:
在开出的空间上申请数组。
poin_var 随便一个变量就可以,系统把它用作指针。
data_var 数组名,需要是以前定义过的变量!
位置 从空间的第几个内存开始使用
page_no 同上。
要使用数组的化,用
数组名[序号]
引用变量,还有一种方法,就是先给指针变量赋值为序号,然后可以通过数组名直接引用。
map[x*y] = 10
!poin_var:x*y! !map:10!
上边两者等价,map数组名,x*y序号,poin_var指针变量
DR data_var
清除数组
DF data_var,count,data
给数组集体赋值
count是个数,是从开头起的
data是赋的值。
ES num,c,x1,y1,x2,y2:
设定清屏范围
EC num:
num是0为全屏,数字则是ES指定的范围。
网络部分略过……
想要的话告诉我……
LC x,y,文件名:
读取外部的cg文件,之前要用J指定坐标。
QD num:
存记录,没用过……
LD num:
读记录,没试过……
PD num:
指定cg的亮度,只限pms
num = 0~255
0黑
255和平时一样
SL num:
演奏的cd中的曲子的循环次数,0无限。
SS num:
cd 演奏
num = 曲番号 (1~1024) , num=0で音楽停止
SG 0,0:
演奏中的midi停止
SG 1,num:
midi演奏,num的指定就是midi工具original中link.hed指定的顺序,参见前边cg序号说明。
SG 3,0:
midi演奏暂停
SG 3,1:
暂停的恢复
ST time:
wave停止,time是停止的秒数,0为永远
SM no:
把wave读进内存,no是wave的号码。
SP no,loop:
演奏wave,loop是循环次数,一般应该是1吧。
SQ noL, noR, loop:
指定声道演奏wave。
SI 0,var:
检测MIDI是否可用,结果放到var中
var =
0:不可用
1:可用
SI 1,var:
检测WAVEが使用可能情况
var =
0:不可用
1:可用
SI 2,var:
检测CD是否可用
var =
0:不可用
1:可用
还有一些重要的操作,下次再说吧。
BY AFIC
[ALICESOFT]SYSTEM3.X的运算 BY AFIC
SYSTEM3.X的运算
[ALICESOFT]System3.x的游戏制作方法[下] BY AFIC
System3.x的游戏制作方法[下]
继续讲解如何编制adv文件:
UP 2,work_dir,file_name:
这个是调用外部的程序,须指定工作目录和文件名。
ZA 0,type:
这是设定整个系统的字体效果的:
type = 種類
0 :通常
1 :影付き(下のみ)
2 :影付き(右のみ)
3 :影付き(右下)
4 :アウトライン付き(轮廓)
5 :—————-
6 :太字(横)(大字)
7 :太字(縦)
8 :太字(全)
9 :アンダーライン(下画线)
10:(3)+(4)
ZB VAR:
文字大小设定,var取0-9默认是7
ZC m,n:
系统使用环境设定,m是本命令的作用,n是对所选项指定颜色0-255。
m = 0 调色版bank変更 ;初期値 = 1
m = 1 文字色変更 ;初期値 = 255
m = 2 選択肢颜色変更 ;初期値 = 255
m = 3 選択肢遍框颜色変更 ;初期値 = 255
m = 4 選択肢框内颜色変更 ;初期値 = 0
m = 5 message框的颜色変更 ;初期値 = 255
m = 6 message框内颜色変更 ;初期値 = 0
m = 7 Hit any key的那个图标的颜色指定 ;初期値 = 255
m = 10 選択肢光标形状設定
m = 11 選択文字列色設定(n = 0~255)
m = 13 message背景透過指定(n = 0~255)
m = 14 選択肢背景透過指定(n = 0~255)
m = 15 设定默认选择值的号码。
对于m=15时,
n = 0 鼠标指针移动到最近的选择肢
n = 数值 移动到指定的那个选择肢,大于实际选择肢数则移动到最后一项。
n = 1000 不移动
ZE sw:
这个是指定如果选择了某个选择肢,是否同时清空message窗口。
sw = 0 不清空
sw = 1 清空(初期値)
ZH switch:
全角半角切替え
switch 0 = 全角
1 = 半角
2 = 無変換
ZR num,変数:
产生0-num范围的随机函数。
ZZ 0,0:
结束system程序,返回操作系统,一般用这个,别的没用。
ZT 0,var:
現在の日時を var0~var6 の変数列に返す。
var0 = 年(1980~2079)
var1 = 月(1~12)
var2 = 日(1~31)
var3 = 時(0~23)
var4 = 分(0~59)
var5 = 秒(0~59)
var6 = 曜日(0~6:日曜日~土曜日)
意思很简单,取得现在的日期,如果你指定的var是d01那么,var0就是d01,var1就是d02,var2就是d03以此类推。
ZT 1,n:
清除计时器n
ZT 10,num,base,count:
设定计时器,计时是基值乘以设定值
num
番号(1~256)
0的话设定所有的计时器
base
基值
1是1/1000秒
10是1/100(=10/1000)秒
任意の値を指定出来る
最大65535
count
设定值,1/1000秒情况,这里无效。
ZT 20,num:
使用计时器,系统在对应计时器设定的时间内进入休眠状态,任何输入无效。但输入在RND中保存。
ZT 21,num:
和上边的一样,使用计时器,但可以按健取消,输入在RND中保存。
至此编程基本上讲完了,基本上是没讲文件操作和数据表格,文件操作是对外部文件进行读写操作等,数据表格是作slg的时候,需要写大量的人物资料,可以通过它按一定格式存储大量数据。如果需要用到哪个我再补上。
补充:
所有命令都是大写的,决大多数最后都有冒号。
最重要的是,system系统内是不支持中文的,如果你写如下语句:
‘成功了,兰斯是白痴。’A
放心,编译100%出错,这怎么办?很简单,解决方法有二。
1先在你要写的话处写上诸如ranceword_1,sillword_2之类的。
编辑生成的ald文件,把这些改成想要的中文即可。
2利用word2000,把你想写的中文写上,选文件,另存为编码文件,选shift-jif,在预览中看没有错误就保存,否则,就换一些意思相近的字,比如,岁这个字在日本没有,它有的是一个和才差不多的字,那么,干脆这个岁就不写了,反正20多和20多岁没什么区别。这样存好编码文件后,打开那个编码文件,把里边的东西(你看一定是乱码)考出来放到相应位置就可以了。我就是采取此法。因为第一种太冒险,而且显示的时候还须开内码转换工具,但你那边操作系统不同,可以两种方法都试试。
最快的学习方法是学源文件,任何一个开发包里都有3个源文件,alice 馆456里更有原版alice游戏的源文件,建议从那三个看起,其实那三个例子看明白了也就差不多了。
在alice馆456里赠送了很多midi资料。
一般读取一副cg都是放在最上边的,这样,可以拷贝被盖掉的部分到看不见的区域,以后,改变显示的时候,再把它考回来,因为拷贝速度很快,所以,根本看不出来。这相当于擦画原理(擦了再画)
全文完
BY AFIC
[ALICESOFT]SYSTEM3.9 网络部分 BY AFIC
KI var,port_num,user_max:
显示网络连接对话框,执行这个命令后自动弹出对话框,要求用户选择自己做服务器(server),还是做客户端(cilent)连别人。
var用来放返回结果 0=取消连接 1=当服务器新建连接 2=作 cilent连别人
port_num是端口号1024到5000随便指定。
user_max是最大可连入的人数。
KN var: 取得自己的连接号码,返回值放在var中 var=0 未连接 var=1 自己做服务器 var>1 自己是cilent
KK user_num: 断开和user_num的连接。
KP var: 监测有无收到数据,var为0无1有
KQ var, user_num:
监测user_num号的人的连接情况, var=0未连接 car=1连接 KW var,num: 发送数据,var是最开始的变量,num是变量的个数 本命令是对连接的所有人发数据,接受侧需要对数据可用性作判断,废弃无用数据,如果想发送文字,需要用MN命令进行数值变换。
KR var: 从缓存读取数据,var是第一个变量的变量名,同样涉及文字的时候要用到mn命令。
LD num: 读纪录,num是纪录号,1对应sa.asd,2对应sb.asd,类推。返回值保存在RND中,RND=0则成功,255则失败。RND〉200就需要检查。
LP num,point,count: 部分读入,num和上边一样,point是第一个变量的位置,count是读入变量的个数,返回值和上边一样保存在RND里边。
QC num1,num2: 把存档num2的内容复制到num1中,然后RND = 1 则成功,RND = 255则失败。
QD num: 存档到num号记录中,对应关系及结果和LD相同。
QP num,point,count: 部分存档,和LP基本相同。 举个例子 QP1,D10,10: ;D10-D20 就是把D10到D20的内容存盘。 RND = 1 则成功,RND = 255则失败。
saveDeleteFile eNum, vResult: 删纪录 eNum是纪录号(1到26对应a到z) vResult 对应结果0失败,1成果。
by AFIC
[ALICESOFT]SYSTEM3.X的DLL调用方法 BY AFIC
SYSTEM3.X的DLL调用方法
system39增加了可以调用DLL文件的重大变化,但39不能直接使用DLL中的函数等等,反过来,DLL中也不能用脚本中写的函数等,要实现互相调用是通过新增的39服务和DLL服务实现的,两者互相调用对方的服务而不是对方内部的东西,这算是程序封装性的体现。
1)39服务
所谓39服务就是system39提供给外界一种访问system39的接口,下边列举一下:
ISys3x
ISys3x是为了访问ISys3x系接口的接口
ISys3xDIB
ISys3xDIB是访问关于显示工作空间的一系列接口,是ISurface的派生。
ISys3xCG
ISys3xCG顾名思义,CG类的接口。
ISys3xMsgBuf
基本上和MG命令相当。
ISys3xSystem
取的系统中的各种标志位。
ISys3xMsgString
利用了MG命令显示的文字就要用这个接口获得。IString的派生。
IWinMsg
windows消息处理的接口。
ITimer
获得时间方面数据的接口。
IUI
取得电脑上使用者的一些记录的接口。
2)DLL服务
既是脱离39的一些库的总称,一个DLL提供一种服务。一览:
Graph
图像处理等。对应24位色。没有特效拷贝处理。
GrEffect
交叉渲染等的处理。
GLEffectCopy
相当于CE命令。
DrawText
有非交互性文字相关的服务。24位色对应。有拷贝特效处理。
SurfaceFactory
提供Graph/GrEffect等所要利用到的一些服务。
BY AFIC
[LINUX]Ubuntu LINUX

《Mandriva Linux Limited Edition 2005》(Mandriva Linux Limited Edition 2005)完全正式发行版(零售版)-国际版[ISO]
关于API HOOK拦截封包原理
作者:不详 来源于:TTee.com 外挂网
http://soft.ttee.com/Article/Catalog32/95.html
我自己做的apihook,是用了陷阱式和导入表式封装在同一个类里的。源代码还没整理,而且是用delphi编写的。本人最近忙其他一个程序,加上工作忙,所以现找来网上的一篇关于apihook的文章。
本论坛很多朋友是用C++的,所以转贴了一篇C++的,原理写的蛮清楚的,用的HOOK方式是陷阱式的。
PS:大名鼎鼎的WPE就是一个优秀的API Hook,怎么样?你也可以编个WPE出来:)
===========================
利用hook截获进程的API调用
作者:Redspider
截获API是个很有用的东西,比如你想分析一下别人的程序是怎样工作的。这里我介绍一下一种我自己试验通过的方法。
首先,我们必须设法把自己的代码放到目标程序的进程空间里去。Windows Hook可以帮我们实现这一点。SetWindowsHookEx的声明如下:
HHOOK SetWindowsHookEx(
int idHook, // hook type
HOOKPROC lpfn, // hook procedure
HINSTANCE hMod, // handle to application instance
DWORD dwThreadId // thread identifier
);
具体的参数含义可以翻阅msdn,没有msdn可谓寸步难行。
这里Hook本身的功能并不重要,我们使用它的目的仅仅只是为了能够让Windows把我们的代码植入别的进程里去。hook Type我们任选一种即可,只要保证是目标程序肯定会调用到就行,这里我用的是WH_CALLWNDPROC。lpfn和hMod分别指向我们的钩子代码及其所在的dll,dwThreadId设为0,表示对所有系统内的线程都挂上这样一个hook,这样我们才能把代码放到别的进程里去。
之后,我们的代码就已经进入了系统内的所有进程空间了。必须注意的是,我们只需要截获我们所关心的目标程序的调用,因此还必须区分一下进程号。我们自己的钩子函数中,第一次运行将进行最重要的API重定向的工作。也就是通过将所需要截获的API的开头几个字节改为一个跳转指令,使其跳转到我们的API中来。这是最关键的部分。这里我想截三个调用,ws2_32.dll中的send和recv、user32.dll中的GetMessageA。
DWORD dwCurrentPID = 0;
HHOOK hOldHook = NULL;
DWORD pSend = 0;
DWORD pRecv = 0;
GETMESSAGE pGetMessage = NULL;
BYTE btNewBytes[8] = { 0x0B8, 0×0, 0×0, 0×40, 0×0, 0x0FF, 0x0E0, 0 };
DWORD dwOldBytes[3][2];
HANDLE hDebug = INVALID_HANDLE_value;
LRESULT CALLBACK CallWndProc( int nCode, WPARAM wParam, LPARAM lParam )
{
DWORD dwSize;
DWORD dwPIDWatched;
HMODULE hLib;
if( dwCurrentPID == 0 )
{
dwCurrentPID = GetCurrentProcessId();
HWND hwndMainHook;
hwndMainHook = ::FindWindow( 0, “MainHook” );
dwPIDWatched = ::SendMessage( hwndMainHook, (WM_USER+100), 0, 0 );
hOldHook = (HHOOK)::SendMessage( hwndMainHook, (WM_USER+101), 0, 0 );
if( dwCurrentPID == dwPIDWatched )
{
hLib = LoadLibrary( “ws2_32.dll” );
pSend = (DWORD)GetProcAddress( hLib, “send” );
pRecv = (DWORD)GetProcAddress( hLib, “recv” );
::ReadProcessMemory( INVALID_HANDLE_value, (void *)pSend, (void *)dwOldBytes[0], sizeof(DWORD)*2, &dwSize );
*(DWORD *)( btNewBytes + 1 ) = (DWORD)new_send;
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pSend, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize );
::ReadProcessMemory( INVALID_HANDLE_value, (void *)pRecv, (void *)dwOldBytes[1], sizeof(DWORD)*2, &dwSize );
*(DWORD *)( btNewBytes + 1 ) = (DWORD)new_recv;
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pRecv, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize );
hLib = LoadLibrary( “user32.dll” );
pGetMessage = (GETMESSAGE)GetProcAddress( hLib, “GetMessageA” );
::ReadProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)dwOldBytes[2], sizeof(DWORD)*2, &dwSize );
*(DWORD *)( btNewBytes + 1 ) = (DWORD)new_GetMessage;
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize );
hDebug = ::CreateFile( “C:\\Trace.log”, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 );
}
}
if( hOldHook != NULL )
{
return CallNextHookEx( hOldHook, nCode, wParam, lParam );
}
return 0;
}
上面的钩子函数,只有第一次运行时有用,就是把三个函数的首8字节修改一下(实际上只需要7个)。btNewBytes中的指令实际就是
mov eax, 0×400000
jmp eax
这里的0×400000就是新的函数的地址,比如new_recv/new_send/new_GetMessage,此时,偷梁换柱已经完成。再看看我们的函数中都干了些什么。以GetMessageA为例:
BOOL _stdcall new_GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax )
{
DWORD dwSize;
char szTemp[256];
BOOL r = false;
//Watch here before it’s executed.
sprintf( szTemp, “Before GetMessage : HWND 0x%8.8X, msgMin 0x%8.8X, msgMax 0x%8.8x \r\n”, hWnd, wMsgFilterMin, wMsgFilterMax );
::WriteFile( hDebug, szTemp, strlen(szTemp), &dwSize, 0 );
//Watch over
// restore it at first
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)dwOldBytes[2], sizeof(DWORD)*2, &dwSize );
// execute it
r = pGetMessage( lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax );
// hook it again
*(DWORD *)( btNewBytes + 1 ) = (DWORD)new_GetMessage;
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize );
//Watch here after it’s executed
sprintf( szTemp, “Result of GetMessage is %d.\r\n”, r );
::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 );
if( r )
{
sprintf( szTemp, “Msg : HWND 0x%8.8X, MSG 0x%8.8x, wParam 0x%8.8X, lParam 0x%8.8X\r\nTime 0x%8.8X, X %d, Y %d\r\n”,
lpMsg->hwnd, lpMsg->message,
lpMsg->wParam, lpMsg->lParam, lpMsg->time,
lpMsg->pt.x, lpMsg->pt.y );
::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 );
}
strcpy( szTemp, “\r\n” );
::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 );
//Watch over
return r;
}
先将截获下来的参数,写入到一个log文件中,以便分析。然后恢复原先保留下来的GetMessageA的首8字节,然后执行真正的GetMessageA调用,完毕后再将执行结果也写入log文件,然后将GetMessageA的执行结果返回给调用者。
整个截获的过程就是这样。你可以把其中的写log部分改成你自己想要的操作。这里有个不足的地方是,截获动作是不能够并发进行的,如果目标进程是多线程的,就会有问题。解决办法是,可以在每次new_GetMessage中加入一个CriticalSection的锁和解锁,以使调用变为串行进行,但这个我没有试验过。
