战魂小筑

讨论群:309800774 知乎关注:http://www.zhihu.com/people/xu-bo-62-87 开源项目:https://github.com/davyxu

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

2016年12月1日 #

Golang中没有设计构造函数. 取而代之的, 设计Golang的大师希望你用普通函数去实现构造的任务. 
一直只是觉得这只是体现Golang这门新语言的精简设计之道, 直到自己实现编译器后才发现构造函数的设计本身是值得商榷的

我们先看下构造函数的规则

构造函数调用规则

构造参数量: 0表示没有构造函数, 1表示有构造函数0个参数

本类构造父类构造处理方法
00不处理
10调本类ctor
01调父类ctor
11调本类ctor, 本类ctor调父类ctor
21调本类ctor, 本类ctor调父类ctor
12报错, 手动调父类ctor
22报错, 手动调父类ctor

普通函数重载规则

实际只用考虑最典型的一种行为: 实例化子类, 转为父类调用方法, 这个时候

如果方法是override, 调用的是子类

如果方法是virutal或者不指明, 调用的是父类

整个重载过程, 子类绝对不会隐式调用父类的行为

需要构造函数么?

构造函数的优点

  • 本身属于一种特殊的成员函数
  • 编译器帮你自动传导调用父级

构造函数的缺点

  • 隐式的调用规则
  • 虽然属于成员函数, 但是与其他成员函数调用规则完全不同, 需要特殊记忆
  • 带参数的构造函数, 在父类参数多于子类时, 需要引用复杂语法来实现父级构造调用

其实我们对初始化函数的需求只有1条: 自定义

所以, 可以理解Golang不加入构造函数的设计是正确的 
即: 简单, 清晰, 有规律

posted @ 2016-12-01 10:45 战魂小筑 阅读(920) | 评论 (0)编辑 收藏

2016年11月2日 #

append, map, len不是关键字

他们其实还是类库功能, 都在buildin包里的, 系统默认给你做了个

  1. import(
  2. . "buildin"
  3. )

将buildin的包内容都映射到全局而已, 其实你也可以用自己的包这么做

打印的另一种写法

想跟脚本一样调试打印数据么?

  1. println("hello world")

无需包含任何包, 因为它在buildin包里

iota不是黑科技

这是在buildin包里的定义

  1. // iota is a predeclared identifier representing the untyped integer ordinal
  2. // number of the current const specification in a (usually parenthesized)
  3. // const declaration. It is zero-indexed.
  4. const iota = 0 // Untyped int.

其实go是有泛型概念的

想想map和数组的定义 
只是泛型没有开放给用户用而已(只许XX放火,不许XX点灯)

map是支持多个key的, 而且很方便

还在为多个key转id的复杂算法而头疼么?

  1. type myKey struct{
  2. number int
  3. str string
  4. }
  5. func main(){
  6. t := map[ myKey] int {
  7. myKey{ 2, "world"}: 1,
  8. }
  9. fmt.Println(t[myKey{2, "world"}])
  10. }
  11. 输出: 1

枚举是可以转成string的

默认定义一个枚举

  1. type MyConst int
  2. const (
  3. MyConst_A MyConst = iota
  4. MyConst_B MyConst = iota
  5. )
  6. func main(){
  7. fmt.Println(MyConst_A)
  8. }

输出: 0 
如果我们想自动化输出MyConst_A字符串时 
就需要使用golang的一个工具链:golang.org/x/tools/cmd/stringer 
将其下载, 编译成可执行工具后, 对代码进行生成 
生成的代码会多出一个xx_string.go 
里面就是枚举的String()string 函数

临时转换一个接口并调用的方法

  1. type love struct{
  2. }
  3. func (self*love)foo(){
  4. fmt.Println("love")
  5. }
  6. func main(){
  7. var chaos interface{} = new(love)
  8. chaos.(interface{
  9. foo()
  10. }).foo()
  11. }

Golang的receiver实际上就是this的变种实现

  1. func( self*MyStruct) foo( p int ){
  2. }

写不惯receiver的写法? 如果这样改下呢?

  1. func foo( self *MyStruct, p int ){
  2. }

所以为什么说Golang还是一个C语言嘛

关于内存分配…

  • new 传入Type类型, 返回*Type类型
  • make 可以在分配数组时设置预分配大小, new不可以
  • make 能分配数组,map, 但不能分配结构体和原始类型
  • new 能分配数组, map, 结构体和原始类型等的所有类型
  • &Type等效于new
  • 切片不需要分配内存(make,new), 直接声明就可以了…

Golang的反射无法通过一个类型名, 创建其实例

C#有Assembly概念, 可以在一个Assembly里搜索, 创建实例

Golang是静态类型语言, 如果需要, 只能注册你需要创建的结构体, 然后将注册好的map用于创建

Golang可以替换Python来进行复杂的工具流程处理

如果你需要跨平台的工具流程处理, 对Python不熟悉, 可以使用

  1. go run yourcode.go 参数1 参数2

方式来进行工具处理 
觉得慢, 可以编译成可执行文件

这样做的好处: 如果有些类库本身就是go写的, Python想使用是很麻烦的, 而Golang来写则轻而易举

例子: 通过go的protobuf库, 对一些文件进行处理

Golang可以自动持有方法的接收者实例

  1. type myType struct{
  2. }
  3. func (self*myType) foo( p int){
  4. fmt.Println("hello", p )
  5. }
  6. func main(){
  7. var callback func( int )
  8. ins := new(myType)
  9. callback = ins.foo
  10. callback( 100 )
  11. }

做过lua的C++代码绑定的童鞋都清楚: lua只支持外部静态或者全局函数调用 
如果要进行C++类成员函数调用时, 要自己处理this和成员函数 
这种技巧因为早起编译器的虚表不同平台实现细节不统一需要专门处理 
后面跨平台虚表统一后, 类成员函数的调用写法也是很恶心复杂的 
但是Golang的小白式用法, 直接吊打C++, 甚至C#复杂的delegate

LiteIDE篇: 多开秘籍

  • 找到 菜单->查看->选项->通用->存储->存储设置到本地ini文件

  • 关闭LiteIDE

  • 复制LiteIDE整个目录, 命名文件夹为你的工程名

  • 每个工程所在的LiteIDE的配置将是独立的, 不会互相干扰

LiteIDE篇: 测试程序也是可以调试的

别以为程序一定要是main开始的才可以调试

Golang的测试程序虽然都是一个个Test开头的函数,但执行go test时, 还是有main入口

在LiteIDE中, 可以在 调试->开始调试测试程序里进行测试程序调试

LiteIDE篇: 在Windows上可以输出linux可执行文件

go的工具链默认支持交叉编译 
在LiteIDE中, 可以通过切换输出平台, 输出不同平台的可执行文件

posted @ 2016-11-02 11:09 战魂小筑 阅读(1342) | 评论 (1)编辑 收藏

2016年9月3日 #

Google官方为Golang的调试例子默认使用了gdb

然而, 使用gdb调试go程序会遇到goroutine的各类问题, 因为gdb不懂go

因此, 这里使用delve黑科技来进行Golang的程序调试

纯命令行调试方法在网上很容易搜索到, 本文主要以LiteIDE来进行程序调试

关闭编译器优化

正常go build/install出的go程序是完全优化过的, 强行使用调试器挂接调试时, 某些local变量/lamda表达式捕获的变量会直接进入寄存器, 无法使用调试器查看

删掉所有的pkg, 为build或install参数加入关闭编译器优化的参数 -gcflags "-N -l"

例如:

  1. go install -gcflags "-N -l" svc\gamesvc

delve调试器安装方法

LiteIDE自带了gdb, 但是没有delve调试器, 需要自行安装, 命令如下

  1. go get github.com/derekparker/delve/cmd/dlv

delve调试器会被放到你的GOPATH/bin下

LiteIDE中的delve调试器配置

选择调试器

在LiteIDE菜单中选择 调试->debugger/delve

delve环境变量设置

这个时候, LiteIDE依然找不到delve, 因为它不在环境变量PATH中, 这里无需修改环境变量, 只需要LiteIDE的环境配置

在LiteIDE菜单中选择 查看->编辑当前环境, 在弹出的文档中修改

  1. PATH=c:\mingw32\bin;%GOROOT%\bin;%PATH%;c:\your\path\to\delve

去掉PATH前的注释#, 在%PATH%添加分号, 然后和你到delve调试器的路径

开始调试

选择你的工程, 点击F5, 进入调试模式

调试器显示变量值

LiteIDE使用delve调试时, 无法在 变量 监视等窗口中正确捕捉delve调试返回数据(因为格式太复杂了…)

没关系, 我们使用命令行配合显示即可

LiteIDE控制台或调试输出窗口在delve调试时, 实际上是一个标准命令行 
命令如下

  • p 变量名可以查看变量值

  • locals查看局部变量

  • ls可查看当前文件

  • stack查看栈

  • help可以查看各种帮助

调试外部程序

如果你的程序是外部程序, 或者使用go install安装到GOPATH/bin目录的程序, 那么使用delve调试器启动程序时, 可能会碰到启动路径错误的问题

使用LiteIDE菜单 调试->调试其他应用程序… 填入你要调试程序的路径以及工作目录, 可以解决这个问题

posted @ 2016-09-03 18:12 战魂小筑 阅读(1657) | 评论 (0)编辑 收藏

2016年8月12日 #

测试用例

我们对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 战魂小筑 阅读(1638) | 评论 (0)编辑 收藏

2016年8月1日 #

这是一个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 战魂小筑 阅读(2095) | 评论 (0)编辑 收藏

2016年7月6日 #

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

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

为什么要热更新

客户端

适应上线需求

对于手游客户端来说, 受到苹果审核的约束, 一次审核提交需要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 战魂小筑 阅读(2750) | 评论 (6)编辑 收藏

2016年6月16日 #

苹果要求在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 战魂小筑 阅读(2341) | 评论 (0)编辑 收藏

2016年5月31日 #

最近整合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 战魂小筑 阅读(1013) | 评论 (0)编辑 收藏

2016年4月28日 #

一个手游的图形技术关键性指标是: 内存占用, 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 战魂小筑 阅读(3221) | 评论 (1)编辑 收藏

2016年3月24日 #

最近在项目中进行资源优化. 我们的项目一直以来都是以传统的电子表格配置为中心的资源驱动加载方法, 拿角色携带的特效要播放出来这个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 战魂小筑 阅读(1300) | 评论 (0)编辑 收藏

仅列出标题  下一页