@@ -0,0 +1,501 @@
# 机器人(人机)后端控制方案 v2.0
> 重新梳理:哪些归后端控制,哪些前端自己处理
---
## 一、边界划分
```
┌─────────────────────────────────────────────────────┐
│ 后端控制 (策略层) │
│ ├─ 总开关 / 状态 / 爆率因子 │
│ ├─ 各技能的概率范围 │
│ └─ 各状态下的行为参数范围 │
├─────────────────────────────────────────────────────┤
│ 前端自理 (执行层) │
│ ├─ 具体找哪条鱼、怎么旋转、子弹怎么飞 │
│ ├─ 按后端概率随机掷骰子决定技能开关 │
│ └─ 技能组合运行时管理、具体物理表现 │
└─────────────────────────────────────────────────────┘
```
**核心原则 ** :后端提供"概率范围",前端在范围内"随机掷骰子"。
---
## 二、后端控制字段(后端传什么)
### 2.1 数据模型
``` csharp
/// <summary>
/// 机器人AI配置 — 后端 GameConfig / RoomConfig 下发
/// </summary>
[Serializable]
public class BotConfig
{
// ===== 一、全局控制 =====
/// <summary>机器人总开关</summary>
public bool enabled ;
/// <summary>AI 状态: "active" | "idle" | "aggressive"</summary>
public string aiState ;
/// <summary>状态刷新间隔(秒), 0=后端主动推送时才变</summary>
public float stateRefreshInterval ;
/// <summary>爆率因子: 1.0=正常, >1=容易杀, <1=难杀。服务端 KillFish 判定时使用</summary>
public float luckFactor ;
// ===== 二、技能配置 (后端提供概率范围,前端随机掷骰子) =====
public BotSkillConfig skills ;
// ===== 三、各状态行为参数 =====
/// <summary>active 状态参数</summary>
public BotBehaviorConfig activeBehavior ;
/// <summary>idle 状态参数</summary>
public BotBehaviorConfig idleBehavior ;
/// <summary>aggressive 状态参数</summary>
public BotBehaviorConfig aggressiveBehavior ;
}
/// <summary>
/// 技能概率配置 — 每种技能独立,可以随机组合
/// 前端每隔 skillRefreshInterval 秒掷一次骰子决定各技能开关
/// </summary>
[Serializable]
public class BotSkillConfig
{
/// <summary>技能刷新间隔(秒): 每隔这个时间重新掷骰子决定技能组合</summary>
public float skillRefreshInterval ;
/// <summary>
/// 各技能的开关概率(0~1)。前端独立掷骰子,可以同时开多种。
/// 例如 lockProb=0.3 表示 30% 时间开启锁定
/// </summary>
/// <summary>锁定 — 自动瞄准最近鱼</summary>
public float lockProb ;
public float lockMinTime ;
public float lockMaxTime ;
/// <summary>狂暴 — 开火速度翻倍</summary>
public float frenzyProb ;
public float frenzyMinTime ;
public float frenzyMaxTime ;
/// <summary>三发散弹 — 一次打3发</summary>
public float tripleProb ;
public float tripleMinTime ;
public float tripleMaxTime ;
/// <summary>双倍炮 — 消耗翻倍/伤害翻倍</summary>
public float doubleProb ;
public float doubleMinTime ;
public float doubleMaxTime ;
/// <summary>冰冻 — 冻住全场鱼</summary>
public float freezeProb ;
public float freezeMinTime ;
public float freezeMaxTime ;
}
/// <summary>
/// 行为参数 — 每个 aiState 一组,控制该状态下机器人的表现
/// </summary>
[Serializable]
public class BotBehaviorConfig
{
/// <summary>开火间隔范围(秒): 实际值随机取 [min, max]</summary>
public float fireIntervalMin ;
public float fireIntervalMax ;
/// <summary>炮台旋转速度(度/秒)</summary>
public float rotateSpeed ;
/// <summary>目标锁定保持时间范围(秒)</summary>
public float targetHoldTimeMin ;
public float targetHoldTimeMax ;
/// <summary>超时后切换目标的概率(0~1)</summary>
public float targetSwitchChance ;
/// <summary>优先打高分鱼(0=纯距离优先, 1=纯分数优先)</summary>
public float targetPreferHighScore ;
/// <summary>是否允许打Boss</summary>
public bool canTargetBoss ;
/// <summary>是否允许打特殊鱼(红包/钻头等)</summary>
public bool canTargetSpecial ;
}
```
### 2.2 JSON 示例
``` json
{
"botConfig" : {
"enabled" : true ,
"aiState" : "active" ,
"stateRefreshInterval" : 0 ,
"luckFactor" : 1.0 ,
"skills" : {
"skillRefreshInterval" : 15 ,
"lockProb" : 0.3 , "lockMinTime" : 5 , "lockMaxTime" : 20 ,
"frenzyProb" : 0.15 , "frenzyMinTime" : 3 , "frenzyMaxTime" : 8 ,
"tripleProb" : 0.2 , "tripleMinTime" : 4 , "tripleMaxTime" : 12 ,
"doubleProb" : 0.1 , "doubleMinTime" : 3 , "doubleMaxTime" : 6 ,
"freezeProb" : 0.05 , "freezeMinTime" : 1 , "freezeMaxTime" : 3
} ,
"activeBehavior" : {
"fireIntervalMin" : 0.3 ,
"fireIntervalMax" : 1.2 ,
"rotateSpeed" : 40 ,
"targetHoldTimeMin" : 2 ,
"targetHoldTimeMax" : 5 ,
"targetSwitchChance" : 0.7 ,
"targetPreferHighScore" : 0.2 ,
"canTargetBoss" : true ,
"canTargetSpecial" : true
} ,
"idleBehavior" : {
"fireIntervalMin" : 1.5 ,
"fireIntervalMax" : 4.0 ,
"rotateSpeed" : 20 ,
"targetHoldTimeMin" : 5 ,
"targetHoldTimeMax" : 10 ,
"targetSwitchChance" : 0.3 ,
"targetPreferHighScore" : 0.1 ,
"canTargetBoss" : false ,
"canTargetSpecial" : false
} ,
"aggressiveBehavior" : {
"fireIntervalMin" : 0.1 ,
"fireIntervalMax" : 0.4 ,
"rotateSpeed" : 80 ,
"targetHoldTimeMin" : 1 ,
"targetHoldTimeMax" : 3 ,
"targetSwitchChance" : 0.9 ,
"targetPreferHighScore" : 0.6 ,
"canTargetBoss" : true ,
"canTargetSpecial" : true
}
}
}
```
---
## 三、机器人用户数据配置(每个 bot 实例独有)
### 3.1 后端在 players 列表中下发的 bot 字段
每个 bot 以 `RoomPlayerInfo` 的形式出现在 `RoomChangeResponse.players` 中,`isBot = true` 。后端需要为每个 bot 提供以下字段:
``` csharp
// 以 RoomPlayerInfo 为例,bot 需要的字段:
{
"userId" : "bot_a1b2c3d4" , // "bot_" + 8位GUID,后端生成
"userName" : "小渔夫" , // 从 BotProfile 随机选取
"avatar" : "https://..." , // 从 BotProfile 随机选取
"isBot" : true , // ← 前端据此识别机器人
"position" : 1 , // 座位号 0~3
"gold" : 50000 , // 初始金币(后端控制金额)
"diamond" : 0 , // 钻石(bot 通常为0)
"cannonId" : "1" , // 炮台皮肤ID(后端决定用什么炮)
"wingsId" : "" , // 翅膀皮肤ID
"warGodId" : "" , // 战神皮肤ID
"cannonMultiple" : 10 , // 炮倍(后端决定基础炮倍)
"beginCoin" : 50000 , // 进场金币
"beginTime" : 1718700000 , // 进场时间戳
"state" : 0 // 0=正常
}
```
### 3.2 字段职责划分
| 字段 | 谁决定 | 说明 |
|------|--------|------|
| `userId` | **后端 ** | 格式 `bot_` + GUID,后端创建 bot 玩家时生成 |
| `userName` | **后端 ** | 从 MongoDB `bot_avatar_config` 集合随机选 |
| `avatar` | **后端 ** | 同上,从 BotProfile 获取 |
| `gold` | **后端 ** | 初始金币金额。后续由服务端 `BotFireCannon` /`BotKillFish` 自动管理加减 |
| `diamond` | **后端 ** | bot 不需要钻石 |
| `cannonId` | **后端 ** | 决定 bot 用什么炮台。可随机从炮台列表选,也可固定 |
| `wingsId` | **后端 ** | bot 是否带翅膀皮肤 |
| `cannonMultiple` | **后端 ** | bot 的基础炮倍。如后端不提供,前端用 `paoBeiList[0]` 兜底 |
| `position` | **后端 ** | 按空余座位顺序分配 |
| `beginCoin` | **后端 ** | 进场金币,由后端记录 |
| `beginTime` | **后端 ** | 进场时间戳 |
### 3.3 前端处理
``` csharp
// GameMainPanel_FishBase.cs — OnShow() 中遍历 players
foreach ( var item in GameManager . Instance . gameData . currRoomData . players )
{
JoinGame ( item . position , item . gold , item . diamond , item . cannonMultiple , 0 ,
item . userId , item . cannonId , item . wingsId ) ;
// JoinGame 内部已处理: user.StartsWith("bot_") → isBot = true
}
// 不需要前端自己创建 bot!后端 players 列表已经包含所有 bot 玩家数据
```
### 3.4 后端创建 bot 流程(参考)
```
第一个真人玩家 JoinRoom
→ 从 MongoDB bot_avatar_config 随机选 robotCount 个 BotProfile
→ 为每个 BotProfile:
生成 userId = "bot_" + Guid.NewGuid().ToString("N")[..8]
随机选一个空座位 position
计算初始 gold = maxMultiple * Random(100, 501) (或其他规则)
设置 cannonId / cannonMultiple
→ 插入 Redis room.players
→ RoomJoinNotice 广播给所有在线玩家
```
### 3.5 炮倍切换
bot 的炮倍有两种玩法:
- **固定炮倍**:后端给定 `cannonMultiple` 后不再变化
- **随机切换**:前端在 `BotConfig` 中加一个 `randomCannonMultiple` 参数,bot 每隔一定时间随机换炮倍
``` csharp
// BotConfig 中可新增:
public bool randomCannonMultiple = false ; // 是否随机切换炮倍
public float cannonMultipleRefreshInterval = 30f ; // 切换间隔(秒)
public int [ ] cannonMultipleOptions = { 1 , 5 , 10 , 20 , 50 , 100 } ; // 可选炮倍列表
```
---
## 四、状态机 + 技能组合
### 4.1 状态机
```
┌──────────────────────────────────────┐
│ active (正常) │
│ 按 activeBehavior 参数运行 │
└───────┬──────────────────┬────────────┘
│ │
后端推送 │ │ 后端推送
▼ ▼
┌──────────────────┐ ┌──────────────────────┐
│ idle (待机) │ │ aggressive (激进) │
│ 懒散/低活跃度 │ │ 高频/高伤害/短间隔 │
└──────────────────┘ └──────────────────────┘
```
### 4.2 技能随机组合流程
```
每个 bot 独立运行:
每隔 skillRefreshInterval 秒:
├─ 掷骰子: lockProb → rand() < prob ? 开启锁定(持续 lockMin~lockMax 秒)
├─ 掷骰子: frenzyProb → rand() < prob ? 开启狂暴(持续 frenzyMin~frenzyMax 秒)
├─ 掷骰子: tripleProb → rand() < prob ? 开启三发(持续 tripleMin~tripleMax 秒)
├─ 掷骰子: doubleProb → rand() < prob ? 开启双倍(持续 doubleMin~doubleMax 秒)
└─ 掷骰子: freezeProb → rand() < prob ? 触发冰冻(持续 freezeMin~freezeMax 秒)
```
**组合效果示例 ** :
- `锁+三发` → 自动锁定目标后发射3发散弹 → 看起来像高手
- `狂暴+双倍` → 超高速开火且双倍伤害 → 看起来像土豪
- `三发+狂暴` → 高速连发3发散弹 → 看起来很猛
- 只开`锁定` → 普通玩家水平
- 什么都不开 → 菜鸟玩家
每种技能独立随机,**6种技能可以有 2^5=32 种组合**(冰冻不和其他技能叠加算),机器人看起来千人千面。
---
## 五、前端实现要点
### 5.1 技能运行时结构
``` csharp
// 每个 bot 座位的运行时技能状态
private class BotSkillRuntime
{
public bool lockActive ;
public float lockEndTime ;
public bool frenzyActive ;
public float frenzyEndTime ;
public bool tripleActive ;
public float tripleEndTime ;
public bool doubleActive ;
public float doubleEndTime ;
public bool freezeActive ;
public float freezeEndTime ;
public float nextRefreshTime ; // 下次掷骰子时间
}
private Dictionary < int , BotSkillRuntime > botSkills = new Dictionary < int , BotSkillRuntime > ( ) ;
```
### 5.2 HandleBotAI 改造
``` csharp
private void HandleBotAI ( )
{
BotConfig cfg = botConfig ; // 从后端读取
if ( cfg = = null | | ! cfg . enabled ) return ;
// ... 房主检查 ...
BotBehaviorConfig behavior = GetBehaviorForState ( cfg ) ;
foreach ( var sit in list_Sits )
{
if ( ! sit . isBot ) continue ;
// ... 找目标 (用 behavior.targetHoldTimeMin/Max, targetSwitchChance 等) ...
// ---- 技能刷新 ----
RefreshBotSkills ( sit , cfg . skills ) ;
// ---- 开火 (用行为参数 + 技能修正) ----
float interval = Random . Range ( behavior . fireIntervalMin , behavior . fireIntervalMax ) ;
var skill = botSkills [ sit . posID ] ;
if ( skill . frenzyActive ) interval * = 0.5f ; // 狂暴加速
botFireTimer [ sit . posID ] + = Time . deltaTime ;
if ( botFireTimer [ sit . posID ] > = interval )
{
botFireTimer [ sit . posID ] = 0f ;
// 三发/双倍通过 fireBurstCount 控制
int count = skill . tripleActive ? 3 : 1 ;
long gold = skill . doubleActive ? sit . consumeGoldNum * 2 : sit . consumeGoldNum ;
WebSocketSendList . BotFireCannon ( roomId , sit . user , gold , count ) ;
SyncFire_ForBot ( sit , sit . currentAngle ) ;
// 冰冻是独立事件,概率触发
if ( skill . freezeActive )
{
skill . freezeActive = false ;
// 触发冰冻... (调用现有的冰冻逻辑)
}
}
}
}
```
### 5.3 技能掷骰子
``` csharp
private void RefreshBotSkills ( SitPosData sit , BotSkillConfig skillCfg )
{
if ( ! botSkills . TryGetValue ( sit . posID , out var s ) )
{
s = new BotSkillRuntime ( ) ;
botSkills [ sit . posID ] = s ;
}
// 未到刷新时间
if ( Time . time < s . nextRefreshTime ) return ;
s . nextRefreshTime = Time . time + skillCfg . skillRefreshInterval ;
// 独立掷骰子
s . lockActive = s . lockActive & & Time . time < s . lockEndTime // 还在持续中就不用重新掷
| | Random . value < skillCfg . lockProb ;
if ( s . lockActive & & Time . time > = s . lockEndTime )
s . lockEndTime = Time . time + Random . Range ( skillCfg . lockMinTime , skillCfg . lockMaxTime ) ;
s . frenzyActive = s . frenzyActive & & Time . time < s . frenzyEndTime
| | Random . value < skillCfg . frenzyProb ;
if ( s . frenzyActive & & Time . time > = s . frenzyEndTime )
s . frenzyEndTime = Time . time + Random . Range ( skillCfg . frenzyMinTime , skillCfg . frenzyMaxTime ) ;
s . tripleActive = s . tripleActive & & Time . time < s . tripleEndTime
| | Random . value < skillCfg . tripleProb ;
if ( s . tripleActive & & Time . time > = s . tripleEndTime )
s . tripleEndTime = Time . time + Random . Range ( skillCfg . tripleMinTime , skillCfg . tripleMaxTime ) ;
s . doubleActive = s . doubleActive & & Time . time < s . doubleEndTime
| | Random . value < skillCfg . doubleProb ;
if ( s . doubleActive & & Time . time > = s . doubleEndTime )
s . doubleEndTime = Time . time + Random . Range ( skillCfg . doubleMinTime , skillCfg . doubleMaxTime ) ;
// 冰冻单独处理(触发型,不是持续型)
if ( Random . value < skillCfg . freezeProb )
{
s . freezeActive = true ;
// 触发冰雪效果...
}
}
```
---
## 六、需要后端新增的接口
### 6.1 Hub 事件:`SyncBotConfig`
服务端 → 所有客户端推送机器人配置变更
``` json
// 事件: connection.on("SyncBotConfig", botConfig)
{
"aiState" : "aggressive" ,
"skills" : { . . . } ,
"aggressiveBehavior" : { . . . }
}
```
**触发时机 ** :
- GM 后台修改配置 → 实时推送
- Boss 出场 → 切 aggressive
- 机器人金币不足 → 切 idle
- 定时:`stateRefreshInterval > 0` 时自动切换
### 6.2 下发位置
放在 `GameConfig.RoomConfig` 中(按房型配置)或 `RoomChangeResponse` 中(按房间实例配置)。
---
## 七、不归后端管的(前端自行处理)
| 内容 | 原因 |
|------|------|
| 具体找哪条鱼(最近/最贵) | 纯客户端渲染逻辑 |
| 旋转角度计算 | 坐标系相关,后端不感知 |
| 子弹飞行/碰撞/特效 | 纯客户端表现 |
| 技能效果视觉表现 | UI 层面 |
| Random 掷骰子的具体时机 | 服从概率就行,具体时间点前端定 |
| 不同 bot 之间的差异化 | 通过统一概率 + 各自独立 random 自然产生 |
---
## 八、总结
```
后端做的事: 前端做的事:
┌──────────────────────┐ ┌──────────────────────┐
│ 给一个概率配置 │ ──下发──► │ 拿配置自己掷骰子 │
│ "30%概率锁定5~20秒" │ │ rand < 0.3 → 锁定打开 │
│ "15%概率狂暴3~8秒" │ │ rand < 0.15 → 狂暴打开 │
│ "三状态下各参数范围" │ │ 三发×狂暴×锁定 随机组合 │
│ │ │ 每个bot独立运行 │
└──────────────────────┘ └──────────────────────┘
```
后端只关心 * * "机器人群体看起来多强/多活跃"**,前端负责 * * "具体怎么做让每个机器人看起来不一样"**。