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

news2025/3/9 19:34:33

导语

"你是否想过用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. 待优化项

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

七、完整代码获取

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

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

# -*- 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的代码。方便不同环境的兄弟们运行:

# -*- 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协议,欢迎二次开发但需保留原作者信息"

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2312295.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ArcGIS操作:13 生成最小外接矩阵

应用情景&#xff1a;筛选出屋面是否能放下12*60m的长方形&#xff0c;作为起降场候选点&#xff08;一个不规则的形状内&#xff0c;判断是否能放下指定长宽的长方形&#xff09; 1、面积初步筛选 Area ≥ 720 ㎡ 面积计算见 2、打开 ArcToolbox → Data Management Tools …

manus对比ChatGPT-Deep reaserch进行研究类学术相关数据分析!谁更胜一筹?

没有账号&#xff0c;只能挑选一个案例 一夜之间被这个用全英文介绍全华班出品的新爆款国产AI产品的小胖刷频。白天还没有切换语言的选项&#xff0c;晚上就加上了。简单看了看团队够成&#xff0c;使用很长实践的Monica创始人也在其中。逐渐可以理解&#xff0c;重心放在海外产…

Python —— pow()函数

一、示例1 # 计算 2 的 3 次幂 result1 pow(2, 3) print(result1) # 输出: 8# 计算 2.5 的 2 次幂 result2 pow(2.5, 2) print(result2) # 输出: 6.25 二、示例2 # 计算 (2 ** 3) % 5 result3 pow(2, 3, 5) print(result3) # 输出: 3 三、示例3 ntxt input("请输…

开发环境搭建-完善登录功能

一.完善登录功能 我们修改密码为md5中的格式&#xff0c;那么就需要修改数据库中的密码和将从前端获取到的密码转化成md5格式&#xff0c;然后进行比对。比对成功则登录成功&#xff0c;失败则禁止登录。 二.md5格式 使用DigestUtils工具类进行md5加密&#xff0c;调用md4Dig…

STM32G431RBT6--(3)片上外设及其关系

前边我们已经了解了STM32的内核&#xff0c;下面我们来介绍片上外设&#xff0c;对于这些外设&#xff0c;如果我们弄清楚一个单片机都有什么外设&#xff0c;弄清他们之间的关系&#xff0c;对于应用单片机有很大的帮助&#xff0c;我们以G431为例&#xff1a; 这个表格描述了…

docker 安装达梦数据库(离线)

docker安装达梦数据库&#xff0c;官网上已经下载不了docker版本的了&#xff0c;下面可通过百度网盘下载 通过网盘分享的文件&#xff1a;dm8_20240715_x86_rh6_rq_single.tar.zip 链接: https://pan.baidu.com/s/1_ejcs_bRLZpICf69mPdK2w?pwdszj9 提取码: szj9 上传到服务…

AI 驱动的软件测试革命:从自动化到智能化的进阶之路

&#x1f680;引言&#xff1a;软件测试的智能化转型浪潮 在数字化转型加速的今天&#xff0c;软件产品的迭代速度与复杂度呈指数级增长。传统软件测试依赖人工编写用例、执行测试的模式&#xff0c;已难以应对快速交付与高质量要求的双重挑战。人工智能技术的突破为测试领域注…

六轴传感器ICM-20608

ICM-20608-G是一个6轴传感器芯片&#xff0c;由3轴陀螺仪和3轴加速度计组成。陀螺仪可编程的满量程有&#xff1a;250&#xff0c;500&#xff0c;1000和2000度/秒。加速度计可编程的满量程有&#xff1a;2g&#xff0c;4g&#xff0c;8g和16g。学习Linux之SPI之前&#xff0c;…

TikTok Shop欧洲市场爆发,欧洲TikTok 运营网络专线成运营关键

TikTok在欧洲的影响力还在持续攀升&#xff0c;日前&#xff0c;TikTok发布了最新的欧盟执行和使用数据报告&#xff0c;报告中提到&#xff1a; 2024年7~12月期间&#xff0c;TikTok在欧盟地区的月活用户达1.591亿&#xff0c;较上一报告期&#xff08;2024年10月发布&#xf…

专业工具,提供多种磁盘分区方案

随着时间的推移&#xff0c;电脑的磁盘空间往往会越来越紧张&#xff0c;许多人都经历过磁盘空间不足的困扰。虽然通过清理垃圾文件可以获得一定的改善&#xff0c;但随着文件和软件的增多&#xff0c;磁盘空间仍然可能显得捉襟见肘。在这种情况下&#xff0c;将其他磁盘的闲置…

你会测量管道液体流阻吗?西-魏斯巴赫方程(Darcy-Weisbach Equation)、Colebrook-White 方程帮你

测量管道液体流阻需要测量以下关键量&#xff1a; 需要测量的量 压力差&#xff08;ΔP&#xff09;&#xff1a;管道入口和出口之间的压力差&#xff0c;通常通过压力传感器或差压计测量。流量&#xff08;Q&#xff09;&#xff1a;流经管道的液体体积流量&#xff0c;可通…

SQL命令详解之多表查询(连接查询)

目录 1 简介 2 内连接查询 2.1 内连接语法 2.2 内连接练习 3 外连接查询 3.1 外连接语法 3.2 外连接练习 4 总结 1 简介 连接的本质就是把各个表中的记录都取出来依次匹配的组合加入结果集并返回给用户。我们把 t1 和 t2 两个表连接起来的过程如下图所示&#xff1a; …

导入 Excel 规则批量修改或删除 Excel 表格内容

我们前面介绍过按照规则批量修改 Excel 文档内容的操作&#xff0c;可以对大量的 Excel 文档按照一定的规则进行统一的修改&#xff0c;可以很好的解决我们批量修改 Excel 文档内容的需求。但是某些场景下&#xff0c;我们批量修改 Excel 文档内容的场景比较复杂&#xff0c;比…

字节码是由什么组成的?

Java字节码是Java程序编译后的中间产物&#xff0c;它是一种二进制格式的代码&#xff0c;可以在Java虚拟机&#xff08;JVM&#xff09;上运行。理解字节码的组成有助于我们更好地理解Java程序的运行机制。 1. Java字节码是什么&#xff1f; 定义 Java字节码是Java源代码经过…

【Spring Boot 应用开发】-04-02 自动配置-数据源-手撸一个最简持久层工具类

设计概述 有时候我们不需要太重的持久层&#xff0c;就像要一个最简的、轻量的持久层&#xff0c;便于维护和扩展&#xff0c;代码掌握在自己手里&#xff0c;那么我们可以基于springboot的自动配置&#xff0c;快速的构建一个自己的持久层轻量框架&#xff0c;不说废话&#…

学之思社区版考试系统docker-compose部署

参考 开源项目-Docker部署学之思管理系统 安装docker sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Bas…

mfc140u.dll是什么?当程序遭遇mfc140u.dll问题:快速恢复正常的秘诀

在使用Windows操作系统运行某些软件时&#xff0c;不少用户会遇到令人头疼的mfc140u.dll文件丢失错误。mfc140u.dll这个错误一旦出现&#xff0c;往往导致相关程序无法正常启动或运行&#xff0c;给用户带来诸多不便。这天的这篇文章将给大家分析mfc140u.dll是什么&#xff1f;…

基于大数据的电影情感分析推荐系统

【大数据】基于大数据的电影情感分析推荐系统&#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 本系统通过结合Flask框架、Vue前端、LSTM情感分析算法以及pyecharts和numpy、pandas等技术&#x…

手写一个Tomcat

Tomcat 是一个广泛使用的开源 Java Servlet 容器&#xff0c;用于运行 Java Web 应用程序。虽然 Tomcat 本身功能强大且复杂&#xff0c;但通过手写一个简易版的 Tomcat&#xff0c;我们可以更好地理解其核心工作原理。本文将带你一步步实现一个简易版的 Tomcat&#xff0c;并深…

清华北大推出的 DeepSeek 教程(附 PDF 下载链接)

清华和北大分别都有关于DeepSeek的分享文档&#xff0c;内容非常全面&#xff0c;从原理和具体的应用&#xff0c;大家可以认真看看。 北大 DeepSeek 系列 1&#xff1a;提示词工程和落地场景.pdf  北大 DeepSeek 系列 2&#xff1a;DeepSeek 与 AIGC 应用.pdf  清华 Deep…