iohao / ioGame

无锁异步化、事件驱动架构设计的 java netty 网络编程框架; 轻量级,无需依赖任何第三方中间件或数据库就能支持集群、分布式; 适用于网络游戏服务器、物联网、内部系统及各种需要长连接的场景; 通过 ioGame 你可以很容易的搭建出一个集群无中心节点、集群自动化、分布式的网络服务器;FXGL、Unity、UE、Cocos Creator、Godot、Netty、Protobuf、webSocket、tcp、socket;java Netty 游戏服务器框架;
http://game.iohao.com
GNU Affero General Public License v3.0
834 stars 183 forks source link

SDK C# ,客户端代码生成;方便 Unity、Godot 对接。 #328

Open iohao opened 2 months ago

iohao commented 2 months ago

新增功能的使用场景

[对接文档][SDK C# 联调代码生成] 根据 action 、广播、错误码 ...等信息,自动生成 Unity、Godot 可使用的 C# 对接代码,减少客户端模板代码的编写 。

通常服务器是服务的提供方,提供协议及 api (即 action)。客户端调用这些 api 就能与服务器实现的互通,此时,客户端还需要根据服务器提供的对接文档来编写一些代码,以确保符合 api 的描述。

如果服务器提供了数百个 action,那么客户端也需要编写数百个对应的联调代码。虽然这些调用的代码量并不会很多,且大部分都是一些模板代码;但如果对接的内容多了,那么工作量也就上来了。

为此,ioGame 提供了这些模板代码的生成,让我们先看下面的服务器代码。

@ActionController(RoomCmd.cmd)
public class FightRoomAction {
    ... ... 省略部分代码
    /**
     * 玩家进入房间
     *
     * @param roomId      房间号
     * @param flowContext flowContext
     */
    @ActionMethod(RoomCmd.enterRoom)
    public void enterRoom(long roomId, FlowContext flowContext) {
    }

    /**
     * 玩家准备
     *
     * @param ready       true 表示准备,false 则是取消准备
     * @param flowContext flowContext
     */
    @ActionMethod(RoomCmd.ready)
    public void ready(boolean ready, FlowContext flowContext) {
    }

    /**
     * 开始游戏
     *
     * @param flowContext flowContext
     */
    @ActionMethod(RoomCmd.startGame)
    public void startGame(FlowContext flowContext) {
    }

    /**
     * 玩家在游戏中的操作
     *
     * @param command     玩家操作数据
     * @param flowContext flowContext
     */
    @ActionMethod(RoomCmd.operation)
    public void operation(FightOperationCommand command, FlowContext flowContext) {
    }

    /**
     * 房间列表
     *
     * @return 房间列表
     */
    @ActionMethod(RoomCmd.listRoom)
    public List<FightRoomNotice> listRoom() {
        return ...;
    }
}

生成的 SDK C# 代码

生成后的 C# 代码,以下内容是根据 action 、广播、错误码 ...等信息自动生成的。是的,你没有看错,除了 api 外,还生成了具体的方法描述、参数描述、返回值描述。

这里只展示了 action 相关的,为了节省篇幅,这里就先不展示广播、错误码相关生成后的代码了。

action 有返回值的接口,会生成两种风格的调用代码。关于两种风格,请阅读相关 SDK #205。

// ioGame csharp generate
using Google.Protobuf;
using Proto;
using Iohao;

namespace csharp_ioGameSDK.action;

public static class FightRoomAction
{
    private static readonly int EnterRoomCmd = CmdKit.Merge(2, 2, "玩家进入房间");
    private static readonly int ReadyCmd = CmdKit.Merge(2, 4, "玩家准备");
    private static readonly int ListRoomCmd = CmdKit.Merge(2, 5, "房间列表");
    private static readonly int OperationCmd = CmdKit.Merge(2, 6, "玩家在游戏中的操作");
    private static readonly int StartGameCmd = CmdKit.Merge(2, 7, "开始游戏");

    /// <summary>
    /// 玩家进入房间
    /// </summary>
    /// <param name="roomId">房间号</param>
    /// <returns>RequestCommand void</returns>
    public static RequestCommand OfEnterRoom(long roomId)
    {
        return RequestCommand.OfLong(EnterRoomCmd, roomId);
    }

    /// <summary>
    /// 玩家准备
    /// </summary>
    /// <param name="ready">true 表示准备,false 则是取消准备</param>
    /// <returns>RequestCommand void</returns>
    public static RequestCommand OfReady(bool ready)
    {
        return RequestCommand.OfBool(ReadyCmd, ready);
    }

    /// <summary>
    /// 房间列表
    /// </summary>
    /// <param name="callback">list of <see cref="FightRoomNotice"/> 房间列表</param>
    /// <returns>RequestCommand</returns>
    public static RequestCommand OfListRoom(CallbackDelegate callback)
    {
        return RequestCommand.Of(ListRoomCmd).OnCallback(callback);
    }

    /// <summary>
    /// 房间列表
    /// </summary>
    /// <returns>ResponseResult,list of <see cref="FightRoomNotice"/> 房间列表</returns>
    public static async Task<ResponseResult> OfAwaitListRoom()
    {
        return await RequestCommand.OfAwait(ListRoomCmd);
    }

    /// <summary>
    /// 玩家在游戏中的操作
    /// </summary>
    /// <param name="command">玩家操作数据</param>
    /// <returns>RequestCommand void</returns>
    public static RequestCommand OfOperation(FightOperationCommand command)
    {
        return RequestCommand.Of(OperationCmd, command);
    }

    /// <summary>
    /// 开始游戏
    /// </summary>
    /// <returns>RequestCommand void</returns>
    public static RequestCommand OfStartGame()
    {
        return RequestCommand.Of(StartGameCmd);
    }
}

使用

因为生成的 api 中生成了具体的方法描述、参数描述、返回值描述,所以在使用时只需通过工具来查看具体的 api 是如何使用的就行了。

客户端开发者在使用 api 时,只需要关注核心业务代码,下面展示了两种编码风格,客户端开发者可根据实际业务的复杂度来选择使用。

async void OnClick()
{
    // 客户端在使用 api 时,只需要编写实际的业务代码

    // 编码风格一
    FightRoomAction.OfListRoom(result =>
    {
        // 实际要处理的业务代码
        var fightEnterRooms = result.ListValue<FightEnterRoom>();
        Console.WriteLine(fightEnterRooms);
    });

    // 编码风格二
    var result = await FightRoomAction.OfAwaitListRoom();
    var fightEnterRooms = result.ListValue<FightEnterRoom>();
    Console.WriteLine(fightEnterRooms);
}

从截图中我们可以知道,接口 api 文档已经明确了返回值的类型

image

小结

SDK C# 代码生成可为客户端开发者减少巨大的工作量,并可为客户端开发者屏蔽路由等概念。直接面向接口编程,面向接口编程的几个优点

  1. 参数明确:服务器需要什么样的参数,会在生成接口时明确好。
  2. 返回值明确:服务器是否会返回数据,返回什么样的数据,会在生成接口时明确好。接口有 callback 回调的,就表示该 api 服务器会有返回值,而具体的值类型则会在接口文档中生成好。
  3. 减少沟通双方的成本。
  4. 减少客户端开发者编写模板代码这部分的工作量,从而只需关注真正的业务逻辑。

其他参考资料