Open18

UnityとVite-React(webpackになった)でゲームを作ったときに困ったこと

きりときりと

ロープの実装が大変、というか暴れる

ロープを実装するために、ロープを分割してそれぞれをjointで接合しているが、それぞれの角運動の計算を簡略化しているため時間がたつほどに実際の数字と乖離していく

ただほかの剛体物理エンジンのBox2Dとかではちゃんと計算してくれていてjointが強靭らしい(unityはデフォルトではPhysX)
https://blog.oimo.io/2022/08/25/calm-joints-down/

が、Unity 2Dでの計算ではBox2Dを使っているらしい
https://learning.unity3d.jp/tag/physics/

今は2Dのゲームを作っているが、まだ恩恵を預かっている感がない

きりときりと

Polygon Collider 2Dの設定がだるい

Polygon Collider 2Dが初期だと解像度によってPathがめっちゃおおくなる

こんなシンプルなオブジェクトが

こんなに複雑になる、とくにかごは中が開いているのでとてつもなく多くなる

しかも、このGUI上ではポイントを消すことができない、InspectorのPointsをちまちま消していくしかない...うむむポイント

本当はこのくらいシンプルにしたい

ただ、sprite editorでGUI上で分かりやすい操作ができるらしい(かなり前の話)、Scene上でもできるようにしてほしいでやんすね...

参考:https://tsubakit1.hateblo.jp/entry/2017/08/22/233000

きりときりと

レイヤーが違くてもオブジェクトが重なってるとクリックできひん

ドラックアンドドロップをonMouseDown()で実装してたが、ある時を境にクリックしても反応しない時がでてきた(うける)

根気強く、クリック連打して反応する場所としない場所を調べたら、
isTriggerだから通り抜けるけどColliderは設定してあるオブジェクトのところが反応しない事に奇跡的に気が付いた

色々調べてたら、解決法っぽいのがでてきた

EventSystemを使った、タッチされたオブジェクトの取得方法を以前紹介しましたが、この方法では1度に1つのオブジェクトしか取得できません。
そのためオブジェクト同士が重なっていると、最初にEventを取得した1つのオブジェクトしか取得することができません。また厄介なことに、取得できるオブジェクトは一番手前のオブジェクトであるとは限らないです。
なので違うアプローチで複数同時タッチに対応します。
https://pengoya.net/unity/object-touch/

Layerが上だったら勝手にクリックできるものだと思っていたが、そういうことではないらしい

大人しくRaycastを飛ばして、大人しく自分のオブジェクトだけを取得する実装に変えたらできた(うける)

before
   void OnMouseDown()
    {
        isDragging = true;
        rb.gravityScale = 0; // 重力を無効にする
        rb.freezeRotation = true; // 回転を固定する
        offset = transform.position - Camera.main.ScreenToWorldPoint(Input.mousePosition);
    }
after
   void Update()
    {
       // クリックが押されたら
        if (Input.GetMouseButtonDown(0))
        {
            //  貫通Raycastビームを飛ばして、ぶつかったオブジェクトを検索
            Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            RaycastHit2D[] hits = Physics2D.RaycastAll(mousePosition, Vector2.zero);

            foreach (RaycastHit2D hit in hits)
            {
                if (hit.collider != null && hit.collider.gameObject == gameObject)
                {
                    OnMouseDown();
                    break;
                }
            }
        }

        if (Input.GetMouseButtonUp(0))
        {
            if (isDragging)
            {
                OnMouseUp();
            }
        }

        if (isDragging)
        {
            OnMouseDrag();
        }
    }
きりときりと

Layer Collision Matrixが効かないお

Edit > Project Settings > PhysicsでLayer Collision Matrixしてるのに効かないお...(^ω^ )

Physics
Physicsの方を見ている愚かな人間

って思ってたら、3Dの方を見てた

Phyisics 2Dをみたら、綺麗なMatrixが!!

Physics 2D
Physics 2D

われわれは、賢明になるためには、まず馬鹿にならなければならない。己れを導くためには、まず盲目にならなければならない。

モンテーニュ 「随想録」

参考:https://discussions.unity.com/t/collision-matrix-not-working/794089

きりときりと

二次元配列をinspectorで設定できない

配列の配列とかを扱いたいときに、insepctorからは設定できない

なのでListのClassのListを使って設定する

public class WeaponShelfManager : MonoBehaviour
{
    [SerializeField]
    private List<WeaponSet> weaponSets; // 武器のセットを管理するリスト
...
}

// 武器のセットを表すクラス
// Unityのinspectorでは2次元配列の設定ができないため、Listのクラスのリストを使用
[System.Serializable]
public class WeaponSet
{
    public List<GameObject> weapons; // 武器のリスト
}

こんなかんじになる

参考:https://qiita.com/TANUKEINA/items/d7c1fb4d43b42de9ef08

きりときりと

型安全にいきたい

Stringで処理を分けているところがよくでてくるんだけど、もっと型安全にやる方法はないのかな
classとか作ればいけるか、普通に

いずれにせよ、ゲームの仕様書作成とかってまじで大変そう、やってる人すごいな

if (child.CompareTag("Weapon") && child.gameObject.layer ==
LayerMask.NameToLayer("WeaponInFront"))
                    {
                        shouldMoveWeapon = false;
                        break;
                    }
きりときりと

オブジェクトのサイズの把握が大変

Inspectorに書いてあるのってscaleで、オブジェクトのサイズって書いてないよね

Sceneにグリッドはあるのに...定規で測るか...

※スクリプトで出すときはこれらしい

GameObject sqObj = GameObject.Find("Square"); // 目的のスプライトのオブジェクトを取得
SpriteRenderer sqSr = sqObj.GetComponent<SpriteRenderer>();//目的のスプライトのSpriteRendererを取得
Debug.Log("四角のサイズは " + sqSr.bounds.size + " です"); // 四角のサイズは (1.0, 2.0, 0.2) です
Debug.Log("四角の横の長さは " + sqSr.bounds.size.x + " です"); // 四角の横の長さは 1 です
Debug.Log("四角の縦の長さは " + sqSr.bounds.size.y + " です"); // 四角の縦の長さは 2 です
Debug.Log("中心からの距離は " +sqSr.bounds.extents+ " です");//中心からの距離は (0.5, 1.0, 0.1) です
Debug.Log("中心の座標は " + sqSr.bounds.center + " です");//中心の座標は (-0.5, 0.2, 0.0) です
Debug.Log("右上の座標は " + sqSr.bounds.max + " です");//右上の座標は (0.0, 1.2, 0.1) です
Debug.Log("左下の座標は " + sqSr.bounds.min + " です");//左下の座標は (-1.0, -0.8, -0.1) です

参考:https://qiita.com/No2DGameNoLife/items/d631b27676c5cd7e9def

きりときりと

Ignore Raycastレイヤーってそうなの?

タップ禁止のゾーンを作って、Raycastでそのゾーンのレイヤーが含まれていたら何も動作しないようにしたかった


タップ禁止ゾーン

「どんなレイヤー名にしようかな~」と思っていたら、デフォルトでなんかっぽい名前のレイヤーを発見!!

使ってみたら、Raycastが反応しなくなった

 foreach (RaycastHit2D hit in hits)
            {
                // Ignore Raycast レイヤーにヒットしたか確認
                if (hit.collider != null && hit.collider.gameObject.layer == LayerMask.NameToLayer("Ignore Raycast"))
                {
                    hitIgnoreRaycast = true;
                    break; // Ignore Raycast レイヤーにヒットした場合はクリックを無視
                }

                // 武器にヒットした場合の処理
                if (hit.collider != null && hit.collider.gameObject == gameObject)
                {
                    hitWeapon = true;
                }
            }

名前だけかと思ったら、そういうロジックがあるのね

レイヤーを使って、レイキャストが交差できるゲームオブジェクトを指定することができます。レイキャストが特定のゲームオブジェクトを無視するようにするには、そのゲームオブジェクトを Ignore Raycast レイヤーに割り当てるか、レイキャスト API 呼び出しに レイヤーマスク を渡します。

大人しく、違う名前のレイヤーにした。ドキュメントはよく読もう

参考:https://docs.unity3d.com/ja/2022.3/Manual/use-layers.html

きりときりと

InspectoreでDictionary型を使うために工夫

がいるらしい、まあ、そうだろう

    [Serializable]
    public class SerializableKeyPair<TKey, TValue>
    {
        [SerializeField] private TKey key;
        [SerializeField] private TValue value;

        public TKey Key => key;
        public TValue Value => value;
    }
  [SerializeField] private SerializableKeyPair<bool,Sprite>[] _menuSprites = default;

参考:https://rimever.hatenablog.com/entry/2022/05/27/000000
https://zenn.dev/tmb/articles/9b4c532da8d467

きりときりと

Instantiateした時にオブジェクトに(clone)って入っちゃう

意図はわかるんだけど、nameで判断したいときにちょっと不便

設定で変えられたらいいのに(それとも、そもそもnameで判断するのが間違ってる??あるかも)

var clone  = Instantiate(source);
clone.name = source.name;

Unityへの機能リクエストとして投げられているみたいだ
https://discussions.unity.com/t/remove-clone-append-to-instantiated-object-names/798445/14

きりときりと

UnityでHTMLを見たいけど、windowsだとWeb-Viewが使えない

unity-webview is a plugin for Unity 5 that overlays WebView components on Unity view. It works on Android, iOS, Unity Web Player, and Mac (Windows is not supported for now).
https://github.com/gree/unity-webview

😇

export packagesでおとなしく、Macに移行することにした

きりときりと

UnityでVite-Reactアプリを見ようとしたら大苦戦

Vite-Reactで作ったアプリをデプロイせずにUnity-WebViewで見ようとしたら大苦戦した

  1. まずは普通にアプリを作る(参考:https://qiita.com/teradonburi/items/fcdd900adb069811bfda)
  2. ビルドする(下記のような出力になる)
dist/
    ├── index.html
    └── assets/
        ├── hogehoge.js
        └── hogehoge.css
  1. UnityでAssets/StreamingAssetsにこれをセッティング
  2. Unity-WebViewを用いてこのファイルを取得して表示する
using UnityEngine;

public class LocalWebViewSample : MonoBehaviour
{
    private WebViewObject webViewObject;

    void Start()
    {
        // WebViewObjectの作成
        webViewObject = new GameObject("WebViewObject").AddComponent<WebViewObject>();

        // WebViewの初期化
        webViewObject.Init(
            cb: (msg) => Debug.Log(string.Format("CallFromJS[{0}]", msg)), // JavaScriptからのコールバック
            err: (msg) => Debug.LogError(string.Format("Error[{0}]", msg)), // エラー時のコールバック
            ld: (msg) => Debug.Log(string.Format("Loaded[{0}]", msg)) // ページが読み込まれたときのコールバック
        );

        // ローカルファイルへのパスを取得
        string url = System.IO.Path.Combine(Application.streamingAssetsPath, "dist/index.html");

        // Androidの場合、ファイルのアクセスには "file://" が必要
        if (Application.platform == RuntimePlatform.Android)
        {
            url = "file:///android_asset/dist/index.html"; // Androidでは特別なパスが必要
        }
        else
        {
            url = "file://" + url.Replace(" ", "%20"); // その他のプラットフォームでは通常のファイルパス
        }

        // WebViewのサイズを設定(左上X, 上Y, 右X, 下Y)
        webViewObject.SetMargins(10, 100, 10, 200);

        // ローカルのHTMLファイルをロード
        webViewObject.LoadURL(url);

        // WebViewを表示
        webViewObject.SetVisibility(true);
    }

    void OnGUI()
    {
        // WebViewを閉じるボタン
        if (GUI.Button(new Rect(10, 10, 150, 100), "Close WebView"))
        {
            webViewObject.SetVisibility(false); // WebViewを非表示に
        }
    }
}
  1. GameObjectにこのスクリプトをアタッチ
  2. ゲームを動かす
  3. 画面が真っ白😇😇😇😇😇😇

こういう時は大体パスがおかしい、という短年の感からtest.htmlという単一ファイルを試してみたら表示された。

そこで、Viteで出力されるアプリを単一ファイルにすれば解決できそうだがそんな都合のいいプラグインなんて...

_人人人人人人人人人人人人_
> vite-plugin-singlefile  <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y  ̄

こりゃあいい

vite.config.tsを変更してbuildしたらindex.htmlだけ出力された!!

vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { viteSingleFile } from "vite-plugin-singlefile";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), viteSingleFile()],
  base: "./",
});

そのindex.htmlAssets/StreamingAssetsに移動したら見れた...

原因はわからなかったがひとまずこの方針で行く

あとは、Unityにイベントを送る&受け取る実装が待ち構えているが、すでに嫌な予感しかしない...😇

きりときりと

vite-react で画像を読むのが大変

vite-plugin-singlefileだとpublic下の画像が取れない

Static resources in public folder (like favicon) are not inlined by Vite, and this plugin doesn't do that either. BUT the output single HTML file CAN work together with these resouces, using relative paths.
https://www.npmjs.com/package/vite-plugin-singlefile

だから、画像自体をビルドしたファイルに用意すれば読み取れはする

SSRだと動的にimageのurlを取得できない

function getImageUrl(name) {
return new URL(./dir/${name}.png, import.meta.url).href
}

SSR では動作しません
ブラウザーと Node.js で import.meta.url のセマンティクスが異なるため、 このパターンは Vite をサーバーサイドレンダリングで使用している場合には動作しません。サーバーバンドルは事前にクライアントホストの URL を決定することもできません。

https://ja.vitejs.dev/guide/assets#new-url-url-import-meta-url

諦めてimport方式でやる

import TitleLogo from "./images/title.png";
きりときりと

UnityでVite-Reactアプリを見るのをやめた

UnityのWebViewでViteでビルドしたアプリが見れない問題について、type=moduleが原因っぽかった。
参考:https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Modules

<script type="module" crossorigin src="main.tsx" />

WebViewで実装しているエンジンでES6が対応していない?理由は分からない...

type="module" 属性がESモジュールを使用するために追加されたのは、2015年にECMAScript 6(ES6)仕様として標準化されたタイミングです。しかし、ブラウザでのサポートは少し遅れて登場しました。以下に主要ブラウザでのサポート開始時期をまとめます。

type="module" のサポート開始時期:
Google Chrome:

サポート開始: Chrome 61
リリース日: 2017年9月
Mozilla Firefox:

サポート開始: Firefox 60
リリース日: 2018年5月
Microsoft Edge:

サポート開始: Edge 16
リリース日: 2017年10月
(ChromiumベースのEdgeの場合、Chromeと同じタイミングでサポート)
Safari:

サポート開始: Safari 11
リリース日: 2017年9月
Opera:

サポート開始: Opera 48
リリース日: 2017年9月

ひとまずWebpackで行うことにしたらできた。

参考:https://zenn.dev/spacemarket/articles/23e5401a074ccc#3.-typescriptを導入する

きりときりと

Text Mesh Proで光の当て方が分からん

URPのLight 2Dのというものがあるらしく使ってみたら、割といい感じだったのだが...

Text Mesh Proを使っている文字だけ表示されてしまった

当然使っているText Mesh Proは2D限定ではないので、理由は分かるが、今度はこいつらを真っ暗にする方法がわからなくなってしまった

やってみたこと

  • Text Mesh Pro > Inspector > Mesh Renderer > Lighting > Cast Shadows > On
    • これやってゲーム実行すると勝手にoffになる😇
  • SDF Material > Lighting
    • なんか若干文字の色が暗くなった

多分だけど、ちゃんとLightの使い方を知っていれば解決する気がする

Unity詳しい人を探す旅に出る...

きりときりと

Unityroomに投稿しようとした時に起きたアレコレ(解決編)

UNITY_EDITORのせいでビルドにこける

UNITY_EDITORはUNITY_EDITORでしか動かない、それはそう

//#if UNITY_EDITOR  // この行を削除
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEditor;
using System.Linq;

public class WeaponDataLoader : MonoBehaviour
{
    // クラス内容はそのまま
}
//#endif // この行を削除

AssetDatabase使っているせいでビルドにこける2

AssetDatabaseはUnityエディター専用のAPIであり、WebGLや他のプラットフォーム向けにビルドされたアプリケーションでは動作しません。

// 修正前
// string[] prefabGUIDs = AssetDatabase.FindAssets("t:Prefab", new[] { prefabFolderPath });
//        foreach (string guid in prefabGUIDs)
//        {
//            string assetPath = AssetDatabase.GUIDToAssetPath(guid);
//            GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
//            if (prefab != null)
//            {
//                prefabs.Add(prefab);
//            }
//        }

// 修正後
GameObject[] prefabs = Resources.LoadAll<GameObject>("Weapons");

WebGLでFile.ReadAllTextができない

JSOSファイルを読み込もうとしたら、FileNotFoundExceptionになった
StreamingAssetsに配置したファイルはIO.Fileで取得できない、らしいのでWebリクエストを送るように修正

参考:https://qiita.com/kazuki_kuriyama/items/3155606bb6cb5861ce68#ファイル読み書き関連

// 修正前
//    string jsonText = File.ReadAllText(jsonFilePath);

// 修正後
// WebGLではUnityWebRequestを使用
UnityWebRequest request = UnityWebRequest.Get(jsonFilePath);
await request.SendWebRequest();
// JSON内容を取得
string jsonText = request.downloadHandler.text;

unity-webviewでエラー発生

なんとかbuildは通ったが、今度はunity-webviewを使ったオブジェクトが表示されない
ひとまず公式のReadMeを読んでできそうな事をやる

  1. WebGL Templatesを作る
  2. デフォルトのテンプレートを持ってくる$ cp -a /Applications/Unity/Hub/Editor/2022.3.45f1/PlaybackEngines/WebGLSupport/BuildTools/WebGLTemplates/Default/TemplateData Assets/WebGLTemplates/unity-webview-2020
  3. /unity-webview/dist/package/Assets/WebGLTemplates/unity-webview-2020にあるindex.htmlunity-webview.jsをWebGL Templatesに持ってくる
きりときりと

Unityroomに投稿しようとした時に起きたアレコレ(未解決編)

AddressablesとWebGL Templates

今2つの問題が起きている

  1. unity-webviewでローカルファイルを読み込むときに、StreamingAssetsを使っているが、UnityroomではStreamingAssetsが使えないので、StreamingAssetsが使いたかったら別のサーバーで配信するしかない
url = "file://" + System.IO.Path.Combine(Application.streamingAssetsPath, "index.html").Replace(" ", "%20");        
// ローカルのHTMLファイルをロード(再利用時もリロードする)
webViewObject.LoadURL(url);
  1. unity-webviewを使うためにWebGL Templatesを用いているが、Unityroomでは見ていないので、どうにか*.loader.jsに含める必要がありそう

思いつく解法

1番
a. unity-webviewの中を書き換えて、StreamingAssetsではなくResourceに向くようにする
 →難しそう、やってみる価値はある
b. StreamingAssetsの中を配信する
 →配信の中身はただのReactnoアプリなので、Vercelでもherokuでもなんでもデプロイできそうだが、「Unity側にメッセージを送信」部分をどうやってデプロイした環境で実行できるのかが分からない(window as any).Unity.call(json);

2番
下記を参照してやる
参考:https://zenn.dev/uimss/articles/9912f9d7147853#webglでのaddressable(assetbundle)読込の流れ

ひとまず

1番のaでやってみる

ちなみにitch.ioならこんなことしなくてもいいらし、次は英語対応のゲームを作ろうと心に誓った

やってみた結果

2番はいけた

1-a
よく分からんかった、unityObject.csの下記のどこかで、Resouseから読み込む処理を追加したらいけると思ったが、そんなことはなかった

unityObject.cs
 public void LoadURL(string url)
    {
        if (string.IsNullOrEmpty(url))
            return;
#if UNITY_WEBGL
#if !UNITY_EDITOR
        _gree_unity_webview_loadURL(name, url);
#endif
#elif UNITY_WEBPLAYER
        Application.ExternalCall("unityWebView.loadURL", name, url);
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || UNITY_EDITOR_LINUX || UNITY_SERVER
        //TODO: UNSUPPORTED
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE
        if (webView == IntPtr.Zero)
            return;
        _CWebViewPlugin_LoadURL(webView, url);
#elif UNITY_ANDROID
        if (webView == null)
            return;
        webView.Call("LoadURL", url);
#endif
    }

1-b

  • VercelでNext.jsのアプリとしてデプロイした
  • CORSの設定書いた
  • scriptにdisplayMessageFromUnity書いた

でもエラー出まくり、unity-webviewのobjectはiframeの中に表示されるが、そのiframeの関数を実行する方法が分からないし、クロスオリジンだと出来なさそう

middleware.ts
import { NextRequest, NextResponse } from "next/server";

const allowedOrigins = [
  "https://unitryroom.com",
  "https://57521.play.unityroom.com",
];

export function middleware(request: NextRequest) {
  // Check the origin from the request
  const origin = request.headers.get("origin") ?? "";
  const isAllowedOrigin = allowedOrigins.includes(origin);

  // Handle preflighted requests
  const isPreflight = request.method === "OPTIONS";

  if (isPreflight) {
    const preflightHeaders = {
      ...(isAllowedOrigin && { "Access-Control-Allow-Origin": origin }),
    };
    return NextResponse.json({}, { headers: preflightHeaders });
  }

  // Handle simple requests
  const response = NextResponse.next();

  if (isAllowedOrigin) {
    response.headers.set("Access-Control-Allow-Origin", origin);
  }

  return response;
}

layout.tsx
"use client";

import "./index.css";
import Provider from "./provider";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ja">
      <head>
        <script>
          {`
        function displayMessageFromUnity(msg) {
            window.unityMessage = msg;
        }`}
        </script>
      </head>
      <body>
        <Provider>{children}</Provider>
      </body>
    </html>
  );
}

結論

unity-webviewから、普通のunityのgameobjectに置き換える

😭

きりときりと

Resoucesファイルって非推奨なのね

3.1. Best Practices for the Resources System
Don't use it.
https://learn.unity.com/tutorial/assets-resources-and-assetbundles#5c7f8528edbc2a002053b5a7

草だこれ

理由としては、要はメモリ管理とか最適化が甘い、とかよくある理由らしい

このように強く推奨する理由はいくつかあります:
Resources フォルダを使用すると、きめ細かなメモリ管理が難しくなる
Resources フォルダの不適切な使用は、アプリケーションの起動時間とビルドの長さを増加させる
Resources フォルダの数が増えると、フォルダ内の Assets の管理が非常に困難に
なる Resources システムは、特定のプラットフォームにカスタム コンテンツを配信するプロジェクトの能力を低下させ、コンテンツのインクリメンタル アップグレードの可能性を排除する
AssetBundle Variants は、デバイスごとにコンテンツを調整するための Unity の主要なツールである。

代わりにAddressableを使おう

https://light11.hatenadiary.com/entry/2020/07/29/202755

そうは言われてもUnityroomで投稿する時にはResourcesに入れないと、外部配信必要になってる気がするからケースバイケースって感じ

多分🤔