nirasan's tech blog

趣味や仕事の覚え書きです。Linux, Perl, PHP, Ruby, Javascript, Android, Cocos2d-x, Unity などに興味があります。

Ruby で Amazon Product Advertising API を使ったメモ

はじめに

インストール

gem 'amazon-ecs'                                                                                                                                                                     

商品リストの検索

初期化

require 'amazon/ecs'
Amazon::Ecs.options = {
  :associate_tag =>     'Amazon アソシエイトのID',                                                                                                                                                 
  :AWS_access_key_id => 'AWS のアクセスキー',                                                                                                                                      
  :AWS_secret_key =>    'AWS のシークレットキー'                                                                                                                   
}

キーワードで検索

# 検索の実行 / オプションの詳細は後述
amazon = Amazon::Ecs.item_search(
  'キーワード', 
  :search_index => 'Books', #=> 検索対象の指定 / 詳細は後述
  :response_group=>"Large", #=> レスポンスに含まれる要素の指定 / 詳細は後述
  :country => 'jp'
)
# 各商品ごとに処理
amazon.items.each do |item|
  puts item.class #=> 商品ごとに Amazon::Element のインスタンスが渡される
  puts item.get('ItemAttributes/Title') #=> タイトルの取得(Amazon::Element.get(PATH) でパスを指定して値の取得)
  puts item.get('DetailPageURL') #=> 商品詳細URL
  puts item.get("LargeImage/URL") #=> 商品画像URL
  puts item.get_element('ItemAttributes').get("Title") #=> Amazon::Element.get_element(ELEMENT_NAME) で子要素を取得
  puts item.get_element('ItemAttributes').elem.css("Title").text #=> Amazon::Element.elem で Nokogiri::XML::Element が取得できるので、css や xpath で要素の検索ができる
end

検索オプション

search_index
response_group
browse_node
item_page
  • 商品は1リクエストで10件取得され、11件目以降は item_page でページ数を指定して取得する。
  • 指定できる値は1〜10

実行例

Kindleストアでコミックを検索し売り上げ順にソートした2ページ目を取得、カテゴリーのIDを表示する。

amazon = Amazon::Ecs.item_search(
  '', #=> browse_node 指定時にはキーワードは省略可
  :search_index => 'KindleStore', 
  :response_group=>"Small, BrowseNodes", 
  :country => 'jp', 
  :browse_node => '2293143051',
  :sort => "salesrank",
  :item_page => "2"
)
amazon.items.each do |item|
  puts "Title: " + item.get('ItemAttributes/Title')
  puts item.get("BrowseNodes/BrowseNode/BrowseNodeId") #=> メインのカテゴリーID
  if item.get_element("BrowseNodes/BrowseNode/Children") then #=> サブのIDがあれば表示
    item.get_element("BrowseNodes/BrowseNode/Children").elem.children.each {|c| puts c.css("BrowseNodeId").text }
  end
end

Unity でユーザーデータをJSONにシリアライズして暗号化して保存する

はじめに

  • Unity でユーザーのデータを保存したい場合、最も手軽なのは PlayerPrefs だが、List が使えなかったり平文だったりして不便な面もある。
  • 今回はユーザーデータを LitJSON でシリアライズして、永続データ領域に保存。保存時に暗号化、読み込み時に復号化、といったことを試してみた。
  • 基本的に 【制作実習】Unityでユーザーデータの保存 | ゴゴゴゴ にある通りだが、省略されている部分を補完する。

LitJSON を使って設定データを読み書きする

LitJSON の導入

ラッパークラスの作成

  • LitJSON だと float 型が扱えないとか有るそうなのでラッパーの作成
using LitJson;

// via http://caitsithware.com/wordpress/archives/140
public class JsonMapper
{
	static JsonMapper()
	{
		LitJson.JsonMapper.RegisterExporter<float>( (obj, writer) => { writer.Write( System.Convert.ToDouble( obj ) ); } );
		LitJson.JsonMapper.RegisterImporter<double,float>( (input) => { return System.Convert.ToSingle( input ); } );
		LitJson.JsonMapper.RegisterImporter<System.Int32,long>( (input) => { return System.Convert.ToInt64( input ); } );
	}

	public static T ToObject<T>( string json )
	{
		return LitJson.JsonMapper.ToObject<T>( json );
	}

	public static string ToJson( object obj )
	{
		return LitJson.JsonMapper.ToJson ( obj );
	}
}

設定データのクラスを作成

  • 内容は任意で
using System.Collections;
using System.Collections.Generic;

public class GameData {

	public int level;
	public List<int> characterIds;
}

設定データ読み書き用クラスの作成

using UnityEngine;
using System.IO;

public class GameDataManager {

	static string fileName = "GameData";

	// via http://www.gogogogo.jp/issue/diary/%E4%B8%80%E4%BA%BA%E5%A4%A7%E5%AD%A6/%E5%88%B6%E4%BD%9C%E5%AE%9F%E7%BF%92/1909/
	public static void Save (GameData gameData) {
		string json = JsonMapper.ToJson(gameData);
		string filePath = GetFilePath ();
		FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
		BinaryWriter writer = new BinaryWriter(fileStream);
		writer.Write(json);
		writer.Close();
	}

	// via http://www.atmarkit.co.jp/fdotnet/dotnettips/669bincopy/bincopy.html
	public static GameData Load () {
		string filePath = GetFilePath ();
		FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
		BinaryReader reader = new BinaryReader(fileStream);
		GameData gameData = new GameData ();
		if (reader != null) {
			string str = reader.ReadString();
			gameData = JsonMapper.ToObject<GameData> (str);
			reader.Close();
		}
		return gameData;
	}

	static string GetFilePath () {
		return Application.persistentDataPath + "/" + fileName;
	}
}

読み書きのテスト

  • 下記のスクリプトを任意の GameObject にアタッチして再生して確認する
  • Mac だと ~/Library/Caches/"PlayerSettingsのCompanyName"/"Project名"/GameData に有るファイルからも確認できる
sing UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class GameDataTest : MonoBehaviour {

	// Use this for initialization
	void Start () {
	
		GameData d = new GameData ();
		d.level = 10000;
		d.characterIds = new List<int> { 1, 2, 3 };

		GameDataManager.Save (d);

		GameData d2 = GameDataManager.Load ();
		Debug.Log (d2.level);
	}
}

設定データの暗号化

  • 設定データ読み書き用クラスを編集して、保存されるデータを暗号化する
using UnityEngine;
using System.IO;
using System.Security.Cryptography;

public class GameDataManager {

	static string fileName = "GameData";

	public static void Save (GameData gameData) {
		string json = JsonMapper.ToJson (gameData);
		json += "[END]"; // 復号化の際にPaddingされたデータを除去するためのデリミタの追記
		string crypted = Crypt.Encrypt (json);
		string filePath = GetFilePath ();
		FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
		BinaryWriter writer = new BinaryWriter(fileStream);
		writer.Write(crypted);
		writer.Close();
	}

	public static GameData Load () {
		string filePath = GetFilePath ();
		FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
		BinaryReader reader = new BinaryReader(fileStream);
		GameData gameData = new GameData ();
		if (reader != null) {
			string str = reader.ReadString();
			string decrypted = Crypt.Decrypt (str);
			decrypted = System.Text.RegularExpressions.Regex.Replace (decrypted, @"\[END\].*$", "");
			gameData = JsonMapper.ToObject<GameData> (decrypted);
			reader.Close();
		}
		return gameData;
	}

	static string GetFilePath () {
		return Application.persistentDataPath + "/" + fileName;
	}

	// via http://yukimemo.hatenadiary.jp/entry/2014/04/15/023802
	private class Crypt {

		private const string AesIV  = @"jCddaOybW3zEh0Kl";
		private const string AesKey = @"giVJrbHRlWBDIggF";

		public static string Encrypt( string text ) {

			RijndaelManaged aes = new RijndaelManaged ();
			aes.BlockSize = 128;
			aes.KeySize = 128;
			aes.Padding = PaddingMode.Zeros;
			aes.Mode = CipherMode.CBC;
			aes.Key = System.Text.Encoding.UTF8.GetBytes (AesKey);
			aes.IV = System.Text.Encoding.UTF8.GetBytes (AesIV);

			ICryptoTransform encrypt = aes.CreateEncryptor ();
			MemoryStream memoryStream = new MemoryStream ();
			CryptoStream cryptStream = new CryptoStream (memoryStream, encrypt, CryptoStreamMode.Write);

			byte[] text_bytes = System.Text.Encoding.UTF8.GetBytes (text);

			cryptStream.Write (text_bytes, 0, text_bytes.Length);
			cryptStream.FlushFinalBlock ();

			byte[] encrypted = memoryStream.ToArray ();

			return (System.Convert.ToBase64String (encrypted));
		}

		public static string Decrypt( string cryptText ) {

			RijndaelManaged aes = new RijndaelManaged ();
			aes.BlockSize = 128;
			aes.KeySize = 128;
			aes.Padding = PaddingMode.Zeros;
			aes.Mode = CipherMode.CBC;
			aes.Key = System.Text.Encoding.UTF8.GetBytes (AesKey);
			aes.IV = System.Text.Encoding.UTF8.GetBytes (AesIV);

			ICryptoTransform decryptor = aes.CreateDecryptor ();

			byte[] encrypted = System.Convert.FromBase64String (cryptText);
			byte[] planeText = new byte[encrypted.Length];

			MemoryStream memoryStream = new MemoryStream (encrypted);
			CryptoStream cryptStream = new CryptoStream (memoryStream, decryptor, CryptoStreamMode.Read);

			cryptStream.Read (planeText, 0, planeText.Length);

			return (System.Text.Encoding.UTF8.GetString (planeText));
		}
	}
}

2D Roguelike を試してみて使いまわせそうなイディオム

はじめに

  • Unity のチュートリアル 2D RogueLike が気になったので、試してみて使えそうなイディオムをいくつかメモ。
  • ゲーム自体は、矢印で移動、移動する毎にfood減少、アイテムでfood増加、敵の攻撃でfood減少、階段で次の階へ、アイテム・壁・敵は階ごとにランダム、という単純な感じ

シングルトン

  • GameManager.cs をシングルトンとして扱う
		public static GameManager instance = null;				//Static instance of GameManager which allows it to be accessed by any other script.

		//Awake is always called before any Start functions
		void Awake()
		{
			//Check if instance already exists
			if (instance == null)
				
				//if not, set instance to this
				instance = this;
			
			//If instance already exists and it's not this:
			else if (instance != this)
				
				//Then destroy this. This enforces our singleton pattern, meaning there can only ever be one instance of a GameManager.
				Destroy(gameObject);	
			
			//Sets this to not be destroyed when reloading scene
			DontDestroyOnLoad(gameObject);
			
			...
		}

敵、壁、アイテムのランダム配置

  • 配置する位置の候補を List にしておき...
		//Clears our list gridPositions and prepares it to generate a new board.
		void InitialiseList ()
		{
			//Clear our list gridPositions.
			gridPositions.Clear ();
			
			//Loop through x axis (columns).
			for(int x = 1; x < columns-1; x++)
			{
				//Within each column, loop through y axis (rows).
				for(int y = 1; y < rows-1; y++)
				{
					//At each index add a new Vector3 to our list with the x and y coordinates of that position.
					gridPositions.Add (new Vector3(x, y, 0f));
				}
			}
		}
  • 候補からランダムに選出して List から除外する
		//RandomPosition returns a random position from our list gridPositions.
		Vector3 RandomPosition ()
		{
			//Declare an integer randomIndex, set it's value to a random number between 0 and the count of items in our List gridPositions.
			int randomIndex = Random.Range (0, gridPositions.Count);
			
			//Declare a variable of type Vector3 called randomPosition, set it's value to the entry at randomIndex from our List gridPositions.
			Vector3 randomPosition = gridPositions[randomIndex];
			
			//Remove the entry at randomIndex from the list so that it can't be re-used.
			gridPositions.RemoveAt (randomIndex);
			
			//Return the randomly selected Vector3 position.
			return randomPosition;
		}

指定の位置まで移動する

  • Physics2D.Linecast で2点間に引いた線と衝突するものを調べることで移動可能かどうか確認する
  • Physics2D.Linecast 実行中は動かしたいオブジェクト自体の Collider を disable にする
		//Move returns true if it is able to move and false if not. 
		//Move takes parameters for x direction, y direction and a RaycastHit2D to check collision.
		protected bool Move (int xDir, int yDir, out RaycastHit2D hit)
		{
			//Store start position to move from, based on objects current transform position.
			Vector2 start = transform.position;
			
			// Calculate end position based on the direction parameters passed in when calling Move.
			Vector2 end = start + new Vector2 (xDir, yDir);
			
			//Disable the boxCollider so that linecast doesn't hit this object's own collider.
			boxCollider.enabled = false;
			
			//Cast a line from start point to end point checking collision on blockingLayer.
			hit = Physics2D.Linecast (start, end, blockingLayer);
			
			//Re-enable boxCollider after linecast
			boxCollider.enabled = true;
			
			//Check if anything was hit
			if(hit.transform == null)
			{
				//If nothing was hit, start SmoothMovement co-routine passing in the Vector2 end as destination
				StartCoroutine (SmoothMovement (end));
				
				//Return true to say that Move was successful
				return true;
			}
			
			//If something was hit, return false, Move was unsuccesful.
			return false;
		}
  • 移動できなかった場合に衝突したものを取得する例
		//The virtual keyword means AttemptMove can be overridden by inheriting classes using the override keyword.
		//AttemptMove takes a generic parameter T to specify the type of component we expect our unit to interact with if blocked (Player for Enemies, Wall for Player).
		protected virtual void AttemptMove <T> (int xDir, int yDir)
			where T : Component
		{
			//Hit will store whatever our linecast hits when Move is called.
			RaycastHit2D hit;
			
			//Set canMove to true if Move was successful, false if failed.
			bool canMove = Move (xDir, yDir, out hit);
			
			//Check if nothing was hit by linecast
			if(hit.transform == null)
				//If nothing was hit, return and don't execute further code.
				return;
			
			//Get a component reference to the component of type T attached to the object that was hit
			T hitComponent = hit.transform.GetComponent <T> ();
			
			//If canMove is false and hitComponent is not equal to null, meaning MovingObject is blocked and has hit something it can interact with.
			if(!canMove && hitComponent != null)
				
				//Call the OnCantMove function and pass it hitComponent as a parameter.
				OnCantMove (hitComponent);
		}

Position の Tween

  • プレイヤーや敵など移動するオブジェクトの親クラス MovingObject.cs でスムーズに位置を変更する
  • Vector3 同士の減算で差のベクトルを出し、Vector3.sqrMagnitude で距離の累乗を算出し、とても小さい数 float.Epsilon より小さければ Vector3 同士が同一であると見なして移動終了とする
		//Co-routine for moving units from one space to next, takes a parameter end to specify where to move to.
		protected IEnumerator SmoothMovement (Vector3 end)
		{
			//Calculate the remaining distance to move based on the square magnitude of the difference between current position and end parameter. 
			//Square magnitude is used instead of magnitude because it's computationally cheaper.
			float sqrRemainingDistance = (transform.position - end).sqrMagnitude;
			
			//While that distance is greater than a very small amount (Epsilon, almost zero):
			while(sqrRemainingDistance > float.Epsilon)
			{
				//Find a new position proportionally closer to the end, based on the moveTime
				Vector3 newPostion = Vector3.MoveTowards(rb2D.position, end, inverseMoveTime * Time.deltaTime);
				
				//Call MovePosition on attached Rigidbody2D and move it to the calculated position.
				rb2D.MovePosition (newPostion);
				
				//Recalculate the remaining distance after moving.
				sqrRemainingDistance = (transform.position - end).sqrMagnitude;
				
				//Return and loop until sqrRemainingDistance is close enough to zero to end the function
				yield return null;
			}
		}

Parse.com でバックグラウンドジョブの登録と実行メモ

Parse.com のダッシュボードからアプリの作成

  • アプリ名は "JobTest" で作成

parse コマンドのインストール

$ curl -s https://www.parse.com/downloads/cloud_code/installer.sh | sudo /bin/bash

Cloud Code のディレクトリを作成

$ parse new JobTest
$ cd JobTest

バックグラウンドジョブの定義

  • cloud/main.js にバックグラウンドジョブを定義する
  • 今回は "JobLog" Object を1件作成するだけのジョブを用意した
  • 記述方法は大体 JavaScript Guide の通りで、成功/失敗で status.success/status.error を返すように
Parse.Cloud.job("firstJob", function(request, status) {
  var JobLog = Parse.Object.extend("JobLog");
  var jobLog = new JobLog();

  jobLog.set("message", "Job is running.");

  jobLog.save(null, {
    success: function(jobLog) {
      status.success("Logging completed successfully.");
    },
    error: function(jobLog, error) {
      status.error("Uh oh, something went wrong.");
    }
  });
});

定義したバックグラウンドジョブを配布

$ parse deploy

バックグラウンドジョブの実行

  • Parse.com のダッシュボードから [Core] > [Jobs] を開き、右上の [Schedule a Job] からバックグラウンドジョブのスケジューリングを行う

動作確認

  • ダッシュボードから [Core] > [Jobs] > [Job Status] でバックグラウンドジョブの成功不成功が確認できる
  • firseJob が成功した場合 [Core] > [Data] で "JobLog" Object が作成されている

Unity で iOS ビルド時に Framework の自動追加・画像の自動追加・任意のファイルの登録・URL Scheme の設定をする

はじめに

  • Unity で PostprocessBuildPlayer を使ってタイトルのようなことをする。
  • 掲載のコードはGAMEFEATのSDKを組み込んだときのもの。

ruby から XCode を操作する gem のインストール

http://starzero.hatenablog.com/entry/2014/02/18/163330 を参考に

ビルド後に実行されるスクリプトの用意

  • Assets/Editor/PostprocessBuildPlayer という名前で以下のスクリプトを配置する
#!/usr/bin/env ruby
 
require 'xcodeproj'
require 'fileutils'

PROJECT = 'Unity-iPhone'
TARGET = 'Unity-iPhone'
LIBRARY = 'Libraries'
 
# システムFramework追加
def add_frameworks(project, names, optional = false)
  project.targets.each do |target|
    next unless TARGET == target.name
 
    build_phase = target.frameworks_build_phase
    framework_group = project.frameworks_group
 
    names.each do |name|
      next if exist_framework?(build_phase, name)
 
      path = "System/Library/Frameworks/#{name}.framework"
      file_ref = framework_group.new_reference(path)
      file_ref.name = "#{name}.framework"
      file_ref.source_tree = 'SDKROOT'
      build_file = build_phase.add_file_reference(file_ref)
      if optional
        build_file.settings = { 'ATTRIBUTES' => ['Weak'] }
      end
    end
  end
end
 
# 外部Framework追加
def add_external_frameworks(project, names)
  project.targets.each do |target|
    next unless TARGET == target.name
 
    target.build_configurations.each do |configuration|
      # Framework Search Pathsを設定
      configuration.build_settings['FRAMEWORK_SEARCH_PATHS'] = configuration.build_settings['LIBRARY_SEARCH_PATHS']
    end
 
    build_phase = target.frameworks_build_phase
    library_group = project.main_group.children.find {|child| child.path == LIBRARY}
 
    names.each do |name|
      next if exist_framework?(build_phase, name)
 
      copy_library(name)
 
      path = "#{LIBRARY}/#{name}.framework"
      file_ref = library_group.new_reference(path)
      file_ref.name = "#{name}.framework"
      file_ref.source_tree = 'SOURCE_ROOT'
      build_phase.add_file_reference(file_ref)
    end
  end
end
 
# Framework追加済みか
def exist_framework?(build_phase, name)
  build_phase.files.each do |file|
    return true if file.file_ref.name == "#{name}.framework"
  end
  false
end
 
# 外部FrameworkをUnityのディレクトリからXcodeのディレクトリへコピー
def copy_library(name)
  asset_path = "#{ARGV[0]}/../Assets"
  from = File.expand_path("#{asset_path}/Editor/iOS/#{LIBRARY}/#{name}.framework", __FILE__)
  to = "#{ARGV[0]}/#{LIBRARY}/#{name}.framework"
  FileUtils.copy_entry(from, to)
end


### ===================================================


project_path = ARGV[0] + "/#{PROJECT}.xcodeproj"
project = Xcodeproj::Project.new(project_path)
 
project.initialize_from_file
 
### Framework の自動追加 ###
# require で追加
add_frameworks(project, ["Foundation", "UIKit", "CoreTelephony"])
# optional で追加
add_frameworks(project, ["AdSupport", "StoreKit"], true)
# 外部 Framework の追加
# 事前に Assets/Editor/iOS/Libraries にファイルを置いておく
add_external_frameworks(project, ["GameFeatKit"])
 

### 画像のコピー ###
# 一度 XCode の Images.xcassets にファイルを追加し、作成されたファイルを Assets/Editor/iOS/Images に保存しておく
asset_path = "#{ARGV[0]}/../Assets"
FileUtils.cp_r("#{asset_path}/Editor/iOS/Images/gamefeat/", "#{ARGV[0]}/Unity-iPhone/Images.xcassets/")


### ライブラリのコピー ###
# 任意のファイルを XCode に登録する
files = [
  # [ファイル名, コピー元, コピー先 ]
  [ "JSONKit.h", "#{asset_path}/Editor/iOS/Libraries/JSONKit/", "#{ARGV[0]}/Libraries/" ], 
  [ "JSONKit.m", "#{asset_path}/Editor/iOS/Libraries/JSONKit/", "#{ARGV[0]}/Libraries/" ], 
]
files.each do |file|
  FileUtils.cp_r(file[1] + file[0], file[2])
  project.targets.each do |target|
    next unless TARGET == target.name
    library_group = project.main_group.children.find {|child| child.path == LIBRARY}
    file = library_group.new_file(file[2] + file[0])
    target.add_file_references([file])
  end
end


### URL Scheme の設定 ###
plist = "#{ARGV[0]}/Info.plist"
appname = "info.nirasan.ssec"
system("/usr/libexec/PlistBuddy -c 'Add :CFBundleURLTypes array' #{plist}")
system("/usr/libexec/PlistBuddy -c 'Add CFBundleURLTypes:0:CFBundleURLSchemes array' #{plist}")
system("/usr/libexec/PlistBuddy -c 'Add CFBundleURLTypes:0:CFBundleURLSchemes:0 string #{appname}' #{plist}")


### 設定の保存 ###
project.save

Unity で Parse Config を使いアプリの設定をリモート管理する 〜 例えば審査中だけアプリの挙動を変化させる

はじめに

  • Parse.com の Parse Config を使うと、アプリの設定を手軽にリモートで管理することが出来、例えば審査中だけ特定の GameObject を表示させないなどの処理が書きやすくなります。
  • Parse Config は単純なキーバリューストアで、以下のような型が扱えます。
    • string
    • bool/int/double/long
    • DateTime
    • ParseFile
    • ParseGeoPoint
    • IList (even nested)
    • IDictionary (even nested)
  • Parse.com の導入は過去記事を参照。

審査中だけ特定の GameObject を非表示にする場合

Parse Confing の設定

  • Parse.com のサイトから Core > Config で Parse Config の画面へ
  • Parameter : "InReview", Type: Boolean, Value true でパラメータの作成
  • ドキュメントは https://parse.com/docs/unity_guide#config

Unity 側のコード

		public GameObject TargetObject;
		ParseConfig parseConfig = null;
		bool? inReview = null;
		
		void Start () {
			GetParseConfig ();
		}

		void Update () {
			if (inReview != null) {
				NGUITools.SetActive (TargetObject, !((bool)inReview));
				inReview = null;
			}
		}
		
		void GetParseConfig () {
			ParseConfig.GetAsync ().ContinueWith (t => {
				if (t.IsFaulted) {
					// using cacehd config
					parseConfig = ParseConfig.CurrentConfig;
				} else {
					parseConfig = t.Result;
				}

				bool result = parseConfig.TryGetValue ("InReview", out inReview);
				if (!result) {
					// failed
					inReview = null;
				}
			});
		}

NGUI の ScrollView 内のアイテムをドラッグアンドドロップで並び替える

はじめに

構成

ゲームオブジェクトの親子関係とアタッチするコンポーネント

  • Scroll View (UISCrollView, UIDragDropRoot)
    • Grid (UIGrid)
      • Item (BoxCollider, UIDragDropItem, UIDragScrollView)
      • Item
      • Item

Scroll View オブジェクトの設定

  • UIPanel の Size を任意で設定
  • UIScrollView の Movement を任意で設定(今回は縦スクロールにするので Vertical に)

Grid の設定

  • UIGrid の Arrangment を(今回は縦スクロールなので)Vertical に
  • 同じく Cell Width と Cell Height を任意で設定
  • Sorting を(縦スクロールの場合は)Vertical に

Item の設定

  • UIDragDropItem の Restriction を Horizontal に

並び替え終了時にリストのアイテムを取得する

using UnityEngine;
using System.Collections;

public class MyDragDropItem : UIDragDropItem {

	protected override void OnDragDropEnd ()
	{
		var list = mGrid.GetChildList ();
		list.ForEach (child => Debug.Log (child.name));
		base.OnDragDropEnd ();
	}
}