MagicalRecord を試したメモ
はじめに
- iOS アプリ開発で ActiveRecord ライクに CoreData を扱うことが出来る MagicalRecord を試したメモ
- CocoaPod を使うのも初めてなのでそこもちょっと詳しく
動作環境
- XCode 6.1.1
- MagicalRecord 2.2
- mogenerator 1.27
参考URL
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
エンティティ(テーブル)の追加
- 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 も削除されるので任意で設定
- Auther の Relationships で "+" を押下しリレーションシップを作成
- Book の設定
- Book の Relationships で "+" を押下しリレーションシップを作成
- Relationships を auther
- Destination を Auther
- Inverse は books
- Book の auther を選択した状態のインスペクタで、Relationships の Type を "To One" に
- Book の Relationships で "+" を押下しリレーションシップを作成
カスタム管理オブジェクトクラスの作成
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
カスタムコントローラの追加
ボタン押下で遷移
- Storyboard でビューコントローラにボタンの追加
- ボタンを右クリック Touch Up Inside からドラッグして遷移先のビューコントローラでドロップ
戻る遷移とコールバックの登録
ビューの要素をコントローラにひもづける
- 右上の 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
はじめに
- 基本的な使い方は以下のページにまとまっていたので、その他のTipsをメモ。
Date型を扱う
- SQLite では Date 型が扱えないので、Long に変換して利用するため、http://dev.classmethod.jp/smartphone/android/activeandroid/ にあるシリアライザーを使う。
- Search の条件で Date 型を指定する場合は、シリアライザーが効かないので都度自分で変換する。
Date now = new Date(); new Select() .from(SomeModel.class) .where("SomeDate = ?", now.getTime()) .execute();
リレーションシップ
"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; }
レコードの存在確認
- 検索したレコードが存在するか確認したい場合は、execute() で結果の List の要素数を調べるか、exists() を使う。
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 を使ったメモ
はじめに
- Ruby で amazon-ecs を使って Amazon Product Advertising API の検索をしたメモ。
インストール
gem 'amazon-ecs'
アカウント作成
Amazon アソシエイトのID作成
商品リストの検索
初期化
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
検索オプション
- https://images-na.ssl-images-amazon.com/images/G/09/associates/paapi/dg/index.html を参考に
- パラメータ名を snake_case にする
search_index
- 検索対象の指定
- All で全て、Books で本、KindleStore でKindle本、その他は https://images-na.ssl-images-amazon.com/images/G/09/associates/paapi/dg/index.html を参照。
response_group
- レスポンスに含まれる要素の指定
- キーワードで指定し、復数の場合はカンマ区切りにする
- search_index によって指定可能な値が異なる
- 指定可能な値は https://images-na.ssl-images-amazon.com/images/G/09/associates/paapi/dg/index.html を参照
- Small, Medium, Large はそれぞれのサイズ感でよく使うものをまとめてある
browse_node
- 検索カテゴリーの指定。カテゴリーのIDを整数で指定する。
- IDの表などは見つけられなかったが、www.amazon.co.jp のカテゴリー別商品ページなどの URL から推測できる。
- 商品ページのURL が http://www.amazon.co.jp/%E6%9C%AC-%E9%80%9A%E8%B2%A9/b/ref=nav_jb?ie=UTF8&node=465392 の場合は 465392
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 の導入
- http://lbv.github.io/litjson/ から dll をダウンロードして Assets 以下の任意のパスに追加
ラッパークラスの作成
- 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)); } } }
参考サイト
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/
http://caitsithware.com/wordpress/archives/140
http://lbv.github.io/litjson/docs/quickstart.html
http://yukimemo.hatenadiary.jp/entry/2014/04/15/023802
http://qiita.com/snaka/items/a99747daadfdb0d9ea0b
http://dobon.net/vb/dotnet/string/trim.html
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 が作成されている