数据专栏

智能大数据搬运工,你想要的我们都有

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

提供了2种EventDispatcher ,一种是用hash table 来存储 string 类型的key
一种是同数组 ,和const int事件类型 来存储 实现比hash还快的 索引
完整代码 using UnityEngine; using System.Collections; using System; using System.Collections.Generic; // this is custom event (use string0 public class EventDispatcher : ZT_Object { private EventDispatcher() { EventSystem.ins.AddEvent_Update(this); } Hashtable objs = new Hashtable(); Hashtable objs_for_once = new Hashtable(); public static EventDispatcher ins { get { return EventDispatcher.GetInstance(); } } private static EventDispatcher _ins = null; public static EventDispatcher GetInstance() { if (_ins == null) { _ins = new EventDispatcher(); } return _ins; } public static void DestroyInstance() { _ins.Clear(); _ins = null; } public static EventDispatcher Create(string name = "UnName") { EventDispatcher ret = new EventDispatcher(); ret.name = name; return ret; } public void PostEvent(string type, object userdata = null) { if (objs.ContainsKey(type)) { ArrayList list = objs[type] as ArrayList; for( int i=0;i func) { this._queue_funcs.Enqueue(func); } private void ProcessOtherThreadFunc() { _queue_funcs.Lock(); while (_queue_funcs.UnSafeEmpty() == false) { Func func = _queue_funcs.UnSafeDequeue() as Func; func(); } _queue_funcs.UnLock(); } public override void Update() { this.ProcessOtherThreadFunc(); } private ThreadSafeQueue _queue_funcs = new ThreadSafeQueue(); public string name = "Global"; }; //this is event which used int for Event public class ZTEventDispatcher : object { private ZTEventDispatcher() { for (int i = 0; i < Event.MAX_EVENT_LENGTH; i++) { objs.Add(null); objs_for_once.Add(null); } } ArrayList objs = new ArrayList(); ArrayList objs_for_once = new ArrayList(); public static ZTEventDispatcher ins { get { return ZTEventDispatcher.GetInstance(); } } private static ZTEventDispatcher _ins = null; public static ZTEventDispatcher GetInstance() { if (_ins == null) { _ins = new ZTEventDispatcher(); } return _ins; } public static void DestroyInstance() { _ins.Clear(); _ins = null; } public static ZTEventDispatcher Create(string name = "UnName") { ZTEventDispatcher ret = new ZTEventDispatcher(); ret.name = name; return ret; } public void PostEvent(int type, object userdata = null) { if (objs[type] == null) { return; } ArrayList list = objs[type] as ArrayList; foreach (ZT_Object obj in list) { obj.OnEvent(type, userdata); } } public void PostEventOnce(int type, object userdata = null) { //call for once if (objs_for_once[type] != null) { ArrayList list = objs_for_once[type] as ArrayList; foreach (ZT_Object obj in list) { obj.OnEvent(type, userdata); } objs_for_once.Remove(type); } } public void AddEventListener(ZT_Object target, int type) { if (objs[type] == null) { objs[type] = new ArrayList(); } ArrayList list = objs[type] as ArrayList; list.Add(target); } public void RemoveEventListener(ZT_Object target, int type) { if (objs[type] == null) { return; } ArrayList list = objs[type] as ArrayList; list.Remove(target); } public void Clear() { objs.Clear(); } public void AddEventListenerOnce(ZT_Object target, int type) { if (objs_for_once[type] == null) { objs_for_once[type] = new ArrayList(); } ArrayList list = objs_for_once[type] as ArrayList; list.Add(target); } public string name = "GlobalZTEvent"; };
ZT_Object using UnityEngine; using System.Collections; using System; public class ZT_Object : IDisposable { // every frame will be call public virtual void Update() { } //every logic frame will be call(Frame Sync to All clients) public virtual void UpdateMS() { } //use this to init you class public virtual bool Init() { return true; } public virtual void OnEvent(string type, object userData) { } public virtual void OnEvent(int type, object userData) { } // you can override this interface to release your Res ,Rem. must call base.OnExit when you override public virtual void OnExit() { } // you can use this interface to notify class to release//OnExit will be call // use this or Release() public void Dispose() { this.OnExit(); } // use this or Dispose() public void Release() { this.Dispose(); } };
此外ZT_Object为客户端所有对象的父类,负责消息派发,事件响应,逻辑更新接口,每帧更新接口,析构处理等。
EventDispatcher 在主线程跑,提供了其他线程添加主线程执行函数 回调方法
软件开发
2017-01-10 22:04:00
模仿球球大作战:

一个大小为1000*1000的地图中,有1W个 食物 需要判断是否能吃掉,游戏设计是帧同步(帧数40),如果全部搜索的话很费时间,于是采用了网格来减少搜索个数。
暂且分为10*10的网格,理论每个网格只有100个食物,计算数量大大减少,
通过玩家坐标个半径,形成一个正方形,一共8个点,然后求8个点所在的格子,把该格子的所有食物 选出来,
下面是粗略实现的代码
软件开发
2017-01-10 21:21:00
只要应用UI在每个平台都保持更新,使用一款跨平台的移动应用开发工具就可以帮助你节省超多时间和票子。
确有一些应用编译平台可以让开发应用不再费钱费力。不过选择合适的移动应用编译平台也是灰常重要的。我们在这里整理了生产环境中非常流行的 Top 5 移动开发工具 :
1. Xamarin
Xamarin 绝对是原生应用的明智之选。Xamarin 复用业务逻辑层并且数据支持跨平台。这在需要实现众多本地数据(存储),支持离线模式,以及图像识别的业务功能中是非常有用的。Xamarin 编译于 C#语言,在 .NET 公共语言库上运行。故这个工具可以广泛支持IOS, Windows 和 Android 平台。Xamarin 基于mono框架并给移动设备开放了 API 通道。 在Xamarin QA和其测试功能的帮助下,Xamarin 同样可以让移动应用开发者创建可伸缩并且健壮的应用。
Xamarin 其中一个美妙之处在于它可以无形之中让 bug 变少,从而可以给市场更多时间。更少的编码意味着更少的bug,并且各个平台的单元测试也可以更有效率。
Xamarin 坚信合作成果,所有开发者都可以自由选择免费或者付费控件。Xamarin 组件商店包括UI控制,第三方网站服务和跨平台类库。类似 Microsoft Azure, Salesforce 以及其他后台都可以很容易的使用 Xamarin 集成上去。 正是众多桂冠,Xamarin 当之无愧的成为跨平台应用开发的王者。
2. Appcelerator
通常,Appcelerator “accelerates(加速)”应用开发进程因其让应用开发者仅需几行代码就可以创建应用。它支持IOS, Android,Windows,以及其他基于浏览器的 Html 5 应用。对需要开发一个原生应用或者跨平台应用的开发者来说,Appcelerator 可以有。其主要帮助开发者处理应用对网络服务器de1响应。
Appcelerator 之所以流行的原因绝不仅仅是因为“一次开发,到处运行”的概念。Appcelerator 有几个平台分别提供 API,功能,以及UI。 因其代码为平台适配,所以它保证能提升性能表现。有了Appcelerator的帮助,不需要单独的学习Andorid 或者 IOS 编程,开发者们就能开发可靠的移动应用了。
Appcelerator 在核心部分使用 JavaScript 和 XML 来编译原生或者混合模式移动应用(介于网页应用于原生应用之间)。其在编译移动网页应用的时候也很有用。Appcelerator 拥有一个包含庞大数目开发者的社区,并且开发者与用户集中于 Appceleator 开放移动市场讨论,购买,售卖,以及分享各种模版,模块以及插件。
3. PhoneGap
PhoneGap 也被分到了跨平台应用开发的类别中。PhoneGap 其中一个好处是,可以用它创建一个可以在所有移动设备中都工作的单独应用。它可以在Html5,CSS3,和JavaScript上工作。
这个框架通过几个类库就可以接入,帮助节约很多时间。作为一个灵活的工具,它可以以更小的代价开发各种各样的移动应用。开发者们可以在插件结构的帮助下继承原应用的功能。
使用 PhoneGap 开发的应用在各平台表现一致,因为在多平台上视觉与感觉相同。此外,强有力的后台系统增加了移动应用的进展速度。另一个不可思议的功能是,这个功能甚至可以开发设备的硬件功能比如,摄像头,加速计,geo定位等。
PhoneGap 具备开源信息许可证,所以这是一个免费的移动应用开发框架。 它有一个很大的用于收集提交改善应用编译质量的新代码,新控件的开发者社区。这是一款不需要任何特殊的专长,技巧即可使用的健壮应用编译工具。
4. Sencha
Sencha Touch 是一款基于MVC的 JavaScript 移动应用编译框架。移动应用其中一个独一无二的功能是不需要鼠标和键盘的触摸控制。Sencha Touch 为你的应用提供了高灵敏度的触摸控制,从而提升用户满意度。它兼容了所有最新版本的Android,IOS 和 黑莓,并且可以用了创建易于浏览(easy-to-navigate)应用。
如果开发者使用 Sencha 开发移动应用,他们可以使用 Html5 格式代码。当代码为 Html5时,这就使得无论Android 和 IOS 都可以很简单的调用他们。代码可以在其他工具,例如 PhoneGap的帮助下被集成。
想让应用更快的客户应该选择 Sencha。Sencha 是一个绝不会浪费您一丁点时间,极具效率的工具。它能开发出高性能的UI组件,包括表格,菜单,列表和导航栏。这个新纪元的工具是用户友好的,能让你的应用更易于使用。
5. Ionic
Ionic 是一个兼容混合模式移动应用的 Html 5 移动应用开发框架。作为一个完全开源的SDK, 它帮助用户使用如CSS,HTML5, SASS 等网页技术创建混合模式移动应用。
它为强交互应用提供了非常棒的工具和服务。 Ionic 刚开始只是几个可以创建移动应用的 CSS 以及 JavaScript 默认控件。 在其位于 AngularJS 框架首位之后,它已然是一个可以非常简单生成应用的工具。
原文: https://dzone.com/articles/top-5-mobile-app-development-tools
编译:开源中国-两味真火
软件开发
2017-01-09 12:01:03
Vector3 dir = box.position-transform.position ; y = Vector3.Cross(transform.forward, dir).y;
首先创建一条射线方向事transform 指向box transform.forward是另一条向量指向 transform的正前方 这两条向量的叉积y值如果为正则box位于transform的右侧否则为左侧
软件开发
2016-11-29 17:23:00
1:目标
2:检查
3:迭代优化
4:额外收益
5:结合unity Profiler优化内存占用
6:总结
1:目标 优化AB,一方面降低了AB的大小,降低了包体大小,另外一方面ab加载时所占用的内存也会减小,降低了游戏内存不足崩溃的概率。
同时通过对AB中包含资源的深入分析,可以避免将一些旧的资源错误打入到ab中。
2:检查
2.1: Unity4.x的ab打包是手动管理依赖,非常麻烦,而Unity5.x自动管理AB之间的依赖,大大降低了开发负担。 手动管理有点类似于c语言直接用指针,控制性强,但是极其容易出错;而自动管理依赖,会导致对ab资源打包原理的缺少分析和关注,使得对ab的优化不够。
打包的基本策略: 将公共依赖的资源打入到一个公共ab中,其它ab依赖于公共ab。
我们使用的是Unity5.x, Unity5.x中打包AB有两种方式,一种是直接在编辑器中为每个资源设置其所属于的ab的名字; 另外一种方式是在代码中,设置每个ab包含哪些资源路径,我们采用的是第二种方式,在代码中指定。
2.2:我们游戏做了若干次AB的检测,逐步优化掉了一些AB打包中的问题:
UWA中的ab资源检测界面,主要分为两个栏目: 一个是以AB为观察对象,另外一个是以 资源本身为观察对象;
第一次检测的结果,可以看到很多ab中有冗余的资源。
可以看资源使用栏目,查看哪些资源冗余了,一般都是些shader,材质,纹理,sprite,动画,之类的资源冗余。
点击资源使用,看到资源冗余的情况。
看到有大量的sprite冗余,因为我们游戏每个UI打入到了一个独立的ab中,而UI公共使用的一些sprite没有打入到公共ab中,导致这些sprite被打入到了每个UI ab中, 而sprite对应的纹理,也被打入到了这些ui的ab中。
解决方案就是,额外加一个公共ab,将这些sprit打入到这个ab中。使UI ab依赖于这个公共ab。
材质的冗余主要是,废弃掉的粒子效果仍然被打入到了ab中,删除掉这些粒子效果。
动画冗余,忘记将ui动画打到公共ab中了,把动画目录添加到公共ab。
Shader冗余,每个材质被单独打到独立的ab中了,导致材质用的shader被重复打入,这里之前有个误区,将shader放到一个prefab中,将prefab打入到公共ab中,并不能使这些材质ab 依赖于这个公共ab, unity5.x中 ab之间的依赖必须是显式的,而不能是隐式的, 必须显式的将 shader的路径加入到公共ab中,才能使材质ab依赖于公共ab。
Font冗余,错误的使用了一个旧的字体,调整所有使用旧字体的ui。
Mesh冗余,模型打ab的时候,忘记处理了。
3迭代优化,验证 发现了这些冗余资源,新增一个公共ab,将冗余资源全部打入其中,运行时,首先加载这个公共ab里面所有的资源。
最后优化的结果:
剩下两个Sprite是错误引用了Unity自己的资源,可以调整相关UI去掉对应的应用。
Texture2d中,2个是上面错误的sprite使用对应的纹理;一个是角色模型的材质中引用到的纹理,没有打入到公共ab中;
还有3个比较特殊,这三个纹理对应的sprite已经打入到公共ab中了,但是在UI中,是以rawImage的方式引用到这几个sprite,rawImage引用的sprite,都相当于直接引用纹理,而不是sprite,导致不能使用公共ab,修正方法额外复制一份这些sprite,设置其importe typ为texture,将这些texture2d打入到公共ab中。
旧的字体的材质和字体本身这两个已经删除了,上图的这个结果是我没删除之前的。
Mesh模型,可以将其打入到公共ab中即可。
可以看到要完全优化掉这些冗余资源,还需要对现有的UI等做一些修改,所以ab优化不单单是只优化打包流程就可以的哦。
4:额外收益 通过查看每个ab中包含哪些资源可以发现一些问题
4.1:首先是我们的sprite竟然都没有打包成sprite atlas,
可以看到,ui纹理都是一张张碎图,究其原因,一是 sprite没有设置packingtag,二是 sprite都放在resources目录下面,导致打包ab的时候,resources目录下的sprite是不会生成图集的,unity的坑,因此调整一下打包代码,打包的时候将这些sprite移动到resources外面某个目录中, 打包ab结束,再放回去。
正常的结果,可以看到texture2d的名字都叫做 spriteatlas 了。
4.2:发现一些旧的sprite也被打包进了ab中,利用uwa的功能,非常容易找到所有引用到旧资源的UI,在unity中调整,去掉对旧资源的引用。
5:结合unity Profiler优化内存占用 ab的冗余去除掉了,但是优化还没有结束,编译一个development的程序,设置连接到Profiler。 在Profiler中查看内存的使用情况: 可以看到纹理内存占用大大降低了。
之前50-60MB, 优化后,20-30MB。
接着做一次内存采样,看一些每个纹理的内存大小:
可以看到有些图特别大,是因为没有设置为压缩格式导致的,ios是pvrtc, android是 etc1格式。 设置为压缩格式品质会有一些降低,但是内存和加载速度会优化。
6:总结 总之,使用UWA的ab分析工具,可以非常直观的看到所有的ab以及资源的状态,非常利于我们进行资源的分析和优化~ 感谢UWA团队的工作~~
软件开发
2016-10-25 00:22:00
壹、| 安装必要项

一、安装Xcode及相关工具
1、安装 Xcode 。
2、安装相应版本的 command line tools 。
下载 command line tools 需要一个苹果账号,如果没有的话注册一个就OK。 Tip :桌面状态command + shift + g (即打开前往文件夹窗口),输入 /Library/Developer/CommandLineTools 回车, 如果存在该文件夹说明安装成功。

二、安装Node.js
1、官方下载 点这里 。
2、安装后文件夹 Node 被放在 /usr/local/bin/node ,文件夹 npm 被放在 /usr/local/bin/npm 。正常情况下 $PATH (环境变量的PATH中)中应该有路径 /usr/local/bin 。
也可参照博客 https://my.oschina.net/u/2399426/blog/768285

三、安装pomelo
1、打开终端,切换到root权限 $ sudo -i
回车后需要输入本机用户密码(就是电脑登陆密码)。
2、安装pomelo项目
先转到需要安装的目录,例如: $ cd /Users/apple/Documents/WorkSpace
安装 $ npm install pomelo -g 》 可能出现的错误
1、错误提示: xcrun: error: active developer path ("/Volumes/Xcode/Xcode.app/Contents/Developer") does not exist, use xcode-select to change
2、解决方案: $ sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer
参考源: 在mac pro 上使用 gcc 命令

貳、| pomelo HelloWorld项目

一、创建项目
在当前目录下新建一个HelloWorld项目 $ pomelo init ./HelloWorld

二、安装必要的两个npm依赖库express和crc
每次新建项目都需要安装依赖库。
1、切换到HelloWorld所在目录 $ cd /private/var/root/HelloWorld
2、安装依赖库文件 $ sh npm-install.sh
可以用上面的这个文件 npm-install 安装,也可以自己下载这两个依赖库。上面这种方式下载速度可能有点慢,可以自己设置。自己设置的方法还未尝试,读者可自行摸索。

三、启动game-server服务器
1、进入game-server目录 $ cd game-server
2、启动game-server $ pomelo start 》 可能出现的错误
1、 错误提示 : throw err; ^ Error: Cannot find module 'pomelo' at Function.Module._resolveFilename (module.js:338:15) at Function.Module._load (module.js:280:25) at Module.require (module.js:364:17) at require (module.js:380:17) at Object. (/Users/apple/Documents/WorkSpace/Cocos2d-x/Projects/HelloWorld/game-server/app.js:1:76) at Module._compile (module.js:456:26) at Object.Module._extensions..js (module.js:474:10) at Module.load (module.js:356:32) at Function.Module._load (module.js:312:12) at Function.Module.runMain (module.js:497:10)
2、 解决方法 :
/usr/local/lib/node_modules/ 路径下新建文件夹 pomelo 在相应位置创建 pomelo 文件夹。如果没有添加依赖库也会出现此类错误。

四、启动web-server服务器:
对 终端 进行 新建窗口 后输入 $ sudo -i $ cd /private/var/root/HelloWorld/web-server $ node app
启动后会给出一个ip地址,例如: http://127.0.0.1:3001/index.html 这个地址可以更改具体更改方法详见 官方:使用 WebStorm IDE 调试 Pomelo 应用程序 。
在浏览器中输入上述 ip,点击 Test Game Server 后弹出如下对话框说明 HelloWorld连接成功 。


五、关闭game-server服务器
1、进入 game-server 所在目录。
2、使用 pomelo stop 命令关闭。

叁、| 参考资料

一、 官方:安装pomelo 。
二、 官方:pomelo wiki(中文版) 。
软件开发
2016-10-23 01:00:00
如果调用手动打包 传入资源列表,则需要设置 资源的ab 为none,
一旦设置某些资源的 ab 名字,并且需要为所有资源分配ab名称,则只能自动打包,否则ab依赖会有问题
软件开发
2016-10-12 14:07:00
T4M : terrain for model。是unity地形系统转换成模型的一个插件,之所以不直接使用unity地形系统 是因为它性能消耗大。
步骤:
1.创建一个新的地形。把地形设置小一些,节约点运算量(演示)。
2.随意创建一些地形。
3.打开插件页面,选择地形,同时选择插件页面的第一个tab,点击prcoss
4.转换完成
5.选择shader
6.添加材质球
7.刷地图
8.完成
软件开发
2016-09-26 16:11:00
####steam 上有大量的ogre开发的游戏
http://www.ogre3d.org/tikiwiki/tiki-index.php?page=Ogre3D+Steam+Games+Sales+Charts
这里下载,仅供分析,支持正版
http://store.steampowered.com/app/387990
http://www.yxdown.com/SoftView/SoftView_119488.html/%E4%BA%86
####分析这些游戏的资源结构
sm这款游戏使用了 mygui, fmod studio, particle universe, ogre 开发
#####fmod studio 导出的音乐格式为bank格式
角色建模流行按照男女性别分类建模
现在的角色材质模型都流行使用 diff, spec , normal 3张贴图来做了
textures 中的高光贴图 使用了 Anisotropic spherical Gaussians. 简称 asg
#####Materials 材质目录:
定义了一些fragment ,和vertex 程序
有点像unity的材质文件,定义使用的shader,以及shader的默认参数
base目录是基本阴影投射物体的材质
deferred 是屏幕空间的一些特效, bloom 和 径向的ray
#####shaders目录 :
#####font 字体:
游戏使用的图字
软件开发
2016-08-13 02:05:00
存在一个mmd模型的unity导入插件;
导入了mmd的物理,将模型每个部位通过 custom的joint进行连接,设定物理属性
将模型的面部做 顶点动画,制作各种表情
软件开发
2016-08-13 01:12:00
using UnityEngine; using System.Collections; public class EventsManager { public delegate void OnEnemyDieEvent(int id); public static event OnEnemyDieEvent eventEnemyDie; public delegate void OnEnemyNumberChangeEvent(int number); public static event OnEnemyNumberChangeEvent eventEnemyNumberChange; public delegate void OnPlayerBloodEvent(int blood); public static event OnPlayerBloodEvent eventPlayerBloodChange; public delegate void OnChangeLevelStateEvent(int level); public static event OnChangeLevelStateEvent eventChangeLevelState; public static void EventEnemyDie(int id) { if (eventEnemyDie != null) { eventEnemyDie(id); } } public static void EventEnemyNumberChange(int number) { if (eventEnemyNumberChange != null) { eventEnemyNumberChange(number); } } public static void EventPlayerBloodChange(int blood) { if (eventPlayerBloodChange != null) { eventPlayerBloodChange(blood); } } public static void EventChangeLevelState(int level) { if (eventChangeLevelState != null) { eventChangeLevelState(level); } } } //////////////////////////////////////////////////// //更新敌人数量 EventsManager.EventEnemyNumberChange(listEnemy.Count); /////////////////////////////////////////////////// using UnityEngine; using UnityEngine.UI; using System.Collections; public class UIEnemyNumberText : MonoBehaviour { // Use this for initialization void Start () { EventsManager.eventEnemyNumberChange += RefreshUIText; } // Update is called once per frame void Update () { } void RefreshUIText(int number) { Debug.Log("number of enemy is "+number); gameObject.GetComponent().text = "敌人数量:" + number; } }
软件开发
2016-07-27 12:50:00
今天研究了一个混合与模板测试相结合的案例,这里做以总结。
案例中一个篮球在地板上不断弹起并且落下,这里主要涉及到了两项技术:混合与模板测试。首先篮球在地板上反射出来的镜像篮球就是通过混合技术实现的,为了让镜像体在运动脱离地板时消失,实现真实的效果,还需要加入模板测试。
首先介绍他们的绘制顺序
1.开启模板测试,关闭深度检测
2.绘制实体地板
3.设置模板测试参数
4.绘制镜像球 ,禁用模板测试
5.开启混合
6.设置混合参数,绘制半透明地板
7.关闭混合,开启深度检测
看到这里是不是有些头晕了,不过没关系,我们来一起理一理其中的思路。
首先关于深度检测:深度检测在此案例中不需要使用,我们完全可以根据绘制的顺序来确定他们的显示顺序,当不开启深度检测时,会根据我们绘制的顺序来决定谁会覆盖谁。
其次是模板测试,先清除上一次绘制产生的模板缓冲,使得缓冲中所有值为0,然后给地板设置模板缓冲的参数,其中设置 glStencilFunc(GL_ALWAYS, 1, 1); 这样一来,地板的像素的“模板值”为1,而其它地方像素的“模板值”为0。glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);这个函数第一个参数是未通过模板测试时的操作,第二个参数是通过模板测试,未通过深度测试的操作,第三个是深度测试和模板测试都通过时的操作。我对这个函数的理解是:当其它物体通过模板测试与深度测试时用其它物体的像素,取代该物体的像素。
//设置模板测试参数
gl.stencilFunc(gl.ALWAYS, 1, 1);
//设置模板测试后的操作
gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
然后设置模板参数为equal,当进行比较时,通过比较ref也就是stencilFunc的第二个参数来确定是否通过模板测试,比较的方式是第一个参数也就是equal相等。所以当镜像体篮球在地板范围内时通过模板测试与深度测试,显示镜像。当球体超出地板范围之后模板测试失败,不显示该球体。
//设置模板测试参数
gl.stencilFunc(gl.EQUAL,1, 1);
//设置模板测试后的操作
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
最后是混合当理解了模板测试之后,混合也就更为简单了,我们在绘制地板时,实际是绘制了两次,第一次绘制了不透明度为1的地板,第二次绘制了不透明度为0.5的地板(也就是一个半透明的地板),半透明地板的作用就是参与混合,使得镜像篮球看起来更加真实,不会和实体篮球一样。所以这里使用了混合,目标因子是篮球的颜色,源因子是半透明地板,(用半透明地板去混合篮球,所以篮球是目标因子,地板是源因子)所以该参数的意思是根据(源因子的不透明度:目标因子减原因子的不透明度)这个比例来混合他们的颜色,最终达到镜像效果。
//开启混合
gl.enable(gl.BLEND);
//设置混合因子
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
下面是绘制部分的代码:
function drawFrame()
{
if(!rectdb||!ball)
{
alert("加载未完成!");//提示信息
return;
}
//清除着色缓冲与深度缓冲
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
//清除模板缓存
gl.clear(gl.STENCIL_BUFFER_BIT);
//关闭深度检测
gl.disable(gl.DEPTH_TEST);
//允许模板测试
gl.enable(gl.STENCIL_TEST);
//设置模板测试参数
gl.stencilFunc(gl.ALWAYS, 1, 1);
//设置模板测试后的操作
gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
ms.pushMatrix();
ms.scale(0.3,0.3,0.3);
//绘制反射面地板
rectdb.drawSelf(ms,texMap["db"]);
ms.popMatrix();
//设置模板测试参数
gl.stencilFunc(gl.EQUAL,1, 1);
//设置模板测试后的操作
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
//绘制镜像体
drawmirror();
//禁用模板测试
gl.disable(gl.STENCIL_TEST);
//开启混合
gl.enable(gl.BLEND);
//设置混合因子
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
ms.pushMatrix();
ms.scale(0.3,0.3,0.3);
//绘制半透明反射面地板
rectdb.drawSelf(ms,texMap["tm"]);
ms.popMatrix();
//开启深度检测
gl.enable(gl.DEPTH_TEST);
//关闭混合
gl.disable(gl.BLEND);
//绘制实际物体
drawball();
}
github地址:https://github.com/StringKun/WebGL-mirror-image
软件开发
2019-12-26 18:34:00
[收藏]恐龙快打dino恐龙新世纪Cadillacs and Dinosaurs作弊码大全详细 mame cheat xml file
dino.xml dino.xml
https://gitee.com/zengfr
https://github.com/zengfr
dino.xml




00
01
08
0B龙虾LOBSTER
0C
0D烤肉BARBECUE
10手雷
12机枪
17
18冰激淋PAFFE
1A来复枪
1B
1D
21
29机关枪M16
1E
2F奶油蛋糕CAKE
30牛排STEAK





00
01手枪
03咖啡
06机枪
08机关枪M16
09牛排STEAK
0A寿司SUSHI
0B烤肉BARBECUE
0C蔬菜沙拉SALAD
11来复枪
16汉堡BURGER
18龙虾LOBSTER
19炸弹
1A
1B
21火箭筒
























Works for Rifle, Gun, Uzi, M-16A1, Shotgun and Bazooka





After enemy hits, dropped weapons are never lost





Weapons are never dropped unless Game Over happens.




















































Jack.T
Hannah.D
Mustapha.C
Mess.O




Turn it OFF on the name entry screen



Turn it OFF on the name entry screen














Jack.T
Hannah.D
Mustapha.C
Mess.O




Turn it OFF on the name entry screen



Turn it OFF on the name entry screen














Jack.T
Hannah.D
Mustapha.C
Mess.O




Turn it OFF on the name entry screen



Turn it OFF on the name entry screen




Press F2 and get OBJECT Test on '10' and SCROLL TEST on '11'




相关资料整理:
恐龙快打dino恐龙新世纪Cadillacs and Dinosaurs作弊码大全详细 mame cheat xml file
https://my.oschina.net/zengfr/blog/3141370
恐龙快打dino恐龙新世纪Cadillacs and Dinosaurs作弊码大全详细 FBA cheat ini file金手指
https://my.oschina.net/zengfr/blog/3145167
街机恐龙快打dino恐龙新世纪Cadillacs and Dinosaurs显示暴击值easy68K汇编代码修正版本
https://my.oschina.net/zengfr/blog/3143536
街机恐龙快打dino恐龙新世纪Cadillacs and Dinosaurs 68000 hack rom代码 68K反汇编-敌兵血量
https://my.oschina.net/zengfr/blog/3144490
街机恐龙快打dino恐龙新世纪Cadillacs and Dinosaurs 68000 hack rom代码 68K反汇编-敌兵倍数与敌兵替换
https://my.oschina.net/zengfr/blog/3145153
Mame DEBUG调试命令详细指令速查大全
https://my.oschina.net/zengfr/blog/3144668
恐龙快打dino恐龙新世纪Cadillacs and Dinosaurs内存地址映射
https://my.oschina.net/zengfr/blog/3145223
github:https://github.com/zengfr/romhack
软件开发
2019-12-10 18:25:00
class Serializer(BaseSerializer, metaclass=SerializerMetaclass): def to_internal_value(self, data): """ Dict of native values <- Dict of primitive datatypes. """ if not isinstance(data, Mapping): message = self.error_messages['invalid'].format( datatype=type(data).__name__ ) raise ValidationError({ api_settings.NON_FIELD_ERRORS_KEY: [message] }, code='invalid') ret = OrderedDict() errors = OrderedDict() fields = self._writable_fields for field in fields: validate_method = getattr(self, 'validate_' + field.field_name, None) # 字段对应的验证器函数 primitive_value = field.get_value(data) try: validated_value = field.run_validation(primitive_value) if validate_method is not None: validated_value = validate_method(validated_value) # 调用字段验证函数 except ValidationError as exc: errors[field.field_name] = exc.detail except DjangoValidationError as exc: errors[field.field_name] = get_error_detail(exc) except SkipField: pass else: set_value(ret, field.source_attrs, validated_value) if errors: raise ValidationError(errors) return ret
软件开发
2019-12-11 14:45:00
负载均衡是一个大话题 可以延伸出的概念 可以有 扩容,减容,弹性计算,容器,灰度更新,


TODO
软件开发
2019-11-28 12:59:00
缘起因由
在一个无所事事的周末下午,突然想起魔兽世界,官方的账号很久没有上了,里面的大小号现在连满级都不是。以前曾经搭过传奇和星际争霸战网的私服自娱自乐,也听说过魔兽世界有开源的服务端模拟,既然兴致来了就小小的研究一下。
目前魔兽世界的私服比较流行的是MaNGOS和Trinity,二者都是模拟魔兽世界服务端。MaNGOS“号称”是一个研究型项目,目的是为了学习大规模的C++项目开发,有道理,不过我不信。Trinity是基于MaNGOS的代码开发的,以前主要是为了合并用户提交的补丁代码而设立的一个项目,不过现在已经单独独立出来了,主要开发成员包括以前MaNGOS的一些老人,现在的主要工作包括清理代码、优化、提供一个更好的服务端内核。
二者现在的代码提交和更新都很频繁,但是具体是否会合并对方的代码就不清楚了。总而言之,也就是说,我感觉Trinity大概也许应该是目前最好的一个魔兽世界服务端模拟了。作为一个程序员来说,玩游戏还在其次,看看代码才是件有意思的事情。整个过程记录在案 http://log4think.com/setup_wow_private_server ,以便事后查询。
现在尝试一下从源码开始搭个魔兽世界的服务器,从源码开始主要还是想顺便看看代码的情况,基于Trinity来做。至于客户端的情况,截止到2011年6月12日,中国国服魔兽世界最新的版本是3.3.5 13930-巫妖王之怒。
安装基本工具
安装之前,需要准备几个要使用到的工具软件,用来下载、编译等等。
**版本控制工具 Git ** 由于 MaNGOS 和 Trinity 都是托管在 GitHub 上,所以得用 Git 才能下到源码: Git for Windows Git 的图形化工具 TortoiseGit
编译工具和库: OpenSSL CMake Visual Studio 2010 Express ,这个是Visual Studio的免费版本。如果装了要钱的Visual Studio 2010,就不必装这个了。
运行环境: MySQL ,安装后也包含编译需要的头文件和库。 MySQL的图形化客户端
以下二者按需选择其一 Microsoft Visual C++ 2010 Redistributable Package x86版本 Microsoft Visual C++ 2010 Redistributable Package x64版本
以下非必需 .Net Framework 3.5 , Visual Studio 2010里面好像带,有了就不必装了
生成项目文件
各个工具都下载、安装完毕(具体细节可Google之,不赘述了),准备工作做完之后,开始下代码编译。 建一个目录,譬如 D:\workspace\trinity 进入这个目录,右键 Git Clone... ,Url那里填入 https://github.com/TrinityCore/TrinityCore.git , 点OK。不喜欢图形化工具的可以直接进到新建的目录里面,命令行上运行 git clone https://github.com/TrinityCore/TrinityCore.git 。 会自动建立一个名为 TrinityCore 的源码目录,然后就是等着代码下完。 在 D:\workspace\trinity 下建一个目录是Build等下放编译结果文件 运行CMake的图形化工具(开始菜单里 CMake 下的 CMake (cmake-gui)) 点 Browse Source... , 选 D:\workspace\trinity\TrinityCore Browse Build... ,选 D:\workspace\trinity\Build 点 Configure ,出一个对话框 确保勾选了 Use default native compilers,下拉框里面选 Visual Studio 10,至于是32位还是64位版本的,根据自己的情况选 点Finish,CMake 工具会搜索源码配置,分析出一些编译选项来。确保 cmake-gui 下面的信息提示里面没有 ERROR 字样,否则根据具体错误修正后重来。 在 cmake-gui 上面的编译选项里面选中 SCRIPTS、SERVERS、TOOLS、USE_COREPCH、USE_SCRIPTPCH ,不要选 USE_MYSQL_SOURCES 。 最后点 Generate , CMake 会在 D:\workspace\trinity\Build 下面生成 Visual Stdio 2010 的项目文件。
配置完成后,我的是这样:
可能出现的问题: 1. 第10步里面_GIT_EXEC应该是msysgit中git的绝对路径,如果没有则是msysgit安装的时候没有把自己加到系统路径里面去 2. 如果下面出现红色的ERROR提示,类似于 Could not copy from: D:/dev/cmake/share/cmake-2.8/Templates/CMakeVSMacros2.vsmacros to: C:/Documents and Settings/Administrator/ÎÒµÄÎĵµ/Visual Studio 2010/Projects/VSMacros80/CMakeMacros/CMakeVSMacros2.vsmacros
这个好像是因为 CMake 无法识别中文路径,把“我的文档”的位置改一下吧,改成路径不带中文的。或者直接自己把提示中的 CMak>eVSMacros2.vsmacros 拷到“我的文档”下的 Visual Studio 2010/Projects/VSMacros80/CMakeMacros/ 。
编译源码
注意以下几点: 如果你的魔兽世界客户端是3.3.5 13930,那么记得给代码打个认证补丁以支持13930,具体请参考另一篇文章 wow_3-3-5_13930_auth_patch 在 Visual Studio 里面打开 D:\workspace\trinity\Build\TrinityCore.sln ,先Build -> Clean Solution,然后Build -> Build Solution 默认是生成 Debug ,结果在 D:\Build\bin\Debug下面,从 MySQL 的安装目录下的Lib目录里面拷一个 libmySQL.dll 到这个目录下, libeay32.dll 和 ssleay32.dll 是 OpenSSL 的,应该默认加到系统路径下了,如果后面提示找不到就从 OpenSSL 的安装目录里面拷过来。

安装数据库
简短截说: 从 https://github.com/TrinityCore/TrinityCore/downloads 下载最新的(或者符合你的客户端版本号的)魔兽世界数据文件, 用MySQL图形化工具,导入 D:\workspace\trinity\TrinityCore\sql\create\create_mysql.sql 执行建立三个数据库 auth 数据库中导入 D:\workspace\trinity\TrinityCore\sql\base\auth_database.sql 执行, characte 数据库中导入 D:\workspace\trinity\TrinityCore\sql\base\character_database.sql 执行, world 数据库中导入第一步中下载的 TDB_full_???.sql 文件执行, 最终建立 auth、character、world 三个数据库。
生成地图文件 在D:\Build\bin\Debug下面建一个makevmaps3_simple.bat文件,内容为
vmap3extractor .exe
md vmaps
vmap3assembler .exe Buildings vmaps

pause
保存运行,会从魔兽世界的客户端目录里面解压缩服务端需要的地图出来。根据机器速度不同,大概需要20分钟到一个小时的时间。运行结束的时候会给个提示"Press any key...",按任意键结束。生成 vmaps 和 buildings 目录, buildings 目录无用可以删除。 假设魔兽世界的客户端目录在 D:\WOW 下面,在 D:\Build\bin\Debug 下运行 mapextractor.exe -i "d:\WOW"
这样会生成 maps 和 dbc 目录。这个工具会根据客户端的雨中在 dbc 目录下生成不同的 dbc 语言版本。如果是中文的客户端,会提取出中文的 dbc 数据。
配置服务器 在 D:\Build\bin\Debug 下有 worldserver.conf.dist 和 authserver.conf.dist 两个文件,分别是游戏服务器和认证服务器的配置模板文件。 复制一份 worldserver.conf.dist 并改名为 worldserver.conf ,配置游戏服务器
LoginDatabaseInfo = "127.0.0.1;3306;root;trinity;auth"
WorldDatabaseInfo = "127.0.0.1;3306;root;trinity;world"
CharacterDatabaseInfo = "127.0.0.1;3306;root;trinity;characters"
分别是认证数据库、世界数据库、玩家角色数据库的地址, root 后面的 trinity 是 MySQL 中 root 的密码,改成你在安装 MySQL 时设置 root 密码。
之前生成了 vmap 文件,这个是地图的相关数据,服务器可以根据这个来判断怪物和玩家之间是否可见(是否有墙,是否在建筑物的同一层上)。没有这个的话,怪物会穿墙打你,或者从楼下直接漂上来打你... 服务器配置这里默认情况下是开启 vmap 数据检测的。如果不想开启 vmap 检测,则将下面这些配置的值改成0
vmap.enableLOS = 1
vmap.enableHeight = 1
vmap.petLOS = 1
vmap.enableIndoorCheck = 1
DetectPosCollision = 1 复制一份 authserver.conf.dist 并改名为 authserver.conf ,配置认证服务器 LoginDatabaseInfo = "127.0.0.1;3306;root;trinity;auth"
同样,把 trinity 改成 MySQL 的 root 密码。 检查数据库 auth 里面 realmlist 表里面的记录,记录中 port 的值应该和 worldserver.conf 里面的 WorldServerPort = 8085 这一项的值一样(这里是 8085 )。同时, gamebuild 的值应该和你客户端的版本号是一致的(登陆界面右下角,当前最新的是 13930)。如果realmlist里面没有记录,则应该加一条。
修改客户端配置
客户端默认是去登陆官方服务器,需要修改一下地址改成让客户端登陆我们自己架设的游戏服务器。假设魔兽世界客户端安装在 D:\WOW 下面,到 D:\WOW\Data\zhCN 下面,备份 realmlist.wtf 文件。该文件原来的内容是
set realmList cn .logon .warcraftchina .com
set patchlist cn .version .warcraftchina .com
set realmlistbn ""
set portal cn
将其内容改成
SET realmlist "127 .0 .0 .1 "
SET patchlist "127 .0 .0 .1 "
注册用户账号
客户端后登录会发现没有账号可用,注册账号的办法有两种: 第一种方法是通过SQL语句直接在数据库里面加
INSERT INTO account
(username, sha_pass_hash, email)
VALUES
( '用户名' , SHA1 ( CONCAT ( UPPER ( '用户名' ), ':' , UPPER ( '密码' ))), '邮件地址' )
替换命令中的 用户名、密码、邮件地址 即可。 第二种方法最简单,后面启动游戏服务器worldserver之后,在这个命令窗口可以输入GM命令: create account 用户名 密码
启动游戏 运行D:\Build\bin\Debug\authserver.exe 运行D:\Build\bin\Debug\worldserver.exe 运行客户端wow.exe
如果运气好的话... 反正我运气不错...
相关的可能还需要一些配置和改动,不过目前我的这个版本能够正常运行,其它的未来再写吧。在此之前,至少可以先研究下源代码...
如何和朋友一起玩
如果想配个私服和朋友一起玩,那么需要一个公网 IP 地址。机房里面没有服务器没有关系,可以搭在自己家里的服务器上,然后去 ip138 上查到自己的IP地址,把 127.0.0.1 相关的地址都改成自己的IP地址就可以了。如果是通过ADSL路由上的网,去ADSL路由配置里面把自己的内外IP地址设置为DMZ主机地址开放给外网即可。
此外,如果是跑服务端的服务器有花生壳的动态域名或者自己的域名(比如我的 http://log4think.com),可以将 127.0.0.1 改成自己的域名。前面在 wowserver 和 authserver 中的地址中配置的 127.0.0.1 都要改,因为服务端要提供这个地址给客户端。 MySQL 相关的 127.0.0.1 地址不用改。非要改也可以,不过就是还得去配MySQL的外网访问的相关安全设置。
同时,如果是要搭在公网上对外提供服务,建议单独找个机器做 auth 服务器(配置不用太好),不同的游戏区跑在不同的服务器上(这个配置要好一点),每个服务器上都要跑worldserver。把服务器列表加到 auth 数据库里面的 realmlist 表里就是。 友情提示:提供公网服务小心被告。
相关的几个小问题: 连接服务器断开的话,到 这里 下个补丁覆盖安装。 登入后没有服务器列表的话,看看客户端的版本号(登陆界面的右下角),然后把auth数据库realmlist表里面的记录最后一项gamebuild改为看到的版本号(比如13930) 。
开发调试
既然是从源码编译的,因此如果中间出了任何问题都是可以通过调试的方式去解决的。调试方法很简单,几个简单的手段 1. 抓包,分析数据记录 2. 启动编译好的程序,开 Visual Studio ,附加到该进程上,下断点跟踪 3. 进入游戏,进行操作,Visual Studio 中如果下了正确的断点,就会在相关的位置停下
退而求其次
如果你觉得以上太复杂搞不定,如果只是想自己随便玩一下而已,那么可以下个别人做好的包,比如 TCCN-3.3.5-Trinity8400.exe ,安装好启动 web 服务器和 wow 服务器,注册个账号就可以进去玩了,简单的无需解释了。不过这个有人数限制,具体限制多少人我也不清楚,总之做公网服务是没戏的。
参考资料
二者的一些相关网站列举如下: MaNGOS 官网: http://getmangos.com MaNGOS 代码库: http://github.com/mangos 更多MaNGOS的链接: http://getmangos.com/wiki Trinity 官网: http://www.trinitycore.info Trinity 项目首页: http://trinitycore.github.com Trinity 代码库: https://github.com/TrinityCore/TrinityCore
本站后续相关文章可以在 http://log4think.com/category/practice/fun/wow-trinity/ 找到
本文全文参考Trinity官方文档 http://www.trinitycore.info/How-to:Win
软件开发
2019-11-12 14:39:00
首先说下目的,是为了实现类似手柄的输入功能。假设一个手柄有A/B两个键,可以输入不同的指令并且可以替换(比如吃血瓶/跳跃/射击等操作)。除此之外还有一个额外的需求,就是可以记录玩家最近输入的X次操作,并且可以撤销。
我们先写一个Actor类,作为被控制的主角,这里只写了HP一个字段,因为我们下面暂时将A/B键的功能设为加血和扣血。 public class Actor { public int HP; public Actor(int hp) { HP = hp; } }
然后写好Command基类,需要设置一个被控制的Actor(游戏中可以替换主角/AI等),并且包含一个执行和撤销的方法。 public class Command { protected Actor actor; public Command(Actor a) { actor = a; } ~Command() { } public virtual void Excute() { } public virtual void Undo() { } }
下面写了一个CommandA,执行的操作是对某一个Actor加10点血量,除此之外还写了一个CommandB,执行扣血,因为与CommandA差不多就不展示了。 public class CommandA : Command { ~CommandA() { } public CommandA(Actor a) : base(a) { } public override void Excute() { actor.HP += 10; Debug.Log("actor 加了10hp, 现在hp: " + actor.HP); } public override void Undo() { actor.HP -= 10; Debug.Log("actor 撤销了加血, 现在hp: " + actor.HP); } }
下面是要实现一个命令的栈,可以记录最近的X次命令(我假设的是五次),并可以逐一撤销。 public class CommandNode //命令栈中的节点,记录的当前的命令以及它的上一个和下一个命令 { ~CommandNode() { Debug.Log("我被释放了"); } public Command m_command; public Command Command{ get { return m_command; } } public CommandNode(Command command) { m_command = command; } public CommandNode preCommand; public CommandNode nextCommand; } public class CommandStack { private int m_iCapacity = 0; //stack容量 private int m_iCount = 0; //stack内命令数量 private CommandNode firstNode; //存储的第一次的命令 private CommandNode lastNode; //最后一次命令 public CommandStack(int capacity) { m_iCapacity = capacity; } public bool IsEmpty() { return m_iCount == 0; } public void Push(Command command) { if(IsEmpty()) //如果stack为空,将第一个命令和最后一个命令都设成当前这个 { CommandNode node = new CommandNode(command); node.preCommand = null; node.nextCommand = null; firstNode = node; lastNode = node; } else //stack不为空,将当前的命令设为最后的命令 { CommandNode node = new CommandNode(command); node.preCommand = lastNode; node.nextCommand = null; lastNode.nextCommand = node; lastNode = node; } m_iCount++; if (m_iCount > m_iCapacity) //命令数超过容量了,去除第一个 { firstNode = firstNode.nextCommand; firstNode.preCommand = null; m_iCount--; } } public Command Pop() { Command result = lastNode.Command; //返回最后一次命令 if (lastNode.preCommand != null) //如果最后一次命令之前还有命令,将其设为最后的命令 { lastNode = lastNode.preCommand; lastNode.nextCommand = null; } else //如果没有了,说明stack空了,将第一次和最后一次命令置空 { firstNode = null; lastNode = null; } m_iCount--; return result; } }
写完上面的下面就是我的主要控制类了。 public class CommandPatternMain : MonoBehaviour { private Actor actor = new Actor(100); CommandStack commands = new CommandStack(5); Command command; Command tempCommand = null; private Command m_CommandA; private Command m_CommandB; private void Start() { SetCommands(); } private void SetCommands() { m_CommandA = new CommandA(actor); m_CommandB = new CommandB(actor); } private void Update() { command = InputHandler(); if(command != null) { command.Excute(); commands.Push(command); } if(Input.GetKeyDown(KeyCode.Space) && !commands.IsEmpty()) { tempCommand = commands.Pop(); if(tempCommand != null) tempCommand.Undo(); } } private Command InputHandler() { if (Input.GetKeyDown(KeyCode.A)) { return m_CommandA; } if (Input.GetKeyDown(KeyCode.B)) { return m_CommandB; } return null; } }
这个类里有两个固定的命令A和B,当玩家按下A和B对应的键位时执行这两个命令。至于具体是什么命令,我们在SetCommands方法里赋值了,当然游戏进行中可以随时替换。每当玩家执行一次命令之后会推入一个容量为5的命令栈,当玩家按下撤销的按键时会Pop出最后一次命令,并执行Undo的方法。下面看看测试结果,我们先加五次血,再扣三次血,再撤销五次,然后再加五次血,再扣三次血,再撤销五次,结果如下:
软件开发
2019-11-12 14:50:00
Android端: package com.myc.sdkbase; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import com.unity3d.player.UnityPlayer; import com.unity3d.player.UnityPlayerActivity; public class MainActivity extends UnityPlayerActivity { private static MainActivity instance; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); instance = this; } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } public int Sum(int x, int y) { return x + y; } public int Max(int x, int y) { return Math.max(x, y); } public static MainActivity GetInstance() { return instance; } public void CallUnityFun(String str) { String receiveObj = "Main Camera"; String receiveMethod = "Receive"; UnityPlayer.UnitySendMessage(receiveObj, receiveMethod, str + " Android Call Unity."); } }
Unity端: using UnityEngine; using UnityEngine.UI; namespace SDKBase { public class MessageHandler : MonoBehaviour { private AndroidJavaClass m_jc; private AndroidJavaObject m_jo; [SerializeField] private Text m_text; [SerializeField] private Text m_textReceive; // Start is called before the first frame update void Start() { m_jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); m_jo = m_jc.GetStatic("currentActivity"); } public void OnClickSum() { AndroidJavaClass jc = new AndroidJavaClass("com.myc.sdkbase.MainActivity"); AndroidJavaObject jo = jc.CallStatic("GetInstance"); m_text.text = jo.Call("Sum", 5, 3).ToString(); } public void OnClickMax() { AndroidJavaClass jc = new AndroidJavaClass("com.myc.sdkbase.MainActivity"); AndroidJavaObject jo = jc.CallStatic("GetInstance"); m_text.text = jo.Call("Max", 5, 3).ToString(); } public void OnClickCall1() { m_jo.Call("CallUnityFun", "Test1"); } public void OnClickCall2() { AndroidJavaClass jc = new AndroidJavaClass("com.myc.sdkbase.MainActivity"); AndroidJavaObject jo = jc.CallStatic("GetInstance"); jo.Call("CallUnityFun", "Test2"); } public void Receive(string str) { m_textReceive.text = str; } } }
参考链接: https://www.cnblogs.com/msxh/p/7220741.html
后来在看宣雨松博客时看到Unity调用Android部分可以写成: using (AndroidJavaClass jc = new AndroidJavaClass("com.myc.sdkbase.MainActivity")) { using (AndroidJavaObject jo = jc.CallStatic("GetInstance")) { m_text.text = jo.Call("Sum", 5, 3).ToString(); } }
了解到using后面接实现了 IDisposable 的对象可以在码块结束后自动Dispose。
软件开发
2019-10-29 17:06:00
Kubernetes 项目作为容器编排领域的事实标准, 成功推动了诸如阿里云 Kubernetes (ACK)等云原生服务的迅速增长。但同时我们也关注到,Kubernetes 的核心 API 资源比如 Service、Deployment 等,实际上只是应用中的不同组成部分,并不能代表一个应用的全部。也许我们可以通过像 Helm charts 这样的方式来尝试表达一个可部署的应用,可一旦部署起来,实际运行的应用中却依旧缺乏以应用为中心的约束模型。这些问题都反映出,Kubernetes 以及云原生技术栈需要一种以应用为中心的 API 资源来提供一个专注于应用管理的、标准的、高度一致的模型,这个 API 资源可以代表完整运行的应用本身,而不仅仅是应用模板或者一个应用的几个组成部分,这就是今天阿里云与微软联合宣布推出 开放应用模型 Open Application Model (OAM) 的原因。
项目地址: https://openappmodel.io

OAM 项目目前由规范和实现两部分组成
什么是 Open Application Model?
OAM 是一个专注于描述应用的标准规范。有了这个规范,应用描述就可以彻底与基础设施部署和管理应用的细节分开。这种 关注点分离(Seperation of Conerns)的设计好处是非常明显的 。 举个例子,在实际生产环境中,无论是 Ingress ,CNI,还是 Service Mesh,这些表面看起来一致的运维概念,在不同的 Kubernetes 集群中可谓千差万别。 通过将应用定义与集群的运维能力分离,我们就可以让应用开发者更专注于应用本身的价值点,而不是”应用部署在哪“这样的运维细节。 此外,关注点的分离让平台架构师可以轻松地把平台的运维能力封装成可被复用的组件,从而让应用开发者能够专注于将这些运维组件与代码进行集成,从而快速、轻松地构建可信赖的应用。 Open Application Model 的目标是让简单的应用管理变得更加轻松,让复杂的应用交付变得更加可控。
一、应用组件(Components)
在 OAM 中,“应用”是由多个概念共同组合而成的。 第一个概念是:应用组件(Components),它是整个应用的重要组成部分。 所以说,应用组件既可以包括应用运行所依赖的服务:比如 MySQL 数据库,也包括应用服务本身:比如拥有多个副本的 PHP 服务器。 开发者可以把他们写的代码“打包”成一个应用组件,然后编写配置文件来描述该组件与其他服务之间的关系。 应用组件的概念,让平台架构师能够将应用分解成一个个可被复用的模块,这种模块化封装应用组成部分的思想,代表了一种构建安全、高可扩展性应用的最佳实践:它通过一个完全分布式的架构模型,实现了应用组件描述和实现的解耦。
二、应用部署配置文件(Application Configuration)
而为了将这些应用组件描述变成一个真正运行起来的应用,应用运维人员会通过一个专门的、包含了所有应用组件信息的部署配置文件来实例化这个待运行的应用。 这个配置文件本身也是 OAM 规范中的一个声明式 API,用来让应用运维人员能够根据开发者或者平台提交的应用描述,实例化出对应的、真正运行起来的应用。
三、应用运维特征(Traits)
最后一个概念是一组应用运维特征(Traits) ,它们描述了应用在具体部署环境中的运维特征,比如应用的水平扩展的策略和 Ingress 规则,这些特征对于应用的运维来说非常重要,但它们在不同的部署环境里却往往有着截然不同的实现方式。 举一个简单例子,同样是 Ingress,它在公有云上和本地数据中心的实现可能是完全不同的:前者一般是 SLB 这样的云服务,而后者则可能是一个专门的硬件。这也就意味着针对这两个环境的 Ingress 运维工作,将会有天壤之别。 但与此同时,无论是在哪个环境里,这个 Ingress 规则对于应用开发人员来说,可能是完全相同的。 应用特征的设计,让这种关注点分离成为可能:只要这两个环境在 OAM 模型下提供了对 Ingress 这个应用运维特征的实现,那么你的应用就可以使用统一的 Ingress 规则描述无差别的在这两个地方运行起来。而与此同时,这两个环境的基础设施供应商可以继续通过配置这些应用特征的实现,来满足它们各自的运维要求(例如:不同环境里 Ingress 实现在满足合规性和安全性上的差异)
OAM:平台无关、高可扩展的应用描述能力
与 PaaS 应用模型相比,OAM 有很多独有的特点,其中最重要一点是:平台无关性。虽然我们目前发布的 OAM 实现(rudr)是基于 Kubernetes 的,但 Open Application Model 与 Kubernetes 并没有强耦合。实际上 ,OAM 可以实现到任意平台或运行环境之上,这当然也包括边缘计算与物联网的场景。我们也认同Kubernetes 在很多运行环境中可能并不是最好的选择,或者是像 Serverless 这类用户并不需要关心基础设施复杂性的运行环境。在这些场景下,OAM 都可以提供完全一致的应用管理体验。
第二个重要的特点是,OAM 的 specification (OAM 规范) 在设计上天然是可扩展的。OAM 不像 PaaS 那样自成封闭体系,也不会通过某种独有的应用管理环境来屏蔽掉底层平台的特点(比如:在 Kubernetes 之上”盖一个大帽子“)。 相反,OAM 使平台层可以通过应用特征系统 (Trait system)来体现平台的特性和差异性。也就是说,只要不同的平台都能够提供应用所需要的某些应用特征 (Trait),开发人员就能轻松地研发跨平台的应用。类似地,哪怕最底层的硬件提供商,也可以通过应用特征系统来体现其平台特性。 OAM 的整体设计,就是为了避免在平台可移植性中经常发生的“最小公分母”锁定问题。相反,OAM 不但提供了可移植性的能力,它还确保了每个平台有能力去透出独有的特性和用途。 OAM 让开发人员可以自由地针对不同平台以标准方式在可移植性和差异化功能之间取得平衡。
开放的社区与未来
如今,开放应用模型以及相应的 Kubernetes 实现有了初步的成果,我们感到非常兴奋。 OAM 规范是基于 Open Web Foundation 协议进行开发的。我们的目标,从一开始就是让开放应用模型 Open Application Model 成为中立基金会的项目,以便实现开放治理与广泛合作。如果您想了解更多信息,请前往开放应用模型项目的GitHub 仓库: OAM specification ,以及 基于 Kubernetes 的 OAM 标准实现 Rudr 。
今天 OAM 项目的发布只是迈出的一小步。我们非常期待得到您的反馈,并与大家密切协作,针对 Kubernetes 和任意云环境打造一个简单、可移植、可复用的应用模型。

本文作者:一绿舟
原文链接
本文为云栖社区原创内容,未经允许不得转载。
软件开发
2019-10-21 15:53:00
带GUI和纯命令行模式 除了在安装系统的时候可以选择,还可以安装好之后动态切换。
powershell 命令:
GUI to Server-Core Uninstall-WindowsFeature Server-Gui-Mgmt-Infra –restart
Server-Core to GUI Install-WindowsFeature Server-Gui-Mgmt-Infra,Server-Gui-Shell –Restart

参考链接:
, http://blchen.com/windows-server-2012-gui-and-core-mode-switch-freely/
软件开发
2019-10-09 22:34:00
概述
xLua加密工具主要是一款Lua安全编译器,配置好xLua环境后,用几维提供的libluajit.a替换对应的默认文件,来实现对Lua文件的加密,加密后的Lua文件只能由几维提供的lua bytecode解释器解析,从而避免Lua文件被逆向分析,达到保护Lua核心代码的目的。
集成
1、获取xLua
从官方下载对应的xLua版本, 默认是master分支
1)这个步骤是属于开发环节,请自行下载并部署好【xLua是开源项目,请自行检测环境变量、Lua和Luajit的版本,以便顺利编译通过】
2)Luajit-2.1.0-beta2以上版本支持64位,几维编译器版本的luajit加密方案基于Luajit-2.1.0-beta2
2、Android快速集成加密版静态库
1)执行build目录下的make_android_luajit.sh(根据自己的平台选择编译脚本),确保原始的编译成功
2)编译成功之后查看luajit源码目录下src/libluajit.a文件
3)用几维提供的libluajit.a进行替换,x86对应x86,armeabi-v7a对应armeabi-v7a
4)修改make_android_luajit.sh文件,编译armeabi-v7a版本的libxlua.so,如下图
5)同理修改编译出x86版本的libxlua.so
6)复制编译好的libxlua.so替换项目中原来的libxlua.so(替换之前最好先做好备份),推荐先使用源码测试,然后换成几维提供的luajit编译器编译出二进制bytecode文件进行测试
3、iOS快速集成加密版静态库
1)执行make_ios_luajit.sh将原始版本的libxlua.a编译出来
注意:xlua默认是armv7、armv7s、arm64三种架构,如果使用xcode版本过高,比如9.3.x以后的不再支持armv7 armv7s,那么生成的只会有arm64这种架构
2)同android一样注释掉生成libluajit.a的过程如图
注意:这里区别在于如果需要arm64架构那么需要使用lipo -create arm64/libluajit.a -output libluajit.a,需要armv7和arm64使用lipo -create arm64/libluajit.a armv7/libluajit.a -output libluajit.a
3)再次执行make_ios_luajit.sh生成libxlua.a
4)替换项目中原来的libxlua.a编译对应平台的bytecode文件,32位对应32位,64位对应64位的,生成项目,使用xcode编译测试(几维luajit安全解释器支持armv7,armv7s,arm64所以支持真机)
友情告知地址,lua脚本加密: https://www.kiwisec.com/product/compiler-lua.html
软件开发
2019-04-17 15:56:00
UiStatus 一个简单且强大的Android Ui视图状态控制库. 我一直觉得程序员就像诗人一样,敲代码就像写诗,好的代码何尝不是一首优美的诗!
所以我把项目中的视图控制部分抽取出来,单独封装成了UiStatus这个库。希望可以帮助大家简化这部分操作,也希望对大家有用。
部分效果演示
优点:
1.轻量:简单且够用!!!
2.省内存:使用ViewStub,所有未使用到的状态均不会初始化,减少视图初始化开销。
3.解耦、封装:降低各种视图状态和业务层耦合,使用者只需要关心业务层逻辑而无需知道视图状态管理内部逻辑。
4.自由:不提供任何状态视图,高度可配置,完全由开发者自己定义。
5.强大:可以使用在任何Activity、Fragment、View当中。
目前该库包含的状态有9种:
常量名称 含义 使用场景 LOADING 加载中 页面加载中状态
NETWORK_ERROR 网络错误 当网络连接错误时展示的界面
LOAD_ERROR 加载失败 接口请求失败时展示给用户的界面
EMPTY 空布局 当获取的数据为空时展示的界面
NOT_FOUND 未找到内容布局 有时获取的内容已被服务器删除,此时展示给用户一个404内容被删除的界面
CONTENT 内容 真正需要展示给用户的内容视图,也是开发者在layout里实际布局的视图
WIDGET_NETWORK_ERROR 网络错误小部件 一个类似于QQ、微信断网时顶部显示的网络错误提示视图
WIDGET_ELFIN
WIDGET_FLOAT
小精灵(提示布局)
底部Float
一种顶部出现的提示布局,可以做类似于简书刷新时提醒推荐内容条数的小部件
一种底部弹出的浮动布局,可以实现底部弹出小组件的功能
在这里我们将前6种状态称之为普通状态视图,后面3种统称为Widget小部件。下文不再赘述!
其中,普通状态视图不会同时显示,Widget小部件状态的显示和前6种状态不冲突(可以同时显示),
视图层次上Widget在普通状态视图之上,WIDGET_ELFIN在WIDGET_NETWORK_ERROR之下。
使用步骤
1.添加依赖
step one: allprojects { repositories { maven { url 'https://www.jitpack.io' } } }
step two : dependencies { implementation 'com.github.FengChenSunshine:UiStatus:{version}' }
2.全局配置 UiStatus库不提供任何状态的视图,完全由开发者自己自定义提供。
①.获取全局视图控制管理者. UiStatusManager.getInstance()
②.配置状态视图. /** * @params uiStatus 视图状态. * @params layoutResId 开发者自定义的该状态视图. */ addUiStatusConfig(@UiStatus int uiStatus, @LayoutRes int layoutResId); /** * @params uiStatus 视图状态. * @params layoutResId 开发者自定义的该状态视图. * @params retryTriggerViewId 该状态视图上点击事件的触发控件id. * @params retryListener 点击事件触发后的回调. */ addUiStatusConfig(@UiStatus int uiStatus, @LayoutRes int layoutResId, @IdRes int retryTriggerViewId, OnRetryListener retryListener); /** * 该方法只针对Widget相关状态有效. * @params uiStatus 视图状态. * @params topMarginPx widget相对于内容视图的顶部距离,比如可能需要预留出顶部Toolbar高度的距离. * @params bottomMarginPx widget相对于内容视图的底部距离,比如可能需要预留出底部导航栏高度的距离. */ setWidgetMargin(@UiStatus @IntRange(from = 7L,to = 9L) int uiStatus, int topMarginPx, int bottomMarginPx);
③.配置网络状态提供者. 考虑到开发者项目中都有自己的网络状态监听、判断的工具类, 所以UiStatus没有必要额外编辑这段功能代码,仅需要开发者配置一个回调即可。 配置网络状态提供者后,在请求显示普通的状态时如果是没有网络那么将自动重定向到NETWORK_ERROR网络错误状态界面。 具体配置方法如下: UiStatusNetworkStatusProvider .getInstance() .registerOnRequestNetworkStatusEvent(OnRequestNetworkStatusEvent networkStatusEvent);
3.具体使用
UiStatus可以使用在任何Activity、View以及Fragment中.
①.Activity中: UiStatusController.get().bind(activity);
②.View中: UiStatusController.get().bind(view);
③.Fragment中: 由于Fragment的特殊性,所以这里稍微麻烦一点点: public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view; //你的视图初始化... mUiStatusController = UiStatusController.get(); return mUiStatusController.bindFragment(view); } 看以看出这里是使用bindFragment(view)方法将原本的内容视图传递给UiStatus,并将该方法返回的控件作为onCreateView的返回值即可。
④.视图状态切换: 对于普通视图切换使用UiStatusController.changeUiStatusIgnore(uiStatus)切换; 对于Widget视图使用UiStatusController.showWidget(uiStatus)进行显示; 或者使用其重载方法showWidget(uiStatus,duration)方法进行显示,使用该方法时会在duration时间后自动隐藏; 也可以使用UiStatusController.hideWidget()方法进行隐藏Widget小部件。
4.成功
经过上面的步骤之后您已经成功的集成并且可以使用UiStatus库了,并且您肯定也看到了想要看到的各种状态下的视图成功切换。
如果您不需要某些页面的个性化定制,那么到这里就OK了;否则请看下面的步骤会满足你的需求。
5.个性化配置
一般情况下使用在Application中的全局配置已经能满足大部分页面视图状态的需求,
如果某些个别界面需要特殊配置那么UiStatus也是支持的。
只需要使用持有的UiStatusController对象,调用其addUiStatusConfig()或其他任何可以在全局配置中使用的方法重新配置即可。
6.优化
一般的开发者只需要在自定义的Application中全局配置一次即可,并且对于LOADING、LOAD_ERROR、EMPTY、NOT_FOUND、CONTENT这些普通视图的切换可以下沉到 统一在基类Activity、Fragment或者网络加载框架中处理,具体使用大同小异,可参考Demo,这里不再赘述。
7.UiStatus可实现效果部分展示
注:图片来自其他APP,仅供参考实现效果使用。其他未列出效果不代表不能实现,具体可实现效果尽情发挥想象!!!


最后给大家分享一份非常系统和全面的Android进阶技术大纲及进阶资料,及面试题集
想学习更多Android知识,请加入Android技术开发企鹅交流 7520 16839
进群与大牛们一起讨论,还可获取Android高级架构资料、源码、笔记、视频
包括 高级UI、Gradle、RxJava、小程序、Hybrid、移动架构、React Native、性能优化等全面的Android高级实践技术讲解性能优化架构思维导图,和BATJ面试题及答案!
群里免费分享给有需要的朋友,希望能够帮助一些在这个行业发展迷茫的,或者想系统深入提升以及困于瓶颈的朋友,在网上博客论坛等地方少花些时间找资料,把有限的时间,真正花在学习上,所以我在这免费分享一些架构资料及给大家。希望在这些资料中都有你需要的内容。
软件开发
2019-03-06 15:51:00
相比文字和图片,直播提供了人与人之间更丰富的沟通形式,其对平台稳定性的考验很大,那么倡导“ 以技术驱动娱乐 ”的虎牙直播(以下简称“虎牙”)是如何在技术上赋能娱乐,本文将为您介绍虎牙在DNS、服务注册、CMDB和服务配置中心等方面的实践。
文章整理自虎牙基础保障部中间件团队负责人张波(社区ID:zhangjimmy)在Dubbo Meetup 广州站沙龙上的分享,阿里巴巴中间件授权发布,分享议题如下:
为什么选用Nacos
DNS-F的技术价值和应用场景
服务注册的实践
CMDB的应用和实践
服务配置的实践
为什么选用 Nacos
虎牙关注 Nacos 是从v0.2 开始的( 最新版本:Pre-GA v0.8 ),我们也参与了社区的建设,可以说是比较早期的企业用户。
Nacos 是一个更易于帮助构建云原生应用的动态服务发现、配置和服务管理平台,提供「注册中心」、「配置中心」和「动态DNS服务」三大功能。
首先,在虎牙的微服务场景中,起初有多个注册中心,每一个注册中心服务于某一部分微服务,缺少一个能融合多个注册中心,并把他们逐一打通,然后实现一个能管理整个微服务体系的大的注册中心。
以下内容摘自我们考虑引入Nacos时,在服务注册中心方案上的选型对比:
Nacos提供 DNS-F功能, 可以与K8S、Spring Cloud和Dubbo等多个开源产品进行集成,实现服务的注册功能。
其次,在服务配置中心方案的选型过程中,我们希望配置中心和注册中心能够打通,这样可以省去我们在微服务治理方面的一些投入。因此,我们也同步比较了一些服务配置中心的开源方案:
例如Spring Cloud Config Server、Zookeeper和ETCD,总体评估下来,基于我们微服务体系现状以及业务场景,我们决定使用Nacos作为我们的服务注册和服务发现的方案。使用过程中,我们发现,随着社区版本的不断更新和虎牙的深入实践,Nacos的优势远比我们调研过程中发现的更多,接下来,我将围绕DNS-F、Nacos-Sync、 CMDB和负载均衡4方面来分享虎牙的实践。
DNS - F 的技术价值
Nacos 提供的DNS-F功能的第一个技术价值在于,弥补了我们内部微服务没有一个全局动态调度能力的空白。 刚才提到,虎牙有多个微服务体系,但并没有一个微服务具备全局动态调度的能力,因为它们各自都是独立的。目前,我们通过Nacos已经融合了四个微服务体系的注册中心,最终目标是把所有的微服务都融合在一起,实现全局动态调动的能力。
第二,DNS-F解决了服务端端到端面临的挑战,即延时大、解析不准、故障牵引慢的问题。
如何去理解呢?
当内部有多个微服务体系的时候,每一个体系的成熟度是不同的。例如,有一些微服务框架对同机房或CMDB路由是不支持的,当一个服务注册到了多个IDC中心,去调用它的服务的时候,即便是同机房,也可能调用到一个不是同机房的节点。这样就会无端的造成服务的延时和解析不准。
即使我们基于DNS做一些解析的优化,但仍然无法完全解决服务的延时和解析不准。这是因为DNS都是IP策略的就近解析,无法根据服务的物理状态、物理信息进行路由。此外,当一个核心服务出现问题,如果缺少一个融合了多个调用方和被调用方的信息的统一的注册中心,就很难去准确判断如何去牵引,从而导致故障牵引慢。有了Nacos后,就可以接入一个统一的注册中心以及配置中心,去解决这些问题。(目前,虎牙还在微服务体系的改造过程中,未完全实现统一的注册中心)
第三,提供专线流量牵引能力。 虎牙的核心机房的流量互通,是使用专线来实现的。专线的特性就是物理建设的,而且我们的专线建设可能不像BAT那么大,例如我们专线容量的冗余只有50%,假设某个直播异常火爆,突发流量高于平常的两百倍,超过了专线的建设能力,这时候一个服务就有可能会导致全网故障。但是,通过全局的注册中心和调动能力,我们就可以把流量牵引到其他地方,例如迁移到公网,甚至牵引到一个不存在的地址,来平衡一下。即便某个服务出现问题,也不会影响我们的全局服务。
第四,支持服务端的多种调度需求 ,包括同机房路由、同机器路由,以及同机架路由,Nacos都可以去做适配。此外,基于Nacos 的DNS-F功能,我们还实现了加速外部域名解析和服务故障牵引秒级生效。
DNS - F 的应用场景
这张图是Nacos DNS-F的一个具体实现,实际上是拦截了OS层的DNS请求。如果经过DNS的域名是内部服务,它就会从Nacos Server 获取结果,如果不是,就会转发到其它的LocalDNS进行解析。
以数据库高可用的应用场景为例,我们的数据库切换效率比较低,依赖业务方修改配置,时效不确定,通常需要10分钟以上(备注:我们的数据库实际上已经实现了主备的功能,但当一个主服务出现问题的时候,总是要去切换IP。)切换IP的过程中,依赖运维和开发的协作,这是一个比较长的过程。
引入DNS后,当主出现问题的时候,就可以很快的用另外一个主的IP来进行替换,屏蔽故障,而且节点的故障检测和故障切换都可以自动完成,并不依赖运维和开发的协作,节省了时间。当然,这个场景的解法有很多,比如说使用MySQL - Proxy也可以去解这个问题,但我们的MySQL - Proxy还在建设中,想尽快的把这个问题解决,所以采用了DNS的方式。
下面我们再着重分享下基于DNS-F对LocalDNS的优化。虎牙还没有去建设自己的LocalDNS,大部分使用的是一些公共的DNS,大致有以下这些组成。
这种组成方式会存在一个问题。假设服务突然一下崩溃后,之后服务又马上正常了,这种情况我们无法重现去找到崩溃原因。因为很多场景下,是一个公共DNS的请求超时导致的,甚至一个解析失败导致的,在那一刻,因为无法保留现场的,所以就发现不了问题。
以我们的监测数据来看,DNS解析错误的比例达到1‰左右,超时比例将更高。意思是在使用公共DNS的情况下,服务有1‰的几率是会超时或失败,如果服务没有做好容错,就会出现异常。同时,一些公共DNS解析的延时都是不定的,比如在亚马逊上一些比较不好的节点,它的延时会比较高,平均超过三四十毫秒。
然后我们基于DNS-F对LocalDNS做了一些优化。优化结果如下: 平均解析时间从之前的超过两百毫秒降低到两毫秒以下; 缓存命中率从92%提升到了99%以上; 解析失败率之前是1‰,现在基本上没有了。
优化的效果也体现在我们的风控服务上,平均延迟下降10ms,服务超时比例下降25%,降低了因延迟或服务超时导致的用户上传的图片或文字违规但未被审核到的风险。
服务注册的实践
虎牙的核心业务是跑在Tars上的。 Tars:腾讯开源的一款微服务框架。
Tars主要是支持C++,但对Java、PHP等开发语言的支持力度比较差,这就使得我们非C++的业务方去调用它就会很别扭。引入Nacos以后,我们通过Nacos支持的DNS协议来实现服务发现过程中对全语言的支持。
当然,Nacos不只是一个注册中心,它具备了融合多个数据中心的能力,支持多数据源的同步,例如,我们目前已经支持了Taf(虎牙内部的一个重要微服务体系)、Nacos自身、ZooKeeper、以及K8S上一些服务注册的同步。
同时,基于Nacos集群的双向同步功能(Nacos-Sync),我们实现了国内的两个可用区,以及国外的多个可用区之间的数据值同步,最终实现了一处注册、多地可读。
Nacos-Sync是事件机制,即同步任务通过事件触发,可以灵活地开启和关闭你要同步的任务,然后根据服务变化事件触发监听,保证实时性,最后通过定时的全量突发同步事件,保证服务数据的最终一致。同时,Nacos-Sync也支持服务心跳维持,即多个数据中心的心跳,可以使用Nacos-Sync代理要来实现远端同步。此外,也支持心跳与同步任务绑定,便于灵活控制。
由于Taf上有数万个注册服务,同步的量特别大,所以我们在Nacos-Sync做了一些改造,通过任务分片来实现数万服务同步的可用性保障。改造步骤是先以服务为粒度定义任务,然后在多个分片上分散任务负载,最后以单分片多副本来保证任务可用性。
对接 CMDB,实现就近访问
在服务进行多机房或者多地域部署时,跨地域的服务访问往往延迟较高,一个城市内的机房间的典型网络延迟在1ms左右,而跨城市的网络延迟,例如上海到北京大概为30ms。此时自然而然的一个想法就是能不能让服务消费者和服务提供者进行同地域访问。
Nacos定义了一个SPI接口,里面包含了与第三方CMDB约定的一些方法。用户依照约定实现了相应的SPI接口后,将实现打成Jar包放置到Nacos安装目录下,重启Nacos即可让Nacos与CMDB的数据打通。
在实际的落地过程中,我们是在DNS-F接入Taf,在DNS-F上实现Taf的中控接口,无缝对接Taf的sdk。DNS-F提供缓存负载均衡和实例信息,Nacos则提供负载均衡信息的查询接口。
服务配置的实践
虎牙的域名(www.huya.com)会接入华南、华中、华北多个IDC机房,每个机房都会建设一个Nginx去做负载均衡,经过负载均衡的流量会通过专线返回到我们的后端服务器上。在这个过程中,如果我们去修改一个在中间的配置,需要下发到多个机房的上百个负责负载均衡的机器上,如果出现配置下发不及时,或下发配置失败,极大可能会出现故障,同时,负责均衡服务的机器对弹性能力的要求较高,在业务高峰如果不能快速扩容,容易出现全网故障。
传统的配置下发方式是通过服务端下发文件更新配置,更新配置生效时间长,由于需要预先知道负责均衡集群的机器信息,扩缩容需要等元信息同步以后才能接入流量,扩容流量的接入时间较长。
引入Nacos后,我们采用了配置中心监听方式,通过客户端主动监听配置更新,配置便可秒级生效,新扩容服务主动拉取全量配置,流量接入时长缩短3分钟+。
虎牙对 Nacos 改造和升级的总结
引入Nacos的过程中,我们所做的改造和升级总结如下。
一是在 DNS-F 上,我们增加了对外部域名的预缓存的支持,Agent的监控数据对接到公司的内部监控,日志输出也对接到内部的日志服务,然后和公司的CMDB对接,并实现了DNS-F Cluster集群。我们之所以去构建一个DNS-FCluster集群,是为了避免内存、硬盘或版本问题导致的DNS服务无效,有了DNS-F Cluster集群,当本地Agent出现问题的时候,就可以通过集群去代理和解析DNS请求。
二是在 Nacos-Sync 上,我们对接了TAF注册服务和K8S注册服务,以及解决了多数据中心环形同步的问题。
三是在 Nacos CMDB 上,我们对Nacos CMDB进行了扩展,对接了虎牙自己的CMDB,并对接了内部的负载均衡策略。
原文链接
本文为云栖社区原创内容,未经允许不得转载。
软件开发
2019-02-20 15:21:00
一、打开Egret Launcher安装Egret 5.3.2引擎。
进入白鹭引擎GitHub(https://github.com/egret-labs/egret-core)下载Egret5.3.2,解压放到某个目录(如D:\Program Files\Egret),然后将egret-core-5.3.2目录拖拽到“安装本地版本”,进行自动安装。
在命令行执行 egret versions,如果安装引擎列表里包含 5.3.2 则代表安装成功:
二、打开一个终端,执行 npm install @egret/paper-cli -g 安装命令行工具。
我直接进入项目目录,D:\EgretProjects
三、创建一个新项目
paper create HelloWorld3D
四、将项目导入到Egret Launcher,使用Wing打开进行操作,提示自动创建编译、调试和资源配置参数点确定。

五、项目安装最新3D库
在Wing的终端执行 paper install egret3d 安装最新3D库。
安装完成后,左侧目录会出现egret3d文件夹。
六、安装最新的 Egret3d 可视化调试库
在Wing的终端执行 paper install inspector 安装最新的Egret3d可视化调试库,完成后执行egret clean。
七、烘焙资源
在Wing的终端中执行 egret bake 烘焙资源。
八、构建项目
九、调试运行项目
软件开发
2018-12-20 10:55:00
一、介绍
JUnit是一款优秀的开源Java单元测试框架,也是目前使用率最高最流行的测试框架,开发工具Eclipse和IDEA对JUnit都有很好的支持,JUnit主要用于白盒测试和回归测试。
白盒测试:把测试对象看作一个打开的盒子,程序内部的逻辑结构和其他信息对测试人 员是公开的; 回归测试:软件或环境修复或更正后的再测试; 单元测试:最小粒度的测试,以测试某个功能或代码块。一般由程序员来做,因为它需要知道内部程序设计和编码的细节;
JUnit GitHub地址: https://github.com/junit-team
二、JUnit使用
开发环境: Spring Boot 2.0.4 RELEASE JUnit 4.12 Maven IDEA 2018.2
2.1 检测JUnit依赖
如果是Spring Boot项目默认已经加入了JUnit框架支持,可在pom.xml中查看: org.springframework.boot spring-boot-starter-test test
如果Maven项目中没有添加JUnit依赖,可参照如上代码,手动添加。
2.2 基础使用
简单的测试代码如下: @RunWith(SpringRunner.class) @SpringBootTest public class SimpleTest { @Test public void doTest() { int num = new Integer(1); Assert.assertEquals(num, 1); } }
在测试类中邮件运行项目,效果如下:
从控制台可以看出测试通过了。
2.3 注解说明
2.3.1 注解列表 @RunWith:标识为JUnit的运行环境; @SpringBootTest:获取启动类、加载配置,确定装载Spring Boot; @Test:声明需要测试的方法; @BeforeClass:针对所有测试,只执行一次,且必须为static void; @AfterClass:针对所有测试,只执行一次,且必须为static void; @Before:每个测试方法前都会执行的方法; @After:每个测试方法前都会执行的方法; @Ignore:忽略方法;
2.3.2 超时测试
代码如下,给Test设置timeout属性即可,时间单位为毫秒: @Test(timeout = 1000)
2.4 断言测试
断言测试也就是期望值测试,是单元测试的核心也就是决定测试结果的表达式,Assert对象中的断言方法: Assert.assertEquals 对比两个值相等 Assert.assertNotEquals 对比两个值不相等 Assert.assertSame 对比两个对象的引用相等 Assert.assertArrayEquals 对比两个数组相等 Assert.assertTrue 验证返回是否为真 Assert.assertFlase 验证返回是否为假 Assert.assertNull 验证null Assert.assertNotNull 验证非null
代码示例如下: @Test public void doTest() { String[] string1 = {"1", "2"}; String[] string2 = string1; String[] string3 = {"1", "2"}; Assert.assertEquals(string1, string2); Assert.assertEquals(string2, string3); Assert.assertSame(string1, string2); Assert.assertSame(string2, string3); //验证不通过,string2、string3指向的引用不同 }
2.5 Web模拟测试
在Spring Boot项目里面可以直接使用JUnit对web项目进行测试,Spring 提供了“TestRestTemplate”对象,使用这个对象可以很方便的进行模拟请求。
Web测试只需要进行两步操作: 在@SpringBootTest注解上设置“ebEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT”随机端口; 使用TestRestTemplate进行post或get请求;
示例代码如下: @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class UserControllerTest { @Autowired private TestRestTemplate restTemplate; @Test public void getName() { String name = restTemplate.getForObject("/name", String.class); System.out.println(name); Assert.assertEquals("Adam", name); } }
其中getForObject的含义代表执行get请求,并返回Object结果,第二个参数设置返回结果为String类型,更多的请求方法: getForEntity:Get请求,返回实体对象(可以是集合); postForEntity:Post请求,返回实体对象(可以是集合); postForObject:Post请求,返回对象;
2.6 数据库测试
在测试数据操作的时候,我们不想让测试污染数据库,也是可以实现的,只需要添加给测试类上添加“@Transactional”即可,这样既可以测试数据操作方法,又不会污染数据库了。
示例代码如下: @Test @Transactional public void saveTest() { User user = new User(); user.setName("Adam"); user.setAge(19); user.setPwd("123456"); userRepository.save(user); System.out.println("userId:" + user.getId()); Assert.assertTrue(user.getId()>0); }
执行效果如下:
我们可以看到Id有了,也测试通过了,说明数据是添加是正常的,但查看数据库发现数据里面是没有这条数据的。
如果把“@Transactional”去掉的话,数据库就会正常插入了。
2.7 Idea快速开启测试
在Idea里面可以快速的添加测试的方法,只需要在要测试的类里面右键选择“GoTo”点击“Test”,选择你需要测试的代码,点击生成即可,如果是Windows 用户可以使用默认快捷键“Ctrl + Shift + T”,效果如下图:

选完方法之后,点击OK按钮,就生成了对应的测试代码,用户只需要完善框架里面的具体测试逻辑就可以了。
软件开发
2018-11-05 13:29:00
iOS Xcode 调试OpenGL shader 步骤详解
注意Apple的官方文档详细的描述了要使用GPU Frame Capture功能,必须使用EAGLContext作为上下文,而且要使用CAEAGLLayer或者GLKView,同时还要保证使用定时器等,去循环调用
[_context presentRenderbuffer:GL_RENDERBUFFER];//方法
否则不能使用GPU Frame Capture功能。
当不能使用GPU Frame Capture功能时,此按钮是灰色不可点击状态

下面介绍调试步骤:
首先设置Options里面的GPU Frame Capture为OpenGL ES默认为Automatically Enabled默认设置其实也可以调试,但是此处为了过程更符合OpenGL ES的流程,此处设置为OpenGL ES。
然后运行程序

运行过程中点击Capture按钮捕获一帧OpenGL ES数据。捕获过程较慢。
捕获完毕之后:

但是到目前并不能看到shader的内容,已经shader内的变量的值,调试shader变量的值才是我们的目的。

在选择All GPU Objects -> All 之后调试shader的界面就出现了调试OpenGL ES shader的界面为:
此处如果想一边查看frameBuffer一边调试OpenGL ES shader可以这样操作:
界面变为:

下面打开frameBuffer界面:
点击后我们想要的界面就出现了一边为frameBuffer界面一边为调试OpenGL ES shader的界面
下面查看OpenGL ES在渲染此帧时shader内的各个变量值是多少
此处以查看shader的变量的值为示例,双击Vertex Array Objective #0
shader 内的变量的值为:
使用的shader为:

异常为:
此异常是为了测试调试信息故意这样写的,去掉此处的异常只需要将GLfloat jeffasdColors[]改为:
//GLfloat jeffasdColors[] = { 1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,1.0f };
至于原因估计属性OpenGL ES的都清除此处不再解释。
软件开发
2018-10-03 17:04:00
萌布猫咪是我跟我妹的基于兴趣做出的作品,我负责产品设计、技术实现还有部分UI设计工作,我妹主要是原画设计。
「萌布猫咪」是一款非常萌跟好玩的猫咪收集游戏,可以放置猫咪送的宝物和制作甜点,宝物甜点会吸引新的猫咪过来玩,每只猫咪都有一段暖心的小故事喔。
实现技术通过cocos2dx
更多详情可以到官网下载体验
萌布猫咪官网
萌布猫咪安卓版下载
github地址
欢迎大家提建议~
软件开发
2018-08-11 17:16:00
一款游戏会用到很多图片资源,通常我们会使用TexturePacker工具进行图片的拼接、压缩,为了考虑性能问题,单个SpriteSheet的尺寸不会设置的太大(最大1024 * 1024),这样就可能会有多个SpriteSheet。每当有图片资源需要更新时,操作起来会有点不方便。所以希望能通过bat来批量重新生成新的SpriteSheet

首先我们需要获取TexturePacker的安装目录,一般我们都会安装TexturePackerGUI工具,它实际运行起来还是调用TexturePacker.exe工具。因为软件环境因人而异可能存放的目录各不相同,这里通过读取注册表相关信息来避免此问题 @echo off echo "获取TexturePackerGUI注册表安装信息..." & echo. setlocal enabledelayedexpansion for /f "skip=4 tokens=1,* delims=REG_SZ " %%a in ('reg query "HKEY_CLASSES_ROOT\Applications\TexturePackerGUI.exe\shell\Open\command" /ve') do ( set "str=%%b" echo !str! & echo. set "str=!str:"%%1"=!" echo TexturePackerGUI的安装路径:!str! & echo. for /f "tokens=1,* delims=" %%i in (!str!) do ( set "str=%%i" set "TexturePacker_Path=!str:TexturePackerGUI.exe=!TexturePacker.exe" echo !TexturePacker_Path! & echo. ) ) pause

在dos窗口下执行查询指令,得到结果如下图所示:
实际注册表的相关信息:


我们会发现,返回的结果中,前4行是无用的信息。然后以“REG_SZ ”作为分隔符进行字符串分隔。得到包含空格的路径信息:"G:\Program Files\TexturePacker\bin\TexturePackerGUI.exe" "%1"
通过set指令替换掉%1 set "str=!str:"%%1"=!"

然后通过for /f "tokens=1,* delims=" %%i in (!str!) do () 来去除””及空格信息

之后就直接调用了TexturePacker.exe echo "TexturePacker安装路径:%TexturePacker_Path%" & echo. "%TexturePacker_Path%" batch_btn.tps "%TexturePacker_Path%" batch_kuang.tps "%TexturePacker_Path%" batch_left.tps "%TexturePacker_Path%" batch_spt.tps "%TexturePacker_Path%" batch_text.tps "%TexturePacker_Path%" batch_expression.tps

下面附几张Texture Packer 的常用设置截图,

通常情况下,我们导出的纹理图片是RGBA8888格式,R、G、B、A各需要占用8个比特(1字节),所以一个像素总共需要4个字节表示。通常我们使用RGBA444来保存图片,不过需要注意设置一个参数来保证压缩后的图片尺寸大小不会变化。勾选“Allow free sizes”,否则使用导出的图片后界面会错位(因为空白区域被裁剪掉了>_<)
使用RGBA4444还需要注意的一个问题,就是选择压缩算法(抖动+通道)

texturepacker官网上有相应的对比图>>
在Command Line中使用TexturePacker
使用TexturePacker批量生成SpriteSheet
bat批处理 教程
(译)在cocos2d里面如何使用Texture Packer和像素格式来优化spritesheet
软件开发
2018-05-15 13:27:00
时间20180508,使用的unity版本2017.2,unity中的.net 4.6平台还是不稳定版本。
示例代码地址: https://github.com/hiramtan/HigRPC_unity
估计还不会有人用在商业手游项目中,原因: .net 4.6还是实验性版本,并不能保证稳定性。 编译可用的gRPC类库到移动平台。 需要验证在Android iOS平台上所有逻辑的稳定性。
在编辑器中使用流程如下:
How to use in unity 设置 .net 4.6 导入 gRPC dll Google.Protobuf.dll Grpc.Core.dll grpc_csharp_ext.x64.dll System.Interactive.Async.dll 创建测试逻辑.
Extention
用户自定义协议及代码生成 Grpc.Tools.1.11.x\tools\windows_x86\protoc.exe -I../../protos --csharp_out Greeter --grpc_out Greeter ../../protos/helloworld.proto --plugin=protoc-gen-grpc=packages/Grpc.Tools.1.11.x/tools/windows_x86/grpc_csharp_plugin.exe
Note: Haven't test on mobile platform
点击链接加入QQ群【83596104】: https://jq.qq.com/?_wv=1027&k=5l6rZEr
软件开发
2018-05-09 15:52:00
一:多进程 import os import time from multiprocessing import Process def func ( args , args2 ) : print ( args , args2 ) #打印参数 time.sleep( 3 ) print ( '子进程 :' , os.getpid()) #查看子进程进程号 print ( '子进程的父进程 :' , os.getppid()) #查看子进程的父进程,多了一个p print ( 12345 ) if __name__ == '__main__' : p = Process( target = func , args = ( '参数' , '参数2' )) # 注册 # p是一个进程对象,还没有启动进程 p.start() # 开启了一个子进程 print ( '*' * 10 ) print ( '父进程 :' , os.getpid()) # 查看当前进程的进程号 print ( '父进程的父进程 :' , os.getppid()) # 查看当前进程的父进程 # 进程的生命周期 # 主进程 # 子进程 # 开启了子进程的主进程 : # 主进程自己的代码如果长,等待自己的代码执行结束, # 子进程的执行时间长,主进程会在主进程代码执行完毕之后等待子进程执行完毕之后 主进程才结束
打印结果: 先打印主进程,然后打印子进程。主进程结束执行子进程。
**********
父进程 : 3832
父进程的父进程 : 2556
参数 参数2
子进程 : 5416
子进程的父进程 : 3832
12345

二:多进程的中的几个方法(多进程即可指子进程,父进程这两个进程,也可指多个实例化两个进程) import time from multiprocessing import Process def func ( arg1 , arg2 ) : print ( '*' * arg1 ) time.sleep( 5 ) print ( '*' * arg2 ) if __name__ == '__main__' : p = Process( target = func , args = ( 10 , 20 )) p.start() print ( 'hahahaha' ) # p.join() # 是感知一个子进程的结束,将异步的程序改为同步 print ( '====== : 运行完了' )
这样就是正常打印,主进程执行完毕执行子进程
hahahaha
====== : 运行完了
**********
********************

如果加上join之后, 是感知一个子进程的结束,将异步的程序改为同步。也就是等待子进程结束后主进程关闭
打印结果:
hahahaha
**********
********************
====== : 运行完了
如果子进程是一个while循环的死循环,那么主进程就一直不会关闭。 import os import time from multiprocessing import Process def func () : while True : print ( '子进程开始' ) time.sleep( 1 ) print ( '***我还在运行' ) if __name__ == '__main__' : p = Process( target = func) p.daemon = True p.start() p.join() print ( '程序结束' )
打印结果:
子进程开始
***我还在运行
子进程开始
***我还在运行
总是在子进程这里循环

小总结:
process小知识: Process([group [ , target [ , name [ , args [ , kwargs]]]]]),由该类实例化得到的对象, 表示一个子进程中的任务(尚未启动) 强调: 1. 需要使用关键字的方式来指定参数 2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号 参数介绍: 1 group参数未使用,值始终为None 2 target表示调用对象,即子进程要执行的任务 3 args表示调用对象的位置参数元组,args = ( 1 , 2 , 'egon' , ) 4 kwargs表示调用对象的字典 , kwargs = { 'name' : 'egon' , 'age' : 18 } 5 name为子进程的名称

进程常见关键字 1 p.start():启动进程,并调用该子进程中的p.run() 2 p.run() : 进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法 3 p.terminate() : 强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程, 使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁 4 p.is_alive() : 如果p仍然运行,返回True 5 p.join([timeout]) : 主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。 timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程

开启多个子进程 import os import time from multiprocessing import Process def func ( filename , content ) : with open ( filename , 'w' ) as f : f.write( content * 10 * '*' ) if __name__ == '__main__' : p_lst = [] for i in range ( 10 ) : p = Process( target = func , args = ( 'info%s' % i , i)) p_lst.append(p) p.start() for p in p_lst : p.join() # 之前的所有进程必须在这里都执行完才能执行下面的代码 print ([i for i in os.walk( r'F:\python10期\day37\day37' )]) # 同步 0.1 * 500 = 50 # 异步 500 0.1 = 0.1 # 多进程写文件 # 首先往文件夹中写文件 # 向用户展示写入文件之后文件夹中所有的文件名
解析:他会在所有进程结束之后才能完成下面的print。

开启多个子进程的第二种方式: import os from multiprocessing import Process class MyProcess (Process) : def __init__ ( self , arg1 , arg2 ) : super (). __init__ () self .arg1 = arg1 self .arg2 = arg2 def run ( self ) : print ( self .pid) print ( self .name) print ( self .arg1) print ( self .arg2) if __name__ == '__main__' : p1 = MyProcess( 1 , 2 ) p1.start() p2 = MyProcess( 3 , 4 ) p2.start() # 自定义类 继承Process类 # 必须实现一个run方法,run方法中是在子进程中执行的代码
这种方式需要第一定义一个类,让他继承process,第二种是传参,需要初始化,然后是定义一个run方法,因为定义run方法之后才能执行start,然后在实例化二个对象。分别执行。
打印结果:
6356
MyProcess-1
1
2
3136
MyProcess-2
3
4
因为此进程是异步,所以process1和process2不确定谁最先打印,随机。super是执行process所有方法。



软件开发
2018-03-26 17:22:00
20180312
ECS编程思想
这种编程思想很早前就已经提出,ECS分别是Entity,Component,System的缩写. Entity是实例,作为承载组件的载体,也是框架中维护对象的实体. Component只包含数据,具备这个组件便具有这个功能. System作为逻辑维护,维护对应的组件执行相关操作.
ECS和传统的OOP相比更看重的是组件,附加组件即具备功能.
C#是面向对象语言,从语言层面上就支持面向对象的编程思想:集成封装和多态. 面向对象:我是什么,我有属性,我有方法,我要继承然后重写成自己独有的. 面向组件:我有什么,我有组件便具备这个功能,我由很多组件组成了实体.
按照ECS的思路,功能拆分成组件,然后把这些细小的组件组合便组成了具备所需功能的对象.实际上Unity也是这种实现思路,但是它的组件式属性和逻辑的合集.
最近看了ECS编程思想,看了Entitas逻辑,写了小Demo,感觉:
编程思路/框架没有最好只有更合适,可能A框架适合RPG游戏,B框架更适合FPS游戏. Entity的时序
开发中经常会遇到控制时序的逻辑,比如某个Entity必须在另一个Entity逻辑完成后再执行,当然可以通过添加组件标识来控制时序执行,但是如果存在大量这种逻辑势必会增加很多对应的组件作为标识. System时序
同样可以添加Component作为控制标识作为过滤条件,A系统要在B系统结束之后,C系统启动之前,D系统挂起时...开发到后期怎么办.(待机,前摇,蓄能,施法,表现,结束,)太多太多状态一帧/分帧维护起来估计也会乱吧. System更合适维护一组Entity 比如FPS游戏,MOBA游戏用ECS思想拆分合适,每个玩家的角色功能对等(都是人物,都在移动,战斗)不同的角色的技能枪支拆分单独实现. 如果是大型RPG游戏,每个门派角色,大厅npc,商城商人,敌人角色的状态动作各不相同,这种ECS当然能实现,每个功能,拆分成细小组件,创建对应System类维护,但是这样归根到底一个System经过遍历筛选其实只维护了一个Entity上面的组件.因为角色功能独有,以后这个组件也不会被其他Entity组合使用. 功能解耦
功能之间解耦,对应的组件和系统只关心本身的功能,不会对其他功能造成影响. 重构
不方便重构 数据维护
数据分散到对应的组件,比如服务器下发一份最新数据,需要一一维护对应组件的属性,数据收集也是如此. 客户端服务器共享代码
客户端拆分表现组件后,只包含纯逻辑功能. 数据层表现层分离
在不同的组件和系统中维护,不必像oop再拆分表现逻辑. 方便扩展
如果设计合理,可以通过现有组件组合新的Entity. 1- 性能
数据在内存中线性存在,Entity复用.
ECS框架:Entitas
基于ECS的框架有很多,Entitas便是其中一个.
官方的ECS也会在2018.1beta版本中推出.
根据wiki很容易就能够搭建起项目,运行起工程,但是有些也需要开发者注意,比如Component作为标识还是作为数据存储;Component生成代码后改变类型或者删除字段后的生成报错,Group/Feature/Unit使用等等.
关于代码生成,它提供两个版本: 免费版 收费版
免费版根据反射生成代码,需要无编译错误下运行;收费版不需要,即使有代码报错也可以生成代码.
自己扩展的代码可以分为两部分: Components 其他代码(System,Service...等其他逻辑)
这样做的好处是重新生成代码时,方便的把这两个目录移除项目(或者StreamingAssets),生成基础代码,然后移回Component代码,然后生成组件代码,然后移回其他代码.
Entitas实践
最简单的输出hello world
源码: https://github.com/hiramtan/test/tree/Entitas_Example_HelloWorld
创建一个Component: public class HelloWorldComponent : IComponent { public string s; }
生成代码后创建两个system,一个用来创建entity并附加组件,另一个用来输出日志: public class CreatSystem : IInitializeSystem { private Contexts _contexts; public CreatSystem(Contexts contexts) { _contexts = contexts; } public void Initialize() { var e = _contexts.game.CreateEntity(); e.AddHelloWorld("hello world"); } } public class HelloWorldSystem : ReactiveSystem { private Contexts _contexts; public HelloWorldSystem(Contexts contexts) : base(contexts.game){ } protected override ICollector GetTrigger(IContext context) { return context.CreateCollector(GameMatcher.HelloWorld); } protected override bool Filter(GameEntity entity) { return entity.hasHelloWorld; } protected override void Execute(List entities) { foreach (var e in entities) { Debug.Log(e.helloWorld.s); } } }
上面的代码已经实现了输出功能,但是为了方便管理上面的这两个System,我们添加一个RootSystems专门管理游戏中的各种System: public class RootSystems : Feature { public RootSystems(Contexts contexts) { Add(new CreatSystem(contexts)); Add(new HelloWorldSystem(contexts)); } }
所有的功能都实现了,在unity工程中添加一个入口方法挂载到场景中,Main.cs: public class Main : MonoBehaviour { private RootSystems s; // Use this for initialization void Start() { s = new RootSystems(Contexts.sharedInstance); s.Initialize(); } // Update is called once per frame void Update() { s.Execute(); } }
运行,正常输出,因为输出实现的是reactivesystem,当值发生变化时便会输出.
Unity ECS
今天untiy在beta版本中公布了自己的ECS系统,Entity,ComponentData,ComponentSystem,随着unity官方的引入,势必会被推广起来.
点击链接加入QQ群【83596104】: https://jq.qq.com/?_wv=1027&k=5l6rZEr
参考资料 https://github.com/sschmid/Entitas-CSharp https://blog.codingnow.com/2017/06/overwatch_ecs.html http://www.ecsframework.com/ https://youtu.be/OJmVBo5HGOY http://t-machine.org/index.php/category/entity-systems/ http://www.benmutou.com/archives/2421 https://www.cnblogs.com/yangrouchuan/p/7436533.html http://blog.csdn.net/u012632851/article/details/75370836 http://blog.csdn.net/langresser_king/article/details/46324977 http://blog.csdn.net/wuxiaoming1733/article/details/79037812 http://blog.lmorchard.com/2013/11/27/entity-component-system/ http://piemaster.net/2011/07/entity-component-primer/ http://www.richardlord.net/blog/ecs/what-is-an-entity-framework.html http://blog.csdn.net/qq992817263/article/details/78155443
软件开发
2018-03-22 11:00:00
TODO
AssetBundle加载的时候 会导致类型错误
非图集 会有Sprite 和Texture2D
图集只有Sprite
只有他们在同一父级下面才会合并为一个batch
软件开发
2018-03-02 10:17:00
网络上的代码, 读的是PNG 与 jpg ,但我这里读不了
研究了一下, 发现能读rbg文件
这里是改变模型的贴图, 还看不出是什么原理
#include #include #include #include #include #include #include #include #include void main() { osgViewer::Viewer viewer; osg::ref_ptr root=new osg::Group(); osg::ref_ptr node=osgDB::readNodeFile("cow.osg"); osg::ref_ptr image=osgDB::readImageFile("Images\\lz.rgb");// osg能读取rgb格式 , data文件夹里带有这张图 if (image.get()) { osg::ref_ptr texture=new osg::Texture2D(); texture->setImage(image.get()); //设置自动生成纹理坐标 osg::ref_ptr texgen=new osg::TexGen(); texgen->setMode(osg::TexGen::SPHERE_MAP); //设置纹理环境,模式为BLEND osg::ref_ptr texenv=new osg::TexEnv; texenv->setMode(osg::TexEnv::Mode::BLEND);// ADD texenv->setColor(osg::Vec4(0.6,0.6,0.6,0.0)); //启动单元一自动生成纹理坐标,并使用纹理 osg::ref_ptr state=new osg::StateSet; state->setTextureAttributeAndModes(1,texture.get(),osg::StateAttribute::ON); state->setTextureAttributeAndModes(1,texgen.get(),osg::StateAttribute::ON); state->setTextureAttribute(0,texenv.get()); node->setStateSet(state.get()); } else { osg::notify( osg::FATAL ) << "Unable to load data file Exiting." << std::endl; } root->addChild(node.get()); viewer.setSceneData(root.get()); viewer.realize(); viewer.run(); }
软件开发
2018-01-22 15:48:00
游戏服务器设计之属性管理器
  游戏中角色拥有的属性值很多,运营多年的游戏,往往会有很多个成长线,每个属性都有可能被N个成长线模块增减数值。举例当角色戴上武器时候hp+100点,卸下武器时HP-100点,这样加减逻辑只有一处还比较好控制,如果某天有个特殊功能当被某技能攻击时,角色武器会被击落,这样就会出现减数值的操作不止一处。如果逻辑处理不当,比如击落的时候没有恰当的减数值,再次穿戴武器就导致属性值加了两边,也就是玩家经常说的刷属性。这种bug对游戏平衡性影响很大,反响很恶劣,bug又很难被测试发现。本文将介绍一种管理属性的思路,最大限度的避免此类bug,如果出现bug,也能够很好的排查。
设计思路
  刷属性bug的核心原因是某功能的模块数值加了N次,所以各个模块加的属性要被记录,加过了必须不能重复加。设计这样的数据结构。 //!各个属性对应一个总值 //!各个属性对应各个模块的分值 template class PropCommonMgr { public: typedef T ObjType; typedef int64_t (*functorGet)(ObjType); typedef void (*functorSet)(ObjType, int64_t); struct PropGetterSetter { PropGetterSetter():fGet(NULL), fSet(NULL){} functorGet fGet; functorSet fSet; std::map moduleRecord; }; void regGetterSetter(const std::string& strName, functorGet fGet, functorSet fSet){ PropGetterSetter info; info.fGet = fGet; info.fSet = fSet; propName2GetterSetter[strName] = info; } public: std::map propName2GetterSetter; }; 关于数据结构的get和set,我们为每个属性命名一个名字,这样处理数据的时候会非常方便(比如道具配增加属性等等),角色属性有很多种,这里不能一一定义,所以属性管理器只是映射属性,并不创建属性值。通过regGetterSetter接口,注册get和set的操作映射。为什么不需要提供add和sub接口能,因为add和sub可以通过get和set组合实现。get和set的接口实现如下: int64_t get(ObjType obj, const std::string& strName) { typename std::map::iterator it = propName2GetterSetter.find(strName); if (it != propName2GetterSetter.end() && it->second.fGet){ return it->second.fGet(obj); } return 0; } bool set(ObjType obj, const std::string& strName, int64_t v) { typename std::map::iterator it = propName2GetterSetter.find(strName); if (it != propName2GetterSetter.end() && it->second.fSet){ it->second.fSet(obj, v); return true; } return false; } 关于add和sub,前面提到要避免刷属性,就必须避免重复加属性。所以每个模块再加属性前必须检查一下是否该模块已经加了属性,如果加过一定要先减后加。因为每次模块加属性都记录在属性管理器中,那么减掉的数值一定是正确的。这样可以避免另外一种常见bug,如加了100,减的时候计算错误减了80,也会积少成多造成刷属性。add和sub的代码如下: int64_t addByModule(ObjType obj, const std::string& strName, const std::string& moduleName, int64_t v) { typename std::map::iterator it = propName2GetterSetter.find(strName); if (it != propName2GetterSetter.end() && it->second.fGet && it->second.fSet){ int64_t ret =it->second.fGet(obj); std::map::iterator itMod = it->second.moduleRecord.find(moduleName); if (itMod != it->second.moduleRecord.end()){ ret -= itMod->second; itMod->second = v; } else{ it->second.moduleRecord[moduleName] = v; } ret += v; it->second.fSet(obj, ret); return ret; } return 0; } int64_t subByModule(ObjType obj, const std::string& strName, const std::string& moduleName) { typename std::map::iterator it = propName2GetterSetter.find(strName); if (it != propName2GetterSetter.end() && it->second.fGet && it->second.fSet){ int64_t ret =it->second.fGet(obj); std::map::iterator itMod = it->second.moduleRecord.find(moduleName); if (itMod == it->second.moduleRecord.end()){ return ret; } ret -= itMod->second; it->second.moduleRecord.erase(itMod); it->second.fSet(obj, ret); return ret; } return 0; } int64_t getByModule(ObjType obj, const std::string& strName, const std::string& moduleName) { typename std::map::iterator it = propName2GetterSetter.find(strName); if (it != propName2GetterSetter.end() && it->second.fGet && it->second.fSet){ int64_t ret =it->second.fGet(obj); std::map::iterator itMod = it->second.moduleRecord.find(moduleName); if (itMod != it->second.moduleRecord.end()){ return itMod->second; } } return 0; } std::map getAllModule(ObjType obj, const std::string& strName) { std::map ret; typename std::map::iterator it = propName2GetterSetter.find(strName); if (it != propName2GetterSetter.end() && it->second.fGet && it->second.fSet){ ret = it->second.moduleRecord; } return ret; }
  如上代码所示,addByModule和subByModule必须提供模块名,比如穿装备的时候加血量:addByModule('HP', 'Weapon', 100),而卸下武器的时候只要subByModule('HP', 'Weapon'),因为属性管理器知道减多少。
总结 属性提供一个名字映射有很多好处,比如装备配属性,buff配属性的,有名字相关联会特别方便 提供一个get和set接口的映射,这样属性管理器就和具体的对象的属性字段解耦了。即使是现有的功能模块也可以集成这个属性管理器。 属性的add和sub操作,都在属性管理器中留下记录,这样即使出现问题,通过getByModule getAllModule两个接口亦可以辅助查找问题。 属性管理已经集成到H2Engine中,github地址: https://github.com/fanchy/h2engine
软件开发
2018-01-09 19:58:00
近来有空,打算写点游戏编程的文章。
今天第一篇,关于第三人称摄像机的。
先看效果

按下鼠标中键,左右可旋转,上下可倾斜。

下面是代码 #include #include // strcpy_s #include using namespace irr; using namespace core; using namespace scene; using namespace video; using namespace io; using namespace gui; #ifdef _IRR_WINDOWS_ #pragma comment(lib, "Irrlicht.lib") #endif #include "my3rdcamera.h" My3rdCamera* myCamera; class EventReceiver: public irr::IEventReceiver { public: virtual bool OnEvent (const irr::SEvent& e){ if(myCamera) myCamera->OnEvent(e); //传入事件到camera里处理 return false; } }; EventReceiver e; int _tmain(int argc, _TCHAR* argv[]) { // create device IrrlichtDevice *device = createDevice(EDT_OPENGL, core::dimension2d(640, 480), 16, false); if (device == 0) return 1; // could not create selected driver. device->setEventReceiver(&e); device->setWindowCaption(L"Irrlicht Engine Demo"); video::IVideoDriver* driver = device->getVideoDriver(); scene::ISceneManager* smgr = device->getSceneManager(); scene::IAnimatedMesh* roomMesh = smgr->getMesh("plane.ms3d"); scene::ISceneNode* room = 0; if (roomMesh) { room = smgr->addMeshSceneNode(roomMesh); room->setScale(vector3df(1000,1000,1000)); room->setMaterialFlag(video::EMF_LIGHTING, false); } scene::IAnimatedMeshSceneNode* node = 0; video::SMaterial material; // Add an MD2 node, which uses vertex-based animation. node = smgr->addAnimatedMeshSceneNode(smgr->getMesh("faerie.md2"), 0 ); node->setPosition(core::vector3df(0,22,0)); // Put its feet on the floor. node->setScale(core::vector3df(1.6f)); // Make it appear realistically scaled node->setMD2Animation(scene::EMAT_POINT); node->setAnimationSpeed(20.f); material.setTexture(0, driver->getTexture("faerie2.bmp")); material.Lighting = false; material.NormalizeNormals = true; node->getMaterial(0) = material; // setup the camera myCamera=new My3rdCamera(device,node); //传入device和要跟随的角色节点 //// add fps camera //scene::ICameraSceneNode* fpsCamera = smgr->addCameraSceneNodeFPS(); //fpsCamera->setPosition(core::vector3df(-50,50,-150)); u32 frames=0; while(device->run()) { driver->beginScene(true, true, video::SColor(0,100,100,100)); myCamera->update(); //处理camera smgr->drawAll(); driver->endScene(); if (++frames==100) { core::stringw str = L"Irrlicht Engine ["; str += driver->getName(); str += L"] FPS: "; str += (s32)driver->getFPS(); device->setWindowCaption(str.c_str()); frames=0; } } device->drop(); return 0; }


My3rdCamera 在github 有, https://github.com/344717871/3drpgdemo/tree/master/client/My3rdCamera.h
https://github.com/344717871/3drpgdemo/tree/master/client/My3rdCamera.cpp

faerie.md2 是irr自带的3d模型,plane.ms3d就是一个矩形平面,作为游戏的地面。
软件开发
2018-01-02 15:16:00
光照计算
光照计算主要目的是 为了得到 高频 中频 低频 信息
高频信息是细节,中低频是整体控制
例如低模 本身是中低频, normal map diffuse map 增加了高频信息
高光也是高频信息
AO 遮蔽,高频
例如在计算雾效果时,简单的线性距离深度变化是 低频, 再加上基于高度变化,基于太阳光朝向颜色,基于距离的 mipmap, 增加了高频细节
光照系统分为:
预先计算
实时计算
光照系统的预先计算整体流程 设置光源,光源分为直接光和间接光, 直接光光源分为 平行光,点光 ,探照灯,区域光,基于图像的光源 3.间接光 包括物体互相反射光,来自light probe,来自环境光,来自雾,来自反射探头,间接光通常需要使用烘焙技术来计算 4.烘焙技术包括 基于 辐射度的 和 基于光线追踪 5.烘焙产生光照贴图,或者light probe 数据 6:在游戏运行时,预先计算的间接光信息,阴影信息,简化参与到物体渲染中
光照烘焙涉及到4个方面
光源,物体,材质,UV或者probe
光照烘焙贴图,基于对场景中静态物件进行UV展开,将光照信息写入
写入信息,可以包括直接光, 间接光,阴影,的颜色和强度
光照贴图还可以包括主要光方向, directional lightmap 模式中,包括了主要光方向信息,用于实时计算的时候,结合normalmap 计算diffuse color , 而不是将light map 作为环境光处理
有了主要光方向信息,结合normal map就可以进行更多的计算
整体技术涉及到:
材质系统,光源,烘焙技术
光照最终效果,涉及到阴影,直接光,间接光 三个效果
在Unity中材质的光照模型主要可以分为,lambert phong 和 最新的 pbr 物理基于着色系统
材质模型:
half lambert 0.5*(N * L) + 0.5
材质也可以toon shading normal * L normal * V 在texture 上采样光带
pbr 微表面, fresnel
光模型
Unity中的光,分为三种,bake烘焙光,实时光,和mixed模式光
Unity中的烘焙系统,有Enlighten 基于辐射度的烘焙计算,和 progressive 基于光线追踪的烘焙计算
其中enlighten 有两种烘焙,纯静态光照贴图烘焙和 实时GI烘焙
其中烘焙GI光照,可以使用光照贴图,也可以使用light probe来收集,其中光照贴图,分为纯Diffuse贴图和 支持 normal map 的 directional lightmap。
light probe 和 光照贴图区别就是,light probe 收集的是空间光照信息,而light map 收集的是 物体表面的光照信息
reflection probe 反射光照 probe
光照效果:
软影子 宽高光, AO
Unity中的光照方案:
全烘焙 ,只使用enlighten 烘焙光照贴图,使用纯烘焙光,光照贴图上包含 直接光,间接光,阴影信息
全实时光 ,使用 cascade shadow map 技术产生4级实时阴影, 使用pbr 材质模型,实时光进行光照计算
mixed light , 分为三种模式
只烘焙 间接光 ,
烘焙阴影,
烘焙距离阴影,近处使用实时阴影
subtractive 模式,直接光 间接光 阴影都烘焙,实时阴影和烘焙阴影采用 subtractive模式混合
纯 light probe + 实时光照 , light probe 提供间接光照信息
软件开发
2018-01-01 09:38:00
在上面的基础上继续延伸 战斗服管理器横向扩展
管理器全互联后就不用依赖redis了, 可以相互发消息,本服不存在的房间号,根据房间号混淆加密
房间号加密混淆可逆 算法 直接找出目标组,然后转发过去即可,但是为了不再继续转发 简单的方式是写2个rpc函数来区分。
跨服加入房间问题解决了,那么另外两个个问题,就涉及到负载均衡了。
当本服创建房间失败 或者单人匹配失败。 那么就要把请求转发给其余的管理器去处理(即跨服开房间 或单人匹配)。
单人匹配 和创建房间目前是仅在本服,跨服操作的话那么就要分开构思了。
单人匹配:单人匹配的跨服操作理论上讲 应该尽可能的 全服填坑式 一个服务器组 一个服务器组的塞满。尽可能的让全服玩家都可能相互匹配到

出现这个的原因是管理器有多个,如果全服只有一个的话,那么就简单多了,比如全服只有一个管理器,但是这样吞吐量不够,因为管理器还承担着 战斗服的负载均衡的功能,全服的战斗服都给他去管理的话,压力很大。
方案1:新添加一个服务器角色(匹配服Match),全服只有一台或者仅限的几台,同台可以相互匹配。那么设计上功能尽可能的简单。网络IO协程尽可能的大,因为匹配时间也许会很长,服务器处理能力有限。

TODO


软件开发
2017-12-21 11:44:00
简要:
各种走弯路, 矩阵还不是很会.
osg的自带漫游器对像机有影响, 起初不知道.
现在还有一些未解之迷, 比如90帧 的帧率是怎么来的? SteamVR提供的?
手柄模型目前依然用面的绘制方式, 不好看, 不会openGL,没办法.
TXT:
找到了加载手柄的代码:
error = vr::VRRenderModels()->LoadRenderModel_Async( pchRenderModelName, &pModel );//1: lh_basestation_vive 2:vr_controller_1_5
但这个函数LoadRenderModel_Async里有源码
CGLRenderModel::Draw() RenderModel把自己画出来, 问题是通过什么方式把自己画出来
里面调用的方法, 是来自glew.h的
那么, 能否用原生的OpenGL 在OSG中绘图?
1>main.obj : error LNK2001: 无法解析的外部符号 __imp___glewActiveTexture
1>main.obj : error LNK2001: 无法解析的外部符号 __imp___glewBindVertexArray
1>MSVCRT.lib(crtexe.obj) : error LNK2001: 无法解析的外部符号 main
因为文件SDL.h, 出现了无法解析main的错误.
LNK2001 是平台问题 sample示例用的是Debug win32
Debug与Release的区别可能没关系,因为Sample项目就是加载Release版的lib,但用Debug版编译
这个Sample是在OSG64文件夹内的,却是32位
编译通过, 现在要加载dll
openvr108\samples\bin\win64 这里面也有个glew32.dll
出现已停止工作 , 出错位置是0x0000 没有出错的代码处
没有OpenGL的基础, 这个错误我解决不了.
############################################################################################
如果, 显示手柄是SteamVR或HTC VIVE内置的api. 那么,是没有模型文件的.
但, 在OpenGL里, 与Unity里, 手柄的表现是不一样的. OpenGL里没有扳机按压的过程.
renderModelName 与OpenGL里的 一样是:vr_controller_vive_1_5
############################################################################################
https://github.com/ValveSoftware/openvr/wiki/API-Documentation#initialization-and-cleanup
IVRSystem 两只眼睛的映射, 头盔手柄的跟踪, 事件的响应
Compositor排字工人 在updatePose里有被用到 WaitGetPoses
IVOverlay 疑似在三维场景中显示二维图像的API 有例子, 但例子是QT的
如果要做模型程序,现有的VR场景要能跑通
Screenshots 我猜是截屏截成图片, 两个sample工程都没用到, 不知具体是什么
那么, 桌面屏幕的投影是怎么实现? HMD的投影是怎么实现的? IVRSystem
扣代码, 直接扣过来用啊. 目前需求是, 在VR里显示osg的场景,就这一点而已.
把OpenVRDll64那个工程再复制一遍过来后, 就没有头文件冲突的那一情况了.
之前封装的函数, 不能正常显示
现在想封装成一个inline函数 , 各种出错
####
为了拷手柄模型获取代码, 还要加引入头文件的目录
glew32.lib
出现不能解析的外部符号bool __cdecl CGLRenderModel::BInit
代码还没拷完
把glew32.dll拷过来, 这个dll在x64下也能用.
GetRenderModelCount() 这个函数取出来的模型数是51 ,太多了, 且不受设备开关的影响,可能是一个常量
helloVR是怎么取到设备名的, helloVR是怎么取到设备id的
helloVR中, 通过 GetTrackedDeviceString这个函数取设备名
手柄的设备号是3或4
osg这边取到的设备名是: generic_controller 与 generic_hmd 分别是3与4
设备数只有4个, 没找到更多设备 vr::k_unTrackedDeviceIndex_Hmd 算上跳过的这个,五个
又要建全局变量, 还好在模块内,其他模块调取不到.
还要应对手柄中途关掉或没电的情况.
在openvrdevice里没法取得viewer所以绘图依赖外部调用.
####
取到的3与4并不是vr手柄模型, 输出所有模型, 没有之前的
lh_basestation_vive 2:vr_controller_1_5
vr 手柄在16个模型之外
大于4的模型名:5 : generic_tracker
大于4的模型名:6 : hard_bounds
大于4的模型名:7 : laser_pointer
大于4的模型名:8 : lh_basestation_01_boysandgirls
大于4的模型名:9 : lh_basestation_02_batman
大于4的模型名:10 : lh_basestation_03_robin
大于4的模型名:11 : lh_basestation_vive
大于4的模型名:12 : lighthouse_ufo
大于4的模型名:13 : mptam_hmd_model
大于4的模型名:14 : oculus_cv1_controller_left
大于4的模型名:15 : oculus_cv1_controller_right
能显示手柄模型了 虽然不完美
###################################################################
现在手柄的移动与现实中不匹配.
看看hellovr里怎么做的.RenderControllerAxes里作了处理
m_rmat4DevicePose这数据也是从mDeviceToAbsoluteTracking里取的.
手柄的旋转乱遭遭的, 是不是左手坐标系?
在hellovr_opengl_main.cpp的1145行做矩阵的试验
所以, 那矩阵的最后一行就是坐标 这行不受
还可以看看头盔是怎么移动的.
头显的移动, 也是受角度影响的, 却没问题. 有一个不同点是, 头显是作用于像机,而手柄是作用于场景中的物体.
环境不一样, hellovr里的取到的矩阵, 取到的最后一行不因角度的改变而改变.
hellovr输出: 1.918463, 0.884200, 0.470154 , 1.000000
osg输出: 0.179302, 0.00267851,0.0541984, 1
####################################################################
要么学会计算, 要么找出坐标不同的原因
####################################################################
openvrdevices是如何刷帧获取设备坐标的?
在OpenVRUpdateSlaveCallback里 updatePose被调用
osg这边, 与openGL有没有什么初始化参数上的区别?
从openvrdevices里, 看不到什么参数
有没有90帧这个参数?没有! 那么updateSlave在哪被调用?
slave中有调用updateSlaveImplementation这个是osg的函数,可能是由这个触发osg刷新的,
不然osg只有60帧
OpenVRUpdateSlaveCallback : public osg::View::Slave::UpdateSlaveCallback
是用OSG的规范
m_view对象只,有两个slave视口
gc->setSwapCallback(swapCallback); 是不是这句有影响?
最后,在swapBuffersImplementation失去线索

####################################################################
例子的代码可读性是有的, OpenVR文档是有的, 但我该怎么做?
如何由VR设备进行刷帧 , 我找不到, 只能由osg触发刷帧
那么, 先做出一个模型程序, 看能否取得View的刷帧事件回调函数.
就先用NodeCallback StackOverflow上,也是这么讲的
现在输出了整个矩阵数组, 发现m[0][3] ~ m[2][3]是可以的.
那为什么Matrix44不行?
建几个立方体来作参照物
跟据参照物, 手柄的X与Z没问题
参照物,大小为.3的, 为z向
1. 对于手柄, 有个轴是反的, 倒底是手柄模型要倒置还是坐标要倒置?
2. 像机的坐标规范, 但能否用手柄的规范
状态1:放桌上
0.957936 0.0671016 0.279027 0
-0.129168 0.969042 0.210413 0
-0.25627 -0.237603 0.936947 0
-0.0621173 0.0138077 -0.157989 1 <状态2:放椅子上
0.865644 0.010904 0.500541 0
-0.125182 0.972721 0.195301 0
-0.484757 -0.23172 0.843396 0
-1.19622 -0.439365 3.71773 1 <状态3: 椅子靠窗
0.945089 0.0206033 0.326164 0
-0.101473 0.967185 0.232931 0
-0.310661 -0.253237 0.916166 0
0.0451128 -0.428728 3.79893 1 <状态4:椅子离窗
0.956137 0.0306015 0.291316 0
-0.106346 0.962934 0.247889 0
-0.272932 -0.267997 0.923951 0
-2.19937 -0.433549 3.14582 1 <完成了两只手柄开关状态的模型显示
用时10天
软件开发
2017-12-06 14:31:00
2篇前置博文:
Http服务器-第十步加入基于Mono平台的C#脚本支持 https://my.oschina.net/kkkkkkkkkkkkk/blog/4322402
架构9-引入C#混合编程-服务端概述 https://my.oschina.net/kkkkkkkkkkkkk/blog/4335583

C#自带GC算法在独立使用时无需额外处理,cpp的话各种内存管理技术也很多如智能指针。
在这里混合编程下,c#层作为脚本使用时,很容易在cpp层书写核心代码,几乎2倍内存的消耗,但是这个不是本文的重点,本文阐述他们之间的内存关系和混合管理方式。这里的方案是cpp层通过handle来指向c#,c#端进行handle和实际的对象的引用。下面通过案例来阐述这种方式。

C#层 public class GameObject : object { private long _native_handle;//cpp ptr for }; //提供全局c# 对象 c++引用 static class CppReferenceInternal { public static Dictionary global_cpp_ref = new Dictionary(); public static void Retain(object obj) { int hash = obj.GetHashCode(); if (global_cpp_ref.ContainsKey(hash) == false) { global_cpp_ref.Add(hash, obj); } } public static void Release(object obj) { int hash = obj.GetHashCode(); if (global_cpp_ref.ContainsKey(hash)) { global_cpp_ref.Remove(hash); } } }

C++层
class GameObject { public: static GameObject*Create() { GameObject *ret = new GameObject(); return ret; }; GameObject() { AllocMonoObject(); } ~GameObject() { FreeMonoObject(); } private: bool FreeMonoObject() { MonoObjectRelease(mono_object_handle); mono_object_handle = nullptr; return true; } bool AllocMonoObject() { MonoClass *cls = mono_class_from_name(mono_assembly_get_image(mono_stick_engine_dll), "", "GameObject"); mono_object_handle = mono_object_new(vm, cls); //call default ctor mono_runtime_object_init(mono_object_handle); MonoObjectRetatin(mono_object_handle); return true; } private: MonoObject * mono_object_handle = nullptr; };
这里只是其中一种管理方式的基本示例。
软件开发
2020-07-09 21:11:05
在UGUI的控件属性之中,最上方的Rect Transform一栏可以看到锚点和中心点:

锚点Anchors
控件用于定位自身的基准点
可以点击左上角的方框,在其中选择锚点的不同方式:

注意图中,黄色的小点即为锚点位置示意,比如最右下角的图表示四个锚点分别位于父控件的四个角落:
设置完之后锚点在视图之中是这样的,父控件四个角的四个三角形即为锚点:

左上角的图则表示四个锚点分别位于父控件的上方边沿(其中左边两个重合,右边两个也重合):


锚点还可以手动填写数字来设置,可以自行尝试:


2. 中心点 Pivot
中心点可以通过点击方框,按住shift来设置:
可以看到,蓝色的即为中心点,控件设置的XY坐标即为中心点到锚点的坐标,比如我们按住shift将锚点设为子控件的左上角,然后不按shift键将锚点设为父控件的中心:

此时我们修改x,y坐标,可以看到子控件的左上角(即中心点)离父控件的中心距离根据我们的设置不同而发生变化:



如果将中心点设为右下角,则xy表示子控件右下角到父控件中心的距离:


中心点还是物体旋转的中心,比如将物体的中心点设为左上角,则改变Rotation之中Z的数值,控件就会以左上角为中心旋转:


3. Alt键的功能
按住Alt键可以设置子控件的位置,比如子控件本来位置是这样的:
按住alt键,选将子控件设为父控件的中间靠右的位置:
效果就是这样:
此外,还可以让子控件填充父控件的宽度高度以及全部:


以此类推。这几个功能配合使用,能够组合出各种布局方案,可以多尝试一下
软件开发
2020-07-06 03:54:00