Kakoのいろいろやったこと記

主にUnity関連でやったことをかいていきます

最強ゲームジャムに参加しました雑記

はい!

www.e-topia-kagawa.jp

どんなイベントだった?

最強のゲームジャムでした。

イベントについて

なんと、Unityから、個人的に尊敬する人物でもある簗瀨様がいらっしゃって、講評や講演、質疑応答をしてくださったり、
会場は何チームもできるほどの人数、
会場にいらっしゃるエンジニアには香川のゲーム会社様の面々がゴリゴリ参加!さらに、
無料のイベントなのに「聖剣伝説」やまるいロボット(スマホで動かせるやつ。Appleからも買えたはず。結構好き。)のプレゼントがあったり!!!!
すごい。
とにかくすごかった。
ちなみに、しれっとPS4版を頂くことになりました。受け取ったら無限にやります。

でもごめんなさい

私、オンライン参加にて、会場の空気はあまりわかりませんでした。
いや、せわしなさすぎて多分、会場でも離人感あったかもです。
会場の皆さん、オンライン側からみても、アイディアもすごく、実際の実装や発表も楽しそうで、至るところで最強を感じました。

今回やったこと

unityroom.com

これを7人で作りました!オンライン、7時間くらい!!!!!ものすごく楽しかったです。

役割

自分は誰に何をふるか、や、どう情報のやり取りをするか、話し合いの際のファシリテーターなどをしました。
人力クラス設計。
あとは各人のサポートや修正箇所特定、ちょっとしたアドバイスとかをしました。
最後にはビルド→修正→ビルド→∞をしてました。

正直
「7人全員エンジニアで
高専生からフリーランスの高低差ありの実力、
2日7時間くらいでやるゲームジャム」
を回したことは人生でないので、それだけでも気持ち的にせわしなかったです。
本当に各人の有能さに救われました。

内容

今回は、テーマが「スタート・ゴール」ということでした。なので、当初は「完成していない迷路の壁を壊し、スタート→ゴールをつなげよう」というアイディアが採用になりました。

f:id:Kakovail:20200906221835p:plain
こんな感じで話をしてました
実際どうだったの?

めっっっっっっっっっっっっちゃ大変でした。
シンプルに時間です。だって7時間くらいしかない2日間よ?
時間以外には「各人のレベルが違う」こと。
C#はある程度使えるがUnityはあんまり使ってなかったり、なぜかエディタがバグってインテリセンスがおかしかったり、1をいえば10を察したり、正直めっちゃ難しいはずの穴掘り法での迷路自動生成を達成したり、
あらゆる意味で差がありました。みんなにも伝えていましたが、
誰に何をふるか、どうふるか、どのくらいで終わると読んで次をいつ準備するか、得意なことがわからないのにこれふっていいのだろうか、
など、色々悩ましく、超大変でした。
結果、各人の能力が凄まじくあり、救われました。
最終、実装をゲームにまとめるのに手間取ってしまいましたが、実質機能単位だと時間内に完成していたレベル。最強。

次に、スパゲティになったブランチ。
Mergeタイミングをミスりまくり、めっっっっっっっちゃくちゃに絡まってました。マジで自業自得。

次に、オンラインゆえのやり取りラグ、は、よく上がると思いますが、ぶっちゃけ「皆無」でした。
やったねかこくん!今後一生リモートジョブだけで生きていけるよ!
対面なら楽なのに!もそんなに発生しなかったのが、個人的に良かったです。いってもオフにはかなわないでしょコミュニケーションは!って考えがゼロではなかったんですが、消え失せました。
オンライン、オフラインはそもそも根本的に性質が違うので、比べるのがナンセンスなのかも。我々はオンラインに対応できる新人類である。

時間内に完成したの?

してないよ!!!!!!
非常に悔しいことに間に合わず、皆さんの発表をもちろんきちんと聞きながら、細かい修正、ビルド、アップロード、修正、ビルド……を回していました。

でも、あとから考えても後悔はなく、ちゃんとそのときのベストは尽くしていたな、と考えています。最強。

楽しかった?

超楽しかった。

以上。
最強。

ローラーマウスを取り上げただけの記事

マウス使ってる?

私はこれまで、そんなにたくさんではないマウスを試してきた。

トラックボールマウス数種類、普通のマウス、普通のゲーミングマウス、トラックパッドなど。Macではマウスは使わず、トラックパッド一筋で生きてきた。

これは、今使っているマウスの全てである。

f:id:Kakovail:20200824230032j:image

なんか変なのない?

f:id:Kakovail:20200824230249j:image

ローラーマウスというのがある。別にアフィリエイトじゃない。

真ん中のバームクーヘンをくるくる回して使う。

f:id:Kakovail:20200824230337j:image

ボタンや大きさはこんな感じ。最初からコピペやダブルクリックがあって便利。

バームクーヘンを押し込むことでもクリックできる。素敵。(動画はいずれ貼る)

 

みんな、使おうな!(3万)

Gitでこれだけあれば大丈夫なやーつの記事

この記事は何か

Gitを初めて使う際の、コマンド(今回はGUIなので言葉自体の事)について、使い方がわからないと聞いたので「怖くないよプルプル▽」というために書いた記事。

言葉の説明のみ。

目次

実際の言葉

怖くない言葉
  • チェックアウト

  もうすでにコピーを持っているときに、他の人(過去の自分含む)がした作業をコピーしてきて使えるようにしたりする

  • クローン

  初めて使うときなどに、完コピを持ってくるときに使う

  • 新規ブランチを作る

  ブランチ、というものが「作業の分岐ルート」で、新しく分岐させたいときにする行動。機能ごとだったり、作業の塊ごとにする

  • コミット

  こんな変更をしたよ、と説明文と一緒に記録することができる。1ヶ月分の月報だと読みづらいけど、日毎の日報だと少し追いやすいよね、1時間ごとのログは過度だよね、って感じで、そこそこ細かく、でもある程度まとめてやると、後で便利。後々、他の人も見られることがあるので、ポエムを書いたり下ネタを書くととても楽しいことが起こるので注意

  • スタッシュ

  チェックアウトしたかったりするとき、でも一旦今の内容を保存しておきたい……なんてときに使う。チェックアウトするっていっても、今の作業どうするの……?ってかわいいPCくんは聞いてくれる。今の作業内容を一旦避難させることができる

  • スクワッシュ

  コミットが細かくなりすぎたり、ちょっとした変更が生じてコミットが増えちゃったとき、押しつぶして一つにまとめることができる

気をつけたい言葉
  • プッシュ

  これで全てが決まると言っても過言。プッシュをすると、Localじゃなくて他の人が見られる状態になる。変なコミットを消すなら今のうち(これの前)

  • リベース

  今作業している内容と、他の人の作業の内容を揃える行為。Pushしなければ他の人に影響はないといえば、ない。全体の作業の本流が、今の作業の根っこより先にいるときに、揃える行為。たまにコンフリクトというのが起きる。このとき、Fixするのにコミットが細かすぎると怠かったりする

  • マージ

何かしらのコミット(ブランチ)を、他の何かの上塗りに使う行為。完成した、チェックが終わった更新をマージしていくことで、本流が最新になっていく。ある程度組織になってる時は、内容を把握してる人がやる行為

 

その他

語弊を恐れず簡単のための書き方をしています。ggる必要があるかなないかな、くらいの判断にどうぞ。不明点はおきがっるに!

香川ゆるもく会にいってきましたFrom岡山

香川ゆるもくって?

sanuki-gamen.jimdofree.com
こちらです

本編感想

きつすぎずゆるすぎず最高でした。
なお、公式Twitterめちゃくちゃ熱がこもったやり取りをしているときの自分たちが映っています( ゚д゚ )


まずゆるめなもくもく会をするのですが、ある程度、もちろん空気を読みながら相談をしたり、技術的な共有も行われていて、良い場だなぁと思いました。
初学者向けの場をセッティングする場合、コーディングメインか、Component理解メインか、の相談を受けたりしました。教えていた経験をめっさ活かして答えられた、と思いたいです。

とあるチームが、ゲームの案を出したりしていて、とても楽しそうだなぁと見ていました。

最後に、成果発表をしたりするのですが、自分は実は今、

こちらの制作をお手伝いしており、これの成果を発表したりしました。作ってて楽しい最高プロダクト!
リリースの際は、是非!

他の方々は、いろんなアイディアが固まった方々だったり、Go言語だったり、様々でした。

カレー

なんと、主催の方がカレーを振る舞いつつの懇親会、みたいなものがありました!!!
めちゃくちゃうまかったですが、本来より数時間遅れての開催となりました。

新プロジェクトの相談

有り体にかくと「ビジネス」に近い話でしたが、ビビり散らかす程度には本格的なお話をしました。在職中もこんなに熱込めた話、少なかったです。
6時間くらい話していたと思います。やばい。
途中で、流石に腹が減った的に、カレーを食べました。めちゃくちゃ美味しかった。

逃げていった終電

そう、お察しの方はいるかもしれないですが、イベント終わってから6時間も話をしていたら、体力枯れるし、なんなら終電?そんなのもうないよってなりました。
ご厚意で、開催会場たるゲストハウスに泊めていただきました……!!!!これまたビビり散らかすほどおしゃれで最高空間でした。はやく喧伝しまくりたい(度をわきまえて)。

アフタートーク

参加者の方と、アフタートークをして、気づけば2時とかでした。楽しすぎる。
Assetの話だったり、技術的な話もしつつ、交流しまくった感じでした。もはやマブ

総括

地方イベントとか関係なく、やる気は存在するし、最高は存在する。再確認できる最高のイベントでした。
積極的にイベントも開催されたりしているので、近いし、香川、日程合う限りマストで行こうと思いました。




ん?家から?2時間くらいですね。

Unityのプロジェクト内で、Componentや自作クラスの参照を見つけるScriptを書きました。

動かなかった場合

導入後,一度プロジェクトを開き直すか,RefreshやReimportをすれば動くようになります.
また、非アクティブなシーンから取得できないこと、
必ず検索をからにしてからシーン遷移をすることが、現状必要です

何がしたいか

コンポーネントの参照をプロジェクト全体から見つけて、問題になっている箇所を見つけたかったので、参照を検索、というのがしたかった。具体的にはHorizontalLayoutGroupが見つけたかった。
ついでに自作クラスも見つけたかった.
SceneにおいてないPrefabも全部探したかった.

結論 : ある

wiki.unity3d.com

でも作った

最近こういうの好きなので参考にしながら作りました。
github.com



Script

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;

public class ReferenceFinder : EditorWindow
{
    private string _targetComponentName;
    private List<GameObject> _foundAssets = new List<GameObject>();
    private Vector2 _currentScrollPosition;
    private static IEnumerable<Type> _cashedComponents;
    private static Dictionary<string, List<Type>> _typeDict;

    static MonoScript[] _monoScripts;

    private static bool _isFirstTime = true;

    //開かれ方と、開かれたときの挙動
    [MenuItem("ReferenceFinder/Search")]
    private static void Open()
    {
        //開かれる際には一応取得
        _cashedComponents = GetAllTypes();
        GetWindow<ReferenceFinder>("Components Reference Finder.");
    }

    private void OnGUI()
    {
        
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.LabelField("Never change Scene before reset this window");
        EditorGUILayout.EndHorizontal();
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.LabelField("This window can search active scene objects and all prefabs");
        EditorGUILayout.EndHorizontal();
        //Editorに枠を出して、入力を_targetComponentNameに格納する
        EditorGUILayout.BeginHorizontal();
        _targetComponentName =
            EditorGUILayout.TextField("Target Component Name: ", _targetComponentName);
        EditorGUILayout.EndHorizontal();

        if (_foundAssets.Count > 0)
        {
            _currentScrollPosition = EditorGUILayout.BeginScrollView(_currentScrollPosition);
            foreach (var asset in _foundAssets)
            {
                EditorGUILayout.ObjectField(asset.name, asset, typeof(GameObject), false);
            }

            EditorGUILayout.EndScrollView();
        }

        if (GUILayout.Button("Search"))
        {
            _foundAssets.Clear();

            var guids = AssetDatabase.FindAssets("t:GameObject", null);

            foreach (var guid in guids)
            {
                string path = AssetDatabase.GUIDToAssetPath(guid);
                var loadAsset = AssetDatabase.LoadAssetAtPath<GameObject>(path);

                var typeCash = GetType(_targetComponentName) ?? null;

                if (typeCash == null)
                {
                    Debug.Log("No Type Found.");
                    return;
                }

                var tmp = loadAsset.GetComponentsInChildren(GetType(_targetComponentName) ?? null);

                foreach (var kari in tmp)
                {
                    _foundAssets.Add(kari.gameObject);
                }
            }

            var allObj = GetObjectsInAllScene();
            foreach (var obj in allObj)
            {
                var typeCash = GetType(_targetComponentName) ?? null;
                var tmp = obj.GetComponentsInChildren(GetType(_targetComponentName) ?? null);

                if (typeCash == null)
                {
                    Debug.Log("No Type Found.");
                    return;
                }

                foreach (var kari in tmp)
                {
                    _foundAssets.Add(kari.gameObject);
                }
            }
        }
    }

    /// <summary>
    /// プロジェクト内に存在する全スクリプトファイル
    /// </summary>
    static MonoScript[] MonoScripts
    {
        get { return _monoScripts ?? (_monoScripts = Resources.FindObjectsOfTypeAll<MonoScript>().ToArray()); }
    }

    /// <summary>
    /// クラス名からタイプを取得する
    /// </summary>
    private static Type GetType(string className)
    {
        if (_typeDict == null)
        {
            // Dictionary作成
            _typeDict = new Dictionary<string, List<Type>>();
            foreach (var type in _cashedComponents)
            {
                if (!_typeDict.ContainsKey(type.Name))
                {
                    _typeDict.Add(type.Name, new List<Type>());
                }

                _typeDict[type.Name].Add(type);
            }
        }

        //クラスが存在する場合、リストに表示
        if (_typeDict.ContainsKey(className))
        {
            return _typeDict[className][0];
        }
        else
        {
            //クラスが存在しない場合、念の為取得、再走
            if (_isFirstTime)
            {
                Debug.Log("Not found. ReScanning...");
                _cashedComponents = GetAllTypes();
                _isFirstTime = false;
                GetType(className);
            }

            _isFirstTime = true;

            return null;
        }
    }

    /// <summary>
    /// 全てのクラスタイプを取得
    /// </summary>
    private static IEnumerable<Type> GetAllTypes()
    {
        //Unity標準のクラスタイプを取得する
        var types = AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(asm => asm.GetTypes())
            .Where(type => type != null && !string.IsNullOrEmpty(type.Namespace))
            .Where(type => type.Namespace.Contains("UnityEngine"));

        //自作クラスも取得できるように
        var localTypes = MonoScripts
            .Where(script => script != null)
            .Select(script => script.GetClass())
            .Where(classType => classType != null)
            .Where(classType => classType.Module.Name == "Assembly-CSharp.dll");

        return types.Concat(localTypes).Distinct();
    }

    private static GameObject[] GetObjectsInAllScene(bool includeInactive = true)
    {
        var sceneNumber = SceneManager.sceneCount;

        // 空の IEnumerable<T>
        IEnumerable<GameObject> resultObjects = (GameObject[]) Enumerable.Empty<GameObject>();

        for (int i = 0; i < sceneNumber; i++)
        {
            var obj = SceneManager.GetSceneAt(i).GetRootGameObjects();
            resultObjects = resultObjects.Concat(obj);
        }

        return resultObjects.ToArray();
    }
}

エディタ拡張(?)楽しい

気軽にできてハック感あるのですごく達成感あります。よだれ鶏くらいの簡単さと充実感。

Unityのアンチパターンの記:Script編:第一部

対象

ゲームをなんとか自力で作れそうな段階。

なぜ書こうと思ったか

今まで本やチュートリアルをなぞってこなかったので、考えや知識の間違いを確認したいことと、初学者の身としてはアンチパターンを知っていきたい、という気持ちがあるので、どっかのなんかの誰かのためになるかな、と思ったことによる。

内容

見たことのあるちょっと良くなさそうなもので初期に改善できそうなものを突っ込んで、実際に改造してみる。
自作自演まさかり。

前提

  • 動作に関わるもの
  • 書きやすさ、読みやすさを主とした好みに関わるもの

を書きます。命名規則「自体」はスルーします。人によるので!

また、間違いがあったらすぐに直しますので、ご指摘いただけると幸いです。

PlayerAntiScript

数字が、良くない番号です。

using UnityEngine;

public class PlayerAntiScript : MonoBehaviour
{
    //1
    private const int Tsuyosa = 5;

    //2
    public Rigidbody Rigidbody => GetComponent<Rigidbody>();

    //3
    private int _hp = default;


    //4
    private GameObject _enemy;

    //5
    private void Start()
    {
    }


    private void Update()
    {
        //4,6
        _enemy = FindObjectOfType<EnemyScript>().gameObject;

        //7
        if (Input.GetMouseButton(0))
        {
            //2,8,9
            Rigidbody.AddForce(transform.forward * 30);
        }
    }

    private void OnCollisionEnter(Collision other)
    {
        //10
        if (other.gameObject.TryGetComponent<EnemyScript>(out var result))
        {
            //11
            result.GetComponent<EnemyScript>().Hp--;
            if (result.GetComponent<EnemyScript>().Hp < 1)
            {
                Destroy(result);
            }
        }
    }

    //1,3,4,12
    private int MaxEnemyNum = 30;
}

直してみたPlayer

using UnityEngine;

public class Player : MonoBehaviour
{
    //使ってないけど修正参考で上に出しておいた感じです
    static readonly int MaxEnemyNum = 30;

    private const int PlayerPower = 1;
    static readonly float Acceleration = 30f;

    private Rigidbody _rigidbody;

    private void Start()
    {
        _rigidbody = GetComponent<Rigidbody>();
    }

    private void FixedUpdate()
    {
        if (!Input.GetMouseButton(0)) return;

        _rigidbody.AddForce(transform.forward * Acceleration);
    }

    private void OnCollisionEnter(Collision other)
    {
        if (!other.gameObject.TryGetComponent<EnemyScript>(out var result)) return;

        //ここでは意味が無いけれどboolを返したら便利そう。名前が長いのは実はやばいけど全容が無いので第一部ではご容赦
        var isAlive = result.AddDamageAndCheckIsAlive(PlayerPower);
    }
}

説明

動作に関わるもの

1 : 後々変わりそうな数字でconstは警戒

constについて、詳しくは調べて頂いたらいいかな、と思いますが、簡単に書くと「進め方によっては数字を変更した際にバグる」です。
constは、事前に組み込まれるのでとても軽い部類なのですが、事前に組み込まれるがゆえに、以前組み込んだ数字のまま動くことがある、という現象があります。
なので「将来的に、まだ調整しそうな定数にしたい数値」はstatic readonly などを使ってあげると、気にしなくて良くなります。
最終的に、完全に確定した際には心置きなくconstを使う、のがいいと思います。

2 : 実質毎フレーム取得と不要なpublic

この宣言の仕方はとても便利なのですが、呼ばれ得る度に取得しているので、Updateで使うと実質毎回参照を取ります。大変です。

また、publicは割と怖くて、他のクラス、オブジェクトからも操作できてしまう、という問題があります。
「それの何が悪いの?気にしときゃいいじゃん」って思うかもしれませんが、例えば3つのオブジェクトが参照、変更をかけている時、同時に変更がかかったりしたらどうなるでしょう?やばいです。
実際、既存の有名Asset等でも、これで競合しちゃってUnityエディタがフリーズする、というのが発生することがあります(特にRefresh系)。
少なくとも手元では、その状況をそもそも発生させないためにも、不要なpublicを消すのが好ましいかな、と。
なので、不要な場合には、無しか、privateにしてあげましょう。

4 : 取得する形と違う必要性をなくせそうだし、これ使ってない……?

6で取得していますが、取得→gameObjectに変換、としていますが、だったらEnemyScriptの型で宣言しちゃってもいいのかな、と。
そもそも使ってないし(説明のために書いたんですけどね)。
仮にgameObject経由で取ってきたい情報があるなら、EnemyScriptの中で取得、こっちに公開して流したり。
もうちょっと進むとInterface切り分けとかも出てきそうな感じありますね。

6 : 毎フレームFindは重い!

この言葉のみです。重い!キャッシュする、とか言いますが、一度取得、どこかにしまっておく、いわゆるキャッシュをすることで、不要な参照を避けられ、軽くなります。これは変更後のRigidbodyの部分でやっています。

8 : AddForceってなんだろう?Updateで使わないように

AddForceといえば「FixedUpdateで使おう」とかよく言われます。なんでなんでしょうか?
これは、UnityのPhisics系が、基本FixedUpdateの時間の流れ方(フレーム間の幅)を単位として動いてくれるので、なにも気にしなくても時間という単位に関して解決してくれるためです。
docs.unity3d.com

Updateで使うなら明示的に時間を管理する必要があったりなかったりするので、そもそもFixedUpdateを使う、とすると幸せになれます。
詳しくやると運動方程式とかをちょっとだけやることになります。

10 : これだと消せないかも?

resultに対して取得したものが入っているんですが、今回はEnemyScriptが入っています。これだと、GameObject自体は消せないので、下のほうにあるDestroyがうまくきかないんじゃないかな?ってなりますね。
もちろん、将棋みたいに、やられたら敵という属性がなくなる、とかなら使いみちはあると思います。

11 : 他人が他人の体力をいじれるの、ちょっと怖い

2とも関連します。公開されている数値は、公開の仕方によっては、他人が直接いじることができます。
あくまでも相手にダメージを通知して、相手自身がダメージを処理結果だけ返したりする、というのが平和ではないでしょうか。
また、ここではPlayerだけがいじっていますが、他の要因、ダメージ床もある、フレンドリーファイアもある、などとなった場合にどんどん変な事になっていきます。
最初のうちは、なるべくなら動作する本人が処理する、のが平和に思えます。

個人的な好み

3 : 名前が変?

命名規則の種類の話ではなく、Script内での名前の規則性がなくて混乱する、というやつです。結構見ます。
Camelがどうとかいうのではなく、最低限内部では揃っていたほうがやりやすいのかな?と思います。

5 : 使わないなら消したい

これだけです。テンプレートを変えない場合、基本出てくるので、残っているのをよく見かけますが、使わないなら消したほうがいいのかなぁ、と思います。
ただ、今回は改造後で使っています。

7 : 早期リターンなんてものもあったり

これは好みですが、条件が1つしかなかったりする場合は、早期リターン、ガード節というのがあります。これもうまくいかない要因を排除できたり、ほんの僅かに軽くなったりがあるっぽいので好きでおすすめです。もちろん場合によります。
そして、今回は段階的に無視しましたが、FixedUpdateでキー入力をみるの、なんか危なかったり、なかったり。

9 : この30はまほうのすうじ

この数字、一体なんの数字で何に使っているんだろう?というのがわかりません。
こういった、なにかよくわからないけど動く魔法のような数字マジックナンバーと呼びます。どこかで宣言、名前を持ってくることで混乱が防げますね。

12 : 貴様……なぜここに……?

これは、シンプルに書く場所に意図がなさそうなのもよく見かけるもので……!
あとはですね、Playerがなぜ敵の数を管理する必要があるのか、というのもちょっと突っ込めるかもです。やること多すぎて大変なので、敵のボス的なScriptで管理するのも良いんじゃないでしょうか?


終わりに

セルフまさかり、心が痛いですね。ちょっと疲れたときに息抜きとして続けたいと思います。

PhotonでPrivateChatの小さいことに躓いて時間が溶けたから、それらを他の人はできるようになる備忘

この記事について

お仕事でいじっていて、プライベートチャットで躓いたこと2点と対処法を書きました。わからないことがあれば聞いていただけると追記します。

躓いたことと解決法

TargetId

doc.photonengine.com
これや、Referenceを参考にしたのですが、TargetUserIdってのが意味わかんなかったです。
自分の環境では、
{UserID}:{UserName}
がくっついたものが、相手のPrivateChatのIdでした……。そういう記述見つけられなかった……。
適当にPriavteChat送ったら、自分も受信するので、OnPrivateMessageでSenderをDebug.Logしてあげて形式を整えましょう

ChatClientの勘違い

ChatClientを自前実装しようとしてたんですが、
ChatClient.{呼びたいもの}でいけるんですね……

疑問点あったら何でも投げてください

めちゃくちゃつまづきまくったので、一通り心当たりがある気がします。気軽に投げてみてください(わからないこともたくさんあるので)。