通过TRAE工具实现贪吃蛇游戏的全过程

0 阅读27分钟

效果图

Screenshot_20260417_151156.png

1. 项目概述

本文将详细介绍如何使用TRAE工具(Trae IDE)从空文件开始,一步步构建一个完整的Android原生贪吃蛇游戏。我们将实现经典贪吃蛇游戏的核心功能,并添加商业化升级特性,最终打造一个具有现代感和良好用户体验的游戏应用。

2. 开发环境准备

2.1 工具要求

  • TRAE IDE:最新版本,支持Android项目开发
  • JDK:Java Development Kit 8或更高版本
  • Android SDK:包含最新的Android平台和构建工具

2.2 项目初始化

首先,我们需要在TRAE IDE中创建一个新的Android项目。选择"Empty Activity"模板,设置项目名称为"SnakeGame",包名为"com.example.snake"。

3. 项目结构搭建

3.1 目录结构设计

我们将采用模块化的项目结构,便于代码管理和功能扩展:

SnakeGame/
├── app/
│   ├── src/main/
│   │   ├── java/com/example/snake/
│   │   │   ├── core/           # 核心游戏逻辑
│   │   │   ├── effects/        # 视觉效果
│   │   │   ├── managers/       # 管理器类
│   │   │   ├── models/         # 数据模型
│   │   │   ├── utils/          # 工具类
│   │   │   ├── MainActivity.java   # 主活动
│   │   │   └── SnakeView.java      # 游戏视图
│   │   └── res/                # 资源文件
│   └── build.gradle            # 应用级构建配置
└── build.gradle                # 项目级构建配置

3.2 基础配置

修改build.gradle文件,添加必要的依赖:

// app/build.gradle
plugins {
    id 'com.android.application'
}

android {
    compileSdk 34
    
    defaultConfig {
        applicationId "com.example.snake"
        minSdk 24
        targetSdk 34
        versionCode 1
        versionName "1.0"
        
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.9.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    
    // MMKV 存储库
    implementation 'com.tencent:mmkv:1.3.1'
}

4. 核心功能实现

4.1 游戏常量定义

创建utils/Constants.java文件,集中管理游戏相关的常量:

package com.example.snake.utils;

/**
 * 游戏常量类
 * 集中管理所有游戏相关的常量
 */
public class Constants {
    
    // ==================== 游戏速度相关 ====================
    public static final int SPEED_EASY = 250;         // 简单难度速度(毫秒)
    public static final int SPEED_NORMAL = 200;       // 普通难度速度(毫秒)
    public static final int SPEED_HARD = 150;         // 困难难度速度(毫秒)
    public static final int SPEED_INSANE = 100;       // 疯狂难度速度(毫秒)
    public static final int SPEED_MIN = 60;           // 最小速度(毫秒)
    public static final int SPEED_DECREMENT = 5;      // 每增加3节身体速度减少量(毫秒)
    
    // ==================== 分数相关 ====================
    public static final int SCORE_FOOD = 10;          // 普通食物分数
    public static final int SCORE_BONUS = 25;         //  bonus食物分数
    public static final int SCORE_GOLDEN = 50;        // 金色食物分数
    
    // ==================== 游戏模式相关 ====================
    public static final int TIME_CHALLENGE_30 = 30;   // 30秒挑战
    public static final int TIME_CHALLENGE_60 = 60;   // 60秒挑战
    public static final int TIME_CHALLENGE_120 = 120; // 120秒挑战
    
    // ==================== 食物生成相关 ====================
    public static final int FOOD_SPAWN_INTERVAL = 5;  // 食物生成间隔(游戏tick)
    public static final int FOOD_LIFETIME = 10000;    // 食物生命周期(毫秒)
    public static final int BONUS_FOOD_CHANCE = 15;   // bonus食物生成概率(%)
    public static final int GOLDEN_FOOD_CHANCE = 5;   // 金色食物生成概率(%)
    public static final int POWER_UP_CHANCE = 8;      // 道具生成概率(%)
    
    // ==================== 道具相关 ====================
    public static final int POWER_UP_DURATION = 8000; // 道具持续时间(毫秒)
    public static final float SLOW_DOWN_FACTOR = 0.5f; // 减速道具效果
    
    // ==================== 动画相关 ====================
    public static final int CRASH_EFFECT_DURATION = 1000; // 碰撞特效持续时间(毫秒)
}

4.2 游戏配置模型

创建models/GameConfig.java文件,定义游戏的难度和模式:

package com.example.snake.models;

import com.example.snake.utils.Constants;

/**
 * 游戏配置类
 * 包含难度、模式和皮肤配置
 */
public class GameConfig {
    
    // ==================== 难度枚举 ====================
    public enum Difficulty {
        EASY(Constants.SPEED_EASY, false),        // 简单难度,不支持穿墙
        NORMAL(Constants.SPEED_NORMAL, false),     // 普通难度,不支持穿墙
        HARD(Constants.SPEED_HARD, false),        // 困难难度,不支持穿墙
        INSANE(Constants.SPEED_INSANE, false);    // 疯狂难度,不支持穿墙
        
        private final int baseSpeed;    // 基础速度(毫秒)
        private final boolean wrapAround; // 是否支持穿墙
        
        Difficulty(int baseSpeed, boolean wrapAround) {
            this.baseSpeed = baseSpeed;
            this.wrapAround = wrapAround;
        }
        
        public int getBaseSpeed() {
            return baseSpeed;
        }
        
        public boolean isWrapAround() {
            return wrapAround;
        }
    }
    
    // ==================== 游戏模式枚举 ====================
    public enum GameMode {
        ENDLESS(0),               // 无尽模式
        TIME_CHALLENGE(30);       // 时间挑战模式,默认30秒
        
        private final int defaultTimeLimit; // 默认时间限制(秒)
        
        GameMode(int defaultTimeLimit) {
            this.defaultTimeLimit = defaultTimeLimit;
        }
        
        public int getDefaultTimeLimit() {
            return defaultTimeLimit;
        }
    }
    
    // ==================== 皮肤ID枚举 ====================
    public enum SkinId {
        NEON_GREEN,   // 霓虹绿(默认风格)
        FIRE,         // 火焰(红橙渐变)
        ICE,          // 冰雪(蓝白冷色)
        GOLD,         // 金色
        CLASSIC       // 经典(绿底黑格)
    }
    
    // ==================== 配置属性 ====================
    private final Difficulty difficulty;
    private final GameMode gameMode;
    private final int timeLimit; // 时间挑战模式的时间限制(秒)
    private final SkinId skinId;
    
    // ==================== 构造函数 ====================
    private GameConfig(Builder builder) {
        this.difficulty = builder.difficulty;
        this.gameMode = builder.gameMode;
        this.timeLimit = builder.timeLimit;
        this.skinId = builder.skinId;
    }
    
    // ==================== Getter方法 ====================
    public Difficulty getDifficulty() {
        return difficulty;
    }
    
    public GameMode getGameMode() {
        return gameMode;
    }
    
    public int getTimeLimit() {
        return timeLimit;
    }
    
    public SkinId getSkinId() {
        return skinId;
    }
    
    // ==================== Builder类 ====================
    public static class Builder {
        private Difficulty difficulty = Difficulty.NORMAL;
        private GameMode gameMode = GameMode.ENDLESS;
        private int timeLimit = GameMode.TIME_CHALLENGE.getDefaultTimeLimit();
        private SkinId skinId = SkinId.NEON_GREEN;
        
        public Builder setDifficulty(Difficulty difficulty) {
            this.difficulty = difficulty;
            return this;
        }
        
        public Builder setGameMode(GameMode gameMode) {
            this.gameMode = gameMode;
            return this;
        }
        
        public Builder setTimeLimit(int timeLimit) {
            this.timeLimit = timeLimit;
            return this;
        }
        
        public Builder setSkinId(SkinId skinId) {
            this.skinId = skinId;
            return this;
        }
        
        public GameConfig build() {
            return new GameConfig(this);
        }
    }
    
    // ==================== 静态方法 ====================
    public static GameConfig getDefault() {
        return new Builder().build();
    }
}

4.3 食物模型

创建models/FoodItem.java文件,定义食物的类型和属性:

package com.example.snake.models;

import android.graphics.Rect;
import android.graphics.Color;

import com.example.snake.utils.Constants;

/**
 * 食物项类
 * 定义游戏中的食物类型和属性
 */
public class FoodItem {
    
    // ==================== 食物类型枚举 ====================
    public enum FoodType {
        NORMAL(Color.parseColor("#00FF88"), Constants.SCORE_FOOD),          // 普通食物,绿色
        BONUS(Color.parseColor("#FFAA00"), Constants.SCORE_BONUS),           //  bonus食物,橙色
        GOLDEN(Color.parseColor("#FFFF00"), Constants.SCORE_GOLDEN),         // 金色食物,黄色
        SLOW_DOWN(Color.parseColor("#00AAFF"), Constants.SCORE_FOOD),        // 减速道具,蓝色
        INVINCIBLE(Color.parseColor("#FF44FF"), Constants.SCORE_FOOD),       // 无敌道具,紫色
        DOUBLE_SCORE(Color.parseColor("#FF4444"), Constants.SCORE_FOOD);     // 双倍分数,红色
        
        private final int color;      // 食物颜色
        private final int score;      // 食物分数
        
        FoodType(int color, int score) {
            this.color = color;
            this.score = score;
        }
        
        public int getColor() {
            return color;
        }
        
        public int getScore() {
            return score;
        }
    }
    
    // ==================== 成员变量 ====================
    private final FoodType type;      // 食物类型
    private final Rect rect;          // 食物位置和大小
    private final long spawnTime;     // 生成时间
    private final long lifetime;      // 生命周期
    private float pulsePhase;         // 脉冲动画相位
    
    // ==================== 构造函数 ====================
    public FoodItem(FoodType type, Rect rect, long spawnTime, long lifetime) {
        this.type = type;
        this.rect = rect;
        this.spawnTime = spawnTime;
        this.lifetime = lifetime;
        this.pulsePhase = 0;
    }
    
    // ==================== Getter方法 ====================
    public FoodType getType() {
        return type;
    }
    
    public Rect getRect() {
        return rect;
    }
    
    public long getSpawnTime() {
        return spawnTime;
    }
    
    public long getLifetime() {
        return lifetime;
    }
    
    public float getPulsePhase() {
        return pulsePhase;
    }
    
    public int getColor() {
        return type.getColor();
    }
    
    public int getScore() {
        return type.getScore();
    }
    
    // ==================== 方法 ====================
    
    /**
     * 设置脉冲相位
     */
    public void setPulsePhase(float phase) {
        this.pulsePhase = phase;
    }
    
    /**
     * 检查食物是否过期
     */
    public boolean isExpired(long currentTime) {
        return currentTime - spawnTime > lifetime;
    }
}

4.4 食物生成器

创建core/FoodSpawner.java文件,负责生成和管理游戏中的食物:

package com.example.snake.core;

import android.graphics.Rect;

import com.example.snake.models.FoodItem;
import com.example.snake.utils.Constants;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 食物生成器
 * 负责生成和管理游戏中的食物
 */
public class FoodSpawner {
    
    private final Random random;
    private final int width;
    private final int height;
    private final int blockSize;
    private final int offsetX;
    private final int offsetY;
    private final int gridWidth;
    private final int gridHeight;
    
    private List<FoodItem> activeFood;
    private int spawnTick;
    
    // ==================== 构造函数 ====================
    public FoodSpawner(int width, int height, int blockSize, int offsetX, int offsetY, int gridWidth, int gridHeight) {
        this.random = new Random();
        this.width = width;
        this.height = height;
        this.blockSize = blockSize;
        this.offsetX = offsetX;
        this.offsetY = offsetY;
        this.gridWidth = gridWidth;
        this.gridHeight = gridHeight;
        this.activeFood = new ArrayList<>();
        this.spawnTick = 0;
    }
    
    // ==================== 方法 ====================
    
    /**
     * 更新食物生成器
     */
    public void update(long currentTime, List<Rect> snakeBody) {
        // 增加生成tick
        spawnTick++;
        
        // 移除过期食物
        removeExpiredFood(currentTime);
        
        // 检查是否需要生成新食物
        if (spawnTick >= Constants.FOOD_SPAWN_INTERVAL) {
            spawnTick = 0;
            spawnFood(currentTime, snakeBody);
        }
        
        // 更新食物动画
        updateFoodAnimation(currentTime);
    }
    
    /**
     * 生成食物
     */
    private void spawnFood(long currentTime, List<Rect> snakeBody) {
        // 确保同时只存在一个食物
        if (!activeFood.isEmpty()) {
            return; // 已有食物,不生成新的
        }
        
        // 计算可用的网格位置
        int maxX = gridWidth / blockSize;
        int maxY = gridHeight / blockSize;
        
        // 随机生成位置
        int gridX = random.nextInt(maxX);
        int gridY = random.nextInt(maxY);
        
        int x = offsetX + gridX * blockSize;
        int y = offsetY + gridY * blockSize;
        Rect rect = new Rect(x, y, x + blockSize, y + blockSize);
        
        // 检查是否与蛇身重叠
        for (Rect segment : snakeBody) {
            if (Rect.intersects(rect, segment)) {
                return; // 与蛇身重叠,不生成
            }
        }
        
        // 确定食物类型
        FoodItem.FoodType type = determineFoodType();
        
        // 创建并添加食物
        FoodItem foodItem = new FoodItem(type, rect, currentTime, Constants.FOOD_LIFETIME);
        activeFood.add(foodItem);
    }
    
    /**
     * 确定食物类型
     */
    private FoodItem.FoodType determineFoodType() {
        int chance = random.nextInt(100);
        
        if (chance < Constants.GOLDEN_FOOD_CHANCE) {
            return FoodItem.FoodType.GOLDEN;
        } else if (chance < Constants.GOLDEN_FOOD_CHANCE + Constants.BONUS_FOOD_CHANCE) {
            return FoodItem.FoodType.BONUS;
        } else if (chance < Constants.GOLDEN_FOOD_CHANCE + Constants.BONUS_FOOD_CHANCE + Constants.POWER_UP_CHANCE) {
            // 随机选择一种道具
            int powerUpType = random.nextInt(3);
            switch (powerUpType) {
                case 0:
                    return FoodItem.FoodType.SLOW_DOWN;
                case 1:
                    return FoodItem.FoodType.INVINCIBLE;
                case 2:
                default:
                    return FoodItem.FoodType.DOUBLE_SCORE;
            }
        } else {
            return FoodItem.FoodType.NORMAL;
        }
    }
    
    /**
     * 移除过期食物
     */
    private void removeExpiredFood(long currentTime) {
        List<FoodItem> expiredFood = new ArrayList<>();
        for (FoodItem food : activeFood) {
            if (food.isExpired(currentTime)) {
                expiredFood.add(food);
            }
        }
        activeFood.removeAll(expiredFood);
    }
    
    /**
     * 更新食物动画
     */
    private void updateFoodAnimation(long currentTime) {
        for (FoodItem food : activeFood) {
            // 更新脉冲相位
            float deltaTime = (currentTime - food.getSpawnTime()) / 1000f;
            food.setPulsePhase(deltaTime * 3f % (float) Math.PI * 2);
        }
    }
    
    /**
     * 检查蛇是否吃到食物
     */
    public FoodItem checkFoodCollision(Rect snakeHead) {
        for (int i = 0; i < activeFood.size(); i++) {
            FoodItem food = activeFood.get(i);
            if (Rect.intersects(snakeHead, food.getRect())) {
                activeFood.remove(i);
                return food;
            }
        }
        return null;
    }
    
    // ==================== Getter方法 ====================
    public List<FoodItem> getActiveFood() {
        return activeFood;
    }
    
    /**
     * 清空所有食物
     */
    public void clearFood() {
        activeFood.clear();
    }
    
    /**
     * 重置生成器
     */
    public void reset() {
        activeFood.clear();
        spawnTick = 0;
    }
}

4.5 道具管理器

创建core/PowerUpManager.java文件,管理游戏中的道具效果:

package com.example.snake.core;

import com.example.snake.models.FoodItem;
import com.example.snake.utils.Constants;

/**
 * 道具管理器
 * 负责管理游戏中的道具效果
 */
public class PowerUpManager {
    
    private boolean isSlowDownActive;
    private boolean isInvincibleActive;
    private boolean isDoubleScoreActive;
    
    private long slowDownEndTime;
    private long invincibleEndTime;
    private long doubleScoreEndTime;
    
    // ==================== 构造函数 ====================
    public PowerUpManager() {
        reset();
    }
    
    // ==================== 方法 ====================
    
    /**
     * 更新道具状态
     */
    public void update(long currentTime) {
        // 检查减速道具是否过期
        if (isSlowDownActive && currentTime > slowDownEndTime) {
            isSlowDownActive = false;
        }
        
        // 检查无敌道具是否过期
        if (isInvincibleActive && currentTime > invincibleEndTime) {
            isInvincibleActive = false;
        }
        
        // 检查双倍分数道具是否过期
        if (isDoubleScoreActive && currentTime > doubleScoreEndTime) {
            isDoubleScoreActive = false;
        }
    }
    
    /**
     * 激活道具
     */
    public void activatePowerUp(FoodItem.FoodType type, long currentTime) {
        switch (type) {
            case SLOW_DOWN:
                isSlowDownActive = true;
                slowDownEndTime = currentTime + Constants.POWER_UP_DURATION;
                break;
            case INVINCIBLE:
                isInvincibleActive = true;
                invincibleEndTime = currentTime + Constants.POWER_UP_DURATION;
                break;
            case DOUBLE_SCORE:
                isDoubleScoreActive = true;
                doubleScoreEndTime = currentTime + Constants.POWER_UP_DURATION;
                break;
        }
    }
    
    /**
     * 获取游戏速度因子
     */
    public float getSpeedFactor() {
        if (isSlowDownActive) {
            return Constants.SLOW_DOWN_FACTOR;
        }
        return 1.0f;
    }
    
    // ==================== Getter方法 ====================
    public boolean isSlowDown() {
        return isSlowDownActive;
    }
    
    public boolean isInvincible() {
        return isInvincibleActive;
    }
    
    public boolean isDoubleScore() {
        return isDoubleScoreActive;
    }
    
    /**
     * 重置道具状态
     */
    public void reset() {
        isSlowDownActive = false;
        isInvincibleActive = false;
        isDoubleScoreActive = false;
        slowDownEndTime = 0;
        invincibleEndTime = 0;
        doubleScoreEndTime = 0;
    }
}

4.6 粒子系统

创建effects/ParticleSystem.java文件,实现游戏中的粒子特效:

package com.example.snake.effects;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 粒子系统
 * 负责生成和管理游戏中的粒子特效
 */
public class ParticleSystem {
    
    private List<Particle> particles;
    private Random random;
    private Paint paint;
    
    // ==================== 构造函数 ====================
    public ParticleSystem() {
        this.particles = new ArrayList<>();
        this.random = new Random();
        this.paint = new Paint();
        this.paint.setAntiAlias(true);
    }
    
    // ==================== 方法 ====================
    
    /**
     * 更新粒子系统
     */
    public void update(long currentTime) {
        List<Particle> expiredParticles = new ArrayList<>();
        
        for (Particle particle : particles) {
            if (!particle.update(currentTime)) {
                expiredParticles.add(particle);
            }
        }
        
        particles.removeAll(expiredParticles);
    }
    
    /**
     * 绘制粒子系统
     */
    public void draw(Canvas canvas) {
        for (Particle particle : particles) {
            particle.draw(canvas, paint);
        }
    }
    
    /**
     * 发射食物被吃特效
     */
    public void emitFoodEaten(float x, float y, int color) {
        for (int i = 0; i < 8; i++) {
            float angle = (float) (Math.PI * 2 * i / 8);
            float speed = 200 + random.nextFloat() * 100;
            particles.add(new ColorParticle(x, y, angle, speed, color, System.currentTimeMillis(), 500));
        }
    }
    
    /**
     * 发射死亡特效
     */
    public void emitDeath(float x, float y) {
        for (int i = 0; i < 12; i++) {
            float angle = (float) (Math.PI * 2 * i / 12);
            float speed = 300 + random.nextFloat() * 200;
            int color = Color.RED;
            particles.add(new ColorParticle(x, y, angle, speed, color, System.currentTimeMillis(), 800));
        }
    }
    
    /**
     * 发射道具特效
     */
    public void emitPowerUp(float x, float y, int color) {
        for (int i = 0; i < 6; i++) {
            float angle = (float) (Math.PI * 2 * i / 6);
            float speed = 150 + random.nextFloat() * 50;
            particles.add(new ColorParticle(x, y, angle, speed, color, System.currentTimeMillis(), 600));
        }
    }
    
    /**
     * 发射分数特效
     */
    public void emitScore(float x, float y, int score) {
        particles.add(new TextParticle(x, y, score, System.currentTimeMillis(), 1000));
    }
    
    /**
     * 清除所有粒子
     */
    public void clear() {
        particles.clear();
    }
    
    // ==================== 粒子基类 ====================
    private abstract static class Particle {
        protected float x;
        protected float y;
        protected long spawnTime;
        protected long lifetime;
        
        public Particle(float x, float y, long spawnTime, long lifetime) {
            this.x = x;
            this.y = y;
            this.spawnTime = spawnTime;
            this.lifetime = lifetime;
        }
        
        public abstract boolean update(long currentTime);
        public abstract void draw(Canvas canvas, Paint paint);
    }
    
    // ==================== 彩色粒子 ====================
    private static class ColorParticle extends Particle {
        private float angle;
        private float speed;
        private int color;
        private float size;
        
        public ColorParticle(float x, float y, float angle, float speed, int color, long spawnTime, long lifetime) {
            super(x, y, spawnTime, lifetime);
            this.angle = angle;
            this.speed = speed;
            this.color = color;
            this.size = 8;
        }
        
        @Override
        public boolean update(long currentTime) {
            long elapsed = currentTime - spawnTime;
            if (elapsed > lifetime) {
                return false;
            }
            
            float progress = (float) elapsed / lifetime;
            float deltaTime = 0.016f; // 假设60fps
            
            // 更新位置
            x += Math.cos(angle) * speed * deltaTime;
            y += Math.sin(angle) * speed * deltaTime;
            
            // 更新大小
            size = 8 * (1 - progress);
            
            return true;
        }
        
        @Override
        public void draw(Canvas canvas, Paint paint) {
            paint.setColor(color);
            paint.setStyle(Paint.Style.FILL);
            canvas.drawCircle(x, y, size, paint);
        }
    }
    
    // ==================== 文字粒子 ====================
    private static class TextParticle extends Particle {
        private int score;
        private float speedY;
        
        public TextParticle(float x, float y, int score, long spawnTime, long lifetime) {
            super(x, y, spawnTime, lifetime);
            this.score = score;
            this.speedY = -200;
        }
        
        @Override
        public boolean update(long currentTime) {
            long elapsed = currentTime - spawnTime;
            if (elapsed > lifetime) {
                return false;
            }
            
            float deltaTime = 0.016f; // 假设60fps
            
            // 更新位置
            y += speedY * deltaTime;
            
            return true;
        }
        
        @Override
        public void draw(Canvas canvas, Paint paint) {
            paint.setColor(Color.YELLOW);
            paint.setTextSize(24);
            paint.setTextAlign(Paint.Align.CENTER);
            canvas.drawText("+" + score, x, y, paint);
        }
    }
}

4.7 音效管理器

创建managers/SoundManager.java文件,管理游戏中的音效:

package com.example.snake.managers;

import android.content.Context;
import android.media.AudioAttributes;
import android.media.SoundPool;

/**
 * 音效管理器
 * 负责游戏中音效的加载和播放
 */
public class SoundManager {
    
    // ==================== 音效ID ====================
    public static final int SOUND_EAT_FOOD = 1;       // 吃食物音效
    public static final int SOUND_GAME_OVER = 2;      // 游戏结束音效
    public static final int SOUND_POWER_UP = 3;       // 获得道具音效
    
    // ==================== 成员变量 ====================
    private SoundPool soundPool;
    private boolean soundLoaded = false;
    
    // ==================== 构造函数 ====================
    public SoundManager(Context context) {
        // 配置SoundPool
        AudioAttributes audioAttributes = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_GAME)
                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                .build();
        
        soundPool = new SoundPool.Builder()
                .setMaxStreams(5)
                .setAudioAttributes(audioAttributes)
                .build();
        
        // 加载音效
        loadSounds(context);
    }
    
    // ==================== 方法 ====================
    
    /**
     * 加载音效资源
     */
    private void loadSounds(Context context) {
        try {
            // 尝试加载音效资源
            // 注意:这里使用反射来避免编译错误
            // 实际使用时需要添加对应的音效文件到raw文件夹
            soundLoaded = false;
        } catch (Exception e) {
            // 音效文件不存在时不影响游戏运行
            soundLoaded = false;
        }
    }
    
    /**
     * 播放音效
     */
    public void playSound(int soundId) {
        if (soundLoaded) {
            soundPool.play(soundId, 1.0f, 1.0f, 1, 0, 1.0f);
        }
    }
    
    /**
     * 释放资源
     */
    public void release() {
        if (soundPool != null) {
            soundPool.release();
            soundPool = null;
        }
    }
}

4.8 游戏视图

创建SnakeView.java文件,实现游戏的核心逻辑和渲染:

package com.example.snake;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.example.snake.core.FoodSpawner;
import com.example.snake.core.PowerUpManager;
import com.example.snake.effects.ParticleSystem;
import com.example.snake.managers.SoundManager;
import com.example.snake.models.FoodItem;
import com.example.snake.models.GameConfig;
import com.example.snake.utils.Constants;

import java.util.ArrayList;
import java.util.List;

/**
 * 贪吃蛇游戏视图类
 * 负责游戏的渲染、逻辑更新和用户交互
 */
public class SnakeView extends SurfaceView implements SurfaceHolder.Callback, Runnable {

    // ==================== 游戏核心变量 ====================
    private SurfaceHolder holder;          // SurfaceView的持有者,用于获取Canvas进行绘制
    private Thread gameThread;             // 游戏线程,负责游戏循环
    private boolean isRunning;             // 游戏是否正在运行
    private boolean isGameStarted;         // 游戏是否已开始(用于控制初始界面显示)
    private boolean isPaused;              // 游戏是否暂停
    
    // ==================== 游戏对象 ====================
    private List<Rect> snake;              // 蛇身体列表,每个元素是一个矩形
    private int direction;                 // 当前移动方向
    private int score;                     // 游戏分数
    private boolean isGameOver;            // 游戏是否结束
    private int foodEaten;                 // 本局吃的食物数量
    private int goldenFoodEaten;           // 本局吃的金色食物数量
    private long gameStartTime;            // 游戏开始时间
    private int gameDuration;              // 游戏时长(秒)
    private int timeRemaining;             // 时间挑战模式剩余时间(秒)
    
    // ==================== 方向常量 ====================
    public static final int UP = 0;        // 向上移动
    public static final int DOWN = 1;      // 向下移动
    public static final int LEFT = 2;      // 向左移动
    public static final int RIGHT = 3;     // 向右移动
    
    // ==================== 游戏参数 ====================
    private static final int GRID_COUNT_X = 25;         // 水平方向网格数量(增加以减小方块大小)
    private static final int GRID_COUNT_Y = 30;         // 垂直方向网格数量(增加以减小方块大小)
    
    // ==================== 动态计算的游戏参数 ====================
    private int blockSize;                 // 实际使用的方块大小
    private int offsetX;                   // 网格水平偏移量(用于居中显示)
    private int offsetY;                   // 网格垂直偏移量(用于居中显示)
    private int gridWidth;                 // 网格区域宽度
    private int gridHeight;                // 网格区域高度
    
    // ==================== 游戏配置 ====================
    private GameConfig gameConfig;         // 游戏配置
    private int gameSpeed;                 // 当前游戏速度(毫秒)
    private GameListener gameListener;     // 游戏监听器
    private SoundManager soundManager;     // 音效管理器
    
    // ==================== 核心组件 ====================
    private FoodSpawner foodSpawner;       // 食物生成器
    private PowerUpManager powerUpManager; // 道具管理器
    private ParticleSystem particleSystem; // 粒子系统
    
    // ==================== 碰撞特效 ====================
    private Rect crashLocation;            // 碰撞位置
    private long crashTime;                // 碰撞时间
    
    // ==================== 绘制相关 ====================
    private Paint snakeHeadPaint;          // 蛇头画笔
    private Paint snakeBodyPaint;          // 蛇身体画笔
    private Paint foodPaint;               // 食物画笔
    private Paint powerUpPaint;            // 道具画笔
    private Paint gridPaint;               // 网格画笔
    private Paint wallPaint;               // 墙体画笔
    private Paint textPaint;               // 文字画笔
    private Paint crashPaint;              // 碰撞特效画笔
    
    // ==================== 构造函数 ====================
    public SnakeView(Context context) {
        super(context);
        init();
    }
    
    public SnakeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    
    public SnakeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    
    // ==================== 初始化方法 ====================
    private void init() {
        // 初始化SurfaceHolder
        holder = getHolder();
        holder.addCallback(this);
        
        // 初始化游戏状态
        resetGameState();
        
        // 初始化核心组件
        foodSpawner = new FoodSpawner(getWidth(), getHeight(), 20, 0, 0, getWidth(), getHeight());
        powerUpManager = new PowerUpManager();
        particleSystem = new ParticleSystem();
        
        // 初始化游戏配置
        gameConfig = GameConfig.getDefault();
        gameSpeed = gameConfig.getDifficulty().getBaseSpeed();
        
        // 初始化画笔
        initPaints();
    }
    
    /**
     * 初始化画笔
     */
    private void initPaints() {
        // 蛇头画笔
        snakeHeadPaint = new Paint();
        snakeHeadPaint.setColor(Color.parseColor("#00FF88"));
        snakeHeadPaint.setStyle(Paint.Style.FILL);
        snakeHeadPaint.setAntiAlias(true);
        
        // 蛇身体画笔
        snakeBodyPaint = new Paint();
        snakeBodyPaint.setColor(Color.parseColor("#00CC66"));
        snakeBodyPaint.setStyle(Paint.Style.FILL);
        snakeBodyPaint.setAntiAlias(true);
        
        // 食物画笔
        foodPaint = new Paint();
        foodPaint.setStyle(Paint.Style.FILL);
        foodPaint.setAntiAlias(true);
        
        // 道具画笔
        powerUpPaint = new Paint();
        powerUpPaint.setStyle(Paint.Style.FILL);
        powerUpPaint.setAntiAlias(true);
        
        // 网格画笔
        gridPaint = new Paint();
        gridPaint.setColor(Color.parseColor("#222222"));
        gridPaint.setStyle(Paint.Style.STROKE);
        gridPaint.setStrokeWidth(1);
        
        // 墙体画笔
        wallPaint = new Paint();
        wallPaint.setColor(Color.parseColor("#00FF88"));
        wallPaint.setStyle(Paint.Style.STROKE);
        wallPaint.setStrokeWidth(3);
        wallPaint.setAntiAlias(true);
        
        // 文字画笔
        textPaint = new Paint();
        textPaint.setColor(Color.WHITE);
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setAntiAlias(true);
        textPaint.setTextAlign(Paint.Align.CENTER);
        
        // 碰撞特效画笔
        crashPaint = new Paint();
        crashPaint.setStyle(Paint.Style.FILL);
        crashPaint.setAntiAlias(true);
    }
    
    // ==================== 游戏配置方法 ====================
    
    /**
     * 配置游戏
     */
    public void configure(GameConfig config) {
        this.gameConfig = config;
        this.gameSpeed = config.getDifficulty().getBaseSpeed();
        resetGame();
    }
    
    // ==================== 游戏控制方法 ====================
    
    /**
     * 计算网格参数
     * 根据屏幕尺寸动态计算方块大小和偏移量,使网格横向铺满屏幕
     */
    private void calculateGridParams() {
        int width = getWidth();
        int height = getHeight();
        
        if (width == 0 || height == 0) return;
        
        // 基于屏幕宽度计算方块大小,确保横向铺满屏幕
        blockSize = width / GRID_COUNT_X;
        
        // 确保方块大小不小于最小值
        blockSize = Math.max(blockSize, 20);
        
        // 计算网格区域尺寸
        gridWidth = width; // 横向铺满屏幕
        gridHeight = GRID_COUNT_Y * blockSize;
        
        // 计算垂直偏移量,使网格在垂直方向居中显示
        offsetX = 0; // 横向不偏移,铺满屏幕
        offsetY = (height - gridHeight) / 2;
        
        // 重新初始化食物生成器
        if (foodSpawner != null) {
            foodSpawner = new FoodSpawner(width, height, blockSize, 0, 0, width, height);
        }
    }
    
    /**
     * 重置游戏
     * 清空蛇身、重置分数、生成新食物
     */
    public void resetGame() {
        // 重置游戏状态
        resetGameState();
        
        // 清空蛇身
        snake.clear();
        
        // 计算初始位置(网格中心)
        int startX = offsetX + (GRID_COUNT_X / 2) * blockSize;
        int startY = offsetY + (GRID_COUNT_Y / 2) * blockSize;
        
        // 创建初始蛇身(5节)
        for (int i = 0; i < 5; i++) {
            snake.add(new Rect(startX - i * blockSize, startY, 
                              startX - i * blockSize + blockSize, startY + blockSize));
        }
        
        // 重置游戏状态
        direction = RIGHT;
        score = 0;
        foodEaten = 0;
        goldenFoodEaten = 0;
        isGameOver = false;
        isGameStarted = true;
        isPaused = false;
        crashLocation = null;
        crashTime = 0;
        gameStartTime = System.currentTimeMillis();
        gameDuration = 0;
        
        // 初始化时间挑战模式
        if (gameConfig.getGameMode() == GameConfig.GameMode.TIME_CHALLENGE) {
            timeRemaining = gameConfig.getTimeLimit();
        } else {
            timeRemaining = 0;
        }
        
        // 重置核心组件
        powerUpManager.reset();
        foodSpawner.reset();
        particleSystem.clear();
        
        // 通知分数变化
        if (gameListener != null) {
            gameListener.onScoreChanged(score);
        }
    }
    
    /**
     * 重置游戏状态
     */
    private void resetGameState() {
        snake = new ArrayList<>();
        direction = RIGHT;
        score = 0;
        isGameOver = false;
        isGameStarted = false;
        isPaused = false;
        foodEaten = 0;
        goldenFoodEaten = 0;
        crashLocation = null;
        crashTime = 0;
    }
    
    /**
     * 调整游戏速度
     * 根据蛇的身体长度逐渐增加速度,更加循序渐进
     */
    private void adjustGameSpeed() {
        // 初始蛇长为5,每增加3节身体减少一次速度
        int speedDecrement = ((snake.size() - 5) / 3) * Constants.SPEED_DECREMENT;
        gameSpeed = Math.max(Constants.SPEED_MIN, gameConfig.getDifficulty().getBaseSpeed() - speedDecrement);
    }
    
    /**
     * 设置移动方向
     * 防止蛇反向移动(例如:向右移动时不能直接向左)
     */
    public void setDirection(int newDirection) {
        if ((newDirection == UP && direction != DOWN) ||
            (newDirection == DOWN && direction != UP) ||
            (newDirection == LEFT && direction != RIGHT) ||
            (newDirection == RIGHT && direction != LEFT)) {
            direction = newDirection;
        }
    }
    
    /**
     * 开始游戏
     * 启动游戏线程
     */
    public void startGame() {
        if (!isRunning) {
            isRunning = true;
            gameThread = new Thread(this);
            gameThread.start();
        }
    }
    
    /**
     * 暂停游戏
     */
    public void pauseGame() {
        isRunning = false;
    }
    
    /**
     * 切换暂停状态
     */
    public void togglePause() {
        if (isGameStarted && !isGameOver) {
            isPaused = !isPaused;
        }
    }
    
    // ==================== Getter方法 ====================
    
    /**
     * 获取游戏分数
     */
    public int getScore() {
        return score;
    }
    
    /**
     * 游戏是否已开始
     */
    public boolean isGameStarted() {
        return isGameStarted;
    }
    
    /**
     * 游戏是否暂停
     */
    public boolean isPaused() {
        return isPaused;
    }
    
    /**
     * 游戏是否结束
     */
    public boolean isGameOver() {
        return isGameOver;
    }
    
    /**
     * 获取游戏配置
     */
    public GameConfig getGameConfig() {
        return gameConfig;
    }
    
    /**
     * 设置游戏监听器
     */
    public void setGameListener(GameListener listener) {
        this.gameListener = listener;
    }
    
    /**
     * 设置音效管理器
     */
    public void setSoundManager(SoundManager soundManager) {
        this.soundManager = soundManager;
    }
    
    // ==================== SurfaceHolder回调方法 ====================
    
    /**
     * Surface创建时调用
     * 初始化蛇的位置并开始游戏
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        calculateGridParams();  // 计算网格参数
        resetGameState();
        startGame();
    }
    
    /**
     * Surface尺寸改变时调用
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        calculateGridParams();  // 重新计算网格参数
        // 如果游戏已开始,重新调整蛇和食物的位置
        if (isGameStarted && !snake.isEmpty()) {
            resetGame();
        }
    }
    
    /**
     * Surface销毁时调用
     * 暂停游戏
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        pauseGame();
    }
    
    // ==================== 游戏主循环 ====================
    
    @Override
    public void run() {
        while (isRunning) {
            // 开始时间
            long startTime = System.currentTimeMillis();
            
            // 更新游戏逻辑
            update();
            
            // 绘制游戏画面
            draw();
            
            // 计算需要睡眠的时间,控制游戏速度
            long elapsedTime = System.currentTimeMillis() - startTime;
            long sleepTime = (long) (gameSpeed * powerUpManager.getSpeedFactor()) - elapsedTime;
            if (sleepTime > 0) {
                try {
                    Thread.sleep(sleepTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    /**
     * 更新游戏逻辑
     * 移动蛇、检测碰撞、处理吃食物
     */
    private void update() {
        // 如果游戏未开始、已结束或暂停,不更新
        if (!isGameStarted || isGameOver || isPaused || snake.isEmpty()) return;
        
        // 更新游戏时长
        gameDuration = (int) ((System.currentTimeMillis() - gameStartTime) / 1000);
        
        // 更新时间挑战模式
        if (gameConfig.getGameMode() == GameConfig.GameMode.TIME_CHALLENGE) {
            timeRemaining = gameConfig.getTimeLimit() - gameDuration;
            if (timeRemaining <= 0) {
                timeRemaining = 0;
                gameOver();
                return;
            }
        }
        
        // 更新核心组件
        long currentTime = System.currentTimeMillis();
        foodSpawner.update(currentTime, snake);
        powerUpManager.update(currentTime);
        particleSystem.update(currentTime);
        
        // 调整游戏速度
        adjustGameSpeed();
        
        // 获取蛇头位置
        Rect head = snake.get(0);
        Rect newHead = new Rect(head);
        
        // 根据方向计算新的蛇头位置
        switch (direction) {
            case UP:
                newHead.top -= blockSize;
                newHead.bottom -= blockSize;
                break;
            case DOWN:
                newHead.top += blockSize;
                newHead.bottom += blockSize;
                break;
            case LEFT:
                newHead.left -= blockSize;
                newHead.right -= blockSize;
                break;
            case RIGHT:
                newHead.left += blockSize;
                newHead.right += blockSize;
                break;
        }
        
        // 检测碰撞(移除穿墙逻辑,直接检测边界)
        if (checkCollision(newHead)) {
            // 记录碰撞位置和时间,用于显示特效
            crashLocation = new Rect(newHead);
            crashTime = System.currentTimeMillis();
            particleSystem.emitDeath(newHead.left + blockSize / 2f, newHead.top + blockSize / 2f);
            gameOver();
            return;
        }
        
        // 检测是否吃到食物
        FoodItem eatenFood = foodSpawner.checkFoodCollision(newHead);
        if (eatenFood != null) {
            handleFoodEaten(eatenFood, newHead);
        } else {
            // 没吃到食物,移除尾巴,保持蛇的长度
            snake.remove(snake.size() - 1);
        }
        
        // 将新头部添加到蛇身最前面
        snake.add(0, newHead);
    }
    
    /**
     * 检测碰撞
     */
    private boolean checkCollision(Rect head) {
        // 检测是否撞墙(基于整个屏幕边界,与绘制的墙体一致)
        int minX = 0;
        int maxX = getWidth();
        int minY = 0;
        int maxY = getHeight();
        
        // 增强边界检测,确保任何超出边界的情况都能被检测到
        if (head.left < minX || head.right > maxX || head.top < minY || head.bottom > maxY) {
            return true;
        }
        
        // 检测是否撞到自己(从第2节开始检测)
        if (!powerUpManager.isInvincible()) {
            for (int i = 1; i < snake.size(); i++) {
                if (Rect.intersects(head, snake.get(i))) {
                    return true;
                }
            }
        }
        
        return false;
    }
    
    /**
     * 处理吃食物
     */
    private void handleFoodEaten(FoodItem food, Rect head) {
        // 增加分数
        int foodScore = food.getScore();
        if (powerUpManager.isDoubleScore()) {
            foodScore *= 2;
        }
        score += foodScore;
        foodEaten++;
        
        // 播放吃食物音效
        if (soundManager != null) {
            if (food.getType() == FoodItem.FoodType.SLOW_DOWN ||
                food.getType() == FoodItem.FoodType.INVINCIBLE ||
                food.getType() == FoodItem.FoodType.DOUBLE_SCORE) {
                soundManager.playSound(SoundManager.SOUND_POWER_UP);
            } else {
                soundManager.playSound(SoundManager.SOUND_EAT_FOOD);
            }
        }
        
        // 处理金色食物
        if (food.getType() == FoodItem.FoodType.GOLDEN) {
            goldenFoodEaten++;
        }
        
        // 处理道具
        if (food.getType() == FoodItem.FoodType.SLOW_DOWN ||
            food.getType() == FoodItem.FoodType.INVINCIBLE ||
            food.getType() == FoodItem.FoodType.DOUBLE_SCORE) {
            powerUpManager.activatePowerUp(food.getType(), System.currentTimeMillis());
            particleSystem.emitPowerUp(head.left + blockSize / 2f, head.top + blockSize / 2f, food.getColor());
        } else {
            particleSystem.emitFoodEaten(head.left + blockSize / 2f, head.top + blockSize / 2f, food.getColor());
        }
        
        particleSystem.emitScore(head.left + blockSize / 2f, head.top + blockSize / 2f, foodScore);
        
        // 通知分数变化
        if (gameListener != null) {
            gameListener.onScoreChanged(score);
        }
    }
    
    /**
     * 游戏结束
     */
    private void gameOver() {
        isGameOver = true;
        
        // 播放游戏结束音效
        if (soundManager != null) {
            soundManager.playSound(SoundManager.SOUND_GAME_OVER);
        }
        
        if (gameListener != null) {
            gameListener.onGameOver(score, foodEaten, gameDuration);
        }
    }
    
    /**
     * 绘制游戏画面
     */
    private void draw() {
        if (holder == null) return;
        
        Canvas canvas = null;
        try {
            canvas = holder.lockCanvas();
            if (canvas == null) return;
            
            // 绘制背景
            canvas.drawColor(Color.parseColor("#000000"));
            
            // 绘制网格
            drawGrid(canvas);
            
            // 绘制墙体
            drawWall(canvas);
            
            // 如果游戏已开始且未结束,绘制游戏元素
            if (isGameStarted && !isGameOver) {
                drawFood(canvas);           // 绘制食物
                drawSnake(canvas);          // 绘制蛇
                drawPowerUps(canvas);       // 绘制道具效果
                drawTimeRemaining(canvas);   // 绘制剩余时间
            }
            
            // 绘制特效(无论游戏是否结束都绘制)
            if (isGameStarted) {
                drawCrashEffect(canvas);    // 绘制碰撞特效
                particleSystem.draw(canvas); // 绘制粒子效果
            }
            
            // 绘制暂停界面
            if (isPaused) {
                drawPauseScreen(canvas);
            }
            
            // 绘制游戏结束界面
            if (isGameOver) {
                drawGameOverScreen(canvas);
            }
            
            // 绘制初始界面
            if (!isGameStarted) {
                drawStartScreen(canvas);
            }
            
        } finally {
            if (canvas != null) {
                holder.unlockCanvasAndPost(canvas);
            }
        }
    }
    
    /**
     * 绘制网格
     */
    private void drawGrid(Canvas canvas) {
        if (blockSize == 0) return;
        
        // 绘制垂直线
        for (int x = 0; x <= getWidth(); x += blockSize) {
            canvas.drawLine(x, 0, x, getHeight(), gridPaint);
        }
        
        // 绘制水平线
        for (int y = 0; y <= getHeight(); y += blockSize) {
            canvas.drawLine(0, y, getWidth(), y, gridPaint);
        }
    }
    
    /**
     * 绘制墙体
     */
    private void drawWall(Canvas canvas) {
        if (gridWidth == 0 || gridHeight == 0) return;
        
        // 绘制主边界,铺满整个屏幕
        int padding = 2;
        canvas.drawRect(padding, padding, 
                       getWidth() - padding, getHeight() - padding, wallPaint);
        
        // 绘制多层边界,从外到内逐渐透明
        for (int i = 0; i < 3; i++) {
            int alpha = 150 - i * 40;   // 透明度逐渐降低
            wallPaint.setAlpha(alpha);
            int offset = i * 3;
            canvas.drawRect(padding + offset, padding + offset, 
                          getWidth() - padding - offset, getHeight() - padding - offset, wallPaint);
        }
        wallPaint.setAlpha(255);  // 恢复透明度
    }
    
    /**
     * 绘制蛇
     */
    private void drawSnake(Canvas canvas) {
        if (snake.isEmpty()) return;
        
        // 绘制蛇身体
        for (int i = 1; i < snake.size(); i++) {
            Rect segment = snake.get(i);
            drawSnakeSegment(canvas, segment, Color.parseColor("#00CC66"));
        }
        
        // 绘制蛇头(特殊处理,颜色不同)
        Rect head = snake.get(0);
        drawSnakeSegment(canvas, head, Color.parseColor("#00FF88"));
    }
    
    /**
     * 绘制蛇的单个身体段
     */
    private void drawSnakeSegment(Canvas canvas, Rect segment, int color) {
        // 计算中心点和半径
        float centerX = segment.left + blockSize / 2f;
        float centerY = segment.top + blockSize / 2f;
        float radius = blockSize / 2f - 2;
        
        // 绘制圆形身体
        snakeBodyPaint.setColor(color);
        canvas.drawCircle(centerX, centerY, radius, snakeBodyPaint);
        
        // 绘制高光效果,增加立体感
        Paint highlightPaint = new Paint();
        highlightPaint.setColor(Color.parseColor("#40FF40"));
        highlightPaint.setStyle(Paint.Style.FILL);
        highlightPaint.setAntiAlias(true);
        highlightPaint.setAlpha(80);
        canvas.drawCircle(centerX - radius * 0.3f, centerY - radius * 0.3f, radius * 0.3f, highlightPaint);
    }
    
    /**
     * 绘制食物
     */
    private void drawFood(Canvas canvas) {
        for (FoodItem food : foodSpawner.getActiveFood()) {
            drawFoodItem(canvas, food);
        }
    }
    
    /**
     * 绘制单个食物
     */
    private void drawFoodItem(Canvas canvas, FoodItem food) {
        // 计算中心点和半径
        float centerX = food.getRect().left + blockSize / 2f;
        float centerY = food.getRect().top + blockSize / 2f;
        float radius = blockSize / 2f - 2;
        
        // 计算脉冲缩放
        float pulseScale = 0.85f + 0.15f * (float) Math.sin(food.getPulsePhase());
        float scaledRadius = radius * pulseScale;
        
        // 绘制食物主体
        powerUpPaint.setColor(food.getColor());
        canvas.drawCircle(centerX, centerY, scaledRadius, powerUpPaint);
        
        // 绘制食物光芒效果
        if (food.getType() == FoodItem.FoodType.GOLDEN) {
            // 金色食物特殊效果
            Paint glowPaint = new Paint();
            glowPaint.setColor(Color.parseColor("#FFFF00"));
            glowPaint.setStyle(Paint.Style.STROKE);
            glowPaint.setStrokeWidth(2);
            glowPaint.setAlpha(100);
            for (int i = 0; i < 3; i++) {
                float glowRadius = scaledRadius + i * 3;
                canvas.drawCircle(centerX, centerY, glowRadius, glowPaint);
            }
        }
    }
    
    /**
     * 绘制道具效果
     */
    private void drawPowerUps(Canvas canvas) {
        // 绘制减速效果
        if (powerUpManager.isSlowDown()) {
            Paint slowDownPaint = new Paint();
            slowDownPaint.setColor(Color.parseColor("#00AAFF"));
            slowDownPaint.setStyle(Paint.Style.STROKE);
            slowDownPaint.setStrokeWidth(2);
            slowDownPaint.setAlpha(100);
            
            // 绘制波浪效果
            long currentTime = System.currentTimeMillis();
            for (int i = 0; i < 3; i++) {
                float radius = 50 + i * 20 + (float) (Math.sin(currentTime / 100f) * 10);
                canvas.drawCircle(getWidth() / 2, 50, radius, slowDownPaint);
            }
        }
        
        // 绘制无敌效果
        if (powerUpManager.isInvincible()) {
            Paint invinciblePaint = new Paint();
            invinciblePaint.setColor(Color.parseColor("#FF44FF"));
            invinciblePaint.setStyle(Paint.Style.STROKE);
            invinciblePaint.setStrokeWidth(2);
            invinciblePaint.setAlpha(100);
            
            // 绘制闪烁效果
            long currentTime = System.currentTimeMillis();
            if ((currentTime / 100) % 2 == 0) {
                for (Rect segment : snake) {
                    float centerX = segment.left + blockSize / 2f;
                    float centerY = segment.top + blockSize / 2f;
                    float radius = blockSize / 2f + 5;
                    canvas.drawCircle(centerX, centerY, radius, invinciblePaint);
                }
            }
        }
        
        // 绘制双倍分数效果
        if (powerUpManager.isDoubleScore()) {
            Paint doubleScorePaint = new Paint();
            doubleScorePaint.setColor(Color.parseColor("#FF4444"));
            doubleScorePaint.setStyle(Paint.Style.FILL);
            doubleScorePaint.setAlpha(100);
            
            // 绘制分数翻倍图标
            textPaint.setColor(Color.parseColor("#FF4444"));
            textPaint.setTextSize(24);
            canvas.drawText("2x", 50, 50, textPaint);
        }
    }
    
    /**
     * 绘制剩余时间
     */
    private void drawTimeRemaining(Canvas canvas) {
        if (gameConfig.getGameMode() == GameConfig.GameMode.TIME_CHALLENGE) {
            textPaint.setColor(Color.parseColor("#FFAA00"));
            textPaint.setTextSize(32);
            canvas.drawText("时间: " + timeRemaining + "s", getWidth() / 2, 50, textPaint);
        }
    }
    
    /**
     * 绘制碰撞特效
     * 在碰撞位置显示爆炸效果
     */
    private void drawCrashEffect(Canvas canvas) {
        if (crashLocation == null) return;
        
        // 计算特效持续时间
        long elapsed = System.currentTimeMillis() - crashTime;
        if (elapsed > Constants.CRASH_EFFECT_DURATION) {
            crashLocation = null;
            return;
        }
        
        // 计算特效进度(0到1)
        float progress = (float) elapsed / Constants.CRASH_EFFECT_DURATION;
        int alpha = (int) (255 * (1 - progress));  // 透明度逐渐降低
        
        // 计算爆炸半径(逐渐增大)
        int radius = (int) (blockSize * (1 + progress * 4));
        
        // 选择颜色(在黄色、红色、白色、洋红色、青色之间切换,更鲜艳)
        int[] colors = {Color.YELLOW, Color.RED, Color.WHITE, Color.MAGENTA, Color.CYAN};
        int colorIndex = (int) (progress * colors.length * 2) % colors.length;
        int color = colors[colorIndex];
        
        crashPaint.setColor(color);
        crashPaint.setAlpha(alpha);
        
        // 计算碰撞位置中心
        int centerX = crashLocation.left + blockSize / 2;
        int centerY = crashLocation.top + blockSize / 2;
        
        // 绘制爆炸效果
        canvas.drawCircle(centerX, centerY, radius, crashPaint);
        
        // 绘制多重同心圆效果,增强层次感
        for (int i = 1; i <= 3; i++) {
            float innerRadius = radius * (0.3f * i);
            crashPaint.setAlpha((int) (alpha * (1 - 0.3f * i)));
            canvas.drawCircle(centerX, centerY, (int) innerRadius, crashPaint);
        }
        
        // 绘制内层发光圆
        crashPaint.setColor(Color.WHITE);
        crashPaint.setAlpha((int) (alpha * 0.5f));
        canvas.drawCircle(centerX, centerY, radius / 3, crashPaint);
        
        // 绘制外层冲击波效果
        crashPaint.setColor(color);
        crashPaint.setStyle(Paint.Style.STROKE);
        crashPaint.setStrokeWidth(2);
        crashPaint.setAlpha((int) (alpha * 0.3f));
        for (int i = 0; i < 3; i++) {
            float waveRadius = radius + i * 10;
            canvas.drawCircle(centerX, centerY, (int) waveRadius, crashPaint);
        }
    }
    
    /**
     * 绘制初始界面
     */
    private void drawStartScreen(Canvas canvas) {
        // 绘制标题 "贪吃蛇"
        textPaint.setColor(Color.parseColor("#00FF88"));
        textPaint.setTextSize(56);
        textPaint.setFakeBoldText(true);
        canvas.drawText("贪吃蛇", getWidth() / 2, getHeight() / 2 - 80, textPaint);
        
        // 绘制提示文字
        textPaint.setColor(Color.parseColor("#FFAA00"));
        textPaint.setTextSize(32);
        textPaint.setFakeBoldText(false);
        canvas.drawText("点击开始游戏", getWidth() / 2, getHeight() / 2 + 20, textPaint);
    }
    
    /**
     * 绘制游戏结束界面
     */
    private void drawGameOverScreen(Canvas canvas) {
        // 绘制 "游戏结束" 文字
        textPaint.setColor(Color.parseColor("#FF4444"));
        textPaint.setTextSize(56);
        textPaint.setFakeBoldText(true);
        canvas.drawText("游戏结束", getWidth() / 2, getHeight() / 2 - 20, textPaint);
        
        // 绘制分数
        textPaint.setColor(Color.parseColor("#FFAA00"));
        textPaint.setTextSize(36);
        canvas.drawText("分数:" + score, getWidth() / 2, getHeight() / 2 + 40, textPaint);
        
        // 绘制重新开始提示
        textPaint.setColor(Color.parseColor("#00FF88"));
        textPaint.setTextSize(28);
        textPaint.setFakeBoldText(false);
        canvas.drawText("点击开始重新游戏", getWidth() / 2, getHeight() / 2 + 90, textPaint);
    }
    
    /**
     * 绘制暂停界面
     */
    private void drawPauseScreen(Canvas canvas) {
        // 绘制半透明背景
        Paint overlayPaint = new Paint();
        overlayPaint.setColor(Color.argb(150, 0, 0, 0));
        canvas.drawRect(0, 0, getWidth(), getHeight(), overlayPaint);
        
        // 绘制 "游戏暂停" 文字
        textPaint.setColor(Color.parseColor("#00FF88"));
        textPaint.setTextSize(56);
        textPaint.setFakeBoldText(true);
        canvas.drawText("游戏暂停", getWidth() / 2, getHeight() / 2 - 20, textPaint);
        
        // 绘制提示文字
        textPaint.setColor(Color.parseColor("#FFAA00"));
        textPaint.setTextSize(28);
        textPaint.setFakeBoldText(false);
        canvas.drawText("点击继续游戏", getWidth() / 2, getHeight() / 2 + 30, textPaint);
    }
    
    // ==================== 游戏监听器接口 ====================
    public interface GameListener {
        void onScoreChanged(int score);
        void onGameOver(int score, int foodEaten, int duration);
    }
}

4.9 主活动

创建MainActivity.java文件,实现游戏的主界面和用户交互:

package com.example.snake;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.example.snake.managers.SoundManager;
import com.example.snake.models.GameConfig;
import com.tencent.mmkv.MMKV;

/**
 * 主活动类
 * 负责游戏的初始化、用户交互和界面管理
 */
public class MainActivity extends Activity {

    // ==================== 成员变量 ====================
    private SnakeView snakeView;          // 游戏视图
    private TextView scoreTextView;        // 分数显示
    private TextView highScoreTextView;    // 历史最高分显示
    private Button startButton;            // 开始/暂停按钮
    
    // ==================== 方向按钮 ====================
    private Button upButton;               // 向上按钮
    private Button downButton;             // 向下按钮
    private Button leftButton;             // 向左按钮
    private Button rightButton;            // 向右按钮
    
    // ==================== 其他 ====================
    private Handler handler;               // 用于更新UI的Handler
    private Runnable scoreUpdater;         // 分数更新任务
    
    // ==================== MMKV相关 ====================
    private MMKV mmkv;
    
    // ==================== 音效相关 ====================
    private SoundManager soundManager;

    // ==================== 生命周期方法 ====================
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 初始化MMKV
        MMKV.initialize(this);
        mmkv = MMKV.defaultMMKV();
        
        // 初始化音效管理器
        soundManager = new SoundManager(this);
        
        // 初始化视图
        initViews();
        
        // 初始化Handler和分数更新任务
        initHandler();
        
        // 显示历史最高分
        updateHighScore();
        
        // 设置音效管理器
        snakeView.setSoundManager(soundManager);
    }
    
    /**
     * 初始化视图
     */
    private void initViews() {
        // 初始化游戏视图
        snakeView = findViewById(R.id.snakeView);
        
        // 初始化分数显示
        scoreTextView = findViewById(R.id.scoreTextView);
        highScoreTextView = findViewById(R.id.highScoreTextView);
        
        // 初始化开始按钮
        startButton = findViewById(R.id.startButton);
        startButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handleStartButtonClick();
            }
        });
        
        // 初始化方向按钮
        upButton = findViewById(R.id.upButton);
        downButton = findViewById(R.id.downButton);
        leftButton = findViewById(R.id.leftButton);
        rightButton = findViewById(R.id.rightButton);
        
        // 设置方向按钮点击监听器
        upButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                snakeView.setDirection(SnakeView.UP);
            }
        });
        
        downButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                snakeView.setDirection(SnakeView.DOWN);
            }
        });
        
        leftButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                snakeView.setDirection(SnakeView.LEFT);
            }
        });
        
        rightButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                snakeView.setDirection(SnakeView.RIGHT);
            }
        });
        
        // 设置游戏监听器
        snakeView.setGameListener(new SnakeView.GameListener() {
            @Override
            public void onScoreChanged(int score) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        scoreTextView.setText("分数: " + score);
                    }
                });
            }
            
            @Override
            public void onGameOver(int score, int foodEaten, int duration) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // 更新最高分
                        updateHighScore();
                    }
                });
            }
        });
    }
    
    /**
     * 初始化Handler和分数更新任务
     */
    private void initHandler() {
        handler = new Handler();
        scoreUpdater = new Runnable() {
            @Override
            public void run() {
                // 更新分数显示
                if (snakeView != null) {
                    int score = snakeView.getScore();
                    scoreTextView.setText("分数: " + score);
                    
                    // 更新按钮状态
                    if (snakeView.isGameStarted() && !snakeView.isGameOver()) {
                        if (snakeView.isPaused()) {
                            startButton.setText("继续");
                        } else {
                            startButton.setText("暂停");
                        }
                    } else {
                        startButton.setText("开始游戏");
                    }
                }
                
                // 每100毫秒更新一次
                handler.postDelayed(this, 100);
            }
        };
        
        // 启动分数更新任务
        handler.post(scoreUpdater);
    }
    
    /**
     * 处理开始按钮点击
     */
    private void handleStartButtonClick() {
        if (snakeView.isGameStarted() && !snakeView.isGameOver()) {
            // 游戏已开始且未结束,切换暂停状态
            snakeView.togglePause();
        } else {
            // 游戏未开始或已结束,开始新游戏
            snakeView.resetGame();
        }
    }
    
    /**
     * 更新历史最高分
     */
    private void updateHighScore() {
        int highScore = mmkv.getInt("high_score", 0);
        int currentScore = snakeView.getScore();
        
        // 如果当前分数高于历史最高分,更新最高分
        if (currentScore > highScore) {
            highScore = currentScore;
            mmkv.putInt("high_score", highScore);
        }
        
        highScoreTextView.setText("最高分: " + highScore);
    }
    
    /**
     * 处理返回按钮点击
     */
    @Override
    public void onBackPressed() {
        // 显示退出确认对话框
        new AlertDialog.Builder(this)
            .setTitle("退出游戏")
            .setMessage("确定要退出游戏吗?")
            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    finish();
                }
            })
            .setNegativeButton("取消", null)
            .show();
    }
    
    /**
     * Activity销毁时调用
     * 清理资源,移除所有回调
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        handler.removeCallbacks(scoreUpdater);   // 移除分数更新任务
        if (soundManager != null) {
            soundManager.release();
        }
    }
}

4.10 布局文件

创建res/layout/activity_main.xml文件,定义游戏的界面布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000000"
    tools:context=".MainActivity">

    <!-- 游戏视图 -->
    <com.example.snake.SnakeView
        android:id="@+id/snakeView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@id/buttonLayout" />

    <!-- 分数显示 -->
    <LinearLayout
        android:id="@+id/scoreLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:orientation="horizontal"
        android:padding="10dp"
        android:background="#222222">

        <TextView
            android:id="@+id/scoreTextView"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="分数: 0"
            android:textColor="#00FF88"
            android:textSize="18sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/highScoreTextView"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="最高分: 0"
            android:textColor="#FFAA00"
            android:textSize="18sp"
            android:textStyle="bold"
            android:gravity="right" />

    </LinearLayout>

    <!-- 按钮布局 -->
    <LinearLayout
        android:id="@+id/buttonLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="vertical"
        android:padding="10dp"
        android:background="#222222">

        <!-- 开始按钮 -->
        <Button
            android:id="@+id/startButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="开始游戏"
            android:textColor="#FFFFFF"
            android:background="@drawable/start_button_bg"
            android:layout_marginBottom="10dp"
            android:padding="15dp"
            android:textSize="18sp"
            android:textStyle="bold" />

        <!-- 方向按钮 -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:gravity="center">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:gravity="center">

                <!-- 向上按钮 -->
                <Button
                    android:id="@+id/upButton"
                    android:layout_width="80dp"
                    android:layout_height="80dp"
                    android:text="↑"
                    android:textColor="#FFFFFF"
                    android:background="@drawable/start_button_bg"
                    android:textSize="24sp" />

                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal">

                    <!-- 向左按钮 -->
                    <Button
                        android:id="@+id/leftButton"
                        android:layout_width="80dp"
                        android:layout_height="80dp"
                        android:text="←"
                        android:textColor="#FFFFFF"
                        android:background="@drawable/start_button_bg"
                        android:textSize="24sp" />

                    <!-- 向右按钮 -->
                    <Button
                        android:id="@+id/rightButton"
                        android:layout_width="80dp"
                        android:layout_height="80dp"
                        android:text="→"
                        android:textColor="#FFFFFF"
                        android:background="@drawable/start_button_bg"
                        android:textSize="24sp" />
                </LinearLayout>

                <!-- 向下按钮 -->
                <Button
                    android:id="@+id/downButton"
                    android:layout_width="80dp"
                    android:layout_height="80dp"
                    android:text="↓"
                    android:textColor="#FFFFFF"
                    android:background="@drawable/start_button_bg"
                    android:textSize="24sp" />
            </LinearLayout>

        </LinearLayout>

    </LinearLayout>

</RelativeLayout>

4.11 按钮背景

创建res/drawable/start_button_bg.xml文件,定义按钮的背景样式:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape android:shape="rectangle">
            <solid android:color="#333333" />
            <stroke android:width="2dp" android:color="#00FF88" />
            <corners android:radius="8dp" />
        </shape>
    </item>
    <item>
        <shape android:shape="rectangle">
            <solid android:color="#222222" />
            <stroke android:width="2dp" android:color="#00FF88" />
            <corners android:radius="8dp" />
        </shape>
    </item>
</selector>

5. 游戏功能特性

5.1 核心功能

  • 经典贪吃蛇玩法:控制蛇移动,吃食物增长身体
  • 碰撞检测:撞到墙壁或自身会游戏结束
  • 分数系统:根据食物类型获得不同分数
  • 难度调整:根据蛇的长度逐渐增加游戏速度

5.2 特色功能

  • 多种食物类型:普通食物、 bonus食物、金色食物
  • 道具系统:减速、无敌、双倍分数
  • 粒子特效:食物被吃、死亡、道具获得时的特效
  • 音效系统:吃食物、游戏结束、获得道具的音效
  • 历史最高分:使用MMKV存储历史最高分
  • 退出确认:防止误操作退出游戏

5.3 视觉效果

  • 霓虹风格:绿色霓虹效果,营造复古游戏机氛围
  • 动态背景:网格背景和墙体效果
  • 流畅动画:蛇的移动、食物的脉冲效果
  • 碰撞特效:撞墙时的爆炸效果

6. 项目构建与运行

6.1 构建项目

在TRAE IDE中,点击"Build" -> "Make Project"构建项目。如果遇到构建错误,请检查以下几点:

  1. 依赖配置:确保build.gradle中的依赖项正确配置
  2. 资源文件:确保所有需要的资源文件都已创建
  3. 代码语法:确保代码中没有语法错误

6.2 运行项目

点击"Run" -> "Run 'app'"运行项目。选择一个模拟器或连接的设备,即可在设备上运行游戏。

7. 项目优化与扩展

7.1 性能优化

  • 线程管理:确保游戏线程正确启动和停止
  • 内存管理:及时释放不需要的资源
  • 绘制优化:减少不必要的绘制操作

7.2 功能扩展

  • 游戏模式:添加更多游戏模式,如时间挑战、障碍模式等
  • 皮肤系统:添加不同的蛇皮肤和游戏主题
  • 成就系统:实现成就解锁和奖励机制
  • 多人对战:添加本地多人对战功能

7.3 商业化升级

  • 广告集成:添加适当的广告位置
  • 内购系统:提供皮肤、道具等付费内容
  • 排行榜:实现全球或本地排行榜
  • 社交分享:添加游戏成绩分享功能

8. 总结

通过本文的详细介绍,我们成功使用TRAE工具实现了一个完整的Android原生贪吃蛇游戏。从项目初始化到功能实现,我们一步一步构建了一个具有现代感和良好用户体验的游戏应用。

这个项目不仅实现了经典贪吃蛇游戏的核心功能,还添加了许多现代化的特性,如道具系统、粒子特效、音效系统等。同时,我们采用了模块化的代码结构,使代码更加清晰和易于维护。

通过这个项目,我们学习了如何:

  • 使用SurfaceView实现游戏渲染
  • 实现游戏循环和碰撞检测
  • 使用粒子系统实现视觉特效
  • 使用MMKV实现数据持久化
  • 构建模块化的Android应用

希望本文能够帮助你理解如何使用TRAE工具开发Android游戏应用,也希望你能够在此基础上进一步扩展和优化这个项目,创造出更加出色的游戏作品。