分类
dev

Godot engine学习 (1)

自打去年在搜索引擎上看到这个开源的游戏引擎来,足足一年时间过去了,却没有真正开始学习/尝试使用这个引擎.

今起,可以利用业余时间开始一步步学习这个引擎,目前看来,作为移动平台小游戏的基础,它应该是满足条件的.

MIT的许可证,也比较适合我们来使用.

将会在博客每天持续更新学习记录.

内容会是以官方文档为基础的学习, 暂不会参考其他网上教程(其实好像也还没有). 时间尽量定在1~2周.

Day 1

第一天,就来了解一些关于Godot引擎的基础好了.

基本上,介绍页解释的是Godot里元素的基础是node, 暂且翻译为“节点”. 一个节点(node)有名称、有可编辑的属性、能接受回调、能被扩展、能被挂到其他节点下作为子节点.

也强调了子节点的组织方式, 称非常重要, 以这种方式组织时,这些nodes形成了tree(树). (感觉...非常...寻常的方式...- -!)

另外,就是Scene(场景). 起特征是:

一个Scene有且只有一个根节点(root node)、Scene可以被保存到磁盘也能被载入、可以被实例化、运行一款游戏意味着运行一个Scene、一个工程里可有多个Scene.

(基本上和其他引擎定义一致)

Godot editor其实就是个Scene editor.

hello, world

  • 选择路径, 创建工程(New Project), 并打开
  • 在Scene面板中, 使用+新增node(Create New Node), Node|CanvasItem|Label
  • Inspector中编辑label的内容, 比如“hello, world”
  • 保存, 并设置该Scene为main scene, 运行即可看到效果

Instancing

基本原理

Godot里,Scene的特征上面已描述过:

godot-scene-1

对于一个正在编辑的Scene来说,其他Scene的示例也可以加到其下:

godot-scene-2

上面图里展示的是Scene B以一个instance加到Scene A中.

官方的demo示例中,在Scene面板中点按“链接形状”按钮选择一个Scene创建instance.

Instancing

Design language

When making games with Godot, 
the recommended approach is to leave aside other design patterns 
such as MVC or Entity-Relationship diagrams 
and start thinking about games in a more natural way. 
Start by imagining the visible elements in a game, 
the ones that can be named not just by a programmer but by anyone.

举例一个简单的射击游戏的思路:
godot-instancing-1

记下想到的元素, 并用箭头表示其ownership.

Day 2

Scripting 脚本

使用的脚本语言称作GDScript. 其设计时有两个目标:

  • First and most importantly, making it simple, familiar and as easy to learn as possible.
  • Making the code readable and error safe. The syntax is mostly borrowed from Python.

任意情况下, 若有性能需求, 可以用C++编写核心块然后交给脚本调用.

Reference

右击node, add script, 意味着extend该node, 故会自动填上Inherit该node.

默认脚本里, 会有一个_ready()函数. 它会在该node(和它所有的children)进入活动scene时被调用. Constructor函数是_init().

# 获取元素
Node.get_node()
# 默认树结构 是 从该脚本控制的node的直接子node开始
# 例如Panel->Label->Button的结构的话, 就是get_node("Label/Button")

# 连接signal信号与响应
Object.connect()
# get_node("Button").connect("pressed",self,"_on_button_pressed")

Processing

有些情况下, 需要脚本处理每一帧. 有两类processing: idle processingfixed processing.

Idle processing由Node.set_process()函数激活. 一旦其激活, Node._process()回调将会被每一帧调用. 示例:

func _ready():
    set_process(true)

func _process(delta):
    # do something...
# delta参数描述了时间间隔, 单位秒, float类型, 间隔自上一次调用_process().

Groups

Nodes可以被添加到groups(想加多少加多少).

  1. 通过界面添加: Node面板下的Groups按钮.
  2. 通过代码添加: add_to_group("enemies")

在此情况下, SceneTree.call_group()可通知一个group下的所有node.

# if the player is discovered sneaking into the secret base
# all enemies can be notified about the alarm sounding
# by using SceneTree.call_group()
func _on_discovered():
    get_tree().call_group(0, "guards", "player_was_discovered")

当然, 也可通过group获取其所有nodes的列表: SceneTree.get_nodes_in_group()

var guards = get_tree().get_nodes_in_group("guards")

Notifications

Godot系统包含一个通知系统.

添加Object._notification()函数即可使用:

func _notification(what):
    if (what == NOTIFICATION_READY):
        print("this is the same as overriding _ready()...")
    elif (what == NOTIFICATION_PROCESS):
        var delta = get_process_time()
        print("this is the same as overriding _process()...")

Overrideable functions

func _enter_tree():
    # When the node enters the _Scene Tree_, it becomes active
    # and  this function is called. Children nodes have not entered
    # the active scene yet. In general, it's better to use _ready()
    # for most cases.
    pass

func _ready():
    # This function is called after _enter_tree, but it ensures
    # that all children nodes have also entered the _Scene Tree_,
    # and became active.
    pass

func _exit_tree():
    # When the node exits the _Scene Tree_, this function is called.
    # Children nodes have all exited the _Scene Tree_ at this point
    # and all became inactive.
    pass

func _process(delta):
    # When set_process() is enabled, this function is called every frame.
    pass

func _fixed_process(delta):
    # When set_fixed_process() is enabled, this is called every physics
    # frame.
    pass

func _paused():
    # Called when game is paused. After this call, the node will not receive
    # any more process callbacks.
    pass

func _unpaused():
    # Called when game is unpaused.
    pass

Creating nodes

代码创建node. 调用.new()方法. 例如:

# 代码创建node. 调用`.new()`方法
var s
func _ready():
    s = Sprite.new() # create a new sprite
    add_child(s)     # add it as a child of this node

# 删除node
func _someaction():
    s.free() # immediately removes the node from the scene and frees it
# 释放一个node, 它的子nodes也会跟着被释放.

删除一个正处于“blocked”状态的node将会导致crashing, 因为它正在发出信号(emitting a signal)/调用方法(calling a function).

最为安全的删除node的办法是Node.queue_free(). 它将在idle状态下删除安全的删除node.

Instancing scenes

代码里instancing一个scene分两步:

  1. 从磁盘载入: var scene = load("res://myscene.scn"), 或是var scene = preload("res://myscene.scn")
  2. 此时scene才是以PackedScene的资源包形式存在着的, 要创建一个实在的node, 需使用PackedScene.instance().
var node = scene.instance()
add_child(node)

二步初始化的优势是, packed的scene可以一直被保持着, 用来快速初始化多个instances. 例如创建多个enemies, bullets等等.

Day 3

Author

Luo Yu

indie.luo@gmail.com

分类
dev

Xcode Objective-C Swift混合项目的小问题

Xcode Objective-C Swift混合项目的小问题

向Objective-C工程中添加Swift

  1. 在Xcode中创建一个*.swift文件

    在Xcode弹出提示时,选择创建一个Objective-C Bridging Header文件

    如果未选择创建,可以手动创建该头文件,并在Build Settings里增加配置Objective-C Bridging Header -> $(SRCROOT)/projectname-Bridging-Header.h

  2. 实现Swift类

    类使用@objc注解

  3. Build Settings的参数检查

    • define module = YES
    • Product Module Name = projectname (不可以有特殊字符)
    • Install Objective-C Compatibility Header = YES
    • Objective-C Bridging Header
  4. 在要用到Swift类的ObjC的实现文件.m内,导入Swift接口的头文件

#import "projectname-Swift.h"

Author

Luo Yu

Friday, July 7, 2017

分类
dev

界面拼接设计

拼接界面

这个需求产生的最初原因,是部分不懂软件开发的人,在自认为懂软件开发的情况下,认为的理所当然的功能.

就是一个模块化/组件化的产品,就应该是像乐高积木一样,可以动态随意组合成应用.

功能是可以以某种形式存在的,但这里的灵活组合只能源于独立的设计,而非已有的功能.

而动态组合,也一定存在局限.

这些是需要先明确的.

设计

思路

需要拼接,自然就能想到将界面中的每一元素,独立成类似属性的视图,使他们能统一进行大小+位置+顺序的操作.

这样就是组合界面了.

而呈现这些视图的程序,则为每一个模块.

也就是说,如果导入一个模块A,它将同时提供一个入口视图(例如一个A按钮),可以配置 大小/位置/顺序.

配置文件也将包含这些属性:

模块排序,视图大小,原点位置.

也就是说,如果配置文件写得足够通用的话,可以做到跨平台使用.

例如统一使用XML格式编写配置. 亦或是直接完成一套Android平台的库.

应用

类似一些语言的桌面应用开发的“布局”方式,这里最终的实现也是有点类似的感觉.

但它更加平台化.

这里我将它应用为一个移动平台专门的界面构建方式.

先按照仅有由上往下的一种布局方式来设计实现.

然后对于手机平台,通常情况下,是横向占满屏幕的,或是以某个数字来均分宽度:

例如:

A 0 0 0 0 0
0 0 0 0 0 0
B 0 0 0 0 0
C 0 0 D 0 0
E 0 0 0 0 0
0 0 0 0 0 0

矩阵一排为44pi的话,第一个组件A即高88pi,B、C、D都高44pi, E高88pi.

而宽度A、B、E都占满(假设是最多可以等分为6块),C和D则各占3块.

当然,在项目中,宽度最小粒度应该也属于可配置的.

在列表的数据中,组件是以数组的形式存在,本身就具有顺序.

语言内部

  1. 组件 -> 模型类

  2. 配置文件 -> 模型类

  3. 配置文件内,应该包含有一个序列的组件.

  4. 配置文件模型可以直接在界面构建中方便的使用,以确定界面如何布局.

  5. 配置文件模型可以导入数据(import),从plist文件或者XML文件.

  6. 配置文件模型可以导出数据(export)到plist文件或者XML文件.

  7. 视图生成器 -> 工具类

其他工作

作为library打包整个功能库,即可方便被应用.

Author 作者

骆昱(Luo Yu

2017-02-17

分类
dev

Xcode 文档与注释

最近在着手把公司项目的数据操作算法与界面控制分离开来,准备了一个数据操作的动态库用来写这部分代码,考虑到代码的使用对象,为本公司里,应用的编写人员,所以文档的需求就变得更为迫切。

大致考虑了一下,文档需要与方法编写同时产生。

应用

实际应用,就是比较简单的策略:

编写代码时,使用规范的注释。完成功能后,生成一遍注释,更新到网上。

选择

  • headerdoc2html

headerdoc2html为自带文档生成工具,命令即可操作,具体参数命令会提示。

  • doxygen

官方链接 doxygen

doxygen的非常不错,也有应用程序方便的生成文档,还有很多不同类的语言支持。

文档相关功能也挺不错的,目录结构,搜索,还有关系图表。

也有配置文件,方便下一次文档更新的生成。

目前暂定该方案。

Author

骆昱(Luo Yu), indie.luo@gmail.com

Monday, January 16, 2017

分类
dev

基于Web技术的桌面应用开发

前因

因为工作的原因,在思考如何选择下一代应用开发技术,应对当前问题。

基于Web相关技术进行跨平台程序开发其实早在移动平台刚刚大红大紫的年代就被炒得很热门了,但是目前由于各种原因(诸如交互体验,以及与其他技术混合等等),我在移动平台应用开发上,首选的仍然会是原生开发的方式。

不过这种方式,放在桌面应用上却是很好的。桌面级应用,有着更好的系统资源,更少的限制条件。

注意事项

Web技术的桌面端应用明显会在电源优化上存在劣势,所以如果应用需要长时间操作,跨越几次系统休眠/睡眠,使用场景有电池续航状态等,还是更应该考虑原生语言开发.

进行

目前在研究的是Electron框架: 官网

Github内部据称使用多年的Atom编辑器,即是使用electron框架编写的。

Author

骆昱(Luo Yu)

2016-12-28

分类
dev

Module化的 iOS应用开发

因工作需要,开始希望移动应用开发能走模块/组件式的过程,来提高功能的复用性,减少开发的时间,从而满足快速的产品输出需求。

需求的来源源自公司的新项目方向通常是与已有的项目存在部分类似的功能,又有自己独特的模块。

在此基础下,将功能尽量独立出来,做到不同项目导入+配置即可使用,除了减少开发、测试的工作,也可提高该功能的可维护性(修改一次代码,可以同时更新多个引用此功能的项目)。

难点在于模块式的iOS应用开发。

首先是解耦功能。

实际情况1

考虑到网络层一般已有AFNetworking/Alamofire这样的库,网络层通常是对应Server RESTAPI进行配合,做初步解析,区分API Error与Success,以便每个数据请求处理结果。

对于某些特定的情况,例如没有对OSS文件服务器接口进行包装的Server,文件上传的操作也是在App内做的情况下,这些操作也应该归纳到网络层的范围内。

这样下来,如果在Server Domain,API前缀,OSS鉴权信息等可配置的情况下,可以将该组件+网络库(如AFNetworking)独立出来,形成一个模块。

实际情况2

用户登录

包含用户的权限判定,与登录界面显示的触发;登录界面UI与输入交互;登录操作的网络请求,成功失败的处理等。

权限判断与界面触发都与其他界面里用户的操作相关联,但可行的是,判断本身都是与固有数据的操作,是外部对模块内的单向调用。

登录界面UI,无法确定,同一家公司的话,可能有一定的复用性,具体情况待定。

网络请求可以暴露出组件,或是可配置的请求地址,待定。

可见登录,是一个有部分独立性的模块。

实际情况3

注册

注册UI,无法确定,不同项目有不同的注册所需字段的需求;
注册步骤,无法确定,不同项目,注册的流程都可能不一样,有的追求简单快速,有的则有许些必要的信息需要录入并验证。

注册不应该做成模块,可复用性也很低。

模块与项目引用的解决方案

首先,应该考虑的是Git与Git Submodule,似乎生来就是为项目模块化设定的。

好处有很多,依赖关系很明确,所见即所得,如何将模块添加到工程都是开发人员自己决定的。

而对于工程本身来说,其实还是一个整体。

编译输出全在一个二进制文件里。

其次,使用私有的Cocoapods进行配置管理。

思路大致如下:

分类
dev

iOS中 OCR光学识别的运用

在一些特定场景下我们需要利用iPhone(or any iOS devices)的摄像头来进行光学识别(OCR-Optical Character Recognition),本文即是对此方向的研究。

版权所有,如转载请先联系indie.luo@gmail.com。


一些轮子:

  • TesseractOCR

Tesseract OCR iOS | 开源的OCR框架,在其GitHub页面上可发现仍然持续更新中。
它是Tesseract框架的一个iOS实现。

关于当前(Jul 19, 2016)TesseractOCR-iOS的一些注意事项:
使用的Tesseract库版本为 3.03-rc1。所以在Github上找到的3.04语言训练文件不能用会报错。
实测可以使用旧版3.02版本的语言训练文件。(同样的问题出现在chi_sim, jp等语言上)。

Github的issues里作者有展开讨论更新3.04的问题,因为官版tag打的位置有bug,所以现在最好还是等下一个更新版。

  • GPUImage

GPUImage | 不用多说,Github上闻名的几个iOS项目之一。一考虑到本项目需要一些摄像头控制,一些图片的调整,就想到GPUImage会派上用场。

  • OpenCV-iOS

利用机器视觉,识别文档边缘,判断方向,识别照片的视角扭曲,对应作出修改等。

  • ImageMagick

ImageMagick | 对图片的进一步修改/编辑操作,可以使用这个命令行版的“Ps”工具。例如场景:需要对有倾斜拍摄的图片进行视角扭曲,可以很好的利用该工具提供的Perspective Distortion(从平时使用Ps做Perspective Crop裁剪得来灵感)。


定义存储数据类型

当前研究是做ID识别,先定义一个存储ID数据的对象。

满足以下要求:

  • 实现 NSCoding和NSCopying

  • 字段 ID号码,姓名,民族,住址,有效期,签发机关

  • 比较方法 -isEqualToID:

  • 描述方法 -description


组织问题

识别是基于整张图片的,所以即使正确识别出所有文字,引擎也无法分辨谁是谁。
所以如果希望区分内容,请在识别之前,对图片进去分割。

分类
dev

iOS Project with Dependency Manager

Notice: 当前Apple为Swift正在准备一套包管理器,详情参见Github

依赖管理器 Dependency Manager 和 包管理器 Package Manager 

Cocoapods 依赖管理器 Dependency Manager
cocoapods.org/
官方简介:

CocoaPods is a dependency manager for Swift and Objective-C Cocoa projects. It has over eighteen thousand libraries and can help you scale your projects elegantly.

官方安装是通过gem源进行的。

但是有以下几点缺点:
gem install需要sudo权限。
默认的gem源https://rubygem.org/国内环境无法访问。淘宝提供的gem镜像也并不稳定。

使用Homebrew包管理器安装

关于Homebrew:(如果已用,请跳过此节)
brew.sh
官方的一点介绍:

Homebrew installs the stuff you need that Apple didn’t.

Homebrew installs packages to their own directory and then symlinks their files into /usr/local.

Homebrew won’t install files outside its prefix, and you can place a Homebrew installation wherever you like.

Trivially create your own Homebrew packages.

It's all git and ruby underneath, so hack away with the knowledge that you can easily revert your modifications and merge upstream updates.

Homebrew formulae are simple Ruby scripts

Homebrew complements OS X. Install your gems with gem, and their dependencies with brew.

强烈推荐Mac环境下使用Homebrew包管理器,而非MacPorts。
Homebrew已有的formula参见:
https://github.com/Homebrew/homebrew-core/tree/master/Formula

安装Homebrew:

Terminal(终端)中粘贴并运行此命令:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

以后需要查找包,直接使用搜索命令:
brew search …

安装包:
brew install …

检查Homebrew状态:
brew doctor

这里我们将使用Homebrew来安装Cocoapods。
brew search cocoapods
可以看到结果里有Cocoapods
那直接brew install cocoapods
等待安装完成~

测试一下Cocoapods是否安装成功,运行命令:
pod
返回结果里有pod正确的使用方法解释,看来是安装成功了。
以后pod升级也不用再sudo gem install了。

使用官方gem安装方法的请注意了:
Cocoapods安装需要sudo权限。但是平时pod时一定不要用sudo。
特别是第一次初始化pod repos的时候,如果使用sudo,将会导致一系列问题,(其实都是权限不对导致)。

使用brew install的,本身不用sudo,不用担心这个问题。

关于Spec repos
初次安装Cocoapods将会克隆一份spec repository到本地。
因为该项目又大文件夹又多,可能容易中断。

实际Spec项目就在Github,链接:
https://github.com/CocoaPods/Specs.git
可以git clone该项目到本地,或者下载zip文件的方式获取它。

将整个repo命名成master,然后放到~/.cocoapods/repos/下即可。

这个时候,再跑一遍命令以完成Cocoapods的初始化:
pod setup

来替代Spec仓库初始化命令的操作。

使用Cocoapods控制第三方库依赖

在Xcode Project目录下,运行pod init命令,将初始化一个Podfile文件。

向其中添加pod库即可。

修改完成后,运行pod install命令,将依据此Podfile生成工程 workspace,包含配置好的所有库。

命令都会输出应有的操作提示。

Where To Go

关于该依赖管理器,还有很多运用,比如自建私有的Pods仓库,本地引用,透过Git引用等。

骆昱
2016-07-05

分类
dev

Objective-C编码规范

Objective-C Coding Standard | Objective-C编码规范

Version 1.0.0
Author Luo Yu
Date Friday, June 24, 2016

本标准的制定参考了Apple官方文档Cocoa Programming Guidelines,Raywenderlich.com Objective-C style guide,Google's Objective-C Coding Standard。

空格

对齐

关于对齐方式,请永远使用Tab,而非空格。

页宽

Page Guide设置为120。
Objective-C的常用长命名方式,再加之当今显示器有更宽的显示范围,这里将页宽调整为120。
可以在Xcode->Preferences->Text Editing->Editing中修改。

请移除行尾多余的空格。

变量

对象类型与*号之间空格,其他不空格。如:
NSString *oneString;

属性声明时,@property后空格,)后空格,对象类型与*号之间空格,其他不空格。如:
@property (nonatomic, strong) NSString *aString;

方法声明

方法类型符号后空一格,返回类型的括号后与方法名之间不空格。如:
- (void)someMethod;
+ (void)someClassMethod;

方法命名时,传参冒号前后都不空格。如:
- (void)someMethod:(NSString *)aString andColor:(UIColor *)aColor;

方法名较长,或参数过多而需换行显示时,优先采取冒号对齐方式。例如:
- (void)someMethodWithParameterOne:(int)paramOne
andParameterTwo:(int)paramTwo;

如冒号对齐无法对齐时,可采用左对齐。

方法调用

空格的采用方式和方法声明时保持一致,冒号前后都无空格。如:
[theObject someMethodWithParameter:1 andParameterTwo:2];

多行调用也与声明的对齐规则一样,优先采用冒号对齐。如:
[theObject someMethodWithParameter:1
          andParameterTwo:2];

关于调用方法时,语法空格的理解。

这是一条最简单Objective-C调用方法时的语法,
[object callMethod];
对象 调用 方法,中间有一个空格。

所以一个连环调用:
[[object callMethodOne] callMethodTwo];
相当于object2 = [object callMehtodOne];
然后有[object2 callMethodTwo];

所以一个不合规范的写法:
[[object callMethodOne]callMethodTwo];
无异于[object2callMethodTwo];
这种不规范的空格简直不能忍。

代码组织

方法分类

使用#pragma mark来分类方法,该标记能在Xcode的代码编辑器中梳理展示。
- 会产生水平分割线
| 可退一格

命名

沿用Objective-C的长命名方式,不推荐缩写。如:
UIImage *caseIconImage;
而不推荐:
UIImage *caseIcoImg;

使用全大写的Prefix。
类名,常量名都应该使用此种Prefix开头。如:
LYObject

常用语法格式

条件语句

if后空格,)后也空格,{不换行。如:
if (!error) {
    // do something ...
}

if语句总是使用{},即使只有一行代码。

else和while遵循同样的规则,在同一行起始{,在新一行结束}。

三元判断时,条件部分使用括号包括。如:
(val == NO) ? case1 : case2;
三元判断语句请勿嵌套使用。

switch语句

switch语句总包含default情况。
switch的case中,总是使用{}包含。

枚举

使用SDK推荐的NS_ENUM()创建枚举类型。避免在Cocoa/Cocoa Touch中使用CF C风格的enum。例如:
typedef NS_ENUM(NSInteger, LYCellType) {
    LYCellTypeHeader,
    LYCellTypeNormal,
    LYCellTypeDisabled,
    LYCellTypeFooter,
};

常量

推荐使用FOUNDATION_EXPORT配合const指定常量,或是static配合const。

尽量避免使用#define的方式。

注释

推荐必要的注释。

注释根据项目要求添加。

为了防止文本文件的编码解码导致的乱码,注释推荐使用英文,但不强制。

分类
dev

Take Ownership REG TEXT

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\*\shell\takeownership]
@="Take ownership"
"HasLUAShield"=""
"NoWorkingDirectory"=""

[HKEY_CLASSES_ROOT\*\shell\takeownership\command]
@="cmd.exe /c takeown /f \"%1\" && icacls \"%1\" /grant administrators:F"
"IsolatedCommand"="cmd.exe /c takeown /f \"%1\" && icacls \"%1\" /grant administrators:F"

[HKEY_CLASSES_ROOT\exefile\shell\takeownership]
@="Take ownership"
"HasLUAShield"=""
"NoWorkingDirectory"=""

[HKEY_CLASSES_ROOT\exefile\shell\takeownership\command]
@="cmd.exe /c takeown /f \"%1\" && icacls \"%1\" /grant administrators:F"
"IsolatedCommand"="cmd.exe /c takeown /f \"%1\" && icacls \"%1\" /grant administrators:F"

[HKEY_CLASSES_ROOT\dllfile\shell\takeownership]
@="Take ownership"
"HasLUAShield"=""
"NoWorkingDirectory"=""

[HKEY_CLASSES_ROOT\dllfile\shell\takeownership\command]
@="cmd.exe /c takeown /f \"%1\" && icacls \"%1\" /grant administrators:F"
"IsolatedCommand"="cmd.exe /c takeown /f \"%1\" && icacls \"%1\" /grant administrators:F"

[HKEY_CLASSES_ROOT\Directory\shell\takeownership]
@="Take ownership"
"HasLUAShield"=""
"NoWorkingDirectory"=""

[HKEY_CLASSES_ROOT\Directory\shell\takeownership\command]
@="cmd.exe /c takeown /f \"%1\" /r /d y && icacls \"%1\" /grant administrators:F /t"
"IsolatedCommand"="cmd.exe /c takeown /f \"%1\" /r /d y && icacls \"%1\" /grant administrators:F /t"