博客

  • 利用统一代理感知模型增强游戏体验

    利用统一代理感知模型增强游戏体验

    随着游戏设计开发技术的提升,加之游戏设备性能的上升,玩家越来越期待更逼真、更真实的感知。而代理感知模型就是一种简单的方法,具体而言包括了听觉模型和视觉模型。

    简单的感知模型容易让游戏本身无趣起来,最常见的例子就是利用离散距离来检查实现。这种情况下,玩家可以很容易的利用绝对盲区将AI逐一击破。

    这是一些简单的改进方法,基础但是可以提升AI的“聪明”程度。

    基本视觉模型

    先来看看基本的视觉模型。一般依次使用这三种计算,分别为视距、视锥和视线。

    视距是一个相当廉价的计算,一般情况计算平方从而避免平方根。

    举例而言,一个代理的视距最远为5个单位,而它的坐标为(0,0,0),当玩家位于(4,2,2)时计算结果为16+4+4=24<25,所以代理可以看到玩家。

    视锥测试是一个中度计算,因为需要一个向量归一化,然后进行点乘。如果结果大于0,那么玩家位于代理的180度视锥内;如果结果大于0.5,那么玩家位于代理的120度视锥内。

    最耗时的是视距计算,这需要从代理起点计算一个通往玩家的射线,这需要考虑到高度还有中间的任何障碍物。

    说明图样

    基本听觉模型

    听觉模型是一个很划算的东西,一点点简单的处理可以让游戏体验上升一个档次。

    最基本的听觉模型是基于位置的,随着代理和玩家距离的变化调整声音的音高和音量。

    其次就是基于障碍物的处理,比如代理位于封闭性障碍物附近,那么玩家听到的声音就会有回音和混响出现。这些的实现可能比较复杂,但是SoX项目可以简单做出很好的效果。

    椭圆视觉模型

    基本视觉模型简单,但是有些生硬和不合情理。比如代理不应该看到正右方的玩家,代理应该在近处有一个较大的视野,而远处应该不断的模糊。

    当然这个问题可以使用多个视锥来叠加,可以部分解决视野的问题。

    多个视锥

    一个小的大范围视锥,一个长度很长的小范围视锥。

    不过这样的计算会多一些,因为毕竟多了一个视锥,而且视锥与视锥之间有一个缺口。

    使用一个长型椭圆就可以解决问题了。

    长型椭圆

    椭圆不经可以增加真实度,还可以简化元素,因为判断一个我们只需要计算几个三角函数就可以解决绝大部分问题。计算焦点距离,和2a比较,不再需要平方。

    确定性的视觉增强

    真实的眼睛其实没有很明确的看得见与看不见,而是更多的出于周边视觉的情况下。

    人的眼睛有两只(废话…),大约有200度的视觉范围,其中110~120度是重叠区域。当查看身后的时候,杆体细胞和视锥细胞会产生颜色和运动的敏感问题,就是说你对眼色反应很少,但是移动的东西却很明显。

    简单的确定性模型

    这其中的数字代表百分之多少,当100%能够辨识时,代理应该采取完全行动,比如高精度射击;当70%能够辨识时,代理转向以获得更高的辨识或者采取低精度射击。

    当然这个区域划分很简陋,但是我一直使用这个。如果你愿意,代理的集中度也可以影响辨识度。

    确定性的听觉模型

    听觉的模拟可以引入举例,但是单纯的举例会让人困惑,特别是在室内环境中。

    听觉的确定性模型和视觉类似,当100%辨识度是玩家能够听到,当辨识度下降时音量等开始变化。

    辨识度本身可以和音源区域,障碍物质地大小等等相关。

    听觉模型的另外一个挑战是遮掩和淹没问题。

    当脚步声出现时它是否会被枪声所淹没,当枪声响起时它是否会被路过的汽车喇叭淹没。

    统一感知模型

    在我们拥有了两个确定性模型以后统一模型就简单了,辨识度可以取最大值,比如max(v,s)或者二者的和v+s

    如果听觉系统并不是游戏的重点,那么你可以考虑比例听觉模型,辨识度可以为…

  • Libgdx使用Gradle构建速度慢的问题

    Libgdx正式推出1.0版本,其中最重要的一个变化就是正式启用Gradle模板支持。

    填写必要信息后会自动生成Gradle配置文件,通过Gradle可以创建Idea和Eclipse的项目文件,更可以快速升级和添加依赖。

    但是很多人再使用的时候,特别是第一次接触Gradle,会遇到很多问题,最关键的一个就是速度慢。本文会介绍这个问题的原因和解决方法。

    Gradle Wrapper

    Gradle的Wrapper很多文章翻译为包装器。包装器的出现是基于这样的需求,即让没有装Gradle的机器上也能正常的构建你的项目。

    它和Groovy里的Grape类似。如果目标机器上没有Gradle,包装器将先下载安装合适版本的Gradle,然后再运行相应的任务。如果有,但是版本不同,它也可以保证构建工具本身的版本相同。

    出发点是好的,但是现实确实复杂的。因为默认下载的地址http://services.gradle.org/在中国访问速度很慢很慢,而且需要下载的文件大小还很大,一般有40+M,基本上是不能下载成功的。

    对于这个问题有两种解决方法:

    • 使用本地Gradle
    • 修改下载地址

    使用本地Gradle

    这种方法实质上是不使用包装器,而是通过各种方法安装Gradle,然后直接执行。注意,执行的时候不要调用gradlew [command]而是使用gradle [command]

    这种方法简单,但是不能享受到包装器的优势。

    修改下载地址

    访问Gradle Distributions的速度慢,我们可以使用一个快的。

    Wrapper任务有一个名为distributionUrl的属性,直接修改它指定新的下载地址就行了。

    可以在build.gradle中修改,比如

    task prepareWrapper(type: Wrapper) {
    gradleVersion ='1.12'
    distributionUrl ='alternative.location'
    }
  • libGDX 1.0发布了

    也许很多人觉得这一天不会来到,但是Libgdx 1.0 今天正式发布了。

    我从2011年开始使用Libgdx,然后一直在0.9.2的某一个分支上奋斗,不断的等待正式版的发布,以便迁移过去。

    然后终于等到了。

    新特性

    其实1.0版相比0.9.9版的变化不多,主要集中在周边的支持上。

    首先是Gradle的支持。通过Gradle来初始化并管理项目就不再需要下载二进制包了,而且版本的升级也会好很多。

    Libgdx曾经使用过Maven来管理项目,也尝试过Gradle,还一度停止了Gradle模板的支持,不过正式版本确定了对Gradle的支持。

    另外一个更新就是文档上的改变,原来的文档是零散的,现在终于重新组织了。不仅包含Wiki和文章等等,还有对应的视频文件。不过视频都在Youtube上,需要科学上网一下。

    其他的都是小问题,比如移除了OpenGL ES 1.x的支持,添加了3.x的支持。参考目前的硬件支持情况,平时还是使用2.x吧。

    Box2D的绑定一直属于Libgdx的核心模块,始终和Libgdx主库在一起,这一次移动到扩展中去了。

    同时,对已以后项目的发展,也定下了一个大致的发布周期,2周到1个月一次。接下来的重点将集中在3D的支持和应用内购买扩展等等。

    Libgdx的历史

    有趣的是,这次发布说明作者回顾了Libgdx的发展历史和4年来的感受。

    项目起源于2009年中期,作者因为开发Android游戏的需要,对OpenGL ES作为封装并命名为AFX,第一个游戏名为Newton,地址是https://play.google.com/store/apps/details?id=com.badlogic.newtonfull

    作者加入了audio的支持,C#等等,这也是早期Libgdx发布到iOS平台的解决方案。

    正式的开源是在2010年3月6号,分布在Google Code上。第一个贡献者Christoph Widulle是在4月,并在5月引入了Box2D绑定。

    在2011年进入0.9版本阶段,我就是在那之后入坑的。随着部分游戏的走红,Libgdx的影响力也在扩大,比如Apparatus

    慢慢有了Freetype支持,iOS支持等。Nate加入的scene2D是我使用的最多的部分。

    Google使用Libgdx算得上一个里程碑,充分证明了外部的认可。

    其他事项

    Libgdx有了新的Setup工具,但是默认使用中央仓库,可以用oschina的镜像替换之。

    用Gradle管理项目可以直接打包到不同平台了,不需要太关心环境。

    参考资料

    libGDX 1.0 released
    Libgdx goes Gradle:

  • 在Libgdx中修正物理引擎Box2d时间步长

    在Libgdx中修正物理引擎Box2d时间步长

    文章翻译并修改自原文David Saltares。博主经验丰富,他的很多关于Libgdx的文章都值得一读,再此特作推荐。

    Fix your Timestep是Glenn Fiedler所写的一篇关于处理物理模拟的文章。这篇文章是2006年的,但是其中大部分内容在今天仍然具有启发性。

    这篇文章涉及了处理物理引擎中的时间增量的不同方法。这个问题很复杂,但是不可否认它对游戏行为和表现的影响。

    我认为将这些内容迁移到使用Box2D物理引擎的Libgdx应用中是非常好的点子。这些也可以应用到Bullet中,它是另外一款优秀的物理引擎,而且迁移的改动非常小。

    简单却错误的方法

    将时间步长设定为1/60每秒,这是非常常见而且简单的方法。它可以让物理模拟稳定运行,但是当游戏运行FPS降到60以下时,物理引擎的表现会非常糟。而这在移动设备上非常常见。

    最合乎逻辑的方案是计算现在时点在最后一帧后经过的时间,并传递给物理引擎。这样做显然会让每一帧的表现不同,整个物理引擎会表现的很不稳定。不同设备之间的表现会由于设备自身的不同(比如内存、运算器)产生变化。很明显,这不是我们期待的方案。

    固定的时间步长

    如果物理引擎以一个固定的步长,如1/60秒去运行,那么有些设备可能会运行的过快并达到120FPS。而同时有些设备可能运行过于缓慢,达到30FPS,那么物理引擎每两帧才前进一次。

    如果游戏以50FPS运行,会出现什么情况
    

    我们需要一个累加器来保存时间,然后尽可能的保持物理引擎和渲染一致。当这一帧时间过多时,我们可以将多余时间保留给下一帧。

    publicclass SionGame extends ApplicationListener {
    private World world;
    privatedouble accumulator;
    privatedouble currentTime;
    privatefloat step =1.0f /60.0f;
    
    publicvoidrender() {
    
  • 使用Robolectric模拟Http请求和响应

    习惯了用Gradle管理依赖以后突然回到Android开发,才发现Idea 12 对Android-Gradle项目支持很烂,只有手动下载依赖。

    Android App的开发比普通Java Web或者Java Application开发麻烦很多。主要是集成测试使用的Instrumented Test必须在模拟器或者真机上测试,挺耽误时间的。
    这样的测试通常粒度较大,测试的编写和维护较为困难,而最为重要的是,由于速度慢,如果使用TDD来进行开发,根本无法达到快速开发的要求。

    所以在Android开发中,尽可能多使用单元测试保证每个模块的正确性,来尽可能保证集成测试的成功率。

     

    基于Mockito的简单测试

    Android SDK也有测试框架,但是那个不是基于普通JVM的,运行起来还是需要模拟器或者真机。要基于普通JVM测试,就必须让模块尽可能的不依赖于Android的东西,需要依赖的也用Mockito等Mock掉。

    如果需要Mock的是自己的代码还好,如果是Android的东西,还需要处理各种状态和返回量。

    比如我们的APP需要调用WifiManager,如果只是检测Wifi是否开启还比较方便,只需要mock以后设定when就行了。

    WifiManager wifiManager = mock(WifiManager.class);
    when(wifiManager.isWifiEnabled()).thenReturn(true);

    如果我们需要扫描Wifi,然后检测Wifi状态等,工作就变动了。你需要仔细思考Android设备正常情况下的状态变动,还需要构造哦一个List<ScanResult>

    WifiManager wifiManager = mock(WifiManager.class);
    when(wifiManager.getScanResults()).thenReturn(Lists.<ScanResult>newArrayList());
    when(wifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED).thenReturn(WifiManager.WIFI_STATE_DISABLED).thenReturn(WifiManager.WIFI_STATE_ENABLED);

    其实以上问题还好,因为我们mock出了WifiManager以后将它传入调用的类就行了。

    publicclass LoginService {
    private WifiManager wifimanager;
    publicLoginService(WifiManager 
  • 使用MiniCluster快速配置Hadoop开发环境

    使用MiniCluster快速配置Hadoop开发环境

    前年的时候用过Hadoop,那时候各种资料缺乏,各种摸索以后写出了能用的东西,然后打包仍服务器就再也没有管过。效率什么的谈不上,但是一直能用。

    当时花费了很多时间搭建一个环境,各种xml配置,然后引入依赖包,写好以后打包然后命令行测试一下,想想就耽误事儿。

    最近需要改动一下之前的程序,然后就纠结了。Hadoop已经2.3.0了,从MR换到了Yarn,虽然老版本兼容,不过还是随大流升级把。

    版本问题还好说,不过实在不想在本机装Hadoop了。

    在源码里面翻了翻,找到了MiniCluster,问题就解决了。

    Hadoop开发环境

    网上有很多安装Hadoop的文章,多是本地单机或者伪分布式模式。如果只是想学习一下,或者集群由专人维护,实在没有必要折腾。

    当然也可以下载做好的虚拟机文件,直接启动。

    对大部分人而言,只要有一个测试少量数据的方法就行了。

    而安装完成开发环境以后需要编写相关程序,然后打包运行。

    网络上也能找到不少测试专用的工具,不过始终没有真正的环境保险。

    从Hadoop的源码中能够找到一些测试,其中就有测试用的环境。然后在Jira找到了相关记录HADOOP-8009

    这个Improvement是将使用Hadoop Client和建立最小集群的包包括在一起,然后发布到maven库,分别命名为org.apache.hadoop:hadoop-clientorg.apache.hadoop:hadoop-minicluster

    主要的原因是因为Hadoop由几个块组成(common,hdfs,mapred),它们的版本可以有不同。其次是没有单独的客户端API。最主要的原因就是为了测试,特别是版本变化的时候。

    这意味着我们可以使用hadoop-minicluster去启动一个最小集群,并且它可以从maven仓库获取。

    从Gradle配置开始

    我使用Gradle管理项目,初始化模板如下:

    apply plugin:'java'
    apply plugin:'idea'
    
    repositories {
    mavenLocal()
    maven {
    url'http://maven.oschina.net/content/groups/public/'
    }
    }
    
    project.ext {
    junitVersion ='4.+'
    
  • 第三方库的更新和升级

    第三方库的更新和升级

    模块化、代码重用等思维的引领下,各种不同功能的库不断出现,基本涵盖了所有功能需求,极大加速了软件开发效率。

    另一方面,包管理工具的流行简化了引用过程。不再需要下载库文件,添加到项目目录。

    第三方库也是一种产品,一样存在生命周期,这样就会有升级的问题产生。

    为什么要升级

    第三方库也是人创造的,同样会有效率,代码质量等问题。伴随着升级会有各种修复、优化和新的特性。

    及时的升级可以排除潜在的bug,提升效率,如果有需要还可以根据API的升级简化代码并优化流程。

    对于一个成熟的、经过广泛验证的第三方库,比如Spring、Hibernate等等。它们的方方面面经过时间的考研,在一个稳定的版本范围内都是可信的。所谓的版本范围是指标准版本号

    如果第三方依赖库的管理是手动的,那么升级的步骤就很无趣了。你需要定期访问你所使用的库的主页,检查是否有升级,然后手动下载新版本,覆盖旧版本。

    如果使用包管理工具或者构建工具,如npm、gradle等,那么升级就比较简单了,直接修改配置本身然后执行更新命令就可以完成了。

    Gradle中的版本管理

    npm很方便,使用也简单,所以下面的例子都是以gradle为例的。

    在gradle中如果你需要引入一个第三方包,你只需要在配置中写道:

    apply plugin:'java'
    
    repositories {
    mavenCentral()
    }
    
    dependencies {
    compilegroup:'org.hibernate', name:'hibernate-core', version:'3.6.7.Final'
    testCompilegroup:'junit', name:'junit', version:'4.11'
    }

    这样gradle就可以自动下载相关的库,而当你需要升级的时候你只需要修改version配置即可。

    当然更多的人可能习惯偏好更紧凑的写法:

    dependencies {
    compile'org.hibernate:hibernate-core:3.6.7.Final'
    testCompile'junit:junit:4.11'
    }

    不过以上的修改都需要你在一大篇缩进不同的字符串中来回寻找,特别是以下情况的时候你可以就觉得有点不方便了:

    testCompile(
    "org.hamcrest:hamcrest-core:1.3",
    "org.hamcrest:hamcrest-library:1.3"
    )
  • 改善Hexo生成页面的描述信息

    改善Hexo生成页面的描述信息

    其实严格来讲这是针对具体的模板而言,不过其中涉及到Hexo的一些数据的调用,所以还是算作Hexo的一个优化吧。

    问题主要源于检查自己的博客被收录的情况,Google很给力,收录了200+,而百度就一页…

    如图所示,Google的很多搜索结果的描述部分是一样的,看着死活觉得不爽,所以来改进一下。

    原因分析

    首先看看这句话出自哪里,是在_config.yml的配置中出现的。根据这个配置这些文字又被写到html页面的head中。

    对于描述信息的具体细节可以参考Google站长帮助—标题和描述

    简单说我们要尽可能提供清晰的描述,这些描述要准确且不重复,可以手动也可以程序生成。当然在Hexo中我们肯定首推程序生成。

    修改

    不同的主题可能具体的文件不一样,不过原理是一致的。我以我使用的Pacman模板为例。

    需要修改的是head.ejs,先看看原始定义:

    <% } if (page.description){ %>
    <meta name="description" itemprop="description" content="<%= page.description %>">
    <% } else if (config.description&&(!is_post())){ %>
    <meta name="description" content="<%= config.description %>">
    <% } else if 
  • 使用Vagrant配置一个稳定的Hexo书写环境

    使用Vagrant配置一个稳定的Hexo书写环境

    Vagrant是一个工具,它可以让你轻松的配置虚拟机。利用它你可以快速的搭建环境,并共享环境。

    它本质上是使用了Virtual Box等虚拟机作为支撑,在此之上对配置分享等做简化。

    很早就知道了Vagrant的存在,但是一直不知道它的用处何在,直到我最近离开了我的笔记本一小段时间才真正享受了它的遍历。

    背景

    我的笔记本安装的是ubuntu,写博客用的hexo。在ubuntu下什么都很方便,特别是hexo的环境,ssh密钥等等都是配置好的。

    其实用ubuntu的原因真的很单纯,因为笔记本性能太差了。跑Window那简直是一卡一卡的,用ubuntu就可以有个很流畅的速度。

    但是最近我回家没有带笔记本,又想写写东西,就觉得各种不适应了。

    家里的系统是Windows的,各种纠结以后装上Virtual Box开始配置虚拟机,在下载镜像的时候放弃了,因为觉得麻烦。

    所以视线有回到Vagrant了。

    准备工作

    Vagrant有各个平台的安装包,Windows下100+M,加上Virtaul Box的100+M,总大小不大,我家小水管也可以接受。从Vagrant Cloud拖一个Ubuntu 12.04的Box下来,300M。

    然后就是安装并重启。

    不要忘记配置一下环境变量。

    创建并启动

    根据官方的说明,使用vagrant init hashicorp/precise32初始化一下,然后使用vagrant up启动。

    这里会自动下载对应的Box,但是速度很慢。

    拿出我们刚才下载好的Box,我用迅雷下的,速度完全不能比呀。

    随便放在哪里,然后执行命令

    vagrant box add precise32 file:///C:/Users/sdx/.vagrant.d/boxes/precise32.box

    再次声明,文件位置随意。我放在那个目录是因为我以为它可以自动识别。

    重新初始化

    vagrant init precise32
  • JDBC驱动加载和Class.forName

    我很少直接使用原生的JDBC,更多是使用mybatis或者hibernate。

    上周需要用一用,就到网上Copy了一段代码,一切运行正常。但是有一句话有点奇怪

    Class.forName("com.mysql.jdbc.Driver")

    这句话我原来也用过,一直是以为这是加载对应驱动用的,直到我删除了它,我才发现好像它不是必须的。那这东西是干嘛的?

    虽然网上的很多代码片段是抄来抄去的,但是我觉得这样写一定是有原因。

    Class.forName

    实现看看这句话本身的用处。

    在Java世界中,类只有在被使用的时候才本加载,更准确的是说在你寻求类的相关信息的时候才加载。

    比如有一个类名为SomeOne

    publicclass SomeOne {
    
    }

    然后在一个调用它的类名为SomeOneCaller

    publicclass SomeOneCaller {
    publicstatic SomeOne someOne =null;
    publicstatic Class<?> someOne = SomeOne.class;
    }

    第一句话并没有导致类的加载,而第二句话触发了加载。

    而Class.forName语句是加载对应名称的类,这里的加载是指读取了字节码,并执行static代码块。

    也就是在使用JDBC时调用这句话可以保证我们在执行DriverManager的getConnection
    方法时它已经被载入JVM中了。

    也就是说Class.forName本身和JDBC没有什么关系,它所做的只是加载对应名称的类。

    JDBC

    JDBC是一套标准API,用于数据库连接和SQL语句执行等等。
    既然是标准,那么一定是有相关规范的。

    要在getConnection时调用正确的类,一般的JDBC驱动都会调用registerDriver方法注册自己。

    比如在Mysql的驱动中:

    static