Code Review (ObjC for iOS project)

Review时, 找寻的目标..

假定项目/公司既有代码规范, coder遵从or不遵从规范都是显而易见的.

故不评审代码样式/风格/规范.

找寻的目标, 应该是潜在bug和有性能问题的代码.

一些ObjC审查问题

代码层面的基础问题:

  1. 内存泄漏; 野指针问题.
  2. 线程安全.
  3. 循环依赖.
  4. 集合类型“越界”可能.
  5. 条件判断等的边界考虑.

代码组织设计上的问题:

  1. 类的单一职责原则;
  2. 方法设计, 与其他的耦合关系;
  3. 模块的设计问题;
  4. 算法类方法的瓶颈审查.

Godot engine学习 (2)

Day 3

GUI

Simplified UI controls
  • Label 显示一个文本的node
  • TextureFrame 显示一个简单的材质
  • TextureButton 显示一个简单的、材质化的按钮, 具有常规的state
  • TextureProgress 显示一个材质化的进度条
Complex UI controls

其他的控件:

  • 需要复杂UIs的情况: PC RPCs, MMOs, strategy, sims游戏等
  • 创建自定义的开发工具以加速内容的创建
  • 创建Godot编辑器的插件
  • 这类控件的重定位更常用containers来完成

Size and anchors

为不同尺寸的设备配置UI控件:

godot-anchors-guide

Splash Screen

基本上,这也是个场景(Scene).

使用自定义的布局, 包含一些基础的UI元素, 例如一个“Start开始”按钮.

一张背景图(TextureFrame).

和一个使用custom font自定义字体的Label, 显示copyright信息.

fonts字体

现今操作系统的字体通常都是矢量图形, 适合不同尺寸的展示, 格式通常为TTF(TrueType)或OTF(OpenType).

渲染此类字体成位图资源是一个复杂的过程. 通常由CPU完成. 而游戏引擎使用GPU渲染, 3D相关APIs并不能高效支持此类渲染, 所以需要在之前将字体转换成一个更友好的格式.

基本上呢, 和使用Unity类似, 有指定字体大小情况下的bitmap生成图, 再交由引擎使用, 是一个好方法.

劣势也是一样的: 字体尺寸问题、中文字符集问题等.

Animations

Godot里的动画. Godot的动画系统是极其强大而灵活的.

增加AnimationPlayer节点(node).

选中时, 会出现Animation Editor面板.

off

(颈椎背痛,休息了四天…)

Day 4

Resources资源

Godot里, 引擎绝大多数的行为与功能都是有nodes实现而来, 与Nodes一样非常重要的另一种数据类型是resource.

Nodes关注与behaviors(行为), 例如绘制一个sprite, 绘制3D模型, 物理引擎, GUI控件等等.

Resources则纯粹是数据容器(data containers). 意思是它们不会有任何action也不会处理任何信息.

Resources只包含数据.

如: Texture, Script, Mesh, Animation, Sample, AudioStream, Font, Translation等等.

当Godot从磁盘存储或载入一个scene(.scn/.xml), 一张图片(.png/.jpg), 一段脚本(.gd)或几乎任何东西, 被操作的那个文件就是一个resource.

当资源(resource)从磁盘载入, 它将只会被载入一次. 这意味着,资源会被载入到内存(memory). 尝试再次载入只会返回该资源.

通常而言, Godot里的每个对象(Node, Resource or anything else)都能输出属性.

属性可以是很多类型, 如string, integer, Vector2等, 有些类型可以是个resource.

这意味着nodes和resources都可以包含resources型的属性. 如图:

godot-resource-1

External 外部 vs built-in 内建

资源属性可以以两种方式关联资源: external(on disk)或是built-in.

代码的两种载入方式:

# 1
func _ready():
        var res = load("res://robi.png") # resource is loaded when line is executed
        get_node("sprite").set_texture(res)

# 2
func _ready():
        var res = preload("res://robi.png") # resource is loaded at compile time
        get_node("sprite").set_texture(res)

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

Jenkins on macOS

通过Homebrew

需注意的是,最新的Jenkins需要Java8的环境。

直接brew安装:

yul@MAC-LUOYU > brew install jenkins
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (caskroom/cask).
No changes to formulae.

==> Using the sandbox
==> Downloading http://mirrors.jenkins-ci.org/war/2.70/jenkins.war
==> Downloading from http://mirrors.tuna.tsinghua.edu.cn/jenkins/war/2.70/jenkins.war
######################################################################## 100.0%
==> jar xvf jenkins.war
==> Caveats
Note: When using launchctl the port will be 8080.

To have launchd start jenkins now and restart at login:
  brew services start jenkins
Or, if you don't want/need a background service you can just run:
  jenkins
==> Summary
🍺  /usr/local/Cellar/jenkins/2.70: 7 files, 72.2MB, built in 1 minute 48 seconds

快速又便捷,并给出使用的提示,

如果想启动Jenkins并且让它重启登录时自启动,使用命令:

brew services start jenkins

或如果你不想让它作为后台服务运行,可以直接使用命令:

jenkins

按照上面的提示开启Homebrew服务,并设置Jenkins随系统启动的服务:

 yul@MAC-LUOYU > brew services start jenkins
==> Tapping homebrew/services
Cloning into '/usr/local/Homebrew/Library/Taps/homebrew/homebrew-services'...
remote: Counting objects: 12, done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 12 (delta 0), reused 7 (delta 0), pack-reused 0
Unpacking objects: 100% (12/12), done.
Tapped 0 formulae (40 files, 53.8KB)
==> Successfully started `jenkins` (label: homebrew.mxcl.jenkins)

配置

Jenkins启动后,默认端口号是8080,

可以直接访问例如本机http://localhost:8080

会显示首次登录的Unlock Jenkins页面

这里会要求一个初始的管理员密码验证,并告知了该密码在服务器上的位置,

所以可以:

cat ~/.jenkins/secrets/initialAdminPassword

安装的过程也很简介,有推荐的插件可以直接开启Jenkins,也可以手选自己需要的插件。

等待各个插件的安装完成..

最后会提示创建一个admin管理员账号:

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

RAM Disk 内存磁盘

RAM Disk 内存磁盘

— 突破磁盘读写瓶颈的好手段!~

特质

RAM内存读写速度特别快,但是断电后会损失数据.

磁盘读写速度相对内存来说,是非常慢的,但是磁盘能持久化数据.

根据此特质,可将内存部分空间划作磁盘进行挂载,而磁盘则为临时文件操作的目录,即可大大提高相关操作的速度(因为提升读写速度).

实现

1. 思路

通过命令创建RAM磁盘 -> 挂载RAM磁盘 -> 创建所需临时文件路径 -> 将系统操作路径指向RAM磁盘内对应的路径 -> DONE.

开机能自动重复此操作: 利用AppleScript编译成Application, 然后使用系统的开机启动应用的设定来自启动该程序.

2. Apple Script

do shell script "
if ! test -e /Volumes/\"Ramdisk\" ; then
diskutil erasevolume HFS+ \"RamDisk\" `hdiutil attach -nomount ram://2097152`
fi
mkdir -p /Volumes/Ramdisk/Library/Developer/Xcode/DerivedData
rm -rf ~/Library/Developer/Xcode/DerivedData
ln -s /Volumes/Ramdisk/Library/Developer/Xcode/DerivedData ~/Library/Developer/Xcode/DerivedData
"

编译成Application进行保存.

3. 开机启动设置

System Preferences -> Users & Groups -> User -> Login Items 添加导出好的App即可.

Author

Luo Yu

indie.luo@gmail.com

界面拼接设计

拼接界面

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

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

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

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

这些是需要先明确的.

设计

思路

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

这样就是组合界面了.

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

也就是说,如果导入一个模块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

Xcode 文档与注释

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

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

应用

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

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

选择

  • headerdoc2html

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

  • doxygen

官方链接 doxygen

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

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

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

目前暂定该方案。

Author

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

Monday, January 16, 2017