20200404のC#に関する記事は10件です。

[Unity] LineRenderer の頂点数を指定する際に旧型式と怒られた話

こんにちは。
今回はUnityで開発中に少しつまずいた事を紹介しようと思います。
とりあえず実装したかったのは、ただ線を描写したかったのです。しかし、Unityのバージョンアップの際にどうやら頂点数の指定方法に変更があったみたい。とりあえずどう変更されたのかを最初に書いていきます。

変更された構文

はい。見出し通り構文が変更されていたみたいです。下に比較して書いていきます。

バージョン 構文     例文
ver5.4以前 SetVertexCount([int]); Line.SetVertexCount(2);
ver5.5 numPositions = [int]; Line.numPositions = 2;
ver5.6以降 positionCount = [int]; Line.positionCount = 2;

上記の通りバージョン事に少しずつ違います。
何が厄介かというと、エラーではなく警告として怒られます。なので気づかない可能性があります。

例のソースコード

まずは警告されるコードを書いていきます。

sample.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DisplayLine : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        // 線オブジェクト設定
        LineRenderer line = gameObject.GetComponent<LineRenderer>();

        // 線の幅を指定
        line.startWidth = 0.1f;
        line.endWidth = 0.1f;

        // 頂点の数を指定
        line.SetVertexCount(2);

        // 開始位置と終了位置を指定
        line.SetPosition(0, Vector3.zero);
        line.SetPosition(1, new Vector3(1f, 1f, 0f));
    }

    // Update is called once per frame
    void Update()
    {

    }
}

このソースコードでは最新バージョンのUnityでは警告がでます。
頂点の数を指定のコメントアウトされているコードが問題のコードです。
では、警告が来ないソースコードに変更しましょう。

sample.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class addViewLine : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        // 線オブジェクト設定
        LineRenderer line = gameObject.GetComponent<LineRenderer>();

        // 線の幅を指定
        line.startWidth = 0.1f;
        line.endWidth = 0.1f;

        // 頂点の数を指定
        line.positionCount = 2;

        // 開始位置と終了位置を指定
        line.SetPosition(0, Vector3.zero);
        line.SetPosition(1, new Vector3(1f, 1f, 0f));
    }

    // Update is called once per frame
    void Update()
    {

    }
}

このように書くことで警告は消えます。

最後に

今回、このような事態がおきてしまった理由は筆者が参考にした資料(ググった)内のソースコードの書き方のバージョンが以前のものであったことです。実際、開発をしていく上でこんなの日常茶飯事なので警告がでたことにそんなにひるまなくてもいいと思いますが、言語の仕様変更に対しては敏感である必要があると改めて感じることができました。いい勉強になりました。

ついでになんですが、筆者は大学生です。はい。バイトでエンジニアしてます。
インスタでは情報発信や記事の投稿通知、役立つ情報、日常を垂れ流したりしてますのでよかったらフォローしてください。多分フォロー返します。沢山の方と繋がれたらなとか思っています。
アカウント→taaa_kundayo

では、記事は以上になります。
ありがとうございました!!

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

Unity LineRenderer の頂点数を指定する際に旧型式と怒られた話

こんにちは。
今回はUnityで開発中に少しつまずいた事を紹介しようと思います。
とりあえず実装したかったのは、ただ線を描写したかったのです。しかし、Unityのバージョンアップの際にどうやら頂点数の指定方法に変更があったみたい。とりあえずどう変更されたのかを最初に書いていきます。

変更された構文

はい。見出し通り構文が変更されていたみたいです。下に比較して書いていきます。

バージョン 構文     例文
ver5.4以前 SetVertexCount([int]); Line.SetVertexCount(2);
ver5.5 numPositions = [int]; Line.numPositions = 2;
ver5.6以降 positionCount = [int]; Line.positionCount = 2;

上記の通りバージョン事に少しずつ違います。
何が厄介かというと、エラーではなく警告として怒られます。なので気づかない可能性があります。

例のソースコード

まずは警告されるコードを書いていきます。

sample.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DisplayLine : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        // 線オブジェクト設定
        LineRenderer line = gameObject.GetComponent<LineRenderer>();

        // 線の幅を指定
        line.startWidth = 0.1f;
        line.endWidth = 0.1f;

        // 頂点の数を指定
        line.SetVertexCount(2);

        // 開始位置と終了位置を指定
        line.SetPosition(0, Vector3.zero);
        line.SetPosition(1, new Vector3(1f, 1f, 0f));
    }

    // Update is called once per frame
    void Update()
    {

    }
}

このソースコードでは最新バージョンのUnityでは警告がでます。
頂点の数を指定のコメントアウトされているコードが問題のコードです。
では、警告が来ないソースコードに変更しましょう。

sample.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class addViewLine : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        // 線オブジェクト設定
        LineRenderer line = gameObject.GetComponent<LineRenderer>();

        // 線の幅を指定
        line.startWidth = 0.1f;
        line.endWidth = 0.1f;

        // 頂点の数を指定
        line.positionCount = 2;

        // 開始位置と終了位置を指定
        line.SetPosition(0, Vector3.zero);
        line.SetPosition(1, new Vector3(1f, 1f, 0f));
    }

    // Update is called once per frame
    void Update()
    {

    }
}

このように書くことで警告は消えます。

最後に

今回、このような事態がおきてしまった理由は筆者が参考にした資料(ググった)内のソースコードの書き方のバージョンが以前のものであったことです。実際、開発をしていく上でこんなの日常茶飯事なので警告がでたことにそんなにひるまなくてもいいと思いますが、言語の仕様変更に対しては敏感である必要があると改めて感じることができました。いい勉強になりました。

ついでになんですが、筆者は大学生です。はい。バイトでエンジニアしてます。
インスタでは情報発信や記事の投稿通知、役立つ情報、日常を垂れ流したりしてますのでよかったらフォローしてください。多分フォロー返します。沢山の方と繋がれたらなとか思っています。
アカウント→taaa_kundayo
URL:https://www.instagram.com/p/B-j3-RJjPWJ/?igshid=poq7k5z6eo3n

では、記事は以上になります。
ありがとうございました!!

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

ASP.NET Core Blazor WebAssembly でログインを作ってみる

ちょっと目をはなしてる隙に、こんなドキュメントが出来てた!

ASP.NET Core Blazor WebAssembly をセキュリティで保護する

Blazor WebAssembly でもログイン機能が公式でいけるんですね。

やってみよう

ということでやってみようと思います。とりあえず、Twitter やら独自ログインやらなんでもござれの Azure AD B2C でやってみようかな。ドキュメントに手順もあるし。

このドキュメントを見ていて気付いたのですが、プロジェクトをつくるためのコマンドはあってるのですが、その後のコードの解説が Azure AD B2C ではなく Azure AD での解説になってるので、注意が必要です。実際に動くコードが欲しい人は、アプリを作成するの手順にある以下のコマンドをうちましょう。

dotnet new blazorwasm -au IndividualB2C --aad-b2c-instance "{AAD B2C INSTANCE}" --api-client-id "{SERVER API APP CLIENT ID}" --app-id-uri "{APP ID URI}" --client-id "{CLIENT APP CLIENT ID}" --default-scope "{DEFAULT SCOPE}" --domain "{DOMAIN}" -ho -ssp "{SIGN UP OR SIGN IN POLICY}" --tenant-id "{TENANT ID}"

では、とりあえず Azure に適当なリソースグループ作って、その中に Azure AD B2C のテナントを作ります。

image.png

今回は ASP.NET Core にホストされたほうで試してみたいので上記リンクの中の下の方のドキュメントに従ってやっていこうと思います。

サーバー側アプリの登録

ということで、Azure AD B2C にアプリを登録しましょう。最終的に今回は Blazor をホストしている ASP.NET Core 側をアプリとして登録するのと、Blazor WebAssembly もアプリとして登録するので、2 つ登録します。

Azure AD B2C の管理画面で「アプリの登録」から Webアプリ/WebAPI の形式のアプリを登録します。

image.png

名前を入れて、任意の組織からのサインインを許可して、リダイレクトURLはWebを指定してURLは空にします。「openid と offline_access アクセス許可に対して管理者の同意を付与します」にチェックを入れて登録して官僚。

登録したアプリの管理画面で「API の公開」を選んで「Scope を追加」しましょう。アプリケーションIDの登録が初回は出るはずなので、登録をして適当なスコープ名を入れます。

image.png

最後にクライアントアプリの「認証」のページで暗黙の付与で「アクセストークン」と「ID トークン」にチェックを入れておきます。

image.png

必要な情報のメモ

ログイン処理を書くために必要な情報をゲットします。
アプリ管理画面の概要から大体取得できます。

image.png

  • アプリケーション ID
  • テナント ID
  • アプリケーション ID の URL
  • 先ほど作った Scope の名前 (今回は API.Manage)

サービスプリンシパルの作成

一通りメモったら先ほどの画面から「サービスプリンシパルの作成」を押して作成しておきます。

クライアントアプリの登録

同じ要領で登録していきます。クライアント側はリダイレクト URL を入れます。今回はテスト用にローカルホストで動かすので、その URL (アプリ新規作成して、ローカルサーバーの https のポート番号を確認しましょう。) に対して /authentication/login-callback というものをつけた形で登録します。

この authentication の部分は決め打ちではなく、後でクライアント側のアプリに Authentication.razor というものを作って @page "/authentication" と指定するものになります。/authentication の後の文字列は Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticatorView というコントロールで使うやつなので決め打ちです。

image.png

クライアント側のアプリの管理画面で「API のアクセス許可」から「アクセス許可の追加」を選んで「自分の API」から、先ほど作ったサーバー側のアプリのスコープを選びます。

image.png

image.png

アクセス許可を与えるボタンを押して完了

image.png

全ての API アクセス許可の状態が緑のチェックマークになれば成功です。

image.png

必要な情報のメモ

クライアントアプリ側で以下の情報を取得しておきます。

  • アプリケーション ID

ユーザー フローの作成

最後にユーザーフローを作ります。これが出来たら Azure AD B2C 画面での操作は終わります。

Azure AD B2C の管理画面からユーザーフローの作成をします。で「サインアップとサインイン」を選んで「ユーザーフローを作成する」を選んで「サインアップとサインイン」を選びます。

image.png

そして、ID プロバイダー(今回は Twitter とかとの連携設定をしてないので、デフォルトの Email signup)とユーザー属性と要求で表示名を選んでおきます。

image.png

そして、作成しましょう。

このユーザーフローの名前をメモっておきます。(今回は B2C_1_SignIn)

アプリにログイン機能を追加しよう

Blazor WebAssembly App を作ります。ASP.NET Core hostedにして、認証はなしを選びます。認証有りにしてもいいのですが、自分で1つずつ追加していきたいので、今回は無しからやっていきましょう。

サーバーアプリの編集

appsettings.Development.json あたりに Azure AD B2C からゲットしてきた情報を追加します。

余談ですが、サーバー側アプリに関しては以下のような情報は、ユーザーシークレットなどに格納するオプションもあります。そうするとプログラム内に秘密の情報を埋め込まなくてもすみます。本番は Azure App Service のアプリケーション設定か Azure Key Vault あたりに設定もできます。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AzureAD": {
    "Instance": "https://blazorsignin.b2clogin.com/",
    "ClientId": "サーバー側のアプリID",
    "Domain": "blazorsignin.onmicrosoft.com",
    "SignUpSignInPolicyId": "B2C_1_SignIn"
  }
}

サーバー側アプリに NuGet から Microsoft.AspNetCore.Authentication.AzureADB2C.UI を追加しましょう。そしてサーバー側の Startup.cs の ConfigureServices に認証系のサービスを追加する処理を追加します。ここで、先ほど appsettings.Development.json の AzureAD に設定した値を options に設定しています。

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(AzureADB2CDefaults.BearerAuthenticationScheme)
        .AddAzureADB2CBearer(options => Configuration.Bind("AzureAD", options));
    services.AddControllersWithViews();
}

そして、Configure メソッドにも認証認可の追加の処理を書きます。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseWebAssemblyDebugging();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseBlazorFrameworkFiles();
    app.UseStaticFiles();

    app.UseRouting();

    // 認証認可
    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapFallbackToFile("index.html");
    });
}

API の保護

デフォルトのプロジェクトテンプレートに含まれている API の WeatherForecastController に `[Authorize]' 属性をつけて認証されていない要求ははじくようにしましょう。

namespace BlazorSignIn.Server.Controllers
{
    [Authorize]
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {

クライアントアプリに認証を追加

以下の MSAL 用の NuGet パッケージをクライアント側のプロジェクトに追加します。

  • Microsoft.Authentication.WebAssembly.Msal

そして、Program.cs の Main メソッドに MSAL の設定を追加します。

public static async Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    builder.RootComponents.Add<App>("app");

    builder.Services.AddBaseAddressHttpClient();
    builder.Services.AddMsalAuthentication(options =>
    {
        var a = options.ProviderOptions.Authentication;
        a.Authority = "https://blazorsignin.b2clogin.com/blazorsignin.onmicrosoft.com/B2C_1_SignIn";
        a.ClientId = "xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx";
        a.ValidateAuthority = false;
        options.ProviderOptions.DefaultAccessTokenScopes.Add(
            "https://blazorsignin.onmicrosoft.com/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx/API.Manage");
    });

    await builder.Build().RunAsync();
}

クライアントアプリの wwwroot にある index.html に MSAL の JavaScript を読み込ませておきます。

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>BlazorSignIn</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/site.css" rel="stylesheet" />
    <!-- 追加!! -->
    <script src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationService.js"></script>
</head>

<body>
    <app>Loading...</app>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">?</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
</body>

</html>

App.razor に認証系のコンポーネントを組み込みます。まずは _Imports.razor に認証系コンポーネントの名前空間を using するように一行追加します。

@using System.Net.Http
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using BlazorSignIn.Client
@using BlazorSignIn.Client.Shared
// これを追加
@using Microsoft.AspNetCore.Components.Authorization

そして、App.razor に追加するコンポーネントを Shared に追加します。

RedirectToLogin.razor
@inject NavigationManager Navigation
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@code {
    protected override void OnInitialized()
    {
        Navigation.NavigateTo($"authentication/login?returnUrl={Navigation.Uri}");
    }
}

認証ページもさくっと追加しましょう。Pages に Authentication.razor を追加します。

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="@Action" />

@code{
    [Parameter] public string Action { get; set; }
}

そして、FetchData.razor にアクセスするとログインを求めるように設定したいと思います。といっても [Authorize] 属性を付けるだけです。こうすると自動でログインが行われるようになり、IAccessTokenProvider からアクセストークンが取れるようになるので、そのアクセストークンを使ってサーバーの API を呼び出します。

@page "/fetchdata"
@using BlazorSignIn.Shared
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

@inject HttpClient Http
@inject IAccessTokenProvider AuthenticationService
@inject NavigationManager Navigation

@attribute [Authorize]
<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        var tokenResult = await AuthenticationService.RequestAccessToken();
        if (tokenResult.TryGetToken(out var token))
        {
            Http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.Value}");
            forecasts = await Http.GetJsonAsync<WeatherForecast[]>("WeatherForecast");
        }
        else
        {
            Navigation.NavigateTo(tokenResult.RedirectUrl);
        }
    }
}

実行してみよう

実行して Fetch data に遷移すると以下のようにログイン画面が表示されます。

image.png

まだ Azrue AD B2C にユーザー追加していなかったことに気付いたので、適当に作りましょう。
ちなみに ID プロバイダーを設定しておくと SNS 認証も出来ます。

image.png

とりあえず今回は Azure AD B2C に直接ユーザーを追加しました。

image.png

ユーザーを追加できたら気を取り直して、アプリを再実行してログイン画面にユーザー ID とパスワードを入れます。そしてサインインをすると無事 API が呼べて結果が画面に表示されました!

image.png

ユーザーデータをとってみよう

Azure AD B2C で表示名を返すようにしていたので表示名はクライアント側でとれるはずです。表示してみましょう。

Index.razor をちょっと変更してサインインしていない場合は「サインインしていません」と表示して、サインインしている場合は「こんにちは〇〇さん」と表示してみたいと思います。名前は @context.User.Identity.Name で取れます。

これも便利なコンポーネントがあってログインしているとき、していないときでデータを出しわけることが簡単に出来るようになっています。ついでなのでサインアウト処理も作ってみましょう。

@page "/"
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager

<h1>Hello, world!</h1>

Welcome to your new app.


<AuthorizeView>
    <Authorized>
        こんにちは @context.User.Identity.Name さん。
        <button class="nav-link btn btn-link" @onclick="BeginLogout">サインアウト</button>
    </Authorized>
    <NotAuthorized>
        <a href="authentication/login">サインイン</a>
    </NotAuthorized>
</AuthorizeView>

@code{
    private async Task BeginLogout(MouseEventArgs args)
    {
        await SignOutManager.SetSignOutState();
        Navigation.NavigateTo("authentication/logout");
    }
}

AuthorizeView の Authorized タグの中がサインインしているときの表示の定義になります。NotAuthorized タグの中がサインインしていないときの見た目の定義になります。

サインアウトのボタンを押したらサインアウトの処理を呼び出して、Authentication.razor に処理を回しています。Authentication.razor ではログインの時と同じでログアウトとかの状態の RemoteAuthenticatorView に見た目をお願いしています。

例えばログアウトが終わった時の見た目はデフォルトだとそっけない英語メッセージなので、そっけない日本語にしたかったら以下のように見た目を自分で定義できます。

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="@Action">
    <LogOutSucceeded>
        ログアウトしたよ!!
    </LogOutSucceeded>
</RemoteAuthenticatorView>

@code{
    [Parameter] public string Action { get; set; }
}

この見た目のカスタマイズで、何がカスタマイズできるかは以下のドキュメントにあります。

認証ユーザーインターフェイスをカスタマイズする

この状態で実行して Index.razor を開くと以下のようになります。

image.png

ログインすると名前が出ますね。

image.png

この状態で Fetch data にいくと当然ですがログインしているので、ログインダイアログが出ずに、ちゃんとデータが出ます。

image.png

Home に戻ってログアウトすると以下のように、ちゃんとそっけない日本語が出ます。

image.png

まとめ

ということで、試しながらつらつらと書いていってみました。Blazor WebAssembly 結構期待できるのでは!?という感じの仕上がりになってきてますね。

乞うご期待。

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

【自分メモ】C#でランダム整数を生成しよう

はじめに

急にC#を齧ることになり、細々と学習をしています。
文法理解がマジでできない底辺の寝言レベルのメモ。

環境

・Windows
・Microsoft Visual Studio Enterprise 2017(version 15.9.)
・.NET Framework(version 4.8.03761)

配列の宣言

test.cs
//Random
Random r = new System.Random();

//100個のint型要素が入る配列を生成。
int[] arr = new int[100];

//1~100までの整数をランダムに生成し、配列に格納する
for (int i = 0; i < arr.length; i++) 
{
     arr[i] = r.Next(0, 100);
}

まとめ

またひとつかしこくなりました。

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

コルーチンの書き方がうまくいかずエラーが出てしまいます

Unityを使ってノベルゲームの制作に挑戦しているのですが1文字ずつ表示させるためのコルーチンでエラーが出てしまいます。エラーは
Assets\Scripts\Story\WriteTxt.cs(23,29): error CS0120: An object reference is required for the non-static field, method, or property 'WriteTxt.Timer()'
です。
コルーチン部分をコメント化すると正常に作動するのでたぶんコルーチン部分が間違っていると思うのですがアドバイスをお願いします。
Unityのバージョンは2019.3.71.fです。

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

//文字送りに必要な者たち
using UnityEngine.UI;
using System.Text.RegularExpressions;

public class WriteTxt : MonoBehaviour
{
public static void Write_Txt(int num)
{
//表示中の文字数を示す変数
int msg_count=0;

    //メッセージの初期化
    MnageSt.messageText.text="";

    while(MnageSt.senario[num].Length>msg_count)
    {
        MnageSt.messageText.text+=MnageSt.senario[num][msg_count];
        msg_count++;
        StartCoroutine (Timer());
    }
} 

IEnumerator Timer () {
    yield return new WaitForSeconds (1);
}

}

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

簡単リモートアセット(Unity Addressable Asset System)を試してみた (その2)

前提

やったこと

  • エディタ拡張で、デフォルトグループ内のサブアセットをルートに展開します。
  • Addressable Assets Systemを完全に理解する ~ Addressablesウィンドウから設定する」で紹介されていたスクリプトが最近のバージョンには整合しないようでした(Addressables 1.7.5で確認)ので、書き直しました。
  • なお、アセットがルートになくてもアクセスはできるので、ルートに展開する必然性はありません。
  • また、このスクリプトは、デフォルトグループだけを対象にしています。
Assets/~/Editor/MoveToRoot.cs
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEngine;

public static class AddressableAssetsUtil {

    // サブエントリをルートへ展開して空になったフォルダを削除
    [UnityEditor.MenuItem ("Assets/Addressables/MoveSubEntryToRoot")]
    public static void MoveSubEntryToRoot () {
        var entriesCount = 0;
        var assetsCount = 0;
        var settings = AddressableAssetSettingsDefaultObject.Settings;
        var entries = new List<UnityEditor.AddressableAssets.Settings.AddressableAssetEntry>  (settings.DefaultGroup.entries);
        var assets = new List<UnityEditor.AddressableAssets.Settings.AddressableAssetEntry> { };
        foreach (var entry in entries) {
            entry.GatherAllAssets (assets, false, true, true);
            if (assets.Count > 0 && entry.MainAsset.GetType () == typeof (UnityEditor.DefaultAsset)) {
                entriesCount++;
                Debug.Log ($"MoveSubEntryToRoot: entry {entriesCount}: {entry.address}");
                foreach (var asset in assets) {
                    assetsCount++;
                    Debug.Log ($"MoveSubEntryToRoot: asset {assetsCount}: {asset.address}");
                    settings.MoveEntry (asset, entry.parentGroup);
                    if (asset.address.StartsWith ("Assets/")) {
                        asset.address = asset.address.Remove (0, 7);
                    }
                }
                assets.Clear ();
                settings.RemoveAssetEntry (entry.guid);
            }
        }
        AssetDatabase.Refresh (); // アセットを更新
        Debug.Log ($"MoveSubEntryToRoot: done (entries: {entriesCount}, assets: {assetsCount})");
    }

}

公式ドキュメント

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

WPFのUserControlで、Designer上でダミー画像が表示されるようにする

TL;DR;

<UserControl ... >
    ... 
    <UserControl.DataContext>
        <local:MockData />
    </UserControl.DataContext>
    ... 
</UserControl>

これで利用側は表示され続けます。
しかし、UserControl側はリビルドすると画像だけ消えます。

やりたかったこと

WPFやUWPの利点の一つにXAMLでUIが描けること、そしてVisualStudioではXAMLのデザイナー(グラフィカルエディタ)が搭載されていることがあります。
折角そんな素晴らしい機能があるので、UIの見た目をリアルタイムで確認しながらXAMLを書きたいものです。
しかし、StackPanelなどの自動レイアウトを使っていると、画像が読み込まれているときとそうでないときでImageコントロールのサイズが変わってしまい、デザイナー上での見た目が潰れて大変悲しいことになってしまいます。

これを何とかするために、実行時にBindする予定の部分にも何かしらダミーの画像を当てておいて、ある程度のサイズを確保してくれるようにしたかったのです。

駄目だったパターン

FallbackValueTargetNullValueについて

「xaml Default Designer」みたいなノリで検索すると、BindingBindingBase.FallbackValueBindingBase.TargetNullValueを設定すればいいよ!といった感じの情報が見つかりました。
しかし、TextBlock.Textなどの場合はうまくいくのですが、Image.Sourceについては描いた瞬間は表示されるものの暫く経つor利用側のデザイナーで表示がされなくなってしまいます。

はじめは、Resourcesから供給される画像がBitmapなのとImage.SourceImageSourceの差の問題かとも思ったのですが、適切なConverterをかませても、暫く経つと表示されなくなってしまっていました。

BadSample.xaml
<Image Source="{
    Binding img,
        FallbackValue={StaticResource prop.Resources.fallback_image_icon},
        Converter={StaticResource local:DefaultImageWhenNull}}"/>
<!-- DefaultImageWhenNullは [ValueConversion(typeof(Bitmap), typeof(ImageSource))] なValueConverter -->

Converterで入力がnullの時にデフォルトの画像を返す方法

OSSの実装例をあさっていたところ、以下のようにConverterでデフォルト画像を指定しているところがありました。
ということでこれも試してみたのですが、結果はDataContextがBindされるまでConverterも動かない(FallbackValueがついていても動かない)ということで駄目でした。
実際、元PJを開いてみてもそもそもデザイナ自体表示できなくなっていたので、私のやりたかったことの答えではない意図っぽいです。
たぶん実行時のnullヨケですね。

BadSample2.cs
[ValueConversion(typeof(ImageSource), typeof(ImageSource))]
public class ImageSourceToThumbnailConverter : IValueConverter
{
    static readonly ImageSource _defaultThumbnail = MainWindow.Current.Resources["thumbnail_default"] as ImageSource;
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        => value ?? _defaultThumbnail;
    ...
}

d:DataContextを設定する。

UserControl側

PageItem.xaml
<UserControl
    ...
    d:DataContext="{
        d:DesignInstance local:MockData,
        IsDesignTimeCreatable=True}">
    ...
    <Image Source="{Binding img}" />
    ...
</UserControl>
PageItem.xaml.cs
...
public class MockData
{
    // 返す値はお好みで
    // 今回はプロジェクトプロパティのResource画像から
    public ImageSource img { get; }
        = Imaging.CreateBitmapSourceFromHBitmap(
            Properties.Resources.fallback_image_icon.GetHbitmap(),
            IntPtr.Zero, Int32Rect.Empty,
            BitmapSizeOptions.FromEmptyOptions());
}

コメント 2020-04-04 033950.png

利用側

何も考えずに取り込めばOK。

MainWindow.xaml
<Window ... >
    ...
    <local:PageItem />
    <local:PageItem />
    <local:PageItem />
    ...
</Window>

コメント 2020-04-04 033933.png

……と思っていたのですが、リビルドをかけた瞬間画像が吹っ飛びました。
どうも、時間が経つと消えるのではなく、リビルドが走ると消えるようです。
というのもリビルドしても、なぜかDataContextに入っているインスタンスがリロードされていない、つまりリビルド以前のインスタンスが入っています。
本当かよって感じですが、GetHashCode をTextBlockにダンプしてみたところ、値は変わりませんでした。
ここからは推測ですが、リビルドをしてアセンブリは切り替わりそれまでのメモリも解放されているのだと思います。
しかし、Image側はそれを握り続けているため解放済みの無効な画像を表示しようとしてもできないくなっていたのではないかと思います。
現に、ImageSourceもダンプしてみましたが、リビルドしてもnullにはなっていませんでした。
意味不明ですね。

参考

Default value at design time XAML(Stackoverflow)
Ito Mitsuhiro/NeeView ImageSourceToThumbnailConverter.cs(BitBucket)
XAMLデザイナ専用ViewModelコンストラクタの作り方(Qiita)

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

【WPF】WPFのUserControlで、Designer上でダミー画像が表示されるようにする

TL;DR;

<UserControl ... >
    ... 
    <UserControl.DataContext>
        <local:MockData />
    </UserControl.DataContext>
    ... 
</UserControl>

これで利用側は表示され続けます。
しかし、UserControl側はリビルドすると画像だけ消えます。

やりたかったこと

WPFやUWPの利点の一つにXAMLでUIが描けること、そしてVisualStudioではXAMLのデザイナー(グラフィカルエディタ)が搭載されていることがあります。
折角そんな素晴らしい機能があるので、UIの見た目をリアルタイムで確認しながらXAMLを書きたいものです。
しかし、StackPanelなどの自動レイアウトを使っていると、画像が読み込まれているときとそうでないときでImageコントロールのサイズが変わってしまい、デザイナー上での見た目が潰れて大変悲しいことになってしまいます。

これを何とかするために、実行時にBindする予定の部分にも何かしらダミーの画像を当てておいて、ある程度のサイズを確保してくれるようにしたかったのです。

駄目だったパターン

FallbackValueTargetNullValueについて

「xaml Default Designer」みたいなノリで検索すると、BindingBindingBase.FallbackValueBindingBase.TargetNullValueを設定すればいいよ!といった感じの情報が見つかりました。
しかし、TextBlock.Textなどの場合はうまくいくのですが、Image.Sourceについては描いた瞬間は表示されるものの暫く経つor利用側のデザイナーで表示がされなくなってしまいます。

はじめは、Resourcesから供給される画像がBitmapなのとImage.SourceImageSourceの差の問題かとも思ったのですが、適切なConverterをかませても、暫く経つと表示されなくなってしまっていました。

BadSample.xaml
<Image Source="{
    Binding img,
        FallbackValue={StaticResource prop.Resources.fallback_image_icon},
        Converter={StaticResource local:DefaultImageWhenNull}}"/>
<!-- DefaultImageWhenNullは [ValueConversion(typeof(Bitmap), typeof(ImageSource))] なValueConverter -->

Converterで入力がnullの時にデフォルトの画像を返す方法

OSSの実装例をあさっていたところ、以下のようにConverterでデフォルト画像を指定しているところがありました。
ということでこれも試してみたのですが、結果はDataContextがBindされるまでConverterも動かない(FallbackValueがついていても動かない)ということで駄目でした。
実際、元PJを開いてみてもそもそもデザイナ自体表示できなくなっていたので、私のやりたかったことの答えではない意図っぽいです。
たぶん実行時のnullヨケですね。

BadSample2.cs
[ValueConversion(typeof(ImageSource), typeof(ImageSource))]
public class ImageSourceToThumbnailConverter : IValueConverter
{
    static readonly ImageSource _defaultThumbnail = MainWindow.Current.Resources["thumbnail_default"] as ImageSource;
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        => value ?? _defaultThumbnail;
    ...
}

d:DataContextを設定する。

UserControl側

PageItem.xaml
<UserControl
    ...
    d:DataContext="{
        d:DesignInstance local:MockData,
        IsDesignTimeCreatable=True}">
    ...
    <Image Source="{Binding img}" />
    ...
</UserControl>
PageItem.xaml.cs
...
public class MockData
{
    // 返す値はお好みで
    // 今回はプロジェクトプロパティのResource画像から
    public ImageSource img { get; }
        = Imaging.CreateBitmapSourceFromHBitmap(
            Properties.Resources.fallback_image_icon.GetHbitmap(),
            IntPtr.Zero, Int32Rect.Empty,
            BitmapSizeOptions.FromEmptyOptions());
}

コメント 2020-04-04 033950.png

利用側

何も考えずに取り込めばOK。

MainWindow.xaml
<Window ... >
    ...
    <local:PageItem />
    <local:PageItem />
    <local:PageItem />
    ...
</Window>

コメント 2020-04-04 033933.png

……と思っていたのですが、リビルドをかけた瞬間画像が吹っ飛びました。
どうも、時間が経つと消えるのではなく、リビルドが走ると消えるようです。
というのもリビルドしても、なぜかDataContextに入っているインスタンスがリロードされていない、つまりリビルド以前のインスタンスが入っています。
本当かよって感じですが、GetHashCode をTextBlockにダンプしてみたところ、値は変わりませんでした。
ここからは推測ですが、リビルドをしてアセンブリは切り替わりそれまでのメモリも解放されているのだと思います。
しかし、Image側はそれを握り続けているため解放済みの無効な画像を表示しようとしてもできないくなっていたのではないかと思います。
現に、ImageSourceもダンプしてみましたが、リビルドしてもnullにはなっていませんでした。
意味不明ですね。

参考

Default value at design time XAML(Stackoverflow)
Ito Mitsuhiro/NeeView ImageSourceToThumbnailConverter.cs(BitBucket)
XAMLデザイナ専用ViewModelコンストラクタの作り方(Qiita)

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

WPFのUserControlで、Designer上でダミー画像が表示されるようにする

TL;DR;

UserControl側

d:DataContextを設定する。

PageItem.xaml
<UserControl
    ...
    d:DataContext="{
        d:DesignInstance local:MockData,
        IsDesignTimeCreatable=True}">
    ...
    <Image Source="{Binding img}" />
    ...
</UserControl>
PageItem.xaml.cs
...
public class MockData
{
    // 返す値はお好みで
    // 今回はプロジェクトプロパティのResource画像から
    public ImageSource img { get; }
        = Imaging.CreateBitmapSourceFromHBitmap(
            Properties.Resources.fallback_image_icon.GetHbitmap(),
            IntPtr.Zero, Int32Rect.Empty,
            BitmapSizeOptions.FromEmptyOptions());
}

コメント 2020-04-04 033950.png

利用側

何も考えずに取り込めばOK。

MainWindow.xaml
<Window ... >
    ...
    <local:PageItem />
    <local:PageItem />
    <local:PageItem />
    ...
</Window>

コメント 2020-04-04 033933.png

やりたかったこと

WPFやUWPの利点の一つにXAMLでUIが描けること、そしてVisualStudioではXAMLのデザイナー(グラフィカルエディタ)が搭載されていることがあります。
折角そんな素晴らしい機能があるので、UIの見た目をリアルタイムで確認しながらXAMLを書きたいものです。
しかし、StackPanelなどの自動レイアウトを使っていると、画像が読み込まれているときとそうでないときでImageコントロールのサイズが変わってしまい、デザイナー上での見た目が潰れて大変悲しいことになってしまいます。

これを何とかするために、実行時にBindする予定の部分にも何かしらダミーの画像を当てておいて、ある程度のサイズを確保してくれるようにしたかったのです。

駄目だったパターン

FallbackValueTargetNullValueについて

「xaml Default Designer」みたいなノリで検索すると、BindingBindingBase.FallbackValueBindingBase.TargetNullValueを設定すればいいよ!といった感じの情報が見つかりました。
しかし、TextBlock.Textなどの場合はうまくいくのですが、Image.Sourceについては描いた瞬間は表示されるものの暫く経つor利用側のデザイナーで表示がされなくなってしまいます。

はじめは、Resourcesから供給される画像がBitmapなのとImage.SourceImageSourceの差の問題かとも思ったのですが、適切なConverterをかませても、暫く経つと表示されなくなってしまっていました。

BadSample.xaml
<TextBlock Text="{
    Binding MyText,
        FallbackValue={StaticResource prop.Resources.fallback_image_icon},
        Converter={StaticResource local:DefaultImageWhenNull}}"/>
<!-- DefaultImageWhenNullは [ValueConversion(typeof(Bitmap), typeof(ImageSource))] なValueConverter -->

Converterで入力がnullの時にデフォルトの画像を返す方法

OSSの実装例をあさっていたところ、以下のようにConverterでデフォルト画像を指定しているところがありました。
ということでこれも試してみたのですが、結果はDataContextがBindされるまでConverterも動かない(FallbackValueがついていても動かない)ということで駄目でした。
実際、元PJを開いてみてもそもそもデザイナ自体表示できなくなっていたので、私のやりたかったことの答えではない意図っぽいです。
たぶん実行時のnullヨケですね。

BadSample2.cs
[ValueConversion(typeof(ImageSource), typeof(ImageSource))]
public class ImageSourceToThumbnailConverter : IValueConverter
{
    static readonly ImageSource _defaultThumbnail = MainWindow.Current.Resources["thumbnail_default"] as ImageSource;
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        => value ?? _defaultThumbnail;
    ...
}

参考

Default value at design time XAML(Stackoverflow)
Ito Mitsuhiro/NeeView ImageSourceToThumbnailConverter.cs(BitBucket)
XAMLデザイナ専用ViewModelコンストラクタの作り方(Qiita)

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

【Unity】オンデマンドレンダリングで描画処理負荷を抑える

はじめに

Unity 2019.3からオンデマンドレンダリング(On-demand rendering)なるものが追加されました。1
レンダリングのパフォーマンスには苦労することの多いモバイルアプリ開発などで特に有用そうな機能でしたのでご紹介します。

どういうもの?

ゲームループ(MonoBehaviour.Updateとか)のフレームレートは通常通りに、レンダリングを一定間隔でスキップできる機能です。
normal_rendering.png

ondemand_rendering.png

これの一番の使いどころは(特にモバイル端末での)「レンダリング処理負荷低減」でしょう。
例えばユーザー入力待機画面のような画面の動きが激しくない(=見た目のフレームレートを落としても影響が小さい)場面なら、レンダリングを適度にスキップすることでクオリティそのままに端末の発熱や消費電力を減らすことが期待できます。

また、従来からあるフレームレート調節方法であるApplication.targetFrameRateを変える方法よりも、入力の応答性という点でより良い選択になると考えられます。2
例として、入力がない間はフレームレートを落としておき、入力があったタイミングでフレームレートを上げて応答することを考えた際に下の違いがあります。

target_frame_rate_interaction.png

ondemand_rendering_interaction.png

あとは、シミュレーションや機械学習といったシチュエーションでも望まれていた機能ではないかと思います。
それらは実際はレンダリングを必要としないことがほとんどなので、この機能を活用してレンダリングにリソースを割かないようにすればより早く結果を得られると考えられます。

使用方法

制御はスクリプトから行います。
名前空間UnityEngine.RenderingOnDemandRenderingというクラスに3つのStaticなAPIが用意されています。

OnDemandRendering.renderFrameInterval

私たちが直接変更できる唯一のプロパティであり、これでレンダリングをおこなう間隔を設定します。
例えばこれに3を代入すれば、ゲームループの3フレーム中に1回レンダリングするようになります。(レンダリング更新周期が1/3)

タッチ入力があるまで20[fps]にしておき、タッチがあったらそこから60[fps]にする例はこんな感じです。

OnDemandRenderingTest.cs
using UnityEngine;
using UnityEngine.Rendering;

public class OnDemandRenderingTest : MonoBehaviour
{
    private void Start()
    {
        QualitySettings.vSyncCount = 0;
        Application.targetFrameRate = 60;

        // 20 (= 60 / 3) fps
        OnDemandRendering.renderFrameInterval = 3;
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            // 60 fps
            OnDemandRendering.renderFrameInterval = 1;
        }
    }
}

OnDemandRendering.effectiveFrameRate

現在の設定から想定されるレンダリングフレームレート値です。
例えばゲームループを60fpsで動かしている端末なら、通常はレンダリングも60fpsですが、renderFrameIntervalを3に設定しているときは20(=60 / 3)が返ってきます。

OnDemandRendering.willCurrentFrameRender

現在のゲームループフレームがレンダリング対象かを返します。

will_current_frame_render.png

これは何に使えるのか…ということですが、レンダリングにリソースを割かなければならないフレームかどうかが判断できるので、「見た目のフレームレートを安定させつつ、高負荷な処理を裏で回す」的なことに使えるっぽいです。
なかなか使う場面は限られそうですが、使い方のイメージとしてはこんな感じでしょうか。

OnDemandRenderingTest2.cs
using UnityEngine;
using UnityEngine.Rendering;

public class OnDemandRenderingTest2 : MonoBehaviour
{
    private void Update()
    {
        if (OnDemandRendering.willCurrentFrameRender)
        {
            // レンダリングするので重い処理はしない
        }
        else
        {
            // レンダリングしてないので重い処理を実行
        }
    }
}

おわりに

今回は特に検証などはしていないですが、上手に使えば高い効果が得られそうです。
モバイルアプリ開発なんかでは積極的に取り入れていきたい機能だと感じました。

参考


  1. オンデマンドは「要求に応じて」の意味。 

  2. そもそもApplicationクラスの値は頻繁に変えることになんか抵抗があります…。 

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