|
|
/前言1.这个大概可以关于Lua的协程与闭包的用途的最简单介绍。专门的文章比较难写,于是一边实践,一边记下来一点东西。 2.这里面用到了math.random,于是在stage开始处调用math.randomseed(0)将种子强制归0,以保证录像播放。另外比较好的方法是在replay中保存seed,只是我懒得研究这个问题了 3.里面创建enemy的方法是coroutine中将闭包返给主进程执行是由于我有些问题搞不定,反正直接调用New就会悲剧= = 4.虽然这是娱乐向的超级短,也欢迎大家打录像玩~ /开始Resty想要自己创建一个关卡,于是在mod下创建了一个叫做dream的文件夹,用作这次的设计目录,然后再在里面创建一个script目录,用来放置脚本,最后创建root.lua,开始编写我们的内容。 首先,我想要有一个开始的菜单界面,于是先把debug下的root.lua中的statge_init的全部相关内容copy过来,这样,菜单就有了= = 然后学着stage_demo的方法创建stage_dream: - stage_dream = stage.New(false)
- function stage_dream:init()
- InitVar()
- New(_G[lstg.var.player_name])
- New(bamboo_background)
-
- self.process = coroutine.create(stage_of_dream)
- end
- function stage_dream:frame()
- flag, func = coroutine.resume(self.process)
- if not flag then
- stage.Set('none','none','stage_init')
- elseif func then
- func()
- end
- end
render部分直接抄过来没有修改。 解释一下,我希望通过coroutine来管理我的stage进程,于是在init的时候创建了self.process, 然后在每一帧的时候remsume一下~。 我希望把stage的内容写到单独的文件中,于是创建了一个文件stage.lua 在root.lua开头加上Include’stage.lua’,另外还有一些函数会经常使用,于是再创建一个文件util.lua,在root.lua开头加上Include’util.lua’ 然后在stage.lua中编写关卡 - function stage_of_dream()
- wait(300)
- end
现在没什么事好做,于是先等待300帧,然后结束。 wait函数在util.lua中编写: - function wait(count)
- if count > 0 then
- coroutine.yield()
- wait(count - 1)
- end
- end
传入要等待的帧数,就会等待这么多帧恩~ 接着我们要考虑加入enemy 首先加入两个概念,mover和firer。mover是一个函数,其作用是控制一个对象的运动轨迹,firer是另一个函数,其作用是控制enemy的开火情况,于是我们创建一个使用mover和firer的enemy对象: - denemy=Class(girl)
- function denemy:init(id, hp, mover, firer, protect_time, drop)
- girl.init(self, id, hp)
- self.mover = mover
- self.firer = firer
- self.ptime = protect_time
- self.protect = true
- if drop then
- self.drop = drop
- end
- end
- function denemy:frame()
- girl.frame(self)
- self.x, self.y = self.mover()
- self.firer(self.x, self.y)
- if self.ptime > 0 then
- self.ptime = self.ptime - 1
- if self.ptime <= 0 then
- self.protect = false
- end
- else
- if self.x < -300 or self.x > 300 or self.y < -250 or self.y > 250 then
- Del(self)
- end
- end
- end
再解释一下子,mover是一个返回三个值(x, y, ang)的函数,firer是一个接受(x, y)作为参数的函数,作为子弹的发射点。 frame函数中, 其调用mover来决定自己的坐标,调用firer来控制是否开火。mover中的ang是用来控制子弹的朝向的,在enemy处理的时候不需要考虑。 接下来,我们先实现一个简单的mover,直线移动: - function mvdirect(x, y, vx, vy)
- local ang = math.atan2(vy, vx)
- return function()
- x = x + vx
- y = y + vy
- return x, y, ang
- end
- end
- function mvdirecta(x, y, v, ang)
- local vx = math.cos(ang) * v
- local vy = math.sin(ang) * v
- return function()
- x = x + vx
- y = y + vy
- return x, y, ang
- end
- end
这两个函数创建的是同样的直线移动的mover,只是传入的参数有差异。 再创建一个不开火的firer - function fire_null()
- return function(x, y) end
- end
注意这里的函数都是mover和firer的构造器,它们的返回值是一个函数,就是对应的mover / firer 于是我们可以在我们的stage中加入第一批敌人: - for i = 1, 6 do
- coroutine.yield(function () New(denemy, 1, 1, mvdirect(-100, 240, 0, -2), fire_null(), 45, {1, 0, 0}) end)
- wait(20)
- end
- wait(120)
- for i = 1, 6 do
- coroutine.yield(function () New(denemy, 1, 1, mvdirect(100, 240, 0, -2), fire_null(), 45, {1, 0, 0}) end)
- wait(20)
- end
这波敌人的mover是从屏幕上方以速度vy=-2向下运动~ 可以先进游戏试试,可以打掉这两波敌人了,它们都会掉落1个红点。 注意到我们这两波敌人,除了mover的x值不同外,其它的都是一样的,我们可以为其这一公共的特性建立抽象,创建函数 - local function egroup1(x)
- for i = 1, 6 do
- coroutine.yield(function () New(denemy, 1, 1, mvdirect(x, 240, 0, -2), fire_null(), 45, {1, 0, 0}) end)
- wait(20)
- end
- end
那么我们的stage可以简单的写成: - function stage_of_dream()
- wait(300)
- egroup1(-100)
- wait(120)
- egroup1(100)
- wait(600)
- end
然后我们再给这些敌人加上一些子弹: 创建firer: - function firer_small()
- local cnt = 40
- return function (x, y)
- cnt = cnt - 1
- if cnt <= 0 then
- ball_to_player(x, y, 4)
- cnt = math.random(30) + 40
- end
- end
- end
cnt是控制两发子弹之间的间隔,当cnt = 0时发射子弹,然后再随机生成下个子弹发射的间隔。 其中ball_to_player是朝player发射一个ball子弹: - function ball_to_player(x, y, speed)
- New(rbullet, ball_mid, 2, true, mvdirecta(x, y, speed, math.atan2(lstg.player.y-y, lstg.player.x-x)))
- End
这样我们修改egroup1中的创建函数,更换相应的firer,即可让这些敌人零散的发射一点子弹。 接下来我们再来创造下一波敌人,这波敌人我希望他们横着运动,这点我们的mvdirect本身就能实现,然后发射更加密集和快速的自机狙: - local function egroup2 ()
- for i = 1, 12 do
- coroutine.yield(function () New(denemy, 2, 2, mvdirect(-200,165,2,0), fire_more(), 30, {0, 1, 0}) end)
- wait(15)
- coroutine.yield(function () New(denemy, 3, 2, mvdirect(200,145,-2,0), fire_more(), 30, {0, 0, 1}) end)
- wait(20)
- end
- end
这里循环中创建了两个敌人,分别从屏幕左侧和右侧进入屏幕中。 然后我们需要实现fire_more() 如下: - function fire_more()
- local cnt = 10
- return function (x, y)
- cnt = cnt - 1
- if cnt <= 0 then
- ball_to_player(x, y, 5)
- cnt = math.random(10) + 10
- end
- end
- end
很容易发现,fire_little和fire_more的形式非常接近,不同的地方只有子弹速度,和时间间隔,于是我们可以将其抽象为fire_some: - function fire_some(init, det, v)
- local cnt = init
- return function (x, y)
- cnt = cnt - 1
- if cnt <= 0 then
- ball_to_player(x, y, v)
- cnt = math.random(det) + init
- end
- end
- end
- function fire_little()
- return fire_some(40, 30, 4)
- end
- function fire_more()
- return fire_some(10, 10, 5)
- end
最后修改我们的stage,加入egroup2就可以观察效果了~ 接下来我们创建一个复杂一点的mover: - function mvcons(t, car, cdr)
- return function()
- local x, y, ang = car()
- if t >= 0 then
- t = t - 1
- if t < 0 then
- car = cdr(x, y, ang)
- end
- end
- return x, y, ang
- end
- end
mvcons的作用是一个连接符,它表示:先使用car这个mover运作t帧,之后用cdr来构造另一个mover,使之控制接下来的进程。 Cdr是一个mover的构造器,其应当接受3个参数:x, y, ang为上一个mover最后一次返回的3个参数,如果你需要考虑到运动的连贯性,则需要这些参数来构造下一个mover(虽然我都没用过ang参数,但当你制作一个之前沿曲线运动,最后朝切线方向飞出的mover时很可能会用的哦)。 我们利用mvcons可以构造一点复杂的效果: 于是我接下来创建一个大型一点的敌人: - local function ebig1()
- coroutine.yield(
- function()
- New( denemy,
- 9,
- 120,
- mvcons(40,
- mvdirect(0, 250, 0, -3),
- function (x, y)
- return mvcons(600,
- function() return x, y end,
- function(x, y) return mvdirect(x, y, 0, 4) end)
- end
- ),
- fire_delay(40, fire_wait(10, fire_around(12, 3.5))),
- 90,
- {24, 6, 6})
- end
- )
- end
注意到这回的敌人的mover是由我们定义的多个基本的mover拼合而成,其粘合剂就是mvcons,另外这个表达式中声明了一个临时的mover即function() return x, y end,其表示呆在原地不动~ 那么这里的mover就表示先从(0, 250)向下移动40帧,然后原地停止600帧,然后再向上运动离开屏幕~ 我在stage中再加入这一段: - ebig1()
- egroup2()
- wait(650)
将这个大型敌人和group2混在一起,作为这个关卡的最后一段。 然后我们来解释ebig1构造中的firer。 我们先在firer中加入下面一些内容: 1. fire_delay : 它接受另一个firer, 并使得该firer延迟cnt帧后才发生作用。 2. fire_wait : 它接受另一个firer, 并使得该firer在每cnt帧被调用一次。 其实现如下: - function fire_delay(cnt, firer)
- return function(x, y)
- if cnt > 0 then
- cnt = cnt - 1
- else
- firer(x, y)
- end
- end
- end
- function fire_wait(cnt, firer)
- local delay = cnt
- return function (x, y)
- delay = delay - 1
- if delay <= 0 then
- delay = cnt
- firer(x, y)
- end
- end
- end
而fire_around只是一个普通的朝着全方位发射固定弹的发射器 - function fire_around(n, v)
- local base = 0
- local dt = 2 * math.pi / n
- return function (x, y)
- base = base + 0.1
- for i = 1, n do
- ball_to_angle(x, y, v, base + i * dt)
- end
- end
- end
其中ball_to_angle的实现是: - function ball_to_angle(x, y, speed, angle)
- New(rbullet, big_arrow, 3, true, mvdirecta(x, y, speed, angle))
- end
而ebig1的firer即fire_delay(40, fire_wait(10, fire_around(12, 3.5))) 可以解释为 40帧后开始发射,每隔10帧发射一次,每次朝着周围发射12发子弹,弹速为3.5 [ 此帖被resty在2011-03-20 10:07重新编辑 ]
|
|