跳转至

🎮 WxBot Enhanced 游戏插件开发指南

![Game Plugin Development](../images/game-plugin-banner.png) **打造互动性强、用户体验佳的Enhanced游戏插件**

📋 目录导航


🎯 游戏插件概述

什么是Enhanced游戏插件

Enhanced游戏插件是WxBot Enhanced的核心特色,提供:

  • 🎮 互动游戏体验 - 丰富的游戏玩法和用户交互
  • 📈 用户成长系统 - 等级、积分、成就解锁
  • 🏆 排行榜机制 - 激励用户持续参与
  • 🎨 智能UI生成 - 自动生成游戏界面图片
  • 📊 数据分析 - 游戏统计和用户画像

现有游戏插件生态

插件名称 类型 特色功能
猜成语Enhanced 知识问答 图片生成、智能提示、成语知识库
成语接龙 文字游戏 多人参与、防重复检测、超时控制
音乐竞猜 娱乐竞技 音频播放、分阶段提示、积分奖励
歇后语游戏 传统文化 文化教育、随机题库、答案解析
签到系统 日常任务 连续签到、积分累积、排行统计

🏗️ 游戏架构设计

基础游戏框架

package mygame

import (
    "sync"
    "time"
    "github.com/ruk1ng001/wxbot/engine/control"
    "github.com/ruk1ng001/wxbot/engine/robot"
)

// 游戏状态枚举
type GameStatus int

const (
    GameStatusIdle GameStatus = iota
    GameStatusWaiting
    GameStatusPlaying
    GameStatusPaused
    GameStatusEnded
)

// 游戏实例结构
type GameInstance struct {
    GameID      string                 `json:"game_id"`
    GroupID     string                 `json:"group_id"`
    PlayerID    string                 `json:"player_id"`
    Status      GameStatus             `json:"status"`
    Score       int                    `json:"score"`
    Level       int                    `json:"level"`
    StartTime   time.Time              `json:"start_time"`
    LastUpdate  time.Time              `json:"last_update"`
    GameData    map[string]interface{} `json:"game_data"`
    mutex       sync.RWMutex
}

// 游戏管理器
type GameManager struct {
    games map[string]*GameInstance
    mutex sync.RWMutex
}

var globalGameManager = &GameManager{
    games: make(map[string]*GameInstance),
}

插件注册模式

func init() {
    engine := control.Register("mygame", &control.Options{
        Alias:            "我的游戏",
        Help:            "一个示例游戏插件",
        DataFolder:      "mygame",
        DisableOnDefault: false,
    })

    // 游戏开始命令
    engine.OnPrefix("开始游戏").SetBlock(true).Handle(startGameHandler)

    // 游戏答题
    engine.OnPrefix("答").Handle(answerHandler)

    // 游戏状态查询
    engine.OnFullMatch("游戏状态").Handle(statusHandler)

    // 排行榜
    engine.OnFullMatch("排行榜").Handle(leaderboardHandler)

    // 管理员命令
    engine.OnCommand("gameadmin", robot.AdminPermission).Handle(adminHandler)
}

🎲 游戏状态管理

状态管理核心代码

// 创建游戏实例
func (gm *GameManager) CreateGame(groupID, playerID string) *GameInstance {
    gm.mutex.Lock()
    defer gm.mutex.Unlock()

    gameID := fmt.Sprintf("%s_%s_%d", groupID, playerID, time.Now().Unix())

    game := &GameInstance{
        GameID:    gameID,
        GroupID:   groupID,
        PlayerID:  playerID,
        Status:    GameStatusWaiting,
        Score:     0,
        Level:     1,
        StartTime: time.Now(),
        LastUpdate: time.Now(),
        GameData:  make(map[string]interface{}),
    }

    gm.games[gameID] = game
    return game
}

// 获取游戏实例
func (gm *GameManager) GetGame(groupID, playerID string) *GameInstance {
    gm.mutex.RLock()
    defer gm.mutex.RUnlock()

    for _, game := range gm.games {
        if game.GroupID == groupID && game.PlayerID == playerID && 
           game.Status != GameStatusEnded {
            return game
        }
    }
    return nil
}

// 更新游戏状态
func (gi *GameInstance) UpdateStatus(status GameStatus) {
    gi.mutex.Lock()
    defer gi.mutex.Unlock()

    gi.Status = status
    gi.LastUpdate = time.Now()
}

// 增加分数
func (gi *GameInstance) AddScore(points int) {
    gi.mutex.Lock()
    defer gi.mutex.Unlock()

    gi.Score += points

    // 等级升级逻辑
    newLevel := gi.Score/100 + 1
    if newLevel > gi.Level {
        gi.Level = newLevel
        // 触发升级事件
        gi.GameData["level_up"] = true
    }
}

游戏数据持久化

import (
    "gorm.io/gorm"
    "github.com/ruk1ng001/wxbot/engine/pkg/sqlite"
)

// 游戏记录数据模型
type GameRecord struct {
    ID          uint      `gorm:"primaryKey"`
    GameID      string    `gorm:"column:game_id;uniqueIndex"`
    GroupID     string    `gorm:"column:group_id;index"`
    PlayerID    string    `gorm:"column:player_id;index"`
    PlayerName  string    `gorm:"column:player_name"`
    Score       int       `gorm:"column:score"`
    Level       int       `gorm:"column:level"`
    Duration    int       `gorm:"column:duration"` // 游戏时长(秒)
    CompletedAt time.Time `gorm:"column:completed_at"`
    CreatedAt   time.Time `gorm:"column:created_at"`
}

// 保存游戏记录
func (gi *GameInstance) SaveRecord() error {
    db := sqlite.GetDB()

    record := &GameRecord{
        GameID:      gi.GameID,
        GroupID:     gi.GroupID,
        PlayerID:    gi.PlayerID,
        Score:       gi.Score,
        Level:       gi.Level,
        Duration:    int(time.Since(gi.StartTime).Seconds()),
        CompletedAt: time.Now(),
        CreatedAt:   gi.StartTime,
    }

    return db.Create(record).Error
}

// 获取用户统计
func GetPlayerStats(playerID string) (*PlayerStats, error) {
    db := sqlite.GetDB()

    var stats PlayerStats
    err := db.Model(&GameRecord{}).
        Select("player_id, COUNT(*) as total_games, MAX(score) as best_score, AVG(score) as avg_score, SUM(duration) as total_time").
        Where("player_id = ?", playerID).
        Group("player_id").
        Scan(&stats).Error

    return &stats, err
}

👥 多人游戏支持

房间管理系统

// 游戏房间结构
type GameRoom struct {
    RoomID      string                 `json:"room_id"`
    GroupID     string                 `json:"group_id"`
    OwnerID     string                 `json:"owner_id"`
    Players     map[string]*Player     `json:"players"`
    Status      GameStatus             `json:"status"`
    MaxPlayers  int                   `json:"max_players"`
    CurrentRound int                  `json:"current_round"`
    RoomData    map[string]interface{} `json:"room_data"`
    CreatedAt   time.Time             `json:"created_at"`
    mutex       sync.RWMutex
}

// 玩家结构
type Player struct {
    PlayerID   string    `json:"player_id"`
    PlayerName string    `json:"player_name"`
    Score      int       `json:"score"`
    Status     string    `json:"status"` // waiting/playing/finished
    JoinedAt   time.Time `json:"joined_at"`
}

// 房间管理器
type RoomManager struct {
    rooms map[string]*GameRoom
    mutex sync.RWMutex
}

var globalRoomManager = &RoomManager{
    rooms: make(map[string]*GameRoom),
}

// 创建房间
func (rm *RoomManager) CreateRoom(groupID, ownerID string, maxPlayers int) *GameRoom {
    rm.mutex.Lock()
    defer rm.mutex.Unlock()

    roomID := fmt.Sprintf("room_%s_%d", groupID, time.Now().Unix())

    room := &GameRoom{
        RoomID:      roomID,
        GroupID:     groupID,
        OwnerID:     ownerID,
        Players:     make(map[string]*Player),
        Status:      GameStatusWaiting,
        MaxPlayers:  maxPlayers,
        CurrentRound: 0,
        RoomData:    make(map[string]interface{}),
        CreatedAt:   time.Now(),
    }

    // 房主自动加入
    room.AddPlayer(ownerID, "房主")

    rm.rooms[roomID] = room
    return room
}

// 加入房间
func (gr *GameRoom) AddPlayer(playerID, playerName string) bool {
    gr.mutex.Lock()
    defer gr.mutex.Unlock()

    if len(gr.Players) >= gr.MaxPlayers {
        return false
    }

    if _, exists := gr.Players[playerID]; exists {
        return false
    }

    gr.Players[playerID] = &Player{
        PlayerID:   playerID,
        PlayerName: playerName,
        Score:      0,
        Status:     "waiting",
        JoinedAt:   time.Now(),
    }

    return true
}

多人游戏逻辑

// 多人游戏处理器
func multiPlayerGameHandler(ctx *robot.Ctx) {
    groupID := ctx.Event.FromWxId
    playerID := ctx.Event.FinalFromWxId
    message := ctx.MessageString()

    room := globalRoomManager.GetRoomByGroup(groupID)
    if room == nil {
        ctx.ReplyText("当前没有进行中的游戏,发送'创建房间'开始新游戏")
        return
    }

    player, exists := room.Players[playerID]
    if !exists {
        ctx.ReplyText("你还没有加入游戏,发送'加入游戏'参与")
        return
    }

    // 处理玩家回答
    result := processPlayerAnswer(room, player, message)

    // 发送结果
    ctx.ReplyText(result.Message)

    // 检查游戏是否结束
    if result.GameEnd {
        endMultiPlayerGame(ctx, room)
    }
}

// 结束多人游戏
func endMultiPlayerGame(ctx *robot.Ctx, room *GameRoom) {
    // 计算排名
    rankings := calculateRankings(room)

    // 发送结果
    resultMsg := "🏆 游戏结束!最终排名:\\n"
    for i, player := range rankings {
        medal := "🥇"
        if i == 1 {
            medal = "🥈"
        } else if i == 2 {
            medal = "🥉"
        } else {
            medal = fmt.Sprintf("%d.", i+1)
        }
        resultMsg += fmt.Sprintf("%s %s - %d分\\n", medal, player.PlayerName, player.Score)
    }

    ctx.ReplyText(resultMsg)

    // 保存游戏记录
    saveMultiPlayerGameRecord(room, rankings)

    // 清理房间
    globalRoomManager.RemoveRoom(room.RoomID)
}

📊 用户成长体系

等级系统设计

// 用户成长数据模型
type UserGrowth struct {
    ID           uint      `gorm:"primaryKey"`
    UserID       string    `gorm:"column:user_id;uniqueIndex"`
    UserName     string    `gorm:"column:user_name"`
    Level        int       `gorm:"column:level;default:1"`
    Experience   int       `gorm:"column:experience;default:0"`
    TotalGames   int       `gorm:"column:total_games;default:0"`
    TotalWins    int       `gorm:"column:total_wins;default:0"`
    TotalScore   int       `gorm:"column:total_score;default:0"`
    Achievements []string  `gorm:"column:achievements;type:json"`
    LastPlayed   time.Time `gorm:"column:last_played"`
    CreatedAt    time.Time `gorm:"column:created_at"`
    UpdatedAt    time.Time `gorm:"column:updated_at"`
}

// 经验值配置
var expConfig = map[int]int{
    1: 100,   // 升到1级需要100经验
    2: 300,   // 升到2级需要300经验
    3: 600,   // 升到3级需要600经验
    4: 1000,  // 升到4级需要1000经验
    5: 1500,  // 升到5级需要1500经验
}

// 增加经验值
func (ug *UserGrowth) AddExperience(exp int) bool {
    ug.Experience += exp

    // 检查是否升级
    oldLevel := ug.Level
    for level, requiredExp := range expConfig {
        if ug.Experience >= requiredExp && level > ug.Level {
            ug.Level = level
        }
    }

    return ug.Level > oldLevel // 返回是否升级
}

// 更新游戏统计
func UpdateUserStats(userID, userName string, gameResult GameResult) {
    db := sqlite.GetDB()

    var userGrowth UserGrowth
    err := db.Where("user_id = ?", userID).First(&userGrowth).Error
    if err != nil {
        // 创建新用户
        userGrowth = UserGrowth{
            UserID:   userID,
            UserName: userName,
            Level:    1,
            Experience: 0,
        }
    }

    // 更新统计
    userGrowth.TotalGames++
    userGrowth.TotalScore += gameResult.Score
    userGrowth.LastPlayed = time.Now()

    if gameResult.IsWin {
        userGrowth.TotalWins++
    }

    // 增加经验
    expGained := calculateExperience(gameResult)
    levelUp := userGrowth.AddExperience(expGained)

    // 检查成就
    newAchievements := checkAchievements(&userGrowth, gameResult)
    userGrowth.Achievements = append(userGrowth.Achievements, newAchievements...)

    // 保存到数据库
    if userGrowth.ID == 0 {
        db.Create(&userGrowth)
    } else {
        db.Save(&userGrowth)
    }

    // 发送升级通知
    if levelUp {
        sendLevelUpNotification(userID, userGrowth.Level)
    }

    // 发送成就通知
    for _, achievement := range newAchievements {
        sendAchievementNotification(userID, achievement)
    }
}

成就系统

// 成就定义
type Achievement struct {
    ID          string `json:"id"`
    Name        string `json:"name"`
    Description string `json:"description"`
    Icon        string `json:"icon"`
    Condition   func(*UserGrowth, GameResult) bool `json:"-"`
}

var achievements = []Achievement{
    {
        ID:          "first_game",
        Name:        "初试身手",
        Description: "完成第一场游戏",
        Icon:        "🎮",
        Condition: func(ug *UserGrowth, gr GameResult) bool {
            return ug.TotalGames == 1
        },
    },
    {
        ID:          "win_streak_5",
        Name:        "连胜达人",
        Description: "连续获胜5场",
        Icon:        "🔥",
        Condition: func(ug *UserGrowth, gr GameResult) bool {
            return gr.WinStreak >= 5
        },
    },
    {
        ID:          "high_score_1000",
        Name:        "高分选手",
        Description: "单局得分超过1000",
        Icon:        "⭐",
        Condition: func(ug *UserGrowth, gr GameResult) bool {
            return gr.Score >= 1000
        },
    },
}

// 检查成就
func checkAchievements(userGrowth *UserGrowth, gameResult GameResult) []string {
    var newAchievements []string

    for _, achievement := range achievements {
        // 检查是否已获得
        hasAchievement := false
        for _, existingAchievement := range userGrowth.Achievements {
            if existingAchievement == achievement.ID {
                hasAchievement = true
                break
            }
        }

        // 检查条件
        if !hasAchievement && achievement.Condition(userGrowth, gameResult) {
            newAchievements = append(newAchievements, achievement.ID)
        }
    }

    return newAchievements
}

🎨 游戏UI设计

图片生成系统

import (
    "image"
    "image/color"
    "image/draw"
    "image/png"
    "golang.org/x/image/font"
    "golang.org/x/image/font/basicfont"
    "golang.org/x/image/math/fixed"
)

// 游戏UI生成器
type GameUIGenerator struct {
    width  int
    height int
    bgColor color.RGBA
    font   font.Face
}

// 创建游戏界面图片
func (g *GameUIGenerator) GenerateGameUI(gameData GameUIData) ([]byte, error) {
    // 创建图片
    img := image.NewRGBA(image.Rect(0, 0, g.width, g.height))

    // 填充背景
    draw.Draw(img, img.Bounds(), &image.Uniform{g.bgColor}, image.ZP, draw.Src)

    // 绘制标题
    g.drawText(img, gameData.Title, 50, 50, color.RGBA{0, 0, 0, 255})

    // 绘制分数
    scoreText := fmt.Sprintf("分数: %d", gameData.Score)
    g.drawText(img, scoreText, 50, 100, color.RGBA{0, 100, 0, 255})

    // 绘制等级
    levelText := fmt.Sprintf("等级: %d", gameData.Level)
    g.drawText(img, levelText, 200, 100, color.RGBA{0, 0, 100, 255})

    // 绘制游戏内容
    g.drawGameContent(img, gameData)

    // 转换为PNG
    var buf bytes.Buffer
    err := png.Encode(&buf, img)
    if err != nil {
        return nil, err
    }

    return buf.Bytes(), nil
}

// 绘制文本
func (g *GameUIGenerator) drawText(img *image.RGBA, text string, x, y int, col color.RGBA) {
    point := fixed.Point26_6{
        X: fixed.Int26_6(x * 64),
        Y: fixed.Int26_6(y * 64),
    }

    d := &font.Drawer{
        Dst:  img,
        Src:  &image.Uniform{col},
        Face: g.font,
        Dot:  point,
    }
    d.DrawString(text)
}

响应式界面设计

// 自适应UI数据
type ResponsiveUIData struct {
    DeviceType string // mobile/desktop
    ScreenSize string // small/medium/large
    Theme      string // light/dark
    Language   string // zh/en
}

// 生成响应式UI
func GenerateResponsiveGameUI(gameData GameUIData, uiConfig ResponsiveUIData) ([]byte, error) {
    var generator *GameUIGenerator

    switch uiConfig.ScreenSize {
    case "small":
        generator = &GameUIGenerator{width: 400, height: 300}
    case "medium":
        generator = &GameUIGenerator{width: 600, height: 450}
    case "large":
        generator = &GameUIGenerator{width: 800, height: 600}
    default:
        generator = &GameUIGenerator{width: 600, height: 450}
    }

    // 设置主题色彩
    if uiConfig.Theme == "dark" {
        generator.bgColor = color.RGBA{30, 30, 30, 255}
    } else {
        generator.bgColor = color.RGBA{240, 240, 240, 255}
    }

    return generator.GenerateGameUI(gameData)
}

⚡ 性能优化

游戏数据缓存

import (
    "sync"
    "time"
)

// 游戏缓存管理器
type GameCache struct {
    data  map[string]interface{}
    mutex sync.RWMutex
    ttl   map[string]time.Time
}

var gameCache = &GameCache{
    data: make(map[string]interface{}),
    ttl:  make(map[string]time.Time),
}

// 设置缓存
func (gc *GameCache) Set(key string, value interface{}, duration time.Duration) {
    gc.mutex.Lock()
    defer gc.mutex.Unlock()

    gc.data[key] = value
    gc.ttl[key] = time.Now().Add(duration)
}

// 获取缓存
func (gc *GameCache) Get(key string) (interface{}, bool) {
    gc.mutex.RLock()
    defer gc.mutex.RUnlock()

    // 检查是否过期
    if expireTime, exists := gc.ttl[key]; exists {
        if time.Now().After(expireTime) {
            delete(gc.data, key)
            delete(gc.ttl, key)
            return nil, false
        }
    }

    value, exists := gc.data[key]
    return value, exists
}

// 清理过期缓存
func (gc *GameCache) cleanup() {
    gc.mutex.Lock()
    defer gc.mutex.Unlock()

    now := time.Now()
    for key, expireTime := range gc.ttl {
        if now.After(expireTime) {
            delete(gc.data, key)
            delete(gc.ttl, key)
        }
    }
}

// 启动清理协程
func init() {
    go func() {
        ticker := time.NewTicker(5 * time.Minute)
        defer ticker.Stop()

        for range ticker.C {
            gameCache.cleanup()
        }
    }()
}

数据库连接池优化

// 优化的数据库操作
func GetUserStatsOptimized(userID string) (*UserGrowth, error) {
    // 先检查缓存
    cacheKey := fmt.Sprintf("user_stats_%s", userID)
    if cached, exists := gameCache.Get(cacheKey); exists {
        return cached.(*UserGrowth), nil
    }

    // 查询数据库
    db := sqlite.GetDB()
    var userGrowth UserGrowth
    err := db.Where("user_id = ?", userID).First(&userGrowth).Error
    if err != nil {
        return nil, err
    }

    // 缓存结果(10分钟)
    gameCache.Set(cacheKey, &userGrowth, 10*time.Minute)

    return &userGrowth, nil
}

🧪 游戏测试

单元测试框架

package mygame

import (
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/ruk1ng001/wxbot/engine/robot"
)

// 测试游戏创建
func TestCreateGame(t *testing.T) {
    manager := &GameManager{
        games: make(map[string]*GameInstance),
    }

    game := manager.CreateGame("group123", "user456")

    assert.NotNil(t, game)
    assert.Equal(t, "group123", game.GroupID)
    assert.Equal(t, "user456", game.PlayerID)
    assert.Equal(t, GameStatusWaiting, game.Status)
}

// 测试游戏逻辑
func TestGameLogic(t *testing.T) {
    game := &GameInstance{
        GameID:   "test_game",
        GroupID:  "group123",
        PlayerID: "user456",
        Status:   GameStatusPlaying,
        Score:    0,
        Level:    1,
    }

    // 测试答对题目
    result := processAnswer(game, "正确答案")
    assert.True(t, result.Correct)
    assert.True(t, game.Score > 0)
}

// 测试经验值系统
func TestExperienceSystem(t *testing.T) {
    userGrowth := &UserGrowth{
        Level:      1,
        Experience: 50,
    }

    levelUp := userGrowth.AddExperience(100)
    assert.True(t, levelUp)
    assert.Equal(t, 2, userGrowth.Level)
    assert.Equal(t, 150, userGrowth.Experience)
}

集成测试

// 集成测试:完整游戏流程
func TestCompleteGameFlow(t *testing.T) {
    // 模拟机器人上下文
    ctx := &robot.Ctx{
        Event: &robot.Event{
            FromWxId:      "group123",
            FinalFromWxId: "user456",
            Message: &robot.Message{
                Content: "开始游戏",
            },
        },
    }

    // 测试开始游戏
    startGameHandler(ctx)
    game := globalGameManager.GetGame("group123", "user456")
    assert.NotNil(t, game)
    assert.Equal(t, GameStatusPlaying, game.Status)

    // 测试回答问题
    ctx.Event.Message.Content = "答案A"
    answerHandler(ctx)

    // 验证分数更新
    assert.True(t, game.Score > 0)
}

性能测试

// 并发性能测试
func TestConcurrentGames(t *testing.T) {
    const numGames = 100
    const numPlayers = 10

    var wg sync.WaitGroup

    for i := 0; i < numGames; i++ {
        wg.Add(1)
        go func(gameIndex int) {
            defer wg.Done()

            groupID := fmt.Sprintf("group%d", gameIndex)

            for j := 0; j < numPlayers; j++ {
                playerID := fmt.Sprintf("user%d_%d", gameIndex, j)

                game := globalGameManager.CreateGame(groupID, playerID)
                assert.NotNil(t, game)

                // 模拟游戏过程
                for k := 0; k < 10; k++ {
                    game.AddScore(10)
                }

                game.UpdateStatus(GameStatusEnded)
            }
        }(i)
    }

    wg.Wait()

    // 验证所有游戏都正确创建
    assert.Equal(t, numGames*numPlayers, len(globalGameManager.games))
}

📚 相关文档


**🎮 创造有趣的游戏,让用户爱不释手!** **让WxBot Enhanced成为最受欢迎的游戏平台!**