九天雁翎的博客
Jekyll
2022-05-22T12:51:44+08:00
http://www.jtianling.com/
http://www.jtianling.com/
http://www.jtianling.com/cross-compile-with-rust
2022-05-18T00:00:00+08:00
2022-05-18T00:00:00+08:00
http://www.jtianling.com
<p><a href="https://www.rust-lang.org/">Rust</a> 作为编译型的语言, 交叉编译挺方便的, 这样开发和部署, 都能简单挺多. 本文以在 Mac 上, 交叉编译一个使用 SDL 库的程序到一个手持 ARM 设备(<a href="https://www.clockworkpi.com/gameshell">clockwork Gameshell</a>) 为例, 记录一下怎么使用 Rust 的交叉编译, 特别是怎么在交叉编译的时候, 还能链接类似 SDL 这种外部的库.
Rust 的生态是比较完善的, 只是相关的资料比较少的, 基本上是一步一个坑. 除了对 Rust 自身的 Rustup 等工具的了解, 还需要用到 <a href="https://www.docker.com/">Docker</a>, Linux 包管理等知识, 希望对同样被困住的同学有帮助. 同时, 本文也会顺便讲讲思路, 以帮助大家将相关知识应用到其他交叉编译的场景.</p>
<!-- more -->
<h1 id="环境">环境</h1>
<p>先厘清几个术语, 在交叉编译的时候, 用于编译的机器, 叫做 Host(宿主), 这里用的是我的 Mac(12.1), 用来运行程序的机器叫做 Target(目标设备), 例子中是前面提到的 Clockwork GameShell, 一个 ARM 设备. 我们的目标就是在宿主机器上编译, 然后直接在目标设备上运行编译好的程序.<br />
这种方式的好处是宿主可以是台式机, 编译速度快, 而目标设备可以是任意设备, 包括速度很慢, 不太适合执行编译任务的嵌入式设备.</p>
<h1 id="最简单的情况">最简单的情况</h1>
<p>假如你并不需要用其他外部库, 那可以直接使用 Rust 内置的交叉编译功能, 使用还挺简单的.
你可以通过 <code class="language-plaintext highlighter-rouge">rustup target list</code> 列出当前系统支持的所有交叉编译目标, 在我的 Mac 上运行以后, 有 86 个之多, 其中会有一个默认的. 显示</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>x86_64-apple-darwin (installed)
</code></pre></div></div>
<p>这个也是我们现在本机用的.<br />
这么多编译目标, 那我们应该用哪一个呢? 这里有个<a href="https://doc.rust-lang.org/nightly/rustc/platform-support.html">官方的列表</a>, 分为几个支持的级别, 可以直接过去看.</p>
<p>同样是 Linux 和 ARM, 选择也不少. 可以在目标设备上, 用 <code class="language-plaintext highlighter-rouge">uname -a</code> 命令看到一部分信息.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ uname -a
Linux clockworkpi 5.3.6-clockworkpi-cpi3 #1 SMP Tue Oct 15 17:26:44 CST 2019 armv7l GNU/Linux
</code></pre></div></div>
<p>起码知道了是 armv7, 但是 armv7 的 target 还有几个, 后来我想到的办法是, 在目标设备上安装 Rust, 然后通过前面的命令来看. 当然, 前提是目标设备也能运行 Rust 才行, 要是不行的话, 那就只能查资料和尝试了.<br />
在我的目标设备, 运行前面的 <code class="language-plaintext highlighter-rouge">rustup target list</code> 命令后, 显示</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>armv7-unknown-linux-gnueabihf (installed)
</code></pre></div></div>
<p>稍微提醒一下, 这个是刚装完 Rust 后的情况, 你要是已经按下面的操作添加了各种 target, 那这个就不准了.<br />
然后, 在宿主设备上, 用以下命令</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rustup target add armv7-unknown-linux-gnueabihf
</code></pre></div></div>
<p>来添加对应的交叉编译目标. 直接编译试试</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo build <span class="nt">--target</span> armv7-unknown-linux-gnueabihf
</code></pre></div></div>
<p>到这一步, 你会看到一大堆的错误(要是这就成功了, 那我就不写这篇文章了)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>error: linking with `cc` failed: exit status: 1
|
= note: "cc"
= note: clang: warning: argument unused during compilation: '-pie' [-Wunused-command-line-argument]
ld: unknown option: --as-needed
clang: error: linker command failed with exit code 1 (use -v to see invocation)
</code></pre></div></div>
<p>基本上的意思, 就是虽然编译是好了, 但是链接的时候发生错误了, 原因在于现在明显是使用了宿主的链接器(linker), 而不是对应的目标设备的链接器.</p>
<p>这里有几种解决办法</p>
<ol>
<li>手动下载目标的链接器, 参考<a href="https://john-millikin.com/notes-on-cross-compiling-rust">这篇</a></li>
<li>直接用 brew 安装一下链接器 (不一定所有 target 都有). 参考<a href="https://sigmaris.info/blog/2019/02/cross-compiling-rust-on-mac-os-for-an-arm-linux-router/">这篇</a>,</li>
</ol>
<p>我这里是 ARM 设备, <code class="language-plaintext highlighter-rouge">brew</code> 是有的, 为了简单, 我直接用 <code class="language-plaintext highlighter-rouge">brew</code> 命令安装了</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew install arm-linux-gnueabihf-binutils
</code></pre></div></div>
<p>然后, 在 <code class="language-plaintext highlighter-rouge">~/.cargo/config</code> 中, 添加如下两行配置, 修改对应 target 的链接器设置</p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[target.armv7-unknown-linux-gnueabihf]</span>
<span class="py">linker</span> <span class="p">=</span> <span class="s">"arm-linux-gnueabihf-ld"</span>
</code></pre></div></div>
<p>再用前面的命令编译试试, 此时还是会报错:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> = note: arm-linux-gnueabihf-ld: cannot find -lgcc_s: No such file or directory
arm-linux-gnueabihf-ld: cannot find -lutil: No such file or directory
arm-linux-gnueabihf-ld: cannot find -lrt: No such file or directory
arm-linux-gnueabihf-ld: cannot find -lpthread: No such file or directory
arm-linux-gnueabihf-ld: cannot find -lm: No such file or directory
arm-linux-gnueabihf-ld: cannot find -ldl: No such file or directory
arm-linux-gnueabihf-ld: cannot find -lc: No such file or directory
</code></pre></div></div>
<p>简单的原因, 就是一切就绪了, 但是对应的一些 gnu 基础库找不到. 此时可以去找到对应的库都下到本地, 还有, 我找到一个神奇的方法, 换成 <code class="language-plaintext highlighter-rouge">armv7-unknown-linux-musleabihf</code> 这个 target, 这里用了 <a href="https://zh.m.wikipedia.org/zh/Musl">musl</a> 这个库, 就不需要用 gnu 的那些库了.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% cargo build --target=armv7-unknown-linux-musleabihf
Finished dev [unoptimized + debuginfo] target(s) in 0.08s
</code></pre></div></div>
<p>编译后, 你可以可以在工程的 <code class="language-plaintext highlighter-rouge">target/armv7-unknown-linux-musleabihf</code> 目录下面, 找到交叉编译后的文件. 在目标设备上实测运行, 是可以运行的.<br />
最后这种方式, 是绕过了配置链接需要的库, 理论上, 手动下载回来然后配置正确的话, 可以在 Mac 上直接编译链接成功, 应该会非常麻烦.<br />
当我准备使用 SDL 这种库, 一定需要配置正确链接的库时, 这种方法就不好用了.<br />
接下来, 介绍一种更全面的方法.</p>
<h1 id="使用-cross-rs">使用 cross-rs</h1>
<p>这个方式, 也是我个人比较推荐的方式, 万能, 而且是利用 Docker 环境来编译, 不需要在本地装一大堆纯为了编译的各种库, 当然, 代价是得装 Docker, 而众所周知, Docker 的 image 动不动就几个 G 的大小.-_-! 可能好处就是不用的时候, 清理起来方便一些了. <br />
而且, 使用 Docker, 可以直接基于 Ubuntu 这种 Linux 环境, apt 的包管理感觉比 Mac 的 brew 还是要更强大.<br />
对了, 其实接下来的步骤, 虽然是利用了 Docker, 但是除了 Docker 的部分, 也可以看做是在 Linux 上使用 Rust 交叉编译的过程. 假如你的宿主机本身是 Linux 的话, 那这些方法也是可以直接使用的(就不用 Docker 了), 怎么说呢, 果然 Linux 才是对开发者最友好的系统.</p>
<h2 id="安装-cross-rs">安装 cross-rs</h2>
<p>参考 <a href="https://github.com/cross-rs/cross">cross-rs 的页面</a>, 每一步都相对清晰.
安装 cross-rs</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cargo install cross
</code></pre></div></div>
<p>然后, 在交叉编译的时候, 直接用 <code class="language-plaintext highlighter-rouge">cross</code> 命令, 替换掉 <code class="language-plaintext highlighter-rouge">cargo</code>. 比如我们前面的那个简单例子, 在安装 cross-rs 后, 改成用 <code class="language-plaintext highlighter-rouge">cross</code> 命令.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% cross build --target=armv7-unknown-linux-gnueabihf
</code></pre></div></div>
<p>在下载了一个 Docker image 后, 直接就成功了…有点意外加惊喜.<br />
此时, 能看到多了一个用于编译 armv7-unknown-linux-gnueabihf 的 docker image.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% docker image list
rustembedded/cross armv7-unknown-linux-gnueabihf-0.2.1
</code></pre></div></div>
<h2 id="链接外部库">链接外部库</h2>
<p>这里用 SDL 为例子, 演示怎么加载外部库.<br />
首先随便找个 <a href="https://sunjay.dev/learn-game-dev/opening-a-window.html">SDL 的例子</a>.
然后继续按上面的 <code class="language-plaintext highlighter-rouge">cross</code> 命令编译, 会报链接错误</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> = note: /usr/lib/gcc-cross/arm-linux-gnueabihf/5/../../../../arm-linux-gnueabihf/bin/ld: cannot find -lSDL2
/usr/lib/gcc-cross/arm-linux-gnueabihf/5/../../../../arm-linux-gnueabihf/bin/ld: cannot find -lSDL2_mixer
/usr/lib/gcc-cross/arm-linux-gnueabihf/5/../../../../arm-linux-gnueabihf/bin/ld: cannot find -lSDL2_image
/usr/lib/gcc-cross/arm-linux-gnueabihf/5/../../../../arm-linux-gnueabihf/bin/ld: cannot find -lSDL2_ttf
/usr/lib/gcc-cross/arm-linux-gnueabihf/5/../../../../arm-linux-gnueabihf/bin/ld: cannot find -lSDL2_gfx
collect2: error: ld returned 1 exit status
</code></pre></div></div>
<p>很明显, 就是前面多次碰到的找不到对应的动态库. 不过这次我们解决这个问题.<br />
前面我们已经能看到默认情况下, cross-rs 会给我们添加一个 image, 但是这个 image 里面没有我们需要的库. 我们来添加一下.</p>
<h3 id="1-交互式运行这个-image-创建一个-container">1. 交互式运行这个 image, 创建一个 container</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% docker run -i -t rustembedded/cross:armv7-unknown-linux-gnueabihf-0.2.1 /bin/bash
</code></pre></div></div>
<h3 id="2-在-container-中添加我们需要的库">2. 在 container 中添加我们需要的库</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># apt-get update
# dpkg --add-architecture armhf
# apt-get update
# apt-get install libsdl2-dev:armhf libsdl2-mixer-dev:armhf libsdl2-ttf-dev:armhf libsdl2-image-dev:armhf libsdl2-gfx-dev:armhf
</code></pre></div></div>
<p>这里是以 SDL 为例, 其中 <code class="language-plaintext highlighter-rouge">dpkg --add-architecture armhf</code> 的这一步, 很关键, 因为 Docker 运行的也不是目标设备的系统, 后面的 <code class="language-plaintext highlighter-rouge">apt-get install</code> 的时候, 也用了 <code class="language-plaintext highlighter-rouge">:armhf</code> 的后缀, 表示安装的是针对 ARM 的对应包. 要是没有这几步, 直接用 <code class="language-plaintext highlighter-rouge">apt-get</code> 安装也是没有用的.</p>
<h3 id="3-用这个做好的-container-来创建我们自定义的-image">3. 用这个做好的 container 来创建我们自定义的 image</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
98744316d89e rustembedded/cross:armv7-unknown-linux-gnueabihf-0.2.1 "/bin/bash" 19 minutes ago Up 19 minutes naughty_mccarthy
</code></pre></div></div>
<p>此时, 注意我们正在运行的container id.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% docker commit 98744316d89e my/clockwork
</code></pre></div></div>
<h3 id="4-然后指定-cross-rs-使用我们自定义的-image">4. 然后指定 cross-rs 使用我们自定义的 image</h3>
<p>在工程中, 增加一个 <code class="language-plaintext highlighter-rouge">Cross.toml</code> 文件, 内容如下:</p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[target.armv7-unknown-linux-gnueabihf]</span>
<span class="py">image</span> <span class="p">=</span> <span class="s">"my/clockwork"</span>
</code></pre></div></div>
<p>准备就绪, 再次使用</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% cross build --target=armv7-unknown-linux-gnueabihf
</code></pre></div></div>
<p>编译, 搞定. 此时可以将编译好的程序拷贝到目标设备上运行, 没有问题.
最后, 因为本身就是配置一个编译环境, 直接交互式运行 image 还是挺方便的, 以后有更多依赖库的时候, 重复上述步骤即可.</p>
<h1 id="参考">参考</h1>
<ol>
<li><a href="https://rust-lang.github.io/rustup">The rustup book</a></li>
<li><a href="https://github.com/cross-rs/cross">cross-rs</a></li>
<li><a href="https://john-millikin.com/notes-on-cross-compiling-rust">Notes on cross-compiling Rust</a></li>
<li><a href="https://sigmaris.info/blog/2019/02/cross-compiling-rust-on-mac-os-for-an-arm-linux-router/">Cross compiling Rust on Mac OS for an ARM Linux router</a></li>
<li><a href="https://sunjay.dev/learn-game-dev/intro.html">Learn Game Development in Rust</a></li>
</ol>
<p><a href="http://www.jtianling.com/cross-compile-with-rust.html">Rust 的交叉编译</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on May 18, 2022.</p>
http://www.jtianling.com/dart-with-pixijs
2020-03-09T00:00:00+08:00
2020-03-09T00:00:00+08:00
http://www.jtianling.com
<p>最近 <a href="https://flutter.dev/">Flutter</a> 的流行, 让 <a href="https://dart.dev/">Dart</a> 这个似乎已经要死的语言又复活了. 最近在找能同时在 iOS 和 H5 两端同时运行的编程语言, 没想到 Dart 竟然是非常合适的对象, 有点意外, 于是看了看 Dart.</p>
<!-- more -->
<h1 id="提要">提要</h1>
<p>实在的说, 语法特性并没有什么亮点. 类型系统除了添加了类似 Mixin 这种相对靠谱的东西外, 写起来和 C++ 的感觉都差不多, 并发上, 添加了类似 JavaScript 的 Async-Await 异步写法, 暂时没有尝试, 不过就使用 JavaScript 的经验来说, 可能真用来写服务器, 并不能很好的写 多线程/多协程 的程序, 可能要用单线程-多进程的思维来实现并发.</p>
<h1 id="dart-with-pixijs">dart with pixijs</h1>
<p>为了熟悉语法, 把最近 <a href="http://www.jtianling.com/learn-python-by-game-examples-2.html">用 Python 写游戏</a>里面的例子实现了一下. 也算有些坑, 主要是在 Dart 和 JS/Dom 的交互上的, 比如获取键盘响应等事件上.</p>
<p><a href="https://github.com/jtianling/dart-pixi-test">源代码</a>放在 Github 上了, 因为是写的第一个 Dart 程序, 写的难看的地方, 就不要太在意了…</p>
<p>因为是 H5 版本, 可以直接通过这个链接看到效果:
<a href="http://www.jtianling.com/dart-pixi-test">http://www.jtianling.com/dart-pixi-test</a></p>
<h1 id="引用的库">引用的库</h1>
<p>pixi 的 Dart 封装:
<a href="https://pub.dev/packages/pixi">https://pub.dev/packages/pixi</a></p>
<p>pixijs:
<a href="https://www.pixijs.com">https://www.pixijs.com</a></p>
<p><a href="http://www.jtianling.com/dart-with-pixijs.html">用 Dart 加 Pixijs 写 HTML 游戏</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on March 09, 2020.</p>
http://www.jtianling.com/learn-python-by-game-examples-2
2020-03-08T00:00:00+08:00
2020-03-08T00:00:00+08:00
http://www.jtianling.com
<p>学编程后, 过了初期的语法熟悉阶段, 新手往往会比较迷茫, 因为也不知道编程能干嘛, 对于这种情况, 我的建议当然是实际的做一些项目, 这里用一些游戏和 Python 的例子, 来真正的了解和熟悉编程吧.</p>
<p>本文为该系列的第二篇</p>
<!-- more -->
<h1 id="主角就绪">主角就绪</h1>
<p>参考<a href="http://www.jtianling.com/learn-python-by-game-examples-1.html">教程的第一篇</a>及<a href="http://www.jtianling.com/learn-python-by-game-examples-1-answer.html">第一篇课后的答案</a>, 我们已经获得了一个操作上较为靠谱的主角, 暂时用一个小圆点来表示.
再次回顾一下代码:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">player_pos_x</span> <span class="o">=</span> <span class="mi">100</span>
<span class="n">player_pos_y</span> <span class="o">=</span> <span class="mi">100</span>
<span class="k">def</span> <span class="nf">draw</span><span class="p">():</span>
<span class="n">screen</span><span class="p">.</span><span class="n">clear</span><span class="p">()</span>
<span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">filled_circle</span><span class="p">((</span><span class="n">player_pos_x</span><span class="p">,</span> <span class="n">player_pos_y</span><span class="p">),</span>
<span class="mi">3</span><span class="p">,</span>
<span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">))</span>
<span class="n">up</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">down</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">left</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">right</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">def</span> <span class="nf">on_key_down</span><span class="p">(</span><span class="n">key</span><span class="p">):</span>
<span class="k">global</span> <span class="n">up</span><span class="p">,</span><span class="n">down</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span><span class="n">right</span>
<span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">UP</span><span class="p">:</span>
<span class="n">up</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">DOWN</span><span class="p">:</span>
<span class="n">down</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">LEFT</span><span class="p">:</span>
<span class="n">left</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">RIGHT</span><span class="p">:</span>
<span class="n">right</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">def</span> <span class="nf">update</span><span class="p">():</span>
<span class="k">global</span> <span class="n">up</span><span class="p">,</span> <span class="n">down</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">,</span> <span class="n">player_pos_x</span><span class="p">,</span> <span class="n">player_pos_y</span>
<span class="k">if</span> <span class="n">up</span><span class="p">:</span>
<span class="n">player_pos_y</span> <span class="o">=</span> <span class="n">player_pos_y</span> <span class="o">-</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">down</span><span class="p">:</span>
<span class="n">player_pos_y</span> <span class="o">=</span> <span class="n">player_pos_y</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">left</span><span class="p">:</span>
<span class="n">player_pos_x</span> <span class="o">=</span> <span class="n">player_pos_x</span> <span class="o">-</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">right</span><span class="p">:</span>
<span class="n">player_pos_x</span> <span class="o">=</span> <span class="n">player_pos_x</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">def</span> <span class="nf">on_key_up</span><span class="p">(</span><span class="n">key</span><span class="p">):</span>
<span class="k">global</span> <span class="n">down</span><span class="p">,</span><span class="n">up</span><span class="p">,</span><span class="n">left</span><span class="p">,</span><span class="n">right</span>
<span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">UP</span><span class="p">:</span>
<span class="n">up</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">DOWN</span><span class="p">:</span>
<span class="n">down</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">LEFT</span><span class="p">:</span>
<span class="n">left</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">RIGHT</span><span class="p">:</span>
<span class="n">right</span> <span class="o">=</span> <span class="bp">False</span>
</code></pre></div></div>
<h1 id="添加敌人">添加敌人</h1>
<p>只有主角, 没有敌人, 那叫什么游戏啊, 我们用小方块来表示敌人吧, 还记得怎么绘制方块吗?</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="n">player_pos_x</span> <span class="o">=</span> <span class="mi">100</span>
<span class="n">player_pos_y</span> <span class="o">=</span> <span class="mi">100</span>
<span class="n">enemy_pos_x</span> <span class="o">=</span> <span class="mi">200</span>
<span class="n">enemy_pos_y</span> <span class="o">=</span> <span class="mi">200</span>
<span class="k">def</span> <span class="nf">draw</span><span class="p">():</span>
<span class="n">screen</span><span class="p">.</span><span class="n">clear</span><span class="p">()</span>
<span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">filled_circle</span><span class="p">((</span><span class="n">player_pos_x</span><span class="p">,</span> <span class="n">player_pos_y</span><span class="p">),</span>
<span class="mi">3</span><span class="p">,</span>
<span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">))</span>
<span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">filled_rect</span><span class="p">(</span><span class="n">Rect</span><span class="p">((</span><span class="n">enemy_pos_x</span><span class="p">,</span> <span class="n">enemy_pos_y</span><span class="p">),</span> <span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">)),</span> <span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span>
</code></pre></div></div>
<p>以上的代码只有 <code class="language-plaintext highlighter-rouge">draw</code> 部分, 不过也能看到, 屏幕上多了一个红色, 看起来很危险的敌人.</p>
<h1 id="让敌人动起来">让敌人动起来</h1>
<p>敌人是静止的, 也没有什么意思, 我们先做一个能追踪主角的敌人吧. 这个时候, 几何知识不够丰富也没有关系, 我们想一想, 怎么样才能让敌人追上主角呢?
最朴素的思想是, 让敌人的 x 坐标和 y 坐标, 都尽量的向主角靠拢, 这个想法够朴素了吧, 代码如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">player_pos_x</span> <span class="o">=</span> <span class="mi">100</span>
<span class="n">player_pos_y</span> <span class="o">=</span> <span class="mi">100</span>
<span class="n">enemy_pos_x</span> <span class="o">=</span> <span class="mi">200</span>
<span class="n">enemy_pos_y</span> <span class="o">=</span> <span class="mi">200</span>
<span class="k">def</span> <span class="nf">draw</span><span class="p">():</span>
<span class="n">screen</span><span class="p">.</span><span class="n">clear</span><span class="p">()</span>
<span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">filled_circle</span><span class="p">((</span><span class="n">player_pos_x</span><span class="p">,</span> <span class="n">player_pos_y</span><span class="p">),</span>
<span class="mi">3</span><span class="p">,</span>
<span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">))</span>
<span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">filled_rect</span><span class="p">(</span><span class="n">Rect</span><span class="p">((</span><span class="n">enemy_pos_x</span><span class="p">,</span> <span class="n">enemy_pos_y</span><span class="p">),</span> <span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">)),</span> <span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span>
<span class="n">up</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">down</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">left</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">right</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">def</span> <span class="nf">on_key_down</span><span class="p">(</span><span class="n">key</span><span class="p">):</span>
<span class="k">global</span> <span class="n">up</span><span class="p">,</span><span class="n">down</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span><span class="n">right</span>
<span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">UP</span><span class="p">:</span>
<span class="n">up</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">DOWN</span><span class="p">:</span>
<span class="n">down</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">LEFT</span><span class="p">:</span>
<span class="n">left</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">RIGHT</span><span class="p">:</span>
<span class="n">right</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">def</span> <span class="nf">update</span><span class="p">():</span>
<span class="k">global</span> <span class="n">up</span><span class="p">,</span> <span class="n">down</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">,</span> <span class="n">player_pos_x</span><span class="p">,</span> <span class="n">player_pos_y</span><span class="p">,</span> <span class="n">enemy_pos_x</span><span class="p">,</span> <span class="n">enemy_pos_y</span>
<span class="k">if</span> <span class="n">up</span><span class="p">:</span>
<span class="n">player_pos_y</span> <span class="o">=</span> <span class="n">player_pos_y</span> <span class="o">-</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">down</span><span class="p">:</span>
<span class="n">player_pos_y</span> <span class="o">=</span> <span class="n">player_pos_y</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">left</span><span class="p">:</span>
<span class="n">player_pos_x</span> <span class="o">=</span> <span class="n">player_pos_x</span> <span class="o">-</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">right</span><span class="p">:</span>
<span class="n">player_pos_x</span> <span class="o">=</span> <span class="n">player_pos_x</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">enemy_pos_x</span> <span class="o"><</span> <span class="n">player_pos_x</span><span class="p">:</span>
<span class="n">enemy_pos_x</span> <span class="o">=</span> <span class="n">enemy_pos_x</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">elif</span> <span class="n">enemy_pos_x</span> <span class="o">></span> <span class="n">player_pos_x</span><span class="p">:</span>
<span class="n">enemy_pos_x</span> <span class="o">=</span> <span class="n">enemy_pos_x</span> <span class="o">-</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">enemy_pos_y</span> <span class="o"><</span> <span class="n">player_pos_y</span><span class="p">:</span>
<span class="n">enemy_pos_y</span> <span class="o">=</span> <span class="n">enemy_pos_y</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">elif</span> <span class="n">enemy_pos_y</span> <span class="o">></span> <span class="n">player_pos_y</span><span class="p">:</span>
<span class="n">enemy_pos_y</span> <span class="o">=</span> <span class="n">enemy_pos_y</span> <span class="o">-</span> <span class="mi">1</span>
<span class="k">def</span> <span class="nf">on_key_up</span><span class="p">(</span><span class="n">key</span><span class="p">):</span>
<span class="k">global</span> <span class="n">down</span><span class="p">,</span><span class="n">up</span><span class="p">,</span><span class="n">left</span><span class="p">,</span><span class="n">right</span>
<span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">UP</span><span class="p">:</span>
<span class="n">up</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">DOWN</span><span class="p">:</span>
<span class="n">down</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">LEFT</span><span class="p">:</span>
<span class="n">left</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">RIGHT</span><span class="p">:</span>
<span class="n">right</span> <span class="o">=</span> <span class="bp">False</span>
</code></pre></div></div>
<p>需要注意的时候, 注意什么样的代码, 写在什么地方, 让敌人向主角靠拢的代码, 记得写在 <code class="language-plaintext highlighter-rouge">update</code> 函数里面, 运行以后, 你会发现, 敌人现在直奔主角而去, 几乎难逃他的魔掌. 作为游戏玩法, 因为敌人的速度和主角一样, 主角几乎难逃魔掌, 并且, 我们能发现, 随着全局变量的增加, 我们的代码有太多的 <code class="language-plaintext highlighter-rouge">global</code> 变量需要声明, 是时候简化一下代码了.</p>
<h1 id="引入类型">引入类型</h1>
<p>我们用 <code class="language-plaintext highlighter-rouge">class</code> 来优化一下代码, 首先用一个 <code class="language-plaintext highlighter-rouge">Global</code> 的类型, 来简化全局变量的声明和使用, 另外引入 <code class="language-plaintext highlighter-rouge">Role</code>, <code class="language-plaintext highlighter-rouge">Player</code> 和 <code class="language-plaintext highlighter-rouge">Enmey</code>, 我们先从简化 <code class="language-plaintext highlighter-rouge">Draw</code>函数的实现开始:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">class</span> <span class="nc">Game</span><span class="p">:</span>
<span class="k">pass</span>
<span class="k">class</span> <span class="nc">Role</span><span class="p">:</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">):</span>
<span class="bp">self</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">x</span>
<span class="bp">self</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">y</span>
<span class="k">def</span> <span class="nf">draw</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">pass</span>
<span class="k">class</span> <span class="nc">Player</span><span class="p">(</span><span class="n">Role</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">draw</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">filled_circle</span><span class="p">((</span><span class="bp">self</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">y</span><span class="p">),</span>
<span class="mi">3</span><span class="p">,</span>
<span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">))</span>
<span class="k">class</span> <span class="nc">Enemy</span><span class="p">(</span><span class="n">Role</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">draw</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">filled_circle</span><span class="p">((</span><span class="bp">self</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">y</span><span class="p">),</span>
<span class="mi">3</span><span class="p">,</span>
<span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span>
<span class="n">g</span> <span class="o">=</span> <span class="n">Game</span><span class="p">()</span>
<span class="n">g</span><span class="p">.</span><span class="n">player</span> <span class="o">=</span> <span class="n">Player</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="mi">100</span><span class="p">)</span>
<span class="n">g</span><span class="p">.</span><span class="n">enemy</span> <span class="o">=</span> <span class="n">Enemy</span><span class="p">(</span><span class="mi">200</span><span class="p">,</span> <span class="mi">200</span><span class="p">)</span>
<span class="n">g</span><span class="p">.</span><span class="n">up</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">g</span><span class="p">.</span><span class="n">down</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">g</span><span class="p">.</span><span class="n">left</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">g</span><span class="p">.</span><span class="n">right</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">def</span> <span class="nf">draw</span><span class="p">():</span>
<span class="n">screen</span><span class="p">.</span><span class="n">clear</span><span class="p">()</span>
<span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">draw</span><span class="p">()</span>
<span class="n">g</span><span class="p">.</span><span class="n">enemy</span><span class="p">.</span><span class="n">draw</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">on_key_down</span><span class="p">(</span><span class="n">key</span><span class="p">):</span>
<span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">UP</span><span class="p">:</span>
<span class="n">g</span><span class="p">.</span><span class="n">up</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">DOWN</span><span class="p">:</span>
<span class="n">g</span><span class="p">.</span><span class="n">down</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">LEFT</span><span class="p">:</span>
<span class="n">g</span><span class="p">.</span><span class="n">left</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">RIGHT</span><span class="p">:</span>
<span class="n">g</span><span class="p">.</span><span class="n">right</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">def</span> <span class="nf">on_key_up</span><span class="p">(</span><span class="n">key</span><span class="p">):</span>
<span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">UP</span><span class="p">:</span>
<span class="n">g</span><span class="p">.</span><span class="n">up</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">DOWN</span><span class="p">:</span>
<span class="n">g</span><span class="p">.</span><span class="n">down</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">LEFT</span><span class="p">:</span>
<span class="n">g</span><span class="p">.</span><span class="n">left</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">RIGHT</span><span class="p">:</span>
<span class="n">g</span><span class="p">.</span><span class="n">right</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">def</span> <span class="nf">update</span><span class="p">():</span>
<span class="k">if</span> <span class="n">g</span><span class="p">.</span><span class="n">up</span><span class="p">:</span>
<span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">y</span> <span class="o">-=</span> <span class="mi">2</span>
<span class="k">if</span> <span class="n">g</span><span class="p">.</span><span class="n">down</span><span class="p">:</span>
<span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">y</span> <span class="o">+=</span> <span class="mi">2</span>
<span class="k">if</span> <span class="n">g</span><span class="p">.</span><span class="n">left</span><span class="p">:</span>
<span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">x</span> <span class="o">-=</span> <span class="mi">2</span>
<span class="k">if</span> <span class="n">g</span><span class="p">.</span><span class="n">right</span><span class="p">:</span>
<span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">x</span> <span class="o">+=</span> <span class="mi">2</span>
<span class="k">if</span> <span class="n">g</span><span class="p">.</span><span class="n">enemy</span><span class="p">.</span><span class="n">x</span> <span class="o"><</span> <span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">x</span><span class="p">:</span>
<span class="n">g</span><span class="p">.</span><span class="n">enemy</span><span class="p">.</span><span class="n">x</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">elif</span> <span class="n">g</span><span class="p">.</span><span class="n">enemy</span><span class="p">.</span><span class="n">x</span> <span class="o">></span> <span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">x</span><span class="p">:</span>
<span class="n">g</span><span class="p">.</span><span class="n">enemy</span><span class="p">.</span><span class="n">x</span> <span class="o">-=</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">g</span><span class="p">.</span><span class="n">enemy</span><span class="p">.</span><span class="n">y</span> <span class="o"><</span> <span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">y</span><span class="p">:</span>
<span class="n">g</span><span class="p">.</span><span class="n">enemy</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">g</span><span class="p">.</span><span class="n">enemy</span><span class="p">.</span><span class="n">y</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">elif</span> <span class="n">g</span><span class="p">.</span><span class="n">enemy</span><span class="p">.</span><span class="n">y</span> <span class="o">></span> <span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">y</span><span class="p">:</span>
<span class="n">g</span><span class="p">.</span><span class="n">enemy</span><span class="p">.</span><span class="n">y</span> <span class="o">-=</span> <span class="mi">1</span>
</code></pre></div></div>
<p>代码比原来多了一些内容, 要是还不明白 <code class="language-plaintext highlighter-rouge">Role</code>, <code class="language-plaintext highlighter-rouge">Player</code>, <code class="language-plaintext highlighter-rouge">Enemy</code> 几个类, 还有定义在 <code class="language-plaintext highlighter-rouge">Game</code> 类型的变量 <code class="language-plaintext highlighter-rouge">g</code> 中的 <code class="language-plaintext highlighter-rouge">player</code>, <code class="language-plaintext highlighter-rouge">enemy</code> 变量的含义, 建议赶紧回去复习一下 Python 的相关内容, 所谓面向对象编程, 不知道怎么定义类型和对象, 那可不行.
上面的代码, 我们的 <code class="language-plaintext highlighter-rouge">player</code>, <code class="language-plaintext highlighter-rouge">enemy</code> 对象, 自己处理了自己的 <code class="language-plaintext highlighter-rouge">draw</code> 而不是由外部来处理, 这种代码设计的方向, 我们称其为自己对自己负责, 这是一个能导向良好代码设计的方向, 因为只有所有对象都为自己负责, 才不需要把自己的更多内容, 告诉外部, 让外部控制自己. 做人也是这样, 不是吗?</p>
<h1 id="更负责的对象">更负责的对象</h1>
<p>既然要做一个负责任的对象, 当然不能仅仅为 <code class="language-plaintext highlighter-rouge">draw</code> 这一件事情负责, 自己的移动这么重要的事情, 当然也不能交给别人来做. 接下来, 我们让 <code class="language-plaintext highlighter-rouge">player</code>, <code class="language-plaintext highlighter-rouge">enmey</code> 对象自己也接管自己的移动吧.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">class</span> <span class="nc">Game</span><span class="p">:</span>
<span class="k">pass</span>
<span class="k">class</span> <span class="nc">Role</span><span class="p">:</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">max_speed</span><span class="p">):</span>
<span class="bp">self</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">x</span>
<span class="bp">self</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">y</span>
<span class="bp">self</span><span class="p">.</span><span class="n">max_speed</span> <span class="o">=</span> <span class="n">max_speed</span>
<span class="k">def</span> <span class="nf">get_pos_x</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">x</span>
<span class="k">def</span> <span class="nf">get_pos_y</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">y</span>
<span class="k">def</span> <span class="nf">draw</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">pass</span>
<span class="k">class</span> <span class="nc">Player</span><span class="p">(</span><span class="n">Role</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">max_speed</span><span class="p">):</span>
<span class="nb">super</span><span class="p">().</span><span class="n">__init__</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">max_speed</span><span class="p">)</span>
<span class="bp">self</span><span class="p">.</span><span class="n">_cur_speed_x</span> <span class="o">=</span> <span class="mi">0</span>
<span class="bp">self</span><span class="p">.</span><span class="n">_cur_speed_y</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">def</span> <span class="nf">draw</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">filled_circle</span><span class="p">((</span><span class="bp">self</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">y</span><span class="p">),</span>
<span class="mi">3</span><span class="p">,</span>
<span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">on_key_down</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span>
<span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">UP</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">_cur_speed_y</span> <span class="o">+=</span> <span class="o">-</span><span class="bp">self</span><span class="p">.</span><span class="n">max_speed</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">DOWN</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">_cur_speed_y</span> <span class="o">+=</span> <span class="bp">self</span><span class="p">.</span><span class="n">max_speed</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">LEFT</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">_cur_speed_x</span> <span class="o">+=</span> <span class="o">-</span><span class="bp">self</span><span class="p">.</span><span class="n">max_speed</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">RIGHT</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">_cur_speed_x</span> <span class="o">+=</span> <span class="bp">self</span><span class="p">.</span><span class="n">max_speed</span>
<span class="k">def</span> <span class="nf">on_key_up</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span>
<span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">UP</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">_cur_speed_y</span> <span class="o">-=</span> <span class="o">-</span><span class="bp">self</span><span class="p">.</span><span class="n">max_speed</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">DOWN</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">_cur_speed_y</span> <span class="o">-=</span> <span class="bp">self</span><span class="p">.</span><span class="n">max_speed</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">LEFT</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">_cur_speed_x</span> <span class="o">-=</span> <span class="o">-</span><span class="bp">self</span><span class="p">.</span><span class="n">max_speed</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">RIGHT</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">_cur_speed_x</span> <span class="o">-=</span> <span class="bp">self</span><span class="p">.</span><span class="n">max_speed</span>
<span class="k">def</span> <span class="nf">move</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="p">.</span><span class="n">x</span> <span class="o">+=</span> <span class="bp">self</span><span class="p">.</span><span class="n">_cur_speed_x</span>
<span class="bp">self</span><span class="p">.</span><span class="n">y</span> <span class="o">+=</span> <span class="bp">self</span><span class="p">.</span><span class="n">_cur_speed_y</span>
<span class="k">class</span> <span class="nc">Enemy</span><span class="p">(</span><span class="n">Role</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">draw</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">filled_circle</span><span class="p">((</span><span class="bp">self</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">y</span><span class="p">),</span>
<span class="mi">3</span><span class="p">,</span>
<span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">chase</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">player</span><span class="p">):</span>
<span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">x</span> <span class="o"><</span> <span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">get_pos_x</span><span class="p">():</span>
<span class="bp">self</span><span class="p">.</span><span class="n">x</span> <span class="o">+=</span> <span class="bp">self</span><span class="p">.</span><span class="n">max_speed</span>
<span class="k">elif</span> <span class="bp">self</span><span class="p">.</span><span class="n">x</span> <span class="o">></span> <span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">get_pos_x</span><span class="p">():</span>
<span class="bp">self</span><span class="p">.</span><span class="n">x</span> <span class="o">-=</span> <span class="bp">self</span><span class="p">.</span><span class="n">max_speed</span>
<span class="k">if</span> <span class="n">g</span><span class="p">.</span><span class="n">enemy</span><span class="p">.</span><span class="n">y</span> <span class="o"><</span> <span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">y</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">y</span> <span class="o">+=</span> <span class="bp">self</span><span class="p">.</span><span class="n">max_speed</span>
<span class="k">elif</span> <span class="n">g</span><span class="p">.</span><span class="n">enemy</span><span class="p">.</span><span class="n">y</span> <span class="o">></span> <span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">y</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">y</span> <span class="o">-=</span> <span class="bp">self</span><span class="p">.</span><span class="n">max_speed</span>
<span class="n">g</span> <span class="o">=</span> <span class="n">Game</span><span class="p">()</span>
<span class="n">g</span><span class="p">.</span><span class="n">player</span> <span class="o">=</span> <span class="n">Player</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="n">g</span><span class="p">.</span><span class="n">enemy</span> <span class="o">=</span> <span class="n">Enemy</span><span class="p">(</span><span class="mi">200</span><span class="p">,</span> <span class="mi">200</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">draw</span><span class="p">():</span>
<span class="n">screen</span><span class="p">.</span><span class="n">clear</span><span class="p">()</span>
<span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">draw</span><span class="p">()</span>
<span class="n">g</span><span class="p">.</span><span class="n">enemy</span><span class="p">.</span><span class="n">draw</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">on_key_down</span><span class="p">(</span><span class="n">key</span><span class="p">):</span>
<span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">on_key_down</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">on_key_up</span><span class="p">(</span><span class="n">key</span><span class="p">):</span>
<span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">on_key_up</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">update</span><span class="p">():</span>
<span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">.</span><span class="n">move</span><span class="p">()</span>
<span class="n">g</span><span class="p">.</span><span class="n">enemy</span><span class="p">.</span><span class="n">chase</span><span class="p">(</span><span class="n">g</span><span class="p">.</span><span class="n">player</span><span class="p">)</span>
</code></pre></div></div>
<p>这种自己为自己负责, 把内部的变量(也叫属性)不让外部使用, 而是仅仅自己知道和使用的方式, 我们称之为 <strong>封装</strong>, 按其字面意思的理解就很贴切了, 意思就是把自己封闭起来,装的很牛的样子…
上面的代码, 已经比原来的玩具代码, 更像正常该有的样子了, 类似 <code class="language-plaintext highlighter-rouge">draw</code>, <code class="language-plaintext highlighter-rouge">on_key_down</code> 函数里面的内容很少, 仅仅只有对某个对象的函数的调用, 而真正的逻辑, 都写在<strong>类</strong>里面.
上面的代码, 其实和前面刚添加完敌人时候的作用一模一样, 但是样子却变化很大, 首先可以自己感受一下, 两者的区别.</p>
<h1 id="课后练习">课后练习</h1>
<p>今天我们添加了敌人, 并且尝试用类封装了主角和敌人, 可能经过这么多修改, 了解了很多新的概念, 但是你还对这些复杂的操作将信将疑, 为什么我们需要这么麻烦呢? 前面的代码看起来也挺直观的, 不是也挺好的吗?
通过一个练习, 我们来感受一下这么封装的好处吧, 那就是, 我们随机的, 在屏幕上生成更多的敌人, 比如每 5 秒,添加 1 个, 上限 100 个.</p>
<p>简单的提示, 用一个列表来存储所有的敌人吧.</p>
<p><a href="http://www.jtianling.com/learn-python-by-game-examples-2.html">用 Python 写游戏 第二篇</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on March 08, 2020.</p>
http://www.jtianling.com/learn-python-by-game-examples-1
2020-03-07T00:00:00+08:00
2020-03-07T00:00:00+08:00
http://www.jtianling.com
<p>学编程后, 过了初期的语法熟悉阶段, 新手往往会比较迷茫, 因为也不知道编程能干嘛, 对于这种情况, 我的建议当然是实际的做一些项目, 这里用一些游戏和 Python 的例子, 来真正的了解和熟悉编程吧.</p>
<!-- more -->
<h1 id="前提">前提</h1>
<p>以下的教程基于 Python3.7, 并且假设你已经会 Python 的基本语法和概念了.<br />
要是还没有学会, Python 的各种教程太多了, 先找来学习一下吧.</p>
<p><a href="https://docs.python.org/zh-cn/3.7/tutorial/index.html">官方文档</a><br />
<a href="https://www.liaoxuefeng.com/wiki/1016959663602400">廖雪峰的 Python教程</a></p>
<h1 id="用什么写游戏">用什么写游戏</h1>
<p>写游戏一般都会用到一个叫<strong>游戏引擎</strong>的库, 游戏引擎的概念来自汽车引擎, 汽车引擎驱动汽车, 提供动力, 游戏引擎驱动游戏, 简化我们写游戏的步骤.</p>
<p>这里, 为了最友好的面向初学者, 我们首先用的是一个叫 pyzero 的游戏引擎, 这个引擎本身就是设计给初学者使用的, 所以接口和使用方式非常简单.</p>
<h1 id="准备环境">准备环境</h1>
<p>准备一个专业的环境, 一共分 3 步:</p>
<ol>
<li>安装 Python</li>
<li>安装 pyzero 库</li>
<li>找个编辑器, 比如 VSCode</li>
</ol>
<p>想长久享受编程乐趣的话, 可以自己学习一下怎么弄, 环境都没弄好, 估计 Python 也还没学会. 不过对于初学者, 还是有个简化的选项, 那就是 <a href="https://codewith.mu/"><strong>Mu</strong></a>, 下载, 安装, bong! 以上 3 步的工具, 就都有了.</p>
<p>不得不感叹, 现在的编程世界, 对初学者的大门是完全敞开的, 学不学得会, 只取决于想不想学, 其实没有什么门槛.</p>
<p>下面的教程, 都基于 Mu 来说明.</p>
<h1 id="mu-的操作">Mu 的操作</h1>
<p>Mu 上面一排大大的按钮, 选择 Mode, 在列表里面选择 Pygame Zero, 就表示我们准备用 pyzero 来写游戏了.
写完游戏, 按 Save 保存, 方便下次用 Load 选择文件(也可以直接把文件拖到编辑区域) 继续编写.
编写完后, 按 Play 运行游戏.</p>
<h1 id="第一个可运行的程序">第一个可运行的程序</h1>
<p>在编程里, 我们一般把一个方向, 最小规模的一个程序, 叫做 <strong>Hello World</strong> 程序, 这个惯例来自于一本上世纪特别流行的 C 语言教材, 有兴趣的朋友可以去了解一下.</p>
<p>下面看我们的程序:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">draw</span><span class="p">():</span>
<span class="n">screen</span><span class="p">.</span><span class="n">fill</span><span class="p">((</span><span class="mi">128</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span>
</code></pre></div></div>
<p>就这么夸张, 就两行代码, 一个函数…
简单说明一下, 定义一个叫 <strong>draw</strong> 的函数, 然后用 <strong>screen</strong> 对象里面的 <strong>fill</strong> 函数表示背景的填充.<br />
运行上面的代码, 你会得到一个夸张的红背景.</p>
<p>其中 <code class="language-plaintext highlighter-rouge">draw</code> 函数, 我们并没有像普通程序一样, 由我们自己调用, 而是由 pyzero 游戏引擎在恰当的时候调用, 我们一般把这种函数, 叫做 <strong>回调函数</strong>, 表示我们不直接调用, 也不关心具体什么时候这个函数被调用, 只提供实现, 而是由类似 pyzero 这样的游戏引擎或者框架调用.
当然, <code class="language-plaintext highlighter-rouge">draw</code> 函数, 就像它的英文意思一样, 表示需要在屏幕上画东西的时候被调用, 我们的 <code class="language-plaintext highlighter-rouge">screen.fill((128, 0, 0))</code> 表示我们具体想画的是什么东西.<br />
而 <code class="language-plaintext highlighter-rouge">screen</code> 对象, 属于 pyzero 游戏引擎帮我们实现的对象, 用来简化我们的绘制步骤, 下面我们会用到很多.</p>
<h1 id="简单解释一下颜色">简单解释一下颜色</h1>
<p>为什么上面 <code class="language-plaintext highlighter-rouge">(128, 0, 0)</code> 表示红色? 首先, 颜色的表示, 我们一般用所谓的光学三原色来表示, 也就是熟称的 红(Red), 绿(Green), 蓝(Blue), 我们往往用三个单词的第一个字母缩写, 即 RGB 来表示颜色的值, RGB 每个数值的范围一般是 0 到 255.<br />
上面的 <code class="language-plaintext highlighter-rouge">(128, 0, 0)</code> 是 Python 里面的 <a href="https://docs.python.org/zh-cn/3.7/tutorial/datastructures.html#tuples-and-sequences">元组</a>, 三个整数分别表示我们需要的颜色的 <code class="language-plaintext highlighter-rouge">(R, G, B)</code> 值.
我们可以尝试调整这个元组数值, 看看打开窗口颜色的变化. 比如 <code class="language-plaintext highlighter-rouge">(0, 255, 0)</code> 表示纯绿色.</p>
<h1 id="画一些其他的东西">画一些其他的东西</h1>
<p>比如, 我们先画一个白色的圆, 来表示主角.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">draw</span><span class="p">():</span>
<span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">circle</span><span class="p">((</span><span class="mi">160</span><span class="p">,</span> <span class="mi">120</span><span class="p">),</span> <span class="mi">3</span><span class="p">,</span> <span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">))</span>
</code></pre></div></div>
<p>运行程序, 能看到在漆黑像夜空一样的背景中, 有一个想星星一样的圆点.</p>
<p>这个圆点还是用 <code class="language-plaintext highlighter-rouge">screen</code> 对象来实现绘制, 现在我们用的是 <code class="language-plaintext highlighter-rouge">screen</code> 对象里面的 <code class="language-plaintext highlighter-rouge">draw</code> 对象的 <code class="language-plaintext highlighter-rouge">circle</code> 函数(中文表示填满的圆的意思).
函数的具体参数的含义, 可以参考<a href="https://pygame-zero.readthedocs.io/en/stable/builtins.html#screen">pyzero官网</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Draw the outline of a circle.
</span><span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">circle</span><span class="p">(</span><span class="n">pos</span><span class="p">,</span> <span class="n">radius</span><span class="p">,</span> <span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">b</span><span class="p">))</span>
</code></pre></div></div>
<p>参数解释:</p>
<ol>
<li>第一个参数, <code class="language-plaintext highlighter-rouge">(160, 120)</code> 也是一个元组, 表示我们想画的这个圆点的位置.</li>
<li><code class="language-plaintext highlighter-rouge">3</code> 表示我们想画的圆的半径, 也就是用来表示圆有多大</li>
<li><code class="language-plaintext highlighter-rouge">(255, 255, 255)</code>, 要是前面的内容看的认真, 看到这里就知道了, 这个表示圆的颜色</li>
</ol>
<p>知道了参数类型以后, 我们尝试改改这几个参数, 看看画出来的圆的效果变化情况吧.</p>
<h1 id="坐标的含义">坐标的含义</h1>
<p>位置坐标的含义, pyzero 里面用的位置, 使用的是所谓的屏幕坐标系, 以左上角为原点(即 0,0 点), X 向右增加, Y 向下增加.
<img src="/public/images/2020/axis.png" alt="坐标示意图" /></p>
<h1 id="更多形状">更多形状</h1>
<p>参考前面的<a href="https://pygame-zero.readthedocs.io/en/stable/builtins.html#screen">pyzero官网</a>, 只要你看明白了参数的类型, 试试更多的形状吧.</p>
<p>我推荐试试下面几个常用的, 下面是参数说明</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Draw a line from start to end. 画线
</span><span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">line</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">end</span><span class="p">,</span> <span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">b</span><span class="p">))</span>
<span class="c1"># Draw the outline of a circle.
</span><span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">circle</span><span class="p">(</span><span class="n">pos</span><span class="p">,</span> <span class="n">radius</span><span class="p">,</span> <span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">b</span><span class="p">))</span>
<span class="c1"># Draw a filled circle. 实心圆
</span><span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">filled_circle</span><span class="p">(</span><span class="n">pos</span><span class="p">,</span> <span class="n">radius</span><span class="p">,</span> <span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">b</span><span class="p">))</span>
<span class="c1"># Draw the outline of a rectangle. 矩形
</span><span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">rect</span><span class="p">(</span><span class="n">rect</span><span class="p">,</span> <span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">b</span><span class="p">))</span>
<span class="c1"># Draw a filled rectangle. 实心矩形
</span><span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">filled_rect</span><span class="p">(</span><span class="n">rect</span><span class="p">,</span> <span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">b</span><span class="p">))</span>
<span class="c1"># Draw text. 文字
</span><span class="n">draw</span><span class="p">.</span><span class="n">text</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="p">[</span><span class="n">pos</span><span class="p">,</span> <span class="p">]</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</code></pre></div></div>
<p>试试运行下面这个程序, 看看各种形状</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">draw</span><span class="p">():</span>
<span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">text</span><span class="p">(</span><span class="s">"Hello World"</span><span class="p">,</span> <span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="mi">100</span><span class="p">))</span>
<span class="c1"># Draw a line from start to end.
</span> <span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">line</span><span class="p">((</span><span class="mi">100</span><span class="p">,</span> <span class="mi">100</span><span class="p">),</span> <span class="p">(</span><span class="mi">300</span><span class="p">,</span> <span class="mi">100</span><span class="p">),</span> <span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span>
<span class="c1"># Draw the outline of a circle.
</span> <span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">circle</span><span class="p">((</span><span class="mi">300</span><span class="p">,</span> <span class="mi">100</span><span class="p">),</span> <span class="mi">10</span><span class="p">,</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span>
<span class="c1"># Draw a filled circle.
</span> <span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">filled_circle</span><span class="p">((</span><span class="mi">400</span><span class="p">,</span> <span class="mi">100</span><span class="p">),</span> <span class="mi">15</span><span class="p">,</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">))</span>
<span class="c1"># Draw the outline of a rectangle.
</span> <span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">rect</span><span class="p">(</span><span class="n">Rect</span><span class="p">((</span><span class="mi">100</span><span class="p">,</span> <span class="mi">200</span><span class="p">),</span> <span class="p">(</span><span class="mi">50</span><span class="p">,</span> <span class="mi">50</span><span class="p">)),</span> <span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">))</span>
<span class="c1"># Draw a filled rectangle.
</span> <span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">filled_rect</span><span class="p">(</span><span class="n">Rect</span><span class="p">((</span><span class="mi">200</span><span class="p">,</span> <span class="mi">200</span><span class="p">),</span> <span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="mi">100</span><span class="p">)),</span> <span class="p">(</span><span class="mi">128</span><span class="p">,</span> <span class="mi">128</span><span class="p">,</span> <span class="mi">128</span><span class="p">))</span>
</code></pre></div></div>
<h1 id="动画">动画</h1>
<p>我们都知道, 游戏不是静态的图片, 那么我们光靠静态的绘制, 也没有什么意思, 现在我们尝试让前面绘制的圆动起来.</p>
<p>看看下面这个程序:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">player_pos_x</span> <span class="o">=</span> <span class="mi">100</span>
<span class="n">player_pos_y</span> <span class="o">=</span> <span class="mi">100</span>
<span class="k">def</span> <span class="nf">draw</span><span class="p">():</span>
<span class="n">screen</span><span class="p">.</span><span class="n">clear</span><span class="p">()</span>
<span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">circle</span><span class="p">((</span><span class="n">player_pos_x</span><span class="p">,</span> <span class="n">player_pos_y</span><span class="p">),</span> <span class="mi">3</span><span class="p">,</span> <span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">update</span><span class="p">():</span>
<span class="k">global</span> <span class="n">player_pos_x</span><span class="p">,</span> <span class="n">player_pos_y</span>
<span class="n">player_pos_x</span> <span class="o">=</span> <span class="n">player_pos_x</span> <span class="o">+</span> <span class="mi">1</span>
</code></pre></div></div>
<p>先运行一下上面的程序, 能看到一个圆, 一直在向右移动.</p>
<h2 id="位置变量的定义">位置变量的定义</h2>
<p>我们在新的程序里面, 不再直接用位置坐标来绘制圆了, 我们用了 <code class="language-plaintext highlighter-rouge">player_pos_x</code>, <code class="language-plaintext highlighter-rouge">player_pos_y</code> 两个变量, 分别表示圆的 x 坐标位置和 y 坐标位置.</p>
<h2 id="update-回调函数">update 回调函数</h2>
<p>这个程序已经比前面复杂很多了, 特别是我们多了一个 <code class="language-plaintext highlighter-rouge">update</code> 函数, 就像 <code class="language-plaintext highlighter-rouge">draw</code> 函数一样, 这个函数也是一个回调函数, 一般我们将我们想做的游戏逻辑, 写在这个 <code class="language-plaintext highlighter-rouge">update</code> 函数里面.<br />
在这个例子里面, 我们是修改了 x 坐标的位置.
你自己尝试一下, 让圆从现在的从左往右移动, 改成从右往左试试.
还有, 可以尝试让圆在 y 轴上移动试试.</p>
<p>需要稍微注意的一点是, 在 <code class="language-plaintext highlighter-rouge">update</code> 函数里面, 因为需要修改函数外部的全局变量, 需要用 <code class="language-plaintext highlighter-rouge">global</code> 关键字说明, 不然 Python 里面默认是找局部变量的.</p>
<h2 id="清理屏幕">清理屏幕</h2>
<p>上面还有一个小的地方需要注意, 和原来的直接绘制圆不同, <code class="language-plaintext highlighter-rouge">draw</code> 函数里面在画圆之前, 多了一句 <code class="language-plaintext highlighter-rouge">screen.clear</code>. 表示在每次绘制的时候, 先把屏幕清理干净.
为了加深对清理屏幕作用的理解, 你可以尝试把这一行删掉, 看看会发生什么.</p>
<h1 id="操作">操作</h1>
<p>游戏, 也不光光是动画, 我们还要能操作才行. 说到这里, 我已经能给出游戏的完整表述的:</p>
<blockquote>
<p>游戏, 就是可以即时交互的动画</p>
</blockquote>
<p>试试下面的程序, 并在程序运行时, 尝试按按键盘或者任何可以操作键.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">player_pos_x</span> <span class="o">=</span> <span class="mi">100</span>
<span class="n">player_pos_y</span> <span class="o">=</span> <span class="mi">100</span>
<span class="k">def</span> <span class="nf">draw</span><span class="p">():</span>
<span class="n">screen</span><span class="p">.</span><span class="n">clear</span><span class="p">()</span>
<span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">circle</span><span class="p">((</span><span class="n">player_pos_x</span><span class="p">,</span> <span class="n">player_pos_y</span><span class="p">),</span> <span class="mi">3</span><span class="p">,</span> <span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">update</span><span class="p">():</span>
<span class="k">global</span> <span class="n">player_pos_x</span><span class="p">,</span> <span class="n">player_pos_y</span>
<span class="n">player_pos_x</span> <span class="o">=</span> <span class="n">player_pos_x</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">def</span> <span class="nf">on_key_down</span><span class="p">():</span>
<span class="k">global</span> <span class="n">player_pos_x</span><span class="p">,</span> <span class="n">player_pos_y</span>
<span class="n">player_pos_y</span> <span class="o">=</span> <span class="n">player_pos_y</span> <span class="o">+</span> <span class="mi">10</span>
</code></pre></div></div>
<p>我们能看到, 除了圆持续的还是往右移动以外, 每次按下任意一个按键, 圆都会往下移动 10 个像素.</p>
<p><code class="language-plaintext highlighter-rouge">on_key_down</code> 这个回调函数, 会在我们按下按键的时候产生被调用. 不过目前我们并没有判断按下的是什么按键.</p>
<h1 id="真实的操作">真实的操作</h1>
<p>前面的例子里面, 我们没有判断按下的按键是什么, 这个比较无聊, 我们来看看真正的简单操作应该是什么样的.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">player_pos_x</span> <span class="o">=</span> <span class="mi">100</span>
<span class="n">player_pos_y</span> <span class="o">=</span> <span class="mi">100</span>
<span class="k">def</span> <span class="nf">draw</span><span class="p">():</span>
<span class="n">screen</span><span class="p">.</span><span class="n">clear</span><span class="p">()</span>
<span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">circle</span><span class="p">((</span><span class="n">player_pos_x</span><span class="p">,</span> <span class="n">player_pos_y</span><span class="p">),</span> <span class="mi">3</span><span class="p">,</span> <span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">on_key_down</span><span class="p">(</span><span class="n">key</span><span class="p">):</span>
<span class="k">global</span> <span class="n">player_pos_x</span><span class="p">,</span> <span class="n">player_pos_y</span>
<span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">LEFT</span><span class="p">:</span>
<span class="n">player_pos_x</span> <span class="o">=</span> <span class="n">player_pos_x</span> <span class="o">-</span> <span class="mi">10</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">RIGHT</span><span class="p">:</span>
<span class="n">player_pos_x</span> <span class="o">=</span> <span class="n">player_pos_x</span> <span class="o">+</span> <span class="mi">10</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">UP</span><span class="p">:</span>
<span class="n">player_pos_y</span> <span class="o">=</span> <span class="n">player_pos_y</span> <span class="o">-</span> <span class="mi">10</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">DOWN</span><span class="p">:</span>
<span class="n">player_pos_y</span> <span class="o">=</span> <span class="n">player_pos_y</span> <span class="o">+</span> <span class="mi">10</span>
</code></pre></div></div>
<p>我们去掉了 <code class="language-plaintext highlighter-rouge">update</code> 函数, 并且判断了 <code class="language-plaintext highlighter-rouge">on_key_down</code> 被回调时, 传进来的 <code class="language-plaintext highlighter-rouge">key</code> 参数的值, 然后再按我们设定的规则去操作了这个圆, 试试吧.
具体的按键枚举, 还是可以在<a href="https://pygame-zero.readthedocs.io/en/stable/hooks.html#buttons-and-keys">pyzero官网</a>看到</p>
<h1 id="课后练习">课后练习</h1>
<p>这里我留下一个思考题, 我们平时操作游戏的时候, 不会一直不停的按键盘, 那样太累, 怎么样把上面的程序改成当我们按下方向键的时候, 圆一直移动呢?</p>
<p>这里给个提示, 你还需要实现 <code class="language-plaintext highlighter-rouge">on_key_up</code> 函数, 来获得我们松开键盘按键的信息.</p>
<h1 id="第一篇的结束">第一篇的结束</h1>
<p>我们现在已经了解了一个游戏需要的基础知识, 游戏开发的大门, 已经向我们打开了, 有想好要做什么游戏了吗?</p>
<p><a href="http://www.jtianling.com/learn-python-by-game-examples-1.html">用 Python 写游戏 第一篇</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on March 07, 2020.</p>
http://www.jtianling.com/learn-python-by-game-examples-1-answer
2020-03-07T00:00:00+08:00
2020-03-07T00:00:00+08:00
http://www.jtianling.com
<p>用 Python 写游戏 第一篇 课后习题答案</p>
<!-- more -->
<h1 id="第一篇课后练习">第一篇课后练习</h1>
<p>这里我留下一个思考题, 我们平时操作游戏的时候, 不会一直不停的按键盘, 那样太累, 怎么样把上面的程序改成当我们按下方向键的时候, 圆一直移动呢?</p>
<p>这里给个提示, 你还需要实现 <code class="language-plaintext highlighter-rouge">on_key_up</code> 函数, 来获得我们松开键盘按键的信息.</p>
<h1 id="答案">答案</h1>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="n">player_pos_x</span> <span class="o">=</span> <span class="mi">100</span>
<span class="n">player_pos_y</span> <span class="o">=</span> <span class="mi">100</span>
<span class="k">def</span> <span class="nf">draw</span><span class="p">():</span>
<span class="n">screen</span><span class="p">.</span><span class="n">clear</span><span class="p">()</span>
<span class="n">screen</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">filled_circle</span><span class="p">((</span><span class="n">player_pos_x</span><span class="p">,</span> <span class="n">player_pos_y</span><span class="p">),</span> <span class="mi">3</span><span class="p">,</span> <span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">))</span>
<span class="n">up</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">down</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">left</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">right</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">def</span> <span class="nf">on_key_down</span><span class="p">(</span><span class="n">key</span><span class="p">):</span>
<span class="k">global</span> <span class="n">up</span><span class="p">,</span><span class="n">down</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span><span class="n">right</span>
<span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">UP</span><span class="p">:</span>
<span class="n">up</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">DOWN</span><span class="p">:</span>
<span class="n">down</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">LEFT</span><span class="p">:</span>
<span class="n">left</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">RIGHT</span><span class="p">:</span>
<span class="n">right</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">def</span> <span class="nf">update</span><span class="p">():</span>
<span class="k">global</span> <span class="n">up</span><span class="p">,</span> <span class="n">down</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">,</span> <span class="n">player_pos_x</span><span class="p">,</span> <span class="n">player_pos_y</span>
<span class="k">if</span> <span class="n">up</span><span class="p">:</span>
<span class="n">player_pos_y</span> <span class="o">=</span> <span class="n">player_pos_y</span> <span class="o">-</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">down</span><span class="p">:</span>
<span class="n">player_pos_y</span> <span class="o">=</span> <span class="n">player_pos_y</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">left</span><span class="p">:</span>
<span class="n">player_pos_x</span> <span class="o">=</span> <span class="n">player_pos_x</span> <span class="o">-</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">right</span><span class="p">:</span>
<span class="n">player_pos_x</span> <span class="o">=</span> <span class="n">player_pos_x</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">def</span> <span class="nf">on_key_up</span><span class="p">(</span><span class="n">key</span><span class="p">):</span>
<span class="k">global</span> <span class="n">down</span><span class="p">,</span><span class="n">up</span><span class="p">,</span><span class="n">left</span><span class="p">,</span><span class="n">right</span>
<span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">UP</span><span class="p">:</span>
<span class="n">up</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">DOWN</span><span class="p">:</span>
<span class="n">down</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">LEFT</span><span class="p">:</span>
<span class="n">left</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">keys</span><span class="p">.</span><span class="n">RIGHT</span><span class="p">:</span>
<span class="n">right</span> <span class="o">=</span> <span class="bp">False</span>
</code></pre></div></div>
<p>该习题完成后, 我们就有了一个可以较自由操作的主角了, 期待接下来的课程吧.</p>
<p><a href="http://www.jtianling.com/learn-python-by-game-examples-1-answer.html">用 Python 写游戏 第一篇 课后习题答案</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on March 07, 2020.</p>
http://www.jtianling.com/review-of-principles
2018-12-16T00:00:00+08:00
2018-12-16T00:00:00+08:00
http://www.jtianling.com
<p>作者瑞·达利欧(Ray Dalio), 是世界上当前资产规模最大的对冲基金, 桥水基金的创始人. 听着 “原则” 这种书名, 又是所谓 “成功人士” 写的书, 放在两年前的话, 这种书我光看介绍, 都会直接过滤掉, 无非就是那种 “成功人士” 向你兜售一些所谓的成功秘方, 只要按照”我”的做, 你就能像”我”一样成功这种.<br />
但是, 去年我因为别人推荐, 看了查理芒格的 “穷查理宝典”, 对做投资的人有了一些新的认识, 甚至对这个世界也有了一些新的认识. 总的来说, 就是我发现, 要是一个人纯粹靠投资就能成功, 一定是对这个世界的运行规律, 有非常深刻的认识, 而为了对这个世界有充分的认识呢, 这个人读的书一定非常多, 比如查理芒格, 你看他的书, 会感觉他就不像个做投资的, 更像个学者, 或者哲学家那种, 这个以后有机会再说说.<br />
实际上, 看完 “原则” 这本书, 也会觉得 瑞·达利欧 是一个很理想主义的学者.<br />
原则这本书分三部分, 第一部分是作者的自传, 第二, 三部分分别是生活原则和工作原则, 原则有好几百条, 我只选择感触最深的三个讲一下.</p>
<!-- more -->
<h1 id="作者的经历">作者的经历</h1>
<p>第一部分的故事非常有意思, 瑞·达利欧讲述了他自己怎么一次又一次的犯下很严重的错误, 好像每一次都快破产了, 然后每次犯错后是怎么思考的, 最后他得出来了一个很有意思的思维过程, 那就是从认为 “我是对的”, 变成问自己 “我怎么知道我是对的”, 而回答这个问题的方式, 他想的是跟其他人交流, 但是呢, 不是去交流其他人的意见或结论, 而是去理解其他人的推理过程, 并且还让别人对自己的推理过程进行 “压力测试”. 他说, “我们都可以通过这种方式降低自己犯错的可能性”.<br />
我想下一次我要做一些特别重要的决定的时候, 可能真会试试这个办法. 同时, 现在我自己有一些想法的时候, 也愿意把推理的过程讲出来, 这样要是有问题, 别人也能指出来.<br />
还有个故事, 我印象挺深的, 他说到他因为精力原因, 放弃了 “桥水中国合作伙伴” 这个公司的时候, 想法很有意思, 他明明确信中国会在 21 世纪成为全球最大的经济体, 而且他要是把全部精力投入到中国这边的公司的话, 他会取得很大的成功, 但是, 尽管错过了这个好机会, 但他说他并不后悔他的决定, 他说</p>
<blockquote>
<p>如果你以勤奋和有创造性的方式工作, 你几乎可以得到你想要的任何东西, 但你不可能同时得到所有东西.<br />
成熟意味着你可以放弃一些好的选择, 从而追求更好的选择.</p>
</blockquote>
<p>他说的意思首先是说不要贪心, 而且, 成熟的选择, 意味着放弃, 我觉得比一般人看问题, 更深刻的地方在, 甚至放弃的还不是差的选择, 那没什么好纠结的, 为了更好的选择, 我们连一些很好的选择都应该放弃.</p>
<h1 id="真实的面对自己--极度求真和极度透明">真实的面对自己 + 极度求真和极度透明</h1>
<p>然后是我感触最深的两个原则.<br />
讲生活原则的时候, 瑞·达利欧说要真实的面对自己, 讲工作原则的时候, 瑞·达利欧说要极度求真和极度透明.<br />
我觉得这两个事情本质上是类似的, 一个是真实的面对自己, 一个是真实的对待他人.</p>
<h2 id="先说真实的面对自己">先说真实的面对自己</h2>
<p>首先, 真实的面对自己真的很难, 因为人的本性就是会拔高自己的, 比如瑞·达利欧就举了个例子, 假如让大家评估自己在某个机构中的贡献比例, 得到的总数大概是 300%, 也就是平均每人会高估自己 3 倍.<br />
其次, 人是不会愿意暴露自己缺点的, 往往要隐藏起来, 这样在别人的心目中的形象会更好一些.<br />
然后, 人都愿意听好话, 不会愿意承认自己实际是有缺点的, 听到对自己的批评本身就是有很强的挫折感, 很痛苦.<br />
那要对自己真实, 怎么做到呢:</p>
<ol>
<li>不要为自身形象担心, 只需关心能不能实现你的目标.</li>
<li>不要让痛苦妨碍进步, 要理解对自己不真实本身, 实际是会阻碍自己进步的, 你都觉得自己啥都知道了, 那还学啥, 我们的目标应该是要真的成为一个很厉害的人, 而不是装成一个很厉害的人.</li>
<li>自我归因, 不要把不好的结果归咎于任何人, 从自己身上找原因.</li>
</ol>
<h2 id="真实的面对他人">真实的面对他人</h2>
<p>瑞·达利欧说的极度透明到什么地步呢, 他说的是:</p>
<blockquote>
<p>若不想当面议论别人, 背地里也不要说, 要批评别别人就当面指出来</p>
</blockquote>
<p>这一条, 对于下属员工也适用的话, 就需要非常透明和真实才行, 还得考虑下属能否真能接受. 就算在美国, 我看瑞·达利欧自己书中也说有媒体说他疯了, 而且看到一些文章说, 桥水的新员工离职率比较高.</p>
<h1 id="培养人">培养人</h1>
<ol>
<li>不断的提供反馈, 这也是我对培养人感觉最有用的手段</li>
<li>反馈包括鼓励和批评, 需要准确的评价</li>
<li>授人以渔</li>
<li>允许犯错, 瑞·达利欧说 他对别人宽容到什么程度时, 说:</li>
</ol>
<blockquote>
<p>我可以容忍你把车蹭掉了漆或撞凹了一块, 但我不会冒很大风险让你把车子给毁了.</p>
</blockquote>
<p>怎么把被培养的人犯的错误控制在能接受的范围内, 本身也是 leader 的职责.</p>
<h1 id="还有很多值得一提的原则">还有很多值得一提的原则</h1>
<ol>
<li>当心 “温水煮青蛙综合症”, 人们都有慢慢习惯于不可接受事物的倾向. 在公司看到不少这种情况, 我后来了解到这种行为是有心理学基础的, 参看 <a href="https://wiki.mbalib.com/wiki/%E4%B9%A0%E5%BE%97%E6%80%A7%E6%97%A0%E5%8A%A9">“习得性无助”</a>, 原来看到乔布斯的 “Stay hungry, Stay foolish”, 欣赏的是用hungry和foolish来表达自己求知的状态, 现在其实发现, 最有力的字是 “Stay”, 一时hungry/foolish易, “stay”最难.</li>
<li>把公司当做一台运转的机器, 由”文化和人”两个部件构成, 当出现问题是, 诊断问题的步骤:</li>
</ol>
<blockquote>
<ol>
<li>结果是好是坏?</li>
<li>谁对结果负责?</li>
<li>如果结果不好, 是因为责任人能力不够还是机器设计有问题?</li>
</ol>
</blockquote>
<p>并且要探寻到问题的根源, 而不是表面.</p>
<ol>
<li>问责过程要触及你直接下属的下一级</li>
<li>创意择优的决策方式</li>
<li>几个公式:</li>
</ol>
<blockquote>
<p>梦想 + 现实 + 决心 = 成功的生活<br />
痛苦 + 反思 = 进步<br />
思考 -> 原则 -> 算法 -> 好决策<br />
创意择优 = 极度求真 + 极度透明 + 可信度加权的决策</p>
</blockquote>
<p>好内容太多, 无法一一列举.</p>
<h1 id="结束">结束</h1>
<p>用瑞·达利欧在第一章前就写到的这句话结尾:</p>
<blockquote>
<p>时间就像一条河流, 带着我们顺流而下, 我们会碰到各种问题, 需要我们去决策, 但我们没有办法停下来, 也没有办法躲避, 只能用我们选择的最好的方式去应对.</p>
</blockquote>
<p><a href="http://www.jtianling.com/review-of-principles.html">读瑞 达利欧的"原则"</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on December 16, 2018.</p>
http://www.jtianling.com/private-module-in-golang
2018-09-28T00:00:00+08:00
2018-09-28T00:00:00+08:00
http://www.jtianling.com
<p>在新的Golang 1.11版本中, 官方实验性添加了一种标准的 Module 写法, 用于替代原来非常不方便的 vendor, GOPATH 那一套东西, 经过了这么长时间, 那批老顽固总算是搞清楚了一些什么. Golang 原来的包管理, 可以说是新一代的语言里面最垃圾的.</p>
<!-- more -->
<h1 id="一个-module-的诞生-go-module-的-hello-world">一个 Module 的诞生, go module 的 hello world</h1>
<p>首先, 在 GOPATH 目录以外, 建一个 Module 想放的目录, 是的, 不要放在原来的 GOPATH 里面.<br />
通过 <code class="language-plaintext highlighter-rouge">go mod init MODULE_NAME</code> 来初始化一个想要建立的 Module, 比如下面这样:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>go mod init github.com/jtianling/goModule
go: creating new go.mod: module github.com/jtianling/goModule
</code></pre></div></div>
<p>此时会在当前目录下面新建一个叫 go.mod 的文件, 你可以理解成类似 package.json 的文件(事实上格式也挺像的)</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$cat</span> go.mod
module github.com/jtianling/goModule
</code></pre></div></div>
<p>然后开始创建自己 Module 的内容吧</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">goModule</span>
<span class="k">import</span> <span class="p">(</span>
<span class="s">"fmt"</span>
<span class="p">)</span>
<span class="k">func</span> <span class="n">Print</span><span class="p">()</span> <span class="p">{</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"hello, world"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>提交项目文件到 github 以后, 我们找个工程来应用这个新写的 Module.</p>
<p>新建一个 goModuleTest 目录, 用 <code class="language-plaintext highlighter-rouge">go mod init goModuleTest</code> 再初始化这个测试工程.<br />
创建一个调用前面 module 的 main.go 文件:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>
<span class="k">import</span> <span class="p">(</span>
<span class="s">"github.com/jtianling/goModule"</span>
<span class="p">)</span>
<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
<span class="n">goModule</span><span class="o">.</span><span class="n">Print</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>
<p>用 <code class="language-plaintext highlighter-rouge">go build</code> 尝试编译运行</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>go build
go: finding github.com/jtianling/goModule latest
go: downloading github.com/jtianling/goModule v0.0.0-20180928104915-d4c10f8ba563
<span class="nv">$.</span>/goModuleTest
hello, world
</code></pre></div></div>
<p>运行没有毛病, 而且自动化的下载了我们引用了的库.</p>
<h1 id="版本控制">版本控制</h1>
<p>go module 使用了<a href="https://semver.org/">语义化的版本号</a>, 简单的说就是v(major).(minor).(patch), 实践中, 只要 API 不兼容的时候, 就应该提 major 版本, 增加 API 或者优化, 可以自增加 minor 版本号.<br />
在 goModule 目录下,</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>git tag v0.0.1
<span class="nv">$ </span>git push <span class="nt">--tags</span>
Total 0 <span class="o">(</span>delta 0<span class="o">)</span>, reused 0 <span class="o">(</span>delta 0<span class="o">)</span>
To github.com:jtianling/goModule.git
<span class="k">*</span> <span class="o">[</span>new tag] v0.0.1 -> v0.0.1
</code></pre></div></div>
<p>然后删掉 goMoudleTest 工程, 再来一次, 结果会有些不一样:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>go build
go build
go: finding github.com/jtianling/goModule v0.0.1
go: downloading github.com/jtianling/goModule v0.0.1
<span class="nv">$ </span><span class="nb">cat </span>go.mod
module goModuleTest
require github.com/jtianling/goModule v0.0.1
</code></pre></div></div>
<p>没错, 增加了版本号, 我们试试升级自己的 module, 并 <code class="language-plaintext highlighter-rouge">git tag v0.0.2</code> 试试,
然后在 goModuleTest 工程中, 可以通过传统的 <code class="language-plaintext highlighter-rouge">go get</code> 加指定的版本号升级:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ go get github.com/jtianling/goModule@v0.0.2
go: finding github.com/jtianling/goModule v0.0.2
go: downloading github.com/jtianling/goModule v0.0.2
$ cat go.mod
module goModuleTest
require github.com/jtianling/goModule v0.0.2
</code></pre></div></div>
<p>可以看到 go.mod 里面的内容也自动变了. 此时再 build 然后运行, 会发现 module 正常更新了.<br />
假如不想指定版本, 仅仅想更新到最新版本, 可以通过<br />
<code class="language-plaintext highlighter-rouge">go get github.com/jtianling/goModule@latest</code> 的形式更新到最新版<br />
<code class="language-plaintext highlighter-rouge">go get -u</code> 的形式升级所有的依赖库</p>
<h1 id="多-package-的-module">多 package 的 module</h1>
<p>一个 package 的 module 好说, 假如你的 module 包含多个 package, 那么所有的 package 指定的时候, 直接用 package 明就好, 但是 import 的时候应该有共同的前缀.
如下:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// content.go in goModule/content</span>
<span class="k">package</span> <span class="n">content</span>
<span class="k">import</span> <span class="p">(</span>
<span class="s">"fmt"</span>
<span class="p">)</span>
<span class="k">func</span> <span class="n">Print</span><span class="p">()</span> <span class="p">{</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"hello, world v0.0.3"</span><span class="p">)</span>
<span class="p">}</span>
<span class="c">// module.go</span>
<span class="k">package</span> <span class="n">goModule</span>
<span class="k">import</span> <span class="p">(</span>
<span class="s">"github.com/jtianling/goModule/content"</span>
<span class="p">)</span>
<span class="k">func</span> <span class="n">Print</span><span class="p">()</span> <span class="p">{</span>
<span class="n">content</span><span class="o">.</span><span class="n">Print</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>
<h1 id="module-测试">module 测试</h1>
<p>module 文件同样支持 golang 自带的 unit test 方法, 可以方便的在发布前进行单元测试:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// goModule_test.go</span>
<span class="k">package</span> <span class="n">goModule_test</span>
<span class="k">import</span> <span class="p">(</span>
<span class="s">"testing"</span>
<span class="s">"github.com/jtianling/goModule"</span>
<span class="p">)</span>
<span class="k">func</span> <span class="n">TestPrint</span><span class="p">(</span><span class="n">t</span> <span class="o">*</span><span class="n">testing</span><span class="o">.</span><span class="n">T</span><span class="p">)</span> <span class="p">{</span>
<span class="n">goModule</span><span class="o">.</span><span class="n">Print</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>
<p>测试方法也一样</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>go <span class="nb">test
</span>hello, world v0.0.3
PASS
ok github.com/jtianling/goModule 0.005s
</code></pre></div></div>
<h1 id="私有的-module">私有的 module</h1>
<p>对于公开的 module, 按上面的做法已经够用了, 私有的 module 需要对 module 的引用做一些处理, 因为 <code class="language-plaintext highlighter-rouge">go get</code> 实际是利用了git, 所以我们通过 <code class="language-plaintext highlighter-rouge">git config</code> 改改 url 就能做到. 下面以 github 为例.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>git config <span class="nt">--global</span> url.<span class="s2">"git@github.com:"</span>.insteadOf <span class="s2">"https://github.com/"</span>
</code></pre></div></div>
<p>对于自己的私有仓库, 可能还需要用 http:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>git config <span class="nt">--global</span> url.<span class="s2">"git@github.com:"</span>.insteadOf <span class="s2">"http://github.com/"</span>
</code></pre></div></div>
<h1 id="参考">参考</h1>
<p>1.<a href="https://github.com/golang/go/wiki/Modules">golang wiki</a></p>
<p><a href="http://www.jtianling.com/private-module-in-golang.html">写一个自己的Golang Module(Golang1.11以后版本支持)</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on September 28, 2018.</p>
http://www.jtianling.com/review-of-a-system-of-patterns
2018-02-28T00:00:00+08:00
2018-02-28T00:00:00+08:00
http://www.jtianling.com
<p>该书把软件的设计模式分类分的更细, 在 GOF 设计模式上, 增加了一个架构模式, 在下面增加了一个 “成例”(Idiom), 也叫代码模式. <br />
书中也算是理清了一些概念, 并给出了一些概念的定义, 但是整体看下来, 并不如 GOF 的设计模式那么经典, 特别是模式的选择上, 要么是一个分类只提供1个模式, 要么是我感觉一些所谓的模式根本不足以支撑这个分类, 还有的模式横跨了几个分类…
另外, 看了这本书后远不如看GOF的书后那种大呼过瘾的感觉, 而是感觉世间的设计模式只有一种–增加中间层</p>
<!-- more -->
<p><img src="/public/images/2018/mindmap-of-a-system-of-patterns.png" alt="面向模式的软件架构1-模式系统一书的思维导图" /></p>
<p><a href="http://www.jtianling.com/review-of-a-system-of-patterns.html">读"面向模式的软件架构1-模式系统"</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on February 28, 2018.</p>
http://www.jtianling.com/review-of-use-your-head
2018-02-26T00:00:00+08:00
2018-02-26T00:00:00+08:00
http://www.jtianling.com
<p>用一天的上下班路上的时间把 “启动大脑” 一书快速略读了一遍, 大概花了1个多小时, 用的方法就是书上介绍的, 先看目录, 找到目标, 然后再略读一遍, 然后再回头复习一遍, 顺便画了下面的思维导图.<br />
最近一年听了 “得到” 上不少的书籍音频, 但是感觉假如能自己快速的阅读的话, 还是需要自己读才能有比较深刻的印象, 不然光凭听一遍, 基本上是过耳忘.</p>
<!-- more -->
<p><img src="/public/images/2018/mindmap-of-use-your-head.png" alt="启动大脑一书的思维导图" /></p>
<p><a href="http://www.jtianling.com/review-of-use-your-head.html">读"启动大脑"</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on February 26, 2018.</p>
http://www.jtianling.com/review-of-the-mind-map-book
2018-02-26T00:00:00+08:00
2018-02-26T00:00:00+08:00
http://www.jtianling.com
<p>最近再次简单的略读了一下”思维导图”这本书, 感觉还是值得看一下的, 毕竟名气那么大, 至于怎么用, 那就看自己了. 顺便, 为”思维导图” 一书画了个思维导图.</p>
<!-- more -->
<p><img src="/public/images/2018/mindmap-of-the-mind-map-book.png" alt="思维导图一书的思维导图" /></p>
<p><a href="http://www.jtianling.com/review-of-the-mind-map-book.html">读"思维导图"</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on February 26, 2018.</p>
http://www.jtianling.com/talk-about-the-first-programming-language-you-learn
2017-10-28T00:00:00+08:00
2017-10-28T00:00:00+08:00
http://www.jtianling.com
<p>很多初学编程的人都喜欢问一个问题, 那就是刚开始学编程, 首先学什么语言好啊, 在不同的阶段, 我自己关于这个问题的思考也不一样, 这里聊聊我现在的思考.</p>
<!-- more -->
<p><strong>目录</strong>:</p>
<ul id="markdown-toc">
<li><a href="#常见的回答" id="markdown-toc-常见的回答">常见的回答</a> <ul>
<li><a href="#不屑的态度" id="markdown-toc-不屑的态度">不屑的态度</a></li>
<li><a href="#从简单的入门" id="markdown-toc-从简单的入门">从简单的入门</a></li>
</ul>
</li>
<li><a href="#我的思考" id="markdown-toc-我的思考">我的思考</a> <ul>
<li><a href="#语言决定了工作的领域" id="markdown-toc-语言决定了工作的领域">语言决定了工作的领域</a></li>
<li><a href="#语言的选择-也是语言环境的选择" id="markdown-toc-语言的选择-也是语言环境的选择">语言的选择, 也是语言环境的选择</a></li>
<li><a href="#一开始熟悉的语言-决定了你的思维方式" id="markdown-toc-一开始熟悉的语言-决定了你的思维方式">一开始熟悉的语言, 决定了你的思维方式</a></li>
<li><a href="#语言决定了你的学习方向" id="markdown-toc-语言决定了你的学习方向">语言决定了你的学习方向</a></li>
<li><a href="#你还觉得入门语言的选择不重要吗" id="markdown-toc-你还觉得入门语言的选择不重要吗">你还觉得入门语言的选择不重要吗</a></li>
</ul>
</li>
<li><a href="#怎么选择入门语言" id="markdown-toc-怎么选择入门语言">怎么选择入门语言</a></li>
<li><a href="#我是怎么选择的" id="markdown-toc-我是怎么选择的">我是怎么选择的</a></li>
<li><a href="#最后" id="markdown-toc-最后">最后</a></li>
</ul>
<h1 id="常见的回答">常见的回答</h1>
<h2 id="不屑的态度">不屑的态度</h2>
<p>常常能看到很多<em>牛人</em>对这种问题都是很不屑的, 包括我在以前某个阶段, 也是这种态度, 总体上的意思就是, 假如你真成为了程序员, 那么你总归会学会一大堆编程语言, 所以, 你刚开始学什么并不重要, 学就好了, 重要的是你快点开始学.<br />
现在我认为这种态度是有问题的, 因为最后你能吃饱, 所以吃什么, 怎么吃, 吃东西的顺序就不重要? 当然不是.</p>
<h2 id="从简单的入门">从简单的入门</h2>
<p>稍微靠谱点的人, 会推荐你从某个简单的语言入门, 比如Python, JavaScript, 理由也是类似语言不重要, 重要的是先学会编程思考的逻辑和通过入门语言学会常见编程语言的特性, 然后就可以触类旁通, 用任何你实际需要的语言工作就好了.<br />
那么问题来了, 入门语言只要是相对简单, 并且没有什么大的毛病就好了吗? 其实没有那么简单.</p>
<h1 id="我的思考">我的思考</h1>
<p>我觉得一般常见的回答都把入门的语言选择这个事情考虑的太简单了, 也把这个决定的影响看的太小.<br />
刚开始学编程时选择的入门语言对初学者的影响其实非常大, 起码有以下几点:</p>
<ol>
<li>语言决定了工作的领域</li>
<li>语言的选择, 也是语言环境的选择</li>
<li>语言决定了你的思维方式</li>
<li>语言决定了你的学习方向</li>
</ol>
<h2 id="语言决定了工作的领域">语言决定了工作的领域</h2>
<p>初学的编程语言, 很可能就是初学者找的第一份工作用的语言, 而因为不同的语言都有自己擅长的领域, 所以这个语言的选择几乎就决定了你第一份工作的领域.<br />
要求初学者先从某个语言入门(比如Python), 然后再考虑自己想找什么工作, 再学会这个工作会用的语言, 这个弯路走的就有点远了.</p>
<p>进一步的说, 就算同样是编程, 在不同的细分领域之间的切换其实是有比较大的成本的, 也没那么简单, 对于一些人来说(特别是没有那么愿意不停折腾的人) 这可能就是你一辈子工作的领域.</p>
<p>这个, 当然需要慎之又慎.</p>
<h2 id="语言的选择-也是语言环境的选择">语言的选择, 也是语言环境的选择</h2>
<p>说只需要学会语言特性, 就能简单的在多种语言中切换的人, 十有八九的确是用新的语言在做一些简单的初级工作而已, 并且还是低效的在工作. 实际的工作并没有那么简单.<br />
对一门语言的学习, 最后真正决定效率的是不仅仅是语法的熟悉, 还有对新语言的惯例的熟悉, 编程风格的熟悉, 运行环境的了解, 社区的了解, 常见库的 API 的熟悉(包括标准库).<br />
这就像你也许以为从 Python 切换到 Ruby 很简单, 因为这两个语言基本上就是一个语言(Guido van Rossum自己说的), 互相的特性也差不太多, 但是要达到一个非常熟悉 Ruby 的程序员一样的工作效率, 这中间还有很多路要走, 哪怕是最接近的切换, 比如从Python Django开发到 Ruby on Rails开发, 你还是需要先熟悉 Rails 的 API, 不同的HTML模板语言, Ruby 不一样的对集合的惯用处理方式, Ruby 的Block, Proc, Ruby 的 Gem, Bundle 的用法, 可能还需要有 Rakefile 工具的使用, Ruby 自己的版本控制, 这些哪是简单的看看语法, 说切换就能切换的.<br />
更何况, 现实中的工作内容切换, 可比从Python Django 到 Ruby on Rails 要复杂的多. 慎重的选择一个入门语言, 并且熟悉相关的环境, 能少浪费很多不必要浪费的时间.</p>
<h2 id="一开始熟悉的语言-决定了你的思维方式">一开始熟悉的语言, 决定了你的思维方式</h2>
<blockquote>
<p>语言的界限就是一个人世界的界限
– 维特根斯坦</p>
</blockquote>
<p>一个人的母语, 是会很大的影响一个人的思维模式的, 一个民族的语言里面没有可以用词来描述的东西, 这个民族的人很难了解. 据说中国人数学比欧美国家好, 有个很大的原因是, 中国的的数字发音都是单音节, 比英语中很多数字的多音节发音效率要高, 这个会影响计算时的速度.</p>
<p>一开始学习的编程语言, 就相当于你编程时的母语, 影响效果也是一样的深远, 你也没法去思考你用的语言中不存在的特性, 也没法去想用其他语言的特性来更简单的实现某个功能. 就像你要是一开始学习的是 C++, 可能就没有函数式编程的思路, 习惯用迭代而不是递归, 习惯于用for迭代, 而不是用reduce和map.</p>
<p>我在<a href="http://www.jtianling.com/The-limits-of-my-language-means-the-limits-of-my-world.html">原来有篇博文</a>中就感叹过类似的意思, 当时从 C++ 的语言环境切换 Ruby 的工作环境, 真的是有些开眼界了的感觉.</p>
<p>而且, 因为前面提到的原因, 你可能在第一个语言上工作很长时间, 可能会适应这个语言的缺点, 就算你将来再去学习其他语言, 要扭转这种思维模式, 会花更大得多的代价.</p>
<h2 id="语言决定了你的学习方向">语言决定了你的学习方向</h2>
<p>你的工作领域很大程度上决定了你工作后继续学习的方向, 这个自然不用说, 但是, 就算你是一个很热爱编程, 业余时间的学习也很难逃脱一开始的选择.<br />
虽然不排除特例, 但是你要是从底层的语言入门的(比如C, C++), 就是可能会更关注代码的效率优化(因为你的工作语言是C, C++, 很可能对代码的运行效率要求更高), 因为优化的原因, 你去了解了操作系统相关的知识, 了解操作系统的任务调度, 内存管理.<br />
要是你一开始学习的是Java, 那么你更可能看的是怎么管理更大规模的代码, 比如设计模式, API 设计, 企业架构等, 更牛一点的, 也就是看到JVM字节码, 很少有人会去看汇编.</p>
<p>还是假设你是个爱学习的人, 那么你业余时间肯定也是会学习, 也是会去了解工作相关行业的信息, 那么, 你学的是C++, 你就会关心C++ 1X的进展, 你学习的是Java, 你会关注Java的新版, 你学习的是Python 你会关心PyCon, PEP, 你学习的是Objective-C, 你会看WWDC, 你会开始学习 Swift, 你学的是 JavaScript, 你会看ES7, Babel, TypeScript等, 简单的说, 你会在你选择的语言相关的信息上花很多很多的时间, 事实上, 说语言之间能简单切换的同学, 你们真的能了解所有这些语言新的特性, 新的成熟的库, 用每个语言最新的特性, 以社区最新的推荐惯例用法来写代码吗? 没有人能做到.</p>
<h2 id="你还觉得入门语言的选择不重要吗">你还觉得入门语言的选择不重要吗</h2>
<p>上述表达都用了”决定”二字, 可能太过绝对, 但是用”很大程度上决定”, 真是一点也不过分. 要是你认为自己天赋异禀, 能轻松掌握各种语言, 或者你一开始就是把编程作为智力游戏, 没打算作为职业, 或者你准备好了一开始随便学个语言, 然后用更多的精力去学习其他语言, 然后再工作, 你都当我没说过, 你开心就好.</p>
<h1 id="怎么选择入门语言">怎么选择入门语言</h1>
<p>既然入门语言这么重要, 就不要那么轻松的做决定了, 不是仅仅简单的说没有大毛病就可以了.</p>
<p>其实我感觉, 回答怎么选择入门语言这个问题之前, 应该想一想自己将来想在哪个领域工作, 然后选择这个领域最热门的语言就好了, 这个可能是最最重要的, 至于这个语言本身什么情况, 哪怕是类似C++这样的语言, 该学也学, 这也比你真的”入错行”, 或者随便学个大家都推荐的Python然后想去开发游戏, 或者App, 发现找不到工作要好.</p>
<p>基于同样的原因, 一些人推荐的Scheme, 我是不太推荐入门就学习的, 除非你的领域真的就需要Scheme.</p>
<p>而思维方式的弥补, 就参考Peter Norvig的<a href="http://www.norvig.com/21-days.html">Teach Yourself Programming in Ten Years</a>吧.</p>
<h1 id="我是怎么选择的">我是怎么选择的</h1>
<blockquote>
<p>Two roads diverged in a wood, and I took the one less traveled by, And that has made all the difference.
– Robert Frost</p>
</blockquote>
<p>懂了那么多道理, 其实还是过不好这一生…<br />
C++是我入门编程学习的第一门语言, 我博客里面关于 C++的内容也是最多的, 当年刚毕业, 很无知, 对于未来干什么, 学习什么语言完全没有概念, 当时的语言选择也没有现在这么多, 无非也就是C++, Java, C#这几个相对热门, 但是我最后比较神奇的选择了 C++, 理由也很简单, 我听说 C++ 很复杂, 所以就选择了 C++ 而不是 JAVA, C#, 这个选择影响深远.<br />
因为学的是 C++, 所以在长沙不好找工作(因为那边原来都是做软件外包, 用的都是 Java), 所以来了北京, 因为 C++ 的应用领域也有限, 最后找的工作是做游戏(这完全是学习 C++然后找工作的被动选择), 然后, 很长很长时间, 都是在做游戏……
我可以这么说, 这个决定就是影响了我的一生的, 假如当时选择了 Java, 我一定是有不一样的人生轨迹, 至于比现在好, 还是差, 这个就只有天知道了.</p>
<h1 id="最后">最后</h1>
<p>最近总算完成了多年的一个心愿, 为了方便在多个编程语言中切换, 做了一个简单的辅助大脑切换的工具.<br />
为 C++ 部分写简介的时候, 莫名其妙就写多了, 有感而发, 又很久没有写博客了, 单独写了这一篇.</p>
<p><a href="http://www.jtianling.com/talk-about-the-first-programming-language-you-learn.html">小议入门语言对初学者的影响</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on October 28, 2017.</p>
http://www.jtianling.com/loading-optimizing-in-html5-game
2016-09-13T00:00:00+08:00
2016-09-13T00:00:00+08:00
http://www.jtianling.com
<p>据说以前的页游的 loading 时间一旦超过 5 秒, 就会流失大量用户, 现在的微信 H5 游戏既然也是页游, 既然也想很好的利用网页游戏不用安装, 即点即玩的好处, 对 loading 的要求应该也是一样的.
最近优化了一下我们的游戏, 用到了一些手段, 这里收集整理如下.</p>
<!-- more -->
<p><strong>目录</strong>:</p>
<ul id="markdown-toc">
<li><a href="#调试和验证方法" id="markdown-toc-调试和验证方法">调试和验证方法</a></li>
<li><a href="#减少初始资源的载入大小" id="markdown-toc-减少初始资源的载入大小">减少初始资源的载入大小</a></li>
<li><a href="#源代码和-json-文件的压缩" id="markdown-toc-源代码和-json-文件的压缩">源代码和 json 文件的压缩</a></li>
<li><a href="#减少-http-的交互次数" id="markdown-toc-减少-http-的交互次数">减少 http 的交互次数</a></li>
<li><a href="#egret-库的合并" id="markdown-toc-egret-库的合并">egret 库的合并</a></li>
<li><a href="#脚本加载的精确控制" id="markdown-toc-脚本加载的精确控制">脚本加载的精确控制</a></li>
<li><a href="#总结" id="markdown-toc-总结">总结</a></li>
</ul>
<h1 id="调试和验证方法">调试和验证方法</h1>
<ol>
<li>在 chrome 中开启 developer 工具后, 选择 Network, 查看每个资源载入的时间和大小, 用于查看各个资源的大小和载入时间, 可以排序选择最大的入手.</li>
<li>在 chrome 中开启 developer 工具后, 选择 Timeline, 然后刷新网页, 会自动 record, 出现比较直观的时间流程图, 可以看到各个脚本应该并行加载的时候正的并行, 看到每次 HTTP 的交互是否是必要的, 是否可以合并.</li>
<li>自己在各个程序运行的关键点输出时间, 主要用于脚本载入以后, 脚本执行初始化流程, 包括登录验证等逻辑流程, 直到游戏画面正常出现过程中的优化.</li>
</ol>
<h1 id="减少初始资源的载入大小">减少初始资源的载入大小</h1>
<p>这个是最核心和关键的因素. 你 loading 10M 的资源, 怎么优化也优化不过只 loading 10K 的.</p>
<p>这里特别明确一下的目的, 我们需要的是将玩家进入游戏的 loading 等待之间尽量缩短, 也就是说, 把进入游戏前 loading 的资源尽量缩小就好了, 进入游戏以后, 再异步 loading 的资源和原来其实差不多大, 但是玩家进入游戏的体验还是提升了.</p>
<p>图片资源, 游戏配置文件, UI 布局文件, 都只 loading 游戏启动必要的部分, 其他可以留到需要的时候再加载.</p>
<p>代码本身, 在稍微大规模项目中, 在将图片和配置减少到一定程度, 代码反而会成为最大的资源, 目前没有特别好的优化手段, 考虑过将代码模块化, 特别是 UI 部分的代码, 实现 UI 操作的时候动态载入运行, 理论上可行, 没有验证.</p>
<p>这里比较尴尬的是显示 loading 进度的背景图本身的问题, 这个图本身的加载就需要时间, 所以我建议尽量精简这个图片本身的大小. 作为更激进的做法, 很多游戏实际是没有 loading 图的, 而是用 1 / 100 这样的纯数字来表示. 这个之间的权衡, 就看各个项目本身的情况了.</p>
<h1 id="源代码和-json-文件的压缩">源代码和 json 文件的压缩</h1>
<p>最简单的办法是利用 http server(或者 CDN) 的 gzip 压缩功能,</p>
<p>首先 CDN 需要支持和开启 gzip 压缩, 并且在源栈配置好合适的类型, javascript 的源代码 和 json(配置) 都是可以通过 gzip 压缩的, 效果明显. 关于源栈, 我们就碰到过 nginx 服务器不自动识别 json, 导致原来的 json 配置都没有压缩的问题.</p>
<p>看 headers 的话, js 是
Content-Encoding:gzip
Content-Type:application/x-javascript</p>
<p>json 是
Content-Encoding:gzip
Content-Type:application/json</p>
<p>表示已经开启了 gzip 压缩, 实际传输大小可以减少很多.</p>
<p>也可以直接在 chrome 的 network 里面打开 Content-Encoding 列, 然后看此列是否有 gzip 标志.</p>
<p>另外, 假如真的有更高的要求, 也是可以自己压缩源代码和 json 文件的, 这样你可以选择用比 gzip 压缩比更高的压缩算法, 不过, 注意 javascript 本身的运行效率, 别用太慢的压缩算法.
比如 <a href="https://stuk.github.io/jszip/">jszip</a> 这个库.</p>
<h1 id="减少-http-的交互次数">减少 http 的交互次数</h1>
<p>因为 http 的交互比较慢, 减少交互的次数可以显著提高载入速度.</p>
<p>对于图片, 载入期必要图片都应该打包成图集, 最好是能打包成 1 个文件, 我们项目中最后做到了, 我命名为 gamemin, 这个图集可以进一步的考虑压缩, 比如使用 png8 格式. 因为最后的大小已经在 150K 的水平了, 没有进一步的使用诸如 webp 等压缩比更大的图片格式, 有更严格要求和追求的话, 可以考虑, 不过需要测试一下对应的 javascript 解码库的效率.</p>
<p>游戏配置, 成规模的项目, 又是策划写 excel 导出的话, 往往有几十个, 可以合并成一个, 去掉空格换行等. 为此我写了个合并工具: <a href="https://github.com/jtianling/json_compacker">json_compacker</a></p>
<h1 id="egret-库的合并">egret 库的合并</h1>
<p>在新的 egret 中, 进行了模块化的设计, 每个模块可以单独选择是否添加, 但是这样一来, 原来只需要 loading 一个库, 现在需要 loading 好几个, 实际是划不来的, 而 egret 官方没有提供官方的解决方案.</p>
<p>合并 js 的工具不少, 不过我在官方论坛上找到一个, 使用是最方便的.
<a href="http://bbs.egret.com/forum.php?mod=viewthread&tid=16765&highlight=%E5%90%88%E5%B9%B6">合并 egret 库的工具</a></p>
<h1 id="脚本加载的精确控制">脚本加载的精确控制</h1>
<p>首先, 在我们游戏中有个例子, 就是接入了很多平台, 原来的做法是所有平台的 sdk 都加载 (你要看平台 SDK 的文档的话, 都是这么教你的), 然后选择性的运行对应的代码. 这样做实际多加载了很多不必要的平台 sdk, 平台这些 sdk 还都是从合作方网站上下载回来的, 我们缺乏控制. 修改后变成首先判断渠道, 然后只加载渠道对应的一个平台 sdk.</p>
<p>动态加载脚本的方法在”高性能 JavaScript”一书中有比较详细的介绍, 简单的说就是可以用下面这两个库:</p>
<p><a href="https://github.com/rgrove/lazyload">lazyload</a>
<a href="https://github.com/getify/LABjs">LABjs</a></p>
<h1 id="总结">总结</h1>
<p>因为是公司项目, 所以略去的所有相关的源代码, 总体来说, 我用到的优化手段都提到了.
需要说明的是, 优化的时候更多的是在一个完全没有优化的版本上进行的, 随便优化一下, 都能获得比较大的改进, 所以实际工作做的不是足够严谨, 在有多个优化方案时, 都是选择相对简单的那个解决方案, 而不是详细的对比多个方案的实际情况, 经过测试然后选择.(这个才是正确的做法)</p>
<p><a href="http://www.jtianling.com/loading-optimizing-in-html5-game.html">HTML5 游戏的 loading 速度优化</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on September 13, 2016.</p>
http://www.jtianling.com/toc-auto-add-utility-for-markdown
2015-10-16T00:00:00+08:00
2015-10-16T00:00:00+08:00
http://www.jtianling.com
<p>因为多次看到有人在博客上说Rust的好, 最近学习了下Rust, 作为练习, 照例是要找个东西来做, 碰巧想给我的博客文章都添加一下目录(英文缩写TOC, table of content) , 于是想到了用Rust来写这个, 虽然这种任务我以前肯定是用Ruby来做, Ruby作为动态类型语言也更适合做这种工作. 本文相当于是个发布通告和使用说明, 具体的可以在<a href="https://github.com/jtianling/toc-auto-add">我的Github</a>上找到源码和编译好的程序.</p>
<!-- more -->
<p><strong>目录</strong>:</p>
<ul id="markdown-toc">
<li><a href="#为什么需要一个这样的工具" id="markdown-toc-为什么需要一个这样的工具">为什么需要一个这样的工具</a></li>
<li><a href="#原理" id="markdown-toc-原理">原理</a></li>
<li><a href="#安装及使用" id="markdown-toc-安装及使用">安装及使用</a></li>
<li><a href="#使用须知" id="markdown-toc-使用须知">使用须知</a></li>
<li><a href="#说明" id="markdown-toc-说明">说明</a></li>
</ul>
<h1 id="为什么需要一个这样的工具">为什么需要一个这样的工具</h1>
<p>我的博客原来是有目录生成工具的, 依赖的是Kramdown(好像是)这个Markdown解析引擎, 但是这个解析引擎有些其他的问题(忘了什么问题了, 印象中是代码块解析不好), 所以我后来换成了Redcarpet, Redcarpet支持TOC, 但是只是在生成的HTML中含有每个目录的id, 而没有TOC本身的生成功能, 网上有很多解决方案, 不管是用Javascript动态生成还是静态生成, 都有些不太好的地方, 具体就不展开说了. 反正是能搜到很多, 但是我发现实际能用的没有一个.</p>
<h1 id="原理">原理</h1>
<p>这个工具, 目录内容本身是基于分析文章本身, 并且只分析两层, 即以#和##开头的标题.</p>
<p>目录的链接基于Redcarpet这个生成工具生成的HTML, 在分析目录的时候, 会跳过<code class="language-plaintext highlighter-rouge">~~~</code>标注的代码(其他形式的代码块, 要是是类似Ruby这种注释也是#开头的, 会把代码的注释看作是文章的标题.)</p>
<p>实现的方式是在<code class="language-plaintext highlighter-rouge"><!-- more --></code>标签后, 添加成对的<code class="language-plaintext highlighter-rouge"><!-- toc-begin --></code>, <code class="language-plaintext highlighter-rouge"><!-- toc-end --></code>标签, 实际目录在两个标签中.<br />
通过jeklly生成以后的效果(也就是Github Pages实际的效果)可以参考本文的目录, 更复杂的可以去本博客的其他文章中查看(新写的都添加了), 比如这篇<a href="http://www.jtianling.com/write-blog-with-jekyll-and-github-pages.html">那篇文章</a>, 效果我还算满意.</p>
<h1 id="安装及使用">安装及使用</h1>
<p>在<a href="https://github.com/jtianling/toc-auto-add">Github</a>上下载源码, 在target/release目录中有编译好的程序. 实际使用的时候, 用文件名作为参数执行程序, 即可:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$.</span>/toc-auto-add filename.md
</code></pre></div></div>
<p>假如想要执行多个文件, 可以一次传递进去, 如下面这样:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$.</span>/toc-auto-add filename1.md filename2.md filename3.md
</code></pre></div></div>
<p>假如想要执行目录下非常多的文件, 就没有在本程序中实现了, 按照Unix的哲学, 你可以使用其他程序组合起来使用, 比如下面这样, 就是转换本目录下所有的md文件:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ls</span> <span class="k">*</span>.md | xargs <span class="nt">-tI</span> <span class="o">{}</span> ./toc-auto-add <span class="o">{}</span>
</code></pre></div></div>
<p>需要重复执行的话, 请用上面的命令自行制作sh文件即可.</p>
<h1 id="使用须知">使用须知</h1>
<p>对于没有耐心看完原理的人, 也不会自己去修改源代码满足自己需求, 希望直接用, 需要理解这个工具因为是为我自己写的, 所以有比较强的环境依赖, 需要满足以下条件:</p>
<ol>
<li>需要有<code class="language-plaintext highlighter-rouge"><!-- more --></code>锚点, 工具才知道把toc添加到什么位置</li>
<li>要想文章后的链接有效, 必须使用redcarpet这个markdown的html生成引擎</li>
<li>假如有代码的话, 请用<code class="language-plaintext highlighter-rouge">~~~</code>这种形式, 也就是老外说的<strong>fenced code blocks</strong>形式</li>
<li>我分析文章实际是没有markdown那么强大, 就是简单的文本分析, 请在标题中不要再使用类似<code class="language-plaintext highlighter-rouge">**</code>强调这种markdown语法了, 会导致链接生成错误</li>
<li>添加一次以后, 文章更新以后, 只要保证toc区域没有被破坏, 可重复执行以刷新toc内容</li>
</ol>
<p>假如你同样是使用Github Pages, 那么相关配置大概是这样子的:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">markdown</span><span class="pi">:</span> <span class="s">redcarpet</span>
<span class="na">markdown_ext</span><span class="pi">:</span> <span class="s">md</span>
<span class="na">excerpt_separator</span><span class="pi">:</span> <span class="s2">"</span><span class="s"><!--</span><span class="nv"> </span><span class="s">more</span><span class="nv"> </span><span class="s">-->"</span>
<span class="na">redcarpet</span><span class="pi">:</span>
<span class="na">extensions</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">fenced_code_blocks"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">with_toc_data"</span><span class="pi">]</span>
</code></pre></div></div>
<h1 id="说明">说明</h1>
<p>在新版本的github page中, 默认使用了kramdown这个markdown解析引擎, 直接可支持toc, 我这个工具已经不需要使用了. 使用了kramdown后, 只需要添加以下文字, 会自动生成目录:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>* TOC
{:toc}
</code></pre></div></div>
<p><a href="http://www.jtianling.com/toc-auto-add-utility-for-markdown.html">发布一个自动为Markdown文章添加目录的工具</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on October 16, 2015.</p>
http://www.jtianling.com/import-comment-from-wordpress-in-uyan
2014-05-21T00:00:00+08:00
2014-05-21T00:00:00+08:00
http://www.jtianling.com
<p>Github Page是静态网页, 本身是没有评论系统的, 国外的好像比较流行<a href="http://disqus.com/">Disqus</a>, 国外的模版一般都是接的<a href="http://disqus.com/">Disqus</a>, 我以前一直用的是友言, 正好也是类似的系统, 也能用, 但是碰到一个问题, <a href="http://www.uyan.cc">友言</a>的评论是以URL为标识的, 而新改了Github Page以后所有博客文章的地址都变了, 所以虽然评论都还保留在<a href="http://www.uyan.cc">友言</a>的服务器上, 但是其实所有的评论都匹配不了新的文章, 等于都丢失了咨询了<a href="http://www.uyan.cc">友言</a>的客服, 没有解决办法, 但是好在<a href="http://www.uyan.cc">友言</a>还算是国内比较开放的企业, 有数据导入和导出功能, 所以感觉可以把评论数据都导出, 然后经过修改, 改成新博客需要的形式, 然后再导入, 实际检验, 这条路还是走通了, 所以如你所见, 原来的评论都还在, 只是貌似<a href="http://www.uyan.cc">友言</a>的数据导入导出有Bug, 把评论的登录信息都丢了. 本文记录的就是这个过程, 和相关的代码, 希望能对其他碰到类似情况的人有帮助.</p>
<!-- more -->
<p><strong>目录</strong>:</p>
<ul id="markdown-toc">
<li><a href="#导出" id="markdown-toc-导出">导出</a> <ul>
<li><a href="#字段解释" id="markdown-toc-字段解释">字段解释:</a></li>
</ul>
</li>
<li><a href="#问题" id="markdown-toc-问题">问题</a></li>
<li><a href="#处理" id="markdown-toc-处理">处理</a></li>
<li><a href="#总结" id="markdown-toc-总结">总结</a></li>
</ul>
<h1 id="导出">导出</h1>
<p>在<a href="http://www.uyan.cc">友言</a>的后台管理里面, 选择安装备份->导出数据, 友言就开始生成导出的数据了, 隔一段时间后, 回来下载即可.<br />
友言导出的数据格式是个Json文件, 格式如下:(来自官网的说明)</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"su"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://d.com/a.html"</span><span class="p">,</span><span class="w">
</span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"测试一下,你就知道"</span><span class="p">,</span><span class="w">
</span><span class="nl">"content"</span><span class="p">:</span><span class="w"> </span><span class="s2">"c"</span><span class="p">,</span><span class="w">
</span><span class="nl">"time"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2012-10-09 10:40:29"</span><span class="p">,</span><span class="w">
</span><span class="nl">"uname"</span><span class="p">:</span><span class="w"> </span><span class="s2">"test"</span><span class="p">,</span><span class="w">
</span><span class="nl">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"zhangsan@sina.com"</span><span class="p">,</span><span class="w">
</span><span class="nl">"ulink"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://blog.jiathis.com"</span><span class="p">,</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"child"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"content"</span><span class="p">:</span><span class="w"> </span><span class="s2">"@test: e"</span><span class="p">,</span><span class="w">
</span><span class="nl">"time"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2012-10-09 10:40:51"</span><span class="p">,</span><span class="w">
</span><span class="nl">"uname"</span><span class="p">:</span><span class="w"> </span><span class="s2">"test"</span><span class="p">,</span><span class="w">
</span><span class="nl">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"zhangsan@sina.com"</span><span class="p">,</span><span class="w">
</span><span class="nl">"ulink"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://blog.jiathis.com"</span><span class="p">,</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"content"</span><span class="p">:</span><span class="w"> </span><span class="s2">"@test: d"</span><span class="p">,</span><span class="w">
</span><span class="nl">"time"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2012-10-09 10:40:37"</span><span class="p">,</span><span class="w">
</span><span class="nl">"uname"</span><span class="p">:</span><span class="w"> </span><span class="s2">"test"</span><span class="p">,</span><span class="w">
</span><span class="nl">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"zhangsan@sina.com"</span><span class="p">,</span><span class="w">
</span><span class="nl">"ulink"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://blog.jiathis.com"</span><span class="p">,</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>
<h2 id="字段解释">字段解释:</h2>
<ul>
<li>su: 自定义页面标识 (如果定义了页面标识则填写,未定义则留空)</li>
<li>url: 页面地址 (su或者url必选一个)</li>
<li>title: 页面标题 (非必选)</li>
<li>content: 评论内容 (必选)</li>
<li>time: 评论时间 (如果没有时间可以不用填写,但是强烈建议填写,以便评论排序)</li>
<li>uname: 昵称 (必选)</li>
<li>email: 邮箱地址 (非必选)</li>
<li>ulink: 个人主页链接地址 (非必选)</li>
<li>status: 评论状态: 0:正常, 1:待验证, 2:垃圾,3:已被删除 (非必选,默认是正常评论)</li>
<li>child: 子评论,格式和父级评论大致相同,但是子集评论无更下一级的评论 (包括content,time,uname,email,ulink,status)</li>
</ul>
<p>可以看到导出信息里面的确没有登录信息, 所以再次导入后, 只能看到正确的用户名, 但是已经看不出来用户是从哪登录并且评论的了.</p>
<h1 id="问题">问题</h1>
<p>看了导出的格式以后, 目的就很明确了, 只需要把原来的url改成新的url就可以了, 但是问题来了, 博客改版的比较扭曲, 没有从原来url到新url的映射规则.<br />
于是首先需要找出这个映射规则, 我想到的办法比较直接, 就是用文章的名字来做映射, 因为url再变, 文章名字其实没变.<br />
这里有几个办法, 可以直接扒<a href="/archive.html">所有文章</a>页面, 然后从里面抓出相关的信息, 但是感觉这样太麻烦了, 我直接在博客中加了一个for_comment.json的json文件, 利用Jekyll的静态网页生成功能, 通过Jekyll生成了一个需要的Json格式的文件. 原文件如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>---
layout: none
---
[
{% for post in site.posts %}{
"date" : "{{ post.date | date: "%Y-%m-%d" }}",
"url" : "{{site.url }}{{ post.url }}",
"title" : "{{ post.title }}"
},{% endfor %}
]
</code></pre></div></div>
<p>通过<code class="language-plaintext highlighter-rouge">jekyll build</code>生成网页后, 得到了如下包含日期, url, title等关键信息的json文件, 这样方便处理.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"date"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2013-12-05"</span><span class="p">,</span><span class="w">
</span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://www.jtianling.com/procrastination-is-not-a-big-deal.html"</span><span class="p">,</span><span class="w">
</span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"拖拉一点也无妨"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"date"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2013-08-21"</span><span class="p">,</span><span class="w">
</span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://www.jtianling.com/a-childhood-dream-come-true-building-spine.html"</span><span class="p">,</span><span class="w">
</span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[译]儿时梦想成真: Spine背后的故事"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h1 id="处理">处理</h1>
<p>接下来的事情就简单了, 一个Ruby程序的事情:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env ruby</span>
<span class="nb">require</span> <span class="s1">'json'</span>
<span class="nb">require</span> <span class="s1">'set'</span>
<span class="c1"># read the jekyll built file</span>
<span class="vg">$list_file</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="s1">'list.json'</span><span class="p">)</span>
<span class="vg">$list_obj</span> <span class="o">=</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="vg">$list_file</span><span class="p">)</span>
<span class="c1"># read the uyan exported file</span>
<span class="vg">$export_file</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="s1">'export.json'</span><span class="p">)</span>
<span class="vg">$export_obj</span> <span class="o">=</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="vg">$export_file</span><span class="p">)</span>
<span class="vg">$export_set</span> <span class="o">=</span> <span class="no">Set</span><span class="p">.</span><span class="nf">new</span>
<span class="c1"># build the map</span>
<span class="vg">$export_obj</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">obj</span><span class="o">|</span>
<span class="vg">$export_set</span><span class="p">.</span><span class="nf">add</span> <span class="n">obj</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="s2">"comment count: </span><span class="si">#{</span><span class="vg">$export_obj</span><span class="p">.</span><span class="nf">count</span><span class="si">}</span><span class="s2">"</span>
<span class="nb">puts</span> <span class="s2">"all file count: </span><span class="si">#{</span><span class="vg">$list_obj</span><span class="p">.</span><span class="nf">count</span><span class="si">}</span><span class="s2">"</span>
<span class="nb">puts</span> <span class="s2">"Export set count </span><span class="si">#{</span><span class="vg">$export_set</span><span class="p">.</span><span class="nf">count</span><span class="si">}</span><span class="s2">"</span>
<span class="vg">$match_count</span> <span class="o">=</span> <span class="mi">0</span>
<span class="vg">$changed_count</span> <span class="o">=</span> <span class="mi">0</span>
<span class="vg">$list_obj</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">obj</span><span class="o">|</span>
<span class="k">if</span> <span class="vg">$export_set</span><span class="p">.</span><span class="nf">include?</span> <span class="n">obj</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</span> <span class="k">then</span>
<span class="vg">$match_count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="vg">$export_set</span><span class="p">.</span><span class="nf">delete</span> <span class="n">obj</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</span>
<span class="k">end</span>
<span class="vg">$export_obj</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">comment</span><span class="o">|</span>
<span class="k">if</span> <span class="n">comment</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</span> <span class="o">==</span> <span class="n">obj</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</span> <span class="k">then</span>
<span class="vg">$changed_count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="n">comment</span><span class="p">[</span><span class="s1">'url'</span><span class="p">]</span> <span class="o">=</span> <span class="n">obj</span><span class="p">[</span><span class="s1">'url'</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># summarize the result and output the Not matched article title.</span>
<span class="vg">$export_set</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">title</span><span class="o">|</span>
<span class="nb">puts</span> <span class="s2">"Not matched title </span><span class="si">#{</span><span class="n">title</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="s2">"Matched count </span><span class="si">#{</span><span class="vg">$match_count</span><span class="si">}</span><span class="s2">"</span>
<span class="nb">puts</span> <span class="s2">"Changed comment count </span><span class="si">#{</span><span class="vg">$changed_count</span><span class="si">}</span><span class="s2">"</span>
<span class="c1"># write changed export file</span>
<span class="no">File</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="s1">'changed.json'</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span>
<span class="n">f</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="no">JSON</span><span class="p">.</span><span class="nf">pretty_generate</span><span class="p">(</span><span class="vg">$export_obj</span><span class="p">))</span>
<span class="k">end</span>
</code></pre></div></div>
<h1 id="总结">总结</h1>
<p>最终的结果还是让人满意的, 这个过程中特别不让人满意的是友言的支持, 由于我的博客评论有2000多条, 190多页, 友言又没有提供一次删除的操作, 最多一次只能删一页, 我要通过导出再导入的办法恢复评论的话, 原来的评论其实是已经没有用了, 但是要删190多页, 这个太折磨人了, 找友言的客服帮下忙, 要他清空一下我友言的数据库, 我再导入其实就好了, 但是就是这么简单的需求, 友言的客服一再的推脱, 找各种理由, 提出各种不能忍的解决方案:</p>
<ul>
<li>新建一个帐号 – 我之所以用友言, 就是因为他和<a href="http://jiathis.com/">jiathis</a>, <a href="http://www.ujian.cc/">友荐</a>一个帐号, 比较方便, 可以统一管理, 要是新建一个友言帐号, 我的jiathis, 友荐帐号就都得变, 不能接受</li>
<li>手工删除 – 等于没有提供任何解决方案</li>
</ul>
<p>最不能接受的是, 不提供解决方案就算了, 还一味的跟我说友言就是要尽最大可能保存用户的数据, 不知道这跟我需要的操作有什么关系, 难道其他网站都不要保存用户数据了? 连github都有彻底删除仓库的方法啊. 总之就是不给我弄, 最后我真只能一页一页的都删了(190多页), 真的非常无奈. 假如不是因为jiathis和友荐, 我一定投向多说了.</p>
<p><a href="http://www.jtianling.com/import-comment-from-wordpress-in-uyan.html">从Wordpress中导入友言的评论</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on May 21, 2014.</p>
http://www.jtianling.com/write-blog-with-jekyll-and-github-pages
2014-05-19T00:00:00+08:00
2014-05-19T00:00:00+08:00
http://www.jtianling.com
<p>最近博客的确写的又少了很多, 也发生了很多事情, 生活的变化也挺大的, 一直忙的不可开交. 最近这段时间可能能逐渐的稍微舒缓一些, 趁这个机会也学习一下自己一直想学习的东西, 做一些自己一直想做的事情吧. 首先要做的, 是恢复博客的更新, 作为起点, 废除了原来架设于Linode上的Wordpress博客, 改为用Github Pages来写博客, 据说每个开始用Github Pages写博客的人都会写篇博客纪念, 我也不免俗吧, 也写一篇博客记录一下.</p>
<!-- more -->
<p><strong>目录</strong>:</p>
<ul id="markdown-toc">
<li><a href="#为什么要用github-pages" id="markdown-toc-为什么要用github-pages">为什么要用Github Pages</a></li>
<li><a href="#静态网站生成的选择" id="markdown-toc-静态网站生成的选择">静态网站生成的选择</a></li>
<li><a href="#博客风格" id="markdown-toc-博客风格">博客风格</a></li>
<li><a href="#模版的改动" id="markdown-toc-模版的改动">模版的改动</a> <ul>
<li><a href="#增加了搜索功能" id="markdown-toc-增加了搜索功能">增加了搜索功能</a></li>
<li><a href="#增加了代码的高亮显示" id="markdown-toc-增加了代码的高亮显示">增加了代码的高亮显示</a></li>
<li><a href="#首页改为部分输出" id="markdown-toc-首页改为部分输出">首页改为部分输出</a></li>
<li><a href="#修改了博客的title-style为直接用title做url" id="markdown-toc-修改了博客的title-style为直接用title做url">修改了博客的title style为直接用title做url.</a></li>
<li><a href="#增加了feed" id="markdown-toc-增加了feed">增加了feed</a></li>
<li><a href="#增加了首页的翻页" id="markdown-toc-增加了首页的翻页">增加了首页的翻页</a></li>
<li><a href="#文章的结尾列出了标签和分类" id="markdown-toc-文章的结尾列出了标签和分类">文章的结尾列出了标签和分类</a></li>
<li><a href="#增加了sitemapxml" id="markdown-toc-增加了sitemapxml">增加了sitemap.xml</a></li>
<li><a href="#导入文章" id="markdown-toc-导入文章">导入文章</a></li>
<li><a href="#评论" id="markdown-toc-评论">评论</a></li>
</ul>
</li>
</ul>
<h1 id="为什么要用github-pages">为什么要用Github Pages</h1>
<p>最重要的原因应该说是觉得每年花一千多租个Linode的服务器就为写博客有些浪费了, 虽然说也可以顺便用SSH做梯子, 但是还是浪费.
同时, 我建在Linode上的博客服务用的是Wordpress, 而Wordpress是用PHP写的, 我对PHP挺没有好感的, 所以一直没有怎么学习, 所以自己想改起来也挺不顺手的, 基于这个原因, 虽然我已经给Wordpress改了一个我自己觉得还可以的模版, 但是还是挺不爽的.
另外, Wordpress的博客速度真的挺慢的, 尽管我已经用了Linode这种相对来说比较昂贵的服务器了, 但是还是挺慢, 用了静态Cache也还是, 可能原来那个Wordpress的模版本身就是个慢东西.
综上所述, 换个博客其实挺有必要的, 同时又正好有Github Pages这个好东西, 我又早就换了Vimpress + Markdown写博客了, 又没有什么障碍, 为什么不用这种免费, 速度快, 操作又方便的服务呢?</p>
<h1 id="静态网站生成的选择">静态网站生成的选择</h1>
<p>要用Github Pages没有什么障碍了, Github Pages目前用的是<a href="http://jekyllrb.com/">Jekyll</a>(Jekyll是用ruby写的, 这个就更加应该要用了..)生成静态网页, 在一开始, 查找了一些资料以后, 我原以为<a href="octopress.org/">Octpress</a>会方便一些, 所以先试用了一段时间的Octpress, 但是因为Octpress比较长时间没有更新了, 用的还是非常老版本的<a href="http://jekyllrb.com/">Jekyll</a>及其他依赖库, 感觉很不爽, 同时要改模版的时候, 为了找到对应的include文件需要找好几层, 这个也觉得有些繁琐, 虽然Octpress整体设计的我觉得还是OK的, 但是最后我还是放弃了. 所以决定直接用Jekyll, 并且准备自己从头写Jekyll的模版, 在为Jekyll加入Bootstrap的时候发现了Jekyll Bootstrap这个项目, 于是也试用了一下, 因为和Octpress类似的原因, 也放弃了.<br />
经过一番折腾以后, 感觉用任何第三方的东西都是挺浪费时间的, 再也没找过其他的程序了, 坚定的直接用Jekyll了.</p>
<h1 id="博客风格">博客风格</h1>
<p>重头写一个博客对我这种对网页开发不熟悉的人来说实在太费劲, 还是找找模版来的快, 在<a href="http://jekyllthemes.org/">Jekyll themes网站</a>上看了一遍所有的模版, 包括Octpress的模版, 我感觉符合我审美的有下面这些:</p>
<ul>
<li><a href="http://lucaslew.com/">Octpress whitespace</a></li>
<li><a href="http://mmistakes.github.io/hpstr-jekyll-theme/">hpstr</a></li>
<li><a href="http://mmistakes.github.io/minimal-mistakes/">mmistakes</a></li>
<li><a href="http://lanyon.getpoole.com/">lanyon</a></li>
<li><a href="http://adrianartiles.com/">Adrian Artiles’s Blog</a></li>
</ul>
<p>其中我最喜欢的是whitespace和lanyon这两个模版, 在正犹豫不觉用哪个模版的时候, 阴差阳错的走进了<a href="http://dzerviniks.com/brume/">brume模版</a>制作者<a href="http://dzerviniks.com/">Aigars Dzerviniks自己的博客</a>, 瞬间被其博客的文艺气息所吸引, 然后就决定改成用他博客的模版了, 但是他博客的模版其实并没有公布出来, 走运的是通过github找到了他本人的帐号, 并且他的博客也正好使用Github page制作的, 所以就给我扒回来了. 当然也没法直接用, 最终的改动挺多, 但是整体的风格还是Aigars Dzerviniks的博客.</p>
<h1 id="模版的改动">模版的改动</h1>
<h2 id="增加了搜索功能">增加了搜索功能</h2>
<p>代码如下:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><form</span> <span class="na">action=</span><span class="s">"http://google.com/search"</span> <span class="na">method=</span><span class="s">"get"</span><span class="nt">></span>
<span class="nt"><fieldset</span> <span class="na">role=</span><span class="s">"search"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"q"</span> <span class="na">value=</span><span class="s">"site:www.jtianling.com"</span> <span class="nt">/></span>
<span class="nt"><input</span> <span class="na">class=</span><span class="s">"search"</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"q"</span> <span class="na">results=</span><span class="s">"0"</span> <span class="na">placeholder=</span><span class="s">"搜索本站"</span><span class="nt">/></span>
<span class="nt"></fieldset></span>
<span class="nt"></form></span>
</code></pre></div></div>
<p>要实现博客中的效果, 还需要在css中去掉默认的边框. 设置border为0:</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">#social</span> <span class="nt">form</span> <span class="nt">fieldset</span> <span class="p">{</span>
<span class="nl">border</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="增加了代码的高亮显示">增加了代码的高亮显示</h2>
<p>_config.yml 设置为:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">highlighter</span><span class="pi">:</span> <span class="s">pygments</span>
<span class="na">pygments</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">markdown</span><span class="pi">:</span> <span class="s">redcarpet</span>
<span class="na">markdown_ext</span><span class="pi">:</span> <span class="s">md</span>
</code></pre></div></div>
<p>通过利用Foundation的响应功能合适的实现高亮代码的正确显示, 有兴趣的读者可以在iPad, iPhone, Android看看我的博客, 我自己感觉效果还不错.</p>
<h2 id="首页改为部分输出">首页改为部分输出</h2>
<p>用<a href="http://docs.shopify.com/themes/liquid-basics">liquid</a>的如下功能实现:
{{ post.excerpt }}</p>
<p>然后用配置实现原来用的多的more标志来确定部分输出的位置:
_config.xml:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">excerpt_separator</span><span class="pi">:</span> <span class="s2">"</span><span class="s"><!--</span><span class="nv"> </span><span class="s">more</span><span class="nv"> </span><span class="s">-->"</span>
</code></pre></div></div>
<h2 id="修改了博客的title-style为直接用title做url">修改了博客的title style为直接用title做url.</h2>
<p>_config.xml:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">permalink</span><span class="pi">:</span> <span class="s">/:title.html</span>
</code></pre></div></div>
<h2 id="增加了feed">增加了feed</h2>
<p>feed.xml:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>---
layout: none
---
<span class="cp"><?xml version="1.0" encoding="utf-8"?></span>
<span class="nt"><feed</span> <span class="na">xmlns=</span><span class="s">"http://www.w3.org/2005/Atom"</span> <span class="na">xml:lang=</span><span class="s">"en"</span><span class="nt">></span>
<span class="nt"><title</span> <span class="na">type=</span><span class="s">"text"</span><span class="nt">></span>{{ site.title }}<span class="nt"></title></span>
<span class="nt"><generator</span> <span class="na">uri=</span><span class="s">"https://github.com/mojombo/jekyll"</span><span class="nt">></span>Jekyll<span class="nt"></generator></span>
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"self"</span> <span class="na">type=</span><span class="s">"application/atom+xml"</span> <span class="na">href=</span><span class="s">"{{ site.url }}/feed.xml"</span> <span class="nt">/></span>
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"alternate"</span> <span class="na">type=</span><span class="s">"text/html"</span> <span class="na">href=</span><span class="s">"{{ site.url }}"</span> <span class="nt">/></span>
<span class="nt"><updated></span>{{ site.time | date_to_xmlschema }}<span class="nt"></updated></span>
<span class="nt"><id></span>{{ site.url }}/<span class="nt"></id></span>
<span class="nt"><author></span>
<span class="nt"><name></span>{{ site.owner.name }}<span class="nt"></name></span>
<span class="nt"><uri></span>{{ site.url }}/<span class="nt"></uri></span>
<span class="nt"><email></span>{{ site.owner.email }}<span class="nt"></email></span>
<span class="nt"></author></span>
{% for post in site.posts limit:20 %}
<span class="nt"><entry></span>
<span class="nt"><title</span> <span class="na">type=</span><span class="s">"html"</span><span class="nt">></span><span class="cp"><![CDATA[{{ post.title | cdata_escape }}]]></span><span class="nt"></title></span>
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"alternate"</span> <span class="na">type=</span><span class="s">"text/html"</span> <span class="na">href=</span><span class="s">"{% if post.link %}{{ post.link }}{% else %}{{ site.url }}{{ post.url }}{% endif %}"</span> <span class="nt">/></span>
<span class="nt"><id></span>{{ site.url }}{{ post.id }}<span class="nt"></id></span>
{% if post.modified %}<span class="nt"><updated></span>{{ post.modified | to_xmlschema }}T00:00:00-00:00<span class="nt"></updated></span>
<span class="nt"><published></span>{{ post.date | date_to_xmlschema }}<span class="nt"></published></span>
{% else %}<span class="nt"><published></span>{{ post.date | date_to_xmlschema }}<span class="nt"></published></span>
<span class="nt"><updated></span>{{ post.date | date_to_xmlschema }}<span class="nt"></updated></span>{% endif %}
<span class="nt"><author></span>
<span class="nt"><name></span>{{ site.owner.name }}<span class="nt"></name></span>
<span class="nt"><uri></span>{{ site.url }}<span class="nt"></uri></span>
<span class="nt"><email></span>{{ site.owner.email }}<span class="nt"></email></span>
<span class="nt"></author></span>
<span class="nt"><content</span> <span class="na">type=</span><span class="s">"html"</span><span class="nt">></span>{{ post.content | xml_escape }}
<span class="ni">&lt;</span>p<span class="ni">&gt;&lt;</span>a href=<span class="ni">&quot;</span>{{ site.url }}{{ post.url }}<span class="ni">&quot;&gt;</span>{{ post.title }}<span class="ni">&lt;</span>/a<span class="ni">&gt;</span> was originally published by {{ site.owner.name }} at <span class="ni">&lt;</span>a href=<span class="ni">&quot;</span>{{ site.url }}<span class="ni">&quot;&gt;</span>{{ site.title }}<span class="ni">&lt;</span>/a<span class="ni">&gt;</span> on {{ post.date | date: "%B %d, %Y" }}.<span class="ni">&lt;</span>/p<span class="ni">&gt;</span><span class="nt"></content></span>
<span class="nt"></entry></span>
{% endfor %}
<span class="nt"></feed></span>
</code></pre></div></div>
<h2 id="增加了首页的翻页">增加了首页的翻页</h2>
<p>除了上下页以外, 增加了直接跳转的页码, 并且为了格式美观, 把页码限定在31页.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">class=</span><span class="s">"center"</span><span class="nt">></span>
{% if paginator.previous_page %}
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"{{ paginator.previous_page_path | prepend: site.baseurl | replace: '//', '/' }}"</span> <span class="na">class=</span><span class="s">"nav_previous"</span><span class="nt">></span>上一页<span class="nt"></a></span>
{% endif %}
{% for page in (1..paginator.total_pages) %}
{% if page <span class="err"><</span> 31 %}
{% if page == paginator.page %}
<span class="nt"><span</span> <span class="na">class=</span><span class="s">"active"</span><span class="nt">></span>{{ page }}<span class="nt"></span></span>
{% elsif page == 1 %}
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"{{ '/index.html' | prepend: site.baseurl | replace: '//', '/' }}"</span><span class="nt">></span>{{ page }}<span class="nt"></a></span>
{% else %}
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"{{ site.paginate_path | prepend: site.baseurl | replace: '//', '/' | replace: ':num', page }}"</span><span class="nt">></span>{{ page }}<span class="nt"></a></span>
{% endif %}
{% endif %}
{% endfor %}
{% if paginator.next_page %}
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"{{ paginator.next_page_path | prepend: site.baseurl | replace: '//', '/' }}"</span> <span class="na">class=</span><span class="s">"nav_next"</span><span class="nt">></span>下一页<span class="nt"></a></span>
{% endif %}
<span class="nt"></div></span>
</code></pre></div></div>
<h2 id="文章的结尾列出了标签和分类">文章的结尾列出了标签和分类</h2>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><p></span>
分类:<span class="ni">&nbsp;</span>
{% for cate in page.categories %}
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"/categories.html#{{ cate }}"</span> <span class="na">class=</span><span class="s">"category"</span><span class="nt">></span>{{ cate }}<span class="ni">&nbsp;</span><span class="nt"></a></span>
{% endfor %}
<span class="nt"><br</span> <span class="nt">/></span>
标签:<span class="ni">&nbsp;</span>
{% for tag in page.tags %}
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"/tags.html#{{ tag }}"</span> <span class="na">class=</span><span class="s">"tag"</span><span class="nt">></span>{{ tag }}<span class="ni">&nbsp;</span><span class="nt"></a></span>
{% endfor %}
<span class="nt"></p></span>
</code></pre></div></div>
<h2 id="增加了sitemapxml">增加了sitemap.xml</h2>
<p>sitemap.xml:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>---
layout: none
---
<span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><urlset</span> <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="na">xsi:schemaLocation=</span><span class="s">"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"</span> <span class="na">xmlns=</span><span class="s">"http://www.sitemaps.org/schemas/sitemap/0.9"</span><span class="nt">></span>
{% for post in site.posts %}
<span class="nt"><url></span>
<span class="nt"><loc></span>{{ site.url }}{{ post.url }}<span class="nt"></loc></span>
{% if post.lastmod == null %}
<span class="nt"><lastmod></span>{{ post.date | date_to_xmlschema }}<span class="nt"></lastmod></span>
{% else %}
<span class="nt"><lastmod></span>{{ post.lastmod | date_to_xmlschema }}<span class="nt"></lastmod></span>
{% endif %}
<span class="nt"><changefreq></span>weekly<span class="nt"></changefreq></span>
<span class="nt"><priority></span>1.0<span class="nt"></priority></span>
<span class="nt"></url></span>
{% endfor %}
{% for page in site.pages %}
{% if page.sitemap != null and page.sitemap != empty %}
<span class="nt"><url></span>
<span class="nt"><loc></span>{{ site.url }}{{ page.url }}<span class="nt"></loc></span>
<span class="nt"><lastmod></span>{{ page.sitemap.lastmod | date_to_xmlschema }}<span class="nt"></lastmod></span>
<span class="nt"><changefreq></span>{{ page.sitemap.changefreq }}<span class="nt"></changefreq></span>
<span class="nt"><priority></span>{{ page.sitemap.priority }}<span class="nt"></priority></span>
<span class="nt"></url></span>
{% endif %}
{% endfor %}
<span class="nt"></urlset></span>
</code></pre></div></div>
<h2 id="导入文章">导入文章</h2>
<p>修改完博客的风格后, 自己感觉还是比较满意的, 原来用markdown写的文章稍微改改就能用了, 但是原来还有很多老的文章, 最开始其实是在<a href="http://blog.csdn.net/vagrxie">CSDN</a>上写的, 并且导入了原来的wordpress博客了, 这里通过Jekyll官方提供的<a href="http://import.jekyllrb.com/docs/wordpress/">导入程序</a>导入.</p>
<h2 id="评论">评论</h2>
<p>原来的博客就接入了<a href="http://uyan.cc">友言</a>, 并且同步了老的评论, 但是因为新的博客所有的地址都变了, 而友言以文章地址为唯一的标识, 所以评论虽然都还在, 但是实际一个都匹配不上了, 感觉丢了2400多条评论也挺可惜的, 所以特别写了个ruby程序导入了原来的评论, 这个单开了一篇文章<a href="/import-comment-from-wordpress-in-uyan.html"><strong>从Wordpress中导入友言的评论</strong></a>来说明.</p>
<p><a href="http://www.jtianling.com/write-blog-with-jekyll-and-github-pages.html">用Github Pages写博客</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on May 19, 2014.</p>
http://www.jtianling.com/procrastination-is-not-a-big-deal
2013-12-05T00:00:00+08:00
2013-12-05T00:00:00+08:00
http://www.jtianling.com
<p>第一次听到结构化拖延法是看<a href="http://v.youku.com/v_show/id_XNTk2NDI0NDg4.html?firsttime=730">罗辑思维第36集</a>的时候, 觉得很新鲜, 一直就想看看怎么样叫做拖拉一点也无妨. 因为我本身就是个有拖延症的人, 看这本书的时间也一拖再拖, 直到前几天我的kindle paperwhite2终于从美国寄到我手里, 才在亚马逊买了这本书来看. 书本身很薄(因为是看电子书, 其实也没法实际的感知书的厚薄了), 很有趣也很容易看完. 在这里跟大家分享一下.</p>
<!-- more -->
<p><strong>目录</strong>:</p>
<ul id="markdown-toc">
<li><a href="#结构化拖延法structured-procrastination" id="markdown-toc-结构化拖延法structured-procrastination">结构化拖延法(structured procrastination)</a></li>
<li><a href="#拖延与完美主义" id="markdown-toc-拖延与完美主义">拖延与完美主义</a></li>
<li><a href="#其他" id="markdown-toc-其他">其他</a></li>
<li><a href="#我自己战胜拖延症的办法" id="markdown-toc-我自己战胜拖延症的办法">我自己战胜拖延症的办法</a></li>
</ul>
<h1 id="结构化拖延法structured-procrastination">结构化拖延法(structured procrastination)</h1>
<p>拖延症, 就像罗胖子的节目名, “大家都有拖延症”, 可能只是拖延的程度不同而已. 没有谁真的能完全理性到任何时候都去做当时最应该做的事情, 并且绝不延迟. 人类毕竟不是机器, 有七情六欲, 知道累, 要睡觉, 有娱乐需求, 总有累的不想动, 把任何重要的事情都抛在脑后, 去玩玩游戏, 看看电影打发时间的时候. 而怎么克服拖延症? 似乎我没有看到有啥公论, 这似乎也不太可能有公论, 因为这本身就根据不同人自己的评判标准而不同, 对自己要求越高的人, 往往越会觉得自己有越严重的拖延症, 反而成天无所事事的人, 不一定觉得自己有啥拖延的.<br />
作者根据自己的亲身经历总结出了一套自称为”结构化拖延法”(structured procrastination)的理论, 不是要告诉我们怎么克服拖延症, 而是告诉我们要怎么利用自己的拖延症来完成工作. 这个太神奇了, 拖延症不就是因为拖拉而耽误工作吗? 怎么还能利用来完成工作呢? 就这么不可思议的事情, 作者亲身实验, 号称颇有成就, 最不可思议的是, 看完书后, 你还真想尝试一下, 因为觉得作者说的好像还真是那么回事.<br />
首先, 我们任何人都不是喜欢无所事事的, 甚至无所事事比干点啥事都还要难受, 所以才有了那么多无聊的事情被发明出来, 比如电视剧, 电影, 扑克, 麻将, 甚至游戏, 这些所谓的娱乐活动, 不就是让我们不至于没有事干吗?<br />
其次, 当一件你很不想干, 但是有很紧急的事情摆在你的面前时, 反而其他的一些你其实也不想干的事情, 就变得没有那么不能接受了. 比如我觉得健身, 学习英语, 学习技术是我当前最重要的三个事情, 健身让我身体健康, 学习英语让我可以看懂英文原著, 听懂英文讲座, 学习技术是我的职业发展需求, 都很重要. 但是其实这三个事情都不是那么轻松的事情, 哪一个都很难让你长年坚持下去, 但是, 假如此时有三个选择, 一个是在跑步机上跑上那么几十分钟, 一个是拿起一本全英文的大部头看个几十分钟, 一个是拿起一本C++的书来看几十分钟, 我可能会突然觉得看本C++的书, 也还是可以接受的了. 尽管我常常以各种理由说服我自己, 不要再看C++的书了-_-!<br />
作者很有意思的的提出了一个诀窍, 告诉你怎么欺骗自己, 把一些不重要的事情怎么看起来重要, 把一些其实优先级不高的事情怎么看起来优先级高, 我想我是做不到了, 以上面我自己的例子为例, 假如我目前有三个很重要的事情要做, 那么哪怕最后我只干了其中我自己最能接受的那一个, 我感觉结果也还是能接受的, 虽然我现在比以前胖多了, 虽然我英语明显退步的比进步的还多, 但是起码技术已经比很多真的无所事事的人好多了.</p>
<h1 id="拖延与完美主义">拖延与完美主义</h1>
<p>我不知道自己算不算完美主义, 但是书中描述的那种害怕做不好就干脆拖着不做的事情我自己是经历过很多. 写博客就有很多这种例子, 常常突然想到某个好的主题, 然后开始就查阅大量文档资料, 然后再反复构思, 自以为研究的这么透彻了, 还不得写个惊天地泣鬼神的大作啊, 不惊天地泣鬼神也行, 不也得在憋了这么老半天后, 写个刘未鹏那种水平的文章啊, 等到真的提笔要写的时候, 还没开个头, 就发现远远达不到自己的要求, 写下来也就是一篇可有可无的平庸之作, 说不定还赶不上自己博客的平均水平, 于是干脆就当作草稿保存下来了, 再也没有写完.
写这篇博客的时候, 虽然没有查阅大量资料吧, 其实写了一半也觉得没啥可写, 也准备放弃了, 突然想到了作者的一些话, 想想就算这篇博客不完美又能怎么样? 真的就会有很多读者跑上来留言说博主你写的文章太烂了吗? 也还不至于吧.</p>
<h1 id="其他">其他</h1>
<p>因为本书其实就是因为第一章比较重要, 后面的内容价值越来越少, 还有几点值得一提:</p>
<ol>
<li>待办清单可以更加细化, 可以给自己更多成就感.</li>
<li>待办清单不仅要写自己要做什么, 还要写自己不要做什么(比如不要在网络上瞎逛).</li>
<li>将枯燥的事情和有趣的事情结合, 这样枯燥的事情也会变得有趣一些, 比如在跑步的时候听音乐(或者有声书). 话说我跑步的时候一般都这么干. 还有开长途车的时候, 上次自驾游去了趟木兰围场, 路上听完了袁腾飞的两宋风云. 还有我常常一边举哑铃, 一边看电影, 记得大学的时候看”拳霸”的时候, 因为电影本身很精彩让人热血沸腾, 整部电影我哑铃就几乎没有放下.</li>
</ol>
<h1 id="我自己战胜拖延症的办法">我自己战胜拖延症的办法</h1>
<p>我对我自己的拖延症有个独家秘籍. 这个也要从一个大家可能都有一些的缺点说起. 以前我们常常说某个人只有三分钟热情, 以此来形容一个人干事情没有耐性. 我自己假如真的坚定的想要干一个事情, 可能无论刚开始有多么坚定的信念, 最后也就坚持了1~2个月, 这后面就会想出各种理由, 将这个本来很重要的事情拖下去.
假如其他人也是这样, 那么这个事情最后也就做了一半, 也就做不成了. 我有些不同, 那就是在这个事情拖了一下以后, 能够自省, 觉得这样做不对, 但是即使这样, 因为事情已经拖下来了, 要再次开始, 往往会动力不够, 于是怀着越来越大的负罪感继续拖下去, 直到一天我觉得这不是办法, 我会想定一个具体的时间节点, 给我自己一个理由, 重新再开始干这个事情.
举个具体的例子, 最近看书明显少了, 于是我把原因归结于晚上开灯看书影响孩子睡觉, 那么怎么解决呢? 我给自己买了个kindle paperwhite2, 带阅读灯的, 够自己看书, 光线又不至于影响孩子睡觉. 于是书一收到, 就先买了”拖延一点也无妨”这本书来看, 并且几天看完了.
我可能内心知道这个很无厘头, 我不买这个kindle也有很多可以看书的办法, 但是我就是需要这么一个时间节点来让我恢复状态. 可能我能够靠这个状态再坚持几个月, 直到自己又懒了, 想出新的理由不去看书, 到那时候, 我又会想出新的办法. 虽然我还是很难长时间坚持干一个事情, 但是实际上长时间的看来, 我坚持干某些事情时间还是挺长的.
这么说, 假如一个人受次激励只有三分钟热情, 那么每三分钟激励他一次, 这个人就能有无限的热情.
这似乎也是另外一种形式的拖拉一点也无妨吧. 就把我们真正因为拖拉而耽误的时间, 当作是状态调节的放松时间, 只要最后能回到正轨就行.
我阅读的版本:
<a href="http://www.amazon.cn/gp/product/B00DMWN5Z0/ref=as_li_ss_tl?ie=UTF8&camp=536&creative=3132&creativeASIN=B00DMWN5Z0&linkCode=as2&tag=jtianlinsblog-23""><img src="http://jtianling-blog.oss-cn-hangzhou.aliyuncs.com/2266/book.jpg" alt="book" /></a></p>
<p><a href="http://www.jtianling.com/procrastination-is-not-a-big-deal.html">拖拉一点也无妨</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on December 05, 2013.</p>
http://www.jtianling.com/a-childhood-dream-come-true-building-spine
2013-08-21T00:00:00+08:00
2013-08-21T00:00:00+08:00
http://www.jtianling.com
<p>Spine是一个2D的骨骼动画编辑器, 因为其良好的UI设计及完整的功能, 在kickstarter上发布以后立即收到追捧, 作为一个几乎只有游戏开发者才会使用的小众工具, 募集了远超目标5倍的资金, 共计6.7W多美元. 以前我写过博客介绍了一些<a href="http://www.jtianling.com/articles/2220.html">Spine的基本用法</a>. <a href="http://esotericsoftware.com/">官方的网站</a>上有更详细的介绍, 还有一段特性介绍的视频.
Spine的作者最近(2013-08-13)在自己的博客上写了一些关于<a href="http://esotericsoftware.com/spine/building-spine/">Spine背后的故事</a>, 我感觉很有意思, 在这里跟大家分享一下, 因为精力有限, 也不做全文的翻译, 主要翻译故事部分, 关于技术部分就不详细翻译, 只是写个概要了.</p>
<!-- more -->
<p><strong>目录</strong>:</p>
<ul id="markdown-toc">
<li><a href="#一段小小的历史-改变我的人生" id="markdown-toc-一段小小的历史-改变我的人生">一段小小的历史, 改变我的人生</a></li>
<li><a href="#需求出现-制作开始" id="markdown-toc-需求出现-制作开始">需求出现, 制作开始</a></li>
<li><a href="#人总是要吃饭的-kickstarter" id="markdown-toc-人总是要吃饭的-kickstarter">人总是要吃饭的: Kickstarter</a></li>
<li><a href="#市场推广-什么是市场推广" id="markdown-toc-市场推广-什么是市场推广">市场推广? 什么是市场推广?</a></li>
<li><a href="#kickstater以后的生活-客户支持和stretch-goals" id="markdown-toc-kickstater以后的生活-客户支持和stretch-goals">Kickstater以后的生活: 客户支持和Stretch Goals</a></li>
<li><a href="#spine用到的技术" id="markdown-toc-spine用到的技术">Spine用到的技术</a></li>
<li><a href="#生活是美好的" id="markdown-toc-生活是美好的">生活是美好的</a></li>
<li><a href="#译后感" id="markdown-toc-译后感">译后感</a></li>
</ul>
<h1 id="一段小小的历史-改变我的人生">一段小小的历史, 改变我的人生</h1>
<p>我(Nate)从很久很久以前就想要制作Spine这样一个骨骼动画编辑器了. 我11岁刚开始学习用QBasic编程时, 我的杰作是一个3D格斗游戏. 这个游戏花费了我几年时间, 但其实也不是那么好玩, 但是它已经有骨骼动画和一个非常简单的编辑器了. 在我19岁离开学校进入公司后, 我又做了12年的软件开发. 在那个时候, 我刚刚离婚, 并且还有房贷尚未还清, 那时候我的生活就是工作, 吃饭和睡觉. 枯燥的生活让我决定要改变这一切, 我放弃了房子, 辞掉了在Mickey Mouse公司的稳定工作, 变成了一个无业游民. 在此后超过一年半的时间里, 我就在我世界各地的朋友们那儿鬼混. 我爱编程, 所以我花了很多时间在开源项目(还有街霸)上.<br />
<a href="http://www.youtube.com/watch?v=d7wntjD8YR8"><img src="/public/images/2013/spine-qbasic.png" alt="3D格斗游戏截图" /></a></p>
<h1 id="需求出现-制作开始">需求出现, 制作开始</h1>
<p>最后, 我的难兄难弟Soren和我决定做一个游戏. 但序列帧动画限制太大, 并且总是需要花费很多时间在美术制作上. Soren是一个美术, 并且精通Softimgae, 所以我写了一个导出脚本和一个简单的骨骼动画库. 它能够正常工作, 但是工作的流程非常的不好. 因为它很难配置, 并且容易碰到不支持的Softimage的特性. 我们找遍了其他所有工具, 但是都没有合适的, 而我们有清晰的目标, 我们知道我们需要制作一个什么样的工具, 所以我们开始自己动手制作这个工具. 在我做那个QBasic格斗游戏足足20年后, 我很高兴的把全部的时间都投入到Spine上!<br />
我们一直推迟着Spine的发布, 直到完成了所有基础的工作以后. 我们有太多关于Spine的想法, 以致很难完成所有功能, 我们只能实现最基本的特性, 所以我们毙掉了很多想法, 我们总是告诉我们自己, “我们总是可以在以后再增加”, 任何复杂并且又不是核心功能的特性要被去掉, 而另外一些复杂但是很重要的特性又必须要实现. 困难就在于知道这两者的不同. 还好Soren有很多实际的工程经验, 并且知道这样一个工具应该怎么工作. 同时Soren也想在Spine中加入成千上万的特性, 此时我就反过来拒绝那些特性, 除非它明显很必要(或者很容易实现).<br />
<img src="/public/images/2013/spine-screen.png" alt="Spine Dragon 截图" /></p>
<h1 id="人总是要吃饭的-kickstarter">人总是要吃饭的: Kickstarter</h1>
<p>我们在Spine的编辑器部分花了9个月, 并且只有libgdx一个运行时库. 我们和投资人没有任何联系, 并且肯定我们正在花光我们的积蓄. 所以我们决定到Kickstarter上来试试, 一方面我们的确需要钱, 另一方面也是向大家宣传一下Spine. 为了让我们合作更方便, 我到了Denmark, 并且住在Soren的家里. 我把我的手机粘在椅子的后面, 录制了那段视频.(译者注: 指Kickstarter上用于宣传的<a href="http://www.kickstarter.com/projects/esotericsoftware/spine">那段视频</a>) 这对我来说不是个容易的事情, 因为我必须尝试很多遍才能达到想要的效果. 我现在做梦(可能还有你)都还能总是会梦到我在说”Hi, my name is Nate!”. Soren录制了Spine的操作视频, 并且做了写视频的后期工作. 我们的目标是在前20秒内吸引住观众, 使他们愿意继续看完剩下的内容.<br />
我们担心不能达到kickstarter的goal, 所以我们设置了一个我们感觉可以完成一些runtime的底限, 1.2万美元. 而实际上我们只用了3天就达到了这个目标, 这是完全在我们意料之外的. 于是我们决定使用一个更好的方法来制作runtime, 那就是做一个通用的runtimes, 用这些runtime可以更加容易的制作目标游戏套件的runtime. 我们在Kickstarter上写了更多以前没有写的特性, 并且把他们都作为stretch goals.(译者注: Kickstarter上达到基础goal后, 通过募集更多资金去完成的更进一步的goal)</p>
<p><img src="/public/images/2013/spine-minichart.png" alt="Spine Kickstater项目截图" /></p>
<p>要想对Kickstarter上的用户维持一个高水平的用户支持是一个艰巨的任务. 好几天, 我们把全部的时间都用在回复Kickstarter上的问题, 评论, 邮件和论坛帖子上. 这个事情非常的耗费精力, <strong>但是我们的确确保了每一个有问题的人都得到了回答.</strong> (译者注: 就我最近使用Spine的经验来看, 作者对提问的回答的确相当即时)
Kickstarter最终募集了67569美元, 是我们初始目标的563%. 我们完成了除最后一个goal外的所有goals. 非常, 非常多的stretch goals. 现在回想起来, 实在是太多goals了. 大部分Kickstarter stretch goals在我们全职工作了6个月后还没有完成, 但是我们将会完成. <strong>我们决定对每一个曾经支持过我们的人兑现我们的承诺</strong>.</p>
<h1 id="市场推广-什么是市场推广">市场推广? 什么是市场推广?</h1>
<p>我们从来没有为Spine做过任何市场推广, 主要可能是我们不知道该怎么做. 我认为Kickstater可能算是一种市场推广的形式吧. 我们的Twitter账户很活跃, 这可能也对Spine的推广有较大的帮助. 我认为肯定还有很多开发者会很喜欢使用Spine, 但是还没有发现它. 我们不得不寄希望于他们能通过Google或者从朋友口中得知Spine. 我知道这很天真, 但是市场推广在我看来是个不可思议的事情, 为市场推广花钱看起来非常的愚蠢, 因为你不会真的知道这个钱花的值不值.</p>
<h1 id="kickstater以后的生活-客户支持和stretch-goals">Kickstater以后的生活: 客户支持和Stretch Goals</h1>
<p>本段为译者写的概要:<br />
在Kickstater以后, 作者花费了相当多的时间回答用户的问题, 并且Nate如期的完成了一些Kickstater中许诺的Stretch Goals.<br />
Ghosting, 在Spine中绘制动画的过去和将来.
<img src="/public/images/2013/spine-ghosting.png" alt="Spine Ghosting功能截图" />,</p>
<p>支持在一个project中同时操作多个骨骼动画, 这样可以让Spine支持制作一些过场动画(以前我们叫cinematic). 但是目前还需要完成多个骨骼动画之间的绘制顺序问题才能进入实用阶段.</p>
<p>内嵌了一个图片打包的工具, 这样就可以在不需要类似Texture Packer等工具的情况下, 单独导出需要的动画.
<img src="/public/images/2013/spine-packer.png" alt="Spine Texture Packer功能截图" />,</p>
<p>增加了event timeline功能, 这样可以在特定的时间点放声音, 粒子效果等. 这个功能很有用, 但是目前大部分runtime都不支持.</p>
<h1 id="spine用到的技术">Spine用到的技术</h1>
<p>本段为译者写的概要:<br />
Spine使用的技术很特别, 语言使用Java, 但是UI使用的是<a href="http://libgdx.badlogicgames.com/">libgdx</a>, 因为作者本身就是libgdx的联合作者. 作者还在这里给libgdx做了下广告, 告诉大家libdgx这个游戏框架有多么好用, 并且可以跨Windows, Mac, Linux, iOS, Android等平台, 甚至可以通过WebGL运行在浏览器中, 而Spine因为是用libgdx开发的, 所以Spine也可以在上述所有平台上使用. 另外, 这倒是解释了为啥Spine一开始就跨那么多平台, 并且主按钮是那么奇怪的放在左上角.<br />
以下是Spine用到的一些开源库列表:</p>
<ul>
<li><a href="https://code.google.com/p/libgdx/wiki/scene2dui">scene2d.ui</a>, libgdx的一部分, 配合libgdx的2D UI库.</li>
<li><a href="http://code.google.com/p/table-layout/">TableLayout</a>, 作者自己写的Java开源库, 配合ligdbx使用的UI layout库, 类似HTML的tables, 实际可以支持libgdx, Swing, Android, TWL等其他UI框架.</li>
<li><a href="http://code.google.com/p/scar/">Scar</a>, 作者自己写的开源库, java一些utilities功能的集合.</li>
<li><a href="http://code.google.com/p/wildcard/">Wildcar</a>, 作者自己写的Java开源库, 用于使用’*‘匹配文件和目录.</li>
<li><a href="http://code.google.com/p/kryo/">Kryo</a>, 作者自己写的开源库, 用于高效的序列化对象.</li>
<li><a href="http://code.google.com/p/jsonbeans/">JsonBeans</a>, 作者自己写的开源库, 用于从json中序列化和反序列化Java对象.</li>
<li><a href="http://code.google.com/p/libgdx/wiki/TexturePacker">TexturePacker</a>, libgdx的一部分, 用户处理图片打包.</li>
</ul>
<p>很不可思议的是, 上面的那些开源库里面都是Nate自己写的(libgdx部分起码是参与了), Nate为了写游戏积累的东西不是一般的多, 很让我感叹.<br />
另外, 作者还提到它使用了<a href="http://www.java-gaming.org/topics/java-continuations-and-greenthreads/28337/view.html">green threads</a>, 这个在一个线程中模拟出多个线程效果的库, 用户级别的多线程实现.<br />
Spine有一个很好的构建系统, 并且在软件中内置了一个自动升级系统, 作者可以一键自动部署到各个平台的所有用户那里. 这个也很牛掰~~(也许是从一个C++开发者眼里看来吧)<br />
Spine的所有官方runtimes都在<a href="https://github.com/EsotericSoftware/spine-runtimes">github</a>上开源发布, 所以它自己虽然只官方支持了13个开发套件, 但是因为为C, C++, Objective-C, C#, Java, ActionScript 3, Lua和JavaScript提供了通用的runtime, 该runtime仅仅是提供了一套<a href="http://esotericsoftware.com/spine/files/runtime-diagram.png">API</a>来操作骨骼和动画, 但是不针对特定开发套件, 忽略了渲染部分, 使得更加容易完成, 因为这个作者认为正确的决策, 第三方的runtime已经超过20个了, 你几乎可以在任何你想要的平台和引擎上使用Spine.</p>
<h1 id="生活是美好的">生活是美好的</h1>
<p>以下为译者写的概要:
Spine卖的不错, 作者得到了税后6万1千美元的收益, 作者说他还有很多增强Spine的想法, 比如增加包围盒等, 作者甚至提到Spine的编辑器中已经有了一个IK(反向动力学)系统(还没有加到runtime中).</p>
<p>以下为作者全文最后一段的翻译:
回顾以往, 会感叹事情的发展太疯狂了, 从一个糟糕的QBasic格斗游戏最后能发展成为自己热爱的事业. 我在今年6月刚刚结婚, 并且住在我老婆的故乡–克罗地亚, 我们住在一个8公里(译者注: 不知道原文这是表示宽还是周长)的小岛上, 这里没有公路, 也没有汽车. 虽然用的是很慢的3G网络, 但是生活成本非常的低, 而且克罗地亚的海滩很amazing. 我每天就是写代码, 游泳, 并且养了一个小狗. 生活很美好~~</p>
<h1 id="译后感">译后感</h1>
<p>最近看到吴军写的一篇<a href="http://www.forbeschina.com/review/201307/0026734.shtml">关于乔布斯的文章</a>, 对其中一句话印象深刻, 我觉得也很适合用于总结本文: <strong>大多数产品经理之所以做不出改变世界的发明, 是因为他们只看见了成功者最后的临门一脚, 而忽视了别人的长期思考.</strong></p>
<p><a href="http://www.jtianling.com/a-childhood-dream-come-true-building-spine.html">[译]儿时梦想成真: Spine背后的故事</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on August 21, 2013.</p>
http://www.jtianling.com/talk-about-cocosino
2013-07-30T00:00:00+08:00
2013-07-30T00:00:00+08:00
http://www.jtianling.com
<p>最近Cocco2d-x的主页上Post了一个项目, 名叫<a href="http://www.cocos2d-x.org/news/129">Cocosino</a>, 是一个Cocos2d-x Javascript Binding/cocos2d-html5的IDE. 很有兴趣, 所以下回来看了一下.</p>
<!-- more -->
<p>Cocosino定位为IDE, 但是因为目前的版本只是非常早期的版本, 所以功能相当的不足, 基本上仅相当于一个项目创建脚本 + 一个简单的文本编辑器. 假如我真的要做一个项目的画, 我甚至更愿意自己写个脚本干这个事情, 起码文本编辑器我还能用VIM.<br />
失望之余, 去Kickstarter看了下Cocosino的<a href="http://www.kickstarter.com/projects/881121752/cocosino">项目介绍</a>. 发现情况相当惨淡, 从七月12号到今天, 已经18天了, 只融到了$1447, 联想到了前段时间的<a href="http://www.kickstarter.com/projects/esotericsoftware/spine">Spine</a>, 两者同样定位为开发工具, Spine30天募集资金$67569, 我倒是想从项目的角度分析一下为啥Spine能成功而Cocosino不行.</p>
<p>项目寻求资金的时间点:
Spine首先做了一个完整版本, 具有全部功能的工具, 募集资金仅仅是用于runtime和更高级功能的开发, 所以宣传视频可以做的很棒, 展示了众多让人眼前一亮的功能. 我尝试了一下具有完整功能的Spine, 立刻决定付费, 成为backer了.<br />
而Cocosino放出的是一个最早期的版本(作者称first version), 基本没有啥功能, 就如我说的, 大概也就相当于一个项目创建脚本和一个简单的文本编辑器, 所以宣传视频都没有啥可以说的, 展示了半天, 也就相当于证明了这个项目创建脚本创建项目的确是成功了. 吸引力实在实在有限.<br />
假如Cocosino稍微再缓一缓, 等IDE方面比较具有吸引力的功能做出来了, 到时候视频展示一下javascript的断点调试, 强大的代码补全功能, 再有个一键签名打包啥的, 然后再出来说”Why do we need your help?”的募集理由时, 可以是说工具已经完善并且稳定了, 但是考虑给Cocosino加上插件系统, 添加Vim, Emacs的植入plugin啥的, 估计一下就能让听闻者流泪, 观看者付费了. 现在这样, 吸引力实在有限.</p>
<p>项目目标人群:<br />
以前我和一个老同事聊天, 就很感叹于原公司的2D动画工具的强大, 而开源社区这块的不足, 理由太简单了, 开发引擎是个多么有挑战和乐趣的事情啊, 而工具呢? 去考虑的是UI, 用户体验和细节, 太多繁琐的东西了. 这根本就不是那些大牛们感兴趣的领域, 以前<a href="http://orx-project.org/">Orx</a>的作者就和我说过, 他Hate开发工具. 简而言之, 2D骨骼动画本身是个很有潜力的东西, 不局限于任何引擎, 任何平台, 除了少数大公司内部有类似的工具, 开源社区极端缺乏一个这样的工具, 所以Spine出来的时候, 我感觉是相见恨晚.<br />
相对而言, Cocosino针对的市场就是另外一回事了, Cocos2d-x Javascript binding本身就还不够成熟, 还没有太多成功的例子, 意味着连用Cocos2d-x javascript binding的开发者都还少, 那对相关开发的IDE的需求就更少了.<br />
对于这个问题, 我觉得是目前市场的原因, 等将来javascript binding成为cocos2d-x开发的主流的时候, cocosino的市场前景还是不错的(虽然还是要小于Spine).</p>
<p>小结:
整体而言, 要是Cocosino能达到他提出的目标:</p>
<ul>
<li>Develop Cocos2d games on Windows, Mac OS and Linux</li>
<li>Create a Cocos2d JavaScript game for both native and web</li>
<li>Develop games with call tip hints and autocompletion of cocos2d JavaScript API</li>
<li>Use advanced documentation, samples and tutorials</li>
<li>Debug JavaScript codes easily</li>
<li>Publish your game for smartphones, tablets, web and desktop with only one click</li>
</ul>
<p>我觉得Cocosino还是一个非常不错的产品的, 但是现在Cocosino的开发者虽然是在干一件正确的事情, 但是很明显是在一个错误的时间.</p>
<p><a href="http://www.jtianling.com/talk-about-cocosino.html">Cocosino项目漫谈</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on July 30, 2013.</p>
http://www.jtianling.com/use-spine-with-cocos2d-x
2013-06-06T00:00:00+08:00
2013-06-06T00:00:00+08:00
http://www.jtianling.com
<p>Spine是一个2D的骨骼动画编辑器, 因为其良好的UI设计及完整的功能, 在kickstarter上发布以后立即收到追捧, 作为一个几乎只有游戏开发者才会使用的小众工具, 募集了远超目标5倍的资金, 共计6.7W多美元. 我在其项目发布后, 成为了Spine在kickstarter的早一批backer, 这是我在kickstarter上第一个, 也是目前唯一一个支持的项目. 随后, 通过不断收到的邮件见证了Spine逐步完善的过程, 直到其发出target完成的邮件. 又过了这么长时间了, 因为手头的项目一直不需要太复杂的2D骨骼动画, 拖着没有研究, 现在也是时候看看Spine了, 可惜的是, Spine的使用还有一系列的<a href="http://esotericsoftware.com/spine-videos/">视频教学</a>可以参考, 而Spine的Runtime使用完全没有文档, 只有一两个简单的例子. Spine团队的主要精力目前还是放在一些新功能的开发和Runtime的继续支持上, 写文档还排在<a href="https://trello.com/board/spine-runtimes/5131f92a7d6864661c002455">Next up</a>上.</p>
<!-- more -->
<p><strong>目录</strong>:</p>
<ul id="markdown-toc">
<li><a href="#runtime使用" id="markdown-toc-runtime使用">Runtime使用</a> <ul>
<li><a href="#简单的循环动画" id="markdown-toc-简单的循环动画">简单的循环动画</a></li>
<li><a href="#播放一次动画" id="markdown-toc-播放一次动画">播放一次动画</a></li>
<li><a href="#连续播放动画" id="markdown-toc-连续播放动画">连续播放动画</a></li>
<li><a href="#程序控制的骨骼动画" id="markdown-toc-程序控制的骨骼动画">程序控制的骨骼动画</a></li>
</ul>
</li>
<li><a href="#其他功能" id="markdown-toc-其他功能">其他功能</a> <ul>
<li><a href="#慢动作和快动作" id="markdown-toc-慢动作和快动作">慢动作和快动作</a></li>
<li><a href="#动画混合" id="markdown-toc-动画混合">动画混合</a></li>
</ul>
</li>
<li><a href="#问题" id="markdown-toc-问题">问题</a></li>
<li><a href="#工具使用上" id="markdown-toc-工具使用上">工具使用上</a></li>
<li><a href="#runtime的问题" id="markdown-toc-runtime的问题">Runtime的问题</a></li>
</ul>
<h1 id="runtime使用">Runtime使用</h1>
<h2 id="简单的循环动画">简单的循环动画</h2>
<p>编辑上看视频教程吧, 只是打包文件需要使用<a href="http://www.codeandweb.com/texturepacker">TexturePacker</a>的libgdx的Data Format来导出, 后缀改为atlas.<br />
然后, 从例子中能学到的东西:</p>
<ol>
<li>用<code class="language-plaintext highlighter-rouge">new CCSkeletonAnimation("test.json", "test.atlas");</code>来创建想要的Spine动画对象.</li>
<li>用<code class="language-plaintext highlighter-rouge">CCSkeletonAnimation</code>的<code class="language-plaintext highlighter-rouge">setAnimation("anim_name", true);</code>来设定想要的动画.</li>
<li>需要将CCSkeletonAnimation对象按node一样处理, 用addChild接口添加到parent node上. 并且对象可以当作普通的Node一样来操作, 因为它实际就继承自CCNodeRGBA.</li>
</ol>
<p>比如在一个node中, 按如下代码可以创建一个spineboy行走的动画, 并且循环播放.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">skeletonNode</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">CCSkeletonAnimation</span><span class="p">(</span><span class="s">"spineboy.json"</span><span class="p">,</span> <span class="s">"spineboy.atlas"</span><span class="p">);</span>
<span class="n">skeletonNode</span><span class="o">-></span><span class="n">setAnimation</span><span class="p">(</span><span class="s">"walk"</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
<span class="n">CCSize</span> <span class="n">windowSize</span> <span class="o">=</span> <span class="n">CCDirector</span><span class="o">::</span><span class="n">sharedDirector</span><span class="p">()</span><span class="o">-></span><span class="n">getWinSize</span><span class="p">();</span>
<span class="n">skeletonNode</span><span class="o">-></span><span class="n">setPosition</span><span class="p">(</span><span class="n">ccp</span><span class="p">(</span><span class="n">windowSize</span><span class="p">.</span><span class="n">width</span> <span class="o">/</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">20</span><span class="p">));</span>
<span class="n">addChild</span><span class="p">(</span><span class="n">skeletonNode</span><span class="p">);</span>
<span class="n">skeletonNode</span><span class="o">-></span><span class="n">release</span><span class="p">();</span>
</code></pre></div></div>
<h2 id="播放一次动画">播放一次动画</h2>
<p>简单的情况, 播放一次动画后就不管了, 那么直接使用<code class="language-plaintext highlighter-rouge">CCSkeletonAnimation::setAnimation</code>, 并且以false为第二个参数就好了.<br />
更复杂的情况, 需要知道什么时候这个动画播放完了, 因为Spine的Runtime中没有动画结束的回调(这是另外一种良好的设计), 只能通过在update中判断. 更进一步的悲剧是, 在cocos2d-x的Runtime中中没有简单的判断方法, example中给了一个方法:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">skeletonNode</span><span class="o">-></span><span class="n">states</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">-></span><span class="n">loop</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">skeletonNode</span><span class="o">-></span><span class="n">states</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">-></span><span class="n">time</span> <span class="o">></span> <span class="mi">2</span><span class="p">)</span> <span class="n">skeletonNode</span><span class="o">-></span><span class="n">setAnimation</span><span class="p">(</span><span class="s">"jump"</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">skeletonNode</span><span class="o">-></span><span class="n">states</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">-></span><span class="n">time</span> <span class="o">></span> <span class="mi">1</span><span class="p">)</span> <span class="n">skeletonNode</span><span class="o">-></span><span class="n">setAnimation</span><span class="p">(</span><span class="s">"walk"</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>这里使用的方法是判断播放时间, 我对此方法表示强烈的反感, 也<strong>绝对的建议大家不要使用</strong>, 因为你不仅需要预先知道每个动画播放的时长, 而且任何时候你改动了动画的播放时间, 这个代码都得回来改, 这样做根本就违反我们使用编辑器的初衷, 甚至我觉得在Runtime的example中给出这种代码是非常不负责任的行为. 这个方法只在一种情况下使用, 那就是你的确是想要在某个动画播放的确定时间干某个事情, 不过这种情况应该非常少见.<br />
在的确需要知道播放完一次动画时, 我建议用以下方式来完成, 因为没有现成的C++接口, 这里借用了一个C代码中的函数来完成工作:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span><span class="p">(</span><span class="n">AnimationState_isComplete</span><span class="p">(</span><span class="n">skeletonNode</span><span class="o">-></span><span class="n">states</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="mi">0</span> <span class="o">==</span> <span class="n">strcmp</span><span class="p">(</span><span class="n">skeletonNode</span><span class="o">-></span><span class="n">states</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">-></span><span class="n">animation</span><span class="o">-></span><span class="n">name</span><span class="p">,</span> <span class="s">"walk"</span><span class="p">)</span> <span class="p">)</span> <span class="p">{</span>
<span class="n">skeletonNode</span><span class="o">-></span><span class="n">setAnimation</span><span class="p">(</span><span class="s">"jump"</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span> <span class="p">{</span>
<span class="n">skeletonNode</span><span class="o">-></span><span class="n">setAnimation</span><span class="p">(</span><span class="s">"walk"</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="连续播放动画">连续播放动画</h2>
<p>上面那个例子中的动画连续动画播放可以直接通过<code class="language-plaintext highlighter-rouge">CCSkeletonAnimation::addAnimation</code>接口来完成, 这样更加简单.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">skeletonNode</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">CCSkeletonAnimation</span><span class="p">(</span><span class="s">"spineboy.json"</span><span class="p">,</span> <span class="s">"spineboy.atlas"</span><span class="p">);</span>
<span class="n">skeletonNode</span><span class="o">-></span><span class="n">addAnimation</span><span class="p">(</span><span class="s">"walk"</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
<span class="n">skeletonNode</span><span class="o">-></span><span class="n">addAnimation</span><span class="p">(</span><span class="s">"jump"</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
<span class="n">skeletonNode</span><span class="o">-></span><span class="n">addAnimation</span><span class="p">(</span><span class="s">"walk"</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
</code></pre></div></div>
<p>但是失去了一些灵活性, 并且, Spine还不支持将多个动画接起来作为一个完整的动画使用, 这个挺弱的, 再加上Spine工具本身就没有连接多个动画的功能, 就更加弱了.</p>
<h2 id="程序控制的骨骼动画">程序控制的骨骼动画</h2>
<p>所谓程序控制的骨骼动画, 就是类似Spine宣传动画中那样, Globin的目光跟随着鼠标的移动. 这个功能的实现, 应该算是骨骼动画中最酷的一部分了. 传统的序列帧动画完全无法实现, 要实现的话还是得在序列帧外拆分肢体, 然后单独实现.<br />
在骨骼动画中, 可以直接取到骨骼, 然后调整骨骼, 实现这样的效果, 要方便很多. 在Spine中取得骨骼的函数是<code class="language-plaintext highlighter-rouge">CCSkeletonAnimation::findBone</code>, 其他的就是设置rotation就行了.</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="n">ExampleLayer</span><span class="o">::</span><span class="n">ccTouchesMoved</span><span class="p">(</span><span class="n">CCSet</span> <span class="o">*</span><span class="n">touches</span><span class="p">,</span> <span class="n">CCEvent</span> <span class="o">*</span><span class="n">event</span><span class="p">)</span> <span class="p">{</span>
<span class="n">CCTouch</span><span class="o">*</span> <span class="n">touch</span> <span class="o">=</span> <span class="p">(</span><span class="n">CCTouch</span><span class="o">*</span><span class="p">)</span><span class="n">touches</span><span class="o">-></span><span class="n">anyObject</span><span class="p">();</span>
<span class="n">CCPoint</span> <span class="n">pos</span> <span class="o">=</span> <span class="n">touch</span><span class="o">-></span><span class="n">getLocation</span><span class="p">();</span>
<span class="n">Bone</span><span class="o">*</span> <span class="n">head</span> <span class="o">=</span> <span class="n">skeletonNode</span><span class="o">-></span><span class="n">findBone</span><span class="p">(</span><span class="s">"head"</span><span class="p">);</span>
<span class="n">CC_ASSERT</span><span class="p">(</span><span class="n">head</span> <span class="o">!=</span> <span class="nb">nullptr</span><span class="p">);</span>
<span class="kt">float</span> <span class="n">tanValue</span> <span class="o">=</span> <span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">y</span> <span class="o">-</span> <span class="n">head</span><span class="o">-></span><span class="n">worldY</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">x</span> <span class="o">-</span> <span class="n">head</span><span class="o">-></span><span class="n">worldX</span><span class="p">);</span>
<span class="kt">float</span> <span class="n">rotation</span> <span class="o">=</span> <span class="n">atan</span><span class="p">(</span><span class="n">tanValue</span><span class="p">)</span> <span class="o">*</span> <span class="mi">180</span> <span class="o">/</span> <span class="mf">3.1415</span><span class="p">;</span>
<span class="n">head</span><span class="o">-></span><span class="n">rotation</span> <span class="o">=</span> <span class="n">rotation</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>需要注意的是, 动画本身要是在播放的话, 不能动这个骨骼(被parent牵引是OK的), 不然的话会被动画本身强制改变, 看不到touch带来的效果.</p>
<h1 id="其他功能">其他功能</h1>
<h2 id="慢动作和快动作">慢动作和快动作</h2>
<p>设置CCSkeletonAnimation的timeScale值.</p>
<h2 id="动画混合">动画混合</h2>
<p>对于一般情况下, 动画的切换要求两个动画完全能衔接上, 不然会出现跳跃感, 这个对于美术来说要求很高, 而Spine加了个动画混合的功能来解决这个问题. 使得不要求两个动画能完全的衔接上.<br />
比如上面的walk和jump动画, 就是衔接不上的, 直接按上面的办法切换, 会出现跳跃, 但是加了混合后, 看起来就很自然了. 哪怕放慢10倍速度观察, 也完美无缺. 这个功能在序列帧动画时是无法实现的, 也是最体现Spine价值的一个功能. 代码如下:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">skeletonNode</span><span class="o">-></span><span class="n">setMix</span><span class="p">(</span><span class="s">"walk"</span><span class="p">,</span> <span class="s">"jump"</span><span class="p">,</span> <span class="mf">0.3</span><span class="n">f</span><span class="p">);</span>
<span class="n">skeletonNode</span><span class="o">-></span><span class="n">setMix</span><span class="p">(</span><span class="s">"jump"</span><span class="p">,</span> <span class="s">"walk"</span><span class="p">,</span> <span class="mf">0.3</span><span class="n">f</span><span class="p">);</span>
</code></pre></div></div>
<h1 id="问题">问题</h1>
<p>Spine的出现, 对于2D骨骼动画编辑工具来说, 绝对是翻天覆地的变化, 划时代的. 这也再次说明了自己的问题, 因为和以前的同事说了很久了, 其实我们需要一个这样的编辑器, 但是自己却从来没有写过一个-_-! 当然, 我们当时讨论的是做一个开源的. 但是, Spine毕竟刚刚出现, 其实还是有不少的问题. 如下:</p>
<h1 id="工具使用上">工具使用上</h1>
<ol>
<li>体验上, 因为Spine用了JAVA来实现偷懒的跨平台, 很多地方都弱爆了, 奇怪的menu就不说了, 那文件对话框难用的要死, 在Mac下, 会觉得那文件对话框简直就是折磨人, 不管是选对一个文件夹, 还是保存文件到一个地方, 都能让人很郁闷. 还能出现原界面被阻塞, 而文件对话框被挡住的情况. 假如要一套代码的跨平台通用, 那就没有用户体验可言. 当然, 其实Spine本身对动画编辑方面的体验还是非常棒的.</li>
<li>功能上, Spine不支持像cocos builder一样直接读取pack后的材质, 只能读原始的材质, 这个有些弱, 导致需要在原始资源上进行编辑. 这个在工程管理上没有直接读取打包后的材质方便. 假如打包出了什么问题, 这里也看不见.</li>
<li>还是功能上, Spine在编辑器中无法直接连接多个动画, 这个功能连cocos builder中都有.</li>
<li>虽然Spine有个用example做UI的演示, 但是实际上因为Spine没有任何地方可以设置点击响应和设置变量绑定, 这个基本上也就是只能做做UI上面的动画.</li>
<li>这个和Runtime也有关, Spine的扩展性几乎没有, 连想自定义一个参数都没有办法, 比如我想用Spine来设置一个Bone的旋转的上限和下限, 也无法做到.</li>
</ol>
<h1 id="runtime的问题">Runtime的问题</h1>
<p>Spine的Runtime有些太马虎了, 问题相当多.</p>
<ol>
<li>没有详细的文档就算了, 甚至连代码的注释生成文档都没有. 同时代码的注释也少的可怜, 根本就不像一个严谨的开源项目. 更进一步, 连example都只有1个, 要是不知道该怎么用, 哭去吧. 这个只能说Spine还不够成熟, 一般来说, 像这种程度的东西, <strong>最好别用</strong>.</li>
<li>为了让一套代码能够尽量支持多的引擎, 有些地方太偷懒了. 对此吐槽的也不止我一个, 比如这里的<a href="http://www.cocos2d-iphone.org/forums/topic/a-call-for-coders-to-build-a-better-spine-runtime-for-cocos2d/"><em>A call for coders to build a better Spine runtime for Cocos2D</em></a>, 就算是C++使用者, 用这简单从C wrap过来的runtime我都感觉非常难受, 更加别说objc的使用者了.</li>
<li>最大的问题是Spine用了一套后缀为atlas的资源文件, 但是这根本不是cocos2d/cocos2d-x的使用方式, 我们要的是plist! 所以Spine用到的资源会和Cocos2d-x中用到的资源格格不入, 无法统一管理和cache. 这种问题使得Spine几乎不可用, 因为一个稍微想点样子的游戏, 也不能容忍每个动画都是在需要播放的时候再加载资源.<br />
这个问题有个解决方案, 就是不用example中使用的<code class="language-plaintext highlighter-rouge">CCSkeletonAnimation</code>创建接口<code class="language-plaintext highlighter-rouge">CCSkeletonAnimation::CCSkeletonAnimation (const char* skeletonDataFile, const char* atlasFile, float scale)</code>, 而是使用<code class="language-plaintext highlighter-rouge">CCSkeletonAnimation::CCSkeletonAnimation (const char* skeletonDataFile, Atlas* atlas, float scale)</code>这个接口, 并且自己首先缓存Atlas文件. 或者, 直接缓存所有可能出现的skeletonNode对象. 只是, 这些解决方法都太麻烦并且不够优美. 并且, 还是没有办法和cocos2d-x原有的资源统一管理.</li>
</ol>
<p><a href="http://www.jtianling.com/use-spine-with-cocos2d-x.html">Spine的使用(With Cocos2d-x)</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on June 06, 2013.</p>
http://www.jtianling.com/cocoslicer-publish
2013-05-21T00:00:00+08:00
2013-05-21T00:00:00+08:00
http://www.jtianling.com
<p>工具很简单, 就是将TexturePacker等工具打包好的图片给分解开, 相当于Texture Pack后资源的解包过程.<br />
主要用于找回自己丢失原图片文件, 但是有打包后资源的情况, 请勿用于其他用途.</p>
<p>工具用Ruby完成, 利用了<a href="http://www.imagemagick.org/script/index.php"><em>Imagemagick</em></a>命令行工具而不是对应的Imagemagick Ruby库, 这个有些特殊, 也就是需要先安装Imagemagick后才能使用. 工具自己的安装非常方便, 因为已经作为Gem提交, 安装Ruby后, 按照普通的gem安装方式安装即可:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>gem <span class="nb">install </span>cocoslicer
</code></pre></div></div>
<p>使用时:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>cocoslicer packed.plist
</code></pre></div></div>
<p>目前工具不支持zwoptex打包的图片.</p>
<p>源代码地址: https://github.com/jtianling/cocoslicer</p>
<p><a href="http://www.jtianling.com/cocoslicer-publish.html">cocoslicer(cocos2d/cocos2d-x 打包后的资源分割器)</a> was originally published by at <a href="http://www.jtianling.com">九天雁翎的博客</a> on May 21, 2013.</p>