2020年8月20日 星期四

從 blogger 轉換到靜態網頁生成

看到這個標題大概就知道發生什麼事了……。
是的,經過十年 blogger 寫作,我終於決定要搬家了。

故事是這樣子的,自從 2011 年起我就持續的有在寫 blog,那時候選的是 google 的 blogger 服務,剛看一下到目前為止總計 246 篇文章,算上這篇搬家文的話是 247(剛好是 13 * 19 ,可以當作 RSA 公鑰),大部分都是技術相關,也有部分是生活雜記跟書評等等。
總括來講 blogger 並不是不好用,相反的 blogger 提供可以用瀏覽器編輯內容、圖片上傳後自動歸檔到近乎無限的 google 雲端(這另一方面也是一個問題,刪除誤上傳的照片很麻煩)、google 在背後保證了網站的可及性和穩定度、手機與電腦都支援的瀏覽介面,如果你對網誌內容沒有什麼太嚴謹的要求,只是想要記錄一下生活貼貼遊記照片,blogger 已經大概有 80 分吧。
特別是不要以今非古,以那個年代的網路環境,找免費空間架站還不是那麼容易的事,託管在 google/blogger 顯然是簡單很多的解法。

blogger 的缺點,最後逼得個人跳槽的最大原因,我認為是:程式碼與凌亂的文章格式。
畢竟個人的 blog 從開始的定位就不是簡單記錄生活就算了,我非程式相關的文章大概 30 篇左右,程式相關內容佔了 80 % 以上,而程式碼是 blogger 最弱的一環,目前我寫作 blogger 的工作流程通常是這樣子:
  1. 在 google doc 上面完成,我一個文件叫 write anything ,想寫什麼就全部往裡面倒。
  2. 整理成文章,把文章內容複製到 vim。
  3. 透過之前寫好的 blogger.vim 將文章內容要劃重點、程式碼的部分加上 tag。
  4. 用 blogger html 編輯器,直接將全篇文章貼入。
  5. 在 blogger 一般編輯器,幫文章加上連結、圖片等。
  6. 來回檢視預覽和編輯器,修正所有顯示不如預期的地方。
是的,我的文章從來不用 blogger 的編輯器,都是全部生好 html 再整個貼進去,程式碼的部分利用 pretty-print 套件(剛剛看到它 archive 了…)為 <pre> 上色,或是 pretty-print 失效時加上 <div> 標籤加框,幾乎每次編輯 blog 都一定要去改動 html,搞得寫程式相關文章變得很痛苦。

第二點也是我為何都用 html 編輯器的原因,透過 blogger 編輯器產生的內容格式非常不穩且難以維護,生出來的 html 簡直嘔吐物,很難再去手動修改 html ,但如上所述在寫文的時候去改動 html 幾乎是必要的,只能用編輯器的功能把所有格式都清除掉,那還不如一開始就寫 html 就好。

特別是這幾年開始寫 rust 相關文章之後,上述問題整個變本加厲,因為 rust 特有的生命周期語法,會讓 pretty-print 的上色功能大噴射(它會以為 lifetime 'a 是字串開頭,其實根本不是…),每寫一篇 rust 的文章想跳槽的意念就深一層,最近的 amethyst 系列真的是壓垮 yodalee 的最後一根稻草,系列文完成前就決定跳槽,想要看完 amethyst 系列文的朋友只好說聲對不起了。

這次跳槽的目標是轉換為 static site generator,其實這幾年來陸續都有朋友推薦這種寫作方式,但礙於懶得搬移舊文章一直沒有動手,推薦的工具也從早年的 Ruby/Jekyll 到最近改推 Go/Hugo 了;本來我是想說身為 rust 使用者,應該選擇 Rust/Zola(hail hydra…欸),只是後來發現 zola 的 theme 實在太少了,還是先用 hugo 撐撐場面。

廢話不多說,讓我們來開始著手搬家吧。

2020年8月9日 星期日

使用 Amethyst Engine 實作小行星遊戲 - 9 UI

現在讓我們來建 UI,我覺得建 UI 是目前掌握度比較低的部分,我也在想怎麼做才是對的。
目標是加上一個計分用的文字:
首先是 resource 的部分,建立 FontRes 來保存載入的字型檔,要建立文字的時候只要取用這個資源即可:
pub struct FontRes {
  pub font : FontHandle
}

impl FontRes {
  pub fn initialize(world: &mut World) {
    let font = world.read_resource::<Loader>().load(
      "font/square.ttf",
      TtfFormat,
      (),
      &world.read_resource(),
    );
    world.insert(
      FontRes { font : font }
    );
  }
  pub fn font(&self) -> FontHandle {
    self.font.clone()
  }
}

這部分跟載入 sprite 沒有差很多,就不贅述;另外我們再實作一個 ScoreRes,用來儲存目前的分數跟儲存 UI 文字的 Entity。
pub struct ScoreRes {
  pub score: i32,
  pub text: Entity,
}

impl ScoreRes {
  pub fn initialize(world: &mut World) {
    let font = world.read_resource::<FontRes>().font();
    let score_transform = UiTransform::new(
      "score".to_string(), Anchor::TopRight, Anchor::MiddleLeft,
      -220., -60., 1., 200., 50.);
    let text = world
      .create_entity()
      .with(score_transform)
      .with(UiText::new(font, "0".to_string(), [0.,0.,0.,1.], 50.))
      .build();

    world.insert(ScoreRes {
      score: 0,
      text: text
    });
  }
}

簡單來說 UI 的文字,自然也是一個 entity,裡面有兩個 components 分別是位移 UiTransform 跟 UiText;UiTransform 會需要幾個參數:
  • id :幫助辨識是哪個 Ui 元件。
  • anchor、pivot:Ui 元素位在 parent 的哪個方位、位在自己的哪個方位,可以用九宮格的方式來指定。
  • 後面五個數字則是指定 x, y, z, width, height。
這裡我們把顯示擊落數的文字定在畫面右上角,x y 的位移值剛好補償它的寬度跟高度。
UiText 需要帶入剛剛讀進來的字型,後面指定文字內容、顏色跟尺寸。

要修改文字的話,我們稍微修改一下先前實作的 Collision System,要新增存取 ScoreRes 這個 resource,另外記得上面所說 UI 文字也是 entity,文字的資訊是保存在 UiText 這個 Component 裡面,所以要改文字,我們一併要存取 UiText 這個 Component:
type SystemData = (
  WriteExpect<'s, ScoreRes>
  WriteStorage<'s, UiText>
)
fn run(&mut self,
          (mut scoretexts,
           mut uitext): Self::SystemData) {
  // change score
  scoretexts.score = scoretexts.score + 1;
  if let Some(text) = uitext.get_mut(scoretexts.text) {
    text.text = scoretexts.score.to_string()
  }
}
先直接修改 resource 裡面儲存的 score 的值,再來是用 Component uitext 去取出 resource 裡記錄的 text entity,這樣拿出來的就是這個 entity 所含的 UiText Component,這時候才能去修改它的 text 屬性。
上面這段 code 我放在處理碰撞的地方,只要有雷射砲跟小行星碰撞就會執行一次,真的要分得非常詳細,可以把這段移到獨立的系統中,比較不會亂掉。

你可能會問,這樣不就…讓 ScoreRes 這個 resource 的實作內容給暴露出來了,不能把 UiText 跟 score 等等的好好好封裝到一個 struct 裡面,並公開介面如 setText 讓外部使用嗎?沒錯當初我也是想要這樣設計,只不過到目前為止都沒有成功過 (._.),目前只能將就一下用這樣難看的寫法,畢竟連官方的範例都是這樣教……如果有大大知道的話也請不吝賜教。