# 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;
    }
}

# 最佳实践

  1. 客户端预测 - 发送前立即执行本地操作,提升响应速度
  2. 时间戳 - 添加timestamp字段,便于调试和排查问题
  3. 合理频率 - 控制发送频率,避免网络拥塞
  4. 数据压缩 - 对于高频操作,考虑压缩数据格式
  5. 使用input.playerId - 在OnFrameReceived事件中,通过input.playerId来识别操作来源

# 相关API