2016.8.22

 • 

在推上吐槽了下 微信 for Mac 2.0,竟然在一天世界的会员通信中被引述作为讨论微信的开头。不鳥萬如一在讨论的最后如是说:“执意要用户使用内建浏览器最终一定是为了实现更好的控制,但如果介意这个,完全不用微信方为正途。”


Star Citizen: 2016 Gamescom Live Alpha 3.0 Demo
Star Citizen 这个视频展示的估计是太空游戏在某种层面的顶尖水平了:星球是真正 1:1 scale 的,而不是 No Man's Sky 里行走面积只有一个地级市那么大的星球(i-Novae Studios 的 Battlescape 其实也能达到类似的效果,而且是最早达到类似效果的
1
,但是毕竟资源不足,开发进度比星际公民慢了很多)。而做到这个,想必在物理引擎和渲染引擎的数字精度上花了不少力气。至于 Unity 中能不能实现类似的效果,我在 Asset Store 里面见过 World Streamer,大概原理是隔一段距离就重置世界坐标到新的 (0,0,0) 并且 Unload 掉无用的场景,这样能避免物理引擎在较大 unit 上出现非常奇怪的精度丢失,但问题是,这个插件仍然是需要在编辑器内预先做好场景然后切割为很多用于 load / unload 的小部分放置于 Unity Build Scenes 里,而不是实时切割。


昨天刚回到家,估计在家呆几天,然后回学校补考。至于什么时候回公司,要看和辅导员,和院里面的老师怎么聊了,然而不幸的是我及其不擅长和大学老师聊天。

  1. 2010 年 i-Novae Studios 就发布过无缝登录的巨大星球登录视频

Epoch Dev Blog 5

 • 

上一次 Epoch Dev Blog 还是三月,中间因为 Unity 在超大 unit 尺度上频繁崩溃的原因几乎就 AFK 了。不过最近 Unity 崩溃的情况有所转好,而且在北京的这段时间几乎每个人看到我的这个 Epoch 原型都在问我为什么不继续开发。但我并不准备在二月的原型上继续开发了,因为当时的代码效率非常低,结构也很乱,现在再看几乎是恨不得重写一遍。
所以我的确重写了 —— 而且我打算分两步走,第一步先基于 Epoch 的核心代码重写一个新的 Epoch Core,包括过程生成的大部分代码,然后生成一个类似于 Mountain 的小工程。然后再基于新的 Epoch Core 填充各类素材,设计故事,等等等。
Teaser Time
最近和 Epoch 比较相关的的事情就是 No Man's Sky 的发售了。差评较多,主要还是集中在核心玩法和优化上:一个具有完整 FPS 视角的太空游戏,大部分时间的操作和 Minecraft 一样,却并没有像 Minecraft 一样直观的反馈,并且不断崩溃。这和很多玩家的想象偏差(也和 Hello Games 在游戏展上演示的效果偏差)很多。过程生成,包括贴图,地形,音效等都只能解决素材问题,但还是不能解决玩法问题。Elite Dangerous 的思路或许更为正常些:它一直宣传自己是一个纯正的太空战斗游戏,而不是什么第一人称射击游戏。

使用 Processing-Android自己实现 METER 动态壁纸

 • 

Recreate METER Wallpapaer using Processing-Android!
关于 METER : METER On Androidexperiments
想要自己实现一个 WIFI 信号的动态壁纸,于是尝试了下。提示:需要 Processing-Android 4.0 Pre-Release

步骤一:画背景及 WIFI 信号背景三角形

Reference :
background() | triangle()

void setup() 
{
  fullScreen(P3D);//当然 P2D 应该也行的样子。
}

void draw() 
{
  background(12, 39, 43);// 背景色
  beginShape(TRIANGLES);
  fill(0,94,83);// WIFI 三角背景色
  vertex(width / 6 * 1, height / 5 * 1);
  vertex(width / 2, height / 5 * 3);
  vertex(width *5 / 6, height / 5 * 1);
  endShape();
}

效果:

步骤二:画 WIFI SSID 字段

先在 Processing - Android - Sketch Permissions 里勾选 android.permission.ACCESS_WIFI_STATE,否则读取 WIFI 信息会闪退。获取 Context 参见 processing-android/issues/227 里的 Commit,这里是用的是 surface.getComponent();

import android.net.wifi.*;
import android.content.*;
import android.app.*;
import processing.core.*;
import android.os.Bundle;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;

String wifiName;
android.content.Context context;

void setup() 
{
  fullScreen(P3D);
}

void draw() 
{
  background(12, 39, 43);
  beginShape(TRIANGLES);
  fill(0,94,83);
  vertex(width / 6 * 1, height / 5 * 1);
  vertex(width / 2, height / 5 * 1 + width / 3 * 2);
  vertex(width *5 / 6, height / 5 * 1);
  endShape();
  
  textSize(width / 20); // WIFI SSID 字体大小
  textAlign(CENTER, CENTER);
  text("WIFI : "+wifiName, width / 2, height / 5 * 1 + width / 3 * 2 + height / 20);
  fill(0, 102, 153);
}
public void onResume() {
  super.onResume();  
  context = (Context) surface.getComponent();
  WifiManager wifiManager = (WifiManager) context.getSystemService (Context.WIFI_SERVICE);
  WifiInfo info = wifiManager.getConnectionInfo ();
  wifiName = info.getSSID(); // 获取 SSID
}
public void onPause() {
  super.onPause();
}

步骤三:画 WIFI 强度

这里还有一个调整,就是获取信号强度和 SSID 都不在 onResume() 做了,因为 onResume() 一般是后台转前台才触发,而我们要保证在 Launcher 里面能动态刷新,所以设立一个 Timer 来每十秒钟获取好了。
截图里面的三角形是全填满的,公司信号太好,我懒得找一个没信号的地方测试了,反正公式是对的就行啦。

import android.net.wifi.*;
import android.content.*;
import android.app.*;
import processing.core.*;
import android.os.Bundle;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import java.util.*;

String wifiName;
int wifiLevel; // 0 - 10
android.content.Context context;
boolean canRefreshWifi;

float Point1X,Point2X,Point3X;
float Point1Y,Point2Y,Point3Y;

void setup() 
{
  fullScreen(P3D);
  
    Timer t = new Timer(); // 每 10 秒 Run 一次。
    t.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            if (canRefreshWifi)
            {
              context = (Context) surface.getComponent();
  WifiManager wifiManager = (WifiManager) context.getSystemService (Context.WIFI_SERVICE);
  WifiInfo info = wifiManager.getConnectionInfo ();
  wifiName = info.getSSID();
  wifiLevel = WifiManager.calculateSignalLevel(info.getRssi(), 11); // 信号强度
            }
        }
    
    },0,10000);
}

void draw() 
{
  background(12, 39, 43);
  beginShape(TRIANGLES); // 背景三角形
  fill(0,94,83);
  vertex(Point1X, Point1Y);
  vertex(Point2X, Point2Y);
  vertex(Point3X, Point3Y);
  endShape();
  
  beginShape(TRIANGLES); // 前景三角形
  fill(37,206,182);
  vertex(Point2X + (Point1X - Point2X) * wifiLevel / 10, Point2Y + (Point1Y - Point2Y) * wifiLevel / 10);
  vertex(Point2X, Point2Y);
  vertex(Point2X + (Point3X - Point2X) * wifiLevel / 10, Point2Y + (Point3Y - Point2Y) * wifiLevel / 10);
  endShape();
  
  textSize(width / 20);
  textAlign(CENTER, CENTER);
  text("WIFI : "+wifiName, width / 2, height / 5 * 1 + width / 3 * 2 + height / 20);
  fill(0, 102, 153);
}
public void onResume() {
  super.onResume();  
  canRefreshWifi = true; // 回到前台,可以继续刷新 WIFI 信息。
  
  Point1X = width / 6;
  Point2X = width / 2;
  Point3X = width / 6 * 5;
  
  Point1Y = height / 5;
  Point2Y = height / 5 * 1 + width / 3 * 2;
  Point3Y = height / 5;
}
public void onPause() {
  super.onPause();
  canRefreshWifi = false;
}

步骤四:实现水平仪效果

加上地理位置的权限,反正就是 ACCESS 开头中间带 LOCATION 的两个,我也忘了具体名字了。
代码略长了,于是放在了 Gist 上。后面写的有些乱,效率上非常差,不过作为示范应该还是能看的。如图。

到这里,基本上实现了一个功能上和 METER 差不多的动态壁纸,可以显示 SSID,显示信号强弱,自带水平仪效果。

2016.8.14

 • 

都 14 号了,过的真快。

期间父母来北京看了我一次。聊了些日常的话题,没吵。这样我已经很满足了,所以没有再扯些其他的,不过还是和母上说了些更远的期望。
比如最好,不要认为现在就可以准备养老了,要能在这个岁数继续学习下去,见识更多的事情。否则,我感觉即使在大三要不要呆北京实习这个事情上我和父母达成一致了,以后还会有更多的分歧。

那么大三到底要不要呆北京呢,这个还要等九月份回去和辅导员商量才能有结果。其实辅导员对我不错,只不过我一直回避直接的接触,但拖不是个办法,事情总是要说的罢。

父母来的那个周末我还想起来一个事情。学校有一个同学,如果和他说要去做一些比较出头的事情,他语气就会变得非常拖拉,肢体语言上也显得非常扭捏和退缩,但他自己永远也不会意识到。后来我想肯定有时候自己也变得这么不自信,明显得外人可以感受到但自己却不自知,真是一件非常糟糕的事情。所以事情还是多勇于尝试为好,至少到后面就不会再有问题了。

和 Cee 一起买了同款耳机,朱砂红的索尼无线耳罩耳机,至于型号就不写了,因为我对耳机真的也没有做过多的研究,没办法详细讲解一番。

马老师请我们(Cee,我,Lighters)吃了火锅。记得四月份临走的时候,和响哥马老师吃饭也是火锅。然后下午参观了多点科技,以及见到了有才

提到设计师们,我最近有个感想是,之前我还在简历上写自己是设计爱好者来着,现在越来越觉得自己什么都不是。设计不是一个靠灵感的工作,或者说,设计很多时候并不需要拼灵感,而是在遵循正常的规律。而必须需要灵感才产出内容的设计师,能做的很好,也能做的非常差,完全看设计师本人的把握。想到这里就觉得自己还是不要瞎掺和了。

的确有很多程序员想学习设计,有很多设计师想学习代码,毕竟,同时掌握开发和设计(我指用户界面设计 / 媒体交互设计)在两个职业的人来看都是一件很酷的事情。有这种想法和热情的人,我两面都见了不少,但我觉得,要真的实现的确是太难了,毕竟这不是说程序员拿个 Dribbble Invite 或者设计师写个 Swift Hello World 就可以了的事情。

C4D 渲染像素风小技巧

 • 

今天才学到一个。
输出 1920 * 1080 的视频,每个像素大小 10 * 10,于是 Renderer Settings 设置实际渲染为 192 * 108:

渲染到 Picture Viewer 上,序列帧右键 Save as

(近似)无损拉伸到 1080P,非常适合渲染像素风。

2016.8.4

 • 

多图。

最近一直忙着公司的东西了,Cetacea for Mac 没有继续开发,不过完成度基本够我放出来一些图。

这个就是 Cetacea 的主界面,夜间模式。如果想要改主题 / 日夜自动切换模式,可以进设置:

点击主题,就会出现一个详细的颜色表:

Editor 的两个细节:当前编辑器行号加粗 和 URL 点击预览

目前能放出来的大致就这些。左侧 Bar 应该最后会改成和 Ulysses 一样,而不是图上的文件查看器。Preview 界面基本上就是实时预览该有的样子,目前基本没动工。


既然都发了这么多图,再发些最近手机拍的照片好了。


Nexus 6,低价入的。挺好用,搞得我都想学习 Android 开发了。(实际上我现在已经基本在写了)


楼下胜博殿外面的巨大毛绒狗,也是一个 Ingress Portal,名为“狗叼鸭”还是什么的


某天到早晨才出公司,准备回住处睡觉。


写字楼往上看。说不清这是什么结构。


猫,挺粘人。

2016.7.29

 • 

近两周口腔溃疡(并且在舌头上),因此吃东西喝水都有些困难。不过还是吃了很多好吃的。面基了很多人,推特上的微信上的,非常有趣。

买了 INSIDE,结果发现 Mac 上玩不了。在 Kevin 家的 Xbox 上玩了一小会。至于 INSIDE 那个 mindfuck 的正常通关结尾,我还是有些耿耿于怀,何苦要这么干呢?虽然这么问很蠢。

推荐一部还不算过时的电影,[The Man from U.N.C.L.E.](The Man from U.N.C.L.E. (2015) - IMDb),期末的时候看的。

Epoch 之前因为 Unity Editor 本身的一个 bug 拖延了一段时间,然后就开学没有再真正持续开发下去。四个月前给 Unity 提交的 Bug 在昨天终于有回复,虽然毫无意义,因为我已经不再用 Unity 5.3.3 这个版本,而且当时的我万万不会想到,四个月后广电的手游规定开始执行,我的 Epoch 怕是再也无法在国内上架了。

想到这个事情我就觉得,以后有什么想法还是要立马去做。

C# REPL FUN.

 •  Filed under unity, c#


最近研究了不少关于 Mono 的东西。之前 Unity 热更新的几个方案 提过使用 CodeDom Compile Class on the fly 的方法,当时出现找不到 gmcs 的异常,不过后来发现是 Unity 没有设置对环境变量,设置一下就好:

var name = "PATH";
			var PATH = System.Environment.GetEnvironmentVariable(name);
			var MonoPath = Path.Combine(EditorApplication.applicationContentsPath, "Frameworks/Mono/bin");
			var value = PATH + ":" + MonoPath;
			var target = System.EnvironmentVariableTarget.Process;
			System.Environment.SetEnvironmentVariable(name, value, target);

当然也可以把 gmcs.exe 和 mono.exe 打包进 Unity 项目,修改下 gmcs.bat 和 gmcs.sh 自己调用。这个做法目前在 Windows / macOS / Linux 上实验成功,但 Android 上仍然不能通过 shell 调用 gmcs,算是一个遗憾。
言归正传,除去 CodeDom 之外之前提到了可以用 Mono.CSharp.Evaluator 运行命令,然后发现了 Mono REPL,觉得挺好玩,所以用 Unity 做了一个小软件,试图在手机上还原 Mono REPL 的体验。
Mono REPL 基于 Mono.CSharp.Evaluator,可以分辨出是否为求值语句,并判断应该执行 Mono.CSharp.Evaluator.Evaluate 或 Mono.CSharp.Evaluator.Run。目前我的实现并没有这个功能,只是简单地用了一套 Material Design 的 Unity UI 结合 Mono.CSharp.Evaluator.Evaluate 执行语句。因此,如果你运行:

var x = 1 + 2;

会报错,而

var x = 1 + 2;
x;

则不会有问题,因为后一个语句最后是求值 x 的,符合 Evaluate() 需要返回值的要求。
代码补全和错误获取这两个功能还没有做,不过我很有兴趣继续研究。
软件叫 Cyan,Google Play 链接:Cyan on Google Play
软件没有 iOS 版,因为 Mono.CSharp 并不能在 AOT 条件实现,毕竟 IL2CPP 后就不能动态调用了。So, Have Fun :-)

跟风的大鱼海棠观后感

 • 
  1. 此片的主要争议在于剧情上,画面则无可挑剔。虽然我觉得作为一个频繁使用 2d 图层远近叠加的动画来说立体眼镜几乎无用,但瑕不掩瑜,配乐也不错。
  2. 没错,我是当爱情动画片看的,我也只能理解到这个份上了。我不是说电影有问题,我是说我自己。
  3. 剧情太快,男一男二和女主的撕扯以一般人的眼光来看又太过用力,因此导致一种故事里的人都在用力作死的感觉。相信这也是很多人对女主颇有微词的原因,若是改为四十集电视连续剧,人物冲突或许会更能为人所理解。
  4. 如果作为爱情片来看,我个人对女主是毫无意见的。毕竟现实里面谁爱谁是一件无法控制的事情,不能说谁付出的多就能获得爱。而对于女主来说,(对于感情的)优柔寡断或许是一个性格的缺点,但并不是人格上的,因此我对女主没有意见。
  5. 以现实的眼光看,电影很漂亮,很纯情,有些声嘶力竭,有点傻。
  6. 我觉得这部电影推荐不推荐取决于观影者想要什么。

Unity 热更新的几个方案

 • 

刚刚接触到热更新这块,感觉作为一个独立开发者的尊严和荣光都没了.....
以下是几个思路。

1. 脚本存为 TextAsset,和其余所有内容一起放到 Asset Bundle 里。客户端拿到 Asset Bundle 后用 Reflection 创建 type。

Link: http://docs.unity3d.com/Manual/scriptsinassetbundles.html

AssetBundle bundle = www.assetBundle;
// Load the TextAsset object
TextAsset txt = bundle.Load("myBinaryAsText", typeof(TextAsset)) as TextAsset;
// Load the assembly and get a type (class) from it
var assembly = System.Reflection.Assembly.Load(txt.bytes);
var type = assembly.GetType("MyClassDerivedFromMonoBehaviour");
// Instantiate a GameObject and add a component with the loaded class
GameObject go = new GameObject();
go.AddComponent(type);

好处:方便。
坏处:不能用 Prefab 存脚本的 Public 属性值,因为所有的脚本都要在读取 Asset Bundle 后重新挂载到 GameObject 上去。

2. Downloading the Hydra

Link: http://angryant.com/2010/01/05/downloading-the-hydra/
Angry Ant 在 10 年的办法。大体就是预先编译好 DLL,然后客户端去拿 DLL,然后运行时的时候加载。
好处:脚本不用改为 TextAsset
坏处:仍然不能保留 Scripts 在 Scene / Prefab 里面的 Reference,即:如果你预先通过 Asset Bundle 加载了一个场景到内存然后再加载一个包含场景需要脚本的 DLL 到内存(或者顺序相反,反正结果一样),这个时候 LoadScene 的结果是场景所有的 Script 仍然会显示为 Missing。Assembly.Load () 是可以加载到运行时,但是不代表 Unity 能认出这个 type。
相关讨论

3. Runtime Evaluating / Compiling CSharp

这个比较奇技淫巧,而且不支持 AOT,不过 Android 上就无所谓了。
具体有好几种思路:
第一种,用 Mono.CSharp.Run / Mono.CSharp.Evaluate

int cnt = 0;
while (cnt < 2)
{
	// this needs to be run twice
	foreach (System.Reflection.Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) {
		if (assembly == null) {
			UnityEngine.Debug.Log ("Null Assembly");
			continue;
		}
		UnityEngine.Debug.Log (assembly);
		try {
			Mono.CSharp.Evaluator.ReferenceAssembly (assembly);
		} catch (NullReferenceException e) {
			UnityEngine.Debug.Log ("Bad Assembly");
		}
	}
	cnt++;
}
Mono.CSharp.Evaluator.Run("using UnityEngine;");
Mono.CSharp.Evaluator.Evaluate ("1+2;");

第二种,用 Mono.CSharp.Compile

var evaluator = new Evaluator(
                new CompilerSettings(),
                new Report(new ConsoleReportPrinter()));

            // Make it reference our own assembly so it can use IFoo
            evaluator.ReferenceAssembly(typeof(IFoo).Assembly);

            // Feed it some code
            evaluator.Compile(
                @"
    public class Foo : MonoCompilerDemo.IFoo
    {
        public string Bar(string s) { return s.ToUpper(); }
    }");

            for (; ; )
            {
                string line = Console.ReadLine();
                if (line == null) break;

                object result;
                bool result_set;
                evaluator.Evaluate(line, out result, out result_set);
                if (result_set) Console.WriteLine(result);
            }

参见:http://blog.davidebbo.com/2012/02/quick-fun-with-monos-csharp-compiler-as.html

第三种,用System.CodeDom.Compiler / System.Reflection.Emit

CSharpCodeProvider provider = new CSharpCodeProvider();
			CompilerParameters parameters = new CompilerParameters();

			foreach (System.Reflection.Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
			{
				//Dbg.Log("refer: {0}", assembly.FullName);
				if( assembly.FullName.Contains("Cecil") || assembly.FullName.Contains("UnityEngine") )
					continue;
				parameters.ReferencedAssemblies.Add(assembly.Location);
			}
			parameters.IncludeDebugInformation = true;
			// Reference to System.Drawing library
//			parameters.ReferencedAssemblies.Add ("System.dll");
//			parameters.ReferencedAssemblies.Add ("System.Core.dll");
			parameters.ReferencedAssemblies.Add (typeof(MonoBehaviour).Assembly.Location);
			//parameters.ReferencedAssemblies.Add("/Applications/Unity/Unity.app/Contents/Frameworks/Managed/UnityEngine.dll");
			// True - memory generation, false - external file generation
			parameters.OutputAssembly = "AutoGen.dll";
			parameters.GenerateInMemory = false;
			// True - exe file generation, false - dll file generation
			parameters.GenerateExecutable = false;

			CompilerResults results = provider.CompileAssemblyFromSource(parameters,scripts);
			if (results.Errors.HasErrors)
			{
				foreach (CompilerError error in results.Errors)
				{
					UnityEngine.Debug.LogError("Error " + error.ErrorNumber + error.ErrorText);
				}
			}

上面是 CodeDom 的例子,不过 Unity 似乎阉割了这个部分,一直报错,我没有在这个方向再往下看。(Edit : GHOST_URL/c-repl-fun/) 至于 CodeDom 和 Emit 的对比,参见:http://stackoverflow.com/questions/2366921/reflection-emit-vs-codedom

以上所有 Runtime 的方法:
好处:可以传 string 执行方法
坏处:不够稳定,以及要自己造一套轮子

4. sLua / uLua / UniLua

好处:可以传 string 执行方法
坏处:效率低(不过可以接受啦),以及不是自己的轮子,有时候不是很好 Debug。