《用Python+PyGame开发双人生存游戏!源码解析+完整开发思路分享》

导语

"你是否想过用Python开发一款可玩性高的双人合作游戏?本文将分享如何从零开始实现一款类《吸血鬼幸存者》的生存射击游戏!包含完整源码解析、角色系统设计、敌人AI逻辑等核心技术点,文末提供完整代码包下载!"


哈哈,怪物可以换成同学 的qq头像

游戏内容如下:

一、游戏展示 & 核心功能
  1. 游戏截图/GIF动图

    (建议添加游戏实际运行画面,展示双人操作、敌人生成、技能特效等)

  2. 核心玩法特性

    • 双人本地合作模式(WASD vs 方向键控制)
    • 两种可选角色:四方向攻击 vs 对角线攻击
    • 动态敌人系统:普通敌人 + 精英Boss
    • 角色成长体系:经验值升级/属性强化
    • 时间限制生存模式(5分钟倒计时)

二、技术实现亮点
  1. 技术栈

    • 语言:Python 3.x
    • 核心库:PyGame
    • 开发周期:约10小时
  2. 关键技术点

    复制代码
    - 精灵(Sprite)系统:玩家/敌人/子弹的统一管理
    - 基于三角函数的子弹轨迹计算(8方向射击)
    - 敌人AI:自动追踪玩家 + 精英怪弹幕攻击
    - 动态难度系统:敌人生成速度随玩家等级提升
    - 经验球漂浮动画(正弦函数实现)
    - 多菜单系统:主菜单/角色选择/游戏内HUD

三、代码结构解析

python

复制代码
# 代码模块示意图
├── Assets/              # 资源文件夹
│   ├── image/           # 游戏素材(角色/敌人/背景图)
├── main.py              # 主程序入口
│   ├── 核心类:
│   │   - Player        # 玩家角色(移动/攻击/成长)
│   │   - Enemy         # 基础敌人AI
│   │   - EliteEnemy    # 精英Boss(弹幕攻击)
│   │   - Bullet        # 子弹物理系统
│   │   - Button        # 交互式GUI按钮
│   ├── 游戏流程:
│   │   - main_menu()       # 主菜单
│   │   - role_selection()  # 角色选择
│   │   - game_loop()       # 核心游戏循环

四、关键代码解读
  1. 角色控制系统

    python

    复制代码
    class Player(pygame.sprite.Sprite):
        def get_attack_directions(self):
            # 角色1:四方向射击 | 角色2:对角线射击
            return ["up", "down", "left", "right"] if self.role_type == 1 else ["up_left", "up_right", ...]
  2. 精英敌人弹幕算法

    复制代码

    python

    复制代码
    class EliteEnemy(Enemy):
        def shoot(self, target):
            # 45度间隔的8方向弹幕
            for angle in range(0, 360, 45):
                rad = math.radians(angle)
                bullet = EliteBullet(..., math.cos(rad)*speed, math.sin(rad)*speed)
  3. 动态难度机制

    复制代码

    python

    复制代码
    # 敌人生成速度随玩家等级提升
    enemy_spawn_interval = 60 - (sum(p.level for p in players) * 2)

五、如何运行游戏
  1. 环境准备

    复制代码

    bash

    复制代码
    pip install pygame
  2. 文件结构要求

    复制代码
    project/
    ├── main.py
    └── image/
        ├── role1.png    # 角色1素材
        ├── enemy.png    # 敌人素材
        └── ...
  3. 启动命令

    复制代码

    bash

    复制代码
    python main.py

六、开发心得 & 优化方向
  1. 踩坑经验

    • PyGame精灵组的碰撞检测优化
    • 双人模式下的事件冲突处理
    • 游戏节奏平衡性调试
  2. 待优化项

    • 添加音效系统
    • 实现网络联机功能
    • 增加更多角色/技能树
    • 开发关卡编辑器

七、完整代码获取

"关注+私信回复【生存游戏】获取完整代码包和素材资源!"

骗你的,代码就在这,复制就能用!

python 复制代码
# -*- coding: utf-8 -*-
import os
import pygame
import random
import math

# 初始化配置
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
IMAGE_DIR = os.path.join(BASE_DIR, 'image')

RESOURCES = {
    'role1': os.path.join(IMAGE_DIR, 'role1.png'),
    'role2': os.path.join(IMAGE_DIR, 'role2.png'),
    'enemy': os.path.join(IMAGE_DIR, 'enemy.png'),
    'elite': os.path.join(IMAGE_DIR, 'elite.png'),
    'bullet': os.path.join(IMAGE_DIR, 'bullet.png'),
    'exp_orb': os.path.join(IMAGE_DIR, 'exp_orb.png'),
    'background': os.path.join(IMAGE_DIR, 'background.png')
}

pygame.init()
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()

# 颜色定义
WHITE = (255, 255, 255)
GRAY = (100, 100, 100)
BUTTON_COLOR = (50, 150, 50)
HOVER_COLOR = (70, 170, 70)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)
ELITE_BULLET_COLOR = (255, 165, 0)

font = pygame.font.Font(None, 24)


def load_image(path, default_size=(30, 30)):
    try:
        image = pygame.image.load(path).convert_alpha()
        return pygame.transform.scale(image, default_size)
    except:
        surf = pygame.Surface(default_size)
        surf.fill(RED)
        return surf


GAME_IMAGES = {
    'role1': load_image(RESOURCES['role1']),
    'role2': load_image(RESOURCES['role2']),
    'enemy': load_image(RESOURCES['enemy'], (20, 20)),
    'elite': load_image(RESOURCES['elite'], (40, 40)),
    'bullet': load_image(RESOURCES['bullet'], (10, 10)),
    'exp_orb': load_image(RESOURCES['exp_orb'], (10, 10)),
    'background': load_image(RESOURCES['background'], (WIDTH, HEIGHT))
}


class Button:
    def __init__(self, text, x, y, w, h):
        self.rect = pygame.Rect(x, y, w, h)
        self.text = text
        self.color = BUTTON_COLOR
        self.hover = False

    def draw(self, surface):
        color = HOVER_COLOR if self.hover else BUTTON_COLOR
        pygame.draw.rect(surface, color, self.rect, border_radius=5)
        text_surf = font.render(self.text, True, WHITE)
        text_rect = text_surf.get_rect(center=self.rect.center)
        surface.blit(text_surf, text_rect)

    def check_hover(self, mouse_pos):
        self.hover = self.rect.collidepoint(mouse_pos)


class Player(pygame.sprite.Sprite):
    def __init__(self, controls, role_type, pos_offset=0, is_player2=False):
        super().__init__()
        self.role_type = role_type
        self.image = GAME_IMAGES['role2' if role_type == 2 else 'role1']
        self.rect = self.image.get_rect(center=(WIDTH // 2 + pos_offset, HEIGHT // 2))
        self.speed = 5
        self.health = 100
        self.exp = 0
        self.level = 1
        self.max_exp = 100
        self.kills = 0
        self.controls = controls

    def update(self, keys):
        if keys[self.controls['up']]: self.rect.y -= self.speed
        if keys[self.controls['down']]: self.rect.y += self.speed
        if keys[self.controls['left']]: self.rect.x -= self.speed
        if keys[self.controls['right']]: self.rect.x += self.speed
        self.rect.clamp_ip(screen.get_rect())

    def get_attack_directions(self):
        return ["up", "down", "left", "right"] if self.role_type == 1 else ["up_left", "up_right", "down_left",
                                                                            "down_right"]


class Bullet(pygame.sprite.Sprite):
    def __init__(self, x, y, direction):
        super().__init__()
        self.image = GAME_IMAGES['bullet']
        self.rect = self.image.get_rect(center=(x, y))
        self.speed = 8
        self.start_pos = (x, y)
        self.max_distance = 300
        self.penetration = 2

        dir_mapping = {
            "up": (0, -1), "down": (0, 1),
            "left": (-1, 0), "right": (1, 0),
            "up_left": (-math.sqrt(0.5), -math.sqrt(0.5)),
            "up_right": (math.sqrt(0.5), -math.sqrt(0.5)),
            "down_left": (-math.sqrt(0.5), math.sqrt(0.5)),
            "down_right": (math.sqrt(0.5), math.sqrt(0.5))
        }
        dx_mult, dy_mult = dir_mapping[direction]
        self.dx = dx_mult * self.speed
        self.dy = dy_mult * self.speed

    def update(self):
        self.rect.x += self.dx
        self.rect.y += self.dy
        if math.hypot(self.rect.x - self.start_pos[0], self.rect.y - self.start_pos[1]) > self.max_distance:
            self.kill()


class Enemy(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = GAME_IMAGES['enemy']
        self.rect = self.image.get_rect(
            center=(random.choice([-100, WIDTH + 100]), random.randint(0, HEIGHT))
        )
        self.speed = 2

    def update(self, targets):
        if not targets: return
        nearest = min(targets, key=lambda t: math.hypot(t.rect.x - self.rect.x, t.rect.y - self.rect.y))
        dx = nearest.rect.x - self.rect.x
        dy = nearest.rect.y - self.rect.y
        dist = math.hypot(dx, dy)
        if dist != 0:
            self.rect.x += dx / dist * self.speed
            self.rect.y += dy / dist * self.speed


class EliteEnemy(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = GAME_IMAGES['elite']
        self.rect = self.image.get_rect(
            center=(random.choice([-100, WIDTH + 100]), random.randint(0, HEIGHT))
        )
        self.speed = 1.5
        self.health = 50
        self.max_health = 50
        self.shoot_timer = 0
        self.bullet_speed = 5

    def update(self, targets):
        if not targets: return
        nearest = min(targets, key=lambda t: math.hypot(t.rect.x - self.rect.x, t.rect.y - self.rect.y))
        dx = nearest.rect.x - self.rect.x
        dy = nearest.rect.y - self.rect.y
        dist = math.hypot(dx, dy)
        if dist != 0:
            self.rect.x += dx / dist * self.speed
            self.rect.y += dy / dist * self.speed

        self.shoot_timer += 1
        if self.shoot_timer >= 60:
            self.shoot(nearest)
            self.shoot_timer = 0

    def shoot(self, target):
        for angle in range(0, 360, 45):
            rad = math.radians(angle)
            bullet = EliteBullet(
                self.rect.centerx,
                self.rect.centery,
                math.cos(rad) * self.bullet_speed,
                math.sin(rad) * self.bullet_speed
            )
            bullets.add(bullet)
            all_sprites.add(bullet)


class EliteBullet(pygame.sprite.Sprite):
    def __init__(self, x, y, dx, dy):
        super().__init__()
        self.image = pygame.Surface((15, 15))
        self.image.fill(ELITE_BULLET_COLOR)
        self.rect = self.image.get_rect(center=(x, y))
        self.dx = dx
        self.dy = dy
        self.max_distance = 400
        self.start_pos = (x, y)

    def update(self):
        self.rect.x += self.dx
        self.rect.y += self.dy
        if math.hypot(self.rect.x - self.start_pos[0], self.rect.y - self.start_pos[1]) > self.max_distance:
            self.kill()


class ExpOrb(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = GAME_IMAGES['exp_orb']
        self.rect = self.image.get_rect(center=(x, y))
        self.float_timer = 0

    def update(self):
        self.float_timer += 1
        self.rect.y += math.sin(self.float_timer * 0.1) * 0.5


def draw_hud(surface, players, time_left):
    time_text = font.render(f"Time: {time_left // 60:02}:{time_left % 60:02}", True, WHITE)
    surface.blit(time_text, (WIDTH // 2 - 60, 10))

    for i, player in enumerate(players):
        y_offset = 40 + i * 80
        pygame.draw.rect(surface, GRAY, (10, y_offset, 100, 10))
        health_width = int((player.health / 100.0) * 100)
        pygame.draw.rect(surface, RED, (10, y_offset, health_width, 10))

        pygame.draw.rect(surface, GRAY, (10, y_offset + 20, 100, 10))
        exp_width = int((player.exp / float(player.max_exp)) * 100)
        pygame.draw.rect(surface, YELLOW, (10, y_offset + 20, exp_width, 10))

        info_text = font.render(f"P{i + 1} Lv{player.level} K{player.kills}", True, WHITE)
        surface.blit(info_text, (10, y_offset + 40))


def role_selection_menu(player_count):
    roles = []
    buttons = []
    descriptions = [
        "Role 1: 4-Direction Attack",
        "Role 2: Diagonal Attack"
    ]

    for i in range(player_count):
        y_base = 150 + i * 150
        buttons.append([
            Button(f"Player{i + 1} Role1", WIDTH // 2 - 250, y_base, 200, 50),
            Button(f"Player{i + 1} Role2", WIDTH // 2 + 50, y_base, 200, 50)
        ])

    confirm_btn = Button("Start Game", WIDTH // 2 - 100, HEIGHT - 100, 200, 50)

    while True:
        screen.fill((30, 30, 30))
        mouse_pos = pygame.mouse.get_pos()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return []
            if event.type == pygame.MOUSEBUTTONDOWN:
                for i, pair in enumerate(buttons):
                    for j, btn in enumerate(pair):
                        if btn.rect.collidepoint(mouse_pos):
                            roles = roles[:i] + [j + 1] + roles[i + 1:] if len(roles) > i else roles + [j + 1]
                if confirm_btn.rect.collidepoint(mouse_pos) and len(roles) == player_count:
                    return roles

        desc_y = 100
        for desc in descriptions:
            text = font.render(desc, True, WHITE)
            screen.blit(text, (WIDTH // 2 - text.get_width() // 2, desc_y))
            desc_y += 30

        for i, pair in enumerate(buttons):
            for j, btn in enumerate(pair):
                btn.check_hover(mouse_pos)
                btn.draw(screen)
                if i < len(roles) and roles[i] == j + 1:
                    pygame.draw.rect(screen, YELLOW, btn.rect.inflate(10, 10), 3, border_radius=7)

        confirm_btn.check_hover(mouse_pos)
        confirm_btn.draw(screen)

        pygame.display.flip()
        clock.tick(30)


def main_menu():
    buttons = [
        Button("1 Player", WIDTH // 2 - 100, HEIGHT // 2 - 50, 200, 50),
        Button("2 Players", WIDTH // 2 - 100, HEIGHT // 2 + 20, 200, 50)
    ]

    while True:
        screen.fill((30, 30, 30))
        mouse_pos = pygame.mouse.get_pos()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return (0, [])
            if event.type == pygame.MOUSEBUTTONDOWN:
                for i, btn in enumerate(buttons):
                    if btn.rect.collidepoint(mouse_pos):
                        roles = role_selection_menu(i + 1)
                        if roles:
                            return (i + 1, roles)

        title = font.render("Vampire Survivors", True, WHITE)
        screen.blit(title, (WIDTH // 2 - title.get_width() // 2, 100))

        for btn in buttons:
            btn.check_hover(mouse_pos)
            btn.draw(screen)

        pygame.display.flip()
        clock.tick(30)


def game_loop(player_count, roles):
    background = GAME_IMAGES['background']
    controls = [
        {'up': pygame.K_w, 'down': pygame.K_s, 'left': pygame.K_a, 'right': pygame.K_d},
        {'up': pygame.K_UP, 'down': pygame.K_DOWN, 'left': pygame.K_LEFT, 'right': pygame.K_RIGHT}
    ]

    players = pygame.sprite.Group()
    for i in range(player_count):
        player = Player(
            controls=controls[i],
            role_type=roles[i],
            pos_offset=-50 + i * 100,
            is_player2=(i == 1)
        )
        players.add(player)

    all_sprites = pygame.sprite.Group(players)
    enemies = pygame.sprite.Group()
    bullets = pygame.sprite.Group()
    exp_orbs = pygame.sprite.Group()

    enemy_spawn_timer = 0
    attack_timer = 0
    start_ticks = pygame.time.get_ticks()
    time_limit = 300

    running = True
    while running:
        screen.blit(background, (0, 0))
        keys = pygame.key.get_pressed()

        elapsed_seconds = (pygame.time.get_ticks() - start_ticks) // 1000
        time_left = max(time_limit - elapsed_seconds, 0)
        if time_left <= 0:
            running = False

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        enemy_spawn_timer += 1
        if enemy_spawn_timer >= 60 - (sum(p.level for p in players) * 2):
            if random.random() < 0.1:
                enemy = EliteEnemy()
            else:
                enemy = Enemy()
            enemies.add(enemy)
            all_sprites.add(enemy)
            enemy_spawn_timer = 0

        attack_timer += 1
        if attack_timer >= 30:
            for player in players:
                for direction in player.get_attack_directions():
                    bullet = Bullet(player.rect.centerx, player.rect.centery, direction)
                    bullets.add(bullet)
                    all_sprites.add(bullet)
            attack_timer = 0

        for player in players:
            player.update(keys)
        enemies.update(players)
        bullets.update()
        exp_orbs.update()

        for bullet in bullets:
            hits = pygame.sprite.spritecollide(bullet, enemies, False)
            if hits:
                bullet.penetration -= 1
                for enemy in hits:
                    if isinstance(enemy, EliteEnemy):
                        enemy.health -= 2
                        if enemy.health <= 0:
                            enemy.kill()
                            for _ in range(5):
                                exp_orb = ExpOrb(enemy.rect.centerx, enemy.rect.centery)
                                exp_orbs.add(exp_orb)
                                all_sprites.add(exp_orb)
                    else:
                        enemy.kill()
                        exp_orb = ExpOrb(enemy.rect.centerx, enemy.rect.centery)
                        exp_orbs.add(exp_orb)
                        all_sprites.add(exp_orb)

                    for player in players:
                        if math.hypot(player.rect.x - enemy.rect.x, player.rect.y - enemy.rect.y) < 100:
                            player.kills += 1
                if bullet.penetration <= 0:
                    bullet.kill()

        for player in players:
            hits = pygame.sprite.spritecollide(player, exp_orbs, True)
            if hits:
                player.exp += 10 * len(hits)
                if player.exp >= player.max_exp:
                    player.level += 1
                    player.exp -= player.max_exp
                    player.max_exp = int(player.max_exp * 1.5)
                    player.speed += 0.5

            if pygame.sprite.spritecollide(player, enemies, True):
                player.health -= 10

            bullet_hits = pygame.sprite.spritecollide(player, bullets, True)
            if bullet_hits:
                player.health -= 5

        alive_players = [p for p in players if p.health > 0]
        if not alive_players or time_left <= 0:
            running = False

        all_sprites.draw(screen)
        draw_hud(screen, players, time_left)
        pygame.display.flip()
        clock.tick(30)

    pygame.quit()


if __name__ == "__main__":
    player_count, roles = main_menu()
    if player_count > 0:
        game_loop(player_count, roles)

下面是python2的代码。方便不同环境的兄弟们运行:

python 复制代码
# -*- coding: utf-8 -*-
import os
import pygame
import random
import math
from datetime import datetime

# 初始化路径
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
IMAGE_DIR = os.path.join(BASE_DIR, 'image')

RESOURCES = {
    'role1': os.path.join(IMAGE_DIR, 'role1.png'),
    'role2': os.path.join(IMAGE_DIR, 'role2.png'),
    'enemy': os.path.join(IMAGE_DIR, 'enemy.png'),
    'bullet': os.path.join(IMAGE_DIR, 'bullet.png'),
    'exp_orb': os.path.join(IMAGE_DIR, 'exp_orb.png'),
    'background': os.path.join(IMAGE_DIR, 'background.png')
}

pygame.init()
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()

# 颜色定义
WHITE = (255, 255, 255)
GRAY = (100, 100, 100)
BUTTON_COLOR = (50, 150, 50)
HOVER_COLOR = (70, 170, 70)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)

font = pygame.font.Font(None, 24)

def load_image(path, default_size=(30, 30)):
    try:
        image = pygame.image.load(path).convert_alpha()
        return pygame.transform.scale(image, default_size)
    except:
        surf = pygame.Surface(default_size)
        surf.fill(RED)
        return surf

GAME_IMAGES = {
    'role1': load_image(RESOURCES['role1']),
    'role2': load_image(RESOURCES['role2']),
    'enemy': load_image(RESOURCES['enemy'], (20, 20)),
    'bullet': load_image(RESOURCES['bullet'], (10, 10)),
    'exp_orb': load_image(RESOURCES['exp_orb'], (10, 10)),
    'background': load_image(RESOURCES['background'], (WIDTH, HEIGHT))
}

class Button:
    def __init__(self, text, x, y, w, h):
        self.rect = pygame.Rect(x, y, w, h)
        self.text = text
        self.color = BUTTON_COLOR
        self.hover = False

    def draw(self, surface):
        color = HOVER_COLOR if self.hover else BUTTON_COLOR
        pygame.draw.rect(surface, color, self.rect, border_radius=5)
        text_surf = font.render(self.text, True, WHITE)
        text_rect = text_surf.get_rect(center=self.rect.center)
        surface.blit(text_surf, text_rect)

    def check_hover(self, mouse_pos):
        self.hover = self.rect.collidepoint(mouse_pos)

class Player(pygame.sprite.Sprite):
    def __init__(self, controls, role_type, pos_offset=0, is_player2=False):
        pygame.sprite.Sprite.__init__(self)
        self.role_type = role_type
        self.image = GAME_IMAGES['role2' if role_type == 2 else 'role1']
        self.rect = self.image.get_rect(center=(WIDTH//2 + pos_offset, HEIGHT//2))
        self.speed = 5
        self.health = 100
        self.exp = 0
        self.level = 1
        self.max_exp = 100
        self.kills = 0
        self.controls = controls

    def update(self, keys):
        if keys[self.controls['up']]:
            self.rect.y -= self.speed
        if keys[self.controls['down']]:
            self.rect.y += self.speed
        if keys[self.controls['left']]:
            self.rect.x -= self.speed
        if keys[self.controls['right']]:
            self.rect.x += self.speed
        self.rect.clamp_ip(screen.get_rect())

    def get_attack_directions(self):
        if self.role_type == 1:
            return ["up", "down", "left", "right"]
        else:
            return ["up_left", "up_right", "down_left", "down_right"]

class Bullet(pygame.sprite.Sprite):
    def __init__(self, x, y, direction):
        pygame.sprite.Sprite.__init__(self)
        self.image = GAME_IMAGES['bullet']
        self.rect = self.image.get_rect(center=(x, y))
        self.speed = 8
        self.start_pos = (x, y)
        self.max_distance = 300
        self.penetration = 2

        self.dx, self.dy = 0, 0
        dir_mapping = {
            "up": (0, -1),
            "down": (0, 1),
            "left": (-1, 0),
            "right": (1, 0),
            "up_left": (-math.sqrt(0.5), -math.sqrt(0.5)),
            "up_right": (math.sqrt(0.5), -math.sqrt(0.5)),
            "down_left": (-math.sqrt(0.5), math.sqrt(0.5)),
            "down_right": (math.sqrt(0.5), math.sqrt(0.5))
        }
        dx_mult, dy_mult = dir_mapping[direction]
        self.dx = dx_mult * self.speed
        self.dy = dy_mult * self.speed

    def update(self):
        self.rect.x += self.dx
        self.rect.y += self.dy
        if math.hypot(self.rect.x - self.start_pos[0], self.rect.y - self.start_pos[1]) > self.max_distance:
            self.kill()

class Enemy(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = GAME_IMAGES['enemy']
        self.rect = self.image.get_rect(
            center=(random.choice([-100, WIDTH+100]), random.randint(0, HEIGHT))
        )
        self.speed = 2

    def update(self, targets):
        if not targets: return
        nearest = min(targets, key=lambda t: math.hypot(t.rect.x-self.rect.x, t.rect.y-self.rect.y))
        dx = nearest.rect.x - self.rect.x
        dy = nearest.rect.y - self.rect.y
        dist = math.hypot(dx, dy)
        if dist != 0:
            self.rect.x += dx / dist * self.speed
            self.rect.y += dy / dist * self.speed

class ExpOrb(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.image = GAME_IMAGES['exp_orb']
        self.rect = self.image.get_rect(center=(x, y))
        self.float_timer = 0

    def update(self):
        self.float_timer += 1
        self.rect.y += math.sin(self.float_timer * 0.1) * 0.5

def draw_hud(surface, players, time_left):
    time_text = font.render("Time: {:02}:{:02}".format(time_left//60, time_left%60), True, WHITE)
    surface.blit(time_text, (WIDTH//2 - 60, 10))
    
    for i, player in enumerate(players):
        y_offset = 40 + i*80
        pygame.draw.rect(surface, GRAY, (10, y_offset, 100, 10))
        health_width = int((player.health / 100.0) * 100)
        pygame.draw.rect(surface, RED, (10, y_offset, health_width, 10))
        
        pygame.draw.rect(surface, GRAY, (10, y_offset+20, 100, 10))
        exp_width = int((player.exp / float(player.max_exp)) * 100)
        pygame.draw.rect(surface, YELLOW, (10, y_offset+20, exp_width, 10))
        
        info_text = font.render("P{} Lv{} K{}".format(i+1, player.level, player.kills), True, WHITE)
        surface.blit(info_text, (10, y_offset+40))

def role_selection_menu(player_count):
    roles = []
    buttons = []
    descriptions = [
        "Role 1: 4-Direction Attack",
        "Role 2: Diagonal Attack"
    ]
    
    for i in range(player_count):
        y_base = 150 + i*150
        buttons.append([
            Button("Player{} Role1".format(i+1), WIDTH//2-250, y_base, 200, 50),
            Button("Player{} Role2".format(i+1), WIDTH//2+50, y_base, 200, 50)
        ])
    
    confirm_btn = Button("Start Game", WIDTH//2-100, HEIGHT-100, 200, 50)
    
    while True:
        screen.fill((30, 30, 30))
        mouse_pos = pygame.mouse.get_pos()
        
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return []
            if event.type == pygame.MOUSEBUTTONDOWN:
                for i, pair in enumerate(buttons):
                    for j, btn in enumerate(pair):
                        if btn.rect.collidepoint(mouse_pos):
                            if len(roles) <= i:
                                roles.append(j+1)
                            else:
                                roles[i] = j+1
                if confirm_btn.rect.collidepoint(mouse_pos) and len(roles) == player_count:
                    return roles
        
        desc_y = 100
        for desc in descriptions:
            text = font.render(desc, True, WHITE)
            screen.blit(text, (WIDTH//2 - text.get_width()//2, desc_y))
            desc_y += 30
        
        for i, pair in enumerate(buttons):
            for j, btn in enumerate(pair):
                btn.check_hover(mouse_pos)
                btn.draw(screen)
                if i < len(roles) and roles[i] == j+1:
                    pygame.draw.rect(screen, YELLOW, btn.rect.inflate(10,10), 3, border_radius=7)
        
        confirm_btn.check_hover(mouse_pos)
        confirm_btn.draw(screen)
        
        pygame.display.flip()
        clock.tick(30)

def main_menu():
    buttons = [
        Button("1 Player", WIDTH//2-100, HEIGHT//2-50, 200, 50),
        Button("2 Players", WIDTH//2-100, HEIGHT//2+20, 200, 50)
    ]
    
    while True:
        screen.fill((30, 30, 30))
        mouse_pos = pygame.mouse.get_pos()
        
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return (0, [])
            if event.type == pygame.MOUSEBUTTONDOWN:
                for i, btn in enumerate(buttons):
                    if btn.rect.collidepoint(mouse_pos):
                        selected = i+1
                        roles = role_selection_menu(selected)
                        if roles:
                            return (selected, roles)
        
        title = font.render("Vampire Survivors", True, WHITE)
        screen.blit(title, (WIDTH//2 - title.get_width()//2, 100))
        
        for btn in buttons:
            btn.check_hover(mouse_pos)
            btn.draw(screen)
        
        pygame.display.flip()
        clock.tick(30)

def game_loop(player_count, roles):
    background = GAME_IMAGES['background']
    controls = [
        {'up': pygame.K_w, 'down': pygame.K_s, 'left': pygame.K_a, 'right': pygame.K_d},
        {'up': pygame.K_UP, 'down': pygame.K_DOWN, 'left': pygame.K_LEFT, 'right': pygame.K_RIGHT}
    ]
    
    players = pygame.sprite.Group()
    for i in range(player_count):
        player = Player(
            controls=controls[i],
            role_type=roles[i],
            pos_offset=-50 + i*100,
            is_player2=(i==1)
        )
        players.add(player)
    
    all_sprites = pygame.sprite.Group(players)
    enemies = pygame.sprite.Group()
    bullets = pygame.sprite.Group()
    exp_orbs = pygame.sprite.Group()
    
    enemy_spawn_timer = 0
    attack_timer = 0
    start_ticks = pygame.time.get_ticks()
    time_limit = 300

    running = True
    while running:
        screen.blit(background, (0, 0))
        keys = pygame.key.get_pressed()
        
        elapsed_seconds = (pygame.time.get_ticks() - start_ticks) // 1000
        time_left = max(time_limit - elapsed_seconds, 0)
        if time_left <= 0:
            running = False

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        enemy_spawn_timer += 1
        if enemy_spawn_timer >= 60 - (sum(p.level for p in players)*2):
            enemy = Enemy()
            enemies.add(enemy)
            all_sprites.add(enemy)
            enemy_spawn_timer = 0

        attack_timer += 1
        if attack_timer >= 30:
            for player in players:
                directions = player.get_attack_directions()
                for direction in directions:
                    bullet = Bullet(player.rect.centerx, player.rect.centery, direction)
                    bullets.add(bullet)
                    all_sprites.add(bullet)
            attack_timer = 0

        for player in players:
            player.update(keys)
        enemies.update(players)
        bullets.update()
        exp_orbs.update()

        for bullet in bullets:
            hits = pygame.sprite.spritecollide(bullet, enemies, False)
            if hits:
                bullet.penetration -= 1
                for enemy in hits:
                    enemy.kill()
                    exp_orb = ExpOrb(enemy.rect.centerx, enemy.rect.centery)
                    exp_orbs.add(exp_orb)
                    all_sprites.add(exp_orb)
                    for player in players:
                        if math.hypot(player.rect.x-enemy.rect.x, player.rect.y-enemy.rect.y) < 100:
                            player.kills += 1
                if bullet.penetration <= 0:
                    bullet.kill()

        for player in players:
            hits = pygame.sprite.spritecollide(player, exp_orbs, True)
            if hits:
                player.exp += 10 * len(hits)
                if player.exp >= player.max_exp:
                    player.level += 1
                    player.exp -= player.max_exp
                    player.max_exp = int(player.max_exp * 1.5)
                    player.speed += 0.5

        alive_players = [p for p in players if p.health > 0]
        for player in alive_players:
            if pygame.sprite.spritecollide(player, enemies, True):
                player.health -= 10

        if len(alive_players) == 0 or time_left <= 0:
            running = False

        all_sprites.draw(screen)
        draw_hud(screen, players, time_left)
        pygame.display.flip()
        clock.tick(30)

    pygame.quit()

if __name__ == "__main__":
    player_count, roles = main_menu()
    if player_count > 0:
        game_loop(player_count, roles) 

互动引导

  1. 投票:

    "如果让你添加新功能,你会选择?

    A) 联机对战 B) 技能组合 C) BOSS战 D) 自定义角色"

  2. 讨论:

    "你在用Python开发游戏时遇到过哪些难题?欢迎评论区交流!"


版权声明

"本项目为开源学习作品,遵循MIT协议,欢迎二次开发但需保留原作者信息"

相关推荐
DeepVisionary7 小时前
从 Qwen3.8-27B 把原生多模态、原生 FP8、桌面级 Agent 三件事一次集齐,看开源大模型的“工业化拐点“
python·自动化
固定资产管理系统软件8 小时前
商务局RFID固定资产管理系统的应用价值与落地要点解析
大数据·python
TheBestRucy8 小时前
Python 九阳神功:从零筑基到线程飞升
服务器·开发语言·网络·人工智能·python
lancyu9 小时前
关于多轮对话机器人的上下文Token优化和解决方案
人工智能·python·深度学习·机器学习·chatgpt·机器人·prompt
草莓橙子碗9 小时前
Claude 使用指南
开发语言·python
工具分享9 小时前
陪跑跟品爆单AI选品助手,高效跟品会赔本吗?
人工智能·python
COOLMO研究AI10 小时前
Python 如何实现 AI API 的提示词(Prompt)版本管理与热更新
人工智能·python·prompt
老郑聊AI业财智造10 小时前
Transformer 技术架构与源码分析
人工智能·python·深度学习·语言模型·架构·transformer·软件工程
程序员三藏10 小时前
自动化测试用例编写详解
自动化测试·软件测试·python·功能测试·测试工具·职场和发展·测试用例
230万光年的思念10 小时前
分子动力学模拟参考文献
python