20201206のAndroidに関する記事は18件です。

写真とUIの意外な共通点 

1年ぶりです、Classi Advent Calendar 2020 7日目は、デザイナーの@shio312が毎年のいつもの記事とはちょっと違った味でお送りします。

はじめに

去年度から今年度にかけて、他部署の方に
「デザイナーの頭の中身ってどうなってるの?」とか、
「特にUIのような画面を起こす人君たち(特に私)の頭はどんな構成なんじゃ?」
とよく訊かれました。

と言うわけで、UIデザイナーの頭をかち割って中身を見せることはできないので、今年は自分はこんなふうにUIに紐づけて世界を見ていますよ、という実例を紹介しようと思います。

写真で。

「エッ?関係あんの?」
と思われるかもしれませんが、趣味として嗜む方も多い写真を用いてお話しする事で、少しでも画面デザイナーに共感と親近感を覚えていただけると嬉しいなと思い、ちょっとした例えでお話ししてみようと思います。

ちなみに、デザイナー皆が皆そうではないことだけは事前にご理解の上、拝読くださいね。

4枚の写真を用いた実例紹介するよ

さてここに私自身が撮影した4枚の写真があります。
----A----
ファイル名
----B----
ファイル名
----C----
ファイル名
----D----
ファイル名

母と京都の南禅寺に行った際に撮った写真たちです。いや〜良い紅葉でした?

うん、写真を通じて友人に古都自慢したいな!

よし、今から記載の写真のどれが一番"秋の古都の魅力"が伝わるか考えたいと思います。

とすると、1枚だけで秋の古都の魅力を伝えるという目的を達成するための最低要件
以下3点が最低情報として写真に収まっていると分かりやすいと考えます

要件
1. 画面に収まる建築物によって(おおよそ)直感でここは古い街だと言うことが分かること
2. 画面に収まり赤や黄色の紅葉が秋だと直感で分かること
3. 感情的に秋の終わりらしい古都の趣きが写真を通じて見る人に伝わること

A〜Dの写真の考察

----A----
紅葉がいっぱいある、秋だなきれいだな!けれど、場所がわからんしどこだよ此処
----B----
なんか古そうな建物もあるし紅葉もあるな、写真としても画角も良いしカッコええんじゃないか?
ただ天気良すぎて視線がそっち行くな...古都らしい雰囲気はCより下か?
----C----
Bほど写真としてのカッコよさはないけど、奥に古い建物(南禅寺の三門)があるし紅葉もあるな?
----D----
紅葉の絨毯が綺麗だけど、古木の渋さを伝えたいわけなじゃいんだ、目的がちがうぞ!

となります。
BとCが目的達成に近そうですね。
ここで写真A〜Dが用件をどれだけクリアしているか星取表を作ってみましょう。

必要用件 要件1 要件2 要件3
A × ×
B
C
D × ×

はい、こうなりました。
画としてはBの方が奇をてらった面白い画ですが、要件の達成度としてはCに軍配が上がりました。

マジか〜(本音)

此処で思い出してみましょう。
Bの写真ですが、「ぱっと見イケてるサイト/アプリなんだけど、あともうちょい分かりやすいUIだったらな〜ファストビュー内にフォームとボタンがあったらな〜(使い勝手の要件が満たされていない)」
と同じ状態というわけです。

Cの写真は素人丸出しかもしれません。正直写真としては品質が微妙です。
けれど、自分の目的における要件を達成しているのはCということになります。

このあとも納得いく写真を求めて沢山撮りました。
先ほど結論づけた結果から、目的達成のための必要条件をクリアしながらも、更に画として目に入る情報の順番が(無理やり言い換えると情報設計ですね)より分かりやすい、そして品質の高い画を作るために私は写真のファインダーを絞っていきました。
目的が達成された品質の高い写真を見た友人との楽しい会話を想像しながら。

と言った感じで私はUIを「使う人がどうやって何ができるか、そして結果笑顔が生まれるか」を考えて画面に落とし込んでいっています。

こんな感じでした、ジャンジャン。

写真や近代絵画などでも「何を伝えたがっているのか」を意識して見てみると、今まで思いもしなかった視点に気がつくかもしれません。
そんなの当たり前だよ〜と思う方が多いかもしれませんが、ぜひ改めて試してみてはいかがでしょうか。
機材で表現を広げるのも手ですが、同じ機材でどこまで伝え方の幅を広げるか工夫を凝らすのも中々楽しいもんです。

モノや絵画、写真、空間においても存在している、人と人とのコミュニケーションを産み、つなぐインターフェース。
そう考えると、この仕事の可能性とそれに携わることができている自分が誇らしいなあと思えます。

これを読んだ方の物づくりの楽しさの視野が、ほんのちょっと広がると私は嬉しいです。
それでは、良いお年を。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

写真とUIデザインの意外な共通点 

1年ぶりです、Classi Advent Calendar 2020 7日目は、デザイナーの@shio312が毎年のいつもの記事とはちょっと違った味でお送りします。

はじめに

去年度から今年度にかけて、他部署の方に
「デザイナーの頭の中身ってどうなってるの?」とか、
「特にUIのような画面を起こす人君たち(特に私)の頭はどんな構成なんじゃ?」
とよく訊かれました。

と言うわけで、UIデザイナーの頭をかち割って中身を見せることはできないので、今年は自分はこんなふうにUIに紐づけて世界を見ていますよ、という実例を紹介しようと思います。

写真で。

「エッ?関係あんの?」
と思われるかもしれませんが、趣味として嗜む方も多い写真を例にお話しする事で、少しでも画面デザイナーに共感と親近感を覚えていただけると嬉しいなと思い、挑戦としてお話ししてみようと思います。

ちなみに、UI関連に携わるデザイナー皆が皆そうではないことだけは事前にご理解の上、拝読くださいね。

4枚の写真を用いた実例紹介するよ

さてここに私自身が撮影した4枚の写真があります。
----A----
ファイル名
----B----
ファイル名
----C----
ファイル名
----D----
ファイル名

母と京都の南禅寺に行った際に撮った写真たちです。いや〜良い紅葉でした?

うん、写真を通じて友人に古都自慢したいな!

よし、今から記載の写真のどれが一番"秋の古都の魅力"が伝わるか考えたいと思います。

とすると、1枚だけで秋の古都の魅力を伝えるという目的を達成するための最低要件
以下3点が最低情報として写真に収まっていると分かりやすいと考えます

要件
1. 画面に収まる建築物によって(おおよそ)直感でここは古い街だと言うことが分かること
2. 画面に収まり赤や黄色の紅葉が秋だと直感で分かること
3. 感情的に秋の終わりらしい古都の趣きが写真を通じて見る人に伝わること

A〜Dの写真の考察

----A----
紅葉がいっぱいある、秋だなきれいだな!けれど、場所がわからんしどこだよ此処
----B----
なんか古そうな建物もあるし紅葉もあるな、写真としても画角も良いしカッコええんじゃないか?
ただ天気良すぎて視線がそっち行くな...古都らしい雰囲気はCより下か?
----C----
Bほど写真としてのカッコよさはないけど、奥に古い建物(南禅寺の三門)があるし紅葉もあるな?
----D----
紅葉の絨毯が綺麗だけど、古木の渋さを伝えたいわけなじゃいんだ、目的がちがうぞ!

となります。
BとCが目的達成に近そうですね。
ここで写真A〜Dが要件をどれだけクリアしているか星取表を作ってみましょう。

必要用件 要件1 要件2 要件3
A × ×
B
C
D × ×

はい、こうなりました。
画としてはBの方が奇をてらった面白い画ですが、要件の達成度としてはCに軍配が上がりました。

マジか〜(本音)

此処で思い出してみましょう。
Bの写真ですが、「ぱっと見イケてるサイト/アプリなんだけど、あともうちょい分かりやすいUIだったらな〜ファストビュー内にフォームとボタンがあったらな〜(使い勝手の要件が満たされていない)」
と同じ状態というわけです。

Cの写真は素人丸出しかもしれません。正直写真としては品質が微妙です。
けれど、自分の目的における要件を達成しているのはCということになります。

このあとも納得いく写真を求めて沢山撮りました。
先ほど結論づけた結果から、目的達成のための必要条件をクリアしながらも、更に画として目に入る情報の順番が(無理やり言い換えると情報設計ですね)より分かりやすい、そして品質の高い画を作るために私は写真のファインダーを絞っていきました。
目的が達成された品質の高い写真を見た友人との楽しい会話を想像しながら。

と言った感じで私はUIを「使う人がどうやって何ができるか、そして結果笑顔が生まれるか」を考えて画面に落とし込んでいっています。

こんな感じでした、ジャンジャン。

写真や近代絵画などでも「何を伝えたがっているのか」を意識して見てみると、今まで思いもしなかった視点に気がつくかもしれません。
そんなの当たり前だよ〜と思う方が多いかもしれませんが、ぜひ改めて試してみてはいかがでしょうか。
機材で表現を広げるのも手ですが、同じ機材でどこまで伝え方の幅を広げるか工夫を凝らすのも中々楽しいもんです。

モノや絵画、写真、空間においても存在している、人と人とのコミュニケーションを産み、つなぐインターフェース。
そう考えると、この仕事の可能性とそれに携わることができている自分が誇らしいなあと思えます。

これを読んだ方の物づくりの楽しさの視野が、ほんのちょっと広がると私は嬉しいです。
それでは、良いお年を。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Android]WorkManagerでユーザを待たせないUIを実現する

はじめに

仕事や日常で、ユーザが操作できない時間が長いアプリを触ることがある。気が短いので、操作できない区間が少しでもあるとイライラしてしまう。スマホアプリでは、安直にユーザ操作を禁止しないで欲しいという思いから、この記事を書く。

メール送信、掲示板の書き込み、つぶやきなどの投稿機能があるアプリを想像しながら読むと分かりやすいと思う。ユーザが操作できない時間が長いUI = ユーザを待たせるUIとする。

話が長いと感じるのであれば、「実践」だけ見れば良し。
後、Firestoreを使っていたり、ローカルDBの変化を監視する仕組みを使っているなら、WorkManager使わなくても同じことができるので、読む必要ないかも。

ユーザを待たせるUIとそうでないUI

ユーザを待たせるUI

具体例
1. (アプリ)投稿内容を入力する画面を表示する
2. (ユーザ)投稿内容を入力して、送信ボタンを押す
3. (アプリ)投稿処理を開始。ProgressDialog1などを表示して、ユーザが操作できないようにする
4. (アプリ)投稿処理が終わったら、ProgressDialogなどを閉じて、ユーザの操作を受け付けるようにする。投稿したものが見れる一覧画面を表示する。

やや古いアプリや意識の低いアプリだとありがちな作り。
フリーズよりはましだが、結局は操作できない時間がある。

投稿完了までの時間が短いなら、それほどストレスも感じないので良い。
投稿完了までに時間がかかる場合は、他の操作をしたくなるので、ユーザ操作を禁止するのはいまいち。

ユーザを待たせないUI

具体例

  1. (アプリ)投稿内容を入力する画面を表示する
  2. (ユーザ)投稿内容を入力して、送信ボタンを押す
  3. (アプリ)投稿処理を開始。投稿一覧画面を表示する。
  4. (ユーザ)他の投稿を眺めたり、新しく投稿内容を入力する
  5. (アプリ)投稿処理が終わったとき、必要であれば、画面を更新する。投稿一覧画面であれば、投稿一覧画面の再読み込みなどをする。

デモ画像を示す。

以下、このようなUIをWorkManagerで実現する方法を書く。

WorkManagerで実現するユーザを待たせないUI

WorkManagerとは

WorkManagerは、AndroidのJetPackの一種。アプリの画面のライフサイクルに紐付かないバックグラウンド処理をするのに使う。1回限りの処理、定期実行処理などができる。また、複数の処理をチェインさせたりも簡単にできる。

WorkManagerを使う理由

WorkManagerを使ったバックグラウンド処理(Workerと呼ぶ)は、実行されることが保証されている。OneShotのWorkerの実行中にアプリのプロセスをタスクキルすると、一旦はWorkerの処理は止まるが、しばらくすると、Workerは再実行される。

この性質は、確実に実行したい処理があり、処理中でもユーザを待たせたくない状況に向いている。

実践

簡単なNote投稿アプリを例に、ユーザを待たせないUIの実践例を示す。

Step1. WorkManagerの導入

app/build.gradle
implementation "androidx.work:work-runtime-ktx:2.4.0"

Step2. 投稿処理をWorkerのサブクラスとして実装する

class PostWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
    override fun doWork(): Result {
        // 開始要求時に指定されたパラメータを取得する(後述)。
        val title = inputData.getString("title") ?: ""
        val description = inputData.getString("desc") ?: ""

        // 投稿処理。
        // Workクラスは、applicationContextにアクセスできる。
        val noteRepository = NoteRepository(
            RemoteNoteService(applicationContext)
        )
        noteRepository.add(
            Note(
                title,
                description
            )
        )

        // 成功を返す。異常時はResult.failure()を返す。
        return Result.success()
    }
}


Step3. Workerの開始を要求する

// WorkManagerのインスタンス取得にはContextを必要とするので、AndroidViewModelを使う。
class NoteEditorViewModel(
    application: Application,
): AndroidViewModel(application) {
    val title = MutableLiveData("")
    val description = MutableLiveData("")
    private val workManager = WorkManager.getInstance(application)

    // 画面の"投稿"ボタンが押されたときに呼ぶ。
    fun post() {
        val data = Data.Builder()
            .putString("title", title.value!!)
            .putString("desc", description.value!!)
            .build()

        // Noteの投稿は1回実行されれば良いので、OneTimeにする。
        val request = OneTimeWorkRequestBuilder<PostWorker>()
            .setInputData(data)   // Workerにパラメータを渡す
            .addTag("PostWorker") // Workerの実行状況の監視時に使うタグを指定。
            .build()

        workManager.enqueue(request)
    }
}

WorkManager#enqueueを呼ぶことで、1つ前で実装したWorkerの処理が開始される。
viewModelScopeと違って、ViewModelのライフサイクルとは紐付いていないので、ViewModelが破棄されてもWorkerは実行される。

Step4. Workerの終了を検知して画面を更新する

WorkManager.getWorkInfosByTagLiveData(tag)で、Workerの状況をLiveDataとして監視できる。Workerを開始した画面とは別の画面でも、この監視方法は使える。

class NotesViewModel(application: Application) : AndroidViewModel(application) {
    private val workManager = WorkManager.getInstance(application)

    ...

    // Worker開始時に指定したタグを使う。
    val saveWorkerInfos = workManager.getWorkInfosByTagLiveData("PostWorker")

    fun reload() {
        ...
    }
}

class NotesFragment : Fragment() {
    private val viewModel by viewModels<NotesViewModel>()

    ...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        ...

        // PostWorkerが終了するたびに画面を再読み込みする。
        viewModel.saveWorkerInfos.observe(viewLifecycleOwner) { listOfWorkInfo ->
            val workInfo = listOfWorkInfo.firstOrNull()
            if (workInfo?.state?.isFinished == true) {
                viewModel.reload()
            }
        }
    }
}

以上!

補足

  • 公式CodeLab
    • Workerのチェイン、キャンセルなどの説明あり
    • ここまで書いておいて何だが、個人のブログより公式のドキュメント読んだり、内部のコードを読むのが一番理解できる
  • サンプルアプリのコード

  1. ProgressDialogはDeprecatedになっているが、安直にユーザの操作を禁止する風潮を感じる。実装は楽だが。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Androidアプリで端末の言語設定を取得する

#はじめに Androidアプリで端末の言語設定を取得する。 使用したプラグインcordova-plugin-globalization。 #コード プラグインをインストールする。 cd src-cordova cordova plugin add cordova-plugin-globalization コード document.addEventListener("deviceready", function(){ window.navigator.globalization.getPreferredLanguage ( function(language) { let deviseLang = language.value; console.log(deviseLang) } ) } , false); app/res/xml/config.xmlに下記を追加する。 <feature name="Globalization"> <param name="android-package" value="org.apache.cordova.globalization.Globalization" /> </feature> #参考サイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Androidアプリで端末の位置情報を取得する

#はじめに Androidアプリで、Cordovaプラグインを使用して端末の位置情報を取得する。 #コード 最終的なコード document.addEventListener("deviceready", function(){ let permissions = window.cordova.plugins.permissions; permissions.requestPermissions( [ permissions.ACCESS_FINE_LOCATION ,permissions.ACCESS_COARSE_LOCATION ] ,function( status ){ if( status.hasPermission ){ navigator.geolocation.getCurrentPosition( function(position) { let latitude = position.coords.latitude let longitude = position.coords.longitude console.log(latitude, longitude) }, function(error) { console.log(error) }, { enableHighAccuracy: false, timeout: 30000, } ); } else{ console.log('status of permissions is error') } } ,function(){ console.log('permissions is error') } ); }, false); #解説 ###Cordovaの読み込みが完了した後で、中の関数を実行する。 https://cordova.apache.org/docs/ja/latest/cordova/events/events.deviceready.html document.addEventListener("deviceready", function(){ .... }, false); ###端末で位置情報取得を許可する。 アプリを開いた時に、「~~~に位置情報へのアクセスを許可しますか」という旨の確認ダイアログを表示させる。 cordova-plugin-android-permissionsをインストール。 cd src-cordova cordova plugin add cordova-plugin-android-permissions 複数のパーミッションをリクエストするため、permissions.requestPermissionsを使用。 permissions.ACCESS_FINE_LOCATIONは、おおよその位置にアクセスできるようにする設定。 permissions.ACCESS_COARSE_LOCATIONは、正確な場所にアクセスできるようにする設定。 let permissions = window.cordova.plugins.permissions; permissions.requestPermissions( [ permissions.ACCESS_FINE_LOCATION ,permissions.ACCESS_COARSE_LOCATION ] ,function( status ){ if( status.hasPermission ){ .... } else{ .... } } ,function(){ .... } ); app/AndroidManifest.xmlに下記を追加する。 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" /> ###geolocationプラグインをインストール。 cordova plugin add cordova-plugin-geolocation // 古いバージョンのCordovaはこっちをインストール(5.0未満?) cordova plugin add org.apache.cordova.geolocation app/res/xml/config.xmlに下記を設定する。 <feature name="Geolocation"> <param name="android-package" value="org.apache.cordova.GeoBroker" /> </feature> ###位置情報を取得する。 navigator.geolocation.getCurrentPositionを使う。 navigator.geolocation.getCurrentPosition( function(position) { let latitude = position.coords.latitude let longitude = position.coords.longitude console.log(latitude, longitude) }, function(error) { console.log(error) }, { enableHighAccuracy: false, timeout: 30000, } ); enableHighAccuracyはより正確な位置情報を取得するためのオプション。 通常はtrueで設定するが、アプリの起動が遅くなる場合はfalseにすると早くなる。 falseでもほぼ正確な位置情報を取得できる。 #参考サイト https://www.npmjs.com/package/cordova-plugin-android-permissions
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Androidアプリでリンクを開けるようにする

#はじめに Cordovaプラグインを使用して、Androidアプリでリンクを開けるようにする。 使用したのはcordova-webintent。 iOSではInAppBrowserを使用する必要がある。 #コード src-cordovaディレクトリにインストール。 cd src-cordova cordova plugin add github:cordova-misc/cordova-webintent.git 関数を作成。 openLink() { window.plugins.webintent.startActivity( { action : window.plugins.webintent.ACTION_VIEW ,url : 'https://~~~' } ,function(){} ,function(){ alert ('Failed to open URL via Android Intent'); } ) } TypeScriptでエラーが出る場合は、declareを使う。 declare let window: any; #参考サイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

エンジニアの道を選んで1年4ヶ月たった話

初めに

初めまして来年からAndroidエンジニアとして働く予定のmessiです。
Twitter -> https://twitter.com/175Atsu175
趣味でカメラやってるのでそのアカウントも是非
Twitter -> https://twitter.com/messi0601

今回は自分がAndroidと出会ってエンジニアで働こうと決めてから1年4ヶ月たった今を振り返ってみたいと思います。
去年の夏まではAndroidなんてやったことないし、大学は経営学部なのでエンジニアとは疎遠です。(Pythonちょっと触ったぐらい)
エンジニアとしてもチュートリアルの本ぐらいしか行ったことがなかった人がなぜか来年からAndroidエンジニアとして働くことになった経緯やエンジニアの道を選んで今までどんなことしてたかを書いていきます。

2019年8月[Androidとの出会い](0ヶ月目)

Androidに初めて出会ったのは2019年の8月でした。出会ったきっかけは8月に参加したAndroidのインターンでした。
申し込んだきっかけとしては、元々iosのチュートリアル本みたいなものを1週やったことがあったのですが、Androidユーザーだったこともあり、手元に端末がなく、実機で試すことが出来ませんでした。
そんなこともありAndroidやりたいなと思い申し込みました。

夏のインターンでは平日の2週間でQRを生成してプロフィール交換するアプリを作りました。

2019年9月[決意](1ヶ月目)

2週間のインターンの後、自分はAndroidエンジニアとして就活していくことを決意しました。
元々エンジニアになるか総合職にするかかなり悩んでいました。エンジニアとして進もうと思った理由として、自分の将来像が関係してきます。
自分は将来的にエンジニアやビジネス、デザイナーなどのパイプのような存在になりたいと思っています。そのため、社会人になった時に仕事と技術の勉強を同時するのがかなり負担になりそうだと思ったのと、しっかり現場のエンジニアとしてエンジニア目線というものをしっかり体感する必要があると思い、エンジニアの道を選びました。

インターン後、本やネットなどを参考に1ヶ月ほど独学で学習しました。
ただ、独学の限界、自分の勉強の方向性とかが不安になり実務インターンを探し始めました。

2019年10~2020年8月[インターン](2~12ヶ月目)

運もよく、10月からベンチャー企業Aでの実務インターンを行い始めました。
コードを読むことに慣れてないところから始まり、初めは小さなバグ修正を主に行っていました。
小さなバグを直そうとした際に他の部分にも影響が出てしまったり、条件に合わない挙動が起きたりかなり手こずりました。
12月の1ヶ月は別の企業のインターンにいっており、1月からまたベンチャーA企業に戻ってきてた際に新規の機能を任されその機能を1から作ることを任されました。なんとか作り終え無事にリリース出来ました。

最終的に約10ヶ月間お世話になりました。
ここでついた力というのはエンジニアとしての精神という物を鍛えられた気がしてます。

2019年12月[インターン2&内定](4ヶ月目)

ベンチャー企業Aを1ヶ月間おやすみをいただいて、2019年12月は1ヶ月間別の会社でインターンを行ってました。
ここでは、まだ当時リリースされていなかったアプリをに携わらせてもらいました。
主にアプリの画面のUI部分を作らせてもらいました。

そしてその企業からAndroidエンジニアとして内定をいただきました???

5月~6月[個人アプリリリース](10ヶ月目)

今まで実務ベースのチーム開発が主な開発でした。そのため一人でアプリを0から作ってリリースしたことがもちろんなく、一つの夢みたいな思いがあったのでそれを行ってみようを思い取り組んでみました。
その結果作ったアプリは「SkyCode」というアプリです。
画像からColourCodeを取得して表示するアプリになってます。
リンク -> https://play.google.com/store/apps/details?id=com.messi.skycode&hl=ja

このアプリの制作からリリースまで約2~3週間で行えました。(短期集中じゃないと飽きそうだったのでw)
詳しい記事はこちらに書いてあるので是非読んでみてください。
https://qiita.com/175atsu/items/66dec0f80b97f2da70ae

2020年9月〜現在[基礎固め](1年~1年4ヶ月目)

ベンチャー企業Aを辞めて内定先バイトを行い始めました。
ここで大きな壁にぶつかりました。
コードを雰囲気で書いていたということです。
コードの本当の意味合いなどをしっかり理解した上で書いていなかったことに気付き、基礎をもっと定着させないと今後きつくなるとメンターさんに言われ、1から勉強し直し始めました。

まとめ

絶賛まだ、基礎固め中です。

エンジニアの道を選んで1年4ヶ月たった今思うこと

色々遠回りしていたのかも知れないと最近になって思います。
ただ、毎回その状況における自分の最適な行動をしてたと自分は思うので、今は今出来ることに取り組んでいきます。

エンジニアの道を選んだことについて

今かなり、きついなーって思うことが多々あるのですが、後悔は一切してないです。(ただ、なぜエンジニアとして採用されたかは永遠の謎です)

漫画の言葉になってしまうのですが自分がかなり好きな名言があるので紹介させてもらいます。
「正しい判断を下す事、下そうとする事は大切だ。でも、決してそれだけが全てじゃない。
判断が正しいか間違っているかなんて、その時には誰も判らない。
だから大切なのは
"判断の後"。
"下した判断を正解にする努力(こと)"。」

引用:約束のネバーランド第109話

エンジニアの道を選んだのは自分なのでひたすらに努力して頑張って自分の目標とする姿を目指していきます。

では良いお年を。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Koinのバージョンを2.1.6から2.2.1に上げる

はじめに

Androidプロジェクトで使っているKotlinのバージョンが1.3系列だったので1.4系列に上げたところエラー。
どうやら使っているKoinのバージョン(2.1.6)だとKotlin 1.4系列に対応していないようです。
新しいバージョンなら対応しているそうなのでじゃあKoinもいっちょ上げてやろうと上げてみたらソースコードがエラーで火を吹いた?

Koinの2.1.6から2.2.1にかけてbreaking change(下位互換性のない変更)があったようです……。セマンティックバージョニングに従ってよ……。

どこをどう変えればビルドが通るようにマイグレーションできるのか探したところ What’s next with Koin? — 2.2 & 3.0 releases という公式の記事を見つけましたが、マイグレーションの観点からは書かれていないので読み解くのにちょっと大変でした。

ということで本記事はマイグレーションの観点からの忘備録になります。新機能の紹介はしていないのでご注意ください。

環境

  • Koin 2.1.6 -> 2.2.1
  • Kotlin 1.4.20
  • Androidプロジェクトで使用
    • Android Xを使用

2.1.6以外の2.1系列だとまた事情が違うかもしれません。ご注意ください。

マイグレーション

以降ソースコード中の - は行の削除を、 + は行の追加を表します。

build.gradle

Koin 2.2から WorkManagerJetpack Compose にも対応しました。
WorkManagerやJetpack Composeを使っていないなら追加しなくてもいいと思います
(筆者はどちらも使用しないので追加していません)。

build.gradle
- koin_version = '2.1.6'
+ koin_version = '2.2.1'

// Koin AndroidX Scope features
implementation "org.koin:koin-androidx-scope:$koin_version"
// Koin AndroidX ViewModel features
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
// Koin AndroidX Fragment features
implementation "org.koin:koin-androidx-fragment:$koin_version"
+ // Koin AndroidX WorkManager
+ implementation "org.koin:koin-androidx-workmanager:$koin_version"
+ // Koin AndroidX Jetpack Compose
+ implementation "org.koin:koin-androidx-compose:$koin_version"
// Koin AndroidX Experimental features
implementation "org.koin:koin-androidx-ext:$koin_version"

KoinComponentの修正1:パッケージ名の修正

2.1.6では KoinComponent インタフェースは org.koin.core に属していましたが2.2.1では org.koin.core.component に属しています。
import文を修正します。

- import org.koin.core.KoinComponent
+ import org.koin.core.component.KoinComponent

- import org.koin.core.inject
+ import org.koin.core.component.inject

- import org.koin.core.get
+ import org.koin.core.component.get

- import org.koin.core.bind
+ import org.koin.core.component.bind

これでKoinComponentの依存性解決はできたのでビルドは通るようになりました。
KoinComponent が警告扱いになっているのが気になる方は「おまけ・KoinComponentの修正2:KoinComponent誤使用のチェックと警告の抑制」を参照してください。

stateViewModel関係の修正

2.1.6ではViewModelSavedStateHandle を持たせる場合――俗に言うStateViewModelを取り扱う場合、stateViewModelgetStateViewModel といった拡張関数を使っていました。
2.2.1ではこれらは viewModelgetViewModel に統合されました。

注入するインスタンスを生成する箇所

2.1.6では viewModel のdefinitionとして (handle: SavedStateHandle) -> のようにSavedStateHandleインスタンスを指定したうえでViewModelクラスにhandleを渡していました。
2.2.1では (handle: SavedStateHandle) -> は不要になりました。代わりに、ViewModelクラスへの注入は get を使用します。

- viewModel { (handle: SavedStateHandle) ->
-     FooViewModel(
-         handle, ...
-     )
- }

+ viewModel {
+    FooViewModel(
+        get(), ...
+    )
+ }

注入する箇所

stateViewModel -> viewModelgetStateViewModel -> getViewModel に変更します。

- import org.koin.androidx.viewmodel.ext.android.getStateViewModel
+ import org.koin.androidx.viewmodel.ext.android.getViewModel

...

- foo = getStateViewModel()
+ foo = getViewModel()

- import org.koin.androidx.viewmodel.ext.android.stateViewModel
+ import org.koin.androidx.viewmodel.ext.android.viewModel

...

- private val fooViewModel: FooViewModel by stateViewModel()
+ private val fooViewModel: FooViewModel by viewModel()

KoinContextHandlerの修正

KoinContextHandler がdeprecatedになりました。
代替は GlobalContext です。

- import org.koin.core.context.KoinContextHandler
+ import org.koin.core.context.GlobalContext

- KoinContextHandler.getOrNull() ?: startKoin {
+ GlobalContext.getOrNull() ?: startKoin {
    ...
}


おまけ・KoinComponentの修正2:KoinComponent誤使用のチェックと警告の抑制

tl;dr

  • KoinComponent が警告扱いになったけどdeprecatedになったわけではない
  • 警告を抑制したい場合は @Suppress("EXPERIMENTAL_API_USAGE") をつけていけばよい
  • KoinComponent の使用が推奨されるのは以下のパターン
    • ユーザーがコンストラクターからインスタンス生成することができない場合
    • Koin開発者やKoinを拡張したい人が使う場合

警告扱いになった経緯

2.2.1にするとAndroid Studio上で KoinComponent が警告表示されるようになりました。
簡単に言うと お前らの KoinComponent の使い方は間違っている から おいそれと KoinComponent を使わせないように警告表示するようにした のが経緯のようです。

この対応が良いかどうかの議論は置いておいておくとして、とりあえず KoinComponent がdeprecatedになったわけではないのでこのまま使い続けて大丈夫そうです。

警告を抑制する方法

警告を抑制したい場合は KoinComponent を使っているクラスに @Suppress("EXPERIMENTAL_API_USAGE") を付与して警告を握りつぶすのがよいと思います。

@KoinApiExtension をつける方がKoin開発者の意図に近いかもしれませんが、@KoinApiExtension だとそのクラスを使っているクラスにさかのぼって警告が出るようになります。
プロジェクトの規模やクラス同士の関係にもよりますが、使っているクラス全部に @KoinApiExtension を付与して回る手間は割けないと思います。

正しく使っているかどうかのチェック

とはいえ、Koin開発者の指摘を全く無視するのもよくありません。
以降では KoinComponent が正しく使われているかどうかを例示します。
あなたのプロジェクトで当てはまるかどうかチェックしてみてください。

KoinComponent が誤って使われている例

はじめに KoinComponent が誤って使われる例です。

この例の最もよくあるパターンの1つはActivityやFragmentだと思います。

import org.koin.component.inject
import org.koin.component.get

// THIS CODE IS WRONG!!
class FooActivity: AppCompatActivity, KoinComponent {
    private val fooModule by inject<FooModule>()
    private lateinit var barModule

    fun barInit() {
        barModule = get()
    }
}
import org.koin.component.inject
import org.koin.component.get

// THIS CODE IS WRONG!!
class FooFragment: Fragment, KoinComponent {
    private val fooModule by inject<FooModule>()
    private lateinit var barModule

    fun barInit() {
        barModule = get()
    }
}

Kotlinでは ComponentCallbacks に拡張関数を用意しています。
ActivityやFragmentはどちらも ComponentCallbacks を継承しているためKoinComponentの継承は不要です。

- import org.koin.component.inject
- import org.koin.component.get
+ import org.koin.android.ext.android.inject
+ import org.koin.android.ext.android.get

- class FooActivity: AppCompatActivity, KoinComponent {
+ class FooActivity: AppCompatActivity {
    private val fooModule by inject<FooModule>()
    private lateinit var barModule

    fun barInit() {
        barModule = get()
    }
}
- import org.koin.component.inject
- import org.koin.component.get
+ import org.koin.android.ext.android.inject
+ import org.koin.android.ext.android.get

- class FooFragment: Fragment, KoinComponent {
+ class FooFragment: Fragment {
    private val fooModule by inject<FooModule>()
    private lateinit var barModule

    fun barInit() {
        barModule = get()
    }
}

Android Studioで開発していると ⌘ + Enter(Windowsでは Alt + Enter)で補完させることが多いのでこういったミスは出やすいですね。

他の例だとコンストラクター引数を使っていないクラスでしょう。

import org.koin.component.inject

// THIS CODE IS NOT RECOMMENDED!!
class FooRepository: KoinComponent {
    private val fooDao by inject<FooDao>()
}

FooRepositoryKoinComponent を除いて何も継承していないプレーンなクラスです。
上記のコードでは inject を使って FooDao インスタンスを注入していますが、このような場合 FooDaoインスタンス はKoinに頼らず普通にコンストラクター引数から与えるようにしてください。そして Koinの module 関数内でFooRepositoryおよびFooDaoを生成するようにしてください。

- import org.koin.component.inject

- class FooRepository: KoinComponent {
-     private val fooDao by inject<FooDao>()
- }

+ class FooRepository(private val fooDao: FooDao) {
+ }

...

module {
+     single {
+         FooRepository(fooDao = get())
+     }
+
+     single {
+         FooDao()
+     }
}

KoinComponent を使わざるをえない例

つぎは、 KoinComponent を使わざるをえない例です。この例ではコードの修正は必要ありません。

import androidx.room.Dao
import org.koin.component.KoinComponent
import org.koin.component.inject

@Dao
interface FooDao: KoinComponent {
    private val fooConfig by inject<FooConfig>()

    ...
}

上記は Android Room を使ったコードです。RoomではDAOインスタンスをユーザーが直接インスタンス生成することはできません。
このように、 ユーザーがコンストラクターからインスタンス生成できないクラスにおいてはKoinComponentを使うのが正しい やり方となります。
警告にびびらずどんどん使っていきましょう。

おまけのおまけ・WorkManagerの対応

2.1.6では WorkManager のWorkクラスも KoinComponent を使わないと注入が実現できませんでした。
2.2.1だとWorkManagerに対応したので KoinComponent は不要になった……らしいのですが、

  • 公式のマニュアル の記述が怪しい。 setupWorkManagerFactory 関数が存在しない(CHANGELOGではデフォルトの処理を用意したから要らなくなったみたいに読める記述はありますが……)
  • WorkManagerに関連するテストコードが全部コメントアウトされている
  • 正式リリースっぽい雰囲気だが実はまだ実験段階( @KoinExperimentalAPI がついている)

(全て2.2.1時点です)

ということで、筆者はまだ導入せずWorkクラスは KoinComponent を使った注入コードのままにしてあります。

どなたか試された方がいたら教えて下さい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google PlayのUSランキングの上位層のアプリを見ながらゲームを考えてみる #1

はじめに

就活用にUnityでなにかハイパーカジュアルゲームを作ろうと思ったのですが、せいぜい十個くらいしかやったことがないのでそこまでこのジャンルについて知りません。
なのでこの記事ではGooglePlayのアメリカの無料ゲームのランキングを見ながらアプリの企画について考えていこうと思います。
餅は餅屋といいますし、上位に入ってるアプリの良い所を参考にすれば共通点やDL数をあげるコツが分かるのでは?という発想です。
なんでアメリカなの?かというとそっちのほうがユーザーが多く、ユーザー数をより多く欲しい場合は対応せざるを得ないと思うからです。

見ていくアプリ

URL:https://play.google.com/store/apps/collection/cluster?clp=0g4cChoKFHRvcHNlbGxpbmdfZnJlZV9HQU1FEAcYAw%3D%3D:S:ANO1ljJ_Y5U&gsr=Ch_SDhwKGgoUdG9wc2VsbGluZ19mcmVlX0dBTUUQBxgD:S:ANO1ljL4b8c&hl=us&gl=us
image.png
image.png

Among Us

いわゆる宇宙人狼ですね。
人狼をグラフィカルにしたようなゲームです。
海外はいまいちわかりませんが、日本ではこのゲームは実況者とかVtuberがやってたから流行ったイメージがありますね。
同じようなゲームでいうと、ProjectWinterを2DにしてSFチックにした感じでしょうか。

https://www.famitsu.com/news/202011/09208954.html

こちらの記事によると海外でもTwitchの大手ストリーマーが取り上げて、それで流行ったようです。
ルールが単純かつ有名で、実況映えしていて、見ているだけでも楽しいし、実際にプレイしてもすぐに楽しめます。

パブリックで見知らぬ人とやるときは大体が海外の方とマッチングしますし、そもそも日本語は入力できないので
英語だけで話して人狼を特定していく感じになります。
人狼側は一人killしてから次のkillまでに長めのクールタイムがあったりで、レベルデザインがすごいなと思います。

昔からの遊びを別の形にしてゲームにするというのは結構やってる所が多い気がしますが、いい成功例だと思います。
世界共通とはいわずとも、やっているところが多い遊びを調べて何かのジャンルと掛け合わせるというのはよさそうですね。

あそびと言って思い出しましたが、世界のアソビ大全とかいうゲームがありましたね。
テーブルゲームが基本ですが、あれも同じような発想なのかな。

Project Makeover

パズル&着せ替えゲームという感じでしょうか。
レビューを見ていると難易度高め&運要素高め&要求ゲーム内通貨が多い&回復手段が少ない、みたいなマゾゲーな印象を感じます。
パズルゲームは基本やらないので参考にしづらいですが、着せ替え要素が一つのゲームの面白さの形としてあるゲームは多いですよね。

Roof Rails

VOODOOさんのハイパーカジュアルゲームですね。
キャラが持っている棒を大きくして、その棒でアイテムを拾ったりアクションしたりするゲームです。
棒が擦り切れたら失敗するようです。
ハイパーカジュアルゲームは結構持っているものを大きくしてなにかするゲームがある気がしますね。

Sushi Roll 3D - Cooking ASMR Game

寿司を作って提供するゲームです。
どこかで見たようなデザインの客が要望してくるメニューを色々なアクションで作っていきます。

オブジェクトハント

隠れん坊オンラインですね。これ。

ショートカットラン

VOODOOさんの作品ですね。
最近の広告だとこれが一番見る気がします。
道でブロックを集めて、そのブロックの分だけ海をショートカットできる。
アイコン、名前、ストアの画像全てで分かり易いですね。
プレイしていなくてもゲームの内容が伝わってきたので、分かりやすさが如何に大事なことなのかと思わされますね。
ぱっと見で内容を伝えるっていうのは凄いことだよなあ、とつくづく思います。

Imposter Solo Kill

Among Usに寄せた暗殺ゲーですね。

Chat Master!

チャットアプリのようですがいまいち500万ダウンロードされている理由が分かりません。
Google Playは謎が多いですね...

BMX Space

BMXができるゲームですね。オンラインプレイにも対応しています。
同じデベロッパーの作品を見るとこれとは別に自由にBMXが楽しめるゲームがあったので、
そちらからの流入が多そう。
一つのジャンルを専門的につくっていくスタイルも一部のユーザーには刺さりそうです。

まとめ

個人的にはスマホゲーは横持ちのほうが好きなものが多いのですが、ランキングで見ると無料ゲームで横持ち要求しそうなのはほとんどないですね。レースゲームであっても縦持ちで作られているものがあります。こういうジャンルで横持ちは人権無さそう。
あと見るからにパクリって分かるレベルでも消されないのも発見ですね。する気もないですが。

自分はレースゲームが好きなので、こういうハイパーカジュアルを作るとしたら車要素やレース要素を含めたいですね。

今回はこの辺で一回終わりにします。
つづきはまた別で投稿することにします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Composeのライフサイクル (onActive, onCommit, onDispose) についてメモ

割とすぐ忘れるので。

以下のようになっていた場合。

@Composable
fun Screen() {
    var selectedTab by remember<MutableState<Tabs>>{ mutableStateOf(Tabs.Tab1) }

    println("no wrap $selectedTab")
    onActive {
        println("onActive")
        onDispose {
            println("onDispose in onActive")
        }
    }
    onCommit {
        println("onCommit 1")
        onDispose {
            println("onDispose in onCommit 1")
        }
    }
    onCommit(selectedTab) {
        println("onCommit 2")
        onDispose {
            println("onDispose in onCommit 2")
        }
    }
    onDispose {
        println("onDispose")
    }
...

onActive()

基本的に一回だけ呼ばれるが、このComposable関数自体が使われなくなればonActive内のonDispose()が呼ばれ、またこのComposableが使われた時にonActive()が呼ばれる。
また外側のActivityがdestroyなどになってもonDispose()が呼ばれる。

onCommit()

基本的に変更ごとに毎回呼ばれる。そのたびに以前にonCommitされていれば、onDispose()が呼び出される。
引数なしでは、基本的にこの外に書くのと違いがなさそうで、引数無しで呼び出されることはなさそう。(そういうパターンがあれば教えていただきたいです。タイミングが違ったりする?)

onCommit(argment)

onActive同様のタイミングと引数が変わったタイミングで呼ばれる。また引数がわかったときにはonDisponseも呼ばれる。

動作を見てみる

起動するだけ

I/System.out: no wrap Tab1
I/System.out: onActive
I/System.out: onCommit 1
I/System.out: onCommit 2

selectedTabの変更

I/System.out: no wrap Tab2
I/System.out: onDispose in onCommit 2
I/System.out: onDispose in onCommit 1
I/System.out: onCommit 1
I/System.out: onCommit 2

selectedTab以外の変更があったとき

I/System.out: no wrap Tab2
I/System.out: onDispose in onCommit 1
I/System.out: onCommit 1

Screen()自体が使われなくなったとき

I/System.out: onDispose
I/System.out: onDispose in onCommit 2
I/System.out: onDispose in onCommit 1
I/System.out: onDispose in onActive

アプリ終了時

I/System.out: onDispose
I/System.out: onDispose in onCommit 2
I/System.out: onDispose in onCommit 1
I/System.out: onDispose in onActive
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Androidスライド削除のやり方

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

openFileInputでファイルを読み込むときのディレクトリの場所

image.png
View -> Tool Windows -> Device File Explorer を開き、data/data/{パッケージ名}/files の中にあるファイルを探すみたい。

PC側のパッケージ名直下のfilesフォルダを探してるかと思ったらAndroidエミュレータ内のfilesフォルダを探してるんですね。結構悩んだのでメモ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android studio:Kotlin:アプリの更新を行う

ビルド時に書き換えるのはバージョン情報

app\build.gradleファイルの

versioncode と versionnameを変える。
versioncodeは整数のみ。
versionnameは1.1的なやつ

build.gradle
    defaultConfig {
        applicationId "com.shopping.noshoppingbag"
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 2 ←ここ
        versionName "1.1" ←ここ

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

ビルドするときは最初にビルドしたときの署名鍵が必要。

android studioの上の
ビルド→署名済みバンドルまたはapkの作成→Android App Bundle

2.PNG

ここのキー保管パスに最初にアプリをビルドしたときに作ったjksファイルを指定する。
なくしてたら更新は不可。アプリをplayから削除して出し直すしかない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android studio:Kotlin:play store へアップロードするaapファイルの作成

android studioの上の
ビルド→署名済みバンドルまたはapkの作成
1.PNG
Android App Bundle→次へ
2.PNG
新規作成
3.PNG
キー保管パスはわかりやすいところを指定。
上のパスワードと下のパスワードを入れる(それぞれ違うやつ)
名前と苗字は入れる。
それ以外は空白でOK。
4.PNG
公開済みアプリを登録するための暗号化されたキーをエクスポートする にチェック(なくてもできるけど一応)
5.PNG
release を選択して完了。

完了するとappフォルダに「release」というフォルダができ、その中にapp-release.aabができるのでそれをplay storeにアップロード。

また新規作成時に指定したフォルダに アプリ名.jksというキーができる。
これをなくすと最新版をリリースできなくなるので注意。
アプリの更新をする場合はキー保管パスはにjksファイルのファイルパスを指定する。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android studio:Kotlin:アプリのプロジェクト作成時にやること

1.プロジェクト作成時にパッケージ名をexampleから変更
キャプチャ.PNG

パッケージ名のcom.exambple.myapplicationのexample部分。
exampleのままだとplay storeでひっかかるので変える。
com.test.applicationみたいな感じで。

もしそのままでつくってしまったらリファクタリング。
手順は以下参考。
https://joh-sys.com/blog/2020/05/12/%E3%80%90android%E3%80%91%E3%80%8Ccom-example%E3%80%8D%E3%81%AF%E7%A6%81%E6%AD%A2%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%81%9F%E3%82%81%E3%80%81%E5%88%A5%E3%81%AE%E3%83%91%E3%83%83%E3%82%B1/#toc3
ただしbundle.gradeはプロジェクト直下と、app\srcにあるので注意。変えるのはapp\srcにある方。

2.アプリの表示名を変える。
\app\src\main\res\values\string.xmlのアプリ名を変更

string.xml
<resources>
    <string name="app_name">ここにアプリ名</string>
</resources>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ethereumでガチャりたい!

はじめに

Ethereum のゲーム dApps を眺めていると、トークンの売り方として「セール形式」が多い印象です。

セール形式とは、運営さんが事前に発行しておいたトークン(キャラやカード)を販売する形式で、「超強いキャラは値段を高く」、「そこそこの強さであれば安く」といった売り方です。
cut01
一方で、スマホアプリでよくあるのが「ガチャ形式」です。

ガチャ形式とは、プレイヤーがガチャを引くたびにトークンが発行される形式で、トークンには内容に応じた確率が設定されます。
1回の値段は抑えめにしておき、「超強いキャラは低確率で排出」、「そこそこの強さであれば高確率で排出」といった売り方です。
cut02
ゲーム性がスマホアプリとよく似ている dApps は結構目にしますが、ガチャ形式が採用されているケースはほとんど見られません。

一体、なぜでしょうか?
色々な理由があると思うのですが、私なりの考えは下記となります。

①コントラクトで確率(乱数)を使えない
②コントラクトのバイナリが公開されるのでガチャの仕組みが解析される
③トランザクションに再現性がある(大当たりのトランザクションが再利用される)
④トークンの発行をプレイヤーに委ねるのが怖い(発行処理が直接叩かれのは不安)

他にも理由はあると思いますが、dApps とガチャの相性はあまり良くないと思われます。

でも、それでも、ブロックチェーンでガチャを引きたいじゃないですか…。
そんな思いにかられて、検証用のサンプル dApps を作ったので、ご覧くださいませ。

CharaMixDemo

ご紹介するサンプル dApps は「CharaMixDemo」といいます。
名前の通り、キャラ(トークン)を生成して合成する内容です。
about
オープンベータ形式で配信しておりますので、お使いのスマホに合わせて下記のリンクからインストールしていただけます。

*Android端末をお使いの方はこちら*
googleplay_icon
*iOS端末をお使いの方はこちら*
testflight_icon

ご注意

このサンプルでは、どこかで見たようなキャラ「39さん」が登場しますが、非営利の検証用に作成したものであり、製品版としてリリースするつもりはありません。ですので、「初音ミクの二次創作ガイドライン」に抵触するものではございません。
※接続するブロックチェーンも Ethereum のテストネット Rinkeby となります

後の説明のわかりやすさ & 作る本人のモチベーションのためにも、「初音ミク」オマージュのキャラを使わせていただいていおります。
ご理解くださいますようお願いいたします。

dAppsの構成と課題への対応

サンプル dApps のクライアントは、iOS / Android アプリとなります。
プレイヤーはアプリ経由で、コイン(ERC20 トークン)を消費してガチャを引き、キャラ(ERC721 トークン)を取得できます。

クライアント上でガチャが引かれるとトランザクションが発行され、ブロックチェーン側の窓口である MAIN コントラクトにより処理されます。
MAIN コントラクトはコイン(CMC)とキャラ(CMA)トークンを管理するコントラクトへアクセスし、トークンを実際に発行/焼却します。
cut03
では、この構成をもとに、課題への対応を見ていきましょう。

*課題①:コントラクトで確率(乱数)を使えない*
乱数(厳密には乱数のシード)はアプリ側で生成することにしました。
この乱数をパラメータに含めてトラザクションを発行するのですが、この時、トランザクションのパラメータを暗号化しておきます。
コントラクト側では受け取ったパラメータを複合化してシードを取り出し、乱数生成に利用します。
cut04
*課題②:コントラクトのバイナリが公開されるのでガチャの仕組みが解析される*
ガチャの結果を決定付ける乱数の暗号化さえ見破られなければ、それ以外の処理が解析されても問題ないと割り切ることにしました。
ガチャのロジックを解析するプレイヤーが出てきたとしても、それはそれでゲームを攻略する楽しみの一環だと思います。

*課題③:トランザクションに再現性がある(大当たりのトランザクションが再利用される)*
コントラクト側で、前回ガチャが引かれた時間(ブロック番号)を保持するようにしました。
アプリ側ではガチャのトランザクションを発行する際、その時点でのブロック番号をパラメータとしてトランザクションへ含めます。
ガチャ処理が呼ばれた MAIN コントラクトでは、パラメータ内のブロック番号と自身の保持するブロック番号を比較し、古いトランザクションであれば弾きます。

また、トランザクションパラメータの暗号化の際には、プレイヤーの Ethereum アドレスも反映させるようにしたため、他者のトランザクションの流用もできなくなっています。
cut05
*課題④:トークンの発行をプレイヤーに委ねるのが怖い(発行処理が直接叩かれのは不安)*
アプリの窓口となる MAIN コントラクトがアクセスされた際、真っ先にトランザクションパラメータの複合を試して、失敗した場合は処理を中断するようにしました。
パラメータの複合に成功した場合にのみ、MAIN コントラクトはトークンコントラクトの発行/焼却処理を呼び出します。
これにより、MAIN コントラクトがアプリを経由せずに直接叩かれても、トークンを発行してしまうことはありません。
cut06_1
また、トークンコントラクトの発行/焼却処理は、MAIN コントラクトからのみ呼び出せるようにしました。
直接トークンコントラクトへアクセスして、トークンの発行/焼却処理を呼び出すようなトランザクションは中断されます。
cut06_2
一方で、CMC / CMA トークンコントラクトはそれぞれ、ERC20 / ERC721 に準拠しているため、MetaMaskOpenSea といった外部ツールやマーケットにおけるトレードが可能です。

以上で、実装面の説明は終わりです。

ガチャの内容

では、ガチャ内容を詳しく見ていきましょう。
このサンプルには「女の子の生成」と「女の子の合成」の2つのガチャがあります。

*生成ガチャ*
ss01_qiita
候補となる基本素体の女の子から個性(パーツ)をバラバラに抽出して新しい女の子を生成するガチャです。
この時、アプリ上でガチャの引き直し(乱数の更新)が好きなだけ行えます。
気に入ったキャラが生成されたらトランザクションを発行し、新たなトークンとして取得できます。

*合成ガチャ*
ss02_qiita
2人の女の子を合体させて個性(パーツ)をシャッフルし、性能を向上させるガチャです。
この時、アプリ上でガチャの引き直し(乱数の更新)が好きなだけ行えます。
気に入ったキャラが合成されたらトランザクションを発行し、新たなトークンとして取得できます。
合成トークン取得時には、素材となった女の子(トークン)は焼却されます。

また、合成により取得される女の子は「バージョン」の値が1つあがります。
ss03_qiita
このバージョンとは、スマホアプリのガチャにおける「レアリティ」に相当する判断基準で、バージョンが高いほど、その女の子の取得までに支払われたコストが多いことを意味します。
※バージョンの初期値は0で、同じバージョンの女の子同士でのみ合成が可能です
※バージョンNの女の子は「バージョン0のトークンを2のN乗個」消費して出来上がっていると判断できます

例えば、「バージョン」の女の子であれば、「8回バージョンアップした」=「バージョン0のトークンを256個消費した」ということが一目で分かります。
このキャラすげぇ!」とうい風にありがたがってもらうための値がバージョンとなります。

ガチャを回そう!

では、実際にガチャを回していきましょう。

まずは、生成ガチャから。
引くのは断然「39さんPickUp」です。
ピックアップされている39さんの抽出率は「25%」となります。
gacha01
…はて、「抽出率」などという見慣れない単語がでてきましたが、これはなんでしょうか?

この生成ガチャでは、「目なら目」、「口なら口」といった具合に、基本素体からパーツを選出して新しい女の子を生成します。
このパーツの選出がどのぐらいの確率で行われるかが抽出率となります。
ですので、「39さんPickUp」ガチャでは25%の確率で、39さんのパーツが選出されることを意味しています。

ところが、困ったことにトークンを構成するパーツは数十個も存在します。
成分100%の39さんを生成ガチャで引こうと思ったら、非現実的な確率(25%の数十乗)となってしまいます。
そこで、このサンプルで想定する遊びは「生成ガチャでそこそこの成分のキャラをたくさん取得」し、「合成ガチャで少しずつ純度を高めていく」というものになります。

生成ガチャで素材厳選

生成ガチャをぶん回して、素材キャラを集めていきましょう。
まずは、39さん成分40%オーバーが出るまで乱数を引き直して、キャラを8体GETしました。
gacha02
50%オーバーが4体。なかなかの引きでした。

合成ガチャで成分抽出

続いて、合成ガチャで39さん成分を抽出していきましょう。
gacha03_1
合成ガチャでは素材となる二人の女の子から、半々(50%)の確率でがパーツが選出されます。
ここでは、選択した2体のキャラの「39さん成分の平均」に対して、「+10%」ぐらいの値を目標に合成ガチャを回していくことにします。

まずは、「バージョン」の8体から1ペアずつ厳選し、「バージョン」の女の子を4体合成しました。
gacha03
続いて、「バージョン」の4体から、「バージョン」を2体厳選。
gacha04
最後に、「バージョン」のキャラを厳選してひと段落です。
これで、8体いた「バージョン」のトークンが、1体に圧縮されました。
gacha5
最終的な成分は「83%」。
ちょっとチンチクリンな見た目ですが、かなりの39さん成分を抽出できました。

目指せ39さん100%

この調子で成分100%を目指しましょう。
生成ガチャと合成ガチャをブン回して、「バージョン」のキャラを追加で3体作成しました。
gacha06
続いて、「バージョン」のキャラから「バージョン」のキャラを厳選し…
gacha07
ついに、成分100%の39さんが完成しました!
gacha08
お〜、かなり39さんです!
ここまでに積み上げた「バージョン」のトークン数は「2の5乗=32個」で、これがこのトークンの資産的な価値ということになります。

パラメータはフレーバー程度のものですが、戦闘力「522」、個体値「80.4%」となりました(すごく強い!多分!)。
これらがこのトークンのゲーム的な価値となり、性能が高いと判断されればプレイヤー間での需要が高まるはずです。

OpenSeaへ出品

さて、実際にトークンを作り出したのであれば、マーケット上での価値も気になるところです。
サンプルアプリから「METAデータの更新」をすることで、トークンの METAデータを OpenSea へアップロードできます。

試しに何体か出品してみましょう。
opensea02
自身の手で生み出したトークンがマーケットで売れてくれたりしたら、感慨もひとしおというものです。
※このサンプルの接続先は Rinkeby なのでリアルマネーは得られませんが…

最後に

トークンの売買が手軽に行えるのが Ethereum の面白味の1つですが、「ここにガチャ要素を入れたら楽しいにきまっている!」との思いで作ったのが今回のサンプル dApps です。

一般的なスマホアプリの「大当たりがでるまで引き続けるガチャ」とは少し毛色が違った、「小当たりを積み重ねていくガチャ」の面白みを感じていただけたのなら幸いです。

興味のある方は、是非サンプルで遊んでみてくださいませ。
ご意見ご感想などございましたら、お教えくださると嬉しいです。

では、ここまでご覧いただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

赤くなったKindle Fireを復活させる(未完)

はじめに

先に言っておきますが、まだ解決していません。

問題の端末

Kindle Fire HD 32GB タブレット(第2世代)
https://www.amazon.co.jp/gp/product/B00960YRBI/
https://developer.amazon.com/ja/docs/fire-tablets/ft-device-specifications-firehd-models.html?v=firehd7_2012

2012年に買った端末ですが、ここ数年はイベントなどでサイネージ端末として活躍していました。

障害内容

ある日電源を入れると真っ赤な画面が表示され起動しない状態になる。

すごく赤いです

復活への道

とりあえず工場出荷時に戻そうと色々やってみたが症状は変わらず。
こうなると通常の方法では初期化することができず、Fastbootモードというので起動して、PCからファームウエアを上書きする必要があるようだ。

というわけで、Fastbootモードで起動するには、Factory Cableという特殊なUSBケーブルが必要となる。
検索すると自作方法が出てくるが、面倒臭いのでAliExpressで注文します。

FactoryCable
https://www.aliexpress.com/item/32643090805.html

届いたFactory Cable(なぜか2本入り)

[Something went wrong]()

Fastbootモード出の起動に成功

[Something went wrong]()

しかし、PCではデバイスとして認識されず。

hichon-no-MacBook-Pro:~ hichon$ adb kill-server
hichon-no-MacBook-Pro:~ hichon$ adb start-server
* daemon not running; starting now at tcp:5037
* daemon started successfully
hichon-no-MacBook-Pro:~ hichon$ adb devices
List of devices attached

hichon-no-MacBook-Pro:~ hichon$

というわけで、続く(?)

参考

https://forum.xda-developers.com/t/bootloader-2nd-bootloader-for-custom-roms-on-kfirehd-7-06-24-cm12-1-twrp-2-8-7-0.2128848/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

赤くなったKindle Fire HDを復活させる(未完)

はじめに

先に言っておきますが、まだ解決していません。

問題の端末

Kindle Fire HD 32GB タブレット(第2世代)
https://www.amazon.co.jp/gp/product/B00960YRBI/
https://developer.amazon.com/ja/docs/fire-tablets/ft-device-specifications-firehd-models.html?v=firehd7_2012

2012年に買った端末ですが、ここ数年はイベントなどでサイネージ端末として活躍していました。

障害内容

ある日電源を入れると真っ赤な画面が表示され起動しない状態になる。

すごく赤いです。
kindle_fire_red_screen.jpg

復活への道

とりあえず工場出荷時に戻そうと色々やってみたが症状は変わらず。
検索するとこいつは、Motorola製の端末らしく、Factory Cableという特殊なUSBケーブルを使用するとFastbootモードというので起動してファームウエアを上書きできるようだ。
検索すると自作方法が出てくるが、面倒臭いのでAliExpressで注文する。

FactoryCable
https://www.aliexpress.com/item/32643090805.html

約2週間後に届いたFactory Cable(なぜか2本入り、おまけ?)

factory_cable_1.jpg

Fastbootモードでの起動に成功

fastboot_mode_1.jpg

しかし、PC(Macbook Pro)からはデバイスとして認識されず。

hichon-no-MacBook-Pro:~ hichon$ adb kill-server
hichon-no-MacBook-Pro:~ hichon$ adb start-server
* daemon not running; starting now at tcp:5037
* daemon started successfully
hichon-no-MacBook-Pro:~ hichon$ adb devices
List of devices attached

hichon-no-MacBook-Pro:~ hichon$

というわけで、続く(?)

参考

https://forum.xda-developers.com/t/bootloader-2nd-bootloader-for-custom-roms-on-kfirehd-7-06-24-cm12-1-twrp-2-8-7-0.2128848/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む