2-幀動畫-從零開始寫一個武俠冒險遊戲

自由布魯斯發表於2016-06-10

從零開始寫一個武俠冒險遊戲-2-幀動畫

---- 用基本繪圖函式實現幀動畫

本章寫作思路:

說明幀動畫的原理->取得素材->把素材整個顯示->擷取子畫面->顯示子畫面->討論在迴圈顯示函式 draw() 中讓子畫面逐幀顯示->挖空素材背景(背景透明化)->讓角色不再原地踏步(橫向或縱向跑起來)->增加背景圖->放大或者縮小角色(跑著跑著變瘦了-瘦身減肥效果)->背景圖動起來->改變角色角度

概述

本文特點

幀動畫是一種應用非常廣泛的動畫技術,現在我打算藉助 Codea 非常友好的程式設計介面用最簡單的例子一步步教你學會幀動畫。

看過網路上不少關於幀動畫的教程, 大多是通過引用這個類那個庫來實現的, 看起來很複雜的樣子, 很少有用基本繪圖語句直接寫的, 對於初學者來說, 理解幀動畫還得先去理解那些類庫, 無形中增加了學習難度, 本文嘗試一種新的講解方式, 全部採用 Codea 基本繪圖語句來演示幀動畫原理.

幀動畫原理

首先介紹一下幀動畫的原理: 簡單說就是把所有的動畫幀都集中放在一個圖片上(好處是可以一次性載入到記憶體裡), 然後依次顯示每一幀, 這樣連續顯示起來的動畫幀利用視覺暫留效應就成功地實現了幀動畫.

具體來說需要這樣一個圖:

貓圖

另外需要的就是每一個子幀在整副圖片中的位置座標和長寬, 一般會用左下角座標和長度,寬度來確定一個子幀, 我們會用這樣一個表來儲存:

positon = {{x1,y,width,height}, {x2,y,width,height}, ... }

在這個資料結構中, 每副子幀的縱座標 y, 寬度 width, 高度 height 都可以保持一樣的值, 這樣處理起來最省事.

這個素材圖是每 3 副子幀完成一個動畫, 它的座標資料如下:

local x,y = 0,43
pos1 = {{0+x,0+y,32,43},{64+x,0+y,32,43},{96+x,0+y,32,43}}

不過有些幀動畫素材, 為了節省那麼一丁點空間, 會根據子幀中角色的實際寬度來放置, 這樣就使得每副子幀的 寬度 width 不一定相同, 比如下面這個素材:

跑步圖

它的座標資料就是這樣的:

pos = {{0,0,110,120},{110,0,70,120},{180,0,70,120},{250,0,70,120},
       {320,0,105,120},{423,0,80,120},{500,0,70,120},{570,0,70,120}}

再看一張大貓圖:

大貓圖

它的座標資料就是這樣的了:

local w,h = 1024,1024
pos2 = {{0,h*3/4,w/2,h/4},{w/2,h*3/4,w/2,h/4},{0,h*2/4,w/2,h/4},{0,h*2/4,w/2,h/4},
        {0,h*1/4,w/2,h/4},{w/2,h*1/4,w/2,h/4},{0,h*0/4,w/2,h/4},{0,h*0/4,w/2,h/4}}

具體實現

直接繪製整副圖

明白了上述原理就好辦了.

首先,我們準備好幀動畫素材,接著把素材讀入記憶體,然後試著把它直接顯示到螢幕上--記住,sprite 命令是後續幀動畫的基礎,程式碼如下

function setup()
    displayMode(FULLSCREEN)

    -- 繪圖模式選 CENTER 可以保證畫面中的動畫角色不會左右漂移
    rectMode(CENTER)
    spriteMode(CENTER)

    -- 載入整副素材圖
    img = readImage("Documents:runner")

end

function draw()
    sprite(img, x, y)
end

本文涉及的 Codea 基本繪圖函式

函式 sprite(圖片, x, y, width, height)

函式說明:

引數說明:

  • 圖片: 圖片物件
  • x: 左下角(或中心點)橫座標
  • y: 左下角(或中心點)縱座標
  • width: 顯示出來的影像的寬度
  • height: 顯示出來的影像的高度

函式 image:copy(x,y,width,height)

函式說明:

引數說明:

  • x: 左下角橫座標
  • y: 左下角縱座標
  • width: 要拷貝影像區域的寬度
  • height: 要拷貝影像區域的高度

更多的函式說明文件請參考:

Codea官方標準函式參考
Codea官方Wiki函式參考

繪製一個子幀

很好, 現在我們從素材中取出第一個子畫面, 也就是左下角座標為(0,0), 寬為 110, 高為 120 的區域:

img1 = img:copy(0,0,110,120)

現在在 draw() 函式裡增加如下語句把它顯示到螢幕上

sprite(img1, x, y)

繪製多幅子幀

非常好,接下來我們按照從左到右的順序依次取出各個子畫面,它們的座標可以根據素材影像的大小進行估算,比如第二個子畫面它的左下角座標的 x 值要在第一個子畫面左下角座標 x 值的基礎上加上第一個子畫面的寬度 110, 高度不變, 它自己的寬度我們可以大致估算為70(根據實際情況調整), 那麼如下

function setup()
    ...
    img2 = omg:copy(110, 0, 70, 120)
    ...
end

function draw()
    ...
    sprite(img2, x, y)
    ...
end

有了這兩個基礎, 我們就知道怎麼處理剩下的子畫面了, 為了後續的操作方便,我們把所有這些子畫面按順序放到一個表中

—- 新建一個空表
images = {}
-- 分離各個子畫面,按順序存入表 imgs 中備用
imgs[1]=img:copy(0,0,110,120)
imgs[2]=img:copy(110,0,70,120)
imgs[3]=img:copy(180,0,70,120)
imgs[4]=img:copy(250,0,70,120)
imgs[5]=img:copy(320,0,105,120)
imgs[6]=img:copy(423,0,80,120)
imgs[7]=img:copy(500,0,70,120)
imgs[8]=img:copy(570,0,70,120)

然後試著在螢幕上單獨顯示一個子畫面,檢驗一下我們是否成功地從素材中取得了所有的子畫面, 程式碼如下:

function draw()
    ...
    -- 分別顯示所有子畫面
    sprite(imgs[1],120,200)
    sprite(imgs[2],130,400)
    sprite(imgs[3],120,600)
    sprite(imgs[4],300,600)
    sprite(imgs[5],320,400)
    sprite(imgs[6],330,200)
    sprite(imgs[7],620,200)
    sprite(imgs[8],630,400)
end

接下來就是關鍵 ,把所有這些子畫面連續顯示到螢幕上, Codeadraw() 函式每秒鐘會自動執行60次, 也就是說它每 1/60 秒(0.01666...秒)會在螢幕上繪製一次.

那麼我們只要在同一個位置每隔一點時間按照子幀的順序顯示一個新的子幀, 就可以形成動畫效果.

這裡我們使用一個全域性變數 q 來索引 imgs 中的子畫面, 首先在 setup() 函式中為 q 賦初值 0

q =0

接著在 draw() 函式中設定為遞增, 每執行一次 draw(), q 就會加 1

q= q+1

然後在 draw() 中依次顯示 imgs[q],

sprite(imgs[q],120,200)

迴圈播放的技巧

這時點選執行就會出現一個錯誤, 提示陣列越界, 怎麼回事呢?

因為我們的 imgs 只有 8 個元素, 一旦 q 的值超過 8 就索引不到資料了, 也就是說我們需要讓 imgs 的索引值保持在 1-8 的區間內.

解決辦法就是用取模函式來限定陣列索引的範圍, 如下:

math.fmod(q, 8)

隨著 q 的不斷遞增, 它返回的值依次為

0,1,2,3,4,5,6,7,0,1,2,3,4,5,6,7,0,1,2,3,4,5,6,7 ….., 我們只要給它加 1 就能保證正好索引到1到8之間, 試著執行一下:

sprite(imgs[math.fmod(q, 8)+1],120,200)

這次沒錯了, 不過新的問題出現了, 好像跑得有些快, 如何解決呢?

控制播放速度

這是因為我們的 draw() 預設每秒執行60次, 那麼我們現在的子幀有 8 副圖, 按照我們上面的程式碼, 沒執行一次 draw() 就會繪製一幅子幀, 也就是說我們的所有子幀會在 8/60 秒內就播放完.

現在我們希望能調整一下幀速率, 需要用到 Codea 提供的一個全域性變數(只讀) ElapsedTime, 這個全域性變數會實時返回程式執行時間, 我們用這樣一個判斷來實現:

用一個 prevTime 記錄上一次執行時間, 在 setup() 中初始化(這時記錄的是第一次執行的時間)

prevTime =0

假設我們希望子畫面每隔 0.1 秒更新一次, 也就是每隔 0.1秒, imgs的索引值增加1, 在 draw() 裡增加這段程式碼:

if ElapsedTime > prevTime + 0.1 then
        prevTime = prevTime + 0.1
        k=math.fmod(i,8)
        i=i+1
end

sprite(imgs[k+1],120,200)

看看現在的效果, 非常好, 現在的動畫速度差不多是原來的 1/6, 而且這個值可以根據需要進行調整.

程式碼優化

使用迴圈

首先是這段程式碼:

-- 新建一個空表
images = {}
-- 分離各個子畫面,按順序存入表 imgs 中備用
imgs[1]=img:copy(0,0,110,120)
imgs[2]=img:copy(110,0,70,120)
imgs[3]=img:copy(180,0,70,120)
imgs[4]=img:copy(250,0,70,120)
imgs[5]=img:copy(320,0,105,120)
imgs[6]=img:copy(423,0,80,120)
imgs[7]=img:copy(500,0,70,120)
imgs[8]=img:copy(570,0,70,120)

我們可以把位置座標提取出來集中放置到一個表中, 然後用迴圈來表示, 如下:

pos = {{0,0,110,120},{110,0,70,120},{180,0,70,120},{250,0,70,120},
       {320,0,105,120},{423,0,80,120},{500,0,70,120},{570,0,70,120}}

for i = 1, 8 do
    imgs[i]=img:copy(pos[i][1], pos[i][2], pos[i][3], pos[i][4])
end

繼續用一個 table.unpack 語句來替換 pos[i][1], pos[i][2], pos[i][3], pos[i][4] 語句, 如下:

pos = {{0,0,110,120},{110,0,70,120},{180,0,70,120},{250,0,70,120},
       {320,0,105,120},{423,0,80,120},{500,0,70,120},{570,0,70,120}}

for i = 1, 8 do
    imgs[i] = img:copy(table.unpack(pos[i]))
end

不過不同的動畫素材使用的子幀數目也不一定都是 8 個, 所以這裡這個子幀數目可以通過 #pos(求 pos 的長度) 來靈活設定, 所以程式碼如下:

pos = {{0,0,110,120},{110,0,70,120},{180,0,70,120},{250,0,70,120},
       {320,0,105,120},{423,0,80,120},{500,0,70,120},{570,0,70,120}}

for i = 1, #pos do
    imgs[i] = img:copy(table.unpack(pos[i]))
end

改寫為類

為了方便使用, 另一方面也讓程式主框架看起來清爽一些, 我們可以把上述實現幀動畫的程式碼封裝成一個類 Sprites, 具體來說就是把初始化的程式碼放在 Sprites:init() 函式中, 把實際繪製的程式碼放在 Sprites:draw() 函式中, 程式碼如下:

Sprites = class() 

function Sprites:init(x,y,img,pos)
    self.x = x
    self.y = y
    self.index = 1
    self.img = img
    self.imgs = {}
    self.pos = pos
    self.i=0
    self.k=1
    self.q=0
    self.prevTime =0

    -- 使用迴圈,把各個子幀存入表中

    for i=1,#self.imgs do
        -- imgs[i] = img:copy(startPos[i][1],startPos[i][2],startPos[i][3],startPos[i][4])
        self.imgs[i] = self.img:copy(table.unpack(self.pos[i]))
    end
        print(#self.imgs)
end

function Sprites:draw()
    -- 確定每幀子畫面在螢幕上停留的時間
    if ElapsedTime > self.prevTime + 0.1 then
        self.prevTime = self.prevTime + 0.1    
        self.k = math.fmod(self.i,#self.imgs) 
        self.i = self.i + 1      
    end
    self.q=self.q+1
    -- rect(800,500,120,120) 
    pushMatrix()
    rotate(30)
    -- sprite(self.imgs[self.k+1],self.i*10%WIDTH+100,HEIGHT/6,HEIGHT/8,HEIGHT/8) 
    --sprite(imgs[math.fmod(q,8)+1],i*10%WIDTH+100,HEIGHT/6,HEIGHT/8,HEIGHT/8) 
    sprite(self.imgs[self.k+1], self.x, self.y)
    popMatrix()
    -- sprite(imgs[self.index], self.x, self.y)
end

使用方法也很簡單, 先在 setup() 中呼叫初始化函式, 然後在 draw() 中呼叫繪製函式:

function setup()
    displayMode(FULLSCREEN)

    -- 繪圖模式選 CENTER 可以保證畫面中的動畫角色不會左右漂移
    rectMode(CENTER)
    spriteMode(CENTER)

    fill(249, 249, 249, 255)
    imgs = {}
    pos = {{0,0,110,120},{110,0,70,120},{180,0,70,120},{250,0,70,120},
           {320,0,105,120},{423,0,80,120},{500,0,70,120},{570,0,70,120}}

    img = readImage("Documents:runner")

    img1 = readImage("Documents:cats")
    pos1 = {{0,0,32,43},{0,0,64,43},{0,0,96,43}}

    -- 初始化
    m = Sprites(600,400,img,startPos)
    -- m1 = Sprites(800,400,img1,pos1)

end

function draw()
    background(39, 44, 39, 255)

    m:draw()
    -- m1:draw()
end

現在的程式碼

最新版本的程式碼

-- 幀動畫物件類

Sprites = class() 

function Sprites:init(x,y,img,pos)
    self.x = x
    self.y = y
    -- self.index = 1
    self.img = img
    self.imgs = {}
    self.pos = pos
    self.i=0
    self.k=1
    self.q=0
    self.prevTime =0

    -- 處理原圖,背景色變為透明
    self:deal()

    -- 使用迴圈,把各個子幀存入表中
    for i=1,#self.pos do
        -- imgs[i] = img:copy(pos[i][1],pos[i][2],pos[i][3],pos[i][4])
        self.imgs[i] = self.img:copy(table.unpack(self.pos[i]))
    end

end

function Sprites:deal()
    ---[[ 對原圖進行預處理,把背景修改為透明,現存問題:角色內部有白色也會被去掉
    local v = 255
    for x=1,self.img.width do
        for y =1, self.img.height do
            -- 取出所有畫素的顏色值
            local r,g,b,a = self.img:get(x,y)
            -- if r >= v and g >= v and b >= v then
            if r == v and g == v and b == v and a == v then
                self.img:set(x,y,r,g,b,0)
            end
        end
    end
    --]]
end

function Sprites:draw()
    -- 確定每幀子畫面在螢幕上停留的時間
    if ElapsedTime > self.prevTime + 0.08 then
        self.prevTime = self.prevTime + 0.08 
        self.k = math.fmod(self.i,#self.imgs) 
        self.i = self.i + 1      
    end
    self.q=self.q+1
    -- rect(800,500,120,120) 
    pushMatrix()
    -- rotate(30)
    -- sprite(self.imgs[self.k+1],self.i*10%WIDTH+100,HEIGHT/6,HEIGHT/8,HEIGHT/8) 
    --sprite(imgs[math.fmod(q,8)+1],i*10%WIDTH+100,HEIGHT/6,HEIGHT/8,HEIGHT/8) 
    sprite(self.imgs[self.k+1], self.x, self.y,50,50)
    popMatrix()
    -- sprite(imgs[self.index], self.x, self.y)
end

-- Main
function setup()
    displayMode(FULLSCREEN)

    -- 繪圖模式選 CENTER 可以保證畫面中的動畫角色不會左右漂移
    rectMode(CENTER)
    spriteMode(CENTER)

    fill(249, 249, 249, 255)
    imgs = {}
    pos = {{0,0,110,120},{110,0,70,120},{180,0,70,120},{250,0,70,120},
           {320,0,105,120},{423,0,80,120},{500,0,70,120},{570,0,70,120}}

    img = readImage("Documents:runner")

    img1 = readImage("Documents:cats")
    local x,y = 128,43
    pos1 = {{0+x,0+y,32,43},{64+x,0+y,32,43},{96+x,0+y,32,43}}

    img2 = readImage("Documents:catRunning")
    local w,h = 1024,1024
    pos2 = {{0,h*3/4,w/2,h/4},{w/2,h*3/4,w/2,h/4},{0,h*2/4,w/2,h/4},{0,h*2/4,w/2,h/4},
            {0,h*1/4,w/2,h/4},{w/2,h*1/4,w/2,h/4},{0,h*0/4,w/2,h/4},{0,h*0/4,w/2,h/4}}

    m = Sprites(600,400,img,pos)
    m1 = Sprites(500,400,img1,pos1)
    m2 = Sprites(500,200,img2,pos2)
end

function draw()
    background(39, 44, 39, 255)

    m:draw()
    m1:draw()
    m2:draw()
end

幀動畫小結

幀動畫是一種應用場景非常廣泛的基礎遊戲開發技術, 遊戲角色的大多數動作都是通過幀動畫來實現的, 例如角色平時的移動, 無聊時的各種小動作, 以及戰鬥時的各種技能釋放, 所以做遊戲開發一定要徹底理解幀動畫的原理和實現, 這樣才能得心應手地把它運用在開發中.

擴充套件閱讀

把素材背景設為透明

到目前為止, 我們的角色幀動畫已經做好了, 不過看起來不是很協調, 尤其是動畫角色頂著一個白色矩形框, 這是因為素材沒有采用透明背景, 所以看起來感覺不太好.

不過既然背景是單一的白色, 那麼我們為什麼不在動畫顯示前把它做一個預處理? 把它的白色背景改為透明? 這裡普及一下, 一般圖片素材都有4個顏色通道, 分別為: r, g, b, a, 前三個分別為紅色, 綠色, 藍色, 第四個 a 就是透明度, 它們的取值範圍都是 0~255, 對於透明度來說, 0 表示透明, 255表示不透明, 中間的值表示不同程度的透明.

那麼我們的思路很簡單, 把每個子畫面的每個畫素點都取出來,判斷它是不是白色(白色的r,g,b,a值分別為255), 如果是, 我們就認為它是白色背景, 把它的 a 置為 0 ,然後寫回到原位置去, 如果不是背景則不做處理, 具體程式碼在這裡:

---[[ 對每個子畫面進行預處理,把背景修改為透明
for i=1,8 do
    for x=1,imgs[i].width do
        for y =1, imgs[i].height do
            r,g,b,a = imgs[i]:get(x,y)
            if r == 255 and g == 255 and b == 255 then
                imgs[i]:set(x,y,r,g,b,0)
            end
        end
    end
end
--]]

等等, 為什麼不直接對整個素材影像進行處理呢, 這樣還可以少一個迴圈, 如下:

---[[ 對原圖進行預處理,把背景修改為透明
for x=1,img.width do
    for y =1, img.height do
        -- 取出所有畫素的顏色值
        r,g,b,a = img:get(x,y)
        -- if r >= 205 and g >= 205 and b >= 205 then
        if r == 255 and g == 255 and b == 255 then
            img:set(x,y,r,g,b,0)
        end
    end
end
--]]

這兩段程式碼最終效果是一樣的, 不過後一種效率更高, 因為它集中處理整副素材圖, 少了一重迴圈.

因為這段程式碼只需要執行一次即可, 所以我們把它放在 setup() 函式中, 看看效果, 果然感覺好多了, 雖然邊緣部分看起來清除得不是那麼好.

最終我們會把這個函式整合到幀動畫類中, 作為一個方法, 程式碼如下:

function Sprites:deal()
    ---[[ 對原圖進行預處理,把背景修改為透明
   for x=1,self.img.width do
        for y =1, self.img.height do
            -- 取出所有畫素的顏色值
            local r,g,b,a = self.img:get(x,y)
            -- if r >= 205 and g >= 205 and b >= 205 then
            if r == 255 and g == 255 and b == 255 then
                self.img:set(x,y,r,g,b,0)
            end
        end
    end
end

可以選擇把這個方法放在 Sprites:init() 中呼叫,

  • 提醒: 如果希望得到更好的圖形效果, 可以用修圖軟體手動修改素材, 這是隻是介紹一種簡單的用程式碼處理影像的思路, 而且正式的遊戲開發總是把能提前處理的步驟都儘量提前處理, 實在沒辦法處理的才用程式碼解決.

現在的效果看起來是不是又有了一些改進? 沒錯, 好的軟體就是從一點一滴的細節改善中做出來的.

讓角色移動

不過感覺還是有點美中不足, 首先黑黑的背景有些影響觀感, 那麼我們增加一個非洲大草原的背景圖, 程式碼如下:

sprite("Documents:bgGrass",(WIDTH/2),HEIGHT/2)

效果貌似稍微好了點, 感覺還是不太對, 現在雖然看起來角色雖然在跑,可是總是在原地踏步, 遊戲中的角色不能一直原地踏步不移動啊! 怎麼辦?

兩個辦法:

  • 一是讓背景畫面移動起來
  • 二是讓角色移動起來

先說第一種, 很簡單, 修改這條顯示背景圖片的語句:

sprite("Documents:bgGrass",(WIDTH/2),HEIGHT/2)

讓它的 x 座標遞減即可, 恰好我們有一個遞增的變數 i 可以直接拿來用

sprite("Documents:bgGrass",(WIDTH/2-10*i),HEIGHT/2)

我們發現背景動是動起來了, 可是隻要超過座標範圍就沒了, 看來還得增加相關處理, 先分析一下出現這種情況的原因, 因為我們的背景圖的大小是 1024*768, 所以一旦左右移動背景超過了這個長寬範圍,就沒有影像了,因此,我們可以有這麼幾種解決思路:

  • 1 預先準備一個大於螢幕尺寸的大背景圖;
  • 2 使用兩個繪圖語句,一個以 0,0 為左下角起點,另一個以 1024,0 為左下角起點(因為是左右移動,所以縱座標不需要修改),如下圖所示:

這樣還有個問題,假設整個背景圖向左移動,那麼當移動到最右邊時,還會出現沒有影像的情況,這時我們可以使用一種前面用過的技術,那就是對橫座標取模,讓它們始終落在 0~1024 這個區間內, math.fmod(x,1024), 其中橫座標 x 是一個遞增的量.

這樣一來, 角色就可以朝各個方向移動了, 現階段為方便測試, 我們可以這麼設定:

  • 點選螢幕左側, 背景圖向右平移;
  • 點選螢幕右側, 背景圖向左平移.

要實現這個設定, 需要我們在 setup 中增加一個全域性變數 s, 在 draw() 中增加繪製全屏背景圖的程式碼, 在 touched 函式中增加一段程式碼, 如下:

function setup()
    displayMode(OVERLAY)
    myStatus = Status()

    -- 以下為幀動畫程式碼
    s = -1
    ...
end

function draw()
    pushMatrix()
    pushStyle()
    -- spriteMode(CORNER)
    rectMode(CORNER)
    background(32, 29, 29, 255)

    -- 增加移動的背景圖: + 為右移,- 為左移
    sprite("Documents:bgGrass",(WIDTH/2+10 * s * m.i)%(WIDTH),HEIGHT/2)
    sprite("Documents:bgGrass",(WIDTH+10 * s * m.i)%(WIDTH),HEIGHT/2)
    ...
end

function touched(touch)
    -- 用於測試修煉
    if touch.x > WIDTH/2 and touch.state == ENDED then myStatus:update() end

    -- 用於測試移動方向:點選左側向右平移,點選右側向左平移
    if touch.x > WIDTH/2 and touch.state == ENDED then 
        s = -1
    elseif touch.x < WIDTH/2 then 
        s = 1
    end
end

現在遊戲還不完整, 所以我們才通過一些設定好的變數來大致控制角色的移動--僅用於測試模組功能, 等後面控制系統完成, 我們會寫一些觸控函式, 用它們來設定這些變數(m.i, s), 這樣我們就可以通過觸控來精確控制角色的移動了.

再說第二種: 讓角色平行移動

明白了第一種讓背景平移的方法後, 第二種讓角色平移的方法就更容易理解了, 也就是在Sprites:draw() 函式中動態修改角色繪製語句

sprite(self.imgs[self.k+1], self.x, self.y,50,50)` 

self.x 值, 結合第一種方法的具體實現, 基本上就是把 self.x 模仿 self.i 的處理方式處理一下就可以了, 具體如下:

function Sprites:draw()
    -- 確定每幀子畫面在螢幕上停留的時間
    if ElapsedTime > self.prevTime + 0.08 then
        self.prevTime = self.prevTime + 0.08 
        self.k = math.fmod(self.i,#self.imgs) 
        self.i = self.i + 1
        self.x = self.x + s    
    end
    ...
    sprite(self.imgs[self.k+1], self.x, self.y,50,50)
end

其他都不必變(當然為了效果更明顯, 可以把背景圖顯示改為固定位置繪製), 修改後程式碼如下:

-- 增加移動的背景圖: + 為右移,- 為左移
-- sprite("Documents:bgGrass",(WIDTH/2+10 * s * m.i)%(WIDTH),HEIGHT/2)
-- sprite("Documents:bgGrass",(WIDTH+10 * s * m.i)%(WIDTH),HEIGHT/2)
sprite("Documents:bgGrass",WIDTH/2,HEIGHT/2)

這兩種方式各有利弊, 我們可以根據實際需要進行選擇.

跟第一章整合

相關文章