生活, 其他记录

用Pygame写球球大作战游戏

主要是参照网上这个代码进行改编的:Python 球球大作战简化版https://www.cnblogs.com/wenqiangblog/p/9469909.html。该代码实现了以下功能:点击一下鼠标,在鼠标的地方会出现一个随机大小的球,然后球随机移动,大球遇到小球,会吃掉小球。可玩性不高。

通过改编后,实现了这些功能:

  1. 根据鼠标的位置,自身球在地图中的位置会发生移动;
  2. 自身球固定在屏幕中心,其他球是会根据自身球相对移动。
  3. 当自身球显示大小不再增大时(因为屏幕大小有限,实际值还在增大),其他球显示的大小会根据自身球而等比例减小(实际值仍保持);
  4. 吃地上的豆豆可以慢慢发育起来;
  5. 大球吃小球, 会按一定比例吸收到自身的体积;
  6. 按住鼠标左键,球会加速;
  7. 敌方的球有一定几率改变运动轨迹;
  8. 敌方的球有一定几率会突然加速;
  9. 敌方的球有一定几率会增加自身球的体积的一部分(为了增加后期挑战性);
  10. 所有的球会按一定的比例减小体积,而且体积越大,减少越多。

可以考虑的更多功能:

  1. 分身;
  2. 吐球;
  3. 扎刺;
  4. 参数科学化;
  5. 设计和美工;
  6. 联网对战等。

以下是改编的球球大作战代码。可以复制到 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()
4,383 次浏览

【说明:本站主要是个人的一些笔记和代码分享,内容可能会不定期修改。为了使全网显示的始终是最新版本,这里的文章未经同意请勿转载。引用请注明出处:https://www.guanjihuan.com

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

Captcha Code