LoslynAnalyzerを作成してUnityで使ってみる

はじめに

LoslynAnalyzer を使用することで,
ソースコードをチェックし問題があれば
error や warning として表示する機能を独自に実装することができます。

Unity でも使用することができるので,
今回は簡単な Loslyn Analyzer プロジェクトを作成し,
Unity に適用するまでを試してみます。

環境

Windows 11 Home 24H2
Unity バージョン 6000.1.6f1
Visual Studio : 2022

Analyzer のソリューションを作成する

Visual Studio にて, 「新しいプロジェクトの作成」を行います。
C# の「Analyzer with Code Fix (.NET Standard)」テンプレートを選択し, 任意のディレクトリに作成します。
自分は Unity の Assets ディレクトリと同階層に SampleAnalyzer を作成し指定しました。

.NETFramework は 4.8 で作成しました。
他の記事だと .NETStandard 2.0 を選択していましたが,
作成した Analyzer の csproj に以下の記述があったのでよしとしました。

<TargetFramework>netstandard2.0</TargetFramework>

ソリューションを作成すると
いくつかのプロジェクトで構成されていることがわかるかと思います。

各プロジェクトの説明は以下です(GPT談)。

  • Analyzer.csproj : コードアナライザーの本体を実装するプロジェクトで違反を検出するロジックを記述する
  • Analyzer.CodeFixes.csproj : コードの問題を自動修正するためロジックを実装する
  • Analyzer.Paclage.csproj : Analyzer と CodeFixes を NuGet パッケージとしてビルドするのに使用する
  • Analyzer.Test.csproj : アナライザーとコードフィックスの単体テストを記述する
  • Analyzer.Vsix.csproj : Visual Studio拡張機能(VSIXファイル)としてビルドするのに使用する


今回はエラーだけ出せれば良いので,
Analyzer と Test 以外は削除してしまいました。

今回は確認用に以下のようなコードを用意しました。
Analyzer.csproj 以下の cs ファイルの内容を書き換えます。

このコードは Debug.Log を使用するとエラーにするものです。

using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace SampleAnalyzer
{
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public sealed class SampleAnalyzerAnalyzer : DiagnosticAnalyzer
    {
        private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
            id: "SA0001",
            title: "SampleAnalyzer Error",
            messageFormat: "messageFormat",
            category: "Usage",
            defaultSeverity: DiagnosticSeverity.Error,
            isEnabledByDefault: true,
            description: "description");

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
            => ImmutableArray.Create(Rule);

        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

            context.RegisterSyntaxNodeAction(AnalyzeInvocation, SyntaxKind.InvocationExpression);
        }

        private static void AnalyzeInvocation(SyntaxNodeAnalysisContext ctx)
        {
            var invocation = (InvocationExpressionSyntax)ctx.Node;

            var symbolInfo = ctx.SemanticModel.GetSymbolInfo(invocation);
            if (!(symbolInfo.Symbol is IMethodSymbol methodSymbol))
            {
                return;
            }

            var containingType = methodSymbol.ContainingType;
            if (containingType == null ||
                containingType.ToDisplayString() != "UnityEngine.Debug")
                return;

            var methodName = methodSymbol.Name;
            if (methodName == "Log" || methodName == "LogWarning" || methodName == "LogError" || methodName == "LogException")
            {
                // Error.
                ctx.ReportDiagnostic(Diagnostic.Create(Rule, invocation.GetLocation()));
            }
        }
    }
}

ビルド

Analyzer の準備ができたので dll を作成します。
Visual Studio のビルドメニューからバッチビルドを選択し, ビルドを実行します。
以下に dll が生成されるかと思います。

SampleAnalyzer\SampleAnalyzer\SampleAnalyzer\bin\Release\netstandard2.0\SampleAnalyzer.dll

Unity に配置する

以下の公式ドキュメントを参考に Unity のディレクトリに dll を複製して配置します。
(ちなみに explorer から D&D で Project View に移動させれば勝手に複製されます)
Unity - Manual: Create and use a source generator
添付画像のように設定して完了です。


添付画像のようにコンソールに Debug.Log を使用している箇所のエラーが出ていれば成功です。


Visual Studio Code でも, Debug.Log を使用するとエラーになることが確認できました。


おわりに

今後 AI にコードを書かせる機会が増えてくるので,
コンパイルエラーで気づけるようにしておくのは今後必要になるかもなと思いました。

Analyzer は既成のものを使用する手法もあるので, そちらも試していきたいです。
以上.

ClaudeCodeのベストプラクティスを読む1

はじめに

Claude Code のベストプラクティス記事が開発元の Anthropic から出ていました。
Claude Code を扱う上で知っておいたほうが良いことが記載されているので,
読みながら自分の参照用にまとめます。

今回まとめた箇所は元記事の一部で,
検証はあまりできていないので注意してください。

正確な内容については以下を参照してください。
https://www.anthropic.com/engineering/claude-code-best-practices

環境の最適化

Claude Code はプロンプトに自動的にコンテキストを取り込みながら
実装をしてくれますが, 特に意識せずに ClaudeCode を使用していると,
コンテキストの収集に時間とトークンを消費してしまいます。

工夫することでこのコンテキストの収集を最適化できます。

CLAUDE.md ファイルの作成

CLAUDE.md は会話を始めたタイミングで,
Claude が自動的にコンテキストとして読み込むファイルです。

ファイル内容の記載形式に決まりは無く,
簡潔に人が読みやすい 形でまとめることが推奨されています。
また, 以下のような情報を記述するとよいとのことです。

  • よく使う Bash コマンド
  • 中核となるファイルやユーティリティ関数
  • コーディング規約
  • テスト実行手順
  • リポジトリの運用ルール(例: ブランチ命名規則、マージかリベースか など)
  • 開発環境のセットアップ方法(例: pyenv の利用、使用可能なコンパイラ
  • プロジェクト特有の予期しない挙動や警告
  • Claude に記憶してほしいその他の情報

便利機能として, claude に対して, /init を実行すると
Claude が自動で CLAUDE.md のひな形を生成してくれます。

CLAUDE.md を配置できる場所

リポジトリのルート

最も一般的な置き場所。
共有したくない場合は CLAUDE.local.md と名付けて
.gitignore に追加することも可能。

実行ディレクトリより上位の任意の親ディレクト

モノレポで便利。
例: root/hoge で claude を実行し,
root/CLAUDE.md と root/hoge/CLAUDE.md の両方が存在する場合,
どちらも自動的にコンテキストに取り込まれる。

実行ディレクトリより下位の任意の子ディレクト

上記の逆パターン。
ディレクトリのファイルを編集・参照するときに,
その都度 Claude が必要な CLAUDE.md を読み込む。

ホームディレクトリ (~/.claude/CLAUDE.md)

すべての Claude セッションに適用される。

CLAUDE.mdのチューニング

CLAUDE.md は常に参照される為,
ファイルの内容は洗練させる必要があります。

CLAUDE.md を更新したら, どの記述がモデルへどう影響するのか
時間をかけて検証することを推奨します。

CLAUDE.md への追記

CLAUDE.md への追記は, 手動でファイルを編集しても良いですが,
# キーを押して Claude に内容を伝え, 関連個所に反映させる方法も有用です。

Anthropic 社内では, 時々 CLAUDE.md を
Prompt Improver(プロンプト改善ツール)にかけたり,
"IMPORTANT" や "YOU MUST" などの強調語を加えて
指示の遵守率を向上させているとのことです。

Claude の許可ツール一覧を管理する

デフォルトの挙動では,
Claude Code はファイル書き込みやコマンド実行など,
システムを変更する可能性があるすべての操作に対して許可を求めます。

安全なツールや巻き戻し可能なツールは許可を求めないように設定することが可能です。

許可ツールを管理する 4 つの方法:
1. セッション中のプロンプトで "Always allow" を選択する
2. /permissions コマンドで許可リストを追加・削除する
- 例:
- EditBash を追加して常にファイル編集を許可
- (git commit:*) を追加して git commit を許可
- mcp__puppeteer__puppeteer_navigate を追加して Puppeteer MCP でのナビゲーションを許可
3. .claude/settings.json または ~/.claude.json を手動編集(前者はチーム共有のためにソース管理に入れることを推奨)
4. セッション単位で --allowedTools CLI フラグを使う

GitHub を使う場合は GitHub CLI をインストールする

GitHub CLIGitHub の操作が可能になるコマンドラインツールのことです。
GitHub CLI をインストールすることで,
Claude が CLI を通して issue や PR の作成, コメント閲覧が可能になります。

GitHub CLI を使わずに GitHub APIGitHubMCP サーバ経由で操作することもできますが,
GitHub CLI の方がスムーズに機能します。

おわりに

CLAUDE.md を適切にメンテナンスすることが, 時間とコストの浪費削減に大きく寄与しそうですね。
Max プランを契約していない方は, Token の上限にすぐ引っかかってしまうので,
この辺りのチューニングに時間をかけてみても良いかもと思いました。

また続きもまとめる予定です。以上.

Claude Code を使って自分の行動ログを集計する GAS を書いてもらう

はじめに

先日 Claude Code が Claude Pro プランでも利用することができるようになりました。
使用量に限りがあるものの定額でエージェント型のコーディングツールに使えるのはありがたいですね。
これを機に Claude Code を使ってみようかと思いました。

今回, Claude Code に作成してもらうのは睡眠や食事などの活動のログの集計を行う GAS です。
活動ログの送信は Google Form から行い, そのタイムスタンプと活動内容から
合計時間を算出する簡単なプログラムを書いてもらうことにしました。

以前から自分は何か活動するたびに「活動ログ」を記録していたのですが,
その集計処理を作成するのが面倒で先延ばしにしていたので簡単にできたら嬉しいですね。

Claude Code について

Claude Code はコマンドラインで操作可能なエージェント型のコーディングツールです。
以下コマンドでインストールできます。詳しくはドキュメントを読んでください。

npm install -g @anthropic-ai/claude-code # Claude Code をインストール
claude # 起動

https://docs.anthropic.com/ja/docs/agents-and-tools/claude-code/overview

Windows では利用できないとのことなので, WSL2 上にインストールして利用します。

準備

まずは Claude Code を使用するための clasp のプロジェクトを用意します。
cd で移動したら, 以下の手順でこのプロジェクトについての CLAUDE.md を作成してもらいます。

claude # Claude を起動
summarize this project # 簡単なコマンドを流す
/init # CLAUDE.md を作ってもらう

CLAUDE.md はプロジェクトに関する概要みたいなもので,
Claude がこのファイルを読み取って作業を行います。詳しくは以下のドキュメントを参照してください。
https://docs.anthropic.com/ja/docs/agents-and-tools/claude-code/overview

以下のように /init を実行して CLAUDE.md を作成してもらいます。

お願いしてみる

Claude Code に作ってもらうのは, 活動ログを参照し, 日ごとに各活動の合計時間を算出する GAS です。
docs/spec.md に以下のような簡単な仕様をまとめて Claude Code に参照して開発してもらうことにしました。

# 活動ログ集計処理について
- Google Form から活動のログを送信します
- 送信した活動ログは "フォームの回答 1" シートに記入されます
- "フォームの回答 1" シートの構造
    - A列2行目移行にタイムスタンプ
    - B列2行目以降に活動の内容
    - "フォームの回答 1"シートを参照し, "日別集計" シートに活動時間を集計する処理を書いてください
- "日別集計" について
    - A列に日付
    - B列以降に活動の内容
        - 各活動の時間を 1 つ前の活動ログから算出し, 合計時間を算出してください
        - 1 つ前の活動が日を跨いだ場合, 当日と前日に分けて集計を行ってください
- この処理はフォームから新しい登録があった際に実行されるようにしてください

以下日本語の指示で実装の依頼をします。

@docs/spec.md を参照して活動ログの合計時間を算出する処理を作成してください

ちょくちょく「こんなことしようとしてるけどやってええか」と聞かれるので,
ええでと返していると, 1分くらいで完成したと返ってきました。

適当にデータを用意して実行してみます。

エラーになることなく処理が通り, スプレッドシートに出力されたのですが,
あっているか以前にみづらすぎるので修正をお願いすることにしました。

ちなみにターミナル上で一度 Claude Code とのやりとりを閉じてしまった場合は
以下のコマンドで前回のやりとりの続きから再開することができます。

claude --continue

以下のようにコードの修正を依頼しました。

処理を実行したところ出力結果が見づらかったので修正してください。列のサイズはヘッダー文字列の長さに合わせていださい。合計時間は 1:00 や 13:00 のように表示してください。

また軽いカンバセーションをしていると 1分くらいで完了したと返ってきました。

コードを実行してみたところ, 合計時間がだいぶ読みやすくなりました。
仮データと見比べてみると合計時間も正しそうです。

列のサイズが見切れてしまっていたので, また以下のようにお願いしました。

ヘッダーの文字列には絵文字が含まれます。絵文字を考慮した長さにしてもらえますか?

こちらの修正もすぐ終わりヘッダーの文字長に合わせて
列のサイズを調整するようになりました。ばっちりです。
一度もコードを書かずに完成させることができました。

Claude に commit と伝えると以下みたいにコミットしてもらえます。

commit e55f48a4946b20976af76b962467df2ce00d9457 (HEAD -> main, origin/main, origin/HEAD)
Author:  <>
Date:   Mon Jun 16 19:42:24 2025 +0900

    Implement comprehensive activity log aggregation with time formatting and emoji-aware column sizing

    - Add activity time calculation based on intervals between consecutive activities
    - Implement cross-day activity splitting for accurate daily aggregation
    - Add time formatting to display hours:minutes format (e.g., 1:00, 13:15)
    - Implement emoji-aware column width calculation for better display
    - Add automatic form submission trigger setup
    - Refactor code structure with improved type definitions and error handling

    🤖 Generated with [Claude Code](https://claude.ai/code)

    Co-Authored-By: Claude <noreply@anthropic.com>

感想

適当に指示するだけでプログラムができますが, 無駄なやりとりでも token を消費します。
テストの実行まで任せたり, 適切な仕様書を用意するなど
エージェントを使用する環境や指示の仕方を工夫することで
手戻りを減らして効率化できそうだと思いました。

Pro プランであれば, 使用できる token の量も限られているので,
そういった効率化が token の節約にも繋がってきそうですね。

UnitySearchで困ったところ

はじめに

Unity Search にプロジェクトで使用するデータを検索する機能を追加しようとした際に,
いくつか実装に手間取ったところがあったのでまとめました。
docs.unity3d.com

環境

Unity : 2022.3.10f1

困ったところ

filterId を検索フィールドに入力した時にのみ動作してほしい

愚直に SearchProvider を実装すると SearchWindow を開いた際,
検索フィールドに何も入力していないのに該当する要素が全て表示されてしまいます。

filterId を検索フィールドに入力した時にのみ
その filterId が有効になるようにするには
SearchProvider.isExplicitProvidertrue を設定します。

[SearchItemProvider]
private static SearchProvider CreateProvider()
{
    return new SearchProvider("sample", "Sample Search Provider")
    {
        isExplicitProvider = true,
        filterId = "s:",
        fetchItems = FetchItems,
    };
}

private static IEnumerable<SearchItem> FetchItems(SearchContext context, List<SearchItem> items, SearchProvider provider)
{
    return AssetDatabase.GetAllAssetPaths().Select(path => provider.CreateItem(path));
}

以下のように filterId の s: を入力した時のみ要素が表示されるようになります。

Table View のカラムを追加したい

Table View とは Search Window の右下の格子状のアイコンを押した時の状態のことを指します。
この状態では Tree View のように要素ごとにカラムが表示され, より多くの情報を確認できます。

Table View では SearchWindow 右上の + ボタンから
カラムを追加することができます。

Table View で利用可能なカラムを追加するには
SearchProvider.fetchColumn
IEnumerable 型のカラム設定を返す関数を登録することで,
コンテキストメニューに要素を追加することができます。

[SearchItemProvider]
private static SearchProvider CreateProvider()
{
    return new SearchProvider("sample", "Sample Search Provider")
    {
        filterId = "s:",
        fetchItems = FetchItems,
    };
}

private static IEnumerable<SearchItem> FetchItems(SearchContext context, List<SearchItem> items, SearchProvider provider)
{
    return AssetDatabase.GetAllAssetPaths().Select(path => provider.CreateItem(path));
}

private static IEnumerable<SearchColumn> FetchColumns(SearchContext context, IEnumerable<SearchItem> items)
{
    yield return new SearchColumn("path/sample column", "sample selector", "search column provider") { width = 200 };
}

カラムに表示するデータの抽出や描画の処理は後述の SearchColumnProviderAttribute を使用して定義します。

カラムの表示を変更したい

SearchColumnProviderAttribute を使用することで,
カラムの表示方法を定義するフォーマットを追加することができます。
docs.unity3d.com

このフォーマットは特定の SearchProvider に依存せず,
どの SearchProvider でも使用できるようになるようです。

なので, 表示内容を保有するデータは SearchProvider 毎に専用のデータ型を定義するより,
Dictionary<string, object> のようなものを使用するのが良いかもしれません。

Attribute の引数に provider を設定しますが, これは SearchProvider の id ではなく,
SearchColumnProvider の id となる名前を設定します。

[SearchItemProvider]
private static SearchProvider CreateProvider()
{
    return new SearchProvider("sample", "Sample Search Provider")
    {
        filterId = "s:",
        fetchItems = FetchItems,
        fetchColumns = FetchColumns,
    };
}

private static IEnumerable<SearchItem> FetchItems(SearchContext context, List<SearchItem> items, SearchProvider provider)
{
    foreach (var path in AssetDatabase.GetAllAssetPaths())
    {
        var data = new Dictionary<string, object>() { { "sample selector", path } };
        yield return provider.CreateItem(path, null, null, null, data: data);
    }
}

private static IEnumerable<SearchColumn> FetchColumns(SearchContext context, IEnumerable<SearchItem> items)
{
    yield return new SearchColumn("path/sample column", "sample selector", "search column provider") { width = 200 };
}

// 
[SearchColumnProvider("search column provider")]
private static void SampleSearchColumnProvider(SearchColumn searchColumn)
{
    searchColumn.getter = args => (args.item.data as Dictionary<string, object>)[args.column.selector];
    searchColumn.drawer = args =>
    {
        GUI.Button(args.rect, args.value.ToString());
        return args.value;
    };
}

要素を右クリックした時のコンテキストメニューを追加したい

SearchActionsProviderAttribute で特定の SearchProvider にコンテキストメニューにアクションを追加できます。

[SearchItemProvider]
private static SearchProvider CreateProvider()
{
    return new SearchProvider("sample", "Sample Search Provider")
    {
        filterId = "s:",
        fetchItems = FetchItems,
        fetchColumns = FetchColumns,
    };
}

// 略

[SearchActionsProvider]
private static IEnumerable<SearchAction> LogAction()
{
    // Provider 毎に使用可能な action を設定する.
    yield return new SearchAction("sample", "Log")
    {
        handler = item => Debug.Log((item.data as Dictionary<string, object>)["sample selector"]),
    };
}

通信などで遅延して要素を表示したい

一覧表示するデータを通信して取得したい時など,
遅延して要素を SearchWindow に表示したいことがあります。

正しいやり方か不明ですが, FetchItems の中で非同期処理をトリガーし,
取得が完了したタイミングで SearchService.RefreshWindows() を実行してあげると,
再度 FetchItems が走り, 表示を更新することができます。

[SearchItemProvider]
private static SearchProvider CreateProvider()
{
    return new SearchProvider("sample", "Sample Search Provider")
    {
        filterId = "s:",
        fetchItems = FetchItems,
        fetchColumns = FetchColumns,
    };
}

private static List<SearchItem> m_CachedItems;

private static IEnumerable<SearchItem> FetchItems(SearchContext context, List<SearchItem> items, SearchProvider provider)
{
    if (m_CachedItems != null)
    {
        foreach (var item in m_CachedItems)
        {
            yield return item;
        }
        
        yield break;
    }

    m_CachedItems = new();
    GetAllAssetPathsAsync().ContinueWith(result =>
    {
        foreach (var path in AssetDatabase.GetAllAssetPaths())
        {
            var data = new Dictionary<string, object>() { { "sample selector", path } };
            m_CachedItems.Add(provider.CreateItem(path, null, null, null, data: data));
        }

        SearchService.RefreshWindows();
    }).Forget();

    yield break;
}

private static async UniTask<List<string>> GetAllAssetPathsAsync()
{
    await UniTask.WaitForSeconds(5);
    return AssetDatabase.GetAllAssetPaths().ToList();
}

検索処理を実装したい

QueryEngine を使用することで, クエリ演算子を用いた検索機能を実装することが可能です。
また, スペース区切りで or 検索になるなど, 基本的なフィルタ機能を備えているので, 大体これで事足りそうです。 docs.unity3d.com

[SearchItemProvider]
private static SearchProvider CreateProvider()
{
    return new SearchProvider("sample", "Sample Search Provider")
    {
        filterId = "s:",
        fetchItems = FetchItems,
        fetchColumns = FetchColumns,
    };
}

private static IEnumerable<SearchItem> FetchItems(SearchContext context, List<SearchItem> items, SearchProvider provider)
{
    var searchItems = AssetDatabase.GetAllAssetPaths()
        .Select(path =>
        {
            var data = new Dictionary<string, object>() { { "sample selector", path } };
            return provider.CreateItem(path, null, null, null, data: data);
        }).ToList();

    var queryEngine = new QueryEngine<SearchItem>();
     // クエリのトークンと使用するデータを追加. sample:○○ でフィルタできるようになる.
    queryEngine.AddFilter("sample", x => (string)(x.data as Dictionary<string, object>)["sample selector"]);
    // 検索に使用するテキストを指定. 今回は CreateItem で path を id に使用しているので, id を指定.
    queryEngine.SetSearchDataCallback(x => new[] { x.id });
    var query = queryEngine.ParseQuery(context.searchQuery);
    foreach (var item in query.Apply(searchItems))
    {
        yield return item;
    }
}

【Unity】ヘルプボックスを表示する PropertyDrawer

はじめに

インスペクター上のフィールドにヘルプボックスを表示するアトリビュートを作成しました。
使用イメージ

    [HelpBox(@"Help Box
Help Box
Help Box")]
    [SerializeField]
    private float m_Value;

ソースコード

HelpBoxAttribute

using System;
using UnityEngine;

[AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
public class HelpBoxAttribute : PropertyAttribute
{
    public readonly string Text;

    public HelpBoxAttribute(string text)
    {
        Text = text;
    }
}

HelpBoxDrawer

using System;
using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(HelpBoxAttribute))]
public class HelpBoxDrawer : PropertyDrawer
{
    private float m_Height;
    private HelpBoxAttribute m_HelpBoxAttribute => attribute as HelpBoxAttribute;
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        var helpBoxAttribute = attribute as HelpBoxAttribute;

        EditorGUI.PropertyField(position, property, label, true);
        position = new Rect(0.0f, position.y + EditorGUI.GetPropertyHeight(property, label, true) + 5.0f, position.width, m_Height);

        EditorGUI.HelpBox(position, helpBoxAttribute.Text, MessageType.Info);
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        var content = new GUIContent(m_HelpBoxAttribute.Text);
        m_Height = GUI.skin.box.CalcHeight(content, EditorGUIUtility.currentViewWidth);
        
}

【Unity】背景をアルファ0でスクリーンショットを撮る

はじめに

ScreenCapture クラスを使用して ゲーム画面をキャプチャすることができますが,
背景を透過してキャプチャしたかったのでクラスを作成しました。
docs.unity3d.com

完成図

ゲーム画面

キャプチャ結果

コード

public static class ScreenCapture
{
    public static void Capture(Camera targetCamera, Vector2Int captureSize)
    {
        // デスクトップに保存
        string desktopDirectoryPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
        var path = desktopDirectoryPath + "/" + DateTime.Now.ToString("yyyyMMdd-HHmmss") + ".png";

        var screenShot = new Texture2D(captureSize.x, captureSize.y, TextureFormat.ARGB32, false);
        var renderTexture = new RenderTexture(screenShot.width, screenShot.height, 32);
        targetCamera.clearFlags = CameraClearFlags.SolidColor;
        targetCamera.backgroundColor = new Color(0, 0, 0, 0);
        var prev = targetCamera.targetTexture;
        targetCamera.targetTexture = renderTexture;
        targetCamera.Render();
        targetCamera.targetTexture = prev;
        RenderTexture.active = renderTexture;
        screenShot.ReadPixels(new Rect(0, 0, screenShot.width, screenShot.height), 0, 0);
        screenShot.Apply();

        var bytes = screenShot.EncodeToPNG();
        UnityEngine.Object.DestroyImmediate(screenShot);
        File.WriteAllBytes(path, bytes);

        Debug.Log("ScreenCaptre: " + path);
    }
}

【Unity】回転する矩形と円形の接触判定

はじめに

以前矩形と円形の接触判定を行う処理の記事を書きましたが,
今回は矩形が回転した場合にも対応します.
yshinya09.hatenablog.com

完成形

以下のように回転する矩形と円形の接触判定をとります.

実装

図形の定義

円形と矩形のクラスをそれぞれ以下のように定義しました。
矩形は Unity の Rect クラスだと回転を持てない為, 定義しなおしました。

// 円形.
record Circle2d(Vector2 Center, float Radius);
// 矩形.
record Rect2d(Vector2 Center, float width, float height, float rotation);

接触判定

private static bool Overlap(Circle2d circle, Rect2d rect2d)
{
    var rect = new Rect(rect2d.Center.x - rect2d.Width / 2.0f, rect2d.Center.y - rect2d.Height / 2.0f, rect2d.Width, rect2d.Height);;

    var cos = Mathf.Cos(rect2d.Rotation * Mathf.Deg2Rad);
    var sin = Mathf.Sin(rect2d.Rotation * Mathf.Deg2Rad);
    var diff = circle.Center - rect.center;
    var pos = rect.center + new Vector2(diff.x * cos + diff.y * -sin, diff.x * sin + diff.y * cos);

    // Rect 上で最も Circle の Center に近い点を求める.
    var x = Mathf.Max(rect.xMin, Mathf.Min(pos.x, rect.xMax));
    var y = Mathf.Max(rect.yMin, Mathf.Min(pos.y, rect.yMax));
    var nearestPoint = new Vector2(x, y);

    // Rect 上で最も Circle の Center に近い点が, Circle の半径より近いをチェック.
    return (nearestPoint - pos).sqrMagnitude <= circle.Radius * circle.Radius;
}