# TapBattleClient.SendFrameInput
# 功能描述
发送玩家操作数据到服务器,用于帧同步。所有玩家的操作会被服务器收集后通过OnFrameReceived事件广播给房间内所有玩家。
# 方法签名
public static void SendFrameInput(SendFrameInputOption option)
# 参数说明
# SendFrameInputOption
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| data | string | 是 | 操作数据(JSON字符串,建议包含playerId、action等信息) |
| success | Action<BattleValidationResponse> | 否 | 发送成功回调 |
| fail | Action<TapCallbackResult> | 否 | 发送失败回调 |
| complete | Action<TapCallbackResult> | 否 | 发送完成回调 |
# 使用说明
# 参考格式
{
"action": "move/attack/skill",
"x": 10,
"y": 20,
"timestamp": 1234567890
}
# 注意事项
- 帧同步中调用 - 只能在帧同步开始后(OnFrameSyncStart之后)调用
- 数据格式自定义 - data可以是任意JSON格式,根据游戏需求自定义
- 服务器自动添加playerId - 服务器会自动为每条输入数据添加发送者的playerId
- 频率控制 - 根据游戏需求控制发送频率,避免过于频繁
# 代码示例
# 示例1:发送移动操作
using UnityEngine;
public class PlayerController : MonoBehaviour
{
private string myPlayerId;
void Update()
{
// 检测键盘输入
if (Input.GetKeyDown(KeyCode.W)) {
SendMoveInput("up");
}
else if (Input.GetKeyDown(KeyCode.S)) {
SendMoveInput("down");
}
else if (Input.GetKeyDown(KeyCode.A)) {
SendMoveInput("left");
}
else if (Input.GetKeyDown(KeyCode.D)) {
SendMoveInput("right");
}
}
private void SendMoveInput(string direction)
{
var moveData = new {
action = "move",
direction = direction,
timestamp = System.DateTimeOffset.Now.ToUnixTimeMilliseconds()
};
TapBattleClient.SendFrameInput(new SendFrameInputOption
{
data = JsonUtility.ToJson(moveData),
success = (result) => {
// 发送成功(可选处理)
},
fail = (result) => {
Debug.LogError($"发送输入失败: {result.errMsg}");
}
});
}
}
# 示例2:发送攻击操作
using UnityEngine;
public class CombatController : MonoBehaviour
{
private string myPlayerId;
public void OnAttackButtonClick()
{
var attackData = new {
action = "attack",
targetId = GetTargetId(),
skillId = 1001,
timestamp = System.DateTimeOffset.Now.ToUnixTimeMilliseconds()
};
TapBattleClient.SendFrameInput(new SendFrameInputOption
{
data = JsonUtility.ToJson(attackData)
});
}
private string GetTargetId() {
// 获取攻击目标ID
return "target_player_id";
}
}
# 示例3:处理帧同步数据
using UnityEngine;
[System.Serializable]
public class PlayerInputData
{
public string action;
public string direction;
public long timestamp;
}
public class FrameSyncHandler : MonoBehaviour
{
private string myPlayerId;
// 收到帧同步数据
public void OnFrameReceived(FrameData frameData)
{
Debug.Log($"收到第{frameData.id}帧数据,包含{frameData.inputs.Length}个操作");
// 处理每个玩家的输入
foreach (var input in frameData.inputs)
{
var inputData = JsonUtility.FromJson<PlayerInputData>(input.data);
if (input.playerId == myPlayerId) {
// 我自己的操作(客户端预测确认)
Debug.Log($"确认我的操作: {inputData.action}");
} else {
// 对手的操作(同步对手状态)
Debug.Log($"同步对手操作: {inputData.action}");
ApplyOpponentInput(input.playerId, inputData);
}
}
}
private void ApplyOpponentInput(string playerId, PlayerInputData data)
{
// 将对手的操作应用到游戏中
if (data.action == "move") {
MovePlayer(playerId, data.direction);
}
}
private void MovePlayer(string playerId, string direction) { }
}
# 示例4:客户端预测
using UnityEngine;
public class PredictiveController : MonoBehaviour
{
private string myPlayerId;
void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
// 1. 立即执行本地移动(客户端预测)
MovePlayerLocally("up");
// 2. 发送操作到服务器
SendMoveInput("up");
}
}
private void MovePlayerLocally(string direction)
{
// 立即在本地执行移动,提升响应速度
transform.Translate(GetDirectionVector(direction));
}
private void SendMoveInput(string direction)
{
var data = new {
action = "move",
direction = direction
};
TapBattleClient.SendFrameInput(new SendFrameInputOption
{
data = JsonUtility.ToJson(data)
});
}
private Vector3 GetDirectionVector(string direction) {
// 返回方向向量
return Vector3.zero;
}
}
# 最佳实践
- 客户端预测 - 发送前立即执行本地操作,提升响应速度
- 时间戳 - 添加timestamp字段,便于调试和排查问题
- 合理频率 - 控制发送频率,避免网络拥塞
- 数据压缩 - 对于高频操作,考虑压缩数据格式
- 使用input.playerId - 在OnFrameReceived事件中,通过input.playerId来识别操作来源
# 相关API
- TapBattleClient.StartFrameSync - 开始帧同步
- ITapBattleEventHandler - OnFrameReceived事件
