一、前言
社交推理游戏如鹅鸭杀、狼人杀深受玩家喜爱,但"凑不齐人""队友挂机""新手体验差"等痛点长期存在。AI队友的引入能有效填补空位、提供陪练、降低门槛,让玩家随时随地享受高质量对局。本文将详细介绍如何利用即构ZEGO AI Agent,在Android项目中为鹅鸭杀、狼人杀类游戏快速集成AI队友功能。
二、技术方案整体介绍
2.1 叠加式架构设计
AI Agent层以"外挂"形式叠加在现有游戏架构之上,无需改动原游戏核心逻辑:
┌─────────────────────────────────────────────────────────┐
│ Android游戏客户端 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ 游戏逻辑层 │ │ 游戏状态机 │ │ UI表现层 │ │
│ │(原逻辑不变) │ │ (回合/投票) │ │ (角色/发言UI) │ │
│ └──────┬──────┘ └──────┬──────┘ └────────┬────────┘ │
│ │ │ │ │
│ ┌──────┴────────────────┴───────────────────┴──────┐ │
│ │ AI Agent适配层 (新增) │ │
│ │ • 状态同步接口 • 语音数据转发 • AI指令解析 │ │
│ └─────────────────────┬─────────────────────────────┘ │
└────────────────────────┼────────────────────────────────┘
│
┌──────────┴──────────┐
│ ZEGO AI Agent │
│ LLM + TTS + ASR │
└─────────────────────┘
2.2 核心模块说明
RTC语音模块:基于ZEGO Express SDK实现玩家与AI的实时语音通话,支持AI降噪(ANS)、回声消除(AEC)、人声检测(VAD),确保多人语音场景下的清晰度。
AI Agent模块:负责智能体的生命周期管理,包括:
- LLM:处理对话理解、推理决策、话术生成
- TTS:将AI回复转换为自然语音
- ASR:将玩家语音转为文字供LLM理解
- 状态同步:将游戏状态(回合、角色、投票)注入AI上下文
SDK嵌入方式:
- Android端集成ZEGO Express SDK(已支持Android)
- AI Agent通过服务端API调用,Android通过HTTP/HTTPS与业务后台交互
- 业务后台负责注册智能体、创建实例、转发游戏状态
三、AI队友功能拆解
3.1 角色配置
AI队友的人设通过SystemPrompt精细控制,以下是一个鹅鸭杀"鸭子"角色的配置示例:
// 鹅鸭杀鸭子角色SystemPrompt
public static String getDuckSystemPrompt(String playerName) {
return String.format(
"你是鹅鸭杀游戏中的鸭子阵营玩家,你的名字是%s。\n\n" +
"【角色身份】\n" +
"- 你是坏人阵营,目标是淘汰所有鹅阵营玩家\n" +
"- 你知道其他鸭子的身份(队友),但不要暴露他们\n" +
"- 你需要伪装成鹅来骗取信任\n\n" +
"【性格特点】\n" +
"- 狡猾、谨慎,善于伪装\n" +
"- 发言时会制造混乱,转移怀疑目标\n" +
"- 遇到质疑时会冷静辩解,必要时卖队友自保\n\n" +
"【发言策略】\n" +
"- 开局阶段:观察为主,偶尔附和他人观点\n" +
"- 中期阶段:带节奏,把怀疑引向鹅玩家或中立角色\n" +
"- 后期阶段:如果被怀疑,制造\"我是大白鹅\"的假象\n\n" +
"【输出要求】\n" +
"- 每次发言控制在2-3句话\n" +
"- 使用口语化表达,像真实玩家一样说话\n" +
"- 可以适当说\"我觉得xx有点可疑\"\"我当时在xxx做任务\"等话术\n" +
"- 不要说出\"我是AI\"或暴露游戏机制",
playerName
);
}
配置要点:
- 角色身份:明确阵营、目标、信息边界(AI不知道其他玩家的真实身份)
- 性格特点:决定AI的行为风格(激进/保守/狡猾/老实)
- 发言策略:根据游戏阶段(开局/中期/后期)调整行为模式
- 输出要求:控制发言长度和语言风格,模拟真人玩家
3.2 发言逻辑
AI发言的触发时机和上下文注入:
触发时机:
- 轮到自己发言:游戏状态机检测到当前是AI玩家的发言回合,调用
主动调用LLM接口 - 自由讨论阶段:玩家发言结束后,AI根据上下文判断是否插话(通过打断机制)
- 紧急事件:发现尸体、触发紧急任务时,AI主动发言
实时上下文注入:
// 构建AI上下文消息
public List<AIAgentMessage> buildContext(GameState state, String aiPlayerId) {
List<AIAgentMessage> messages = new ArrayList<>();
// 1. 注入系统提示(角色设定)
messages.add(new AIAgentMessage("system", getDuckSystemPrompt(aiPlayerId)));
// 2. 注入游戏状态(当前回合、存活玩家等)
String alivePlayers = String.join(",", state.getAlivePlayers());
messages.add(new AIAgentMessage("user",
String.format("【游戏状态】当前回合:%d,存活玩家:%s,你的上一轮发言后:%s",
state.getRound(), alivePlayers, state.getLastEvents())));
// 3. 注入历史发言记录(最近N条)
for (ChatMessage chat : state.getRecentChats()) {
String role = chat.getPlayerId().equals(aiPlayerId) ? "assistant" : "user";
messages.add(new AIAgentMessage(role,
String.format("%s:%s", chat.getPlayerName(), chat.getContent())));
}
return messages;
}
发言控制策略:
- 发言长度:通过Prompt要求AI控制在2-3句话,避免长篇大论
- 发言时机:利用AI Agent的
VADSilenceSegmentation参数,设置500ms静音阈值,确保玩家说完后AI再发言 - 打断处理:开启语音打断,当玩家紧急插话时AI立即停止发言
3.3 听与理解
AI如何获取真人语音并更新决策:
语音流转文字:
// 在Unity中监听RTC语音流
public void OnRemoteAudioFrame(string streamId, AudioFrame frame)
{
// 将语音数据转发给AI Agent进行ASR识别
// AI Agent会自动将识别的文字推送给LLM
}
// 接收AI Agent返回的ASR结果(通过回调)
public void OnASRResult(string playerId, string text)
{
// 将玩家发言内容更新到游戏状态
gameState.AddChatLog(playerId, text);
// 通知所有AI玩家更新上下文
foreach (var ai in aiPlayers)
{
UpdateAIContext(ai.InstanceId, gameState);
}
}
决策更新流程:
- 玩家语音 → RTC传输 → AI Agent ASR识别 → 文字内容
- 文字内容注入到AI的
MessageHistory上下文 - LLM基于新信息重新分析局势,更新内部推理状态
- 当需要AI发言时,LLM生成基于最新局势的回复
3.4 投票与行动
基于局势的投票逻辑:
AI的投票不是随机的,而是基于LLM的推理:
// 请求AI进行投票
public void requestAIVote(String aiInstanceId, GameState state, AIVoteCallback callback) {
// 构建投票请求Prompt
String alivePlayers = String.join(",", state.getAlivePlayers());
String votePrompt = String.format(
"现在进入投票环节,你需要投票淘汰一名玩家。\n" +
"存活玩家:%s\n" +
"上轮发言摘要:%s\n" +
"你的怀疑对象:根据发言分析谁最可疑\n\n" +
"请从以下玩家中选择一名进行投票,只返回玩家ID:%s",
alivePlayers, state.getChatSummary(), alivePlayers
);
// 调用AI Agent的主动调用LLM接口
aiAgentClient.sendLLMRequest(aiInstanceId, votePrompt, new AIAgentCallback() {
@Override
public void onSuccess(String response) {
// 解析返回的玩家ID
String votedPlayerId = parseVoteResponse(response);
callback.onResult(new VoteResult(aiInstanceId, votedPlayerId));
}
@Override
public void onError(Exception e) {
callback.onError(e);
}
});
}
状态机流转:
白天发言 → 投票阶段 → 夜晚行动 → 白天发言
↓ ↓ ↓
AI倾听 AI投票 AI技能
玩家发言 基于推理 使用技能
更新上下文 选择目标 (刀人/查验等)
四、开发流程与实战代码
4.1 准备工作:进入即构控制台
在正式开发前,需要先在即构控制台完成以下配置:
步骤1:创建项目获取AppID
- 登录即构控制台,点击"创建项目"
- 选择"实时互动AI Agent"服务,记录生成的AppID和AppSign
步骤2:开通AI Agent服务
- 在项目管理页,找到"实时互动AI Agent"模块
- 点击"立即开通",完成服务开通(新用户可免费试用)
步骤3:获取ServerSecret
- 进入"项目配置" → "密钥管理"
- 复制ServerSecret,用于服务端API调用签名生成
步骤4:配置LLM和TTS(可选)
- 在"AI Agent配置"页,可以配置默认的LLM和TTS参数
- 支持火山引擎、MiniMax、阿里云等多家厂商
完成以上配置后,即可在Unity项目中集成ZEGO AI Agent SDK。
4.2 AI Agent初始化注册
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class ZegoAIAgentManager {
private static final String API_BASE = "https://aigc-aiagent-api.zegotech.cn";
private static final String TAG = "ZegoAIAgentManager";
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
private final String appId;
private final String serverSecret;
private final OkHttpClient httpClient;
public ZegoAIAgentManager(String appId, String serverSecret) {
this.appId = appId;
this.serverSecret = serverSecret;
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build();
}
// 注册AI智能体(通常在服务端完成,Android调用业务后台接口)
public void registerAgent(String agentId, String agentName, String systemPrompt,
AgentCallback callback) {
String timestamp = getTimestamp();
String signature = generateSignature();
String url = String.format(
"%s?Action=RegisterAgent&AppId=%s&Timestamp=%s&Signature=%s",
API_BASE, appId, timestamp, signature
);
try {
JSONObject body = new JSONObject();
body.put("Name", agentName);
JSONObject llm = new JSONObject();
llm.put("Url", "https://ark.cn-beijing.volces.com/api/v3/chat/completions");
llm.put("ApiKey", "your_api_key"); // 替换为真实密钥
llm.put("Model", "doubao-1-5-pro-32k-250115");
llm.put("SystemPrompt", systemPrompt);
llm.put("Temperature", 0.7);
llm.put("TopP", 0.9);
body.put("LLM", llm);
JSONObject tts = new JSONObject();
tts.put("Vendor", "ByteDance");
JSONObject ttsParams = new JSONObject();
JSONObject ttsApp = new JSONObject();
ttsApp.put("appid", "your_tts_appid");
ttsApp.put("token", "your_tts_token");
ttsApp.put("cluster", "volcano_tts");
ttsParams.put("app", ttsApp);
JSONObject ttsAudio = new JSONObject();
ttsAudio.put("voice_type", "zh_female_wanwanxiaohe_moon_bigtts");
ttsAudio.put("speed_ratio", 1.0);
ttsParams.put("audio", ttsAudio);
tts.put("Params", ttsParams);
body.put("TTS", tts);
JSONObject asr = new JSONObject();
asr.put("VADSilenceSegmentation", 500); // 500ms静音分割
asr.put("VADMinSpeechDuration", 100); // 最小100ms才算有效语音
body.put("ASR", asr);
RequestBody requestBody = RequestBody.create(body.toString(), JSON);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "注册失败: " + e.getMessage());
callback.onError(e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
Log.i(TAG, "智能体 " + agentId + " 注册成功");
callback.onSuccess(agentId);
} else {
Log.e(TAG, "注册失败: " + response.body().string());
callback.onError(new Exception("注册失败: " + response.code()));
}
}
});
} catch (JSONException e) {
callback.onError(e);
}
}
private String getTimestamp() {
return String.valueOf(System.currentTimeMillis() / 1000);
}
private String generateSignature() {
// 签名生成逻辑(参考即构签名文档)
// 实际应在服务端完成签名,避免暴露serverSecret
return "signature";
}
public interface AgentCallback {
void onSuccess(String agentId);
void onError(Exception e);
}
}
4.3 创建AI队友实例
// 创建AI智能体实例,将AI加入RTC房间
public void createAIAgentInstance(String agentId, String roomId, String aiUserId,
CreateInstanceCallback callback) {
String timestamp = getTimestamp();
String signature = generateSignature();
String url = String.format(
"%s?Action=CreateAgentInstance&AppId=%s&Timestamp=%s&Signature=%s",
API_BASE, appId, timestamp, signature
);
try {
JSONObject body = new JSONObject();
body.put("AgentId", agentId);
JSONObject rtc = new JSONObject();
rtc.put("RoomId", roomId);
rtc.put("UserId", aiUserId); // AI玩家的UserId
rtc.put("StreamId", aiUserId + "_main"); // AI的推流ID
body.put("RTC", rtc);
JSONObject messageHistory = new JSONObject();
messageHistory.put("SyncMode", 1); // 使用MessageHistory模式
messageHistory.put("Messages", new org.json.JSONArray()); // 初始空上下文
messageHistory.put("WindowSize", 20); // 每次调用LLM使用最近20条消息
body.put("MessageHistory", messageHistory);
JSONObject advancedConfig = new JSONObject();
advancedConfig.put("MaxIdleTime", 300); // 300秒无操作自动销毁
advancedConfig.put("InterruptMode", 0); // 开启语音打断
body.put("AdvancedConfig", advancedConfig);
RequestBody requestBody = RequestBody.create(body.toString(), JSON);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "创建实例失败: " + e.getMessage());
callback.onError(e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
Log.i(TAG, "AI实例创建成功,已进入房间 " + roomId);
callback.onSuccess();
} else {
Log.e(TAG, "创建实例失败: " + response.body().string());
callback.onError(new Exception("创建失败: " + response.code()));
}
}
});
} catch (JSONException e) {
callback.onError(e);
}
}
public interface CreateInstanceCallback {
void onSuccess();
void onError(Exception e);
}
4.4 完整发言调用链
// 主动调用AI发言(由游戏状态机触发)
public void triggerAISpeak(String aiInstanceId, GameState state) {
// 1. 构建当前上下文(包含最新游戏状态)
List<AIAgentMessage> contextMessages = buildContext(state, aiInstanceId);
// 2. 更新AI的上下文
updateAIContext(aiInstanceId, contextMessages, new SimpleCallback() {
@Override
public void onSuccess() {
// 3. 触发AI发言(调用LLM生成回复)
String timestamp = getTimestamp();
String signature = generateSignature();
String url = String.format(
"%s?Action=SendAgentInstanceLLM&AppId=%s&Timestamp=%s&Signature=%s",
API_BASE, appId, timestamp, signature
);
try {
JSONObject body = new JSONObject();
body.put("InstanceId", aiInstanceId);
body.put("Prompt", "现在轮到你发言,请根据当前局势发表你的看法。");
body.put("AddToHistory", true); // 将回复加入上下文历史
RequestBody requestBody = RequestBody.create(body.toString(), JSON);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "触发AI发言失败: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) {
// AI Agent会自动完成:
// 1. LLM生成回复 → 2. TTS合成语音 → 3. 通过RTC推流播放
// Android端只需拉取AI的音频流即可听到AI发言
Log.i(TAG, "AI发言触发成功");
}
});
} catch (JSONException e) {
Log.e(TAG, "触发AI发言失败: " + e.getMessage());
}
}
@Override
public void onError(Exception e) {
Log.e(TAG, "更新AI上下文失败: " + e.getMessage());
}
});
}
// 更新AI上下文
public void updateAIContext(String instanceId, List<AIAgentMessage> messages,
SimpleCallback callback) {
String url = String.format("%s?Action=ResetAgentInstanceMsgList&AppId=%s",
API_BASE, appId);
try {
JSONObject body = new JSONObject();
body.put("InstanceId", instanceId);
org.json.JSONArray messagesArray = new org.json.JSONArray();
for (AIAgentMessage msg : messages) {
JSONObject msgObj = new JSONObject();
msgObj.put("role", msg.getRole());
msgObj.put("content", msg.getContent());
messagesArray.put(msgObj);
}
body.put("Messages", messagesArray);
RequestBody requestBody = RequestBody.create(body.toString(), JSON);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
callback.onError(e);
}
@Override
public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
callback.onSuccess();
} else {
callback.onError(new Exception("更新失败: " + response.code()));
}
}
});
} catch (JSONException e) {
callback.onError(e);
}
}
public interface SimpleCallback {
void onSuccess();
void onError(Exception e);
}
4.5 真人发言监听与状态更新
public class GameVoiceManager {
private ZegoExpressEngine engine;
private ZegoAIAgentManager aiAgentManager;
private GameState gameState;
private UIManager uiManager;
private static final String TAG = "GameVoiceManager";
public void init(Context context, long appId, String appSign) {
// 初始化ZEGO Express SDK
ZegoEngineProfile profile = new ZegoEngineProfile();
profile.appID = appId;
profile.appSign = appSign;
profile.scenario = ZegoScenario.GENERAL;
profile.application = context.getApplicationContext();
ZegoExpressEngine.createEngine(profile, new IZegoEventHandler() {
@Override
public void onRoomStateUpdate(String roomID, ZegoRoomState state, int errorCode, JSONObject extendedData) {
Log.i(TAG, "房间状态更新: " + roomID + ", state: " + state);
}
});
engine = ZegoExpressEngine.getEngine();
// 注册AI Agent相关回调
registerAIAgentCallbacks();
}
// 注册AI Agent回调
private void registerAIAgentCallbacks() {
// AI开始说话回调(通过业务后台回调或RTC SEI消息接收)
onAgentSpeakStart = instanceId -> {
// 更新UI:显示AI正在发言
uiManager.showSpeakingIndicator(instanceId);
};
// AI结束说话回调
onAgentSpeakEnd = instanceId -> {
// 更新UI:隐藏发言指示器
uiManager.hideSpeakingIndicator(instanceId);
};
// 接收到AI字幕(可用于游戏内聊天框展示)
onAgentSubtitle = (instanceId, text) -> {
// 将AI发言内容加入游戏聊天记录
gameState.addChatLog(instanceId, text);
uiManager.updateChatBox(instanceId, text);
};
// 真人玩家语音识别结果(通过业务后台转发)
onPlayerASRResult = (playerId, text) -> {
// 更新游戏状态
gameState.addChatLog(playerId, text);
// 通知所有AI玩家更新上下文
if (aiAgentManager != null) {
aiAgentManager.broadcastToAIs(gameState);
}
};
}
// 玩家加入房间并拉取AI语音流
public void joinRoom(String roomId, String userId) {
ZegoUser user = new ZegoUser(userId);
ZegoRoomConfig config = new ZegoRoomConfig();
config.maxMemberCount = 16;
engine.loginRoom(roomId, user, config);
engine.startPublishingStream(userId + "_main");
// 拉取所有AI玩家的语音流
if (gameState != null && gameState.getAiPlayers() != null) {
for (AIPlayer aiPlayer : gameState.getAiPlayers()) {
engine.startPlayingStream(aiPlayer.getUserId() + "_main");
}
}
}
// 回调接口定义
private AgentSpeakCallback onAgentSpeakStart;
private AgentSpeakCallback onAgentSpeakEnd;
private AgentSubtitleCallback onAgentSubtitle;
private PlayerASRCallback onPlayerASRResult;
public interface AgentSpeakCallback {
void onSpeak(String instanceId);
}
public interface AgentSubtitleCallback {
void onSubtitle(String instanceId, String text);
}
public interface PlayerASRCallback {
void onResult(String playerId, String text);
}
}
五、扩展方向
从鹅鸭杀扩展到狼人杀,主要差异在于角色配置:
- 狼人杀角色更丰富(预言家、女巫、猎人等),需在SystemPrompt中增加技能使用逻辑。
- 多AI互动可通过创建多个智能体实例实现,AI之间会通过RTC语音自然交流。
- 多模态升级可为AI添加数字人形象,结合ZEGO数字人SDK实现可视化AI队友,进一步提升沉浸感。
六、总结
通过ZEGO AI Agent,Android开发者可以在不改动原游戏逻辑的前提下,快速为鹅鸭杀、狼人杀等社交语音游戏集成AI队友。 核心收益包括:解决凑人难题、提供24小时陪练、降低新手门槛、增强游戏趣味性。 即构提供完整的SDK文档、示例代码和技术支持,开发者可访问即构官网获取详细接入指南,开启AI+社交游戏的新篇章。