战魂小筑

讨论群:309800774 知乎关注:http://zhihu.com/people/sunicdavy 开源项目:https://github.com/davyxu

   :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  252 随笔 :: 0 文章 :: 506 评论 :: 0 Trackbacks

#

测试用例

我们对Golang的结构体变量赋值, 以及单参数函数调用进行反射和native操作的测试

 

package main

 

import (

"reflect"

"testing"

)

 

type data struct {

Hp int

}

 

const AssignTimes = 100000000

 

func TestNativeAssign(t *testing.T) {

 

v := data{Hp: 2}

 

for i := 0; i < AssignTimes; i++ {

v.Hp = 3

}

 

}

 

func TestReflectAssign(t *testing.T) {

 

v := data{Hp: 2}

 

vv := reflect.ValueOf(&v).Elem()

 

f := vv.FieldByName("Hp")

 

for i := 0; i < AssignTimes; i++ {

 

f.SetInt(3)

}

 

}

 

func TestReflectFindFieldAndAssign(t *testing.T) {

 

v := data{Hp: 2}

 

vv := reflect.ValueOf(&v).Elem()

 

for i := 0; i < AssignTimes; i++ {

 

vv.FieldByName("Hp").SetInt(3)

}

 

}

 

func foo(v int) {

 

}

 

const CallTimes = 100000000

 

func TestNativeCall(t *testing.T) {

for i := 0; i < CallTimes; i++ {

 

foo(i)

}

}

 

func TestReflectCall(t *testing.T) {

 

v := reflect.ValueOf(foo)

 

for i := 0; i < CallTimes; i++ {

 

v.Call([]reflect.Value{reflect.ValueOf(2)})

}

}

性能测试数据

=== RUN TestNativeAssign
— PASS: TestNativeAssign (0.03s)
=== RUN TestReflectAssign
— PASS: TestReflectAssign (0.41s)
=== RUN TestReflectFindFieldAndAssign
— PASS: TestReflectFindFieldAndAssign (9.86s)
=== RUN TestNativeCall
— PASS: TestNativeCall (0.03s)
=== RUN TestReflectCall
— PASS: TestReflectCall (21.46s)

测试评测

  • 在结构体变量赋值测试用例中, 我们发现TestReflectFindFieldAndAssign赋值格外的耗时. 分析性能点在FieldByName这个函数上, 我们查了下底层如何实现的:

// FieldByName returns the struct field with the given name

// and a boolean to indicate if the field was found.

func (t *structType) FieldByName(name string) (f StructField, present bool) {

// Quick check for top-level name, or struct without anonymous fields.

hasAnon := false

if name != "" {

for i := range t.fields {

tf := &t.fields[i]

if tf.name == nil {

hasAnon = true

continue

}

if *tf.name == name {

return t.Field(i), true

}

}

}

if !hasAnon {

return

}

return t.FieldByNameFunc(func(s string) bool { return s == name })

}

各位看官必须吐槽用for来遍历获取数据, 但冷静下来分析. 这样做无可厚非.
试想如果reflect包在我们使用ValueOf时使用map缓冲好一个结构体所有字段的访问数据后, 肯定访问指定字段速度会很快
但是, 以空间换速度的需求其实最多满足了1%的需求.
同样的例子是图形API里访问Shader变量的方法, 总是默认使用字符串获取, 速度很慢. 当你想快速访问时, 请提前按需缓存字段
那么, Golang使用的也是这样的思路. 虽然暴力了一点, 但是能够让程序跑对, 性能优化的东西放在之后来做, 缓冲下就可以解决

  • 在调用测试用例中, 毫无悬念的, 调用速度很慢
    因此, 我们在平时使用反射时, 尽量偏向于反射变量缓冲存在下的变量赋值或者获取
    而调用的需求尽量减少, 如果有goroutine存在的情况下, 则不必太多担心.
posted @ 2016-08-12 15:26 战魂小筑 阅读(2249) | 评论 (0)编辑 收藏

这是一个Unity3D元老级bug, 表现就是:  角色在屏幕边缘放特效,  离开屏幕持续一段时间后再回到屏幕后, 发现特效重新播放. 显然这是错误的效果

解决方法:

在ParticleSystem组件上勾选SubEmitter, 不要问为什么, 做就好

相关官方链接

http://answers.unity3d.com/questions/218369/shuriken-particle-system-not-rendering-particles-w.html

 

天煞的, 4.X程序无法访问粒子系统的参数, 所以只能辛苦美术兄弟们了

posted @ 2016-08-01 15:23 战魂小筑 阅读(4071) | 评论 (0)编辑 收藏

热更新的内容可以是美术资源, 可以是代码, 但相对来说, 美术资源的更新不会受到约束, 代码实际上是重灾区, 本文介绍的主要是代码热更新

热更新对于开发者来说是一件麻烦事, 特别对于看重效率,便捷性和结构的程序员来说, 热更新就是运营人员的不懂技术的表现
然而, 对于上线才是刚刚开始的网络游戏, 特别是手游来说. 热更新是极为重要的基础功能

为什么要热更新

客户端

适应上线需求

对于手游客户端来说, 受到苹果审核的约束, 一次审核提交需要10~20天不等的等待时间, 而这段时间, 开发进度依然会推进很多.

一旦手游上线, 第一个版本在玩家疯狂行为下, 出点问题是必然的, 所以”上线更”就成了家常便饭. 如果你要说, 必须大包, 无法热更, 那么10~20

多天后, 游戏估计就没啥人了, 更别说渠道, 发行投入巨大资金进行推广之下让玩家迎来的一堆bug的版本以及所谓程序员的傲慢和清高.

热调试, 热开发, 热发布

除了线上问题之外, 由于Unity3D为了适应64位应用需求, 将C#编译出的IL代码利用il2cpp第三方库编译成为c++. 效率提升了倒是好,

但工程编译和发布时间变得相当感人, 没个1~2个小时完全搞不定. 即便加装ssd, 为了修改一个bug, 也不知道要等多少根烟的时间…

只要核心功能不变化的情况下, 完全可以让热更新成为开发期间的好工具, lua代码修改后, 马上可以在手机上看效果, 没有编译, 发布的时间损耗, 其实反而提升了开发效率

服务器

对于服务器来说, 常见游戏类型的玩家一般在半夜的在线人数会急速下降. 但是对于比较热门的MMO, 以沟通为基础的游戏, 半夜也会有很多人在线

因此传统的停服更新对于玩家的热情秒杀很大的. 想想看,屁股先锋公测停15天各位是什么感受? 所以为了玩家体验, 同时保证服务器稳定的前提下

修复一些轻微bug, 用热更新再合适不过了. 所以老服务器程序员, 千万不能以服务器稳定为借口而忽略了玩家体验.

技术是用来解决问题的, 不是用来装X的

怎么热更新

以下是Unity3D的几种热更新方式

基于C#, 使用动态加载Assembly反射更新代码

这种方式在安卓上完全可行, 对现有架构无需大的修改, 一样使用C#和Unity3D的方式进行开发

但在iOS上受到限制, 因此对于全平台首发的游戏, 或者双平台都要上的游戏, 已经慢慢的不使用这种方法进行热更新了

基于Lua, 将Lua代码视为资源, 动态加载并运行

云风团队早期研究出的UniLua是基于C#编写的Lua虚拟机来运行, 而且只支持字节码解释, 因此无法做动态功能, 效率奇低

后期, ulua的出现, 彻底将Lua作为比较正统的更新方式存在. ulua基于Tolua库进行封装, 添加了一些便捷封装, 代码打包和基本的框架

ToLua本身是一个基于C版Lua上扩充的库, 以静态链接库方式与Unity3D代码链接. 因此, 可以说ToLua是跑在C层上, 速度不亚于C++和Lua的组合

基于Lua的代码更新方式, 无论跨任何平台都可以以同一套代码和工作流进行, 因此避免很多麻烦, 成为现在主流的开发方式

游戏逻辑全都用Lua写么?

做过网页和手机App的童鞋都发现, js, 一个bug超多, 设计奇怪的语言居然成为主流界面开发语言, 为啥?

动态特性适合制作ui

另外一个反例就是: 使用C++开发界面, 例如Qt, MFC之类, 虽然设计严谨, 但是最终挡不住各种奇葩的修改需求

因此, 界面非常推荐使用动态语言来开发, 游戏界就是用Lua

而游戏核心, 根据各自游戏类型来定, 总的一点, 效率瓶颈点, Update之类的, 尽量使用C#或者C++来实现

写在最后

当前中国大环境下的玩家和各种氪金理由与纯的不能再纯的游戏人的基本愿望是冲突的

然而国外游戏的各种设计和机制, 暴雪战网更新不及时, 版本不对没提示, 这些基本错误在中国的网游都不会出现的

技术上无法赶英超美的我们, 在体验上已经输出了我们的价值观, 老外们都在学

对于程序员来说, 只是多贴近玩家, 多了解外面的世界而已

posted @ 2016-07-06 11:03 战魂小筑 阅读(5051) | 评论 (6)编辑 收藏

苹果要求在2016年6月1日后新的app必须支持ipv6网络, 技术发展靠苹果果然没错, 但开发者还是要开始忙起来了
这里介绍下Unity3D的适配的一些经验

基本注意点

  • ios ipv6适配无需修改服务器, 也就是说, 如果你的服务器依然是ipv4的也是可以使用的
  • 苹果的适配方案是将ipv4的地址转换为ipv6, 到了路由层再转回去继续利用ipv4网络传输

测试网络环境搭建

转载请注明:http://www.cppblog.com/sunicdavy战魂小筑

网上有很多翻译了苹果官方的搭建ipv6测试网络环境的文章, 例如:
http://www.cocoachina.com/ios/20160525/16431.html
注意以下几点

  • 无需路由器支持ipv6, 但猫(modem)必须要支持ipv6. 因为现在大多数都是光猫
    以下截图是光猫管理端
    3440e3f9-12de-435b-85ab-a7a3be8b384b[6]
    光猫里的ipv6支持默认是关闭的, 所以需要手动打开, 按默认值配置即可

  • 请确认mac os系统必须是osx 10.11以后的版本才可以打开NAT64

  • 正确连接mac的ios设备应是如下截图示意
    91f54476-4b5d-4585-a364-0da2139774c1[6]

  • 默认连接上wifi时看连接信息时, 一般只会有红色DNS地址或者根本不显示
  • 只有在第一次访问网络, 例如打开浏览器进入任意网站时, 才会显示上面的几条信息
  • 如果只有DNS没有IP地址和子网掩码, 一般是光猫没有打开ipv6的DHCP, 没有分配IP
  • 还有一种测试ipv6 DHCP是否正常工作的方法: 关闭NAT64时可以上网, 但打开NAT64无法上网

转载请注明:http://www.cppblog.com/sunicdavy战魂小筑

Unity3D的Socket适配

WWW类本身已经支持了IPV6, 无需处理, 这里讲解使用C#原生Socket的处理

  • 测试用的设备的iOS版本必须是9.3以上的
  • Socket构造时, AddressFamily 设置为InterNetworkV6时只支持ipv6网络, 传入InterNetwork时只支持ipv4网络
  • 4.7.2和5.4.3的当前版本在mono层并未支持ipv6代码适配的核心函数getaddrinfo, 因此需要通过oc层做转换, 以下是代码
    这段代码将getaddrinfo的地址转换成一个完整字符串, 格式是:
    ipv4|ipv4地址|ipv6|ipv6地址|

P.S. copyStr这种用法参考了http://www.codeinsect.net/blog/2016/05/26/unity-ipv6-socket-%E6%94%AF%E6%8C%81%EF%BC%8C%E5%B7%B2%E6%B5%8B%E8%AF%95%E9%80%9A%E8%BF%87/
会造成内存泄露, 如果有更好的方法欢迎反馈

转载请注明:http://www.cppblog.com/sunicdavy战魂小筑

iosaddrinfo.mm

   1:  #include <sys/socket.h>
   2:  #include <netdb.h>
   3:  #include <arpa/inet.h>
   4:  #include <err.h>
   5:  #define OUTSTR_SIZE 4096
   6:  extern "C"
   7:  {
   8:      const char* copyStr( const char* str )
   9:      {
  10:          char* s = (char*)malloc(strlen(str) + 1);
  11:          strcpy(s, str);
  12:          return s;
  13:      }
  14:      const char* IOSGetAddressInfo(const char *host )
  15:      {
  16:          if( NULL == host )
  17:              return copyStr("ERROR_HOSTNULL");
  18:          char outstr[OUTSTR_SIZE];
  19:          struct addrinfo hints, *res, *res0;
  20:          memset(&hints, 0, sizeof(hints));
  21:          hints.ai_family = PF_UNSPEC;
  22:          hints.ai_socktype = SOCK_STREAM;
  23:          hints.ai_flags = AI_DEFAULT;
  24:          printf("getaddrinfo: %s\n", host);
  25:          int error = getaddrinfo(host, "http", &hints, &res0);
  26:          if (error != 0 )
  27:          {
  28:              printf("getaddrinfo: %s\n", gai_strerror(error));
  29:              return copyStr("ERROR_GETADDR");
  30:          }
  31:          memset( outstr, 0, sizeof(char)*OUTSTR_SIZE );
  32:          struct sockaddr_in6* addr6;
  33:          struct sockaddr_in* addr;
  34:          const char* solvedaddr;
  35:          char ipbuf[32];
  36:          for (res = res0; res; res = res->ai_next)
  37:          {
  38:              if (res->ai_family == AF_INET6)
  39:              {
  40:                  addr6 =( struct sockaddr_in6*)res->ai_addr;
  41:                  solvedaddr = inet_ntop(AF_INET6, &addr6->sin6_addr, ipbuf, sizeof(ipbuf));
  42:                  strcat ( outstr, "ipv6|");
  43:                  strcat ( outstr, solvedaddr);
  44:              }
  45:              else
  46:              {
  47:                  addr =( struct sockaddr_in*)res->ai_addr;
  48:                  solvedaddr = inet_ntop(AF_INET, &addr->sin_addr, ipbuf, sizeof(ipbuf));
  49:                  strcat ( outstr, "ipv4|");
  50:                  strcat ( outstr, solvedaddr);
  51:              }
  52:              strcat ( outstr, "|");
  53:          }
  54:          return copyStr(outstr);
  55:      }
  56:  }
转载请注明:http://www.cppblog.com/sunicdavy战魂小筑

iosaddrinfo.h

   1:  #pragma once
   2:  extern "C"{
   3:      const char* IOSGetAddressInfo(const char *host );
   4:  }
  • C#层的处理假设多个地址中都是统一的地址类型,要么全是v4要么全是v6
    返回给定的host内多个IP地址, 可以供处理复杂的北网通,南电信问题

   1:  using System;
   2:  using System.Net;
   3:  using System.Net.Sockets;
   4:  using System.Runtime.InteropServices;
   5:  using UnityEngine;
   6:  using System.Collections;
   7:  using System.Collections.Generic;
   8:  public class IOSIPV6
   9:  {
  10:      [DllImport("__Internal")]
  11:      private static extern string IOSGetAddressInfo(string host );  
  12:      public static IPAddress[] ResolveIOSAddress(string host, out AddressFamily af)
  13:      {
  14:          af = AddressFamily.InterNetwork;
  15:          var outstr = IOSGetAddressInfo(host);
  16:          Debug.Log("IOSGetAddressInfo: " + outstr);
  17:          if (outstr.StartsWith ("ERROR")) 
  18:          {
  19:              return null;
  20:          }
  21:          var addressliststr = outstr.Split('|');
  22:          var addrlist = new List<IPAddress>();
  23:          foreach (string s in addressliststr)
  24:          {
  25:              if (String.IsNullOrEmpty(s.Trim()))
  26:                  continue;
  27:              switch( s )
  28:              {
  29:                  case "ipv6":
  30:                      {                        
  31:                          af = AddressFamily.InterNetworkV6;
  32:                      }
  33:                      break;
  34:                  case "ipv4":
  35:                      {
  36:                          af = AddressFamily.InterNetwork;
  37:                      }
  38:                      break;
  39:                  default:
  40:                      {
  41:                          addrlist.Add(IPAddress.Parse(s));
  42:                      }
  43:                      break;
  44:              }
  45:          }
  46:          return addrlist.ToArray();
  47:      }
  48:  }
转载请注明:http://www.cppblog.com/sunicdavy战魂小筑

参考链接

官方文档
https://developer.apple.com/library/ios/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/UnderstandingandPreparingfortheIPv6Transition/UnderstandingandPreparingfortheIPv6Transition.html#//apple_ref/doc/uid/TP40010220-CH213-SW1

某人的解决方案
http://www.codeinsect.net/blog/2016/05/26/unity-ipv6-socket-%E6%94%AF%E6%8C%81%EF%BC%8C%E5%B7%B2%E6%B5%8B%E8%AF%95%E9%80%9A%E8%BF%87/
注意, 此方案中的方法可用, 但是地址并不能解决南北互通的问题

posted @ 2016-06-16 14:18 战魂小筑 阅读(3893) | 评论 (0)编辑 收藏

最近整合ulua到项目里进行热更新, protoc-gen-lua这古老的东西重新让我继续发博客, 因为坑

生成好的协议报错找不到protobuf

在每个protoc-gen-lua生成的lua文件里, 都有一行

local protobuf = require "protobuf"

本身按照官方出的没问题, 但是在ulua的目录里, 总是报protobuf找不到的错误. 前后对比了下我生成的lua和ulua官方生成的代码里

发现居然他修改了地址改为了

local protobuf = require "protobuf/protobuf"

好吧, 只有修改生成器代码protoc-gen-lua\plugin\protoc-gen-lua中第412行改为

lua('local protobuf = require "protobuf/protobuf"\n')
 

生成消息无法找到Descriptor反射查信息

在LuaFramework\ToLua\Lua\protobuf\protobuf.lua的939行添加
message_meta._member.Descriptor = descriptor
在消息里就可以通过msg.Descriptor获得此消息的反射信息

由于proto文件定义的内容过多导致的lua local超过限制的警告

image

这个错误真是让我哭笑不得, protoc-gen-lua的可用性再一次被怀疑

我们的协议好歹分成了接近100个, 每个里面消息和数据是混合的, 更别说有些童鞋喜欢把一个项目的协议全写在一个文件里, 那生成的local数量简直是酸爽

 

 

 

后记

搜索protoc-gen-lua时, 无意间又搜到3年前自己的博文http://www.cppblog.com/sunicdavy/archive/2013/04/24/199693.html

记得那个时候准备在服务器使用lua, 还好没这么干, 转了go, 否则后果不堪设想

lua上使用pb其实并不容易, 云风的pbc写的不错, 但怕有坑, sproto直接不兼容现有项目, 风险大于易用性所以果断弃用

因此, 看来有必要自己写一个支持良好的lua pb库

posted @ 2016-05-31 11:26 战魂小筑 阅读(3155) | 评论 (0)编辑 收藏

一个手游的图形技术关键性指标是: 内存占用, DrawCall和包大小. 

这三个参数是训练有素的程序和UI美术都需要关注的重要问题

接下来我们来讲解下UI美术怎么对待这三个问题

内存占用

手机的内存不会明显区分内存和显存, 大部分都是共享访问的. 这里说的内存, 一般可以通过一些工具直接看到

, 比如说XCode等

图形上对内存影响最大的就是纹理, 而纹理上最关键的问题就是纹理的大小, 也就是纹理面积

我曾经见过一些训练不是那么有素的UI美术, 用鼠标选中几个png文件, 点击属性告诉我: 诺, 你都看到了, 我这边的图片

才占几kb, 为啥你总是说内存占用大

纹理的内存占用, 只决定于纹理的面积以及发色数, 纹理面积就是长乘宽(像素), 发色数就是一般常说的: 16位色, 32位色

之所以把内存占用放在首位, 是因为, 大多数的手机一旦超过限定内存就会开始清理后台挂起的程序, 实在清理不了只有杀掉最占内存的程序

这就肯定杀到了你写的游戏之上

转载请注明http://www.cppblog.com/sunicdavy,战魂小筑, 否则木有小JJ

DrawCall(DC)

美术来理解这个概念可以这么说:  绘制一张图片需要耗费1次DC, 假设界面上有10个图标, 那就需要耗费10个DC

而一般手游的DC需要限定在150个之内, 如何降低DC呢, 就需要通过Atlas技术来合并图片

将多张图片打到一张纹理上的技术被叫做Atlas, 俗称大图或者图集, 被打之前的图也就叫小图

大图上的每个图元素叫做精灵

每个精灵被绘制无数次最终也只会耗费1个DC

但我们不能把所有游戏用到的图片都打成图集, 这并不划算

我们会根据图的使用频率, 用途来按需打图集

比如说:

1. 进游戏只看一次的宣传图, 为了方便制作和加载迅速, 做一张整图动态加载会比较好

2. 反复查看的图标, 因为数量相对固定,数量不会膨胀, 我们就做成图集

3. 但是类似于刀塔传奇中50+英雄, 普通玩家看不到那么多英雄但又被打成图集是不划算的, 所以损失一点DC按小图绘制及加载是正确方法

4. 一般时候, 我们将尺寸小的图片打成图集, 配合大尺寸图片同时加载

转载请注明http://www.cppblog.com/sunicdavy,战魂小筑, 否则木有小JJ

包大小

包大小对于游戏来说, 会影响的是玩家首次下载的时间, 如果连游戏都不下载, 做的再漂亮的游戏也是没用的

降低包大小的方法很多, 例如:

1. 分包机制. 先玩小包, 根据需要下载大包, 多见于MMORPG

2. 良好的资源管理方法及习惯

3. 剔除冗余资源

4. 尽量使用3D渲染代替2D纹理图片

以上3个概念是游戏美术, 程序必须了解的重要概念

但一个合格的美术, 除了事后优化, 还需要做的是事前优化

事前优化包括: 在游戏立项后, UI美术需要了解基本的游戏功能设计方案

出一套基本的对话框, 提示框, 图标装饰等的图素, 这些资源往往只有不到512见方的资源

利用这些图片可以拼凑出70%的界面及美化效果

在这之后的UI内容, 只是特效,动画的设计.

转载请注明http://www.cppblog.com/sunicdavy,战魂小筑, 否则木有小JJ

 

一些道理:

1. 进游戏因为内存超标就崩溃, 再漂亮的图片也是没用的.

2. 游戏是多门艺术的综合, 游戏美术的不仅要画的好, 还要能做出优化的好的资源

3. 手游和端游的美术资源标准有本质区别, 资源做出来是给人看的, 不是屏幕. 因此高低分辨率的搭配, 尤为重要.

4. 还是那句话, 多看看别人做的游戏, 多问问别人怎么做的

posted @ 2016-04-28 13:55 战魂小筑 阅读(4163) | 评论 (1)编辑 收藏

最近在项目中进行资源优化. 我们的项目一直以来都是以传统的电子表格配置为中心的资源驱动加载方法, 拿角色携带的特效要播放出来这个case来具体点说就是:

1. 技能部分的特效可以遍历动作表播放的所有特效id, 提前预载

2. buff类特效是动态确定的,无法分析. 需要通过角色表添加资源id在加载角色时加载特效

这种做法的缺点:

当角色特效效果调整时, 美术和策划需要调整特效id表. 多出来不用的特效也加载是感觉不出来的, 分析也是很困难的

所以这种以传统的电子表格配置为中心的方式在Unity3D里, 内存, 包优化会是个大问题.

 

那么, 什么是Unity3D的开发核心思想?

除了组件思想外, 就是Prefab, 贯彻整个编辑器及引擎自始至终

 

处理角色携带特效加载后播放的这个case, 用Prefab为中心的资源管理来做的话, 大概就是这样:

1. 程序编写一个角色特效列表脚本, 把List暴露出来可以在编辑器里使用

2. 美术在做技能时, 把要用到的特效拖拽到List中

3. 特效无需再编制全局ID编码

4. 策划根据这个角色挂接的特效索引, 在配置表里添加播放指令

这样做的优点:

角色引用到的资源才会被打到最终游戏包内, 不使用的资源是不会被加载的

 

类似的, 在UI特效里, 也应该是将要播放的特效挂接到对象中, 而不是动态通过代码去加载

在Unity3D中, Prefab将图片,Shader, 特效, 脚本等一切平等看待, 只要有引用, 一次性加载.

同时, 也可以通过静态工具分析Prefab.

如果是通过代码加载的效果, 则只能让程序员做优化, 这种过程无法让Unity3D官方后期提供的工具进行优化

 

所以, 推荐使用Prefab为中心的资源管理模式

posted @ 2016-03-24 16:33 战魂小筑 阅读(1560) | 评论 (0)编辑 收藏

本文编写时, Google 官方的 protobuf 版本是3.0.0beta

下面介绍下proto3的一些细节变化

Proto3的语法变化

语法标记

这个版本的protoc的protobuf编译器已经可以支持proto2语法和proto3的语法

如果你的proto文件没有添加syntax说明的话, 用这个版本的编译器会报错, 提示你默认proto2支持, 请添加语法标记

syntax = "proto2";

 

optional不需要了

只保留repeated标记数组类型, optional和required都被去掉了

实际使用证明, required的设计确实是蛋疼, C++的调试版会弹出assert,release版和optional也没啥区别

map支持

map编写格式为

map<key_type, value_type> map_field = N;
例如:
map<string, Project> projects = 3;
代码生成确认支持map, 这对于很多语言来说又可以偷懒了

字段default标记不能使用了

位于proto2语法的字段number后的[default=XX]

这个东西不能用了, 理由是:

对于同一段序列化后的数据, 如果序列化端的default和反序列化端的default描述不一样会导致最终结果完全不一致

即: 同一个数据两个结果, 这是不可预测的结果, 因此干掉这个特性

不过本人觉得, 对于游戏来说, 这个功能本身可以压缩很多数据,虽然会有隐患

 

枚举默认值一定是0

proto2里的默认值是枚举的第一个value对应的值, 不一定为0

proto3在你定义value时, 强制要求第一个值必须为0

这个修改为避免隐患还是有帮助的

泛型描述支持

any类型, 可以代表任何类型, 可以先读进来, 再进行解析, 没具体用, 步子跨大了怕扯到蛋

支持json序列化

这个极好, json再次被同化了

增加了多种语言支持

js, objc, ruby, C#等等

然而, C#版本的基础runtime库是用C# 6.0的语法写的,这对于Unity mono祖传2.0来说, 确实扯到蛋了,没法用

Protobuf现在使用CMAKE做配置系统

编译起来稍微麻烦, 还要下个被墙掉的cmake…

 

 

第三方库里对于proto3的变化

Golang的官方protobuf支持: https://github.com/golang/protobuf

生成代码中的结构体字段类型变化

对于proto2的文件, 生成的go代码中的结构体依然使用类型指针作为默认存储, 兼容老的系统

对于proto3的文件, 生成的go代码中的结构体直接使用字段作为默认存储, 不再使用GetXXX来作为字段值访问, 赋值时也无需使用proto.类型() 函数进行指针类型字段值创建.

这个调整很是方便, 但丢失了optional判断功能, 对应C++里就是hasXXX的功能, 不过好歹这个逻辑现在用的不多了

这个修改大概也是配合json序列化来做的, go默认的json序列化时, 无法使用proto2生成的结构体的, 因为都是指针,无法赋值..

 

新版protoc-gen-go的插件会生成descriptor的压缩数据

新插件会给每次生成的文件添加这样一段代码

var fileDescriptor0 = []byte{
    // 220 bytes of a gzipped FileDescriptorProto
    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x8f, 0xcd, 0x4e, 0xc5, 0x20,
    0x10, 0x85, 0x53, 0xbd, 0x35, 0x32, 0xb7, 0xdd, 0x4c, 0x5c, 0xb0, 0x70, 0x71, 0xd3, 0xb8, 0x70,
    0x75, 0x17, 0xfa, 0x04, 0xc6, 0xd8, 0xb8, 0x50, 0x63, 0xa8, 0x2f, 0x80, 0xed, 0x44, 0x89, 0x28,
    0x04, 0xc6, 0xbf, 0x47, 0xf1, 0x6d, 0x95, 0x49, 0x8d, 0x4d, 0x5c, 0x01, 0xdf, 0x39, 0x7c, 0x30,
    0x00, 0x1c, 0x82, 0xdf, 0xc6, 0x14, 0x38, 0xe0, 0xaa, 0xec, 0xbb, 0x37, 0x68, 0x2e, 0x3e, 0x62,
    0x48, 0x7c, 0x49, 0x76, 0xa2, 0x84, 0x47, 0xd0, 0xde, 0x96, 0xf8, 0xee, 0x33, 0xd2, 0x8d, 0x7d,
    0x26, 0x5d, 0x6d, 0xaa, 0x63, 0x65, 0xda, 0xb8, 0x84, 0xd8, 0x41, 0x63, 0xc2, 0x7b, 0xef, 0xc8,
    0x4f, 0x52, 0xda, 0x91, 0x52, 0x93, 0x16, 0x0c, 0x0f, 0x41, 0x89, 0xa9, 0x77, 0x9e, 0xf4, 0xae,
    0x14, 0x54, 0xfc, 0x05, 0xdd, 0x57, 0x05, 0x4a, 0xba, 0xd7, 0xc4, 0x16, 0xb7, 0x80, 0x03, 0x27,
    0xf7, 0xf2, 0x70, 0x72, 0xe5, 0x32, 0x0f, 0xd1, 0x3b, 0xa6, 0x34, 0x5b, 0x31, 0xff, 0x4b, 0x70,
    0x03, 0x6b, 0x43, 0x91, 0x2c, 0x9f, 0x3f, 0xd2, 0xf8, 0x24, 0xf6, 0x7d, 0xb3, 0x4e, 0x7f, 0x08,
    0x0f, 0xa0, 0x3e, 0xf3, 0xce, 0x66, 0xbd, 0x12, 0x49, 0x6d, 0xcb, 0xa1, 0x4c, 0x37, 0xbf, 0xf3,
    0xb3, 0xbc, 0x8e, 0xac, 0x6b, 0xb9, 0xd9, 0xe6, 0x25, 0xbc, 0xdf, 0x93, 0x6f, 0x9e, 0x7e, 0x07,
    0x00, 0x00, 0xff, 0xff, 0x0c, 0x9f, 0x10, 0xa8, 0x2e, 0x01, 0x00, 0x00,
}

对于meta信息的提取还是很方便的

然而

对于多个文件的生成, 这样做非常的麻烦, 因为这个字段会重复导致编译错误

很多人在论坛里吐槽, 官方给出的解决方法是, 使用protoc一次性传入一个package下的所有的proto直接生成一个go

而不是现在的一个proto一个go

生成代码会自动注册到全局, 并可以方便的查询

以前这个代码需要自己来做, 现在官方提供了支持, 很是方便

然而, 为什么不支持遍历… 残念啊, 又要自己动手了

posted @ 2016-01-25 14:23 战魂小筑 阅读(19198) | 评论 (0)编辑 收藏

项目地址:

https://github.com/davyxu/tabtoy

posted @ 2016-01-25 14:00 战魂小筑 阅读(4403) | 评论 (2)编辑 收藏

最近碰到一个蹊跷的设备相关问题。我们的游戏使用的是Unity3D 4.X 真机测试环境都是ios8越狱,从iPhone6,iPad3到iPhone5s都有。所有包在我们本机测试都是OK的,结果包发出去, 在iTouch5,iPhone6s这些2015年新出的设备上一律卡进度条

随即,我们进行了分析。期初推断是arm64引起的问题,尝试调整为il2cpp同时启用armv7和arm64的通用包,问题没有解决。

继续分析:因为游戏正常启动, 只是初次加载卡进度条, 那么可以排除是arm64位问题导致的,因为如果是不兼容包, 在安装时直接会报出架构错误,无法正常安装。

给游戏内部加入了一个HTTP日志系统, 给服务器报错。跟踪了一次, 结果发现了一些奇怪日志

image

在检测下载之前的加载没有出现任何问题

但是下载错误报了两次, 第一个错误在我们本机也会报,但可以忽略。 但第二个错误只有iTouch5,iPhone6s会出现

报错后, 所有日志都出现了两次。

对比了下代码,发现了一些逻辑漏洞。但同时需要注意的是, 这个bug的问题的核心就是在这一个错误描述上

The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.

查过文章发现,ios9开始默认要求所有的app的HTTP访问必须使用HTTPS加密协议保证安全

所以结合前面的测试环境, 证明这个问题确定被修复

posted @ 2016-01-07 14:56 战魂小筑 阅读(1127) | 评论 (3)编辑 收藏

仅列出标题
共26页: 1 2 3 4 5 6 7 8 9 Last