ゲーム制作勉強中!あこがれだったプログラマーに今からなろう!

昔、あこがれていたプログラマー。今からでも勉強してみようと思い立ち、チャレンジ開始! 勉強メモや、悪戦苦闘な日々の記録です。

3D脱出ゲーム:ItemBoxをドラッグできるようにしよう。

前回からだいぶん間が空いてしまいました😭
ゲーム作成をしていなかったわけではなく、あまりにもプログラミングや仕組み作りが楽しすぎて、ついついブログがおざなりになってしまってました💦

前回までで基本システムの構想はできたので、いよいよゲームにしていく段階に入りました。
脱出ゲームの部屋として、色々とアセットストアを探っていて、イメージに近いものが見つかりました。

assetstore.unity.com

このアセットで部屋を作り始めましたが、画面の一番下に表示されているアイテムボックスがどうにも気になり始めたので
アイテムボックスを開閉する様に改造する事にしました。
そうすると、アイテムボックスのリュックマークにアイテムをドラッグして取得する仕組みがうまくいかないので、
結局、アイテムの取得方法はクリックに戻すことになるのですが、そこは仕方ありません。🤣

パネルにそれっぽいイメージを重ねて、固定のアイテムボックスからSlotを移植して、こんな感じになりました。

開閉できるウィンドウ型にしました。
ウィンドウ型にしたので、やっぱりドラッグで移動できるようにもしようと思います。
前回まで散々学んだドラッグの仕組みの応用でできそうですし。😁

ということで、作成したスクリプトはこんな感じです。

using UnityEngine;
using UnityEngine.EventSystems;

public class DragUI : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler

{
    Vector3 screenPosition;
    [SerializeField]Canvas canvas;

    public void OnBeginDrag(PointerEventData eventData)
    {
            screenPosition=Input.mousePosition; //マウスポインタのスクリーン座標を取得
            var scrPos=new Vector3(screenPosition.x,screenPosition.y,canvas.planeDistance+0.01f)
            var worldPos=Camera.main.ScreenToWorldPoint(scrPos);
    }

    public void OnDrag(PointerEventData eventData)
    {

            screenPosition=Input.mousePosition; //マウスポインタのスクリーン座標を取得
            var scrPos=new Vector3(screenPosition.x,screenPosition.y,canvas.planeDistance+0.01f);//2fはカメラからキャン
            var worldPos=Camera.main.ScreenToWorldPoint(scrPos);
            transform.position = worldPos;
        
    }

    public void OnEndDrag(PointerEventData eventData)
    {
    }

}
canvas.planeDistance

と言うのは、Canvasの平面距離を取得するプロパティです。
今までは数値を直接入力していたのですが、やっぱり、マジックナンバーはよくないと思い、調べてみたら、ちゃんと取得するプロパティがありました。

で、動作確認ですが、

youtu.be

ItemBoxのTransform.positionに直接マウスの座標をぶちこんでいるので、ItemBoxのピポットとのズレを一瞬で移動していて、ItemBoxがぴょこっと瞬間移動しているように見えています。
もちろん、明らかな失敗です。😭
考えてみれば、今までのドラッグでも、このマウスの座標とピポットのズレは発生していたと思われるのですが、
今までのObjectの移動では、Objectのサイズ縮小を行っていたので、このズレに気づいていなかったのかもしれません😅

さっそく色々な記事を読み漁りながら、対策方法を考えていたら、今日もぴったりな解決方法が見つかりました。😁

www.youtube.com

あのゲームの作り方さんのyoutubeがズバリ、この問題の解決方法でした。

ざっくり要約すると、
ドラッグを開始するときに、マウスの座標とItemBoxのTransform.positionの差分をoffsetとして変数に確保しておき、
ドラッグ中に、ItemBoxのTransform.positionにマウス座標を入れるときに、offsetで補正をかけるという仕組みです。

早速、先ほどのスクリプトに導入。

using UnityEngine;
using UnityEngine.EventSystems;

public class DragUI : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler

{
    Vector3 m_offset;

    Vector3 screenPosition;
    [SerializeField]Canvas canvas;

    public void OnBeginDrag(PointerEventData eventData)
    {

            screenPosition=Input.mousePosition; //マウスポインタのスクリーン座標を取得
            var scrPos=new Vector3(screenPosition.x,screenPosition.y,canvas.planeDistance+0.01f);
            var worldPos=Camera.main.ScreenToWorldPoint(scrPos);
            m_offset=new Vector3( //ここでoffsetに座標の差分を格納する。
                transform.position.x-worldPos.x,
                transform.position.y-worldPos.y,
                worldPos.z 
            );
    }

    public void OnDrag(PointerEventData eventData)
    {

            screenPosition=Input.mousePosition; //マウスポインタのスクリーン座標を取得
            var scrPos=new Vector3(screenPosition.x,screenPosition.y,canvas.planeDistance+0.01f);
            var worldPos=Camera.main.ScreenToWorldPoint(scrPos);

            transform.position = new Vector3(worldPos.x+m_offset.x,worldPos.y+m_offset.y,worldPos.z);//offset分補正している。
        
    }

    public void OnEndDrag(PointerEventData eventData)
    {
    }
}

これでItemBoxが自由に動かせる様になりました。

youtu.be

ドラッグの仕組みは、色々と応用が聞いて面白いですね。
Event Triggerも色々と使い所が多く、早いうちに学習できたのは、かなりありがたいと思います。

さて、次は、アイテムのズーム機能の改造を行います。

UniTaskとオブジェクトプールの併用でasync/awaitが動かなかったので解決方法を実験。

昨日書いた様に、3D脱出ゲームはちょっと休憩して、今日はUniTaskの実験。

Unityroomのゲームジャムに参加した際、どうせならとUniTaskを導入して使ってみた。
敵キャラの動作をコルーチンの代わりにasync/awaitでやってみたのだけれど、なぜか上手くいかない。
敵キャラはオブジェクトプールを使って、同じオブジェクトを使い回す様に作ったけれど、敵がやられてオブジェクトプールに返って、
その後に再生成されると、async/awaitが動いてくれなかった。

もっとしっかり解決方法を調べたかったけれど、ゲームジャムは一週間と言う期限があったので、仕方なくコルーチンに戻して完成させる事にしたけれど、
ちょっとモヤモヤが残ってしまったので、早いうちにきちんと実験しておきたかった。

その時のスクリプトはこんな感じ。

public async void SetParameter()
{
     if(cts==null)
     {
        cts=new CancellationTokenSource();
        Token=cts.Token;
     }

    Moving(Token);
}


async UniTask Moving(CancellationToken token)
{
            //省略〜

            //〜省略

            await UniTask.Yield(PlayerLoopTiming.Update, token); 
}

//unitaskのキャンセル
private void OnDisable() 
{
    cts.Cancel();
}

private void OnDestroy() 
{
    cts.Cancel();
}

}

結論から言うと、この処理がいけなかった様だ。

     if(cts==null)
     {
        cts=new CancellationTokenSource();
        Token=cts.Token;
     }

一度、CancellationTokenを作っておけば、それを使いまわせるんじゃないかと思い、cts変数の中身を確認して、空でなければ
CancellationTokenを再生成しないようにしていた。
どうも、この処理が余計だった様で、このifを外すと、オブジェクトプールから再生成した時にasync/awaitが動いてくれない問題はあっさり解決してしまいました。

でも、まだちょっと気になる点があります。


オブジェクトの数とUniTaskの数が合っていません。

実は、先ほどのifは、この解決になるのではと試行錯誤していた名残だったわけです。
で、色々と実験しているうちに、async/awaitのメソッドを呼ぶときに.forgetを使うとこの問題が解決する事に気づきました。

public async void SetParameter()
{
     cts=new CancellationTokenSource();
     Token=cts.Token;

    Moving(Token).Forget();
}


このforgetを使うことで、数が合わなくなることは、一応解決しました。
けれど、なぜそうなるのかは理解できませんでした。

ちなみに、forgetの意味をCopilotくんに聞いてみると、

Copilot
.Forget()は、非同期メソッドを投げっぱなしで実行する際に便利なメソッドです。以下に詳細を説明します。

.Forget()の役割:
UniTaskを非同期的に実行する際、awaitを使わずに非同期処理を投げっぱなしで実行したい場合があります。
このとき、コンパイラが「awaitしていない非同期メソッド」に対して警告を出します。
.Forget()を使うことで、この警告を抑制できます。

注意点:
.Forget()を使う際、非同期メソッド内で発生した例外はキャッチされません。
例外処理が必要な場合、適切にハンドリングするようにしてください。

との回答で、今の疑問についての説明になっていないのではないかと思います。
色々と記事を探してみましたが、結局、コレと言う記事を見つける事はできませんでした。

UniTaskについては、まだまだ判らない事だらけですが、とりあえず最初に話していた、「オブジェクトプールから再生成した時にasync/awaitが動いてくれない問題」は解決したので、これからも色々と使ってみようと思います。

3D脱出ゲーム:カバンからアイテムをドラッグして出せるようにしよう。

昨日の続き、アイテムを所定位置にセットできるようになったので、次はカバンからアイテムを取り出せるようにしよう。
基本的な仕組みは、実はもうできています。

フィールドにあるアイテムをドラッグする仕組みの、DragObjスクリプト
カバンからアイテムをドラッグするのも、同じイメージになるので、大筋のスクリプトは同じになります。

DragObjスクリプトの動きをざっくり説明すると、

①Drag開始した時にアイテムを縮小し、Canvasの手前に移動させる。
②Drag中は縮小したアイテムの位置をマウスの座標に移動し続ける。
③Drag終了時、アイテムから後方にRayを飛ばしてセットできる場所にいるか調べる。
 →セットできる位置にいれば、セットできる位置のメソッドにアイテムを引き渡す。
 →セットできる位置でなければ、アイテムをDrag開始位置に戻す。
④アイテムの大きさを元に戻す。

というものです。

今回やりたいのは、フィールドにあるアイテムではなく、カバンのアイテムをDragするというものです。
その違いは、カバンはあくまでSlotにImageを表示しているだけで、アイテムがそこにあるわけではないという事です。

なので、DragObjをベースに、機能の違いを変更した新しいスクリプト、DragObjUIスクリプトを作成する事にします。

DragObjからの変更点としては、
まず、Drag開始時点では動かすべきアイテムがないので、OnBeginDragでDrag用のアイテムを生成して、以降の処理はこの生成したアイテムを対象にするように変更します。

    public void OnBeginDrag(PointerEventData eventData)
    {
        if(!eventData.pointerDrag.GetComponent<Slot>().IsEmpty())//DragしたSlotにアイテムがあるか確認する。
        {
            Dragslot=eventData.pointerDrag.GetComponent<Slot>();//アイテムがあれば、Slotを格納。

            screenPosition=Input.mousePosition; //マウスポインタのスクリーン座標を取得
            var scrPos=new Vector3(screenPosition.x,screenPosition.y,2f);//1.5fはカメラからキャンバスまでの距離(2f)の手前にくる様に指定
            var worldPos=Camera.main.ScreenToWorldPoint(scrPos);

            GameManager.instance.OnDrag=true;

            //ドラッグ用のオブジェクトを生成
            this.item = eventData.pointerDrag.GetComponent<Slot>().GetItem();//Slotのスクリプトを利用してアイテムの情報を取得。
            PickUPObj=Instantiate(item.PickUPObj);//取得した情報からDrag用のObjectを生成。
            PickUPObj.transform.position=worldPos;//Drag用のObjectの座標をマウス座標に移動。
            PickUPObj.name="PickUPObj";        
            { ///transformの情報を取得
                ObjStartTransform = PickUPObj.transform;
                ObjStartPos = PickUPObj.transform.position;
                ObjStartRot = PickUPObj.transform.rotation.eulerAngles;
                ObjStartScale = PickUPObj.transform.localScale;
            }

            { //移動オブジェクトのスケールを変更  
                rectTransform=PickUPObj.GetComponent<RectTransform>();
                float currentHighest = rectTransform.sizeDelta.y;
                float scaleFactor=10/currentHighest;//移動オブジェクトの高さを10に指定
                
                PickUPObj.transform.localScale=new Vector3(scaleFactor,scaleFactor,scaleFactor);
            }
        }
    }

次に、Drag中の処理のOnDragもDragしたSlotではなく、生成したアイテムを対象にするように変更します。

    public void OnDrag(PointerEventData eventData)
    {

        if(!eventData.pointerDrag.GetComponent<Slot>().IsEmpty())
        {
            screenPosition=Input.mousePosition; //マウスポインタのスクリーン座標を取得
            var scrPos=new Vector3(screenPosition.x,screenPosition.y,2f);
            var worldPos=Camera.main.ScreenToWorldPoint(scrPos);

            PickUPObj.transform.position = worldPos;//生成したアイテムの座標をマウス座標に移動。
        }
    }

最後にDrag終了時点、ここは変更点がとても多くあるので注意が必要です。

Rayを飛ばす処理ですが、DragしているのはあくまでSlotなので、Rayの起点は生成したアイテムの位置=マウスの位置にする必要があります。
また、Rayに「セットできる位置」がヒットした場合、「セットできる位置」のメソッドに引き渡すのも、生成したアイテムに変更しなければなりません。
さらに、「セットできる位置」がヒットしなかった場合のアイテムを初期位置に戻す処理は、生成したアイテムなので、元に戻すのではなく、Destroyするようにします。

    public void OnEndDrag(PointerEventData eventData)
    {
       GameManager.instance.OnDrag=false;

        var scrPosition=Input.mousePosition; //マウスポインタのスクリーン座標を取得
        var scrPos=new Vector3(scrPosition.x,scrPosition.y,2f);//2fはカメラからキャン
        var worldPos=Camera.main.ScreenToWorldPoint(scrPos);

        //カバンの位置でない場合、Rayを飛ばしてオブジェクトが何かのセット位置に当たっているかを調べる。
        Vector3 direction = (worldPos - Camera.main.transform.position).normalized;
        Ray ray = new Ray(worldPos, direction);
        RaycastHit hit;
        int layerMask = 1 << LayerMask.NameToLayer("SetObjectStage");

        if (Physics.SphereCast(ray, 0.5f, out hit, 12, layerMask))
        {
            //当たっていれば、そのオブジェクトのSetPositionメソッドに、生成したPikuUPObjを引き渡す。
            hit.collider.gameObject.GetComponent<DropObj>().SetPosition(PickUPObj);
        }
        Invoke("CheckPosition",0.03f);
    }

    void CheckPosition()
    {
        if(!PickUPObj.GetComponent<DragObj>().IsDropSuccess)
        {
            Destroy(PickUPObj);
        }
        else
        {
            PickUPObj.transform.localScale = ObjStartScale;
            PickUPObj.transform.rotation = Quaternion.Euler(ObjStartRot);

            Dragslot.GetComponent<Slot>().SetItem(null);
            Dragslot.GetComponent<Slot>().HideBGPanel();
            ItemBox.instance.ResetSelectedItem();

        }
    }

これで変更は完了なので、このDragObjUIスクリプトをSlotにアタッチしてやれば完成です。
youtu.be

これで、アイテムをカバンに入れて、カバンから取り出してセットして、またカバンに戻すと一連の動きができるようになりました。

さて明日以降ですが、ちょっと脱出ゲームは休憩してUniTaskの実験をしようと思っています。
ゲームジャムで作ったゲーム「ColorChangePanic」で、敵Objectにはasync・awaitを使ったスクリプトをアタッチしているのですが
この敵Objectをオブジェクトプールで使い回すと、スクリプトの動きが思った通りにならなかったので、それの解決方法を考えてみようと思います。

3D脱出ゲーム:ドラッグしたアイテムを所定位置にセットできるようにしよう。

先週は仕事に明け暮れて、ゲーム制作をするほど時間を確保できませんでした。
ただ特に忙しくなる最終週の前に、Unityroomのゲームジャムに参加していたので、結果の確認がてら他の人の作品を毎日遊んでいました。

他の人のゲームを遊ぶのは、色々と勉強になり楽しいものです。
ゲームジャンルも多種多様ですが、何より、テーマの「かわる」をどう捉えるかも様々。
素晴らしい発想が多く、いかに自分がまだ「自分の常識」に囚われているかを思い知らされました。

作品の中には、一週間で作り上げたとは思えないほど、完成度が高く、ハイクオリティーなものもあり
僕もまだまだ頑張らないといけないなと感じました。
「自分の常識」を壊して、自由な発想ができるように、もっといろいろな事を学んでいきたいと思います。

さて、年度末の繁忙期もようやくひと段落。
そろそろ、またゲーム制作を再稼働していこうと思います。

UniTaskの勉強というか実験もしたかったけれど、ゲームジャム参加の前にやっていた3D脱出ゲームの続きを再開することにしました。

しかし、一週間ゲーム制作から離れただけなのに、自分が作ったプログラムを理解するのにやたら時間がかかります。
どこで何をやっているのか、何が何やら、、、
コメントで注釈をいれる必要性をひしひしと感じました。

とにもかくにも、ある程度思い出したので、
先週の続き、ドラッグしたアイテムをドロップした場所が、所定位置であれば、そこにセットされる仕組み作りです。
下図にあるシリンダーを右のステージ上にセットできるようにします。

今は、シリンダーとステージは横並びの位置にいますが、
先週までに作成した仕組みで、シリンダーをドラッグすると、シリンダーは縮小され、Canvasの手前に移動されるようになっています。
そのため座標が違いすぎて、EventTriggerのDropでは判定できません。

そのため別の手段で、ドラッグしたObjectの位置が、ステージの手前なのかを調べる必要があります。
いろいろな方法があると思いますが、僕の場合は使い慣れてきたRaycastを使う事にしました。

Objectのドラッグが終了するOnEndDragでObjectからカメラと反対方向にRayを放ち、ステージにヒットしたら
ステージの関数を呼び出して、Objectをステージにセットさせるという計画。

まずはObjectがセットされる位置を格納して、その位置を返すスクリプトを作って、ステージにアタッチしてやる。

using UnityEngine;
using DG.Tweening;

public class DropObj : MonoBehaviour
{
    [SerializeField] private Transform SetPos;

    public void SetPosition(GameObject other)
    {
        var TriggerObj=other;
        TriggerObj.transform.DOMove(SetPos.position,0.5f);//0.5秒かけてSetPosの位置に移動
        TriggerObj.GetComponent<DragObj>().IsDropSuccess=true;//オブジェクトのIsDropSuccessをtrueにする
    }
}

次に、Objectからカメラと反対方向にRayを放って判定する。
カメラと逆の方向を出すには、Objectのpositionからカメラのpositionを引いてやって、それを正規化してやればOK。
ステージにヒットしたら、ステージのメソッドを呼び出してセット位置を取得する仕組み。
最後に、セット位置を取得できていればその位置で、そうでなければドラッグ開始位置でObjectの大きさと向きを元に戻してやる。

    public void OnEndDrag(PointerEventData eventData)
    {
        //省略〜

        //〜省略

        //カバンの位置でない場合、Rayを飛ばしてオブジェクトが何かのセット位置に当たっているかを調べる。
        Vector3 direction = (transform.position - Camera.main.transform.position).normalized;
        Ray ray = new Ray(transform.position, direction);
        RaycastHit hit;
        int layerMask = 1 << LayerMask.NameToLayer("SetObjectStage");

        if (Physics.SphereCast(ray, 0.5f, out hit, 12, layerMask))
        {
            Debug.Log("Hit " + hit.collider.name);
            //当たっていれば、そのオブジェクトのSetPositionメソッドを呼び出す。
            hit.collider.gameObject.GetComponent<DropObj>().SetPosition(gameObject);
        }

        Invoke("CheckPosition",0.03f);
    }

    void CheckPosition()
    {
        if(IsDropSuccess)
        {
            transform.localScale = ObjStartScale;
            transform.rotation = Quaternion.Euler(ObjStartRot);
        }
        else
        {
            transform.position = ObjStartPos;
            transform.rotation = Quaternion.Euler(ObjStartRot);
            transform.localScale = ObjStartScale;
        }
        canvasGroup.blocksRaycasts = true;

    }

動きはこんな感じになります。
www.youtube.com

UnityRoomの1Wゲームジャム参加:Color Change Panicを公開しました。

疲れた〜
この一週間は脱出ゲームは置いといて、UnityRoomの1Wゲームジャムに参加していました。
unityroom.com

unityroom.com

お題は「かわる」
かわると言ったら、色。くらいしか思いつかなかったので、敵の色が変わる簡単なパズルシューティングです。

ゲームの説明用に動画も作りました😆
www.youtube.com

せっかくなのでUniTaskの勉強を兼ねて、ノーコルーチンを目指してみましたが、
ObjectPoolでObjectを使いまわした時に、それぞれのObjectが持っているスクリプトのUniTaskが
どうしてもうまく動いてくれなくて、そこだけ断念しました😅
この辺りは、今後の勉強課題。

何はともあれ、無事に公開に漕ぎ着けましたので、よかったら遊んでいただけると大変嬉しいです!

ドラッグ&ドロップで3Dオブジェクトを移動したり取得したりしよう!

3D脱出ゲームの勉強と試行中。

しまづさんのyoutubeシリーズ

www.youtube.com

を、金曜・土曜・日曜で
# 08ズームパネルの作成
# 09ズームオブジェクトの作成
# 10ズームオブジェクトの回転
# 11アイテムの設置
# 12 パスワードギミックの作成
# 13パスワードクリアの実装
# 14カメラの回転
# 15カメラのズーム
# 16設計(フローチャート)の作成
# 17モデルのインポート編
# 18モデルの設定
#19 アイテムの取得 メイン編
まで、一気に学習しました。

その関係で、オブジェクトをドラッグで移動したり、所定の枠に嵌めたりする方法の説明を、スタジオしまづさんのサロンで
以前にしていた事があったのを思い出したので、過去の記録を探してみたけれど、どうしても見つかりませんでした。
仕方がないので、しまづさんにもう一度説明をお願いしたところ、以前より濃い内容の説明動画をyoutubeに上げてくださいました😄

www.youtube.com

ありがたいことに冒頭に僕の紹介までしてくださいました!🙇
いつもながら判りやすい説明で、理解しやすかったです。
Event Triggerコンポーネントを今まで全く使っていなかったので、すごく勉強になりました。

youtubeでしまづさんが仰られた様に、3D脱出ゲームに組み込みたい機能だったので、
しまづさんのyoutubeシリーズで作成した3D脱出ゲームに、Event Triggerを使うドラッグを早速組み込んでみることにしましたが
youtubeは2Dをベースに説明されており、僕が作成している3Dには、そのままでは使えません。

座標変換の方法は、以前に皿割りゲームで勉強したことがあり

amaniku.hatenablog.com

その時はワールド座標をUI座標に変換する方法でしたが、この時の経験のおかげで、逆の変換についても理解しやすかった様に思います。

要はxとyの世界のスクリーン座標を、xとyとzのワールド座標に変換するためには、どうしても、zに相当するものが必要になってくるので、
まずは、カメラとCanvasの位置関係から、ドラッグしたオブジェクトをどう見えるようにしたいかを考えて、
カメラからどれくらい離した座標で扱うかを決めてやれば良いわけです。

僕の場合は、下図の様に2枚のCanvasがカメラから2.5と3.5の距離にあり、それより手前にドラッグしたオブジェクトを見せたいと
思っているので、距離を2にしました。

次に、オブジェクトをカメラから2の距離で、そのままの大きさで表示させると、カメラに近づきすぎて画面がオブジェクトに占有されてしまうので、ドラッグ中はスケールを縮小させることにしました。

オブジェクトの縮小率を一定にするためには、現在のオブジェクトの高さを調べる必要がありますが、
これも以前に、Rect Transformを使う方法の記事を見た覚えがあったので、今回も採用することにして、オブジェクトのTransformをRectTransformに変更しました。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;

public class DragObj : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    Transform ObjStartTransform;//ドラッグ開始時のTransform格納用。
    Vector3 ObjStartPos;//Positionを格納用。
    Vector3 ObjStartRot;//回転を格納用。
    Vector3 ObjStartScale;//スケールを格納用。

    CanvasGroup canvasGroup;
    public bool IsDropSuccess=false;

    [SerializeField]RectTransform rectTransform;//Rect Transform格納用。
    Vector3 screenPosition;

    private void Awake()
    {
        canvasGroup = GetComponent<CanvasGroup>();
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        ObjStartTransform = transform;
        ObjStartPos = transform.position;
        ObjStartRot = transform.rotation.eulerAngles;
        ObjStartScale = transform.localScale;
        canvasGroup.blocksRaycasts = false;
        IsDropSuccess=false;

        rectTransform=eventData.pointerDrag.GetComponent<RectTransform>();
        float currentHighest = rectTransform.sizeDelta.y; //オブジェクトの高さを取得。
        float scaleFactor=10/currentHighest;//オブジェクトの高さを10にするスケールを算出。
                
        transform.localScale=new Vector3(scaleFactor,scaleFactor,scaleFactor); //算出したスケールを適用する。
    }

    public void OnDrag(PointerEventData eventData)
    {
        screenPosition=Input.mousePosition; //マウスポインタのスクリーン座標を取得

        //スクリーン座標をワールド座標に変換
        var scrPos=new Vector3(screenPosition.x,screenPosition.y,2f);
        var worldPos=Camera.main.ScreenToWorldPoint(scrPos);

        //ワールド座標に変換した座標をオブジェクトの座標に代入
        transform.position = worldPos;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        if(screenPosition.x>35 && screenPosition.x<185    
         && screenPosition.y>35 && screenPosition.y<185)//画面右下のカバンのパネルの範囲
        {
            eventData.pointerDrag.transform.GetComponent<PickUpObj>().ClickObj();
            canvasGroup.blocksRaycasts = true;
            return;
        }

        if(IsDropSuccess)
        {
            transform.localScale = ObjStartScale;
            transform.rotation = Quaternion.Euler(ObjStartRot);
        }
        else
        {
            transform.position = ObjStartPos;
            transform.rotation = Quaternion.Euler(ObjStartRot);
            transform.localScale = ObjStartScale;
        }
        canvasGroup.blocksRaycasts = true;
    }
}

元々のしまづさんのyoutubeシリーズでは、オブジェクトをクリックする事で、アイテムとして取得する方法になっていますが、
改造版ではItem Boxパネルのカバンにドラッグする事で、アイテムとして取得するようにしています。

このために、ドラッグしたオブジェクトをItem BoxパネルのCanvasより手前に表示したかったわけです。

www.youtube.com

次は、所定位置にアイテムを設置する機能と、Item Boxからアイテムをドラッグして取り出せるようにしてみます。

Quaternionの意味を知らなくてもいいんだけど・・・

今日も、しまづさんのyoutubeシリーズで3D脱出ゲームの勉強です。

www.youtube.com

今日は、
# 08ズームパネルの作成
# 09ズームオブジェクトの作成
# 10ズームオブジェクトの回転
をやりました。

# 07アイテムの使用の時点で、ItemBoxの選択の初期化が抜けているように思えていたので
ちょっとドキドキしていたのですが、# 08ズームパネルの作成でしっかり対策されていました🤣
そりゃそうよね〜😆


それはともかく、今回の動画での一番の学びは、やっぱり「# 10ズームオブジェクトの回転」での、
Objectの回転軸を指定する方法と、その回転軸に補正をかける方法になると思う。

Quaternion.Euler(parent.rotation.eulerAngles)*Vector3.up

このQuaternionってのは、今までもなんとなく出てきて、何となく使っていたのだけど、
ちょっと興味をそそられたので、調べてみることにしました。

まず、Transform.rotation。ここのVector3はいわゆるオイラー角という表現になるらしい。
オイラー角は、XYZの角度をそのまま示しているので、とても判りやすいのだけど、どうやら、
このオイラー角同士の加算・減算にはあまり向かないらしい。
(何でなのかは理解できませんでした😭)

そこで、オイラー角度ではなく、xyzの他に回転軸を表す第四の数値wを追加して四元数なる表現で、
回転量を計算する方がいいらしい。

らしい。ばかりなのは、ちょっと、いや、だいぶ理解が追いついていないから🤣
ちょっと齧る程度では理解できませんでした😣

そんな中途半端な理解で、今回の補正について考えてみる。

まず、Unityのリファレンスマニュアル
docs.unity.cn

のQuaternion.Eulerの説明に、

public static Quaternion Euler (float x, float y, float z);

説明
Z 座標を軸に z 度、X 座標を軸に x 度、Y 座標を軸に y 度 (順番はこのとおり) 回転する回転を返します。

とあるので、
そこにparent.rotation.eulerAnglesを入れるという事は、親の角度になるための回転を求めて、
それをVector3.up(今回の回転軸)に掛けることで、回転軸自体を回転させているから、補正になる。
ってことになるのだろうか🧐

つくづく、もっと数学勉強しとくんだったなぁ・・・💦