第7章 游戏开发智能化实践:从原型到完整游戏

在前几章中,我们探索了桌面应用、Web应用开发的AI辅助模式。本章将进入一个更具挑战性和创造性的领域——游戏开发。游戏开发融合了图形学、物理模拟、交互设计、音效系统等多个技术领域,传统上被认为是编程的"硬核"方向。然而,随着AI辅助工具的成熟,游戏开发的门槛正在大幅降低。

据2025年的行业数据显示,使用AI辅助的游戏开发者,其原型开发速度平均提升300%以上。掌趣科技联合蓝亚盒子发布的LayaIdea工具,实现了从策划文档到3D游戏项目的自动化生成,将互动试玩广告的开发周期从一周缩短至一天。网易接入通义千问后,游戏测试系统的开发效率提升了50%。甚至有开发者完全借助AI工具,三个月内完成了一款100%由AI生成的游戏《Codex Mortis》。

本章将通过一系列由浅入深的案例,展示如何利用ChatGPT和GitHub Copilot加速游戏开发。我们将从简单的视觉效果开始,逐步构建完整的游戏项目。所有代码均采用Allman风格,使用驼峰命名,确保在VS Code和VS 2022中可直接运行。

7.1 视觉特效:奠定游戏的美学基础

游戏开发的第一步通常是视觉效果。AI可以帮助我们快速生成粒子系统、纹理动画等基础特效。

7.1.1 旋转星系:粒子系统的AI实现

旋转星系是一个经典的粒子特效案例,通过粒子的螺旋运动模拟星系的旋转效果。使用Pygame实现这一特效,需要理解粒子系统的基本原理:每个粒子具有位置、速度、角度等属性,在每帧中更新这些属性并绘制。

需求描述

向ChatGPT提出需求:"请用Python和Pygame实现一个旋转星系特效。要求:

  1. 在窗口中心生成数千个粒子,形成螺旋形状
  2. 粒子围绕中心旋转,内圈速度快,外圈速度慢
  3. 粒子颜色从中心向外渐变(中心白/黄,外围蓝/紫)
  4. 添加淡淡的拖尾效果
  5. 帧率稳定在60FPS"

ChatGPT会生成如下代码(我们调整为Allman风格并添加详细注释):

import pygame
import math
import random

# 初始化Pygame
pygame.init()

# 窗口设置
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("旋转星系 - AI生成特效")

# 颜色常量
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
YELLOW = (255, 255, 100)
BLUE = (100, 150, 255)
PURPLE = (180, 100, 255)

class Particle:
    """
    单个粒子类
    """
    
    def __init__(self, distance, angle, speed):
        """
        初始化粒子
        
        Parameters:
            distance (float): 粒子到中心的距离
            angle (float): 初始角度(弧度)
            speed (float): 旋转速度
        """
        
        self.distance = distance
        self.angle = angle
        self.speed = speed
        self.size = random.uniform(1.0, 2.5)
        
        # 根据距离计算颜色(中心暖色,外围冷色)
        self.color = self.CalculateColor(distance)
        
    def CalculateColor(self, distance):
        """
        根据距离计算颜色
        
        Parameters:
            distance (float): 粒子到中心的距离
            
        Returns:
            tuple: RGB颜色值
        """
        
        maxDist = 250.0
        t = min(distance / maxDist, 1.0)
        
        if t < 0.3:
            # 中心区域:白到黄
            r = 255
            g = 255 - int(t * 155)  # 从255降到200左右
            b = 200 - int(t * 150)   # 从200降到50左右
        else:
            # 外围区域:蓝到紫
            localT = (t - 0.3) / 0.7
            r = 150 + int(localT * 105)   # 150 -> 255
            g = 100 - int(localT * 60)    # 100 -> 40
            b = 255
            
        return (r, g, b)
        
    def Update(self, centerX, centerY):
        """
        更新粒子位置
        
        Parameters:
            centerX (int): 旋转中心X坐标
            centerY (int): 旋转中心Y坐标
        """
        
        # 角度随时间增加(旋转)
        self.angle += self.speed
        
        # 计算笛卡尔坐标
        self.x = centerX + math.cos(self.angle) * self.distance
        self.y = centerY + math.sin(self.angle) * self.distance
        
    def Draw(self, surface, trailSurface):
        """
        绘制粒子
        
        Parameters:
            surface (Surface): 主绘制表面
            trailSurface (Surface): 拖尾效果的表面
        """
        
        # 在主表面绘制粒子
        pygame.draw.circle(surface, self.color, (int(self.x), int(self.y)), int(self.size))
        
        # 在拖尾表面绘制半透明粒子(用于拖尾效果)
        alphaColor = (*self.color, 30)  # 添加alpha通道
        trailSurface.set_at((int(self.x), int(self.y)), alphaColor)


class Galaxy:
    """
    星系特效类
    """
    
    def __init__(self, particleCount=3000):
        """
        初始化星系
        
        Parameters:
            particleCount (int): 粒子数量
        """
        
        self.particles = []
        self.centerX = WIDTH // 2
        self.centerY = HEIGHT // 2
        
        # 创建拖尾表面
        self.trailSurface = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
        
        # 生成粒子
        self.GenerateParticles(particleCount)
        
    def GenerateParticles(self, count):
        """
        生成粒子
        
        Parameters:
            count (int): 粒子数量
        """
        
        for _ in range(count):
            # 随机距离,分布偏向中心
            distance = random.gauss(120, 80)
            distance = max(10, min(350, abs(distance)))
            
            # 随机角度
            angle = random.uniform(0, math.pi * 2)
            
            # 旋转速度:内圈快,外圈慢
            speed = 0.01 * (250 / max(distance, 50))
            
            self.particles.append(Particle(distance, angle, speed))
            
    def Update(self):
        """更新所有粒子"""
        
        for particle in self.particles:
            particle.Update(self.centerX, self.centerY)
            
    def Draw(self, screen):
        """
        绘制星系
        
        Parameters:
            screen (Surface): 屏幕表面
        """
        
        # 每帧轻微淡化拖尾表面(制造拖尾效果)
        self.trailSurface.fill((0, 0, 0, 10))  # 半透明黑色填充
        
        # 先绘制粒子到拖尾表面
        for particle in self.particles:
            particle.Draw(screen, self.trailSurface)
            
        # 将拖尾表面混合到主屏幕
        screen.blit(self.trailSurface, (0, 0), special_flags=pygame.BLEND_ALPHA_SDL2)
        
        # 在中心绘制一个亮核
        pygame.draw.circle(screen, (255, 255, 200), (self.centerX, self.centerY), 8)
        pygame.draw.circle(screen, (255, 200, 100), (self.centerX, self.centerY), 5)


def main():
    """主函数"""
    
    clock = pygame.time.Clock()
    galaxy = Galaxy(4000)
    
    running = True
    while running:
        # 事件处理
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    running = False
                    
        # 更新
        galaxy.Update()
        
        # 绘制
        screen.fill(BLACK)
        galaxy.Draw(screen)
        
        # 显示FPS
        font = pygame.font.SysFont("Arial", 16)
        fpsText = font.render(f"FPS: {int(clock.get_fps())}", True, WHITE)
        screen.blit(fpsText, (10, 10))
        
        pygame.display.flip()
        clock.tick(60)
        
    pygame.quit()


if __name__ == "__main__":
    main()

代码解析

这段AI生成的代码展示了几个关键的游戏开发概念:

  1. 粒子系统Particle类封装了单个粒子的属性(距离、角度、速度、颜色)和行为(更新、绘制)。
  2. 颜色渐变CalculateColor方法根据粒子到中心的距离计算颜色,实现中心暖色、外围冷色的效果。
  3. 运动规律:旋转速度与距离成反比,内圈快外圈慢,模拟星系旋转的物理特性。
  4. 拖尾效果:使用一个带Alpha通道的表面存储上一帧的粒子轨迹,通过每帧半透明填充实现拖尾。

运行这段代码,可以看到数千个粒子在中心周围旋转,形成美丽的螺旋星系效果。

AI辅助心得:生成这段代码时,ChatGPT正确理解了"内圈快外圈慢"的物理规律,并实现了颜色渐变。如果对效果不满意,可以继续与AI对话,例如要求"增加一些随机闪烁的星星"或"让中心更亮一些",AI会给出相应的修改建议。

7.1.2 移动的纹理:动态背景生成

第二个案例实现移动的纹理效果,常用于游戏背景或动态菜单。我们将创建一个流动的星云纹理,通过噪声函数生成动态图案。

与Copilot协作

在VS Code中新建文件moving_texture.py,输入导入语句和类定义,观察Copilot的补全。

import pygame
import numpy as np
import math
import random

class MovingTexture:
    """
    移动纹理类 - 生成动态流动的星云效果
    """
    
    def __init__(self, width, height):
        """
        初始化纹理
        
        Parameters:
            width (int): 纹理宽度
            height (int): 纹理高度
        """
        
        self.width = width
        self.height = height
        self.time = 0
        
        # 创建表面
        self.surface = pygame.Surface((width, height))
        
        # 预计算一些噪声参数
        self.octaves = 4
        self.persistence = 0.5
        self.lacunarity = 2.0
        
    def Noise(self, x, y):
        """
        生成简单的噪声值
        
        Parameters:
            x, y (float): 坐标
            
        Returns:
            float: 噪声值在0-1之间
        """
        
        # 使用正弦函数组合模拟噪声
        return (math.sin(x * 0.01) * math.cos(y * 0.01) + 
                math.sin(x * 0.02 + 1.0) * math.cos(y * 0.02 + 2.0) * 0.5 +
                math.sin(x * 0.05 + 3.0) * math.cos(y * 0.05 + 4.0) * 0.25)
                
    def FractalNoise(self, x, y):
        """
        生成分形噪声
        
        Parameters:
            x, y (float): 坐标
            
        Returns:
            float: 噪声值在0-1之间
        """
        
        value = 0.0
        amplitude = 1.0
        frequency = 1.0
        maxAmplitude = 0.0
        
        for _ in range(self.octaves):
            value += amplitude * self.Noise(x * frequency + self.time * 0.1, 
                                           y * frequency)
            maxAmplitude += amplitude
            amplitude *= self.persistence
            frequency *= self.lacunarity
            
        return value / maxAmplitude
        
    def Update(self):
        """更新纹理状态"""
        
        self.time += 1
        
        # 创建像素数组
        pixels = pygame.surfarray.pixels3d(self.surface)
        
        # 遍历每个像素
        for x in range(self.width):
            for y in range(self.height):
                # 生成三个通道的噪声
                r = self.FractalNoise(x, y)
                g = self.FractalNoise(x + 100, y + 50)
                b = self.FractalNoise(x - 50, y + 100)
                
                # 映射到0-255范围并设置颜色
                pixels[x, y] = [
                    int(r * 255),
                    int(g * 255),
                    int(b * 255)
                ]
                
        # 释放像素数组
        del pixels
        
    def Draw(self, screen, offsetX=0, offsetY=0):
        """
        绘制纹理到屏幕
        
        Parameters:
            screen (Surface): 目标屏幕
            offsetX, offsetY (int): 偏移量
        """
        
        screen.blit(self.surface, (offsetX, offsetY))


def main():
    pygame.init()
    
    WIDTH, HEIGHT = 800, 600
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("移动纹理 - 动态星云")
    
    clock = pygame.time.Clock()
    
    # 创建两个纹理实现无缝滚动
    texture1 = MovingTexture(WIDTH, HEIGHT)
    texture2 = MovingTexture(WIDTH, HEIGHT)
    
    offset = 0
    
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
                
        # 更新纹理
        texture1.Update()
        texture2.Update()
        
        # 清除屏幕
        screen.fill((0, 0, 0))
        
        # 绘制两个纹理,实现滚动效果
        texture1.Draw(screen, 0, offset - HEIGHT)
        texture2.Draw(screen, 0, offset)
        
        # 更新偏移量
        offset += 1
        if offset >= HEIGHT:
            offset = 0
            
        pygame.display.flip()
        clock.tick(60)
        
    pygame.quit()


if __name__ == "__main__":
    main()

Copilot在编写过程中发挥了重要作用:在输入def FractalNoise后,它自动补全了分形噪声的实现;在像素循环部分,它建议了使用pygame.surfarray进行快速像素操作的方法。

7.1.3 随机运动的小球:物理模拟入门

第三个案例模拟多个小球在屏幕内的随机运动,包含简单的物理特性:碰撞检测、反弹、摩擦力等。

需求描述

向ChatGPT提出:"用Pygame实现一个随机运动的小球模拟。要求:

  1. 创建20个小球,随机大小(10-30像素)、随机颜色、随机初始位置和速度
  2. 小球之间发生碰撞时,根据物理规律改变速度
  3. 小球与窗口边界碰撞时反弹(能量损失系数0.8)
  4. 添加简单的摩擦力,使小球逐渐减速
  5. 用鼠标点击可以在点击位置生成新小球"

ChatGPT生成的代码需要包含向量运算和碰撞检测。我们整理后的代码采用面向对象设计:

import pygame
import math
import random

# 初始化Pygame
pygame.init()

# 窗口设置
WIDTH, HEIGHT = 900, 700
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("随机运动的小球 - 物理模拟")

# 颜色
BLACK = (0, 0, 0)

class Vector2:
    """
    二维向量类,用于表示位置、速度等
    """
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
    def __add__(self, other):
        return Vector2(self.x + other.x, self.y + other.y)
        
    def __sub__(self, other):
        return Vector2(self.x - other.x, self.y - other.y)
        
    def __mul__(self, scalar):
        return Vector2(self.x * scalar, self.y * scalar)
        
    def Length(self):
        """向量长度"""
        
        return math.sqrt(self.x * self.x + self.y * self.y)
        
    def Normalize(self):
        """归一化"""
        
        length = self.Length()
        if length > 0:
            return Vector2(self.x / length, self.y / length)
        return Vector2(0, 0)
        

class Ball:
    """
    小球类
    """
    
    def __init__(self, x, y, radius=None, color=None):
        """
        初始化小球
        
        Parameters:
            x, y (float): 初始位置
            radius (int, optional): 半径
            color (tuple, optional): 颜色
        """
        
        self.pos = Vector2(x, y)
        
        # 随机半径
        if radius is None:
            self.radius = random.randint(8, 25)
        else:
            self.radius = radius
            
        # 随机颜色
        if color is None:
            self.color = (
                random.randint(50, 255),
                random.randint(50, 255),
                random.randint(50, 255)
            )
        else:
            self.color = color
            
        # 随机速度
        angle = random.uniform(0, math.pi * 2)
        speed = random.uniform(2, 8)
        self.vel = Vector2(math.cos(angle) * speed, math.sin(angle) * speed)
        
        # 物理参数
        self.mass = self.radius  # 质量与半径成正比
        self.friction = 0.995    # 摩擦力系数
        
    def Update(self):
        """更新位置"""
        
        # 应用摩擦力
        self.vel = self.vel * self.friction
        
        # 更新位置
        self.pos = self.pos + self.vel
        
    def CheckBoundaryCollision(self):
        """检查与边界的碰撞并反弹"""
        
        bounceFactor = 0.8  # 能量损失系数
        
        if self.pos.x - self.radius < 0:
            self.pos.x = self.radius
            self.vel.x = abs(self.vel.x) * bounceFactor
        elif self.pos.x + self.radius > WIDTH:
            self.pos.x = WIDTH - self.radius
            self.vel.x = -abs(self.vel.x) * bounceFactor
            
        if self.pos.y - self.radius < 0:
            self.pos.y = self.radius
            self.vel.y = abs(self.vel.y) * bounceFactor
        elif self.pos.y + self.radius > HEIGHT:
            self.pos.y = HEIGHT - self.radius
            self.vel.y = -abs(self.vel.y) * bounceFactor
            
    def CheckBallCollision(self, other):
        """
        检查与另一个小球的碰撞
        
        Parameters:
            other (Ball): 另一个小球
            
        Returns:
            bool: 是否发生碰撞
        """
        
        # 计算距离
        delta = self.pos - other.pos
        distance = delta.Length()
        
        if distance == 0 or distance > self.radius + other.radius:
            return False
            
        # 发生碰撞,计算新的速度
        # 使用弹性碰撞公式
        normal = delta.Normalize()
        
        # 相对速度
        relativeVel = self.vel - other.vel
        
        # 计算冲击速度
        velAlongNormal = relativeVel.x * normal.x + relativeVel.y * normal.y
        
        if velAlongNormal > 0:
            return False  # 正在分离,不处理
            
        # 弹性系数
        e = 0.8
        
        # 计算冲量标量
        impulseScalar = -(1 + e) * velAlongNormal
        impulseScalar /= (1 / self.mass + 1 / other.mass)
        
        # 应用冲量
        impulse = normal * impulseScalar
        self.vel = self.vel + impulse * (1 / self.mass)
        other.vel = other.vel - impulse * (1 / other.mass)
        
        # 分离重叠(避免粘在一起)
        overlap = self.radius + other.radius - distance
        if overlap > 0:
            displacement = normal * (overlap * 0.5)
            self.pos = self.pos + displacement
            other.pos = other.pos - displacement
            
        return True
        
    def Draw(self, surface):
        """绘制小球"""
        
        pygame.draw.circle(surface, self.color, 
                          (int(self.pos.x), int(self.pos.y)), 
                          self.radius)
        # 绘制高光
        highlightPos = (int(self.pos.x - self.radius * 0.3), 
                       int(self.pos.y - self.radius * 0.3))
        pygame.draw.circle(surface, (255, 255, 255, 100), 
                          highlightPos, self.radius // 4)


class BallSimulation:
    """
    小球模拟主类
    """
    
    def __init__(self, ballCount=20):
        """
        初始化模拟
        
        Parameters:
            ballCount (int): 初始小球数量
        """
        
        self.balls = []
        
        for _ in range(ballCount):
            # 随机位置,避免重叠
            while True:
                x = random.randint(50, WIDTH - 50)
                y = random.randint(50, HEIGHT - 50)
                radius = random.randint(8, 25)
                
                # 检查与现有小球的重叠
                overlapping = False
                for ball in self.balls:
                    dx = x - ball.pos.x
                    dy = y - ball.pos.y
                    distance = math.sqrt(dx * dx + dy * dy)
                    if distance < radius + ball.radius + 10:
                        overlapping = True
                        break
                        
                if not overlapping:
                    self.balls.append(Ball(x, y, radius))
                    break
                    
    def AddBall(self, x, y):
        """
        添加新小球
        
        Parameters:
            x, y (int): 鼠标点击位置
        """
        
        self.balls.append(Ball(x, y))
        
    def Update(self):
        """更新所有小球"""
        
        # 更新位置
        for ball in self.balls:
            ball.Update()
            
        # 边界碰撞
        for ball in self.balls:
            ball.CheckBoundaryCollision()
            
        # 小球间碰撞检测
        for i in range(len(self.balls)):
            for j in range(i + 1, len(self.balls)):
                self.balls[i].CheckBallCollision(self.balls[j])
                
    def Draw(self, surface):
        """绘制所有小球"""
        
        surface.fill(BLACK)
        
        for ball in self.balls:
            ball.Draw(surface)


def main():
    clock = pygame.time.Clock()
    sim = BallSimulation(15)
    
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1:  # 左键
                    x, y = event.pos
                    sim.AddBall(x, y)
                    
        sim.Update()
        sim.Draw(screen)
        
        # 显示小球数量
        font = pygame.font.SysFont("Arial", 18)
        countText = font.render(f"小球数量: {len(sim.balls)}", True, (255, 255, 255))
        screen.blit(countText, (10, 10))
        
        pygame.display.flip()
        clock.tick(60)
        
    pygame.quit()


if __name__ == "__main__":
    main()

物理模拟的关键点

  1. 向量运算Vector2类简化了位置、速度的计算。
  2. 碰撞检测:基于距离检测,使用弹性碰撞公式计算碰撞后的速度。
  3. 分离重叠:碰撞后移动小球以避免粘在一起。
  4. 边界反弹:检测是否超出边界,反转速度分量并乘以能量损失系数。

运行这段代码,可以看到小球在屏幕内自由运动,相互碰撞,点击鼠标可以添加新小球。AI生成的物理代码虽然不如专业物理引擎精确,但对于学习和原型验证已经足够。

7.2 经典游戏实现:从太空大战到水果忍者

掌握了基础特效和物理模拟后,我们可以尝试实现完整的经典游戏。这部分案例将展示如何用AI辅助构建游戏的核心玩法。

7.2.1 太空大战:飞船射击小行星

太空大战(类似《小蜜蜂》或《太空侵略者》)是2D射击游戏的经典原型。我们将实现玩家控制飞船,发射子弹击碎小行星的功能。

与ChatGPT讨论架构

首先向ChatGPT描述游戏需求:"用Pygame实现一个太空大战游戏,包含以下要素:

  1. 玩家飞船在屏幕底部,可以左右移动并发射子弹
  2. 小行星从屏幕顶部随机位置生成,向下运动
  3. 子弹击中行星时,行星碎裂成更小的碎片(如果碎片足够小则消失)
  4. 玩家与行星碰撞则游戏结束
  5. 分数显示
  6. 随着分数增加,难度逐渐提高"

ChatGPT会建议采用实体-组件架构,将游戏对象分为PlayerBulletAsteroid等类。我们使用Copilot逐步实现这些类。

核心类实现

import pygame
import math
import random

# 初始化
pygame.init()
WIDTH, HEIGHT = 900, 700
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("太空大战 - AI辅助开发")
clock = pygame.time.Clock()

# 颜色
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)

class Player:
    """
    玩家飞船类
    """
    
    def __init__(self):
        self.width = 40
        self.height = 30
        self.x = WIDTH // 2 - self.width // 2
        self.y = HEIGHT - 80
        self.speed = 5
        self.bullets = []
        self.shootCooldown = 0
        self.shootDelay = 10  # 帧数间隔
        
    def Update(self, keys):
        """更新玩家状态"""
        
        # 左右移动
        if keys[pygame.K_LEFT] and self.x > 0:
            self.x -= self.speed
        if keys[pygame.K_RIGHT] and self.x < WIDTH - self.width:
            self.x += self.speed
            
        # 射击
        if self.shootCooldown > 0:
            self.shootCooldown -= 1
            
        if keys[pygame.K_SPACE] and self.shootCooldown == 0:
            self.Shoot()
            self.shootCooldown = self.shootDelay
            
        # 更新子弹
        for bullet in self.bullets[:]:
            bullet.Update()
            if bullet.y < 0:
                self.bullets.remove(bullet)
                
    def Shoot(self):
        """发射子弹"""
        
        bullet = Bullet(self.x + self.width // 2 - 2, self.y)
        self.bullets.append(bullet)
        
    def Draw(self, surface):
        """绘制玩家"""
        
        # 绘制飞船(三角形)
        points = [
            (self.x + self.width // 2, self.y),
            (self.x, self.y + self.height),
            (self.x + self.width, self.y + self.height)
        ]
        pygame.draw.polygon(surface, WHITE, points, 0)
        
        # 绘制子弹
        for bullet in self.bullets:
            bullet.Draw(surface)
            
    def GetRect(self):
        """获取碰撞矩形"""
        
        return pygame.Rect(self.x, self.y, self.width, self.height)


class Bullet:
    """
    子弹类
    """
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.width = 4
        self.height = 10
        self.speed = 8
        
    def Update(self):
        self.y -= self.speed
        
    def Draw(self, surface):
        pygame.draw.rect(surface, YELLOW, (self.x, self.y, self.width, self.height))
        
    def GetRect(self):
        return pygame.Rect(self.x, self.y, self.width, self.height)


class Asteroid:
    """
    小行星类
    """
    
    def __init__(self, x=None, y=None, size=3):
        """
        初始化小行星
        
        Parameters:
            x, y (float, optional): 位置
            size (int): 大小等级 3=大, 2=中, 1=小
        """
        
        if x is None:
            self.x = random.randint(50, WIDTH - 50)
        else:
            self.x = x
            
        if y is None:
            self.y = random.randint(-100, -40)
        else:
            self.y = y
            
        self.size = size
        
        # 根据大小设置属性
        if size == 3:
            self.radius = 30
            self.speed = random.uniform(1.0, 2.0)
            self.points = 20
        elif size == 2:
            self.radius = 20
            self.speed = random.uniform(1.5, 2.5)
            self.points = 50
        else:
            self.radius = 10
            self.speed = random.uniform(2.0, 3.0)
            self.points = 100
            
        # 随机运动方向(略微左右飘移)
        self.dx = random.uniform(-0.5, 0.5)
        self.dy = self.speed
        
        # 随机旋转角度
        self.angle = 0
        self.rotationSpeed = random.uniform(-2, 2)
        
        # 生成随机形状的顶点(使小行星看起来不规则)
        self.vertices = []
        vertexCount = random.randint(6, 10)
        for i in range(vertexCount):
            angle = (i / vertexCount) * math.pi * 2
            r = self.radius * random.uniform(0.7, 1.3)
            self.vertices.append((
                math.cos(angle) * r,
                math.sin(angle) * r
            ))
            
    def Update(self):
        """更新位置"""
        
        self.x += self.dx
        self.y += self.dy
        self.angle += self.rotationSpeed
        
        # 如果移出屏幕,重新从顶部出现
        if self.y > HEIGHT + 100:
            self.y = random.randint(-100, -40)
            self.x = random.randint(50, WIDTH - 50)
            
    def Draw(self, surface):
        """绘制小行星"""
        
        # 旋转顶点
        rotatedVerts = []
        cosA = math.cos(math.radians(self.angle))
        sinA = math.sin(math.radians(self.angle))
        
        for vx, vy in self.vertices:
            rx = vx * cosA - vy * sinA
            ry = vx * sinA + vy * cosA
            rotatedVerts.append((self.x + rx, self.y + ry))
            
        pygame.draw.polygon(surface, (150, 150, 150), rotatedVerts, 2)
        
    def GetRect(self):
        """获取碰撞矩形(简化用圆形检测,但返回矩形供sprite.collide使用)"""
        
        return pygame.Rect(self.x - self.radius, self.y - self.radius, 
                          self.radius * 2, self.radius * 2)
                          
    def CheckCollision(self, other):
        """圆形碰撞检测"""
        
        dx = self.x - other.x
        dy = self.y - other.y
        distance = math.sqrt(dx * dx + dy * dy)
        return distance < self.radius + other.radius


class Game:
    """
    游戏主类
    """
    
    def __init__(self):
        self.player = Player()
        self.asteroids = []
        self.score = 0
        self.level = 1
        self.gameOver = False
        self.font = pygame.font.SysFont("Arial", 24)
        self.bigFont = pygame.font.SysFont("Arial", 48)
        
        # 初始化小行星
        for _ in range(5):
            self.asteroids.append(Asteroid(size=3))
            
    def Update(self):
        """更新游戏状态"""
        
        if self.gameOver:
            return
            
        keys = pygame.key.get_pressed()
        self.player.Update(keys)
        
        # 更新小行星
        for asteroid in self.asteroids[:]:
            asteroid.Update()
            
            # 检查与子弹的碰撞
            for bullet in self.player.bullets[:]:
                if asteroid.CheckCollision(bullet):
                    # 分数增加
                    self.score += asteroid.points
                    
                    # 子弹消失
                    if bullet in self.player.bullets:
                        self.player.bullets.remove(bullet)
                        
                    # 小行星碎裂
                    if asteroid.size > 1:
                        # 生成两个更小的碎片
                        for _ in range(2):
                            newSize = asteroid.size - 1
                            newAsteroid = Asteroid(
                                asteroid.x + random.randint(-20, 20),
                                asteroid.y + random.randint(-20, 20),
                                newSize
                            )
                            self.asteroids.append(newAsteroid)
                            
                    # 移除原小行星
                    if asteroid in self.asteroids:
                        self.asteroids.remove(asteroid)
                    break
                    
            # 检查与玩家的碰撞
            if asteroid.CheckCollision(self.player):
                self.gameOver = True
                
        # 控制小行星数量
        if len(self.asteroids) < 5 + self.level * 2:
            if random.random() < 0.01:  # 1%概率生成新小行星
                size = random.choices([3, 2, 1], weights=[0.5, 0.3, 0.2])[0]
                self.asteroids.append(Asteroid(size=size))
                
        # 难度随分数增加
        self.level = 1 + self.score // 500
                
    def Draw(self, surface):
        """绘制游戏"""
        
        surface.fill(BLACK)
        
        if not self.gameOver:
            self.player.Draw(surface)
            
            for asteroid in self.asteroids:
                asteroid.Draw(surface)
                
            # 显示分数和等级
            scoreText = self.font.render(f"分数: {self.score}", True, WHITE)
            levelText = self.font.render(f"等级: {self.level}", True, WHITE)
            surface.blit(scoreText, (10, 10))
            surface.blit(levelText, (10, 40))
        else:
            # 游戏结束画面
            gameOverText = self.bigFont.render("游戏结束", True, RED)
            finalScoreText = self.font.render(f"最终分数: {self.score}", True, WHITE)
            restartText = self.font.render("按R重新开始", True, WHITE)
            
            textRect = gameOverText.get_rect(center=(WIDTH//2, HEIGHT//2 - 50))
            scoreRect = finalScoreText.get_rect(center=(WIDTH//2, HEIGHT//2))
            restartRect = restartText.get_rect(center=(WIDTH//2, HEIGHT//2 + 50))
            
            surface.blit(gameOverText, textRect)
            surface.blit(finalScoreText, scoreRect)
            surface.blit(restartText, restartRect)
            
    def Reset(self):
        """重置游戏"""
        
        self.__init__()


def main():
    game = Game()
    
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_r and game.gameOver:
                    game.Reset()
                    
        game.Update()
        game.Draw(screen)
        
        pygame.display.flip()
        clock.tick(60)
        
    pygame.quit()


if __name__ == "__main__":
    main()

AI辅助的关键点

  1. 小行星的不规则形状:ChatGPT建议用随机顶点生成不规则多边形,使小行星看起来更自然。
  2. 碎裂逻辑:子弹击中较大行星时生成两个小行星,Copilot自动补全了递归碎裂的代码。
  3. 难度平衡:根据分数动态调整小行星生成概率和数量。

这个游戏已经具备了基本的可玩性,可以在此基础上继续添加特效、音效等。

7.2.2 粒子爆炸效果

在太空大战的基础上,我们为小行星碎裂添加粒子爆炸效果。这需要实现一个简单的粒子系统,在碎裂位置生成大量短暂存在的小粒子。

粒子类实现

class ExplosionParticle:
    """
    爆炸粒子类
    """
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.vx = random.uniform(-3, 3)
        self.vy = random.uniform(-3, 3)
        self.size = random.randint(2, 5)
        self.color = random.choice([(255, 200, 0), (255, 100, 0), (200, 200, 200)])
        self.life = random.randint(20, 40)
        self.maxLife = self.life
        
    def Update(self):
        self.x += self.vx
        self.y += self.vy
        self.vy += 0.1  # 重力
        self.life -= 1
        
    def Draw(self, surface):
        if self.life > 0:
            alpha = int(255 * self.life / self.maxLife)
            color = (self.color[0], self.color[1], self.color[2])
            pygame.draw.circle(surface, color, (int(self.x), int(self.y)), self.size)
            
    def IsAlive(self):
        return self.life > 0

Asteroid碎裂时,生成一批粒子加入游戏主循环:

# 在Game类中添加
self.particles = []

# 在小行星碎裂时
for _ in range(15):
    self.particles.append(ExplosionParticle(asteroid.x, asteroid.y))
    
# 在Update中更新粒子
for particle in self.particles[:]:
    particle.Update()
    if not particle.IsAlive():
        self.particles.remove(particle)

7.2.3 音乐播放器与游戏的结合

第6.5节提到"基于pygame的音乐播放器",这展示了游戏引擎的另一个用途:制作音乐可视化工具。我们可以结合之前的粒子系统,实现一个随音乐节奏变化的视觉效果。

核心思路

  1. 使用pygame.mixer.music加载并播放音乐
  2. 分析音频数据(需要numpyaudioop)获取频谱
  3. 根据频谱强度调整粒子系统的参数(颜色、速度、数量)

由于音频分析较为复杂,这部分可以先用模拟数据演示,然后逐步完善。

7.2.4 《水果忍者》的简化实现

《水果忍者》的核心玩法是手指划过屏幕切割抛出的水果。在PC上用鼠标模拟手指,实现简化版本。

需求分解

  1. 水果从底部向上抛出,呈抛物线运动
  2. 玩家用鼠标画线,检测线与水果的碰撞
  3. 切割成功时水果碎裂(粒子效果)
  4. 炸弹混在其中,切到炸弹游戏结束

使用Copilot生成水果类

class Fruit:
    """
    水果类
    """
    
    def __init__(self, fruitType="apple"):
        self.type = fruitType
        self.radius = 25
        
        # 初始位置(底部随机)
        self.x = random.randint(50, WIDTH - 50)
        self.y = HEIGHT - 20
        
        # 抛物线运动参数
        self.vx = random.uniform(-2, 2)
        self.vy = random.uniform(-12, -8)
        self.gravity = 0.4
        
        # 颜色
        if fruitType == "apple":
            self.color = (255, 0, 0)
        elif fruitType == "banana":
            self.color = (255, 255, 0)
        elif fruitType == "watermelon":
            self.color = (0, 255, 0)
        else:  # bomb
            self.color = (50, 50, 50)
            self.radius = 20
            
        # 是否被切割
        self.cut = False
        
    def Update(self):
        """更新位置"""
        
        self.x += self.vx
        self.y += self.vy
        self.vy += self.gravity
        
    def Draw(self, surface):
        """绘制水果"""
        
        if not self.cut:
            pygame.draw.circle(surface, self.color, (int(self.x), int(self.y)), self.radius)
            # 高光
            highlightPos = (int(self.x - self.radius * 0.3), int(self.y - self.radius * 0.3))
            pygame.draw.circle(surface, (255, 255, 255), highlightPos, self.radius // 4)
            
    def IsOffscreen(self):
        """判断是否移出屏幕"""
        
        return self.y > HEIGHT + 100 or self.x < -100 or self.x > WIDTH + 100
        
    def CheckSlice(self, lineStart, lineEnd):
        """
        检查是否被线段切割
        
        Parameters:
            lineStart, lineEnd (tuple): 鼠标移动的线段起点和终点
            
        Returns:
            bool: 是否被切割
        """
        
        if self.cut:
            return False
            
        # 简化为检测圆心到线段的距离是否小于半径
        # 这里使用点到线段距离公式
        x1, y1 = lineStart
        x2, y2 = lineEnd
        
        # 计算点到线段的最短距离
        dx = x2 - x1
        dy = y2 - y1
        
        # 线段长度平方
        l2 = dx * dx + dy * dy
        
        if l2 == 0:
            # 线段退化为点
            dist = math.sqrt((self.x - x1) ** 2 + (self.y - y1) ** 2)
            return dist < self.radius
            
        # 计算投影参数t
        t = ((self.x - x1) * dx + (self.y - y1) * dy) / l2
        t = max(0, min(1, t))
        
        # 投影点
        projX = x1 + t * dx
        projY = y1 + t * dy
        
        # 计算距离
        dist = math.sqrt((self.x - projX) ** 2 + (self.y - projY) ** 2)
        
        return dist < self.radius

鼠标画线的实现需要记录每一帧的鼠标位置,连接成线段:

# 在Game类中
self.mouseTrail = []  # 存储鼠标历史位置
self.trailLength = 5  # 保留最近5帧

# 在事件处理中
if event.type == pygame.MOUSEMOTION:
    self.mouseTrail.append(event.pos)
    if len(self.mouseTrail) > self.trailLength:
        self.mouseTrail.pop(0)
        
# 在切割检测中
for i in range(len(self.mouseTrail) - 1):
    for fruit in self.fruits:
        if fruit.CheckSlice(self.mouseTrail[i], self.mouseTrail[i + 1]):
            fruit.cut = True
            # 生成粒子等

7.2.5 《雷电》风格的纵版射击游戏

《雷电》是经典的纵版STG游戏。实现完整版工作量较大,但我们可以用AI构建核心框架:

  1. 玩家飞机:移动、射击(多种子弹类型)
  2. 敌机:多种类型(直线、追踪、发射子弹)
  3. 碰撞检测:玩家子弹 vs 敌机,敌机子弹 vs 玩家
  4. 道具系统:增强火力、保险等

架构建议

使用组件模式,定义Entity基类,派生出PlayerEnemyBulletPowerUp等。使用Sprite组管理碰撞。

Copilot生成子弹模式

class Bullet:
    def __init__(self, x, y, angle, speed, damage, owner="player"):
        self.x = x
        self.y = y
        self.vx = math.cos(angle) * speed
        self.vy = math.sin(angle) * speed
        self.damage = damage
        self.owner = owner  # "player" 或 "enemy"
        
    def Update(self):
        self.x += self.vx
        self.y += self.vy

敌机AI

class Enemy:
    def __init__(self, enemyType="basic"):
        self.type = enemyType
        
        if enemyType == "basic":
            # 直线下飞
            self.pattern = "straight"
            self.speed = 3
        elif enemyType == "zigzag":
            # 之字形
            self.pattern = "zigzag"
            self.speed = 2
            self.amplitude = 50
            self.frequency = 0.05
            self.startX = self.x
        elif enemyType == "shooter":
            # 会射击
            self.pattern = "straight"
            self.speed = 1.5
            self.shootCooldown = 0
            self.shootDelay = 60
            
    def Update(self):
        if self.pattern == "straight":
            self.y += self.speed
        elif self.pattern == "zigzag":
            self.y += self.speed
            self.x = self.startX + math.sin(self.y * self.frequency) * self.amplitude
            
    def CanShoot(self):
        if self.type == "shooter":
            if self.shootCooldown <= 0:
                self.shootCooldown = self.shootDelay
                return True
            else:
                self.shootCooldown -= 1
        return False

7.3 AI游戏开发的行业现状与未来展望

7.3.1 工业级AI游戏开发工具

2025年9月,掌趣科技联合蓝亚盒子发布了LayaIdea,这是业界首个真正可商用的3D游戏AIGC开发工具。它实现了从策划文档到完整3D游戏项目的自动化生成,主要聚焦于两大场景:

  1. 互动试玩广告:将开发周期从一周缩短至一天,大幅节省程序开发成本,同时保证生成的代码符合各大广告平台规范。
  2. 游戏原型验证:帮助游戏制作人快速完成玩法验证、试错、迭代修改,降低试错成本。

网易则接入通义千问,打造游戏自动测试系统,帮助游戏研发提效50%。其伏羲实验室联合阿里云全球首发了游戏Copilot队友功能,AI队友能自主完成跑图、搜寻物资、参与战斗,还能通过自然语音与玩家互动。

7.3.2 100% AI生成游戏的尝试与争议

2025年底,开发者Grolaf发布了《Codex Mortis》,宣称该游戏100%由AI驱动:美术素材由ChatGPT创作,动画通过Claude Code编写的着色器实现,代码完全由TypeScript编写,未使用任何传统游戏引擎。

开发者的体会是:"与传统应用开发相比,这种方式对精神消耗要小得多,感觉就像给建筑工人装上了外骨骼。"然而,游戏在Steam上获得"褒贬不一"的评价,评论区充斥着负面声音,反映出市场对纯AI生成内容的态度仍存分歧。

7.3.3 AI辅助游戏开发的最佳实践

综合本章案例和行业动态,可以总结出AI辅助游戏开发的有效模式:

  1. 原型快速验证:用ChatGPT讨论设计思路,生成可运行的原型代码,快速测试核心玩法。
  2. 分步实现复杂功能:将游戏分解为多个模块(粒子系统、物理引擎、AI行为),逐个用AI生成并测试。
  3. 代码审查与优化:将生成的代码发给ChatGPT进行审查,发现潜在问题并提出优化建议。
  4. 资源生成:使用DALL-E、Midjourney等工具生成游戏素材。有开发者反馈,保持风格一致性是最大挑战,需要精心设计提示词。
  5. 调试与完善:将错误信息贴给AI,快速定位并修复问题。学生开发者反映,“利用AIGC工具来debug解决代码中报错的问题十分省事省心”。

7.4 本章小结

通过本章的八个实战案例,我们从简单的视觉特效开始,逐步构建了完整的游戏项目,探索了AI在游戏开发全流程中的应用:

  • 旋转星系移动纹理展示了AI生成粒子系统和动态背景的能力
  • 随机运动的小球实现了基础的物理模拟
  • 太空大战构建了完整的射击游戏框架,包含碰撞检测、分数系统和难度平衡
  • 粒子爆炸增强了游戏的视觉反馈
  • 水果忍者雷电则展示了更复杂玩法的实现思路

这些案例证明:AI辅助工具不仅能生成代码片段,还能帮助开发者理解游戏开发的核心理念——状态管理、物理模拟、碰撞检测、粒子系统等。正如一位学生在开发心得中写道:“用AIGC生成程序主框架十分好用,如果遇到不懂的地方让AIGC工具来解释,十分高效易懂。”

游戏开发曾经是编程中最具挑战性的领域之一,需要掌握图形学、物理模拟、交互设计等多方面知识。AI辅助编程正在降低这一门槛,让创意能够快速转化为可玩的游戏。无论你是希望开发独立游戏的学生,还是需要快速验证原型的专业人士,本章介绍的方法都能帮助你大幅提升效率,将更多精力投入到游戏创意和玩法设计上。

在下一章,我们将探讨AI在办公自动化领域的应用,展示如何用自然语言生成处理Excel、Word、PPT的代码,进一步提升工作效率。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐