nirasan's tech blog

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

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];
}
画像の取り込み手順
  • iOS 側で表示する画像は XCode のプロジェクト内に取り込む必要がある
  • 上記のコードでは前回の Plugins/Android/res/drawable/*.png を使いまわす想定
  • 以下手順
    • Unity で Platform を iOS にしてビルド
    • XCode でプロジェクトを開く
    • Images.xcassets を選択し
    • 中央ペインで右クリック
    • Import を選んで前記の画像を選択して Open で取り込む

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 で OS ネイティブの読み込み中アニメーションを表示するメソッドとして Handheld.StartActivityIndicator がある。
  • これは Android では画面左上に表示されてしまうので、画面中央に表示させられるようにネイティブプラグインを作ったメモ。

バージョン

ネイティブプラグインの作り方

プラグインからリソースを参照する

  • 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 への取り込み

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)
};

NGUI で UILabel の文字量に従って縦幅だけ変動させるスクリプト

using UnityEngine;
using System.Collections;

/// <summary>
/// UILabel の横幅を固定して text に従った縦幅に設定するスクリプト
/// </summary>
public class UILabelHeightFitter : MonoBehaviour {

    /// <summary>
    /// リサイズ対象の UILabel
    /// </summary>
    public UILabel label;

    private string beforeText = "";

	void Update () {
        // UILabel.text が更新されるたびにリサイズを実行
        if (label.text != beforeText) {
            ResizeHeight();
            beforeText = label.text;
        }
	}

    /// <summary>
    /// UILabel.text の文字量に従って UILabel.height を変更する。
    /// 簡単のため「UILabel.fontSize == 文字の幅 == 文字の高さ」という前提で計算しているので、
    /// 半角全角の混在や可変幅フォントの使用によりずれが生じる場合がある
    /// </summary>
    void ResizeHeight () {
        int line = 1;
        int chara = 0;
        int maxChara = label.width / (label.fontSize + label.spacingX);
        string text = label.text;
        for (int i = 0; i < text.Length; i++) {
            if (text[i] != '\n') {
                chara++;
                if (chara >= maxChara) {
                    chara = 0;
                    line++;
                }
            } else {
                chara = 0;
                line++;
            }
        }
        label.height = (label.fontSize + label.spacingY) * line;
    }
}

自分用 RubyMine ショートカットメモ

ジャンプ
宣言へジャンプ
Command + b
使用箇所のリスト
Alt + F7
タブ移動
左へ
Command + Shift + @
右へ
Command + Shift + [

NGUI の ScrollView で気持ちのいいスクロールを実装する

気持ちが良いスクロールとは?

  • 今回は「ちょっとスワイプするだけで次の要素にスッと切り替わる」ものを実装する。

バージョン情報

  • Unity 4.6.1f
  • NGUI 3.7.6

ヒエラルキー

UI Root
	ScrollView
		Grid

ScrollView の作成

  • メニューの [NGUI] > [Create] > [Scroll View] で作成

Grid の作成

  • メニューの [NGUI] > [Create] > [Grid] で作成
  • UICenterOnChild をアタッチ

オプション設定

  • UIGrid の Sorting を None 以外にする
    • わからなければ Custom にする
  • UICenterOnChild の NextPageThreshold を 1 にする

動作確認

  • Grid 配下に任意の UIDragScrollView とコライダーがアタッチされたオブジェクトを追加して動作を確認する。

解説

  • UICenterOnChild.NextPageThreshold が 0 より大きく、UIGrid の Sorting が None 以外である時、スワイプした距離が NextPageThreshold より大きければ UICenterOnChild により次あるいは前の要素に切り替わる。

注意点

  • ScrollView をループさせるスクリプトの UIWrapContent が有効であると、この処理は正常に行われなかった。