今天才学到一个。
输出 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.
最近研究了不少关于 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 :-)
跟风的大鱼海棠观后感
- 此片的主要争议在于剧情上,画面则无可挑剔。虽然我觉得作为一个频繁使用 2d 图层远近叠加的动画来说立体眼镜几乎无用,但瑕不掩瑜,配乐也不错。
- 没错,我是当爱情动画片看的,我也只能理解到这个份上了。我不是说电影有问题,我是说我自己。
- 剧情太快,男一男二和女主的撕扯以一般人的眼光来看又太过用力,因此导致一种故事里的人都在用力作死的感觉。相信这也是很多人对女主颇有微词的原因,若是改为四十集电视连续剧,人物冲突或许会更能为人所理解。
- 如果作为爱情片来看,我个人对女主是毫无意见的。毕竟现实里面谁爱谁是一件无法控制的事情,不能说谁付出的多就能获得爱。而对于女主来说,(对于感情的)优柔寡断或许是一个性格的缺点,但并不是人格上的,因此我对女主没有意见。
- 以现实的眼光看,电影很漂亮,很纯情,有些声嘶力竭,有点傻。
- 我觉得这部电影推荐不推荐取决于观影者想要什么。
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。
新主题
更新了主题,Daring,仿 Daring Fireball。这算是我目前为止找到的最满意的一个 Ghost 主题了,也很类似于 www.livid.cn 的风格。单页,所有的文章一次性铺开,除非需要评论不需要再点进单个文章的页面,非常好用,总算是把我一直以来觉得博客文章需要点击查看全文非常累赘的问题给解决了。
同时给这个主题添加了代码高亮和脚注支持,博客系统也升级到了 Ghost 0.8.0,评论系统也换用了 Disqus。
1
Banner 是仿照 Design Week Portland 做的。如果您想看原本是什么样子,可以去 Eli Schiff 的个人简介页面查看绿色的 Design Week Portland 2014 Banner。
How do you guys feel about it?
-
有关 Disqus 的 URL 参数问题以及多说迁移 Disqus 可以参见 http://www.justzht.com/disqusguan-yu-shi-bie-wei-xin-fen-xiang-can-shu-de-jie-jue/ ↩
2016.7.14
现在是周四凌晨两点五十四分,我刚写完这个学期最后一篇实验报告,然后十小时后要动身去北京。
在北京住哪还没确定,只能等到北京找自如看房了。如果一切顺利,应该能办完手续然后周五去公司,Cee 应该也在那个时候入职了,挺好的。
电工实验周 (7.1 - 7.14)中间我一直在写 Cetacea 的 Mac 版本。写的挺顺利,而且因为没有用 NSDocument 而是自己写的一个 Data Model 来做存储,Cetacea 的 iCloud Drive 可以在 iOS 和 macOS 版之间共享一套文件,比较省事。至于 Dropbox,沙盒模式下如何访问到文件还是个问题,所以目前我就只限于用 iCloud Drive 做同步了。
题外话:
实验周中间,广电的手游审批公告公布,并且 iTunes Connect 也同步加上了所谓的“请输入批准号码和日期”。苹果的这个同步的做法我也不知道说什么好,反讽的是截止到目前,iOS App Store 的首页推荐还有一栏叫做”精品中文独立游戏“。
呵呵。
以后不会再有中文精品 独立 游戏了。
NSOutlineView 实现文件管理器层级视图
效果图
Data Model
JZiCloudFileSystemItem.h
@interface JZiCloudFileSystemItem : NSObject
@property (nonatomic,strong) NSString *relativePath;
@property (nonatomic,weak) JZiCloudFileSystemItem *parent;
@property (nonatomic,strong) NSMutableArray *children;
@property (nonatomic,strong) NSMutableArray *childrenFolderOnly;
@property (nonatomic,strong) NSMutableArray *childrenMDOnly;
+ (JZiCloudFileSystemItem *)rootItem;
- (NSInteger)numberOfChildren;// Returns -1 for leaf nodes
- (JZiCloudFileSystemItem *)childAtIndex:(NSUInteger)n; // Invalid to call on leaf nodes
// 当前 JZiCloudFileSystemItem 文件夹内的 Markdown 数量
- (NSInteger)numberOfChildrenMD;
// 当前 JZiCloudFileSystemItem 文件夹内的 文件夹 数量
- (NSInteger)numberOfChildrenFolder;// Returns -1 for leaf nodes
- (JZiCloudFileSystemItem *)childFolderAtIndex:(NSUInteger)n; // Invalid to call on leaf nodes
- (NSString *)fullPath;
- (NSString *)relativePath;
- (void)refresh;
@end
JZiCloudFileSystemItem.m
@implementation JZiCloudFileSystemItem
@synthesize relativePath,parent,children,childrenFolderOnly,childrenMDOnly;
static JZiCloudFileSystemItem *rootItem = nil;
static NSMutableArray *leafNode = nil;
+ (void)initialize {
if (self == [JZiCloudFileSystemItem class])
{
leafNode = [[NSMutableArray alloc] init];
}
}
- (id)initWithPath:(NSString *)path parent:(JZiCloudFileSystemItem *)parentItem {
self = [super init];
if (self) {
relativePath = [[path lastPathComponent] copy];
parent = parentItem;
}
return self;
}
- (void)refresh
{
rootItem = nil;
}
+ (JZiCloudFileSystemItem *)rootItem {
if (rootItem == nil)
{
NSString *icloudRoot = [[[JZiCloudStorageManager sharedManager] ubiquitousDocumentsURL] path];
rootItem = [[JZiCloudFileSystemItem alloc] initWithPath:icloudRoot parent:nil];
}
return rootItem;
}
// Creates, caches, and returns the array of children
// Loads children incrementally
- (NSArray *)children {
if (children == nil)
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *fullPath = [self fullPath];
BOOL isDir, valid;
valid = [fileManager fileExistsAtPath:fullPath isDirectory:&isDir];
if (valid && isDir) {
NSArray *array = [fileManager contentsOfDirectoryAtURL:[NSURL fileURLWithPath:fullPath isDirectory:YES]
includingPropertiesForKeys:[NSArray arrayWithObject:NSURLNameKey]
options:NSDirectoryEnumerationSkipsHiddenFiles
error:nil];
NSUInteger numChildren, i;
numChildren = [array count];
children = [[NSMutableArray alloc] initWithCapacity:numChildren];
for (i = 0; i < numChildren; i++)
{
JZiCloudFileSystemItem *newChild = [[JZiCloudFileSystemItem alloc]
initWithPath:[[array objectAtIndex:i] path] parent:self];
[children addObject:newChild];
}
}
else {
children = leafNode;
}
}
return children;
}
- (NSArray *)childrenFolderOnly
{
if (childrenFolderOnly == nil)
{
childrenFolderOnly = [[NSMutableArray alloc] initWithCapacity:[[self children] count]];
for (NSUInteger i = 0; i < [[self children] count]; i++)
{
NSString *path = [[[self children] objectAtIndex:i] fullPath];
NSURL *url = [NSURL fileURLWithPath:path];
BOOL isDirectory;
BOOL fileExistsAtPath = [[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&isDirectory];
if (isDirectory && fileExistsAtPath)
{
[childrenFolderOnly addObject:[[self children] objectAtIndex:i]];
}
}
}
return childrenFolderOnly;
}
- (NSArray *)childrenMDOnly
{
if (childrenMDOnly == nil)
{
childrenMDOnly = [[NSMutableArray alloc] initWithCapacity:[[self children] count]];
for (NSUInteger i = 0; i < [[self children] count]; i++)
{
NSString *path = [[[self children] objectAtIndex:i] fullPath];
NSURL *url = [NSURL fileURLWithPath:path];
BOOL isDirectory;
BOOL fileExistsAtPath = [[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&isDirectory];
BOOL isMarkdown = ([[url pathExtension] compare: @"md"] == NSOrderedSame);
if (fileExistsAtPath & isMarkdown)
{
[childrenMDOnly addObject:[[self children] objectAtIndex:i]];
}
}
}
return childrenMDOnly;
}
- (NSString *)relativePath {
return relativePath;
}
- (NSString *)fullPath {
// If no parent, return our own relative path
if (parent == nil)
{
return [[[JZiCloudStorageManager sharedManager] ubiquitousDocumentsURL] path];
}
// recurse up the hierarchy, prepending each parent’s path
return [[parent fullPath] stringByAppendingPathComponent:relativePath];
}
- (JZiCloudFileSystemItem *)childAtIndex:(NSUInteger)n {
return [[self children] objectAtIndex:n];
}
- (JZiCloudFileSystemItem *)childFolderAtIndex:(NSUInteger)n
{
return [[self childrenFolderOnly] objectAtIndex:n];
}
- (NSInteger)numberOfChildren
{
NSArray *tmp = [self children];
return (tmp == leafNode) ? (-1) : [tmp count];
}
- (NSInteger)numberOfChildrenMD
{
NSArray *tmp = [self childrenMDOnly];
return (tmp == leafNode) ? (-1) : [tmp count];
}
- (NSInteger)numberOfChildrenFolder
{
NSArray *tmp = [self childrenFolderOnly];
return (tmp == leafNode) ? (-1) : [tmp count];
}
@end
NSOutlineViewDataSource
#pragma mark - NSOutlineViewDataSource
- (NSInteger)outlineView:(NSOutlineView *)outlineView
numberOfChildrenOfItem:(id)item
{
return (item == nil) ? 1 : [item numberOfChildrenFolder];
}
- (BOOL)outlineView:(NSOutlineView *)outlineView
isItemExpandable:(id)item
{
return (item == nil) ? YES : ([item numberOfChildrenFolder] != 0);
}
- (id)outlineView:(NSOutlineView *)outlineView
child:(NSInteger)index
ofItem:(id)item
{
return (item == nil) ? [JZiCloudFileSystemItem rootItem] : [(JZiCloudFileSystemItem *)item childFolderAtIndex:index];
}
- (id)outlineView:(NSOutlineView *)outlineView
objectValueForTableColumn:(NSTableColumn *)tableColumn
byItem:(id)item
{
return (item == nil) ? @"/" : [item relativePath];
}
NSOutlineViewDelegate
- (NSView *)outlineView:(NSOutlineView *)outlineView
viewForTableColumn:(NSTableColumn *)tableColumn
item:(id)item
{
if (NO)
{}
else
{
JZFolderOutlineCellView *view = [self.outlineView makeViewWithIdentifier:@"DataCell" owner:self];
view.textField.stringValue = [item relativePath];
view.childCountButton.title = [NSString stringWithFormat:@"%ld",(long)[item numberOfChildrenMD]];
return view;
}
}
- (CGFloat)outlineView:(NSOutlineView *)outlineView
heightOfRowByItem:(id)item
{
return 30.0f;
}
Storyboard
View Based, Identifier = @“DataCell”
手动实现 NSTabViewController 的 Rect Transition 及 Propagate Title
不知为何 我在 OS X 10.11.5 及 Xcode 7.3 的 Storyboard 中设置 Tab View Controller 的 Transition 属性时,Tab View Controller 并不能自动根据子 View Controller 的 Preferred Content Size 来动画渐变,因此只能自己实现了(包括 Propagate Title)。
目前的 Storyboard 设置:
记得关掉 Propagate Title,因为在我的 build 上没有任何用处,而且还影响后面通过 Delegate 手动设置标题。
Step 1:
Subclass TabView Controller,这里是JZSettingsTabViewController
,加上 NSTabViewDelegate
。
Step 2:
实现协议,
- (void)updateWindowSizeWithItem:(NSTabViewItem *)item
{
NSWindow *window = self.view.window;
NSSize contentSize = item.viewController.preferredMinimumSize;
NSSize newWindowSize = [window frameRectForContentRect:(CGRect){CGPointZero, contentSize}].size;
NSRect frame = [window frame];
frame.origin.y += frame.size.height;
frame.origin.y -= newWindowSize.height;
frame.size = newWindowSize;
[self.view.window setFrame:frame display:YES animate:YES];
window.title = item.label;
}
Step 3:
既然上面是通过 preferredMinimumSize
获取的 Size,那么在子 View Controller 的实现文件里加上这句,这个时候就不要写 Preferred Content Size 了:
- (CGSize)preferredMinimumSize
{
return CGSizeMake(500, 400);
}
虽然这样 Size 就不是通过 Storyboard 设置了略嫌麻烦,不过考虑到 TabView Controller 的每个子 VC 都肯定要实现的,也还算可以。
我这里是因为要写 设置界面 所以用到了 NSTabViewController 并且保持固定的 Window 大小(通过去除 NSResizableWindowMask),如果需要一个可以 Resize 的 Window 同时保持不同 Tab 的大小切换动画,
NSSize contentSize = item.viewController.preferredMinimumSize;
这句可以适当修改,比如修改为当前拉伸后的预期大小。