20190310のC#に関する記事は8件です。

【Unity(C#)】ゲームオブジェクトを毎回違う場所に配置する方法

  
  
  
この記事は

『プログラミング完全未経験からUnityでの開発現場に迎え入れてもらえた世界一の幸せ者』

の記事です。そのつもりでお読みください。

ランダムではない

毎回違う場所に配置すると書きましたが、
ランダムとは少しだけ違います。

私がやりたかったことは2回連続で同じ場所には絶対に出ないという実装です。

なのでちょっと面倒でした。

↓↓↓補足(2019.3/11)↓↓↓

同じエリアに連続出現しないだけで座標は毎回ランダムにしたい

これです!まさにこれ!
コメントありがとうございます!

考え方

まずゲームオブジェクトを出現させたい位置を分割します。
randomArea.png

上のとてもわかりやすい図のように分けた場合、
もし0に出現したら次は必ず1、2、3のどこかに出現するようにします。

もっと効率の良い方法があるのかもしれませんが、ちょっと急いでたのでこのやり方で実装しました。

値を返すメソッドを用意

ランダムな値を渡すとその値と同じ値を返すメソッドを用意しました。
これにより、一度出現したエリアの番号を記録できます。

 int? RandomArea(int patternNum)
    {
        if (patternNum == 0)
        {
            holeParentPosition = new Vector3(randomX_A, holeParentPosition.y, randomZ_A);
            this.gameObject.transform.localPosition = holeParentPosition;

            return 0;
        }

        if (patternNum == 1)
        {
            holeParentPosition = new Vector3(randomX_B, holeParentPosition.y, randomZ_B);
            this.gameObject.transform.localPosition = holeParentPosition;

            return 1;
        }

        if (patternNum == 2)
        {
            holeParentPosition = new Vector3(randomX_C, holeParentPosition.y, randomZ_C);
            this.gameObject.transform.localPosition = holeParentPosition;

            return 2;
        }

        if (patternNum == 3)
        {
            holeParentPosition = new Vector3(randomX_D, holeParentPosition.y, randomZ_D);
            this.gameObject.transform.localPosition = holeParentPosition;

            return 3;
        }

        return null;

    }

最終的なコード

    public GameObject hole;

    Vector3 holeParentPosition;

    int[] patternNumber = { 0, 1, 2, 3 };
    int patternArray;


    float randomX_A;
    float randomZ_A;

    float randomX_B;
    float randomZ_B;

    float randomX_C;
    float randomZ_C;

    float randomX_D;
    float randomZ_D;

    void Start()
    {
        holeParentPosition = this.gameObject.transform.localPosition;
    }


 void Update()
    {
        patternArray = Random.Range(0, patternNumber.Length);

        randomX_A = Random.Range(0.5f, 4.0f);
        randomZ_A = Random.Range(-5f, -1.5f);

        randomX_B = Random.Range(0.5f, 4.0f);
        randomZ_B = Random.Range(-9f, -5.5f);

        randomX_C = Random.Range(-4.0f, -0.5f);
        randomZ_C = Random.Range(-9f, -5.5f);

        randomX_D = Random.Range(-4.0f, -0.5f);
        randomZ_D = Random.Range(-5f, -1.5f);

        //コルーチン開始(一回で抜ける)
        if (ThisProjectSingleton.Instance.isGameStart)
        {
            StartCoroutine(HoleRandomCoroutine());
            ThisProjectSingleton.Instance.isGameStart = false;
        }


    }

    int? RandomArea(int patternNum)
    {
        if (patternNum == 0)
        {
            holeParentPosition = new Vector3(randomX_A, holeParentPosition.y, randomZ_A);
            this.gameObject.transform.localPosition = holeParentPosition;

            return 0;
        }

        if (patternNum == 1)
        {
            holeParentPosition = new Vector3(randomX_B, holeParentPosition.y, randomZ_B);
            this.gameObject.transform.localPosition = holeParentPosition;

            return 1;
        }

        if (patternNum == 2)
        {
            holeParentPosition = new Vector3(randomX_C, holeParentPosition.y, randomZ_C);
            this.gameObject.transform.localPosition = holeParentPosition;

            return 2;
        }

        if (patternNum == 3)
        {
            holeParentPosition = new Vector3(randomX_D, holeParentPosition.y, randomZ_D);
            this.gameObject.transform.localPosition = holeParentPosition;

            return 3;
        }

        return null;

    }

    IEnumerator HoleRandomCoroutine()
    {
        int? tmpNum = 0;
        int? memoryNum = 0;
        while (/*ゲームオーバーの条件を記述*/)
        {
            yield return new WaitForSeconds(0.5f);
            tmpNum= RandomArea(patternNumber[patternArray]);

            //もしランダムな数字( tmpNum)が前回(memoryNum)と一致したら通過。別の数字をtmpNumに代入して抜ける
            while (tmpNum == memoryNum)
            {
                yield return new WaitForEndOfFrame();
                tmpNum = RandomArea(patternNumber[patternArray]);
            }
            memoryNum = tmpNum;
            hole.SetActive(true);


            //ホールにボールが入るまで待つ
            yield return new WaitUntil(() => ThisProjectSingleton.Instance.isHoleEnter);
            ScoreText.ScoreAdd();

            //ホールが消える
            yield return new WaitForSeconds(0.5f);
            hole.SetActive(false);
            ThisProjectSingleton.Instance.isHoleEnter = false;
        }
    }

いま自分で読み返しても読みづらいしもっといい方法がありそうです。
こんな方法もあるよ~って方、コメントください。

unity1week

unity1weekという企画がありまして、
その名の通りUnityを用いて一週間でゲームを作ろうというおもしろい企画です。

私も参加するつもりなのですが、
そこで作るのはWebGLというブラウザ上で起動する形式です。

せっかく作ったのにビルドとかアップロードの仕方がわからない!
と本番でならないようにunity1dayと称して、
簡単なゲームを作りunityroomにテストアップロードしました。

今回紹介したゲームオブジェクトを毎回違う場所に配置する方法も用いているので
よかったら覗いてみてください。↓

Lava is hot(訳:溶岩はあったかいよ)

人のコード見た後にそのゲームをプレイするの結構おもしろいです。

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

Visual Studio Codeを使い、C#の開発環境を途中まで整える

今回はMicrosoftにより開発されたソースコードエディターであるVisual Studio Codeを使ってC#の統合開発環境を整えるところまでまとめたいと思います。

具体的な手順は以下の通りです。

  1. Visual Studio Codeをインストールする。
  2. .NET Core SDKをインストールする。
  3. Visual Studio Codeを開き、C#の拡張機能をインストールする。
  4. 試しにHello Worldのコードを実行してみる。

Visual Studio Codeをインストールする

まずVisual Studio Codeをインストールします。
以下のリンクを開き、「Download for [OS名] Stable Build」という
緑色のリンクを選択するとVisual Studio Codeのダウンロードが開始されます。

Visual Studio Code

右の記号を選択すると、詳細なインストーラの選択が可能となります。

ダウンロードしたインストーラを起動し、Visual Studio Codeのインストールを開始します。
各種設定は今回はデフォルトのままで行いました。

.NET Core SDKをインストールする

次に、アプリケーション制作のためのプラットフォームを提供する、.NET Core SDKをインストールします。
以下のリンクから「Download .NET Core SDK」を選択し、ダウンロードします。

.NET Core SDK

先ほどと同じように、ダウンロードしたインストーラを起動し、インストールを開始します。
インストールの際に特に設定は要りません。

C#の拡張機能をインストールする

そして、C#の開発環境を整えるためにC#の拡張機能をインストールします。
Visual Studio Code から Marketplaceでインストールできるようですが、
今回はWebブラウザからインストールします。

Visual Studio Codeをインストールした状態で、
下記のリンクからC#の拡張機能のページへ移り、「Install」を選択します。
出現したポップアップ画面の「Visual Studio Codeを実行する」を選択します。

C#の拡張機能

するとVisual Studio Codeが開かれ、C#の拡張機能ページが表示されるため、
再度「Install」を選択します。選択してInstalledになるまで待ちましょう。

Installedの表示になったらokです。

試しにHello Worldのコードを実行してみる。

Visual Studio Codeを開き、
左上のExplorerアイコンをクリックして、Open Folderを選択します。

コードを入れる適当なフォルダーを選択します。
フォルダー名はVisual Studio Codeにおけるプロジェクト名となります。

次に、上メニューより[View]>[Terminal]と選択し、(Ctrl+@でも可)
下部分に表示されたTerminal上で

 dotnet new console 

と実行します。
その際に、もし下記のエラーが出力された場合、

dotnet : 用語 'dotnet' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラムの名前として認識されません。名前が正しく記
述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確認してから、再試行してください。
発生場所 行:1 文字:1

以下の手順を踏むと、 解消される場合があります。

  1. [プログラムと機能]から[アンインストールまたは変更]へと移り、
  2. 最新版のMicrosoft Visual C++ Redistributable (x86)を選択し、修復を選択。
  3. 次に最新版のMicrosoft Visual C++ Redistributable (x64)を選択し、修復を選択。
  4. 最後に.NET Core SDK 1.0.1.を選択し、修復を選択。
  5. 4の修復が完了したら、Visual Studio Codeを再起動。

参考: https://stackoverflow.com/questions/40568758/dotnet-is-not-recognized-as-the-name-of-a-cmdlet

また、Terminalの背景色が蛍光ペンで塗りつぶされたようにおかしくなった場合には、PowerShellのプロパティから背景色を黒に合わせると(デフォルトのカラースキームのTerminalの背景色に合わせると)表示が正常に戻ります。

さて、「dotnet new console」とコマンドを打った時点で、既にHello Worldのコードは完成しています。(Program.ps)

そこでTerminalで

dotnet run

と実行すると、「Hello World」と出力されます。

まとめ

今回はここまでにしたいと思います。

デバッグやクラスの追加はまた次回、ということで
自分もさらに勉強して知識や経験を吸収したいと思います。

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

WPF の ListBox と DataGrid で Dcitionary をバインドする

WPF の ListBox と DataGrid で Dcitionary をバインドする

名前と値を Dictionary を使ってバインドしようとして少しはまって結果的に ViewModel に Dictionary の Value についての Binding 設定を一切書かずにすっきりできたのでメモ。

開発環境

  • Windows 10
  • Visual Studio 2017

ソリューションの拡張機能

  • Prism.Unity

新規プロジェクトの作成

Template Pack を参照ください。


RegionManagerにViewを登録

MainWindow に ContentView を登録

MainWindowViewModel.cs
        public MainWindowViewModel(IRegionManager regionManager)
        {
            regionManager.RegisterViewWithRegion("ContentRegion", typeof(ContentView));
        }

Person クラス を作成

Models フォルダを作り Person クラスを作成

Person.cs
    class Person
    {
        public string Name { get; set; }

        public string Sex { get; set; }

        public int Age { get; set; }
    }

ContentViewModel を ViewModels に作成

バインド用プロパティを定義してコンストラクタでサンプルデータ設定

ContentViewModel.cs
        public ContentViewModel()
        {
            this.PersonDictionaries = new Dictionary<string, Person>()
            {
                {"長男", new Person(){Name="Ichiro", Sex ="男", Age=10, } },
                {"次男", new Person(){Name="Jiro", Sex ="男", Age=9, } },
                {"三男", new Person(){Name="Saburo", Sex ="男", Age=8, } },
                {"四男", new Person(){Name="Shiro", Sex ="男", Age=7, } },
                {"長女", new Person(){Name="Goro", Sex ="男", Age=8, } },
            };

            this.Items = new Dictionary<string, Dictionary<string, string>>()
            {
                {"長男", new Dictionary<string, string>() {{"名前", "一郎"}, { "性別", "男" }, { "年齢", "20"}} },
                {"次男", new Dictionary<string, string>() {{"名前", "二郎"}, { "性別", "男" }, { "年齢", "19"}} },
                {"三男", new Dictionary<string, string>() {{"名前", "三郎"}, { "性別", "男" }, { "年齢", "18"}} },
                {"四男", new Dictionary<string, string>() {{"名前", "四郎"}, { "性別", "男" }, { "年齢", "17"}} },
                {"長女", new Dictionary<string, string>() {{"名前", "花子"}, { "性別", "女" }, { "年齢", "18"}} },
            };
        }

        private Dictionary<string, Person> _personDictionaries;

        public Dictionary<string, Person> PersonDictionaries
        {
            get { return _personDictionaries; }
            set { SetProperty(ref _personDictionaries, value); }
        }

        private Dictionary<string, Dictionary<string, string>> _Items;

        public Dictionary<string, Dictionary<string, string>> Items
        {
            get { return _Items; }
            set { SetProperty(ref _Items, value); }
        }

ContentView を Views に作成

ここで親データ(ListBox)と子データ(TextBlock or DataGrid)で直接バインド設定する

  • ListBox は x:Name で名前付けする

  • ListBox で Dictionary をバインドして、DisplayMenber に Key を設定して SelectedValuePath に Value を設定

  • Value は 名前付けした ListBox の SelectedValue を直接参照するため ViewModel 側でバインドプロパティを定義する必要がない

ContentView.xaml
<UserControl x:Class="PrismDictionaryBinding.Views.ContentView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <ListBox x:Name="lb1" ItemsSource="{Binding PersonDictionaries}" DisplayMemberPath="Key" SelectedValuePath="Value" />
        <Viewbox Grid.Column="1" >
            <StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
                <TextBlock Text="{Binding ElementName=lb1, Path=SelectedValue.Name}"/>
                <TextBlock Text="{Binding ElementName=lb1, Path=SelectedValue.Sex}"/>
                <TextBlock Text="{Binding ElementName=lb1, Path=SelectedValue.Age}"/>
            </StackPanel>
        </Viewbox>
        <ListBox x:Name="lb2" Grid.Row="1" ItemsSource="{Binding Items}" DisplayMemberPath="Key" SelectedValuePath="Value" />
        <DataGrid Grid.Row="1" Grid.Column="1" ItemsSource="{Binding ElementName=lb2, Path=SelectedValue}" />
    </Grid>
</UserControl>

実行

上が Dictionary<string, person>

下が Dictionary<string, Dictionary<string, string>>

実行画面

まとめ

最初は SelectedItem をバインドしてそのプロパティセッターで対象データを表示しるということを ViewModel 側でやっていたんですが直接親コントロールとなる ListBox のプロパティを参照するようにしたことでシンプルに書くことが出来ました。

あと、SelectedItem の型を Dictionary にしていてエラーになってなぜかーと思っていたら KeyValuePair の存在をすっかり忘れていてハマりました…。

ソースは こちら に置いてあります。

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

【C#】GPS信号を使用して現在の時刻を取得する

なんでそんなことするの?

端末がインターネットに接続できない場合、サーバから現在時刻を取得できません。
GPS信号(NMEAフォーマットデータ)には、UTC時刻が含まれているため、
インターネットには繋がらないけどGPSは拾えるという特殊な環境である場合、選択肢となるかもしれません。

Microsoft.WindowsAPICodePack.Sensors を使う

.Net標準であるGeoCoordinateWatcherで取得できる情報は、緯度・経度に限られているので、
Microsoft.WindowsAPICodePack.Sensors名前空間のSensorクラスを使用します。

GPSセンサを用意

Sensorクラスを使うためには、GPSセンサが必要です。
イマドキのWindows端末には基本的にGPSセンサが搭載されていないので、用意する必要があります。

とりあえず動作を試すだけであれば、amazon格安センサ( https://www.amazon.co.jp/gp/product/B01MA21TSX/ )で十分です。
ソースを動かすには、デバイスマネージャーのセンサーにGPSセンサーが追加されていれば問題ありません。

ソースコード

using Microsoft.WindowsAPICodePack.Sensors;

// 位置情報GUID
// 参考:https://docs.microsoft.com/en-us/windows/desktop/sensorsapi/sensor-category-location
private static Guid SENSOR_DATA_TYPE_LOCATION_GUID = new Guid("055C74D8-CA6F-47D6-95C6-1ED3637A0FF4");

// GPSセンサの取得
Sensor GeolocationSensor = SensorManager.GetSensorsByTypeId(SensorTypes.LocationGps)[0];

// GPS信号を取得する度に、イベント実行
GeolocationSensor.DataReportChanged += DataReportChanged;

        // GPS信号受信時の処理
        private void DataReportChanged(Sensor sender, EventArgs e)
        {
            // NMEAフォーマットデータ
            string[] gpsData = sender.DataReport.Values[SENSOR_DATA_TYPE_LOCATION_GUID][25].ToString().Split(',');

            // gpsData[1]:HHmmss.00 gpsData[9]:yyMMdd
            // NMEAフォーマットデータに含まれる時刻は、UTC時刻となる
            DateTimeOffset dto = new DateTimeOffset(
                DateTime.ParseExact(string.Format("{0}{1}", gpsData[9], gpsData[1].Substring(0, 6)), "yyMMddHHmmss", null), TimeSpan.Zero);
        }

https://github.com/TakadaTentaro/GetGPSTime

GPS信号データは、DataReportChangedイベントのsenderを通じて取得します。

上記のGPSセンサを使用した環境では、
sender.DataReport.Values[SENSOR_DATA_TYPE_LOCATION_GUID]は、[0]:経度、[1]:緯度と続いていき、[25]にNMEAフォーマットの生データ(\$GPRMC,\$GPVTG,\$GPGNS...)が格納されています。

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

Windows Formでの高DPI対応でハマったところ

マニフェストファイルの設定

基本的には下記ページの通りに設定すれば、文字がボヤける現象をほぼ自動的に回避できるはずです。
Windows フォーム アプリの DPI Aware への変更

自分のプロジェクトでは、単体の実行ファイルのみで動作させたときに適用されませんでした。
(マニフェストファイルが同一ディレクトリにある場合はそのファイルが読み込まれて回避設定が適用されます)

.csprojの設定

そこで、csprojのGenerateManifestsを次のようにfalseに変更しました。
これで単体でも回避できるようになりました。
プロジェクト設定画面のどこかに設定項目があるのかもしれないのですが、見当たりませんでした…。

project.csproj
  <PropertyGroup>
    <GenerateManifests>false</GenerateManifests>
  </PropertyGroup>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】画像生成の基本(Bitmap)

はじめに

【Java】簡単な模様を描くのコードを転用して、「簡単な模様の画像」をC#で生成してみました。

コード

  • 以下の2つのライブラリを使うと、ラスター形式の画像(BMP,JPG,PNGなど)を操作することができます。
    • System.Drawing;
    • using System.Drawing.Imaging;
  • 所望の座標のピクセルに対する色付けは、SetPixelメソッドを使います。
    • {Bitmapオブジェクト}.SetPixcel(x座標,y座標,色)
  • 生成した画像の保存は、Saveメソッドを使います。
    • {Bitmapオブジェクト}.Save({保存先のパス}, {画像の形式})
  • 余談ですが、System.Drawingは「GDI+」というグラフィックサブシステムを利用しているそうです。
    • System.DrawingのAPIを見ると、「GDI+の基本的なグラフィックス機能を使用できるようにします」と書かれています。
ImageCreator.cs
public class ImageCreator
{
    /// <summary>
    /// バツ印の画像を生成する。
    /// </summary>
    /// <param name="size">画像の縦・横のサイズ。px数を指定する。</param>
    public static void CreateCrossMark(int size)
    {
        Bitmap bmp = new Bitmap(size, size);

        // 全ピクセルに色付け
        for (int row = 0; row < size; row++)
        {
            for (int col = 0; col < size; col++)
            {
                if (row == col || row + col == size - 1)
                {
                    bmp.SetPixel(col, row, Color.White);
                }
                else
                {
                    bmp.SetPixel(col, row, Color.Black);
                }
            }
        }

        bmp.Save(@"C:\image\cross_mark.png", ImageFormat.Png);
    }
}

生成された画像

  • 以下のように「黒地に白のバツ印」の画像を生成できました。
    • CreateCrossMarkメソッドの引数の値を変えることで、大小様々なサイズの画像を生成できました。

cross_mark.png

まとめ

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

[Unity][ParticleSystem]3D座標を起点にuGUI上にパーティクルを発生・収束させる

サンプル動画

3Dアクションゲームなどで良くある、
「敵を攻撃したらパーティクルが発生して、UI上のゲージに向かって飛んでゲージが溜まる。」
というものを実装してみました。
(割と需要あると思っているのですが、どこにもサンプルが無かったので自作することに)

サンプルプロジェクト

以下のGithubに上げてあります
https://github.com/madoramu/ParticleHorming
※サンプル動画は別プロジェクトの物なので、上記URLのプロジェクトは少し内容が異なります。

環境

Unity2018.3
WindowsとAndroidで動作確認済み
重要:ScreenSpaceCamera設定のCanvasのみ対応しています

実装方法

サンプルプロジェクトの中身を抜粋して説明していきます。
細かい部分などはプロジェクトを閲覧してください。

メインカメラとUIカメラの用意・設定

メインカメラ

こちらはキャラクターの追尾、およびUI以外の描画を担当。

  • CullingMaskで「UI」を除外する。
  • Depthを「0」にする。
    • UIカメラより低ければおk

UIカメラ

UIの描画のみ担当。

  • メインカメラには絶対映らない場所に移動させる
    • AudioListenerは外しておく
  • ClearFlagsを「Depth Only」にする。
  • CullingMaskで「UI」だけにする。
  • Depthを「10」にする。
    • メインカメラより高ければおk

上記二つのカメラ設定により、メインカメラの描画の後にUIレイヤーの情報が上に描画されます。

キャンバスの用意と設定(重要)

UI表示用・座標変換用で2つのScreenSpaceCamera設定Canvasを用意します。

  • UI表示用にはUIカメラ、座標変換用にはメインカメラをそれぞれ設定する
  • RectTransform以外のパラメーターは全て同じにする事
    • 一応念のため

※何故設定カメラ以外同じパラメーターのCanvasを二つ使用したのかは感想に記載しています。

ParticleSystemの設定

ここは好みに合わせて弄って問題ありませんが、以下の2点は必須です。

  • ParticleSystemオブジェクトのLayerは「必ず「UI」に設定する事
  • UI表示用のCanvas直下に配置する事

今回は以下のように設定しました(プロジェクトから抜粋)
キャプチャ.PNG

「何故World設定か?」

  • 後述のパーティクル発生処理で解説しますが、ParticleSystemそのものを移動させるので、Localだとその移動にパーティクルも追従してしまうため。

「Rederer→Order in Layerについて」

  • 上記画像には映っていませんが、今回はUIより前面に出したかったため値を増やしました。ここはお好みでおk。

パーティクル発生処理

ParticleSystem.Emitを使用して、意図的にパーティクルを発生させることが出来ます。
https://docs.unity3d.com/ja/current/ScriptReference/ParticleSystem.Emit.html

処理の流れ

  • 3D座標からScreenSpaceCanvas上の2D座標に変換
    • ゲームだと主に敵に攻撃が当たった時の、敵座標位置が3D座標になります。
    • テラシュールブログを参考(というかほぼ丸パクリ)にして作成しました。
    • 下記ページの「World Space を Screen Space Cameraへ」です。
    • http://tsubakit1.hateblo.jp/entry/2016/03/01/020510
  • 変換した2D座標にParticleSystemを移動
  • Emitでパーティクル生成。

以下コード抜粋

ParticleHorming.cs
public void CreateParticle()
{
    // 3D空間座標からカメラスクリーン上の座標に変換する
    Vector3 basePos = m_Emit3DTransformList.GetRandom().position;   // GetRandom()についてはListExtensionsを参照
    Vector2 screenPos = m_Camera.WorldToScreenPoint(basePos);

    // カメラスクリーン座標をキャンバス上のローカル座標に変換する
    Vector2 cameraCanvasPos = Vector2.zero;
    RectTransformUtility.ScreenPointToLocalPointInRectangle(m_RaycasterCameraCanvasRectTransform, screenPos, m_Camera, out cameraCanvasPos);

    // 座標確認
    Debug.LogFormat("rectPos{0}", cameraCanvasPos);

    // ParticleSystemを放出位置に移動させてEmit
    m_ParticleSystem.transform.localPosition = cameraCanvasPos;
    m_ParticleSystem.Emit(m_EmitParticleCount);
}

パーティクル更新(収束)処理

普段パーティクルをスクリプト側で操作することは余り無いですが、今回は特定位置に収束させたかったのでググった結果、公式サイトで以下のページが出てきました。
https://docs.unity3d.com/ja/current/ScriptReference/ParticleSystem.GetParticles.html

このページを参考に以下の様な処理で更新させることにしました。

ParticleHorming.cs
// 更新
for(int i = 0; i < m_ActiveParticleCount; ++i)
{
    float rate = (1.0f - m_ParticleList[i].remainingLifetime / m_ParticleList[i].startLifetime);
    rate = Mathf.Pow(rate, m_ReactionDistance); // 指数関数を加えることにより、収束する勢いを変更できるようにしてる
    m_ParticleList[i].position = Vector3.Lerp(m_ParticleList[i].position, m_TargetTransform.position, rate);
}
m_ParticleSystem.SetParticles(m_ParticleList, m_ActiveParticleCount);

今回は指数関数を使って、等速直線移動ではなく少しアレンジをしています。
ここに関しては書き方によって幾通りも表現が出来ると思います。

感想

当初はScreenSpaceCameraのCanvas一つで解決できないか試行錯誤していましたが、キャラを追尾する = Canvasが移動する関係上、パーティクルが毎フレームぶれたり座標がおかしなことになったりした結果、今の形に落ち着きました。
冒頭でも言った通りScreenSpaceOverlay設定のCanvasには対応できないので、結構使いどころは限定的かも。
ですが、ParticleSystem上でパーティクルをいつも通り設定できるメリットは大きいので、個人的には満足度高めです。

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

Bit演算のちょっとしたテクニック備忘録。

初めに。

あくまで自分用のメモです。
最適化しきれていなかったり、時々間違えたりします。
間違いのご指摘や、何か他に良い方法をご存知でしたら、
コメントの方よろしくお願いいたします。

使用言語

  • C#

一覧


Abs

絶対値を返します。

public static int Abs(int num) => (num ^ (num >> 31)) - (num >> 31);

Sign

符号を返します。 (負数 => -1,零 => 0,正数 => 1)

public static int Sign(int num) => (num >> 31) - (-num >> 31);

Clamp

数値を a以上b以内 もしくは b以上a以内 に制限します。

public static int Clamp(int num, int a, int b) => (a + b + ((Abs(num - a) - Abs(num - b)) ^ ((b - a) >> 31)) - ((b - a) >> 31)) >> 1;

LSB

最下位ビットを取得します。

public static int LSB(int num) => num & -num;

MSB

最上位ビットを取得します。

public static int MSB(int num) => num & ~(FillMSBtoLowest(num) >> 1);

FillMSBtoLowest

最上位ビットから下位のビットすべてを1で埋めます。

public static int FillMSBtoLowest(int num)
{
    num |= num >> 1;
    num |= num >> 2;
    num |= num >> 4;
    num |= num >> 8;
    return num >> 16;
}

FillLSBtoHighest

最下位ビットから上位のビットすべてを1で埋めます。

public static int FillLSBtoHighest(int num)
{
    num |= num << 1;
    num |= num << 2;
    num |= num << 4;
    num |= num << 8;
    return num << 16;
}

BitCount

立っているビットの個数(1の個数)を取得します。

public static int BitCount(int num)
{
    num = (num & 0x55555555) + ((num >> 1) & 0x55555555);
    num = (num & 0x33333333) + ((num >> 2) & 0x33333333);
    num = (num & 0x0F0F0F0F) + ((num >> 4) & 0x0F0F0F0F);
    num = (num & 0x00FF00FF) + ((num >> 8) & 0x00FF00FF);
    return (num & 0x0000FFFF) + ((num >> 16) & 0x0000FFFF);
}

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