Lachie Dazdarian介绍这一系列课程的目的是帮助不了解BASIC的新手了解FreeBASIC中编程的基础知识,以创建任何电脑游戏。一些基本的BASIC知识将会有很多帮助,但我相信不了解BASIC的人也应该了解这些课程。我在这里使用这个词(好吧,它是一个首字母缩略词)“BASIC”而不是FreeBASIC,因为如果你知道QuickBASIC,Visual BASIC或者BASIC的其他变体的基础知识,这些课程应该很容易理解。
我开始这个系列,因为我觉得这样的教程总是是我们的社区缺乏的,甚至在FreeBASIC之前。在我的编程生涯中,我已经对应了很少的编程新手,并且在尝试编程游戏时都有相同的问题。所以我想我能够检测到初学者需要的很好,以及需要向他们解释的东西。我也记得我的开始和使用分离的例程的问题,这些例程从来没有意图组合并用于创建游戏。我的突破点是当我发现了RelLib(R.E.Lope的QuickBASIC图形库)和使用它创建的滚动引擎的那一刻。这个滚动引擎促使我探索其力学,并扩展它(在R.E.Lope的帮助下)。在一个时刻,我获得了自己编程大部分东西(完成游戏所需)的能力。就像开一辆自行车。获得实际技能的时刻持续一秒钟。
这就是我这个系列的目标。足够的学习,所以你会在90%的情况下自给自足。学习新事物的最好方法就是看它们的应用。许多教程由于太通用而在此失败。您将永远需要更多专家程序员的帮助,但重要的是您不需要每一步。请记住,这取决于您正在开发的游戏类型和您正在使用的图形库/工具。
我们将创建的示例程序和迷你游戏将在GFXlib(FreeBASIC的内置图形库)中进行编码。Lynn的Legacy,ArKade,Mighty Line和Poxie已被编码(其中很多),我认为这些游戏是很好的参考。但别担心当您了解如何在至少一个代码中时,从一个图形库切换到另一个图形库相对容易。
本教程不会处理raycasting引擎(3D编程)或某种“进步”。如果你想要但是是一个初学者,你需要第一个以下的课程。
由于我们要在FreeBASIC中编写代码,您需要先从http://www.freebasic.net获取FreeBASIC(如果您还没有)(这些示例使用版本0.18b编译),并且FreeBASIC之一IDE可用。我推荐FBIDE或FBEdit。
示例#1:一个简单的程序 - 圆移动
我们将从一些基本的东西开始。我们将要编码的第一个程序不会使用外部图形,因为从外部文件(通常是BMP图像)加载图形总是一个肮脏的业务,并且会在这一点上混淆你。相信我这个耐心一点。
我们将创建的程序将允许您围绕屏幕移动一个圆圈。一个非常简单的程序,但通过这样做,我们将学习使用GFXlib创建任何游戏所需的重要事实和许多基本说明和方法。
当我们使用GFXlib时,您需要注意放在/ FreeBASIC / docs目录中的gfxlib.txt文件(GFXlib的文档)。这是我们的圣经,对这些教训非常有用,因为我不会解释示例程序中使用的每个语句的所有参数(最有可能)。FreeBASIC继续使用新版本,所以请务必参考这篇在线FreeBASIC手册(FreeBASIC Wiki的一部分)。
在FBIDE中打开一个新程序。首先我们要做的是设置图形模式。什么是图形模式?选择程序的图形分辨率和颜色深度(8位,16位,...)。例如,8位颜色深度是标准的256色模式(每像素8位)。图形模式使用SCREEN语句设置,如下所示:
Screen 13,8,2,0
13表示320 * 200图形分辨率,8表示8位图形,2表示两个工作页,0表示窗口模式(全屏模式输入1)。任何图形相关程序建议最少2个工作页。这些事情稍后会变得更加清晰。有关SCREEN语句的更多详细信息,请参阅GFXlib的文档或FreeBASIC Wiki(SCREEN语句的“高级”版本是SCREENRES)。
接下来我们要做的是设置一个循环播放,直到用户按下键盘上的字母Q。循环是任何程序的基础,而不仅仅是一个电脑游戏。编写一个程序,它将停止/停止每一次,然后等待用户键入的东西是一个BAD和错误的方式来编程任何你想要的其他人玩。我们将使用循环作为程序等待用户执行某些操作的地方(用鼠标点击或按下一个键),程序根据用户的操作执行某些程序。它也将被用作不受玩家(敌人)控制的对象的管理/移动的地方。循环是必须的。
如果您了解所有这些内容,可以跳到本节的最后部分并下载完成的示例(附注释)。如果有东西在你身上不明白,那就回到这里。
我们可以通过更多的方式设置循环(使用WHILE:WEND语句,使用GOTO语句 - Noooo!),但最好的方法是使用DO ... LOOP。这种类型的循环只需重复一个语句,直到满足条件。您使用UNTIL设置LOOP后的条件。检查以下代码:
Screen 13,8,2,0 ' Sets the graphic mode
Do
' We'll put our statements here later
Loop Until Inkey$ = "Q" Or Inkey$ = "q"
如果你编译这个代码并运行它,你会得到一个小的黑色的空闲的320 * 200窗口,你可以通过按下Q来关闭它(你可能需要按住它)。程序只需循环,直到按“Q”或“q”。我使用大写和小写“Q”符号,以防止键盘上的Caps Lock被打开。INKEY $是一个返回键盘上最后一个键的语句。我稍后会解释为什么它不应该与游戏一起使用,什么是更好的替代品。
要绘制一个圆圈,我们将使用CIRCLE语句(请参阅GFXlib的文档)。检查以下代码:
Screen 13,8,2,0 ' Sets the graphic mode
Do
Circle (150, 90), 10, 15
Loop Until Inkey$ = "Q" Or Inkey$ = "q"
最后一个代码在坐标150,90上绘制一个圆圈,半径为10,颜色为15(纯白色)在循环中,您可以检查编译代码。那么如何移动这个圈子?我们需要将其坐标连接到VARIABLES。为此,我们将使用两个名为circlex和circley的变量。检查以下代码:
Dim Shared As Single circlex, circley
Screen 13,8,2,0 ' Sets the graphic mode
circlex = 150 ' Initial circle position
circley = 90
Do
Circle (circlex, circlex), 10, 15
Loop Until Inkey$ = "Q" Or Inkey$ = "q"
这对我们的程序的结果没有改变,但这是我们想要完成的一步。你可以改变圈数和圈数等于改变圈子初始位置的数量,但这不是我们真正想要的。为了移动圆圈,我们需要使用键盘语句连接圆环和圆圈变量。
我们在程序中声明了前两个变量。由于FreeBASIC ver.0.17 FreeBASIC程序中的所有变量必须被声明,虽然在编译过程中使用-lang qb命令行时可以使用旧的QBasic兼容性方言进行编译(我不推荐它,因为它会使您被剥夺可能的进步,默认的FB兼容性已经提供并将提供的扩展)。有关此检查的更多信息,请参阅FreeBASIC wiki的相应页面 - 使用命令行。变量以这种方式声明(大小):
Dim variable_name [As type_of_variable]
Or...
Dim [As type_of_variable] variable1, variable2, variable3, ...
[ ]内的数据是可选的,不使用括号。FreeBASIC中可用的变量类型有BYTE,SHORT,INTEGER,STRING,SINGLE,DOUBLE等等,但是我没有找到关于这个级别重要的细节。您现在需要知道的是,当它们持有图形数据(存储器缓冲器持有图形)时,或者当它们表示不需要十进制精度(生命数,点数等)的数据时,应该声明变量或数组AS INTEGER。需要十进制精度的变量被声明为SINGLE或DOUBLE。这些通常是游戏中使用的变量,这些变量依赖于诸如街机驾驶游戏或跳跃游戏(重力效应)的物理公式。简单来说,每个周期的两个像素的速度和每个周期的一个像素的速度之间的差异通常太大,在这些限制中,您不能以最令人满意的方式模拟像流体运动之类的效果。此外,在DIM之后,您应该放置SHARED,使得特定变量在整个程序(所有子例程)中都可读。不要使用SHARED只与子程序中声明的变量(我很少这样做)。如果您要在子程序中声明ARRAYS,我建议您用REDIM替换DIM。字符串用于保存文本数据。喜欢YourName =“Dodo”,但您需要首先声明YourName为STRING。
现在我将介绍一个新的语句,而不是INKEY $,它可以检测多个按键,并且比INKEY $更响应(完美响应)。INKEY $的缺陷,以及非常不响应(当您尝试关闭以前编译的示例时您可能会检测到),它只能在任何给定的时刻检测到一个按键,从而使其完全无法使用在游戏中。
我们将使用的替代品是MULTIKEY(GFXlib语句),它仅具有一个参数,这就是要查询的密钥的DOS扫描码。你现在可能会迷路DOS扫描码只不过是将计算机引用到某个键盘键的代码。如果您检查GFXlib文档的附录A,您将看到每个代码代表什么。例如,MULTIKEY(&h1C)查询如果您按ENTER键。GFXlib允许您用“易读”常量替换这些扫描码,如附录A所述。要使用GFXlib,您需要将.bi文件(fbgfx.bi)包含到您的源文件中。什么是.bi文件?那么它可以是您附加到您的源代码的任何类型的模块,哪些可以使用各种子例程(如果您不知道什么是子例程,稍后再介绍)和主模块中使用的声明。您需要添加的代码是这两行,如下所示:
#include "fbgfx.bi"
Using FB
最好将这两行放在程序开始的某个位置(子声明之前或之后)。您不需要设置路径到fbgfx.bi,因为它被放置在/ FreeBASIC / inc目录中。如果源文件不在该目录中,则只需设置一个.bi文件的路径。使用FB告诉程序,我们将访问没有命名空间的GFXlib符号,这意味着,而不必放置“FB”。在每个GFXlib符号的前面。参考FreeBASIC Wiki使用。
现在乐趣开始了。
我们将添加一个名为circlespeed的新变量,其中标记(集合)圆圈将在一个周期(循环)中移动多少个像素。运动将用箭头键完成。每次用户按下某个箭头键,我们会告诉程序将圆形或圆圈(取决于按键)改变圆圈速度。检查以下代码:
#include "fbgfx.bi"
Using FB
Dim Shared As Single circlex, circley, circlespeed
Screen 13,8,2,0 ' Sets the graphic mode
circlex = 150 ' Initial circle position
circley = 90
circlespeed = 1 ' Circle's speed => 1 pixel per loop
Do
Circle (circlex, circley), 10, 15
' According to pushed key we change the circle's coordinates.
If MultiKey(SC_RIGHT) Then circlex = circlex + circlespeed
If MultiKey(SC_LEFT) Then circlex = circlex - circlespeed
If MultiKey(SC_DOWN) Then circley = circley + circlespeed
If MultiKey(SC_UP) Then circley = circley - circlespeed
Loop Until MultiKey(SC_Q) Or MultiKey(SC_ESCAPE)
如您所见,我们也改变了UNTIL之后的状况,因为我们现在使用MULTIKEY。现在可以按ESCAPE退出程序(我添加了一个条件)。
如果你编译最后一个版本的代码,我们不想发生的两件事就会发生。程序将运行得如此之快,甚至不会注意到圆圈的移动,圆圈将“屏蔽”屏幕(以前循环中不同坐标绘制的圆圈将保留在屏幕上)。为了避免拖尾,您需要在循环中具有CLS语句(清除屏幕),以便在每个新循环中,在绘制新的循环之前,先前循环中的旧圆被清除。
为了降低程序的速度,最快的修复是SLEEP命令。它能做什么?它等待直到指定的时间量(毫秒)或键被按下。要退出按键选项,使用SLEEP毫秒,1。此声明也是100%CPU使用问题的有效解决方案。您会看到,如果不使用该语句,任何一种具有循环(即使是最简单的循环)的FreeBASIC程序都将保留所有计算机周期,并使您可能正在运行的所有其他Windows任务执行爬网。当这种FreeBASIC程序运行时,这也使您难以与其他任务一起运行。Err ...这不是一个巨大的问题,到目前为止,发布FreeBASIC游戏的程序员相当多,没有办法解决它。
复制并粘贴以下代码并进行编译:
#include "fbgfx.bi"
Using FB
Dim Shared As Single circlex, circley, circlespeed
Screen 13,8,2,0 ' Sets the graphic mode
circlex = 150 ' Initial circle position
circley = 90
circlespeed = 1 ' Circle's speed => 1 pixel per loop
Do
Cls
Circle (circlex, circley), 10, 15
' According to pushed key we change the circle's coordinates.
If MultiKey(SC_RIGHT) Then circlex = circlex + circlespeed
If MultiKey(SC_LEFT) Then circlex = circlex - circlespeed
If MultiKey(SC_DOWN) Then circley = circley + circlespeed
If MultiKey(SC_UP) Then circley = circley - circlespeed
Sleep 10, 1
Loop Until MultiKey(SC_Q) Or MultiKey(SC_ESCAPE)
中提琴!我们的圈子正在移动,“足够慢”。
代码的最后一个版本并不代表所需的编码方式,但是我不得不简化代码,以使本课程易于理解。我们接下来需要做的是在任何“严肃”的程序中声明我们的变量,并显示为什么我们有两个工作页面以及我们可以做什么。
在上述代码中声明变量的方式并不是最大的方案,在大型项目中,我们有大量的变量通常与几个对象相关联(一个对象可以是玩家,敌人或任何以超过一个变量定义的任何东西)。
首先,我们将使用可以包含更多变量/数组的语句TYPE来定义一个用户定义的数据类型(留在我身边)。我们将这个用户数据类型ObjectType命名。代码:
Type ObjectType
x As Single
y As Single
speed As Single
End Type
之后,我们将我们的圈子声明为一个对象:
Dim Shared CircleM As ObjectType
' We can't declare this variable with "Circle"
' since then FB can't differ it from
' the statement CIRCLE, thus "CircleM".
这种方法有什么好处?它允许我们以更有效和更干净的方式来管理程序变量。而不是(在这个例子中)必须分别声明每个圈子的特征(它的位置,速度等),我们只需使用一个包含所有这些变量的类型:def,并将一个变量或一个数组关联情况就是CircleM)。所以现在,圆圈的X位置被CircleM.X标记,圆圈的y位置与CircleM.Y和圆圈的速度是CircleM.speed。我希望你现在看到为什么这更好。一个用户定义的类型可以连接更多的变量或数组。在这个例子中,您可以添加另一个对象,例如DIM SHARED EnemyCircle(8)AS ObjectType,这将允许我们使用ObjectType类型的变量,使用特定的一组例程(某种AI)来管理8个“邪恶”的圈子:def(x,y,speed),这些圈子可能会以某种方式“攻击”用户的圈子。所有这一切将在下一课中变得更加清晰。请记住,并不是所有变量都需要使用类型:def声明。这只适用于您的游戏中的“对象”,它们被定义(特征)与更多的变量(如由健康,金钱,得分,力量等决定的英雄)。
更改后,代码的最终版本如下所示:
#include "fbgfx.bi"
Using FB
' Our user defined type.
Type ObjectType
x As Single
y As Single
speed As Single
End Type
Dim Shared CircleM As ObjectType
' We can't declare this variable with "Circle"
' since then FB can't differ it from
' the statement CIRCLE, thus "CircleM".
Screen 13,8,2,0 ' Sets the graphic mode
SetMouse 0,0,0 ' Hides the mouse cursor
CircleM.x = 150 ' Initial circle's position
CircleM.y = 90
CircleM.speed = 1 ' Circle's speed => 1 pixel per loop
Do
Cls
Circle (CircleM.x, CircleM.y), 10, 15
' According to pushed key we change the circle's coordinates.
If MultiKey(SC_RIGHT) Then CircleM.x = CircleM.x + CircleM.speed
If MultiKey(SC_LEFT) Then CircleM.x = CircleM.x - CircleM.speed
If MultiKey(SC_DOWN) Then CircleM.y = CircleM.y + CircleM.speed
If MultiKey(SC_UP) Then CircleM.y = CircleM.y - CircleM.speed
Sleep 10, 1 ' Wait for 10 milliseconds.
Loop Until MultiKey(SC_Q) Or MultiKey(SC_ESCAPE)
你会注意到我在代码中添加了一个语句。SETMOUSE语句定位系统鼠标光标(前两个参数)并显示或隐藏(第三个参数; 0隐藏它)。您应该在默认情况下在SCREEN语句(重要!)之后的每个程序中输入这些参数,因为即使您的程序将具有鼠标可控界面,您很有可能绘制自己的游标。相信我这个呃,我用这种方式太频繁了。
在源代码中下载完整的示例,其中包含额外的注释:move_circle.zip
呃,我们完成了第一个例子。有些人可能会认为我有太多的细节,但我觉得所有这些舞蹈都需要让下一个例子和课程更愉快的冒险。
不过,这个例子远不是我们想要的,对吧?所以下一章将会了解如何从外部文件加载图形等。
示例2:围绕绿色领域运行的战士
在下一个例子中,我们将应用第一个例子中的所有知识,所以不要指望这个例子再次进入每个语句。我会解释每一个新的陈述,只是刷掉旧的。
在本节中,我们将开始编写本课程中将不会完成的迷你游戏。在本课中,我们将创建一个战士围绕一个绿色领域(单一屏幕)运行的程序。
首先,我会告诉你我们将要使用什么图形。我们将以8位颜色深度模式工作,因此我们要使用的图像需要以该模式(256色模式)保存。对于战士精灵,我将使用我的第一个游戏Dark Quest中的主角的精灵。
http://hmcsoft.org/fb/htpagl1-sprites.png
当你看到这个图像具有我们的战士的12个精灵,每个20 * 20像素大。当战士用他的剑摆动时,每个方向两个(步行动画)和每个方向的一个精灵。在第一堂教练中,剑的摆动将不会实现,但后来将变得必要。
第二个图像是您可以检查/下载的背景图像,如果您点击这里(320 * 200像素大,8位BMP图像)。
下载两个图像并将它们放在您将放置源的位置,或者在本节结尾处下载完整的示例。
在我们的程序的开始,我们应该包括fbgfx.bi,与第一个例子相同,然后设置相同的图形模式。代码:
#include "fbgfx.bi"
Using FB
Screen 13,8,2,0 ' Sets the graphic mode
SetMouse 0,0,0 ' Hides the mouse cursor
现在我们将声明两个内存指针,它将指向内存缓冲区,我们的图形将被存储(一个用于精灵,一个用于后台)。
第一个指针我们命名为background1,并用以下行声明它:
Dim Shared background1 As Any Ptr
任何PTR告诉我们,background1实际上是一个内存指针。定义为ANY PTR的指针禁用编译器检查其指向的数据类型。它是有用的,因为它可以指向不同类型的数据。我们将使用指针,因为我们将使用IMAGECREATE语句为我们的图形分配内存。如果我们输入其高度和宽度,则IMAGECREATE为一张图形(sprite / image)分配适当的内存量。否则我们必须手动执行,这意味着,计算所需的内存量是精灵大小,位深度和可变大小的结果。IMAGECREATE这样做是为了使用。由于IMAGECREATE结果带有指针,因此我们需要引用一个指针,而不是一个变量。不要担心,如果你不知道任何关于指针。你不需要(了解本教程)。
我们将声明的下一个指针将指向包含12个战士精灵的内存缓冲区。我们将该指针定义为单维数组,数组中的每个元素表示一个精灵。
Dim Shared WarriorSprite(12) As Any Ptr
这两行都应该放在SCREEN语句之前的代码中。这就是你写每个程序的方式。子例程声明,然后变量声明,然后需要额外的子例程声明,然后是真正的代码。我们的程序的开始现在应该是这样的:
#include "fbgfx.bi"
Using FB
Dim Shared background1 As Any Ptr ' A pointer that points to a memory
' buffer holding the background graphics
Dim Shared WarriorSprite(12) As Any Ptr ' A pointer that points to a memory
' buffer holding the warrior sprites
Screen 13,8,2,0 ' Sets the graphic mode
SetMouse 0,0,0 ' Hides the mouse cursor
屏幕分辨率,颜色深度和工作页数设置后,我们将在加载图形之前隐藏我们的工作页面,因为我们不希望用户每次启动我们的程序时都可以看到所有程序的图形。为了完成这个,我们将使用SCREENSET语句。它能做什么?它设置工作页面(第一个参数)和可见页面(第二个参数)。在我们的例子中,我们将页面1设置为工作页面,将页面0设置为可见页面。每当我们在屏幕上绘制或加载某些东西,使用“SCREENSET 1,0”后,将在工作页面上加载/绘制,直到我们使用不同参数的SCREENCOPY或SCREENSET语句(SCREENSET 1,1)。这样我们可以将图形加载到屏幕上,我们不希望用户在将工作页面上的内容应用到可见页面之前查看和删除它。此页面翻转在“图形要求”程序的循环中也很有用,以避免闪烁或其他不必要的事件。