Google Play上线的游戏,C#服务端如何校验内购订单

准备工作

  1. 在Google Play Console中启用API权限
  2. 在Google Cloud中准备好服务账号
  3. 坑:如果游戏内购商品早于服务账号创建,需要修改一下内购商品的信息,更新一下,才能真的获得权限

1. 在Google Play Console中启用API权限

打开Google Play Console后台的首页(注意:不是某个游戏管理的首页)。

如果你是第一次打开这个页面,会提示你是否启用。点击启用按钮,会问你是否绑定Google Cloud、Google Developer等等,一路确定就好了。

2. 在Google Cloud中准备服务账号

然后点击 创建新的服务账号,根据指引进入 Google Cloud 的后台进行创建,名字取好,其他都默认就可以了。

创建完以后,很重要的一步,是要创建密钥。

选择默认的json密钥就可以了。点击创建以后,会自动下载一个json文件。这个json文件待会在代码中会用到。这个文件就是这个账号的密钥了,一定要保存好,因为之后就没有机会让你再下载第二次了。

这个时候返回 Google Play Console 刚刚的页面,刷新一下服务账号,就可以看到刚刚创建的服务账号了。然后根据你自己的需要对这个账号进行授权,就可以了。

3. 没有权限的问题解决

如果做完了以上两步,直接进行下面的代码测试,发现返回错误

那恭喜你,遇到了Google的坑。这是由于你的服务账号创建以及绑定权限晚于内购商品的创建造成的。不过问题也比较容易解决,只要修改一下内购商品的信息,更新一下,就能真的获得权限了。

代码以及测试

API

我先找到了这个API文档,
https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.products

它一上来就告诉我,会返回什么值,但是就是不告诉我怎么发起请求。

于是我又找到了Google Api的C#封装:https://github.com/google/google-api-dotnet-client

并且提供了nuget支持:https://www.nuget.org/packages/Google.Apis.AndroidPublisher.v3

那一切就好办了

代码

测试

包名和商品ID都是固定的,所以我们只要拿到一个 PurchaseToken 就可以进行测试了。如果你以为测试一定要集成到app中,才能得到 PurchaseToken,那就错了。其实我们在Google Play Console的后台,就能获得这个 PurchaseToken 了。打开Console默认页(非单款游戏管理页面),左边的菜单中,有个 订单管理,进入 订单管理,选择你想测试的游戏的订单,点击箭头,进入订单详情,在详情中,就可以复制 PurchaseToken 进行测试了。

参考

  1. https://stackoverflow.com/questions/64217553/purchases-products-get-is-ignoring-productid-value-and-returns-null-in-productpu
  2. https://stackoverflow.com/questions/43536904/google-play-developer-api-the-current-user-has-insufficient-permissions-to-pe

C#中使用Pomelo进行MySql的原生Json查询

Mysql已经原生支持json,有5年多了。但我还没有用过,搜了一圈,MySql本身的语法还是很好写的

但是有些语言偏偏搞了很复杂的封装,比如C#。我做过的项目,主要用Pomelo库来连接MySql。而Pomelo库曾经做了一套JsonObject的方案来使用MySql的json,后来又弃用了。但是网上大量留下了JsonObject的教程,这是多么尴尬的局面。最尴尬的是Pomelo库本身并没有编写完整的文档,所以能搜到的信息,基本都是误导信息。经过一番探索,我总结一下目前正统的方案。

方案开始

首先安装必要的库,我这里使用的是Microsoft的Json序列化方案,因为它支持递归引用的序列化,所以更推荐用它来做序列化。

经过验证,以下代码是无意义的,并且可能导致运行时异常。
然后需要在Startup.cs中的ConfigureServices,添加以下代码

完整示例

最后,在需要使用json查询的位置,使用

就能生成这样的Sql代码

参考

在WinForm项目中使用Windows Runtime的方法

在WinForm项目中使用Windows Runtime的方法

最近需要在Winform项目中使用蓝牙,蓝牙模块是同事负责的,找了好多版本的蓝牙库,对BLE的支持都不好。最后发现系统直接提供了Windows.Devices.Bluetooth这个库可以用,但是只能在Universal项目中使用。试过在nuget中找到的Target.WindowsRuntime,但是根本不能用。经过一番google,发现可以用hack的方法在Winform中使用,特此记录。

PS: 我用的是vs2015,win10,.net 4.5,据说win8 vs2013也是可以的,我没有测试过。如果使用其他版本的操作系统,或者.net版本,请自行修改对应参数测试。

步骤说明

  1. 手工修改csproj项目文件
  2. 添加对Windows.XXXXX库的引用
  3. 添加project.json配置文件
  4. 添加对WindowsRuntime库的引用

修改项目文件

需要关闭项目工程文件,手工在目标csproj文件中添加如下代码

你期望编译的目标操作系统是win10,就写10.0,如果是win8,就写8.0,以此类推。

上个截图,更容易理解

添加对库的引用

这个时候,启动sln工程文件,然后右键点击引用-添加引用...,会发现,左侧的分类,多了一类Universal Windows

赶紧把需要的库加进来吧,加进来以后,发现代码中可以正常引用了。

但是会编译不过,提示

添加project.json文件

上面的错误,提示我们需要project.json,在项目中新建这个名称的json文件,然后复制下面的内容

其中的v4.5可以改成任意你需要的.net版本号。

再编译一次试试,大功告成,这样就可以顺利编译通过了。

添加对WindowsRuntime库的引用

这个时候虽然编译通过了,但是实际使用Windows相关类库的时候,还是会有问题,需要做最后一步操作来解决这个问题。

继续添加引用,并选择从文件添加,在下面的目录中,找到System.Runtime.WindowsRuntime.dll,并加入引用。

如果需要用到async/await,还需要添加对Windows.winmd的引用,在下面的目录中

这样,就可以在WinForm项目中使用Universal的类库啦。

参考资料

CocosBuilder的Cocos2dx-lua绑定方案

CocosBuilder的Cocos2dx-lua绑定方案

前情提要

似乎写每一份代码,每一篇文章,都有些理由,所以忍不住要写个前情提要。

CocosBuilder虽然已经2年没有更新了,已经被SpriteBuilder所取代,但是由于我们早期的项目使用的就是CocosBuilder,而SpriteBuilder增加的功能并没有特别出彩,没有足够的理由让我们抛弃CocosBuilder,于是沿用下来。但是如果拿不开源的CocosStudio来比较,我还是推荐使用CocosBuilder

据我所知,最早让Cocos2dx的lua版本支持CocosBuilder的,是这个项目:LuaProxy。不知道为什么,CocosBuilder官方库只做了对js的支持,甚至做了html5的支持,却完全没有考虑对lua的支持。而LuaProxy就是建立在官方对js支持的基础上,做了lua的实现。但是读完CocosBuilder中js方案的实现,以及Cocos2dxCCBReader对js方案的实现,我居然产生了,这不可能是一个人写的吧,这种想法。因为js方案与cpp方案的思路完全不同,如果是同一个人做的,这也太蛋疼了吧?(不过我没有看js那边的使用方法,只是按照LuaProxy的使用方法去理解的。)

cpp方案用了一种非常优雅的方式,加载的过程中,自定义类需要有一个对应的Loader,使用字符串来索引Loader,以帮助CCBReader生成对应的类的实例。

LuaProxy的方案的缺陷

  1. 首先最不能忍的,就是用了一个全局table ccb来替代cpp中的registerCCNodeLoader,看起来好像可以解决,但是必须事先把所有需要绑定的Controller的名字写入这个ccb里面,也就意味着很可能许多不需要的代码会被提前加载,对于lua这样的脚本语言来说,这完全是不必要的代价。
  2. 全局ccb也带来了另外一个问题,同屏加载多个相同ccbi时,动态更新数据的问题。
    • 试想这种情况:你用CocosBuilder的粒子编辑器做了一束烟花,但是粒子贴图要在几种里面随机,这个需要在代码里面实现。于是你把这个CCParticleSystemQuad绑定到了ccb的一个table中。然后需求变了,策划需要同时放多个烟花,于是同时出现的烟花,只有最后一个的贴图可以随机了。因为他们共享同一个全局ccb的table,后加载的覆盖了前面加载的。这里只是举一个简单的例子,实际应用可能比这个情况复杂得多。
  3. 嵌套CCBFile时,无法对子CCBFile做操作的问题。可能子CCBFile绑定了某个lua对象,有一些函数可以调用,有一些成员变量可以使用。但是全在全局ccb里面,父节点根本不知道子CCBFile对应全局ccb的哪个对象。

解决方案

对于lua这样的脚本语言来说,完全可以在加载ccbi的过程中,动态加载需要的lua文件,进行绑定,我们需要做的只是设定一个绑定规则。然后对CocosBuilder做一些小改动,让publish的时候,同时生成lua文件,那整个开发流程将大大被缩短。

So,为了解决上面的3个问题,我做了两件事

  1. 修改了CCBReader读取的一些方法,提供给lua读取嵌套CCBReader的方法。并且重写了lua读取ccbi的代码。
  2. 修改了CocosBuilder的源代码,让它在publish的时候,直接生成对应的lua文件。

准备工作

1.下载以下cpp文件,覆盖掉cocos2dx的相应文件,然后重新编译Runtime

PS: 值得注意的是,我修改的是cocos2dx 3.2,不确定覆盖更高或者更低的版本会不会出问题,如果不是3.2的用户,可以查看这个commit,自行修改相应代码。

2.下载以下lua文件,放到lua项目的src对应目录下

3.获取修改过的CocosBuilder

有2个方法

  1. 从Github clone源代码自己编译:https://github.com/jennal/CocosBuilder
  2. 下载我编译好的版本:https://github.com/Jennal/CocosBuilder/blob/master/Release/CocosBuilder.app.tar.gz?raw=true

PS: 值得注意的是,由于我们的项目使用的默认设计尺寸是1200x800,所以代码中的缩放比例都是针对这个分辨率的。修改也不难,在这里:CocosBuilder/ccBuilder/ResolutionSetting.m,把顶部的两个define改成你自己要的设计尺寸就行了。

使用方法

改完Cocos2dx的框架代码,下载了lua代码,有了Cocosbuilder的修改版,准备完成,正式进入使用阶段。

1.ccb文件制作

ccb文件的制作基本没有什么特别,只是有几个点需要注意:

  1. Publish Settings里面多了个选项,记得勾起来。新建项目,默认是勾的。
    Publish Settings
  2. 文件名与该文件的Controller名,必须保持一致,这是为了简化接口,也为了让代码与设计统一
    文件名与该文件的Controller名,必须保持一致

2.代码绑定

生成的lua文件已经包含了一些注释,几乎不用有太多额外的代码,就可以很容易地绑定ccbi文件与lua类。

下面讲解几个需要注意的地方

  1. CCBLoader:setRootPath的两个参数

  1. 生成的lua文件的ctor相当于cpp中使用ccbi的onNodeLoaded,换句话说,代码执行到这里的时候,这个节点的子节点以及绑定都应该已经完成,可以放心使用了。可以在这里做一些初始化数据的工作。
  2. 创建ccbi节点的方法

示例下载

https://github.com/Jennal/CocosBuilder/blob/master/Sample/CCBSample-lua-binding.tar.gz?raw=true

感谢

虽然LuaProxy的方案并不完美,但也为我的改进铺平的道路,在这里感谢LuaProxy的作者shawnclovie

lua的面向对象库

接上一篇文章cocos2d-x中lua-binding的面向对象开发的研究,终于有时间整理一下我自己写的面向对象方案。
先上github地址:https://github.com/Jennal/LuaUtils/blob/master/src/Oop.lua

为什么要写这个库?

我们一直使用cocos2d-x来作为底层框架进行开发。之前用的是cpp,最近转到lua,所以难免保留一些cpp的习惯,包括饱受诟病的多继承。我不想讨论多继承有多好,或者有多糟。我认为,作为库来说,应该尽量提供符合大家习惯的机制,至于是否使用,如何使用,取决于用户。

我们知道lua在语言级别并没有支持类,而是提供了更灵活的metatable,我们可以利用metatable来实现类似cpp的类。cocos2d-x的lua绑定提供了类的支持,支持创建lua级别的类,也支持从cpp继承,但是对于面向对象机制来说,只提供继承,远远不够。

我想要的面向对象机制

  • 多层级继承
    • 从lua继承
    • 从lua继承后的lua继承
    • 从cpp继承
    • 从cpp继承后的lua继承
  • 单继承
  • 多继承
    • 调用不同父类的构造函数
    • 调用不同父类的同名函数
    • 多继承不允许从2个或者以上的cpp继承
  • 接口继承
    • 接口用来约束必须实现的成员函数
  • 判断是否是某个类的实例
    • 包括是否是某个父类的子类的实例

如何使用我的库

单继承

从lua创建对象

从cpp创建对象

多继承

纯lua多继承

lua与cpp的多继承

接口

判断是否某个类或接口的实例

在MacOSX下用cmake编译cocos2dx的笔记

最近项目想用Qt来做cocos2d-x的跨平台编辑器。由于对Qt和OpenGL都是新手,所以想用相对低成本的方法来做,参考了不少项目。发现好多人有用Qt来做cocos2d-x的编辑器的想法,包括cocos2d-x官方,但不知道什么原因,几乎所有的项目都中途流产了。大部分项目使用的是相对较旧的cocos2d-x版本,有些甚至无法编译成功。不过这些项目也给了我不少帮助,参考了很多有价值的信息。

最后,我决定自己做一个QtPort,选用CMake来做项目管理,因为新版的cocos2d-x已经带了CMakeLists.txt,如果直接用Qt的pro/pri/prf等项目文件,那工程量就太大了。

工具

otool

这个工具帮了很大忙,因为使用CMake,所有需要链接的库都需要自己设置。而我不知道需要哪些库,利用otool -L xxx来查看用XCode编译出来的xxx使用了哪些库,也让我顺利把所需的库都加进来。

安装组件

首先要用到的就是HomeBrew了。安装完HomeBrew,就可以用它安装各种东西了。

这里教大家一个小技巧,当编译提示

这里的-l后面就是缺失的库的名字,可以把这个名字直接放到brew install后面进行安装。
如果名字不对的话,也可以使用brew search freetype进行搜索。

问题

  • Spine库的CCSkeleton.cpp文件会编译不过,我们也用不到Spine库,所以暂时不编译它。
  • Audio相关的库编译不过,我在CMakeLists里加了个AUDIO_BUILDoption的选项,也暂时关闭它。
  • 编译lua时遇到了比较大的问题
    • 首先,因为我们禁用了Spine和Audio的编译,但是cocos2dx的tolua代码中,并没有根据这个条件编译进行判断,是否包含这些文件。为了防止使用这些文件出问题,我加了#ifdef来判断,当定义了不要Spine或者Audio时,把相关的lua函数都注册成空函数。
    • 然后是luasocket的问题,cocos2dx官方根本就没有写luasocket的CMakeLists.txt,不过写起来还算顺利,唯一需要注意的是luasocket的wsocket.c和usocket.c,是需要分平台编译的,wsocket.c是针对Win平台写的,usocket.c是针对unix-like系统写的。

关于CMakeLists的代码

好像没有特别需要说明的,基本就是CMake的规则,唯一值得一提的是,MacOSX下面的编译,记得这么做

这样生成的目录结构才会是这样的

最后

最后给出我的项目地址:https://github.com/Jennal/cocos2dx-3.2-qt

[转]javascript中判断变量是否为array

javascript中要判断一个变量是否为array通常是比较困难的,因为

通常的做法是判断

可是这个方法有一个问题,如果一个数组是来自另一个frame中的,那么它的constructor 将是另一个对象。
YUI中用了如下的方法:

简单来说,就是判断这个变量有没有 length 属性,同时有没有 splice 方法。可惜,这样的判断方法是可以用一个 object 来伪造数组的。
Google 的 Mark Miller 发现,用下面方法,可以简单又可靠的得到一个变量是否一个数组:

转自:http://stauren.net/log/fqibpxyz3.html

更新一下我的jlUtil

修正了几个BUG,已经在实际项目中使用,没有问题。

WordPress在首页文章显示评论的方法

前一阵给TTT弄了个博客:http://ttt.jennal.cn/
TTT周五有个要求,她要博客的首页可以看到评论,不要点进去才能看到,那样太麻烦。
在网上找了很多资料,没有找到单独对这个进行介绍的。所以只好找了其他很多资料,加上不断的尝试,昨晚做到快4点,为了防止这次伟大的事件在我的记忆中被删除,还是写下来纪录一下。
要在首页显示评论,我们需要修改四个文件(可能你的文件命名会有些不同,没有关系)。
这些文件都位于wp-content/themes/我的主题目录下

index.php很简单,在你要放评论的地方加上如下代码,当然要放在日志的循环里面:

$withcomments 是一定要设置的,首页默认为0
至于comments_template这个函数,它会帮你comments先取出来,供你的模板使用。如果有看文档的话,应该知道有一个wp_list_comments函数,这个函数的参数没有接受postId,它是不做数据库查询工作的,具体它是怎么工作的,我也没有深究,只是用到它的回调功能来自定义我们的comments列表,这个在下面会提到。
下面我们来看一下homepage_comments.php怎么写

本来以为直接在comments模板里面写结构和样式就可以了。但是发现不行,除非直接去取$wp_query->comments,但是这样不好,最好还是调用它的API,不然以后版本升级可能要出问题。
这里我把wp_list_comments的全部参数都列出来了,其实没有必要,只要设置你想要的参数就可以了。
这里要补充一下需求,我不希望把所有的评论都列出来,我只要列出最新的3条,而且要倒序,就是最新评论的在最上面。所以我把per_page设置成3,reverse_top_level设置成true。但是这样有个问题,显示的时候是显示最后一页,可能你有4条评论,那它只会显示第4条。在不改变和添加新API的情况下,我没想出解决的办法。如果在看这篇文章的你知道怎么解决,请告诉我。顺便说一下,type如果设置成别的可能会出问题。我之前就学别的模板,把它设置成comments,结果每个post的comment全变成一样了。然后设会all就好了,这个问题也不去深究,有兴趣的自己看源码/wp-includes/comment-template.php。
重点来了,重点是'callback' => 'homepage_comments'这个参数。顾名思义,这是设置回调函数,那homepage_comments就是函数名。为了代码的整洁和统一,我把这个函数放在functions.php里了。那在循环遍历comments的时候,就会调用这个函数。
下面来看functions.php这个文件

这个太长了,就不一一详细介绍了,重点讲一下$GLOBALS['comment'] = $comment;,这个是为下面那些API作准备的,类似comment_text()等。这个主要是结构,HTML和PHP混合在一起,所以代码比较乱。但是wp的API函数都能顾名思义出来,需要改结构的请自行调整。
最后我们看一下样式文件comments.css

OK,一切搞定,如果出问题请自行解决,哈哈哈,本人概不负责。
可惜现在没有示例网站可以看,因为TTT看了首页显示出评论的页面以后说“但我现在想让它变回去”。唉。。辛苦了半天。。算了,至少提高了职业技能,哈哈哈