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個版本編寫完成。運行程序,稍等片刻,就能看到從遊戲窗口的兩側不斷地游出栩栩如生的各種魚。