功能介紹
-
水平方向:設定X軸向右走的速度為大於0,向左走的速度為小於0
-
豎直方向:設定Y軸向下的速度為大於0,向上的速度為小於0
-
站立不動:水平方向速度為0,且豎直方向站在某個物體上。
-
向左或向右走:水平方向速度的絕對值大於0,且豎直方向站在某個物體上。
-
向上跳:豎直方向方向速度小於0,且上方沒有碰到某個物體,同時需要玩家按住jump鍵。
-
向下降落:豎直方向方向速度大於0或者玩家沒有按住jump鍵,且下方沒有碰到某個物體。
碰撞檢測
-
下方紅色的長方形物體就是地面(ground)
-
右邊的幾個紅色小方塊是階梯(step)
-
左邊空中的像牆一樣的是磚塊(brick)
-
帶問號的是箱子(box)

游戲代碼
游戲實現代碼的github鏈接 超級瑪麗
這邊是csdn的下載鏈接 超級瑪麗
代碼介紹
人物行走代碼
有一個單獨的人物類,在source\components\player.py 中,其中有個handle_state 函數,根據人物當前的狀態執行不同的函數。
為了簡潔下面所有函數中將不相關的代碼都省略掉了。
代碼已打包,學習python可加交流群:887934385 分享視頻教程
1 學習python可加交流群:887934385 分享視頻教程 2 def handle_state(self, keys, fire_group): 3 if self.state == c.STAND: 4 self.standing(keys, fire_group) 5 elif self.state == c.WALK: 6 self.walking(keys, fire_group) 7 elif self.state == c.JUMP: 8 self.jumping(keys, fire_group) 9 elif self.state == c.FALL: 10 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中值的含義如下:
1 keybinding = { 2 'action':pg.K_s, 3 'jump':pg.K_a, 4 'left':pg.K_LEFT, 5 'right':pg.K_RIGHT, 6 'down':pg.K_DOWN 7 }
- 先根據當前是否有按下 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。
1 def walking(self, keys, fire_group): 2 if keys[tools.keybinding['action']]: 3 self.max_x_vel = self.max_run_vel 4 self.x_accel = self.run_accel 5 else: 6 self.max_x_vel = self.max_walk_vel 7 self.x_accel = self.walk_accel 8 9 if keys[tools.keybinding['jump']]: 10 if self.allow_jump: 11 self.state = c.JUMP 12 if abs(self.x_vel) > 4: 13 self.y_vel = self.jump_vel - .5 14 else: 15 self.y_vel = self.jump_vel 16 17 if keys[tools.keybinding['left']]: 18 self.facing_right = False 19 if self.x_vel > 0: 20 self.frame_index = 5 21 self.x_accel = c.SMALL_TURNAROUND 22 23 self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel, True) 24 elif keys[tools.keybinding['right']]: 25 self.facing_right = True 26 if self.x_vel < 0: 27 self.frame_index = 5 28 self.x_accel = c.SMALL_TURNAROUND 29 30 self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel) 31 else: 32 if self.facing_right: 33 if self.x_vel > 0: 34 self.x_vel -= self.x_accel 35 else: 36 self.x_vel = 0 37 self.state = c.STAND 38 else: 39 if self.x_vel < 0: 40 self.x_vel += self.x_accel 41 else: 42 self.x_vel = 0 43 self.state = c.STAND 44 45 def cal_vel(self, vel, max_vel, accel, isNegative=False): 46 """ max_vel and accel must > 0 """ 47 if isNegative: 48 new_vel = vel * -1 49 else: 50 new_vel = vel 51 if (new_vel + accel) < max_vel: 52 new_vel += accel 53 else: 54 new_vel = max_vel 55 if isNegative: 56 return new_vel * -1 57 else: 58 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
1 JUMP_GRAVITY = .31 2 GRAVITY = 1.01 3 4 def jumping(self, keys, fire_group): 5 """ y_vel value: positive is down, negative is up """ 6 self.allow_jump = False 7 self.frame_index = 4 8 self.gravity = c.JUMP_GRAVITY 9 self.y_vel += self.gravity 10 11 if self.y_vel >= 0 and self.y_vel < self.max_y_vel: 12 self.gravity = c.GRAVITY 13 self.state = c.FALL 14 15 if keys[tools.keybinding['right']]: 16 self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel) 17 elif keys[tools.keybinding['left']]: 18 self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel, True) 19 20 if not keys[tools.keybinding['jump']]: 21 self.gravity = c.GRAVITY 22 self.state = c.FALL
standing函數和 falling 函數比較簡單,就省略了。
碰撞檢測代碼
-
人物的碰撞檢測代碼在 source\states\level.py 中的入口是
update_player_position函數 ,可以看到這邊分成水平方向和豎直方向:
- 根據人物的水平方向速度x_vel 更新人物的X軸位置,同時人物的X軸位置不能超出游戲地圖的X軸范圍,然后調用check_player_x_collisions函數進行水平方向的碰撞檢測。
- 根據人物的水平方向速度x_vel 更新人物的X軸位置,同時人物的X軸位置不能超出游戲地圖的X軸范圍,然后調用check_player_x_collisions函數進行水平方向的碰撞檢測。
- 根據人物的豎直方向速度y_vel 更新人物的Y軸位置,然后調用check_player_y_collisions函數進行豎直方向的碰撞檢測
1 def update_player_position(self): 2 self.player.rect.x += round(self.player.x_vel) 3 if self.player.rect.x < self.start_x: 4 self.player.rect.x = self.start_x 5 elif self.player.rect.right > self.end_x: 6 self.player.rect.right = self.end_x 7 self.check_player_x_collisions() 8 9 if not self.player.dead: 10 self.player.rect.y += round(self.player.y_vel) 11 self.check_player_y_collisions()
具體實現時將同一類物體放在一個pygame.sprite.Group類中
1 pygame.sprite.Group 2 A container class to hold and manage multiple Sprite objects. 3 Group(*sprites) -> Group
這樣每次調用pg.sprite.spritecollideany 函數就能判斷人物和這一類物體是否有碰撞。
1 pygame.sprite.spritecollideany() 2 Simple test if a sprite intersects anything in a group. 3 spritecollideany(sprite, group, collided = None) -> Sprite Collision with the returned sprite. 4 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軸位置。
1 def check_player_x_collisions(self): 2 ground_step_pipe = pg.sprite.spritecollideany(self.player, self.ground_step_pipe_group) 3 brick = pg.sprite.spritecollideany(self.player, self.brick_group) 4 box = pg.sprite.spritecollideany(self.player, self.box_group) 5 ... 6 if box: 7 self.adjust_player_for_x_collisions(box) 8 elif brick: 9 self.adjust_player_for_x_collisions(brick) 10 elif ground_step_pipe: 11 if (ground_step_pipe.name == c.MAP_PIPE and 12 ground_step_pipe.type == c.PIPE_TYPE_HORIZONTAL): 13 return 14 self.adjust_player_for_x_collisions(ground_step_pipe) 15 elif powerup: 16 ... 17 elif enemy: 18 ... 19 elif coin: 20 ...
adjust_player_for_x_collisions 函數先根據人物和碰撞物體的X軸相對位置,判斷人物在碰撞物體的左邊還是右邊,來調整人物的X軸位置,然后設置人物水平方向的速度為0。
1 def adjust_player_for_x_collisions(self, collider): 2 if collider.name == c.MAP_SLIDER: 3 return 4 5 if self.player.rect.x < collider.rect.x: 6 self.player.rect.right = collider.rect.left 7 else: 8 self.player.rect.left = collider.rect.right 9 self.player.x_vel = 0
學習python可加交流群:887934385 分享視頻教程
check_player_y_collisions 函數也是對不同group分別進行碰撞檢測,Y軸方向這3類group如果檢測到有碰撞時,會調用adjust_player_for_y_collisions 函數,來調整人物的Y軸位置。 最后調用check_is_falling函數判斷人物是否要設成 向下降落 的狀態。
1 def walking(self, keys, fire_group): 2 if keys[tools.keybinding['action']]: 3 self.max_x_vel = self.max_run_vel 4 self.x_accel = self.run_accel 5 else: 6 self.max_x_vel = self.max_walk_vel 7 self.x_accel = self.walk_accel 8 9 if keys[tools.keybinding['jump']]: 10 if self.allow_jump: 11 self.state = c.JUMP 12 if abs(self.x_vel) > 4: 13 self.y_vel = self.jump_vel - .5 14 else: 15 self.y_vel = self.jump_vel 16 17 if keys[tools.keybinding['left']]: 18 self.facing_right = False 19 if self.x_vel > 0: 20 self.frame_index = 5 21 self.x_accel = c.SMALL_TURNAROUND 22 23 self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel, True) 24 elif keys[tools.keybinding['right']]: 25 self.facing_right = True 26 if self.x_vel < 0: 27 self.frame_index = 5 28 self.x_accel = c.SMALL_TURNAROUND 29 30 self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel) 31 else: 32 if self.facing_right: 33 if self.x_vel > 0: 34 self.x_vel -= self.x_accel 35 else: 36 self.x_vel = 0 37 self.state = c.STAND 38 else: 39 if self.x_vel < 0: 40 self.x_vel += self.x_accel 41 else: 42 self.x_vel = 0 43 self.state = c.STAND 44 45 def cal_vel(self, vel, max_vel, accel, isNegative=False): 46 """ max_vel and accel must > 0 """ 47 if isNegative: 48 new_vel = vel * -1 49 else: 50 new_vel = vel 51 if (new_vel + accel) < max_vel: 52 new_vel += accel 53 else: 54 new_vel = max_vel 55 if isNegative: 56 return new_vel * -1 57 else: 58 return new_vel