4.3. 编程实现
(续上)
1. 实现鱼群的生成和鱼的游动
在第2个阶段,将按照“编程思路”中介绍的鱼群生成策略和鱼的游动策略进行编程,在屏幕上创造出一群栩栩如生的海洋鱼类,并让它们以随机路线游动。在“”项目bydr 录中,把 version1.jl复制一份并命名为version2.jl,然后在第 1 个版本的基础上编写第 2 个版本的代码。
1) 生成鱼类角色
新建一个空白源文件,并以game_sprites.jl作为文件名保存到bydr目录中。在这个文件中编写代码,根据原书表 31-2 中各种鱼的参数生成各种栩栩如生的鱼,根据鱼的游动策略生成随机的游动路线等。
使用函数FishSprite()创建鱼类角色。
1 function FishSprite(name = "", item = dict{}) 2 #println(item) 3 #'''鱼精灵''' 4 actor=Actor(item["file"][1]) 5 actor.name = name #鱼的名字 6 actor.life = item["life"] #生命值 7 actor.score = item["score"] #得分 8 actor.is_turn = item["turn"] #是否允许游动时转向 9 actor.is_capture = false #是否被捕获 10 actor.alpha = 255#false #是否可见(alpha值0-255) 11 actor.death_time = 0 #鱼挣扎死亡的时间 12 actor.change_time = time() #鱼上次改变路线的时间 13 actor.rotation = 0 #旋转角度 14 actor.speed = item["speed"] #游动速度 15 actor.acc = 0 #加速度 16 actor.images=item["file"] #用于生成鱼游动的动画的图片文件集 17 actor.frame=1 #记录鱼动画当前显示的图片在集合中的索引 18 19 #'''鱼的起点''' 20 if rand(1:10) > 5 21 actor.x = 0 - rand(256:512) 22 else 23 actor.x = rand(1024:1536) 24 end 25 actor.y = rand(0:668) 26 point(actor,512, 284) 27 return actor 28 end
注意上面代码中,“鱼的起点”实现鱼游动的第1个策略,其中rand函数中参数已做了调整,原书的参数比较大,生成的鱼的初始位置距离窗口中心太远,鱼运动到画面中的时间过长,导致游戏开始时,等待时间较长,所以这里调小了参数。
创建swim()方法,用于实现鱼的自由游动,能够随机改变鱼的游动方向和速度,以及让鱼游出给定范围时消失,即实现鱼游动的第2个和第3个策略.
1 function swim(fish, dt) 2 #'''鱼儿游动''' 3 if time() - fish.change_time > 3 4 fish.change_time = time() 5 if -512 < fish.x < 1536 && -484 < fish.y < 1052 6 if fish.is_turn==1 7 fish.rotation = rand(-10:10) 8 fish.acc = rand(-10:fish.speed // 2) 9 end 10 end 11 end 12 #随机转向、加减速移动 13 left(fish,fish.rotation * dt) 14 move(fish,(fish.speed + fish.acc) * dt) 15 #超出范围时让鱼消失 16 if !(-1024 < fish.x < 2048 && -768 < fish.y < 1536) 17 #fish.visible = False 18 fish.alpha=0 19 end 20 end
创建 check_dead()方法,用于实现鱼披捕获时能够扭动 ls 后再消失。
1 function check_dead(fish::Actor, dt) 2 #'''鱼儿是否死亡''' 3 if fish.alpha==255 4 fish.death_time += 1 * dt 5 if fish.death_time > 1 6 fish.alpha=0 7 #self.visible = False 8 end 9 end 10 end
至此,game_sprites.jl中代码写完,可在version2.jl中引入。
2) 控制鱼群的生成和鱼的游动
切换到“version2.jl”源文件,导入game_sprites 模块。
game_include("game_sprites.jl")
创建实现鱼群生成策略的计划任务,用来创造一群栩栩如生的鱼在屏幕上游动,并控制鱼的游动和处理被捕获等。
原书将生成鱼群和控制鱼运动这两个操作代码写在同一个函数里了,本文为了使代码结构更优,分别用两个函数实现。首先创建make_fishes(dt)函数:
function make_fishes(dt)…end
然后从字典变量dictFishes中读取各种鱼的参数,按照鱼群的生成策略,使用FishSprite()函数创建出鱼角色的实例 ,并将其放人fishes列表中。其中字典变量dictFishes需要显式地从json字符串中转换而来,因此需要在代码开头,引入Julia的json库,并转换赋值。
1 import JSON 2 …… 3 dictFishes=JSON.parse(fishes_config) 4 …… 5 function make_fishes(dt) 6 global game_time 7 game_time += 1 * dt 8 9 for (fish_name, item) in dictFishes 10 if item["alive"] == 0 11 num = item["max"] // 2 12 elseif item["alive"] < item["max"] // 3 13 num = item["max"] - item["alive"] 14 else 15 num = 0 16 end 17 if fish_name == "蓝鲨" && floor(Int,game_time + 1) % 300 == 0 18 num = item["max"] - item["alive"] 19 end 20 if fish_name == "金鲨" && floor(Int,game_time + 1) % 480 == 0 21 num = item["max"] - item["alive"] 22 end 23 for i in 1 : num #range(num) 24 fish = FishSprite(fish_name,item) 25 item["alive"] += 1 26 push!(fishes,fish) 27 end 28 end 29 end
再创建fishes_control()函数作为鱼运动的控制函数。
function fishes_control(dt)…end
在这个函数中,读取 fishes 列表中的各个鱼精灵实例,让生命值大于 0 的鱼角色游动;让披捕获的鱼角色扭动身体1s,并释放出一枚硬币;将不可见状态的鱼角色从fishes列表中移除,再将鱼角色销毁。由于julia具有自动垃圾回收机制,因此鱼Actor可以不必主动销毁。
1 function fishes_control(dt) 2 #鱼群 3 for fish in fishes 4 if fish.life > 0 5 swim(fish,dt) 6 else 7 if not fish.is_capture 8 fish.is_capture = true 9 #将索指定为在鱼披捕获时显示扭动的动画图像的第一张 10 fish.frame=length(fish.images)/2+1 11 #释放金币 12 release_coin(fish.position, fish.score) 13 else 14 #挣扎1秒后消失 15 check_dead(fish,dt) 16 end 17 end 18 if fish.alpha==0 19 #减少鱼的存活数 20 dictFishes[fish.name]["alive"] -= 1 21 #fishes.remove(fish) 22 #delete!(fishes,fish) 23 filter!(e->e≠fish,fishes) 24 #fish.delete() 25 end 26 end 27 end
至此,函数fishes_control()编写完毕,原书使用用这个回调函数在程序人口中创建一个计划任务。不过GameZero有个天然的计划任务,那就是update()事件函数,因此我们将回调写在这个函数内部:
function update(g::Game,dt)
make_fishes(dt)
fishes_control(dt)
end
在这个游戏程序中,使用几个列表变量fishes, bullets, nets,和 coins 分别存放鱼角色、炮弹角色、渔网角色和硬币角色。使用draw_actor(actors)函数能够将这些列表变量中存放的处于可见状态的各个角色绘制到游戏窗口中,该函数的代码如下:
function draw_actor(actors) #绘制角色 for actor in actors #println(actor.alpha) if actor.alpha==255 draw(actor) end end end
3) 生成鱼游动的动画
原书通过pyglet库,可以很方便地设置鱼游动的动画。其原理是读取序列帧大图中的图像序列,利用pyglet库的Animation模块生成动画。但是GameZero只能通过轮动更换Actor的图像来模拟动画,因此笔者专门定义了一个函数来实现对所有鱼类逐一轮换图片,以实现动画的生成(当然首先是将原书的序列帧大图通过工具分解成单个图片)。
1 #鱼动画 2 function fishes_animation() 3 for fish in fishes 4 _frame = fish.frame 5 #鱼是否被捕获 6 if fish.is_capture==false 7 if _frame < (length(fish.images)/2+1) 8 fish.image = fish.images[_frame] 9 _frame += 1 10 11 else 12 _frame = 1 13 end 14 fish.frame=_frame 15 else 16 if _frame < length(fish.images)+1 17 fish.image = fish.images[_frame] 18 _frame += 1 19 20 else 21 _frame = length(fish.images)/2+1 22 end 23 fish.frame=_frame 24 end 25 26 end 27 schedule_once(fishes_animation, 1/16) 28 29 end
为什么要用一个函数来实现所有的鱼类游动的动画呢?在以上函数中我们可以看出,利用了计划任务schedule_once来实现类似递归的操作,而计划任务底层是定义了一个定时器,一个定时器相当于开了一个线程。如果为每个鱼单独处理动画,就需要为每个鱼设置一个定时器,由于鱼类在不断地生成,定时器开的越来越多,也就意味着程序中有越来越多的线程,这样对资源的消耗比较大,所以我们才用一个函数(一个定时器)来处理。当然一个函数的缺点是要在fishes集合中轮询所有的鱼类,并逐一处理,时间上会有损耗,如果fishes集合元素过多,可能会出现动画不连贯,当然由于程序处理的速度足够快,这点损耗可以忽略不记。
在程序开始时直接调用这个函数就可以。
至此,这个游戏程序的第2个版本编写完成。运行程序,稍等片刻,就能看到从游戏窗口的两侧不断地游出栩栩如生的各种鱼。