主要是参照网上这个代码进行改编的:Python 球球大作战简化版https://www.cnblogs.com/wenqiangblog/p/9469909.html。该代码实现了以下功能:点击一下鼠标,在鼠标的地方会出现一个随机大小的球,然后球随机移动,大球遇到小球,会吃掉小球。可玩性不高。
通过改编后,实现了这些功能:
- 根据鼠标的位置,自身球在地图中的位置会发生移动;
- 自身球固定在屏幕中心,其他球是会根据自身球相对移动。
- 当自身球显示大小不再增大时(因为屏幕大小有限,实际值还在增大),其他球显示的大小会根据自身球而等比例减小(实际值仍保持);
- 吃地上的豆豆可以慢慢发育起来;
- 大球吃小球, 会按一定比例吸收到自身的体积;
- 按住鼠标左键,球会加速;
- 敌方的球有一定几率改变运动轨迹;
- 敌方的球有一定几率会突然加速;
- 敌方的球有一定几率会增加自身球的体积的一部分(为了增加后期挑战性);
- 所有的球会按一定的比例减小体积,而且体积越大,减少越多。
可以考虑的更多功能:
- 分身;
- 吐球;
- 扎刺;
- 参数科学化;
- 设计和美工;
- 联网对战等。
以下是改编的球球大作战代码。可以复制到 Python 中试玩,也可以改参数后增加挑战性。前提是需要安装 pygame 库。安装方法是:win+R, 输入 cmd 回车, 输入 pip install pygame 后回车。需要说明的是:这里的代码没有做额外的优化,可能有些代码内容比较相似或重复,仅供参考。
这是版本一代码的游戏界面截图:

版本一(纯手工编程):
"""
This code is supported by the website: https://www.guanjihuan.com
The newest version of this code is on the web page: https://www.guanjihuan.com/archives/703
"""
import pygame
import random
import math
import numpy as np
# 参数
screen_width = 1500 # 屏幕宽度
screen_height = 900 # 屏幕高度
map_width = screen_width*4 # 地图的大小
map_height = screen_height*4 # 地图的大小
number_enemy = map_width*map_height/500000 # 敌人的数量
number_dots = map_width * map_height / 50 # 点点的数量
max_show_size = 100 # 球显示的最大半径(屏幕有限,球再增大时,改变的地图比例尺寸)
my_value = 1000 # 我的初始值
enemy_value_low = 500 # 敌人的初始值(最低)
enemy_value_high = 1500 # 敌人的初始值(最高)
dot_value = 30 # 点点的值(地上的豆豆/食物值)
my_speed = 10 # 我的球运动的速度
speed_up = 20 # 按下鼠标时加速
speed_enemy = 10 # 敌人球正常运动速度
speed_enemy_anomaly = 20 # 敌人突然加速时的速度(速度异常时的速度)
anomaly_pro = 0.5 # 敌人加速的概率
change_pro = 0.05 # 敌人移动路径变化的概率,也就是1/change_pro左右会变化一次
eat_percent = 0.9 # 吃掉敌人的球,按多少比例并入自己的体积,1对应的是100%
loss = 0.001 # 按比例减小体重(此外越重的减少越多,10万体积损失值为loss的一倍)
enemy_bigger_pro = 0.0005 # 敌人的值增加了我的球的值的enemy_bigger_rate倍的几率
enemy_bigger_rate = 0.1 # 增加我的球的体积的enemy_bigger_rate倍
class Color(object): # 定义颜色的类
@classmethod # 加了这个可以不需要把实例化,能直接调用类的方法
def random_color(cls): # cls, 即class,表示可以通过类名直接调用
red = random.randint(0, 255)
green = random.randint(0, 255)
blue = random.randint(0, 255)
return red, green, blue
class Ball(object): # 定义球
def __init__(self, x, y, sx, sy, color, value): # 初始化
self.x = x # 球的地图位置参数
self.y = y
self.sx = sx # 速度参数
self.sy = sy
self.color = color # 颜色
self.value = value # 球的值,也就是球的大小(不是显示的大小)
self.is_alive = True # 球默认是存活状态
class My_Ball(Ball): # 定义我的球,继承了Ball类的方法
def __init__(self, x, y, sx, sy, color, value):
# 注意:如果重写了__init__() 时,实例化子类,就不会调用父类已经定义的__init__()
# 如果子类不重写__init__()方法,实例化子类后,会自动调用父类的__init__()的方法
# 如果子类重写__init__()方法又需要调用父类的方法,则要使用super关键词。
super().__init__(x, y, sx, sy, color, value) # 调用父类Ball的初始化方法__init__()
self.radius = int(self.value**0.5) # 我的球的半径(不考虑系数pi)
if self.radius >= max_show_size: # 如果半径比规定的最大半径还大,则显示最大半径
self.show_radius = max_show_size # 我的球显示的半径
else:
self.show_radius = self.radius # 如果半径没有超过规定最大的半径,则显示原来实际大小的半径
self.position_x = int(screen_width/2) # 把我的球固定在屏幕中间position_x,是屏幕显示的位置
self.position_y = int(screen_height/2) # 把我的球固定在屏幕中间position_y,是屏幕显示的位置
def draw(self, window): # 把我的球画出来
self.radius = int(self.value ** 0.5) # 这里重复上面的,因为除了初始化之后,还要更新
if self.radius >= max_show_size:
self.show_radius = max_show_size
else:
self.show_radius = self.radius
self.position_x = int(screen_width / 2)
self.position_y = int(screen_height / 2)
pygame.draw.circle(window, self.color, (self.position_x , self.position_y), self.show_radius)
def eat_ball(self, other): # 吃别的球(包括小点点和敌人)
if self != other and self.is_alive and other.is_alive: # 如果other不是自身,自身和对方也都是存活状态,则执行下面动作
distance = ((self.position_x - other.position_x) ** 2 + (self.position_y - other.position_y) ** 2) ** 0.5 # 两个球之间的距离
if distance < self.show_radius and (self.show_radius > other.show_radius or (self.show_radius == other.show_radius and self.value > other.value)): # 如果自身半径比别人大,而且两者距离小于自身半径,那么可以吃掉。
other.is_alive = False # 吃球(敌方已死)
self.value += other.value*eat_percent # 自己的值增大(体量增大)
self.radius = int(self.value ** 0.5) # 计算出半径
if self.radius >= max_show_size: # 我的球的显示半径
self.show_radius = max_show_size
else:
self.show_radius = self.radius
def move(self): # 移动规则
self.x += self.sx # 地图位置加上速度
self.y += self.sy
# 横向出界
if self.x < 0: # 离开了地图左边
self.x = 0
if self.x > map_width: # 离开了地图右边
self.x = map_width
# 纵向出界
if self.y <= 0: # 离开了地图下边
self.y = 0
if self.y >= map_height: # 离开了地图上边
self.y = map_height
class Enemy_Ball(Ball): # 定义敌人的球,继承了Ball类的方法
def __init__(self, x, y, sx, sy, color, value, host_ball): # 初始化带上host_ball,也就是我的球
super().__init__(x, y, sx, sy, color, value)
self.host_ball = host_ball
self.radius = int(self.value**0.5)
if self.host_ball.radius >= max_show_size: # 如果我的球比规定的最大尺寸还大,则敌人的球显示的比例要减小
self.show_radius = max(10, int(self.radius/(self.host_ball.radius/max_show_size))) # 敌人的球也不能太小,最小半径为10
self.position_x = int((self.x - self.host_ball.x) / (self.host_ball.radius / max_show_size)) + int(
screen_width / 2) # 计算出敌人的球和我的球的相对位置,并且按比例减小
self.position_y = int((self.y - self.host_ball.y) / (self.host_ball.radius / max_show_size)) + int(
screen_height / 2) # 计算出敌人的球和我的球的相对位置,并且按比例减小
else:
self.show_radius = self.radius # 正常显示
self.position_x = (self.x - self.host_ball.x) + int(screen_width / 2) # 敌人和我的球的相对位置
self.position_y = (self.y - self.host_ball.y) + int(screen_height / 2) # 敌人和我的球的相对位置
# 画出球
def draw(self, window):
self.radius = int(self.value ** 0.5)
if self.host_ball.radius >= max_show_size: # 这边把初始化的内容再写一遍,因为敌人的球初始化之后还要根据我的球而动态改变
self.show_radius = max(10, int(self.radius/(self.host_ball.radius/max_show_size)))
self.position_x = int((self.x - self.host_ball.x) / (self.host_ball.radius / max_show_size)) + int(
screen_width / 2)
self.position_y = int((self.y - self.host_ball.y) / (self.host_ball.radius / max_show_size)) + int(
screen_height / 2)
else:
self.show_radius = self.radius
self.position_x = (self.x - self.host_ball.x) + int(screen_width / 2)
self.position_y = (self.y - self.host_ball.y) + int(screen_height / 2)
pygame.draw.circle(window, self.color, (self.position_x, self.position_y), self.show_radius)
def eat_ball(self, other):
if self != other and self.is_alive and other.is_alive:
distance = ((self.position_x - other.position_x) ** 2 + (self.position_y - other.position_y) ** 2) ** 0.5
if distance < self.show_radius and (self.show_radius > other.show_radius or (self.show_radius == other.show_radius and self.value > other.value)):
other.is_alive = False # 吃球
self.value += other.value*eat_percent
self.radius = int(self.value ** 0.5)
def move(self): # 移动规则
self.x += self.sx # 地图位置加上速度
self.y += self.sy
# 横向出界
if self.x < 0: # 离开了地图左边
self.sx = -self.sx
self.x = 0
if self.x > map_width: # 离开了地图右边
self.sx = -self.sx
self.x = map_width
# 纵向出界
if self.y <= 0: # 离开了地图下边
self.sy = -self.sy
self.y = 0
if self.y >= map_height: # 离开了地图上边
self.sy = -self.sy
self.y = map_height
class Dot_Ball(Ball): # 定义地上的小点点,供自己的球和敌人的球吃,继承了Ball类的方法
def __init__(self, x, y, sx, sy, color, value, host_ball):
super().__init__(x, y, sx, sy, color, value)
self.host_ball = host_ball
self.radius = 8 # 初始小点点大小
if self.host_ball.radius >= max_show_size:
self.show_radius = max(3, int(self.radius/(self.host_ball.radius/max_show_size))) # 小点点显示也不能太小,最小显示半径为3
self.position_x = int((self.x - self.host_ball.x) / (self.host_ball.radius / max_show_size)) + int(
screen_width / 2)
self.position_y = int((self.y - self.host_ball.y) / (self.host_ball.radius / max_show_size)) + int(
screen_height / 2)
else:
self.show_radius = self.radius
self.position_x = (self.x - self.host_ball.x) + int(screen_width / 2)
self.position_y = (self.y - self.host_ball.y) + int(screen_height / 2)
# 画出球
def draw(self, window):
if self.host_ball.radius >= max_show_size: # 这边把初始化的内容再写一遍,因为小点点初始化之后还要根据我的球而动态改变
self.show_radius = max(3, int(self.radius/(self.host_ball.radius/max_show_size)))
self.position_x = int((self.x - self.host_ball.x) / (self.host_ball.radius / max_show_size)) + int(
screen_width / 2)
self.position_y = int((self.y - self.host_ball.y) / (self.host_ball.radius / max_show_size)) + int(
screen_height / 2)
else:
self.show_radius = self.radius
self.position_x = (self.x - self.host_ball.x) + int(screen_width / 2)
self.position_y = (self.y - self.host_ball.y) + int(screen_height / 2)
pygame.draw.circle(window, self.color, (self.position_x, self.position_y) , self.show_radius)
def creat_my_ball(): # 产生我的球
x = random.randint(0, map_width) # 我的球在地图中的位置,随机生成
y = random.randint(0, map_height)
value = my_value # 我的球的初始值
color = 255, 255, 255 # 我的球的颜色
sx = 0 # 速度默认为0
sy = 0
host_ball = My_Ball(x, y, sx, sy, color, value) # 调用My_Ball类
return host_ball # 返回我的球
def auto_creat_ball(balls, host_ball): # 自动产生敌人的球
if len(balls) <= number_enemy: # 控制敌人的数量,如果个数够了,就不再生成
x = random.randint(0, map_width) # 敌人球在地图中的位置,随机生成
y = random.randint(0, map_height)
value = random.randint(enemy_value_low, enemy_value_high) # 敌人的球初始值
sx = random.randint(-speed_enemy, speed_enemy) # 敌人的球移动速度
i2 = random.randint(0, 1) # y的移动方向
if i2 == 0:
sy = int((speed_enemy**2 - sx**2) ** 0.5)
else:
sy = -int((speed_enemy ** 2 - sx ** 2) ** 0.5)
color = Color.random_color() # 敌人的颜色随机生成
enemy = Enemy_Ball(x, y, sx, sy, color, value, host_ball)
balls.append(enemy)
def auto_creat_dots(dots, host_ball): # 自动生成点点
if len(dots) <= number_dots: # 控制点点的数量
x = random.randint(0, map_width) # 随机生成点点的位置
y = random.randint(0, map_height)
value = dot_value # 点点的值
sx = 0 # 点点速度为0
sy = 0
color = Color.random_color() # 颜色
dot = Dot_Ball(x, y, sx, sy, color, value, host_ball)
dots.append(dot)
def control_my_ball(host_ball): # 控制我的球
host_ball.move()
host_ball.value = host_ball.value*(1-loss*host_ball.value/100000)
for event in pygame.event.get(): # 监控事件(鼠标移动)
# print(event)
if event.type == pygame.MOUSEBUTTONDOWN:
pos = event.pos
speed = speed_up
elif event.type == pygame.MOUSEMOTION:
pos = event.pos
if event.buttons[0] == 1:
speed = speed_up
if event.buttons[0] == 0:
speed = my_speed
elif event.type == pygame.MOUSEBUTTONUP:
pos = event.pos
speed = my_speed
else:
pos = [screen_width/2, screen_height/2]
speed = my_speed
if abs(pos[0] - screen_width/2) < 30 and abs(pos[1] - screen_height/2) < 30:
host_ball.sx = 0
host_ball.sy = 0
elif pos[0] > screen_width/2 and pos[1] >= screen_height/2:
angle = abs(math.atan((pos[1] - screen_height/2) / (pos[0] - screen_width/2)))
host_ball.sx = int(speed * math.cos(angle))
host_ball.sy = int(speed * math.sin(angle))
elif pos[0] > screen_width/2 and pos[1] < screen_height/2:
angle = abs(math.atan((pos[1] - screen_height/2) / (pos[0] - screen_width/2)))
host_ball.sx = int(speed * math.cos(angle))
host_ball.sy = -int(speed * math.sin(angle))
elif pos[0] < screen_width/2 and pos[1] >= screen_height/2:
angle = abs(math.atan((pos[1] - screen_height/2) / (pos[0] - screen_width/2)))
host_ball.sx = -int(speed * math.cos(angle))
host_ball.sy = int(speed * math.sin(angle))
elif pos[0] < screen_width/2 and pos[1] < screen_height/2:
angle = abs(math.atan((pos[1] - screen_height/2) / (pos[0] - screen_width/2)))
host_ball.sx = -int(speed * math.cos(angle))
host_ball.sy = -int(speed * math.sin(angle))
elif pos[0] == screen_width/2:
host_ball.sx = 0
if pos[1] >= 0:
host_ball.sy = speed
else:
host.ball.sy = -speed
def enemy_move(balls, host_ball): # 敌人移动
for enemy in balls:
enemy.move() # 移动
enemy.value = enemy.value*(1-loss*enemy.value/100000)
if random.randint(1, int(1/enemy_bigger_pro)) == 1:
enemy.value += host_ball.value*enemy_bigger_rate
if random.randint(1, int(1/anomaly_pro)) == 1:
speed_enemy0 = speed_enemy_anomaly # 敌人异常速度
else:
speed_enemy0 = speed_enemy # 敌人正常速度
i = random.randint(1, int(1/change_pro)) # 一定的概率改变轨迹
if i == 1:
enemy.sx = random.randint(-speed_enemy0, speed_enemy0)
i2 = random.randint(0, 1)
if i2 == 0:
enemy.sy = int((speed_enemy0 ** 2 - enemy.sx ** 2) ** 0.5)
else:
enemy.sy = -int((speed_enemy0 ** 2 - enemy.sx ** 2) ** 0.5)
def eat_each_other(host_ball, balls, dots): # 吃球
for enemy in balls:
for enemy2 in balls:
enemy.eat_ball(enemy2) # 敌人互吃
for food in dots:
enemy.eat_ball(food) # 敌人吃点点
for enemy in balls:
host_ball.eat_ball(enemy) # 我吃敌人
enemy.eat_ball(host_ball) # 敌人吃我
for food in dots:
host_ball.eat_ball(food) # 我吃点点
def paint(host_ball, balls, dots, screen):
screen.fill((0, 0, 0)) # 刷漆
if host_ball.is_alive:
host_ball.draw(screen)
for enemy in balls: # 遍历容器
if enemy.is_alive:
enemy.draw(screen)
else:
balls.remove(enemy)
for food in dots: # 遍历容器
if food.is_alive:
food.draw(screen)
else:
dots.remove(food)
def main():
pygame.init() # 初始化
screen = pygame.display.set_mode((screen_width, screen_height)) # 设置屏幕
pygame.display.set_caption("球球大作战") # 设置屏幕标题
balls = [] # 定义一容器 存放所有的敌方球
dots = [] # 定义一容器 存放所有的点点
is_running = True # 默认运行状态
host_ball = creat_my_ball() # 产生我的球
i00 = 0 # 一个参数
while is_running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
is_running = False
auto_creat_dots(dots, host_ball) # 自动生成点点
auto_creat_ball(balls, host_ball) # 自动生成敌人
paint(host_ball, balls, dots, screen) # 把所有的都画出来 调用draw方法
pygame.display.flip() # 渲染
pygame.time.delay(30) # 设置动画的时间延迟
control_my_ball(host_ball) # 移动我的球
enemy_move(balls, host_ball) # 敌人的球随机运动
eat_each_other(host_ball, balls, dots) # 吃球 调用eat_ball方法
i00 += 1
if np.mod(i00, 50) == 0:
print(host_ball.value)
if __name__ == '__main__':
main()
版本二(AI 辅助生成):
"""
This code is supported by the website: https://www.guanjihuan.com
The newest version of this code is on the web page: https://www.guanjihuan.com/archives/703
"""
import pygame
import random
import math
import sys
# 初始化pygame
pygame.init()
# 颜色定义
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
YELLOW = (255, 255, 0)
PURPLE = (128, 0, 128)
ORANGE = (255, 165, 0)
CYAN = (0, 255, 255)
# 游戏设置
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
WORLD_WIDTH = 1400
WORLD_HEIGHT = 1050
FPS = 60
# 创建窗口
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("球球大作战")
clock = pygame.time.Clock()
# 字体 - 尝试加载系统中文字体
def get_font(size):
# Windows系统字体路径 - 尝试更多常见字体
windows_fonts = [
"C:/Windows/Fonts/simhei.ttf", # 黑体
"C:/Windows/Fonts/msyh.ttc", # 微软雅黑
"C:/Windows/Fonts/msyhbd.ttc", # 微软雅黑粗体
"C:/Windows/Fonts/msyhl.ttc", # 微软雅黑细体
"C:/Windows/Fonts/simsun.ttc", # 宋体
"C:/Windows/Fonts/simkai.ttf", # 楷体
"C:/Windows/Fonts/arial.ttf", # Arial
]
# 只文件名尝试
font_names = [
"simhei.ttf",
"msyh.ttc",
"simsun.ttc",
"arial.ttf",
]
# 先试完整路径
for font_path in windows_fonts:
try:
return pygame.font.Font(font_path, size)
except:
continue
# 再试文件名
for font_name in font_names:
try:
return pygame.font.Font(font_name, size)
except:
continue
# 如果找不到中文字体,使用默认字体
print("警告:未找到中文字体")
return pygame.font.Font(None, size)
font = get_font(36)
small_font = get_font(24)
class Cell:
"""单个球体(细胞)类"""
def __init__(self, x, y, radius, color, is_player=False):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.is_player = is_player
self.mass = math.pi * radius * radius
def update_mass(self):
"""根据质量更新半径"""
self.radius = math.sqrt(self.mass / math.pi)
def add_mass(self, mass):
"""增加质量"""
self.mass += mass
self.update_mass()
def contains(self, other):
"""检查是否包含另一个球(可以吃掉它)
覆盖约80%就能吃,不需要完全覆盖
"""
dist = math.hypot(self.x - other.x, self.y - other.y)
# 80%覆盖判定:dist + 0.8 * other.radius < self.radius
return dist + 0.8 * other.radius < self.radius and self.mass > other.mass
def draw(self, surface, camera_x, camera_y, zoom=1):
"""绘制球体
camera_x, camera_y: 摄像头跟踪的世界坐标中心点
zoom: 缩放系数,<1 表示缩小显示
"""
# 计算屏幕坐标:以摄像头为中心缩放
screen_x = int(SCREEN_WIDTH // 2 + (self.x - camera_x) * zoom)
screen_y = int(SCREEN_HEIGHT // 2 + (self.y - camera_y) * zoom)
# 缩放半径
scaled_radius = int(self.radius * zoom)
# 只有在屏幕可见范围内才绘制
if (-scaled_radius < screen_x < SCREEN_WIDTH + scaled_radius and
-scaled_radius < screen_y < SCREEN_HEIGHT + scaled_radius):
pygame.draw.circle(surface, self.color, (screen_x, screen_y), scaled_radius)
# 绘制边框
pygame.draw.circle(surface, BLACK, (screen_x, screen_y), scaled_radius, 2)
# 如果是玩家,显示分数
if self.is_player:
text = small_font.render(str(int(self.mass)), True, WHITE)
text_rect = text.get_rect(center=(screen_x, screen_y))
surface.blit(text, text_rect)
class Food:
"""食物类"""
def __init__(self, x, y):
self.x = x
self.y = y
self.radius = 6
self.mass = 25 # 增大食物的质量值
self.color = random.choice([RED, BLUE, GREEN, YELLOW, PURPLE, ORANGE, CYAN])
def draw(self, surface, camera_x, camera_y, zoom=1):
"""绘制食物,支持缩放"""
screen_x = int(SCREEN_WIDTH // 2 + (self.x - camera_x) * zoom)
screen_y = int(SCREEN_HEIGHT // 2 + (self.y - camera_y) * zoom)
scaled_radius = int(self.radius * zoom)
if (-scaled_radius < screen_x < SCREEN_WIDTH + scaled_radius and
-scaled_radius < screen_y < SCREEN_HEIGHT + scaled_radius):
pygame.draw.circle(surface, self.color, (screen_x, screen_y), scaled_radius)
class Game:
"""游戏主类"""
def __init__(self):
self.player_cells = []
self.ai_cells = []
self.foods = []
self.score = 0
self.game_over = False
self.paused = False
self.final_mass = 0
# 初始化玩家
start_x = WORLD_WIDTH // 2
start_y = WORLD_HEIGHT // 2
player = Cell(start_x, start_y, 20, BLUE, is_player=True)
self.player_cells.append(player)
# 生成初始食物 - 更多更密
self.generate_food(200)
# 生成初始AI球球
self.generate_ai(5)
def generate_food(self, count):
"""生成食物"""
for _ in range(count):
x = random.randint(10, WORLD_WIDTH - 10)
y = random.randint(10, WORLD_HEIGHT - 10)
self.foods.append(Food(x, y))
def generate_ai(self, count):
"""生成AI球球"""
for _ in range(count):
x = random.randint(50, WORLD_WIDTH - 50)
y = random.randint(50, WORLD_HEIGHT - 50)
radius = random.randint(15, 30)
color = (random.randint(100, 255), random.randint(100, 255), random.randint(100, 255))
self.ai_cells.append(Cell(x, y, radius, color))
def update_player(self, mouse_x, mouse_y):
"""更新玩家位置 - 所有玩家的球都跟随鼠标移动"""
if not self.player_cells:
return
# 摄像头位置基于玩家重心(最大的球)
main_player = self.get_main_player()
if not main_player:
return
# 计算世界坐标中的目标位置
camera_x = main_player.x - SCREEN_WIDTH // 2
camera_y = main_player.y - SCREEN_HEIGHT // 2
world_mouse_x = mouse_x + camera_x
world_mouse_y = mouse_y + camera_y
# 是否按住加速键
mouse_buttons = pygame.mouse.get_pressed()
boost_active = mouse_buttons[0] # 左键加速
# 更新所有玩家球
for player in self.player_cells:
# 向鼠标方向移动 - 整体加速,大球也不会太慢
speed = 28000 / player.mass + 45 # 提高基础速度和整体速度
# 如果按住鼠标左键,激活加速(4倍速),不消耗质量
if boost_active:
speed *= 4
dx = world_mouse_x - player.x
dy = world_mouse_y - player.y
dist = math.hypot(dx, dy)
if dist > 5:
dx /= dist
dy /= dist
player.x += dx * speed / FPS
player.y += dy * speed / FPS
# 边界限制:允许球最多一半超出地图,这样能吃到角落的食物
player.x = max(-player.radius/2, min(WORLD_WIDTH + player.radius/2, player.x))
player.y = max(-player.radius/2, min(WORLD_HEIGHT + player.radius/2, player.y))
# 自动合体:检查所有小球,如果接近主球就合体
self.merge_cells()
def merge_cells(self):
"""自动合体 - 分裂的球靠近时会合并回主球"""
if len(self.player_cells) <= 1:
return
main_player = self.get_main_player()
if not main_player:
return
# 检查其他小球
to_merge = []
for i, cell in enumerate(self.player_cells):
if cell is main_player:
continue
# 计算距离
dist = math.hypot(cell.x - main_player.x, cell.y - main_player.y)
# 80%覆盖就能合体(和吞噬规则一致
if dist + 0.8 * cell.radius < main_player.radius:
to_merge.append(i)
main_player.add_mass(cell.mass)
# 移除合并的球(倒序删除避免索引问题)
for i in reversed(to_merge):
del self.player_cells[i]
def update_ai(self):
"""更新AI移动"""
for ai in self.ai_cells:
# 简单AI:向附近较小的球移动,避开较大的球
target = None
closest_dist = float('inf')
# 寻找最近的较小目标
for food in self.foods:
dist = math.hypot(ai.x - food.x, ai.y - food.y)
if dist < closest_dist:
closest_dist = dist
target = (food.x, food.y)
# 检查玩家
for player in self.player_cells:
dist = math.hypot(ai.x - player.x, ai.y - player.y)
if ai.mass > player.mass * 1.2:
if dist < closest_dist:
closest_dist = dist
target = (player.x, player.y)
elif player.mass > ai.mass * 1.2 and dist < 200:
# 逃离大球
dx = ai.x - player.x
dy = ai.y - player.y
dist = math.hypot(dx, dy)
if dist > 0:
dx /= dist
dy /= dist
ai.x += dx * (5 / FPS) * 8
ai.y += dy * (5 / FPS) * 8
target = None
# 向目标移动
if target:
speed = 18000 / ai.mass + 30 # 提高AI速度,加快游戏节奏
dx = target[0] - ai.x
dy = target[1] - ai.y
dist = math.hypot(dx, dy)
if dist > 5:
dx /= dist
dy /= dist
ai.x += dx * speed / FPS
ai.y += dy * speed / FPS
# 边界限制:允许球最多一半超出地图
ai.x = max(-ai.radius/2, min(WORLD_WIDTH + ai.radius/2, ai.x))
ai.y = max(-ai.radius/2, min(WORLD_HEIGHT + ai.radius/2, ai.y))
def check_collisions(self):
"""检查碰撞 - 吃东西和互相吞噬"""
# 玩家吃食物
for player in self.player_cells:
to_remove = []
for i, food in enumerate(self.foods):
if player.contains(Cell(food.x, food.y, food.radius, None)):
player.add_mass(food.mass)
to_remove.append(i)
self.score += food.mass
# 移除被吃的食物
for i in reversed(to_remove):
del self.foods[i]
# AI吃食物
for ai in self.ai_cells:
to_remove = []
for i, food in enumerate(self.foods):
if ai.contains(Cell(food.x, food.y, food.radius, None)):
ai.add_mass(food.mass)
to_remove.append(i)
for i in reversed(to_remove):
del self.foods[i]
# AI吞噬玩家
for ai in self.ai_cells[:]:
for player in self.player_cells[:]:
if ai.contains(player):
ai.add_mass(player.mass)
# 在移除前保存最终质量
if len(self.player_cells) == 1:
self.final_mass = player.mass
self.player_cells.remove(player)
elif player.contains(ai):
player.add_mass(ai.mass)
self.ai_cells.remove(ai)
self.score += ai.mass
break
# AI之间互相吞噬
for ai1 in self.ai_cells[:]:
for ai2 in self.ai_cells[:]:
if ai1 != ai2 and ai1.contains(ai2):
ai1.add_mass(ai2.mass)
self.ai_cells.remove(ai2)
break
# 补充食物 - 更多更密
if len(self.foods) < 300:
self.generate_food(20)
# 动态补充AI:玩家质量越高,生成越多对手
main_player = self.get_main_player()
if main_player:
# 限制最多35个AI,平衡挑战性和流畅性,减少卡顿
target_ai_count = min(35, 15 + int(main_player.mass / 150))
else:
target_ai_count = 12
# 如果AI数量少于目标,持续补充
if len(self.ai_cells) < target_ai_count:
self.generate_ai(1)
# 质量衰减:所有球按比例减小体积,越大减少越多
self.mass_decay()
def get_main_player(self):
"""获取玩家最大的球"""
if not self.player_cells:
return None
return max(self.player_cells, key=lambda c: c.mass)
def draw(self):
"""绘制游戏,支持自动缩放"""
# 背景
screen.fill((240, 240, 240))
# 获取主玩家
main_player = self.get_main_player()
if main_player:
# 摄像头始终跟随主玩家中心点
camera_x = main_player.x
camera_y = main_player.y
# 自动缩放计算:主球显示直径最大固定为屏幕宽度的一半
# 达到后,主球显示大小不再增大,实际质量继续增长,其他球跟着缩小
max_display_radius = SCREEN_WIDTH / 4 # 半径 = 1/4宽度 → 直径 = 1/2宽度,正好是屏幕一半
if main_player.radius > max_display_radius:
# 需要缩小整个画面,保持主球显示大小不超
zoom = max_display_radius / main_player.radius
else:
# 还没到最大,正常显示
zoom = 1.0
# 最小缩放到0.15,避免缩得太小
zoom = max(0.15, zoom)
else:
camera_x = WORLD_WIDTH // 2
camera_y = WORLD_HEIGHT // 2
zoom = 1.0
# 绘制网格背景(网格也随缩放调整)
grid_size = int(40 * zoom)
if grid_size < 5:
grid_size = 5
# 计算网格起始位置
# 因为现在 camera 在中心,所以起始点计算方式改变
start_x = int((-camera_x * zoom) % grid_size)
start_y = int((-camera_y * zoom) % grid_size)
start_x += SCREEN_WIDTH // 2
start_y += SCREEN_HEIGHT // 2
for x in range(start_x - grid_size, SCREEN_WIDTH, grid_size):
pygame.draw.line(screen, (220, 220, 220), (x, 0), (x, SCREEN_HEIGHT), 1)
for y in range(start_y - grid_size, SCREEN_HEIGHT, grid_size):
pygame.draw.line(screen, (220, 220, 220), (0, y), (SCREEN_WIDTH, y), 1)
# 绘制食物(所有物体都按zoom缩放)
for food in self.foods:
food.draw(screen, camera_x, camera_y, zoom)
# 绘制AI
for ai in self.ai_cells:
ai.draw(screen, camera_x, camera_y, zoom)
# 绘制玩家
for player in self.player_cells:
player.draw(screen, camera_x, camera_y, zoom)
# 绘制UI(UI不缩放,始终固定在屏幕)
if main_player:
mass_text = font.render(f"质量: {int(main_player.mass)}", True, BLACK)
screen.blit(mass_text, (10, 10))
# 分数显示已禁用
# score_text = font.render(f"分数: {self.score}", True, BLACK)
# screen.blit(score_text, (10, 50))
# 游戏结束
if not self.player_cells and not self.game_over:
self.game_over = True
# 已经在AI吃掉最后一个球时保存了final_mass,如果还有其他球在这里补充
if self.final_mass == 0:
self.final_mass = sum(p.mass for p in self.player_cells)
if self.game_over:
game_over_text = font.render(f"游戏结束! 最终质量: {int(self.final_mass)}", True, RED)
text_rect = game_over_text.get_rect(center=(SCREEN_WIDTH//2, SCREEN_HEIGHT//2))
screen.blit(game_over_text, text_rect)
restart_text = small_font.render("按R重新开始", True, BLACK)
restart_rect = restart_text.get_rect(center=(SCREEN_WIDTH//2, SCREEN_HEIGHT//2 + 40))
screen.blit(restart_text, restart_rect)
if self.paused:
pause_text = font.render("暂停", True, RED)
text_rect = pause_text.get_rect(center=(SCREEN_WIDTH//2, SCREEN_HEIGHT//2))
screen.blit(pause_text, text_rect)
# 操作提示 - 分成两行显示避免超出屏幕,使用普通字符
hint_text1 = small_font.render(f"鼠标: 移动 左键: 加速(不耗质量) 空格: 分裂({len(self.player_cells)}/16)", True, BLACK)
hint_text2 = small_font.render("R: 重来 P: 暂停 完全靠近: 合体 大球衰减更多", True, BLACK)
screen.blit(hint_text1, (10, SCREEN_HEIGHT - 50))
screen.blit(hint_text2, (10, SCREEN_HEIGHT - 25))
def split_player(self, mouse_x, mouse_y):
"""玩家分裂 - 最多16个分身"""
main_player = self.get_main_player()
if not main_player or main_player.mass < 36: # 最小分裂质量
return
# 限制最多16个分身(4次分裂:1→2→4→8→16)
if len(self.player_cells) >= 16:
return
camera_x = main_player.x - SCREEN_WIDTH // 2
camera_y = main_player.y - SCREEN_HEIGHT // 2
world_mouse_x = mouse_x + camera_x
world_mouse_y = mouse_y + camera_y
# 计算方向
dx = world_mouse_x - main_player.x
dy = world_mouse_y - main_player.y
dist = math.hypot(dx, dy)
if dist == 0:
dist = 0.1
dx = 1
dy = 0
dx /= dist
dy /= dist
# 创建新球
new_mass = main_player.mass / 2
main_player.mass = new_mass
main_player.update_mass()
new_x = main_player.x + dx * (main_player.radius + 5)
new_y = main_player.y + dy * (main_player.radius + 5)
new_cell = Cell(new_x, new_y, 0, BLUE, is_player=True)
new_cell.mass = new_mass
new_cell.update_mass()
self.player_cells.append(new_cell)
# 给新细胞一个推力
new_cell.x += dx * 10
new_cell.y += dy * 10
def mass_decay(self):
"""质量衰减:所有球按比例减小体积,越大减少越多,设置最小质量保证不会太小没法吃食物"""
# 最小质量下限,不会比这个更小
MIN_MASS = 20
# 衰减系数,每帧衰减当前质量的比例
# 纯比例衰减:质量越大衰减越多,小球衰减很少几乎不衰减,符合要求
decay_rate = 0.0001
# 玩家球衰减
for i, player in reversed(list(enumerate(self.player_cells))):
decay_amount = player.mass * decay_rate
player.mass -= decay_amount
# 限制最小质量
if player.mass < MIN_MASS:
player.mass = MIN_MASS
player.update_mass()
# AI球衰减
for i, ai in reversed(list(enumerate(self.ai_cells))):
decay_amount = ai.mass * decay_rate
ai.mass -= decay_amount
if ai.mass < MIN_MASS:
ai.mass = MIN_MASS
ai.update_mass()
def restart(self):
"""重新开始游戏"""
self.player_cells = []
self.ai_cells = []
self.foods = []
self.score = 0
self.game_over = False
self.paused = False
# 初始化玩家
start_x = WORLD_WIDTH // 2
start_y = WORLD_HEIGHT // 2
player = Cell(start_x, start_y, 20, BLUE, is_player=True)
self.player_cells.append(player)
# 生成初始食物 - 更多更密
self.generate_food(200)
# 生成初始AI球球
self.generate_ai(5)
def main():
"""主游戏循环"""
game = Game()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1 and not game.game_over and not game.paused:
# 左键加速已经在update处理
pass
if event.type == pygame.KEYDOWN:
# 获取字符,转小写(兼容输入法)
char = event.unicode.lower() if event.unicode else ''
# 处理扫描码掩码:某些键盘/SDL会加上0x40000000掩码
SCANCODE_MASK = 1 << 30 # 1073741824
key = event.key
if key & SCANCODE_MASK:
key = key & ~SCANCODE_MASK
# 空格键分裂
if key == pygame.K_SPACE and not game.game_over and not game.paused:
mouse_x, mouse_y = pygame.mouse.get_pos()
game.split_player(mouse_x, mouse_y)
# R键重启:兼容不同键盘、输入法、大小写
if (key == pygame.K_r
or key == 114
or char == 'r'
or char == 'R'):
game = Game()
# P键暂停:兼容不同键盘、输入法、大小写
if (key == pygame.K_p
or key == 112
or char == 'p'
or char == 'P'):
game.paused = not game.paused
if not game.game_over and not game.paused:
# 获取鼠标位置
mouse_x, mouse_y = pygame.mouse.get_pos()
# 更新游戏
game.update_player(mouse_x, mouse_y)
game.update_ai()
game.check_collisions()
# 绘制
game.draw()
# 更新显示
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()
【说明:本站主要是个人的一些笔记和代码分享,内容可能会不定期修改。为了使全网显示的始终是最新版本,这里的文章未经同意请勿转载。引用请注明出处:https://www.guanjihuan.com】