nirasan's tech blog

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

MagicalRecord を試したメモ

はじめに

  • iOS アプリ開発で ActiveRecord ライクに CoreData を扱うことが出来る MagicalRecord を試したメモ
  • CocoaPod を使うのも初めてなのでそこもちょっと詳しく

動作環境

  • XCode 6.1.1
  • MagicalRecord 2.2
  • mogenerator 1.27

CoreDateの用語

  • 管理オブジェクト(NSManagedObject) : レコード
  • 管理オブジェクトコンテキスト(NSManagedObjectContext) : ORM
  • 管理オブジェクトモデル(NSManagedObjectModel) : スキーマ

プロジェクトの作成

  • "Use Core Data" にチェックを入れて作成

MagicalRecord のインストール

CocoaPod のインストール

  • インストールしていなければ
sudo gem install cocoapods

MagicalRecord のインストール

# cd /path/to/xcode/project
# /usr/bin/pod init
# vi Podfile
target 'ProjectName' do
  pod "MagicalRecord"
  pod "MagicalRecord/Shorthand"
end
# /usr/bin/pod update

MagicalRecord を読み込む

  • pod update によって CocoaPods が有効な状態の XCode プロジェクトファイル(APPNAME.xcworkspace)が生成されているので、これを開く
# open App.xcworkspace
  • CocoaPods のビルドを行う
    • 画面左上の方にあるビルド対象のセレクトボックスをクリックして "New Scheme" を選択
    • "Pods-APPNAME" を選択してビルド対象として作成
    • ビルドデバイスを "iOS Device" に変更し、ビルドを実行

エンティティ(テーブル)の追加

  • APPNAME.xcdatamodeld ファイルを選択
  • "Add Entity" ボタンでエンティティの作成
  • 画面右のインスペクタで Entity の Name と Class を設定 (Class はクラスファイル生成時に必要)
  • エンティティを選択して "Add Attribute" ボタンでアトリビュート(カラム)の作成

リレーションシップについて

  • http://qiita.com/yuiseki/items/33ec35ec99b5304be90a
  • Auther エンティティと Book エンティティが 1:多 の関係になるとする
  • Auther の設定
    • Auther の Relationships で "+" を押下しリレーションシップを作成
      • Relationships を books
      • Destination を Book
      • Inverse はとりあえずそのまま(Book 側の Inverse を設定すれば自動でセットされる)
    • Auther の books を選択した状態のインスペクタで、Relationships の Type を "To Many" に
    • 同じく "Delete Rule" を "Cascade" にすると Auther 削除時に関連する Book も削除されるので任意で設定
  • Book の設定
    • Book の Relationships で "+" を押下しリレーションシップを作成
      • Relationships を auther
      • Destination を Auther
      • Inverse は books
    • Book の auther を選択した状態のインスペクタで、Relationships の Type を "To One" に

カスタム管理オブジェクトクラスの作成

mogenerator のインストール

# brew install mogenerator
# /usr/local/bin/mogenerator -v

ファイルの作成

# /usr/local/bin/mogenerator -m Model.xcdatamodeld/Model.xcdatamodel -O CoreData/ --template-var arc=true

MagicalRecord の初期化

AppDelegate.h

#import <MagicalRecord.h>
#import <MagicalRecord+Setup.h>

AppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:@"app.db"]; // 追記
    return YES;
}
- (void)applicationWillTerminate:(UIApplication *)application {
    [MagicalRecord cleanUp]; // 追記
}

MagicalRecord を使う

ViewController.h

  • Auther.h, Book.h はカスタム管理オブジェクトクラス
#import <CoreData/CoreData.h>
#import <CoreData+MagicalRecord.h>
#import "Auther.h"
#import "Book.h"

ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    
	// Auther の作成
    Auther* auther = [Auther MR_createEntity];
    auther.name = @"Neal Stephenson";
    
	// Book の作成と Auther との関連付け
    Book* book1 = [Book MR_createEntity];
    book1.title = @"Snow Crash";
    [auther addBooksObject:book1];

    Book* book2 = [Book MR_createEntity];
    book2.title = @"The Diamond Age";
    [auther addBooksObject:book2];

    Book* book3 = [Book MR_createEntity];
    book3.title = @"Cryptonomicon";
    [auther addBooksObject:book3];
	
	// 作成したレコードの登録
    [[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
    
	// Auther の一覧を取得
    NSArray* array = [Auther MR_findAll];
    int i = 0;
    NSLog(@"Count:%lu", (unsigned long)[array count]);
    for (Auther* a in array) {
        NSLog(@"#####%d name=%@", i, a.name);
		// Auther に関連付いた Book の取得
        for (Book* b in a.books) {
            NSLog(@"----- title=%@", b.title);
        }
        i++;
    }
	
	// 削除
	[auther MR_deleteEntity];
    [[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
}

iOSアプリ開発チュートリアルメモ

ナビゲーションコントローラの追加

  • タイトル表示や戻るなどが出来るようになる
  • 追加したいビューコントローラを選択した状態で、メニューから Editor > Embed In > Navigation Controller

カスタムコントローラの追加

  • File > New > File
  • iOS > Source > Cocoa Touch Class
  • 任意の名前、任意の親クラスの選択
    • 素のコントローラなら UIViewController、テーブルなら UITableViewController
  • Storyboard でひもづけたいビューコントローラを選択、アイデンティティインスペクタで Class オプションの変更

ボタン押下で遷移

  • Storyboard でビューコントローラにボタンの追加
  • ボタンを右クリック Touch Up Inside からドラッグして遷移先のビューコントローラでドロップ

戻る遷移とコールバックの登録

  • 戻り先のビューコントローラで -(IBAction)SomeName:(UIStoryboardSegue*)segue; なメソッドを宣言すると戻り時のコールバックメソッドになる
  • Storyboard 戻り元のビューコントローラの Exit を選択、コネクションインスペクタで先ほどのコールバックメソッドから戻るボタンへドラッグしてひもづける

ビューの要素をコントローラにひもづける

  • 右上の Show the Assistant Editor (蝶ネクタイのアイコン) をクリックして Storyboard とコントローラのソースを表示
  • Storyboard で要素を右クリック、Referencing Outlet をコントローラの @interface セクションにひもづける

プロパティを宣言する

  • public なら .h の @interface で
  • private なら .m の @interface で
  • 記法は @property (オプション) 型 プロパティ名;
  • オプション
    • readonly : 読み込み専用
  • プロパティの初期値は nil

メソッドの宣言

遷移前実行メソッド

- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if (sender != self.doneButton) return;
    if (self.textField.text.length > 0) {
        self.toDoItem = [[XYZToDoItem alloc] init];
        self.toDoItem.itemName = self.textField.text;
        self.toDoItem.completed = NO;
    }
}

テーブル関連

セクション数を返すメソッド

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
#warning Potentially incomplete method implementation.
    // Return the number of sections.
    return 0;
}

アイテム数を返すメソッド

  • 例は配列の要素数を返す
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return [self.toDoItems count];
}

ポジション毎にアイテムをセットするメソッド

  • 例は配列から対応する要素を取得してテキストにセットしている
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ListPrototypeCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    XYZToDoItem *toDoItem = [self.toDoItems objectAtIndex:indexPath.row];
    cell.textLabel.text = toDoItem.itemName;
    return cell;
}

ドキュメントを見る

  • Help > "Documentation and API Reference"

ActiveAndroid Tips

はじめに

Date型を扱う

Date now = new Date();
new Select()
	.from(SomeModel.class)
	.where("SomeDate = ?", now.getTime())
	.execute();

実行したSQL文のデバッグ表示

com.activeandroid.util.Log.setEnabled(true);

リレーションシップ

"1:多"な親子関係の定義

  • Category が親で Item が子
@Table(name = "Categories")
public class Category extends Model {
    @Column(name = "Name")
    public String name;

    public List<Item> items() {
        return getMany(Item.class, "Category");
    }
}

@Table(name = "Items")
public class Item extends Model {

    @Column(name = "Name")
    public String name;

    @Column(name = "Category")
    public Category category;
}

外部キー制約で親の削除時に子も削除する

  • デフォルトでは子のいる親を削除すると例外が発生する
  • Column の定義時に onDelete を適宜指定すると、子も同時に削除できる
@Table(name = "Items")
public class Item extends Model {

    @Column(name = "Name")
    public String name;
	
    // 変更箇所
    @Column(name = "Category", onDelete = Column.ForeignKeyAction.CASCADE)
    public Category category;
}

レコードの存在確認

    public void testExistsWhereClause() {
        cleanTable();
        populateTable();

        From from = new Select()
                .from(MockModel.class)
                .where("intField = ?", 1);

        final List<MockModel> list = from.execute();
        final boolean exists = from.exists();

        assertTrue(exists);
        assertTrue(list.size() > 0);
    }

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 が作成されている