作者:寰子
来源:http://www.hgechina.com/
前言: 写道:

无意中发现了hge中文社区,听朋友介绍,认识了hge,然后开始对它进行研究,并使用hge开始制作游戏。
因为我所得的资料基本上都是来源于各位高手的无私翻译,所以,我打算也做一些贡献出来,在这里写一篇hge的基础教程,供刚接触hge的朋友研究学习。
教程中可能会出现一些错误,请大家及时指正。由于我也是初学者,包括c++,也只是一个初学者,所以写出的教程可能质量并不高,也请大家谅解。
另外,学习此教程需要c++的基本知识,不需要非常高深,只需要了解函数调用、结构体、枚举、常量、变量、类的基本知识、函数指针等基本知识即可。

关于转载:
本教程可以随处转载,我的名字就不用了,不过请著名来源于hgechina中文论坛:http://www.hgechina.com/,我们将对此表示感谢。

我使用的编译器:
我使用的编译器是microsoft的visual c++ 2005,但是visual c++ 6.0或者visual c++ 2003等visualstudio的c++编译器都是可以使用hge的,另外,borlandc++也是可以使用hge的,本教程将使用visual studio 2005为大家做讲解,如果有一些编译器问题上的出入,请大家搜索或者提问解决。

hge基础教程第一章:初识hge

c++有一个理念,我记得是在我看c++ primer学习时看到的,那就是一次代码编写,处处使用,所以,就出现了诸如dll,lib等便于大家使用的代码库。其思想基本就是把写好的代码打包,然后只要在工程中链接lib或dll,就可以使用dll或者lib中写好的代码,它们的出现,为资源共享创造了空前的便利。那么,hge也是贯彻这个思想的一个库,它把directx进行封装,方便大家使用,由于直接使用directx需要直接接触其他硬件 上的东西,所以不利于入门,hge的出现,让高效图形编程成为了简单的东西。

关于免费:
hge是一个完全免费的,并且开源的引擎,所以我们可以随便使用在自己的商业或非商业项目,在此,我们也向hge的编写者致敬。

激动人心的时刻:第一个hge程序。

创建工程:
我们在visual c++中新建一个win32工程,选择win32控制台程序/win32 console,为你的项目起一个名字。选择一个目录保存你的项目,点击确定-下一步。
选择windows应用程序,并选择“空项目”/“empty project”。点击确定,工程创建完毕,项目文件和代码就保存在刚才你选择的目录里。

工程的设置:
创建好工程后,我们需要进行一些设置,在左边的解决方案 管理器中,看到你的项目名称,点右键,选择【属性r】
打开属性窗口后,点击c/c++,将右边的附加包含目录设置到你存放hge引擎头文件的目录下,一般情况下,下载了hge引擎解压后,在hge目录下的include目录下。
设置完成后,再双击左边的链接器,打开子选项后,点【常规】,右边的附加库目录,选到hge目录下的lib/vc/目录
再点击左边的

此文来自: 马开东博客 转载请注明出处 网址: http://www.makaidong.com
【输入】,右边附加依赖项:hge.lib、hgehelp.lib
如此一来,工程就设置好了。

好了,我们首先创建一个任意名字的.cpp文件。敲入以下代码:

另:解释,我就不写了,程序很简短,注释也很清楚。大家编译即可创建出第一个hge程序了。怎么样,很简单吧!

代码: 写道:

#include "hge.h"//包含hge头文件

hge *hge=0;//创建一个指向hge类的指针。

bool renderfunc()//绘制函数,程序开始后hge将不停调用它
{
return false;//程序正常时总是返回false,返回true将从system_start往下执行
}
bool framefunc()//逻辑函数,程序开始后hge将不停调用它,一些逻辑判断或者处理可以写在这里。
{
return false;//总是返回false
}

int winapi winmain(hinstance, hinstance, lpstr, int)//winmain函数,程序的入口。
{
hge=hgecreate(hge_version);//使用hgecreate函数创建hge接口,参数必须传递正确的hge_version,它是在hge.h中定义的
hge->system_setstate(hge_screenwidth, 800);//将屏幕宽度设置为800
hge->system_setstate(hge_screenheight,600);//将屏幕高度设置为600
hge->system_setstate(hge_framefunc, framefunc);//设置逻辑函数为framefunc函数
hge->system_setstate(hge_renderfunc,renderfunc);//设置绘制函数为renderfunc函数
hge->system_setstate(hge_title, "我的第一个hge程序");//设置窗口标题为“我的第一个hge程序”
hge->system_setstate(hge_windowed,true);//设置使用窗口模式
hge->system_setstate(hge_usesound,false);//设置不使用声音(第一个程序我们先不讲解声音和图像的知识)
if(hge->system_initiate())//用hge类的system_initiate()方法,检测初始化是否有错误出现。
{
hge->system_start();//如果没有问题,则使用system_start方法,开始程序。
}
hge->system_shutdown();//程序停止
hge->release();//释放hge所占用的内存。
}

以上代码应该比较简单易懂,那么,我说一点小东西,是大家需要留意一下的。
一:请注意hge headfile和dll的版本匹配,如果两个东西的版本不匹配的话,程序是会出错的,举个例子,比如你用了hge 1.7的头文件,而使用hge 1.8的dll,那么就会报错。
二:如果你发现了程序开始会有hge的logo,那么不用担心,它并

此文来自: 马开东博客 转载请注明出处 网址: http://www.makaidong.com
不是需要付费才可以解除的,我们只需要加入这一句:h->system_setstate(hge_showsplash,false);即可解除掉程序开头hge的logo。
三:关于framefunc和renderfunc的返回值,renderfunc需要一直返回false,这貌似是hge的一个规则,必须这么写,但是framefunc就不一样了,如果返回了true,程序就会从hge->system_start();函数下面继续运行,运行到了shutdown,程序就结束了。所以,有时候返回true是结束程序的好方法,而不是随地乱扔system_shutdown和system_release函数。

hge基础教程第二章:可以看到的东西--图像显示

图像显示,是所有程序,包括windows操作系统在内必不可少的部分,代码编写者(程序员)将美工绘制好的图像显示到程序中,让使用者更加直观的看到程序的意图。废话就不多说了,这里我们来讲一下在hge中,怎么显示图像,我相信跟我一样,大多数朋友在刚刚接触一个图像引擎的时候最关心的就是这个部分。ok,我们看下去。

htexture:
这是hge中定义的一个数据类型,用来保存我们加载的图像,texture的意思是“纹理”,说白了就是图片,当然,当你的程序越来越复杂的时候,你会很乐意接受这种很规范的命名。我们现在只需要知道,这是一个数据类型,至于怎么把图像加载、释放、显示,我们下面一一道来

屏幕上的精灵--hgesprite类:
精灵,看起来很有意思的名字,但是很简单,它就是呈现图像的一个工具,每一个精灵都有一些属性,包括它的图像,关于这个,可以在置顶贴中找到hgesprite类的全面资料。并且附加了代码。

灵魂部分:代码
说了那么多,不如一段代码来的实在,同样,我将保留以上简洁明了的代码风格,让大家很方便的读懂它,并且会有详尽的注释:
另外,请用具体的参数替代代码中红字部分

代码: 写道:

#include "hge.h"
#include "hgesprite.h"
hge *hge=0;//创建hge指针
hgesprite *spr;//创建精灵类指针
htexture tex;//定义一个texture(纹理)对象

bool renderfunc()//绘制函数,程序开始后hge将不停调用它
{
hge->gfx_beginscene();//开始渲染
hge->gfx_clear(0xff000000);//以某颜色清屏,oxff000000为透明度为0的黑色
spr->render(显示位置x,显示位置y);//在指定位置显示精灵
hge->gfx_endscene();//结束渲染
return false;//总是返回false
}
bool framefunc()//逻辑函数,程序开始后hge将不停调用它,一些逻辑判断或者处理可以写在这里。
{
return false;//程序正常时总是返回false,返回true将从system_start往下执行
}

int winapi winmain(hinstance, hinstance, lpstr, int)//winmain函数,程序的入口。
{
hge=hgecreate(hge_version);//使用hgecreate函数创建hge接口,参数必须传递正确的hge_version,它是在hge.h中定义的
hge->system_setstate(hge_screenwidth, 800);//将屏幕宽度设置为800
hge->system_setstate(hge_screenheight,600);//将屏幕高度设置为600
hge->system_setstate(hge_framefunc, framefunc);//设置逻辑函数为framefunc函数
hge->system_setstate(hge_renderfunc,renderfunc);//设置绘制函数为renderfunc函数
hge->system_setstate(hge_title, "显示图像");//设置窗口标题为“显示图像”
hge->system_setstate(hge_windowed,true);//设置使用窗口模式
hge->system_setstate(hge_usesound,false);//设置不使用声音(第二个程序我们先不讲解声音的知识)
if(hge->system_initiate())//用hge类的system_initiate()方法,检测初始化是否有错误出现。
{
tex=hge->texture_load("图片路径和后缀,这里是相对目录,vc++2005是debug目录");//根据路径载入图片
if(tex)//检测是否图片成功载入
spr=new hgesprite(tex,图片的显示起始位置x,起始位置y,图片宽,图片高);//初始化精灵spr,并且指定tex为它的纹理
hge->system_start();//如果没有问题,则使用system_start方法,开始程序。
}
hge->texture_free(tex);//释放纹理
delete spr;//释放精灵
hge->system_shutdown();//程序停止
hge->release();//释放hge所占用的内存。
}

编译运行代码后,是不是看到你指定的图片被显示在了屏幕上呢?很简单吧,游戏制作的第一步,就这样轻松的被我们迈了出去。
寰子的话:凌晨1:30了,累死我了,我才发现写教程不是最累的,最累的是排版……

hge基础教程第三章:可以听到的--声音播放

heffect:
这是hge中的一个数据类型,就像htexture一样,但是它是用来保存载入的声音的,而htexture是用来载入保存的纹理(图像)的。我们根据以上的一章,知道了载入纹理是需要用texture_load函数的,那么,针对heffect数据类型,也有一个函数用来专门载入声音。

effect_load函数
这是一个有返回值的函数,返回的值便是一个heffect数据,于是,我们可以写:snd=effect_load("source/music.mp3");

effect_free函数
这是一个没有返回值的函数,看函数的名字,free,也就是释放了,那么这个函数不难理解,就是释放已经载入的声音数据,作用基本同于delete。

effect_play函数
这个函数的返回值是一个陌生数据类型,这个数据类型叫做hchannel,什么意思呢?就是音轨的意思,我们知道,如果有5个同时播放而互不影

此文来自: 马开东博客 转载请注明出处 网址: http://www.makaidong.com
响的音乐,那么,它们处在5个不同的音轨上。那么,这里也会返回一个音轨,方便我们来对这个音轨上播放的文件进行操作,诸如:“暂停,播放,继续,停止”等。

实例代码:
以上简单列举了一下声音播放要接触到的几个基本函数,当然,我们首先让程序出声就可以了,随后再去考虑变调、变速、或者多轨混合等等这些东西。

代码: 写道:

#include "hge.h"

hge *hge=0;//创建hge指针

heffect snd;//定义一个effect(音效)对象

bool renderfunc()//绘制函数,程序开始后hge将不停调用它
{
return false;//总是返回false
}
bool framefunc()//逻辑函数,程序开始后hge将不停调用它,一些逻辑判断或者处理可以写在这里。
{
if(hge->input_getkeystate(hgek_a))//检测是否按下了a键
hge->effect_play(snd);//如果按下了a键,则播放音乐snd
return false;//程序正常时总是返回false,返回true将从system_start往下执行
}

int winapi winmain(hinstance, hinstance, lpstr, int)//winmain函数,程序的入口。
{
hge=hgecreate(hge_version);//使用hgecreate函数创建hge接口,参数必须传递正确的hge_version,它是在hge.h中定义的
hge->system_setstate(hge_screenwidth, 800);//将屏幕宽度设置为800
hge->system_setstate(hge_screenheight,600);//将屏幕高度设置为600
hge->system_setstate(hge_framefunc, framefunc);//设置逻辑函数为framefunc函数
hge->system_setstate(hge_renderfunc,renderfunc);//设置绘制函数为renderfunc函数
hge->system_setstate(hge_title, "播放声音");//设置窗口标题为“播放声音”
hge->system_setstate(hge_windowed,true);//设置使用窗口模式
hge->system_setstate(hge_usesound,true);//设置使用声音
if(hge->system_initiate())//用hge类的system_initiate()方法,检测初始化是否有错误出现。
{
snd=hge->effect_load("声音路径和后缀,这里是相对目录,vc++2005是debug目录");//根据路径载入声音
if(snd)//检测声音是否已经载入成功
hge->system_start();//如果没有问题,则使用system_start方法,开始程序。
}
hge->effect_free(snd);//释放声音资源
hge->system_shutdown();//程序停止
hge->release();//释放hge所占用的内存。
}

这基本就是最简单的声音播放了,根据以上学过的知识,我再写出几个需要注意的东西,第三章就结束了:
1:使用hgesprite类,htexture,heffect数据类型时,都不要忘记在不需要的时候或者程序结束的时候进行释放。
2:一定要在使用资源以前检测资源是否被载入,否则在使用资源操作函数时程序会出错退出。(如以上代码中的if(tex)....)

hge基础教程第四章:显示顺序的仲裁者--z-buffer

z-buffer:
z-buffer又被称为深度缓冲,不要觉得深奥,我一一道破它的秘密。2d空间中,我们习惯了x,y两轴的坐标,聪明的朋友可能一眼就知道z是用来做什么的,没错,既然非要给2d空间安上一个z坐标轴,那么它的作用,就是决定2d空间上的对象的前后顺序,前面的挡住后面的。有人说了:那直接改变绘制顺序不就好了,回答是:这样的程序是在编译时决定,而z,是在运行时决定,这就意味着程序的灵活性将又有一个质的飞跃,你觉得哪个好呢?

开启2d空间中的第三根坐标轴:
不难发现,hgesprite也好,hgefont也好,都有一个方法:setz(float z);那么,可能有的朋友也试过,为啥不管用呢?可能你忽略了一步:打开z-buffer功能。如何打开呢?很简单,在开始的时候,使用加上以下一句:
hge->system_setstate(hge_zbuffer,true);
这样,z-buffer的功能就被启用了。

z-buffer的范围:
z-buffer虽然是一个float,但是,它的有效范围只在0.0-1.0之间,至于为什么,我就不知道了,需要去问directx了,呵呵。在0.0-1.0这个范围内,z值越小的对象,将被越靠前绘制,举个例子:
两个精灵,spritea,spriteb,他们的z值分别是0.1,0.2
这样的话,不管你render的顺序是什么,他们的覆盖顺序都是spritea覆盖spriteb。

为什么要使用z-buffer:

本来我已经困了,打算明天再写,不过想了想,这节也没太多东西,一顺儿写完吧!

好,回归正题。除了刚才说到的“编译时决策”和“运行时决策”的问题,使用z-buffer又有一个好处,那就是它可以优化速度。拿刚才spritea和spriteb的例子来说,
如果按我们的老思路,如果要a覆盖b,那么绘制的顺序是:renderb,rendera,由远及近,远处的先画,近处的后画,近处的覆盖远处的,就成了一个覆盖顺序正确的图像。那么,既然有了z-buffer,我么就要颠覆一下这套理论了,我们要先rendera,再renderb,为什么呢?因为当你rendera再renderb的时候,directx会自己判断,有些地方已经有a的存在了,所以b就不需要在那些地方绘制东西了,直接跳过,小图不说,如果是两张满屏的大图呢?试想一下要节约多少fps?这样的方法被称为逆向画家算法,但是,逆向画家算法也不是随便用的,必须要有z-buffer的支持,而且,最重要的是,z-buffer的顺序要设置正确,比如,a覆盖b,那么a的z值要比b小,也就是更靠前一些,然后颠倒正常的绘制顺序,就达到了我们优化的目的。

说到这里,一定有人问了,能优化到什么程度呢?我说一下我的试验吧,一张800*600的满屏图,前面5000个50*100的动画小人不停走动,而且是每次都clear屏幕全部重绘的实时刷新,我是geforce go 7200的显卡,fps达到了30+fps。拿hge带的例子比较,我在我的机器上试了,它的小人比我的小,到2000个的时候,就是30fps,再往上就卡得要死了,当然,也是没有经过任何优化,这么以来,大家比一比,结果就出来了。

以上的东西我已经尽力写的很明白了,如果还有看不懂的就回帖提问吧!另外,毕竟我也是新手,老手勿笑!

hge基础教程第五章:你需要知道的更多一些

hgesprite的常用方法之--sethotspot:
sprite,翻译过来是精灵,它是为我们来呈现texture的一个类,开始在图像显示的时候我也在代码里用到了这个类,这是我们在显示图像的时候经常用的东西,可能很多方法大家都不太了解,我现在说几个常用实用的。首先,我们知道,图像在屏幕上呈现的位置通常由一个坐标控制,这个坐标相对于屏幕左和上边缘,那么,相对于图像呢?可能有的人习惯认为图像的坐标相对于图像就是图像左上角的位置了,但是又有的人习惯把图像的中心作为这个决定位置的点,别怕,hgesprite都会满足我们的要求,无论多么苛刻,那就是sethotspot方法,使用这个方法,你可以为你的精灵设置一个热点,这个热点,不仅仅决定了显示坐标相对于图像的位置,同时也决定了旋转时的中心,可谓是非常实用了。

hgesprite的常用方法之--setcolor:
看名字就知道,是设置渲染色的,如果你设置一个红色的渲染色,那么,图像呈现出来就是带着红色渲染效果的,非常简单,设置的方法很简单,是一个dword 值,你可以理解为long,是这样的格式:0xff00ff00,那么除去0x,前两位ff是透明度,这里是16进制,后面的6位分别是r,g,b,ff就相当于我们平时的255了。但是这样看起来可就太不方便了,那我们动用一下系统函数:argb(),传递给这个函数a,r,g,b,这4个参数,它就会反回一个相应的16进制数,那么,我们就可以这样些:setcolor(argb(alpha,r,g,b));这样看起来,是不是简单多了呢?好了,先说这么两个,说说其他。

如果我重新载入了texture呢?
我们前面可以看到,hgesprite是和htexture配套出现使用的,htexture是数据,而hgesprite负责呈现它们,那么如果重新载入了图像呢?有的朋友会习惯性的学习new和delete使用时的方式,也就是要换,先调用hge->texture_free(tex)来释放texture,然后再delete 精灵,其实是不用的,开始我也是这么写的,但是经过站长vicky_lh指点,我们这样写就可以:第一,在更换texture的时候你只需要tex=hge->texture_load(path)即可,不需要free,其次,精灵更不需要delete,只有在对象被卸载,或者程序结束的时候,才需要这么做。

程序可能出错,但是我想把错误写到我的文件里去
如果我们设置了logfile,那么hge会把所有的错误记录都会写到这个log里,如果我们有了自己的需要,那么如何截获hge发出的错误消息呢?使用这个方法:hge->system_geterrormessage(),它会返回一个char *,一个c风格字符串,里面就是程序到现在为止最后一条发生的错误信息。很方便吧?

我知道怎么播放音乐了,可是我该如何获取音乐的长度、暂停、继续等操作呢?
很简单啦,可能你也注意到了,有channel_getlength、channel_isplaying等方法,可是怎么用呢?有朋友就问过我这个问题,我说一下如何解决,比如我们载入了一个heffect数据,并且播放了它,那么,在使用hge->effect_play()方法的时候,它会反悔一个hchannel数据,这个数据就是音轨数据,它返回了你这个音乐播放的音轨,我们要操纵这段音乐,就要靠音轨来操作,看代码片段(这是不能直接编译执行的,只是代码片段):

引用:

heffect snd;//声音数据
hchannel channel;

snd=hge->effect_load("音乐路径");

HGE基础教程的更多相关文章

  1. matlab基础教程——根据Andrew Ng的machine learning整理

    matlab基础教程--根据Andrew Ng的machine learning整理 基本运算 算数运算 逻辑运算 格式化输出 小数位全局修改 向量和矩阵运算 矩阵操作 申明一个矩阵或向量 快速建立一 ...

  2. <<Bootstrap基础教程>> 新书出手,有心栽花花不开,无心插柳柳成荫

    并非闲的蛋疼,做技术也经常喜欢蛋疼,纠结于各种技术,各种需求变更,还有一个很苦恼的就是UI总是那么不尽人意.前不久自己开源了自己做了多年的仓储项目(开源地址:https://github.com/he ...

  3. Memcache教程 Memcache零基础教程

    Memcache是什么 Memcache是danga.com的一个项目,来分担数据库的压力. 它可以应对任意多个连接,使用非阻塞的网络IO.由于它的工作机制是在内存中开辟一块空间,然后建立一个Hash ...

  4. Selenium IDE 基础教程

    Selenium IDE 基础教程 1.下载安装     a 在火狐浏览其中搜索附件组件,查找 Selenium IDE     b 下载安装,然后重启firefox 2.界面讲解      在菜单- ...

  5. html快速入门(基础教程+资源推荐)

    1.html究竟是什么? 从字面上理解,html是超文本标记语言hyper text mark-up language的首字母缩写,指的是一种通用web页面描述语言,是用来描述我们打开浏览器就能看到的 ...

  6. 转发-UI基础教程 – 原生App切图的那些事儿

    UI基础教程 – 原生App切图的那些事儿 转发:http://www.shejidaren.com/app-ui-cut-and-slice.html 移动APP切图是UI设计必须学会的一项技能,切 ...

  7. 【Unity3D基础教程】给初学者看的Unity教程(四):通过制作Flappy Bird了解Native 2D中的RigidBody2D和Collider2D

    作者:王选易,出处:http://www.cnblogs.com/neverdie/ 欢迎转载,也请保留这段声明.如果你喜欢这篇文章,请点[推荐].谢谢! 引子 在第一篇文章[Unity3D基础教程] ...

  8. oracle基础教程(8)oracle修改字符集

    oracle基础教程(8)oracle修改字符集 1.用dba连接数据库 -->sqlplus / as sysdba 2.查看字符集 -->SELECT parameter, value ...

  9. 改写《python基础教程》中的一个例子

    一.前言 初学python,看<python基础教程>,第20章实现了将文本转化成html的功能.由于本人之前有DIY一个markdown转html的算法,所以对这个例子有兴趣.可仔细一看 ...

随机推荐

  1. 为什么java源文件中只允许一个public类存在

    1.提出问题 为什么java源文件中只允许一个public类存在? 2.分析问题 问题涉及到的条件:源文件的名字    public类     main方法 一般我们在编写一个源文件的时候: 一个pu ...

  2. C# - 重写虚方法

    项目目录: 创建教师类(Teacher),虚方法有Teach(); 创建学生类(Student),重写的方法是Teach(); 教师类: · 加上关键字 Virtual 就是声明可以重写此方法. us ...

  3. JSP的学习(4)——中文乱码的解决

    本篇将以JSP页面中可能存在的中文乱码问题进行分析和解决. 中文乱码的问题一直是国人在编程过程中的一大头疼问题,这点上在JSP.Servlet或Tomcat上随处可见.比如我们在写一个Servlet时 ...

  4. Spark Sreaming与MLlib机器学习

    Spark Sreaming与MLlib机器学习 本来这篇是准备5.15更的,但是上周一直在忙签证和工作的事,没时间就推迟了,现在终于有时间来写写Learning Spark最后一部分内容了. 第10 ...

  5. 在DLL中封装的VCL窗体Tab键响应的问题

    在DLL中的子窗体不会响应Tab按键的,这个时候就需要手动去指定Tab键的操作,但是前提是主窗体要向这个窗体发送一个消息,一个Tab键按下的消息.基本顺序是这样的: 1. 主窗体用Hook技术捕获Ta ...

  6. 14.6.7?Limits on InnoDB Tables InnoDB 表的限制

    14.6.7?Limits on InnoDB Tables InnoDB 表的限制 警告: 不要把MySQL system tables 从MyISAM 到InnoDB 表. 这是不支持的操作,如果 ...

  7. perl 贪婪匹配小例子

    redis01:/root# cat x2.pl my $str="a19823a456123"; if ($str =~/a(.*)23/){print "1----& ...

  8. Excel单元格内容太多会覆盖遮住下一单元格范围

    Excel单元格内容太多会覆盖遮住下一单元格范围分步阅读 Excel中的单元格内容,有着不同的对齐方式.用户可根据自己的需求,在处理数据的时候,自行设置所需要的对齐方式. 当您在处理数据的时候,如果设 ...

  9. hdu 4090 GemAnd Prince

    题目大意: 别人说是消消看,至于你玩没玩过.反正我是没玩过的. 就是选择一个钻石,可以消除与它相连的所有钻石.并获得 消除数量*消除数量  的分 思路: 直接暴搜,然后用一个cnt数组表示每一种钻石剩 ...

  10. Oracle中如何插入特殊字符:& 和 ' (多种解决方案)

    今天在导入一批数据到Oracle时,碰到了一个问题:Toad提示要给一个自定义变量AMP赋值,一开始我很纳闷,数据是一系列的Insert语句,怎么会有自定义变量呢?后来搜索了一下关键字AMP发现,原来 ...