python 超等玛丽代码实现(1):界面和状态机实现 游戏先容 状态机先容 状态机代码实现 完备代码 demo 代码 constants.py state_demo.py 用到的图片 编译情况
游戏先容
小时间的经典游戏,代码参考了github上的项目Mario-Level-1,利用pygame来实现,从中学习到了横版过关游戏实现中的一些处理惩罚方法。原项目实现了超等玛丽的第一个小关。
在原项目的底子上,游戏利用json文件来生存每一个关卡的数据,将数据和代码解耦合,现在已开发4个小关,后续关卡的扩展也很方便,只必要添加json文件和舆图图片,支持新的怪物就行。游戏还支持进入水管,到新的子舆图。
这篇文章是要先容鄙俚戏中的几个界面表现和界面之前怎样转换 ,以是特意写了一个demo步伐,完备的游戏代码在下面的github链接中下载。
游戏代码
游戏实今世码的github链接 超等玛丽
状态机先容
游戏中的状态机一样寻常都是有限状态机,简写为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 os
import pygame as pg
import constants as c
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( )
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 in os. 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] = img
return graphics
GFX = 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 用于记录时间。
class State ( ) :
def __init__ ( self) :
self. start_time = 0.0
self. current_time = 0.0
self. done = False
self. next = None
self. persist = { }
@abstractmethod
def startup ( self, current_time, persist) :
'''abstract method'''
def cleanup ( self) :
self. done = False
return self. persist
@abstractmethod
def update ( 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)。
class LoadScreen ( State) :
def __init__ ( self) :
State. __init__( self)
self. time_list = [ 2400 , 2600 , 2635 ]
def startup ( self, current_time, persist) :
self. start_time = current_time
self. persist = persist
self. game_info = self. persist
self. next = self. set_next_state( )
info_state = self. set_info_state( )
self. overhead_info = Info( self. game_info, info_state)
def set_next_state ( self) :
return c. LEVEL
def set_info_state ( self) :
return c. LOAD_SCREEN
def update ( 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 函数对于差别的状态,会初始化差别的信息。
class Info ( ) :
def __init__ ( self, game_info, state) :
self. coin_total = game_info[ c. COIN_TOTAL]
self. total_lives = game_info[ c. LIVES]
self. state = state
self. game_info = game_info
self. 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中,在反面创建笔墨时会用到。
def create_font_image_dict ( self) :
self. image_dict = { }
image_list = [ ]
image_rect_list = [
( 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 ) ,
( 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的四个极点的坐标。
def create_info_labels ( self) :
. . .
self. mario_label = [ ]
. . .
self. create_label( self. mario_label, 'MARIO' , 75 , 30 )
def create_label ( self, label_list, string, x, y) :
for letter in string:
label_list. append( Character( self. image_dict[ letter] ) )
self. set_label_rects( label_list, x, y)
def set_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 = y
if letter. image == self. image_dict[ '-' ] :
letter. rect. y += 7
letter. rect. x += 2
Control类
Control 是状态机类,main 函数是游戏的主循环,setup_states 函数设置游戏启动时运行的状态。
class Control ( ) :
def __init__ ( self) :
self. screen = pg. display. get_surface( )
self. done = False
self. clock = pg. time. Clock( )
self. fps = 60
self. current_time = 0.0
self. keys = pg. key. get_pressed( )
self. state_dict = { }
self. state_name = None
self. state = None
def setup_states ( self, state_dict, start_state) :
self. state_dict = state_dict
self. state_name = start_state
self. state = self. state_dict[ self. state_name]
def main ( self) :
while not self. done:
self. event_loop( )
self. update( )
pg. display. update( )
self. clock. tick( self. fps)
event_loop 函数负责监听输入(键盘输入和退出按钮),slef.keys 生存键盘输入。
update 函数会检测状态的done值,调用状态的更新函数。假如检测到当前状态竣事,就调用flip_state 函数举行旧状态的清算利用,并转换到下一个状态。
def update ( self) :
self. current_time = pg. time. get_ticks( )
if self. state. done:
self. flip_state( )
self. state. update( self. screen, self. keys, self. current_time)
def flip_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)
def event_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( )
完备代码
demo 代码
有两个文件constants.py 和 state_demo.py
constants.py 生存了全部的字符串界说和常量
constants.py
GAME_TIME_OUT 表现游戏的超时时间,这边为了demo演示,设成了5秒,实际是300秒。
SCREEN_HEIGHT = 600
SCREEN_WIDTH = 800
SCREEN_SIZE = ( SCREEN_WIDTH, SCREEN_HEIGHT)
ORIGINAL_CAPTION = "Super Mario Bros"
GAME_TIME_OUT = 5
BLACK = ( 0 , 0 , 0 )
SIZE_MULTIPLIER = 2.5
BRICK_SIZE_MULTIPLIER = 2.69
BACKGROUND_MULTIPLER = 2.679
GROUND_HEIGHT = SCREEN_HEIGHT - 62
MAIN_MENU = 'main menu'
LOAD_SCREEN = 'load screen'
TIME_OUT = 'time out'
GAME_OVER = 'game over'
LEVEL = 'level'
PLAYER1 = '1 PLAYER GAME'
PLAYER2 = '2 PLAYER GAME'
COIN_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 os
import pygame as pg
from abc import ABC, abstractmethod
import constants as c
class State ( ) :
def __init__ ( self) :
self. start_time = 0.0
self. current_time = 0.0
self. done = False
self. next = None
self. persist =