# 乒乓球小遊戲(完整) ## 遊戲畫面 ### 開始畫面 ![](https://i.imgur.com/HMKokER.png) ### 準備1秒畫面 ![](https://i.imgur.com/DyG6VO8.png) ### 遊戲中畫面 ![](https://i.imgur.com/yc8WU6D.png) ### 結束畫面 ![](https://i.imgur.com/83g2uZf.png) ### 板子圖示 ![](https://i.imgur.com/WxBOgM1.png) ### 球圖示 ![](https://i.imgur.com/7olVxcY.png) ## 啟動pygame、生成視窗和標題 開頭要先init pygame再定義畫面的長寬跟標題 #pygame.init() #window = pygame.display.set_mode((寬, 長)) #pygame.display.set_caption(標題) ![](https://i.imgur.com/8mAQmF2.png) ```python= import time import pygame import random pygame.init() window_height = 600 window_width = 1000 window = pygame.display.set_mode((window_width, window_height)) pygame.display.set_caption("Ping Pong") ``` ## 因為等等要建立上下兩個桌球拍,因此要先建立桌球拍的class以增加方便性和可讀性 class內先設定長、寬、速度、圖示再定義建構子和需要的函式(移動, 顯示) 建構子內要設定座標跟方向 移動函式內要將桌球拍的座標加上方向乘上速度再顯示在window上,且要判斷是否撞牆 顯示函式內就只要將桌球拍顯示在window上 #image =pygame.image.load(圖片位置) #image = pygame.transform.scale(圖片, (寬, 長)) #image.convert() #window.blit(圖片, 座標) ![](https://i.imgur.com/nwLppBk.png) ```python= class Paddle: height = window_height // 20 width = window_height // 3 speed = 0.7 paddle_image = pygame.image.load("Resources/paddle.png") paddle_image = pygame.transform.scale(paddle_image, (width, height)) paddle_image.convert() def __init__(self, x, y, direction): self.x = x self.y = y self.direction = direction def move(self): if self.x <= 0 and self.direction == -1: self.direction = 0 self.x = 0 if self.x >= window_width - Paddle.width and self.direction == 1: self.direction = 0 self.x = window_width - Paddle.width self.x += self.direction * Paddle.speed window.blit(self.paddle_image, (self.x, self.y)) def update_position(self): window.blit(self.paddle_image, (self.x, self.y)) ``` ## 定義顏色的rgb, 宣告桌球拍 paddle = Paddle(x, y, direction) ![](https://i.imgur.com/nT4R1Aj.png) ```python= WHITE = (255, 255, 255) BLACK = (0, 0, 0) user = Paddle((window_width - Paddle.width) // 2, window_height - Paddle.height - 5, 0) user2 = Paddle((window_width - Paddle.width) // 2, 5, 0) ``` ## 定義球的半徑, x, y, 速度, 方向(x), 斜率(y/x), 圖示 半徑設為相對大小 x、y、方向、斜率設為隨機 速度設為固定大小 之後設定圖示 #random.randint(a, b) ![](https://i.imgur.com/mrTNRs8.png) ```python= ball_radius = Paddle.width // 5 ball_x = random.randint(2 * Paddle.width, window_width - 2 * Paddle.width) ball_y = random.randint(Paddle.height + 5, window_height - Paddle.height - 5 - ball_radius) ball_speed = 0.4 ball_direction = (random.randint(1, 2) - 1.5) * 2 ball_slope = random.randint(7, 15) * 0.1 * ball_direction ball_image = pygame.image.load("Resources/ball.png") ball_image = pygame.transform.scale(ball_image, (ball_radius, ball_radius)) ball_image.convert() ``` ## 因為下一步要顯示文字,因此先建立文字class 內容包含建構子、顯示函式 建構子內要設定文字內容、文字大小、顏色、座標 >先將字型跟輸入的數據丟進變數內 >之後再個別設定將文字框的正中、中上、中下設在宣告的座標(正中已經在圖片內,中上跟中下就如法炮製就好) > 顯示函式包含顯示在正中、中上、中下、左上(正常) #position_center = surface.get_rect() #position_center.center = position ![](https://i.imgur.com/Qd5SNeO.png) ```python= class Text: def __init__(self, text, size, color, position): self.font = pygame.font.SysFont('impact', size) self.surface = self.font.render(text, True, color) self.position = position self.position_center = self.surface.get_rect() self.position_center.center = position self.position_midtop = self.surface.get_rect() self.position_midtop.midtop = position self.position_midbottom = self.surface.get_rect() self.position_midbottom.midbottom = position def show_center(self): window.blit(self.surface, self.position_center) def show_midtop(self): window.blit(self.surface, self.position_midtop) def show_midbottom(self): window.blit(self.surface, self.position_midbottom) def show(self): window.blit(self.surface, self.position) ``` ## 透過左右鍵判斷pvp or pve 再回圈內要先填滿畫面為白色(覆蓋上一幀) 再定義並顯示文字,一個在中上顯示,一個在中下顯示 顯示完後更新畫面 然後判斷滑鼠左右鍵是否被按下 #window.fill(顏色) #text = Text(text, size, color, position) #if mouse_pressed[0~2]: #....statement #pygame.display.update()更新螢幕 ![](https://i.imgur.com/HMKokER.png) ![](https://i.imgur.com/O2gS5vQ.png) ```python= while True: window.fill(WHITE) text_begin_1 = Text("left-click:PVE", 100, BLACK, (window_width // 2, window_height // 2)) text_begin_2 = Text("right-click:PVP", 100, BLACK, (window_width // 2, window_height // 2)) text_begin_1.show_midbottom() text_begin_2.show_midtop() pygame.display.update() for event in pygame.event.get(): if event.type == pygame.QUIT: quit() mouse_pressed = pygame.mouse.get_pressed() if mouse_pressed[0]: PVP_bool = False break elif mouse_pressed[2]: PVP_bool = True break ``` ## 為了給玩家緩衝時間,要先顯示初始畫面1秒再開始 要先填滿畫面為白色(覆蓋上一幀) 再顯示桌球拍跟球 再更新螢幕 之後等待1秒 ![](https://i.imgur.com/DyG6VO8.png) ![](https://i.imgur.com/ZOSd7Zv.png) ```python= window.fill(WHITE) user.update_position() user2.update_position() window.blit(ball_image, (ball_x, ball_y)) pygame.display.update() time.sleep(1) ``` ## 初始化、計時開始, 進入迴圈, 判斷板子方向並移動板子 初始化球的落點(上方如果是電腦要由此判斷該怎麼移動桌球拍) 初始化判斷是否為球正在往上跑的變數(在判斷落點時需要) 計時開始 進入迴圈 要先填滿畫面為白色(覆蓋上一幀) 判斷是否離開遊戲及板子方向(左右、ad or 球落點) >沒按鍵被按著時使桌球拍不要動 >左右鍵控制下桌球拍 >ad鍵控制上桌球拍,如果是pvp的話 >如果是pve則先判斷球的落點 >>如果球往上跑則落點為現在的x加上(y離桌球拍的距離除斜率),如果超出邊界則修正 >>如果往下跑則因為不好寫,因此先設在正中間 >>之後桌球拍的方向就是看現在落點跟桌球拍的位置做決定 > 移動板子 #timer = time.time() 獲取現在時間 #pygame.K_RIGHT右鍵 #pygame.K_LEFT左鍵 ![](https://i.imgur.com/yc8WU6D.png) ![](https://i.imgur.com/gxvjeeO.png) ![](https://i.imgur.com/lV9ukAW.png) ```python= collision_x = window_width // 2 - ball_radius // 2 user2_turn = False begin = time.time() running = True while running: window.fill(WHITE) for event in pygame.event.get(): if event.type == pygame.QUIT: quit() elif event.type == pygame.KEYUP: user.direction = 0 user2.direction = 0 break keys_pressed = pygame.key.get_pressed() if keys_pressed[pygame.K_RIGHT]: user.direction = 1 if keys_pressed[pygame.K_LEFT]: user.direction = -1 if PVP_bool: if keys_pressed[pygame.K_a]: user2.direction = -1 if keys_pressed[pygame.K_d]: user2.direction = 1 else: if user2_turn: collision_x = ball_x - (ball_y - user2.y - Paddle.height) // ball_slope while True: if 0 <= collision_x <= window_width - ball_radius: break if collision_x >= window_width - ball_radius: collision_x = 2 * (window_width - ball_radius) - collision_x elif collision_x <= 0: collision_x = -1 * collision_x else: collision_x = window_width // 2 - ball_radius // 2 if user2.x + Paddle.width <= collision_x + ball_radius: user2.direction = 1 elif user2.x >= collision_x: user2.direction = -1 else: user2.direction = 0 user2.move() user.move() ``` ## 移動球, 判斷球是否撞牆、撞板子、碰底, 顯示球, 更新並顯示計時器, 更新螢幕 移動球(x加上方向乘上速度, y加上x變化量乘上斜率) 判斷球是否撞牆(左右) >撞了就改變球的斜率跟方向 > 判斷球是否撞板子 >撞了就改變球的斜率跟判斷球往上或往下的變數 > 判斷球是否會碰底 顯示球 更新並顯示計時器 更新螢幕 ![](https://i.imgur.com/yc8WU6D.png) ![](https://i.imgur.com/z9Y1Esr.png) ![](https://i.imgur.com/zkCC6c7.png) ```python= ball_x += ball_direction * ball_speed ball_y += ball_direction * ball_speed * ball_slope if ball_x <= 0 or ball_x >= window_width - ball_radius: ball_direction *= -1 ball_slope = -1 * ball_slope if ball_y + ball_radius >= user.y: if user.x <= ball_x + ball_radius // 2 <= user.x + Paddle.width: ball_slope = -1 * ball_slope user2_turn = True if ball_y <= user2.y + Paddle.height: if user2.x <= ball_x + ball_radius // 2 <= user2.x + Paddle.width: ball_slope = -1 * ball_slope user2_turn = False if ball_y + ball_radius > user.y + abs(ball_speed * ball_slope) or ball_y < user2.y + Paddle.height - abs( ball_speed * ball_slope): break window.blit(ball_image, (ball_x, ball_y)) time_taken = int(time.time() - begin) score = Text("Time " + str(time_taken) + "s", 50, BLACK, (0, window_height // 10)) score.show() pygame.display.update() ``` ## 結束計時, 顯示存活秒數直到滑鼠或鍵盤有動作後再等一秒在關 先計算過了多久 進入迴圈 (因為畫面不變因此不需要塗白螢幕) 顯示桌球拍跟球和分數(經過時間) 更新螢幕 如果接收到動作則等1秒後離開 #time.sleep(x)等待x秒 ![](https://i.imgur.com/83g2uZf.png) ![](https://i.imgur.com/iaPwVpT.png) ```python= time_taken = int(time.time() - begin) while True: user.update_position() user2.update_position() window.blit(ball_image, (ball_x, ball_y)) score = Text("Time " + str(time_taken) + "s", 100, BLACK, (window_width // 2, window_height // 2)) score.show_center() pygame.display.update() if len(pygame.event.get()) != 0: time.sleep(1) break ``` ## 完整程式碼 ```python= import time import pygame import random pygame.init() window_height = 600 window_width = 1000 window = pygame.display.set_mode((window_width, window_height)) pygame.display.set_caption("Ping Pong") class Paddle: height = window_height // 20 width = window_height // 3 speed = 0.7 paddle_image = pygame.image.load("Resources/paddle.png") paddle_image = pygame.transform.scale(paddle_image, (width, height)) paddle_image.convert() def __init__(self, x, y, direction): self.x = x self.y = y self.direction = direction def move(self): if self.x <= 0 and self.direction == -1: self.direction = 0 self.x = 0 if self.x >= window_width - Paddle.width and self.direction == 1: self.direction = 0 self.x = window_width - Paddle.width self.x += self.direction * Paddle.speed window.blit(self.paddle_image, (self.x, self.y)) def update_position(self): window.blit(self.paddle_image, (self.x, self.y)) WHITE = (255, 255, 255) BLACK = (0, 0, 0) user = Paddle((window_width - Paddle.width) // 2, window_height - Paddle.height - 5, 0) user2 = Paddle((window_width - Paddle.width) // 2, 5, 0) ball_radius = Paddle.width // 5 ball_x = random.randint(2 * Paddle.width, window_width - 2 * Paddle.width) ball_y = random.randint(Paddle.height + 5, window_height - Paddle.height - 5 - ball_radius) ball_speed = 0.4 ball_direction = (random.randint(1, 2) - 1.5) * 2 ball_slope = random.randint(7, 15) * 0.1 * ball_direction ball_image = pygame.image.load("Resources/ball.png") ball_image = pygame.transform.scale(ball_image, (ball_radius, ball_radius)) ball_image.convert() class Text: def __init__(self, text, size, color, position): self.font = pygame.font.SysFont('impact', size) self.surface = self.font.render(text, True, color) self.position = position self.position_center = self.surface.get_rect() self.position_center.center = position self.position_midtop = self.surface.get_rect() self.position_midtop.midtop = position self.position_midbottom = self.surface.get_rect() self.position_midbottom.midbottom = position def show_center(self): window.blit(self.surface, self.position_center) def show_midtop(self): window.blit(self.surface, self.position_midtop) def show_midbottom(self): window.blit(self.surface, self.position_midbottom) def show(self): window.blit(self.surface, self.position) while True: window.fill(WHITE) text_begin_1 = Text("left-click:PVE", 100, BLACK, (window_width // 2, window_height // 2)) text_begin_2 = Text("right-click:PVP", 100, BLACK, (window_width // 2, window_height // 2)) text_begin_1.show_midbottom() text_begin_2.show_midtop() pygame.display.update() for event in pygame.event.get(): if event.type == pygame.QUIT: quit() mouse_pressed = pygame.mouse.get_pressed() if mouse_pressed[0]: PVP_bool = False break elif mouse_pressed[2]: PVP_bool = True break window.fill(WHITE) user.update_position() user2.update_position() window.blit(ball_image, (ball_x, ball_y)) pygame.display.update() time.sleep(1) collision_x = window_width // 2 - ball_radius // 2 user2_turn = False begin = time.time() running = True while running: window.fill(WHITE) for event in pygame.event.get(): if event.type == pygame.QUIT: quit() elif event.type == pygame.KEYUP: user.direction = 0 user2.direction = 0 break keys_pressed = pygame.key.get_pressed() if keys_pressed[pygame.K_RIGHT]: user.direction = 1 if keys_pressed[pygame.K_LEFT]: user.direction = -1 if PVP_bool: if keys_pressed[pygame.K_a]: user2.direction = -1 if keys_pressed[pygame.K_d]: user2.direction = 1 else: if user2_turn: collision_x = ball_x - (ball_y - user2.y - Paddle.height) // ball_slope while True: if 0 <= collision_x <= window_width - ball_radius: break if collision_x >= window_width - ball_radius: collision_x = 2 * (window_width - ball_radius) - collision_x elif collision_x <= 0: collision_x = -1 * collision_x else: collision_x = window_width // 2 - ball_radius // 2 if user2.x + Paddle.width <= collision_x + ball_radius: user2.direction = 1 elif user2.x >= collision_x: user2.direction = -1 else: user2.direction = 0 user2.move() user.move() ball_x += ball_direction * ball_speed ball_y += ball_direction * ball_speed * ball_slope if ball_x <= 0 or ball_x >= window_width - ball_radius: ball_direction *= -1 ball_slope = -1 * ball_slope if ball_y + ball_radius >= user.y: if user.x <= ball_x + ball_radius // 2 <= user.x + Paddle.width: ball_slope = -1 * ball_slope user2_turn = True if ball_y <= user2.y + Paddle.height: if user2.x <= ball_x + ball_radius // 2 <= user2.x + Paddle.width: ball_slope = -1 * ball_slope user2_turn = False if ball_y + ball_radius > user.y + abs(ball_speed * ball_slope) or ball_y < user2.y + Paddle.height - abs( ball_speed * ball_slope): break window.blit(ball_image, (ball_x, ball_y)) time_taken = int(time.time() - begin) score = Text("Time " + str(time_taken) + "s", 50, BLACK, (0, window_height // 10)) score.show() pygame.display.update() time_taken = int(time.time() - begin) while True: user.update_position() user2.update_position() window.blit(ball_image, (ball_x, ball_y)) score = Text("Time " + str(time_taken) + "s", 100, BLACK, (window_width // 2, window_height // 2)) score.show_center() pygame.display.update() if len(pygame.event.get()) != 0: time.sleep(1) break ```