sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

Unity「2D game art, animation, and lighting for artists」を日本語で読む

2022年5月6日に Unity Blog より「過去最大ボリュームの e ブック:アーティストのための 2D ゲームアート、アニメーション、ライティング」の記事があがりました。

まだ内容はよくわかりませんが、面白そうなので読み進めていこうと思います。個人的に和訳したテキストは、正しいのかどうか自信ありませんが、(自分でも読み返したいので)参考程度に zenn に保存しておきます。なお、画像は(可能な範囲で)自分でも Unity を動かして取り直しているものもあります。

zenn.dev

5/14 加筆:序盤は、デザイナー向けの情報が多めですが Unity で 2D を扱う上で便利な情報がまとまっていると思いました。(カメラの設定例や、Sprite Atlas など)

結構労力をかけて進めることになりそうなので、興味のあるかたはぜひ。(思ったよりもつまらなかったら、途中で打ち切りもあり得ます。その際はお察しください)

更新履歴

  • 2022/5/10 Introduction, Project setup の追加
  • 2022/5/11 The art for your game の追加
  • 2022/5/13 Choose the perspective の追加
  • 2022/5/14 Resolution of your assets の追加
  • 2022/5/23 Level design の追加(まだ途中まで P38)
  • 2022/6/05 Level design の追加(まだ途中まで P41)

C# Excel ファイルからセルのデータを読み込む(ClosedXML)

C#Excel ファイルを読み込んで利用する方法は、ライブラリーを利用したいくつかのやり方があると思います。おそらくよく検討に挙がりそうなのは以下のあたりだと思います。

個人的な感想だと、開発が比較的に盛んなものは NPOI で、コードが比較的に読みやすいものは ClosedXML だと思いました。MIT ライセンスで機能の充実している SpreadsheetLight も存在感があり、海外だと EPPlus も盛んな印象ですが、ライセンスは他と違う点で留意しましょう。(商用の場合はライセンス購入要と思われます)速度の面では、ExcelDataReader が優れるといったデータもありました。

古くからあるライブラリーは .xls 形式(バイナリ形式)に対応しています。が、注意点として COM を利用している場合があります。C# だと COM オブジェクト解放をしないと、メモリー使用の上で問題が生じることになる上、EXCEL 自体を参照することがあるので、そもそも EXCEL のインストールが必要になります。

比較的新しいライブラリーは、.xlsx(OpenXML 形式)にのみ対応していることがあります。(ClosedXML など)オープン規格に対応する形なので、COM オブジェクト問題のようなことが発生しませんし、EXCEL のインストールも必須になりません。

これ以外に、.xls/.xlsx 形式に対応して COM 利用が無い、EXCEL インストールも必須ではないし、グラフの扱いもできるといったケースも存在するみたいです。(SpreadsheetLight など)

まとめると 2022 年現在だと、さほど複雑ではない Excel ファイルを利用する際は、NPOI, ClosedXML, ExcelDataReader あたりの選択が個人的にはベターだと思いました。(開発頻度と、人気などから)

すこし高度な EXCEL の扱いを検討する場合、SpreadsheetLight はよいかもしれませんが、開発は個人で、更新頻度も低い点は気になりました。(最終更新も .NET Standard 2 への対応なので)

2020 年ごろで更新が止まっているプロジェクトが多く、ある程度成熟した分野になって手を加えることがあまりないのかもしれませんが、どのライブラリーも非同期処理 (async/await) をサポートしていないと思います……。

ClosedXML を使った読み込み

ClosedXML は、利用してみたところ比較的に用意されているメソッドがわかりやすいと思いました。

特定セルのデータを取得するサンプルは以下のとおり。

var result = Read(@"C:\sample.xlsx", "A1");
private string Read(string filePath, string address)
{
    using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    using var book = new XLWorkbook(stream);

    var sheet = book.Worksheets.FirstOrDefault();

    if (sheet == null) return "";

    var cell = sheet.Cell(address);

    return cell?.GetValue<string>() ?? "";
}

細かいポイントだけど new XLWorkbook(filePath) でもいいのに stream で渡しています。ファイルパスを直接渡してしまうと、EXCEL を開きながらセルのデータを取得できないからですね。以下の例外が発生してしまうと面倒なので。

別のプロセスで使用されているため、プロセスはファイル“sample.xlsx”にアクセスできません。

それから、この処理は File I/O です。現在だと ReadAsync のような非同期処理にしたいことがあるかも。ざっくり対応するなら:

private async Task<string> ReadAsync(string filePath, string address)
{
    return await Task.Run(() => Read(filePath, address));
}

とりあえず、Read メソッド自体を書き換えて、Task.Run() を盛り込まないほうがいいと思います。(可能なら使いたくないし)ネストも深くなってしまう。もともとライブラリーで対応していないのだから、仕方なくオプションとして Async メソッドを追加しておく。そのぐらいでいいんじゃないかと思います。(例外処理に注意)

サンプル

GitHub に今回のサンプルを公開しています。

参考

Visual Studio ClickOnce 「項目 "xxx" の公開プロパティを適用できません」の対策

f:id:shikaku_sh:20220418151631p:plain:w200

.NET (.NET Core) でも ClickOnce を利用したアプリケーションの公開(発行)ができると知ったのでテストしてみたところ、「項目 "xxxxx.png" 公開プロパティを適用できません」といった内容の出力が出てしまい、特定のリソース(アセット)をインストール先のアプリケーションに含めることができませんでした。

この対応がちょっとわかりづらかったので、(おそらく改善しそうだけど)やり方をメモ。

ClickOnce でアプリを発行する

ClickOnce で発行するプロセスは、「プロジェクト」を右クリックで選択し「発行」のメニューを押下します。

「ターゲット」「特定のターゲット」「発行場所」「インストール場所」これらは、それほど迷うところは無いと思います。

f:id:shikaku_sh:20220418151858p:plain:w200f:id:shikaku_sh:20220418151901p:plain:w200

注意するポイントは、「設定」。

f:id:shikaku_sh:20220418152341p:plain:w300f:id:shikaku_sh:20220418152343p:plain:w300

「アプリケーションファイル」のリンクを選択して、必要なリソース(画像や設定ファイルなど)が含まれていること(除外されていないか)を確認します。

マニュフェストの署名は、従来通りですね。動かすだけなら、なにも必要ありません。作ってみたいときは「証明書を作成する」が参考になると思いますので割愛。

最終的に setup.exe でインストールを実行します。

発行の設定ファイル

  • プロジェクトフォルダー > Properties > PublishProfiles

.pubxml ファイルを参照します。発行に不明な問題が発生したら、このファイルに異常がでている恐れがあります。

アプリケーションのインストール先

ClickOnce を利用してインストールしたアプリケーションは、Program Files にインストールされるわけではありません。以下のパスになります:

  • C:\Users\ユーザー名\AppData\Local\Apps\2.0

動作検証の段階ではアプリケーションがどのようにインストールされたかを把握するとよいと思います。(アプリケーションのフォルダー名もわかりづらいです)

エラー:項目 "〇〇" 公開プロパティを適用できません

通常であれば、上記の発行をすれば、アプリケーションを配布できるはずなのですが、ビルドの出力情報を読むと特定のリソースについてのメッセージが出力されていました。

setup.exe を作成した発行先フォルダーには、問題のリソースファイルはコピーされています。しかし、setup.exe を使ってインストールしたユーザーのフォルダーには、問題のリソースファイルはコピーされていません

対策

問題の「ClickOnce」の公開(発行)設定を削除しておきます。(念のため再構築する目的)

f:id:shikaku_sh:20220418152458p:plain

問題のあるプロジェクトの .csproj ファイルをメモ帳等で開きます。下のように、ItemGroup を新しくひとつ追加して、Content Include= の形式で問題リソースを記述します。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0-windows10.0.17763.0</TargetFramework>
    <RootNamespace>JSL_Enterprise</RootNamespace>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="DotNetEnv" Version="2.3.0" />
    <PackageReference Include="Prism.Unity" Version="8.1.97" />
  </ItemGroup>

  <!-- ItemGroup に公開プロパティを適用できなかった項目を記述 -->
  <ItemGroup>
    <Content Include=".env" />
    <Content Include="Assets\Images\error.png" />
    <Content Include="Assets\Images\info.png" />
    <Content Include="Assets\Images\warning.png" />
  </ItemGroup>

  <ItemGroup>
    <None Update=".env">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Assets\Images\error.png">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Assets\Images\info.png">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Assets\Images\warning.png">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>

編集できたら Visual Studio を再起動して、もう一度「ClickOnce」を再編集し「発行」します。問題の出力が表示されないことを確認して、アプリを再インストールします。

AppData の実際のファイル構成も確認しておくとよいです。

参考

徹底解説Visual Studio Code

徹底解説Visual Studio Code

Amazon

IList から List<T>, IEnumerable<T> に変換する

f:id:shikaku_sh:20210823173353p:plain:w300

稀に IList 型のデータを受け取ることがあります。ObservableCollection の CollectionChanged のイベントなどは、IList で変更のあったデータのコレクションを返却します。 しかし、IList から IList<T> に変換する際は一工夫しないとダメです。その内容をメモ。

IList to IList<T>

IListIList<T> は単純に as 句で変換できそうなものですが、これがうまくいきません。 継承関係をよーく見てみよう。(IList から IEnumerable<T> に変換したいときも同じ)

f:id:shikaku_sh:20220413160124p:plain

C# 2.0 で追加されたジェネリック型に対応した IList<T>IList は別々の派生になっており IEnumerable くらいしか互換性が無さそうです。 as 句だけだとうまく変換できない理由はわかったので、拡張メソッドで解決する例を示すことにします。

internal static class Extension
{
    public static List<T> ToList<T>(this System.Collections.IList source)
    {
        return source?.Cast<T>().ToList() ?? new List<T>();
    }

    public static T[] ToArray<T>(this System.Collections.IList source)
    {
        return source?.Cast<T>().ToArray() ?? Array.Empty<T>();
    }
}

こんな感じで変換します。

Samples.CollectionChanged += (sender, args) =>
{
    // OldItems が IList? 型
    var list = args.OldItems?.ToList<Data>() ?? new ();

    list.ForEach(p => Debug.WriteLine(p));
};

この種の拡張メソッド定義は、独自のライブラリーなんかによく収録されたりすると思いますが、名前空間なしに定義するのも(当然)憚られたり、標準の名前空間を共有するのもややこしいと感じることがあったりする。

そういうときは、global using が役に立つことがあります。

global using ディレクティブ

拡張メソッドをまとめたようなライブラリーの名前空間 using 句をすべてのプログラムファイルに記述するのは、「わかりきっているため」冗長に感じることがあります。 そういうときは、思い切って global using を使うのもよいと思います。

わかりきっているような using は、今となっては得られる情報もすくないので、省略するような感じです。(たとえば、以下の using 句の並びは見慣れたものだと思います)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

global using を利用する際は、using.csimports.cs ファイルなどプロジェクトごとに1つのファイルで実施するようにしましょう。やたらめったらに書くと混乱します。

個人的なシーンでは、毎回使うような独自のオリジナルライブラリーを持っていると、プロジェクトのたくさんのファイルに using を追加する必要があって面倒だったりします。

特に拡張メソッドを定義した名前空間なんかは、なるべく共有したいので、大きいプロジェクトでは global using 用のファイルを1つ生成しておきます。

global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Text;
global using System.Threading.Tasks;
// コメント
global using MyLibrary.Extensions;

それほど難しい機能ではないですが、マナーやお作法として注意が必要な機能です。 どういったものか見ておきましょう。

参考

LINQ 式のデータベースアクセスでクラス/インターフェースの型などを含めるとエラーになるときの対処

C#LINQ 式によるデータベースアクセスの際に、クラス/インターフェースの値など、プリミティブ型ではない型を含めるとエラーになることがあります。

具体的なエラー内容

たとえば、次のようなコードが考えられます。

問題例

public async Task<IEnumerable<IProduct>> SearchProductsAsync(string orderNo, string refMasterNo, string productName, IEndUser endUser, bool needHidden, int limit)
    {
        List<Product> products;

        using (var context = new DbContext())
        {
            products = await context.Products
                .Where(p => !string.IsNullOrEmpty(orderNo) ? p.OrderNo.Contains(orderNo) : true)
                .Where(p => !string.IsNullOrEmpty(refMasterNo) ? p.RefMasterNo.Contains(refMasterNo) : true)
                .Where(p => !string.IsNullOrEmpty(productName) ? p.Name.Contains(productName) : true)
                .Where(endUser != null ? p.EndUserId == endUser.Id : true) // エラー
                .Where(p => needHidden ? true : p.IsDeleted == false)
                .Take(limit)
                .ToListAsync();
        }

        return products;
    }

テーブルから、入力のあった値だけ絞り込みをかけたい場合、上述のように記述することもできると思いますが、これだとエラーになってしまいます。

Unable to create a constant value of type '***.Services.Db.Interfaces.IEndUser'.
Only primitive types or enumeration types are supported in this context

LINQ を生成するときに、enum やプリミティブ型以外のものを持ってくると、正しいコードが生成できなくてエラーになってしまいます。コードをシンプルにしてやる必要があるわけですね。

対応例

先にプリミティブ型 int にデータを移してから、LINQ を実行します。

public async Task<IEnumerable<IProduct>> SearchProductsAsync(string orderNo, string refMasterNo, string productName, IEndUser endUser, bool needHidden, int limit)
    {
        List<Product> products;
        int endUserId = endUser?.Id ?? 0;

        using (var context = new DbContext())
        {
            products = await context.Products
                .Where(p => !string.IsNullOrEmpty(orderNo) ? p.OrderNo.Contains(orderNo) : true)
                .Where(p => !string.IsNullOrEmpty(refMasterNo) ? p.RefMasterNo.Contains(refMasterNo) : true)
                .Where(p => !string.IsNullOrEmpty(productName) ? p.Name.Contains(productName) : true)
                .Where(p => endUserId > 0 ? p.EndUserId == endUserId : true)
                .Where(p => needHidden ? true : p.IsDeleted == false)
                .OrderBy(p => p.UpdatedDateTime)
                .Take(limit)
                .ToListAsync();
        }

        return products;
    }

考察

単純に DB 問い合わせをするクエリの中に、知らない型を含めたらダメじゃないか(そこまで都合よく最適化してくれない)という理解でいいと思います。

という記事があって、これも DB データとローカルのコレクションをおそらく Join しようとしてエラーになっているのだと思います。なので、DB アクセスをする処理のときは、ローカルなデータから利用する型は、プリミティブ型だけに制限できているのか注意が必要です。

エラー発生の雰囲気がちょっとだけ通常と違う不思議な感じ(コンパイル前の構文チェックでは問題ないし)なので、原因は何になるのかドキっとするかもしれないけど、エラーメッセージにちゃんと原因(理由)が書いてありました。

エラー内容を読んであげようという学び(常識だった)

参考

WPF トーストの表示

WPF (.NET6) でトースト表示をテストしてみた内容を記録します。

かつてのトースト表示は、アプリケーションをパッケージ化したり証明書が必要であったりしたため、利用には面倒がありました。 現在は、かなりの面倒がなくなっており、ちょっとした通知の実装の際はおススメできるかもしれないです。

f:id:shikaku_sh:20220311163800g:plain
トースト通知

NuGet

  • .csproj を直接メモ帳で開く
    • TargetFrameworkWindowsnet*.0-windows10.0.17763.0 に変更
  • VisualStudio を再起動
  • Microsoft.Toolkit.Uwp.Notifications を追加

f:id:shikaku_sh:20220311163914p:plain:w500

f:id:shikaku_sh:20220311163848p:plain:w500

基本的な通知

var imageUri = System.IO.Path.GetFullPath(@"Images\info.png");

new ToastContentBuilder()
    .AddText("テストタイトル")
    .AddText("テキスト")
    .AddAppLogoOverride(new Uri(imageUri))
    .AddButton(new ToastButton().SetContent("OK"))
    .Show();

WPF の場合は、ローカルの画像をこのように単純なファイル指定で利用することができます。

トーストの UI は Add + 「コントロール」のメソッドを使っていくつもコンテンツを追加していくことができます。基本的には:

  • テキスト
  • 画像
  • ボタン
  • 効果音
  • テキストボックス、コンボボックスのような対話型コントロール

トーストによる通知は、Windows が通知した履歴を長期間保管しているものではないので、一度クリアしてしまうと再表示できないです。基本的には、アプリケーションをアクティブウィンドウとして利用していないときに、メッセージを配信する用途で利用されるくらいの想定みたいです。(Microsoft Docs 参照)

通知の明示的な削除

通知は、ユーザーがマニュアルで削除するか、以下のコマンドで削除するなど。

ToastNotificationManagerCompat.History.Clear();

サンプル

作成したサンプルを公開します。

GitHub - ToastTest

参考

ソフトウェア開発のマニフェストを読んでみよう

f:id:shikaku_sh:20220303140544p:plain:w200

ベタープログラマを読んでいて、ソフトウェア開発のためのマニフェストがあることを知りました。(37章 多くのマニフェスト

より良いコードを書こうと思っているコーダーが、どういったマニフェストを宣言しているのか、いくつか読んだものをまとめたいと思います。

マニフェストとは、個人や団体が方針や意図を知らしめるための文書や演説。

アジャイルマニフェスト

日本語訳が形式ばった感じだったので、自分が読むと以下のように思いました。

雑訳した例

私たちは、ソフトウェア開発のより良い方法を、実際に行ったり他人を助けたりする中で、その方法を明らかにしてきました。開発を通じて、私たちは次のことを大切にします:

  • Individuals and interactions over processes and tools
    プロセスやツールよりも、個々人の意思疎通
  • Working software over comprehensive documentation
    包括的なドキュメントよりも、実用的なソフトウェア
  • Customer collaboration over contract negotiation
    契約よりも、顧客とのコラボレーション(協調)
  • Responding to change over following a plan
    計画よりも、変化への対応

この項目はどれも価値を持ちますが、太字の項目はより大切です。

クラフトマンシップマニフェスト

このマニフェストは、アジャイル開発が実践を重要視するあまり、開発者が優れたコードを書く責任感を低下させているのではないか、というところから生じたマニフェストらしいです。

残念ながら日本語に対応していませんが読んでみたところ、たぶんこんな感じではないかと思います。

雑訳した例

Raising the bar. (水準を引き上げる)

ソフトウェアのクラフトマン(職人)を目指す私たちは、ソフトウェアの開発、他人が技術を学ぶことを助けることで、プロフェッショナルなソフトウェア開発の水準を引き上げています。

開発を通じて、私たちは次のことを大切にします:

  • Not only working software, but also well-crafted software
    動作するソフトウェアというだけではなく、良くできたソフトウェアであることも大切
  • Not only responding to change, but also steadily adding value
    変化に対応することだけではなく、しっかりとした付加価値があることも大切
  • Not only individuals and interactions, but also a community of professionals
    個々人の意思疎通だけではなく、プロフェッショナルなコミュニティであることも大切
  • Not only customer collaboration, but also productive partnerships
    顧客とのコラボレーション(協調)だけではなく、生産的なパートナーシップ(協調)も大切

つまり、左項目を追及すると、右項目が欠かせないことだとわかりました。

リファクタリングマニフェスト

リファクタリングに関するマニュフェストも Bas Vodde, Lasse Koskela の書いたものがあります。

見ての通り、パッションを意識したような内容です。

雑訳した例

  1. 製品を長持ちさせよう!
    リファクタリングとは、製品を長持ちさせる機会を作ることです。(コードを)捨てないで、縫合しよう。コードを終わらせないで、改善します。リファクタリングは、無駄なコストではありません。(プログラムの)変化を拒んでいる不必要に複雑になったコードへの対策(改善)です。
  2. デザインはリファクタリングしやすいようにシンプルにすべき
    Product designers: あなたの製品を簡単に変更できるように作ります。クリーンに理解しやすいコードを書きます。
    Consumers: 継続的にリファクタリングされている製品を買うか、なぜ開発者がリファクタリングしていないのかを明らかにします。しつこく、口やかましくなって。
  3. リファクタリングはリライトではない
    書き直し(リライト)することは、壊れたもの (bit) を捨てることです。これは、私たちが話をしているリファクタリングではありません。
  4. 切り捨てないものは、より強くなる
    コードをリファクタリングするたびに、コードにポテンシャル、歴史(履歴)、精神、そして、美しさが加えられます。
  5. リファクタリングとは、想像的な挑戦
    リファクタリングは、想像力を働かせます。新しい技術(ツール、素材等)を使うことで、可能性を先導します。(デッドエンドではなくて)
  6. リファクタリングはファッションを生き残る(流行に左右されない)
    リファクタリングは、スタイリング(デザインの様式)や流行と関係ありません。リファクタリングされたコードに期限はありません。
  7. リファクタリングとは、発見すること
    オブジェクトをリファクタリングすると、どのように動作するのか、驚くようなことを学ぶことができます。(または、うまくいかないか)
  8. 悪いときでもリファクタリングすること
    もし、あなたがこのマニフェストが不景気なときは例外と思っているなら、それは違います。これは努力の問題ではなくて、メンタリティの問題です。
  9. リファクタリングとは、独立性のこと
    レガシーコードの奴隷にならず、レガシーコードのマスターになりましょう。もし、コードが壊れているなら、リファクタリングをしてより良い状態にしましょう。もし、あなたがマスタになれたら、他人にも力を与えてください。
  10. あなたは何でもリファクタリングすることができる。まったくのゴミでも
    しかし、完全なゴミのリファクタリングは避けることをおススメします。リファクタリングは、コードがゴミになるのを防ぐものだから。

コードの書き直しを止めましょう。リファクタリングを始めましょう。

感想

マニフェストやポリシーは、個人的に NOT(~してはいけません)のようなものは、あまり好きじゃないです。Let's(~しよう)のほうが好きです。気持ちよく受け取れるというか。

ベタープログラマ自体は、技術書というよりは啓蒙するような内容です。良い習慣・考え方を知ろう! みたいなものなので、これで新しいコーディングテクニックが身につくわけじゃないと思います。

NOT(~してはいけません)のようなものを考えるときは、「誓約」と「制約」と考えたりもします。(HUNTERxHUNTER)いずれにしても、いままで以上の力をパフォーマンスとして発揮し、発揮したパフォーマンスを正しいベクトルに出力することで、より良いプログラマになろう、というものだと思います。

参考