iT邦幫忙

2023 iThome 鐵人賽

DAY 19
1
自我挑戰組

WiFiBoy Python 玩學機介紹系列 第 19

19. 經典遊戲(四): Sprite引擎介紹與小精靈

  • 分享至 

  • xImage
  •  

Sprite 精靈圖介紹

精靈圖(Sprite)又叫做「拚合圖」,可將許多單一的小圖片合併成一張大圖,透過索引與座標來控制圖像的顯示。可減少檔案讀取時間,增加系統反應速度。精靈圖源於1974年街機興起,唯讀記憶體中的精靈圖被大量使用,增加遊戲畫面的可看性與顯示速度。

近年來網路技術興起,CSS 拼合圖(CSS Sprite)技術也借鏡早期精靈圖的核心,將需要分別顯示的多張圖片整合成單一大圖,利用CSS分別定位顯示各圖的技術,減少瀏覽器下載圖片的數量,提高網頁顯示速度。

https://ithelp.ithome.com.tw/upload/images/20230920/20105707QMwCvYDHhz.png

https://ithelp.ithome.com.tw/upload/images/20230920/20105707AkJsRQUZrr.png

玩學機內建的 Sprite 引擎

玩學機除了物聯網的應用外,另外一個強項就是拿來開發復古小遊戲。玩學機團隊中,就有當年開發紅白機遊戲的資深工程師。團隊為了要引導孩子「做中學、玩中學」的玩學精神,特地在系統內建了一張 Sprite 圖。程式教學的教師與學生,可利用這些素材,做出生動有趣的小遊戲,讓學習者了解當年製作遊戲的許多技巧,更能理解電腦圖學教科書中的許多理論知識。

因為 Sprite 圖形已經燒錄到系統中,我們是無法直接修改它。為了要知道裡面有什麼元件,我們先寫一個小程式來抓出裡面的排列規則。

wb.blitpal()
while True:
    for i in range(0, 80):
        wb.blitbuf()
        wb.blitimg(i*16, 0, 0, 128, 128)
        if wb.getkey() == 1:					    # 我們運用一個技巧,按住右邊黃鍵
            time.sleep(20)                      # 暫停20秒,可拿出手機拍攝記錄
        wb.blit()
        time.sleep(0.1)

透過擷取這些照片,我們可以推斷規則,這樣學員便可快速找出圖片的索引值,方便使用。幾個簡單的規則,整理如下:

  1. 英文數字符號規則
  2. WiFiBoy Logo
  3. 小精靈遊戲素材
  4. 坦克大戰素材

其他還有許多圖案,有興趣的同學可以自行研究。

取出數字、符號與字元

內建的 Sprite 圖檔為 128X640 大小的圖形,裡面的數字、符號與字元元素是 8x8 的像素圖檔,128 / 8 = 16,640 / 8 = 80,每一個像素圖檔有索引值可以呼叫指定的圖形。

  1. 列出字型檔全部圖案X: 8 * 16 = 128,Y: 8 * 6 = 48 wb.blitimg(0, 8, 64, 128, 48) # 畫出圖型
  2. 印出大寫A wb.blitimg(65-32, 8, 64, 8, 8) # 畫出圖型
  3. 印白色WiFiBoy.org wb.blitimg(96, 8, 56, 64, 8)
  4. 印藍色WiFiBoy.org wb.blitimg(104, 8, 64, 64, 8)
wb.blitpal()							   # 啟動調色盤
for i in range(0,1024):					   # 啟動列印像素圖迴圈
    wb.blitbuf()						   # 清除記憶空間
    wb.blitimg(i, 8, 64, 8, 8)	           # 畫出圖型
    if wb.getkey() == 64:                  # 按下右上方 menu 綠色按鍵暫停 10 秒
        time.sleep(10)
    wb.blit()
    time.sleep(0.1)

https://ithelp.ithome.com.tw/upload/images/20230920/201057071QBdy8MPFx.png

印出 WiFiBoy 的 Logo

# 印出藍白商標圖
for i in range(0,2):						# 啟動列印像素圖迴圈
    wb.blitbuf()							# 清除記憶空間
    # 印白色WiFiBoy.org
    wb.blitimg(96, 8, 56, 64, 8)
    # 印藍色WiFiBoy.org
    wb.blitimg(104, 8, 64, 64, 8)
    wb.blit()
    time.sleep(0.1)

小精靈的素材

https://ithelp.ithome.com.tw/upload/images/20230920/201057079cGlCvV1KJ.png

畫出小精靈地圖

wb.cls()
wb.blitbuf()
wb.blitimg(464, 20, 4, 120, 16) # 地圖上面一排,索引第 464 個圖案,起點(20,4),長度120,原始圖案為16X16,取8x8 即為上半部
# 畫垂直邊界
for i in range(14):
    wb.blitimg(424, 131, 12+i*8) # 右邊界圖像
    wb.blitimg(421, 21, 12+i*8)  # 左邊界圖像

wb.blitimg(480, 20, 124, 120, 8) # 地圖最下面一排。
# 畫地圖中間兩個牆壁
wb.blitimg(400, 40, 52, 8, 32); wb.blitimg(400, 112, 52, 8, 32)
# 畫地圖上方兩個牆壁
wb.blitimg(401, 40, 28, 32, 8); wb.blitimg(401, 88, 28, 32, 8)
# 畫地圖上方兩個牆壁
wb.blitimg(401, 40, 100, 32, 8); wb.blitimg(401, 88, 100, 32, 8)
# 畫地圖最中間的方塊,鬼出現的基地
wb.blitimg(405, 64, 52, 32, 32);
# 畫櫻桃
wb.blitimg(368, 72, 56, 16, 16)
# 畫地圖中間的豆子
for i in range(13):
    for j in range(13):
        if bean[i+j*13]==1: wb.blitimg(417, 28+i*8, 16+j*8)
        elif bean[i+j*13]==2: wb.blitimg(418, 28+i*8, 16+j*8)
        elif bean[i+j*13]==3: wb.blitimg(368, 72, 84, 16, 16)

wb.blit() # blit all to refresh display

https://ithelp.ithome.com.tw/upload/images/20230920/20105707iZVoH0kurb.png

坦克大戰素材

https://ithelp.ithome.com.tw/upload/images/20230920/201057071u1HyDb6nj.png

PacBoy 特製版小精靈

感謝玩學運算科技公司提供底下的範例程式,讓我們可以了解如何在 短短兩百九十行內寫完一個這麼有趣的小精靈遊戲。

https://ithelp.ithome.com.tw/upload/images/20230920/20105707b5Fud9c66g.png

# WiFiBoy OK:ESP32 Pacman Clone Game (BLITENGINE)
# Jan 3, 2020, by Peter Gabriel under CC-BY-4.0 license
# Modifiy By Daniel Teng 2023.09.18
import machine, time
try:
    if snd: snd.deinit()
except: pass

machine.Pin(17,3).value(1)
snd=machine.PWM(machine.Pin(25, 3))
snd.duty(0)

env1=[90,60,30,0,0,0,0,0]
env2=[90,60,30,20,15,10,5,0]
env3=[20,90,20,80,20,60,5,0]
env4=[90,20,90,20,10,20,10,0]

f=[round(110*2**(x/12)) for x in range(51)]

mel1=[22,13,0,0]
mel2=[30,32,34,36]
mel3=[30,32,38,0]
mel4=[16,22,28,0,26,0,28,0]
mel5=[39,0,51,0,46,0,43,0, 51,46,0,0,43,0,0,0,
      40,0,52,0,47,0,44,0,52,0,47,0,44,0,0,0,
      39,0,51,0,46,0,43,0, 51,46,0,0,43,0,0,0,
      44,44,0,45,0,45,46,0,47,0,47,48,49,0,51,51,0]
mel6=[40,0,42,0,44,0,45,0,47,0,49,0,51,0,0]
mel7=[40,41,0,0,0,0,0,40,0,0]

def play(dt, mel, env, shift): # melody engine
    for i in range(0, len(mel)):
        if (mel[i]!=0):
            snd.freq(f[mel[i]+shift])
            ei=0
        for j in range(0, 4):
            if (ei<8):snd.duty(env[ei])
            time.sleep(dt)
            ei=ei+1
            
def soundit(f):
    snd.freq(f)
    snd.duty(90)
    time.sleep(0.005)
    snd.duty(0)

cposx = [0,48,96,0,24,48,72,96,0,24,48,72,96,0,48,96]
cposy = [0,0,0,24,24,24,24,24,72,72,72,72,72,96,96,96]
cturn = [1,6,2,5,6,8,6,7,5,8,6,8,7,3,8,4]
dirx=[0, 2, 0, -2] # up,right,down,left
diry=[-2, 0, 2, 0]
level = 1; life = 5
score = pwrcount = kbr = pani = pflp = 0

def reset_ghost():
    global gx, gy, gdx, gdy, gtype, pwrcount
    gx = [0, 96, 24, 72, 48, 0, 96, 96, 48]
    gy = [0, 24, 48, 48, 24, 96, 96, 0, 0]
    gdx = [1, 0, 0, 0, 1, 0, -1, -1, 0]
    gdy = [0, -1, -1, 1, 0, -1, 0, 0, 1]
    gtype = [0,0,0,0,0,0,0,0,0]
    pwrcount=0
    
def reset_game():
    global bean, bcount, px, py, dx, dy, pwrcount
    bean = [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
            1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1,
            1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1,
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1,
            1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1,
            1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1,
            1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1,
            1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1,
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1,
            1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1,
            2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2]
    px = 48; py = 72
    bcount = dx = dy = 0
    pwrcount=0
    reset_ghost()
    play(0.012, mel5, env1, -24)
# 繪製場景
def draw_scene():
    wb.blitbuf()
    wb.blitimg(464, 20, 4, 120, 8)
    for i in range(14): wb.blitimg(424, 131, 12+i*8); wb.blitimg(421, 21, 12+i*8)
    wb.blitimg(480, 20, 124, 120, 8)
    for i in range(13):
        for j in range(13):
            if bean[i+j*13]==1: wb.blitimg(417, 28+i*8, 16+j*8)
            elif bean[i+j*13]==2: wb.blitimg(418, 28+i*8, 16+j*8)
            elif bean[i+j*13]==3: wb.blitimg(368, 72, 84, 16, 16)
    wb.blitimg(400, 40, 52, 8, 32); wb.blitimg(400, 112, 52, 8, 32)
    wb.blitimg(401, 40, 28, 32, 8); wb.blitimg(401, 88, 28, 32, 8)
    wb.blitimg(401, 40, 100, 32, 8); wb.blitimg(401, 88, 100, 32, 8)
    wb.blitimg(405, 64, 52, 32, 32); wb.blitimg(368, 72, 56, 16, 16)
    
def draw_score():
    wb.blitstr("SCORE "+str(score)+" Lv"+str(level),20,0)
    wb.blitstr("x"+str(life),71,72);
    wb.blit() # blit all to refresh display
    
def caught():
    global life, level, score, px, py, dx, dy, bean
    dx=dy=0
    for i in range(8):
        draw_scene()
        wb.blitimg(304+i*2, px+24, py+12, 16, 16)
        draw_score()
        for j in range(8): soundit(90*(9-i)+j*50)
    life -= 1
    draw_scene()
    if life==0: wb.blitstr("GAME OVER!", 40,24)
    else: wb.blitstr("CAUGHT!!", 48,24)
    wb.blitstr("PRESS A KEY", 37,96)
    draw_score()  
    if life==0: life=5; score=0; level=1; reset_game()
    else: px = 48; py = 72; dx = dy = 0
    while wb.getkey()!=0: pass
    while wb.getkey()==0: pass
    reset_ghost()
    if bean[123]==3: bean[123]=0 #clean 1up bonus

def eat_ghost(n):
    global score, gx, gy, gdx, gdy, gtype
    score+=100
    gx[n]=gy[n]=48
    gdx[n]=0; gdy[n]=-1
    gtype[n]=0
    play(0.005, mel6, env2, -24)

def ghost_turn2(n, a, b):
    r = wb.rand(2)
    if r==1: gdx[n]=dirx[a]; gdy[n]=diry[a]
    else: gdx[n]=dirx[b]; gdy[n]=diry[b]

def ghost_turn3(n, a, b, c):
    r = wb.rand(3)
    if r==1: gdx[n]=dirx[a]; gdy[n]=diry[a]
    elif r==2: gdx[n]=dirx[b]; gdy[n]=diry[b]
    else: gdx[n]=dirx[c]; gdy[n]=diry[c]

def ghost_go():
    global gx, gy, gdx, gdy
    for i in range(level):
        if gx[i]==px and gy[i]==py: 
            if gtype[i]==1: eat_ghost(i)
            else: caught()
            return
        gx[i] += gdx[i]
        gy[i] += gdy[i]
        if gx[i]==px and gy[i]==py: 
            if gtype[i]==1: eat_ghost(i)
            else: caught()
            return
        if (gx[i]%8!=0) and (gy[i]%8!=0): return
        for j in range(16):
            if (gx[i]==cposx[j] and gy[i]==cposy[j]):
                if cturn[j]==1: ghost_turn2(i, 1, 2)
                elif cturn[j]==2: ghost_turn2(i, 2, 3)
                elif cturn[j]==4: ghost_turn2(i, 0, 3)
                elif cturn[j]==3: ghost_turn2(i, 0, 1)
                elif cturn[j]==5: ghost_turn3(i, 0, 1, 2)
                elif cturn[j]==6: ghost_turn3(i, 1, 2, 3)
                elif cturn[j]==7: ghost_turn3(i, 0, 2, 3)
                elif cturn[j]==8: ghost_turn3(i, 0, 1, 3)
    
def check_input():
    global px, py, dx, dy, bcount, score, level, pwrcount, life
    px += dx
    py += dy
    if (px%8!=0) and (py%8!=0): return
    if pwrcount>0: soundit(200)
    pos = int(px/8)+int(py/8)*13
    if bean[pos] == 1: 
        bean[pos]=0 
        score += 10
        if pwrcount>0: play(0.001, mel3, env3, 0)
        else: soundit(300) 
        bcount += 1
        if bcount==80: # level done
            dx=dy=0
            draw_scene()
            pac_go()
            wb.blitstr("CLEAN!!", 56,40)
            wb.blitstr("NEXT LEVEL..", 36,112)
            draw_score()
            level += 1
            if level > 9: level=9                
            while wb.getkey()!=0: pass
            while wb.getkey()==0: pass
            reset_game()
            return
        elif bcount==60: #60-bean 1up
            if bean[0]+bean[12]+bean[156]+bean[168]>5: 
                bean[123]=3
                play(0.005, mel1, env1, 0)
    elif bean[pos] == 2: 
        bean[pos]=0 # power bean
        play(0.005, mel2, env1, 0)
        pwrcount=100
        for i in range(level): gtype[i]=1
    elif bean[pos] == 3:
        life += 1; bean[pos] = 0
        if life>9: life=9
        play(0.005, mel7, env2, -2)   
    for i in range(16):
        if (px==cposx[i] and py==cposy[i]):
            kb = wb.getkey()
            if cturn[i]==1: #corner 0
                if kb&4==4: dx=2; dy=0
                elif kb&16==16: dx=0; dy=2
                else: dx=0; dy=0
            elif cturn[i]==2:
                if kb&8==8: dx=-2; dy=0
                elif kb&16==16: dx=0; dy=2
                else: dx=0; dy=0
            elif cturn[i]==4:
                if kb&32==32: dx=0; dy=-2
                elif kb&8==8: dx=-2; dy=0
                else: dx=0; dy=0
            elif cturn[i]==3:
                if kb&32==32: dx=0; dy=-2
                elif kb&4==4: dx=2; dy=0
                else: dx=0; dy=0
            elif cturn[i]==5:
                if kb&32==32: dx=0; dy=-2
                elif kb&16==16: dx=0; dy=2
                elif kb&4==4: dx=2; dy=0
                elif dy==0: dx=0
            elif cturn[i]==6:
                if kb&16==16: dx=0; dy=2
                elif kb&8==8: dx=-2; dy=0
                elif kb&4==4: dx=2; dy=0
                elif dx==0: dy=0    
            elif cturn[i]==7:
                if kb&32==32: dx=0; dy=-2
                elif kb&16==16: dx=0; dy=2
                elif kb&8==8: dx=-2; dy=0    
                elif dy==0: dx=0
            elif cturn[i]==8:
                if kb&32==32: dx=0; dy=-2
                elif kb&8==8: dx=-2; dy=0
                elif kb&4==4: dx=2; dy=0 
                elif dx==0: dy=0
            return
    kb = wb.getkey()
    if kb&32==32 and dy==2: dy=-2
    elif kb&16==16 and dy==-2: dy=2
    elif kb&8==8 and dx==2: dx=-2
    elif kb&4==4 and dx==-2: dx=2

def pac_go():
    global pani, pflp, pwrcount
    pani+=1
    if pani%4==0: pflp = 1-pflp
    if pwrcount>0:
        pwrcount -= 1
        if pwrcount==0:
            for i in range(level): gtype[i]=0
    #ghost
    for i in range(level):
        if gtype[i]==1: wb.blitimg(376+pflp*2, gx[i]+24, gy[i]+12, 16,16)
        else: wb.blitimg(336+(i%4)*4+pflp*2, gx[i]+24, gy[i]+12, 16,16)
    #pacman        
    if dx>0: wb.blitimg(272+pflp*2, px+24, py+12, 16, 16)
    elif dx<0: wb.blitimg(280+pflp*2, px+24, py+12, 16, 16)
    elif dy<0: wb.blitimg(284+pflp*2, px+24, py+12, 16, 16)
    elif dy>0: wb.blitimg(276+pflp*2, px+24, py+12, 16, 16)
    else: wb.blitimg(304, px+24, py+12, 16, 16)
        
wb.blitbuf()
wb.blitimg(272, 41, 40, 16, 16)
wb.blitimg(336, 61, 40, 16, 16)
wb.blitimg(340, 81, 40, 16, 16)
wb.blitimg(344, 101, 40, 16, 16)
wb.blitstr("PacBoy Demo 2023", 16, 70)
wb.blit()
reset_game()

while True:
    draw_scene()
    check_input()
    ghost_go()
    pac_go()
    draw_score()

堅持到現在真是不簡單,明天我們將要介紹「45行寫出小蜜蜂遊戲」。


上一篇
18. 經典遊戲(三) 吃豆子遊戲
下一篇
20. 經典遊戲(五): 小蜜蜂
系列文
WiFiBoy Python 玩學機介紹30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言