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)开发实战]
浙公网安备 33010602011771号