【Unity】カウントダウンを実装する

レースゲームでお馴染みのカウントダウンを実装していきます。

必要なものは3つ
①カウントダウンのアニメーション
②カウントダウン終了後の処理
③カウントダウン終了時のトリガー

①カウントダウンのアニメーションを作成する

カウントダウンのアニメーションを作成します。
f:id:dev-oma:20170730224133p:plain
今回は、カウントダウンを示す数字が
右から左へ流れるようなアニメーションを作成しました。
www.youtube.com

イベントハンドラ用の関数を書く

カウントダウン終了後に行いたい処理

  • 車を走らせる
  • 各ボタンを有効にする 等…

を記述した関数を用意しておきます。

public class Hoge : MonoBehaviour {
    /// <summary>
    /// カウントダウン終了後に呼ばれる
    /// </summary>
    public void CompletedCountDown() {
        //カウントダウン終了後に行いたい処理
        ・・・
    }
}

③カウントダウン終了時のトリガーをつくる

アニメーションイベントを利用します。
先ほど用意したイベントハンドラ用の関数を持つスクリプトを、
①カウントダウンのアニメーションが登録されたAnimatorを持っているオブジェクトにアタッチします。


次に、カウントダウンアニメーションのタイムラインを開き、
カウントダウン終了時のタイミングにカーソルを合わせて右クリックし、「Add Animation Event」を選択します。
f:id:dev-oma:20170730231544p:plain


すると白いタグのようなものが表示されます。
f:id:dev-oma:20170730231950p:plain


こちらの白いタグをクリックしてInspectorを開き、
「Function」に②で作成した関数を選択します。
f:id:dev-oma:20170730233337p:plain:w300


選択欄に表示されていない場合は、

この辺をチェックしてみてください。


そんな訳でカウントダウン終了後に車を走らせてみました。
www.youtube.com

対象のオブジェクトを振動させるクラス作ったよー

コード

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 振動タイプ
/// </summary>
internal enum VibrateType
{
    VERTICAL,
    HORIZONTAL
}

/// <summary>
/// 対象オブジェクトの振動を管理するクラス
/// </summary>
public class VibrateController : MonoBehaviour {

    [SerializeField] private VibrateType vibrateType;          //振動タイプ
    [Range(0, 1)] [SerializeField] private float vibrateRange; //振動幅
    [SerializeField] private float vibrateSpeed;               //振動速度

    private float initPosition;   //初期ポジション
    private float newPosition;    //新規ポジション
    private float minPosition;    //ポジションの下限
    private float maxPosition;    //ポジションの上限
    private bool directionToggle; //振動方向の切り替え用トグル(オフ:値が小さくなる方向へ オン:値が大きくなる方向へ)

    // Use this for initialization
    void Start () {
        //初期ポジションの設定を振動タイプ毎に分ける
        switch (this.vibrateType) {
            case VibrateType.VERTICAL:
                this.initPosition = transform.localPosition.y;
                break;
            case VibrateType.HORIZONTAL:
                this.initPosition = transform.localPosition.x;
                break;
        }

            this.newPosition = this.initPosition;
            this.minPosition = this.initPosition - this.vibrateRange;
            this.maxPosition = this.initPosition + this.vibrateRange;
            this.directionToggle = false;
    }
	
    // Update is called once per frame
    void Update () {
        //毎フレーム振動を行う
        Vibrate ();
    }

    private void Vibrate() {

        //ポジションが振動幅の範囲を超えた場合、振動方向を切り替える
        if (this.newPosition <= this.minPosition ||
            this.maxPosition <= this.newPosition) {
            this.directionToggle = !this.directionToggle;
        }

        //新規ポジションを設定
        this.newPosition = this.directionToggle ? 
            this.newPosition + (vibrateSpeed * Time.deltaTime) :
            this.newPosition - (vibrateSpeed * Time.deltaTime);
        this.newPosition = Mathf.Clamp (this.newPosition, this.minPosition, this.maxPosition);

        //新規ポジションを代入
        switch (this.vibrateType) {
            case VibrateType.VERTICAL:
                this.transform.localPosition = new Vector3 (0, this.newPosition, 0);
                break;
            case VibrateType.HORIZONTAL:
                this.transform.localPosition = new Vector3 (this.newPosition, 0, 0);
                break;
        }
    }
}

使い方

振動させたいオブジェクトに上記スクリプトをアタッチします。


f:id:dev-oma:20170723162852p:plain:w400
今回はレースゲームで出てくる車の車体にくっつけます。

次に各プロパティを設定します。
f:id:dev-oma:20170723163350p:plain

  • Type...振動タイプ。VERTICALが縦振動で、HORIZONTALが横振動。
  • Range...振動させる範囲。値が大きいほど揺れ幅がでかくなる。
  • Speed...振動速度。値が大きいほど時間あたりの振動回数が増える。

実行した結果がこちら↓

とあるサイトの文字化けを修正した話

とあるサイトの予約ページで、入力した値が全て文字化けしているので修正してほしいとの依頼があった。


確認してみたところ、サイト内の文字は全てShift_JISで扱われているが
POSTされた値がUTF-8で飛んでくるため文字化けが発生している様子。
(PHP5.6から変わったっぽい?)


  • ini_set('default_charset', 'Shift_JIS')

  • mb_internal_encoding('Shift_JIS')

  • <form method='post' accept-charset='Shift_JIS'>

  • Shift_JISに変換するよう色々試してみたがうまくいかず。


    表示する値とPOSTする値を変換したら行けた

    mb_convert_encoding($value, 'Shift_JIS')
    

    しかし、このやり方だと修正箇所が相当な数になってしまうため
    どうしてようかと悩んでいたところ・・・


    便利な関数がありました。

    string mb_convert_variables ( string $to_encoding , mixed $from_encoding , &$vars )

    $to_encoding :変換先の文字エンコード
    $from_encoding:変換元の文字エンコード
    $vars     :変換したい文字列を含む配列


    この関数は配列内の文字列を全て変換したい文字コードに変換してくれます。
    (第3引数は参照渡しの為、代入する必要はない)

    $data = $_POST['data'];
    
    //文字コード変換
    mb_convert_variables('Shift_JIS', 'UTF-8', $data);
    
    ―終了!


    【Unity】お気に入りリンク集

    ●Unity開発で気を付けておきたい7つの事

    www.shibuya24.info

    ●Unity AssetStoreまとめ

    assetsale.hateblo.jp

    Unityまとめ

    unity-matome.com

    アプリに含めるアセットを抑えて、アプリサイズを小さくする

    tsubakit1.hateblo.jp

    【Swift】WKWebView まとめ

    SwiftでのWKWebView実装方法について自分なりにまとめてみました。

    ①WKWebViewを作成する

    import UIKit
    import WebKit
    
    class ViewController: UIViewController, WKNavigationDelegate {
    
        @IBOutlet weak var contentView: UIView! //WebViewの表示領域
    
        private var webView: WKWebView!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            
            //WkWebView 生成
            self.webView = WKWebView(frame: CGRect.zero)
    
            //デリケート設定
            self.webView.navigationDelegate = self
    
            //フリップでの戻る・進むを有効にする
            self.webView.allowsBackForwardNavigationGestures = true
            self.contentView.addSubview(self.webView)
    
            //ページ読み込み
            let url = NSURL(string: "https://www.google.co.jp/")
            let urlRequest = URLRequest(url: url as! URL)
            self.webView.load(urlRequest)
        }
    
        override func viewDidLayoutSubviews() {
            //WKWebView リサイズ
            self.webView.frame = CGRect(origin: CGPoint.zero, size: self.contentView.frame.size)
        }
    }
     
    


    注意点

  • WebKitをインポートする
  • ViewControllerにWKNavigationDelegateを継承させておく
  • xib側でViewControllerの「Adjust Scroll View Insets」項目のチェックを外しておく
     (ここにチェックが入っていると、ナビゲーションバーの高さ分レイアウトがずれてしまう)

  • ②各デリケートの処理

    // MARK: - WKWebView Delegate
        
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        let requestURL: URL? = navigationAction.request.url;
            
        // 電話対応
        if(requestURL?.scheme == "tel") {
            var telNumber = String(describing: requestURL!)
            telNumber = telNumber.substring(from: telNumber.index(telNumber.startIndex, offsetBy: 4))
            //アラートを出す
            ・・・
            decisionHandler(.cancel)
            return
        }
        // メール対応
        if(String(describing: requestURL).contains("http://") == false &&
            String(describing: requestURL).contains("https://") == false) {
            if UIApplication.shared.canOpenURL(requestURL!){
                UIApplication.shared.openURL(requestURL!)
            }
            decisionHandler(.cancel)
            return
        }
        // target=blankも開く
        if (navigationAction.navigationType == WKNavigationType.linkActivated) {
            if (navigationAction.targetFrame == nil || navigationAction.targetFrame!.isMainFrame) {
                self.mainWebView.load(URLRequest(url: requestURL!))
                decisionHandler(.cancel);
                return;
            }
        }
        decisionHandler(.allow)
    }
    
    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
        // ネットワークインジケータを表示
        UIApplication.shared.isNetworkActivityIndicatorVisible = true;
    }
    
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        // ネットワークインジケータを非表示
        UIApplication.shared.isNetworkActivityIndicatorVisible = false;
    }
    
    func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
        //ユーザーキャンセルによるエラーは無視する
        if((error as NSError).code != NSURLErrorCancelled) {
            //インジケータ非表示
            UIApplication.shared.isNetworkActivityIndicatorVisible = false;
            //エラー時の処理
            ・・・
        }
    }
    


    ポイント

  • 電話、メール、target=blankへの対応を行う
  • 電話の時はopenURLを叩く前に、アラートを出して「電話をかけてもよろしいでしょうか?」等の確認を挟む
  • 通信中かどうかが分かるように、インジケータを表示させる
  • エラー処理ではユーザーキャンセルによるエラーを無視する

  • ③iOS10に対応する

    input type=“file” 等のリンクをタップした時に写真ライブラリやカメラにアクセスする場合がある。 iOS10では、info.plistに使用目的を記述しておかないとアプリがクラッシュするので注意。
    f:id:dev-oma:20170630112013p:plain

    参考:http://dev.classmethod.jp/smartphone/iphone/ios10-privacy-data-purpose-description/

    TeamViewerを使ってPCからAndroid端末を遠隔操作する

    PCからAndroid端末をリモートコントロールできないか、と依頼された事があったので。

     

    ①操作される側(Android端末)の準備

    初めに、遠隔操作を行いたい端末に

    TeamViewer QuickSupport」をインストールします。

     

    インストールが完了したら、TeamViewer QuickSupportを起動します。

    起動時にアドオンのダウンロードを促すダイアログが表示される場合があります。

    f:id:dev-oma:20170510152920j:plain

     

    インストールしたアプリを実行すると、下のような画面が表示されます。

    「ダウンロード」ボタンを押すとPlayストアに移動するので、必要なアドオンをインストールします。

     

    TeamViewer QuickSupportを起動すると下のような画面が表示されます。

     

    f:id:dev-oma:20170510152009j:plain

     

    「使用中のID」欄に、IDが表示されていることを確認できたら

    Android側の準備は終了です。

     

    TeamViewer QuickSupportは立ち上げたままにしておきます。

     

    ②操作する側(PC)の準備

    遠隔操作を行うパソコンに「TeamViewer」をインストールします。

    Windows版 / Mac版と別れているので、必要な方をインストール。)

     

    TeamViewerを立ち上げると、このような画面が表示されます。

    f:id:dev-oma:20170510154332p:plain

    画像赤枠部分にTeamViewer QuickSupportで表示されていたIDを入力します。

    「パートナーに接続」ボタンを押すと、Android端末にリモートコントロールの許可を求めるダイアログが表示されるので、「許可」を押してリモートコントロールを開始します。

    ③実行

    f:id:dev-oma:20170526142119p:plain

     

    表示されているシミュレーターはマウスで操作することが可能です。

    シミュレーターを操作すると、実機でも操作が同期されていることが確認できるかと思います。