20200327のUnityに関する記事は3件です。

【Unity】MonoScript is registered as both Editor and Runtime script! というエラーについて

はじめに

何の前触れもなく次のようなエラーが出ることがありました。

MonoScript is registered as both Editor and Runtime script!

このようなエラーが何故起こるのか、そしてどういう意味があるのかを調べてみたところ、Unity Forumで言及されていたので、その内容を和訳してメモしておきたいと考え、この記事を書きました。

エラーの原因

  • Unityの内部では、どのスクリプトがエディタスクリプトで、どのスクリプトがランタイムスクリプトであるかを記憶するキャッシュがある
  • 同じスクリプトがランタイムキャッシュとエディタキャッシュの両方に存在するせいで起こるバグがある。(このバグはエディタとランタイムアセンブリ間でスクリプトを移動する際に発生する)
  • そのバグが起こるのはどのようなケースかを調べるためにこのエラーが存在する

解決法

エディタを再起動する。(エディタを再起動するとキャッシュが再構築されるため)

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityでFF7みたいなエンカウント演出を実装する

はじめに

今制作しているゲームにエンカウント演出(シュワアアァァって音が鳴りながら画面になんかエフェクトがかかってフェードアウトしていくようなやつ)を付けたいなと思ったので、実装してみました。

今回はOpecCVを利用してエンカウント演出を実装してみた例を紹介します。

やりかた

Unity 2018.2.0f2で実装しています。
最新のバージョンでもたぶん動くと思います。

アセットのインポート

OpenCV plus Unityをプロジェクトにインポートします
コンソールにエラーがごちゃごちゃ出る場合は、PlayerSettingsを開いてOtherSettingsの"Allow 'unsafe' Code"にチェックを入れます。

GameObjectの準備

HierarchyからCanvasを作成して、演出用のImageも作成します。
hierarhy.JPG

ImageのRect TransformのAnchorをstretchにしておけばそれだけで画面全体を覆うUIになります。
image.JPG

Scriptの準備

以下のScriptを用意します。

EncountEffectComponent.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using OpenCvSharp;

/// <summary>
/// FF7風のエンカウント演出
/// </summary>
public class EncountEffectComponent : MonoBehaviour {

    [SerializeField]
    private Image targetImage;

    [SerializeField, Range(0, 720f)]
    private float rotationSpeed = 180f;

    [SerializeField, Range(0, 10f)]
    private float zoomSpeed = 2.5f;

    [SerializeField, Range(0.1f, 1f)]
    private float pixelRate = 0.5f;

    [SerializeField, Range(0f, 1f)]
    private float sourceAlpha = 0.80f;

    [SerializeField, Range(0f, 1f)]
    private float outAlpha = 0.18f;

    private bool isStartCameraEffect;

    public bool IsEffect {
        get {
            return this.isStartCameraEffect;
        }
    }

    protected void Start () {
        this.targetImage.enabled = false;
    }

    public void StartEffect () {
        this.StopAllCoroutines();
        this.isStartCameraEffect = true;
        this.StartCoroutine(this.EffectCoroutine());
    }

    public void StopEffect () {
        this.targetImage.enabled = false;
        this.isStartCameraEffect = false;
        this.StopAllCoroutines();
    }

    private IEnumerator EffectCoroutine () {
        yield return new WaitForEndOfFrame();

        this.targetImage.enabled = true;

        var texture = new Texture2D(Screen.width, Screen.height, TextureFormat.RGBA32, false);
        texture.ReadPixels(new UnityEngine.Rect(0, 0, Screen.width, Screen.height), 0, 0);
        TextureScale.Bilinear(texture, (int)(Screen.width * this.pixelRate), (int)(Screen.height * this.pixelRate));
        texture.Apply();

        this.targetImage.sprite = Sprite.Create(texture, new UnityEngine.Rect(0,0, texture.width, texture.height), new Vector2(0.5f, 0.5f));

        var center = new Point2f(texture.width / 2, texture.height / 2);

        Mat mat = OpenCvSharp.Unity.TextureToMat((Texture2D)this.targetImage.sprite.texture);

        while (true) {
            yield return null;

            Mat tmpMat = this.RotateAndResize(mat, center);
            Mat outMat = new Mat();

            Cv2.AddWeighted(mat, this.sourceAlpha, tmpMat, this.outAlpha, 0f, outMat);
            var outTexture = OpenCvSharp.Unity.MatToTexture(outMat);
            this.targetImage.sprite = Sprite.Create(outTexture, new UnityEngine.Rect(0, 0, outTexture.width, outTexture.height), new Vector2(0.5f, 0.5f));

            mat = outMat;
        }
    }

    private Mat RotateAndResize (Mat src, Point2f center) {
        var dest = new Mat();
        var rMat = Cv2.GetRotationMatrix2D(center, this.rotationSpeed * Time.deltaTime, 1f + (this.zoomSpeed * Time.deltaTime));
        Cv2.WarpAffine(src, dest, rMat, src.Size(), InterpolationFlags.Cubic);
        return dest;
    }

}

だいたいの内容としては、コルーチンを回してCv2.GetRotationMatrix2D()で回転とズームを施した画像をCv2.AddWeighted()でいい感じに合成しているものとなります。

画像処理に関してはこことかこことかが参考になりました。

以下の処理に関しては、TextureScale - Unify Community Wikiからソースコードを拝借してProjectに置かないと動作しません。ここではtextureのリサイズを行っています。

TextureScale.Bilinear(texture, (int)(Screen.width * this.pixelRate), (int)(Screen.height * this.pixelRate));

コンポーネントのアタッチ

適当なGameObject(CanvasとかでOK)にEncountEffectComponentをアタッチして、インスペクタから設定をいじります。

ins.JPG

  • Target Image
    • HierarchyからImageを選択します。
  • Rotation Speed
    • 回転/sを設定します。
  • Zoom Speed
    • 拡大率/sを設定します。
  • Pixel Rate
    • 出力される解像度の比率を設定します。解像度が高いとクソ重いです……。
  • Source Alpha
    • 前フレーム(エフェクトがかかる前)の画像の透過度
  • Out Alpha
    • エフェクトがかかった後の画像の重なり具合

Source AlphaとOut Alphaの合計が1未満だと徐々にブラックアウトします。合計が1を超えるとホワイトアウトします。お好みで。

実行

適当なScriptでも作って、EncountEffectComponent.StartEffect()を呼べば動きます。StopEffect ()を呼ぶと演出がOFFになります。

課題

設定によっては画像の隅っこが暗くなる

Rotation Speed を上げすぎたりすると、画面の隅っこが暗くなってしまいます。

encount.JPG

出力対象をあらかじめズームした状態にするなどの実装をすれば解決できそうです。

解像度が高いと激重

カメラの解像度と同じくらいにしたりすると多分恐ろしくカクつきます。スマホだと特にヤバそう。

処理の最適化とかその辺については今回は触れていないので、だ、誰か……。

終わりに

今回はFF7風のエンカウント処理の例を紹介しましたが、合成の処理をいろいろ弄れば様々なゲームのエンカウント処理っぽいものを実装できそうです。

本当は「バテン・カイトス」というゲームのエンカウント処理(画面が波打ってから吸い込まれるやつ)を実装したかったのですが、実装方法がまるでわからず断念(;ω;)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】簡単なTimer機能の実装したときにやったこと

Unityでタイマー機能をググってみたのだけれどもいまいちピンとくるものがなかったのでひとまずやったことをメモする。

事前準備

CanvasにTimeオブジェクトを作成してCanvasと紐付けをする。

当初Scoreだけを実装していたのでScoreの値がCanvasに直接くっついている状態だった。

スクリプトに新しくTimerスクリプトを作成

仕様は以下のようにした

  • 60秒からカウントダウンする。
  • ScoreUIの下に整数部分だけを表示する。
  • カウントが0になったらカウントをストップする。

60秒からカウントダウンする。

まず固定値で1分を設定する。

Updateで使うTime.deltaTimeがInt型だとエラーになってしまうのんでFloat型で60秒を宣言する。

private float countTime = 60;

描画するテキストオブジェクトは以下を宣言する。

public Text timeCount;

最初は単純にカウントダウンができるかを確認したかったのでUpdateをこんな感じで実装してみた。

    void Update()
    {
        countTime -= Time.deltaTime;   
     timeCount.text = countTime.ToString();
    }

こんな感じになる

ScoreUIの下に整数部分だけを表示する。

しかし、今の実装だと小数点以下も見えてしまうので仕様上と違う

ここ描画は整数のみの部分を切り出した実装としたいのでコードの追加する。

//整数とする変数を追加
private int second;

    // Update is called once per frame
    void Update()
    {
        countTime -= Time.deltaTime;
        second = (int)countTime;
       //(int)countTimeでint型に変換して表示させる。
            timeCount.text = second.ToString();
    }

こんな感じに整数でTimeが表示された。

カウントが0になったらカウントをストップする。

整数までカウントダウンできるようになったが、このままだとマイナスのカウントが永遠と描画されてしまう。

タイマーをストップする基準は
* カウントが0になったらそれ以上カウントしない
なのでif文でカウントが0になったら0のままにする制御を追加する。

        if (countTime <= 0)
        {
            second = 0;
            timeCount.text = "0";
        }

一度テストしてみる。
ここでは60秒も待つと長いので、5秒で0のままになるかを数える。

描画は0で止まっているのでOK

今後の課題

  • 今はタイマーを動かすことだけしかできないので、今後ゲームオーバーの制御などをするときの制御などからアクセスしやすいように改良したい。
  • Timeの箇所とタイマー数値オブジェクトを今回切り離して実装したけれども後々考えるとレンダリングをスクリプトで書けば一つにできるやんと後で気づいたので修正したい。

development/Starry_Hill

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む