功能介紹git
人物行走github
人物的行走速度這邊分紅水平方向(X軸)和豎直方向(Y軸),水平方向的速度要考慮加速度和摩擦力,豎直方向的速度要考慮重力加速度。web
水平方向:設定X軸向右走的速度爲大於0,向左走的速度爲小於0;shell
豎直方向:設定Y軸向下的速度爲大於0,向上的速度爲小於0。數組
遊戲中的人物有下面幾個主要的狀態:app
站立不動:水平方向速度爲0,且豎直方向站在某個物體上。ide
向左或向右走:水平方向速度的絕對值大於0,且豎直方向站在某個物體上。函數
向上跳:豎直方向方向速度小於0,且上方沒有碰到某個物體,同時須要玩家按住jump鍵。ui
向降低落:豎直方向方向速度大於0或者玩家沒有按住jump鍵,且下方沒有碰到某個物體。idea
向上跳和向降低落的狀態判斷可能一開始比較難理解,能夠看後面的具體實現,目的是若是玩家長按jump鍵時,可讓人物跳的更高。
上面的判斷是否站在某個物體上,或者是否碰到某個物體,就須要用到物體之間的碰撞檢測。
碰撞檢測
對於遊戲中出現的每同樣東西,好比磚塊,箱子,水管,地面,還有人物均可以當作是一個獨立的物體,因此每一個物體類都繼承了pygame的精靈類pg.sprite.Sprite,可使用精靈類提供的碰撞檢測函數來判斷。
設置source\constants.py 中的變量DEBUG值爲True,能夠看到圖1的遊戲截圖,好比最簡單的地面,能夠當作是一個長方形的物體。
下方紅色的長方形物體就是地面(ground);
右邊的幾個紅色小方塊是階梯(step);
左邊空中的像牆同樣的是磚塊(brick);
帶問號的是箱子(box)。
由於人物是站在地面上,且水平速度爲0,因此當前的人物狀態就是站立不動。
圖1
遊戲代碼
遊戲實現代碼的github連接:https://github.com/marblexu/PythonSuperMario.git
這邊是csdn的下載連接:https://download.csdn.net/download/marble_xu/11391533
代碼介紹
人物行走代碼
有一個單獨的人物類,在source\components\player.py 中,其中有個handle_state 函數,根據人物當前的狀態執行不一樣的函數。
爲了簡潔下面全部函數中將不相關的代碼都省略掉了。
def handle_state(self, keys, fire_group): if self.state == c.STAND: self.standing(keys, fire_group) elif self.state == c.WALK: self.walking(keys, fire_group) elif self.state == c.JUMP: self.jumping(keys, fire_group) elif self.state == c.FALL: self.falling(keys, fire_group)
人物的狀態就是上面說的4個狀態:
站立不動:c.STAND
向左或向右走:c.WALK
向上跳:c.JUMP
向降低落:c.FALL
人物類關於行走速度的成員變量先了解下:
水平方向相關的:
x_accel:水平方向的加速度,值大於0,不區別方向。
max_x_vel:水平方向的最大速度,值大於0,不區別方向。
x_vel:水平方向的速度,值大於0表示向右走,值小於0表示向左走。
初始值:max_run_vel和max_walk_vel 表示最大速度,run_accel和walk_accel表示加速度。
facing_right:值爲True表示當前是向右走,值爲False表示當前是向左走,這個是用來設置人物的圖像。
豎直方向相關的:
gravity:重力加速度,值大於0,表示方向向下。
jump_vel:起跳時豎直方向的初始速度,值小於0,表示方向向上。
y_vel:豎直方向的速度。
看下最複雜的 walking 函數,keys數組是當前按下的鍵盤輸入,tools.keybinding中值的含義以下:
keybinding = { 'action':pg.K_s, 'jump':pg.K_a, 'left':pg.K_LEFT, 'right':pg.K_RIGHT, 'down':pg.K_DOWN }
先根據當前是否有按下 keybinding[‘action’] 鍵來設置不一樣的最大水平方向速度和水平方向加速度。
若是有按下 keybinding[‘jump’] 鍵,則設置人物狀態爲c.JUMP,初始化豎直方向的速度。
若是有按下keybinding[‘left’]鍵,表示要向左走,若是 x_vel 大於0,表示以前是向右走的,因此設置一個轉身的加速度爲SMALL_TURNAROUND,而後調用cal_vel 函數根據以前的速度和加速度,計算出當前的速度。
若是有按下keybinding[‘right’]鍵,表示要向右走,和上面相似。
若是沒有按下keybinding[‘left’]鍵和keybinding[‘right’]鍵,就像有摩擦力的存在,則水平方向的速度會慢慢變成0,若是 x_vel 值爲0,則設置人物狀態爲c.STAND。
def walking(self, keys, fire_group): if keys[tools.keybinding['action']]: self.max_x_vel = self.max_run_vel self.x_accel = self.run_accel else: self.max_x_vel = self.max_walk_vel self.x_accel = self.walk_accel if keys[tools.keybinding['jump']]: if self.allow_jump: self.state = c.JUMP if abs(self.x_vel) > 4: self.y_vel = self.jump_vel - .5 else: self.y_vel = self.jump_vel if keys[tools.keybinding['left']]: self.facing_right = False if self.x_vel > 0: self.frame_index = 5 self.x_accel = c.SMALL_TURNAROUND self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel, True) elif keys[tools.keybinding['right']]: self.facing_right = True if self.x_vel < 0: self.frame_index = 5 self.x_accel = c.SMALL_TURNAROUND self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel) else: if self.facing_right: if self.x_vel > 0: self.x_vel -= self.x_accel else: self.x_vel = 0 self.state = c.STAND else: if self.x_vel < 0: self.x_vel += self.x_accel else: self.x_vel = 0 self.state = c.STAND def cal_vel(self, vel, max_vel, accel, isNegative=False): """ max_vel and accel must > 0 """ if isNegative: new_vel = vel * -1 else: new_vel = vel if (new_vel + accel) < max_vel: new_vel += accel else: new_vel = max_vel if isNegative: return new_vel * -1 else: return new_vel
再看下jumping 函數:
開始gravity 設爲 c.JUMP_GRAVITY,能夠看到JUMP_GRAVITY 比GRAVITY值小不少,若是玩家長按jump鍵時,可讓人物跳的更高。
若是豎直方向速度y_vel 大於0,表示方向向下,則設置人物狀態爲c.FALL。
若是按下 keybinding[‘left’]鍵或 keybinding[‘right’]鍵,則計算水平方向的速度。
若是沒有按 keybinding[‘jump’]鍵,則設置人物狀態爲c.FALL。
JUMP_GRAVITY = .31 GRAVITY = 1.01 def jumping(self, keys, fire_group): """ y_vel value: positive is down, negative is up """ self.allow_jump = False self.frame_index = 4 self.gravity = c.JUMP_GRAVITY self.y_vel += self.gravity if self.y_vel >= 0 and self.y_vel < self.max_y_vel: self.gravity = c.GRAVITY self.state = c.FALL if keys[tools.keybinding['right']]: self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel) elif keys[tools.keybinding['left']]: self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel, True) if not keys[tools.keybinding['jump']]: self.gravity = c.GRAVITY self.state = c.FALL
standing函數和 falling 函數比較簡單,就省略了。
碰撞檢測代碼
人物的碰撞檢測代碼在 source\states\level.py 中的入口是update_player_position函數 ,能夠看到這邊分紅水平方向和豎直方向:
根據人物的水平方向速度x_vel 更新人物的X軸位置,同時人物的X軸位置不能超出遊戲地圖的X軸範圍,而後調用check_player_x_collisions函數進行水平方向的碰撞檢測。
根據人物的豎直方向速度y_vel 更新人物的Y軸位置,而後調用check_player_y_collisions函數進行豎直方向的碰撞檢測。
def update_player_position(self): self.player.rect.x += round(self.player.x_vel) if self.player.rect.x < self.start_x: self.player.rect.x = self.start_x elif self.player.rect.right > self.end_x: self.player.rect.right = self.end_x self.check_player_x_collisions() if not self.player.dead: self.player.rect.y += round(self.player.y_vel) self.check_player_y_collisions()
具體實現時將同一類物體放在一個pygame.sprite.Group類中:
pygame.sprite.Group A container class to hold and manage multiple Sprite objects. Group(*sprites) -> Group
這樣每次調用pg.sprite.spritecollideany 函數就能判斷人物和這一類物體是否有碰撞。
pygame.sprite.spritecollideany() Simple test if a sprite intersects anything in a group. spritecollideany(sprite, group, collided = None) -> Sprite Collision with the returned sprite. spritecollideany(sprite, group, collided = None) -> None No collision
不一樣物體的group以下,另外敵人,金幣和蘑菇等物體的碰撞檢測先忽略。
ground_step_pipe_group:地面,階梯和水管的group。
brick_group:磚塊的group, 若是是金幣磚塊,從下面碰撞會獲取金幣。
box_group:箱子的group,從下面碰撞箱子能夠出現金幣,蘑菇,花等的獎勵。
由於不一樣種類group撞擊時,後續產生的結果會有區別,全部須要對每一類group分別進行碰撞檢測。
X軸方向上面3類group若是檢測到有碰撞時,會調用adjust_player_for_x_collisions 函數,來調整人物的X軸位置。
def check_player_x_collisions(self): ground_step_pipe = pg.sprite.spritecollideany(self.player, self.ground_step_pipe_group) brick = pg.sprite.spritecollideany(self.player, self.brick_group) box = pg.sprite.spritecollideany(self.player, self.box_group) ... if box: self.adjust_player_for_x_collisions(box) elif brick: self.adjust_player_for_x_collisions(brick) elif ground_step_pipe: if (ground_step_pipe.name == c.MAP_PIPE and ground_step_pipe.type == c.PIPE_TYPE_HORIZONTAL): return self.adjust_player_for_x_collisions(ground_step_pipe) elif powerup: ... elif enemy: ... elif coin: ...
adjust_player_for_x_collisions 函數先根據人物和碰撞物體的X軸相對位置,判斷人物在碰撞物體的左邊仍是右邊,來調整人物的X軸位置,而後設置人物水平方向的速度爲0。
def adjust_player_for_x_collisions(self, collider): if collider.name == c.MAP_SLIDER: return if self.player.rect.x < collider.rect.x: self.player.rect.right = collider.rect.left else: self.player.rect.left = collider.rect.right self.player.x_vel = 0
check_player_y_collisions 函數也是對不一樣group分別進行碰撞檢測,Y軸方向這3類group若是檢測到有碰撞時,會調用adjust_player_for_y_collisions 函數,來調整人物的Y軸位置。
最後調用check_is_falling函數判斷人物是否要設成向降低落的狀態。
def check_player_y_collisions(self): ground_step_pipe = pg.sprite.spritecollideany(self.player, self.ground_step_pipe_group) # decrease runtime delay: when player is on the ground, don't check brick and box if self.player.rect.bottom < c.GROUND_HEIGHT: brick = pg.sprite.spritecollideany(self.player, self.brick_group) box = pg.sprite.spritecollideany(self.player, self.box_group) brick, box = self.prevent_collision_conflict(brick, box) else: brick, box = False, False if box: self.adjust_player_for_y_collisions(box) elif brick: self.adjust_player_for_y_collisions(brick) elif ground_step_pipe: self.adjust_player_for_y_collisions(ground_step_pipe) elif enemy: ... elif shell: ... self.check_is_falling(self.player)
adjust_player_for_y_collisions 函數先根據人物和碰撞物體的Y軸相對位置,判斷人物在碰撞物體的下邊仍是上邊,來調整人物的Y軸位置:
若是人物在碰撞物體的下邊,則有一個反彈的效果,設置人物的豎直方向速度爲7,調整人物的Y軸位置,設置人物狀態爲c.FALL。若是碰撞物體爲磚塊或箱子,還要進行後續處理。
若是人物在碰撞物體的上邊,設置人物的豎直方向速度爲0,調整人物的Y軸位置,通常狀況下設置人物狀態爲c.WALK。
def adjust_player_for_y_collisions(self, sprite): if self.player.rect.top > sprite.rect.top: if sprite.name == c.MAP_BRICK: ... elif sprite.name == c.MAP_BOX: ... elif (sprite.name == c.MAP_PIPE and sprite.type == c.PIPE_TYPE_HORIZONTAL): return self.player.y_vel = 7 self.player.rect.top = sprite.rect.bottom self.player.state = c.FALL else: self.player.y_vel = 0 self.player.rect.bottom = sprite.rect.top if self.player.state == c.FLAGPOLE: self.player.state = c.WALK_AUTO elif self.player.state == c.END_OF_LEVEL_FALL: self.player.state = c.WALK_AUTO else: self.player.state = c.WALK
check_is_falling函數 判斷人物下方是否有物體,有個小技巧,就是先將人物的Y軸位置向下移動1,而後判斷和上面三類group是否有碰撞:
若是沒有碰撞,表示人物下方沒有物體,這時候若是人物狀態不是 c.JUMP 和一些特殊狀態,就設置人物狀態爲 c.FALL。
若是有碰撞,則不用管。
最後將人物的Y軸位置恢復(向上移動1)。
def check_is_falling(self, sprite): sprite.rect.y += 1 check_group = pg.sprite.Group(self.ground_step_pipe_group, self.brick_group, self.box_group) if pg.sprite.spritecollideany(sprite, check_group) is None: if (sprite.state == c.WALK_AUTO or sprite.state == c.END_OF_LEVEL_FALL): sprite.state = c.END_OF_LEVEL_FALL elif (sprite.state != c.JUMP and sprite.state != c.FLAGPOLE and not self.in_frozen_state()): sprite.state = c.FALL sprite.rect.y -= 1