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

一、游戏展示 & 核心功能
-
游戏截图/GIF动图
(建议添加游戏实际运行画面,展示双人操作、敌人生成、技能特效等) -
核心玩法特性
- 双人本地合作模式(WASD vs 方向键控制)
- 两种可选角色:四方向攻击 vs 对角线攻击
- 动态敌人系统:普通敌人 + 精英Boss
- 角色成长体系:经验值升级/属性强化
- 时间限制生存模式(5分钟倒计时)
二、技术实现亮点
-
技术栈
- 语言:Python 3.x
- 核心库:PyGame
- 开发周期:约10小时
-
关键技术点
- 精灵(Sprite)系统:玩家/敌人/子弹的统一管理 - 基于三角函数的子弹轨迹计算(8方向射击) - 敌人AI:自动追踪玩家 + 精英怪弹幕攻击 - 动态难度系统:敌人生成速度随玩家等级提升 - 经验球漂浮动画(正弦函数实现) - 多菜单系统:主菜单/角色选择/游戏内HUD
三、代码结构解析
python
# 代码模块示意图
├── Assets/ # 资源文件夹
│ ├── image/ # 游戏素材(角色/敌人/背景图)
├── main.py # 主程序入口
│ ├── 核心类:
│ │ - Player # 玩家角色(移动/攻击/成长)
│ │ - Enemy # 基础敌人AI
│ │ - EliteEnemy # 精英Boss(弹幕攻击)
│ │ - Bullet # 子弹物理系统
│ │ - Button # 交互式GUI按钮
│ ├── 游戏流程:
│ │ - main_menu() # 主菜单
│ │ - role_selection() # 角色选择
│ │ - game_loop() # 核心游戏循环
四、关键代码解读
-
角色控制系统
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", ...] -
精英敌人弹幕算法
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) -
动态难度机制
python
# 敌人生成速度随玩家等级提升 enemy_spawn_interval = 60 - (sum(p.level for p in players) * 2)
五、如何运行游戏
-
环境准备
bash
pip install pygame -
文件结构要求
project/ ├── main.py └── image/├── role1.png # 角色1素材├── enemy.png # 敌人素材└── ... -
启动命令
bash
python main.py
六、开发心得 & 优化方向
-
踩坑经验
- PyGame精灵组的碰撞检测优化
- 双人模式下的事件冲突处理
- 游戏节奏平衡性调试
-
待优化项
- 添加音效系统
- 实现网络联机功能
- 增加更多角色/技能树
- 开发关卡编辑器
七、完整代码获取
"关注+私信回复【生存游戏】获取完整代码包和素材资源!"
骗你的,代码就在这,复制就能用!
# -*- 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 surfGAME_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 = textself.color = BUTTON_COLORself.hover = Falsedef draw(self, surface):color = HOVER_COLOR if self.hover else BUTTON_COLORpygame.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_typeself.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 = 5self.health = 100self.exp = 0self.level = 1self.max_exp = 100self.kills = 0self.controls = controlsdef update(self, keys):if keys[self.controls['up']]: self.rect.y -= self.speedif keys[self.controls['down']]: self.rect.y += self.speedif keys[self.controls['left']]: self.rect.x -= self.speedif keys[self.controls['right']]: self.rect.x += self.speedself.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 = 8self.start_pos = (x, y)self.max_distance = 300self.penetration = 2dir_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.speedself.dy = dy_mult * self.speeddef update(self):self.rect.x += self.dxself.rect.y += self.dyif 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 = 2def update(self, targets):if not targets: returnnearest = 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.xdy = nearest.rect.y - self.rect.ydist = math.hypot(dx, dy)if dist != 0:self.rect.x += dx / dist * self.speedself.rect.y += dy / dist * self.speedclass 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.5self.health = 50self.max_health = 50self.shoot_timer = 0self.bullet_speed = 5def update(self, targets):if not targets: returnnearest = 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.xdy = nearest.rect.y - self.rect.ydist = math.hypot(dx, dy)if dist != 0:self.rect.x += dx / dist * self.speedself.rect.y += dy / dist * self.speedself.shoot_timer += 1if self.shoot_timer >= 60:self.shoot(nearest)self.shoot_timer = 0def 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 = dxself.dy = dyself.max_distance = 400self.start_pos = (x, y)def update(self):self.rect.x += self.dxself.rect.y += self.dyif 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 = 0def update(self):self.float_timer += 1self.rect.y += math.sin(self.float_timer * 0.1) * 0.5def 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 * 80pygame.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 * 150buttons.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 rolesdesc_y = 100for desc in descriptions:text = font.render(desc, True, WHITE)screen.blit(text, (WIDTH // 2 - text.get_width() // 2, desc_y))desc_y += 30for 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 = 0attack_timer = 0start_ticks = pygame.time.get_ticks()time_limit = 300running = Truewhile running:screen.blit(background, (0, 0))keys = pygame.key.get_pressed()elapsed_seconds = (pygame.time.get_ticks() - start_ticks) // 1000time_left = max(time_limit - elapsed_seconds, 0)if time_left <= 0:running = Falsefor event in pygame.event.get():if event.type == pygame.QUIT:running = Falseenemy_spawn_timer += 1if 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 = 0attack_timer += 1if 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 = 0for 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 -= 1for enemy in hits:if isinstance(enemy, EliteEnemy):enemy.health -= 2if 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 += 1if 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 += 1player.exp -= player.max_expplayer.max_exp = int(player.max_exp * 1.5)player.speed += 0.5if pygame.sprite.spritecollide(player, enemies, True):player.health -= 10bullet_hits = pygame.sprite.spritecollide(player, bullets, True)if bullet_hits:player.health -= 5alive_players = [p for p in players if p.health > 0]if not alive_players or time_left <= 0:running = Falseall_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 surfGAME_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 = textself.color = BUTTON_COLORself.hover = Falsedef draw(self, surface):color = HOVER_COLOR if self.hover else BUTTON_COLORpygame.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_typeself.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 = 5self.health = 100self.exp = 0self.level = 1self.max_exp = 100self.kills = 0self.controls = controlsdef update(self, keys):if keys[self.controls['up']]:self.rect.y -= self.speedif keys[self.controls['down']]:self.rect.y += self.speedif keys[self.controls['left']]:self.rect.x -= self.speedif keys[self.controls['right']]:self.rect.x += self.speedself.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 = 8self.start_pos = (x, y)self.max_distance = 300self.penetration = 2self.dx, self.dy = 0, 0dir_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.speedself.dy = dy_mult * self.speeddef update(self):self.rect.x += self.dxself.rect.y += self.dyif 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 = 2def update(self, targets):if not targets: returnnearest = 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.xdy = nearest.rect.y - self.rect.ydist = math.hypot(dx, dy)if dist != 0:self.rect.x += dx / dist * self.speedself.rect.y += dy / dist * self.speedclass 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 = 0def update(self):self.float_timer += 1self.rect.y += math.sin(self.float_timer * 0.1) * 0.5def 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*80pygame.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*150buttons.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+1if confirm_btn.rect.collidepoint(mouse_pos) and len(roles) == player_count:return rolesdesc_y = 100for desc in descriptions:text = font.render(desc, True, WHITE)screen.blit(text, (WIDTH//2 - text.get_width()//2, desc_y))desc_y += 30for 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+1roles = 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 = 0attack_timer = 0start_ticks = pygame.time.get_ticks()time_limit = 300running = Truewhile running:screen.blit(background, (0, 0))keys = pygame.key.get_pressed()elapsed_seconds = (pygame.time.get_ticks() - start_ticks) // 1000time_left = max(time_limit - elapsed_seconds, 0)if time_left <= 0:running = Falsefor event in pygame.event.get():if event.type == pygame.QUIT:running = Falseenemy_spawn_timer += 1if enemy_spawn_timer >= 60 - (sum(p.level for p in players)*2):enemy = Enemy()enemies.add(enemy)all_sprites.add(enemy)enemy_spawn_timer = 0attack_timer += 1if 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 = 0for 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 -= 1for 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 += 1if 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 += 1player.exp -= player.max_expplayer.max_exp = int(player.max_exp * 1.5)player.speed += 0.5alive_players = [p for p in players if p.health > 0]for player in alive_players:if pygame.sprite.spritecollide(player, enemies, True):player.health -= 10if len(alive_players) == 0 or time_left <= 0:running = Falseall_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)
互动引导
-
投票:
"如果让你添加新功能,你会选择?
A) 联机对战 B) 技能组合 C) BOSS战 D) 自定义角色" -
讨论:
"你在用Python开发游戏时遇到过哪些难题?欢迎评论区交流!"
版权声明
"本项目为开源学习作品,遵循MIT协议,欢迎二次开发但需保留原作者信息"
相关文章:
《用Python+PyGame开发双人生存游戏!源码解析+完整开发思路分享》
导语 "你是否想过用Python开发一款可玩性高的双人合作游戏?本文将分享如何从零开始实现一款类《吸血鬼幸存者》的生存射击游戏!包含完整源码解析、角色系统设计、敌人AI逻辑等核心技术点,文末提供完整代码包下载!" 哈…...
ArcGIS操作:13 生成最小外接矩阵
应用情景:筛选出屋面是否能放下12*60m的长方形,作为起降场候选点(一个不规则的形状内,判断是否能放下指定长宽的长方形) 1、面积初步筛选 Area ≥ 720 ㎡ 面积计算见 2、打开 ArcToolbox → Data Management Tools …...
manus对比ChatGPT-Deep reaserch进行研究类学术相关数据分析!谁更胜一筹?
没有账号,只能挑选一个案例 一夜之间被这个用全英文介绍全华班出品的新爆款国产AI产品的小胖刷频。白天还没有切换语言的选项,晚上就加上了。简单看了看团队够成,使用很长实践的Monica创始人也在其中。逐渐可以理解,重心放在海外产…...
android为第三方提供部分系统接口
文章目录 Settings - 亮灭屏Settings - 恢复出厂设置Settings - 数字锁屏/解锁Settings - 设置系统时间PackageInstaller - 安装/卸载第三方应用摘要:本文对系统模块进行改造,提供广播等形式的接口对外提供无法直接调用的系统级别接口,实现部分功能的集合。如果是广播形式,…...
在虚拟机上安装Hadoop
以下是在虚拟机上安装Hadoop的一般步骤: 准备工作 - 安装虚拟机软件:如VMware Workstation或VirtualBox等。 - 创建虚拟机:选择合适的操作系统镜像,如Ubuntu或CentOS等Linux发行版,为虚拟机分配足够的CPU、内存和磁盘…...
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中的格式,那么就需要修改数据库中的密码和将从前端获取到的密码转化成md5格式,然后进行比对。比对成功则登录成功,失败则禁止登录。 二.md5格式 使用DigestUtils工具类进行md5加密,调用md4Dig…...
el-table(elementui)表格合计行使用以及滚动条默认样式修改
一、el-table新增合计行以及el-table展示数据出现的问题 1. 使用合计行 el-table的属性show-summary设为true,即可在表格尾部展示合计行。默认情况下,第一列不展示数据,而显示合计二字,可以通过sum-text自己配置,其余…...
STM32G431RBT6--(3)片上外设及其关系
前边我们已经了解了STM32的内核,下面我们来介绍片上外设,对于这些外设,如果我们弄清楚一个单片机都有什么外设,弄清他们之间的关系,对于应用单片机有很大的帮助,我们以G431为例: 这个表格描述了…...
【CSS】Tailwind CSS 与传统 CSS:设计理念与使用场景对比
1. 开发方式 1.1 传统 CSS 手写 CSS:你需要手动编写 CSS 规则,定义类名、ID 或元素选择器,并为每个元素编写样式。 分离式开发:HTML 和 CSS 通常是分离的,HTML 中通过类名或 ID 引用 CSS 文件中的样式。 示例&#…...
#UVM# 关于 config_db 机制中省略 get 语句的条件
在 UVM 中,set 和 get 函数通常成对出现,但在某些特定情况下,可以省略 get 函数。我们在实际代码中,可以知道这一点,不至于漏出笑话。 以下是允许省略 get 函数的条件: 1. 满足特定条件 省略 get 函数的条件包括: 类必须注册到 UVM Factory:使用 uvm_component_util…...
docker 安装达梦数据库(离线)
docker安装达梦数据库,官网上已经下载不了docker版本的了,下面可通过百度网盘下载 通过网盘分享的文件:dm8_20240715_x86_rh6_rq_single.tar.zip 链接: https://pan.baidu.com/s/1_ejcs_bRLZpICf69mPdK2w?pwdszj9 提取码: szj9 上传到服务…...
AI 驱动的软件测试革命:从自动化到智能化的进阶之路
🚀引言:软件测试的智能化转型浪潮 在数字化转型加速的今天,软件产品的迭代速度与复杂度呈指数级增长。传统软件测试依赖人工编写用例、执行测试的模式,已难以应对快速交付与高质量要求的双重挑战。人工智能技术的突破为测试领域注…...
六轴传感器ICM-20608
ICM-20608-G是一个6轴传感器芯片,由3轴陀螺仪和3轴加速度计组成。陀螺仪可编程的满量程有:250,500,1000和2000度/秒。加速度计可编程的满量程有:2g,4g,8g和16g。学习Linux之SPI之前,…...
TikTok Shop欧洲市场爆发,欧洲TikTok 运营网络专线成运营关键
TikTok在欧洲的影响力还在持续攀升,日前,TikTok发布了最新的欧盟执行和使用数据报告,报告中提到: 2024年7~12月期间,TikTok在欧盟地区的月活用户达1.591亿,较上一报告期(2024年10月发布…...
专业工具,提供多种磁盘分区方案
随着时间的推移,电脑的磁盘空间往往会越来越紧张,许多人都经历过磁盘空间不足的困扰。虽然通过清理垃圾文件可以获得一定的改善,但随着文件和软件的增多,磁盘空间仍然可能显得捉襟见肘。在这种情况下,将其他磁盘的闲置…...
你会测量管道液体流阻吗?西-魏斯巴赫方程(Darcy-Weisbach Equation)、Colebrook-White 方程帮你
测量管道液体流阻需要测量以下关键量: 需要测量的量 压力差(ΔP):管道入口和出口之间的压力差,通常通过压力传感器或差压计测量。流量(Q):流经管道的液体体积流量,可通…...
SQL命令详解之多表查询(连接查询)
目录 1 简介 2 内连接查询 2.1 内连接语法 2.2 内连接练习 3 外连接查询 3.1 外连接语法 3.2 外连接练习 4 总结 1 简介 连接的本质就是把各个表中的记录都取出来依次匹配的组合加入结果集并返回给用户。我们把 t1 和 t2 两个表连接起来的过程如下图所示: …...
导入 Excel 规则批量修改或删除 Excel 表格内容
我们前面介绍过按照规则批量修改 Excel 文档内容的操作,可以对大量的 Excel 文档按照一定的规则进行统一的修改,可以很好的解决我们批量修改 Excel 文档内容的需求。但是某些场景下,我们批量修改 Excel 文档内容的场景比较复杂,比…...
字节码是由什么组成的?
Java字节码是Java程序编译后的中间产物,它是一种二进制格式的代码,可以在Java虚拟机(JVM)上运行。理解字节码的组成有助于我们更好地理解Java程序的运行机制。 1. Java字节码是什么? 定义 Java字节码是Java源代码经过…...
【Spring Boot 应用开发】-04-02 自动配置-数据源-手撸一个最简持久层工具类
设计概述 有时候我们不需要太重的持久层,就像要一个最简的、轻量的持久层,便于维护和扩展,代码掌握在自己手里,那么我们可以基于springboot的自动配置,快速的构建一个自己的持久层轻量框架,不说废话&#…...
学之思社区版考试系统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…...
1.11.信息系统的分类【DSS】
决策支持系统(DSS)技术解析 一、DSS核心定义 🚀 智能决策引擎 由三大智能模块构成的复合系统: #mermaid-svg-UdoVtlw3MrxynK6Q {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#me…...
mfc140u.dll是什么?当程序遭遇mfc140u.dll问题:快速恢复正常的秘诀
在使用Windows操作系统运行某些软件时,不少用户会遇到令人头疼的mfc140u.dll文件丢失错误。mfc140u.dll这个错误一旦出现,往往导致相关程序无法正常启动或运行,给用户带来诸多不便。这天的这篇文章将给大家分析mfc140u.dll是什么?…...
傅里叶变换:跨越时空的数学魔法
引言:从振动到信息——傅里叶的智慧 傅里叶变换(Fourier Transform)是数学与工程领域最具影响力的工具之一。它的核心思想是将复杂的信号分解为简单的正弦波和余弦波的叠加,从而揭示隐藏在数据背后的频率信息。自19世纪法国数学家…...
【YOLOv12改进trick】StarBlock引入YOLOv12,创新涨点优化,含创新点Python代码,方便发论文
🍋改进模块🍋:StarBlock 🍋解决问题🍋:采用StarBlock将输入数据映射到一个极高维的非线性特征空间,生成丰富的特征表示,使得模型在处理复杂数据时更加有效。 🍋改进优势🍋:简单粗暴的星型乘法涨点却很明显 🍋适用场景🍋:目标检测、语义分割、自然语言处理…...
基于大数据的电影情感分析推荐系统
【大数据】基于大数据的电影情感分析推荐系统(完整系统源码开发笔记详细部署教程)✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 本系统通过结合Flask框架、Vue前端、LSTM情感分析算法以及pyecharts和numpy、pandas等技术&#x…...
手写一个Tomcat
Tomcat 是一个广泛使用的开源 Java Servlet 容器,用于运行 Java Web 应用程序。虽然 Tomcat 本身功能强大且复杂,但通过手写一个简易版的 Tomcat,我们可以更好地理解其核心工作原理。本文将带你一步步实现一个简易版的 Tomcat,并深…...
清华北大推出的 DeepSeek 教程(附 PDF 下载链接)
清华和北大分别都有关于DeepSeek的分享文档,内容非常全面,从原理和具体的应用,大家可以认真看看。 北大 DeepSeek 系列 1:提示词工程和落地场景.pdf 北大 DeepSeek 系列 2:DeepSeek 与 AIGC 应用.pdf 清华 Deep…...
用CMake编译glfw进行OpenGL配置,在Visual Studio上运行
Visual Studio的下载 Visual Studio 2022 C 编程环境 GLFW库安装 GLFW官网地址 GLFW官网地址:https://www.glfw.org下载相应版本,如下图: CMake软件进行编译安装 下载CMake 下载的如果是源码包,需要下载CMake软件进行编译安装…...

