【C#】「C#は参照型でも値(プリミティブ)型でも値渡し」

はじめに

・「参照型渡すときにrefパラメータ修飾子つける意味あんの?」

・「参照型でも値(プリミティブ)型でも値渡し」→「??」

C#のパラメータ修飾子について学んでいるときに、
上記の疑問がわいたので調べた内容をまとめた記事です。



これ↓って参照渡しじゃないよ。

void Test()
{
    List<Hoge> a = new List<Hoge>();
    Val(a);
    Debug.Log("count = " + a.Count);
}

private void Val(List<Hoge> b)
{
    b.Add(new Hoge());
}

▼実行結果

count = 1

上記のソースで「countが増えてる=参照渡しだ」と判断していたのですが、
参照渡しではなく値渡しです。

参照型の値渡し

上記のソースでは、
・参照型変数aはListの参照先を保持した変数です。
・参照型変数aを値渡しします。
値渡しをするということは、参照型変数aの値=Listの参照 をコピーします
コピーされた変数が参照型変数bです。
Val()の中で、参照型変数bの値=Listの参照 を元にListにAddします。Listのカウント数は1になりました。
Test()の中で、参照型変数aの値=Listの参照 を元にListのカウント数をデバッグ出力します。

ということが行われています。

参照型変数abが持つ参照の参照先のListは同じなのですが、それぞれで参照をもっているということです。

以下のようなイメージです。

f:id:snoopopo:20190317095633p:plain

参照型の「参照渡し」

一方、refを用いて「参照渡し」した場合は、
同じ参照を参照型変数abも持つことになります。

以下のようなイメージです。

f:id:snoopopo:20190317100231p:plain

結果が異なるパターン

先程のソースでは実行結果が同じだったので、実行結果が異なる例を挙げます。

void Test()
{
    List<Hoge> a = new List<Hoge>();
    Ref(ref a);
    //Val(a); RefかValのどちらか呼ぶ
    Debug.Log("count = " + a.Count);
}

private void Val(List<Hoge> b)
{
    b = new List<Hoge>();
    b.Add(new Hoge());
}

private void Ref(ref List<Hoge> b)
{
    b = new List<Hoge>();
    b.Add(new Hoge());
}

Ref()(参照渡し)を呼んだときの実行結果

count = 1

Val()(値渡し)を呼んだときの実行結果

count = 0

このように結果が異なります。


先程、値渡しをした場合はコピーされ、
変数a,bそれぞれで参照をもっていると書きました。

‘Val()‘ の中で変数bが持っている参照の参照先が新しくnewされたListに変わります。
変数aの参照先は変わりません。
Test()では変数aの参照先をデバッグ出力しているので、カウント数は0のままになります。

以下のようなイメージです。

f:id:snoopopo:20190317101426j:plain


次に参照渡しの場合です。
参照渡しの場合は変数abがもっている参照は同一です。

‘Val()‘ の中で変数bが持っている参照の参照先が新しくnewされたListに変わります。
変数aと変数bが持っている参照は同一なので、変数aの参照先も新しくnewされたListに変わります。

以下のようなイメージです。

f:id:snoopopo:20190317102337j:plain

最後に

このように、参照型の値渡しと参照渡しとでは動きが異なります。

最初に疑問に思ったことの回答・・・

「参照型渡すときにrefパラメータ修飾子つける意味あんの?」

→ある。無駄なコピーはなくせるのでrefはつけた方がいい。

だけど無駄なコピーなくすってメリットぐらいしか思いつかん。。(※)
ほかにメリットある??

(※)今回例であげたような、
呼び出し先のメソッドでnew されるのが前提のソースの場合は、
呼び出し元でnewせずパラメータ修飾子もrefじゃなくてoutでもらうべきだと思うからです。

参考

参考にさせてもらいました、あざしたー★!

teratail.com

sorasorasora.hatenablog.com

UnityのDotweenよく使うものまとめ

ドキュメント

dotween.demigiant.com

namespace

using DG.Tweening;

移動

(RectTransform)(gameObject.transform).DOLocalMove(new Vector3(8, 0, 0), 0.5f); //8,0,0の位置に移動

DOMoveはワールド座標での移動。DOLocalMoveはローカル座標。

絶対座標ではなく相対座標で移動したい。

(a,b,c)に移動したい、という場合ではなく、今いる位置から(d,e,f)移動したいという場合は、.SetRelativeをつけてあげれば良い。

(RectTransform)(gameObject.transform).DOLocalMove(new Vector3(8, 0, 0), 0.5f).SetRelative(); //今いる場所から右へ8移動

シーケンス

Sequence seq = DOTween.Sequence();
seq.Play();

ループ数を指定する

Sequence seq = DOTween.Sequence();
seq.SetLoops(-1); //-1なら無限ループ
seq.Play();

Append

Sequence seq = DOTween.Sequence();
seq.Append(((RectTransform)scrollArrowDown.transform).DOLocalMoveY(10, 0.5f).SetRelative());
seq.Append(((RectTransform)scrollArrowDown.transform).DOLocalMoveY(-10, 0.5f).SetRelative())
seq.Play();

AppendInterval

シーケンス中にxx秒待つ

Sequence seq = DOTween.Sequence();
sequence.AppendInterval(0.3f);
seq.Play();

AppendCallback

CallBackを設定

Sequence seq = DOTween.Sequence();
sequence.AppendCallback (()=>{
    if(onFinish != null){
        onFinish();
    }
});
seq.Play();

Fade

Image.DOFade

CrossFadeAlpha は unityの仕組みでdotweenじゃないけどおなじようなことできる

DoKill

今行っているアニメを消す。 component は uGUIの親のComponent。

component.DOKill()

onComplete

↓の例だと、スケールで小さくしてからゲームオブジェクトをDestroyするみたいな感じ

transform.DOScale(0, this.effectTime).onComplete = () =>
{
    Destroy(gameObject);
};

xx秒後にする

SetDelay を使うことで、xx秒後にtweenの処理をさせることができる。 ↓の例は、0.5秒後に0.6秒かけてサイズを200x200にする。シーケンス作るまでもない動きならこっちの方がいい。

rectTransform.DOSizeDelta(new Vector2(200, 200), 0.6f, true).SetDelay(0.5f)

To 〇〇の値をxxにする

以下は、CanvasGroupのalpha の値を0.5f秒かけて、0にする例

CanvasGroup canvasGroup = effect.gameObject.AddComponent<CanvasGroup>();
DOTween.To(() => canvasGroup.alpha, (n) => canvasGroup.alpha = n, 0, 0.5f).onComplete = () =>
{
    Destroy(effect.gameObject);
};

xxの方向に向きを変える DOLookAt

Vector3 targetPos = targetObj.transform.position; //
transform.DOLookAt(targetPos, 0.25f);

f:id:snoopopo:20210803153142g:plain
▲白と黒のキャラが、赤いキャラの方向を向きます

別のブランチのファイルを取り込みたい

複数ブランチで開発していると、別ブランチのファイルを取り込みたいときがある

merge じゃなくて、そのまま上書きたい場合です

取り込み先(ファイルをもらう側)のブランチに切り替えた状態で、

git checkout <取り込み元ブランチ名> -- <取り込みたいファイル名>

取り込み元ファイルがあるブランチとファイル名を指定すると取り込める

リモートのブランチを取得する

リモートのブランチの一覧は-aオプションをつけることで表示される。

 git branch -a

これで表示されない場合は、リモートの情報を取得していないのでfetchをする

git fetch

意図したリモートブランチが表示されたら、
checkoutしてローカルに新しいブランチを作成する

 git checkout -b test_branch origin/test_branch

-bしておくと新しくできたブランチに切り替えまで行ってくれる

リモートの最新を開発ブランチにも取り込む

リモートの最新を開発ブランチにも取り込む

複数人で開発している場合、他の人が行ったコミットを自分の開発ブランチにも取り込みたい。

master ブランチを例に書きますが、どのブランチでも同じ。

1) ローカルのmasterブランチにリモートのoriginにコミットされた最新を取り込む
2) 開発ブランチにmasterブランチをマージする

git checkout master
git pull origin master
git checkout dev
git merge origin master

ローカルの master ブランチに移動(checkout)して、
pull(fetch->merge) でリモートのmasterブランチに最新情報を取得(fetch)し、ローカルのmasterにマージする

その後、ローカルの開発ブランチにに移動して、
masterブランチと開発ブランチをマージする

【Aseprite】Layerごとにファイル出力したい

f:id:snoopopo:20190213124515p:plain

▲開発中のゲームだよ^^


今回やりたいのは、Layerごとにファイル出力したいってことよ

今回は、Asepriteで「レイヤーを部品ごとに分けたんでレイヤーごとに出力したいんだけど!」っていう記事です。

例えば、メニュー画面などで、
メニュー画面自体を描画するための背景画像と、
そのうえにのる実際のメニュー画面の要素(ボタン等)は、別々の画像にするかと思います。

うえにのる実際の要素はアニメしたり、表示・非表示が切り替わったりいろいろ変わるので一つの画像では表現できないためです。

というわけで、今回はAsepriteのLayerを部品単位に分けて開発してます。

こんなかんじ↓

f:id:snoopopo:20190213124246p:plain

これを、一度にレイヤーごとに出力する方法を今回書きます。

guiからはひとつずつしか出力できないっぽい

File -> Export File

f:id:snoopopo:20190213130927p:plain

Layers から 対象のレイヤーだけ出力はできます。

が、今回はいっきに全部のレイヤーをバラバラのファイルに出力してほしいのです。

※バージョン v1.2.8

結論:コマンドならできる

というわけでコマンドからは行けます。

$ ./Aseprite.exe -b --split-layers [asepriteファイル名] --save-as [出力ファイル名]

例)

$ ./Aseprite.exe -b --split-layers ~/Desktop/menu.aseprite --save-as ~/Desktop/menu.png

https://www.aseprite.org/docs/cli/#export-all-layers-into-different-png/gif-files

▼実行するとこんな風にレイヤーごとに出力されたよ。

f:id:snoopopo:20190213131712p:plain

ファイル名は自動で決まりました ⇒ 追記:指定できるー!「ファイル名をレイヤー名にする」見て

追記①:不要な透明部分も除去できる

レイヤーごとにファイルを出力した場合、画像の大きさはすべてキャンバスのサイズになっていて、 不要な透明部分含めて出力されてしまう…

f:id:snoopopo:20190214121000p:plain

▲ こんな感じに、透明部分を含んだ大きい画像になってしまう。

そこで、--trim オプションを指定すると!不要な透明部分の除去もできた!素晴らしい!

$ ./Aseprite.exe -b --split-layers [asepriteファイル名] --trim --save-as [出力ファイル名]

例)

$ ./Aseprite.exe -b --split-layers ~/Desktop/menu.aseprite --trim --save-as ~/Desktop/menu.png

https://www.aseprite.org/docs/cli/#trim

f:id:snoopopo:20190214120742p:plain

▲ 透明部分を除去した画像が生成されました!

追記②:ファイル名をレイヤー名にする

それぞれのファイルをレイヤー名で指定することもできた!

上述しているとおり、 ファイル名を指定しない場合は [画像名] ([レイヤー名]).png というフォーマットで出力される。

これを、レイヤー名だけにしたい。って場合は、ファイル名の指定に {layer} とつけるとできました!

例)

$ ./Aseprite.exe -b --split-layers ~/Desktop/menu.aseprite --trim --save-as ~/Desktop/{layer}.png

https://www.aseprite.org/docs/cli/#filename-format

f:id:snoopopo:20190214122610p:plain