記事一覧

東京都のQRコード決済で10%還元キャンペーンが3/23(土)で早期終了

東京都のQRコード決済で10%還元キャンペーンが3/23(土)で早期終了のニュースがありました。この記事を書いているのが「3/21」なので、今日を入れて残り3日になります。3/31(日)までの予定だったので、8日早い終了になります。公式サイトhttps://kurashisupport.metro.tokyo.lg.jp/私は「PayPay」「auPAY」「d払い」「楽天Pay」の中で、「PayPay」がほとんどでしたが、どんな利用割合だったかきになりますね。10%のポイント還元でしたけど、利用店舗が多かったので結構利用した感じはありました。このようなキャンペーンを通して、キャッシュレス化がまた加速しますね。

大阪のお土産って何があるのだろう?

3/23と3/24で大阪に行く予定です。そこで、大阪のお土産について、調べてみました。『りくろーおじさんの店』の「チーズケーキ」と『551』の「豚まん」は、マストですね!楽しみです。私の周りからのお勧めりくろーおじさんの店 焼きたてチーズケーキhttp://www.rikuro.co.jp/551の豚まんhttps://www.551horai.co.jp/カールチーズあじhttps://qa.meiji.co.jp/faq/show/4301?category_id=1&site_domain=default辻利抹茶ラスクhttps://www.otabe.jp/view/category/ct223Web検索1/2(じゃらん.net)https://www.jalan.net/news/article/93846/喜ばれる大阪府のお土産19選!地元民「人気ランキング」&編集部おすすめを紹介01位 豚まん【551蓬莱】02位 焼きたてチーズケーキ【りくろーおじさんの店】03位 堂島ロール【モンシェール】04位 じゃがりこ たこ焼き味ソースマヨ風味【カルビー】05位 GRAND Calbee【カルビー】06位 みるく饅頭 月化粧【青木松風庵】07位 みたらし小餅【千鳥屋宗家】08位 茜丸 五色どらやき【茜丸本舗】09位 本千鳥さぶれ【千鳥屋宗家】10位 ひとくち餃子【点天】編集部おすすめお土産情報岩おこし・粟おこし【あみだ池大黒】大阪花ラング【あみだ池大黒】ポテトチップス 関西だししょうゆ【カルビー】手焼・しょう油味たこ焼【たこ昌】ふる里の味 とん蝶【御菓子司 絹笠】ええもんちぃ【五感】本千鳥饅頭【千鳥屋宗家】お好み焼せんべえ【大阪の味本舗】肉桂餅【八百源来弘堂】Web検索1/2(JR東海ツアーズ)https://travel.jr-central.co.jp/plan/area/osaka/omiyage/絶対外さない!大阪の定番お土産6選1.生乳の香りとコクを包み込んだケーキ 「堂島ロール」2.大阪土産の定番中の定番 「551蓬莱 豚まん」3.30年愛される大阪産(もん) 「千鳥屋宗家 みたらし小餅」4.このかたさが癖になる 「あみだ池大黒 岩おこし」5.行列も納得の美味しさ 「りくろーおじさんの店 焼きたてチーズケーキ」6.黒豆入りのマドレーヌ 「ええもんちぃ」ワンランク上の見た目と味!大阪のおしゃれなお土産6選1.プレミアムなポテトチップス 「グランカルビー」2.パティシエが考えた新しい大阪土産 「瓢月堂 たこパティエ」3.お花のカタチのラングドシャ 「あみだ池大黒 大阪花ラング」4.健康や美容を気遣う方へ 「KAGOME GREENS Catch the Rainbow 食べるスムージー」5.日本で作ったフランス伝統のお菓子 「カヌレ堂CANELÉ du JAPON カヌレ」6.大阪発の新しいチーズスイーツ 「ウメダチーズラボ クッキー」

TポイントがVポイントに変わる?

Tポイントを持っている人が多いので、気になっている人は多いのではないですかね?ただ、実際どうなるのか調べていない人が大半なのではないでしょうか。そんな中の一人の私が、調べてみました。参考サイトhttps://web.tsite.jp/vpoint/質問形式いつから変更されるの?→2024年4月22日Tカードは使えなくなるの?→そのまま利用ができる何かしないといけないの?→必要なしVポイントに変わると何がある?→利用できる店舗が増える(すき屋、なか卯、LOTTELIAなど)変更点は?→「Tポイント」アプリをアップデートすると、「VポイントPay」アプリに変更される(予定)感想Tポイントは、今までポイントカードだったが、「VポイントPay」としてQRコード決済できるようになると利用の仕方が変わるかも。後、TポイントからVポイントへの手続きが一番気になるポイントだったが、カードはそのまま、アプリはアップデートする事で、そのまま利用できるのであれば、とてもスムーズで良いと思いました。気になる点としては、TポイントはPayPayなどのアプリで連携しているので、そこは再設定など必要になるかが少し気になりました。

私の近所にある複数のスーパーのメリット・デメリットを書き出してみた

2024年3月12日に書いています。最近、色々なスーパーに足を運ぶことが多いので足で稼いだ情報を整理しています。私の通っているスーパーなので、他の店舗とサービスが違うかもしれません。最初に現状の推しを言っておくと「マルエツ」です。理由は、セルフレジや、Scan&Goなど、スーパーで一番不満になる「待ち時間」について、新しいサービスを早く導入して緩和をしようとしているからです。ただ利用頻度で言うと、「サミット」、「ハナマサ」を多く利用しています。候補5つ①マルエツ②ピーコック③サミット④まいばすけっと⑤ハナマサ①マルエツ【メリット】セルフレジが充実Scan&Goが導入されていて、スマホで商品バーコードをスキャンしてすぐに決済ができるヨーグルトの種類が多く比較的安いTポイントが付く ※Scan&Goのポイントと併用は不可冷凍食品が充実しているパンが充実している【デメリット】2階建て ※生鮮食品などが2階にあり上らなくてはならない買い物カートが利用しにくい ※たくさんの買い物がしにくい②ピーコック【メリット】100円ショップが一緒にあるイオンペイが使える基本は1フロアで欲しい物が揃う ※文房具や日用品は地下時々、アナログの割引券を配っている ※分かりやすく行く動機になる冷凍食品が充実している【デメリット】イオンペイ以外のQRコード決済が使えないセルフレジがないイオン系列の安価な商品が置いていない③サミット【メリット】駐車場がある1フロアで全ての買い物ができる買い物カートを利用しやすい試食をやっていて、かつ気軽に食べやすい季節に合ったサービスを積極的に行っているヨーグルトの種類が多く比較的安いパンが充実している【デメリット】セルフレジがない独自の決済カードのチャージが現金でしかできない④まいばすけっと【メリット】良い意味でコンパクト ※ピンポイントの買い物は利用しやすいイオン系列の安価な商品を購入できるイオンペイが使えるセルフレジがある【デメリット】商品が少ないイオンペイ以外のQRコード決済が使えない⑤ハナマサ【メリット】肉、牛乳、卵、トマトなど、必需品で他に比べて安い商品が多いクレジットのタッチ決済がある冬は比較的高い確率で、みかんの箱売りをやっている【デメリット】店が狭い、階段を下りるのが大変商品カートが使えないセルフレジがない

令和6年4月から"プラスチック"ゴミの出し方が変わる(墨田区版)

墨田区のチラシで、ゴミ捨て方法の変更の案内がきていたので確認してみます。ゴミ捨てについては、市区町村ごとにルールが違います。今回は、墨田区について確認していきます。内容令和6年4月から"プラスチック"ゴミの出し方が変わる詳細素材が全てプラスチックでできている製品を「プラスチック資源」として出す【プラスチックの例】食品トレー"プラマーク"の付いた包装・容器プラスチック製品(概ね30cm以内)【"プラマーク"の付いた包装や容器】※安全でキレイなプラスチック100%素材容器・キャップ類カップ・パック類チューブ類トレー(皿型容器)類食料品や日用品の袋類発砲スチロールなど【プラスチック製品(概ね30cm以内)】バケツ、じょうろストロー、スプーン歯ブラシハンガーなど【プラスチックとして回収できない物】軽くすすいでも汚れが付着しているもの刃物類・発火の危険があるもの在宅医療で使用したものプラスチック以外のものが付着しているものリサイクル整備の故障になるものペットボトル【二重袋に注意!】回収したプラスチックは、全て袋を破いて中身を確認しているため、家庭から出す際は袋などには入れず、そのまま出して欲しいとのこと。確認した感想試みは理解できるが、"プラスチック"の見分け方が複雑に感じるプラスチック資源の分別方法(見分け方)は、対処まで記載されていてGood!個別にルールを決めるのではなく、国で統一できないのだろうか?チラシ(PDF)https://www.city.sumida.lg.jp/kurashi/gomi_recycle/oshirase/pura_start.files/honnkakutirasi.pdf

ナレッジ?ここって何をするんですか?

説明が無いので良く分かんないのです…。 ちなみに画像は私の落描きです適当に貼りました…。

サッカーなどで利用できる都立公園のスポーツ施設がWeb予約可能になったのを知っていますか?

団体でスポーツする際、場所に困っていませんか?私は中野区でサッカーチームに参加しているのですが、場所の確保にはとても苦労しています。区などが管理しているグラウンドは、区に在住か在勤のメンバーを集めての団体登録が必要になるため、チームの中心になっている地域以外の場所を借りるのは、とても難しい状況だと思います。そんな私のような人に朗報で、2024/3/1から都立公園のスポーツ施設がWebから予約可能になりました。都立公園のスポーツ施設は、個人で利用者登録ができ、予約することが可能です。※以前は、施設に行かないと利用者登録ができませんでした、、都立公園スポーツレクリエーション予約システムhttps://kouen.sports.metro.tokyo.lg.jp/web/場所は3つ①代々木公園②高井戸公園③府中の森公園私も3月になって、早速登録して予約をしました。ただ、とても便利になったので、ライバルは多そうです...。なるべく確立の高そうな場所、時間を狙って予約しようと思いますー

「お好み鯛焼き」って知ってますか?

昨日、「お好み鯛焼き」を食べました初めて食べたのだけど、メチャ旨い!また食べたいので、場所をチェック。錦糸町駅のそば、丸井の中にありました。こちらは本店ですね!https://www.0101.co.jp/054/restaurant-menu/omedetaiyaki.htmlつぶあん、カスタードも気になるところ、、

東京都で3/11(月)よりQRコード決済で10%還元のキャンペーンが開始

来週の月曜から、東京都でQRコード決済のポイント還元キャンペーンが始まるので要チェックですね。ポイントを書き出していきます。公式ページhttps://kurashisupport.metro.tokyo.lg.jp/概要キャンペーン期間中に、都内の対象店舗において、対象のQRコード決済を行うと、後日、決済額の最大10%(上限3,000円相当)のポイントを還元!詳細キャンペーン期間2024/3/11(月)~2024/3/31(日) ※早期終了の可能性あり内容対象店舗での決済で10%のポイントが還元されるポイントの付与は後日ポイント付与の上限は、3,000円相当のポイント対象のQRコード(4つ)①PayPay②楽天Pay③au Pay④d払い対象者対象のQRコード決済を行った利用者(都民でなくてもOK)対象店舗キャンペーン開始日から、各QRコード決済のアプリ・HPで確認できるとのこと※以下の対象外を見ると、飲食以外も対象になりそうです!対象外の店舗国、地方公共団体、公共法人が管理運営する施設金融商品取引業者の店舗銀行、信託会社、保険会社等保険医療機関及び保険薬局等風俗営業等の規制及び業務の適正化等に関する法律第2条に該当する施設その他、本事業の目的・趣旨から適切でないと都が判断するもの間違いそうな点対象のQRコード決済ごとに上限3,000円相当のポイントが付与される※4つのQRコード決済を全て利用すると最大12,000円相当のポイントが受け取れる。

GKの"6秒ルール"が変わる?サッカーのルール改善案が下部カテゴリで試験導入の記事

3/4のYahoo記事からピックアップしました。GKの“6秒ルール”は8秒に増加して厳格化、違反時の再開方法も変更へ…IFABが下部カテゴリでの試験導入を承認https://news.yahoo.co.jp/articles/b4bba588cfe02e7dbf5c9261ce2495ec101f1c5dGKの"6秒ルール"とは?GKは、ボールをキャッチしてから6秒以内でボールを手や腕からはなさなければならない変更内容秒数を6秒→8秒に増加する主審は残り5秒から片手を挙げてカウントダウンする再開を「間接FK」→「CK」or「スローイン」に変更する変更の理由ゴール前からの間接FKとする現在の規定が主審に計測の迷いを生じさせているため。感想現在、私は40歳以上(シニア)のカテゴリーでサッカーをやっています。都リーグを始め、区のリーグ、その他の独自リーグなどにも参加しているが、このGKの「6秒ルール」でファールになっているのは見たことがないです。勝つための時間稼ぎは多発するので、導入は進んでいくと思いました。主審のカウントダウンも分かりやすくて良いので、現状のルールでも取り入れて良さそう。秒数は確かにきっちり計測してないので、6秒だと短いのか8秒が適切なのかは気になるところです。

Widgetテストにおける認証系のテスト

Widgetテストでは、エミュレーターや実機を使えないので、Firebaseに問い合わせるユーザー認証の機能が使えません。なので、モックのFirebaseAuthやGoogleSignInのパッケージを使って認証系のテストをします。https://pub.dev/packages/firebase_auth_mockshttps://pub.dev/packages/google_sign_in_mocksテストの流れFirebaseAuthとGoogleSignInのインスタンスのProviderをoverrideして、それぞれのモックのインスタンスに置き換えるAuthenticator のメソッドがモック化されたので、そのメソッドを使ってテストをするpackages/skimie/test/presentation/pages/login_page/components/signup_buttons_test.dartGoogleログインのWidgetテストを解説します。まず始めに、setUpにて、mockUserやmockGoogleSignIn、mockFirebaseAuthのインスタンスを用意します。このときに、mockFirebaseAuthには、mockUserのインスタンスを渡しておきます。 setUp(() async { final MockUser mockUser = MockUser( initialDisplayName: 'skimie_test', ); mockGoogleSignIn = MockGoogleSignIn(); mockFirebaseAuth = MockFirebaseAuth(initCurrentUser: mockUser); await Firebase.initializeApp(); });Widgetテストを開始するときに、対象のProviderをoverrideしてモック化します。ここでは、Googleログインのテストをしているので、そのメソッドがあるGoogleAuthenticatorをモック化しています。 testWidgets('Googleログイン', (WidgetTester tester) async { await tester.pumpWidget( ProviderScope( overrides: [ googleAuthenticatorProvider.overrideWithValue( GoogleAuthenticator( auth: mockFirebaseAuth, googleSignIn: mockGoogleSignIn, ), ), ], child: const MaterialApp( home: Material( child: SignUpButtons(animating: false), ), ), ), );SignUpButtonsをタップしたときには、FirebaseAuthのsignInWithCredentialを使っているので、モッククラス内でこのメソッドを修正します。 @override Future<UserCredential> signInWithCredential( AuthCredential credential, ) async { // ログインしたプラットフォームを確認 Log.i('ログイン : ${credential.signInMethod}'); final userCredential = MockUserCredential(); // currentUserを更新 currentUser = userCredential.user; return userCredential; } setUp内でcurrentUserにMockUserをセットしていましたが、このメソッドを走らせると、別のユーザーにcurrentUserが置き換わる様に修正しました。メソッドが走ったことをユーザー名が変更されていることで確認します。 // signInWithGoogleが呼ばれたことを確認 expect(loggedInCurrentUser.displayName, 'test_login_success');参考:https://qiita.com/mogmet/items/ade07bd842495192922dhttps://github.com/atn832/firebase_auth_mocks/blob/master/test/firebase_auth_mocks_test.darthttps://riverpod.dev/ja/docs/cookbooks/testing#%E3%83%97%E3%83%AD%E3%83%90%E3%82%A4%E3%83%80%E3%81%AE%E6%8C%99%E5%8B%95%E3%82%92%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%82%A4%E3%83%89%E3%81%99%E3%82%8B

インテグレーションテストについて

Flutter公式のドキュメントhttps://docs.flutter.dev/testing/integration-testsはじめに、テスト対象とするOSのバージョンとデバイスを決めてください。デバイスやOSバージョンの普及率などから、テスト範囲を決められるといいと思います。https://developer.apple.com/jp/support/app-store/packages/skimie/integration_test/test_setting.dart上記のファイルのコメントアウトを外すことで、テスト対象とすることができます。iOSの場合XcodeのインストールされているPCで行ってください。テストの流れ利用可能なエミュレータの情報を取得し、Mapに変換するテストに必要なデバイスがインストールされているかをチェックし、なければ追加するエミュレーターを起動し、テストするエミュレーターを終了し、キャッシュを削除する3. に戻り、テスト対象のOSバージョンとデバイスが終わるまで繰り返す1. 利用可能なエミュレータの情報を取得し、Mapに変換するfinal result = await Process.run('xcrun', ['simctl', 'list']);上記のコードで、使用可能なエミュレーターの一覧の文字列が取得できるの、正規表現を使って、Mapに変換する2. テストに必要なデバイスがインストールされているかをチェックし、なければ追加するtest_setting.dart でテスト対象にしているOSバージョンのデバイスが 1. で作成したMapに存在するかをチェックする。ない場合は、インストールする必要があるので、final result = await Process.run( 'xcrun', [ 'simctl', 'create', targetDevice, targetDevice, 'com.apple.CoreSimulator.SimRuntime.iOS-$major-$miner' ], );上記のコードで、追加する。3. エミュレーターを起動し、テストする// エミュレーター起動 await Process.run('xcrun', ['simctl', 'boot', device.id]);上記のコードで、エミュレーターを起動する。// テスト開始 final result = await Process.run( 'flutter', [ 'test', 'integration_test/app_test.dart', '--dart-define=FLAVOR=stg', '--dart-define=INTEGRATION_TEST=true', '-d', device.id, ], );上記のコードで、テストを開始する。packages/skimie/integration_test/app_test.dart に書かれているテストが実行される。4. エミュレーターを終了し、キャッシュを削除する// シミュレーター終了 print('Test Device Shut Down'); await Process.run('xcrun', ['simctl', 'shutdown', device.id]); // キャッシュの削除 await Process.run('xcrun', ['--kill-cache']);上記のコードで、エミュレーターを終了し、念のため、キャッシュを削除しておく。5. 3. に戻り、テスト対象のOSバージョンとデバイスが終わるまで繰り返す3〜5の操作をFor in で回しているので、指定されたテスト範囲が終了するまで、テストを回す。Androidの場合Android Studioをインストールしてある、PCで行ってください。iOSの場合とテストの流れは、大きくは変わりません。テストの流れ利用可能なエミュレータの情報を取得し、Mapに変換するテストに使用するPCのCPUアーキテクチャを確認しますテストに必要なデバイスがインストールされているかをチェックし、なければ追加する必要なシステムイメージがインストールされているかチェックする、なければ追加するエミュレーターを起動し、テストするエミュレーターを終了する。5. に戻り、テスト対象のOSバージョンとデバイスが終わるまで繰り返す1. 利用可能なエミュレータの情報を取得し、Mapに変換する final result = await Process.run('emulator', ['-list-avds']);上記のコードで、使用可能なエミュレーターの一覧の文字列が取得できるの、正規表現を使って、Mapに変換する2. テストに使用するPCのCPUアーキテクチャを確認します// CPUアーキテクチャを判別して、インストールするsystem imageを選択 Future<String> getSystemStringImagesType() async { if (Platform.isMacOS) { final result = await Process.run('sysctl', ['-n', 'machdep.cpu.brand_string']); if ((result.stdout as String).contains('Apple')) { return 'arm64-v8a'; } return 'x86_64'; } return 'x86_64'; }Widndowの場合は、'x86_64'になり、Macは、Apple Silicon(M1など)の場合があるので確認が必要。エミュレーターをインストールするときに指定する必要がある。3. テストに必要なデバイスがインストールされているかをチェックし、なければ追加するテストの対象になっているOSバージョンのエミュレーターがない場合は、インストールする。await Process.run( 'avdmanager', [ 'create', 'avd', '-n', id, '-k', 'system-images;android-$targetApi;google_apis_playstore;$systemImageType', '-d', targetDevice.deviceSetup, ], );上記のコードで、エミュレーターがインストールされる。4. 必要なシステムイメージがインストールされているかチェックする、なければ追加するテストの対象になっているシステムイメージのチェックをするfinal result = await Process.run('sdkmanager', ['--list']);上記のコードで、インストールされているシステムイメージのリストが取得できる。正規表現を使用して、文字列を加工し、テストに必要なシステムイメージがあれば、インストールする // テストに必要なsystem-imageをインストール for (final systemImage in filteredLines) { await Process.run('sdkmanager', [systemImage]); }5. エミュレーターを起動し、テストする// エミュレーター起動 final process = await Process.start( 'emulator', ['-avd', device.id, '-no-snapshot'], );上記のコードで、エミュレーターを起動。// テスト開始 final result = await Process.run( 'flutter', [ 'test', 'integration_test/app_test.dart', '--dart-define=FLAVOR=stg', '--dart-define=INTEGRATION_TEST=true', '--device-id', deviceId ], );上記のコードで、テストを開始する。packages/skimie/integration_test/app_test.dart に書かれているテストが実行される。6. エミュレーターを終了するawait Process.run( 'adb', ['-s', deviceId, 'emu', 'kill'], );上記のコードで、エミュレーターを終了。7. 5. に戻り、テスト対象のOSバージョンとデバイスが終わるまで繰り返す5〜6の操作をFor in で回しているので、指定されたテスト範囲が終了するまで、テストを回す。インテグレーションテストの追加繰り返しているテストは、packages/skimie/integration_test/app_test.dart に書かれているので、テストはこのファイルに追加する。intro_test や email_test の様にそれぞれのテストをディレクトリで分けて、追加していくと、可読性が良いかと思います。import 'package:authenticator/authenticator.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:integration_test/integration_test.dart'; import 'package:skimie/main.dart' as app; import 'login_page/email_login_test.dart' as email_login; import 'intro_page/intro_test.dart' as intro_test; // Test実行 // dart integration_test/test.dart // 実機やエミュレーターでテストする場合は、localeに気をつけてください。 // エミュレーターの場合、'en'になっていることが多いかと思います。 void main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('Integration Test', ( WidgetTester tester, ) async { // アプリ起動 await app.main(); await tester.pumpAndSettle(); // ログアウトしておく final container = ProviderContainer(); final authenticator = container.read(authenticatorProvider); await authenticator.signOut(); await tester.pumpAndSettle(); // イントロ画面の動作確認 await intro_test.test(tester); // Emailログインのテスト await email_login.test(tester); }); }

Firebase Authを用いた認証機能について

FirebaseAuthについては、丁寧なドキュメントがありますので、まずこちらを参考にしてみてください。https://firebase.google.com/docs/auth?hl=jaFlutterでの導入方法も説明されています。https://firebase.google.com/docs/auth/flutter/start?hl=ja各種認証に関しては、Fireabseのドキュメントにコード例も記載してあり、そのコードを利用するだけで、実装可能です。参考:https://zenn.dev/kazutxt/books/flutter_practice_introduction/viewer/30_chapter4_authenticationhttps://zenn.dev/flutteruniv_dev/articles/5f05c75b070b38https://zenn.dev/flutteruniv_dev/articles/25ae47164a1d44

SNS共有機能について

https://zenn.dev/taminaryosuke/articles/fda7ad009a6c69SNS共有機能は、上記の記事を参考にしました。

Flutter おすすめナレッジ

Flutter開発で便利なナレッジを紹介します。カラー色が少ない場合は、Flutterにデフォルトで用意されているClorsクラスから指定すればいいのですが、細かな指定がある場合(カラーコードなど)は、その都度調べて書いていては、間違う可能性もあり、何より面倒です。なので、アプリ全体で色を管理するクラスを始めに作成することが多いです。import 'dart:ui'; class AppColor { const AppColor._(); static const defaultBlack = black900; static const red = Color(0xFFE00000); static const white = Color(0xFFFFFFFF); static const black100 = Color(0xFFE6E6E6); static const black200 = Color(0xFFCCCCCC); static const black300 = Color(0xFFB3B3B3); static const black400 = Color(0xFF999999); static const black500 = Color(0xFF808080); static const black600 = Color(0xFF666666); static const black700 = Color(0xFF4D4D4D); static const black800 = Color(0xFF333333); static const black900 = Color(0xFF1A1A1A); }テキストスタイルフォントの指定や、テキストサイズの指定などは、アプリ内でそれほど多くの種類があるわけではないので、こちらもカラーと同じように、アプリ全体でテキストスタイルを管理するクラスを作成した方が間違うことがなく、統一感も出ると思います。下のように定義しても、適宜、修正することも可能です。import 'package:flutter/material.dart'; import 'app_color.dart'; class AppTextStyle { const AppTextStyle._(); static TextStyle get _base => const TextStyle( color: AppColor.defaultBlack,                 // フォントの指定がある場合は、ここに追加する ); static TextStyle get style8 => const TextStyle( fontSize: 8, height: 16 / 8, fontWeight: FontWeight.w300, ).merge(_base); static TextStyle get style12 => const TextStyle( fontSize: 12, height: 20 / 12, fontWeight: FontWeight.w400, ).merge(_base); static TextStyle get style16 => const TextStyle( fontSize: 16, height: 24 / 16, fontWeight: FontWeight.w400, ).merge(_base); static TextStyle get style20 => const TextStyle( fontSize: 20, height: 28 / 20, fontWeight: FontWeight.w500, ).merge(_base); } Text( num.toString(), style: AppTextStyle.style20.copyWith( color: AppColor.red, ), ),

in_app_purchase を用いたアプリ内課金について

アプリ内課金に使用しているパッケージhttps://pub.dev/packages/in_app_purchase実装例https://github.com/flutter/packages/blob/main/packages/in_app_purchase/in_app_purchase/example/lib/main.dartSkimieでは、課金のメソッドをNotifierProviderで管理しています。アプリ内課金を実装するためには、Google Play Console とApp Store Connect(Apple)の設定が必要です。それぞれのプラットフォームの設定は、Google :https://developer.android.com/google/play/billing/getting-ready?hl=jahttps://www.revenuecat.com/docs/android-productsApple:https://www.revenuecat.com/docs/ios-productsアプリ内課金の流れアプリ内購入が利用可能な状態なのか確認する購入品の製品IDのSetを用意する。それぞれのストアに問い合わせて製品情報を取得する上記で取得した製品情報を使って購入処理を行う購入処理進行を監視して、購入が完了した後にバックエンドでレシートの検証を行う検証が成功した場合、ジェムをユーザーに付与する(課金の対価を付与する)返ってきた検証結果に応じて、通知を行う購入のトランザクションを完了させるNotifierProviderで持つ状態と上記の順番に沿って説明を進めます。全体のコードを確認するには、/skimie/lib/services/app_purchase/app_purchase_provider.dartを参照してください。Stateとしては、下記の状態を定義しました。@freezed class AppPurchaseState with _$AppPurchaseState { const AppPurchaseState._(); const factory AppPurchaseState({ // 課金利用の可否 @Default(false) bool isAvailable, // バックエンドから取得した課金商品情報(productIdや購入品Image) Items? purchaseItems, // AppleやGoogleのプラットフォームから取得した課金商品の情報 @Default([]) List<ProductDetails> products, // 課金処理の状態 @Default(false) bool purchasePending, }) = _AppPurchaseState; ProductDetails? getProductDetail(Item item) { final productId = item.code; return products.firstWhereOrNull((e) => e.id == productId); } }purchasePending 以外は、全てAPIから取得した値を定義しています。getProductDetailsのメソッドは、OpenAPIで定義されたItemの型の中にある製品IDに該当するproductDetailsをリストの中から取得するメソッドです。単回購入処理のときに使います。1. アプリ内購入が利用可能な状態なのか確認する // アプリ内購入が利用できるかを確認 final isAvailable = await _inAppPurchase.isAvailable(); // アプリ内購入が利用できない場合 if (!isAvailable) { Log.e('アプリ内課金が利用できません。'); state = state.copyWith( isAvailable: isAvailable, ); return; }ストアにアクセスできない、デバイスがGoogleにログインしていないなど、利用不可の場合は、この先の処理には進むことはないです。2. 購入品の製品IDのSetを用意する。 // 課金商品の情報を取得 final purchaseItems = await _purchaseService.getProductIds(); final productIds = purchaseItems.items.map((p0) => p0.code).toSet();バックエンドから、購入品のItemsのデータを取得し、製品IDのSetを作成します。3. それぞれのストアに問い合わせて製品情報を取得する // 課金処理に使用する商品一覧を取得 final response = await _inAppPurchase .queryProductDetails({...productIds, 'android.test.purchased'});このAPIは、1. で作成した製品IDに該当する購入品の情報を Google Play Console や App Store Connect から List<ProductDetails> という型で取得することができます。ProductDetailsからは、購入品のid や title、description、price などの情報が取得できます。4. 上記で取得した製品情報を使って購入処理を行うこの処理はUI側で行われる処理です。 // アプリ内購入品のリスト List<Widget> purchaseItemList = []; final purchaseItems = ref.watch(appPurchaseServiceProvider .select((s) => s.purchaseItems?.items.toList())) ?? []; final isAvailable = ref.watch(appPurchaseServiceProvider.select((s) => s.isAvailable)); for (final item in purchaseItems) { final product = ref.watch(appPurchaseServiceProvider).getProductDetail(item); if (product == null) continue; purchaseItemList.add( ActionBtn( label: "", disabled: !isAvailable, onPressed: () async { await ref.read(appPurchaseServiceProvider.notifier).buyProduct( context, product: product, ); }, color: Colors.amber, width: (width - marginValue * 2 - 80) / 3, height: (width - marginValue * 2 - 80) / 3, image: ProductImage( item: item, displayCount: product.title.getNumInTarget, ), ), ); }appPurchaseServiceProviderのStateから、purchaseItemsを取得し、for in で回します。各item に該当するProductDetailsを取得して、notifierのbuyProductに渡します。AppPurchaseServiceクラス内の単回購入のメソッド、 // 単回購入 Future<void> buyProduct( BuildContext context, { required ProductDetails product, }) async { _context = context; state = state.copyWith(purchasePending: true); final purchaseParam = PurchaseParam(productDetails: product); await _inAppPurchase.buyConsumable( purchaseParam: purchaseParam, autoConsume: _kAutoConsume, ); }各プラットフォームのアプリ内購入が実行されます。※ autoConsume について、デフォルトがtrueになっており、消耗型の購入は、購入と同時にトランザクションが閉じられます。https://techblog.booklista.co.jp/entry/2023/09/29/130633#%E8%B3%BC%E5%85%A5%E5%87%A6%E7%90%86%E3%81%AE%E6%9B%B4%E6%96%B0%E3%82%92listen%E3%81%99%E3%82%8B:~:text=%E3%81%99%E3%81%B9%E3%81%8D%E7%82%B9-,Android,-%E3%81%A7%E3%81%AF%E8%B3%BC%E5%85%A5%E3%82%A2%E3%82%A4%E3%83%86%E3%83%A05. 購入処理進行を監視して、購入が完了した後にバックエンドでレシートの検証を行う /// 購入の処理を監視する Future<void> listenPurchaseUpdated() async { final Stream<List<PurchaseDetails>> purchaseUpdated = _inAppPurchase.purchaseStream; _subs = purchaseUpdated.listen( (List<PurchaseDetails> purchaseDetailsList) { // 購入などが行われた場合の処理 _listenToPurchaseUpdated(purchaseDetailsList); }, onError: (Object error) { Log.e('Error: ${error.toString()}'); errorNotification(msg: _l10n.errorPurchase); }, ); }Streamにて、購入が行われるとpurchaseDetailsList が自動で返される処理になります。返されたpurchaseDetailsListを_listenToPurchaseUpdatedに渡して、検証する流れになります。 // 購入処理が行われた場合にそのトランザクションを処理する void _listenToPurchaseUpdated( List<PurchaseDetails> purchaseDetailsList, ) { purchaseDetailsList.forEach( (PurchaseDetails purchaseDetails) async { if (purchaseDetails.status == PurchaseStatus.pending) { state = state.copyWith(purchasePending: true); } else { // 購入処理がエラーの場合 if (purchaseDetails.status == PurchaseStatus.error) { Log.e('Error: ${purchaseDetails.error}'); errorNotification(msg: _l10n.errorPurchase); } // 購入がキャンセルされた場合 if (purchaseDetails.status == PurchaseStatus.canceled) { successNotification(msg: _l10n.canceledPurchase); } // 購入処理が完了、または復元された場合 if (purchaseDetails.status == PurchaseStatus.purchased || purchaseDetails.status == PurchaseStatus.restored) { // レシートの検証をする final receipt = purchaseDetails.verificationData.localVerificationData; final valid = await _purchaseService.postPurchaseReceipt( receipt: base64Encode(utf8.encode(json.encode(receipt))), itemMasterCode: purchaseDetails.productID, ); if (valid) { // レシートの検証が成功時の処理 successNotification(); // 購入完了の処理 if (purchaseDetails.pendingCompletePurchase) { await _inAppPurchase.completePurchase(purchaseDetails); } } else { // レシートの検証が失敗時の処理 errorNotification(msg: _l10n.errorPurchase); } state = state.copyWith(purchasePending: false); } } }, ); }受け取った、purchaseDetailsListは、forEachで回され、個別の状態に応じて処理をする。purchaseDetails.status == PurchaseStatus.purchased購入済みの状態であれば、  // レシートの検証をする   final receipt = purchaseDetails.verificationData.localVerificationData;レシートを取得することができるので、バックエンドに渡して、レシート検証をしてもらう。6. 検証が成功した場合、ジェムをユーザーに付与する(課金の対価を付与する)バックエンドの処理。7. 返ってきた検証結果に応じて、通知を行うレシート検証から返ってきた値(valid)によって、成功、失敗の通知を行う。8. 購入のトランザクションを完了させる // 購入完了の処理 if (purchaseDetails.pendingCompletePurchase) { await _inAppPurchase.completePurchase(purchaseDetails); }トランザクションを完了していないpurchaseDetailsは、完了の処理を行う。バックエンドの処理が失敗した時の対策https://techblog.booklista.co.jp/entry/2023/09/29/130633こちらの記事で紹介されている方法で実装。 // 未完了のレシートを検出して再検証する Future<void> reCheckPendingTransaction() async { // 未完了のレシートを格納するリスト final List<PurchaseDetails> pendingReceiptList = []; // iOSの場合 if (Platform.isIOS) { final paymentQueueWrapper = SKPaymentQueueWrapper(); final transactions = await paymentQueueWrapper.transactions(); for (final transaction in transactions) { // TODO:transactionから未完了のPurchaseDetailsを取得する } } // Androidの場合 if (Platform.isAndroid) { final androidAddition = _inAppPurchase .getPlatformAddition<InAppPurchaseAndroidPlatformAddition>(); // 過去の購入履歴を取得 final response = await androidAddition.queryPastPurchases(); for (final purchaseDetails in response.pastPurchases) { if (purchaseDetails.pendingCompletePurchase) { pendingReceiptList.add(purchaseDetails); } } } // 未完了の購入トランザクションがあったときにはレシートを再検証する if (pendingReceiptList.isNotEmpty) { pendingReceiptList.forEach( (PurchaseDetails purchaseDetails) async { // 購入処理が完了、または復元された場合 if (purchaseDetails.status == PurchaseStatus.purchased || purchaseDetails.status == PurchaseStatus.restored) { // レシートの検証をする final receipt = purchaseDetails.verificationData.localVerificationData; final valid = await _purchaseService.postPurchaseReceipt( receipt: base64Encode(utf8.encode(json.encode(receipt))), itemMasterCode: purchaseDetails.productID, ); if (valid) { // 購入完了の処理 if (purchaseDetails.pendingCompletePurchase) { await _inAppPurchase.completePurchase(purchaseDetails); } } } }, ); } }リチェックの処理で参考になるレポジトリ:https://github.com/erase2004/InstantCoffee/blob/9278973eb13f70e04653076a2cf651dc13b0cce5/lib/helpers/iap_subscription_helper.dart#L33

Firebase Cloud Messaging について

Push通知を実装するために、Firebase Cloud Messaging が使われています。https://firebase.google.com/docs/cloud-messaging?hl=jaFlutterのドキュメントhttps://firebase.google.com/docs/cloud-messaging/flutter/client?hl=jaPush通知の実装では、主に4つの機能を実装する必要があります。通知の権限許可をとるFCMトークンを取得するフォアグラウンドでの通知を監視するバックグラウンドでの通知を監視するSkimieでは、Push通知関連の機能をStateNotifierで管理しています。packages/skimie/lib/services/messaging/messaging_provider.dart参考にしたレポジトリ:https://github.com/altive/flutter_app_template/tree/main/packages1. 通知の権限許可をとる /// 通知権限の確認 Future<void> requestPermission() async { // Androidの場合 if (Platform.isAndroid) { await _flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>()! .requestNotificationsPermission(); } // IOSの場合 if (Platform.isIOS) { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { return _messaging.requestPermission( alert: true, announcement: false, badge: true, carPlay: false, criticalAlert: false, provisional: false, sound: true, ); }); } // stateの更新 await refetchSettings(); }通知の権限を確認します。2. FCMトークンを取得する // デバイスに一意のFCMトークンを登録する // - ログイン後の画面で実施すること // - idToken は変わる場合があるらしいため、ウォッチして変わったら更新すること Future<void> saveFcmToken() async { final fcmToken = await _messaging.getToken(); Log.i('FCM Token: $fcmToken'); if (fcmToken == null) return; try { final deviceService = DeviceService(); await deviceService.save(fcmToken); } catch (e) { Log.e(e.toString()); } } // FCMトークンの変更を監視する void tokenRefreshListen() { final deviceService = DeviceService(); try { _tokenStream = _messaging.onTokenRefresh.listen( (fcmToken) async { await deviceService.save(fcmToken); }, ); } catch (e) { Log.e(e.toString()); } }FCMトークンを取得します。このトークンは変更される可能性があるので、変更を監視し、変更があった場合には、再度登録し直す様にしています。3. フォアグラウンドでの通知を監視する /// 通知を監視する Future<void> notificationListen(BuildContext context) async { FirebaseMessaging.onMessage.listen( (RemoteMessage message) { final text = message.notification?.body ?? ''; showNotificationToast( context, text: text, onPressed: () { debugPrint("OnPressed."); }, ); }, ); }フォアグラウンドでの、通知を監視している。通知を受け取った場合にlisten 以下が発火し、showNotificationToastに、通知のメッセージを渡して、実行している。4. バックグラウンドでの通知を監視する /// バックグランド設定 void onBackgroundMessage() { FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); } // バックグランドで受け取った通知の処理 @pragma('vm:entry-point') Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async { final notification = message.notification; Log.i('PUSH Message(Back Ground): ${notification?.body}'); }アプリが閉じている時の通知を監視している。1〜4の処理は、通知を初期化する関数で、実行されるようになっている。プラットフォーム側の設定https://zenn.dev/flutteruniv_dev/articles/flutter_push_notification?redirected=1(古くなっている可能性もあります、最新を確認してください。)通知アイコンの変更https://qiita.com/mkosuke/items/45dd14c61b633efcfd17https://firebase.google.com/docs/cloud-messaging/android/topic-messaging?hl=ja#edit-the-app-manifest

Flutterの状態管理 riverpod について

参考になる資料としては、有料ですが、https://zenn.dev/riscait/books/flutter-riverpod-practical-introductionこちらの本が、ほぼ全てが網羅されています。Skimieでよく使われているのが、Provider と FutureProviderになります。あと、NotifierProvider (StateNotifierProviderとも呼ばれる)は、一番多用されるものになるので、最後に説明します。Provider(基本的には) 変更されない値(インスタンス)を持つことができます。final firebaseAuthProvider = Provider((ref) => FirebaseAuth.instance);上記のコードでは、FirebaseAuthクラスのインスタンスを持つProviderを作成しています。riverpod_generatorでは、省略形で書くことができます。(このナレッジでは省略形は書きません。)Providerは、単体で使うこともありますが、他のProviderと組み合わせることもできます。final authenticatorProvider = Provider<Authenticator>((ref) {   return Authenticator(     auth: ref.watch(firebaseAuthProvider),     appleAuthenticator: ref.watch(appleAuthenticatorProvider),     googleAuthenticator: ref.watch(googleAuthenticatorProvider),   ); });上記のコードのように、先ほどのFirebaseAuthのインスタンスのProviderを別のProviderの中で呼び出すことができます。FutureProvider変更されない値を非同期的に処理することができるProviderです。非同期処理を、Loading / error / data と処理の進行と結果に応じて返す値を変えてくれます。APIを叩く時や時間がかかる処理の時に、使われるProviderです。final resourceProvider = FutureProvider<Resources>((ref) async { return ResourceService().find(); })UIで使用するときには、final state = ref.watch(eventPageNotifierProvider); state.when( // ローディング時 loading: () { return const LoadingIndicator(); }, // エラー時 error: (error, __) { Log.e(error.toString()); // リロードアイコン return Container( margin: const EdgeInsets.all(kSkimieMargin), child: ReloadApiBtn( onPressed: () => ref.read(eventPageNotifierProvider.notifier).refresh(), ), ); },                 // データ取得時 data: (state) { final padding = MediaQuery.of(context).padding; final activeTab = state.activeTabType; List<Widget> children = [];非同期の処理中の状態に応じてUIを変えることができる。NotifierProviderState(状態)と、それを操作するNotifierを持つProviderです。非同期な初期化をする場合は、AsyncNotifierProviderを使用することもあります。状態を定義しますimport 'package:freezed_annotation/freezed_annotation.dart'; part 'state.freezed.dart'; @freezed class HomePageState with _$HomePageState { const factory HomePageState({ @Default(0) int num, }) = _HomePageState; }Freezedを使います。Freezed + StateNotifier はセットで使います。https://pub.dev/packages/freezedこの状態を持ったStateNotifierを継承したクラスを定義します。class HomePageNotifier extends StateNotifier<HomePageState> { HomePageNotifier() : super(const HomePageState()); void plus() async { state = state.copyWith(num: state.num + 1); } void minus() async { if ((state.num - 1) < 0) return; state = state.copyWith(num: state.num - 1); } }クラス内に、Stateを操作するメソッドを追加することができます。このクラスをAutoDisposeStateNotifierProviderに持たせます。final homePageNotifierProvider = AutoDisposeStateNotifierProvider<HomePageNotifier, HomePageState>((ref) { return HomePageNotifier(); });NotifierProviderは、使い方によって、autodisposeのあるなしを決めた方がいいです。あるUIのViewModelとして、使用する場合は、autodisposeをつけることで、参照されなくなったときに自動でインスタンスを破棄してくれます。autodisposeをつけない(一度参照されるとアプリを停止するまで、存在し続ける)例としては、user情報を保持するものや、Skimieだとジェムやコインの状態を保持するProviderは、アプリが動いている間は、いろいろなページで参照される可能性があるので、破棄しない方針の方が良いと思われます。レポジトリーを参照:https://github.com/HI-0123/riverpod_explanation/tree/main

人気ボードゲームが漫画化『ボルカノス』サンデーうぇぶりで連載開始!

ボードゲームが他のメディアに進出するというと、デジタルゲーム化がイメージしやすいかなと思います。小説化したり舞台化したのもあったりします。今回ご紹介するのは漫画化の話題です。「Kaiju on the Earth」シリーズの『ボルカノス』が漫画化、連載開始しました。電ファミニコゲーマーで記事が出ていますので、まずはそちらをどうぞ。「怪獣映画の登場人物になったかのような体験ができる」人気ボードゲーム『ボルカルス』マンガ版の連載がスタート。謎めいた第1話で今後の展開が気になるhttps://news.denfaminicogamer.jp/news/231225c『ボルカノス』どんなゲーム?漫画の内容の前に原作の『ボルカノス』がどんなゲームかご紹介。アークライトとドロッセルマイヤーズによって共同企画された怪獣戦略ボードゲームシリーズ「Kaiju on the Earth」の一作目が『ボルカノス』です。富士火口に突如現れた全身から溶岩を噴出する怪獣「ボルカノス」に対し日本政府は「怪獣災害緊急対策本部」を設置。市民の避難、火災消火、自衛隊を配備しての防衛を行いながら怪獣を調査し撃退方法を探します。1人の怪獣役と1~3人の人間チームとに分かれて遊ぶ非対称対戦ゲームとなっており、怪獣は破壊、人間チームは防衛によって獲得できるポイントを競います。そのターンの行動を事前にカード伏せて計画してから実行に移すという進行をするので、怪獣映画の作戦会議シーンのような会話が自然に発生するのが楽しい戦略ゲームです。Kaiju on the Earth 公式サイトhttps://kaijuontheearth.com/vulcanus/漫画ではどうなるの?ボードゲーム『ボルカノス』は「怪獣が出現!街を守れ!」というのがゲーム本編で、怪獣映画などで言われる「人間ドラマパート」のようなストーリーラインはありません。つまり、オリジナルストーリーになりますね。公開済みの第1話では、幼少の頃からボードゲームを作って今では売れないクリエイターとなった主人公が、友人の結婚式の帰りに渋谷で幼馴染と再会。そこに突如現れるボルカノスですが、幼馴染はその存在をなぜか事前に察知しており……といった具合で、謎めいたストーリーを予感させます。個人的には怪獣VS自衛隊みたいな漫画を創造していたので、第1話のラストには「こう来るの!?」と驚かされました。さりげなく描かれているボードゲームや行動の予測など、今後の展開によっては細かな要素で原作の雰囲気を出してきそうなところも気になりますね。サンデーうぇぶり『Kaiju on the Earth ボルカルス』https://www.sunday-webry.com/episode/14079602755571963643

鉄のリサイクルをボードゲームに『鉄の転生すごろく』本日22時に「積分サークル」とのコラボ動画公開

今回はちょっと変わったボードゲームの話題です。鉄のリサイクルをモチーフにしたボードゲーム『鉄の転生すごろく』が、株式会社カヤック、一般社団法人日本鉄鋼連盟、株式会社デイリースポーツ案内広告社がタッグによって制作されました。プロモーションの一環として、理数系の企画動画で人気のYouTubeチーム「積分サークル」とコラボした動画が本日22日19時30分に公開されるとのことです。PR TIMESにプレスリリース記事が出ていますのでまずはそちらをどうぞ。リサイクルのループを表現したボードゲーム「鉄の転生すごろく」誕生!面白法人カヤックが(一社)日本鉄鋼連盟とタッグを組み、遊びながら鉄のリサイクル優位性を啓蒙するアイテムを製作https://prtimes.jp/main/html/rd/p/000000694.000014685.html『鉄の転生すごろく』どんなゲーム?『鉄の転生すごろく』は、鉄がリサイクルによってさまざまな製品に生まれ変わることを表現したすごろくゲームです。各プレイヤーはゲーム開始時に目標とする鉄製品をカードでランダムに決めます。「∞」の形のコースをサイコロを振って進み、イベントマスに止まったらカードを引いて様々な効果を受けます。転生マスにたどり着くと転生カードを引き、目標の鉄製品に転生できれば勝利となります。転生した製品ごとにコマが用意されており、最初のコマであるスチール缶を含めて8種類のコマがあります。鉄は磁石を使えば選別しやすく、リサイクル率が高い素材であるという特徴を表現したゲームになっています。プレイ動画が積分サークルYouTubeで22日19時30分より公開されるので、ルールが気になる方はそちらもどうぞ。「鉄の転生すごろく」紹介映像https://www.youtube.com/watch?v=kiHnZ4XFqQY積分サークルYouTube(「鉄の転生すごろく」プレイ動画) https://youtu.be/aG1Oi4obHDoイベントや期間限定で遊べる。かも『鉄の転生すごろく』はすごろくのコースもコマもサイコロも鉄を使っているようで、一般販売はされないものと思われます。現在は一般社団法人日本鉄鋼連盟で1体のみ保管しているとのこと。一般人では触れないボドゲという訳ですが、その辺りは今後普及に取り組んでいくそうです。まだ具体的な情報は出ていませんが、将来的にはイベントや期間限定で出張して遊べるようにする予定とのことなので、一点ものの珍しいボドゲに触ってみたい方はサイトをチェックしてみてはいかがでしょうか。鉄くる(JISF) Webサイト(「鉄の転生すごろく」紹介) https://tetsukuru.com/sugoroku/

磁石を使った人気ゲーム『Kluster(クラスター)』がリニューアル+パワーアップ!『Kluster DUO(クラスター・デュオ)』先行予約開始

皆さんは、Amazonなどで磁石を使ったオモチャが規制されたという話を聞いたことがありますか?小さなボール状の磁石をくっつけて様々な形を作れるオモチャがあったのですが、なんでもこれを子供が誤飲した場合が厄介で、お腹の中で内蔵を挟んだ状態でくっついて取り出すために手術が必要になってしまうとか。そんな訳で磁石の取り扱いに制限がかかったらしいです。この影響はボードゲームにも出ており、紐で囲われたエリアの中に磁石を置いていくアクションゲーム『Kluster(クラスター)』が日本では販売禁止となっていました。そんな『Kluster(クラスター)』ですがこのたび誤飲対策に加えたパワーアップ版『Kluster DUO(クラスター・デュオ)』として販売再開するとのことです。PR TIMESにプレスリリース記事が出ていますのでまずはそちらをどうぞ。人気マグネットアクション・ボードゲーム「Kluster(クラスター)」が新たな魅力を追加しパワーアップして登場!https://prtimes.jp/main/html/rd/p/000000004.000060111.html『Kluster DUO(クラスター・デュオ)』どんなゲーム?『Kluster DUO』は『Kluster』同様に紐と磁石を使用したアクションゲームです。紐で囲われたエリア内に磁石を置いていき、手持ちを全て置けたら勝利というシンプルなゲームです。ただし、磁石同士がくっついてしまうとくっついた分は手持ちに加わってしまいます。ちなみに紐を動かしてエリアを変形させたり、既に置かれている磁石を移動させるというテクニックもアリらしいです。『DIO』では磁石の形が変更されて、釣りの浮きのような細長い楕円形になりました。この変更は最初に書いたように誤飲対策が理由のようです。ただ、この変更によって予測しづらい転がり方をするため、ゲームがさらにスリリングで戦略的になったとのこと。怪我の功名ですね。『Kluster(クラスター)』と混ぜても遊べる旧バージョンの『Kluster(クラスター)』を持っていれば、2つのバージョンを混ぜて遊ぶこともできるようです。異なる大きさ、異なる形の磁石が混ざった状態で遊ぶのでより一層複雑で奥深いプレイが可能になります。現在公式ショップにて予約販売が開始されており、2024年1月末までに手元に届く予定です。旧バージョンにハマったファンは新バージョンもゲットしたいところですね。販売サイトhttps://www.r-enterprise.jp/products/kluster-duo

”良い感じ”なデジタルゲームの話をしよう『Undying』

”良い感じ”なデジタルゲームの話をしよう第35回『Undying』ゲームの情報って発売決定とか開発決定って瞬間にワーっと流れるんですが、リリースまで間があると忘れてます。100%忘れます。そりゃ夏くらいに「発売は冬!」みたいに言われて半年近く期待を維持できる人の方が少ないでしょ。つまり、ニンテンドーダイレクト方式ができるニンテンドーは強い。という話ではなく、だいぶ前にゲームのコンセプトが発表されて「お、良いじゃん」と思ってたゲームが今月リリースされていたのでその話をしたいと思います。Steam『Undying』https://store.steampowered.com/app/638990/_/ゾンビに噛まれた母。息子に生きる術を託す今回は『Undying』です。ゾンビが蔓延るようになってしまった世界で一組の母子が辿る運命を描くストーリーです。プレイヤーは母親として息子を守りながら、彼に生き残る術を教えていきますが残された時間は長くありません。なぜならすでに自分はゾンビに噛まれてしまっているから。という、非常に追い詰められた状況のゲームです。ゾンビに囲まれて死んでもリスポーンできる雰囲気ではないですね。このコンセプトが息苦しくも母の強さも見えて良い緊張感がありますね。ゾンビ化するまでの時間を何とか遅らせつつサバイバル生活をする中、息子は資源の収集方法、戦い方、料理など生きる術を学んでいきます。早期アクセスが2021年から続いていましたが、正式リリースに伴いストーリー部分が追加されたようなので、個人的に一番楽しみな部分が揃ったという感じです。現在ローカライズに難ありただ、直近のレビューは不評気味。ゲーム性に関する不満では若干操作が重たいという声もあるようですが、それ以上に目立っているが日本語ローカライズの低品質さへの不満です。恐らく機械翻訳だろうと見られ、キャラクターの一人称が不安定だったり情感がなかったりと、ストーリーに魅力を感じて手にした人には致命的な状態のようです。来年春にはswitch版がリリースされる予定らしいので、それに向けて日本語版アップデートが行われるかも知れません。という訳で、日本語が直ってから評判を見て買おうかな……来年春か、忘れてそうだなー。

新作マーダーミステリー『赤ずきん、舞踏会で死体と出会う。』本日発売!ゲームマーケット2023秋で先行販売・完売した作品が正式リリース

今回は数年前にブームが来て以来、根強い人気を獲得しているマーダーミステリージャンルの新作の話題です。ボードゲーム・アナログゲームというとちょっと趣が違うと感じる人もいそうですが、ゲムマでも1つのジャンルとして売られています。本日リリースされる新作マーダーミステリー『赤ずきん、舞踏会で死体と出会う。』はゲムマ2023秋で先行販売され、完売した作品です。正式リリースに合わせてPR TIMESでプレスリリース記事が出ていますので、まずはそちらをどうぞ。ゲームマーケットにて完売したマーダーミステリーゲーム『赤ずきん、舞踏会で死体と出会う。』(双葉社)発売開始!https://prtimes.jp/main/html/rd/p/000000420.000014531.html『赤ずきん、舞踏会で死体と出会う。』どんなマダミス?『赤ずきん、舞踏会で死体と出会う。』はNetflixで実写映画化もされたミステリー作品です。今回ご紹介するマーダーミステリー版は原作小説の第一章を原案にしたオリジナルストーリーとのこと。小説版第一章『ガラスの靴の共犯者』で犯人を無事捕まえた赤ずきんは、その功績を認められ王子様に舞踏会へ招待されます。貴族、町の住人、使用人など多くの人が集まった舞踏会の日、城内で庭師の死体が見つかります。探偵として実績のある赤ずきんは、4人の容疑者たちと協力して犯人探しを行うことに。この4人の協力者というのをプレイヤーが担当するようです。詳しくは、公式サイトをご参照ください。『赤ずきん、舞踏会で死体と出会う。』公式サイトhttps://www.futabasha.co.jp/introduction/aoyagi/madamisu.html協力型シナリオも収録本作にはボーナスコンテンツとしてwebゲーム『ガラスの靴の共犯者』を楽しめる二次元コードが封入されているそうです。原作小説の第一章をモチーフにした内容のようで、こちらは協力型のゲームとなっています。マーダーミステリーと言えば、プレイヤーの中に犯人がいて秘密を探り合って、というのが一般的なイメージかと思います。実際は色々なモノがあって、中にはNPCが犯人だったり、犯人が存在しなかったりといった推理小説だったら怒られそうな変化球もあります。プレイヤー同士でコミュニケーションを取って謎に近づく部分がゲームの肝なので、そういうのもアリなんでしょうね。面白ければ何でもアリです。初心者も楽しめる難易度で原作未読でも大丈夫とのことなので、初めてマダミスをする原作ファンも原作は知らないけ童話モチーフのマダミスに興味が沸いた方にも安心ですね。

真四角で大容量!拡張機能付き「ボードゲーム専用多機能バッグ」クラウドファンディング実施中!

今回はボードゲーム関連のグッズのご紹介です。ボードゲームを持って出かける機会がある人は共感できそうな悩みですが、ボドゲは嵩張るんですよね。特に重たいゲームは箱がデカい。そんな大きな箱もまとめて持ち運べる大容量のバッグがクラウドファンディングで開発中とのこと。Gamerで紹介記事が出ていますので、まずはそちらをどうぞ。ボードゲーム専用バッグ「GAMER'S BAG」のクラウドファンディングがMakuakeで実施中!https://www.gamer.ne.jp/news/202312180042/大容量多機能 Gamer’sBagGamer’sBagは幅35.5cm、高さ56cmの大型バッグです。厚みは通常状態で20cm、一周ぐるっと取り付けられたファスナーを開くと35.5cmに拡張できます。ボードゲームの「大箱」と呼ばれるサイズが30cm×30cmくらいらしいので、このバッグのサイズだと余裕を持って入れられそうです。リュックスタイルでもショルダースタイルでも手提げでも使える他、キャリーバッグのハンドルを入れられるスリットもあるのでサークル参加する際の荷物搬入にも良さそうですね。小物入れが充実していたり、内部に衝撃対策のパットが入っていたり、防水仕様になっているなど細かな気遣いも嬉しい所。ボドゲに限らず、書籍を入れても良さそうですし、レジャーなどにも使えそうです。Makuake 圧倒的な収納力!より充実したボードゲームライフを。多機能なボードゲーム専用バッグhttps://www.makuake.com/project/gamers_bag/プロジェクト期間は来年1月末まで現在このバッグのプロジェクトは達成率82%で、未達成となっています。とはいえ、あと1人か2人参加したら達成できそう。プロジェクトの期間は1月30日までで、まだ時間はありますが気になった方はお早めにどうぞ。現在クラウドファンディング中のプランの中で最も安い、超早割プランなら23,990円。大容量バッグが欲しい方は検討してみてはいかがでしょうか。