• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            戰(zhàn)魂小筑

            討論群:309800774 知乎關(guān)注:http://zhihu.com/people/sunicdavy 開(kāi)源項(xiàng)目:https://github.com/davyxu

               :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
              257 隨筆 :: 0 文章 :: 506 評(píng)論 :: 0 Trackbacks

            #

            測(cè)試用例

            我們對(duì)Golang的結(jié)構(gòu)體變量賦值, 以及單參數(shù)函數(shù)調(diào)用進(jìn)行反射和native操作的測(cè)試

             

            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)})

            }

            }

            性能測(cè)試數(shù)據(jù)

            === 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)

            測(cè)試評(píng)測(cè)

            • 在結(jié)構(gòu)體變量賦值測(cè)試用例中, 我們發(fā)現(xiàn)TestReflectFindFieldAndAssign賦值格外的耗時(shí). 分析性能點(diǎn)在FieldByName這個(gè)函數(shù)上, 我們查了下底層如何實(shí)現(xiàn)的:

            // 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來(lái)遍歷獲取數(shù)據(jù), 但冷靜下來(lái)分析. 這樣做無(wú)可厚非.
            試想如果reflect包在我們使用ValueOf時(shí)使用map緩沖好一個(gè)結(jié)構(gòu)體所有字段的訪問(wèn)數(shù)據(jù)后, 肯定訪問(wèn)指定字段速度會(huì)很快
            但是, 以空間換速度的需求其實(shí)最多滿足了1%的需求.
            同樣的例子是圖形API里訪問(wèn)Shader變量的方法, 總是默認(rèn)使用字符串獲取, 速度很慢. 當(dāng)你想快速訪問(wèn)時(shí), 請(qǐng)?zhí)崆鞍葱杈彺孀侄?br>那么, Golang使用的也是這樣的思路. 雖然暴力了一點(diǎn), 但是能夠讓程序跑對(duì), 性能優(yōu)化的東西放在之后來(lái)做, 緩沖下就可以解決

            • 在調(diào)用測(cè)試用例中, 毫無(wú)懸念的, 調(diào)用速度很慢
              因此, 我們?cè)谄綍r(shí)使用反射時(shí), 盡量偏向于反射變量緩沖存在下的變量賦值或者獲取
              而調(diào)用的需求盡量減少, 如果有g(shù)oroutine存在的情況下, 則不必太多擔(dān)心.
            posted @ 2016-08-12 15:26 戰(zhàn)魂小筑 閱讀(3032) | 評(píng)論 (0)編輯 收藏

            這是一個(gè)Unity3D元老級(jí)bug, 表現(xiàn)就是:  角色在屏幕邊緣放特效,  離開(kāi)屏幕持續(xù)一段時(shí)間后再回到屏幕后, 發(fā)現(xiàn)特效重新播放. 顯然這是錯(cuò)誤的效果

            解決方法:

            在ParticleSystem組件上勾選SubEmitter, 不要問(wèn)為什么, 做就好

            相關(guān)官方鏈接

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

             

            天煞的, 4.X程序無(wú)法訪問(wèn)粒子系統(tǒng)的參數(shù), 所以只能辛苦美術(shù)兄弟們了

            posted @ 2016-08-01 15:23 戰(zhàn)魂小筑 閱讀(5568) | 評(píng)論 (0)編輯 收藏

            熱更新的內(nèi)容可以是美術(shù)資源, 可以是代碼, 但相對(duì)來(lái)說(shuō), 美術(shù)資源的更新不會(huì)受到約束, 代碼實(shí)際上是重災(zāi)區(qū), 本文介紹的主要是代碼熱更新

            熱更新對(duì)于開(kāi)發(fā)者來(lái)說(shuō)是一件麻煩事, 特別對(duì)于看重效率,便捷性和結(jié)構(gòu)的程序員來(lái)說(shuō), 熱更新就是運(yùn)營(yíng)人員的不懂技術(shù)的表現(xiàn)
            然而, 對(duì)于上線才是剛剛開(kāi)始的網(wǎng)絡(luò)游戲, 特別是手游來(lái)說(shuō). 熱更新是極為重要的基礎(chǔ)功能

            為什么要熱更新

            客戶端

            適應(yīng)上線需求

            對(duì)于手游客戶端來(lái)說(shuō), 受到蘋果審核的約束, 一次審核提交需要10~20天不等的等待時(shí)間, 而這段時(shí)間, 開(kāi)發(fā)進(jìn)度依然會(huì)推進(jìn)很多.

            一旦手游上線, 第一個(gè)版本在玩家瘋狂行為下, 出點(diǎn)問(wèn)題是必然的, 所以”上線更”就成了家常便飯. 如果你要說(shuō), 必須大包, 無(wú)法熱更, 那么10~20

            多天后, 游戲估計(jì)就沒(méi)啥人了, 更別說(shuō)渠道, 發(fā)行投入巨大資金進(jìn)行推廣之下讓玩家迎來(lái)的一堆bug的版本以及所謂程序員的傲慢和清高.

            熱調(diào)試, 熱開(kāi)發(fā), 熱發(fā)布

            除了線上問(wèn)題之外, 由于Unity3D為了適應(yīng)64位應(yīng)用需求, 將C#編譯出的IL代碼利用il2cpp第三方庫(kù)編譯成為c++. 效率提升了倒是好,

            但工程編譯和發(fā)布時(shí)間變得相當(dāng)感人, 沒(méi)個(gè)1~2個(gè)小時(shí)完全搞不定. 即便加裝ssd, 為了修改一個(gè)bug, 也不知道要等多少根煙的時(shí)間…

            只要核心功能不變化的情況下, 完全可以讓熱更新成為開(kāi)發(fā)期間的好工具, lua代碼修改后, 馬上可以在手機(jī)上看效果, 沒(méi)有編譯, 發(fā)布的時(shí)間損耗, 其實(shí)反而提升了開(kāi)發(fā)效率

            服務(wù)器

            對(duì)于服務(wù)器來(lái)說(shuō), 常見(jiàn)游戲類型的玩家一般在半夜的在線人數(shù)會(huì)急速下降. 但是對(duì)于比較熱門的MMO, 以溝通為基礎(chǔ)的游戲, 半夜也會(huì)有很多人在線

            因此傳統(tǒng)的停服更新對(duì)于玩家的熱情秒殺很大的. 想想看,屁股先鋒公測(cè)停15天各位是什么感受? 所以為了玩家體驗(yàn), 同時(shí)保證服務(wù)器穩(wěn)定的前提下

            修復(fù)一些輕微bug, 用熱更新再合適不過(guò)了. 所以老服務(wù)器程序員, 千萬(wàn)不能以服務(wù)器穩(wěn)定為借口而忽略了玩家體驗(yàn).

            技術(shù)是用來(lái)解決問(wèn)題的, 不是用來(lái)裝X的

            怎么熱更新

            以下是Unity3D的幾種熱更新方式

            基于C#, 使用動(dòng)態(tài)加載Assembly反射更新代碼

            這種方式在安卓上完全可行, 對(duì)現(xiàn)有架構(gòu)無(wú)需大的修改, 一樣使用C#和Unity3D的方式進(jìn)行開(kāi)發(fā)

            但在iOS上受到限制, 因此對(duì)于全平臺(tái)首發(fā)的游戲, 或者雙平臺(tái)都要上的游戲, 已經(jīng)慢慢的不使用這種方法進(jìn)行熱更新了

            基于Lua, 將Lua代碼視為資源, 動(dòng)態(tài)加載并運(yùn)行

            云風(fēng)團(tuán)隊(duì)早期研究出的UniLua是基于C#編寫的Lua虛擬機(jī)來(lái)運(yùn)行, 而且只支持字節(jié)碼解釋, 因此無(wú)法做動(dòng)態(tài)功能, 效率奇低

            后期, ulua的出現(xiàn), 徹底將Lua作為比較正統(tǒng)的更新方式存在. ulua基于Tolua庫(kù)進(jìn)行封裝, 添加了一些便捷封裝, 代碼打包和基本的框架

            ToLua本身是一個(gè)基于C版Lua上擴(kuò)充的庫(kù), 以靜態(tài)鏈接庫(kù)方式與Unity3D代碼鏈接. 因此, 可以說(shuō)ToLua是跑在C層上, 速度不亞于C++和Lua的組合

            基于Lua的代碼更新方式, 無(wú)論跨任何平臺(tái)都可以以同一套代碼和工作流進(jìn)行, 因此避免很多麻煩, 成為現(xiàn)在主流的開(kāi)發(fā)方式

            游戲邏輯全都用Lua寫么?

            做過(guò)網(wǎng)頁(yè)和手機(jī)App的童鞋都發(fā)現(xiàn), js, 一個(gè)bug超多, 設(shè)計(jì)奇怪的語(yǔ)言居然成為主流界面開(kāi)發(fā)語(yǔ)言, 為啥?

            動(dòng)態(tài)特性適合制作ui

            另外一個(gè)反例就是: 使用C++開(kāi)發(fā)界面, 例如Qt, MFC之類, 雖然設(shè)計(jì)嚴(yán)謹(jǐn), 但是最終擋不住各種奇葩的修改需求

            因此, 界面非常推薦使用動(dòng)態(tài)語(yǔ)言來(lái)開(kāi)發(fā), 游戲界就是用Lua

            而游戲核心, 根據(jù)各自游戲類型來(lái)定, 總的一點(diǎn), 效率瓶頸點(diǎn), Update之類的, 盡量使用C#或者C++來(lái)實(shí)現(xiàn)

            寫在最后

            當(dāng)前中國(guó)大環(huán)境下的玩家和各種氪金理由與純的不能再純的游戲人的基本愿望是沖突的

            然而國(guó)外游戲的各種設(shè)計(jì)和機(jī)制, 暴雪戰(zhàn)網(wǎng)更新不及時(shí), 版本不對(duì)沒(méi)提示, 這些基本錯(cuò)誤在中國(guó)的網(wǎng)游都不會(huì)出現(xiàn)的

            技術(shù)上無(wú)法趕英超美的我們, 在體驗(yàn)上已經(jīng)輸出了我們的價(jià)值觀, 老外們都在學(xué)

            對(duì)于程序員來(lái)說(shuō), 只是多貼近玩家, 多了解外面的世界而已

            posted @ 2016-07-06 11:03 戰(zhàn)魂小筑 閱讀(6056) | 評(píng)論 (6)編輯 收藏

            蘋果要求在2016年6月1日后新的app必須支持ipv6網(wǎng)絡(luò), 技術(shù)發(fā)展靠蘋果果然沒(méi)錯(cuò), 但開(kāi)發(fā)者還是要開(kāi)始忙起來(lái)了
            這里介紹下Unity3D的適配的一些經(jīng)驗(yàn)

            基本注意點(diǎn)

            • ios ipv6適配無(wú)需修改服務(wù)器, 也就是說(shuō), 如果你的服務(wù)器依然是ipv4的也是可以使用的
            • 蘋果的適配方案是將ipv4的地址轉(zhuǎn)換為ipv6, 到了路由層再轉(zhuǎn)回去繼續(xù)利用ipv4網(wǎng)絡(luò)傳輸

            測(cè)試網(wǎng)絡(luò)環(huán)境搭建

            轉(zhuǎn)載請(qǐng)注明:http://www.shnenglu.com/sunicdavy戰(zhàn)魂小筑

            網(wǎng)上有很多翻譯了蘋果官方的搭建ipv6測(cè)試網(wǎng)絡(luò)環(huán)境的文章, 例如:
            http://www.cocoachina.com/ios/20160525/16431.html
            注意以下幾點(diǎn)

            • 無(wú)需路由器支持ipv6, 但貓(modem)必須要支持ipv6. 因?yàn)楝F(xiàn)在大多數(shù)都是光貓
              以下截圖是光貓管理端
              3440e3f9-12de-435b-85ab-a7a3be8b384b[6]
              光貓里的ipv6支持默認(rèn)是關(guān)閉的, 所以需要手動(dòng)打開(kāi), 按默認(rèn)值配置即可

            • 請(qǐng)確認(rèn)mac os系統(tǒng)必須是osx 10.11以后的版本才可以打開(kāi)NAT64

            • 正確連接mac的ios設(shè)備應(yīng)是如下截圖示意
              91f54476-4b5d-4585-a364-0da2139774c1[6]

            • 默認(rèn)連接上wifi時(shí)看連接信息時(shí), 一般只會(huì)有紅色DNS地址或者根本不顯示
            • 只有在第一次訪問(wèn)網(wǎng)絡(luò), 例如打開(kāi)瀏覽器進(jìn)入任意網(wǎng)站時(shí), 才會(huì)顯示上面的幾條信息
            • 如果只有DNS沒(méi)有IP地址和子網(wǎng)掩碼, 一般是光貓沒(méi)有打開(kāi)ipv6的DHCP, 沒(méi)有分配IP
            • 還有一種測(cè)試ipv6 DHCP是否正常工作的方法: 關(guān)閉NAT64時(shí)可以上網(wǎng), 但打開(kāi)NAT64無(wú)法上網(wǎng)

            轉(zhuǎn)載請(qǐng)注明:http://www.shnenglu.com/sunicdavy戰(zhàn)魂小筑

            Unity3D的Socket適配

            WWW類本身已經(jīng)支持了IPV6, 無(wú)需處理, 這里講解使用C#原生Socket的處理

            • 測(cè)試用的設(shè)備的iOS版本必須是9.3以上的
            • Socket構(gòu)造時(shí), AddressFamily 設(shè)置為InterNetworkV6時(shí)只支持ipv6網(wǎng)絡(luò), 傳入InterNetwork時(shí)只支持ipv4網(wǎng)絡(luò)
            • 4.7.2和5.4.3的當(dāng)前版本在mono層并未支持ipv6代碼適配的核心函數(shù)getaddrinfo, 因此需要通過(guò)oc層做轉(zhuǎn)換, 以下是代碼
              這段代碼將getaddrinfo的地址轉(zhuǎn)換成一個(gè)完整字符串, 格式是:
              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/
            會(huì)造成內(nèi)存泄露, 如果有更好的方法歡迎反饋

            轉(zhuǎn)載請(qǐng)注明:http://www.shnenglu.com/sunicdavy戰(zhàn)魂小筑

            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:  }
            轉(zhuǎn)載請(qǐng)注明:http://www.shnenglu.com/sunicdavy戰(zhàn)魂小筑

            iosaddrinfo.h

               1:  #pragma once
               2:  extern "C"{
               3:      const char* IOSGetAddressInfo(const char *host );
               4:  }
            • C#層的處理假設(shè)多個(gè)地址中都是統(tǒng)一的地址類型,要么全是v4要么全是v6
              返回給定的host內(nèi)多個(gè)IP地址, 可以供處理復(fù)雜的北網(wǎng)通,南電信問(wèn)題

               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:  }
            轉(zhuǎn)載請(qǐng)注明:http://www.shnenglu.com/sunicdavy戰(zhàn)魂小筑

            參考鏈接

            官方文檔
            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/
            注意, 此方案中的方法可用, 但是地址并不能解決南北互通的問(wèn)題

            posted @ 2016-06-16 14:18 戰(zhàn)魂小筑 閱讀(5338) | 評(píng)論 (0)編輯 收藏

            最近整合ulua到項(xiàng)目里進(jìn)行熱更新, protoc-gen-lua這古老的東西重新讓我繼續(xù)發(fā)博客, 因?yàn)榭?/p>

            生成好的協(xié)議報(bào)錯(cuò)找不到protobuf

            在每個(gè)protoc-gen-lua生成的lua文件里, 都有一行

            local protobuf = require "protobuf"

            本身按照官方出的沒(méi)問(wèn)題, 但是在ulua的目錄里, 總是報(bào)protobuf找不到的錯(cuò)誤. 前后對(duì)比了下我生成的lua和ulua官方生成的代碼里

            發(fā)現(xiàn)居然他修改了地址改為了

            local protobuf = require "protobuf/protobuf"

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

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

            生成消息無(wú)法找到Descriptor反射查信息

            在LuaFramework\ToLua\Lua\protobuf\protobuf.lua的939行添加
            message_meta._member.Descriptor = descriptor
            在消息里就可以通過(guò)msg.Descriptor獲得此消息的反射信息

            由于proto文件定義的內(nèi)容過(guò)多導(dǎo)致的lua local超過(guò)限制的警告

            image

            這個(gè)錯(cuò)誤真是讓我哭笑不得, protoc-gen-lua的可用性再一次被懷疑

            我們的協(xié)議好歹分成了接近100個(gè), 每個(gè)里面消息和數(shù)據(jù)是混合的, 更別說(shuō)有些童鞋喜歡把一個(gè)項(xiàng)目的協(xié)議全寫在一個(gè)文件里, 那生成的local數(shù)量簡(jiǎn)直是酸爽

             

             

             

            后記

            搜索protoc-gen-lua時(shí), 無(wú)意間又搜到3年前自己的博文http://www.shnenglu.com/sunicdavy/archive/2013/04/24/199693.html

            記得那個(gè)時(shí)候準(zhǔn)備在服務(wù)器使用lua, 還好沒(méi)這么干, 轉(zhuǎn)了go, 否則后果不堪設(shè)想

            lua上使用pb其實(shí)并不容易, 云風(fēng)的pbc寫的不錯(cuò), 但怕有坑, sproto直接不兼容現(xiàn)有項(xiàng)目, 風(fēng)險(xiǎn)大于易用性所以果斷棄用

            因此, 看來(lái)有必要自己寫一個(gè)支持良好的lua pb庫(kù)

            posted @ 2016-05-31 11:26 戰(zhàn)魂小筑 閱讀(4954) | 評(píng)論 (0)編輯 收藏

            一個(gè)手游的圖形技術(shù)關(guān)鍵性指標(biāo)是: 內(nèi)存占用, DrawCall和包大小. 

            這三個(gè)參數(shù)是訓(xùn)練有素的程序和UI美術(shù)都需要關(guān)注的重要問(wèn)題

            接下來(lái)我們來(lái)講解下UI美術(shù)怎么對(duì)待這三個(gè)問(wèn)題

            內(nèi)存占用

            手機(jī)的內(nèi)存不會(huì)明顯區(qū)分內(nèi)存和顯存, 大部分都是共享訪問(wèn)的. 這里說(shuō)的內(nèi)存, 一般可以通過(guò)一些工具直接看到

            , 比如說(shuō)XCode等

            圖形上對(duì)內(nèi)存影響最大的就是紋理, 而紋理上最關(guān)鍵的問(wèn)題就是紋理的大小, 也就是紋理面積

            我曾經(jīng)見(jiàn)過(guò)一些訓(xùn)練不是那么有素的UI美術(shù), 用鼠標(biāo)選中幾個(gè)png文件, 點(diǎn)擊屬性告訴我: 諾, 你都看到了, 我這邊的圖片

            才占幾kb, 為啥你總是說(shuō)內(nèi)存占用大

            紋理的內(nèi)存占用, 只決定于紋理的面積以及發(fā)色數(shù), 紋理面積就是長(zhǎng)乘寬(像素), 發(fā)色數(shù)就是一般常說(shuō)的: 16位色, 32位色

            之所以把內(nèi)存占用放在首位, 是因?yàn)? 大多數(shù)的手機(jī)一旦超過(guò)限定內(nèi)存就會(huì)開(kāi)始清理后臺(tái)掛起的程序, 實(shí)在清理不了只有殺掉最占內(nèi)存的程序

            這就肯定殺到了你寫的游戲之上

            轉(zhuǎn)載請(qǐng)注明http://www.shnenglu.com/sunicdavy,戰(zhàn)魂小筑, 否則木有小JJ

            DrawCall(DC)

            美術(shù)來(lái)理解這個(gè)概念可以這么說(shuō):  繪制一張圖片需要耗費(fèi)1次DC, 假設(shè)界面上有10個(gè)圖標(biāo), 那就需要耗費(fèi)10個(gè)DC

            而一般手游的DC需要限定在150個(gè)之內(nèi), 如何降低DC呢, 就需要通過(guò)Atlas技術(shù)來(lái)合并圖片

            將多張圖片打到一張紋理上的技術(shù)被叫做Atlas, 俗稱大圖或者圖集, 被打之前的圖也就叫小圖

            大圖上的每個(gè)圖元素叫做精靈

            每個(gè)精靈被繪制無(wú)數(shù)次最終也只會(huì)耗費(fèi)1個(gè)DC

            但我們不能把所有游戲用到的圖片都打成圖集, 這并不劃算

            我們會(huì)根據(jù)圖的使用頻率, 用途來(lái)按需打圖集

            比如說(shuō):

            1. 進(jìn)游戲只看一次的宣傳圖, 為了方便制作和加載迅速, 做一張整圖動(dòng)態(tài)加載會(huì)比較好

            2. 反復(fù)查看的圖標(biāo), 因?yàn)閿?shù)量相對(duì)固定,數(shù)量不會(huì)膨脹, 我們就做成圖集

            3. 但是類似于刀塔傳奇中50+英雄, 普通玩家看不到那么多英雄但又被打成圖集是不劃算的, 所以損失一點(diǎn)DC按小圖繪制及加載是正確方法

            4. 一般時(shí)候, 我們將尺寸小的圖片打成圖集, 配合大尺寸圖片同時(shí)加載

            轉(zhuǎn)載請(qǐng)注明http://www.shnenglu.com/sunicdavy,戰(zhàn)魂小筑, 否則木有小JJ

            包大小

            包大小對(duì)于游戲來(lái)說(shuō), 會(huì)影響的是玩家首次下載的時(shí)間, 如果連游戲都不下載, 做的再漂亮的游戲也是沒(méi)用的

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

            1. 分包機(jī)制. 先玩小包, 根據(jù)需要下載大包, 多見(jiàn)于MMORPG

            2. 良好的資源管理方法及習(xí)慣

            3. 剔除冗余資源

            4. 盡量使用3D渲染代替2D紋理圖片

            以上3個(gè)概念是游戲美術(shù), 程序必須了解的重要概念

            但一個(gè)合格的美術(shù), 除了事后優(yōu)化, 還需要做的是事前優(yōu)化

            事前優(yōu)化包括: 在游戲立項(xiàng)后, UI美術(shù)需要了解基本的游戲功能設(shè)計(jì)方案

            出一套基本的對(duì)話框, 提示框, 圖標(biāo)裝飾等的圖素, 這些資源往往只有不到512見(jiàn)方的資源

            利用這些圖片可以拼湊出70%的界面及美化效果

            在這之后的UI內(nèi)容, 只是特效,動(dòng)畫的設(shè)計(jì).

            轉(zhuǎn)載請(qǐng)注明http://www.shnenglu.com/sunicdavy,戰(zhàn)魂小筑, 否則木有小JJ

             

            一些道理:

            1. 進(jìn)游戲因?yàn)閮?nèi)存超標(biāo)就崩潰, 再漂亮的圖片也是沒(méi)用的.

            2. 游戲是多門藝術(shù)的綜合, 游戲美術(shù)的不僅要畫的好, 還要能做出優(yōu)化的好的資源

            3. 手游和端游的美術(shù)資源標(biāo)準(zhǔn)有本質(zhì)區(qū)別, 資源做出來(lái)是給人看的, 不是屏幕. 因此高低分辨率的搭配, 尤為重要.

            4. 還是那句話, 多看看別人做的游戲, 多問(wèn)問(wèn)別人怎么做的

            posted @ 2016-04-28 13:55 戰(zhàn)魂小筑 閱讀(4547) | 評(píng)論 (1)編輯 收藏

            最近在項(xiàng)目中進(jìn)行資源優(yōu)化. 我們的項(xiàng)目一直以來(lái)都是以傳統(tǒng)的電子表格配置為中心的資源驅(qū)動(dòng)加載方法, 拿角色攜帶的特效要播放出來(lái)這個(gè)case來(lái)具體點(diǎn)說(shuō)就是:

            1. 技能部分的特效可以遍歷動(dòng)作表播放的所有特效id, 提前預(yù)載

            2. buff類特效是動(dòng)態(tài)確定的,無(wú)法分析. 需要通過(guò)角色表添加資源id在加載角色時(shí)加載特效

            這種做法的缺點(diǎn):

            當(dāng)角色特效效果調(diào)整時(shí), 美術(shù)和策劃需要調(diào)整特效id表. 多出來(lái)不用的特效也加載是感覺(jué)不出來(lái)的, 分析也是很困難的

            所以這種以傳統(tǒng)的電子表格配置為中心的方式在Unity3D里, 內(nèi)存, 包優(yōu)化會(huì)是個(gè)大問(wèn)題.

             

            那么, 什么是Unity3D的開(kāi)發(fā)核心思想?

            除了組件思想外, 就是Prefab, 貫徹整個(gè)編輯器及引擎自始至終

             

            處理角色攜帶特效加載后播放的這個(gè)case, 用Prefab為中心的資源管理來(lái)做的話, 大概就是這樣:

            1. 程序編寫一個(gè)角色特效列表腳本, 把List暴露出來(lái)可以在編輯器里使用

            2. 美術(shù)在做技能時(shí), 把要用到的特效拖拽到List中

            3. 特效無(wú)需再編制全局ID編碼

            4. 策劃根據(jù)這個(gè)角色掛接的特效索引, 在配置表里添加播放指令

            這樣做的優(yōu)點(diǎn):

            角色引用到的資源才會(huì)被打到最終游戲包內(nèi), 不使用的資源是不會(huì)被加載的

             

            類似的, 在UI特效里, 也應(yīng)該是將要播放的特效掛接到對(duì)象中, 而不是動(dòng)態(tài)通過(guò)代碼去加載

            在Unity3D中, Prefab將圖片,Shader, 特效, 腳本等一切平等看待, 只要有引用, 一次性加載.

            同時(shí), 也可以通過(guò)靜態(tài)工具分析Prefab.

            如果是通過(guò)代碼加載的效果, 則只能讓程序員做優(yōu)化, 這種過(guò)程無(wú)法讓Unity3D官方后期提供的工具進(jìn)行優(yōu)化

             

            所以, 推薦使用Prefab為中心的資源管理模式

            posted @ 2016-03-24 16:33 戰(zhàn)魂小筑 閱讀(2037) | 評(píng)論 (0)編輯 收藏

            本文編寫時(shí), Google 官方的 protobuf 版本是3.0.0beta

            下面介紹下proto3的一些細(xì)節(jié)變化

            Proto3的語(yǔ)法變化

            語(yǔ)法標(biāo)記

            這個(gè)版本的protoc的protobuf編譯器已經(jīng)可以支持proto2語(yǔ)法和proto3的語(yǔ)法

            如果你的proto文件沒(méi)有添加syntax說(shuō)明的話, 用這個(gè)版本的編譯器會(huì)報(bào)錯(cuò), 提示你默認(rèn)proto2支持, 請(qǐng)?zhí)砑诱Z(yǔ)法標(biāo)記

            syntax = "proto2";

             

            optional不需要了

            只保留repeated標(biāo)記數(shù)組類型, optional和required都被去掉了

            實(shí)際使用證明, required的設(shè)計(jì)確實(shí)是蛋疼, C++的調(diào)試版會(huì)彈出assert,release版和optional也沒(méi)啥區(qū)別

            map支持

            map編寫格式為

            map<key_type, value_type> map_field = N;
            例如:
            map<string, Project> projects = 3;
            代碼生成確認(rèn)支持map, 這對(duì)于很多語(yǔ)言來(lái)說(shuō)又可以偷懶了

            字段default標(biāo)記不能使用了

            位于proto2語(yǔ)法的字段number后的[default=XX]

            這個(gè)東西不能用了, 理由是:

            對(duì)于同一段序列化后的數(shù)據(jù), 如果序列化端的default和反序列化端的default描述不一樣會(huì)導(dǎo)致最終結(jié)果完全不一致

            即: 同一個(gè)數(shù)據(jù)兩個(gè)結(jié)果, 這是不可預(yù)測(cè)的結(jié)果, 因此干掉這個(gè)特性

            不過(guò)本人覺(jué)得, 對(duì)于游戲來(lái)說(shuō), 這個(gè)功能本身可以壓縮很多數(shù)據(jù),雖然會(huì)有隱患

             

            枚舉默認(rèn)值一定是0

            proto2里的默認(rèn)值是枚舉的第一個(gè)value對(duì)應(yīng)的值, 不一定為0

            proto3在你定義value時(shí), 強(qiáng)制要求第一個(gè)值必須為0

            這個(gè)修改為避免隱患還是有幫助的

            泛型描述支持

            any類型, 可以代表任何類型, 可以先讀進(jìn)來(lái), 再進(jìn)行解析, 沒(méi)具體用, 步子跨大了怕扯到蛋

            支持json序列化

            這個(gè)極好, json再次被同化了

            增加了多種語(yǔ)言支持

            js, objc, ruby, C#等等

            然而, C#版本的基礎(chǔ)runtime庫(kù)是用C# 6.0的語(yǔ)法寫的,這對(duì)于Unity mono祖?zhèn)?.0來(lái)說(shuō), 確實(shí)扯到蛋了,沒(méi)法用

            Protobuf現(xiàn)在使用CMAKE做配置系統(tǒng)

            編譯起來(lái)稍微麻煩, 還要下個(gè)被墻掉的cmake…

             

             

            第三方庫(kù)里對(duì)于proto3的變化

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

            生成代碼中的結(jié)構(gòu)體字段類型變化

            對(duì)于proto2的文件, 生成的go代碼中的結(jié)構(gòu)體依然使用類型指針作為默認(rèn)存儲(chǔ), 兼容老的系統(tǒng)

            對(duì)于proto3的文件, 生成的go代碼中的結(jié)構(gòu)體直接使用字段作為默認(rèn)存儲(chǔ), 不再使用GetXXX來(lái)作為字段值訪問(wèn), 賦值時(shí)也無(wú)需使用proto.類型() 函數(shù)進(jìn)行指針類型字段值創(chuàng)建.

            這個(gè)調(diào)整很是方便, 但丟失了optional判斷功能, 對(duì)應(yīng)C++里就是hasXXX的功能, 不過(guò)好歹這個(gè)邏輯現(xiàn)在用的不多了

            這個(gè)修改大概也是配合json序列化來(lái)做的, go默認(rèn)的json序列化時(shí), 無(wú)法使用proto2生成的結(jié)構(gòu)體的, 因?yàn)槎际侵羔?無(wú)法賦值..

             

            新版protoc-gen-go的插件會(huì)生成descriptor的壓縮數(shù)據(jù)

            新插件會(huì)給每次生成的文件添加這樣一段代碼

            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,
            }

            對(duì)于meta信息的提取還是很方便的

            然而

            對(duì)于多個(gè)文件的生成, 這樣做非常的麻煩, 因?yàn)檫@個(gè)字段會(huì)重復(fù)導(dǎo)致編譯錯(cuò)誤

            很多人在論壇里吐槽, 官方給出的解決方法是, 使用protoc一次性傳入一個(gè)package下的所有的proto直接生成一個(gè)go

            而不是現(xiàn)在的一個(gè)proto一個(gè)go

            生成代碼會(huì)自動(dòng)注冊(cè)到全局, 并可以方便的查詢

            以前這個(gè)代碼需要自己來(lái)做, 現(xiàn)在官方提供了支持, 很是方便

            然而, 為什么不支持遍歷… 殘念啊, 又要自己動(dòng)手了

            posted @ 2016-01-25 14:23 戰(zhàn)魂小筑 閱讀(27131) | 評(píng)論 (0)編輯 收藏

            項(xiàng)目地址:

            https://github.com/davyxu/tabtoy

            posted @ 2016-01-25 14:00 戰(zhàn)魂小筑 閱讀(5469) | 評(píng)論 (2)編輯 收藏

            最近碰到一個(gè)蹊蹺的設(shè)備相關(guān)問(wèn)題。我們的游戲使用的是Unity3D 4.X 真機(jī)測(cè)試環(huán)境都是ios8越獄,從iPhone6,iPad3到iPhone5s都有。所有包在我們本機(jī)測(cè)試都是OK的,結(jié)果包發(fā)出去, 在iTouch5,iPhone6s這些2015年新出的設(shè)備上一律卡進(jìn)度條

            隨即,我們進(jìn)行了分析。期初推斷是arm64引起的問(wèn)題,嘗試調(diào)整為il2cpp同時(shí)啟用armv7和arm64的通用包,問(wèn)題沒(méi)有解決。

            繼續(xù)分析:因?yàn)橛螒蛘?dòng), 只是初次加載卡進(jìn)度條, 那么可以排除是arm64位問(wèn)題導(dǎo)致的,因?yàn)槿绻遣患嫒莅?在安裝時(shí)直接會(huì)報(bào)出架構(gòu)錯(cuò)誤,無(wú)法正常安裝。

            給游戲內(nèi)部加入了一個(gè)HTTP日志系統(tǒng), 給服務(wù)器報(bào)錯(cuò)。跟蹤了一次, 結(jié)果發(fā)現(xiàn)了一些奇怪日志

            image

            在檢測(cè)下載之前的加載沒(méi)有出現(xiàn)任何問(wèn)題

            但是下載錯(cuò)誤報(bào)了兩次, 第一個(gè)錯(cuò)誤在我們本機(jī)也會(huì)報(bào),但可以忽略。 但第二個(gè)錯(cuò)誤只有iTouch5,iPhone6s會(huì)出現(xiàn)

            報(bào)錯(cuò)后, 所有日志都出現(xiàn)了兩次。

            對(duì)比了下代碼,發(fā)現(xiàn)了一些邏輯漏洞。但同時(shí)需要注意的是, 這個(gè)bug的問(wèn)題的核心就是在這一個(gè)錯(cuò)誤描述上

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

            查過(guò)文章發(fā)現(xiàn),ios9開(kāi)始默認(rèn)要求所有的app的HTTP訪問(wèn)必須使用HTTPS加密協(xié)議保證安全

            所以結(jié)合前面的測(cè)試環(huán)境, 證明這個(gè)問(wèn)題確定被修復(fù)

            posted @ 2016-01-07 14:56 戰(zhàn)魂小筑 閱讀(1545) | 評(píng)論 (3)編輯 收藏

            僅列出標(biāo)題
            共26頁(yè): 1 2 3 4 5 6 7 8 9 Last 
            久久免费视频网站| 久久国产精品国语对白| 亚洲国产成人精品91久久久| avtt天堂网久久精品| 中文字幕热久久久久久久| 色青青草原桃花久久综合| 久久久精品人妻一区二区三区蜜桃| 久久人人超碰精品CAOPOREN| 99久久无码一区人妻| 久久精品人人做人人爽97| 色狠狠久久AV五月综合| 人妻无码久久一区二区三区免费| 日韩欧美亚洲综合久久| 久久中文字幕人妻丝袜| 久久狠狠爱亚洲综合影院| 99久久国产宗和精品1上映| 亚洲中文字幕无码久久精品1| 国内精品久久国产| 色婷婷综合久久久久中文一区二区 | 久久久精品波多野结衣| 国产精品永久久久久久久久久| 国产精久久一区二区三区| 国内精品久久久久影院网站| 久久久国产精品| 97精品国产97久久久久久免费 | 久久久SS麻豆欧美国产日韩| 久久精品国产亚洲AV影院| 久久精品国产亚洲精品2020| 久久久久无码精品国产不卡| 九九99精品久久久久久| 久久精品国产一区二区电影| 一本一道久久a久久精品综合| 亚洲女久久久噜噜噜熟女| 久久99国产精品二区不卡| 久久乐国产精品亚洲综合| 久久精品亚洲AV久久久无码| …久久精品99久久香蕉国产| 国产精品欧美亚洲韩国日本久久| 思思久久99热只有频精品66| 久久99精品久久久久久动态图| 99久久免费只有精品国产|