博客

  • 通过apt-get安装Sublime

    Sublime是一个界面很棒的文本编辑器,最简单的安装方法就是下载然后解压了。

    不过对于ubuntu而言,通过apt-get安装更加方便。

    Sublime-Text-2:

    sudoadd-apt-repositoryppa:webupd8team/sublime-text-2
    sudo apt-getupdate
    sudo apt-get install sublime-text

    Sublime-Text-3:

    sudoadd-apt-repositoryppa:webupd8team/sublime-text-3
    sudo apt-getupdate
    sudo apt-get install sublime-text-installer

    相关链接:

    Sublime Text 3
    http://askubuntu.com/questions/172698/how-do-i-install-sublime-text-2-3

  • Spring Mvc的参数注入分析

    Spring Mvc的一个强大之处在于对于各种参数注入的支持。

    除了基本的request,session等等以外,还支持RequestParam等等便利的东西。基本而言使用Spring mvc你可以直接在方法中指明你希望获得的参数,让Spring注入即可。

    参数注入的原理

    Spring Mvc中有一个HandlerMethodArgumentResolver接口,其中包含两个方法:

    publicbooleansupportsParameter(MethodParameter parameter)
    publicObject (MethodParameter parameter,ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory)throws Exception

    当请求被Spring Mvc处理并获取到对应的执行方法时,它会首先检查所需的所有参数,然后依次检查现有的HandlerMethodArgumentResolver等否支持此类型,如果可以再调用resolveArgument方法获取参数对象。

    Spring Mvc自身有RequestParamMethodArgumentResolver,RequestParamMapMethodArgumentResolver等等常用的参数解析器。

    在RequestMappingHandlerAdapter中的getDefaultArgumentResolvers方法中硬编码了它们,而且顺序是固定无法改变的。

    Cookie的注入

    只要实现了HandlerMethodArgumentResolver就可以实现参数注入,以Spring Mvc自带的ServletCookieValueMethodArgumentResolver。

    因为从request从取出cookie值本身是一件相对简单的事情,所以这个类是一个很好的学习的例子。

    @Overrideprotected ObjectresolveName(String cookieName, MethodParameter parameter, NativeWebRequest webRequest)throws Exception {
    HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
    Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName);
    if (Cookie.class.isAssignableFrom(parameter.getParameterType())) {
    return cookieValue;
    }elseif (cookieValue !=null) {
    returnthis.urlPathHelper.decodeRequestString(servletRequest, cookieValue.getValue());
    }else {
    returnnull;
    }
    }

    `

    直接获取到webRequest,从中取出所需的值即可。

    自定义参数注入器

    Spring Mvc提供了很多常见的支持,但是有时候有一些特殊的需求,需要我们自己定义。

    首先创建一个对应的实现,实现supportsParameterresolveArgument

    然后将这个类配置起来

    <mvc:annotation-driven>
    <mvc:argument-resolvers>
    <beans:bean class="your resolver"/>
    </mvc:argument-resolvers>
    </mvc:annotation-driven>

    这里有个问题需要注意,那就是RequestMappingHandlerAdapter中已有的参数注入器的顺序是硬编码的,而自定义的部分在最后,所以要注意自定义的注入器不会和被Spring Mvc自带的部分解析掉。

    private List < HandlerMethodArgumentResolver >getDefaultArgumentResolvers() {
    List < HandlerMethodArgumentResolver > resolvers =new ArrayList < HandlerMethodArgumentResolver > ();
    
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(),false));
    resolvers.add(new RequestParamMapMethodArgumentResolver());
    resolvers.add(new PathVariableMethodArgumentResolver());
    resolvers.add(new PathVariableMapMethodArgumentResolver());
    resolvers.add(new MatrixVariableMethodArgumentResolver());
    resolvers.add(new MatrixVariableMapMethodArgumentResolver());
    resolvers.add(new ServletModelAttributeMethodProcessor(false));
    resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
    resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));
    resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new RequestHeaderMapMethodArgumentResolver());
    resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
    
    resolvers.add(new ServletRequestMethodArgumentResolver());
    resolvers.add(new ServletResponseMethodArgumentResolver());
    resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));
    resolvers.add(new RedirectAttributesMethodArgumentResolver());
    resolvers.add(new ModelMethodProcessor());
    resolvers.add(new MapMethodProcessor());
    resolvers.add(new ErrorsMethodArgumentResolver());
    resolvers.add(new SessionStatusMethodArgumentResolver());
    resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
    
    if (getCustomArgumentResolvers() !=null) {
    resolvers.addAll(getCustomArgumentResolvers());
    }
    
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(),true));
    resolvers.add(new ServletModelAttributeMethodProcessor(true));
    
    return resolvers;
    }

    参考资料

    Spring 3.1+ argument resolver cannot handle proprietary Person class argument

  • 修改Gradle中Wrapper版本

    一直在用Gradle,它提供的wrapper是一个很实用的功能。

    可以快速切换版本,保证构建的一致性,还可以方便没有安装Gradle的用户,也能保持构建工具版本的一致性。

    启用Wrapper以后会创建一个gradle目录,其中包含一个gradle-wrapper.properties文件,内容一般如下:

    distributionBase=GRADLE_USER_HOME
    distributionPath=wrapper/dists
    zipStoreBase=GRADLE_USER_HOME
    zipStorePath=wrapper/dists
    distributionUrl=https://services.gradle.org/distributions/gradle-2.0-bin.zip

    偶尔需要修改Gradle Wrapper版本的时候我会直接修改最后一行的distributionUrl参数。

    今天看文档才发现了一个Wrapper任务,每次的修改只需要修改这个任务,然后运行就会自动创建或者修改上面那个文件了。

    task wrapper(type: Wrapper) {
    gradleVersion = '2.0'
    }

    之后运行./gradlew wrapper即可。

  • Mockito中RETURNS_DEEP_STUBS对于enum的处理

    Mockito是一个测试常用的库,可以快速模拟对象。

    加上对于Junit的支持不错,配有对应的Runner,使用起来也是相当方便。

    一般使用

    最常用的使用自然是SomeClass some=mock(SomeClass.class),如果你使用注解,还可以写成

    @Mock
    private SomeClass some;

    如果这个类有这样的方法some.getObjectOne().getValue(),我们是不能直接使用given或者when断言简单mock的,需要使用

    ObjectOne one=mock(ObjectOne.class);
    given(some.getObjectOne()).willReturn(one);
    given(one.getValue()).willReturn("value");

    Mockito提供了RETURNS_DEEP_STUBS模式,可以自己处理这种情况,如果我们使用SomeClass some=mock(SomeClass.class, RETURNS_DEEP_STUBS),我们就只需要一句简单的

    given(some.getObjectOne().getValue()).willReturn("value");

    就解决了。

    对于Enum的处理

    RETURNS_DEEP_STUBS本质上调用的ReturnsDeepStubs类。

    这个类在org.mockito.internal.stubbing.defaultanswers下面。

    public Objectanswer(InvocationOnMock invocation)throws Throwable {
    GenericMetadataSupport returnTypeGenericMetadata = actualParameterizedType(invocation.getMock()).resolveGenericReturnType(invocation.getMethod());
    Class < ?>rawType = returnTypeGenericMetadata.rawType();
    if (!mockitoCore().isTypeMockable(rawType)) {
    return delegate().returnValueFor(rawType);
    }
    return deepStub(invocation, returnTypeGenericMetadata);
    }

    在方法中检测了该对象能否mock

    publicbooleanisTypeMockable(Class < ?>type) {
    return ! type.isPrimitive() && !Modifier.isFinal(type.getModifiers());
    }

    就是检查了一下基本的东西,比如是否final。

    然后获取了对应类型的默认值。

    然而对于Enum是没有默认值这种说法的,所以直接返回了null,这样的话直接使用类似

    some.getObjectOne().getSomeEnum().getValue();

    是会出现NPE的。

    参考资料

    ISSUE-207

  • 自定义Homebrew的Formula

    Homebrew是Mac Os X系统下一个非常优秀的软件包管理工具。使用包管理工具的优势就是省心,不再需要在网页上下载安装或者下载后解压再配置环境变量了,只需要使用brew install就可以完成安装了。

    Homebrew支持很多热门的应用和软件,而每一个支持都是一个.rb文件提供的,成为Formula。

    虽然官方支持上千种Formula,但是总有需要自己自定义的时候。

    本文以Moco为例子,创建一个Formula。

    基本内容

    Moco提供了一个命令行的jar,这也是最简单的情况,我们不需要下载编译,而是直接下载,然后链接就行了。

    我们需要的是一个standalone的jar包,版本是0.9.2,地址在这里。

    首先创建一个Formula,运行命令行brew create moco,编辑生成的moco.rb文件,添加内容

    require"formula"
    class Moco < Formula
    homepage"https://github.com/dreamhead/moco"
    url"http://central.maven.org/maven2/com/github/dreamhead/moco-runner/0.9.2/moco-runner-0.9.2-standalone.jar"
    sha1"a21445d7b275c48874ed7756477ade74de299e17"
    def install
    libexec.install"moco-runner-0.9.2-standalone.jar"
    bin.write_jar_script libexec/"moco-runner-0.9.2-standalone.jar","moco"
    end
    end

    这是一个基本的情况,我们下载对应地址的jar包,然后用命令moco链接。

    完成后运行brew install moco就可以看到效果。

    添加测试

    Homebrew提供了测试方法以测试Formula的可靠性,最简单的就是调用--help或者--version命令行。

    require"formula"
    class Moco < Formula
    homepage"https://github.com/dreamhead/moco"
    url"http://central.maven.org/maven2/com/github/dreamhead/moco-runner/0.9.2/moco-runner-0.9.2-standalone.jar"
    sha1"a21445d7b275c48874ed7756477ade74de299e17"
    def install
    libexec.install"moco-runner-0.9.2-standalone.jar"
    bin.write_jar_script libexec/"moco-runner-0.9.2-standalone.jar","moco"
    end
    testdo
    system bin/"moco","--help"
    end
    end

    运行brew test moco可以看到效果。

    更复杂的测试

    单单是打印一个版本或者帮助信息确实太少了。

    在Moco的官网有一个简单的例子,启动一个返回Hello,Moco的服务。测试依照此修改为启动Moco,然后访问对应的网址获取信息,对比信息是否为Hello,Moco。

    调用ruby中的net/http来获取信息。

    require"formula"
    require'net/http'
    class Moco < Formula
    homepage"https://github.com/dreamhead/moco"
    url"http://central.maven.org/maven2/com/github/dreamhead/moco-runner/0.9.2/moco-runner-0.9.2-standalone.jar"
    sha1"a21445d7b275c48874ed7756477ade74de299e17"
    def install
    libexec.install"moco-runner-0.9.2-standalone.jar"
    bin.write_jar_script libexec/"moco-runner-0.9.2-standalone.jar","moco"
    end
    testdo
    port=12306
    (testpath/'config.json').write <<-TEST_SCRIPT.undent
    [
    {
    "response":
    {
    "text":"Hello, Moco"
    }
    }
    ]
    TEST_SCRIPT
    startMoco=Thread.newdo
    system(bin/"moco","start","-p", port,"-c",(testpath/'config.json'))
    end
    sleep5#wait moco start
    actualResponse=Net::HTTP.get(URI('http://localhost:'+port.to_s))
    if(actualResponse!='Hello, Moco')
    onoe"Error! The response is not right."
    end
    startMoco.exit
    end
    end

    运行的效果可以查看这里Pull Request #32742
    参考资料

    Formula Cookbook

  • 在游戏开发中应该使用测试驱动开发吗?

    测试驱动开发(TDD)逐渐成为越来越流行的实践,它提升了代码的易用性和独立性,也增强了开发信心。

    TDD作为一种实践自然不存在不适用的领域,但是在一些领域它是具有挑战性的,比如游戏开发。

    知你所愿

    测试驱动开发在游戏开发中应用的最大挑战并不是来自UI。
    其核心问题是你通常并不清楚你的UI是什么样的。UI是你必须面对的东西,而和它的交互更多时候是一个深层次的东西。

    游戏的本质是状态机,而获得某种状态或者到达某种状态有时候需要付出相当大的代价。

    测试这些东西是困难而且浪费的。

    Uncle Bob曾说过:‘不要TDD UI’。

    对于UI做纯粹的TDD是困难的,但是对于部分做TDD确实可能的。

    比如游戏中的算法,状态机本身的变化等等。

    对于UI它的确不合适,但是对于其他部分,TDD是一个不错的选择。

    单一责任原则

    遵循SRP原则是一个很好的补充。

    将你的代码分离出来,从某种意义来看这样是为了测试而改变,但是从长远来看,也是一种很好的维护方法。

    有很多事情是可以做到的,比如将可以测试的算法部分从UI中剥离,分离一些不同原因而存在的代码。

    有一点是需要牢记的,有些代码第一次看的时候很难测试,但并不意味着第二次的时候仍然很复杂。

    在流程上的妥协并不是什么大问题,首先使用你喜欢的方法写出代码,然后编写测试。

    你需要预防的只有一次性写完整个游戏再进行测试的冲动。

    在事后写测试的问题在于代码耦合,进而难以测试那些重要而有用的类。如果你发现你写的代码难以测试,请尽量遵循依赖倒置原则和开闭原则。

    这样才能保证代码解耦足够支撑你事后测试。

    实体系统

    将代码分离,说着很容易,操作起来确实困难的。

    如果分离?依照何种标准归类?

    Entity System本身就是一种分离规则,将整个游戏分离成组件、实体和系统。

    Libgdx有Ashley支持,它是一个轻量级的实体系统。

    一旦整个项目跟随这个规则来拆分,大部分代码都是可以测试的。

    游戏的核心部分将归入系统之中,而系统本身和绘制,即UI是可以分离的,这样游戏中的各种状态和情况都可以很快速的测试出来。

    BDD合适吗?

    BDD本身是一种设计方式,换一种方法思考本身并不能改变什么。

    从本质上讲,二者也不是一种东西。

    参考资料

    IEEE:TDD
    BDD
    Game engine 101

  • 升级Libgdx到最新版本

    升级Libgdx到最新版本

    Libgdx现在已经是1.X时代了,但是手上很多项目都还是在0.9.X上面,感觉越来越不顺手了。

    没法使用最新版本就意味着对于iOS的支持还停留在以前的解决方案中,即Xamarin+Monotouch。Xamarin收费不说,主要是慢而且要收费。

    看着别人快乐的使用最新版本,实在受不了了,开始升级之旅。

     

    基础Gradle配置

    最简单的办法应该是直接自己根据项目配置专属的build.gradle配置,然后将原有依赖移除,重新构建项目就行了。

    但是具体操作中感觉略麻烦,所以最后选择了新建初始化的项目,然后直接将旧的代码拷贝过去。

    建立初始化项目比较简单,直接使用setup工具就行了。

    我以DartsShaSha为例,这是一个简单的示例程序。

    setup

    创建好初始化项目以后直接拷贝原有的项目文件过来,然后运行./gradlew idea创建项目并引入Intellij IDEA。

    基础配置调整

    首先新版本已经移除了对于OpenGL ES 1.0的支持,原有配置的中的config.useGL10不再支持,默认启用OpenGL ES 2.0,config中可以不再书写。

    对于配置中的useGLSurfaceView20API18这一项,默认为false,就不要轻易改动了。

    Stage的缩放

    新增的Viewport类增强了Libgdx的屏幕自适应能力,原有的构造函数Stage(480,320,true)就不再适用,可以考虑使用new Stage(new StretchViewport(480, 320));替换。

    除了StretchViewport以外,可以选择的还有FitViewport、FillViewport、ScreenViewport、ExtendViewport和CustomViewport。

    一般情况用StretchViewport就够用了,不需要自己实现一个。

    一些细节

    有些细节有点复杂,比如Actor中的public void draw(SpriteBatch batch, float parentAlpha)变成了public void draw(Batch batch, float parentAlpha)

    我最开始没有注意到这个问题,绘制中很多东西都没有绘制出来,感觉特别奇怪。

    参考资料

    Viewport

  • Ashley使用实例

    上一篇文章提到了Ashley,但是没给出具体的例子,加之wiki资料不全,可能使用上有些困难。

    superjumper是Libgdx的一个demo,siondream使用Ashley改造了整个项目,特别适合学习入门。

    地址是:https://github.com/siondream/ashley-superjumper

    组件

    components是entity system的主要构成,这里就成为组件吧。

    组件只包含具体的值,比如宽度,高度,状态等等。

    比如我们的主角Bob

    publicclass BobComponent extends Component {
    publicstaticfinalint STATE_JUMP =0;
    publicstaticfinalint STATE_FALL =1;
    publicstaticfinalint STATE_HIT =2;
    publicstaticfinalfloat JUMP_VELOCITY =11;
    publicstaticfinalfloat MOVE_VELOCITY =20;
    publicstaticfinalfloat WIDTH =0.8f;
    publicstaticfinalfloat HEIGHT =0.8f;
    
    publicfloat heightSoFar =0.0f;
    }

    而对于金币Coin

    publicclass CoinComponent extends Component {
    publicstaticfinalfloat WIDTH =0.5f;
    publicstaticfinalfloat HEIGHT =0.8f;
    publicstaticfinalint SCORE =10;
    
    publicstaticfinalint STATE_NORMAL =1;
    }

    对于整个游戏的状态,比如时间、分数等等

    publicclass StateComponent extends Component {
    privateint state =0;
    publicfloat time =0.0f;
    
    publicintget() {
    return state;
    }
    
    publicvoidset(int newState) {
    state = newState;
    time =0.0f;
    }
    }

    这些都是游戏中需要用到的Value Object,但是对于游戏常有的一些组件也需要处理,比如Camera和Texture

    publicclass TextureComponent extends Component {
    public TextureRegion region =null;
    }
    publicclass CameraComponent extends Component {
    public Entity target;
    public OrthographicCamera camera;
    }

    系统

    System是整体的逻辑所在,但是System和Component不是一一对应的。

    以人物的移动为例,这个MovementSystem需要MovementComponent和TransformComponent。

    MovementSystem需要处理的就是人物的移动,读取当前的移动方向,然后改变坐标.

    MovementSystem继承Ashley的IteratingSystem类。

    publicclass MovementSystem extends IteratingSystem {
    private Vector2 tmp =new Vector2();
    privateboolean pause =false;
    
    publicMovementSystem() {
    super(Family.getFamilyFor(TransformComponent.class, MovementComponent.class));
    }
    
    @Override
    publicvoidprocessEntity(Entity entity,float deltaTime) {
    TransformComponent pos = entity.getComponent(TransformComponent.class);
    MovementComponent mov = entity.getComponent(MovementComponent.class);
    
    tmp.set(mov.accel).scl(deltaTime);
    mov.velocity.add(tmp);
    
    tmp.set(mov.velocity).scl(deltaTime);
    pos.pos.add(tmp.x, tmp.y,0.0f);
    }
    
    @Override
    publicbooleancheckProcessing() {
    return !pause;
    }
    
    publicvoidpause(boolean pause) {
    this.pause = pause;
    }
    }

    再看看StateSystem,对于SuperJumper而言,主要就是记录时间,所以相对简单。同样继承于Ashley之中的IteratingSystem类。

    publicclass StateSystem extends IteratingSystem {
    privateboolean pause =false;
    
    publicStateSystem() {
    super(Family.getFamilyFor(StateComponent.class));
    }
    
    @Override
    publicvoidprocessEntity(Entity entity,float deltaTime) {
    entity.getComponent(StateComponent.class).time += deltaTime;
    }
    
    @Override
    publicbooleancheckProcessing() {
    return !pause;
    }
    
    publicvoidpause(boolean pause) {
    this.pause = pause;
    }
    }

    使用

    Ashley的使用主要就是声明所需的System,然后把所有组件都加入Engine就可以了。

    world =new World(engine);
    
    engine.addSystem(new BobSystem(world));
    engine.addSystem(new SquirrelSystem());
    engine.addSystem(new PlatformSystem());
    engine.addSystem(new CameraSystem());
    engine.addSystem(new BackgroundSystem());
    engine.addSystem(new GravitySystem());
    engine.addSystem(new MovementSystem());
    engine.addSystem(new BoundsSystem());
    engine.addSystem(new StateSystem());
    engine.addSystem(new AnimationSystem());
    engine.addSystem(new CollisionSystem(world, collisionListener));
    engine.addSystem(new RenderingSystem(game.batcher));
    engine.addSystem(new RemovalSystem());
    
    engine.getSystem(BackgroundSystem.class).setCamera(engine.getSystem(RenderingSystem.class).getCamera());
    
    world.create();

    在update中直接调用engine.update(deltaTime);即可。

    Ashley内置的系统

    Ashley目前只内置了一种系统,即IteratingSystem。

    它对于每一个实体都调用processEntity()来处理。

    如果有其他需要可以通过继承EntitySystem类来自己扩展。

    参考资料

    Ashley文档

  • libGDX的实体框架Ashley

    游戏开发之中有时候会使用实体框架,比如Artemis。

    在游戏开发中使用实体框架可以极大的提升项目层次和可测试性。

    比如利用Artemis,你的游戏代码可能是

    publicclass MyGame {
    
    publicvoidMyGame() {
    world =new World();
    
    world.setSystem(new MovementSystem());
    world.setSystem(new RotationSystem());
    world.setSystem(new RenderingSystem());
    
    world.initialize();
    
    while(true) {
    world.setDelta(MyGameTimer.getDelta());
    world.process();
    }
    }
    }

    Ashley

    Ashley并没有提供特别的功能,它是一个很简单的实体框架。

    它属于Libgdx大家族,但它并不是选用的,你可以继续使用Ash或者Artemis。

    使用的时候在你的gradle配置中加上

    dependencies {
    compile"com.badlogicgames.ashley:ashley:1.0.1"
    }

    就行了。

    具体的使用方法稍后Wiki也会放出的。

    最核心的类是EntitySystem。

    参考资料

    实体系统的好处
    API

  • Hexo升级到2.8

    Hexo升级到2.8

    Hexo使用的Yaml Parser做出了一定的修改,所以很有可能在升级Hexo以后会出现HexoError错误。

    [error] { name:'HexoError',
    reason:'incomplete explicit mapping pair; a key node is missed',
    mark:
    { name:null,
    buffer:'categories: Categoriesnsearch: Searchntags: Tagsntagcloud: Tag Cloudnprev: Prevnnext: Nextncomment: Commentsncontents: Contentsnarchive_a: Archivesnarchive_b: Archives: %snpage: Page %dnrecent_posts: Recent Postsnmenu: Menunlinks: Linksnrss: RSSnshowsidebar: Show Sidebarnhidesidebar: Hide Sidebarnupdated: Updatednnextpost: NEXTnprevpost: PREVnu0000',
    position:167,
    line:9,
    column:19 },
    message:'Process failed: languages/default.yml',
    domain:
    { domain:null,
    _events: { error: [Function] },
    _maxListeners:10,
    members: [ [Object] ] },
    domainThrown:true,
    stack:undefined }

    具体的修改很简单,用引号包裹字符串就行了,比如

    archive_b: Archives: %s => archive_b:"Archives: %s"

    具体需要修改的还有

    文件

    只需要关心languages/default.yml,其他的不需要修改。