こんにちは、この記事はGoodpatchのエンジニア※がお送りするGoodpatch Advent Calendar 2015の22日目の記事です。
先日はAndroidエンジニアの @u-16 MaterialDesignと戯れてみて思ったこととか でした。
私はProttでプロジェクトマネージャーをやっています。今回は、プログラミング未経験でPebble TimeのWatchfaceを開発してみた話を非エンジニア枠でひっそり書いてみようと思います。
#Pebble Timeって?
Pebble TimeはスマートウォッチのひとつでApple WatchやAndroid Wearと違う点は
- OS:独自のPebble OS
- ペアリング:AndroidとiOSの両方とペアリングが可能!
- 操作方法:タップではなく、左右についているボタンのみ
です。他、Pebble Time Roundはびっくりするくらい薄くて、普通の時計をつけている感覚でつけていられます。
アニメーションがいい感じで使っていてすごい楽しいです。Watchfaceはおそらく非公式であろうキャラクターものや、アナログ、デジタル共にたくさんあります。
Pebble Time for Androidはこんなかんじ。
#Watchfaceを作ってみる
##まずはじめに
##開発環境
Cloud Pebbleという、オンラインの開発環境で今回は開発をしました。
##言語
基本的にC言語での開発になりますが、Simply.jsを利用することで Pebble.jsを利用することで JavaScript のみの開発もできるとのこと。
英語ですが、公式できちんとドキュメントを用意してくれています。
##画面サイズとか
Pebble Timeはシリーズで現在までで、3種類出てるので画面サイズなどはそれぞれ、下記で用意が必要です。
Pebble内では上記の色のみ使用可能なので、デザインするときは上記を参考にしながら作ります。
デザインガイドラインも用意してあります。
上記に目を通してみて、初めてのプログラミングは、まず簡単にこんな感じに作ってみよう!っていうことで開発をスタート。
#作ってみた
とりあえずチュートリアルとか、サンプルコードを引っ張ってきて、理解しながらやってみました。初めてのプログラミングでも、コードをちゃんと読めば理解できるので、いいかんじ。
#include <pebble.h>
static Window *s_main_window;
static GBitmap *s_bitmap;
static BitmapLayer *s_bitmap_layer;
static TextLayer *s_time_layer, *s_date_layer;
static void update_time() {
// Get a tm structure
time_t temp = time(NULL);
struct tm *tick_time = localtime(&temp);
// Write the current hours and minutes into a buffer
static char s_buffer[8];
strftime(s_buffer, sizeof(s_buffer), clock_is_24h_style() ?
"%H:%M" : "%I:%M", tick_time);
// Display this time on the TextLayer
text_layer_set_text(s_time_layer, s_buffer);
// Copy date into buffer from tm structure
static char date_buffer[16];
strftime(date_buffer, sizeof(date_buffer), "%b %d (%a)", tick_time);
// Show the date
text_layer_set_text(s_date_layer, date_buffer);
}
static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
update_time();
}
static void main_window_load(Window *window) {
// Get information about the Window
Layer *window_layer = window_get_root_layer(window);
GRect bounds = layer_get_bounds(window_layer);
// Create the TextLayer with specific bounds
s_time_layer = text_layer_create(
GRect(0, PBL_IF_ROUND_ELSE(48, 52), bounds.size.w, 50));
// Create time TextLayer
text_layer_set_background_color(s_time_layer, GColorClear);
text_layer_set_text_color(s_time_layer, GColorBlack);
text_layer_set_text(s_time_layer, "00:00");
text_layer_set_font(s_time_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_MEDIUM_NUMBERS));
text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter);
// Add it as a child layer to the Window's root layer
layer_add_child(window_layer, text_layer_get_layer(s_time_layer));
// Create date TextLayer
s_date_layer = text_layer_create(GRect(0, 93, 180, 40));
text_layer_set_text_color(s_date_layer, GColorBlack);
text_layer_set_background_color(s_date_layer, GColorClear);
text_layer_set_font(s_date_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD));
text_layer_set_text_alignment(s_date_layer, GTextAlignmentCenter);
// Add to Window
layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_date_layer));
s_bitmap = gbitmap_create_with_resource(RESOURCE_ID_DOG);
s_bitmap_layer = bitmap_layer_create(bounds);
bitmap_layer_set_bitmap(s_bitmap_layer, s_bitmap);
bitmap_layer_set_compositing_mode(s_bitmap_layer, GCompOpSet);
layer_add_child(window_layer, bitmap_layer_get_layer(s_bitmap_layer));
}
static void main_window_unload(Window *window) {
// Destroy TextLayer
text_layer_destroy(s_time_layer);
bitmap_layer_destroy(s_bitmap_layer);
gbitmap_destroy(s_bitmap);
}
static void init() {
// Create main Window element and assign to pointer
s_main_window = window_create();
// Set handlers to manage the elements inside the Window
window_set_window_handlers(s_main_window, (WindowHandlers) {
.load = main_window_load,
.unload = main_window_unload
});
// Show the Window on the watch, with animated=true
window_stack_push(s_main_window, true);
// Make sure the time is displayed from the start
update_time();
// Register with TickTimerService
tick_timer_service_subscribe(MINUTE_UNIT, tick_handler);
}
static void deinit() {
// Destroy Window
window_destroy(s_main_window);
}
int main(void) {
init();
app_event_loop();
deinit();
}
基礎もなにも勉強してないので、最初によくわからなかったポイントは表示位置の変更ルールでした。
日にちを表示させる
s_date_layer = text_layer_create(GRect(0, 93, 180, 40));
ここの(GRect(0, 93, 180, 40));
数字を動かしてあれやこれややってみて、表示したい位置にしてみたけど本当は
(x,y,w,h)
できちんと計算してやらなければいけないとのこと。
##画像のアップロード
RESOURCESのADD NEWからファイルがアップロードできます。
IDENTIFITERでコード内で指定する名前に設定。
##フォント
システムフォントが何種類か用意されてます。もちろん、カスタムフォントも使えます。今回はシステムフォントを使っています。
##動作確認
実機でもエミュレーターでもできます。COMPILATIONからどっちで表示させるか変更する。
右上の緑の再生ボタンを押すと確認できます。こんな感じ。
###Pebble実機での確認
Pebble TimeアプリをDeveloper Connectionで接続すると、確認できるようになります
いぬ書いて時計にしてみたよ #pebbletimeround pic.twitter.com/CswTnz3ozy
— mari okada (@px1717) 2015, 12月 21
作ったものが実機ですぐ確認できます。最初はベースの色を黒ではなく、グレーにしてたんだけどすごく薄く見えたので、黒に変更したりしました。
##とりあえず完成!
一旦、上記の流れで
Watchface内で画像を表示させる、時間を表示させる、日にちを表示させるということをやってみました。
全部含めて、2時間もかかりませんでした!
#もっと動きがあるもの作りたい...
Pebble Watchfaceを見ていると、動きがあってかわいいものが多い。ということで、やってみました。
##Animation png
Pebbleはgif animationが使えないので、apngを作ります。使ったサービスは下記。
- TinyPNG:ファイルサイズを落とす
- APNG Assembler 2.9:apngを作る
普通のpng表示と違うコードが必要になります。
##あれ...動かない。
いろいろやってみたけど、apngまわりでとっても苦戦しました。下記を何度も確認して、やっと動くようになりました...
##Raw binary blobでアップロードしているかどうか
ファイルアップロードする際に、pngとか他のものでアップロードすると動いてくれません。
###ファイルサイズが大きすぎていないか
あまり大きすぎると、Pebble上で動いてくれません。20KB以下くらいがよさそう。10KB以下がベスト。
###コードをみてみる
Pebble公式のANIMATED PNGS 内のコードで動かしてみたけど、動かず。
HOW-TO replace with your own animated .gif
ここを見てやってみたら、動くようになりました。(まだちゃんと確認できていないけど)
###一応動いた!
こんなかんじ。だけど、なぜか実機で確認ができない...謎です。まだ解決できていません。。
#一通りやってみて
たのしい。だけど、ちょっと難しいことをやろうとすると全然理解できないので、もっと精進します...
一応簡単なWatchfaceを作れるようになったので、もうちょっとブラッシュアップして、アプリのストアに出してみようと思います!
ŧ‹"ŧ‹"ŧ‹"ŧ‹"(๑´ㅂ`๑)ŧ‹"ŧ‹"ŧ‹"ŧ‹"
明日は @migi さんです。おたのしみに!
#追記:2016/04/21
ひっそり、初めてWatchfaceリリースしてみました。(上記のものとは違うけど) Appstoreへのアップ方法など追ってまとめたいと思います!