Blender5でVroid・Mixamo・AutoRigProで3Dモデルを動かす流れメモ
Vroidで作成したモデルを、Mixamoで選んだアニメーションで、Blender上で動かした。
時間がないのでパパッと流れだけ備忘録。
服は肩を上げた際に不自然に浮いていたりしていたので一旦下着しか着せてない。
手順的に無駄な作業とかもあるとは思うけど、ひとまず動いたので手順メモ。
(歩く動作をさせたが、まだ腕が腰を貫通しながら歩いているので調節は必要)
Vroid
服なし(体にぴったりくっついている下着のみ)でエクスポート(VRM形式)
Blender
VroidからエクスポートしたVRMファイルをインポート
アーマチュアを全て削除。
目玉の右・目玉の左・上の歯・下の歯・舌を別オブジェクトに分離する。(今はまだやってないけど顔のリグも設定する予定なので)
オブジェクトを全て選択して編集モードに切り替える。全ての頂点を選択し距離でマージ。
FBX形式でエクスポート。パスにしてテクスチャもFBXファイルに含めさせる。
Mixamo
BlenderからエクスポートしたFBX形式のファイルをアップロードし、リグの設定を行う。(テクスチャもFBXファイルに埋め込んでいるはずではあるけど、リグ設定画面では真っ白表示になりました。後述のダウンロードしてBlenderで開いた際は、テクスチャもちゃんと表示されたのでMixamoの表示の問題なのかなと思います)
好きなアニメーションを選んでスキン有りでダウンロード。
Blender
元のモデルをARPでリグ。(頂点のマージをしてないとウェイトが変なことになりがち(手を動かしたら髪の毛がくっついてきたりした)。リギングについては以下のサイトがわかりやすかったです。(他にも参考にしたので後で追加で貼っておきたい、、)
)
Mixamoから出したアニメーションのFBXファイルをインポート。元のモデルと被らないように左右どちらかにずらす。
AutoRigProのRemapメニューからリマップ作業。(AutoScaleボタンは押さなくていいと思う。) Bones ListでIKがMixamoで出力した側のボーンに紐づいていたら、その値は削除する(空欄にする)。
MixamoのHipとAutoRigProのmasterのボーンの紐付けの行を選択して、mark as rootにチェックを入れてリマップさせる。
スペースキーを押してアニメーション開始。
Blenderでパーツの一部がいまいちボーンに追従しない
状況
Blenderで、アニメ絵キャラクターのスキニング作業中。
こちらの書籍に従って学習を進めています。
Amazon.co.jp: Blenderでアニメ絵キャラクターを作ろう!モデリングの巻 (Compass Booksシリーズ) eBook : 夏森轄: Kindleストア
困ったこと
頭部のボーンを動かした際に、目のパーツがゆっくり追従してくるので目がずれてしまう。
ウェイトペイント上の表示を確認した限りでは問題なさそうなのに。



どう対処したか
目のパーツの編集モードから、頂点ごとに確認できるウェイト情報を確認。
(編集モードに切り替え後、頂点選択モードにして目のパーツのいずれかの頂点を選択。キーボードのNを押して頂点ウェイトの項目を確認。)
想定していないボーンのウェイト設定が残っていたので、そのボーンの影響を削除して対処できました。
(頂点ウェイトの項目上で、写真でいう「Skirt06.R」の右側にあるバツボタンをクリック。他の頂点をShiftキーを用いて全て選択し、コピーボタンをクリック。瞳の裏側の頂点もお忘れなく。)

(頂点ウェイトの、「Skirt06.R」に1.000が設定されている)

何故起きたか
スカートのボーンのウェイトペイン時に、下からのアングルにして塗っていた時に気づかないうちに瞳の方まで塗ってしまっていたのかも・・・。いつの間にか塗ってしまっていたので本当に気づかなかったです。
Blenderでカウントダウン&爆発する立方体を作る
作るもの
こんな感じのカウントダウン&爆発
環境
Blender5.0.0
作る上での要点
カウントダウン部分
- ジオメトリノードエディター上でマテリアルインデックス設定のノードを用意して、マテリアルインデックスの値に対してアニメーションのキーフレームを設定する
立方体が爆発する部分
サブディビジョンサーフェスを設定して分割数の値に対してアニメーションのキーフレームを設定する
パーティクルの設定で、サブディビジョンサーフェスのモディファイヤーが適用された状態に対してアニメーションがされるようにする
作り方
1. カウントダウン部分
1-1. blenderを起動
全般でスタート。

1-2. カウントダウン用の画像を用意する
やりやすいやり方で、以下のような「3」「2」「1」の画像ファイルを用意します。
(自分の場合は、macだったのでフリーボードを開いて四角の図形の上にテキストエリアを用意してそれぞれ色を設定し、それをスクリーンショットに収めました)



1-3. マテリアル設定を行う
ビューポートシェーディングをマテリアルプレビューに変更(zから選べるメニューの下のやつ)します。
立方体を選択します。
マテリアル設定でデフォルトでMaterialが選択されているので、サーフェス>ベースカラーの黄色い丸をクリックして「画像テクスチャ」を選択し、カウントダウンの「3」の画像を指定します。
3Dビューポートのウィンドウを分割するなりして、3Dビューポートの隣にUVエディターを開きます。

- 立方体を選択して編集モードに切り替えます。

- UVエディターの同期ボタンをクリックして同期を解除状態にします

- 3Dビューポートで1つの面を選択→UVエディターで表示される面を移動して、カウントダウンの数字が表示されるように調整します

- マテリアルスロットを追加し、新しいマテリアルをカウントダウン3を設定したときのように設定します
(画像テクスチャを選択し、カウントダウン2の画像を選択する)

- 3Dビューポートで面選択モードにした状態で立方体の全ての面を選択し、カウントダウン2の画像テクスチャのマテリアルを割り当てます。
割り当てボタンを押した瞬間に3Dビューポートでの表示が3から2に変わると思います

同様にしてカウントダウン1用のマテリアル設定も用意します
マテリアルスロットの名前をわかりやすいように変えます。マテリアルスロットも3→2→1となるように並び替えておきます
※マテリアルスロットの並び順が、このあと設定するマテリアルインデックス設定で使う値に影響します。

1-4. マテリアルの設定をジオメトリノードエディター上で実施
- UVエディターをジオメトリノードエディターに切り替えます

- ジオメトリノードエディターの右上の「新規」をクリックします

Shift+Aまたはジオメトリノードエディター上部の「追加」から、(Blender5の場合は)ジオメトリ>マテリアル>マテリアルインデックス設定 を選択して追加します
※Blender4の場合は「ジオメトリ」の階層がなかった気がしますノードは以下の図のように、グループ入力-マテリアルインデックス設定-グループ出力となるように繋ぎます。

- 3Dビューポートエディターで面選択で全ての面を選択し、マテリアル設定で「3」を割り当てておきます
1-5. マテリアルインデックス設定に対してアニメーションを設定する
- タイムラインエディターで1のフレームを選択し、ジオメトリノードエディターのマテリアルインデックス設定で「0」を入力して、入力欄の上でキーボードの「i」キーを押します

タイムラインエディターで25のフレームを選択し、ジオメトリノードエディターのマテリアルインデックス設定で「1」を入力して、入力欄の上でキーボードの「i」キーを押します。
続いて、タイムラインエディターで49のフレームを選択し、ジオメトリノードエディターのマテリアルインデックス設定で「1」を入力して、入力欄の上でキーボードの「i」キーを押します
※「出力」でフレームレートを24FPSにしています
1-6. カウントダウンのアニメーションの動作確認
- タイムラインの表示は以下の感じになってるかと思います
(自分の状態だと、ビュー>選択物を表示 のチェックを外すとキーフレームが表示されました)

- タイムラインエディターで現在のフレームを1にして再生ボタンを押すと、3Dビューポート上で以下のような動画が見れるようになってると思います
youtube.com https://youtube.com/shorts/tYZnDiRHqUY
2. 爆発部分
2-1. 爆発のエフェクトを追加
- タイムラインエディターで72のフレームを選択後、オブジェクトモードで立方体を選択し、上の方にある「オブジェクト」>クイックエフェクト>クイック爆発を選択します
※爆発の開始を73にするとアニメーション再生時に74フレームから爆発してるように見えたので、72にしています

- パーティクルの設定から、モディファイアースタックを使用する旨にチェックをいれます

2-2. サブディビジョンサーフェスのモディファイヤーを追加
- サブディビジョンサーフェスのモディファイヤーを追加します
追加する場所は、Particle、Explodeよりも上になるように並び替えます(分割した上で爆発させたいので).

2-3. サブディビジョンサーフェスの設定に対してキーフレームを挿入する
タイムラインで72のフレーム選択後に、サブディビジョンサーフェスの「ビューポートのレベル」及び「レンダー」をいずれも0にした上でそれぞれの入力欄上でキーボードの「i」を押してキーフレームを挿入します
タイムラインで73のフレーム選択後に、サブディビジョンサーフェスの「ビューポートのレベル」及び「レンダー」をいずれも4にした上でそれぞれの入力欄上でキーボードの「i」を押してキーフレームを挿入します
3. 微調整
あとは、爆発やアニメーション具合を微調整したら完成です。
(自分は、アニメーションの終了フレームを100にしたり、パーティクルプロパティでノーマル・オブジェクトの方向の値などを自分の好きな爆発具合になるように値をいじったりしました).
4. 完成
ライティングやカメラの設定をいじった上でアニメーションを書き出せば完成。
自分の完成品はこちら
DBスペシャリストの勉強におすすめの書籍と、資格取得だけで終わらせないために読んだ書籍紹介
前置き:資格取得は「実務で役立つ」のか?
自分は2022年にDBスペシャリスト試験に合格した身です。
資格取得ってそれなりに時間もかかるし、「実務で役に立つの?」などの声もあり、日々時間に限られる中、取り組むべきなのかそうでないのか悩ましいですよね。
でも自分にとっては資格試験の勉強は以下のメリットがあると感じています。
非情報学部出身の自分が、エンジニアとして当たり前に知っているようなことを身につけることができる。
↑により、いろんな技術書を読む上での前提知識を身につけることができ、技術書やネット上の技術に関する記事を読みやすくなる・理解しやすくなりその後の学習を加速することができる
(自分が一番感じるメリットはこれです)試験のために暗記するので記憶に長期的に残せる
(さらっと書籍を読んで知ったつもりで終わらないで済む。記憶に定着することで「進研ゼミでやったとこだ」式に実務での関連を感じられる)自分の進歩を客観的に感じられるので精神的な安定剤になる
つまり、個人的意見としては 資格取得は結果的に実務で役立つ と思っています。
とはいえ、やはり直接的に実務で役立つ知識も身につけられた方が、限りある時間を有効活用するという意味でも嬉しいですよね。
そこで本記事では、 「資格取得のための勉強」と「実務で活かせる勉強」を同時に進めたい! という方のために、自分が実際に読んでよかったと感じた書籍を以下2つの視点からご紹介します。
- 資格試験対策メインで読むべき本: 合格に必要な知識を効率的に学べる書籍
- 実務にも役立つ、資格対策にもなる本: 業務で活用しながら、試験の知識も深められる書籍
DBスペシャリスト試験は、データベースの設計という業務に直結する資格だと感じています。しかし、この資格の勉強だけでDBに関する全ての知識が網羅できるわけではありません。(むしろ資格取得によって入り口に到達したと思っています)
今回ご紹介する書籍は、DBの知識をさらに深めるための入り口として、ぜひ手に取っていただきたいものばかりです。
※書籍名のリンクは、Amazonへのリンクになっておりますm( )m
資格試験対策メインで読むべき本(1冊)
『情報処理教科書 データベーススペシャリスト』(翔泳社)
いわゆる試験対策はこの本をしっかりやり込めば十分だと思います。
まずは基本知識を身につけるべく知識解説の章を一通り読む
読者特典の午前問題の解説をやり込む
(問題集のように、間違えたところは繰り返し解く。午前問題は隙間時間で少しずつできるので、通勤の電車の中などで毎日3問ずつなど少しずつ取り組んでいくのがおすすめです。一気にたくさん取り組むよりも挫折しづらく、また長期で取り組むので長期的に記憶にも残ってくれたように感じています).まとめとして過去問に取り組む。解説も理解できるまで読む
(自分は育児もありでまとまった時間を取りづらかったので、朝早起きして大問を1つ解く & 丸つけだけやる→通勤の電車の中で解説を読む、というやり方をとって午後の試験対策を行いました)
実務にも役立つ、資格対策にもなる本(4冊)
試験合格後にDBに関する業務として取り組んだものは、新規案件のER図作成・テーブル設計を1プロジェクトで行なっただけではあります。(試験勉強の経験を活かしてこれらの業務はかなりスムーズに進めることができたと感じています。 ). ですが、試験の受験以前にはプロジェクトの追加開発・保守にも取り組んでおり、以下の技術書を「もっと早く読んでおけばよかった」と感じるものが多かったので、実務で役立つとみなしてリストアップしております。
① 『達人に学ぶDB設計徹底指南書』
論理設計や正規化についての章もあるので、試験勉強にももちろん使えます
タイトル通りで、DB設計をする時に直接的に役立ちます
基本知識的なところも説明があるため、早めに読んでおくと知識のベースづくりとしても役に立ちます
② 『SQLアンチパターン』
※自分が読んだ時はまだ第二版は出ていませんでした
アンチパターンの書籍というのは先人たちの失敗から効果的に学べるので全般的におすすめです
特に有名な本なので、会社の人との共通見識を身につけるためにも読んでおくと良いと思います
正規化の大切さを感じることができます
③ 『失敗から学ぶRDBの正しい歩き方』
繰り返しになりますが、アンチパターンの書籍というのは先人たちの失敗から効果的に学べるので全般的におすすめです
自分も実際、実務でプロジェクトを経験したり色々やらかした後に読んだが、すごくみに沁みました。実務をする上では絶対読んでおきたいです。
④ 『SQL実践入門 ──高速でわかりやすいクエリの書き方』
SQL問題に慣れる上でもこの本を読むことをお勧めします
自分はwindow関数(試験には出ないかなと思います)の理解ができていなかったけどこの本を通して理解できました
実践と銘打ってる分実務でも活かしやすいと思います
(DB設計は機会に恵まれないとなかなかないけど、SQLに触れる機会はとてもたくさんあるのではないでしょうか)
まとめ
「資格勉強」✖️「実務経験」で、エンジニアとしてより効率的にパワーアップしていきませう
(さらに実務経験の振り返り術を身につけて、実務経験からの学びを最大化していきたいですね。)
サポートページ/Support Page/지원 페이지/支援頁面/支持页面
TiraTom名義のアプリについてのお問い合わせは、以下のGoogleFormからご入力またはブログのコメント欄をご利用ください。
For inquiries regarding the TiraTom app, please fill out the following Google Form or use the comment section on the blog.
TiraTom 앱에 대한 문의는 아래의 Google 폼을 작성하거나 블로그의 댓글란을 이용해 주세요.
關於TiraTom應用程式的詢問,請填寫以下Google表單或使用博客的評論區。
关于TiraTom应用程序的咨询,请填写以下Google表单或使用博客的评论区。
アプリのお問い合わせページではお問い合わせ用メールアドレスを記載しておりますので、そちらを利用して問い合わせていただくのでも大丈夫です。(その場合はタイトルをアプリの指定のものにしていただけると対応時の迷惑メールとの区別ができるので助かります。
Alternatively, you can also use the contact email address provided on the app's inquiry page. (If you choose to do so, it would be helpful if you could use the specified title for the app to differentiate it from spam during our response.).
또는 앱의 문의 페이지에 제공된 이메일 주소를 사용해 주셔도 괜찮습니다. (이 경우, 대응 시 스팸 메일과 구분할 수 있도록 앱의 지정된 제목을 사용해 주시면 감사하겠습니다.)
也可以使用應用程序諮詢頁面上提供的聯繫電子郵件地址。(在這種情況下,如果能在標題中注明應用程序的指定名稱,以便在處理時區分垃圾郵件,我們將不勝感激。)
也可以使用应用程序咨询页面上提供的联系电子邮件地址。(在这种情况下,如果能在标题中注明应用程序的指定名称,以便在处理时区分垃圾邮件,我们将不胜感激。)
Compose Multiplatformで使うAboutLibrariesのカスタマイズ
前置き
前回AboutLibrariesを用いてライセンス表示ページを作ったものの、自分の設定の問題か以下のような問題がありました。
①ライラリ名が長い場合には...のように省略されてしまいフルの名称がわからない
②タップした場合に何も起こらない
①については、本当はタップ時に詳細を表示する等のアクションが実際はありそうなので、②が解消できれば問題なくなります。また、長い場合に3点リーダーになるのはデフォルトの挙動です。
そこで、②について自分なりに対処してみたので、その方法について記載します。
完成イメージ
ライセンス表示画面

ライセンスタップ時

ライブラリ詳細タップ時に遷移するページ

ライセンス詳細タップ時に遷移するページ

実装説明
前提
AboutLibrariesは導入してある前提とします。
未導入の方は、こちらの記事をよろしければご参考にしてください。
tiratom.hatenablog.com.
ソースコード
LicenseListScreen
先ほどライセンス表示画面のスクリーンショットを貼りましたが、そちらが以下のソースコードとなります。
@OptIn(ExperimentalResourceApi::class)
@Composable
fun LicenseListScreen(
navigator: Navigator,
modifier: Modifier,
) {
// aboutlibraries.json に使用しているライブラリ情報を出力してあるので、それを読み込んでいます。
var bytes by remember { mutableStateOf(ByteArray(0)) }
LaunchedEffect(Unit) {
bytes = Res.readBytes("files/aboutlibraries.json")
}
// ライブラリ名をタップしたときに表示するモーダルから外部サイトを開くので、外部サイトを開くときに必要なurlHandlerをここですでに宣言しております
val urlHandler = LocalUriHandler.current
// モーダルの表示を管理する変数です
var isLibraryDetailsShowing by remember {
mutableStateOf(false)
}
// モーダルの表示内容はどのライブラリをタップしたかによって適宜出し分けたいので、ここで変数を宣言してあります
var nameForLicenseDetails by remember { mutableStateOf("") }
var authorForLicenseDetails by remember { mutableStateOf("") }
var detailsUriForLicenseDetails by remember { mutableStateOf("") }
var licenseUriForLicenseDetails by remember { mutableStateOf("") }
Scaffold(
topBar = {
ChildrenScreenAppBar(
currentScreen = PittaScreen.LicenseList,
back = navigator::goBack,
modifier = modifier,
)
}
) { innerPadding ->
// モーダル表示部分です。LicenseDetailsDialogは後述します。
if (isLibraryDetailsShowing) {
LicenseDetailsDialog(
name = nameForLicenseDetails,
authorForLicenseDetails = authorForLicenseDetails,
detailsUri = detailsUriForLicenseDetails,
licenseUri = licenseUriForLicenseDetails,
onDismissRequest = { isLibraryDetailsShowing = false },
uriHandler = urlHandler,
modifier = modifier
)
}
// ライブラリ表示部分です。onLibrariClickの部分で、ライブラリクリック時にやるアクションを記載しています。タップしたときはAboutLibraries内部で元々用意されているアクションは使わず、モーダル表示に必要な変数に対してタップしたライブラリに対応する情報を代入しています。
LibrariesContainer(
aboutLibsJson = bytes.decodeToString(),
modifier = modifier.padding(innerPadding),
onLibraryClick = { library ->
isLibraryDetailsShowing = true
nameForLicenseDetails = library.name
authorForLicenseDetails = library.author
detailsUriForLicenseDetails = library.website.orEmpty()
licenseUriForLicenseDetails = library.licenses.firstOrNull()?.url.orEmpty()
}
)
}
}
LicenseDetailsDialog
先ほどライセンスタップ時のスクリーンショットを貼りましたが、そちらが以下のソースコードとなります。 要はモーダルです。
@Composable
fun LicenseDetailsDialog(
name: String,
authorForLicenseDetails: String,
detailsUri: String,
licenseUri: String,
onDismissRequest: () -> Unit,
uriHandler: UriHandler,
modifier: Modifier,
) {
AlertDialog(
text = {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier.verticalScroll(rememberScrollState())
) {
// ライブラリ名を表示しています。
Text(
text = name,
color = if (isSystemInDarkTheme()) {
Color.White
} else {
Color.Black
},
fontWeight = FontWeight.Bold,
modifier = modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
Spacer(modifier.size(5.dp))
// authorを表示しています。なお、authorはemptyの場合もあります。
Text(
text = authorForLicenseDetails,
color = if (isSystemInDarkTheme()) {
Color.LightGray
} else {
Color.Gray
},
fontWeight = FontWeight.Bold,
modifier = modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
Spacer(modifier.size(15.dp))
// ライブラリの詳細ページへのリンクを用意しています。
ClickableText(
text = buildAnnotatedString {
withStyle(
style = SpanStyle(
color = if (isSystemInDarkTheme()) {
Color(0xFFBFFFFF)
} else {
Color.Blue
},
textDecoration = TextDecoration.Underline
)
) {
append("Library Details")
}
},
onClick = {
try {
// iOSでも動くように、uriHander.openUriを使っています。
uriHandler.openUri(detailsUri)
} catch (e: Exception) {
println("ライブラリのURLを開くのに失敗しました: $e")
}
},
)
Spacer(modifier.height(5.dp))
// ライセンスの詳細ページへのリンクを用意しています。
ClickableText(
text = buildAnnotatedString {
withStyle(
style = SpanStyle(
color = if (isSystemInDarkTheme()) {
Color(0xFFBFFFFF)
} else {
Color.Blue
},
textDecoration = TextDecoration.Underline
)
) {
append("License Details")
}
},
onClick = {
try {
uriHandler.openUri(licenseUri)
} catch (e: Exception) {
println("ライセンスのURLを開くのに失敗しました: $e")
}
},
)
Spacer(modifier.size(10.dp))
}
},
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(onClick = onDismissRequest) {
// 閉じるボタンは多言語対応してあるので、Res.string.closeから取得しています。
Text(stringResource(Res.string.close))
}
}
)
}
補足
AboutLibrariesのどこが原因で自分の場合うまくいかなかったかまでは調査できてないです・・・。
ShardLibraries.ktのLicenseDialogあたりの表示で何か起きてるのかもと思いつつではありますが・・・。
Compose MultiplatformでAdmobのインタースティシャル広告を表示させる
前提
- Compose MultiplatformでiOSのみの開発をしています。
(元々Jetpack Composeで作成していたAndroidアプリのiOS版を作成しよう&Multiplatformの実装について学ぼうということでCompose Multiplatformを導入しており、Android版は元々のままJetpack Composeのプロジェクトとして運用しております)
そのため、iOS&Androidを同時開発している場合にはそのままだとちょっと動かない可能性もあります。 - こうやったら動いたという趣旨の記事のため、ベストな記述方法ではない可能性があります。
- Compose Multiplatformは日々発展中の技術のため、記事の記述内容はすぐ古くなっている可能性があります。(多分ちゃんとした公式ドキュメントが用意される日も近いと信じています)
上記諸々すみませんがご承知おきください。
この記事の内容
- Compose MultiplatformでAdmob広告(インタースティシャル広告)を表示する方法についてのメモです。
- Admobでのアプリ追加方法(実装で必要なアプリIDの取得)についてはこの記事では書いていません。
- 最終的に以下の感じで広告が表示されます。
大まかな流れ
- スタートガイドに沿ってMobile Ads SDKをインポート
- Info.plist を更新する
- Mobile Ads SDK を初期化する(SwiftUIの例を参考に)
- SwiftUIでのフルスクリーンのAdmob導入の手順に従って広告表示のための実装を追加
- 共通処理側で広告表示の処理を呼び出す
developers.google.com developers.google.com
流れ詳細
1. Mobile Ads SDKのインポート(Swift Package Managerを利用)
- CocoaPodsが推奨なので、そちらでやるのがベストかもしれません。
- 筆者はCocoaPodsを使ったことがなく、Xcodeの画面からぽちぽちするだけでできるSwift Package Managerが簡単に感じたのでSwift Package Managerを使ったという単純な動機です
2. Info.plist を更新する
- スニペット全文がドキュメントに用意されている(https://developers.google.com/admob/ios/quick-start?hl=ja#expandable-1)ので、アプリIDの値を変更した上で自分のInfo.plistファイル(筆者の場合、パスはiosApp/iosApp/Info.plist)を更新します。
ソースコードを表示させて一気にペーストすると編集が簡単です。 なお、Admob用のアプリIDは、Admobの アプリ> アプリの設定 のアプリの情報欄から確認可能です。
3〜 SwiftUIの例を参考に実装していく&共通側で広告表示のための処理を呼びだす
- SwiftUIの例(https://developers.google.com/admob/ios/quick-start?hl=ja#example_appdelegate_excerpt)を参考にMobile Ads SDK を初期化します。
- SwiftUIのアプリにAdmobの実装をする方法についてのページを参照し(SwiftUI | iOS | Google for Developers)、実装します。
- 広告の読み込み処理・表示処理をSwift側のコードで関数にして、共通処理側で必要な時に呼び出します。
最終的にiosAppは以下の感じの実装にしています(すみません力尽きて詳細まで書けておりません・・・)
iosApp/iosApp/ContentView.swift
import UIKit
import SwiftUI
import ComposeApp
struct ComposeView: UIViewControllerRepresentable {
let loadAdMethod: () -> Void
let showAdMethod: () -> Void
init(loadAdMethod: @escaping () -> Void, showAdMethod: @escaping () -> Void) {
self.loadAdMethod = loadAdMethod
self.showAdMethod = showAdMethod
}
func makeUIViewController(context: Context) -> UIViewController {
MainViewControllerKt.MainViewController(
loadAdMethod: { self.loadAdMethod() },
showAdMethod: { self.showAdMethod() }
)
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}
struct ContentView: View {
private let adCoordinator = AdCoordinator()
var body: some View {
ComposeView(
loadAdMethod: { adCoordinator.loadAd() },
showAdMethod: { adCoordinator.presentAd()}
).ignoresSafeArea(.keyboard) // Compose has own keyboard handler
}
}
iosApp/iosApp/iOSApp.swift
import SwiftUI
import GoogleMobileAds
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
GADMobileAds.sharedInstance().start(completionHandler: nil)
return true
}
}
@main
struct iOSApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
iosApp/iosApp/AdCoordinator.swift (広告の実装のために新規作成)
import ObjectiveC
import GoogleMobileAds
class AdCoordinator: NSObject, GADFullScreenContentDelegate {
private var ad: GADInterstitialAd?
private var isLoading: Bool = false // この辺は、広告読み込み中なら再度読み込み処理は呼び出さないために記述してみています。
private var adShowTiming: Int64 = Int64(Date().timeIntervalSince1970) + 20 // これは、広告を1回出したら連続して広告が出ないようにするためのインターバルを設けるために実装してみています。
func loadAd() {
if (isLoading) {
return
} else {
isLoading = true
}
GADInterstitialAd.load(
withAdUnitID: "ca-app-pub-3940256099942544/4411468910", request: GADRequest()
) { ad, error in
if let error = error {
return print("Failed to load ad with error: \(error.localizedDescription)")
}
self.ad = ad
self.ad?.fullScreenContentDelegate = self
}
}
func presentAd() {
print("\(#function) called")
if (isAdShowTiming()) {
guard let fullScreenAd = ad else {
return print("Ad wasn't ready")
}
// View controller is an optional parameter. Pass in nil.
fullScreenAd.present(fromRootViewController: nil)
}
}
func adDidRecordImpression(_ ad: GADFullScreenPresentingAd) {
print("\(#function) called")
}
func adDidRecordClick(_ ad: GADFullScreenPresentingAd) {
print("\(#function) called")
}
func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
print("\(#function) called")
}
func adWillPresentFullScreenContent(_ ad: GADFullScreenPresentingAd) {
print("\(#function) called")
}
func adWillDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
print("\(#function) called")
}
func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
print("\(#function) called")
// ここは広告のアプリ上での表示が終わって閉じられた時に呼び出される関数なので、再度広告読み込みができるように自分で用意したフラグや変数やらを書き換えています
isLoading = false
adShowTiming = Int64(Date().timeIntervalSince1970) + 50
loadAd()
}
func isAdShowTiming() -> Bool {
return adShowTiming <= Int64(Date().timeIntervalSince1970)
}
}
composeApp/src/iosMain/kotlin/MainViewController.kt
import androidx.compose.ui.window.ComposeUIViewController
fun MainViewController(
loadAdMethod: () -> Unit,
showAdMethod: () -> Unit,
) = ComposeUIViewController { App(
loadAdMethod = loadAdMethod,
showAdMethod = showAdMethod,
) }
composeApp/src/commonMain/kotlin/App.kt
@Composable
fun App(
loadAdMethod: () -> Unit,
showAdMethod: () -> Unit,
) {
loadAdMethod() // EUなどだと広告を読み込む前に広告配信の同意取得が必要です。EU等にもアプリを配信する場合はいきなりloadしないように注意してください。
// 後略。必要な箇所でshowAdを呼び出せば広告が表示されます。そのあとはloadAdを呼び出して広告を再読み込みしてください。
}
筆者の広告
- 以下のAndroidゲーム(JetpackComposeで開発)のiOS版もCompose Multiplatformを利用して作成しようと試みています。
play.google.com