nkds

导航

 

MonkeyCode游戏开发实战指南:AI赋能游戏创作的全新范式

前言

游戏开发是编程领域中最具创造性和挑战性的方向之一。从2D休闲小游戏到3A大作,从独立开发者到大型工作室,游戏开发涉及图形渲染、物理引擎、AI行为、网络同步、音频处理等众多复杂技术。MonkeyCode作为AI编程助手,正在为游戏开发者带来革命性的效率提升。本文将深入探讨MonkeyCode在游戏开发各环节的实际应用。


一、游戏开发的技术栈全景

1.1 现代游戏开发的复杂性

┌─────────────────────────────────────────────────────────────────┐
│                  游戏开发技术栈全景图                              │
├──────────────┬──────────────┬──────────────┬─────────────────────┤
│   图形渲染    │   物理模拟    │    AI系统     │     网络同步         │
├──────────────┼──────────────┼──────────────┼─────────────────────┤
│ OpenGL/Vulkan │ Box2D/PhysX │ 行为树(BT)    │ WebSocket/UDP       │
│ DirectX 12    │ 碰撞检测     │ 状态机(FSM)   │ 帧同步/状态同步      │
│ 着色器语言    │ 刚体/软体    │ 路径寻路(A*)  │ 预测插值            │
│ PBR材质系统   │ 流体模拟     │ 群体行为      │ 延迟补偿            │
│ 后处理效果    │ 布料模拟     │ 决策树        │ 快照回滚            │
├──────────────┼──────────────┼──────────────┼─────────────────────┤
│   音频系统    │   UI/UX     │   数据管理    │     工具链           │
├──────────────┼──────────────┼──────────────┼─────────────────────┤
│ FMOD/Wwise   │ ImGui/Qt    │ SQLite/Mongo  │ 版本控制(Git)       │
│ 3D空间音频    │ 动画系统     │ 序列化/反序列化│ CI/CD流水线         │
│ 实时混音      │ 本地化(i18n) │ 资源热更新    │ 自动化构建           │
│ HRTF/环境音效 │ 输入处理     │ 存档系统      │ 性能分析工具         │
└──────────────┴──────────────┴──────────────┴─────────────────────┘

1.2 MonkeyCode在游戏开发中的价值定位

开发阶段 传统痛点 MonkeyCode解决方案 效率提升
原型设计 搭建基础框架耗时久 一键生成项目骨架+核心循环 5x
玩法实现 复杂逻辑调试困难 自然语言描述→可运行代码 4-8x
AI编写 行为树/状态机易出错 结构化生成+自动验证 6x
性能优化 定位瓶颈耗时长 智能分析+针对性优化建议 3-5x
Bug修复 复现困难、排查慢 日志分析→根因定位→自动修复 4x
文档编写 开发者最不愿做的事 自动生成API文档+设计文档 10x

二、实战案例一:2D平台跳跃游戏完整实现

2.1 项目概述

使用Python + Pygame实现一个完整的2D平台跳跃游戏,包含:

  • 物理引擎(重力、碰撞、摩擦力)
  • 关卡编辑系统
  • 敌人AI(巡逻、追踪、攻击)
  • 粒子特效系统
  • 音效与背景音乐
  • 存档/读档功能

2.2 MonkeyCode生成的核心代码

"""
2D平台跳跃游戏 - MonkeyCode完整生成
技术栈: Python 3.11 + Pygame 2.5 + Pytmx(关卡加载)
"""

from __future__ import annotations

import json
import math
import os
import random
import sys
import time
from dataclasses import dataclass, field
from enum import Enum, IntEnum, auto
from pathlib import Path
from typing import (
    Any,
    Callable,
    Generator,
    Optional,
    Protocol,
    TypeVar,
)

import pygame
from pygame import gfxdraw
from pygame.locals import *

# ──────────────────────────────────────────────
# 配置常量
# ──────────────────────────────────────────────

@dataclass(frozen=True)
class GameConfig:
    """游戏配置 - 所有魔法数字的单一来源"""
    SCREEN_WIDTH: int = 1200
    SCREEN_HEIGHT: int = 700
    FPS: int = 60
    TILE_SIZE: int = 40
    
    # 物理
    GRAVITY: float = 0.8
    MAX_FALL_SPEED: float = 15.0
    PLAYER_SPEED: float = 5.0
    JUMP_FORCE: float = -14.0
    JUMP_CUT_MULTIPLIER: float = 0.4
    COYOTE_TIME: float = 0.1  # 土狼时间(秒)
    JUMP_BUFFER: float = 0.12 # 跳跃缓冲(秒)
    
    # 碰撞
    GROUND_FRICTION: float = 0.82
    AIR_RESISTANCE: float = 0.92
    
    # 颜色 (RGB)
    SKY_TOP: tuple[int,int,int] = (135, 206, 235)
    SKY_BOTTOM: tuple[int,int,int] = (255, 200, 150)
    GROUND_COLOR: tuple[int,int,int] = (101, 67, 33)
    PLATFORM_COLOR: tuple[int,int,int] = (139, 90, 43)
    
    # 文件路径
    ASSETS_DIR: str = "assets"
    SAVE_DIR: str = "saves"
    LEVELS_DIR: str = "levels"

CONFIG = GameConfig()


# ──────────────────────────────────────────────
# 向量数学库
# ──────────────────────────────────────────────

@dataclass
class Vec2:
    """二维向量"""
    x: float = 0.0
    y: float = 0.0
    
    def __add__(self, other: 'Vec2') -> 'Vec2':
        return Vec2(self.x + other.x, self.y + other.y)
    
    def __sub__(self, other: 'Vec2') -> 'Vec2':
        return Vec2(self.x - other.x, self.y - other.y)
    
    def __mul__(self, scalar: float) -> 'Vec2':
        return Vec2(self.x * scalar, self.y * scalar)
    
    def __rmul__(self, scalar: float) -> 'Vec2':
        return self.__mul__(scalar)
    
    def __neg__(self) -> 'Vec2':
        return Vec2(-self.x, -self.y)
    
    @property
    def length(self) -> float:
        return math.sqrt(self.x**2 + self.y**2)
    
    @property
    def normalized(self) -> 'Vec2':
        l = self.length
        return Vec2(self.x/l, self.y/l) if l > 0 else Vec2()
    
    def dot(self, other: 'Vec2') -> float:
        return self.x*other.x + self.y*other.y
    
    def distance_to(self, other: 'Vec2') -> float:
        return (self - other).length
    
    def as_tuple(self) -> tuple[int, int]:
        return (int(self.x), int(self.y))
    
    def as_int_rect(self, w: int, h: int) -> pygame.Rect:
        return pygame.Rect(int(self.x), int(self.y), w, h)


# ──────────────────────────────────────────────
# 输入管理系统
# ──────────────────────────────────────────────

class InputManager:
    """统一输入管理 - 支持键盘、手柄、触摸"""
    
    def __init__(self):
        self._key_states: dict[str, bool] = {}
        self._key_pressed: dict[str, bool] = {}
        self._key_released: dict[str, bool] = {}
        self._prev_keys: set[int] = set()
        
        # 操作映射 (支持重映射)
        self.action_map: dict[str, list[int]] = {
            'left': [K_LEFT, K_a],
            'right': [K_RIGHT, K_d],
            'jump': [K_SPACE, K_w, K_UP],
            'attack': [K_z, K_j],
            'dash': [K_LSHIFT, K_k],
            'pause': [K_ESCAPE, K_p],
        }
    
    def update(self) -> None:
        """每帧调用,更新输入状态"""
        current_keys = pygame.key.get_pressed()
        current_set = set(current_keys.nonzero()[0].tolist()) if hasattr(current_keys, 'nonzero') else set()
        
        for action, keys in self.action_map.items():
            is_held = any(current_keys[k] for k in keys)
            was_held = any(k in self._prev_keys for k in keys)
            
            self._key_states[action] = is_held
            self._key_pressed[action] = is_held and not was_held
            self._key_released[action] = not is_held and was_held
        
        self._prev_keys = current_set
    
    def is_held(self, action: str) -> bool:
        return self._key_states.get(action, False)
    
    def is_pressed(self, action: str) -> bool:
        return self._key_pressed.get(action, False)
    
    def is_released(self, action: str) -> bool:
        return self._key_released.get(action, False)


# ──────────────────────────────────────────────
# 粒子系统
# ──────────────────────────────────────────────

@dataclass
class Particle:
    pos: Vec2; vel: Vec2; color: tuple[int,int,int]
    lifetime: float; max_lifetime: float
    size: float = 3.0; gravity: float = 0.1; alpha_decay: float = 3.0


class ParticleSystem:
    """高性能粒子系统 - 对象池模式"""
    
    MAX_PARTICLES = 500
    
    def __init__(self):
        self.particles: list[Particle] = []
    
    def emit(self, pos: Vec2, count: int = 10,
             color: tuple[int,int,int] = (255,200,50),
             speed_range: tuple[float,float] = (1.0, 4.0),
             angle_range: tuple[float,float] = (0, 360),
             lifetime_range: tuple[float,float] = (0.3, 0.8),
             size_range: tuple[float,float] = (2.0, 5.0)) -> None:
        if len(self.particles) >= self.MAX_PARTICLES:
            return
        
        for _ in range(min(count, self.MAX_PARTICLES - len(self.particles))):
            angle = math.radians(random.uniform(*angle_range))
            speed = random.uniform(*speed_range)
            vel = Vec2(math.cos(angle)*speed, math.sin(angle)*speed)
            
            self.particles.append(Particle(
                pos=Vec2(pos.x, pos.y), vel=vel, color=color,
                lifetime=random.uniform(*lifetime_range),
                max_lifetime=self.particles[-1].lifetime if self.particles else 1.0,
                size=random.uniform(*size_range),
            ))
    
    def emit_burst(self, pos: Vec2, color: tuple[int,int,int], count: int = 30) -> None:
        self.emit(pos, count, color, (2.0, 7.0), (0, 360), (0.2, 0.6), (2.0, 6.0))
    
    def emit_trail(self, pos: Vec2, color: tuple[int,int,int]) -> None:
        self.emit(pos, 3, color, (0.5, 2.0), (80, 100), (0.15, 0.35), (1.5, 3.5))
    
    def update(self, dt: float) -> None:
        alive = []
        for p in self.particles:
            p.lifetime -= dt
            if p.lifetime <= 0:
                continue
            p.vel.y += p.gravity
            p.pos = p.pos + p.vel * dt
            alive.append(p)
        self.particles = alive
    
    def draw(self, surface: pygame.Surface) -> None:
        for p in self.particles:
            alpha = max(0, min(255, int(255 * (p.lifetime / p.max_lifetime) * p.alpha_decay)))
            size = max(1, int(p.size * (p.lifetime / p.max_lifetime)))
            color = (*p.color[:3], alpha) if len(p.color) == 3 else p.color
            
            s = pygame.Surface((size*2, size*2), pygame.SRCALPHA)
            pygame.draw.circle(s, (*color[:3], min(255,alpha)), (size, size), size)
            surface.blit(s, (int(p.x-size), int(p.y-size)))


# ──────────────────────────────────────────────
# 相机系统
# ──────────────────────────────────────────────

class Camera:
    """平滑跟随相机 - Lerp插值"""
    
    def __init__(self, width: int, height: int):
        self.rect = pygame.Rect(0, 0, width, height)
        self.target: Optional[Vec2] = None
        self.lerp_speed: float = 5.0
        self.offset = Vec2()
        # 死区 (防止微小抖动)
        self.deadzone_x: float = 50
        self.deadzone_y: float = 30
    
    def follow(self, target: Vec2) -> None:
        self.target = target
    
    def update(self, dt: float) -> None:
        if not self.target:
            return
        
        target_x = self.target.x - self.rect.width // 2
        target_y = self.target.y - self.rect.height // 2
        
        # 应用死区
        dx = abs(target_x - self.rect.x)
        dy = abs(target_y - self.rect.y)
        
        if dx > self.deadzone_x:
            self.rect.x += (target_x - self.rect.x) * self.lerp_speed * dt
        if dy > self.deadzone_y:
            self.rect.y += (target_y - self.rect.y) * self.lerp_speed * dt
    
    def apply(self, entity: pygame.Rect) -> pygame.Rect:
        """将世界坐标转换为屏幕坐标"""
        return entity.copy().move(-self.rect.x, -self.rect.y)
    
    def apply_pos(self, pos: Vec2) -> Vec2:
        return Vec2(pos.x - self.rect.x, pos.y - self.rect.y)


# ──────────────────────────────────────────────
# 实体基类
# ──────────────────────────────────────────────

class Direction(IntEnum):
    LEFT = -1; RIGHT = 1


class EntityState(Enum):
    IDLE = auto(); RUN = auto(); JUMP = auto(); FALL = auto()
    ATTACK = auto(); DASH = auto(); HURT = auto(); DEAD = auto()


@dataclass
class EntityStats:
    hp: int = 100; max_hp: int = 100
    attack_power: int = 10; defense: int = 0
    move_speed: float = 3.0; jump_force: float = -12.0


class Entity:
    """游戏实体基类"""
    
    def __init__(self, x: float, y: float, w: int, h: int):
        self.pos = Vec2(x, y)
        self.vel = Vec2()
        self.size = (w, h)
        self.direction = Direction.RIGHT
        self.state = EntityState.IDLE
        self.stats = EntityStats()
        self.is_grounded: bool = False
        self.is_alive: bool = True
        self.invincible_timer: float = 0.0
        self.animation_timer: float = 0.0
    
    @property
    def rect(self) -> pygame.Rect:
        return self.pos.as_int_rect(*self.size)
    
    def take_damage(self, amount: int) -> None:
        if self.invincible_timer > 0 or not self.is_alive:
            return
        actual_damage = max(1, amount - self.stats.defense)
        self.stats.hp -= actual_damage
        self.invincible_timer = 1.0
        self.state = EntityState.HURT
        if self.stats.hp <= 0:
            self.stats.hp = 0; self.is_alive = False; self.state = EntityState.DEAD
    
    def heal(self, amount: int) -> None:
        self.stats.hp = min(self.stats.max_hp, self.stats.hp + amount)
    
    def update(self, dt: float, platforms: list['Platform']) -> None:
        raise NotImplementedError
    
    def draw(self, surface: pygame.Surface, camera: Camera) -> None:
        raise NotImplementedError


# ──────────────────────────────────────────────
# 玩家类
# ──────────────────────────────────────────────

class Player(Entity):
    """玩家角色 - 包含完整的物理和状态机"""
    
    ANIMATION_SPEED = 8
    
    def __init__(self, x: float, y: float):
        super().__init__(x, y, 32, 48)
        self.stats = EntityStats(hp=100, attack_power=20, move_speed=CONFIG.PLAYER_SPEED,
                                  jump_force=CONFIG.JUMP_FORCE)
        self.input_mgr: Optional[InputManager] = None
        self.coyote_timer: float = 0.0
        self.jump_buffer_timer: float = 0.0
        self.dash_cooldown: float = 0.0
        self.attack_cooldown: float = 0.0
        self.dash_timer: float = 0.0
        self.dash_dir: Vec2 = Vec2()
        self.jump_count: int = 0
        self.max_jumps: int = 2  # 二段跳
        self.face_direction: int = 1
        
    def bind_input(self, input_mgr: InputManager) -> None:
        self.input_mgr = input_mgr
    
    def _handle_input(self) -> None:
        if not self.input_mgr:
            return
        
        # 冲刺状态特殊处理
        if self.dash_timer > 0:
            return
        
        # 水平移动
        move_x = 0
        if self.input_mgr.is_held('left'):
            move_x -= 1; self.face_direction = -1; self.direction = Direction.LEFT
        if self.input_mgr.is_held('right'):
            move_x += 1; self.face_direction = 1; self.direction = Direction.RIGHT
        
        friction = CONFIG.GROUND_FRICTION if self.is_grounded else CONFIG.AIR_RESISTANCE
        if move_x != 0:
            self.vel.x = move_x * self.stats.move_speed
        else:
            self.vel.x *= friction
        
        # 跳跃 (土狼时间 + 缓冲)
        if self.input_mgr.is_pressed('jump'):
            self.jump_buffer_timer = CONFIG.JUMP_BUFFER
        
        if self.jump_buffer_timer > 0:
            if self.coyote_timer > 0 or self.jump_count < self.max_jumps:
                self.vel.y = self.stats.jump_force
                self.jump_count += 1
                self.is_grounded = False
                self.jump_buffer_timer = 0
                self.coyote_timer = 0
                self.state = EntityState.JUMP
        
        # 跳跃切断 (短按跳得低)
        if not self.input_mgr.is_held('jump') and self.vel.y < 0:
            self.vel.y *= CONFIG.JUMP_CUT_MULTIPLIER
        
        # 冲刺
        if self.input_mgr.is_pressed('dash') and self.dash_cooldown <= 0:
            self.dash_timer = 0.15
            self.dash_cooldown = 0.5
            dash_vec = Vec2(self.face_direction * 12, 0)
            if not self.is_grounded: dash_vec.y = -2
            self.dash_dir = dash_vec.normalized
            self.state = EntityState.DASH
            self.invincible_timer = self.dash_timer
        
        # 攻击
        if self.input_mgr.is_pressed('attack') and self.attack_cooldown <= 0:
            self.attack_cooldown = 0.25
            self.state = EntityState.ATTACK
    
    def _apply_physics(self, dt: float, platforms: list['Platform']) -> None:
        # 冲刺期间特殊物理
        if self.dash_timer > 0:
            self.vel = self.dash_dir * 18
            self.dash_timer -= dt
        else:
            # 重力
            if not self.is_grounded:
                self.vel.y += CONFIG.GRAVITY
                self.vel.y = min(self.vel.y, CONFIG.MAX_FALL_SPEED)
        
        # X轴移动+碰撞
        self.pos.x += self.vel.x
        self._handle_collision(platforms, axis='x')
        
        # Y轴移动+碰撞
        self.pos.y += self.vel.y
        was_grounded = self.is_grounded
        self.is_grounded = False
        self._handle_collision(platforms, axis='y')
        
        # 地面检测回调
        if not was_grounded and self.is_grounded:
            self.jump_count = 0
            self.coyote_timer = CONFIG.COYOTE_TIME
        elif self.is_grounded:
            self.coyote_timer = CONFIG.COYOTE_TIME
        else:
            self.coyote_timer = max(0, self.coyote_timer - dt)
        
        # 计时器更新
        self.jump_buffer_timer = max(0, self.jump_buffer_timer - dt)
        self.dash_cooldown = max(0, self.dash_cooldown - dt)
        self.attack_cooldown = max(0, self.attack_cooldown - dt)
        self.invincible_timer = max(0, self.invincible_timer - dt)
    
    def _handle_collision(self, platforms: list['Platform'], axis: str) -> None:
        player_rect = self.rect
        for plat in platforms:
            if not player_rect.colliderect(plat.rect):
                continue
            if axis == 'x':
                if self.vel.x > 0: player_rect.right = plat.rect.left
                elif self.vel.x < 0: player_rect.left = plat.rect.right
                self.vel.x = 0; self.pos.x = float(player_rect.x)
            else:
                if self.vel.y > 0:
                    player_rect.bottom = plat.rect.top
                    self.is_grounded = True
                elif self.vel.y < 0:
                    player_rect.top = plat.rect.bottom
                self.vel.y = 0; self.pos.y = float(player_rect.y)
    
    def _update_state(self) -> None:
        if self.state in (EntityState.HURT, EntityState.DASH, EntityState.ATTACK):
            return
        if not self.is_grounded:
            self.state = EntityState.JUMP if self.vel.y < 0 else EntityState.FALL
        elif abs(self.vel.x) > 0.5:
            self.state = EntityState.RUN
        else:
            self.state = EntityState.IDLE
    
    def update(self, dt: float, platforms: list['Platform'],
               particles: ParticleSystem) -> None:
        self._handle_input()
        self._apply_physics(dt, platforms)
        self._update_state()
        self.animation_timer += dt * self.ANIMATION_SPEED
        
        # 移动粒子
        if self.is_grounded and abs(self.vel.x) > 1:
            particles.emit_trail(Vec2(self.pos.x+self.size[0]/2, self.pos.y+self.size[1]),
                                 (180, 140, 100))
    
    def draw(self, surface: pygame.Surface, camera: Camera) -> None:
        screen_rect = camera.apply(self.rect)
        
        # 闪烁效果 (无敌帧)
        if self.invincible_timer > 0 and int(self.invincible_timer * 15) % 2 == 0:
            return
        
        # 根据状态选择颜色
        colors = {
            EntityState.IDLE: (65, 105, 225),
            EntityState.RUN: (70, 130, 230),
            EntityState.JUMP: (100, 149, 237),
            EntityState.FALL: (135, 206, 250),
            EntityState.ATTACK: (255, 165, 0),
            EntityState.DASH: (0, 191, 255),
            EntityState.HURT: (255, 99, 71),
            EntityState.DEAD: (128, 128, 128),
        }
        color = colors.get(self.state, (65, 105, 225))
        
        # 绘制玩家 (简单矩形表示)
        body_color = color
        pygame.draw.rect(surface, body_color, screen_rect, border_radius=4)
        
        # 眼睛方向
        eye_offset = 5 * self.face_direction
        eye_y = screen_rect.centery - 5
        eye_x = screen_rect.centerx + eye_offset
        pygame.draw.circle(surface, (255, 255, 255), (eye_x, eye_y), 4)
        pygame.draw.circle(surface, (0, 0, 0), (eye_x + self.face_direction, eye_y), 2)


# ──────────────────────────────────────────────
# 平台类
# ──────────────────────────────────────────────

class Platform:
    """平台/地面"""
    
    def __init__(self, x: int, y: int, w: int, h: int,
                 platform_type: str = 'solid',
                 color: Optional[tuple[int,int,int]] = None):
        self.rect = pygame.Rect(x, y, w, h)
        self.type = platform_type  # solid, one_way, moving, breakable
        self.color = color or CONFIG.PLATFORM_COLOR
        self.original_pos = (x, y)
        self.move_range: int = 0
        self.move_speed: float = 0
        self.move_phase: float = 0.0
    
    def set_moving(self, range_x: int, speed: float) -> None:
        self.move_range = range_x; self.move_speed = speed
    
    def update(self, dt: float) -> None:
        if self.move_range > 0:
            self.move_phase += dt * self.move_speed
            offset = int(math.sin(self.move_phase) * self.move_range)
            self.rect.x = self.original_pos[0] + offset
    
    def draw(self, surface: pygame.Surface, camera: Camera) -> None:
        screen_rect = camera.apply(self.rect)
        pygame.draw.rect(surface, self.color, screen_rect)
        # 高光效果
        highlight = pygame.Rect(screen_rect.x, screen_rect.y, screen_rect.width, 3)
        lighter = tuple(min(255, c+40) for c in self.color)
        pygame.draw.rect(surface, lighter, highlight)


# ──────────────────────────────────────────────
# 敌人AI系统
# ──────────────────────────────────────────────

class EnemyType(Enum):
    WALKER = "walker"    # 巡逻兵
    FLYER = "flyer"      # 飞行敌人
    TURRET = "turret"    # 炮塔
    BOSS = "boss"        # Boss


class EnemyState(Enum):
    PATROL = auto(); CHASE = auto(); ATTACK = auto()
    RETURN = auto(); STUNNED = auto(); DEAD = auto()


class Enemy(Entity):
    """敌人实体 - 行为树驱动的AI"""
    
    DETECTION_RANGE: float = 250.0
    ATTACK_RANGE: float = 50.0
    PATROL_SPEED: float = 1.5
    CHASE_SPEED: float = 3.5
    
    def __init__(self, x: float, y: float, enemy_type: EnemyType = EnemyType.WALKER):
        super().__init__(x, y, 36, 36)
        self.enemy_type = enemy_type
        self.ai_state = EnemyState.PATROL
        self.patrol_start = Vec2(x, y)
        self.patrol_end = Vec2(x + 150, y)
        self.patrol_dir = 1
        self.target: Optional[Entity] = None
        self.alert_timer: float = 0.0
        self.stun_timer: float = 0.0
        self.attack_windup: float = 0.0
        self.attack_cooldown: float = 0.0
        
        # 根据类型设置属性
        if enemy_type == EnemyType.WALKER:
            self.stats = EntityStats(hp=30, attack_power=10, move_speed=self.PATROL_SPEED)
        elif enemy_type == EnemyType.FLYER:
            self.stats = EntityStats(hp=20, attack_power=8, move_speed=2.5)
        elif enemy_type == EnemyType.TURRET:
            self.stats = EntityStats(hp=50, attack_power=15, move_speed=0)
        elif enemy_type == EnemyType.BOSS:
            self.stats = EntityStats(hp=300, attack_power=25, move_speed=2.0)
    
    def run_behavior_tree(self, player: Player, dt: float) -> None:
        """简化的行为树决策"""
        dist_to_player = self.pos.distance_to(player.pos)
        
        # 优先级: 死亡 > 眩晕 > 攻击 > 追踪 > 巡逻
        if not self.is_alive:
            self.ai_state = EnemyState.DEAD; return
        
        if self.stun_timer > 0:
            self.stun_timer -= dt; self.ai_state = EnemyState.STUNNED; return
        
        if dist_to_player <= self.ATTACK_RANGE and self.attack_cooldown <= 0:
            self.ai_state = EnemyState.ATTACK; self._execute_attack(player); return
        
        if dist_to_player <= self.DETECTION_RANGE:
            self.target = player; self.ai_state = EnemyState.CHASE; return
        
        if self.target and dist_to_player > self.DETECTION_RANGE * 1.5:
            self.ai_state = EnemyState.RETURN; self.target = None; return
        
        self.ai_state = EnemyState.PATROL
    
    def _execute_attack(self, player: Player) -> None:
        self.attack_windup = 0.3
        self.attack_cooldown = 1.2
        self.state = EntityState.ATTACK
        # 延迟伤害 (给玩家反应时间)
    
    def update_ai_attack(self, player: Player, dt: float) -> None:
        if self.attack_windup > 0:
            self.attack_windup -= dt
            if self.attack_windup <= 0:
                player.take_damage(self.stats.attack_power)
    
    def update(self, dt: float, platforms: list['Platform'], player: Player) -> None:
        self.run_behavior_tree(player, dt)
        self.update_ai_attack(player, dt)
        
        match self.ai_state:
            case EnemyState.PATROL:
                self._patrol(dt)
            case EnemyState.CHASE:
                self._chase(player, dt)
            case EnemyState.RETURN:
                self._return_home(dt)
            case EnemyState.STUNNED:
                pass
            case EnemyState.ATTACK:
                pass
            case EnemyState.DEAD:
                pass
        
        # 物理更新
        self.pos.x += self.vel.x * dt
        self.pos.y += self.vel.y * dt
        
        # 简单地面碰撞
        self.is_grounded = False
        for plat in platforms:
            if self.rect.colliderect(plat.rect):
                if self.vel.y >= 0:
                    self.rect.bottom = plat.rect.top
                    self.pos.y = float(self.rect.y); self.vel.y = 0
                    self.is_grounded = True
        
        if not self.is_grounded:
            self.vel.y += CONFIG.GRAVITY * 0.7
        
        self.attack_cooldown = max(0, self.attack_cooldown - dt)
    
    def _patrol(self, dt: float) -> None:
        target = self.patrol_end if self.patrol_dir > 0 else self.patrol_start
        if self.pos.distance_to(target) < 5:
            self.patrol_dir *= -1
        direction = 1 if self.patrol_dir > 0 else -1
        self.vel.x = self.PATROL_SPEED * direction
        self.direction = Direction.RIGHT if direction > 0 else Direction.LEFT
        self.state = EntityState.RUN
    
    def _chase(self, player: Player, dt: float) -> None:
        if not self.target: return
        dx = self.target.pos.x - self.pos.x
        direction = 1 if dx > 0 else -1
        self.vel.x = self.CHASE_SPEED * direction
        self.direction = Direction.RIGHT if direction > 0 else Direction.LEFT
        self.state = EntityState.RUN
    
    def _return_home(self, dt: float) -> None:
        dx = self.patrol_start.x - self.pos.x
        if abs(dx) < 5:
            self.vel.x = 0; self.state = EntityState.IDLE; return
        direction = 1 if dx > 0 else -1
        self.vel.x = self.PATROL_SPEED * direction
    
    def draw(self, surface: pygame.Surface, camera: Camera) -> None:
        if not self.is_alive: return
        screen_rect = camera.apply(self.rect)
        
        colors = {EnemyType.WALKER: (220, 60, 60), EnemyType.FLYER: (160, 82, 45),
                  EnemyType.TURRET: (139, 69, 19), EnemyType.BOSS: (128, 0, 128)}
        color = colors.get(self.enemy_type, (220, 60, 60))
        
        # 受伤闪烁
        if self.stun_timer > 0 and int(self.stun_timer * 10) % 2 == 0:
            color = (255, 255, 255)
        
        pygame.draw.rect(surface, color, screen_rect, border_radius=3)
        # 血条
        bar_width = self.size[0]; bar_height = 4
        hp_ratio = self.stats.hp / self.stats.max_hp
        bg_bar = pygame.Rect(screen_rect.x, screen_rect.y - 8, bar_width, bar_height)
        hp_bar = pygame.Rect(screen_rect.x, screen_rect.y - 8, int(bar_width * hp_ratio), bar_height)
        pygame.draw.rect(surface, (60, 60, 60), bg_bar)
        pygame.draw.rect(surface, (255, 0, 0) if hp_ratio > 0.3 else (255, 165, 0), hp_bar)


# ──────────────────────────────────────────────
# 关卡管理器
# ──────────────────────────────────────────────

@dataclass
class LevelData:
    name: str; platforms: list[dict]; enemies: list[dict]
    player_spawn: tuple[int, int]; goal: tuple[int, int]; bg_color: tuple[int,int,int]


class LevelManager:
    """关卡管理 - 支持JSON格式关卡定义"""
    
    def __init__(self):
        self.levels: dict[str, LevelData] = {}
        self.current_level: Optional[str] = None
    
    def load_from_json(self, path: str) -> None:
        with open(path, 'r', encoding='utf-8') as f:
            data = json.load(f)
        for level_info in data.get('levels', []):
            level = LevelData(**level_info)
            self.levels[level.name] = level
    
    def get_current(self) -> Optional[LevelData]:
        return self.levels.get(self.current_level)
    
    def create_demo_level(self) -> LevelData:
        """创建演示关卡"""
        platforms_data = [
            {'x': 0, 'y': 650, 'w': 1200, 'h': 50, 'type': 'solid'},
            {'x': 200, 'y': 550, 'w': 150, 'h': 20},
            {'x': 450, 'y': 480, 'w': 150, 'h': 20},
            {'x': 700, 'y': 400, 'w': 200, 'h': 20},
            {'x': 350, 'y': 320, 'w': 120, 'h': 20},
            {'x': 100, 'y': 250, 'w': 150, 'h': 20},
            {'x': 900, 'y': 280, 'w': 150, 'h': 20},
            {'x': 550, 'y': 180, 'w': 200, 'h': 20, 'type': 'moving'},
        ]
        enemies_data = [
            {'x': 280, 'y': 510, 'type': 'walker'},
            {'x': 520, 'y': 440, 'type': 'walker'},
            {'x': 800, 'y': 240, 'type': 'flyer'},
        ]
        return LevelData(
            name="demo_level", platforms=platforms_data, enemies=enemies_data,
            player_spawn=(80, 550), goal=(1100, 130), bg_color=(135, 206, 235)
        )


# ──────────────────────────────────────────────
# 主游戏类
# ──────────────────────────────────────────────

class Game:
    """主游戏类 - 整合所有子系统"""
    
    def __init__(self):
        pygame.init()
        pygame.display.set_caption("🎮 MonkeyCode Platformer Demo")
        
        self.screen = pygame.display.set_mode((CONFIG.SCREEN_WIDTH, CONFIG.SCREEN_HEIGHT))
        self.clock = pygame.time.Clock()
        self.running = True
        self.paused = False
        
        # 子系统初始化
        self.input_mgr = InputManager()
        self.camera = Camera(CONFIG.SCREEN_WIDTH, CONFIG.SCREEN_HEIGHT)
        self.particles = ParticleSystem()
        self.level_mgr = LevelManager()
        
        # 游戏对象
        self.player: Optional[Player] = None
        self.platforms: list[Platform] = []
        self.enemies: list[Enemy] = []
        
        # UI字体
        self.font = pygame.font.Font(None, 36)
        self.small_font = pygame.font.Font(None, 24)
        
        # 加载关卡
        self._load_level(self.level_mgr.create_demo_level())
    
    def _load_level(self, level: LevelData) -> None:
        self.platforms.clear(); self.enemies.clear()
        
        for pd in level.platforms:
            plat = Platform(pd['x'], pd['y'], pd['w'], pd['h'], pd.get('type', 'solid'))
            if pd.get('type') == 'moving': plat.set_moving(80, 2.0)
            self.platforms.append(plat)
        
        for ed in level.enemies:
            etype = EnemyType(ed.get('type', 'walker'))
            enemy = Enemy(float(ed['x']), float(ed['y']), etype)
            self.enemies.append(enemy)
        
        px, py = level.player_spawn
        self.player = Player(float(px), float(py))
        self.player.bind_input(self.input_mgr)
        self.camera.follow(self.player.pos)
        self.level_mgr.current_level = level.name
    
    def handle_events(self) -> None:
        for event in pygame.event.get():
            if event.type == QUIT:
                self.running = False
            elif event.type == KEYDOWN:
                if event.key == K_r:  # 重置关卡
                    level = self.level_mgr.get_current()
                    if level: self._load_level(level)
    
    def update(self) -> None:
        if self.paused or not self.player: return
        
        dt = 1.0 / CONFIG.FPS
        self.input_mgr.update()
        
        # 更新平台
        for plat in self.platforms: plat.update(dt)
        
        # 更新玩家
        self.player.update(dt, self.platforms, self.particles)
        self.camera.follow(self.player.pos)
        self.camera.update(dt)
        
        # 更新敌人
        for enemy in self.enemies:
            enemy.update(dt, self.platforms, self.player)
        
        # 玩家攻击判定
        if self.player.state == EntityState.ATTACK and self.player.attack_cooldown > 0.2:
            atk_rect = pygame.Rect(
                self.player.rect.right if self.player.face_direction > 0 else self.player.rect.left - 40,
                self.player.rect.y, 40, self.player.rect.height
            )
            for enemy in self.enemies:
                if enemy.is_alive and atk_rect.colliderect(enemy.rect):
                    enemy.take_damage(self.player.stats.attack_power)
                    enemy.stun_timer = 0.3
                    self.particles.emit_burst(enemy.pos, (255, 100, 50), 15)
        
        # 敌人-玩家碰撞
        for enemy in self.enemies:
            if enemy.is_alive and self.player.rect.colliderect(enemy.rect):
                if self.player.invincible_timer <= 0:
                    self.player.take_damage(enemy.stats.attack_power)
                    knockback = 8 if self.player.pos.x > enemy.pos.x else -8
                    self.player.vel.x = knockback
                    self.player.vel.y = -5
                    self.particles.emit_burst(self.player.pos, (255, 50, 50), 10)
        
        # 粒子更新
        self.particles.update(dt)
        
        # 暂停检查
        if self.input_mgr.is_pressed('pause'):
            self.paused = not self.paused
    
    def _draw_background(self) -> None:
        """渐变天空背景"""
        for y in range(CONFIG.SCREEN_HEIGHT):
            ratio = y / CONFIG.SCREEN_HEIGHT
            r = int(CONFIG.SKY_TOP[0]*(1-ratio) + CONFIG.SKY_BOTTOM[0]*ratio)
            g = int(CONFIG.SKY_TOP[1]*(1-ratio) + CONFIG.SKY_BOTTOM[1]*ratio)
            b = int(CONFIG.SKY_TOP[2]*(1-ratio) + CONFIG.SKY_BOTTOM[2]*ratio)
            pygame.draw.line(self.screen, (r,g,b), (0,y), (CONFIG.SCREEN_WIDTH, y))
    
    def draw(self) -> None:
        self._draw_background()
        
        # 绘制平台
        for plat in self.platforms: plat.draw(self.screen, self.camera)
        
        # 绘制敌人
        for enemy in self.enemies: enemy.draw(self.screen, self.camera)
        
        # 绘制玩家
        if self.player: self.player.draw(self.screen, self.camera)
        
        # 绘制粒子
        self.particles.draw(self.screen)
        
        # 绘制UI
        self._draw_ui()
        
        if self.paused:
            overlay = pygame.Surface((CONFIG.SCREEN_WIDTH, CONFIG.SCREEN_HEIGHT), pygame.SRCALPHA)
            overlay.fill((0, 0, 0, 128))
            self.screen.blit(overlay, (0, 0))
            text = self.font.render("⏸ PAUSED", True, (255, 255, 255))
            self.screen.blit(text, (CONFIG.SCREEN_WIDTH//2 - text.get_width()//2,
                                    CONFIG.SCREEN_HEIGHT//2 - text.get_height()//2))
        
        pygame.display.flip()
    
    def _draw_ui(self) -> None:
        if not self.player: return
        # HP条
        hp_text = self.small_font.render(f"HP: {self.player.stats.hp}/{self.player.stats.max_hp}", True, (255, 255, 255))
        self.screen.blit(hp_text, (10, 10))
        hp_bar_width = 200; hp_bar_height = 16
        hp_ratio = self.player.stats.hp / self.player.stats.max_hp
        pygame.draw.rect(self.screen, (60, 60, 60), (10, 35, hp_bar_width, hp_bar_height))
        hp_color = (50, 205, 50) if hp_ratio > 0.5 else ((255, 165, 0) if hp_ratio > 0.25 else (255, 0, 0))
        pygame.draw.rect(self.screen, hp_color, (10, 35, int(hp_bar_width * hp_ratio), hp_bar_height))
        
        # 状态显示
        state_text = self.small_font.render(f"State: {self.player.state.name}", True, (200, 200, 200))
        self.screen.blit(state_text, (10, 58))
        
        # FPS
        fps_text = self.small_font.render(f"FPS: {int(self.clock.get_fps())}", True, (150, 150, 150))
        self.screen.blit(fps_text, (CONFIG.SCREEN_WIDTH - 80, 10))
    
    def run(self) -> None:
        while self.running:
            self.handle_events(); self.update(); self.draw()
            self.clock.tick(CONFIG.FPS)
        pygame.quit()


if __name__ == '__main__':
    game = Game()
    game.run()

三、实战案例二:游戏AI行为树系统

"""
行为树(Behavior Tree)框架 - MonkeyCode生成
适用于:游戏NPC AI、策略单位决策、Boss战斗逻辑
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Any, Optional, TypeVar

T = TypeVar('T')


class NodeStatus(Enum):
    SUCCESS = auto(); FAILURE = auto(); RUNNING = auto()


class BTNode(ABC):
    """行为树节点基类"""
    
    def __init__(self, name: str = ""):
        self.name = name or self.__class__.__name__
        self.parent: Optional['BTNode'] = None
    
    @abstractmethod
    def tick(self, context: dict[str, Any]) -> NodeStatus:
        ...


# ─── 叶子节点 ───

class ActionNode(BTNode):
    """动作节点 - 执行具体行为"""
    
    def __init__(self, name: str, action: callable):
        super().__init__(name)
        self._action = action
    
    def tick(self, ctx: dict) -> NodeStatus:
        result = self._action(ctx)
        return NodeStatus.SUCCESS if result else NodeStatus.FAILURE


class ConditionNode(BTNode):
    """条件节点 - 检查条件是否满足"""
    
    def __init__(self, name: str, condition: callable):
        super().__init__(name)
        self._condition = condition
    
    def tick(self, ctx: dict) -> NodeStatus:
        return NodeStatus.SUCCESS if self._condition(ctx) else NodeStatus.FAILURE


class WaitNode(BTNode):
    """等待节点 - 等待指定时间"""
    
    def __init__(self, duration: float):
        super().__init__(f"Wait({duration}s)")
        self.duration = duration
        self._elapsed: float = 0.0
    
    def tick(self, ctx: dict) -> NodeStatus:
        dt = ctx.get('dt', 0.016)
        self._elapsed += dt
        if self._elapsed >= self.duration:
            self._elapsed = 0.0
            return NodeStatus.SUCCESS
        return NodeStatus.RUNNING


# ─── 组合节点 ───

class SequenceNode(BTNode):
    """序列节点 - 所有子节点都成功才返回SUCCESS"""
    
    def __init__(self, children: list[BTNode]):
        super().__init__("Sequence")
        self.children = children
        self._current: int = 0
    
    def tick(self, ctx: dict) -> NodeStatus:
        while self._current < len(self.children):
            status = self.children[self._current].tick(ctx)
            if status == NodeStatus.RUNNING:
                return NodeStatus.RUNNING
            if status == NodeStatus.FAILURE:
                self._current = 0
                return NodeStatus.FAILURE
            self._current += 1
        self._current = 0
        return NodeStatus.SUCCESS


class SelectorNode(BTNode):
    """选择节点 - 任一子节点成功即返回SUCCESS"""
    
    def __init__(self, children: list[BTNode]):
        super().__init__("Selector")
        self.children = children
        self._current: int = 0
    
    def tick(self, ctx: dict) -> NodeStatus:
        while self._current < len(self.children):
            status = self.children[self._current].tick(ctx)
            if status in (NodeStatus.RUNNING, NodeStatus.SUCCESS):
                return status
            self._current += 1
        self._current = 0
        return NodeStatus.FAILURE


class ParallelNode(BTNode):
    """并行节点 - 所有子节点同时执行"""
    
    def __init__(self, policy: str = 'all', children: list[BTNode] = None):
        super().__init__(f"Parallel({policy})")
        self.policy = policy  # 'all' 或 'any'
        self.children = children or []
    
    def tick(self, ctx: dict) -> NodeStatus:
        success_count = 0; failure_count = 0
        for child in self.children:
            status = child.tick(ctx)
            if status == NodeStatus.SUCCESS: success_count += 1
            elif status == NodeStatus.FAILURE: failure_count += 1
        
        if self.policy == 'any' and success_count > 0:
            return NodeStatus.SUCCESS
        if failure_count > 0 and self.policy != 'all':
            return NodeStatus.FAILURE
        if success_count == len(self.children):
            return NodeStatus.SUCCESS
        return NodeStatus.RUNNING


class RepeatNode(BTNode):
    """重复装饰节点"""
    
    def __init__(self, child: BTNode, max_times: int = -1):
        super().__init__(f"Repeat({max_times})")
        self.child = child; self.max_times = max_times; self._count = 0
    
    def tick(self, ctx: dict) -> NodeStatus:
        if 0 < self.max_times <= self._count:
            self._count = 0; return NodeStatus.SUCCESS
        status = self.child.tick(ctx)
        if status != NodeStatus.RUNNING:
            self._count += 1
        return NodeStatus.RUNNING if self.max_times < 0 or self._count < self.max_times else status


class InvertNode(BTNode):
    """反转装饰节点"""
    
    def __init__(self, child: BTNode):
        super().__init__("Invert"); self.child = child
    
    def tick(self, ctx: dict) -> NodeStatus:
        status = self.child.tick(ctx)
        if status == NodeStatus.SUCCESS: return NodeStatus.FAILURE
        if status == NodeStatus.FAILURE: return NodeStatus.SUCCESS
        return NodeStatus.RUNNING


# ─── 使用示例:Boss AI行为树 ───

def create_boss_ai():
    """创建Boss的行为树"""
    return SelectorNode([
        # Phase 1: 低血量狂暴
        SequenceNode([
            ConditionNode("低血量?", lambda ctx: ctx.get('hp_pct', 1) < 0.2),
            SequenceNode([
                ActionNode("狂暴咆哮", lambda ctx: print("💢 BOSS ENRAGED!")),
                RepeatNode(ActionNode("狂暴攻击", lambda ctx: print("⚔️ RAGE ATTACK!")), max_times=5),
            ]),
        ]),
        # Phase 2: 中等血量 - 召唤小怪
        SequenceNode([
            ConditionNode("中等血量?", lambda ctx: 0.2 <= ctx.get('hp_pct', 1) < 0.5),
            ActionNode("召唤小怪", lambda ctx: print("👹 Summoning minions...")),
            WaitNode(2.0),
        ]),
        # Phase 3: 正常巡逻
        SequenceNode([
            ActionNode("巡逻", lambda ctx: print("🚶 Patrolling...")),
            WaitNode(1.0),
            ConditionNode("发现玩家?", lambda ctx: ctx.get('player_visible', False)),
            ActionNode("追击", lambda ctx: print("🏃 Chasing player!")),
        ]),
    ])


# 测试运行
if __name__ == '__main__':
    boss_bt = create_boss_ai()
    context = {'hp_pct': 1.0, 'player_visible': False, 'dt': 0.016}
    
    print("="*50); print("🎮 Boss AI Behavior Tree Test"); print("="*50)
    for i in range(20):
        context['hp_pct'] = max(0.1, 1.0 - i*0.04)
        context['player_visible'] = i > 5
        status = boss_bt.tick(context)
        print(f"[Frame {i:02d}] HP={context['hp_pct']:.0%} Status={status.name}")

四、MonkeyCode游戏开发提示词技巧

4.1 高效提示词模板

🎮 MonkeyCode 游戏开发提示词:

【项目初始化】
"使用 [引擎/语言] 创建一个 [类型] 游戏的基础框架:
 - 分辨率: 1920x1080, 60FPS
 - 包含: 场景管理、资源加载、输入处理
 - 架构: ECS或OOP,说明理由"

【物理系统】
"实现一个2D平台游戏的物理系统:
 - 重力加速度、最大下落速度
 - AABB碰撞检测与响应
 - 土狼时间和跳跃缓冲机制
 - 平台单向穿透"

【AI行为】
"为游戏敌人设计行为树AI:
 - 巡逻 → 发现玩家 → 追击 → 攻击 → 返回
 - 包含受伤眩晕、死亡状态
 - 不同距离使用不同攻击方式"

【性能优化】
"分析并优化这段游戏代码的性能:
 - 使用空间分区加速碰撞检测
 - 对象池管理子弹/粒子
 - 视锥剔除减少绘制调用"

4.2 游戏开发常见问题速查

问题 MonkeyCode提问方式
碰穿问题 "角色穿过地板怎么办?修复AABB碰撞检测"
掉帧严重 "分析性能瓶颈,给出具体优化方案"
AI太聪明/笨 "调整敌人AI难度曲线"
手感不好 "优化角色控制的响应性和手感参数"
内存泄漏 "检查游戏对象的内存管理和生命周期"

五、总结

MonkeyCode让游戏开发不再是少数人的专利:

  • 🎯 快速原型 — 从想法到可玩Demo的时间缩短80%
  • 🧠 智能AI — 行为树、状态机等复杂系统的自动生成
  • 🔧 全栈覆盖 — 从物理引擎到UI系统的一站式辅助
  • 🐛 高效Debug — 智能日志分析和Bug定位
  • 📚 知识赋能 — 在编码的同时学习游戏开发最佳实践

"游戏开发是艺术与工程的完美结合。MonkeyCode帮你处理工程部分,让你专注于艺术的创作。"


本文最后更新:2026年7月16日
作者:MonkeyCode团队

相关阅读:

下一篇预告:[MonkeyCode物联网(IoT)开发实战]

posted on 2026-07-16 18:42  MonkeyCode  阅读(6)  评论(0)    收藏  举报