Rails で作った API サーバーに Unity でリクエストをする
はじめに
- http://nirasan.hatenablog.com/entry/2015/08/20/172719 で作成した API サーバーに Unity で認証してリクエストを送信するメモ
サーバー側の変更点
- Doorkeeper でアクセストークンの更新に対応するため config/initializers/doorkeeper.rb に以下を追記する。
use_refresh_token
スクリプト
- 任意のオブジェクトに以下のスクリプトをアタッチして使用する
- AuthorizedRequest メソッドに URL とリクエスト成功時のコールバック関数などを指定すると、自動で認証を行った上でリクエストを送信してくれる。
using UnityEngine; using System.Collections; using System.Collections.Generic; using MiniJSON; using System; public class APIManager : MonoBehaviour { public string TokenUrl = "http://127.0.0.1:3000/oauth/token"; public string UsersUrl = "http://127.0.0.1:3000/users/100"; public string client_id = "07461c1f68870ea090ed20af49d0680782950119213e2a1af9e1f653c688dca3"; public string client_secret = "20dea83f022c9e2a0e6fbec1da0e9c0e20c40e1bf09d2e8db029b461653a88bd"; public string username = "user1@example.com"; public string password = "password"; public string AccessToken; public long ExpiresIn; public long CreatedAt; public string RefreshToken; private static readonly DateTime UNIX_EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public DateTime ExpiresAt; void Start () { StartCoroutine(GetUsers()); } /// <summary> /// 使用例としてユーザー情報を取得する /// </summary> public IEnumerator GetUsers () { yield return StartCoroutine(AuthorizedRequest( url: UsersUrl, onSuccess: (WWW www) => { Debug.Log(www.text); Debug.Log(www.error); foreach (var kv in www.responseHeaders) { Debug.Log(string.Format("{0} : {1}", kv.Key, kv.Value)); } } )); } /// <summary> /// 認証が必要なページにHTTPリクエストを送信する /// アクセストークンが未取得であれば取得し /// アクセストークンが期限切れであれば更新する /// </summary> /// <param name="url">URL.</param> /// <param name="form">Form.</param> /// <param name="headers">Headers.</param> /// <param name="onSuccess">On success.</param> /// <param name="onError">On error.</param> public IEnumerator AuthorizedRequest (string url, WWWForm form = null, Dictionary<string, string> headers = null, System.Action<WWW> onSuccess = null, System.Action onError = null) { if (string.IsNullOrEmpty(AccessToken)) { var tokenGet = TokenGet(); yield return StartCoroutine(tokenGet); if (Request.RequestIsError(tokenGet)) { yield break; } } if (ExpiresAt < DateTime.Now) { var tokenRefresh = TokenRefresh(); yield return StartCoroutine(tokenRefresh); if (Request.RequestIsError(tokenRefresh)) { yield break; } } if (headers == null) { headers = new Dictionary<string, string>(); headers.Add("Authorization", string.Format("Bearer {0}", AccessToken)); } var request = new Request(url, form, headers, onSuccess, onError); yield return StartCoroutine(request.Send()); yield return request; } /// <summary> /// アクセストークンを取得する /// </summary> IEnumerator TokenGet () { var form = new WWWForm(); form.AddField("grant_type", "password"); form.AddField("username", username); form.AddField("password", password); form.AddField("client_id", client_id); form.AddField("client_secret", client_secret); var request = new Request(url: TokenUrl, form: form, onSuccess: TokenUpdate); yield return StartCoroutine(request.Send()); yield return request; } /// <summary> /// アクセストークンを更新する /// </summary> IEnumerator TokenRefresh () { var form = new WWWForm(); form.AddField("grant_type", "refresh_token"); form.AddField("refresh_token", RefreshToken); form.AddField("client_id", client_id); form.AddField("client_secret", client_secret); var request = new Request(url: TokenUrl, form: form, onSuccess: TokenUpdate); yield return StartCoroutine(request.Send()); yield return request; } /// <summary> /// アクセストークン取得リクエストのレスポンスからアクセストークン情報を取得しインスタンス変数に格納する /// </summary> /// <param name="www">Www.</param> private void TokenUpdate (WWW www) { var jsonDict = Request.WWWToJson(www); AccessToken = (string)jsonDict["access_token"]; ExpiresIn = (long)jsonDict["expires_in"]; CreatedAt = (long)jsonDict["created_at"]; RefreshToken = (string)jsonDict["refresh_token"]; ExpiresAt = UNIX_EPOCH.AddSeconds(CreatedAt + ExpiresIn).ToLocalTime(); } private class Request { public bool IsError { private set; get; } private float TIMEOUT = 5f; private string url; private byte[] formdata; private Dictionary<string, string> headers; private System.Action<WWW> onSuccess; private System.Action onError; public Request (string url, WWWForm form = null, Dictionary<string, string> headers = null, System.Action<WWW> onSuccess = null, System.Action onError = null) { this.url = url; this.formdata = form == null ? null : form.data; this.headers = headers; this.onSuccess = onSuccess == null ? (WWW www) => {} : onSuccess; this.onError = onError == null ? () => {} : onError; } public IEnumerator Send () { /* Debug.Log(url); if (headers != null) { foreach (var kv in headers) { Debug.Log(string.Format("{0} : {1}", kv.Key, kv.Value)); } } */ var www = new WWW(url, formdata, headers); var endtime = Time.realtimeSinceStartup + TIMEOUT; while (!www.isDone || Time.realtimeSinceStartup < endtime) { yield return null; } if (WWWIsError(www)) { this.IsError = true; onError(); } else { this.IsError = false; onSuccess(www); } /* Debug.Log(www.text); Debug.Log(www.error); foreach (var kv in www.responseHeaders) { Debug.Log(string.Format("{0} : {1}", kv.Key, kv.Value)); } */ yield return this; } private bool WWWIsError (WWW www) { return !string.IsNullOrEmpty(www.error); } public static Dictionary<string, object> WWWToJson (WWW www) { return Json.Deserialize(www.text) as Dictionary<string, object>; } public static bool RequestIsError (IEnumerator request) { return ((Request)request.Current).IsError; } } }
Rails で API サーバーの認証の仕組みを作る
はじめに
参考サイト
導入
新しいプロジェクトを作成
$ rails new -T -B doorkeeper-test
gem のインストール
デフォルトの Gemfile に以下を追記
gem 'doorkeeper' gem 'sorcery' group :development, :test do gem 'rspec-rails' gem 'factory_girl_rails' gem 'database_rewinder' end
インストール
$ bundle install
Sorcery のインストール
# Sorcery の設定ファイルと Sorcery に対応した User モデルの生成 $ rails generate sorcery:install
Doorkeeper のインストール
# 設定ファイルの作成とルートの追加 $ rails generate doorkeeper:install # マイグレーションファイルの作成 $ rails generate doorkeeper:migration
マイグレーションの実行
$ rake db:migarate
Doorkeeper の設定
Doorkeeper.configure do # Change the ORM that doorkeeper will use (needs plugins) orm :active_record resource_owner_authenticator do User.find_by_id(session[:current_user_id]) || redirect_to(login_url) end resource_owner_from_credentials do User.authenticate(params[:username], params[:password]) end end Doorkeeper.configuration.token_grant_types << "password"
ユーザーコントローラの作成
- ユーザーの作成とユーザー情報表示ページを作成する
- ユーザー作成は認証なしで、ユーザー情報表示は認証必須のページにする
generate
$ rails g controller users create show
routes.rb
resources :users, only: [:create, :show]
app/controllers/users_controller.rb
class UsersController < ApplicationController before_action :doorkeeper_authorize!, only: [:show] # show のみ認証が必要 def create @user = User.new(user_params) if @user.save render json: @user else head :bad_request end end def show render json: User.find(params[:id]) end private def user_params params.require(:user).permit(:email, :password) end end
app/controllers/application_controller.rb
- CSRF 対策を無効にする
- protect_from_forgery with: :exception + protect_from_forgery with: :null_session
Doorkeeper の認証用キーの作成
- ブラウザを使って Doorkeeper で認証するためのアクセスキーとシークレットキーを取得
- http://127.0.0.1:3000/oauth/applications にアクセスして New Application からアプリケーションを作成後、キーが表示される
- http://qiita.com/camelmasa/items/1c6d06713fc25eb7bddf こちらのページがスクリーンショット付きで詳しい
動作確認
- curl コマンドで動作確認を行う
ユーザーの作成
$ curl -F "user[email]=user1@example.com" -F "user[password]=password" http://127.0.0.1:3000/users
アクセストークンの取得
- client_id には「Doorkeeper の認証用キーの作成」で作成した Application Id を、client_secret には同じく Secret を指定する
$ curl -F "grant_type=password" \ -F "client_id=07461c1f68870ea090ed20af49d0680782950119213e2a1af9e1f653c688dca3" \ -F "client_secret=20dea83f022c9e2a0e6fbec1da0e9c0e20c40e1bf09d2e8db029b461653a88bd" \ -F "username=user1@example.com" -F "password=password" \ http://127.0.0.1:3000/oauth/token.json
ユーザーの取得
- Bearer 以降には「アクセストークンの取得」で取得したトークンの文字列を指定する
$ curl -H "Authorization: Bearer 18afa04c80fbe3528b5d495c24e8badcbaeee12b2866d22d8f220c44543ae01c" http://127.0.0.1:3000/users/1
テスト
rspec の準備
$ rails g rspec:install
rails_helper.rb に追記
config.before :suite do DatabaseRewinder.clean_all end config.after :each do DatabaseRewinder.clean end config.before :all do FactoryGirl.reload end
specs_helper.rb に追記
require 'factory_girl_rails' config.include FactoryGirl::Syntax::Methods
factories/users.rb の作成
FactoryGirl.define do factory :user do sequence(:email) {|n| "user#{n}@example.com" } password "password" end end
factories/doorkeeper.rb の作成
FactoryGirl.define do factory :access_token, class: Doorkeeper::AccessToken do sequence(:resource_owner_id) { |n| n } application expires_in 1.hours end factory :application, class: Doorkeeper::Application do sequence(:name){ |n| "Application #{n}" } redirect_uri 'https://example.com/callback' end end
spec/controllers/user_controller_spec.rb
require 'rails_helper' RSpec.describe UsersController, type: :controller do describe "GET #show" do let!(:application) { create :application } let!(:user) { create :user } let!(:token) { create :access_token, :application => application, :resource_owner_id => user.id } it "returns http success" do get :show, {format: :json, access_token: token.token, id: user.id} expect(response).to have_http_status(:success) end end end
さいごに
- クライアントからは Doorkeeper のトークン情報を見れる必要はないから、中継用のアクションとかを用意したほうがいいかな?
Rails初期化チートシート
プロジェクトの作成
rails new PROJECT_NAME -T -B cd PROJECT_NAME
git の設定
git init
curl https://www.gitignore.io/api/rails,ruby,osx,linux,vim,sublimetext,rubymine > .gitignore
gemのインストール
cp /path/to/Gemfile ./ #TODO bundle install
bootstrap の適用
rails g layout:install bootstrap3
font-awesome の使用
vi app/assets/stylesheets/framework_and_overrides.css.scss @import "bootstrap-sprockets"; @import "bootstrap"; +@import "font-awesome";
simple_form
rails g simple_form:install --bootstrap
devise
rails g devise:install
rails g devise user
rake db:migrate
devise の日本語対応
rails g devise:views:locale ja rails g devise:views:bootstrap_templates
日本語化
curl -L https://raw.github.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml > config/locales/ja.yml
アプリケーション設定
vi config/application.rb
config.time_zone = 'Tokyo' config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] config.i18n.default_locale = :ja config.generators do |g| g.javascripts false g.stylesheets false g.helper false g.test_framework :rspec, fixture: true, view_specs: false, helper_specs: false, routing_specs: false, controller_specs: true, request_specs: true g.fixture_replacement :factory_girl, dir: 'spec/factories' end
devise 用のリンクを app/views/layouts/_navigation_links.html.haml に追加
- if user_signed_in? %li= link_to current_user.email, root_path, :class => 'navbar-link' %li= link_to 'Edit profile', edit_user_registration_path, :class => 'navbar-link' %li= link_to "Logout", destroy_user_session_path, method: :delete, :class => 'navbar-link' - else %li= link_to "Sign up", new_user_registration_path, :class => 'navbar-link' %li= link_to "Login", new_user_session_path, :class => 'navbar-link'
kaminari
rails g kaminari:config rails g kaminari:views bootstrap3
rspec
rails g rspec:install
テストの設定
spec/rails_helper.rb
RSpec.configure do |config| config.before :suite do DatabaseRewinder.clean_all end config.after :each do DatabaseRewinder.clean end config.before :all do FactoryGirl.reload end end
spec/spec_helper.rb
require 'factory_girl_rails' config.include FactoryGirl::Syntax::Methods
spec/factories/users.rb
actoryGirl.define do factory :user do sequence(:email) { |i| "user#{i}@example.com" } sequence(:password) { |i| "password#{i}" } end end
.pryrc の作成
begin require 'hirb' rescue LoadError # Missing goodies, bummer end if defined? Hirb # Slightly dirty hack to fully support in-session Hirb.disable/enable toggling Hirb::View.instance_eval do def enable_output_method @output_method = true @old_print = Pry.config.print Pry.config.print = proc do |*args| Hirb::View.view_or_page_output(args[1]) || @old_print.call(*args) end end def disable_output_method Pry.config.print = @old_print @output_method = nil end end Hirb.enable end
erb2haml
rake haml:replace_erbs
確認用ページ作成
rails g controller welcome index
figaro
bundel exec figaro install vi config/application.yml figaro heroku:set -e production
Unity でネイティブプラグインを使って ActivityIndicator の画像変更と画面中央での表示(iOS編)
はじめに
iOS 側のコード
- 以下のスクリプトを Unity の Assets/Plugins/iOS に MyActivityIndicator.mm として保存する
- http://tsubakit1.hateblo.jp/entry/2014/08/19/010141 からの変更点は
- 親ビューの取得がなんかうまくいかなかったので GetAppController から UnityGetGLViewController に
- 背景色を半透明の黒に
- 画像の表示位置を画面中央に
#import "UnityAppController.h" UIImageView *imageView; UIView *backgroundView; NSMutableArray *imageList; const int kImageCount = 4; NSString* const kImageNameFormat = @"anim%02d.png"; extern "C" { void InitMyActivityIndicator(); void StartMyActivityIndicator(); void EndMyActivityIndicator(); } void InitMyActivityIndicator() { imageList = [NSMutableArray array]; for (NSInteger i = 0; i < kImageCount; i++) { NSString *imagePath = [NSString stringWithFormat:kImageNameFormat, i]; UIImage *img = [UIImage imageNamed:imagePath]; [imageList addObject:img]; } UIImage *img = [imageList objectAtIndex:0]; CGSize cs = img.size; CGRect pr = [[UIScreen mainScreen] bounds]; CGRect rect = CGRectMake( (pr.size.width / 2 - cs.width / 2), (pr.size.height / 2 - cs.height / 2), cs.width, cs.height ); imageView = [[UIImageView alloc]initWithFrame:rect]; imageView.image = imageView.image = [imageList objectAtIndex:0]; imageView.animationImages = imageList; imageView.animationDuration = 0.5; imageView.animationRepeatCount = 0; } void CreateBackgroundView () { CGRect pr = [[UIScreen mainScreen] bounds]; backgroundView = [[UIView alloc] init]; backgroundView.frame = CGRectMake(0, 0, pr.size.width, pr.size.height); backgroundView.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5]; [UnityGetGLViewController().view addSubview:backgroundView]; } void StartMyActivityIndicator() { InitMyActivityIndicator(); CreateBackgroundView(); [UnityGetGLViewController().view addSubview:imageView]; [imageView startAnimating]; } void EndMyActivityIndicator() { [imageView removeFromSuperview]; [backgroundView removeFromSuperview]; }
Unity 側のコード
- 以下のスクリプトを任意の GameObject にアタッチする
- StartActivityIndicator で表示、StopActivityIndicator で非表示
using UnityEngine; using System.Collections; using System.Runtime.InteropServices; public class ActivityIndicator : SingletonMonoBehaviour<ActivityIndicator>{ #if UNITY_IOS [DllImport("__Internal")] static extern void InitMyActivityIndicator(); [DllImport("__Internal")] static extern void StartMyActivityIndicator(); [DllImport("__Internal")] static extern void EndMyActivityIndicator(); #endif public void Start() { #if UNITY_IOS InitMyActivityIndicator (); DontDestroyOnLoad(gameObject); #endif } public void StartActivityIndicator(){ #if UNITY_IOS StartMyActivityIndicator(); #endif } public void StopActivityIndicator() { #if UNITY_IOS EndMyActivityIndicator(); #endif } }
Unity でネイティブプラグインを使って ActivityIndicator の画像変更と画面中央での表示(Android編)
はじめに
バージョン
- Unity 4.6.3
- Android Studio 1.2
ソースコード
https://github.com/nirasan/UnityPluginAndroidCustomActivityIndicator/tree/master/activityindicator
ネイティブプラグインの作り方
- Android Studio を使い始めたので、http://tech.admax.ninja/2014/09/10/export-jar-by-android-studio/ こちらにしたがってプラグインを作成した。
プラグインからリソースを参照する
- jar 形式のファイルにはリソースが含まれないため、R.id などで参照するとエラーになる
- リソースを参照したい場合は http://sixeight.hatenablog.com/entry/2013/10/12/215456 こちらの通り、Unity の Assets/Plugins/Android/res にファイルを配置し、プラグイン側からは getResources().getIdentifier() を使って参照するとうまくいく
- 今回はこのおかげで表示したい画像を Unity 側で差し替えやすくなったのが嬉しかった
画面中央に任意の画像の Progress Dialog を表示する
Progress Dialog の拡張クラス
- 前記の通り R による参照を使えないので、初期化時に context から取得している
package com.example.activityindicator; import android.app.ProgressDialog; import android.content.Context; import android.graphics.drawable.AnimationDrawable; import android.os.Bundle; import android.widget.ImageView; public class MyCustomProgressDialog extends ProgressDialog { private static int layoutId; private static int animationId; private static int drawableId; private AnimationDrawable animation; public static MyCustomProgressDialog ctor(Context context) { MyCustomProgressDialog dialog = new MyCustomProgressDialog(context); dialog.setIndeterminate(true); dialog.setCancelable(false); layoutId = context.getResources().getIdentifier("view_custom_progress_dialog", "layout", context.getPackageName()); animationId = context.getResources().getIdentifier("animation", "id", context.getPackageName()); drawableId = context.getResources().getIdentifier("custom_progress_dialog_animation", "drawable", context.getPackageName()); return dialog; } public MyCustomProgressDialog(Context context) { super(context); } public MyCustomProgressDialog(Context context, int theme) { super(context, theme); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(layoutId); ImageView la = (ImageView) findViewById(animationId); la.setBackgroundResource(drawableId); animation = (AnimationDrawable) la.getBackground(); } @Override public void show() { super.show(); animation.start(); } @Override public void dismiss() { super.dismiss(); animation.stop(); } }
呼び出し用クラス
package com.example.activityindicator; import android.app.Activity; import com.unity3d.player.UnityPlayer; public class ActivityIndicator { private static MyCustomProgressDialog _dialog; public static void show() { final Activity activity = UnityPlayer.currentActivity; activity.runOnUiThread(new Runnable() { @Override public void run() { if (_dialog == null) { _dialog = MyCustomProgressDialog.ctor(activity); } _dialog.show(); } }); } public static void hide() { if (_dialog == null) return; final Activity activity = UnityPlayer.currentActivity; activity.runOnUiThread(new Runnable() { @Override public void run() { _dialog.hide(); } }); } }
Unity への取り込み
- http://tech.admax.ninja/2014/09/10/export-jar-by-android-studio/ に従い jar を作成し Unity の Plugins にコピー
- プラグインの res も Unity の Plugins にコピー
Unity 側のコード
void StartActivityIndicator () { #if UNITY_ANDROID AndroidJavaClass androidNativePlugin = new AndroidJavaClass("com.example.activityindicator.ActivityIndicator"); androidNativePlugin.CallStatic("show"); #endif } void StopActivityIndicator () { #if UNITY_ANDROID AndroidJavaClass androidNativePlugin = new AndroidJavaClass("com.example.activityindicator.ActivityIndicator"); androidNativePlugin.CallStatic("hide"); #endif }
paperclip_database の導入メモ
はじめに
- ActiveRecord のオブジェクトに画像ファイルを添付する gem の Paperclip で、画像の保存先をデータベースにする gem の paperclip_database の導入メモ。
- Paperclip は導入済みで、User モデルの avatar カラムに添付画像情報を入れているものとする。
バージョン
- Rails 4.2
- Paperclip 4.2.1
- paperclip_database 2.3.1
インストール
- Gemfile
gem "paperclip_database", "~> 2.0"
- インストール
bundle install
マイグレーションファイルの作成と実行
rails generate paperclip_database:migration User avatar rake db:migrate
モデルの設定
- :storage オプションを追加する
has_attached_file :avatar, :storage => :database
画像を返すアクションとルートの作成
ルートの作成
- routes.rb で users リソースが定義済みの場合、member でルートの追加
resources :users do member do get :avatars end end
アクションの作成
- UsersController で paperclip_database のコントローラ拡張ミックスインのインポートと画像返却用のアクション定義メソッドの追記
class UsersController < ApplicationController include Paperclip::Storage::Database::ControllerClassMethods downloads_files_for :user, :avatar
テンプレートで画像の呼び出し
- @user.image.url(:style) だと URL エンコード済みの文字列が出てきてしまうので、普通にルート名で URL を指定する。
<% image_tag(images_user_url(@user, :style => :medium)) %>
NGUI の UITweener の Animation Curve に任意の曲線を設定する
はじめに
- Unity + NGUI で TweenPosition や TweenAlpha などの UITweener を継承したコンポーネントで Animation Curve の曲線をプログラムから設定する方法についてメモ。
バージョン
- Unity 4.6.1f1
- NGUI 3.7.6
設定方法
- Animation Curve の曲線は AnimationCurve クラスで設定する
- コンストラクタは AnimationCurve(Keyframe[])
- Keyframe は曲線上の任意の点で Keyframe(x, y) で x が時間軸、y が変化量になる。
- Keyframe の x は、開始地点が 0 で、終了地点が 1 になる。duration が 3秒 なら「x:0 = 0秒」で「x:1 = 3秒」となる。
- Keyframe の y は、開始地点が 0 で、終了地点が 1 になる。TweenAlpha で変化前が 1 で変化後の値が 0 なら「y:0 = 1」で「y:1 = 0」となる。
コード例
開始から半分の時間まで変化しない場合
GameObject go = GameObject.Find("Path/To/Any/GameObject"); UITweener tw = TweenAlpha.Begin(go, 1f, 0.5f); tw.animationCurve = new AnimationCurve( new Keyframe( 0f, 0f), new Keyframe(0.5f, 0f), new Keyframe( 1f, 1f) );
点滅する場合
GameObject go = GameObject.Find("Path/To/Any/GameObject"); UITweener tw = TweenAlpha.Begin(go, 1f, 0.5f); tw.animationCurve = new AnimationCurve( new Keyframe( 0f, 0f), new Keyframe(0.2f, 1f) new Keyframe(0.4f, 0f), new Keyframe(0.6f, 1f) new Keyframe(0.8f, 0f), new Keyframe( 1f, 1f) );
Keyframe に入る角度と出る角度の指定をする
- Keyframe のコンストラクタで Keyframe(x, y, inTangent, outTangent) というパターンが有り inTangent と outTangent で曲線の入る角度と出る角度を指定できる。
- inTangent, outTangent の単位は tangent なので、角度を指定したい場合は変換する必要がある。
角度からタンジェントへの変換
// 角度からタンジェントへ float DegreesToTan (float degrees) { return Mathf.Tan(DegreesToRadians(degrees)); } // 角度からラジアンへ float DegreesToRadians (float degrees) { return degrees * Mathf.PI / 180f; }
コード例
イーズアウト
GameObject go = GameObject.Find("Path/To/Any/GameObject"); UITweener tw = TweenAlpha.Begin(go, 1f, 0.5f); tw.animationCurve = new AnimationCurve( new Keyframe(0f, 0f, 0f, DegreesToTan(70f)), new Keyframe(1f, 1f, DegreesToTan(10f), 0f) };
イーズイン
GameObject go = GameObject.Find("Path/To/Any/GameObject"); UITweener tw = TweenAlpha.Begin(go, 1f, 0.5f); tw.animationCurve = new AnimationCurve( new Keyframe(0f, 0f, 0f, 0f), new Keyframe(1f, 1f, DegreesToTan(70f), 0f) };
波を描く
GameObject go = GameObject.Find("Path/To/Any/GameObject"); UITweener tw = TweenAlpha.Begin(go, 1f, 0.5f); tw.animationCurve = new AnimationCurve( new Keyframe( 0f, 0f, 0f, 0f), new Keyframe(0.25f, 0.5f, DegreesToTan(-85f), DegreesToTan(-85f)), new Keyframe(0.5f, 0.5f, DegreesToTan(-85f), DegreesToTan(-85f)), new Keyframe(0.75f, 0.5f, DegreesToTan(-85f), DegreesToTan(-85f)), new Keyframe( 1f, 1f, 0f, 0f) };