mieki256's diary



2024/12/01() [n年前の日記]

#1 [cg_tools] Inpaint anythingを試用

画像生成AI Stable Diffusion web UI reForge の img2img を使っていて、人物と背景をマスクで塗り分けるのが面倒臭いなと感じてきた。こういう作業こそAIでどうにかしてほしい…。何か良いツールは無いものか…。

ググってみたら、Stable Diffusion web UI用の拡張、Inpaint anything が使えそうな気がしてきた。
_Uminosachi/sd-webui-inpaint-anything: Inpaint Anything extension performs stable diffusion inpainting on a browser UI using masks from Segment Anything.
_Stable Diffusionで指定部分だけ変更できる拡張機能『Inpaint Anything』の使い方! | romptn Magazine
_【Stable Diffusion】Inpaint Anythingの使い方!特定の領域に自動でマスクを作成 | イクログ
_【超便利】Inpaint Anythingの使い方 前編【stable diffusion】 - YouTube
_【超便利】Inpaint Anythingの使い方 後編【stable diffusion】 - YouTube

Windows10 x64 22H2 + Stable Diffusion web UI reForge で使ってみた。 *1

インストールとモデルデータのダウンロード :

インストールは、Stable Diffusion web UI の拡張機能タブから行える。リストを読み込んで「anything」と検索欄に打ってやればリストアップされるので、インストールボタンを押す。インストールができたら Stable Diffusion web UI reForge を再起動。

インストールに成功すると Inpaint anythingタブが追加されるのでそれを選択。

セグメントで分けるためにはモデルデータが必要になるので、使いたいモデルデータを選んでダウンロードボタンを押す必要がある。モデルデータは、以下の場所に配置される。
Stable Diffusion web UI reForgeインストールフォルダ\extensions\sd-webui-inpaint-anything\models\

今回ダウンロードしたモデルデータは以下。
sam2_hiera_base_plus.pt308.5MB323493298 byte
sam2_hiera_small.pt175.8MB184309650 byte
sam_vit_l_0b3195.pth1.2GB1249524607 byte
sam_vit_b_01ec64.pth357.7MB375042383 byte
sam_hq_vit_l.pth1.2GB1254865805 byte
sam_hq_vit_b.pth361.8MB379335069 byte
FastSAM-x.pt138.2MB144943063 byte
FastSAM-s.pt22.7MB23832055 byte
mobile_sam.pt38.8MB40728226 byte
  • huge, large, base のモデルデータがあって、_h, _l, _b とファイル名につけてある。ファイルサイズは、huge > large > base になっている。
  • あるいは、large > base > small > tiny の順になっている場合もある。
  • hq とついているのは高品質な結果を出す、らしい。
  • Fast とついているのは、処理が早い。精度は甘くなる。

使い方 :

  1. 画像をエクスプローラ等からドラッグアンドドロップすれば読み込まれる。
  2. セグメント分割するためのモデルデータを選んで、Run Segment Anything ボタンをクリック。
  3. そこそこ時間がかかって処理が行われる。セグメントで分割された画像が出てくる。
  4. マスクとして使いたい領域の上でマウスの左クリック。もしくはドラッグ。点や線で指示されたセグメントが選択された領域扱いになる。
  5. Create Mask をクリック。マスク領域が作成される。

マスク領域の上には、マウスクリックやマウスドラッグで何かしらを描いていける。このレイヤーをスケッチと呼んでいるららしい。

3つのボタンの意味は以下。
  • Expand mask region : マスク領域を1ドット広げる。
  • Trim mask by sketch : マスク領域から、スケッチに描かれた分を取り除く。
  • Add mask by sketch : マスク領域に、スケッチに描かれた分を追加する。

マスク領域ができたら、Inpainting で、マスク領域内を別の画像上で置き換えることができる。

ただ、この Inpainting をするためには、更にモデルデータが必要で…。どれかしらを選んで処理をすると、もしかするとモデルデータをダウンロードして処理してくれるのかもしれない。

ただ、どこにモデルデータが配置されるのか分からなかった。README.md には、以下に置いてある、と書いてあるのだけど…。中を覗いても見当たらない…。
C:\Users\(USERNAME)\.cache\huggingface\hub\

Mask only を使えば、Get mask でマスクを作成して、img2img に送ることもできる。

出力フォルダ :

インストール直後は、生成画像は以下に記録されるらしい。
.\outputs\inpaint-anything\

ただ、設定を変更すれば、img2img の出力フォルダに切り替えることもできる。設定 → Inapint anything。

*1: ハードウェアスペックは、CPU : AMD Ryzen 5 5600X, RAM : 32GB, GPU : NVIDIA GeForce GTX 1060 6GB。

2024/12/02(月) [n年前の日記]

#1 [digital] COBライトを充電しようとして困った

以前ダイソーで購入した充電式COBライトの電池が切れかかっていたようで、充電しようとしたのだけど充電端子が USB Type-Cだったのでちょっと困った。手持ちのUSB充電ケーブルは micro USBしかない…。Type-C なんて持ってない…。

幸い親父さんが Type-C ケーブルを持っていたので充電できたけれど。自分も入手しておかないとマズいな…。今後、色んな機器が、USB Type-C にごっそり移行してしまうのだろうし。

それにしてもUSB規格って、端子形状だけを見ても結構変わってしまったものだなと。最初は USB Type-A と Type-B しかなかった気もするけど、mini USB だの micro USB だの Type-C だのが出てきて…。ググってみたら、mini USB Type-B、micro USB Type-B と言うのか…。他に、micro USB Type-B 3.0 もあるらしい。

_【解説】USBケーブルの種類がまるわかり! ケーブルの見分け方や使い方を解説します - ITをもっと身近に。ソフトバンクニュース
_USBとは?|サンワサプライ株式会社

転送速度についても把握しようとすると、もう何が何だか…。Gen1、Gen2 と名前をつけ直したあたりでおかしくなった気がする…。

2024/12/03(火) [n年前の日記]

#1 [windows] Windows11へのアップグレード勧誘が全画面で表示されて途方に暮れた

Windows10 x64 22H2 をインストールしてあるメインPCを再起動したら、Windows11へのアップグレード勧誘が全画面で表示されて途方に暮れた。

選択肢が2つ表示されていたけれど…。 どっちを選んでも Windows11 にアップグレードされてしまうではないか…。

そのうちアップグレードしなきゃ、とは思ってるけど、それは今じゃない。今はまだ Windows10 で作業したいのだけど。問答無用でアップグレードしろと? クソが。これだから独占企業は…やりたい放題だな…。

「24時間以内に〜」の画面に行って、他の選択肢が無いことを把握して、また戻って…。そんな操作を繰り返しているうちに、ふと、画面左下に小さく薄い色で「Windows10を使い続ける」の選択肢があることにようやく気づいた。一応選択肢はあるのか…。しかしこのフォントサイズと言い、文字色と言い、完全に騙す気満々では。いや、選択肢があるだけまだマシなのかもしれんけど。

「使い続ける」ボタンを押したら画面が変わって、「アップグレードを拒否する」という選択肢が出てきたけれど、まさかこれ、永遠にアップグレードを拒否してしまう選択肢ではなかろうな…。そのうちいつかアップグレードしようとは思ってるのだけど、本当にコレをクリックして大丈夫? どうも説明が足りてない選択肢になってる気がする。英文をそのまま訳したらこうなるのだろうか…?

去年からこの画面は出ていたらしい :

ググってみたら、去年の年頭、2023/02頃から、こういう画面を表示していると知った。

_MicrosoftがWindows 11への更新を全画面で求めてくる上にダークパターンを用いていたという報告 - GIGAZINE
_Microsoft、Windows 10の起動画面にWindows 11への更新を促す画面を強制表示 | TECH+(テックプラス)

たしかにこれは「ダークパターン」としか思えない。選択肢を3つ並べればいいだけなのに…。悪質だ…。 *1

今回の画面は、2024/10 に Windows Update経由でインストールされていたらしい。時限爆弾よろしく指定期日になったら発動するプログラムだったのだろうか…?

_MicrosoftがWindows 11へのアップグレードを定期的に促すアップデートをこっそり配信 - GIGAZINE

PCメーカのFAQページにも対処方法が書かれてしまう始末。

_FMV Q&A - [Windows 10] 「サポート終了の前に今すぐ PC をアップグレードしてください」と表示されます。 - FMVサポート : 富士通パソコン

だよな…。色々準備してからじゃないとアップグレードなんて怖くてできないよな…。

各メーカが「この手のソレはちゃんとバックアップをしてから作業しましょうね」と、事あるごとに、こうして啓蒙してるというのに、いきなり突然何の準備もさせずにアップグレードさせるとか一体何を考えてるんだ Microsoft は。各メーカの啓蒙活動を台無しにしやがって…。

ドキュメントフォルダその他の位置は大丈夫だろうか :

自分のメインPCは、ドキュメント、ダウンロード、ピクチャー、ミュージック等のフォルダを、C:\Users\USEARNAME\ 以下ではなくて別の場所にしてあるのだけど。この状態で Windows11 にアップグレードしても本当に大丈夫なのだろうか?

「デフォルトの場所から変更してある場合、中に入ってたファイルやフォルダは全て消えます」みたいなことになってたら困るというか絶望してしまうのだけど…。こういった状況でも問題無く処理できるように、ちゃんと動作確認してあるのだろうか…? もしかして、動作確認なんかしてなくて、ユーザ側から報告が来たらその時初めて動作チェックするつもり、とかだったら困る…。

*1: いやまあ、無料でアップグレードできる云々についてはユーザにとってはありがたい話だろうけど。なんだかやってることが頓珍漢な気もする。

#2 [zatta] USB Type-Cケーブルを購入

Seriaで、USB Type-C充電ケーブルを購入してきた。

型番は VN-30A。発売元は丸七株式会社。税抜き100円商品。USB Type A オス - USB Type C オス。5V 3A まで対応。長さ 50cm。色は黒。通信もサポートしてるらしい。断線防止、ひっぱりにも強いと謳ってる。USB PD(Power Delivery)は非対応、56KΩの抵抗付き、とパッケージには記述されていた。

ダイソーの充電式COBライト(充電端子 : USB Type-C)を充電できるか試してみたけど、フツーに充電できた。今回入手したケーブルは、初期不良品では無さそう。

ただ、端子の刺さり方が甘いのか、COBライトの充電LED(色は赤。充電完了すると緑になる)がなかなか点灯しなくて…。ただ、このCOBライト、他のケーブルでも似た状態になったので、COBライト側の端子が精度的にアレな可能性が高そう。ケーブル側は悪くない。はず。たぶん。

これで今後は Type-C端子の何かを入手しても問題無し。3Aまでなら、という条件付きではあるけど…。

それにしても、店頭には色んな種類の USB Type-Cケーブルがあって…。どれを選べば良さそうなのか、ちょっと悩んだ。流れる電流の量については差がありそう。昔、Raspberry Pi用の電源ケーブルについて調べた際には、銅線が太くて短いほうが抵抗値が少なくて良い、みたいな話があった気もするけど…。

2024/12/04(水) [n年前の日記]

#1 [zatta] LEDライトの改造記事を眺めてる

LEDライトの改造記事を、ググって探して眺めてた。

やりたいことは、天井のあたりにLEDライトを設置して、そこからケーブルを伸ばして手が届くところにスイッチだか電源だかを持ってきて、パチパチしてON/OFFしたい。できればLEDライトは電池で動くタイプがいい。コンセントから電源を取るのはちょっと厳しい。

「だったらリモコンでON/OFFできるLEDライトでもいいのでは?」とも思ったけれど。ワイヤレスになると、受信側が24時間365日、「電波(or 赤外線)が飛んできてないか? 飛んできてないか?」と待ち受けしてる状態だから必ず電気を食う。そうなると、すぐに電池が無くなって、電池交換で一苦労しそうだなと。コンセントから電源を取るタイプならそういう不便さはないけれど、今度はどこのコンセントから電源を取るかという問題が出てくる。そのあたりを考えると、見た目はダサいけど細いケーブルが這ってるほうがまだ妥当かなと。

そんなわけで、工作時のヒントがないかと、その手の記事を眺めてた。

経緯 :

ウチの玄関は照明関係の設計がおかしくて…。
  • 玄関の照明のスイッチは、玄関より奥の廊下についている。
  • 玄関についてるスイッチは、玄関の外の照明のスイッチ。玄関の照明ではない。

例えば呼び鈴が鳴って、家の中に居る人が玄関に向かった時は、途中で廊下にあるスイッチをつければ玄関の照明がつくので便利なのだけど。外が暗くなって、外出してた人が玄関に入った際には困ってしまう。灯りが欲しいのに、照明のスイッチは玄関よりもっと奥。玄関に居ては点けようがない。さりとて、玄関にあるスイッチをつけても、玄関の外しか明るくならない。

だから、暗い中で靴を脱いだりゴソゴソしたりする羽目になる。かなり不便。どうしてこんな配置/配線にしてしまったんだか…。

こういうのって、玄関と廊下の2ヶ所でスイッチをON/OFFできるように設計しておくべきなのでは。例えば、階段の上と下にスイッチがあって、どちらでもON/OFFできるようになってたりするけれど、ああいう感じで…。自分は学生時代に電気工事士の資格を取ったけど、その際そういう配線もできることを勉強していたので、ますます不思議。この玄関、なんでそういう配線にしなかったんだろう…。

とは言え、今から工事をして変えるのも面倒極まりないし。仕方ないので、人感センサ付きLEDライトを玄関に置いて誤魔化してるけど、これがそれほど明るくない製品で…。もっとガツンと明るくなる照明が欲しい。できれば目線の高さで光るモノではなくて、天井から下に向かって照らす状態だと嬉しい。目線の高さに置く何かでは視界に入った時に眩しい。そのくせ、ろくに周囲を照らしてくれなかったりする。

ということで、どうにかならんかなと夢想しながらその手の改造記事を眺めているところ。

2024/12/05(木) [n年前の日記]

#1 [zatta] CanDo版COBライトを購入

ここ数日、犬の散歩をする際に、ダイソーで購入した充電式COBライト(LEDライト)を持ち歩いているのだけど、330円と言う値段の割に、なかなか明るくてイイ感じだなと感心しているところ。

ただ、ダイソー版は最大で250lm(ルーメン)の明るさ。この手の製品は最大500lmのスペックが多かったりするので、500lmだとまた違うのだろうかと気になってきた。

そんなわけで、最大500lmを謳っている、CanDo版の充電式COBライトも購入してしまった。税込770円。

ダイソーで販売されてるCOBライトとの大きな違いは以下。
さておき、パッケージには以下が記述されていた。念のために一応メモ。
注意事項として以下が記述されていた。
今頃気づいたけれど、「5V/2AのACアダプタを使って充電せよ」と明記されていたのだな…。

購入時から充電はされているようではあったけど、初回時は充電せよと明記されていたので、一応充電はしてみた。充電LEDが赤くなったし、充電が終わったら緑になったので、初期不良品では無さそう。

充電後、ダイソー版と比較してみたけれど、たしかに明るさはほぼ倍のような気がする。
明るさにしても、値段にしても、CanDo版はダイソー版の2倍と思っておけばいいのかもしれない。

もっとも、ダイソー版でも十分に明るい気もする。100円ショップで販売されてきた今までのLEDライトと比べたら、ダイソー版COBライトもレベルが違う明るさ、と言ってしまっていいのではないかと…。

発熱が結構ある :

動作確認中に気づいたけれど、発熱が結構あるなと…。もしかするとこの熱でバッテリーが急速に劣化する可能性もあったりしないか…。もしそうだとすれば、明るい製品のほうがいいとは一概に言えなくなってくるのかも。暗ければ発熱も少ないだろうし、動作時間もその分長くなるだろうし。結局はバランス、という話になるのかな…。

2024/12/06(金) [n年前の日記]

#1 [zatta] COBライトにトレーシングペーパーを貼ってみた

昨日購入した充電式COBライトが眩し過ぎて、うっかり直視したら目が潰れそうになるので、試しに表面にトレーシングペーパーを数枚貼ってディフューザー代わりにならないか実験してみた。

効果は無かった…。相変わらず眩しい…。

光量があり過ぎる場合、この手のソレは効果が無いということだろうか。それとも元々COBライトで面が発光してる状態に比較的近いから、今更光を拡散させてもさほど意味はないのかも。

あるいは、トレーシングペーパーではさほど拡散なんてしないのかもしれない。もっと適した材質で実験しないとダメだろうか…? 世の中にはディフューザーフィルムというものがあるらしいけど、そういった材料を使わないと効果は出ないのかな。ただ、お値段を調べたら結構するので気楽に試せる感じでは無さそう。

COBタッチライトにも貼ってみた :

以前ダイソーで購入した、COBタッチライトにもトレーシングペーパーを貼ってみた。直径65mmの円をハサミで切り出して、2枚重ねて、木工ボンドっぽいもので貼り付け。

_mieki256's diary - ダイソーで買ってきたタッチライトを設置
_ローコスパ: ダイソーの「COBタッチライト」。
_100均のCOBタッチライト[ホワイト・オレンジ]のレビュー
_セリアで新商品発見 | 時代遅れのアナログ爺さん - 楽天ブログ

こちらはトレーシングペーパーを貼ってみたことで眩しさが多少減ってくれた気がする。

せっかくだから玄関に設置。写真を撮ってみた。

cobtouchlight_ss01.jpg

真ん中に貼ってあるのは蓄光シール。どこを押せばいいのか分からない感じだったので印をつける感じで貼ってみた。

以下のような画鋲を使って壁に引っ掛けてみた。

cobtouchlight_ss02.jpg

夜になってから明るさを確認してみたけど、一応玄関全体をほんのり照らしてくれる程度の明るさかなと…。蛍光灯の明るさには全然敵わないけど…。いや、横方向に光ってるのがよろしくない気もする。天井に設置して下に向けて照らせばもっとイイ感じになりそうだけど、そうなるとスイッチが押せなくなる…。

壁に引っ掛けるための専用の画鋲はないのだろうか :

100円ショップで購入できるこの手の製品の背面には、壁に引っ掛けるための穴があったりするけれど、その穴にピタリと入る何かをどうやって用意するのかが悩ましい。大きさや太さ的に入らなかったり、入っても高さがあり過ぎて前述の写真のように傾いてしまったり。

この手の用途に特化した画鋲は無いのだろうか…。

木ネジならしっかり固定はできるけど、壁に大きな穴が開くので、後から外すことになった際によろしくない。その点画鋲なら最低限の穴で済む。もっともその代わり、強度(?)はそれほどないから、軽い何かをぶら下げることしかできないけれど。

2024/12/07() [n年前の日記]

#1 [zatta] 釘や木ネジの代わりになりそうな画鋲が見つからない

背面に固定用の穴が開いているLEDライトを壁に固定したい。画鋲でどうにかできないかなと良さそうな画鋲をググって探してみたけれど、これが全然見つからない…。

背面に固定用の穴が開いている時計やLEDライトは、おそらく釘や木ネジに引っ掛けることを想定して穴を開けてあるのではないのかなと思うのだけど。

釘や木ネジは、壁や柱に大きな穴を開けてしまう。持ち家ならともかく、賃貸の部屋に住んでる場合、大きな穴を開けてしまうのはちょっとよろしくないはず。その点、画鋲やピンの類なら、開ける穴は小さくて済む。だから、そういった固定用の画鋲やピンが商品として存在しているのではないか、絶対にあるはずだ、存在しないわけがない。

そう思ってググってみたのだけど、これが不思議なぐらいに見つからない…。

いや。考えてみたら、不思議でも何でもないのだな…。

画鋲は、頻繁に刺したり抜いたりすることを前提にしているので、指でつまんで抜き差しに力を入れる部分が「大きくて」「長くて」「太い」ほうが使い勝手がいい。故に、巷で販売されてる画鋲の類は、ダルマ型と称される、「大きくて」「長くて」「太い」形になってるし、商品としてアピールする際も「つまむ部分が『大きくて』『長くて』『太い』ですよ! 便利そうでしょ?」と謳っている。

しかし今回、釘や木ネジの代わりになる画鋲が欲しいわけで…。その場合、指でつまむ部分は、「小さくて」「短くて」「細い」ほうがいい。

だけどそんな形では、抜き差しに苦労すること間違い無し。「そんな不便な画鋲を作っても売れるわけないやろ。アホちゃうか」と、どのメーカも思うはず。だからどこも作らない。実に当たり前。不思議でも何でもない。

でも。「固定用の穴が開いている何かしらを壁や柱に固定したい」「できれば大きな穴を開けずに処理したい」という需要は必ずあるはずで…。

なのにググった感じでは、その需要にフォーカスした品は見当たらない…。まあ、ニッチな需要ではあるのだろうけど。ニッチだけど必ず存在する需要のはず、なんだけどなあ…。

2024/12/08追記 :

「時計用ピンフック」でググればそれらしい商品を見つけることができると分かってきた。「時計」や「クロック」というワードがポイントらしい。

たしかに、壁にかける何かと言えば時計が一番メジャーな気もする…。LEDライトを壁に引っ掛ける事例なんてまず無いよな…。

2024/12/08() [n年前の日記]

#1 [anime] 「劇場版スラムダンク」4作品を視聴

BS12で放送されていたので視聴。昔作られた「スラムダンク」アニメ版の、劇場公開された4作品を一挙放送とのことらしい。

以前も放送されていて、一応視聴した記憶があるのだけど、内容を忘れていたのでそこそこ楽しめた。

当時としては原作の絵柄をかなり再現しようと試みていた気がするし、こんなに線が多いのに結構動かしていた気がする。しかしこれでも漫画原作者から不満を持たれていたのか…。キビシー。

スポーツ漫画をアニメにすると試合展開がギクシャクしてしまうのは仕方ないかも、とも思えた。例えば、主人公がボールをリバウンドで取った後、「ミッチー! パス!」と叫ぶカットがあったのだけど、そう叫んでる間、主人公はボールを持ったままその場でずっと止まっていた。

実際のバスケの試合ではそんな動きはありえないわけだけど、アニメの場合はその台詞を言い終わるまで状況を変化させるわけにはいかないので、えてしてそこで動きがピタリと止まってしまう。当然、試合の流れがスムーズに進むことはなく、どれかしらのキャラが何か喋ったり叫んだりするたびに、試合の流れがそこでピタリと止まってギクシャクしてしまうわけで…。

「スラダン(スラムダンク)」は「ブルーロック」ほど主人公が頭の中でアレコレ考えているわけではないのに、そのスラダンですらこういう見せ方をちょくちょく挿入していかないとアニメらしくならないのだなと…。おそらく漫画なら気に留めずにすらすら読んでしまうのだろうけど、アニメではそういうわけにはいかない。漫画ってやっぱりズルイよなーと再認識。まあ、漫画の側から見ると「アニメってやっぱりズルイ」と思わせるアレコレもあるんだろうけど。メディアの違い、得手不得手ってあるよな…。

#2 [nitijyou] 部屋の中に蚊が居るような気がする

部屋の中に蚊が居るような気がする…。顎のあたりが痒い。刺されたかも。仕方ないので蚊取り線香をつけた。

数日前にも台所で蚊を見つけて潰したので、まだそのへんを飛んでいるということだろうか。こんなに寒いのに…。

フィラリアの予防薬を貰ってこないといけない :

この時期に蚊が居るのは、飼い犬にとって困る…。12月中も蚊が居るなら、蚊を媒介にして犬の体内にフィラリアの幼虫が既に入ってる可能性がある…。飼い犬のフィラリアの薬は12月頭に飲ませた分しか貰ってきていないので、1月分も貰ってきて飲ませないと…。

フィラリアの幼虫は2ヶ月で成虫になって宿主の犬の寿命を縮めるのだけど。フィラリアの予防薬は、犬の体内で1ヶ月ほど幼虫を放置しておいて、1ヶ月に1度、薬を与えて幼虫を殺して成虫になるのを防ぐ仕組み。飲ませてから1ヶ月効き続けるとかそういう種類の薬じゃない。飲ませた時だけ効く。だから、もう蚊が居ないと思われる時期まで飲ませ続けないといけない。

本来、冬は蚊が出ないから薬を飲ませなくて済むのだけど、なんだかこの調子だと1年中飲ませる状況になってしまいそう。これも温暖化のせいだったりするのだろうか…。

#3 [nitijyou] 壁の中にネズミが居る

壁の中からゴソゴソと音がする。ネズミが居る…。ここ最近は聞こえなかったのに…。

気温が下がってきたから、家の中に入ってきたのだろうな…。どこから入ってきたんだか…。床下の穴の入り口をチェックしてみないと…。

2024/12/09(月) [n年前の日記]

#1 [pc] PC用ゴルフゲームを検索してた

録画していたTVアニメ「オーイ!とんぼ」を見ているうちに、なんとなくゴルフゲームをプレイしてみたい気持ちになってきた。でも、PC上で動くゴルフゲームってあるのかな。以前探したときはほとんど見当たらなかったけど…。気になったのでググってみた。

やはり、PC用のゴルフゲームとなると、壊滅的なまでに存在してないようだなと…。

いやまあ、Steamで入手できる、英語圏向けのゴルフゲームならあるっぽいけど、それも1タイトルぐらいしか見つからなかった。ミニゴルフ、2D画面のゴルフゲーム、奇抜なコースでプレイする系のゲームなら山ほどあるけれど…。硬派(?)なタイプは見当たらない。

ただ、スマホ上で動作するゴルフゲームならたくさんあるので、ゴルフゲームと言うジャンル自体が死滅したわけではなさそう。あくまで、PC上で動くゴルフゲーム、それも3Dで描画される例のタイプがほぼ死滅状態という感じ。

どうしてこんな状態になってしまったのだろうな…。原因としては何が考えられるのか…。

2024/12/10(火) [n年前の日記]

#1 [pc][zatta] ゴルフが衰退した理由を調べてた

思考メモ。

PC向けのゴルフゲームがほぼ死滅状態になった理由が知りたくて少しググってた。その過程で、そもそもゴルフと言うスポーツ自体が衰退しつつあることを知った。

_少子高齢化、富が高齢者に集中した結果のゴルフの終焉|More Access! More Fun
_ゴルフはなぜここまで凋落してしまったのか 半減した市場、「6000億円」増提言の現実味 | ゴルフとおカネの切っても切れない関係 | 東洋経済オンライン
_ゴルフのコロナバブル終焉がゴルフ場にもたらす影響 | takashioya.com
_ゴルフブームはいつまで続く?若者から人気を集めているのはなぜ?
_「初心者」に厳しいと、結局、自分たちのクビを締めることになる | Books&Apps
_初心者には厳しいルールよりゴルフの楽しさを。 | 奈良柳生カントリークラブ総支配人ブログ

全盛期の1/3までプレイ人口が落ち込んでるらしいけど、色々な理由が考察されてる。お金がかかるとか、時間がかかるとか、一緒に回るメンバーが集めにくいとか、初心者に厳しいスポーツだから新規に始める人が少ないとか。どれも納得。団塊世代がゴルフをできなくなったら更に酷い状況になるだろうと数年前の記事では予言されている。

ただ、コロナウイルス流行の影響で、3密を避けられるゴルフが若干人気を取り戻したという話も見かけた。その人気が継続すればまた状況は変わるのだろうか。

ゴルフゲームが衰退した理由 :

PC向けゴルフゲームが死滅状態になった理由はよく分からなかった。お金がかかる云々で言えば、ゲームで遊べば実際にゴルフをするよりお金がかからないはずだからプラスに働きそうな気もするのだけど、実際はそうなってない…。

ゴルフゲームと言えば「みんなのゴルフ」シリーズだけど、そのあたりの勢いが無くなった件については考察を見かけた。プレステの値段が上がって、ゲームマニアしか購入しない状態になったから、ゴルフゲームをプレイしていたのであろうライトユーザー層がごっそり消えて、ゴルフゲームと言うジャンル自体を支持してくれる層が消滅した、とかなんとか。

スマホ用のゴルフゲームはまだまだたくさんあることを考えると、その考察は当たってるような気もする。スマホならプレステと違って皆持ってるだろうし…。

ただ、PC向けが無くなったあたりは、やはりよく分からない。

これは自分の勝手な想像だけど、PCでゲームをプレイすること自体がマニア層の行動なのかなとも…。PCでゲームと言えばゲーミングPC云々というイメージになるわけで、そういったPCを買う人達がゴルフゲームを支持する光景はちょっとイメージできない…。

まあ、単に、ゴルフゲームはプレイ時間が長過ぎるというイメージがあるから、それで避けられてしまったのだろうか。「みんなのゴルフ」シリーズも、18ホールを回ろうとすると2時間ぐらいかかった記憶があるし。映画一本分の自由時間があっさり消費されてしまう。色々な娯楽コンテンツがある中で、ゴルフゲームは時間を無駄にしてる感が強いのだろうか。

もっとも、以前スマホでプレイしたゴルフゲームは、好きなホールを選べるようになっていて、数分で1ホールをプレイして、その結果は記録されて次回のプレイに反映されたりしていたので、作り方次第でプレイ時間短縮はできそうな気もする。もっとも、「ゴルフゲームは時間がかかる」と一度思い込まれたら、またプレイしてくれる可能性は低いだろうし…。

ウチの親父さんも、以前ゴルフゲームを勧めたら「ゴルフは時間がかかるから嫌だ」と言っていたし。そういうことなのかもしれない。

ゴルフゲームの魅力 :

自分がゴルフゲームに感じる魅力は何だろうと考えると…。緑が多い風景の中をウロウロできることかな、と思えてきた。つまるところ、簡易オープンワールドゲームっぽいものを件のジャンルに感じているのかもしれない。

ただ、今時は色々なジャンルでオープンワールド云々が実装されているわけで…。そんな状況であえてゴルフをやらなくてもいいのでは、他にもっと良さそうなジャンルがあるだろう、と思ってしまいそうな気もする…。

逆に、オープンワールドを売りにしてるゲームの中にゴルフを入れてしまうと言うのもアリなのだろうか。例えば Minecraft の中でゴルフができたら…。と妄想してしまったところで気になってググってみたら、あのゲームはそういうこともできるらしい。皆、似たようなことを考えるのだな…。

まあ、只の思考メモです。オチは無いです。

2024/12/11(水) [n年前の日記]

#1 [godot] Godot Engineでゴルフゲームっぽいものが作れそうか実験中

ゴルフゲームについて調べてるうちに、今時のゲーム制作ツールを使えば比較的楽に作れたりしないのかな、どうなんだろうと気になってきた。

そんなわけで、無料で利用できるゲームエンジン、Godot Engine を試用して実験してみることにした。

環境は Windows10 x64 22H2 + Godot Engine 4.3。

Godot Engineの入手 :

Godot Engine は以下から入手できる。

_Download for Windows - Godot Engine

最初は32bit版の Godot_v4.3-stable_win32.exe.zip (Windows - x86_32) を使ってみたけれど、blender からエクスポートしたモデルデータ(.glb、.fbx)がインポートできずに Godot Engine が落ちて落ちて落ちまくる不具合に遭遇して困ってしまった。.obj ならインポートできるのだけどな…。32bit版のバグなのか、それとも Godot 4.3 のバグなのか。

64bit版の Godot_v4.3-stable_win64.exe.zip (Windows - x86_64) を使ってみたら、.glb も .fbx もすんなりインポートできてしまった。インポートできなかったのは32bit版特有のバグらしい。どうやら 64bit版を使ったほうがハマらずに済むようだなと…。

さておき。解凍すると以下の2つのファイルが入ってる。
Godot_v4.3-stable_win64.exe
Godot_v4.3-stable_win64_console.exe
どちらかを実行すると、Godot Engine の画面が表示される。

*_console.exe は、DOS窓(コマンドプロンプトウインドウ)が開いた状態で動作する版。DOS窓上に何か警告メッセージが表示されている時があるので、自分は *_console.exe を使ってる。

ひとまず、適当な名前でプロジェクトを新規作成。

3Dゲームっぽい画面の基礎 :

Godot Engine は、ノードと呼ばれるオブジェクトを追加していくことでゲームが作れる。

とりあえず、最低限の3Dゲームっぽい画面に見えてくれそうなノード構成を試してみた。
  • 画面左上にあるシーンタブの、左上の「+」ボタンをクリックすればノードを追加できる。
  • 各ノードはドラッグアンドドロップで位置や階層を変更できる。

godot3dtake01_ss01.png
Node3D
├─ Camera3D
├─ DirectionalLight3D
└─ StaticBody3D
     ├─ MeshInstance3D
     └─ CollisionShape3D

一応ざっくり説明。読み飛ばし推奨。
  • Node3D は、3Dのオブジェクトの基礎となるノード。Godot 3.x では Spatial というノード名だったけど、4.x では Node3D に置き換わった。たぶん。
  • Camera3D は、カメラ担当ノード。Godot Engine で3Dゲームっぽいものを作ろうとする場合、必ずカメラが必要になる。これを追加し忘れて、実行しても画面に何も出なくておかしいなー変だなーと悩むのは Godot Engine のあるあるネタ。
  • DirectionalLight3D は平行光源。太陽の光の向きを指定するというか…。一方向から平行に照明を当ててくれる。
  • StaticBody3D は、画面内で静止している物体を入れておくノード。地面とか床とか壁とか、その世界の中で動かないものを担当する。更に StaticBody3D は、見た目を担当するノードと、コリジョン(アタリ判定、衝突判定)を担当するノード、2種類のノードを含めることになる(子ノードとして登録する)。
  • MeshInstance3D は、表示されるポリゴンモデルデータを担当するノード。見てくれ担当。
  • CollisionShape3D は、コリジョン/アタリ判定のための領域を指定するノード。アタリ範囲担当。


前述のようにノードを配置すると、Godot Engine の画面上では以下のような見た目になる。(箱のサイズ等は弄ってる)

godot3dtake01_ss02.png


画面右上の右向きの三角をクリックするか、F5キーを押せば実行できる。最初の実行時は「メインシーンが指定されてないけどどうする?」みたいなダイアログが開くけど、現在開いているシーンを使う、的な選択肢を選べばOK。

godot3dtake01_ss14.png


以下のような実行画面が表示された。画面内に、MeshInstance3D で指定したソレが表示されている。

godot3dtake01_ss03.png


そして、StaticBody3D を登録してあるので、この板はピクリとも動かない…。

物理計算するオブジェクトを追加する :

重力が影響して落ちていくオブジェクトを追加したい。そんな時は、RigidBody3D ノードを使う。

godot3dtake01_ss04.png
Node3D
├─ Camera3D
├─ DirectionalLight3D
├─ StaticBody3D
│   ├─ MeshInstance3D
│   └─ CollisionShape3D
└─ RigidBody3D
     ├─ MeshInstance3D
     └─ CollisionShape3D

今回は、RigidBody3D の子ノードの MeshInstance3D や CollisionShape3D に、球(SphereMesh、SphereShape3D) を指定してみた。

実行すると以下のようになる。




球が、重力で落ちていって、StaticBody3D の板の上で止まっていることが分かる。

これで、最低限、物理計算で何かが動いてるっぽい感じの画面は作れた。これをアップグレードして、それらしいゲーム画面を作っていく。

補足。組み込みの形状を指定する流れ :

見てくれを担当するノード、MeshInstance3D は、あらかじめ簡単な形状を組み込みで持っている。その組み込み形状を選ぶ操作もメモしておく。

MeshInstance3D ノードを選んでる状態で、画面右端のインスペクタータブで形状を選ぶ。Mesh という項目が「空」になっているはずなので、その右の下向きの三角?をクリック。

godot3dtake01_ss05.png


選べる形状がずらりと表示される。

godot3dtake01_ss06.png

Box は箱。Sphere は球。アイコンを見れば、大体の形は想像できるかな…。


BoxMesh を選んでみた。箱が表示される。この箱の大きさを指定したい。箱のアイコン?をクリック。

godot3dtake01_ss07.png


変更できる項目がずらりと表示される。

godot3dtake01_ss08.png


Size を変更してみた。大きさが変わって、箱が板のような見た目になってくれた。

godot3dtake01_ss09.png


同様に、コリジョン形状も選択することができる。CollisionShape3D を選んだ状態で、インスペクタータブを操作する。Shape が空になっているのでクリック。

godot3dtake01_ss10.png


これも選べる形状がずらりと表示される。

godot3dtake01_ss11.png


BoxShape3D を選んでみた。これも大きさを変更できる。BoxShape3D と表示されてるところをクリック。

godot3dtake01_ss12.png


これも、Size を変更することで大きさを変えられる。先ほど MeshInstance3D で指定した値と同じにしておけば、表示されている箱(板?)と同じサイズのコリジョン範囲を指定できる。

godot3dtake01_ss13.png

#2 [godot][zatta] ゴルフコースモデルデータを作成中に悩んでしまった

blender を使ってゴルフコースっぽいモデルデータを作成しようとしたけれど、ゴルフコースには見えないモデルデータになってしまって悩んでしまった。どういうモデルデータならそれっぽく見えてくるのだろう…。

ここは一つ、実在のコースをトレースしてポリゴンモデルにできないか? と思ったところでまた悩んでしまった。その場合、権利関係が絡んできたりするのではないか…?

100年前に作られたコースのデザインは今でも保護されるのだろうか :

昔に作られたゴルフコースなら、さすがに色々な権利が切れてたりするのではないのかなと思いついた。でも、昔に作られたゴルフコースって、どんなものがあるのだろう?

ググってみたら、世界最古のゴルフコースは、セント・アンドリュース オールドコース、ということになっているらしい。1552年に作られた、とある。

_セント・アンドリュース オールドコース - Wikipedia

日本最古のゴルフコースは、神戸ゴルフクラブ、ということになっているらしい。1903年。明治36年に創立。

_歴史 | 一般社団法人 神戸ゴルフ倶楽部

日本で二番目に古いゴルフコースは、雲仙ゴルフ場らしい。1913年。大正2年。

_雲仙ゴルフ場


さて、約100年前、あるいは470年前のゴルフコースデザインは、今でも法的に保護されていたりするのだろうか…? どうなんだろ…? 著作権ですら70年も保護されることになっていてふざけんなって感じなのに、それより長期に渡って保護されちゃうとしたら、それはさすがにおかしい気もする…。

そもそもコースデザインって一体何で保護されるのか。意匠権? だとすると日本国内の場合25年で切れることになるのかな。ただしそれは最長の場合、とも書いてある…。しかも毎年登録料を払わないと維持されないっぽい…?

_意匠制度の概要 | 経済産業省 特許庁

あるいは著作権で保護されるのだろうか。しかし、ゴルフコースって著作権が適用されるものなのか? 著作権が認められるには条件があるはずだけど…。

_著作権とは?権利の種類・期限・著作権侵害にならないケースなどを分かりやすく解説!

ゴルフコースは一体何で保護されているのか。あるいは保護されていないのか。ググってみたけれど、それらしい解説には辿り着けなかった…。勝手に使っていいのか、使ってはいけないのか、どっちなんだろう…。

とかなんとか考えていくと、現実にはありえない奇抜なコースデザインのゴルフゲームがたくさんあることにもなんだか納得してしまう。実在するコースを盛り込むのは、ひょっとすると権利的に面倒臭い可能性が…? 法律に詳しい専門家に問い合わせをするより、最初からトンデモコースを作っちゃったほうが安心だよな…。

ゴルフ場の名前を出してしまうのはアウトだとしても、なんだか似た感じのコースがゲーム等で出てくるのは、アウトなのかセーフなのか。よく分からん…。

ちなみに、韓国ではアウトらしい。そういう裁判事例を見かけた。

_他人が運営するゴルフ場の様相を再現した3D映像を製作しスクリーンゴルフ運営会社に提供した行為は成果物盗用不正競争行為に該当するとされた事例 | 判例データベース - 知的財産に関する情報 - 韓国 - アジア - 国・地域別に見る - ジェトロ

日本ではどうなるの…?

宣伝材料にならないだろうか :

ゴルフ場のコースデザインをゲームだのアニメだので使っていいよと言い出すことで、ゴルフ場の宣伝になったりはしないかな、などとつい妄想してしまった。

「ウチのゴルフ場の名前を宣伝してくれるならコースデザイン使っていいよ」
「でもさすがに全ホールを使われるとなんかヤバそうだからウチの自慢のn番ホールだけなら使っていいよ」
「せっかくだからn番ホールの地形データも配布しちゃうよ」
みたいな。

例えば、アニメ番組の中で実在の場所が登場すると、聖地と称されて観光客が増えたりする現象が稀に起きたりするけれど、あんな感じの展開をほんのり期待できないか…。例のプール、みたいな感じで、○○業界では有名なあの○○ゴルフ場の○番ホール、として広まったりしないか…。そのうちどこぞのユーチューバさんが「ここがあの聖地! ○○ゴルフ場です!」と動画の中で紹介し始めたり、とか…。

そんなバカ妄想をしてみたものの、ゴルフ場と言うのはゴルフのプレイヤーさんに来てもらって商売してる場所だから、ゴルフをしない観光客が増えても旨味も何も無いよなと気が付いた。そもそも観光客の類が入ってもいい場所なの…? ダメなんじゃないの? 行ったことないから分からんです。

ゴルフ場の名前だけが広く知れ渡っても、これっぽっちも嬉しくない業界、なのではないかなあ…。他の業界とは色々違うはず…。たぶん。知らんけど。

2024/12/12(木) [n年前の日記]

#1 [godot] RigidBody3Dが地面をすり抜けてしまう

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit で実験中。

blender で作成した地形モデルデータっぽいものを Wavefront形式(.obj, .mtl)でエクスポートして、Godot 4.3 64bit版にインポートして利用しようとしているのだけど。

ボール相当の RigidBody3D が、地面のメッシュを ―― blenderで作成したポリゴンモデルデータをすり抜けてしまって悩んだ。ちゃんと地面と衝突してくる時がほとんどだけど、稀に、清々しいまでにスポーンと抜けていってしまう時がある…。一体どうすれば…。

以下のやり取りで解決策が提示されていた。

_RigidBody slips through floor and I can't figure why : r/godot

色々な手はあるようだけど、とりあえず、RigidBody3D の、Solver → Continuous CD にチェックを入れて有効にする、というのが手っ取り早そう。ドキュメントには、動きを予測してコリジョン判定してくれる、と書いてあった。 *1 試したところ、地面をすり抜けない状態になった。

他にも、プロジェクト設定で、「1秒あたりの物理ティック数」を増やすのも効果が期待できそう。プロジェクト → プロジェクト設定 → 物理 → 一般 → 1秒あたりの物理ティック数、を変更する。先ほどの Continuous CD はオフにした状態で、デフォルト値 60 から 120 に変更したところ、これも地面をすり抜けない状態になった。その分CPU負荷は増えるだろうけど、地面をすり抜けるよりは全然マシだろう…。

また、Godot Jolt という物理エンジンを追加することで改善される場合もあるらしい。

_Godot Jolt - Godot Asset Library
_godot-jolt/godot-jolt: Godot Jolt is a Godot extension that integrates the Jolt physics engine

CPU負荷は標準の物理エンジンとほぼ同等だけど精度が改善される、と謳っているように見える。Godot 4.3 でしか利用できないけれど、Godot Asset Library に登録されているので導入は比較的楽らしい。ただ、今回は前述の2つの方法で解決できてしまったので、この追加ライブラリは利用しなかった。
*1: これは勝手な想像だけど、おそらく前フレームと現フレームの位置の差を延長してアタリ範囲に加える、とかそういう感じの処理を追加してくれるのかもしれない。知らんけど。

#2 [godot][zatta] ゴルフコースのデザインをAIにお願いしたら失敗

ゴルフコースっぽく見える仮モデルデータを作るべく、既存のゴルフコースの画像を眺めて雰囲気を勉強しようと思ったのだけど、そこでふと、「こういうのって画像生成AIに作ってもらったらいいんじゃね?」と魔が差して(?)しまった。彼等もそのあたり多少は学習してたりしないか。イイ感じにアチコチからパク、いや、エッセンスを反映させつつ生成してくれるのでは。

そんな期待をしながら、Microsoft や Google の画像生成AIに生成してもらったのだけど…。これはちょっと…。こういうのはちょっと無理っぽいな…。ゴルフコースらしさって何だろうと思わず考え込んでしまう画像ばかり出てきてしまった。

いやまあ、どの生成画像も、トンデモゴルフ漫画だったら全然使えそうなコースデザインではあるのだけど。

「うげエ! グリーンが5つ…いや6つ! どれが本物のグリーンかわからねえ!」とか。
「なんて長いコースなんだ! 端のほうが霞んでしまって見えねえぞ!」とか。

そういう感じのハチャメチャゴルフコースならバンバン生成してくれる。あるいは、不思議の国のアリスあたりに出てきそうな、どこか悪夢めいたゴルフ画像なら任せとけ、みたいな印象も受けた。場面によっては使えるのかもしれん…。

とは言え今回はちょっと使えない感じ。

2024/12/13(金) [n年前の日記]

#1 [godot] Godot Engineにblenderで作成したモデルデータをインポートする

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit で、ゴルフゲームっぽいものを作成できそうか実験中。

コースのモデルデータを作らないと実験できないよなと…。そのあたりを試していた。

blenderで地形モデルデータを作成 :

ゴルフコースに相当するモデルデータは、blender で作成したい。blender 3.6.18 LTS を利用して作業した。

まずは blender上の単位を合わせないといけない。シーンタブを選んで、単位を設定。単位系はメートル法、長さはメートルになっていることを確認。

godot3d02_ss01.png

まあ、ゴルフ関係って、メートルじゃなくてヤードが多いけど…。Googleで「360ヤード メートル」と打って検索すれば変換値を出してくれるので、それでどうにか…。


ゴルフコースの大体の長さを測りつつ作業していきたい。メジャーツールがあるので、それを使って距離を測る。

godot3d02_ss02.png

  • メジャーツールは、マウスの左ボタンを押したままドラッグすれば線が引かれて、その線の長さを表示してくれる。
  • Ctrl + 左ボタンで頂点に吸着(スナップ)してくれる。Ctrlキーを押すと吸着中を示す丸が表示されるので、その状態で頂点上で左ボタンを押したままドラッグする感じで操作する。
  • メジャーの削除は、消したいメジャーが選ばれている状態でXキー。


ポリゴンの表裏が統一されてないと、Godot Engine に持っていった時にポリゴンが表示されなかったりするので、表裏についても確認しておく。ビューポートオーバーレイ(?)の設定で、面の向き、にチェックを入れると、表が青、裏が赤で表示される状態になるので、視覚的に確認できる。

godot3d02_ss03.png

godot3d02_ss04.png


ポリゴンの表裏を統一させたい時は…。基本的には、ポリゴンを全選択して、メッシュ → ノーマル → 面の向きを外側に揃える、を選べば良さそう。
  • Alt + N でノーマル(法線)に対する処理のメニューが表示される。
  • あるいは Shift + N で面の向きを外側に揃えることができる。


モデルデータができたら、Wavefront(.obj)形式でエクスポートする。ファイル → エクスポート → Wavefront (.obj) を選ぶ。

godot3d02_ss05.png


今回は地形モデル部分を選択した状態で、対象:選択中のみ、メッシュの三角形化、にチェックを入れてエクスポートした。

godot3d02_ss14.png


これで、.obj と .mtl がエクスポートできた。

ちなみに、blender からエクスポートされた .obj, .mtl を使うと、ポリゴンの色が全体的に暗くなる。blender はポリゴンの色をガンマ補正しながら表示しているけれど、.obj でエクスポートするとマテリアルカラーはガンマ補正されない値で出力されるので、他の何かにインポートされると色合いが変わる。できるだけ色を近づけたいなら、.mtl の各値を補正するか、インポート先でマテリアルカラーを設定し直す必要がある。

_mieki256's diary - blenderでobjをエクスポートした際の色を補正

一応、今回作成したモデルデータを置いておきます。実験用の仮モデルデータぐらいにはなるんじゃないかなと…。

_golf_course_take02.zip

Godot Engineでインポートする :

blender 3.6.18 LTS からエクスポートした .obj, .mtl を Godot Engine 4.3 64bit にインポートする。

ちなみに、Godot Engine 4.3 32bit版で処理すると .glb や .fbx をインポートできずに Godot が落ちるので、Godot 64bit版を使ったほうがいいと思う。64bit版なら落ちない。まあ、.obj をインポートする分には32bit版でも落ちないけれど…。

プロジェクトフォルダの中にモデルデータをコピーすれば、Godot でプロジェクトを開いた際にモデルデータが自動でインポートされる。今回は、imports という名前のフォルダを作成して、その中にモデルデータを入れておくことにした。

MeshInstance3Dノードを追加。Mesh のところが「空」になっているので、インポートした .obj をドラッグアンドドロップしてやる。

godot3d02_ss06.png


.obj がメッシュとして選択された。

godot3d02_ss07.png


このメッシュから、コリジョン用のモデルデータを作りたい。MeshInstance3D が選択されてる状態で、メッシュ → コリジョン形状を作成... を選ぶ。

godot3d02_ss08.png


ダイアログが開くので、ノードの配置場所、シェイプの型を選ぶ。

godot3d02_ss09.png


CollisionShape3D が新規作成されてシーンに追加された。

godot3d02_ss10.png


CollisionShape3D を選択すると、プレビューウインドウ内で、コリジョン用モデルデータが作られていることが分かる。

godot3d02_ss11.png

ボールやカメラを動かす :

RigidBody3D をボール相当にしたい。キーボードのキーを押したら、前後左右に動いたり、前方に飛び出したりするようにしたい。

RigidBody3D にスクリプトをアタッチする。内容は以下のようにした。

_rigid_body_3d.gd
extends RigidBody3D

@export var speed: float = 20.0
@export var shot_speed: float = 30.0
var target_velocity = Vector3.ZERO
var init_pos = Vector3.ZERO

func _ready():
    init_pos = global_position


func _process(delta):
    if Input.is_action_just_pressed("ui_home"):
        global_position = init_pos
        
    if Input.is_action_just_pressed("ui_accept"):
        apply_central_impulse(Vector3(0, shot_speed, -shot_speed))
        
    var d = Vector3.ZERO
    if Input.is_action_pressed("ui_left"):
        d.x = -1
    if Input.is_action_pressed("ui_right"):
        d.x = 1
    if Input.is_action_pressed("ui_up"):
        d.z = -1
    if Input.is_action_pressed("ui_down"):
        d.z = 1
    if d != Vector3.ZERO:
        d = d.normalized()
        d.x *= speed
        d.z *= speed
        apply_central_force(d)

  • @export を使うと、Godotの画面上で、GUIで値を変更できるようになる。
  • _ready() は、そのオブジェクトが画面に出現した際に呼ばれる関数。
  • _process(delta) は、毎フレーム呼ばれる関数。delta には前フレームからの経過秒数が入っている。単位は秒。
  • apply_central_impulse() で、瞬間的に速度を追加できる。
  • apply_central_force() で、力を加える。
  • Input.is_action_just_pressed() で、キーが押された瞬間を検知できる。
  • Input.is_action_pressed() で、キーが押されているかどうかを検知できる。

上記のスクリプトでは、以下のキー判定と処理が書いてある。
  • Homeキー ("ui_home") : 初期位置に戻る
  • スペースキー ("ui_accept") : 前方に飛んでいく
  • カーソルキー : 前後左右に転がる


カメラにもスクリプトをアタッチする。ボールを追いかけるような処理を追加したい。

_camera_3d.gd
extends Camera3D

@export var target: Node3D
@export var offset: Vector3 = Vector3(0, 3, 8)
@export var follow_speed: float = 12.0

func _ready():
    var tpos = target.global_position + offset
    global_position = tpos
    look_at(target.global_position, Vector3.UP)
    # rotation.x = 0

func _process(delta):
    if not target:
        return
        
    var tpos = target.global_position + offset
    global_position = global_position.lerp(tpos, follow_speed * delta)

  • target には、追いかけたいオブジェクトを指定しておく。
  • target で指定されたオブジェクトのグローバル座標に、オフセット値を追加した座標を、カメラ位置にしている。
  • lerp() という補間を行う関数を使っているので、ほんのちょっとだけ目標座標に遅れる感じで動いてくれる。

結果 :

以下のような結果になった。




blender で作った地形モデルデータの上に、ボールがぽとりと落ちて止まってくれた。

ただ、できれば、地面に落ちたボールがポーンポーンポーンと跳ね返ってほしい…。

跳ね返りの設定を変更する :

ボール担当の RigidBody3D、もしくは、地面相当の StaticBody3D の、Physics Material Override を設定して、跳ね返りを設定する。

StaticBody3D を選択した状態で、Physics Material Override が「空」になっているので、クリックして「新規 PhysicsMaterial」を選択。

godot3d02_ss12.png


Bounce を、0.0 より大きい値にする。0.5 とか 0.75 とか。

godot3d02_ss13.png


これで試すと…。




多少は跳ね返るようになってくれた。

課題 :

ちょっとはそれっぽい見た目になってきたけど、色々な課題が…。

坂に落ちると、ボールがどこまでも転がって行ってしまう。どうやったら止められるのか…。

落ちた場所によって跳ね返り方を変えたい。でも、落ちた瞬間と、どんな種類のポリゴンに落ちたかを、どうやって調べたらいいのか…。

飛ばしたボールが途中でどんどん失速している気がする。挙動としては正しいのだろうか…?

どんな風に飛んでいるのかボールの軌跡を描画したいけど、どうしたらいいのか…。

2024/12/14() [n年前の日記]

#1 [godot] Godot Engine 4.3のRigidBody3Dについて調べてた

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものを作れそうかどうか実験中。

RigidBody3D で作ったボールが、StaticBody3Dで作った地面の上をいつまでも転がり続けて悩んでる。どうにかして、どこかのタイミングでピタリと止めたい。RigidBody3D の設定で解決できないものかと調べてた。

とりあえず分かった点をメモしておく。

余談。ボールを球のままにしておくと回転具合がさっぱり分からなかったので、試しに球を箱にしてみたところ、回転具合が分かりやすくなった。コリジョンさえ球になっていれば、見た目のモデルは何でもいいわけで、わざわざ動作が分かりづらいモデルを表示しなくてもいいのだよな…。

質量を指定 :

mass を指定すると、質量(重さ)をkg単位で指定できる。デフォルトは 1kg になっていた。

以下のページによると、ゴルフボールは 45.93グラムらしいので、mass に 0.045 を指定してみた。今までと同じ力で撃ち出したらとんでもない速度で飛んでいった…。

_ゴルフボールの基礎知識 | ボール | DUNLOP GOLFING WORLD

ただ、この値を弄っても、ボールは相変わらず地面の上をいつまでも転がり続けてしまった。

スリープ状態を有効にする :

Deactivation の Can Sleep を有効にしておくと、ある程度動きが小さくなったらスリープ状態になってくれる。おそらく、スリープ状態になれば、動きもピタリと止まるのかなと思うけど、これがなかなかスリープ状態にならない…。

スリープ状態かどうかは、スクリプト内で sleeping という変数を見るか、もしくは is_sleeping() という関数で調べればわかる。true か false が入る。

スリープ状態の変更は、set_sleeping(value: bool) だろうか。ただ、スリープ状態に設定しても、状況によってはスリープが自動で解除される。

スリープより強力な freeze というのもある。これが有効になると完全にその場で静止する。重力も、力も、反映されない。空中に居ても平気で静止する。モード(FreezeMode)によっては他のオブジェクトと衝突もしなくなる、とドキュメントには書いてあった。

移動/回転速度の減衰率 :

Damp という項目は、減衰率を指定できるらしい。減衰率を指定すれば、動いているうちに自然と速度が減っていくようになる。
  • Linear → Damp を指定すると、移動速度の減衰率を指定できる。
  • Angular → Damp を指定すると、回転速度の減衰率を指定できる。

Linear Damp と Angular Damp は、プロジェクト設定 → 物理 → 3D、でもデフォルト値が設定されている。Default Linear Damp が 0.1、Default Angular Damp が 0.1 になっていた。

Damp Mode が Combine になっていると、プロジェクト設定の Damp値と、RigidBody3D のDamp値を加算した値を実際のDamp値として使う、とドキュメントに書いてあった気がする。デフォルトでは、RigidBody3D 側の Damp値は 0 なので、プロジェクト設定の Damp値がそのまま反映されるのだろう。

Damp Mode が Replace なら、RigidBody3D の Damp値で置き換えてしまうので、プロジェクト設定のDamp値は無視される。

ただ、この Linear/Angular Damp は、RigidBody3D が空中を飛んでる時も反映されてしまうようで…。いやまあ、空気抵抗があると考えたらそのほうが自然なのかな…?

しかし、何かに衝突した時に、移動速度や回転速度がガツンと減ってほしい気もする…。

衝突してる何かの数を調べる :

Solver → Contact Monitor を有効にすることで、何かと衝突してるかどうかを ―― RigidBody3D と衝突してるポリゴンの枚数を調べることができるようになる。たぶん。ちょっと自信無し。

Contact Monitor を有効にすると Max Contacts Reported という設定項目が増える。ここで指定した数だけ、衝突しているポリゴン枚数を返してくれるので、0 より大きい値を指定しておく。0 が入っていると、衝突枚数として常に 0 しか返ってこない。

スクリプト内では、get_contact_count() を使って衝突個数/枚数を調べることができる。何も当たってなければ0、ポリゴン1枚と当たってたら1、ポリゴンとポリゴンの境界をウロウロしていたら2が返ってきた。3や4が返ってくる時もあるのだろうか?

get_contact_count() を使って、空中に居るのか、地面の上に居るのかを判別できそうな気がする…。0 なら何も当たってないから空中に居る。1以上なら地面のどこかに当たってる。

そして、地面の上に居る時だけ、移動速度と回転速度を減らしてやれば、転がり続ける状態は回避できるのではないか…?

移動速度と回転速度 :

スクリプト内で、移動速度には linear_velocity、回転速度には angular_velocity でアクセスできるらしい。

地面の上に居る時だけ、移動/回転速度をガツンと減らしたいなら、以下のような記述になるだろうか…?
@export var vec_damp: float = 0.7
@export var ang_damp: float = 0.3

# ...

    if get_contact_count() > 0:
        linear_velocity *= vec_damp
        angular_velocity *= ang_damp

状態を画面に表示したい :

RigidBody3D がスリープ状態なのかどうか、あるいは何かと当たっているのか、そういった情報を画面上に表示したい。こんな時は何のノードを使うのだろう?

ググったところ、3D画面の場合でも Labelノードが使えるらしい。

Root の直下に Label を追加。

RigidBody3D のスクリプトで、以下のような感じで記述すればいいのかな。
@export var label: Label

# ...

    var txt = "hit %d / on ground %f sec\n" % [hit_count, on_ground]
    txt += "%.6f , %.6f , %.6f\n" % [angular_velocity.x, angular_velocity.y,  angular_velocity.z]
    txt += "%s" % ("sleep" if sleeping else "active")
    label.text = txt

@export を記述すると、その変数の内容をGUIで変更できるようになる。label という設定項目が増えるから、先ほど追加登録した Label を指定。

Label の .text に文字列を代入してやれば、その文字列が画面に表示される。"\n" で改行を入れれば複数行表示もできる。

GDScript での文字列の変数展開は、Python とちょっと違うらしい…?
  • Python ... "Hoge %s %d %f" % ("String", 10, 1.5)
  • GDScript ... "Hoge %s %d %f" % ["String", 10, 1.5]

#2 [blender] blenderで複数オブジェクトの属性値を一括して変更したい

blender 3.6.18 LTS上で、Drop to Ground というアドオンを使うと、選択オブジェクトを地面等のオブジェクトの上に移動させることができる。杭っぽいモデルを作って、空中にたくさん複製して、Drop to Ground を使ったら、一発で地面の上にたくさんの杭を並べることができた。

ただ、地面の角度が反映されて、各オブジェクトの角度も変わってしまう。角度については 0 にしたい。でも、たくさんあるから手作業ではやってられない。

つまり、複数のオブジェクトを選択して、角度を全部0にしたい。どうすればいいのか…。

以下のページが参考になった。

_「Blender」アトリビュートの一括編集 - Suggypop 3DCG Memo

blender 3.6.18 LTS では、アドオン Copy Atrribute を使えば実現できた。
  1. 複数のオブジェクトを選択。一番最後に選んだオブジェクトがコピー元になる。一番最後に選んだオブジェクトがコピー元になる。大事なことなので2回言いました。
  2. Ctrl + C でメニューが開く。
  3. コピーしたいアトリビュート(属性値?)を選べば、選択されている複数のオブジェクトに、コピー元の値をコピーできる。


あるいは、項目の上で右クリックして、以下を選ぶことでも同じことができる。

また、回転をクリアしたいだけなら、Alt + R でもできる。

Alt+クリックは効かなかった :

blenderの昔のバージョンなら Alt + 左クリック、Alt + Enter でも行えたらしい…?

_Blender で複数の要素を一度に変更する
_【Blender】複数オブジェクトを同時にトランスフォームするTips | 謎の技術研究部
_Blender 2.8 複数のオブジェクトの設定を一度にまとめて編集する方法-Suit Dimension Lab

旧バージョンで試したところ、blender 2.83.20 なら Alt + 左クリックを利用できたが、blender 2.93.18 - 4.2.4 では利用できなかった。

Alt + Enter はどのバージョンでも利用できたが、これは現在値に変化後の相対値を加算する処理に見えた。値を入力値で置き換えるわけではないらしい。

2024/12/15() [n年前の日記]

#1 [godot] ゴルフゲームのパワー入力画面を眺めてる

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものが作れないものかなと試してる。

ボール(RigidBody3D)が、いつまでも地面の上を転がり続ける状態については、ボールが地面に当たってる時だけ移動/回転速度をグングン減らすという処理を追加することで多少は症状(?)を緩和できた。

後は、グリーン、フェアウェイ、ラフ、バンカー等々のどれにボールが乗ってるかを判別して、移動/回転速度をどれだけ減らすのかを変更してしまえば、ゴルフゲームっぽくなるのではないか。

ただ、その実験をするためには、狙ったところにボールを落とせる仕様が必要になる。

今現在は、スペースキーを叩くと、ボールの特定の位置に対して、一定の力が加わる仕様になっている。これでは飛距離が大体決まってしまうので、狙った位置にボールを落とすのが難しい。せめて、撃ち出す際に加える力を ―― パワーを調整できたら違ってきそう。

そのためには、パワー入力仕様を盛り込まないといけない。そのあたりはどういう仕様がいいのかなと悩み始めた。

とりあえず、「みんなのゴルフ」の動画を眺めてる。例えば「みんなのゴルフ 6」では操作種類が5種類まで増えたらしい。それぞれ何が違うのか気になる。参考にさせてもらおうかなと。

動画を眺めた感じでは、基本となる昔ながらのパワー入力は以下のように思えた。3回ボタンを押すのだろう。
ただ、3回目のボタン押し ―― 特定の範囲に入った時の仕様が動画ではよく分からない。基準位置からずれていると左右に曲がるのか、それともボールの上下で打ち分けて回転具合が変わるのか。どういう効果が出るのだろう…。

パワーゲージの形もどれが良いのか…。横一直線、縦一直線、カーブ、円があるようだけど…。

#2 [anime][movie] 「ボルテスV レガシー」を視聴

BS12で放送されてたので視聴。昭和の日本製ロボットアニメをフィリピンで実写映画化した作品。

合体シーンが素晴らしい…。あの合体シーンだけで、自分の中ではこの映画を名作認定。

ただ、正直、ドラマパートは苦行だなと…。とにかくテンポが悪くて…。オリジナルのアニメ版を忠実に再現した展開らしいのだけど…。

でも、合体シーンだけは良い。そこだけで「ヨシ!」な感じ。

重量感とスピード感 :

とにかく重量感を重視した見せ方だった気がするけれど、その代わりスピード感が皆無で、これはこれで厳しいなと…。いや、昨今はスピード感重視の見せ方が多いから、この重量感に全振りした見せ方は逆に新鮮かもしれないけれど。

重量感を重視するとスピード感が無くなる。スピード感を重視すると重量感が無くなる。相反する要素のような気がする。両立はできないものだろうか。何か上手い手は無いか。いや、素人考えでもちょっと思いつかないけど…。

ゆっくり動くモノと、めっちゃ早く動くモノを、一つの画面の中で同時に見せたら変わってこないか。対比というか、コントラストをつけるというか…。本当はそんなにゆっくり動いてないんだけど、手前のソレがちょこまかピュンピュンと早い動きをしてるから、奥にあるソレはゆっくり動いてるように見える、とかそういう…。

実写版トランスフォーマーなどはそんな感じのカットがあるような気がしてきた。いや、ゴジラでも、等身大サイズの群衆が手前を忙しなく走ってて奥にゆっくり動くゴジラ、とかあるよな…。重量感とスピード感の両立云々とは違う話だろうけど、速度が違うモノ同士を一つの画面に収めるのも何かしらのテクニックなのだろうか…?

コクピット内の構図が気になった :

コクピット内を映す際、真正面から捉える構図ばかりで、見ていてちょっと飽きてしまった。もっと色んな角度から見せたほうがいいのではないか…。いやまあ、録画してたソレを確認してみたら、多少は斜めから捉えたりもしてたのだけど、どうにも印象が…。

考えてみたら、実写作品でこの手のコクピット内を映す時、えてして真正面からしか映さないなとも思えてきた。
  • STAR WARS EP4 〜 6 も真正面から捉えた構図がほとんどだった気がする。まあ、真横から見た構図も稀にあった気もするのだけど。
  • ウルトラマンシリーズもそんな印象。パイロットの真正面の顔ばかり思い浮かぶ。
  • 戦隊シリーズも基本的には真正面だけど、ロボに合体する前は真横も稀に入ったような…。ロボ内で斜め横の構図もちょくちょく見かけた気もする。
  • 先日TVで見たトップガンも真正面が多かったような…。

後ろからパイロットを捉えつつ、奥のモニタに敵が映ってる、とか。足元のペダルの隙間からパイロットの全身を舐めつつ顔まで捉える、とか。そういうのは難しいのかな…。

実写はカメラをどこに設置するかという問題が付きまとうはずだろうし、自由な構図を選べないのだろうか。コクピットの中って本来は狭いはずだもんな…。また、コクピットの外が入ってしまうと合成作業が必要になって面倒なのかも。それともカメラ設置場所を固定=コクピット内映像っぽくする記号、なのかな…。

せめて、顔をアップにする時だけでも、俯瞰やアオリが欲しい気もする…。いや、それだと床や天井も入っちゃうから撮れないのかな…。天井には照明があるのだろうし。足元も何か不都合があるのかな。

実写には色々な事情があるんだろうな…。コクピットの中にも富野アニメ的に上手と下手を盛り込めないものか…。真正面の構図しか無くても、画面を傾けるだけで印象が変わりそうな気もするけれど。例えば1stガンダムもそんな感じで見せてた印象があるんだけど、偽記憶だろうか…。シャアが斜めになってるカットとかあったような…。でもアレは天地が無い宇宙で戦ってるからアリの構図だったのかしらん。

2024/12/16追記 :

録画していたソレを再度確認してみたら、富野アニメでお馴染みのカットインも使ってた。最初の敵ロボットに頭突きをする時に一瞬だけ…。そういう見せ方も実写でやろうと思えば全然できるのだな…。

ED映像がアニメと実写の違いを感じさせてくれる…。アニメ特有の、アニメキャラの謎の高い跳躍って、実写でやるとワイヤーアクションになっちゃうんだなと…。きららジャンプも実写でやったらワイヤーアクションになるのかなあ。せめて最初の仮面ライダーのようにトランポリンでやれないか…。

この記事へのツッコミ

Re: 「ボルテスV レガシー」を視聴 by がんした    2025/01/05 01:22
本編見れてませんが前から気になってました。youtubeでPVぽいの見たら、鳥島基地とソーラーバード号の合体シーンが凝ってて、海外の製作者の方々にも「分かってる人」居るなーとリスペクト感じてました。

TV版は。カ〇ワ、〇タ〇と差別用語のセリフオンパレードで、昨今のコンプラ的に地上波再放送は無理と言われ続けてきたのに、2020年台の今になって海外から新作リメイクを見られるとは感慨深いですね。

2024/12/16(月) [n年前の日記]

#1 [godot] Godot Engineでバーが伸び縮みする処理を試しに書いた

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものが作れないものかなと試してる。

ショットを打つ際にパワーを決めなければいけないけれど、少しだけリアルタイム性を持たせたい。バーが伸び縮みして、キーを押すとパワーが決まる感じにしたい。

どういうノードを使えばそれらしく表示できるのか、別プロジェクトを新規作成しながら試してみた。

ノード構成 :

以下のようなノード構成にしてみた。本来なら綺麗なパワーゲージ画像を作成して配置するべきだけど、今回は仮表示ということで…。

powerbar_test01_ss01.png

  • CanvasLayer 以下がバー表示用のノード。この手のUI用ノードは CanvasLayer の子ノードとして配置すれば扱いやすくなるらしい。
  • ReferenceRect は、境界線だけを持ってる四角(矩形)。
  • ColorRect は、色で塗り潰す四角(矩形)。
  • 上記の構成では、枠の影、枠、バー、を配置している。


ReferenceRect については少し注意が必要。以下のような感じで指定したけど、Editor Only のチェックを外して無効にしておかないとゲーム画面に枠線/境界線が表示されない。逆に、Godotエディタ上でのみ表示したいならチェックを入れて有効化する。

powerbar_test01_ss02.png

  • Border Color で境界線の色を指定。
  • Border Width で境界線の線幅を指定。
  • 大きさや位置は、Layout → Transform → Size / Position で指定。


以下のような見た目にした。

powerbar_test01_ss03.png

スクリプト :

ルートノードにスクリプトをアタッチして、以下の内容を記述した。

_main.gd
extends Node3D

@export var powerbar: ColorRect
var powerbar_def_size: Vector2
var powerbar_def_pos: Vector2
var power_value: float = 0.0
var power_value_d: float = 0.0
var powerbar_mode: int = 0
var powerbar_timer: float = 0

# Called when the node enters the scene tree for the first time.
func _ready():
    powerbar_def_size = powerbar.size
    powerbar_def_pos = powerbar.position
    power_value = 0.0
    powerbar_mode = 0
    powerbar.hide()


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
    
    match powerbar_mode:
        0:
            if Input.is_action_just_pressed("ui_accept"):
                # Push Space key
                power_value = 0.0
                power_value_d = 1
                powerbar_mode = 1
                powerbar.color = Color(1, 0.5, 0, 1)
                powerbar.show()
        1:
            power_value += (delta * power_value_d)
            if power_value >= 1.0:
                power_value = 1.0
                power_value_d = -1.0
            elif power_value <= 0.0:
                power_value = 0.0
                power_value_d = 1.0
                
            if Input.is_action_just_pressed("ui_accept"):
                # Push Space key
                powerbar_mode = 2
                powerbar_timer = 2.0  # set wait seconds
                powerbar.color = Color(1, 0.25, 0, 1)
        2:
            # wait
            powerbar_timer -= delta
            if powerbar_timer <= 0.0:
                powerbar.hide()
                powerbar_mode = 0
                
    powerbar.size.x = power_value * powerbar_def_size.x

スペースキーを1回押すと、パワー入力バーが伸び縮みを始めて、2回目を押すとパワーが決定される処理にしてみた。

  • powerbar には、パワー入力バーとして使いたい ColorRect を指定しておく。
  • _process(delta) の delta には、前回フレームからの経過時間が入ってくる。単位は秒。
  • .hide() は非表示に、.show() は表示にする。

結果は以下。




それっぽくなったかな…。

1ショットを打つために何回ボタンを押すか :

以降は思考メモ。

有名なゴルフゲーム「みんなのゴルフ」では、基本的にはボタンを3回押すことでショットが打てるらしい。開発スタッフさん曰く「チャーシューメン」と称する操作方法/操作仕様だそうで…。

_【インタビュー】「CLAP HANZ GOLF」開発者インタビュー - GAME Watch

  • 1回目のボタン押しで、バーが伸び始める。
  • 2回目のボタン押しで、パワーが決定される。
  • 3回目のボタン押しで、インパクト(?)とやらが決まる。インパクトは、ミスショットになるか否かのパラメータらしい。

ただ、「みんなのゴルフ」には、必ずインパクトを成功させるゴルフクラブが存在するそうで…。その場合、2回目のボタン押しでショットが打てる。

そこでふと閃いた。もしかして、インパクトを決める3回目のボタン押しは無くてもいいのかもしれないなと…。簡単操作にしたいなら、ミスショットは全然出ないほうがいいのでは…。まあ、「みんなのゴルフ」の場合、上達してくるとインパクトなんて当たり前のように成功させられるようになるから、わざわざ今更入力させる必要は無いだろう → 特殊なゴルフクラブをゲットして使うことでスキップできる仕様に変更できる、ということかもしれない。

加えて、パワーを決めるための最初のボタン押しも要らないような気もしてきた。ずっとパワーゲージが動き続けていてもいいんじゃないか。

昔のゲームはリアルタイム性を盛り込むことで、不確定要素を取得して、思い通りにいかない展開にしていったのだろうけど。今はもうそういう時代じゃないよな…。リアルタイム性は極力排除することで不確定要素を減らして、簡単にサクサクとプレイできるのが良い、とされているような気もする。もちろんジャンルによるとは思うけど。

となると、パワーゲージが常に画面の中で動き続けていて、そろそろ打つかと思ったら、適当なタイミングでボタンを押せばパワーだけが決定されてボールが飛んでいく、という仕様でも ―― ボタンを1回押すだけでショットが打てる感じでも良さそうだなと…。単にパワーだけをプレイヤーに決めてもらえばいいのではないか…?

できれば、そのパワー決定すらリアルタイム性を排除したい気もする…。ただ、素早く指定する方法がちょっと思いつかない。マウス等のポインティングデバイスを使っても良いならできそうだけど…。

バーが伸びる方向 :

「みんなのゴルフ」では、バーは右から左に向かって伸びる。何故だろう?

おそらく、プレイヤーキャラがゴルフクラブを振る際、右から左に向かって振り上げるので、その向きに合わせてあるのではないかなと。

あるいは、縦方向にバーが配置されるバージョンもある。プレイヤーキャラがゴルフクラブを振る時は、上に向かって振り上げてから下に向かって振り下ろすので、向きとしては縦方向のほうが合ってるのかもしれない。あるいは、力の大きさを伝える時に、横方向より縦方向で量を示したほうが分かりやすい可能性もあるなと…。

今回はプログラム的に簡単になるように、左から右に向かってバーが伸びるようにしてしまったけれど、分かりやすさというか、感覚的にしっくりくるかどうかを考えたら、別の向きで動かすのもアリだろうなと…。

でもまあ、どんな向き/形が適しているかの検討は、今後の宿題ということで…。

#2 [zatta] 温度計の操作方法をググった

親父さん達の寝室に置いてあるデジタル温度計の時計がめちゃくちゃずれてることに気づいた。時刻合わせをしようとしたけれど、操作方法が分からない…。

型番は、HTC-1 と書いてある。

ググってみた。おそらく以下の製品だろうか。操作方法が列挙されていて助かる…。ありがたや…。

_温湿度計 HTC-1日本語マニュアル | キュウの暮らしのメモ帳

MODEボタンの長押しで時刻合わせモードになるのか…。長押しは気づかなかった…。後はADJボタンで数値変更と…。

ただ、手元の個体には年の表示が無かった。最近の製品で追加されている機能なのかな…。

#3 [zatta] ブレーカーカバーが気になる

お袋さんがブレーカー近辺の掃除をしていた際、ブレーカーに触って停電させてしまった…。自宅サーバや録画用PCが不意の電源断。HDDが壊れてないだろうな…。

考えてみたら、ブレーカーなんて頻繁に触れるものでもないし、カバーの類をつけてうっかり触らないようにしておくべき、なのではなかろうか。企業などでは金属製の箱で覆われていて、鍵をかけることすらできたりするけれど。しかし、昔に建てられた木造家屋の一般家庭では、カバーなんてついてないのだよな…。

後付けでつけられないものかなとググってみたら、自作している方々が結構いらっしゃるらしい。気になる。自分もチャレンジしてみようか。

ただ、自作されたそれらは木製だったりして、なんだか不安になってきた。火事になったりしないか…。しかし金属となると加工がシンドイだろうし…。

ググってみたら、むしろ木のほうが燃えにくいっぽい…? であれば、もう少し製作事例を眺めてみよう…。

2024/12/17(火) [n年前の日記]

#1 [godot] Godot EngineでRigidBody3Dが何に当たったか調べたい

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものが作れないものかなと試してる。

ボールを RigidBody3D で用意しているけれど、地面を担当してる StaticBody3D と衝突した時、何と衝突しているのか調べたい。ゴルフコースには色々な場所があるはずで。グリーンとかフェアウェイとかバンカーとか。どこに当たっているかが分からないと、ボールの挙動を変化させられない。

ただ、その何かの情報を、何を通じて持たせるのかで悩む…。当たったポリゴンのマテリアル名を取得できれば、とも思ったけれど、ググった感じでは Godot Engine はそういう機能を持ってないらしい。いや、頑張ればやれないこともないらしいけど…。

_How to detect the mesh material on contact collision : r/godot


Godot Engine が標準で持っている機能だけで実現するにはどうしたらいいのか試してみた。以下を参考にした。

_How to detect "collision" of RigidBody3D and StaticBody3D? : r/godot

とりあえず、以下のような結果にはなった。ボール(RigidBody3D)が乗っている、床(StaticBody3D)の種類を調べることができている。




Godotエディタ上での見た目は以下。

collision_get_group_ss03.png


ノード構成はこんな感じになった。

collision_get_group_ss02.png

StaticBody3D がずらずら並んでるあたりがポイント、ではあるけれど、ダサい…。結局、StaticBody3D を種類分用意して、それぞれにグループを設定して、衝突したことが分かった StaticBody3D がどのグループに属しているかを調べることで、何と衝突しているかを把握している。

スクリプトは以下。RigidBody3D にアタッチしてる。「# get hit group」のすぐ下が、判定している部分。

_ball_rigid_body_3d.gd
extends RigidBody3D

@export var label3d: Label3D
@export var move_speed: float = 3.0

# Called when the node enters the scene tree for the first time.
func _ready():
    pass # Replace with function body.


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
    
    # move ball by cursor key
    var d = Vector3.ZERO
    if Input.is_action_pressed("ui_left"):
        d.x = -1
    if Input.is_action_pressed("ui_right"):
        d.x = 1
    if Input.is_action_pressed("ui_up"):
        d.z = -1
    if Input.is_action_pressed("ui_down"):
        d.z = 1
    if d != Vector3.ZERO:
        d = d.normalized()
        apply_central_force(d * move_speed)
    
    # get hit group
    var grp_name_list = ["bunker", "water", "green", "rough", "ob"]
    var txt = ""
    for grp in get_colliding_bodies():
        for grpname in grp_name_list:
            if grp.is_in_group(grpname):
                txt += "on %s\n" % grpname
    
    label3d.global_position = global_position + Vector3(0, 1.2, 0)
    label3d.text = txt


しかし、この方法はダサい…。ゴルフコースにある場所の種類分、blenderでモデルデータを複数作って、複数ファイルをエクスポートして、Godotにインポートしたらそれぞれにコリジョンとグループを設定していかないといけない…。どう考えても面倒臭い…。

でもまあ、100や200の種類があるわけではないから手作業でやれなくはないし、この方法なら確実に目的を果たせそうではあるけれど…。しかし、ダサい…。

この方法のまま、どこかを自動化するとしたら、1ファイルの .objモデルデータを読み込んで、マテリアル別に分割して複数の .obj として出力するスクリプトを書く、みたいな感じになるのだろうか。

余談。グループの設定方法 :

グループ名は、Godotエディタの右端のあたりで指定できる。StaticBody3D を選択した状態で…。

collision_get_group_ss04.png

  1. ノード → グループ、を選んで、
  2. 「+」ボタンをクリックして必要になりそうなグループ名を追加して、
  3. *Body3D に割り当てたいグループ名にだけチェックを入れる。

試してはいないけど、おそらく複数のグループに所属させることもできるのではないかと…。

余談。特定フォルダをインポートの対象から外したい :

プロジェクトフォルダ内の特定フォルダを、Godot Engine の自動インポート処理の対象から外したい。スクリーンショット画像も入れておいたら、インポートされてしまったので…。そのフォルダは無視して欲しい…。

そのフォルダの中に「.gdignore」という名前で空のファイルを作成しておくだけでいいらしい、と知ったのでメモ。

_【Godot Engine】始める前に抑えておきたい事【ベストプラクティス?】 | ひらまめゲーム制作研究室
_プロジェクトの構成 - Godot Engine (4.x)の日本語のドキュメント
特定のフォルダを無視する

Godot が特定のフォルダに含まれるファイルをインポートしないようにするには、フォルダ内に .gdignore という空のファイルを作成します (先頭に . が必要です)。これは、(既存の構成などからの)初期プロジェクトのインポートを高速化するのに役立ちます。

2024/12/18(水) [n年前の日記]

#1 [godot] Godot EngineでRigidBody3Dが何に当たったか調べたい。その2

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものが作れないものかなと試してる。

昨日の実験で、StaticBody3D を複数用意して、それぞれにグループを設定してやれば、どのグループと当たっているか判別することは可能と分かった。

ただ、そのためには、blender上でマテリアル別にモデルデータを作って、複数のファイルとしてエクスポートして、その複数のファイルを Godot Engine にインポートして、それぞれにグループを指定する作業が必要になってしまう。

1つのモデルデータの中から、衝突したポリゴンを特定して、そのポリゴンのマテリアル情報を取得することができれば、複数のモデルデータを云々という作業を回避できるのではないか。そんなことができるのかどうか実験してみた。

結論を先に書くと、一応できる。できるのだけど、ちょっと問題がある。それについては後述。

実験結果 :

以下が結果のスクリーンショット動画。ボールが落ちている地面(ポリゴン)のマテリアル名を取得することができている。

構成 :

Godotエディタ上の見た目は以下。

get_material_info_ss02.png


ノード構成は以下にした。

get_material_info_ss03.png

  • RigidBody3D がボール担当のノード。今回は見た目を分かりやすくするために、球ではなく箱を表示してる。アタリ範囲は球のまま。
  • StaticBody3D が地面を担当するノード。子ノードとして、見た目のモデルデータを担当する MeshInstance3D と、アタリ範囲を担当する CollisionShape3D を持っている。
  • RayCast3D は、ボールの中心座標から真下に光線を伸ばして何かと当たっているか調べるためのノード。見た目を分かりやすくするために MeshInstance3D も子ノードとして持っているけれど、本来は無くていい。
  • Label3D は、衝突結果をウインドウ上にテキスト表示するために配置した。


ボール担当の RigidBody3D の設定については、以下のような感じ。

get_material_info_ss04.png

  • Can Sleep を無効にして、スリープしないようにしている。
  • Continuous CD を有効にして、継続的に衝突判定するようにしている。
  • Contact Monitor を有効にして、衝突状態を常時調べるようにしている。
  • Max Contacts Reported を10にして、10個まで衝突した結果を取れるようにしている。0のままだと衝突結果を1つも得られない。
  • 一応、Mass を変更して、重量を指定した。
  • ちなみにプロジェクト設定で、1秒あたりの物理ティック数を 60 から 120 に変更してる。そうしないと地面をすり抜けてしまったので…。Continuous CD を有効にしただけではダメだった…。


RigidBody3D のシグナルについては、body_entered(body:Node) を、RigidBody3D にアタッチしたスクリプトに「接続」(connect)した。

get_material_info_ss05.png

この body_entered というシグナルは、*Body3D と衝突した時に発生する。そして、接続先の関数が ―― 上図で言えば緑色のアイコンがついている _on_body_entered() が呼ばれる。その関数の中で *Body3D と当たった瞬間の処理を書けばいい。

スクリプト :

RigidBody3D にアタッチしたスクリプトは以下。

_rigid_body_3d.gd
extends RigidBody3D

@export var raycast3d: RayCast3D
@export var camera3d: Camera3D
@export var label3d: Label3D
@export var camera_offset: Vector3 = Vector3(0, 2, 4)
@export var move_speed = 100.0

var hit_face_index = -1
var hit_surface_index = -1
var hit_surface_name = " "

var init_pos: Vector3

func _ready():
    init_pos = global_position


func _process(delta):
    # Reset position by Home key
    if Input.is_action_just_pressed("ui_home"):
        global_position = init_pos
        linear_velocity *= 0
    
    # Jump by Space key
    if Input.is_action_just_pressed("ui_accept"):
        apply_central_impulse(Vector3(0, 0.25, 0))
    
    # Move by Left, Right, Up, Down key
    var d = Vector3.ZERO
    d.x = Input.get_axis("ui_left", "ui_right")
    d.z = Input.get_axis("ui_up", "ui_down")
    apply_central_force(d.normalized() * move_speed * delta)
    
    # Set Camera3D, RayCast3D, Label3D position
    raycast3d.global_position = global_position
    camera3d.global_position = global_position + camera_offset
    label3d.global_position = global_position + Vector3(0, 1.2, 0)
    label3d.text = "Hit face[%d]\n[%d] %s" % [hit_face_index, hit_surface_index, hit_surface_name]


func _on_body_entered(body):
    var starttime = Time.get_ticks_msec()
    print("call _on_body_entered()")
    print(body)
    
    if not is_instance_of(body, StaticBody3D):
        print("Not StaticBody3D")
        return
        
    if not body.is_in_group("ground"):
        print("Not ground group")
        return
        
    # Check collision position of the RayCast3D and StaticBody3D
    var col_pos = Vector3.ZERO
    raycast3d.global_position = global_position
    raycast3d.force_raycast_update()
    if raycast3d.is_colliding():
        var c = raycast3d.get_collider()
        if c is StaticBody3D:
            col_pos = raycast3d.get_collision_point()
            print("RayCast3D hit StaticBody3D, Pos: ", col_pos)
        else:
            print("Not RayCast3D hit StaticBody3D")
    else:
        print("Not hit RayCast3D")
    
    if col_pos == Vector3.ZERO:
        return

    hit_face_index = -1
    hit_surface_index = -1
    hit_surface_name = " "
    
    var staticbody3d: StaticBody3D = body
    var meshinstance3d = staticbody3d.get_node("MeshInstance3D")
    print(meshinstance3d)
    
    var mesh: Mesh = meshinstance3d.mesh
    print(mesh)
    
    var mesh_face_count = len(mesh.get_faces())
    print("Mesh face count : %d" % mesh_face_count)

    var surface_count = mesh.get_surface_count() # surface is blender material
    print("Surface count : %d" % surface_count)
    
    var fcnt = 0
    for surface_index in range(surface_count):
        var mdt = MeshDataTool.new()
        mdt.create_from_surface(mesh, surface_index)

        # get materal name
        var material = mesh.surface_get_material(surface_index)
        var matname = material.resource_name
        
        var face_count = mdt.get_face_count()
        print("[%d %s] face count %d" % [surface_index, matname, face_count])
        fcnt += face_count

        for face_index in range(face_count):
            # get vertext index
            var i0 = mdt.get_face_vertex(face_index, 0)
            var i1 = mdt.get_face_vertex(face_index, 1)
            var i2 = mdt.get_face_vertex(face_index, 2)
            
            # get vertex position
            var p0 = mdt.get_vertex(i0)
            var p1 = mdt.get_vertex(i1)
            var p2 = mdt.get_vertex(i2)
            
            var cp = Vector2(col_pos.x, col_pos.z)
            var a = Vector2(p0.x, p0.z)
            var b = Vector2(p1.x, p1.z)
            var c = Vector2(p2.x, p2.z)
            if is_point_in_triangle(cp, a, b, c):
                hit_face_index = face_index
                hit_surface_index = surface_index
                hit_surface_name = matname
                print("Hit face index : %d" % hit_face_index)
                
    if hit_face_index >= 0:
        print("Hit face index : %d, surface index, name : %d, %s" % [hit_face_index, hit_surface_index, hit_surface_name])

    var endtime = Time.get_ticks_msec()
    print("%d msec, face count : %d" % [(endtime - starttime), fcnt])


func cross2d(a: Vector2, b: Vector2):
    return a.x * b.y - a.y * b.x


func is_point_in_triangle(point: Vector2, a: Vector2, b: Vector2, c: Vector2):
    var an: Vector2 = a - point
    var bn: Vector2 = b - point
    var cn: Vector2 = c - point

    var orientation: bool = cross2d(an, bn) > 0
    if ((cross2d(bn,cn) > 0) != orientation):
        return false
    return (cross2d(cn,an) > 0) == orientation

少し解説 :

_process(delta) の中ではボールの位置を動かすための処理しか書いてないので無視していい。今回の実験の肝は、_on_body_entered(body) 関数の中。

_on_body_entered(body) は、RigidBody3D が *Body3D と衝突した瞬間に呼ばれる。ずっと呼ばれ続けるわけではなくて、衝突した瞬間のみ ―― ボールが落下してきて地面と触れた時だけ呼ばれる。ボールがずっと地面の上に居る時は呼ばれない。前述の動画内で、マテリアルを取得できてない場面が多々あるけど、それは地面と衝突した瞬間しか検出できてないから。

_on_body_entered(body) の引数には、衝突した相手が入ってる。今回は、地面を担当する StaticBody3D が入ってくるはず。

やっていることをざっくり説明すると…。
  1. RayCast3D を使って、ボールの真下にレイ(= 光線)を伸ばして、地面と衝突している座標を取得する。
  2. 見た目を担当している MeshInstance3D、が持っている Mesh (三角形ポリゴンで構成されてるモデルデータ) に対して、MeshDataToolというものを利用して、各面(フェイス、ポリゴン)を調べやすい状態にする。
  3. x-z平面上で、衝突座標 (x, z) を内包する三角ポリゴンがどれなのか、総当たりで調べる。該当するポリゴンが見つかったら、サーフェイス情報(blenderで言うところのマテリアル情報)を確定する。
これを書いてる時に気づいたけれど、実は RayCast3D を使う必要はなかったな…。ボールの中心座標の (x, z) を使えば済んじゃうよな…。


RayCast3D が、見た目を担当している MeshInstance3D に対しても利用できたら話は早いのだけど…。どうも調べた感じでは、RayCast3D はアタリ範囲を持つ CollisionShape3D の類としか衝突判定してくれないようで…。

しかし、CollisionShape3D が持ってるメッシュデータはアタリ判定処理に特化しちゃっていて、マテリアル等の見た目に関する情報はごっそり削除したデータになっているはずだから、CollisionShape3D が持っているメッシュデータを頼りにして、どのポリゴンがどのマテリアルを使っているか調べることはおそらくできない…。このへん何か誤解してたり、裏技がある可能性もありそうだけど…。

となると、見た目を担当している MeshInstance3D(の Mesh)を対象にして、衝突したポリゴンの特定をしないといけない。ここが面倒臭い。

そんな時に使えるのが MeshDataTool なる機能。

表示用メッシュデータは、表示しやすい状態に並べたデータを持っているようで、そんなデータを相手にして調べていくのはどうも面倒臭いっぽい。これを、各面に対して調べやすいように並べ替えたデータにしてくれるのが MeshDataTool、らしい。変換作業で処理時間がかかりそうではあるけど…。

その MeshDataTool だけど、どうやらサーフェイス(Surface, blender で言うところのマテリアル情報)毎に作って処理してやらないといけないっぽい。だから、サーフェイス数(マテリアル種類数)だけループ処理をすることになる。

MeshDataTool が作れたら、そのサーフェイスを使っている/割り当てられている面の枚数も分かる。その面の枚数で更にループ処理をして…。各面の頂点インデックス番号を取得して、そこから頂点座標を取得して、その三角形の中に衝突座標が入ってるかどうかをチェックしていくことになる。

今回、座標が三角形の中に内包されてるかどうかは外積で調べてる。以下のやり取りで紹介されてたコードを利用させてもらった。元々は Godot Engine のソース内のコードらしい。であれば Godot Engine に、点と三角形の内包判定機能があっても良さそうだけど…。

_Calculate if bool is inside tri with shader : r/godot

問題点 :

一応目的は果たせたけど問題が…。処理が遅い…。予想はしてたけど…。

時間を測ってみたら、今回の判定処理だけで5〜7ミリ秒かかってた。ポリゴン枚数は355枚。ローポリだからこんなもんで済んでるけれど、枚数が2倍3倍10倍になっていったら処理時間も比例して増えて、あっさり1フレーム = 16.7ミリ秒をオーバーしてしまいそう。

動作確認で使ってるPCのCPUは Ryzen 5 5600X (6C 12T、3.7 - 4.6GHz)。もっと非力なCPUで動かしたらどうなることか…。

表示用モデルデータのポリゴンを総当たりで調べてるから、そりゃ遅いはずで…。

ただ、今回は実験だから、全ポリゴン枚数をループでチェックしてるけど。本来、途中で衝突ポリゴンが見つかったらループから抜けてしまうことで多少は改善されるはず。しかしそれでも最悪の場合、全枚数をチェックすることになるはずで…。

課題 :

マテリアル種類別(サーフェイス種類別)でループ処理をして、全ポリゴンの頂点座標を取得することができてきるのだから…。似たような処理をして、1つの表示用モデルデータから、マテリアル別の StaticBody3D + CollisionShape3D をゲームプログラムの初期化処理内で生成してしまえば、昨日行ったような形で衝突判定ができそうな気もする。そうすれば、判定処理が少しは速くなるのではないか…。

問題は、どうやったらその CollisionShape3D 用の Mesh を作れるのか、そこが分からないという…。

今回の CollisionShape3D には、ConcavePolygonShape3D が設定されていた。つまり、Mesh から ConcavePolygonShape3D を作成する方法が分かれば…。

ConvexPolygonShape3D というものもあるらしいけれど、それは凸多面体、中身があることになっている形状を扱う時に使うらしい。

ConcavePolygonShape3D は中身が無い形状とのことで…。そこにある面をすり抜けたら、もう戻ってくることができない形状、ということかな…。

2024/12/19(木) [n年前の日記]

#1 [godot] ゴルフコースのモデルデータを修正中

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものが作れないものかなと試してる。

衝突判定の都合上、ゴルフコースのモデルデータを、マテリアル別に分割してエクスポートしたい。したいのだけど、その作業をしてしまうとモデルデータの修正作業が後からしにくくなる。

であれば、今のうちにもうちょっとモデルデータをそれっぽく作り込んでおこう、テクスチャも貼ってしまおうかと。

そんなわけで blender 3.6.18 でテクスチャを貼り始めたのだけど、色々と問題が…。

テクスチャのリピート感 :

CC0ライセンスのシームレステクスチャ画像をググって探してネットから入手して貼ってみたけれど、これがなかなか酷いことになった…。テクスチャのリピート感(タイリング感?)がスゴイ…。まるで方眼紙。どうにかしないと…。

結局、GIMP 2.10.34 + 同梱パターン画像を使って、ほぼゼロからテクスチャ画像を作り直し。単にノイズがざっくり乗っただけのテクスチャに見えなくもないけど、リピート感はさほど目立たない感じにはなった。

これが blender や Shade 等、3DCGソフト内で完結する作業なら、もっと大きいスケールで、別のテクスチャをうっすら乗せることでリピート感が減ってくれたりするらしいのだけど…。

_タイリングを目立たなくするには - ユーザフォーラム / Shade 3D - Shade3D フォーラム
_【Blender3.0】テクスチャのリピート感をなくす方法【カンタン5分】 - YouTube

今回は Godot Engine で使うモデルデータを作っているので、Godot Engine上でスケールを変えながら二重三重にテクスチャを重ねることができるのかどうか分からない。

このあたりのテクニックを勉強してみないと…。

ノーマルマップの指定の仕方が分からない :

余談 :

みんなのゴルフシリーズはどんな感じのテクスチャになっているのか気になって動画の類を眺めてみたのだけど。プロは凄いな…。全く不自然さがない…。そもそもどういうポリゴンの組み合わせになっているのか、結果画面からは想像できない…。

フェアウェイとラフ、グリーンとラフの境い目のあたりも、どういう作りになってるんだろう…。なんだかグラデーションで変化しているように見えるのだけど…。もしかして、フェアウェイとラフのポリゴンが重なっていて、ボールの挙動にも変化を与えている、とか? ポリゴンのアルファチャンネルの値が、ボールの挙動の数値にも絡んでくる…?

2024/12/20(金) [n年前の日記]

#1 [godot] ゴルフコースのモデルデータをマテリアル別に分離してみた

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものが作れないものかなと試してる。

今まで、ゴルフコースのモデルデータは、blender上で1つのオブジェクトとして扱っていたけれど。Godot Engine上で衝突判定をしやすくするために、マテリアル毎に別オブジェクトとして分離してみることにした。その結果、ボールがどの場所と衝突しているか、すんなり判別できるようになった。

結果画面は以下のような感じ。ボールが居る場所を判別できている。

この方法は面倒臭い :

「分離してみた」と、さらっと書いてるけれど、実際にやってみたら、この作業が面倒臭い…。

以下のような流れで作業したのだけど…。
  1. blender上で、マテリアル毎に、別オブジェクトに「分離」する。編集モードに入って、マテリアルを選択。マテリアルリストのすぐ下にある「選択」をクリックして、そのマテリアルが割り当てられているポリゴン群を選択状態にする。Pキーを押すと出てくる「分離」メニューから「選択」を選んで、選択中のポリゴンを別オブジェクトして分離。…これをマテリアル数分、繰り返す。
  2. 分離して作ったオブジェクトを1つだけ選択して、Wavefront形式(.obj)で「選択中」のオブジェクトだけをエクスポート。…これをマテリアル数分、繰り返す。
  3. Godot Engine のプロジェクトフォルダに、複数の .obj をコピー。Godot Engine は自動でインポートして使えるようにしてくれる。
  4. StaticBody3D と MeshInstance3D を追加して、MeshInstance3D の Mesh に、.obj を1つだけ、ドラッグアンドドロップで指定。…これをマテリアル数分、繰り返す。
  5. .obj を指定した MeshInstance3D を選択して、上部メニューの「メッシュ」→「コリジョン作成」を選んで、コリジョン用ノード CollisionShape3D を生成。…これをマテリアル数分、繰り返す。
  6. 各 StaticBody3D に、グループ名を指定する。…これをマテリアル数分、繰り返す。

とにかく各作業を、マテリアルの数だけ繰り返さないといけない…。

1回だけしかこの作業をしないなら、まあ手作業でもどうにかと思えるけど。実際には、元になるコースモデルデータに何か修正を加えたら、この作業をまた最初からやり直さないといけない。こんな作業の流れでは、トライアンドエラーがやりづらい…。

もし、どうしてもこの方法でどうにかしたいなら、どこかしらを自動化しないといかん気がする。
  • blender上で、1つのオブジェクトをマテリアル毎に別オブジェクトとして「分離」してくれるPythonスクリプトを書く。
  • なおかつ、blender上で複数選択されたオブジェクト、もしくは特定の条件に合致した複数オブジェクトを、それぞれ別の .obj として一括エクスポートするPythonスクリプトを書く。
  • あるいは、1つのWavefront形式(.obj)を読み込んで、マテリアル毎に別の .obj として変換/保存する Pythonスクリプトを書く。
  • はたまた、Godot Engine上で、1つの表示用モデルデータ(Mesh)を元にして、マテリアル毎に、StaticBody3D、CollisionShape3D、CollisionShape3D用のMeshを生成する処理を書く。

一番最後の方法は、毎回ゲームを実行するたびに初期化処理で時間がかかってしまうから、それはちょっとどうなんだという気もする…。本来、最初から処理済み/分離済みのモデルデータを持っておけば、その初期化処理は不要になるはずで…。

スクリプト内の記述 :

どの場所と衝突しているかについては、RigidBody3D にアタッチしたスクリプト内で、以下のように書いてみた。

var gndkind: String = ""

var gndgrouplist = [
    {"grpname": "ob", "vec_damp": 0.3, "ang_damp": 0.2},
    {"grpname": "pond", "vec_damp": 0.1, "ang_damp": 0.1},
    {"grpname": "road", "vec_damp": 0.9, "ang_damp": 0.6},
    {"grpname": "bunker", "vec_damp": 0.3, "ang_damp": 0.2},
    {"grpname": "rough", "vec_damp": 0.6, "ang_damp": 0.4},
    {"grpname": "teeingarea", "vec_damp": 0.8, "ang_damp": 0.6},
    {"grpname": "fairway", "vec_damp": 0.7, "ang_damp": 0.5},
    {"grpname": "green", "vec_damp": 0.8, "ang_damp": 0.6},
    {"grpname": "cup", "vec_damp": 0.9, "ang_damp": 0.9},
    ]

# ...

func _integrate_forces(state):
    if not sleeping:
        gndkind = ""
        var vec_damp = 1.0
        var ang_damp = 1.0
        for node in get_colliding_bodies():
            for grp in gndgrouplist:
                var grpname = grp["grpname"]
                if node.is_in_group(grpname):
                    gndkind = grpname
                    vec_damp = grp["vec_damp"]
                    ang_damp = grp["ang_damp"]

        if is_shot <= 0.0:
            if vec_damp <= 1.0 or ang_damp <= 1.0:
                linear_velocity *= vec_damp
                angular_velocity *= ang_damp

_integrate_forces(state) という関数は、物理計算をする上で毎フレーム呼ばれる関数。普通は _process(delta) という関数の中に処理を書くけれど、物理計算が必要、かつ、_process(delta) よりもっと細かいフレーム数で確実に処理したい場合は、_integrate_forces(state) の中に処理を書けばいいらしい。

ただ、この _integrate_forces(state) は、スリープ中、もしくはフリーズ中の時には呼ばれないらしい…。上記の処理では、sleeping という変数を見て、スリープ中じゃなければ処理をすることにしているけれど、Godoy Engine側が正常に動作しているのであれば、ここで sleeping は見なくてもいいんじゃないかな…。

get_colliding_bodies() で、その時衝突している *Body3D のリストが得られる。

node.is_in_group(グループ名) で、そのノードが、指定したグループの中に含まれているかどうかを調べることができる。

linear_velocity は移動速度。angular_velocity は回転速度。今回、当たった場所に応じて、移動速度と回転速度を減衰させるようにしている。

ただ、ボールを撃ち出した瞬間にも速度が減衰されてボールが飛んでいかない場面が多々あったので、撃ち出した瞬間に is_shot という変数に一定の秒数を入れて、撃ち出してから0.n秒間は速度を一切減衰させないことにした。

場所の種類 :

必要になりそうな場所の種類としては、以下になるのだろうか…。
cup (カップ)
green (グリーン)
fairway (フェアウェイ)
teeingarea (ティーイングエリア)
rough (ラフ)
bunker (バンカー)
road (道路)
pond (池、川)
ob (OB、アウトオブバウンズ)

いや、以下も必要かもしれない。
flag (旗)
flagstick (旗の竿)
tree or trunk (木、木の幹)
leaves or brunch (木の葉、木の枝)

おそらく、旗包み? なる状態もあるんだろうな…。おそらく、ボシュッと音がして、速度がゼロになるはず。

ピン(旗竿)にボールが当たって跳ね返る状態もあるだろう…。カツーンと跳ね返りそうなイメージだけど、実際はどうなんだろう。

木の幹に当たった時も、カツーンと跳ね返りそうな気がする。ただ、木の葉や木の枝に当たった時は、そこで速度が減速するのではないか。たぶん。

カップインかどうかを判別するために、カップの底も種類として必要かなと思ったけれど、ボールが停止状態になった時にカップの中にあるかどうかを判別すれば、カップインかどうかの判別はできるかな…。たぶん。

各アイテム大きさと重さ :

一応メモ。ゴルフボールの大きさと重さは以下。
  • 重さ : 45.93 g (0.04593 kg)
  • 直径 : 42.67 mm (0.04267 m)

カップの大きさ。
  • 直径 : 108 mm (0.108 m)
  • 深さ : 101.6 mm (0.1016 m)
  • 地表からの深さ : 25.4 mm (0.0254 m) より下であること。

旗竿(ピン)の長さ。
  • 長さ : 2.13 〜2.43 m

blender上で、このあたりを意識しながらコースのモデリングをしないといけない。

ただ、あくまでゲームなので、ボールが見えやすいように大きくしちゃっても良さそうな気もする。2倍〜10倍のサイズでもいいんじゃないか…? もちろん、カップやピンの大きさも、ボールに合わせて大きくしないと…。

以下、参考ページ。

_ゴルフボールの基礎知識 | ボール | DUNLOP GOLFING WORLD
_ゴルフボールの大きさ知っていますか? | インドアゴルフ業界で店舗数No1,STEPGOLF
_なぜゴルフのカップはあの大きさなのか | インドアゴルフ業界で店舗数No1,STEPGOLF
_ゴルフのカップ(ホール)の直径はなぜ108ミリなのか | Honda GOLF | Honda公式サイト
_ゴルフのピン(旗竿)の長さは何メートル?ワンピンについても解説(季節・暮らしの話題 2022年09月10日) - 日本気象協会 tenki.jp
_<ゴルフ英語-その1->なぜ「旗竿」を「ピン」と呼ぶの? | Gridge[グリッジ]?ゴルフの楽しさをすべての人に!
_“旗竿"のことを「ピン」と呼ぶのはなぜ? 【明日使えるゴルフ用語】 - Myゴルフダイジェスト
_なぜ旗竿はピンと呼ぶの? - ゴルフスクールナビ

#2 [blender][godot] Wavefront形式はノーマルマップを持てるのかどうか

blender 3.6.18 LTS でエクスポートした Wavefront形式(.obj)を、Godot Engine 4.3 64bit でインポートした際、ノーマルマップ(法線マップ)が反映されてないように見えて、このあたりどうなってるのか気になって少し調べた。

Wavefront形式では、頂点データを .objファイルに、マテリアル情報を .mtlファイルに保存するけれど。今回エクスポートした .mtl を覗いたら、ノーマルマップ用画像が map_bump という項目で指定されていた。

どうやら Wavefront形式は map_bump もしくは bump というワードでバンプマップ用のテクスチャ画像を指定できるようではある…。ということは、Wavefront形式は少なくともバンプマッピングには対応しているということになるのかな…。ノーマルマップについては分からんけど。

_Wavefront .objファイル - Wikipedia

ただ、一部のソフトで、ノーマルマップ用画像の指定に、map_Kn というワードを使っていたりもするらしい。ただ、この指定は標準的な仕様ではないと議論されてるようでもある…。

_MTL using non-standard using "map_Kn" for normal map when most others use "norm" - Issue #3726 - assimp/assimp
_c++ - How is NormalTexture represented in the Wavefront resource material format? - Stack Overflow

バンプマッピング用画像はグレースケール画像だけど、ノーマルマップ用画像はRGB画像なので、バンプマッピング用画像としてノーマルマップ画像を指定したらいかんような気もするのだけど…。

_法線マップとは - Shade3D Knowledge Base
_MME:法線マップについて|CzPanel

Wavefront形式の拡張について、ノーマルマップを指定できるように云々と言う話も出ていたようでもあるし…。

_Exocortex | Extending Wavefront MTL for Physically-Based Rendering (WebArchive)

Wavefront形式の標準仕様ではノーマルマップには対応していない気配がする。バンプマッピングには対応していて、出力するソフトによってはバンプマッピング用画像の代わりにノーマルマップ用画像を指定している時もある、という感じだろうか。

読み込むソフト側で、バンプマッピング用として指定されてる画像がグレースケールか、それともRGBかを判別すれば、バンプマッピングとノーマルマップのどちらのつもりでその画像を使っているのか判別できそうな気もする。

2024/12/21() [n年前の日記]

#1 [godot] ゴルフコースにピンを追加してみた

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものが作れないものかなと試してる。

コースにピンが ―― 旗と旗竿が無いとそれっぽくないなと思えてきたので、blender 3.6.18 LTS で旗と旗竿を作成して追加してみた。また、各StaticBody3D に、グループとして flag と flagstick も追加。当たった何かがこのグループ名なら、旗、もしくは旗竿に当たっていると判別できる。

旗竿にボールが当たると跳ね返ることは確認できた。

旗にも処理を追加したい。いわゆる旗包みを表現したい。しかし、ここでハマった…。

ボールが当たらない :

まず、ボールが旗に当たってくれない。そのうち偶然当たってくれるだろうと気楽に試してたら、何十回もボールを打つ羽目に…。

こんなことはやってられないと、Ctrl+Z を押したら打つ前の位置に戻る仕様を入れてみた。これで動作確認が随分楽になった。もしかするとこの仕様は、ゲームにする時も入れておいたほうがいいのかもしれない…? 打ち直しができたほうがいいのだろうか…?

旗にボールが貼り付く :

そのうちようやくボールが旗に当たってくれたのだけど、今度は旗にボールがピタリとくっ付いて、目が点になった。

考えてみたら当たり前。旗とボールが衝突していたらボールの速度をゼロにするという安易な処理を書いてしまったので…。ボールが旗にめり込んだ状態でボールの速度がゼロになって、そのままずっと旗にめり込んでるから、ボールの速度もずっとゼロにされてしまう。ピクリとも動かなくなるのは当たり前。

結局、前フレームではどこに当たっていたかを記憶して、衝突した場所が、空中 → 旗に変わった瞬間だけ速度をゼロにするようにしてみた。これなら旗に当たってからポトリと下に落ちてくれた。

カップイン判別処理を追加 :

せっかくだから、カップイン判別も入れてみた。ボールが静止した状態でカップに衝突していたらカップイン。「CUP IN !」ラベルもその時だけ表示。

これだけでも一気にゲームっぽい感じになってきた。

グリーン上の動作を別処理にした :

グリーン上でボールを打つときは、斜め上ではなく、横方向にだけ力を追加するようにしてみた。ちなみに、RigidBody3D の apply_impulse() で、瞬間的に力を加えることができる。

しかし、ボールが全然転がらない…。グリーンに限らず、何かと当たっていたら移動速度と回転速度をどんどん減衰させるように処理しているから、ボールに急激なブレーキがかかってしまう。

パターモードを用意して、グリーン上で打った時はそのモードにして、ボールの速度の減衰の仕方を変えてみるようにしたら多少はマシになった。

しかしそれでも、グリーン上でパターを打ってる感じには程遠い…。もしかするとミニゴルフの類を別途作って、そこで作り方を勉強したほうがいいのかもしれない。

表示モデルデータをglTF 2.0にしてみた :

今までは、blender から Wavefront形式(.obj)で3Dモデルデータをエクスポートして、Godot Engine にインポートしていたけれど、.obj はノーマルマップ(法線マップ)が使えないっぽいので、別の形式で表示用モデルデータをインポートすることにした。

とりあえず、glTF 2.0 にしてみる。glTF 2.0 はエクスポート時に、以下の3種類のどれかを選べる。
  • glTFバイナリ(.glb)
  • glTF Separate (.gltf+.bin+テクスチャ)
  • glTF Embedded (.gltf)
今回は glTF Embedded (.gltf) を選んでみた。どこかの動画で、この形式が一番問題が起きにくいと言ってたので…。

glTF ならノーマルマップが反映されることを確認できた。

ただ、.obj のように、MeshInstance3D を作成して Mesh に .obj をドラッグアンドドロップする感じではなく、シーンタブ(ノード一覧リスト)にドラッグアンドドロップして、Node3Dとして追加する感じになるっぽい? この操作で合ってるのかな…。ちょっとよく分からない。

コリジョン用モデルデータを作る際には、今まで通り、.obj をインポートして、メッシュ → コリジョン形状を作成、を選ぶことにした。.gltf を利用した場合は、コリジョン形状作成をする方法が分からない…。

#2 [anime][neta] 勇者を名乗ることが許されるのはイケメンだけなのだろうか

妄想メモ。

ファンタジー異世界系の某TVアニメを視聴中にふと思ったのだけど。その手のジャンルの作品に登場する勇者という存在は、見てくれは人間で、なおかつ、イケメンでなければいけないものなのだろうか、と…。 *1

件のアニメは、主人公が暴走すると魔物みたいな外見になる設定なのだけど。その主人公とは別に、勇者という存在がその世界には居るはずだ、ということになってるようで…。見ていた感じでは、主人公がその勇者じゃないの? と思えたのだけど、劇中では主人公の見た目からして、コイツが勇者とかナイナイ、ありえない、ということになってるっぽくて。

そこでふと妄想してしまった。作中で勇者と呼ばれるキャラは、魔王だの邪竜だのを倒す存在だろうけど。そんなゴイスな連中を倒す存在がフツーの人間の見た目のままというのは、ちと無理があったりしないか…?

ショッカー怪人を倒せるのは、元ショッカー怪人だし。怪獣を倒せるのは、怪獣だし。宇宙人を倒せるのは、妖怪やモノノ怪の類だろうし。

であれば、魔王や邪竜を倒せる存在は、魔王や邪竜にしか見えないキャラだったりするほうが実は自然なのではないか…? 人間にしか見えないキャラがそういうアレコレを倒してしまうのって、本当は不自然じゃないか? 物語に登場する民衆は、勇者という存在を、きっと人間に違いない、それもイケメンに違いないと安易に思い込んでいたりしないか?

まあ、既にデビルマンのようなヒーローが存在していたりするので…。勇者とやらがブサメンだったり、そもそも人間の姿に見えない作品も、今ちょっと思い出せないだけで既にたくさんあるだろうなと…。

もっとも、現実世界でも熊だの虎だのゾウだのを人間が道具を使って倒しちゃうので、勇者が人間というのもそれほど不自然ではないのかもしれない…。害獣退治の一種と考えれば、まあ…。

いや、でも、見るからにマタギっぽい人と、ジャニーズタレントっぽい人が並んでたとして、どっちが害獣退治を成功させてくれそうかと言えば…。やっぱり前者じゃない? どうしてその手の作品では、後者がやり遂げてくれるはずだと皆で思い込めるのだろう…?

と、そこまで書いて、ふと脳裏にTOKIOの方々の姿が浮かんだ…。もしかして、勇者ってああいうイメージ? 「きっと彼なら…彼ならやってくれるはず…!」みたいな。あー、なんかちょっと分かったような気分にもなってきたような…? 勇者ってTOKIOみたいなソレかー。

只の妄想メモです。オチは無いです。
*1: ちなみに、「パーティーから追放されたその治癒師、実は最強につき」というアニメだった。

2024/12/22() [n年前の日記]

#1 [godot] OBになった時の処理をどうするか考え中

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものが作れないものかなと試してる。

OBになった時の処理をどうするかで悩んでる。ボールを打った時の座標と、ボールが着地してOBになった時の座標を結んで直線を作って、OBの境界線との交点を求めないとその後の処理ができないのかもしれないな、とか考え始めてしまって…。

ボールを打った点を始点にして、かつ、ボールの着地点を終点にして、その2つの座標のちょうど真ん中の座標で地面の種類をチェックしてみて、そこがOBなら始点側を、OBじゃなければ終点側を対象にして、更に中点の座標を求めてチェックしてみて…。それを延々繰り返せばOBとの境界が見つけられるかな、でもそれって処理が重くならないか、とか考え始めていたのだけど。

そこでふと閃いた。ボールが飛んでいく時の座標をずっと記録しておけば、その座標を逆に辿っていくうちに、いつかはOB以外の場所に出るのではないか。そこがOBとの境界、ということでもいいのでは…。

となると、ボールの座標を記録し続ける処理を書かないといけない。せっかくだから、その座標群を元にして何かを表示すればボールの軌跡が表示できるのでは…。

そんなわけで、ボールの軌跡を表示する処理のほうを先に書き始めてしまった。Sprite3Dを使って、軌跡っぽいものは一応表示できた。

しかし、そこで不安になって、改めてゴルフのOBのルールを調べてみたら…。

OBになった時は打数を+1して、打った場所からもう一度打ち直しをするのが競技ルール、と書いてあって。OBとの境界を求める必要はなかった…。

いやまあ、たしか池に入ったときは、池の手前から打ち直すことになっていた気もするし、どのみちどこかで境界を求める処理が必要になるんだろうけど。少なくともOBについては、OBになった → 打ち直し、で済ませてしまって良さそうだなと…。

もっとも、競技ルールということならそれで済むけれど。ローカルルールとやらでは、やり方が全然変わるようでもあり。色んなパターンがあって、それぞれに対応させようとしたら結構大変なことになりそうだなと…。

_OBの新ルールを図解|どこからプレーできる?処置と救済は? | Honda GOLF | Honda公式サイト
_ゴルフルール改正で最低限知っておきたい 新ルール。バンカーやOBなど厳選5つをおさらい!|初心者ゴルフナビ
_ゴルフのOB、数え方をスッキリ解決! 新ルールや1ペナとの違いも解説【2024年版】 - ゴルフ総合サイト ALBA Net
_OBにはラインとゾーンがある | ルールについて | パークゴルフについて | 公益社団法人日本パークゴルフ協会

解説図を見てもよく分からなかった…。OBの境界線上の位置とピンまでの距離で円を描いたその線がフェアウェイの境界と重なった場所が打ち直し場所、ということでいいのだろうか…。それってフェアウェイと重ならなかったらどうなるんだ? ややこしいな…。

まあ、今回はローカルルールには非対応でいいんじゃないかと…。それ以前に実装しなきゃいけないところがたくさんあるし…。

配列の宣言がよく分からなかった :

Godot Engine の GDScript で、型を指定しながら配列を宣言するあたりの書き方が分からなくてググってた。

以下のような感じでいいのかな…?

var ary: Array[Node3D] = []

試しに新規プロジェクトを作成して、この書き方でいいのか試してみた。別途作った、Node3D + Sprite3D のシーンを読み込んで、64個複製(?)して毎フレーム座標値を変更する処理。複製したシーン(オブジェクト)を覚えておくために配列を使う。

extends Node3D

const ghost_ball = preload("res://ghost_ball.tscn")
const BALL_NUM = 64

var ghost_balls: Array[Node3D] = []

var timer = 0

func _ready():
    var ghost_node: Node3D = get_node("ghost_balls")
    for i in range(BALL_NUM):
        var obj: Node3D = ghost_ball.instantiate()
        obj.global_position = Vector3.ZERO
        ghost_node.add_child(obj)
        ghost_balls.append(obj)


func _process(delta):
    var a = timer * 180.0
    for i in range(ghost_balls.size()):
        var x: float = (i - (BALL_NUM / 2)) * 0.25
        var y: float = 2 * sin(deg_to_rad(a + 10 * i))
        var z: float = -0.2 * i
        var obj: Node3D = ghost_balls[i]
        obj.global_position = Vector3(x, y + 2.5, z)
    
    timer += delta

  • preload() で、別途作ったシーンを読み込めるようではある。
  • 別途作ったシーンのインスタンスを作るには、.instantiate() を使う。
  • インスタンスを、.add_child() を使って、既にあるノードの子ノードとして追加しないと表示されないので注意。この一行を書き忘れて、「表示されないな…おかしいな…」とハマってしまった。
  • 配列への追加は、.append() を使う。

ちなみに上記は、動かすとこんな感じの見た目になる。


2024/12/23(月) [n年前の日記]

#1 [godot] OBになった時の処理が上手く行かなくてハマってた

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものが作れないものかなと試してる。

ボールが、OB やペナルティエリア(ウォーターハザード)に入った時に、打った場所に戻す処理を追加したのだけど、これがバグってしまってずっとハマってた。

ボールの動きが止まる → 地面がOB/ペナルティエリアだと検出される → OB/ペナルティエリアと表示して数秒待つ → ボールの座標を打った場所に書き戻す、という処理をするのだけど。書き戻したら、そこの地面はフェアウェイ/ラフ等を検出してくれるはず…。なのに、何故かOBやペナルティエリアに居ると検出されてしまって、またOB/ペナルティエリアが表示されてしまう。

必ずそうなるわけでもなくて、時々何かのタイミングで、地面の属性を正しく取得できない状態になる…。

どうやら RigidBody3D が、スリープを脱してアクティブにならない瞬間があるっぽい。地面の属性の検出は、物理計算用として呼ばれる _integrate_forces(state) の中で処理しているけれど、スリープになったままだとこの関数が呼ばれないから、地面の属性も更新されないのだろう。

結局、フラグを用意して対処した。ボール座標を変更したらフラグを立てて、そのフラグが立ってる間、地面がOB/ペナルティエリアなら何もしない。RigidBody3D がスリープから抜けて、地面の属性が変わってくれたらフラグを下ろして、以降の処理を始める。みたいな。

また、ボール座標を変更する時は、sleeping変数を true にしつつ、下向きに少し大きめの速度を設定してしまうことにした。これでスリープ状態を脱してくれる可能性が高まるのではないかと期待…。

ただ、何か不具合が起きるたびにフラグを新規に用意して場当たり的に対処してる気がする。そのせいか、ソースはもうグチャグチャ。こういう時にこそ、ステップ処理を使って、モード別で処理するべきなのでは…? ごっそり書き換えてしまおうか…。

余談。ペナルティエリアについて :

ゴルフコースには、いわゆる「池ポチャ」に繋がるウォーターハザードと呼ばれるものがあると今まで思い込んでいたけれど。

ググってみたら、ゴルフのルールが改正されて、ウォーターハザードなる呼び名は無くなったらしい。代わりに、レッドペナルティエリアとイエローペナルティエリアという呼び方になった、という話を見かけた。

ペナルティエリアに入ったときの打ち直しの仕方も、2〜3パターンあるようで…。打ち直し場所の算出がややこしい…。そもそも、どのパターンを採用すればいいのか、そこからして悩む…。

_「池ポチャ」ルール図解! 知っておきたいペナルティの処置と数え方【2024年版】 - ゴルフ総合サイト ALBA Net
_ゴルフボールが池に入った場合の対処法や新ルールを徹底解説(季節・暮らしの話題 2022年01月04日) - 日本気象協会 tenki.jp
_ペナルティーエリア / ジェネラルエリア|用語解説

何にせよ、ボールが、OB/ペナルティエリアとの境界線を横切った座標を取得できないと、その後の処理ができないのだよな…。

レイを飛ばして地面の高さを知る :

カメラが地面の下に潜り込んでしまって変な見た目になる時があったので、地面の高さを取得してカメラ位置の高さを補正したいなと。上から下にレイ(光線)を飛ばして地面のポリゴンモデルと衝突してるか調べて、衝突位置の座標を得ることで補正できそうかな、と…。

レイの飛ばし方は、以下が参考になった。

_Ray-casting - Godot Engine (stable) documentation in English

一応実装できた。レイと、地面ポリゴンモデルの、衝突位置座標を取得するところだけ抜き出してみる。
    y_adjust = false
    var space_state = get_world_3d().direct_space_state
    var from_pos = Vector3(newpos.x, 50, newpos.z)
    var to_pos = Vector3(newpos.x, -50, newpos.z)
    var query = PhysicsRayQueryParameters3D.create(from_pos, to_pos)
    var result = space_state.intersect_ray(query)
    if result:
        var gy = result.position.y
        if gy > newpos.y:
            newpos.y = gy + 0.4
            y_adjust = true

衝突していたら、result という変数に情報が入ってくる。そこから座標を取得できる。

2024/12/24(火) [n年前の日記]

#1 [godot] ピンがある方向に自動で向く処理を入れてみた

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものが作れないものかなと試してる。

ボールが打てる状態になったら、目標角度がピンのある方向に自動で向く処理を入れてみた。これで、スペースキーを叩いてパワーだけを決めればサクサク進む状態になった。

Godot Engine の GDScript には atan2() があるので、それを使えば角度を求められる。今回は x-z平面上だけで角度を考えればいいから、これで充分だろう…。Vector3 のメソッドの中に、似たようなものがありそうな気もしているけれど…。

func set_dir_to_pin():
    var d = pinposition.global_position - global_position
    var ang = rad_to_deg(atan2(d.z, d.x))
    dir_ang = ang + 90

ただ、目標座標 = ピンの座標をどうやって指定するかで考え込んでしまった。blender上であらかじめ指定できたらいいのかもしれないけれど、エクスポートした際にどんな情報をピン座標として渡せばいいのかで悩んでしまって…。例えばemptyオブジェクトを渡せたりするのだろうか? どうなんだろう…。

結局、Godot Engine上で Node3D を1つ追加して、目視でピンの位置に置いて済ませてしまった。目的は果たせるけれど、作業手順としてはかなりダサい気もする…。

もう一点、ボールがピンを飛び越えて、ほとんど真後ろに目標角度を変更する時の、カメラの追従の仕方がどうもしっくりこない。注視点座標とカメラ座標が、若干遅れて本来の座標まで変化するようにしてあるけれど、真後ろを向こうとした時は画面の見た目がギョロン(?)と変わってしまう。その動きは…違うんだよな…。カメラの動きを決定する際、角度を管理する変数も入れてみたほうがいいのかもしれない。

さておき、これでゲームっぽく見える仕様はそこそこ入ったので、地形モデルデータをもう少し修正しつつ、木を作って配置してみようかなと。

しかし、どういう木のモデルデータを作るべきか…。さすがに今時ビルボードは無いよな…。

ちなみに、PS1のみんなのゴルフでは木はビルボードだったけど、PS2版ではポリゴンモデルになってた記憶がある。観客はビルボードだったけど。

#2 [digital][neta] AIはまだちょっと信用できない

年賀状用のイラストを今年もAIに作ってもらおうと思ったのだけど、そもそも来年の干支は何だっけと…。いや、ググれば出てくるけど…。

気になって、Microsoft Edge経由で Microsoft Copilot に尋ねてみたら、「来年は寅年です」と自信満々に言ってきて驚いた。そうだったっけ? いや、違うよね?

copilot_ss01_20241224.png

しかし同じ質問を繰り返すと、正しい回答をしてくる。どういうことだろう…。Copilot、どうなってんの?


Google Gemini で同じ質問をしてみたら、こちらは正しい回答をしてきた。

gemini_ss01_20241224.png


将来的に、ネットでググったらなんでもかんでもまずはAIが答えてきたり、AIが自動生成したWebページばかりがリストアップされる状況になってしまうのかもしれないけれど。今回の初っ端の、間違った回答しか返ってこないようでは、かなりマズイことになりそうだよなと…。

2024/12/25(水) [n年前の日記]

#1 [godot][blender] コースデータ修正中

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものが作れないものかなと試してる。

コースデータを blender 3.6.19 LTS を使って修正中。

以下は blender関係のメモ。

複数オブジェクトを任意オブジェクトで置換 :

OBの境界を示す杭を今まで六角形で作っていたけれど、この手の杭は四角形でもいいんじゃないかなと思えてきたので、blender で修正。

ただ、既に50本ほど配置してしまったオブジェクト群を、別のオブジェクトで一つ一つ、手作業で置き換えていくのはシンドイ…。blender上で、複数選択したオブジェクトを一括して何かのオブジェクトで置換してしまう方法はないか…。

ググってみたら、できるらしいと知った。

_blender備忘録 特定のオブジェクトを任意のオブジェクトに置き換えたい|Nsima
_【Blender】一瞬で別オブジェクトに差し替える|yugaki

  1. Shift+クリックで置換先のオブジェクトを複数選択。
  2. 置換元のオブジェクトを最後に選択。
  3. Ctrl + L でメニューを表示して、「オブジェクトデータのリンク」を選ぶ。

一括して置換された。助かった。ありがたや。

マテリアル毎に複数オブジェクトに分離 :

Godot Engine上で衝突判定処理を軽くするために、複数のマテリアルが使われている1ホール分のコースデータを、マテリアル毎に.objファイルとして保存したい。そのためには、マテリアル毎に、別オブジェクトとして「分離」する必要がある。

本来は、というか以前の作業では、以下の操作をして「分離」していた。
  1. 編集モードに入る。(TABキー)
  2. マテリアルを選択後、「選択」をクリックして、そのマテリアルを使ってるポリゴンを選択。
  3. Pキーを押して分離メニューを出して、「選択」を選んで、選択中のポリゴン群を別オブジェクトに分離する。

しかし、面倒臭い。マテリアルが10個前後あるけれど、面倒臭い。

色々試してたら、以下の方法に辿り着けた。
  • Wavefront形式(.obj)でエクスポートする際、グループ化 → マテリアルグループ、にチェックを入れた状態でエクスポートする。これで、マテリアル別にグループ化されている .obj が得られる。
  • その .obj をインポートする際、オブション → グループで分離、にチェックを入れてインポートする。
これで結果的に、マテリアル別で複数のオブジェクトに分離した状態が得られた。

ただ、bldenr 3.6.19 LTS では、objエクスポート時に「グループの分離」があったけど、blender 3.3.21 LTS では該当項目が見当たらなかった…。比較的最近のバージョンの blender を使わないと、こういうことはできない可能性が高そう。

更に、.obj でエクスポートしてしまうと、.objがサポートしてないせいで、失われてしまう情報が色々ありそう。どんなモデルデータもこの方法で対処できるわけではないだろうなと…。

でもまあ、シンプルなモデルデータなら、これでどうにかできそうな気はする。

と、ここまで書いてから気づいたけれど。Pキーを押して出てくる分離メニューの中から「マテリアル」を選べば、マテリアル別で分離できるのでは…? オブジェクト名は連番になってしまうけど…。

複数オブジェクトを一括エクスポート :

選択した複数のオブジェクトを、オブジェクト1つにつき1ファイルとして、一括して自動でエクスポートできるアドオンがあるらしいと知った。そんなアドオンが使えたら、作業が随分と楽になる。

Capsule というアドオンが使えるらしいと知ったのだけど、手元の blender 3.3.21 LTS, 3.6.19 LTS, 4.2.5 LTS では何故か使えなかった。Export preset が「なし(None)」のままで、何も選択できない…。エクスポートしようとしても「Export presetが指定されてないから処理できない」と言われてしまう。指定しようにもリストには何も無いのですが…。一体どうしろと。

_Capsule (Blender Addon) by Takanu
_Takanu/Capsule: (Blender 4.1) Universal batch export manager
_複数オブジェクトやコレクションをワンクリックでエクスポートすることができる無料の Blenderアドオン Capsule 1.3 | CGinterest


ただ、他にも Blender Super Batch Export なるアドオンがあることを知った。

_mrtripie/Blender-Super-Batch-Export: Allows you to export multiple meshes in a blend file to separate files in one click.
_Blender Super Batch Export Addon - YouTube
_複数選択したオブジェクトを個別のfbxとして書き出すアドオン。検索して出てきたBatexやCapsuleは / X

super_batch_export_2.1.1.zip を入手してインストール。blender 3.3.21 LTS、3.6.19 LTS 上で試した。

インストールすると、一番上のメニューの最後、「ヘルプ」の右側に、上向き矢印のアイコンが追加される。このアイコンをクリックすれば一括でエクスポートできるらしい。アイコンの右側の小さな下向き三角を押せば、設定ウインドウが表示される。

このアドオンなら、blender 3.3.21 LTS、3.6.19 LTS上でも動作してくれた。ありがたや。

と思ったが、エクスポートされた .obj の向きがおかしな状態になっていた。手作業で一つ一つエクスポートする分には、向きはおかしくならないのだけど…。

Obj Settings の Preset で、保存済みのプリセットを選んでしまうとおかしくなるっぽい? それとも Set Rotation 等のチェックを外すといいのだろうか。何にせよ、そのあたりを変更しながら試したら、向きが正常な状態でエクスポートできた模様。どうやら使う際には何かコツがあるっぽい。

Godot Engineに読み込むのが面倒 :

これで10ファイル前後の.objを用意できたけど、これを Godot Engine に読み込んでいくのが面倒…。Godot Engine のプロジェクトフォルダ/imports/ の中に .obj と .mtl をコピーしておけば、Godot Engine から使えるようになるけれど…。
  • StaticBody3D をシーンに追加。
  • その中に、MeshInstance3D を子ノードとして追加。
  • MeshInstance3D の Mesh に、.obj をドラッグアンドドロップ。
  • 上部メニューの「メッシュ」から、コリジョン用メッシュを作成。CollisionShape3D が作られる。
  • CollisionShape3D が作られたので、MeshInstance3D は不要。削除する。
  • StaticBody3D の設定を変更。PhysicsMaterial を新規作成して、bounce 等を設定。
  • StaticBody3D にグループ名を指定。
これを10回ぐらい繰り返さないといけない。面倒臭い。

事前にこういう作業をしておくことで、動作中のプログラムの衝突判定処理は軽くなってくれるのではないかと想像しているけれど…。このインポート作業というか、配置作業と言うか、下準備作業、自動化できないものかな…。

2024/12/26(木) [n年前の日記]

#1 [godot][blender][cg_tools] 木の3DCGモデルを生成するツールをいくつか試用

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものが作れないものかなと試してる。

コース上に木を置きたい。そのためには木の3DCGモデルを作らないといけない。

木のモデルデータを生成してくれるツールを使えないかなと思いついて、その手のツールを試用してみた。

_tree[d] - frecle (WebArchive)
_TreeIt | Tree Generator
_Arbaro - tree generation for povray
_ngPlant | Open Source plant modeling
_SnappyTree

以下、参考ページ。

_LightWave で植栽 - 100光年ダイアリー
_無料で3D樹木、植物を作るツールをご紹介 : ファイン技術開発ブログ

ただ、どれもしっくりこない…。

POV-Rayもインストールした :

Arbaroの出力結果をレンダリングするために POV-Ray 3.5 が必要と書いてあったので、POV-Ray もインストールすることにした。ただ、以前の日記に、Windows10上で POV-Ray 3.5 は動かないとメモしてあったので、今回は POV-Ray 3.62、3.7、3.8-beta2 をインストールした。

_POV-Ray: Download
_POV-Ray: Download Legacy version 3.6
_Releases - POV-Ray/povray

以下の3ファイルを入手。
povwin362-32bit.msi
povwin-3.7-agpl3-setup.exe
povwin-v3.8.0-beta.2-setup.exe

実行すればセットアップが始まる。今回は、D:\Prog\POV-Ray\ 以下に、それぞれ v3.6、v3.7、v3.8-beta2 というフォルダ名でインストールしてみた。

POV-Ray 3.7 と 3.8-beta2 は、インストールが終わるとエディタ用のプログラムのダウンロードとインストールを勧められた。入手先ページには、ライセンスの問題で別にして配布しないといけない、と書かれているように見えた。

指示に従って、以下のプログラムを入手。
povwin-3.7-editor.exe
povwin-3.8-beta-editor.exe

実行したところ、POV-Rayインストールフォルダにインストールされたように見えた。


ただ、ここまでやってみたものの、Arbaro から POV-Ray 3.62、3.7 を使ってレンダリングできないようで…。POV-Ray が「I/Oエラーが起きてる」と文句を言ってくる。

どうやら Arbaro が、POV-Ray で利用する .incファイルを出力するのに数分かかってしまう模様。その間 .inc ファイルが、ずっと書き込み中になってしまうから、POV-Ray が .inc にアクセスできなくてエラーが発生してるのだと思う。

.inc と .pov ファイルがどうにか生成された後で、POV-Ray を立ち上げて .pov を開いてみたらレンダリングに成功した。

まあ、Arbaro の出力結果は膨大なポリゴン枚数になっているようで、inc だけで数十MBもあったから…。今回はゲーム用モデルデータとして使えそうかどうかを調べているので、どのみち Arbaro の生成結果は使えないなと…。

2024/12/27(金) [n年前の日記]

#1 [godot][blender][cg_tools] ゲーム用の木の3DCGモデルの作り方を勉強中

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものが作れないものかなと試してる。

ゲーム用の木のモデルデータの作り方を勉強中。

葉より枝のテクスチャを使ったほうが良さそう :

一般的に、3DCGソフトで木のモデルを作る場合は、葉っぱ一枚を担当するポリゴンに、葉のテクスチャを貼って云々、という感じで作るのだろうと想像するのだけど。その作り方ではポリゴン枚数が増えてしまって、ゲーム用としては厳しいだろうなと…。

ローポリモデルの作り方を紹介する動画を色々眺めてみたけれど、どうやら葉のテクスチャを使うのではなく、枝のテクスチャを使って木を作ったほうが良さそうな感じに見えた。

そんなわけで、枝のテクスチャが入手できないか探してみたのだけど、これがなかなか見つからない…。

色々探しているうちに、OpenGameArt で配布されてるテクスチャ群に辿り着いた。

_60 CC0 Vegetation textures | OpenGameArt.org
_Photomanipulated plant textures | OpenGameArt.org

最初からゲーム用を意識して作られたテクスチャだけあって、これならイイ感じかも…。しかもライセンスはCC0。ありがたや。使わせてもらおう…。

ポリゴン枚数が増え過ぎて厳しい :

枝のテクスチャを使って、試しにモデルを作り始めたのだけど、気づいたら5000ポリゴンほどに。コースデータですら1500ポリゴンで済んでいるのに、1本の木で5000ポリゴンはダメだろ…。たくさん配置するつもりなのに…。作り直さないと…。

十字タイプその他も試した :

ポリゴン枚数を少なくする作り方と言えば、木のテクスチャを十字に配置するやり方もあったなと思い出して、そのあたりも試してみたのだけど…。
  • 2枚の矩形ポリゴンを十字に配置するタイプ
  • 3枚の矩形ポリゴンを60度ずつ回転させて配置するタイプ。
  • 3枚の矩形ポリゴンを中心線で折って、120度ずつ元のテクスチャが見えるようにするタイプ。

_ArtStation - 80LV: Creating a Stylized Chaparral Environment in UE4
_ゲーム用に板ポリとして草や木を作成する時のTips画像を和訳してみました - 3DCG最新情報サイト MODELING HAPPY

ただ、どれもしっくりこない…。そもそもこのタイプは、上から眺めるとちょっとマズいことになる。上から見た時もそれらしく見せるために、木のテクスチャを屋根っぽい感じで配置してみたけれど、今度は横から見た時に屋根モドキが主張してしまって見苦しい。

光の当たり方で、ここにポリゴンがあります感が出てしまうのも厳しい。Godot Engineのマテリアル設定で、シェーディング(?)を無効にすればそういう状態は回避できるけど、今度はベタッとした見た目になってしまってなんだかショボい…。

いっそ遠景と割り切ってビルボードにしてしまおうか。Godot Engine のマテリアル設定を確認していたら、ビルボード用の設定もあって、そこで種類を選ぶだけでビルボードになってくれるようで…。いや、それだと上から見た時に厳しい気もする…。

2024/12/28() [n年前の日記]

#1 [godot][blender][cg_tools] ゲーム用の木の3DCGモデルの作り方を勉強中その2

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものが作れないものかなと試してる。

ゲーム用の木のモデルデータの作り方を勉強中。

昨日もメモしたけど、矩形ポリゴンを120度ずつ配置するタイプだと、以下のようになってしまってよろしくない…。各ポリゴンの明るさが、ポリゴンと光源ベクトルの角度によって変わるので、ポリゴンの境界がはっきり分かってしまう。

tree3d_ss01.png


Godot Engine側でマテリアル設定を弄って、ポリゴンの陰影無しを設定してみると、以下のようになる。ポリゴンの境界は分かりづらくなったけど、ベタっとした感じになって、なんだかこれもちょっと今一つ…。

tree3d_ss02.png


頑張って、ローポリの木のモデルを作って配置してみた。

tree3d_ss03.png

これならなんとかなりそうな気配がしてきたけど、現状ではモデルの見た目がダサい。もっとそれらしい形をモデリングする術を身に着けないと…。

ビルボードも試した :

遠景についてはビルボードでいいんじゃないかと試してみたけど、結構ハマった。

blender上で、ビルボードを配置したい場所に板ポリゴンしか持ってないオブジェクトをリンク複製(Alt+D)でどんどん置いて、Wavefront形式(.obj)でエクスポートして…。Godot Engineで.objを読み込んでから、マテリアル設定だけは Godot Engine側で指定して、その際Billboardを有効(Billboard → Mode → Y-Billboard) にしてみたのだけど…。

各ポリゴンが (0, 0, 0) を原点として回転してしまうようで、全ポリゴンが明後日の場所に移動して、とんでもない見た目になってしまった。blender上では各ポリゴン(オブジェクト)の根元に原点を設定してあるのだけど…。.obj経由ではそういった情報は無くなってしまうらしい。どうしたもんか。

いっそ Godot側で Sprite3D を作成して、複製してチマチマ配置してしまおうか…。blender上でも、結局はポリゴン1枚1枚の位置や角度を微調整して作業していたから、手間暇は似たようなものではないか…。しかし、その場合、地形データをblender上で修正して差し替えたら、Godot上でビルボードの配置作業もやり直す羽目になる。ここはできるだけ blender上で作業を終わらせたい…。

その後試していたら、gltf 2.0 でエクスポートして経由させれば目的を果たせそうと分かった。Godot で .gltf を開いてから .tscn で保存して、マテリアル設定を Surface Material Override で作り直して、そこでビルボードの指定をすればどうにかなりそう。

2024/12/29() [n年前の日記]

#1 [godot][blender][cg_tools] ゲーム用の木の3DCGモデルの作り方を勉強中その3

_昨日 に続き、Windows10 x64 22H2 + Godot Engine 4.3 64bit でゴルフゲームっぽいものが作れないものかなと試してる。

ゲーム用の木のモデルデータの作り方を勉強中。以前作ったソレがあまりにアレなので再度作り直してみたけれど、理想の見た目には程遠い…。特に、木のてっぺんのあたりの見た目が酷い…。

以上、29 日分です。

過去ログ表示

Prev - 2024/12 -
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project