20200521のC#に関する記事は4件です。

OnBeginDrag内でSerializeFieldを取得できない

つい最近Unityでゲーム制作を始めたのですが、標記の通りで困っています。
Unityの画面からオブジェクトを設定した状態で
OnBeginDragのコード上でDebug.Log(オブジェクト名)をしてもnullと表示されてしまいます。
これはUnityの仕様でしょうか?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityで作成したC#スクリプトがVisual Studio 2019で正しく読み込まれない問題

結論から言います

まずUnityを開き、以下の手順で環境設定まで行きます。
 [Edit]->[Preferences...]

External Script EditorをVisual Studio 2019(Community)に変更します。
 [External Tools]->[External Script Editor]->Visual Studio 2019(Community)

これで認識させることができました。

参考ページ

SSchmidさん、感謝です。
https://stackoverflow.com/questions/26094908/unity-project-doesnt-have-solution-file

背景

気が向いたら書きます。。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】ScrollViewでボタン付きのリストを作成する

注意

備忘録で、記事に時間をかけたくないので
わりと適当に書いてます。

環境

Unity 2018.4.19 (64bit)
.Net 4.x

目的

・ボタンがついた表を作成する。
・削除ボタンでその行を削除する。
・選択ボタンでその行の値を取り出す。

構成

ScrollView.png

準備

  1. Viewport内のContentを自動的にサイズ調整してもらう
     ScrollView->Viewport->Contentに以下をAddComponentしましょう
    ・Vertical Layout Group
    ・Content Size Filter
    ScrollView2.png

  2. RowのPrefabを作成
    構成を下図に示します.
    ScrollView1.png

Script(ScrollViewRow.cs)

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

//Listおよび出力先を管理
public class ScrollViewManager
{
    public List<ScrollViewRow> list = new List<ScrollViewRow>();
    public Text _Output;

    public ScrollViewManager(Text Output)
    {
        _Output = Output;
    }

    public void SetOutput(string text)
    {
        _Output.text = text;
    }
}


public class ScrollViewRow : MonoBehaviour
{
    private int _value;         //Text内で表示される値
    private ScrollViewManager _manager;

    public static ScrollViewRow Create(GameObject ScrollView_Content, GameObject prefab)
    {

        //オブジェクトの生成
        GameObject obj = Instantiate(prefab, new Vector3(0, 0, 0), Quaternion.identity,ScrollView_Content.transform);
        //コンポーネントをAdd
       return obj.AddComponent<ScrollViewRow>();
    }

    public ScrollViewRow ValueSet(ScrollViewManager manager, int value)
    {
        _manager = manager;
        _value = value;
        return this;
    }

    void Start()
    {
        _manager.list.Add(this);

        //テキストの設定
        gameObject.transform.Find("Text").GetComponent<Text>().text = _value.ToString();

        //選択ボタンイベント登録
        gameObject.transform.Find("SelectButton").GetComponent<Button>().onClick.AddListener(SelectButtonOnClick);

        //削除ボタンイベント登録
        gameObject.transform.Find("DeleteButton").GetComponent<Button>().onClick.AddListener(DeleteButtonOnClick);

    }

    private void SelectButtonOnClick()
    {
        _manager.SetOutput(_value.ToString());
    }

    private void DeleteButtonOnClick()
    {
        _manager.list.Remove(this);
        Destroy(gameObject);
    }
}

Script(Sample.cs)

using UnityEngine;
using UnityEngine.UI;
public class SampleScript : MonoBehaviour
{
    [SerializeField] Text Output;
    [SerializeField] GameObject ScrollView_Content;
    [SerializeField] GameObject RowPrefab;
    void Start()
    {
        ScrollViewManager manager = new ScrollViewManager(Output);

        for (int i = 0; i < 100; i++)
        {
            ScrollViewRow.Create(ScrollView_Content, RowPrefab).ValueSet(manager, i);
        }
    }

}

結果

ScrollView3.png

あとがき

はじめて記事を書きましたが、時間が結構かかるので
つづけられる気がしません

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DataGridやListBox内でクリックされたら自身の行を上下に移動するButton

概要

DataGridやListBoxで複数のアイテムを表示しているときに、アイテム自身に上下へのボタンをつけたいことがあります。
image.png

? "JIRO"横の⬆をクリック

image.png

MVVMでやっている場合は、アイテムごとViewModelに上下移動Commandを用意して、、、となります。
なしの場合はコードビハインドで、上下移動ボタンが押されたItemを検索して、、、となります。
どちらにしても、手間ですし他のコードで使い回せません。

そこで、添付プロパティを使って、Buttonに自身が所属しているItemsControlから行上下移動させる機能を追加します。

Viewだけで完結しているので、ViewModel側には追加作業は必要ありません。
MVVMを使用していない場合も、コードビハインドから呼び出して使えます。
ItemsControlを継承しているコントロールなら使えるので、ItemsControl、DataGrid、ListBox、ListView、ComboBoxでも使えます。

方法

以前投稿した、DataGridやListBox内でクリックされたら自身の行を削除するButtonを改造して作ります。
削除ボタン機能の一部を共通化して、上下移動ボタンを作ります。

添付プロパティ

まず、指定されたオブジェクトが所属している操作するコレクションとオブジェクトのインデックスを検索します。
ここでのコレクションはDataGridのItemsSourceがViewModelのコレクションにBindingされているなら、それが取得されます。

/// <summary>
/// 指定されたオブジェクトを含む親コレクションとインデックスを返す
/// </summary>
private static (IList, int) GetParentListAndIndex(DependencyObject elementInItem)
{
    DependencyObject parent = elementInItem;
    var parentTree = new List<DependencyObject> { parent };

    //指定されたオブジェクトのVisualTree上の親を順番に探索し、ItemsControlを探す。
    //ただし、DataGridは中間にいるDataGridCellsPresenterは無視する
    while (parent != null && !(parent is ItemsControl) || parent is DataGridCellsPresenter)
    {
        parent = VisualTreeHelper.GetParent(parent);
        parentTree.Add(parent);
    }
    if (!(parent is ItemsControl itemsControl))
        return (null, -1);

    //ItemsControlの行にあたるオブジェクトを探索履歴の後ろから検索
    var item = parentTree
        .LastOrDefault(x => itemsControl.IsItemItsOwnContainer(x));

    //削除するIndexを取得
    int removeIndex = itemsControl.ItemContainerGenerator?.IndexFromContainer(item)
        ?? -1;

    //Bindingしていた場合はItemsSource、違うならItemsから削除する
    IList targetList = ((itemsControl.ItemsSource as IList) ?? itemsControl.Items);

    return (targetList, removeIndex);
}

次に、指定されたオブジェクトを含む行を上下に移動するメソッドを定義します。
コードビハインドを使用する場合は、このメソッドをButtonのクリックイベントから呼び出しても使えます。

/// <summary>
/// 指定されたオブジェクトを含む行のIndexを増やす = 下の行に移動する
/// </summary>
public static void IncrementItemFromParent(DependencyObject elementInItem)
{
    var (targetList, index) = GetParentListAndIndex(elementInItem);

    if (targetList == null || index < 0)
        return;

    //最後の行だったら何もしない
    if ((index + 1) >= targetList.Count)
        return;

    //一度削除して、1つ大きいIndexに入れ直す
    var targetElement = targetList[index];
    targetList?.RemoveAt(index);
    targetList?.Insert(index + 1, targetElement);
}

/// <summary>
/// 指定されたオブジェクトを含む行のIndexを減らす = 上の行に移動する
/// </summary>
public static void DecrementItemFromParent(DependencyObject elementInItem)
{
    var (targetList, index) = GetParentListAndIndex(elementInItem);

    if (targetList == null || index < 0)
        return;

    //最初の行だったら何もしない
    if (index <= 0)
        return;

    //一度削除して、1つ少ないのIndexに入れ直す
    var targetElement = targetList[index];
    targetList?.RemoveAt(index);
    targetList?.Insert(index - 1, targetElement);
}

/// <summary>
/// 指定されたオブジェクトを含む行を親のItemsControlから削除する
/// </summary>
public static void RemoveItemFromParent(DependencyObject elementInItem)
{
    var (targetList, index) = GetParentListAndIndex(elementInItem);

    if (targetList == null || index < 0)
        return;

    targetList?.RemoveAt(index);
}

そして、Buttonのクリックイベントでこのメソッドを呼ぶ添付プロパティを用意します。

#region RemoveItem添付プロパティ
public static bool GetRemoveItem(DependencyObject obj) => (bool)obj.GetValue(RemoveItemProperty);
public static void SetRemoveItem(DependencyObject obj, bool value) => obj.SetValue(RemoveItemProperty, value);
public static readonly DependencyProperty RemoveItemProperty =
    DependencyProperty.RegisterAttached("RemoveItem", typeof(bool), typeof(DataGridOperation),
        new PropertyMetadata(false, (d, e) => OnPropertyChanged(d, e, RemoveItem)));
private static void RemoveItem(object sender, RoutedEventArgs e) => RemoveItemFromParent(sender as DependencyObject);
#endregion

#region IncrementItem添付プロパティ
public static bool GetIncrementItem(DependencyObject obj) => (bool)obj.GetValue(IncrementItemProperty);
public static void SetIncrementItem(DependencyObject obj, bool value) => obj.SetValue(IncrementItemProperty, value);
public static readonly DependencyProperty IncrementItemProperty =
    DependencyProperty.RegisterAttached("IncrementItem", typeof(bool), typeof(DataGridOperation),
        new PropertyMetadata(false, (d, e) => OnPropertyChanged(d, e, IncrementItem)));

private static void IncrementItem(object sender, RoutedEventArgs e) => IncrementItemFromParent(sender as DependencyObject);
#endregion

#region DecrementItem添付プロパティ
public static bool GetDecrementItem(DependencyObject obj) => (bool)obj.GetValue(DecrementItemProperty);
public static void SetDecrementItem(DependencyObject obj, bool value) => obj.SetValue(DecrementItemProperty, value);
public static readonly DependencyProperty DecrementItemProperty =
    DependencyProperty.RegisterAttached("DecrementItem", typeof(bool), typeof(DataGridOperation),
        new PropertyMetadata(false, (d, e) => OnPropertyChanged(d, e, DecrementItem)));
private static void DecrementItem(object sender, RoutedEventArgs e) => DecrementItemFromParent(sender as DependencyObject);
#endregion

private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e, RoutedEventHandler actionClick)
{
    if (!(d is ButtonBase button))
        return;

    if (!(e.NewValue is bool b))
        return;

    if (b)
        button.Click += actionClick;
    else
        button.Click -= actionClick;
}

使用方法

ViewModel側にこんなプロパティがあるとします。

public ObservableCollection<string> Names { get; } = new ObservableCollection<string>(new[] { "TARO", "JIRO", "SABRO" });

それに対して、ViewではDataGridで上記のNamesプロパティにBindingしています。

DataGrid(MVVM)
<DataGrid
   AutoGenerateColumns="False"
   ItemsSource="{Binding Names}">
   <DataGrid.Columns>
      <DataGridTextColumn Binding="{Binding}" />
      <DataGridTemplateColumn>
         <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
               <StackPanel Orientation="Horizontal">
                  <Button local:DataGridOperation.IncrementItem="True" Content="⇩" />
                  <Button local:DataGridOperation.DecrementItem="True" Content="⬆" />
                  <Button local:DataGridOperation.RemoveItem="True" Content="✖" />
               </StackPanel>
            </DataTemplate>
         </DataGridTemplateColumn.CellTemplate>
      </DataGridTemplateColumn>
   </DataGrid.Columns>
</DataGrid>

ListBoxの場合は以下です。

ListBox(MVVM)
<ListBox
   ItemsSource="{Binding Names}">
   <ListBox.ItemTemplate>
      <DataTemplate>
         <StackPanel Orientation="Horizontal">
            <TextBlock Width="100" Text="{Binding}" />
            <Button local:DataGridOperation.IncrementItem="True" Content="⇩" />
            <Button local:DataGridOperation.DecrementItem="True" Content="⬆" />
            <Button local:DataGridOperation.RemoveItem="True" Content="✖" />
         </StackPanel>
      </DataTemplate>
   </ListBox.ItemTemplate>
</ListBox>

添付プロパティを使用せず、コードビハインドから使用する場合は、以下のようになります

DataGrid(Plane)
<DataGrid>
   <DataGrid.Columns>
      <DataGridTextColumn Binding="{Binding Text}" />
      <DataGridTemplateColumn>
         <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
               <StackPanel Orientation="Horizontal">
                  <Button Click="IncrementButton_Click" Content="⇩" />
                  <Button Click="DecrementButton_Click" Content="⬆" />
                  <Button Click="XButton_Click" Content="✖" />
               </StackPanel>
            </DataTemplate>
         </DataGridTemplateColumn.CellTemplate>
      </DataGridTemplateColumn>
   </DataGrid.Columns>
   <TextBlock Text="AAA" />
   <TextBlock Text="BBB" />
   <TextBlock Text="CCC" />
</DataGrid>

コードビハインドに以下を追加します。

private void IncrementButton_Click(object sender, RoutedEventArgs e)
{
    if (sender is DependencyObject dObj)
        DataGridOperation.IncrementItemFromParent(dObj);
}
private void DecrementButton_Click(object sender, RoutedEventArgs e)
{
    if (sender is DependencyObject dObj)
        DataGridOperation.DecrementItemFromParent(dObj);
}
private void XButton_Click(object sender, RoutedEventArgs e)
{
    if (sender is DependencyObject dObj)
        DataGridOperation.RemoveItemFromParent(dObj);
}

注意点

DataGridなどで並び替えしていると、正しく動きません。Index算出は並び替え後のIndexですが、削除・移動時はデフォルトの並びでのIndexを指定する必要があるためです。

View側での変更をViewModelに伝えるため、ItemsSourceのBindingはTwo-Wayにする必要があります。
つまり、ReadOnlyなコレクションがBindingされていた場合は使えません。

環境

VisualStudio2019
.NET Core 3.1
C#8

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む