アナログ金木犀

つれづれなるまままにつれづれする

2017年を振り返る

こんばんは。釘宮です。

shirajiさんや muumuumuumuumuuさんがやってる振り返りテンプレートが楽そうだったので真似してみた。

楽そうと思ったけどツイカスすぎるが故にツイート数が多くてぶっちゃけ大変だった。

1月

2016年のSHIROBAKOアドベントカレンダーの打ち上げの柗亭新年会をセッティングした

twitter.com

がっつりセッティングして、さぁ明日と言う深夜にノロを発症しました。

twitter.com

twitter.com

と、散々なスタートでした。

2月

lottieに感動する

twitter.com

ブログにもまとめました。

motida-japan.hatenablog.com

バスケの大会に出た

長いので数少ない自分のゴールシーンだけまとめました。長いので...

中高でバスケをしていて、それ以降やっていなかったので、10年以上ぶり...。

3月

DroidKaigi 2017

DroidKaigi2017に登壇してきた。スパラクーアに泊まってDroidKaigiアプリをガーーーッと仕上げたり開発合宿感もあって楽しかった。

motida-japan.hatenablog.com

4月

いろいろあったが柗亭で癒される

ツイートが少ない月。役職が変わったりでバタバタしていた。

konifar-zatsu.hatenadiary.jp

5月

ツイートが少ない月。きっとリリースも近くなってきて大変だったのであろう。

いろいろあったが晩杯屋で乗り切る

twitter.com

6月

6月前半はまだ平和だったんだ。

twitter.com

twitter.com

7月

デスマーチ

近年稀に見るデスマでシャバ*1の世界から姿を消す。

twitter.com

twitter.com

8月

シャバの世界へ

ようやく解放され久しぶりのシャバの世界に困惑する。

twitter.com

Oisixで副業始めた

creators.oisix.co.jp

9月

一人旅してきた

motida-japan.hatenablog.com

10月

スタディサプリENGLISHの裏側を大公開した

www.recruit-mp.co.jp

tech.recruit-mp.co.jp

11月

温泉旅行

湯河原へ旅行に行ってきた。

海辺の途中 というレストランのディナーに行ったのだが量も十分多いし美味しいしでめっちゃ満足した。その日に忘れ物をしてしまったので、翌日のランチもここでお世話になった。

店内から見える景色が綺麗だった。ついでに店員さんに言うと屋上に通してもらえたのだが、それはもういい景色だわ風が気持ちいいわで最高だった。

DroidKaigi採択

出していたCfPが無事通った。資料作り頑張ります。

12月

SHIROBAKO Advent Calendarを完走できた

SHIROBAKO Advent Calendar 2017 - Adventar を今年も完走できた。これで三年連続完走したことになる。

来年が本当に愛を試される年だと思うので来年もなんなら一人で10日以上分書くくらいの勢いでしっかり書きたい。

来年

来年書きます!!!

*1:ジャバではありません

2017年買ってよかったもの、行ってよかったところなど

素直に 買ってよかったもの としたかったのですが、そもそも物欲がないのであまり買ったものの記憶がないのでちょっと範囲を広げてみました。

スコープを広げても少ないですが、でもオススメです。

Nine Parchments

Switch用の複数プレイ専用ゲームです。

www.nintendo.com

www.youtube.com

古き良きアーケードゲームあるいはスーファミでよくあったような、魔法をつかい敵を倒しながらステージをクリアしていく系のゲームです。途中でセーブはできません。懐かしいですよねこの感じ。

魔法で攻撃していくのですが、その魔法が味方に当たった時にしっかりダメージ判定されるのがこのゲームのポイントでしょう。

彼女が隙をみては「Nine Parchmentsしよ〜」と言ってくるほどめずらしく大変気に入っているようでして、まぁそれがなんか嬉しいのでオススメです。

方向は変えられるけど位置をうごかせなかったりなどのバグが発生するのがたまに傷です。ただそれでも他のソフト(6000~8000円)に対して半額くらいの値段(2440円)なのでコスパは高いです。

ドラクエ11

ドラクエ11です。

ぶっちゃけ「ドラクエシリーズはそれなりにやったことあって、またゼロからレベル上げとかだるいし、どうなんだろうなぁ楽しいのかなぁ?でもまぁやってみようかなぁ」みたいな期待値低めのテンションで始めたのですが、完全にいい意味で裏切られました。

一言で言うと幼女を救うお話 (人によって諸説あります) でした。最高でした。

深山桜庵への宿泊

旅日記的なものは下記に以前書きました。

motida-japan.hatenablog.com

いくつかここ以外にも宿泊はしたのですが、疲れてて世間と一線ひきたくて、ひたすら風呂入ったり寝たり風呂入ったり、うまいメシを食いたいだけの人には超オススメです。

海にいくと開けた空気が漂っておりしっかり自分に向き合う感じは感じれなかったので、何かモヤモヤしてる人の一人旅は山が向いてる気がします。

家計簿アプリ・家計簿ソフト「マネーフォワード」

以前は他のものを使っていたのですが、マネーフォワードで管理するようにしました。

moneyforward.com

例えばATMから2万円引き出すと、家計簿の「現金・カード」のところに2万円と表示され、そのあとに飲み会とかで5000円現金で払ったので交際費で登録すると、自然と 「現金・カード」の方は1万5000円になり感動しました。

基本的にカード払いしてれば手動での入力は必要ないし、手動でする部分に関しても財布にいくら入ってるかとかをいい感じにしてくれるのが大変ありがたいです。

L字デスク

L字デスクかっこいいです。

item.rakuten.co.jp

あぐらチェア

気持ちよく、あぐらがかけます。

item.rakuten.co.jp

柗亭

毎月一回は行っております。ホームです。お約束の柗亭です。しのがブログに以前書いてくれてますので詳細は下記から。

shanonim.hatenablog.com

なんか思い出したらどんどん追加していきます。

天才アニメーター?久乃木愛

こんばんは。釘宮です。

この記事はSHIROBAKOアドベントカレンダー24日目 の記事です。

今回は新人アニメーター久乃木愛にフォーカスを当て、彼女の魅力を紹介したいと思います。

久乃木愛の役割

SHIROBAKOにはリアルっぽく描かれている(おそらくモデルがいる人)キャラクターとアニメっぽく描かれてるキャラクターがいます。

キャラクターデザインを担当した関口さん曰くこの二種類のキャラクターをうまく同じ絵にあてはめるに困難したといったような旨をどこかのインタビューで言っておりました。

今回フォーカスする久乃木愛は後者です。後者のキャラクターにはいわゆる名前のないモブキャラも含まれてますが、彼女にはしっかりと名前が与えらています。 前半には一回きりしかも静止画でのみの登場だった彼女が後半がっつりと存在感をあらわにしてきたのはなぜでしょうか。 アリアに希望をもたらすためにルーシーというキャラクターが生まれたように、そこには彼女のキャラクターとして役割があったと思うのです。今回はそこを邪推してみようと思います。

その前に軽く紹介

まずは彼女について紹介していきましょう。

外見的には、SHIROBAKOでは数少ないアホ毛の持ち主です。感情に従ってアホ毛が動きます。

彼女が初めて登場したのは第十二話 〈えくそだす・クリスマス〉でみんなが忘年会に行っているシーンでした。堂本さんと会話しているであろう静止画、声もなくBGMオンリーな初登場でした。

そして15話の 〈こんな絵でいいんですか〉にて初めのて作打ちで絵麻が付き添っているところからはしっかり声も発しており、以降は頻繁に登場するようになります。

"初めての作打ち" という言葉からもわかるように、 彼女は第三飛行少女隊から原画マンになりましたエクソダス時代では動画をやっております。

彼女の一番特徴的はやっぱり そのコミュ症っぷり でしょう。「あ!」や「う!」や「さっがっ!」など一文字二文字でしか言葉を発せず、視聴者によっては苦手な人もいたようです。

また設定資料集では 髪の毛を母親に切ってもらっている。 という一文が添えられています。おそらく実家暮らしなのでしょう。

久乃木愛の役割

前半の役割

彼女が登場してきてから初めの役割は「絵麻の成長の象徴」なのではないかと考えています。

去年のAdvent Calendarで述べてますが、前半12話での絵麻の焦りは尋常ではないです。

motida-japan.hatenablog.com

父親との期限付きの約束。そしてその期限を超えて数年経っても未だに食べていけると確信できない。そう言った中でのリテイク。

それもなんとか周りの協力や彼女自身の実力と成長のおかげで乗り越えることができました。

ここで考えて見ましょう。 もし久乃木愛がエクソダスの頃から原画マンだったら

絵麻に久乃木愛の面倒をみる余裕があったでしょうか?

後輩の悩みに応える時間があったでしょうか?

後輩の作打ちに一緒に付きそっていたでしょうか?

食べていけるかどうかの瀬戸際で切り詰めて生活している彼女が、実家でぬくぬく暮らしていてコミュニケーションも取れない後輩の面倒をみる余裕なんてなかったはずです。もし面倒を見れたとしても、相当のストレスになったでしょう。

だからこそ 久乃木愛 は後半からの登場なのだと、自分は邪推します。

成長した後であるからこそ、久乃木愛の面倒を問題なくみることができる 。彼女がいることによって絵麻ちゃんのその成長っぷり、余裕っぷりが際立っていくんです。

絵のことについて、「人に教える余裕」もでてきました。

(天使の笑顔かよ...)

後半の役割

後半からは絵麻の手から離れていく自走するシーンが目立ちました。後半以降の彼女のアニメの中での役割は、宮森の最終回の言葉を体現するものではないかと考えています。

「細いロウソクの火みたいなかもしれないけれど、その小さな火が次々に受け継がれて、永遠に消えることのない炎となって世界を照らすものじゃないかって」

杉江さんから小笠原さん、小笠原さんから井口さん、井口さんから絵麻、そして絵麻から久乃木愛へ。

絵麻が井口さんからもらい、絵麻が久乃木愛に渡す という 、バトンを絵麻が回していく様を表したかったのではないでしょうか。

それを表現するためには久乃木愛自身がただ頼るだけの後輩のままアニメが終わってしまってはダメで、しっかり手離れして成長していく必要があったはずです。

まさに、最後の方は久乃木愛自身の勉強熱心さ、そして彼女の成長にフォーカスされます。

初めの作打ちでは「あ」や「う」しか話すことができなかった彼女が、初めて一人で行った作打ちでは「これは下着...ですか?」という名言をたたきこみ、またその次の作打ちでは「風速何メートルですか?」となんだか絵に必要そうな良い質問をしています。

一番象徴的なシーン

最終話〈遠すぎた納品〉での一幕でした。

「このカット、誰?」

監督や脚本が唸るほどカットを書いたのは我らが久乃木愛でした。

ここなにがすごいかというと 動物のカット なんです。

井口さんにとってのもぐら、絵麻ちゃんにとっての猫と、 この師弟筋にとって初めての動物は鬼門 のように描かれてました。

(もしかしたら作中描かれてないだけで初めてでないかもですが、とはいえ第三飛行書状態で動物カットはそうそうないはず。)

その鬼門を物ともせず、むしろ監督陣営を唸らせるほどのクオリティ。 天才 なのかもしれません。

このシーンは普通に考えたら、ずかちゃんがようやく抜擢されて、そのシーンを友達の絵麻が他にも十分に作業があるにもかかわらず自ら立候補してルーシーのシーンを書いたという流れだったので、絵麻がクオリティ高いものを書いて監督陣営から絶賛されるというのが自然な流れじゃないですか?それなのにその流れをぶった切って久乃木愛は全てを持って行くわけです。最高ですね。

考えてみると、久乃木愛は絵麻と対比的な設定が多くあります。〈実家暮らし〉と〈つめつめな一人暮らし〉、〈初めての動物シーンで監督陣営に大絶賛〉と〈初めてのシーンで作監から「これはない」と言われる〉などなど...

そんな対比を物ともせずに、多少の嫉妬くらいあってもいいはずなのに

「褒められてるよ、久乃木さん」

と、我が子のように接する絵麻の成長っぷりよ。

その二つ〈絵麻の成長〉〈久乃木愛の成長でなりたつ次の世代への伝授〉がしっかり表現されたいいシーンでした。

まとめ

もしかしたらこれから先に、鬼門をものともしなかった久乃木愛ですし〈下からどんどん成長する久乃木愛によって焦る絵麻〉とう未来がありえそうです。

しかし、それでも〈久乃木愛が絵で迷い、例の公園に絵麻が連れていく〉シーンを想像しながらニヤニヤしておきます。

長文にお付き合いいただきありがとうございました。

Rxなインスタンスを返却するメソッドを実装する時の注意点

そういえばよくレビューで指摘してたなぁと思い出したのでメモ書き。

例えばこういうコード。

fun findContent(): Maybe<Content> {
    val connectivityManager = application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    return if (connectivityManager.activeNetworkInfo.isConnected) {
        apiRepository.find()
    } else {
        localRepository.find()
    }
}

Content を取得したい時に、オンラインだったら apiRepository を使って、オフラインだったら localRepository を使うというコード。

このコードにはいくつか問題があって、 as じゃなくて as? を使った方が安全とかは間違いなくそうなんだけど、それよりも オンラインかオフラインか の判定ロジックが Rxな世界に包まれていないところが問題だと思っている。

オンラインかオフラインか の場所で Throwable が投げられる可能性がある場合、 これを使っている側がしっかり onError を実装していたとしてもで落ちてしまう。それも、悪質なのは subscribe している箇所ではなく findContent を呼んでいる箇所で、だ。 いうまでもなく subscribe されなくてもこの判定ロジックは処理されてしまう

もし Throwable が投げられる可能性がないとしても、判定に時間がかかったりすることもあり、想定と違うものになりかねない。

なので、最終的に返すもの以外のロジックすべてをちゃんとRxな世界で包んであげましょう

自分の場合だと下記みたいに書くと思う。

fun findContent(): Maybe<Content> =
    isConnected().flatMapMaybe { isConnected ->
                    if (isConnected) {
                        apiRepository.find()
                    } else {
                        localRepository.find()
                    }
                }

private fun isConnected(): Single<Boolean> = Single.create { emitter ->
    val connectivityManager = application.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
    if (connectivityManager != null) {
        emitter.onSuccess(connectivityManager.activeNetworkInfo.isConnected)
    } else {
        emitter.onError(RuntimeException("cannot get connectivity"))
    }
}

Android Gradle Plugin3系におけるマルチモジュール時のconfigurationの解決について

こんにちは。釘宮です。

マルチモジュールなプロジェクトをやって行く上で、以前は下記のような記述をよく書いていました。

devCompile project(path: 'library', configuration: 'dev')

Android Gradle Plugin3系(gradle4系)から compile(など)ではなく implementation(など)を使っていきましょうともろもろ変わりました。

なので、単純に下記のように implementation と書き直してみます。

devImplementation project(path: 'library', configuration: 'dev')

これでビルドをして見ると、がっつりエラーが出てきます

解決方法などをまとめたので今回記事として見ました。

ここに書いていあることは 公式ページ に書いてあることの一部 + αでdす。

結論から言うと implementation project('library') で良い (条件付き)

app/build.gradle library/build.gradle
android {
  :
  flavorDimensions 'api'
  productFlavors {
    dev {
      dimension 'api'
    }
    stg {
      dimension 'api'
    }
    prod {
      dimension 'api'
    }
  }
  :
}
dependencies {
  :
  implementation project(":library")
  :
}
android {
  :
  flavorDimensions 'api'
  productFlavors {
    dev {
      dimension 'api'
    }
    stg {
      dimension 'api'
    }
    prod {
      dimension 'api'
    }
  }
  :
}

このようになっている場合、appではlibraryで同じ名前のflavorのものを選択してbuildされます。

いままでは

devCompile project(path: ':library', configuration: 'dev')
stgCompile project(path: ':library', configuration: 'stg')
prodCompile project(path: ':library', configuration: 'prod')

などと書いてましたが、下記一行で済むようになりました。便利ですね。

implementation project(":library")

ただこれに関しては app側にある api-dimension に所属する dev, stg, prod をlibrary側も同じものを持っているという条件を満たしている時に限ります。

そのため、library側が余計に dev1 というflavorを持っていても使われないだけでビルドは問題なく通ります。

以下から簡略化のため、 api-dimensionに所属する dev, stg, prod のflavorがある 状態を [api => (dev, stg, prod)] と書くようにしますね。

例1. appでは[api => (dev, stg, qa, prod)]、libraryでは[ api => (dev, stg, prod)]となっている時

app側に qa があるけど library側にそれがない場合です。

appの qa 時には library では stg を選択したいとします。

こう言う時は matchingFallbacks を使用します。

app/build.gradle library/build.gradle
android {
  :
  flavorDimensions 'api'
  productFlavors {
    dev {
      dimension 'api'
    }
    stg {
      dimension 'api'
    }
    qa {
      dimension 'api'
      matchingFallbacks = ["stg"]
    }
    prod {
      dimension 'api'
    }
  }
  :
}
dependencies {
  :
  implementation project(":library")
  :
}
android {
  :
  flavorDimensions 'api'
  productFlavors {
    dev {
      dimension 'api'
    }
    stg {
      dimension 'api'
    }
    prod {
      dimension 'api'
    }
  }
  :
}

依存先に対象flavorが見つからない場合に matchingFallbacks によって優先順位を指定できます。 上記の例では stg のみ指定しいます。 (なお、 flavor dimention が一致していないと、matchingFallbacks に指定しても動きません)。 加えてこのmatchingFallbacks は下記のようにbuildTypeでも同様の使い方ができます。

app/build.gradle library/build.gradle
android {
  :
  buildTypes {
    debug {
    }
    stg {
    }
    qa {
      matchingFallbacks = ["stg"]
    }
    release {
    }
  }
  :
}
dependencies {
  :
  implementation project(":library")
  :
}
android {
  :
  buildTypes {
    debug {
    }
    stg {
    }
    release {
    }
  }
  :
}

例2. library側に知らないdimensionがある時

そもそもdimensionとは

あえて触れてこなかったんですが flavorDimensions 'api' のところの話です。

Android Gradle Plugin3.0.0より前ではなかったものです。 いままでもありましたが、3系からは指定が必須になったようです。

いままでは productFlavors とはフラットなものでして、あるflavorと他のflavorを組み合わせるということはできませんでした。これを可能にするのが flavorDimension です。

言葉だと難しいので実際に設定して見ましょう。

productFlavors {
    buildTypes {
      debug
      release
    }
    flavorDimensions 'api', 'persistence'
    dev {
        dimension 'api'
    }
    stg {
        dimension 'api'
    }
    prod {
        dimension 'api'
    }
    local {
        dimension 'persistence'
    }
    cloud {
        dimension 'persistence'
    }
}

flavorDimension を指定しない場合、 buildVariant は (dev, stg, prod, local, cloud) × (debug, release) の 5×2 = 10通りのconfigurationがとなっていました。

これが、 現状は上記のようにdimensionを使い分けることで (dev, stg, prod) × (local, cloud) × (debug, release) の 3×2×2 = 12通りconfigurationが作られるようになったと言うことです。

何が便利かと言うと、観点ごとにコードを分けれることでしょうか。上記の場合だと api は (dev, stg, prod) のどの環境を使うか、永続化(persistence)は ローカルなのかクラウドなのかという例になっています。

ざっくりここまでがdimensionの説明です。

appは [api => (dev, stg, prod)]、 libraryが [api => (dev, stg, prod), persistence => (local, cloud)] の時

appの知らない persistence dimension がlibraryにある場合を考えます。

appで、例えば stg をbuildしたい時に、 library側では stg と言っても stgLocalstgCloud の組み合わせがあるのでどっちを使えば良いのかわからないと言う状況です。;

この場合は missingDimensionStrategy を使用します

missingDimensionStrategy (dimension名) (flavor名), (flavor名),...

と言うふうに 知らないdimensionがきた時に、どのflavorを優先的に採用するかということを決めることができます。

missingDimensionStrategy 'persistence' 'local' 'cloud'

この場合は persistence dimensionがきたら local, cloud の順で採用すると言うことになります。

これを踏まえると下記のようになりました。

app/build.gradle library/build.gradle
android {
  :
  defaultConfig {
    :
    missingDimensionStrategy 'persistence', 'local', 'cloud'
    :
  }
  flavorDimensions 'api'
  productFlavors {
      dev {
          dimension 'api'
          missingDimensionStrategy 'persistence', 'local'
      }
      stg {
          dimension 'api'
          missingDimensionStrategy 'persistence', 'cloud'
      }
      prod {
          dimension 'api'
          missingDimensionStrategy 'persistence', 'cloud'
      }
  }
  :
}
dependencies {
  :
  implementation project(":library")
  :
}
android {
  :
  flavorDimensions 'api', 'persistence'
  productFlavors {
      dev {
          dimension 'api'
      }
      stg {
          dimension 'api'
      }
      prod {
          dimension 'api'
      }
      local {
          dimension 'persistence'
      }
      cloud {
          dimension 'persistence'
      }
  }
  :
}

上記のように各Flavorごとに missingDimensionStrategy を設定することができます。

まとめ

これからは configuration の解決は dependencies のところではなく flavor名、 matchingFallbacksmissingDimensionStrategy で頑張りましょう。