Lua Player 编程初级指南



掌叔
2008-06-06 11:22:21

摘自:tgbus
本文由gadget2k翻译自[url]http://www.evilmana.com/tutorials/lua_tutorial_01.php[/url]的网上教程,转载请注明。想进一步深造的,请学习下面的《Programming in Lua》([url=http://down.tgbus.com/soft/20082.shtml]点此下载[/url]),这是对Lua的全面阐述。


掌叔
2008-06-06 11:24:42

  本系列教程旨在帮助完全的编程新手学会使用lua脚本语言为PSP开发小型程序和游戏。在开始之前我们需要做一些准备工作,虽然不都是非要不可,但是有了它们你会事半功倍。

  译者注:本教程全部学完了你也就是会爬而已。要学会走,甚至跑起来,就看你的热情和毅力了!

  LuaPlayer for PSP - 这是头等大事,没有它你的lua脚本就无法在PSP上运行。[url=http://down.tgbus.com/soft/20082.shtml]点此下载[/url]

  LuaPlayer for Windows - 这是在PC上运行的LuaPlayer,写好了脚本可以先在PC上检验,省得为了测试而在PC和PSP之间来回倒腾文件。[url=http://down.tgbus.com/soft/20082.shtml]点此下载[/url]

  译者注1:该PC版本基于luaPlayer 0.20,LuaPlayer HM 5.0支持的新函数可能无法运行。

  译者注2:键盘与PSP按键的对应。SELECT = a,START = s,LTRIGGER = q,RTRIGGER = w,TRIANGLE = r,CIRCLE = f,CROSS = c,SQUARE = d,十字键 = 方向键

  Notepad++ - 比windows自带的notepad要强悍多的开源免费文本编辑器,可以高亮显示多种编程语言,还具备自动完成功能,自动对代码树级管理。[url=http://down.tgbus.com/soft/20082.shtml]点此下载[/url]

  当然你也可以使用其他任何文本编辑器编写脚本,包括Notepad。 (以前推荐的miro lua不如这个强,删除。)

  Windows版LuaPlayer的使用

  解压luaPlayerWindows.rar至硬盘。在luaplayer.exe所在目录下,创建一个文本文件,内容是

代码:

luaplayer script.lua
pause

  将其存为script.cmd,确保不要存成txt后缀。这样可以方便在PC上运行你的程序。其中luaplayer是指所在文件夹的 luaplayer.exe,而script.lua则是你编写的程序文件名。Pause则是保证发生错误时命令行窗口不会关闭。这样我们就可以为程序捉虫。

  注意:如果双击script.cmd文件出现下述错误

引用:

'luaplayer' is not recognized as an internal or external command, operable program or batch file

那么就用luaplayer.exe的全路径替代luaplayer。例如:

代码:

"C:Documents and SettingsOwnerDesktopmygameluaplayer.exe"
script.lua
pause

  现在我们使用Notepad(或者其他任何编辑器,例如ConTEXT)再建一个文本文件,命名为script.lua(注:如果你尝试在PSP上运行该脚本发生“脚本未找到”错误时你可能需要将其改名为 index.lua)。我们暂时就留空,下一讲我们会让你在其中编写代码并测试它的。以后要让Windows版LuaPlayer运行你的代码,很简单,只要双击script.cmd文件就行了。


掌叔
2008-06-06 11:25:23

  现在我们开始为PSP编写自己的第一个Lua程序。请谨记我们在整个教程中都会利用到LuaPlayer的Windows版,虽然有些东西在 windows版上不能很好运行(例如音频不是很好),甚或不能运行(例如LuaPlayer新增的一些函数),但是对于我们将要学习的东西来说, windows版还是足够使用的。

  LuaPlayer在PSP上的安装一般来说把LuaPlayer文件夹放在PSP/GAME目录下皆可。

  任何时候你想要在PSP上测试你的程序的话,只要把你的script.lua文件复制到EBOOT.PBP所在目录下,然后运行LuaPlayer即可。

  首先用文本编辑器打开你的script.lua文件。我们的第一个程序很简单,就是在屏幕上打印一些东西。让我们开始吧!

注释

  我们第一件要学的就是如何给你的代码做注释。注释是为了帮助其他人看懂你的代码。相信我,这也能帮助你自己。在你停止一个项目几个月后再回头看自己的代码,你会发现没有注释的话你会在自己的代码中迷失!所以我们的新程序第一行以下列注释开始:

代码:
-- 我的第一个 Lua 程序
-- 作者: 你的名字

  正如你看到的,注释以双划线开始( -- )。在此之后输入的任何文字都不会被视为代码,在执行时将被完全忽略掉。但是,请注意,你必须保持注释在一行内。如果你用回车到了下一行,请记住也要用双划线起头。参照我们上面给出的代码,你应该已经知道怎么做注释了。不难吧,是不是?

  译者注:多行注释以 --[[ 开头,]] 结尾。如下:

代码:

--[[
*********************************************
* 庐~山~升~龙~霸 *
* 天~马~流~星~拳 *
*********************************************
]]

创建一个色彩对象

  接下来我们将创建一个色彩对象,以便打印文字到屏幕上。我们将为此指定一个特定颜色然后用于print命令。将下面的代码加入到你的程序中,记得要另起一行。你也可以多空一行以便和上面的注释隔开。

  警告:Lua是区分大小写的,也就是说所有命令必须按照我们显示的大小写输入。例如下述代码中如果“Color”被打成“color”的话将无法运行。

代码:

red = Color.new(255, 0, 0)

  这样我们就创建了一个色彩对象。我们使用RGB值指定颜色,并将其存为一个变量red。你可以通过任何图像编辑程序获得任意颜色的RGB值。变量 red 储存了指定的色彩信息,但是这不意味着只要把它换成“blue”就会变成蓝色。变量名与其指代的颜色毫无关系。颜色来自RGB值。所以,你把red改成 BigFatTurkey也没有关系。如果你还是不理解何谓变量,不用着急。我们很快就会专门学习它的。

打印至屏幕

  哈,到我们程序最精彩的部分了。我们将使用print命令打印一些文字到屏幕上。将下述代码复制到你的程序代码末尾。

代码:
screen:print(200, 100, "Look! I made text appear!", red)

  让我们对此解析一下。screen:print就是告诉我们的程序打印一些东西到屏幕上。在其后的括号里你看到有几个数值。这些值称为参数,每个函数所带的参数其个数和类型都不同。对于print来说我们调用了4个参数。

  它的语法结构: screen:print( x, y, stringOfText, colorObject)

  第一个参数,x,告诉程序在距离屏幕左侧多少像素的地方打印文字。

  第二个参数,y,告诉程序在距离屏幕顶端多少像素的地方打印文字。

  第三个参数,就是你打算让程序打印的文字。记得用引号把文字括起来。

  至于第四个...嘿,那就是我们早先创建的色彩对象red!这告诉程序使用红色打印文字,该色彩是我们先前创建的。记住在print语句使用颜色之前你必须先创建它!

离屏缓存转至显屏

  下述代码可能有点让人迷惑。但是别担心,即使你不完全理解它,也没什么关系。原样照抄到你代码的最后一行就是,象之前一样。

代码:
screen.flip()

  简单的说,所有的东西都是先在屏下绘制的,所以我们必须使用此命令让它显示到屏幕上面。

一个循环?

  没错,接下来我们要创建一个程序循环。它让一段代码循环执行直至某事让它停止。在本例中使用循环是为了让我们的程序不至于显示完文字就结束。没有这个循环的话,我们根本没机会看到文字,因为程序结束的太快了。下面是代码,我想你现在知道该放哪儿了吧。

代码:
while true do
screen.waitVblankStart()
end

  以后我们会深入探讨循环的。现在你只要知道它必须在那儿!

  到此我们的第一个程序完成!保存你的代码文件,双击script.cmd运行你的程序。如果一切顺利你应该在屏幕上看到你的红色文本了。


掌叔
2008-06-06 11:26:08

  本课我们学习怎样在程序中使用变量。变量是很重要的工具,将来也必然成为你最好的朋友。一言以蔽之,变量就是信息的存储容器,供你在程序中随时调用。

  现在我们就来写一个使用变量的简单程序。同时也学习一下Lua中的简单算术。这次我们对每一行代码都给与注释。

  首先,让我们再次以创建色彩对象开始,用来在屏幕上打印信息。这次我们选用绿色。

代码:
-- 绿色色彩对象
green = Color.new(0, 255, 0)

  接着就开始创建我们第一个变量吧。这将是一个存储你出生年份的变量。请随意替换1981为你自己的出生年份,事实上我也建议你这么做!

代码:
-- 将出生年份存为变量 myBirthYear
myBirthYear = 1981

  就是这样!我们所做的就是取得数值1981(或是你使用的自己的出生年份)并将其保存在我们命名为myBirthYear的变量中。稍后我们在程序中将使用该变量。

  现在移下一行再设一个变量用来存储当前年份。

代码:
-- 将当前年份存为变量 currentYear
currentYear = 2008

  现在让我们创建一个略微不同的变量。

代码:
-- 创建一个空变量
myAge = nil

  请注意这次我们为变量赋值为nil,就是表明该变量尚未赋值。我们会在稍后为此变量存入信息。

  那么,我们最后再创建一个变量,向你表明变量除了存储数值以外,也能存储字串(文本)。注意文本要用引号括起来。

代码:
-- 在变量中存储一些文字
someText = "My age is roughly "

  到此我想你已知道如何创建变量了,那么让我们开始使用它们。我们将要做的就是使用我们的变量和一些基础算术计算出你的年龄。开始添加下面的代码到你的程序中吧:

代码:

-- 用 currentYear 减去 myBirthYear 并将结果保存在变量 myAge 中
myAge = currentYear - myBirthYear

  这一步是提取myBirthYear中存储的值,用currentYear的值减去,然后将结果保存在myAge变量中。这样myAge就被赋予了值!

  现在,让我们使用上一讲学到的print命令将我们的文本变量打印到屏幕上。

代码:
-- 打印文本变量至屏幕
screen:print(10,100,someText,green)

  与我们上一讲所做的唯一不同(除了颜色)在于我们这次打印的是变量的值,而不是直接的文本。请注意此处我们没有使用引号。这句代码将以绿色文本在x-10,y-100的位置打印someText变量中存储的任何内容。

  现在我们将在这句话的旁边打印我们的年龄结果。我们得用一个新命令。要在 someText 的文本右侧打印 myAge,我们可以使用双句点(..)

  这称为“串联”。那么我们把刚才那一行代码略作修改如下:

代码:
-- 同时打印文本变量和年龄至屏幕
screen:print(10,100,someText .. myAge,green)

  最后我们把screen.flip()和循环扔进代码以结束程序。

代码:
-- 离屏缓存转至显屏
screen.flip()
-- 不停循环
while true do
screen.waitVblankStart()
end

  保存你的程序,运行查看结果。

  通过使用变量,我们只要改变myBirthYear的值,程序就可以自动计算出任何人的年龄。没有变量的话,你就不得不修改很多行代码,有些程序甚至得上万行。

  本节课我们只用到了减法,很简单,就是 - 符号。

  加法请使用 +
  乘法请使用 *
  除法请使用 /


掌叔
2008-06-06 11:27:45

  Lua中的数据表(tables)很像变量。主要区别是数据表可以用来存储多个数据,而变量只能存一个。你把它想象成一个带有很多“抽屉”存放不同内容的容器就行了。你今后编写的游戏很可能会用到数个数据表。在Lua中使用数据表基本相当于其他编程语言中使用的数列(arrays)。本课我们还会顺便学一些别的内容。

  假设我们要写一个小游戏,共有5类敌兵,2个玩家。为此我们要让程序知道这些角色的相关信息。你觉得玩家和敌兵需要什么信息呢?我相信你会有成堆的想法,不过既然你是初学,还是简单点好。首先敌兵得有生命值(health),这样才能被杀死。因此我们需要给程序提供信息以跟踪敌兵的生命值。我们还需要信息以区分我们使用的敌兵种类。对于玩家生命值自然也是不可少的。还要什么信息?比如玩家拥有的武器种类怎样!有了这些信息我们应该可以开始了。

  现在开始数据表的学习。下文中的代码只有我们明确说明的才需要添加到你的程序中去。因为本课我会给出几个你并不需要的参考代码。

  数据表的定义方式:

代码:
tablename = { }

  首先是数据表名(任何你喜欢的名字),然后是等号,然后是左右(开闭)尖括号。这样就创建了一个空的数据表。要为数据表赋值,只要把数据放进括号里就行,用逗号隔开。这里有个例子:

代码:
myTable = { 12, 14, 16, 18, 20 }

  记住它的用法跟变量完全相同。只是看起来有点不一样。现在叫你使用方法。数据表内的每个数据作为一个数列元素而被索引,这样我们只需使用数据表名加上内有索引号的开闭方括号就能调用该数据。没搞懂?!看看下例吧:

代码:
luckyNumber = myTable[1]

  此处我们将数据表的第一个数据的值(12)赋予了变量luckyNumber。再回看一下我们刚创建的数据表,总共有几个数据在内?如果你说5 就对了。第一个是12,第二个是14,依次下去。用上述方法我们可以轻松的调用里面的数据。索引以 1 起始,而不像某些语言那样以 0 开始。下面列出了该表所有可用的数据元素:

代码:
myTable[1]
myTable[2]
myTable[3]
myTable[4]
myTable[5]

  到此我想你应该试着用数据表写自己的程序了。还记得第一课我们学过打印一行到屏幕吧?现在不用变量,而是创建一个数据表并使用print命令将其中一个元素打印出来。你也可以用多个打印语句让它打印数个或全部元素。但是记得要修改print命令的Y参数,不然就像我们之前说过的,打印结果会堆叠起来。你无需更改X参数,因为该参数只影响水平位置。程序不会自动打印到下一行,你必须告诉它这么做。你必须告诉程序向下多少个像素,以避免交叠或相距太远。就像你之前用变量打印时一样做法,只不过这次换成数据表元素:tablename[number]。去尝试一下吧,我发誓我会在这等你的!

  好了,我希望你做的不错。我还想指出,你可以先创建空的数据表,稍后再在程序中为它赋值。下面是一个示例:

代码:
myTable = { } myTable[1] = "apple" myTable[2] = "orange" myTable[3] = "banana"

  你一定注意到了,赋值除了数字,字符串也是可以的,只要你用引号括起来。

  译者注:此处你也看到不同语句可以写在一行内,只要用空格分开就行了。当然这样不利于阅读,所以我建议最好还是分行写。

  接下来我们再学一个更复杂点的数据表。你可以给数据表原属添加额外的属性,有时又被称为字典或类型。这是非常非常有用的,可以让你的代码齐整易读。让我们来看个例子。我将创建一个空数据表并在下一行引入这一方法。

代码:
shirt = { }
shirt[1] = { color = "blue", size = "small" }

  我们做的就是创建一个数据表,并在其中的shirt[1]元素中创建了另一个数据表。现在假设程序要获取衬衫的颜色和尺寸。方法如下:

代码:
shirt[1].color
shirt[1].size

  如果我们在print语句中或者任何其他命令中使用,shirt[1].color 的结果将是 blue,而 shirt[1].size 的结果将是 small。这个搭配不是很糟啦,不用想太多!

  现在回到我们的程序上来。首先,我们要创建一个数据表存放我们的敌兵信息。这包括敌兵的生命值和敌兵类型。这是我们程序的第一行代码(终于!),所以赶紧复制到新的script.lua文件中去吧。第一行仍旧是我们的老朋友,色彩对象。你可以随意改用绿色以外的颜色。

代码:
green = Color.new(0, 255, 0)
Enemy = { }
Enemy[1] = { type = "gargoyle", health = 100 }
Enemy[2] = { type = "vampire", health = 100 }
Enemy[3] = { type = "goomba", health = 100 }
Enemy[4] = { type = "ghost", health = 100 }
Enemy[5] = { type = "zombie", health = 100 }

  就是这样。现在我们创建了5个敌兵元素,并告诉程序它们的种类和总生命值。现在添加关于玩家的代码。

代码:
Player = { }
Player[1] = { weapon = "sword", health = 100 }
Player[2] = { weapon = "knife", health = 100 }

  这样我们的示例程序所需的信息就都有了。请注意,在一个能移动的真正游戏中,Player数据表将是存储玩家在屏幕上所处位置的完美之处,敌兵则利用Enemy数据表。本例中不会有任何角色移动,所以我们还是留待以后再讨论这个问题。我们将要做的是将玩家和敌兵的信息打印到屏幕上,并在按下 X 键时取走敌兵的生命值。我们只选一个敌兵和玩家 1 做这件事。可怜的玩家 2 就得坐冷板凳了,还有除了敌兵 1 外的其他敌兵,等待下一个教程吧。

  接下来我们就要进入我们的游戏循环。其中我们还会用到按钮的设置代码,你应该还记得吧。复制下述代码到你的程序:

代码:
while true do
pad = Controls.read()

  现在添加清屏命令以便每次循环时清空屏幕。

代码:
screen:clear()

  然后在每次循环中打印玩家的生命值和武器信息到屏幕上。

代码:
screen:print(5,10,"Player 1 Health: " .. Player[1].health,green)
screen:print(5,20,"Player 1 Weapon: " .. Player[1].weapon,green)

  以免你又迷失了,我们解释一下。第一条print语句表明将在X坐标值5、Y坐标值10的地方打印,也就是距屏幕左端5个像素、顶端10个像素的位置。PSP的屏幕是宽480像素,高272像素。后面的"Player 1 Health: "即表明在该位置打印此字符串,然后我们将数据表元素 Player[1].health 串联(还记得吗?)其后,这样它的值将打印在该字符串右侧。还记得Player[1].health 里存储的是什么吗?你应该记得!最后,是我们用了数次的色彩对象。第二句在屏幕稍低的位置,就是第一行下方,用同样的方法打印不同的信息。

  现在让我们在屏幕另一侧打印我们的敌兵信息。添加下述代码:

代码:
screen:print(250,10,"Enemy Health: " .. Enemy[1].health,green)
screen:print(250,20,"Enemy Type: " .. Enemy[1].type,green)

  接下来我们使用按钮输入命令侦测X键是否按下,如果是则从敌兵生命值中减去5。添加下述代码:

代码:
if pad:cross() then
Enemy[1].health = Enemy[1].health - 5
end

  这是说如果 X 键按下则从 Enemy[1].health 元素的值中减去5。

  最后,让我们缓存、翻屏并终止循环。添加下述代码:

代码:
screen.waitVblankStart()
screen.flip()
end

  保存运行,测试一下吧。然后赶紧回来!

  啊哦,怎么回事!敌兵的生命值下降太快了,都变成负值了!可不能在游戏中这么搞!这向你显示了循环执行的速度有多快。我们可以修正它,仅在按下X键时减生命值,按住时不减。

  首先在代码顶端的数据表之前添上此行。在色彩变量之前或之后都可以。

代码:
oldpad = Controls.read()

  其次找到代码行 "if pad:cross() then" 更改如下:

代码:
if pad:cross() and oldpad:cross() ~= pad:cross() then

  ...最后,就在 screen.flip() 之后和 "end" 之前添加此行:

代码:
oldpad = pad

  目前我们暂时不纠缠于细节。简单的说,此代码检测你按下的按钮是否上一循环相同,如果是,则“if”语句不会执行。~= 表示“不相等”。我们很快会学到运算符的。

  保存运行,测试一下。

  怎样,运行的不错,除了还会变成负数以外。正好,我们正要学习另一个运算符呢。

  找到代码行 "if pad:cross() and oldpad:cross() ~= pad:cross() then" 更改如下:

代码:
if pad:cross() and oldpad:cross() ~= pad:cross() and Enemy[1].health > 0 then

  我们添加了 Enemy[1].health > 0 这个条件。 > 表示“大于”,可能你已经知道了。所以修改后的整行代码表示如果X键按下,并且没有按住与上一循环相同的按钮,并且敌兵的生命值大于0则执行随后的语句(生命值减5)。注意要执行此代码就必须所有条件都满足。

  保存运行,现在应该一切正常了。敌兵的生命值不会变得小于0。

  本课到此为止。注意本课的重点是数据表。所以其他东西你不是完全理解也没关系。该休息一下了,下节课再见。


掌叔
2008-06-06 16:21:30

  其实我们之前的程序已多少用到了一些表达式,现在我们就深入的学习一下。有些我们已经熟悉了,比如“if”语句和“>”运算符。我们将编写一个利用这些表达式的程序,它与我们上一次写的很相似,只是更深一些。先创建一个新文件,但暂时不要添加代码,除非得到明确告知,象上一讲一样。

  首先要讲的就是算术表达式,太简单了,无非就是加减乘除而已。我也不多说了,举例如下:

代码:
x = 3 + 5 -- 加法
y = 3 - 1 -- 减法
a = 4 * 5 -- 乘法
b = 10 / 2 -- 除法
c = 2 ^ 8 -- 乘方

  不难吧,嗯?记住,我教你的都是基础,我不会介绍每一个命令。等你学会了我传授的,你就能自己查询资料,学习我没有教的东西了。

  接着要讲的是关系表达式。这是编程时超常用的,用来比较数据。返回真或假(又称为布尔值)。下面是列表:

  == (相等)
  ~= (不相等)
  < (小于)
  > (大于)
  <= (小于或等于)
  >= (大于或等于)

  用这些就能检查各种有用的信息。例如,如果一个敌兵的生命值是0,他是不是就该死去了呢?或者检查一下我们是否够钱买下那把新剑!当然光靠这些运算符还是不行的,大多数情况下我们还需要下一组新的表达式。

  我的最爱第一个,“if”语句。用来检测某个条件是否为真,如果是则执行一段代码。还可以增加功能,如果条件不满足则做另一件事。简单的“if”表达式如下:

代码:
if <某条件> then
<执行某代码>
end

  现在举个现实的例子。假设有个程序设了两个变量,myCash 和 itemCost。myCash 是我们拥有的金钱数,itemCost 则是我们想要购买的物品价格。在游戏中我们需要检查是否有足够的钱购买该物品。如果有,则从总金钱数中扣除,打印一条已购买消息到屏幕上。如果钱不够,则打印一则消息到屏幕,通知玩家不够钱买。代码应该跟下面的差不多:

代码:
myCash = 253
itemCost = 100

if myCash > itemCost then
myCash = myCash - itemCost
screen:print(100,100,"Item Bought",blue)
else
screen:print(100,100,"Not enough money",blue)
end

  让我们分析一下。首先,我们设了两个变量,告知我们有多少钱,和物品值多少钱。然后是我们的if表达式,其含义等同于 "if 253 > 100 then",就是问变量 myCash 的值是否大于变量 itemCost 的值。如果是...则运行下一行,获取 itemCost 的值并将其从 myCash 的值中扣除。换言之就是 253-100,然后将答案存于 myCash。接着下一行则是打印字串“Item Bought”至屏幕。在下一行我们用了"else",即随后代码将在 myCash 不大于 itemCost 时执行,在此情况下它将打印字符串 "Not enough money" 至屏幕。最后,自然还是“end”,结束整个表达式。

  在上例中,我们有足够的钱购买那个物品。因此“else”段的代码不会被执行。许多时候你甚至不需要使用else,有时你则需要使用多个else。这种情况下你可以使用“elseif”。兹举一例:

代码:
myNumber = 4
if myNumber == 1 then
screen:print(100,100,"Your lucky number is 1",blue)
elseif myNumber == 2 then
screen:print(100,100,"Your lucky number is 2",blue)
elseif myNumber == 3 then
screen:print(100,100,"Your lucky number is 3",blue)
else
screen:print(100,100,"Your number isn't very lucky!",blue)
end

  这段代码检查 myNumber 是否等于 1、2 或 3。如果是,则打印“Your lucky number is (number)”。但是如果 myNumber 不等于 1、2 或 3 则打印“Your number isn't very lucky”。注意,此例程可以简单化,只用一个 else 无需 elseif,只要改成“if myNumber > 0 and myNumber < 4 then”即可。接下来就让我们学一学“and”关键字和其他的知识。

  如上所述,逻辑运算符可以用来比较多个事物。我们将学习其中两个,“and”和“or”。还有一个“not”。

  假设你在编写一个角色扮演游戏,有点象 Diablo 那种。游戏的玩家角色想要装备一把屠龙刀(Dragon Slayer)。你打算设定玩家只有达到20级且是战士(warrior)类时方能装备该物品。在程序开始我们创建一个数列存放这些信息,例如:

代码:
Player = { }
Player[1] = { level = 15, class = "warrior", weapon = "knife" }

  稍后我们的代码将检查玩家是否有资格装备该武器,如下:

代码:
if Player[1].level >= 20 and Player[1].class == "warrior" then
Player[1].weapon = "Dragon Slayer"
end

  第一行检查玩家的等级是否大于或等于 20,还有玩家的类别是否属于战士类。如果两个条件都满足,则玩家的武器就被设定为屠龙刀。如果只有一个条件满足,则返回值为假,那么武器就不会变更。在本例中,我们的玩家虽然的确是战士类,但是等级只有15,没有大于或等于 20,所以该玩家无法使用屠龙刀。你可以使用不止一个“and”,例如你可以这样写:

代码:
if hat == "blue" and shirt == "red" and age > 15 and state ~= "Florida" then
screen:print(100,100,"You are accepted!",yellow)
end

  使用“or”的方法完全一样,除了只需要其中一个条件满足返回值即为真以外。例如:

代码:
myCar = "Mustang"

if myCar == "Corvette" or myCar == "Mustang" then
screen:print(10,20,"Nice car!",green)
end

  本例检查 myCar 是否等于 Corvette 或 myCar 是否等于 Mustang。二者只要有一个为真,程序就打印“Nice Car!”到屏幕上。myCar 不等于 Corvette 但等于 Mustang,所以返回值为真。

  那么,让我们付诸行动吧。我们还是用上一讲编写的程序,用我们今天学的修改一下。现在打开那个文件。什么?你扔掉了?别着急,去下载上一讲的附件吧。

  上一讲中,我们设定每次按下 X 键时减去敌兵生命值 5 点。本课我们将添加一些表达式,让程序能在当前敌兵死亡时让另一个敌兵上阵。我们还得检查何时已无兵可用。

  第一件要做的就是添加一个变量,告知我们正与之作战的当前敌兵是哪个。我们会给该变量赋一个值。你该发现我们的敌兵数列有5个敌兵,由1至5分别索引。我们将用这些数字来指称这些敌兵。在代码顶端,绿色变量的下面,添加此变量:

代码:
currentEnemy = 1

  现在我们添加一些代码,检查当前敌兵的生命值是否为 0,如果是则将 currentEnemy 变量的值递增 1,但是只在该值不超过 5 的情况下,也就是最后一个敌兵。在你的 if pad:cross() 代码行之上添加此代码:

代码:
if Enemy[currentEnemy].health == 0 and currentEnemy <= 4 then
currentEnemy = currentEnemy + 1
end

  注意,此处我们用 currentEnemy 变量替代了数列 Enemy 中的数字。这样我们只要设置 currentEnemy 变量为正确的敌兵,那么代码中任何使用 Enemy 数列的地方都可以使用currentEnemy,而无需检查每个敌兵((Enemy[1] Enemy[2] 等等...),这样代码更自动化。有了“<= 4”的检查项,currentEnemy 变量就不会超过 5。如此说来,我们得修改几行代码。找到下面几行:

代码:
screen:print(250,10,"Enemy Health: " .. Enemy[1].health,green)
screen:print(250,20,"Enemy Type: " .. Enemy[1].type,green)

修改为:

代码:
screen:print(250,10,"Enemy Health: " .. Enemy[currentEnemy].health,green)
screen:print(250,20,"Enemy Type: " .. Enemy[currentEnemy].type,green)

  现在当敌兵变更时,程序会自动打印出正确敌兵的生命值和兵种。我们还需要修改下述两行:

代码:
if pad:cross() and oldpad:cross() ~= pad:cross() and Enemy[1].health > 0 then
Enemy[1].health = Enemy[1].health - 5

  修改为:

代码:
if pad:cross() and oldpad:cross() ~= pad:cross() and Enemy[currentEnemy].health > 0 then
Enemy[currentEnemy].health = Enemy[currentEnemy].health - 5

  作为结束,我们将添加一些代码,如果5个敌兵都被杀死则打印“All enemies are dead”至屏幕。把这个代码置于主循环中打印敌兵生命值和类型的screen:print行下。

代码:
if currentEnemy == 5 and Enemy[currentEnemy].health == 0 then
screen:print(50,100,"All enemies are dead",green)
end

  这段代码是说,如果当前敌兵编号是 5 且其生命值等于 0 则打印“all enemies are dead”。保存你的代码,运行之。按下 X 直到一个敌兵死亡。然后下一个敌兵会上阵,一个跟一个,直到所有 5 个敌兵都被杀死为止。

  我希望本课能对你今后的表达式学习有所帮助。


掌叔
2008-06-06 16:23:19

  到目前为止,我们所有的程序均只使用了文本。我想下水前先湿湿脚比较好,有了基本编程知识,将使一切容易领会。在本节课中,我将介绍使用图像,及一些获取图像信息的命令。

  至于我们打算编写什么?嗯,就做一个看起来真正象是游戏的程序吧。它不会做任何华而不实或令人兴奋的事情,但足够应付本教程的目的了,图像。我们这里所学的只是众多图像命令的一部分。我们将在以后的教程里进一步了解这些。你也可以自己在luaplayer的文档中查询。我们的作品将包括一个可移动的角色,他可以在屏幕中自由走动,但不能够走出屏幕边缘。

  第一件事是将下面提供的图像保存到你的工作目录。这些图像只是做个样本,你完全可以用自己的。LuaPlayer 可以载入 PNG 和 JPEG 图像。大多数情况下我会使用 PNG 而不选 JPEG,尤其当你需要透明的时候。现在保存这些图像:[url=http://down.tgbus.com/soft/20082.shtml]点此下载[/url]

  解压至工作目录。你应该有如下3个图像:

  player.png
  grass.png
  flower.png

  现在我们先回到教程的第一部分,稍后再编写我们的代码。

载入图像

  要在lua中使用图像,你必须先将其载入程序,并赋予一个变量(或数据表)。你可以在程序开头这么做,也可以稍后再做,这取决于你什么时候需要该图像。在我们待会儿要写的代码中我们会在开始就预先载入图像。这里是一个载入图像的示例:

代码:
grass = Image.load("grass.png")

  这一句将载入图像文件 "grass.png" 并赋予变量 "grass"。要正确载入图像你得确保该图像与你的lua脚本文件位于同一目录。如果你把图像放在一个子文件夹,例如 "Images",你就得用 "Images/grass.png" 而不是 "grass.png"。大小写也不要搞错。image 的 I 要大写,load 的 l 要小写。载入图像很简单吧?!

显示图像

  要在屏幕上真正显示图像并不比载入更难。这里是完整命令:

代码:
screen:blit(x, y, Image source, [sourcex, sourcey, width, height], [alpha = true])

  这里有很多信息要填!不是所有的参数都必要,方括号里的如果你不需要你可以完全忽略。现在让我们解释一下。

  代码起始的 screen:blit 是一个命令,它将图像粘贴到屏幕上。

  括号里的内容是真正乐趣所在。

  x 和 y 与 print 命令中的相同。指定图像粘贴到屏幕上的位置。

  Image source 指定要粘贴的图像。上文中我们载入了一个图像叫 grass,所以我们可以用 grass 作为图像源。

  [sourcex, sourcey, width, height] 是可选的。用来载入某幅大图的一部分。分片图是个极好的例子。你可以载入一幅单图,分成好几个部分,使用此代码你可以只粘贴其中的一部分。sourcex 和 sourcey 是要截取区域开始点的 x 和 y 坐标值。这样粘贴的不是整幅图,而是其中一部分。

  width 和 height 是指图像截取区域的宽度和高度。

  命令的最后是 alpha 参数。这是用来设定图像的透明度的。如果设为 true 则图像透明, 设为 false 则显示原图。

  本课我们不会使用全部参数。

获取图像尺寸

  获取载入的图像尺寸有时很有用,这要用下面两个命令:

代码:
image:width()
image:height()

  实际使用时,用你想获取宽高的图像变量名替换此处的“image”。例如,要获取我们的grass图像的宽度和高度,你可以用:

代码:
grass:width()
grass:height()

开始编写程序

  让我们开始编写我们的游戏。第一件要做的就是载入我们的图像。创建一个新文件,输入如下代码:

代码:
grass = Image.load("grass.png")
player = Image.load("player.png")
flower = Image.load("flower.png")

  就这样。我们已经把我们的三个图像载入了程序。

  现在我们来用用那些获取图像尺寸的命令。先输入下列代码,然后我会解释的。我还想提醒你,你如果自己手工输入这些代码会比复制粘贴的效果要好。这样你会对这些命令记得更牢。不管怎样,添加如下代码:

代码:
screenwidth = 480 - player:width()
screenheight = 272 - player:width()

  这有什么用吗?我来解释一下它的用途。这部分代码帮助我们让玩家角色到达屏幕边缘时停止,无论是往左或往右(水平方向)。我们知道PSP屏幕尺寸是480,但是如果我们设定屏幕边界是480,我们的角色就会超出屏幕32个像素,因为角色是32像素宽。player:width() 获取玩家图像的宽度,也就是 32 像素。从屏幕的宽度和高度中扣除该值就可以让角色图像在屏幕边缘停止。

  接着我们来创建一个数据表以存储玩家角色的信息。此例程中需要的唯一信息就是玩家在屏幕上的 x 和 y 坐标位。输入如下代码:

代码:
Player = { } Player[1] = { x = 200, y = 50 }

  这将存储玩家在屏幕上的x、y位置。我们将让玩家从 x 200、y 50 的位置开始。

  现在开始我们的主循环。输入如下代码:

代码:
while true do
pad = Controls.read()
screen:clear()

  在下一部分代码中我将引入一个新的循环命令。就是“for”循环。我先举个例子让你了解它的工作方式,无需放在你的代码中。这个例子通过使用一个小循环打印了5个玩家的武器。

代码:
for a = 1,5 do
screen:print(10, 10, Player[a].weapon, green)
end

  要使用 for 循环,你要赋予一个临时变量起始值和终止值。

  在上文中我们说 "for a = 1,5 do",这意味着创建一个循环,其变量 a 的起始值为 1,终止值为 5。你可以理解为“a 等于 1 至 5”。

  然后在循环中我们要打印玩家的武器到屏幕上。

  最后一行是 end 语句,必须使用以终止循环。

  这样,该循环将不断重复直到 a 等于终止值,然后循环终止。第一次循环执行时 a 等于 1。下一次循环时 a 将等于 2,然后是 3,然后是 4,最后是 5。这样就可以快速的将玩家 1 至 5 的武器都打印到屏幕上。当然。实际操作时你需要更改 print 命令的屏幕坐标,不然就都打印到一起去了。但是这展示了该循环的用法。在 for 循环中你也可以根据需要使用 if 语句。

  我们将使用这个命令把我们的grass图像平铺在整个屏幕上。输入下述代码,然后我会解释。当然,如果要做一个带卷轴之类的复杂游戏,使用平铺引擎将是更好的办法,不过目前我们不需要用它。

代码:
for a = 0, 14 do
for b = 0,8 do
screen:blit(32 * a, 32 * b, grass)
end
end

  这里我们在一个 for 循环中嵌套了又一个 for 循环!这将把 grass 图像粘贴到整个屏幕上。横向贴 15 幅图,纵向 9 幅图。请注意,我们设定起始值为 0 而不是 1,这样图像会从屏幕最左边贴起。

  现在让我们再贴一些图像。

代码:
screen:blit(100,100,flower)
screen:blit(300,220,flower)
screen:blit(Player[1].x,Player[1].y,player)

  这一段就是简单的贴上两幅 flower 图像,还有 player 的图像。很快,当我们使用移动按键时我们将在每次循环中让玩家的坐标随着按键的按下而改变。使用上述 blit 命令我们的 player 图像将自动粘贴到屏幕的正确区域。

  现在让我们加上那些按键,以检测是否按下了方向键。输入下述代码:

代码:
if pad:left() and Player[1].x > 0 then
Player[1].x = Player[1].x - 2
end

if pad:right() and Player[1].x < screenwidth then
Player[1].x = Player[1].x + 2
end

if pad:up() and Player[1].y > 0 then
Player[1].y = Player[1].y - 2
end

if pad:down() and Player[1].y < screenheight then
Player[1].y = Player[1].y + 2
end

  这些代码将检测我们是否按下了“上下左右”键。使用 and 语句同时确保我们没有超出屏幕边界。既然屏幕的最左侧和最顶端的坐标是 0,我们只要检查我们的位置是否大于 0 即可。其他部分我们使用 screenwidth 向右走,screenheight 则用来向下走。这些变量我们之前已创建。如果我们的位置通过了 if 测试,则玩家的 x 或 y 坐标递增或递减 2 点。每次主循环执行都会将 player 图像粘贴到提供给它的新位置。

  最后让我结束循环。使用如下代码:

代码:
screen.waitVblankStart()
screen.flip()
end

  保存你的工作,运行吧。使用方向键让角色在屏幕上穿梭。你应该是无法穿越屏幕边缘的。


掌叔
2008-06-06 16:24:21

  本课我们将学习怎么使用函数。创建一个函数相当于新建一个属于你自己的Lua命令。你可以将经常使用的代码放进函数中,这样你就可以随时调用了。函数放在主循环之外。一般放在代码开头的变量声明附近比较好。

  首先让我们看一下函数是怎么声明的。检查下面代码:

代码:
function functionName()
(函数中要执行的代码。
可以使用多行代码。)
end

  这就是函数的构成方式,让我们看看下面的示例函数,它将在每次调用时打印一条简单的消息到屏幕。

代码:
function printMessage()
screen:print(100,100, "Functions are fun!")
end

  现在,要想在程序中打印这条消息,就必须在要使用的地方调用这个函数。就上例而言使用下述代码就可以调用该函数,将函数的代码执行一次。

代码:
printMessage()

  这么简单?!呣...恐怕就是这样!使用上述代码将会执行我们先前创建的函数代码。通过调用 printMessage() 就可把 "Functions are fun!" 打印到屏幕上。

  当你开始编写更复杂的程序或游戏时,你将会大量使用函数。在你的主循环中调用函数而不是实际的代码将使你的代码更加整齐易懂。之前我说过函数放在主循环外面,这是对的。但是,从主循环内调用它们是没问题的。

  函数也可以创建来返回一个值。创建函数时加个括号和参数就行。比如说,我们要建个函数将两个数值加起来并把结果返回。首先我们得创建这个函数。看下面:

代码:
function additUp(a, b)
sum = a + b
return sum
end

  请注意,在括号中我们放了字母 "a" 和 "b",其实你可以用任何字母或者甚至单词。它们是用于本函数内部的变量,也只存在与本函数中。因为我们打算将两个数相加,所以我们需要两个变量来存储。在你的函数中可以使用任意多个变量,只要你用逗号分开它们。

  然后我们在函数中创建了一个变量叫 "sum",它将被赋予变量 "a" 和 "b" 相加的数值。等你看到它的调用就会更清楚了。接下来我们使用了一个命令 return,它告诉函数一旦我退出函数将返回一个值给此函数。我们现在要返回的是变量 "sum" 的值。最后,我们 end 了我们的函数。

  现在让我们看看如何使用这个函数,再做一些解释。看如下代码:

代码:
screen:print(100,100,additUp(5,6), green)

  这将把我们函数的结果打印到屏幕上。请注意,在调用函数时我们使用了实际的数值。此例中我们使用了 5 和 6。就是说将数值 5 赋值给 a,数值 6 赋值给 b。现在在我们先前创建的函数内,任何 a 或 b 被使用的地方,都将被我们实际赋予的数值代替。所以函数中 "sum = a + b" 这句话将被解析为 "sum = 5 + 6"。这将把 11 这个数值赋予 "sum" 变量然后返回给我们的 print 命令,打印到屏幕上。

  如果需要我们也可以将此信息存在一个变量中。这样我们就不必在不需要的时候不得不立即使用它,或者在以后重复使用时不用每次都跑一遍整个函数。请看下面:

代码:
myTotal = additUp(5,6)

  这句代码就把我们函数的返回值存储在变量 myTotal 里。myTotal 将等于 11。

  让我们看一下上一讲我们编写的程序。代码应该与下面的类似:

代码:
grass = Image.load("grass.png")
player = Image.load("player.png")
flower = Image.load("flower.png")

screenwidth = 480 - player:width()
screenheight = 272 - player:width()

Player = { }
Player[1] = { x = 200, y = 50 }

while true do
pad = Controls.read()
screen:clear()

for a = 0, 14 do
for b = 0,8 do
screen:blit(32 * a, 32 * b, grass)
end
end

screen:blit(100,100,flower)
screen:blit(300,220,flower)

screen:blit(Player[1].x,Player[1].y,player)

if pad:left() and Player[1].x > 0 then
Player[1].x = Player[1].x - 2
end

if pad:right() and Player[1].x < screenwidth then
Player[1].x = Player[1].x + 2
end

if pad:up() and Player[1].y > 0 then
Player[1].y = Player[1].y - 2
end

if pad:down() and Player[1].y < screenheight then
Player[1].y = Player[1].y + 2
end

screen.waitVblankStart()
screen.flip()
end

  我们可以创建一些函数,让主循环看上去更干净整齐一些。我们可以将所有的方向键检查放入一个函数,还可以将屏幕绘制的代码放入一个函数。这两个都不需要返回值。看看下面的代码,了解函数的使用:

代码:
grass = Image.load("grass.png")
player = Image.load("player.png")
flower = Image.load("flower.png")

screenwidth = 480 - player:width()
screenheight = 272 - player:width()

Player = { }
Player[1] = { x = 200, y = 50 }

-- Function to check player movements
function playerMovement()
pad = Controls.read()
if pad:left() and Player[1].x > 0 then
Player[1].x = Player[1].x - 2
end

if pad:right() and Player[1].x < screenwidth then
Player[1].x = Player[1].x + 2
end

if pad:up() and Player[1].y > 0 then
Player[1].y = Player[1].y - 2
end

if pad:down() and Player[1].y < screenheight then
Player[1].y = Player[1].y + 2
end
end

-- Function to paste images to screen
function pasteImages()
for a = 0, 14 do
for b = 0,8 do
screen:blit(32 * a, 32 * b, grass)
end
end

screen:blit(100,100,flower)
screen:blit(300,220,flower)

screen:blit(Player[1].x,Player[1].y,player)
end

-- Main Loop
while true do

screen:clear()

pasteImages()

playerMovement()

screen.waitVblankStart()
screen.flip()
end

  你看,我们的主循环干净许多了吧。所有复杂的、细节的代码被放到了函数里,而在主循环中我们只需调用它。原先代码中混在循环中的一堆代码都被移走,放到了代码顶端。

  我希望这能帮助学习有关函数使用的知识。以后的教程我们将使用函数了。


掌叔
2008-06-06 16:24:59

  本课我们将学习如何读取文件和写入文件。这对于制作游戏来说是很有帮助的。你可以调用别的文件中的代码,也可以保存最高分、游戏存档、玩家状态等信息到文件里……

  我们这就开始吧!

  首先,让我们看一个简单的命令:dofile。

  这个命令会读入另一个文件的代码并立即执行。

代码:
dofile("./test.lua")

  很简单的命令。注意 ./ 是指根目录,不是子目录。如果是子目录,应该这样用:

代码:
dofile("./files/test.lua")

  那么,如果我们的文件 test.lua 包含下述代码:

代码:
playerx = 10
playery = 20
enemyx = 40
enemyy = 50

  ...则一旦我们用 dofile 调用这个文件,这些变量将立即得到声明。文件不是非得用 lua 后缀,txt 或其他后缀都可以。

从文件中读取一行:

  假设你有一个很多行文本的文件,你想从中读取一行,并打印到屏幕上。为此我们可以使用 io.open() 命令打开文件并读取。

  命令的语法是 io.open(filename, mode)。此处我们将使用 r 作为 mode 的值,意指读取(read)模式。
作为参考,下面是所有可用模式的列表:

引用:

r - 读取模式
w - 写入模式(覆盖现有内容)
a - 附加模式(附加在现有内容之后)
b - 二进制模式
r+ - 读取更新模式(现有数据保留)
w+ - 写入更新模式(现有数据擦除)
a+ - 附加更新模式(现有数据保留,只在文件末尾附加)

  第一件要做的事情就是打开文件并存为一个变量。看下面。

代码:
file = io.open("testRead.txt", "r")

  现在我们的文件 testRead.txt 已打开并处于读取模式。接着我们使用 read() 命令读取文件中的单行内容存为另一个变量。

代码:
ourline = file:read()

  这将读取我们的文本文件的第一行。下一次我们使用 read() 命令它将读取第二行。再下一次它会读取第三行,依此类推。

  请注意,此命令前的 file 是我们之前打开文件并保存的变量名,你可以使用任何其他名字。file.read() 还可以使用下列任一个参数,只要用引号括起来放入括号里就可以。

引用:

*n - 读取一个数字并返回它。例:file.read("*n")
*a - 从当前位置读取整个文件。例:file.read("*a")
*l - (默认) - 读取下一行,在文件尾 (EOF) 处返回 nil。例:file.read("*l")
number - 返回一个指定字符个数的字符串,或在 EOF 时返回 nil。例:file.read(5)

  既然我们已经读取到所要的那一行,我们需要关闭打开的文件,像这样:

代码:
file:close()

  现在要打印我们的文件至屏幕我们只要这么做:

代码:
screen:print(10,10,ourline,white)

读取所有行

  要读取一个文件的所有行,你可以在一个 for 语句中使用上述技巧。请看下述代码:

代码:
y = 10

file = io.open("testRead.txt","r")

for line in file:lines() do
y = y + 10
screen:print(100,y,line,white)
end

file:close()

  这个代码设置了一个起始值 y 用于打印,这样它可以在屏幕不同的纵坐标打印每一行。照例,先打开文件。然后是 for 语句,这句代码是说文件的行数有多少下面的代码就执行几次。我们的 y 坐标则每次循环加上 10 以便打印命令可以打印下一行。

写入文件(覆盖)

  写入文件的方式大致相同。下面的代码将覆盖任何先前写入文件的内容:

代码:
file = io.open("testRead.txt","w")
file:write("hello")
file:close()

  注意,我们这次使用了 w 模式,而不是 r 模式,因为我们是要写入而不是读取。我们用 file:write() 写入括号里作为参数的、用引号括起来的文本。你也可以用变量替代。如果你使用变量就不要用引号,请看下例:

代码:
file = io.open("testRead.txt","w")
myText = "Hello"
file:write(myText)
file:close()

写入文件(附加)

  你可以使用附加模式,这样就可以在写入文件时将新内容附加在已有文本末尾,而不是删除原来的。做法与上面基本相同,除了模式换成“附加”之外。请看代码:

代码:
file = io.open("testRead.txt","a")
myText = "
Hello"
file:write(myText)
file:close()

  注意到另一个区别没?在 myText 变量里我们为字符串添加了
。这个命令代表换行,这样写入的文本将从下一行开始。虽然在你的文件中可能显示为同一行只是中间隔了一个方块,但是技术上它是新的一行。在 C/C++ 语言中也是这么用的。

  将你所学的知识用在自己的小程序上吧。

  我们的下一讲将讨论音频的处理,也会使用我们此处所学的读写文件的命令做一个小程序。


掌叔
2008-06-06 16:27:26

  现在该是时候给我们的项目加点响声了。任何游戏都需要声音,这一讲我们就来学学如何在Luaplayer中发声。开始上课!

  luaplayer 可以播放如下音乐格式*:UNI, IT, XM, S3M, MOD, MTM, STM, DSM, MED, FAR, ULT 或 669,WAV 文件可用作声音。MIDI可以先转换为上述格式再使用,比如 Modplug Tracker 就可以转。请记住 luaplayer 不支持 mp3 文件*。在学习了一些音频命令之后我们将结合之前的成果创建一个小程序。

  *译者注:此说法已过时,目前luaplayer已支持mp3,ogg,aa3,oma,omg,详见HM版相关函数。但是由于luaplayer目的在于编游戏,所以对mp3播放的支持程度不高。

  先来看第一个命令,此命令将播放一个音乐文件。

代码:
Music.playFile( string file, bool loop )

  此处的“string file”是指你的音乐文件名,例如“song.xm”。“bool loop”处放置“true”或者“false”。设“true”则一直循环播放该音乐,“false”则只播一遍。下面是个放歌的示例:

代码:
Music.playFile("mysong.mod", true)

  接下来看看这个命令。它们是在开始播放音乐之后使用的:

代码:
Music.pause()
Music.stop()
Music.resume()

  这些命令不用解释你也能看懂。
  Music.pause() 将暂停歌曲播放。
  Music.stop() 将停止歌曲播放。
  Music.resume() 将恢复播放被暂停的歌曲。

  还有一个命令是用来侦测一首歌是否正在播放。这将返回 true 或 false。看一下:

代码:
Music.playing()

  假如说我们想侦测我们的歌曲是否在播放,如果是则打印一条消息到屏幕。我们可以这样做:

代码:
if Music.playing() == true then
screen:print(10,10,"Music is playing",white)
end

  还有一个命令是用来设置音乐文件的音量的。只要在括号中填入一个数值,范围在 0 至 128 之间。下面是命令格式:

代码:
Music.volume(Number)

  现在让我们学学用于音效的声音文件,尤其是 wav 文件。这和播放音乐文件有些区别。下面是如何载入一个声音:

代码:
bonkSound = Sound.load("bonk.wav",false)

  wav 文件作为一个变量被载入,稍后我们就可以用这个变量来指称该声音。此处我们设“false”是以防循环播放。记住设为“true”将循环播放,如果你的确是这么想的话。

  关于 WAV 文件的一个要点是,在 luaplayer 中只能使用单声道的,立体声的不行。你可以通过右键点击文件查看其属性的摘要页来确认这一点。

  现在让我们来再学一些命令。下个命令是播放该文件。使用你载入声音时设的变量名。如下:

代码:
bonkSound:play()

  但是我不建议你这样播放声音文件。这会导致一个常见错误,类似“loop ingettable(无法取得循环)”之类消息。下面是我播放声音文件的方法。不同之处就是用另一个变量取代 bonkSound。

代码:
local sound = bonkSound
voice = sound:play()
--要停止声音使用:
voice:stop()

  还有一个命令与音乐文件的命令类似,侦测声音是否正在播放。命令如下:

代码:
voice:playing()

  还有一些声音和音乐方面的命令等待你去探究,但是这些已足够你使用声音和音乐了。

  现在我们开始做一个小玩意儿。我们将以一个菜单屏幕为起始,用来选择蓝色还是黑色背景。按下 X 时程序会往下执行,而背景色已按照我们的选择设好。此处我们的程序将显示其已被执行多少次。怎么做?嗯,就是每次在菜单屏幕按下 X 时程序就打开一个文件(我们稍后会创建),然后写一个数字进文件。程序也会从该文件中读取该数字并保存在一个变量中。如果你完全退出程序然后再返回,它仍然能显示被执行了多少次,因为这个数据已存在一个文件里了。那么,让我们忙起来吧。

  首先,我想要你用notepad或任何其他编辑器创建一个普通的txt文件。在该文本文件中输入数字 0。保存为 counter.txt。就是一个普通的文本文件。

  然后开始编写我们的 lua 文件。先创建一些要用到的色彩对象:

代码:
white = Color.new(255,255,255)
blue = Color.new(0,0,255)
black = Color.new(0,0,0)

  接着是一些变量。

代码:
oldpad = Controls.read()
startcolor = black
gamestate = "menu"

  oldpad 将储存我们上一次按下的按键。
  startcolor 存储我们稍后将在程序第二部分使用的颜色。我们以 black 开始。
  gamestate 则是用来查看该使用哪个函数。我们将创建两个函数。一个是显示菜单的,另一个执行程序余下的工作。既然我们从菜单开始,该变量的起始值就如此设置。

  下一部分就要处理声音了!首先,请下载两个小小的 WAV 文件,放在程序所在目录下。

  现在让我们在程序中载入这些 wav 文件。我们不想让它们循环,所以使用 false。

代码:
menusound = Sound.load("beep.wav",false)
goodbye = Sound.load("goodbye.wav",false)

  够简单吧!还记得上一讲的那些文件命令吗?我们来次回顾吧。此处我们将打开我们之前创建的 counter.txt 文件,并从中读取数值存入变量 counter 中。

代码:
file = io.open("counter.txt", "r")
counter = file:read("*n")
file:close()

  请注意我们使用了"*n"以读入一个数字。

  接着我们将创建一个数据表,用于我们的起始屏幕选择器。这会包含一个我们创建来用作选择器的图像,以及它的 x 和 y 坐标值。数据表下一行的命令则将我们的选择器图像以蓝色清空。代码如下:

代码:
selector = { image = Image.createEmpty(145,15), x = 147,y = 77 }
selector.image:clear(blue)

  现在我们建一个称为 drawMenu() 的函数,执行菜单所有的必要任务。下面是起始行:

代码:
function drawMenu()

  我们将把此函数用作一个循环函数,所以每次循环我们先清屏然后读取按键的输入。代码如下:

代码:
screen:clear()
pad = Controls.read()

  接下来我们将把我们的选择器绘制(blit)到屏幕上,并打印两行字。代码如下:

代码:
screen:blit(selector.x,selector.y,selector.image)
screen:print(150,80,"Start Game (Black)",white)
screen:print(150,100,"Start Game (Blue)",white)

  请注意我们是怎么使用数据表的值来绘制选择器到屏幕上的。这个选择器图像将会上下移动以高亮显示被选择的菜单项。要确保在打印文字之前绘制,否则图像将覆盖文字。

  打印的两行字则是我们给玩家的选项。要么以黑色屏幕开始游戏,要么是蓝色的屏幕。

  在函数的下一部分将检测按键是否按下。先做上键的部分吧。

代码:
if pad:up() and oldpad:up() ~= pad:up() then
selector.y = 77
startcolor = black
local sound = menusound
voice = sound:play()
end

  第一行是说如果上键被按下并且不是最后被按下的按钮则.....

  第二行将 selector 的 y 值设置为 77。这是为了在文本周围贴上图像以便看起来像是被选择的。
然后设置 startcolor 为 black。

  接着使用我之前建议的方法播放 menusound 这个 wav 文件。

  现在处理下键。代码与上面很相似,除了图像向下一点,颜色改为蓝色以外。代码如下:

代码:
if pad:down() and oldpad:down() ~= pad:down() then
selector.y = 97
startcolor = blue
local sound = menusound
voice = sound:play()
end

  现在来处理 X 按钮。

代码:
if pad:cross() and oldpad:cross() ~= pad:cross() then
gamestate = "game"
counter = counter + 1
file = io.open("counter.txt","w")
file:write(counter)
file:close()
end
end

  此处我们将 gamestate 设为“game”以便菜单的代码不再执行(在我们编写了后面的代码之后)。我们以 1 为基数递增 counter 变量。然后打开 txt 文件,写入 counter 的新值,替换文件里的旧值。不要忘了关闭文件。

  注意,多出来的那个 end 是用来终结函数的。这个函数已完成了!

  我们下一个函数将执行离开菜单后的游戏代码。这个函数相当小巧简单,全部代码如下:

代码:
function playGame()
screen:clear(startcolor)
pad = Controls.read()
screen:print(100,100,"This program has been executed " .. counter .. " times.",white)
screen:print(100,110,"Press Start to exit to Menu",white)

if pad:start() then
gamestate = "menu"
local sound = goodbye
voice = sound:play()
end
end

  在此函数中,请注意我们使用了在菜单中通过 startcolor 变量选择的颜色清屏。打印了两个消息,检查了 start 按键的按下。按下 start 键将把 gamestate 设回 menu 并播放一个 wav 文件。

  接下来开始我们的程序主循环。由于使用了函数,此处很简短。在循环中我们将检查游戏的状态并执行该状态的函数。代码如下!

代码:
while true do

if gamestate == "menu" then
drawMenu()
end

if gamestate == "game" then
playGame()
end

screen.flip()
oldpad = pad
end

  ...接下来... Oh yeah!已经完成啦!运行试试看吧。


掌叔
2008-06-06 16:28:03

  本课我们将学习在程序中如何使用毫秒定时器。定时器可以在程序中派很多用处。通过定时器你可以使某些代码仅在一个正确的时间段内执行。

  下面这个小型的PSP程序将教您使用定时器,并显示讯息在屏幕上。显示的信息内容取决于计时器的时间。

  首先,让我们创建一个白色对象。

代码:
white = Color.new(255,255,255)

  为了创建一个定时器,我们使用命令 Timer.new()。 我们将此定时器存为一个变量。下面让我们创建一个定时器。

代码:

counter = Timer.new()

  要启动或停止一个定时器请使用命令 timername:start() 和 timername:stop()。我们想在程序开始启动我们的定时器,所以加上下面代码:

代码:
counter:start()

  请注意我们在命令中使用了刚才创建的定时器名字。 下一步,我们来创建我们程序的主循环,并添加每次循环清屏的代码。如下:

代码:
while true do
screen:clear()

  现在,在我们的程序中将需要与定时器的计时步伐保持一致。我们将利用定时器的当前时间显示信息到屏幕上。我们可以通过命令 countername:time() 得到定时器的当前时间。添加如下代码:

代码:
currentTime = counter:time()

  这会把定时器的当前时间保存到变量 currentTime 中。下面我们加入一些代码,在每次循环时将定时器的当前时间打印到屏幕上。代码如下:

代码:
screen:print(10,10,"Counter Time: " .. currentTime,white)

  现在,我们将使用一些 if 语句来判断我们的定时器是否在某个时间,若是则显示一则消息。先来加上第一项:

代码:
if currentTime < 1000 then
screen:print(100,100,"Less than 1000",white)
end

  这段代码是检查定时器的时间是否小于1000。如果是,则“Less than 1000”将打印到屏幕上。现在,让我们再加入一项。

代码:
if currentTime > 1000 and currentTime < 2000 then
screen:print(100,100,"Greater than 1000",white)
end

  这将检查定时器的时间是否比1000大,但小于2000。如果是,则“Greater than 1000”将被打印。最后的 if 语句将在时间超过2000时把我们的定时器复位回零。 我们可以使用 timername:reset(number) 命令重置定时器。

  括号内的数值是定时器将被设回的起始时间。复位定时器也将停止定时器,所以我们还要重新启动它。代码如下:

代码:
if currentTime > 2000 then
counter:reset(0)
counter:start()
end

  通过此代码,如果定时器时间超过2000,我们的定时器将复位回零并重新开始。最后,让我们结束我们的主循环。

代码:
screen.waitVblankStart()
screen.flip() end

  保存并运行你的程序,看看动起来的定时器吧。


掌叔
2008-06-06 16:28:37

  本课我们讲解如何在 lua 中制作一些简单的碰撞。本方法使用盒式碰撞,没有达到像素级精度。因为这是你的第一次碰撞尝试,所以简单一点好。

  我们将要做的这个小演示将使用一个可移动的玩家角色。屏幕上还有三个实施了碰撞效果的方块。玩家将无法穿过这些方块。

  首先我们创建两个要用的色彩。一个用于玩家,一个用于我们的方块。

代码:
green=Color.new(0,255,0)
white = Color.new(255,255,255)

  接着创建玩家和方块要用的图像。先创建空的 32x32 的方块,然后用我们刚刚创建的颜色填充它们。

代码:
player = Image.createEmpty(32,32)
player:clear(white)
block = Image.createEmpty(32,32)
block:clear(green)

  现在我们需要创建一个数列来存放我们的玩家信息。本演示中就是简单的保存玩家的 x 和 y 坐标位置。

代码:
Player = { x = 30, y = 100 }

  接下来我们要定义两个变量,存储我们玩家图像的高和宽。这些值将用于我们的碰撞函数中。记住我们刚才是以 32x32 创建图像的,所以那就是我们要用的值。

代码:
playerHeight = 32
playerWidth = 32

  然后,为我们的三个方块也创建一个数列来存储信息。自然,我们要存储每个方块在屏幕上所处的 x,y 值,还有每个方块的宽度与高度。我们使用 width() 和 height() 命令来自动从我们刚才创建的方块图像中获取这些值。代码如下:

代码:
Block = {}
Block[1] = { x = 100, y = 80, height = block:height(), width = block:width() }
Block[2] = { x = 300, y = 30, height = block:height(), width = block:width() }
Block[3] = { x = 200, y = 58, height = block:height(), width = block:width() }

  现在让我们写一个函数让玩家动起来。每次循环都会调用这个函数以检查移动。如果你认真学了前面的课程,应该不难理解下面的代码:

代码:
function movePlayer()
pad = Controls.read()
if pad:left() then
Player.x = Player.x - 1
end
if pad:right() then
Player.x = Player.x + 1
end
if pad:up() then
Player.y = Player.y - 1
end
if pad:down() then
Player.y = Player.y + 1
end
end

  ...终于到好玩的地方了。该是时候创建一个函数来检查碰撞是否产生了。这个函数将使得我们能对游戏中任何一个对象检测碰撞,只要在循环中为该对象调用该函数。先看一下整个函数,然后我们来分析一下。

代码:
function collisionCheck(object)
if (Player.x + playerWidth > object.x) and (Player.x < object.x + object.width) and (Player.y + playerHeight > object.y) and (Player.y < object.y + object.height) then
Player.x = oldx
Player.y = oldy
end
end

  看一下第一行:function collisionCheck(object)

  这是创建一个名为 collisionCheck 的函数。注意括号里的词 object,简单的说这就是一个“替代”变量。当我们稍后实际使用这个函数时需要用我们想要测试碰撞的对象名替换这个词。再往下看你会发现 object 这个词出现了很多次。一旦该函数执行,所有的 object 都会被替换为我们测试的实际对象名。

  有没有觉得第二行很华丽?!

  这一行的 if 语句检测了好几个条件。请注意我把每一块条件都放进了括号里,这样如此长的代码会容易阅读一些。

  首先是检测是否 (Player.x + playerWidth > object.x)。一个图像的 x 值就是指该图像的左上角顶端的横坐标。碰撞的关键就是检测两个图像是否彼此有重叠,如果是则让他们停止重叠。那么为什么要在玩家的 x 值上再加上玩家的宽度呢?很简单,因为如果我们只检测玩家的 x 值,那么只有玩家图像的左上角顶端重叠了才会检测到碰撞。如果我们向右移动,而有一个方块挡在路上,我们需要碰撞在玩家图像的右侧边缘发生。玩家的 x 值加上其宽度就是该图像的右上角顶端的值。

  简单来说,我们要做的就是在图像周围创建一个要求碰撞的盒型区域。一旦移动该盒型区域即为发生碰撞。

  我画了几张图让你对这行代码有形象化的理解。蓝色方盒是个静止的方块,而那个绿色方盒就是玩家。黑线表明碰撞发生的地方。

  到目前为止我们只是检测了玩家的右侧是否越过了其他物体的 x 坐标。

  正如你看到的,现在我们的玩家处于碰撞区域中……但是我们还有更多的检测要做。下一个条件是 (Player.x < object.x + object.width)。
这个代码用于向左移动碰上物体图像右侧的时候。就是前一条件的反例而已。物体对象的 x 值加上它的宽度就是该物体的右侧。现在让我们看看前两个检查条件下的碰撞区域。

  目前为止我们仍然处于碰撞区域。在设定了前两个条件之后,必须在这两条黑线之间碰撞才会发生。

  下一个条件是 (Player.y + playerHeight > object.y)。

  这是用于向下移动的时候。玩家的 y 坐标加上玩家的高度就是玩家图像的底端。如果该值大于其他物体的 y 坐标,则碰撞发生。让我们看看三个条件下的碰撞区域。

  现在你会发现物体方块的上方已不再是碰撞区域,而玩家图像的底端必须大于物体方块图像的顶端才能使碰撞发生。

  最后一个检查条件是 (Player.y < object.y + object.height)。

  这部分代码用于检查向上移动。如果玩家的 y 坐标小于物体的底端(y 坐标加高度)则碰撞发生。这次我们使用“小于”是因为在屏幕上向上移动时 y 值是递减的。现在看看碰撞情况。

  现在物体方块的底部以下也不是碰撞区域了。唯一会发生碰撞的地方就是方块所占据的区域。通过 if 语句的四个条件我们将方块所占区域包围了起来。

  接着我们必须设定一旦进入该区域会发生什么,所以我们将玩家的 x 和 y 值设为 oldx 和 oldy。

  此时我们尚未定义 oldx 和 oldy 的值。这是我们在主循环中要做的第一件事。这些值在每次循环开始时会被玩家当前的 x 和 y 值设定。如果我们的函数判断玩家处于碰撞区域则将玩家的位置设回上一次的位置,也就是 oldx 和 oldy 存储的值。这样我们就永远无法走进物体方块中了。函数在此结束。希望我上述的详细解析能帮到你理解这个函数。

  最后就是我们的主循环。学到这你应该对下面的代码不难理解了。

代码:
while true do

-- store player's position at beginning of each loop
oldx = Player.x
oldy = Player.y
screen:clear()

movePlayer()

--check collision for each block
collisionCheck(Block[1])
collisionCheck(Block[2])
collisionCheck(Block[3])

--paste player to screen
screen:blit(Player.x,Player.y,player)

--paste all 3 blocks to screen
for a = 1,3 do
screen:blit(Block[a].x,Block[a].y,block)
end

screen.waitVblankStart()
screen.flip()
end


love_xiaolu
2009-09-26 11:19:05

贼帅!!!收下了


oblivioned
2009-11-06 22:28:44

很怀念啊··我也是看这个教程起步的说··真心感谢他的作者了·!!!


Lanf
2009-12-05 17:45:17

呵呵,又见这个教程了,最早就是看这个的。但是可悲的是我现在的水平也没好到哪里去…………


梦影℃无幽
2010-02-02 16:56:39

牛啊。。。顶起来看看~~


replaysss
2010-02-22 18:22:09

看到好东西了,支持啊


angelnet01
2010-03-13 19:17:37

这么少啊- - 应该还有很多的说


gyyfifafans
2010-03-16 21:00:14

那就麻烦楼上将它补全了……!!!!


GnNu
2010-04-03 10:36:41

- -研究ruby的某人路过


1239985932
2010-04-19 21:54:42

我说我什么都不会的,看完可以会用吧?


GnNu
2010-06-19 19:00:49

什么时候出现的东西- -。。。