多人实时游戏,第一个项目终于完整上线。有问必答

这是一个多人联网实时碰撞小游戏,因为这个比较简单所以就选择做这个。不过感觉好难搞啊,对于我来说搞了快要一年了!都快搞到放弃了。明天不知道能不能回一个包子钱。:grin: 真是让人期待啊

微信平台

头条,刚刚上线

##广告也打了,是时候讨论一下技术了或者与到的坑了

  1. 头条审核太慢,比较严格,一般一个星期最多给你审核两次(我的是这样),工作人员周末不上班,录屏功能必须要,交互提示也不能有bug
  2. 实时游戏数据传输问题,自己写了一个 MiniBuffer,应该算是模仿Protobuffer 的取名了,意喻微小的,微小的字节,希望在网络传输中不浪费一个字节。为什么不用Protobuffer,因为想优化到比Protobuffer更小更快。所以自己动手写了这个工具包。上上前端核心代码,大家看看就会明白了。
export enum ByteType {
    Bool = 0,
    Int8 = 1,
    UInt8 = 2,
    Int16 = 3,
    UInt16 = 4,
    Int32 = 5,
    UInt32 = 6,
    String = 9,
    Object = 10,

    //数组
    BoolArray = 19,
    Int8Array = 20,
    UInt8Array = 21,
    Int16Array = 23,
    UInt16Array = 24,
    Int32Array = 25,
    UInt32Array = 26,
    StringArray = 29,
    ObjectArray = 30,

    //新增int24
    Int24 = 31,
    UInt24 = 32,
    Int24Array = 33,
    UInt24Array = 34,
}

3.2 如何使用

/* 定义消息体类*/
@BtyeContract
export class MoveTop_SMsg implements ServerMsg {
    /// <summary>
    ///  服务器消息类型
    /// </summary>
   @ByteMember(0, ByteType.Uint8)
    public ServerMsgType: ServerMsgType = ServerMsgType.MoveTop;
    /// <summary>
    ///  实体id
    /// </summary>
    @ByteMember(1, ByteType.Uint16)
    public Id: number;
    /// <summary>
    ///  坐标 x
    /// </summary>
    @ByteMember(2, ByteType.Int16)
    public PX: number;
    /// <summary>
    ///  坐标 y
    /// </summary>
    @ByteMember(3, ByteType.Int16)
    public PY: number;
    /// <summary>
    /// 速度 x
    /// </summary>
    @ByteMember(4, ByteType.Int16)
    public VX: number;
    /// <summary>
    /// 速度 y
    /// </summary>
    @ByteMember(5, ByteType.Int16)
    public VY: number;
}

//具体消息
var msg=new MoveTop_SMsg();
msg.Id=100;
msg.PX=20;
.......

//写为 arraybuffer,
var arrayBuffer= Buffer.WirteObject(msg);
//从 arraybuffer 中读
var msg=Buffer.ReadObject<MoveTop_SMsg>(MoveTop_SMsg,arrayBuffer);

3.3 如何实现的

  看看代码,主要是装饰器,然后就是对 DataView 的读写了,我就写的思路,写下面也是最核心的代码,剩下的就交给你自己思考了。


/**属性修饰
 * ByteMember 
 * @param order 
 * @param type 
 */
export function ByteMember(order: number, type: ByteType, fun: Function = null) {
    return function (target: any, propertyKey: string) {
        var byteInfo = new ByteInfo(propertyKey, order, type, fun)
        var byteInfoArray = Buffer.ClassInfoMap.get(target.constructor.name);
        if (!byteInfoArray) {
            byteInfoArray = Array<ByteInfo>();
            byteInfoArray.push(byteInfo);
            Buffer.ClassInfoMap.set(target.constructor.name, byteInfoArray);
        } else {
            byteInfoArray.push(byteInfo);
            byteInfoArray.sort((a, b) => a.Order - b.Order)//排序
        }
    }

}

/**类修饰
 * 需要序列化的类的 标识
 */
export function BtyeContract(target: any) {
    Buffer.ClassMap.set(target.name, target);
}


/**byte 类型
 * 要写入的 
 */
export class ByteInfo {
    public Order: number;
    public Type: ByteType;
    public Function: Function;
    public PropertyKey: string;
    constructor(propertyKey: string, order: number, type: ByteType, fun: Function = null) {
        this.PropertyKey = propertyKey;
        this.Order = order;
        this.Type = type;
        this.Function = fun;
    }
}

export class Buffer {

    /**
     * 名称与构造器
     */
    public static ClassMap = new Map<string, Function>();

    /**
     * 名称与属性
     */
    public static ClassInfoMap = new Map<string, Array<ByteInfo>>();

    //#region read method
    /**
     * 从 buffer 中反射 出 一个 classType 实例
     * @param classType 
     * @param buffer 
     */
    public static ReadObject<T>(classType: Function, buffer: ArrayBuffer): T {
        var offSet = 0;
        var object = new classType.prototype.constructor();//也可以 new (<any>classType())
        var dataView = new DataView(buffer);
        var byteInfoArray = Buffer.ClassInfoMap.get(classType.name);
        for (let i = 0; i < byteInfoArray.length; i++) {
            let byteInfo = byteInfoArray[i];
            let byteLength = this.readProperty(dataView, offSet, byteInfo, object, byteInfo.PropertyKey);
            offSet += byteLength;
        }
        return object;
    }
    
    //#region write method
    public static WirteObject(obj: Object) {
        var offSet = 0;
        var cacheBuffer = new ArrayBuffer(128);
        var dataView = new DataView(cacheBuffer);
        var byteInfoArray = Buffer.ClassInfoMap.get(obj.constructor.name);
        for (let i = 0; i < byteInfoArray.length; i++) {
            let byteInfo = byteInfoArray[i];
            let byteLength = this.writeProperty(dataView, offSet, byteInfo.Type, obj[byteInfo.PropertyKey]);
            offSet += byteLength;
        }
        var buffer = cacheBuffer.slice(0, offSet);
        return buffer;
    }
    剩下的就是对 各种属性 使用 dataview 读写了
.............
4赞

从初识cocos 到目前正好9个月。唯一感触就是开发游戏实在是太难了,一下素材,一下音乐,各个平台接口,规则。服务器,域名,还得会点抠图。作为一个业余的玩家,对于我来说就是时间太少了。为游戏开发者点赞。

1赞

你用的什么写的服务器啊

.net core mvc

哇大哥,.net啊,我也准备用。net webapi 写已个弱联网服务器。。。大哥你是用的WebSocket吗。后端完全自己用。net写的啊?还是用了什么框架的?

关系 联机怎么同步的

直接 .net core mvc,启用 websocket ,实时的再websocket 中使用

应该是状态同步,客户端发来消息,比如移动消息,那么服务器就广播这个消息给其他 客户端。如果是碰撞消息,那么根据对应的逻辑(碰撞参数小球),那么产生了创建小球的消息,把消息广播到其他client 。client 的收到 对应的消息,处理对应逻辑就行

如果有人网络不好,那我岂不是会无缘无故的掉血么?

这里有一个bug,,但是已经不爱修复了,原因:两个人碰撞,两个client 端都会向服务器发出碰撞的消息。由于我处理两个碰撞问题,是取先到达的服务器的那个碰撞消息,后面那个碰撞消息丢弃。所以当有一个玩家网络不稳定时,就会出现这个情况,

解决这个情况,应该以网络好的那个玩家信息优先。或者碰撞消息到达时,再去检测 一下服务器最新两个玩家位置再做对应处理。

没有写轮眼,没有手里剑。。。

没玩懂:cry:

谢谢支持,:blush:哈哈,做的不好

有点类似我的飞镖玩的贼溜。挺不错的。

玩法还挺有意思的。。 联网游戏对网络要求很高。。。 而且你这种游戏 得用帧同步。。。 否则体验很差的~

目前使用状态同步,帧同步没搞过

这是TS代码吗

嗯,是的

我的乔峰版阿里云 不知道能否支撑得住这种类型游戏服务