🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

LINQ

LINQとはLanguage INtegrated Queryの略で、「統合言語クエリ」という意味です。C#やVisual Basicといった言語のコード内に記述することができるクエリです。

Q&A

解決済

2回答

5776閲覧

【Unity,C#】SortedListのKeyの値が同一の物がある場合の上書きを防止する方法について

Y0241-N

総合スコア1066

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

LINQ

LINQとはLanguage INtegrated Queryの略で、「統合言語クエリ」という意味です。C#やVisual Basicといった言語のコード内に記述することができるクエリです。

0グッド

0クリップ

投稿2019/11/19 06:15

前提・実現したいこと

投稿記事:【C#,Unity】Dictionary<GameObject,float>をfloat基準で昇順にSortしようとしたら謎処理を挟んでいた話

もともとQiitaの方でDictionaryを使ってSortをする際に苦戦したという話を投稿した際に、
コメントで読み取りの頻度が高いのであればSortedListの方が向いていますよ。というアドバイスをいただき、

変更したのですが、後日、SortedListは同じKeyの値が登録された場合、追加ではなく上書きされるので防止したほうがいいですよ、
(Unityで試しに同じ値を追加しようとしたらArgumentエラーが発生しました)
とアドバイスと頂き、コードを提示までした頂いたのですが、まるで理解が及ばず困惑しています。

実際に現在使用しているスクリプトが以下のスクリプトになります。

cs

1using System.Collections.Generic; 2using UnityEngine; 3using System.Linq; 4 5public class LockOnTargetDetector : MonoBehaviour 6{ 7 public GameObject player; 8 public List<GameObject> hitsOb {get; set;} 9 private SortedList<float,GameObject> SortList; 10 11 void Start() 12 { 13 if(player == null) 14 { 15 player = GameObject.FindGameObjectWithTag("Player"); 16 } 17 SortList = new SortedList<float,GameObject>(); 18 hitsOb = new List<GameObject>(); 19 } 20 21 public void GetTargetClosestScreenCenter() 22 { 23 float search_radius = 10f; 24 25 var hits = Physics.SphereCastAll( //playerを中心に半径10fの半球内にあるTargetを取得 26 player.transform.position, 27 search_radius, 28 player.transform.forward, 29 0.01f, 30 LayerMask.GetMask("Target") 31 ).Select(h => h.transform.gameObject).ToList(); 32 33 hits = FilterTargetObject(hits); 34 35 if (0 < hits.Count()) { 36 37 foreach (var hit in hits) { 38 39 Vector3 targetScreenPoint = hit.transform.position; //各Targetの位置を取得 40 float target_distance = Vector2.Distance( //playerの位置から各Targetまでの距離を取得 41 player.transform.position, targetScreenPoint ); 42 43 SortList.Add(target_distance,hit); //各Targetと距離を格納 44 } 45 46 foreach(var item in SortList) //Sortしなくとも自動で昇順に並び替えて出力してくれる 47 { 48 hitsOb.Add(item.Value); 49 } 50 SortList = new SortedList<float,GameObject>(); 51 52 } else { 53 SortList = new SortedList<float,GameObject>(); //何もなかった場合リストを初期化 54 hitsOb = null; //何もなかったらNullを返す 55 } 56 } 57 protected List<GameObject> FilterTargetObject(List<GameObject> hits) //Physics.SphereCastAllでゲットしたTargetが画面内に入っているか判定 58 { 59 return hits 60 .Where(h => { 61 Vector3 screenPoint = Camera.main.WorldToViewportPoint(h.transform.position); 62 return screenPoint.x > 0 && screenPoint.x < 1 && screenPoint.y > 0 && screenPoint.y < 1; 63 }) 64 .ToList(); 65 } 66}

処理内容的には、画面内のTargetオブジェクトをプレイヤーに近い順に取得する。となっています。
ここに提示していただいたコードを追加しようとしたのですが、記事の方にも書いてあるように
各値に何を渡してあげればいいのか、どこに記述して処理を実行すればいいのか、まるでわかりません。

そこで教えていただきたいのは、提示していただいたコードの各値に上記のスクリプトのどの値を渡せばいいのか、
提示していただいたコード1と2の関係性はどうなっているのか?
そもそもこの中に組み込むことが出来ないのか?等の知識を教えてください。

理解が及ばないため、どうか皆様のお力をお借りできれば幸いです。

提示していただいたコード

1.cs

1class MultiSortedList : SortedList<float, List<GameObject>> 2{ 3 //Addメソッドのオーバーロード 4 public void Add(GameObject obj) { 5 float distance = obj.距離計算メソッド(); 6 if (this.TryGetValue(distance, out insertTarget)) { 7 //すでに同じ距離の登録済みオブジェクトあり 8 insertTarget.Add(obj); 9 } else { 10 //同じ距離のオブジェクトなし 11 this.Add(distance, new List<GameObject> { obj }; 12 } 13 } 14 15 //ソート済みリストを一次元リストに展開して取り出す 16 IEnumerable<GameObject> GetSotedObjects() { 17 return this.Values.SelectMany(elems => elems); 18 } 19}

cs

1//本当は「距離でソートしたリストを作成」と「リスト順に処理を実行」は別メソッドに分けた方がいいと思う 2public void Sort() { 3 var list = new MultiSortedList(); 4 //ソートしたいオブジェクトを全部登録 5 foreach(var obj in itemTable.Where(ここにフィルタ条件を記述)) { 6 float distance = objから距離を算出; 7 list.Add(dicstance, obj);//距離もパラメータとして渡すことにします。メソッドの実装はわざわざコードを書かなくてもわかるんじゃないかと思います。 8 } 9 10 //距離順に個別処理を実行 11 foreach(var obj in list.Values) {//個別処理には距離情報が不要な場合 12 //個別処理 13 } 14}

試したこと

分からないなりに無理やり突っ込んでみましたが、結局どの値をどこに渡せばいいかわからず実装できませんでした。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2019/11/19 06:57

なぜこの段階でこっちで聞くんでしょうか? もう少しやり取りして、向こうで解決できないことがはっきりしてからでも遅くないように思います。
Y0241-N

2019/11/19 07:10

一対一でのやりとりですと、情報の交換できる速度が遅く、またアドバイスをしていただけるのは ありがたいことなのですが、先方の表現では私には理解できなかったので、 他の方からの解説や助言が欲しかったので、こちらで質問させていただきました。
guest

回答2

0

ベストアンサー

色々と誤解をしているようです

DictonaryやSortedListというのは連想配列というもので、
数値以外も指定できる配列です。

data[0] = "Tokyo"; Console.WriteLine(data[0]); // Tokyoが出力される data["yamada"] = "Tarou"; Console.WriteLine(data["yamada"]); // Tarouが出力される

0やらyamadaやらがKeyの部分で、TokyoやTarouがValueとなります。

連想配列は、Keyが指定されると高速にValueを探し出すように作られています。
この辺が同じKeyがあると上書きされる、という理由になります。

ただ、「同じKeyで上書きされるのが困る」「順番に並べたい」となると、
DictonaryやSortedListを使わず、Listに入れたあとソートしたほうが処理的にも速いです。

[追記]
ただのペアとして管理したいのであれば、それ専用のクラスを作ってまとめるべきで、
使いもしない余計な機能がついているDictonaryを使うべきではありません

public class DistanceData{ public float distance; public GameObject gobj; } List<DistanceData> dlist = new List<DistanceData>(); // 面倒ならこれでもあり、First, Secondという変数になるけれども List<Pair<float, GameObject>> ddlist = new List<Pair<float, GameObject>>(); ddlist.Add(new Pair<float, GameObject>(1.0, gameObject)); Console.WriteLine($"distance : {ddlist[0].First} obj:{ddlist[0].Second.ToString()}");

投稿2019/11/19 07:05

編集2019/11/19 07:24
izmktr

総合スコア2856

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2019/11/19 07:11

計測していませんが、処理的には間違いなくそちらの方が速いでしょうね。Qiita で教えられているのは全要素にリストを突っ込むという非常に重そうな処理ですから。
Y0241-N

2019/11/19 07:14

説明ありがとうございます。 DictionaryやSortedListを使っている理由は「Target」と「Targetまでの距離」を紐づけて管理する為に使用しているので、それぞれを単体でListに入れて管理はできません。
Zuishin

2019/11/19 07:20

GameObject なので、distance プロパティを作るだけで紐付けできます。どうしても SortedList を使いたいならキーと値を逆にすればそもそも上書きされなくて済みます。
Y0241-N

2019/11/19 07:31

今までclassをそのように扱ったことがなかった為、勉強になりました。 ひとまず自分で試してみることにします。お手数をおかけしました。
Y0241-N

2019/11/19 07:49

何度もすみません、dlist.Addで要素を追加するのかと思い実行してみたのですが、 オーバーロードはありませんと返されてしまいます。 要素の追加の仕方が間違っているのでしょうか?
izmktr

2019/11/19 07:52

状況がわからないので、一度この質問はクローズして新しく立て直したほうがいいと思います
Zuishin

2019/11/19 07:55

新しい質問じゃないと思います。型を間違えているだけでしょう。DistanceData が必要です。
Y0241-N

2019/11/19 07:58

C# List Addで検索して出てきた情報を試したところ、 dlist.Add(new DistanceData() {distance = 代入する数値, gobj = 代入するオブジェクト}); としたところ、要素を追加できました。すみません。
izmktr

2019/11/19 08:05

>Zuishinさん 一応補足 質問を立てろってのは、今の状況を言語化して整頓しろ、って意味です
Y0241-N

2019/11/19 08:19

お二方ともにありがとうございました、教えていただいた方法で問題は解決することが出来ました。 また適切な表現ができていなかったようで、ご迷惑をお掛け致しました。 こちらの回答をベストアンサーとし、自己回答で変更したコードを追記しておきます。
guest

0

教えていただいた方法に変更したスクリプトです。
大変勉強になりました、以後も精進してまいります。

cs

1using System; 2using System.Collections.Generic; 3using UnityEngine; 4using System.Linq; 5 6public class LockOnTargetDetector : MonoBehaviour 7{ 8 public GameObject player; 9 public List<GameObject> hitsOb {get; set;} 10 public class DistanceDate //追加 11 { 12 public float distance; 13 public GameObject TargetOb; 14 } 15 List<DistanceDate> DisList; //追加 16 17 void Start() 18 { 19 if(player == null) 20 { 21 player = GameObject.FindGameObjectWithTag("Player"); 22 } 23 DisList = new List<DistanceDate>(); //追加 24 hitsOb = new List<GameObject>(); 25 } 26 void Update() 27 { 28 Debug.DrawRay(player.transform.position,player.transform.forward *10f,Color.yellow,float.PositiveInfinity); 29 if(Input.GetKeyDown(KeyCode.Q)) 30 { 31 GetTargetClosestScreenCenter(); 32 } 33 } 34 public void GetTargetClosestScreenCenter() 35 { 36 float search_radius = 10f; 37 38 var hits = Physics.SphereCastAll( //playerを中心に半径10fの半球内にあるvalveTargetを取得 39 player.transform.position, 40 search_radius, 41 player.transform.forward, 42 0.01f, 43 LayerMask.GetMask("valve") 44 ).Select(h => h.transform.gameObject).ToList(); 45 46 hits = FilterTargetObject(hits); 47 48 if (0 < hits.Count()) { 49 50 foreach (var hit in hits) { 51 52 Vector3 targetScreenPoint = hit.transform.position; //各Targetの位置を取得 53 float target_distance = Vector2.Distance( //playerの位置から各Targetまでの距離を取得 54 player.transform.position, targetScreenPoint ); 55 56 DisList.Add(new DistanceDate() { distance = target_distance, TargetOb = hit}); //変更 57 } 58 DisList.Sort((a,b) =>Math.Sign( a.distance - b.distance )); //ソート 59 60 foreach(var item in DisList) 61 { 62 Debug.Log("Distance : " + item.distance + " / Object : " + item.TargetOb); 63 hitsOb.Add(item.TargetOb); //並び替えたオブジェクトのみ別のListに登録 64 } 65 DisList = new List<DistanceDate>(); //取得用のListは初期化 66 67 } else { 68 DisList = new List<DistanceDate>(); 69 hitsOb = null; //何もなかったらNullを返す 70 } 71 } 72 protected List<GameObject> FilterTargetObject(List<GameObject> hits) //Physics.SphereCastAllでゲットしたTargetが画面内に入っているか判定 73 { 74 return hits 75 .Where(h => { 76 Vector3 screenPoint = Camera.main.WorldToViewportPoint(h.transform.position); 77 return screenPoint.x > 0 && screenPoint.x < 1 && screenPoint.y > 0 && screenPoint.y < 1; 78 }) 79 .ToList(); 80 } 81}

投稿2019/11/19 08:45

Y0241-N

総合スコア1066

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問