如何用 Python 實現超級瑪麗的界面和狀態機?

2021-01-14 CSDN

作者 | marble_xu

責編 | 郭芮

出品 | CSDN博客

小時候的經典遊戲,代碼參考了github上的項目Mario-Level-1(https://github.com/justinmeister/Mario-Level-1),使用pygame來實現,從中學習到了橫版過關遊戲實現中的一些處理方法。原項目實現了超級瑪麗的第一個小關。

在原項目的基礎上,遊戲使用json文件來保存每一個關卡的數據,將數據和代碼解耦合,目前已開發4個小關,後續關卡的擴展也很方便,只需要添加json文件和地圖圖片,支持新的怪物就行。遊戲還支持進入水管,到新的子地圖。

這篇文章是要介紹下遊戲中的幾個界面顯示和界面之前如何轉換,所以特意寫了一個demo程序,完整的遊戲代碼在下面的github連結(https://github.com/marblexu/PythonSuperMario)中下載。

狀態機介紹

遊戲中的狀態機一般都是有限狀態機,簡寫為FSM(Finite State Machine),簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動作等行為的數學模型。

狀態機的每一個狀態至少需要有下面三個操作:

Startup:當從其他狀態進入這個狀態時,需要進行的初始化操作;Update :在這個狀態運行時進行的更新操作;Cleanup:當從這個狀態退出時,需要進行的清除操作。狀態需要的變量:

next: 表示這個狀態退出後要轉到的下一個狀態;persist:在狀態間轉換時需要傳遞的數據;done:表示這個狀態是否結束,狀態機會根據這個值來決定轉換狀態。遊戲界面狀態機的狀態轉換圖如下,箭頭表示可能的狀態轉換方向:(注意有個轉換不太好畫出來:Time Out狀態可以轉換到Game Over狀態。)

圖1

這幾個狀態的意思比較簡單,下面把遊戲界面的截圖發一下。

Main Menu:主菜單,啟動程序就進入這個狀態,可以用UP和DOWN鍵選擇player 1或player 2,按回車鍵開啟遊戲。

圖2

Load Screen:遊戲開始前的加載界面。

圖3

Game Run:遊戲運行時的狀態,在代碼實現中是Level類。

圖4

Game Over:人物死亡且生命數目為0時到這個狀態。

圖5

Time Out:在遊戲中時間超時會到這個狀態,這個和Game Over類似,就不截圖了。

狀態機代碼實現

因為這篇文章的目的是遊戲界面的狀態機實現,所以專門寫了一個state_demo.py文件,讓大家可以更加方便的看代碼。

遊戲啟動代碼

開始是 pygame的初始化,設置屏幕大小為c.SCREEN_SIZE(800, 600)。所有的常量都保存在單獨的constants.py中。

import osimport pygame as pgimport constants as cpg.init()pg.event.set_allowed([pg.KEYDOWN, pg.KEYUP, pg.QUIT])pg.display.set_caption(c.ORIGINAL_CAPTION)SCREEN = pg.display.set_mode(c.SCREEN_SIZE)SCREEN_RECT = SCREEN.get_rect()

load_all_gfx函數查找指定目錄下所有符合後綴名的圖片,使用pg.image.load函數加載,保存在graphics set中。

GFX 保存在resources/graphics目錄找到的所有圖片,後面獲取各種圖形時會用到。

def load_all_gfx(directory, colorkey=(255,0,255), accept=('.png', '.jpg', '.bmp', '.gif')): graphics = {}for pic inos.listdir(directory): name, ext = os.path.splitext(pic)if ext.lower() in accept: img = pg.image.load(os.path.join(directory, pic))if img.get_alpha(): img = img.convert_alpha()else: img = img.convert() img.set_colorkey(colorkey) graphics[name] = imgreturn graphicsGFX = load_all_gfx(os.path.join("resources","graphics"))

下面是demo的入口函數,先創建了一個保存所有狀態的state_dict set,調用setup_states函數設置起始狀態是 MAIN_MENU。

if __name__=='__main__': game = Control() state_dict = {c.MAIN_MENU: Menu(), c.LOAD_SCREEN: LoadScreen(), c.LEVEL: Level(), c.GAME_OVER: GameOver(), c.TIME_OUT: TimeOut()} game.setup_states(state_dict, c.MAIN_MENU) game.main()

狀態類

先定義一個State 基類, 按照上面說的狀態需要的三個操作分別定義函數(startup, update, cleanup)。在 init 函數中定義了上面說的三個變量(next,persist,done),還有start_time 和 current_time 用於記錄時間。

classState():def__init__(self): self.start_time = 0.0 self.current_time = 0.0 self.done = False self.next = None self.persist = {} @abstractmethoddefstartup(self, current_time, persist):'''abstract method'''defcleanup(self): self.done = Falsereturn self.persist @abstractmethoddefupdate(sefl, surface, keys, current_time):'''abstract method'''

看一個狀態類LoadScreen的具體實現,這個狀態的顯示效果如圖3。

startup 函數保存了傳入的persist,設置 next 為Level 狀態類,start_time保存進入這個狀態的開始時間。初始化一個Info類,這個就是專門用來顯示界面信息的。

update 函數根據在這個狀態已運行的時間(current_time - self.start_time),決定顯示內容和是否結束狀態(self.done = True)。

classLoadScreen(State):def__init__(self): State.__init__(self)self.time_list = [2400, 2600, 2635]defstartup(self, current_time, persist):self.start_time = current_timeself.persist = persistself.game_info = self.persistself.next = self.set_next_state() info_state = self.set_info_state()self.overhead_info = Info(self.game_info, info_state)defset_next_state(self):return c.LEVELdefset_info_state(self):return c.LOAD_SCREENdefupdate(self, surface, keys, current_time):if (current_time - self.start_time) < self.time_list[0]: surface.fill(c.BLACK)self.overhead_info.update(self.game_info)self.overhead_info.draw(surface) elif (current_time - self.start_time) < self.time_list[1]: surface.fill(c.BLACK) elif (current_time - self.start_time) < self.time_list[2]: surface.fill((106, 150, 252))else:self.done = True

Info類

下面介紹Info類,界面的顯示大部分都是由它來完成,init函數中create_info_labels函數創建通用的信息,create_state_labels函數對於不同的狀態,會初始化不同的信息。

classInfo():def__init__(self, game_info, state):self.coin_total = game_info[c.COIN_TOTAL]self.total_lives = game_info[c.LIVES]self.state = stateself.game_info = game_infoself.create_font_image_dict()self.create_info_labels()self.create_state_labels()self.flashing_coin = FlashCoin(280, 53)

create_font_image_dict函數從之前加載的圖片GFX[『text_images』]中,截取字母和數字對應的圖形,保存在一個set中,在後面創建文字時會用到。

defcreate_font_image_dict(self):self.image_dict = {} image_list = [] image_rect_list = [# 0 - 9 (3, 230, 7, 7), (12, 230, 7, 7), (19, 230, 7, 7), (27, 230, 7, 7), (35, 230, 7, 7), (43, 230, 7, 7), (51, 230, 7, 7), (59, 230, 7, 7), (67, 230, 7, 7), (75, 230, 7, 7), # A - Z (83, 230, 7, 7), (91, 230, 7, 7), (99, 230, 7, 7), (107, 230, 7, 7), (115, 230, 7, 7), (123, 230, 7, 7), (3, 238, 7, 7), (11, 238, 7, 7), (20, 238, 7, 7), (27, 238, 7, 7), (35, 238, 7, 7), (44, 238, 7, 7), (51, 238, 7, 7), (59, 238, 7, 7), (67, 238, 7, 7), (75, 238, 7, 7), (83, 238, 7, 7), (91, 238, 7, 7), (99, 238, 7, 7), (108, 238, 7, 7), (115, 238, 7, 7), (123, 238, 7, 7), (3, 246, 7, 7), (11, 246, 7, 7), (20, 246, 7, 7), (27, 246, 7, 7), (48, 246, 7, 7),# -* (68, 249, 6, 2), (75, 247, 6, 6)] character_string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ -*'for character, image_rect in zip(character_string, image_rect_list):self.image_dict[character] = get_image(GFX['text_images'], *image_rect, (92, 148, 252), 2.9)

get_image函數從一個大的Surface sheet 中按照 area(x, y, width, height)截取出部分圖片 放入Surface image對應的起始位置(0,0),並按照scale參數調整大小。

pygame的 blit 函數介紹如下:

pg.Surface.blit(source, dest, area=None, special_flags=0) -> Rect draw one image onto another

def get_image(sheet, x, y, width, height, colorkey, scale): image = pg.Surface([width, height]) rect = image.get_rect() image.blit(sheet, (0, 0), (x, y, width, height)) image.set_colorkey(colorkey) image = pg.transform.scale(image, (int(rect.width*scale),int(rect.height*scale)))return image

看一下create_info_labels函數中其中一個字符串』MARIO』是如何在界面上顯示的。

create_label函數參數 (x, y) 表示字符串在界面上的起始位置,從self.image_dict中根據字符獲取對應的Surface 對象。

set_label_rects函數會設置字符串中每一個Surface 對象 rect 的(x, y)值。

pygame.Rect 對象中常用的成員變量(x,y),表示這個Surface的左上角的位置。top, bottom: 表示Surface 在y軸上最上邊和最下邊的值, 所以top和y 值是一樣的left, right: 表示Surface 在x軸上最左邊和最右邊的值,所以left 和x 值是一樣的

下面的坐標圖可以看到,在左上角是整個屏幕的原點(0,0), 圖中標識了長方形rect的四個頂點的坐標。

defcreate_info_labels(self): ...self.mario_label = [] ...self.create_label(self.mario_label, 'MARIO', 75, 30)defcreate_label(self, label_list, string, x, y):for letter instring: label_list.append(Character(self.image_dict[letter]))self.set_label_rects(label_list, x, y)defset_label_rects(self, label_list, x, y):for i, letter in enumerate(label_list): letter.rect.x = x + ((letter.rect.width + 3) * i) letter.rect.y = yif letter.image == self.image_dict['-']: letter.rect.y += 7 letter.rect.x += 2

Control類

Control 是狀態機類,main函數是遊戲的主循環,setup_states函數設置遊戲啟動時運行的狀態。

classControl():def__init__(self):self.screen = pg.display.get_surface()self.done = Falseself.clock = pg.time.Clock()self.fps = 60self.current_time = 0.0self.keys = pg.key.get_pressed()self.state_dict = {}self.state_name = Noneself.state = Nonedefsetup_states(self, state_dict, start_state):self.state_dict = state_dictself.state_name = start_stateself.state = self.state_dict[self.state_name]defmain(self):whilenotself.done:self.event_loop()self.update() pg.display.update()self.clock.tick(self.fps)

event_loop函數負責監聽輸入(鍵盤輸入和退出按鈕),slef.keys 保存鍵盤輸入。

update函數會檢測狀態的done值,調用狀態的更新函數。如果檢測到當前狀態結束,就調用flip_state函數進行舊狀態的清理操作,並轉換到下一個狀態。

defupdate(self):self.current_time = pg.time.get_ticks()ifself.state.done:self.flip_state()self.state.update(self.screen, self.keys, self.current_time)defflip_state(self): previous, self.state_name = self.state_name, self.state.next persist = self.state.cleanup()self.state = self.state_dict[self.state_name]self.state.startup(self.current_time, persist)defevent_loop(self):for event in pg.event.get():if event.type == pg.QUIT:self.done = True elif event.type == pg.KEYDOWN:self.keys = pg.key.get_pressed() elif event.type == pg.KEYUP:self.keys = pg.key.get_pressed()

完整代碼

有兩個文件constants.py 和 state_demo.py,constants.py 保存了所有的字符串定義和常量。

constants.py

GAME_TIME_OUT 表示遊戲的超時時間,這邊為了demo演示,設成了5秒,實際是300秒。

SCREEN_HEIGHT = 600SCREEN_WIDTH = 800SCREEN_SIZE = (SCREEN_WIDTH,SCREEN_HEIGHT)ORIGINAL_CAPTION = "Super Mario Bros"GAME_TIME_OUT = 5## COLORS ### R G BBLACK = ( 0, 0, 0)SIZE_MULTIPLIER = 2.5BRICK_SIZE_MULTIPLIER = 2.69BACKGROUND_MULTIPLER = 2.679GROUND_HEIGHT = SCREEN_HEIGHT - 62#STATES FOR ENTIRE GAMEMAIN_MENU = 'main menu'LOAD_SCREEN = 'load screen'TIME_OUT = 'time out'GAME_OVER = 'game over'LEVEL = 'level'#MAIN MENU CURSOR STATESPLAYER1 = '1 PLAYER GAME'PLAYER2 = '2 PLAYER GAME'#GAME INFO DICTIONARY KEYSCOIN_TOTAL = 'coin total'SCORE = 'score'TOP_SCORE = 'top score'LIVES = 'lives'CURRENT_TIME = 'current time'LEVEL_NUM = 'level num'PLAYER_NAME = 'player name'PLAYER_MARIO = 'mario'PLAYER_LUIGI = 'luigi'ITEM_SHEET = 'item_objects'

state_demo.py

上面講的狀態類,狀態機類都放在這裡。

import osimport pygame as pgfrom abc import ABC, abstractmethodimport constants as cclassState():def__init__(self):self.start_time = 0.0self.current_time = 0.0self.done = Falseself.next = Noneself.persist = {} @abstractmethoddefstartup(self, current_time, persist):'''abstract method'''defcleanup(self):self.done = Falsereturnself.persist @abstractmethoddefupdate(sefl, surface, keys, current_time):'''abstract method'''classMenu(State):def__init__(self): State.__init__(self) persist = {c.COIN_TOTAL:0, c.SCORE:0, c.LIVES:3, c.TOP_SCORE:0, c.CURRENT_TIME:0.0, c.LEVEL_NUM:1, c.PLAYER_NAME: c.PLAYER_MARIO}self.startup(0.0, persist)defstartup(self, current_time, persist):self.next = c.LOAD_SCREENself.persist = persistself.game_info = persistself.overhead_info = Info(self.game_info, c.MAIN_MENU)self.setup_background()self.setup_player()self.setup_cursor()defsetup_background(self):self.background = GFX['level_1']self.background_rect = self.background.get_rect()self.background = pg.transform.scale(self.background, (int(self.background_rect.width*c.BACKGROUND_MULTIPLER), int(self.background_rect.height*c.BACKGROUND_MULTIPLER)))self.viewport = SCREEN.get_rect(bottom=SCREEN_RECT.bottom)self.image_dict = {} image = get_image(GFX['title_screen'], 1, 60, 176, 88, (255, 0, 220), c.SIZE_MULTIPLIER) rect = image.get_rect() rect.x, rect.y = (170, 100)self.image_dict['GAME_NAME_BOX'] = (image, rect)defsetup_player(self):self.player_list = [] player_rect_info = [(178, 32, 12, 16), (178, 128, 12, 16)]for rect inplayer_rect_info: image = get_image(GFX['mario_bros'], *rect, c.BLACK, 2.9) rect = image.get_rect() rect.x, rect.bottom = 110, c.GROUND_HEIGHTself.player_list.append((image, rect))self.player_index = 0defsetup_cursor(self):self.cursor = pg.sprite.Sprite()self.cursor.image = get_image(GFX[c.ITEM_SHEET], 24, 160, 8, 8, c.BLACK, 3) rect = self.cursor.image.get_rect() rect.x, rect.y = (220, 358)self.cursor.rect = rectself.cursor.state = c.PLAYER1defupdate(self, surface, keys, current_time):self.current_time = current_timeself.game_info[c.CURRENT_TIME] = self.current_timeself.player_image = self.player_list[self.player_index][0]self.player_rect = self.player_list[self.player_index][1]self.update_cursor(keys)self.overhead_info.update(self.game_info) surface.blit(self.background, self.viewport, self.viewport) surface.blit(self.image_dict['GAME_NAME_BOX'][0],self.image_dict['GAME_NAME_BOX'][1]) surface.blit(self.player_image, self.player_rect) surface.blit(self.cursor.image, self.cursor.rect)self.overhead_info.draw(surface)defupdate_cursor(self, keys):ifself.cursor.state == c.PLAYER1:self.cursor.rect.y = 358if keys[pg.K_DOWN]:self.cursor.state = c.PLAYER2self.player_index = 1self.game_info[c.PLAYER_NAME] = c.PLAYER_LUIGI elif self.cursor.state == c.PLAYER2:self.cursor.rect.y = 403if keys[pg.K_UP]:self.cursor.state = c.PLAYER1self.player_index = 0self.game_info[c.PLAYER_NAME] = c.PLAYER_MARIOif keys[pg.K_RETURN]:self.done = TrueclassLoadScreen(State):def__init__(self): State.__init__(self)self.time_list = [2400, 2600, 2635]defstartup(self, current_time, persist):self.start_time = current_timeself.persist = persistself.game_info = self.persistself.next = self.set_next_state() info_state = self.set_info_state()self.overhead_info = Info(self.game_info, info_state)defset_next_state(self):return c.LEVELdefset_info_state(self):return c.LOAD_SCREENdefupdate(self, surface, keys, current_time):if (current_time - self.start_time) < self.time_list[0]: surface.fill(c.BLACK)self.overhead_info.update(self.game_info)self.overhead_info.draw(surface) elif (current_time - self.start_time) < self.time_list[1]: surface.fill(c.BLACK) elif (current_time - self.start_time) < self.time_list[2]: surface.fill((106, 150, 252))else:self.done = TrueclassGameOver(LoadScreen):def__init__(self): LoadScreen.__init__(self)self.time_list = [3000, 3200, 3235]defset_next_state(self):return c.MAIN_MENUdefset_info_state(self):return c.GAME_OVERclassTimeOut(LoadScreen):def__init__(self): LoadScreen.__init__(self)self.time_list = [2400, 2600, 2635]defset_next_state(self):ifself.persist[c.LIVES] == 0:return c.GAME_OVERelse:return c.LOAD_SCREENdefset_info_state(self):return c.TIME_OUTclassLevel(State):def__init__(self): State.__init__(self)defstartup(self, current_time, persist):self.game_info = persistself.persist = self.game_infoself.player = Noneself.overhead_info = Info(self.game_info, c.LEVEL)self.setup_background()defsetup_background(self):self.background = GFX['level_1']self.bg_rect = self.background.get_rect()self.background = pg.transform.scale(self.background, (int(self.bg_rect.width*c.BACKGROUND_MULTIPLER), int(self.bg_rect.height*c.BACKGROUND_MULTIPLER)))self.bg_rect = self.background.get_rect()self.level = pg.Surface((self.bg_rect.w, self.bg_rect.h)).convert()self.viewport = SCREEN.get_rect(bottom=self.bg_rect.bottom)defupdate(self, surface, keys, current_time):self.game_info[c.CURRENT_TIME] = self.current_time = current_timeself.overhead_info.update(self.game_info, self.player)ifself.overhead_info.time <= 0:self.update_game_info()self.done = Trueself.draw(surface)defupdate_game_info(self):self.persist[c.LIVES] -= 1ifself.persist[c.LIVES] == 0:self.next = c.GAME_OVER elif self.overhead_info.time == 0:self.next = c.TIME_OUTelse:self.next = c.LOAD_SCREENdefdraw(self, surface):self.level.blit(self.background, self.viewport, self.viewport) surface.blit(self.level, (0,0), self.viewport)self.overhead_info.draw(surface)classCharacter(pg.sprite.Sprite):def__init__(self, image): pg.sprite.Sprite.__init__(self)self.image = imageself.rect = self.image.get_rect()classInfo():def__init__(self, game_info, state):self.coin_total = game_info[c.COIN_TOTAL]self.total_lives = game_info[c.LIVES]self.state = stateself.game_info = game_infoself.create_font_image_dict()self.create_info_labels()self.create_state_labels()self.flashing_coin = FlashCoin(280, 53)defcreate_font_image_dict(self):self.image_dict = {} image_list = [] image_rect_list = [# 0 - 9 (3, 230, 7, 7), (12, 230, 7, 7), (19, 230, 7, 7), (27, 230, 7, 7), (35, 230, 7, 7), (43, 230, 7, 7), (51, 230, 7, 7), (59, 230, 7, 7), (67, 230, 7, 7), (75, 230, 7, 7), # A - Z (83, 230, 7, 7), (91, 230, 7, 7), (99, 230, 7, 7), (107, 230, 7, 7), (115, 230, 7, 7), (123, 230, 7, 7), (3, 238, 7, 7), (11, 238, 7, 7), (20, 238, 7, 7), (27, 238, 7, 7), (35, 238, 7, 7), (44, 238, 7, 7), (51, 238, 7, 7), (59, 238, 7, 7), (67, 238, 7, 7), (75, 238, 7, 7), (83, 238, 7, 7), (91, 238, 7, 7), (99, 238, 7, 7), (108, 238, 7, 7), (115, 238, 7, 7), (123, 238, 7, 7), (3, 246, 7, 7), (11, 246, 7, 7), (20, 246, 7, 7), (27, 246, 7, 7), (48, 246, 7, 7),# -* (68, 249, 6, 2), (75, 247, 6, 6)] character_string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ -*'for character, image_rect in zip(character_string, image_rect_list):self.image_dict[character] = get_image(GFX['text_images'], *image_rect, (92, 148, 252), 2.9)defcreate_info_labels(self):self.score_text = []self.coin_count_text = []self.mario_label = []self.world_label = []self.time_label = []self.stage_label = []self.create_label(self.score_text, '000000', 75, 55)self.create_label(self.coin_count_text, '*00', 300, 55)self.create_label(self.mario_label, 'MARIO', 75, 30)self.create_label(self.world_label, 'WORLD', 450, 30)self.create_label(self.time_label, 'TIME', 625, 30)self.create_label(self.stage_label, '1-1', 472, 55)self.info_labels = [self.score_text, self.coin_count_text, self.mario_label,self.world_label, self.time_label, self.stage_label]defcreate_state_labels(self):ifself.state == c.MAIN_MENU:self.create_main_menu_labels() elif self.state == c.LOAD_SCREEN:self.create_player_image()self.create_load_screen_labels() elif self.state == c.LEVEL:self.create_level_labels() elif self.state == c.GAME_OVER:self.create_game_over_labels() elif self.state == c.TIME_OUT:self.create_time_out_labels()defcreate_player_image(self):self.life_times_image = get_image(GFX['text_images'], 75, 247, 6, 6, (92, 148, 252), 2.9)self.life_times_rect = self.life_times_image.get_rect(center=(378, 295))self.life_total_label = []self.create_label(self.life_total_label, str(self.total_lives), 450, 285)ifself.game_info[c.PLAYER_NAME] == c.PLAYER_MARIO: rect = (178, 32, 12, 16)else: rect = (178, 128, 12, 16)self.player_image = get_image(GFX['mario_bros'], *rect, (92, 148, 252), 2.9)self.player_rect = self.player_image.get_rect(center=(320, 290))defcreate_main_menu_labels(self): mario_game = [] luigi_game = [] top = [] top_score = []self.create_label(mario_game, c.PLAYER1, 272, 360)self.create_label(luigi_game, c.PLAYER2, 272, 405)self.create_label(top, 'TOP - ', 290, 465)self.create_label(top_score, '000000', 400, 465)self.state_labels = [mario_game, luigi_game, top, top_score, *self.info_labels]defcreate_load_screen_labels(self): world_label = []self.stage_label2 = []self.create_label(world_label, 'WORLD', 280, 200)self.create_label(self.stage_label2, '1-1', 430, 200)self.state_labels = [world_label, self.stage_label2, *self.info_labels, self.life_total_label]defcreate_level_labels(self):self.time = c.GAME_TIME_OUTself.current_time = 0self.clock_time_label = []self.create_label(self.clock_time_label, str(self.time), 645, 55)self.state_labels = [*self.info_labels, self.clock_time_label]defcreate_game_over_labels(self): game_label = [] over_label = []self.create_label(game_label, 'GAME', 280, 300)self.create_label(over_label, 'OVER', 400, 300)self.state_labels = [game_label, over_label, *self.info_labels]defcreate_time_out_labels(self): timeout_label = []self.create_label(timeout_label, 'TIME OUT', 290, 310)self.state_labels = [timeout_label, *self.info_labels]defcreate_label(self, label_list, string, x, y):for letter instring: label_list.append(Character(self.image_dict[letter]))self.set_label_rects(label_list, x, y)defset_label_rects(self, label_list, x, y):for i, letter in enumerate(label_list): letter.rect.x = x + ((letter.rect.width + 3) * i) letter.rect.y = yif letter.image == self.image_dict['-']: letter.rect.y += 7 letter.rect.x += 2defupdate(self, level_info, level=None):self.level = levelself.handle_level_state(level_info)defhandle_level_state(self, level_info):self.score = level_info[c.SCORE]self.update_text(self.score_text, self.score)self.update_text(self.coin_count_text, level_info[c.COIN_TOTAL])self.update_text(self.stage_label, level_info[c.LEVEL_NUM])self.flashing_coin.update(level_info[c.CURRENT_TIME])ifself.state == c.LOAD_SCREEN:self.update_text(self.stage_label2, level_info[c.LEVEL_NUM])ifself.state == c.LEVEL:if (level_info[c.CURRENT_TIME] - self.current_time) > 1000:self.current_time = level_info[c.CURRENT_TIME]self.time -= 1self.update_text(self.clock_time_label, self.time, True)defupdate_text(self, text, score, reset=False):if reset and len(text) > len(str(score)): text.remove(text[0]) index = len(text) - 1for digit in reversed(str(score)): rect = text[index].rect text[index] = Character(self.image_dict[digit]) text[index].rect = rect index -= 1defdraw(self, surface):self.draw_info(surface, self.state_labels)ifself.state == c.LOAD_SCREEN: surface.blit(self.player_image, self.player_rect) surface.blit(self.life_times_image, self.life_times_rect) surface.blit(self.flashing_coin.image, self.flashing_coin.rect)defdraw_info(self, surface, label_list):for label inlabel_list:for letter inlabel: surface.blit(letter.image, letter.rect)classFlashCoin(pg.sprite.Sprite):def__init__(self, x, y): pg.sprite.Sprite.__init__(self)self.frame_index = 0self.frames = []self.load_frames()self.image = self.frames[self.frame_index]self.rect = self.image.get_rect()self.rect.x = xself.rect.y = yself.animation_timer = 0defload_frames(self): sheet = GFX[c.ITEM_SHEET] frame_rect_list = [(1, 160, 5, 8), (9, 160, 5, 8), (17, 160, 5, 8), (9, 160, 5, 8)]for frame_rect inframe_rect_list:self.frames.append(get_image(sheet, *frame_rect, c.BLACK, c.BRICK_SIZE_MULTIPLIER))defupdate(self, current_time): time_list = [375, 125, 125, 125]ifself.animation_timer == 0:self.animation_timer = current_time elif (current_time - self.animation_timer) > time_list[self.frame_index]:self.frame_index += 1ifself.frame_index == 4:self.frame_index = 0self.animation_timer = current_timeself.image = self.frames[self.frame_index]classControl():def__init__(self):self.screen = pg.display.get_surface()self.done = Falseself.clock = pg.time.Clock()self.fps = 60self.current_time = 0.0self.keys = pg.key.get_pressed()self.state_dict = {}self.state_name = Noneself.state = Nonedefsetup_states(self, state_dict, start_state):self.state_dict = state_dictself.state_name = start_stateself.state = self.state_dict[self.state_name]defupdate(self):self.current_time = pg.time.get_ticks()ifself.state.done:self.flip_state()self.state.update(self.screen, self.keys, self.current_time)defflip_state(self): previous, self.state_name = self.state_name, self.state.next persist = self.state.cleanup()self.state = self.state_dict[self.state_name]self.state.startup(self.current_time, persist)defevent_loop(self):for event in pg.event.get():if event.type == pg.QUIT:self.done = True elif event.type == pg.KEYDOWN:self.keys = pg.key.get_pressed() elif event.type == pg.KEYUP:self.keys = pg.key.get_pressed()defmain(self):whilenotself.done:self.event_loop()self.update() pg.display.update()self.clock.tick(self.fps)defget_image(sheet, x, y, width, height, colorkey, scale): image = pg.Surface([width, height]) rect = image.get_rect() image.blit(sheet, (0, 0), (x, y, width, height)) image.set_colorkey(colorkey) image = pg.transform.scale(image, (int(rect.width*scale), int(rect.height*scale)))return imagedefload_all_gfx(directory, colorkey=(255,0,255), accept=('.png', '.jpg', '.bmp', '.gif')): graphics = {}for pic in os.listdir(directory): name, ext = os.path.splitext(pic)if ext.lower() inaccept: img = pg.image.load(os.path.join(directory, pic))if img.get_alpha(): img = img.convert_alpha()else: img = img.convert() img.set_colorkey(colorkey) graphics[name] = imgreturn graphics# pygame related initial code pg.init()pg.event.set_allowed([pg.KEYDOWN, pg.KEYUP, pg.QUIT])pg.display.set_caption(c.ORIGINAL_CAPTION)SCREEN = pg.display.set_mode(c.SCREEN_SIZE)SCREEN_RECT = SCREEN.get_rect()GFX = load_all_gfx(os.path.join("resources","graphics"))if __name__=='__main__': game = Control() state_dict = {c.MAIN_MENU: Menu(), c.LOAD_SCREEN: LoadScreen(), c.LEVEL: Level(), c.GAME_OVER: GameOver(), c.TIME_OUT: TimeOut()} game.setup_states(state_dict, c.MAIN_MENU) game.main()

用到的圖片

圖片文件名要保存為對應的,不然代碼中會找不到,並且保存到state_demo.py所在目錄下的resources\graphics子目錄中。如果能上github,可以直接下載resources\graphics目錄中的圖片。

1、item_objects.png

2、level_1.png

3、mario_bros.png

4、text_images.png

5、tile_set.png

6、title_screen.png

編譯環境:python3.7 + pygame1.9。

聲明:本文為CSDN博主「marble_xu」的原創文章,原文連結:https://blog.csdn.net/marble_xu/article/details/96427946

【End】

相關焦點

  • 如何用Python實現超級瑪麗的界面和狀態機?
    原項目實現了超級瑪麗的第一個小關。在原項目的基礎上,遊戲使用json文件來保存每一個關卡的數據,將數據和代碼解耦合,目前已開發4個小關,後續關卡的擴展也很方便,只需要添加json文件和地圖圖片,支持新的怪物就行。遊戲還支持進入水管,到新的子地圖。
  • FPGA工程師:如何在FPGA中實現狀態機?
    本文引用地址:http://www.eepw.com.cn/article/201710/367294.htm  理論上講,狀態機可以分為Moore狀態機和Mealy狀態機兩大類。它們之間的差異僅在於如何生成狀態機的輸出。Moore狀態機的輸出僅為當前狀態的函數。典型的例子就是計數器。而Mealy狀態機的輸出是當前狀態和輸入的函數。典型的例子就是Richards控制器。
  • 界面超級乾淨、最好用的手機瀏覽器——夸克
    第一次打開它的時候,我就被它的乾淨深深吸引住了,沒有啟動廣告、首屏沒有任何新聞推送,只有中間一個搜索框和超級精簡的底部導航欄。下面我來詳細說明。其實logo和廣告界面在開發中完全可以省去,打開APP就可以直接出現看視頻的界面,但是這些商業應用必然會帶廣告,所以延遲了APP的打開時間。而夸克瀏覽器,沒有任何的拖延,打開直接就是瀏覽器界面,沒有任何阻斷用戶操作的元素,每次打開就能用,特別快。
  • 如何在Python中實現交互兩個數
    如何在Python中實現交互兩個數【原理】生活中我們要交互兩個杯中的水,小朋友們都知道我們需要再拿一個空杯子來倒換水,今天我們來探索一下python中如何實現交互兩個數【編程】首先我們需要輸入兩個數x=int(input("x="))
  • 1個GUI界面,隨機生成若干姓名並保存為Excel,python如何實現?
    這就是我們今天要利用Python實現的效果。如何實現首先,我們生成一個GUI界面,接收用戶對於隨機姓名的一些個性化定製。GUI界面講過很多了,PyQt5和tkinter等的介紹,之前也有很多內容,下面是一些有意思的案例:
  • 超級瑪麗吃的是什麼蘑菇
    上上期血腥瑪麗播出之後,小穎說,你這個瑪麗大家都不熟,我們80後還是最愛「超級瑪麗
  • 用STATECAD快速設計有限狀態機
    作者Email: zlyadvocate@163.com 數字系統通常劃分為信息處理單元和控制單元。信息單元主要進行信息的傳輸和運算, 而控制單元的主要任務是控制信息處理單元的微操作的順序。控制單元的實現方式有: 有限狀態機、控制寄存器和微代碼控制器等。
  • 如何用python在工作中「偷懶」?
    (升職加薪了別忘了回來發紅包哦~)那麼如何將這些統統實現呢?我將這些分為以下幾類,大家可以自行評估,各取所需:系統錄入自動化由於你經常需要不斷的將一些信息錄入系統,每一次錄入的過程中你可能需要不斷的點擊一些按鈕,面對這種情況,完全可以寫一個自動腳本,每次代替你來執行這些點擊的行為。
  • 資源|用Python和NumPy學習《深度學習》中的線性代數基礎
    本文系巴黎高等師範學院在讀博士 Hadrien Jean 的一篇基礎學習博客,其目的是幫助初學者/高級初學者基於深度學習和機器學習來掌握線性代數的概念。掌握這些技能可以提高你理解和應用各種數據科學算法的能力。
  • 解析UML狀態機視圖狀態和轉換
    本節和大家一起學習一下UML狀態機視圖的概念,這裡主要介紹一下UML狀態機視圖的狀態和轉換兩部分內容,相信通過本節的介紹你對UML狀態機視圖有一定的了解。下面是具體介紹。UML狀態機視圖狀態和轉換◆狀態狀態描述了一個類對象生命期中的一個時間段。
  • 如何繪畫狀態機來描述業務的變化
    然後我們不得不新增了一個中間狀態「已確認」,讓客服審核無誤後,再傳給網倉走發貨流程。再後來我們發現除了主業務-下單購物之外,還需要兼顧支線業務-退款退貨,此時不得不需要引入「退款中」狀態並且增加退款子狀態機、退貨子狀態機。以上這些我最開始是用文字描述,然後加上憑感覺畫的流程圖來表示,服務端RD很難理解,並且無法清楚所有狀態以及轉移條件,不得不多次反覆確認。
  • python快速求解不定積分和定積分
    python求解不定積分接下來,我們將介紹上述的不定積分的求解。首先導入sympy庫中的所有類和函數。python求解定積分定積分的求解和不定積分類似,唯一的區別在於,定積分說明了積分的上下限。50行代碼實現簡單的網站伺服器50行代碼實現網站伺服器 250行代碼實現網站伺服器 3Tomcat源碼分析之 doGet方法(一)Tomcat源碼分析之 doGet方法(二)
  • 用python識別驗證碼
    今天,我們就簡單的說下,怎麼用python來處理驗證碼。(注意:我所有的python相關的文章用的都是python3。)準備工作1、tesseract-ocr軟體Tesseract的OCR引擎最先由HP實驗室於1985年開始研發,至1995年時已經成為OCR業內最準確的三款識別引擎之一。然而,HP不久便決定放棄OCR業務,Tesseract也從此塵封。
  • 關於達爾文3號、超級瑪麗3號MAX,你想知道的都在這兒了!
    來源:她理財大家好,我是你們最愛的@保險小秘書 最近達爾文3號、超級瑪麗3號MAX定期方案要下架,財蜜們關注保險的熱情明顯升高。因為好的產品,好的方案要離我們遠去,誰都想抓住最後的機會。不過,財蜜們在投保過程中,可能會遇到這樣或那樣的疑問,今天小秘書就為大家整理一下。
  • 用Python實現職工信息管理系統
    想要實現一個職工管理系統首先我們看一下想要實現什麼功能最基礎的增刪改查肯定要實現的然後增加一下數據顯示、數據排序、數據統計功能下面直接上代碼1.增加職工數據```python# 接收用戶收入id = input('請輸入職工號')name = input('請輸入姓名')sex = input('請輸入性別')age = input('請輸入年齡')education = input('請輸入學歷')address = input
  • 如何以面向對象的思想設計有限狀態機
    狀態機的概念有限狀態機又稱有限狀態自動機,簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動作等行為的數學計算模型,用英文縮寫也被簡稱為 FSM。FSM 會響應「事件」而改變狀態,當事件發生時,就會調用一個函數,而且 FSM 會執行動作產生輸出,所執行的動作會因為當前系統的狀態和輸入的事件不同而不同。
  • 英國已經出現超級傳播者,什麼是超級傳播者?來聽聽瑪麗的故事
    但國外的情況明顯有些不樂觀,根據外國媒體的報導,目前韓國和英國都已經出現了超級傳播者,其中韓國的「31號患者」已經導致至少37個人被感染,而英國的史蒂夫·沃爾什至少也感染了11個人。如果,其他國家在防控疫情方面不採取積極主動政策的話,那麼,就會導致越來越多的超級傳播者出現。
  • 用Python 實現手機自動答題,這下百萬答題遊戲誰也玩不過我!
    但是有時候就會想,能不能實現手機自動答題呢,畢竟網絡上是充斥著很多問題的答案,自己手動搜題速度顯然來不及。答案是當然可以,今天我們就來用手機連接電腦,讓電腦自動搜索答案,省時省力省心。人們在生產和生活中,要處理大量的文字、報表和文本。為了減輕人們的勞動,提高處理效率,50年代開始探討一般文字識別方法,並研製出光學字符識別器。60年代出現了採用磁性墨水和特殊字體的實用機器。60年代後期,出現了多種字體和手寫體文字識別機,其識別精度和機器性能都基本上能滿足要求。如用於信函分揀的手寫體數字識別機和印刷體英文數字識別機。
  • 科悟學院介紹什麼是Python、python能做什麼?
    這是很多人想知道的,今天小編就給你揭秘一個行業——Python(AI人工智慧),有人會問python到底是什麼?能做什麼?下面科悟學院介紹什麼是python和python能做什麼,希望對於正在學習的你有所幫助。
  • 用Python 實現手機自動答題,這下百萬答題遊戲誰也玩不過我
    60年代出現了採用磁性墨水和特殊字體的實用機器。60年代後期,出現了多種字體和手寫體文字識別機,其識別精度和機器性能都基本上能滿足要求。如用於信函分揀的手寫體數字識別機和印刷體英文數字識別機。70年代主要研究文字識別的基本理論和研製高性能的文字識別機,並著重於漢字識別的研究。