内容目录
引言
Flappy Bird是一款经典的移动游戏,以其简单但极具挑战性的玩法吸引了全球玩家。本文将介绍如何使用Python和Pygame制作一个Flappy Bird克隆版游戏,并添加一些有趣的功能,如道具系统和在线排行榜。
准备工作
在开始之前,你需要安装以下工具和库:
Python
Pygame
Flask(用于后端)
SQLite(数据库)
requests(HTTP请求库)
可以使用以下命令安装这些库:
pip install pygame flask flask_sqlalchemy requests
图片资源
音效资源
项目结构
创建一个新的项目文件夹,并按照以下结构组织你的文件:
flappy_bird/
├── app.py
├── main.py
├── bird.py
├── pipe.py
├── power_up.py
├── settings.py
└── assets/
├── bird.png
├── pipe.png
├── background.png
├── power_up.png
└── invincibility_power_up.png
第一步:基础游戏实现
首先,我们需要定义游戏的基本元素:小鸟和管道。
bird.py
import pygame
from settings import BIRD_IMAGE_PATH, FLAP_STRENGTH, GRAVITY
class Bird(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.image.load(BIRD_IMAGE_PATH)
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.velocity = 0
def update(self):
self.velocity += GRAVITY
self.rect.y += self.velocity
def flap(self):
self.velocity = FLAP_STRENGTH
pipe.py
import pygame
import random
from settings import PIPE_IMAGE_PATH, PIPE_WIDTH, PIPE_HEIGHT, PIPE_GAP, PIPE_SPEED, SCREEN_WIDTH
class Pipe(pygame.sprite.Sprite):
def __init__(self, x, y, position):
super().__init__()
self.image = pygame.image.load(PIPE_IMAGE_PATH)
self.image = pygame.transform.scale(self.image, (PIPE_WIDTH, PIPE_HEIGHT))
self.rect = self.image.get_rect()
if position == 'top':
self.rect.bottomleft = (x, y - PIPE_GAP // 2)
else:
self.rect.topleft = (x, y + PIPE_GAP // 2)
self.passed = False # 添加passed属性
def update(self):
self.rect.x -= PIPE_SPEED
if self.rect.right < 0:
self.kill()
def create_pipe_pair(x):
y = random.randint(PIPE_GAP, SCREEN_WIDTH - PIPE_GAP)
top_pipe = Pipe(x, y, 'top')
bottom_pipe = Pipe(x, y, 'bottom')
return top_pipe, bottom_pipe
settings.py
# settings.py
# Screen dimensions
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 600
# Colors
WHITE = (255, 255, 255)
# Game settings
GRAVITY = 0.25
FLAP_STRENGTH = -5
PIPE_GAP = 150
PIPE_WIDTH = 50
PIPE_HEIGHT = 320
PIPE_SPEED = 3
# Paths to assets
BIRD_IMAGE_PATH = 'assets/bird.png'
PIPE_IMAGE_PATH = 'assets/pipe.png'
BACKGROUND_IMAGE_PATH = 'assets/background.png'
# Path to power-up image
POWER_UP_IMAGE_PATH = 'assets/power_up.png'
INVINCIBILITY_POWER_UP_IMAGE_PATH = 'assets/invincibility_power_up.png'
# Power-up settings
POWER_UP_WIDTH = 30
POWER_UP_HEIGHT = 30
POWER_UP_SPEED = 3
POWER_UP_DURATION = 5000 # in milliseconds
INVINCIBILITY_DURATION = 5000 # in milliseconds
# Power-up end message settings
POWER_UP_END_MESSAGE = "Power-up ended!"
POWER_UP_END_MESSAGE_DURATION = 2000 # in milliseconds
# Menu and end screen settings
MENU_BACKGROUND_COLOR = (0, 0, 0)
MENU_TEXT_COLOR = (255, 255, 255)
MENU_FONT_SIZE = 48
END_BACKGROUND_COLOR = (0, 0, 0)
END_TEXT_COLOR = (255, 0, 0)
END_FONT_SIZE = 48
第二步:添加道具系统
为了让游戏更有趣,我们可以添加道具系统。当小鸟获得道具时,可以触发不同的效果,例如无敌和加速。
power_up.py
import pygame
import random
from settings import POWER_UP_IMAGE_PATH, POWER_UP_WIDTH, POWER_UP_HEIGHT, POWER_UP_SPEED, SCREEN_WIDTH, SCREEN_HEIGHT, PIPE_GAP, INVINCIBILITY_POWER_UP_IMAGE_PATH
class PowerUp(pygame.sprite.Sprite):
def __init__(self, x, y, power_up_type='default'):
super().__init__()
if power_up_type == 'invincibility':
self.image = pygame.image.load(INVINCIBILITY_POWER_UP_IMAGE_PATH)
else:
self.image = pygame.image.load(POWER_UP_IMAGE_PATH)
self.image = pygame.transform.scale(self.image, (POWER_UP_WIDTH, POWER_UP_HEIGHT))
self.rect = self.image.get_rect()
self.rect.topleft = (x, y)
self.power_up_type = power_up_type
def update(self):
self.rect.x -= POWER_UP_SPEED
if self.rect.right < 0:
self.kill()
def create_power_up(pipe_y):
x = SCREEN_WIDTH
# 确保道具在管道之间生成
safe_zone_top = pipe_y + PIPE_GAP // 2
safe_zone_bottom = safe_zone_top + PIPE_GAP // 2 - POWER_UP_HEIGHT
y = random.randint(safe_zone_top, safe_zone_bottom)
power_up_type = random.choice(['default', 'invincibility']) # 随机选择道具类型
return PowerUp(x, y, power_up_type)
第三步:添加在线排行榜
为了增加竞争性,我们可以添加一个在线排行榜,用于存储和显示玩家的最高分。
app.py (Flask 后端)
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///scores.db'
db = SQLAlchemy(app)
class Score(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
score = db.Column(db.Integer, nullable=False)
@app.route('/add_score', methods=['POST'])
def add_score():
data = request.get_json()
new_score = Score(name=data['name'], score=data['score'])
db.session.add(new_score)
db.session.commit()
return jsonify({'message': 'Score added successfully!'})
@app.route('/get_scores', methods=['GET'])
def get_scores():
scores = Score.query.order_by(Score.score.desc()).limit(10).all()
scores_list = [{'name': score.name, 'score': score.score} for score in scores]
return jsonify(scores_list)
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)
main.py
import pygame
import sys
import random
import requests
import pygame_textinput
from settings import (
SCREEN_WIDTH, SCREEN_HEIGHT, WHITE, BACKGROUND_IMAGE_PATH, FONT_PATH, FONT_SIZE, FONT_COLOR,
MENU_BACKGROUND_COLOR, MENU_TEXT_COLOR, MENU_FONT_SIZE, END_BACKGROUND_COLOR, END_TEXT_COLOR, END_FONT_SIZE,
PASS_PIPE_SOUND_PATH, POWER_UP_DURATION, POWER_UP_END_MESSAGE, POWER_UP_END_MESSAGE_DURATION, INVINCIBILITY_DURATION
)
from bird import Bird
from pipe import create_pipe_pair
from power_up import create_power_up
API_URL = "http://127.0.0.1:5000" # 后端服务的URL
def show_menu(screen):
font = pygame.font.Font(FONT_PATH, MENU_FONT_SIZE)
title_text = font.render("Flappy Bird", True, MENU_TEXT_COLOR)
start_text = font.render("Press SPACE to Start", True, MENU_TEXT_COLOR)
while True:
screen.fill(MENU_BACKGROUND_COLOR)
screen.blit(title_text, (SCREEN_WIDTH // 2 - title_text.get_width() // 2, SCREEN_HEIGHT // 2 - title_text.get_height() // 2 - 50))
screen.blit(start_text, (SCREEN_WIDTH // 2 - start_text.get_width() // 2, SCREEN_HEIGHT // 2 - start_text.get_height() // 2 + 50))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
return
pygame.display.flip()
def show_end_screen(screen, score):
font = pygame.font.Font(FONT_PATH, END_FONT_SIZE)
small_font = pygame.font.Font(FONT_PATH, FONT_SIZE - 10)
end_text = font.render("Game Over", True, END_TEXT_COLOR)
score_text = small_font.render(f"Score: {score}", True, END_TEXT_COLOR)
restart_text = small_font.render("Press SPACE to Restart", True, END_TEXT_COLOR)
textinput = pygame_textinput.TextInputVisualizer()
textinput.font_color = END_TEXT_COLOR
textinput.font_size = FONT_SIZE
textinput.font_object = pygame.font.Font(FONT_PATH, FONT_SIZE)
add_score_screen = True
high_scores = get_high_scores()
while True:
screen.fill(END_BACKGROUND_COLOR)
screen.blit(end_text, (SCREEN_WIDTH // 2 - end_text.get_width() // 2, SCREEN_HEIGHT // 2 - end_text.get_height() // 2 - 150))
screen.blit(score_text, (SCREEN_WIDTH // 2 - score_text.get_width() // 2, SCREEN_HEIGHT // 2 - score_text.get_height() // 2 - 100))
if add_score_screen:
name_text = small_font.render("Enter your name:", True, END_TEXT_COLOR)
screen.blit(name_text, (SCREEN_WIDTH // 2 - name_text.get_width() // 2, SCREEN_HEIGHT // 2 - name_text.get_height() // 2 - 50))
events = pygame.event.get()
textinput.update(events)
screen.blit(textinput.surface, (SCREEN_WIDTH // 2 - textinput.surface.get_width() // 2, SCREEN_HEIGHT // 2))
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
add_score(score, textinput.value)
add_score_screen = False
else:
screen.blit(restart_text, (SCREEN_WIDTH // 2 - restart_text.get_width() // 2, SCREEN_HEIGHT // 2 - restart_text.get_height() // 2))
high_scores_text = small_font.render("High Scores", True, END_TEXT_COLOR)
screen.blit(high_scores_text, (SCREEN_WIDTH // 2 - high_scores_text.get_width() // 2, SCREEN_HEIGHT // 2 + 50))
for i, hs in enumerate(high_scores):
hs_text = small_font.render(f"{hs['name']}: {hs['score']}", True, END_TEXT_COLOR)
screen.blit(hs_text, (SCREEN_WIDTH // 2 - hs_text.get_width() // 2, SCREEN_HEIGHT // 2 + 80 + i * 30))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
return
pygame.display.flip()
def add_score(score, name="Player"):
data = {"name": name, "score": score}
try:
response = requests.post(f"{API_URL}/add_score", json=data)
if response.status_code == 200:
print("Score added successfully!")
else:
print("Failed to add score!")
except Exception as e:
print(f"Error: {e}")
def get_high_scores():
try:
response = requests.get(f"{API_URL}/get_scores")
if response.status_code == 200:
return response.json()
else:
print("Failed to fetch high scores!")
return []
except Exception as e:
print(f"Error: {e}")
return []
def main():
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
clock = pygame.time.Clock()
background = pygame.image.load(BACKGROUND_IMAGE_PATH)
# 加载音效
pass_pipe_sound = pygame.mixer.Sound(PASS_PIPE_SOUND_PATH)
while True:
show_menu(screen) # 显示菜单界面
bird = Bird(100, SCREEN_HEIGHT // 2)
bird_group = pygame.sprite.Group()
bird_group.add(bird)
pipe_group = pygame.sprite.Group()
power_up_group = pygame.sprite.Group()
pygame.time.set_timer(pygame.USEREVENT, 1500)
# 初始化分数
score = 0
font = pygame.font.Font(FONT_PATH, FONT_SIZE)
end_message_font = pygame.font.Font(FONT_PATH, 30)
# 初始化道具效果
power_up_active = False
power_up_start_time = 0
invincibility_active = False
invincibility_start_time = 0
show_power_up_end_message = False
power_up_end_message_start_time = 0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
bird.flap()
if event.type == pygame.USEREVENT:
pipes = create_pipe_pair(SCREEN_WIDTH)
pipe_group.add(pipes[0])
pipe_group.add(pipes[1])
if random.randint(1, 10) == 1: # 10%的概率生成道具
power_up = create_power_up(pipes[0].rect.bottom)
power_up_group.add(power_up)
# 检测鸟是否通过了管道
for pipe in pipe_group:
if pipe.rect.right < bird.rect.left and not getattr(pipe, 'passed', False):
score += 1
pipe.passed = True
pass_pipe_sound.play() # 播放音效
# 检测鸟是否获得了道具
for power_up in pygame.sprite.spritecollide(bird, power_up_group, True):
if power_up.power_up_type == 'invincibility':
invincibility_active = True
invincibility_start_time = pygame.time.get_ticks()
else:
power_up_active = True
power_up_start_time = pygame.time.get_ticks()
# 更新和绘制
screen.blit(background, (0, 0))
bird_group.update()
bird_group.draw(screen)
pipe_group.update()
pipe_group.draw(screen)
power_up_group.update()
power_up_group.draw(screen)
# 显示当前分数
score_text = font.render(f"Score: {score}", True, FONT_COLOR)
screen.blit(score_text, (10, 10))
# 处理道具效果
current_time = pygame.time.get_ticks()
if power_up_active:
if current_time - power_up_start_time > POWER_UP_DURATION:
power_up_active = False
show_power_up_end_message = True
power_up_end_message_start_time = current_time
if invincibility_active:
if current_time - invincibility_start_time > INVINCIBILITY_DURATION:
invincibility_active = False
show_power_up_end_message = True
power_up_end_message_start_time = current_time
# 显示道具效果结束提示
if show_power_up_end_message:
power_up_end_text = end_message_font.render(POWER_UP_END_MESSAGE, True, (255, 0, 0))
screen.blit(power_up_end_text, (SCREEN_WIDTH // 2 - power_up_end_text.get_width() // 2, SCREEN_HEIGHT // 2 - power_up_end_text.get_height() // 2))
if current_time - power_up_end_message_start_time > POWER_UP_END_MESSAGE_DURATION:
show_power_up_end_message = False
# 检测碰撞(如果没有激活无敌效果)
if not invincibility_active and not power_up_active:
if pygame.sprite.spritecollideany(bird, pipe_group) or bird.rect.top <= 0 or bird.rect.bottom >= SCREEN_HEIGHT:
running = False # 碰撞或飞出屏幕时结束游戏
pygame.display.flip()
clock.tick(60)
show_end_screen(screen, score) # 显示结束界面
if __name__ == "__main__":
main()
通过这些步骤,你将能够创建一个功能丰富且具有挑战性的Flappy Bird克隆版游戏。希望你在开发过程中享受乐趣,并能够通过这些示例和建议进一步扩展游戏功能!