Tanks联机版服务端开发

该文章不具通用性,仅仅是自己对公司服务端开发的学习记录,这里仅仅是做一个回顾,服务端源码暂时不便公开。

服务端底层采用C++来实现,高层利用Lua来写逻辑功能。

  对于服务端开发,首先是要先设计好协议,协议是客户端和服务器公用的,这里采用的是谷歌公司的protobuf。每一个协议都需要由一个lua来实现其具体的功能。用于实现协议的脚本文件以svc_开头。服务端的架构是由Base服来完成与客户端的连接,Base服负责把消息分发到Cell服进行处理,也就是Cell服才是逻辑运算的核心,每一个独立的玩法功能都可以在Cell服里面添加一个文件夹来实现管理。
  对于多服的服务端开发,采用的是路由来实现的。通过路由配置转发消息到Cell服或者是Base服。坦克大战采用单服来实现,所以把Cell服的逻辑功能都移植到Base服即可。每一个独立的功能会对应一个协议,比如有登录、和主玩法两个主要功能,我们在base下面就相应的分别建立两个文件夹login和tanks。
  这里写图片描述
  然后编写服务协议。以tanks.proto为例,该协议的定义如下:

syntax = "proto2";

package tanks;

import "empty_msg.proto";
import "tanks/msg.proto";

// U3d坦克教程网络版
service Tanks {
    // 玩家请求进入配对.
    // 服务器配对后会通知客户端 TanksPush.EnterRoom()。
    rpc EnterMatch(rpc.EmptyMsg) returns (rpc.EmptyMsg);
    // 玩家退出,不管是正在配对或者已进入房间对战。
    rpc Exit(rpc.EmptyMsg) returns (rpc.EmptyMsg);

    // 移动
    rpc MoveTo(PositionMsg) returns (rpc.EmptyMsg);
    // 转向
    rpc RotateTo(RotationMsg) returns (rpc.EmptyMsg);

    // 发射炮弹
    rpc Fire(FireMsg) returns (rpc.EmptyMsg);
    // 炮弹爆炸。同一炮弹,双方各自向服务器报告爆炸结果。
    // 服务器统一推送伤害值:TanksPush::Damage(DamageMsg)
    rpc ShellExplode(ShellExplosionMsg) returns (rpc.EmptyMsg);
}

// 客户端向服务器报告炮弹爆炸结果。
// 只有击中信息。伤害值由服务器计算后向双方推送 TanksPush.ShellExplode()
message ShellExplosionMsg {
    optional uint32 shell_id = 1;  // 炮弹ID, 与发射中的ID相对应
    optional PositionMsg position = 2;  // 击中点
}

  然后根据这个协议,我们在tanks目录下建立一个svc_tanks.lua的文件来实现该协议。部分代码如下:

local M = { }

local log = require("log"):new("tanks.svc")
local pb = require("protobuf")
local empty_msg_data = pb.encode("rpc.EmptyMsg", { })
local matcher = require("tanks.matcher")
local room_mgr = require("tanks.room_mgr")

log:debug("loading service...")

local function get_room(game_clt_id)
    return room_mgr.get(game_clt_id)
end  -- get_room

function M.EnterMatch(ctx, content)
    log:debug("EnterMatch")
    c_rpc.reply_to(ctx, empty_msg_data)
    matcher.enter(ctx:get_game_clt_id())
end  -- EnterMatch()

require("rpc_request_handler").register_service("tanks.Tanks", M)
return M

  该文件定义了一个模块M,其实就是table变量。每一个M的方法都对应于协议里面的方法,最后返回模块M,这样子就可以在其他Lua文件中以模块的方式加载M,并调用M的方法。其中require("rpc_request_handler").register_service("tanks.Tanks", M) 这句话是注册M为tanks.Tanks服务。这样子客户端便可以通过tanks.Tanks这个服务名访问到的M,从而调用相应的方法,以此实现客户端请求服务器的方法。服务端接受客户端发送的请求并处理后可以通过c_rpc.reply_to(ctx, empty_msg_data) 这个方法来回应服务端的响应,第一个参数ctx是上下文,用于找到返回的客户端,第二个参数是返回的数据。同样,服务端也可以主动发送消息给客户端,如self:rpc_request("tanks.TanksPush", "Damage", req_str) ,“tanks.TanksPush”为推送消息到客户端的请求服务名,Damage为对应的方法名,req_str是发送的数据。
  客户端采用的是公司的GameBox下面的一个轻框架来实现。通过继承一个借口,实现消息响应方法。

        public void OnDisconnect()
        {
            // 处理服务器连接丢失
            GlobalLogger.L("Disconnected!");
        }

        public bool PushRequest(uint id, string service, string method, byte[] content)
        {
            // 处理服务器推送过来的请求,返回true表示已经处理;反之则下一帧还会推送过来
            GlobalLogger.L("PushRequest: " + service + "." + method);
            if ("tanks.TanksPush" == service)
            {
                m_TanksPushService.Request(id, method, content);
                return true;
            }
            return true;
        }
        public bool PushResponse(uint id, byte[] content)
        {
            // 处理服务器推送过来的响应,返回true表示已经处理;反之则下一帧还会推送过来
            GlobalLogger.L("PushResponse");
            if (!m_ResponseHandler.ContainsKey(id)) return true;
            ResponseHandler h = (ResponseHandler)m_ResponseHandler[id];
            m_ResponseHandler.Remove(id);
            h.Invoke(content);
            return true;
        }

  同时,实现了Controller这个类,用于发送消息到服务端

        public static void SendRequest(string service, string method, byte[] content,
            Client.ResponseHandler responseHandler = null)
        {
            uint rpcId = ++m_RpcId;
            m_Client.SetResponseHandler(rpcId, responseHandler);

            m_Server.SendRequest(rpcId, service, method, content);

        }
m_Server是用于与服务端进行通信的,它由轻框架实现。

  这里再列举下实现的逻辑:
  

1 . 移动/转向
- 禁止敌方单位受键盘控制
- 发送移动坐标到服务端
- 服务端接受消息,并保存玩家坐标,再转发消息给另一位玩家
- 客户端接受消息,并更新相应玩家的坐标
- 发送转向消息到服务端
- 服务端接受消息,并转发消息给另一位玩家
- 客户端接受服务的消息,更新旋转角度
 
2 . 开火/伤害
- 禁止敌方单位受键盘控制
- 发送开火消息到服务端
- 服务端接受消息并转发消息
- 客户端接受消息并创建炮弹并设置属性
- 爆炸时客户端发送爆炸点坐标到服务端
- 服务端根据爆炸点坐标和玩家之间的距离分别计算对两者造成的伤害
- 服务端向两位玩家推送伤害数据
- 客户端接受伤害数据并更新UI界面

3 . 服务端调用流程
这里写图片描述


这里写图片描述

最后来一张效果图:
这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值