- 投稿日:2019-11-24T21:55:17+09:00
EntityFrameworkCoreでMigrationsしたときのテーブル名や定義名
EntityFrameworkCoreで、業務で何も考えずに名前を決めたせいで外部キー(以下、「FK」)で困ったので、テーブル名や定義名がどう設定されるのか確認した。
- 確認環境
- SQL Server 14.0
- .net core 2.2
- EntityFrameworkCore 2.2
テーブル名
結論
DbContextクラスのプロパティ名がそのままテーブル名になるCorporateDbContext.cspublic class CorporateDbContext : DbContext { public CorporateDbContext() { } public CorporateDbContext(DbContextOptions<ApplicationCommandDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { } public DbSet<User> User { get; set; } public DbSet<UserDetail> UserDetails { get; set; } public DbSet<Companys> Company { get; set; } public DbSet<CompanyDetails> CompanyDetails { get; set; } }この場合だと、
User
とCompanys
のクラスが単数形に、
UserDetail
とCompanyDetails
のクラスが複数形になる。カラム名(FK)
結論
クラス名+"Id"で自動生成されるCompanys.cspublic class Companys { public Guid Id { get; set; } public string Name { get; set; } public virtual ICollection<CompanyDetail> CompanyDetail { get; set; } }CompanyDetail.cspublic class CompanyDetail { public Guid Id { get; set; } public string DetailKey { get; set; } public string DetailValue { get; set; } }上記のように親テーブルから子テーブルに対してプロパティを実装した場合、EntityFrameworkCoreではシャドウプロパティとしてFKが自動的に設定され、テーブルにも実装される。
上の場合だと、データベースのCompanyDetailテーブルにはCompanysId
のカラムが実装される。最後に
クラスやプロパティの命名は昔からきちんと決めることが大事と言われていたけど、それを痛感した。
私がやってしまったのはFKのほうで、明示的にFKのプロパティを考えなしに実装した結果、CompanyId
とCompanysId
の両方がテーブルに実装されてしまった。参考
EntityFrameworkCore シャドウ プロパティ
EntityFrameworkCore リレーションシップ
- 投稿日:2019-11-24T19:57:39+09:00
C#からC/C++の関数をコールして構造体を渡す【#1】
はじめに
当記事は @TackKaiware さまの、
「C#からC/C++の関数をコールする方法 まとめ①」 を基に作成されています。
C#からC++の関数コールをして、構造体を構造体ポインタとして渡してみます。ソースコード
C++ dll ソースコード
mylib.h#include <Windows.h> typedef struct _MY_STRUCT { bool myFlag; char* name; void* output; }; extern "C" __declspec(dllexport) int Test(PVOID mystruct);mylib.cpp#include <iostream> #include "mylib.h" int Test(PVOID mystruct) { _MY_STRUCT* ms = (_MY_STRUCT*)mystruct; std::cout << "CPPの関数が呼び出されました。" << std::endl; if (ms->myFlag) { std::cout << "Flag on" << std::endl; } else { std::cout << "Flag off" << std::endl; } std::cout << "name: " << ms->name << std::endl; std::cout << "output: " << ms->output << std::endl; return 634; }C# WinForm
MyForm.cspublic struct _MY_STRUCT { public bool myFlag; public string name; public float output; } [DllImport("mylib.dll")] public unsafe static extern int Test(IntPtr pStructure); private unsafe void Button1_Click(object sender, EventArgs e) { _MY_STRUCT myStruct = new _MY_STRUCT(); myStruct.myFlag = true; myStruct.name = "SETOKOUJI"; myStruct.output = 999.9f; IntPtr pStructure = Marshal.AllocCoTaskMem(Marshal.SizeOf(myStruct)); Marshal.StructureToPtr(myStruct, pStructure, false); int x = Test(pStructure); WriteLog(x.ToString()); Marshal.FreeCoTaskMem(pStructure); }実行結果
構造体のマーシャリング
構造体を「構造体ポインタ」として渡す為、該当構造体をマーシャリングする必要があります。
アンマネージドメモリを確保し、Marshal.StructureToPtr
を使用して構造体ポインタへと変換します。IntPtr pStructure = Marshal.AllocCoTaskMem(Marshal.SizeOf(myStruct)); Marshal.StructureToPtr(myStruct, pStructure, false);今回は各チェックは省いています。
if(pStructure == IntPtr.Zero || pStructure == null) { return; }最後にリソースを開放します。
Marshal.FreeCoTaskMem(pStructure);C++側: Voidポインタから構造体への変換
PVOID mystruct
はVoidポインタ(void*
)です。
関数コールの際に構造体ポインタを受け取ります。int Test(PVOID mystruct) { _MY_STRUCT* ms = (_MY_STRUCT*)mystruct; }構造体ポインタから構造体へのキャストは、下記でも同様です。
_MY_STRUCT* ms = static_cast<_MY_STRUCT*>(mystruct);おわり
今回は、C#からC/C++の関数をコールして構造体を渡してみました。
同様にして、dll側から返り値として構造体を渡すこともできます。
- 投稿日:2019-11-24T19:57:39+09:00
C#からC/C++の関数をコールして構造体を渡す
はじめに
当記事は @TackKaiware さまの、
「C#からC/C++の関数をコールする方法 まとめ①」 を基に作成されています。
C#からC++の関数コールをして、構造体を構造体ポインタとして渡してみます。ソースコード
C++ dll ソースコード
mylib.h#include <Windows.h> typedef struct _MY_STRUCT { bool myFlag; char* name; void* output; }; extern "C" __declspec(dllexport) int Test(PVOID mystruct);mylib.cpp#include <iostream> #include "mylib.h" int Test(PVOID mystruct) { _MY_STRUCT* ms = (_MY_STRUCT*)mystruct; std::cout << "CPPの関数が呼び出されました。" << std::endl; if (ms->myFlag) { std::cout << "Flag on" << std::endl; } else { std::cout << "Flag off" << std::endl; } std::cout << "name: " << ms->name << std::endl; std::cout << "output: " << ms->output << std::endl; return 634; }C# WinForm
MyForm.cspublic struct _MY_STRUCT { public bool myFlag; public string name; public float output; } [DllImport("mylib.dll")] public unsafe static extern int Test(IntPtr pStructure); private unsafe void Button1_Click(object sender, EventArgs e) { _MY_STRUCT myStruct = new _MY_STRUCT(); myStruct.myFlag = true; myStruct.name = "SETOKOUJI"; myStruct.output = 999.9f; IntPtr pStructure = Marshal.AllocCoTaskMem(Marshal.SizeOf(myStruct)); Marshal.StructureToPtr(myStruct, pStructure, false); int x = Test(pStructure); WriteLog(x.ToString()); Marshal.FreeCoTaskMem(pStructure); }実行結果
構造体のマーシャリング
構造体を「構造体ポインタ」として渡す為、該当構造体をマーシャリングする必要があります。
アンマネージドメモリを確保し、Marshal.StructureToPtr
を使用して構造体ポインタへと変換します。IntPtr pStructure = Marshal.AllocCoTaskMem(Marshal.SizeOf(myStruct)); Marshal.StructureToPtr(myStruct, pStructure, false);今回は各チェックは省いています。
if(pStructure == IntPtr.Zero || pStructure == null) { return; }最後にリソースを開放します。
Marshal.FreeCoTaskMem(pStructure);C++側: Voidポインタから構造体への変換
PVOID mystruct
はVoidポインタ(void*
)です。
関数コールの際に構造体ポインタを受け取ります。int Test(PVOID mystruct) { _MY_STRUCT* ms = (_MY_STRUCT*)mystruct; }構造体ポインタから構造体へのキャストは、下記でも同様です。
_MY_STRUCT* ms = static_cast<_MY_STRUCT*>(mystruct);おわり
今回は、C#からC/C++の関数をコールして構造体を渡してみました。
同様にして、dll側から返り値として構造体を渡すこともできます。
- 投稿日:2019-11-24T19:43:55+09:00
Blazorでコードビハインドでロジックとビューを分離して記述する
概要
Blazorのコンポーネントでロジックとビューを分離して記載する方法のメモ。
よくある記載例
Blazorのサンプルコードの多くは、下記のようにrazorファイルの中にHTML要素とバインドするC#コードを合わせて記載しています。
counter.razor@page "/counter" <h1>Counter</h1> <p>Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { private int currentCount = 0; private void IncrementCount() { currentCount++; } }このような手法とは別に、razorコンポーネントとC#コードを分離して記載するコードビハインドといった記載も可能です。今回はその方法を紹介します。
コードビハインドでの記載方法
モデル(C#コード)
まずは元となるC#側のコードを作成します。
CounterModel.cspublic class CounterModel : ComponentBase { public int CurrentCount { get; private set; } public void IncrementCount() { CurrentCount++; } }分離しない場合の変更点としては下記になります。
- ComponentBaseクラスを継承したクラスとする。(Microsoft.AspNetCore.Components)
- ビューからバインドする変数及び関数をprivateからpublicにする。ビュー(razorコンポーネント)
次にrazorコンポーネント側を作成します。
counter.razor@page "/counter" @inherits CounterModel <h1>Counter</h1> <p>Current count: @CurrentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>分離しない場合のrazorコンポーネントとの違いは下記になります。
- C#コードの代わりにデータバインドするモデルクラス名をinheritsで記載する。DIの方法
Inject属性を付与したプロパティを使用することで依存性の注入が可能になります。
(具体的なクラスの指定は通常通りStarup.csから指定する。)CounterModel.cspublic class CounterModel : ComponentBase { [Inject] protected IDataAccess DataRepository { get; set; } public int CurrentCount { get; private set; } public void IncrementCount() { CurrentCount++; } }まとめ
簡単にですが、Blazorにおけるコードビハインドの方法を紹介しました。
ロジックとビューを分離することで、下記のようなメリットなどが得られます。
- ロジックに対するUnitTestの記載が可能
- 基底のコンポーネントクラスの作成などによる共通処理の作成
参考資料
https://www.telerik.com/blogs/using-a-code-behind-approach-to-blazor-components
https://docs.microsoft.com/ja-jp/aspnet/core/blazor/dependency-injection?view=aspnetcore-3.0
https://gunnarpeipman.com/blazor-code-behind/
- 投稿日:2019-11-24T18:38:14+09:00
【Unity】UIで押させたいボタンだけフォーカスさせる
背景
デジゲー博に展示して試遊してる様子を見て気づいたんですが、押させたい場所にポップを表示させるだけでは人は押してくれませんでした(見ててまじかよと思った)。
そこで、押させたい場所しか押せない仕組みが必須と分かり実装してみました。
概要
UIで押させたいボタンだけフォーカスさせる実装方法
— アズマゴロー@デジゲー博B-03a (@azumagoro) November 24, 2019
①Stopper用Canvas(+Image)を通常のUIのCanvasより前に表示させる
②押させたいボタンにAddComponentで動的にCanvasを追加し、SortOrderをStopperCanvasより前に表示させる。
③するとUIのレイヤー関係を壊さずに最前面に表示できる。 pic.twitter.com/TRnADbzk1Lコードとか
StopperController.csusing UnityEngine; public class StopperController : MonoBehaviour { [SerializeField] private Canvas _stopperCanvas; public static StopperController Instance; private void Awake() { Instance = this; } public void Focus(GameObject go) { _stopperCanvas.gameObject.SetActive(true); var canvas = go.AddComponent<Canvas>(); // ここtrueにしてからでないとsortingOrderの値を変更できない! canvas.overrideSorting = true; canvas.sortingOrder = _stopperCanvas.sortingOrder + 1; canvas.renderMode = RenderMode.ScreenSpaceOverlay; } public void RemoveFocus(GameObject go) { var canvas = go.GetComponent<Canvas>(); if (canvas !=null) { Object.Destroy(canvas); } _stopperCanvas.gameObject.SetActive(false); } #if UNITY_EDITOR // 動作テスト用のコードは#if UNITY_EDITORで囲ってビルドに含まれないようにするのをよくやります [SerializeField] private GameObject testGameObject; [ContextMenu("TestFocus")] void TestFocus() { Focus(testGameObject); } [ContextMenu("TestRemoveFocus")] void TestRemoveFocus() { RemoveFocus(testGameObject); } #endif }StopperCanvas
StopperController
参考になった場合はよろしければいいねお願いします?
おわり
- 投稿日:2019-11-24T15:21:40+09:00
ネットに転がっているC#のサンプルコードがコンパイルできないときの主な要因
※個人の感想です。
千差万別なので解決策は書いていません。というより書けない…1.コードが断片である
1-1.
Main
関数がない
static void Main(string[] args)
とかがない。1-2.
using
ディレクティブがない
using System.なんとか.かんとか;
がない。不足している。1-3. Visual Studio前提のコード
Visual Studioで作成したコードの一部しか載せていない場合。
partial class
とかInitializeComponent
とかを使っているのはこのケースが多い。2. 新しめのC#バージョンの機能を使用している
C#のバージョンごとの機能は、ここが詳しい。
https://ufcpp.net/study/csharp/古いバージョンへのポーティング手法
https://ufcpp.net/study/csharp/cheatsheet/listfxlangversion/タプル(
Tuple
)とか?.
とか??=
とか$"{X}"
とか使ってたらバージョンによるものかもしれない。3.コンパイルしていないコードが置かれている
あまりないけど、まれに。
コンパイルしたあとに修正したりして、コンパイル通らなくなったコードになってたり。4.参照設定が必要なのに言及がない / ライブラリのバージョンが合わない
説明が書かれているケースが多いが、環境が違うとハードル高い。
Appendix. コンパイルはできるが挙動がおかしい場合
環境差によるもの / コード起因のもの
- プログラムの入出力の文字コードがあっていない。
- ソースコードの文字コードがあっていない。
- コンパイルオプションがあっていない。
- 64bit / 32bitを考慮していないコードになっている。
-Main
に[STAThread]
がついていない。あたりを疑う。
- 投稿日:2019-11-24T14:52:18+09:00
C# - Windowsのリンクファイル(.lnk)からリンク先情報を取得する / Drag&Dropサンプル
サンプルコード
フォームにlnkファイルをDrag&Dropすると、コンソールにlnkファイル自身のパスとリンク先パスを出力します。
using System; using System.Runtime.InteropServices; using System.Drawing; using System.Windows.Forms; class ShortcutTest : Form { public static string GetTargetPath(string fullPath) { dynamic shell = null; // IWshRuntimeLibrary.WshShell dynamic lnk = null; // IWshRuntimeLibrary.IWshShortcut try { var type = Type.GetTypeFromProgID("WScript.Shell"); shell = Activator.CreateInstance(type); lnk = shell.CreateShortcut(fullPath); if (string.IsNullOrEmpty(lnk.TargetPath)) { return "lnk file does not exists."; } //lnk.Arguments, //lnk.Description, //lnk.FullName, //lnk.Hotkey, //lnk.IconLocation, //lnk.TargetPath, //lnk.WindowStyle, //lnk.WorkingDirectory return lnk.TargetPath; } finally { if (lnk != null) Marshal.ReleaseComObject(lnk); if (shell != null) Marshal.ReleaseComObject(shell); } } ShortcutTest() { this.AllowDrop = true; this.DragEnter += (sender,e) => { // e は DragEventArgs if (e.Data.GetDataPresent(DataFormats.FileDrop)) { e.Effect = DragDropEffects.Copy; } else { e.Effect = DragDropEffects.None; } }; this.DragDrop += (sender,e) => { var fileNames = (string[])e.Data.GetData(DataFormats.FileDrop, false); if (fileNames != null) { foreach(string s in fileNames){ if ( s.EndsWith(".lnk", true, null) ) {// Note: 第2引数はignoreCase Console.WriteLine(s); Console.WriteLine(" -> " + GetTargetPath(s)); } else { Console.WriteLine(s); } } } }; } [STAThread] static void Main(string[] args) { Application.Run(new ShortcutTest()); } }実行結果
C:\SvnLocal\trunk\LnkFile\Command Prompt.lnk -> C:\Windows\system32\cmd.exe参考サイト
1のコードは、C#バージョン7で導入されたTupleを使用しているので、そのままだと、デフォルトで入っているcsc.exeではコンパイルできないかもしれません。 ↩
- 投稿日:2019-11-24T12:10:04+09:00
C# - ListViewのOwnerDrawサンプル(Detailsビュー)
経緯
検索結果(単一行)の表示用に使えそうなオーナードロー部分を作った。
※今回のソースコードに検索機能はないです。画面キャプチャ
ソースコード
using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Globalization; using System.Windows.Forms; class SampleUsage : Form { CustomComponent.ListViewOwnerDraw lsv; SampleUsage() { ClientSize = new Size(500,500); lsv = new CustomComponent.ListViewOwnerDraw(); lsv.Dock = DockStyle.Fill; lsv.GridLines = true; lsv.View = View.Details; lsv.FullRowSelect = true; lsv.Columns.Add("Path" , 100, HorizontalAlignment.Left); lsv.Columns.Add("Name" , 80, HorizontalAlignment.Left); lsv.Columns.Add("LineNo", 50, HorizontalAlignment.Center); lsv.Columns.Add("Text" , 250, HorizontalAlignment.Left); CustomComponent.ListViewItemOwnerDraw listViewItem1 = new CustomComponent.ListViewItemOwnerDraw(new string[] { @"c:\src" , "sample.c" , "10", "#define THIS_IS_SAMPLE ((unsigned short)5)" }); CustomComponent.ListViewItemOwnerDraw listViewItem2 = new CustomComponent.ListViewItemOwnerDraw(new string[] { @"c:\src\doc", "sample.txt", "5" , "change THIS_IS_SAMPLE value to ..." }); CustomComponent.ListViewItemOwnerDraw listViewItem3 = new CustomComponent.ListViewItemOwnerDraw(new string[] { @"c:\src\tmp", "dummy.txt" , "20", "THIS_IS_SAMPLE THIS_ISNOT_SAMPLE THIS_IS_SAMPLE hoge" }); lsv.Items.AddRange(new CustomComponent.ListViewItemOwnerDraw[] { listViewItem1, listViewItem2, listViewItem3 }); lsv.SetWord("THIS_IS_SAMPLE"); lsv.SetSubIndex(3); Controls.Add(lsv); } [STAThread] static void Main() { Application.Run(new SampleUsage()); } } namespace CustomComponent { public class ListViewItemOwnerDraw : ListViewItem { public bool _InvalidationFlag; void _init() { } public ListViewItemOwnerDraw(String[] a1, String a2, Color a3, Color a4, Font a5):base(a1,a2,a3,a4,a5){_init();} public ListViewItemOwnerDraw(String[] a1, Int32 a2, Color a3, Color a4, Font a5):base(a1,a2,a3,a4,a5){_init();} public ListViewItemOwnerDraw(ListViewItem.ListViewSubItem[] a1, String a2):base(a1,a2){_init();} public ListViewItemOwnerDraw(String[] a1, String a2):base(a1,a2){_init();} public ListViewItemOwnerDraw(String[] a1, Int32 a2):base(a1,a2){_init();} public ListViewItemOwnerDraw(String a1, String a2):base(a1,a2){_init();} public ListViewItemOwnerDraw(String a1, Int32 a2):base(a1,a2){_init();} public ListViewItemOwnerDraw(String[] a1):base(a1){_init();} public ListViewItemOwnerDraw(String a1):base(a1){_init();} public ListViewItemOwnerDraw(ListViewItem.ListViewSubItem[] a1, Int32 a2):base(a1,a2){_init();} } public class ListViewOwnerDraw : ListView { string _word; int _subIndex; public void SetWord(string word) { _word = word; } public void SetSubIndex(int subIndex) { _subIndex = subIndex; } public ListViewOwnerDraw() { this.View = View.Details; this.OwnerDraw = true; this.DrawItem += this_DrawItem; this.DrawSubItem += this_DrawSubItem; this.DrawColumnHeader += this_DrawColumnHeader; this.MouseUp += this_MouseUp; this.MouseMove += this_MouseMove; this.ColumnWidthChanged += this_ColumnWidthChanged; this.Invalidated += this_Invalidated; } // Selects and focuses an item when it is clicked anywhere along its width. // The click must normally be on the parent item text. private void this_MouseUp(object sender, MouseEventArgs e) { ListViewItem clickedItem = this.GetItemAt(5, e.Y); if (clickedItem != null) { clickedItem.Selected = true; clickedItem.Focused = true; } } static void MyDrawFocusRectangle(DrawListViewItemEventArgs e) { Rectangle rect = e.Bounds; int w = rect.Width-1; int h = rect.Height-2; if(w<0){w=0;} if(h<0){h=0;} rect = new Rectangle(rect.X,rect.Y,w,h); Pen pen = new Pen(Color.Black, 1.0f); pen.DashStyle = DashStyle.Dot; e.Graphics.DrawRectangle(pen, rect); } void MyFillMatchedRect(DrawListViewSubItemEventArgs e, Font fnt, StringFormat sf) { var cr = CalcCharRanges(e); sf.SetMeasurableCharacterRanges(cr); Region[] regions = e.Graphics.MeasureCharacterRanges(e.SubItem.Text, fnt, e.Bounds, sf); foreach(Region sr in regions) { RectangleF rect = sr.GetBounds(e.Graphics); rect.Y++; rect.Height-=3; e.Graphics.FillRectangle(Brushes.Yellow, rect); } } // Draws the backgrounds for entire ListView items. private void this_DrawItem(object sender, DrawListViewItemEventArgs e) { if ((e.State & ListViewItemStates.Selected) != 0) { // Draw the background and focus rectangle for a selected item. //e.DrawBackground(); e.Graphics.FillRectangle(Brushes.LightBlue, e.Bounds); //e.DrawFocusRectangle(); MyDrawFocusRectangle(e); } else { e.DrawBackground(); } // Draw the item text for views other than the Details view. // if (this.View != View.Details) { e.DrawText(); } } // Draws subitem text and applies content-based formatting. private void this_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) { TextFormatFlags flags = TextFormatFlags.Left; using (StringFormat sf = new StringFormat()) { switch (e.Header.TextAlign) { case HorizontalAlignment.Center: sf.Alignment = StringAlignment.Center; flags = TextFormatFlags.HorizontalCenter; break; case HorizontalAlignment.Right: sf.Alignment = StringAlignment.Far; flags = TextFormatFlags.Right; break; } if (e.ColumnIndex == _subIndex) { MyFillMatchedRect(e, this.Font, sf); } else if ((e.ItemState & ListViewItemStates.Selected) == 0) { e.DrawBackground(); } if ( e.ColumnIndex == _subIndex ) { e.Graphics.DrawString(e.SubItem.Text, this.Font, Brushes.Black, e.Bounds, sf); } else { e.DrawText(flags); } } } CharacterRange[] CalcCharRanges(DrawListViewSubItemEventArgs e) { var ret = new List<CharacterRange>(); if ( _word != null && _word.Length >= 1 ) { string s = e.SubItem.Text; for ( int i = 0 ; i < s.Length ; i += _word.Length ) { i = s.IndexOf(_word, i); if ( i < 0 ) { break; } ret.Add(new CharacterRange(i,_word.Length)); } } return ret.ToArray(); } // Draws column headers. private void this_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e) { using (StringFormat sf = new StringFormat()) { switch (e.Header.TextAlign) { case HorizontalAlignment.Center: sf.Alignment = StringAlignment.Center; break; case HorizontalAlignment.Right: sf.Alignment = StringAlignment.Far; break; } e.DrawBackground(); e.Graphics.DrawString(e.Header.Text, this.Font, Brushes.Black, e.Bounds, sf); } return; } // Forces each row to repaint itself the first time the mouse moves over // it, compensating for an extra DrawItem event sent by the wrapped // Win32 control. This issue occurs each time the ListView is invalidated. private void this_MouseMove(object sender, MouseEventArgs e) { ListViewItemOwnerDraw item = this.GetItemAt(e.X, e.Y) as ListViewItemOwnerDraw; if (item != null && !item._InvalidationFlag) { this.Invalidate(item.Bounds); item._InvalidationFlag = true; } } // Resets the item tags. void this_Invalidated(object sender, InvalidateEventArgs e) { foreach (ListViewItem itemTmp in this.Items) { ListViewItemOwnerDraw item = itemTmp as ListViewItemOwnerDraw; if (item == null) return; item._InvalidationFlag = false; } } void this_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e) { this.Invalidate(); // to repaint } } }参考サイト
- 投稿日:2019-11-24T12:10:04+09:00
C# - ソースコード検索結果の表示に使えそうなListViewのOwnerDrawサンプルを作った
経緯
検索結果(単一行)の表示用に使えそうなオーナードロー部分を作った。
※今回のソースコードに検索機能はないです。画面キャプチャ
ソースコード
参考サイト1のサンプルをベースに作成。
参考サイト1のサンプルではListViewItem
のTag
が内部フラグ管理に使われていたので、別のメンバを充てて、Tagを使えるようにした。using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Globalization; using System.Windows.Forms; class SampleUsage : Form { CustomComponent.ListViewOwnerDraw lsv; SampleUsage() { ClientSize = new Size(500,500); lsv = new CustomComponent.ListViewOwnerDraw(); lsv.Dock = DockStyle.Fill; lsv.GridLines = true; lsv.View = View.Details; lsv.FullRowSelect = true; lsv.Columns.Add("Path" , 100, HorizontalAlignment.Left); lsv.Columns.Add("Name" , 80, HorizontalAlignment.Left); lsv.Columns.Add("LineNo", 50, HorizontalAlignment.Center); lsv.Columns.Add("Text" , 250, HorizontalAlignment.Left); CustomComponent.ListViewItemOwnerDraw listViewItem1 = new CustomComponent.ListViewItemOwnerDraw(new string[] { @"c:\src" , "sample.c" , "10", "#define THIS_IS_SAMPLE ((unsigned short)5)" }); CustomComponent.ListViewItemOwnerDraw listViewItem2 = new CustomComponent.ListViewItemOwnerDraw(new string[] { @"c:\src\doc", "sample.txt", "5" , "change THIS_IS_SAMPLE value to ..." }); CustomComponent.ListViewItemOwnerDraw listViewItem3 = new CustomComponent.ListViewItemOwnerDraw(new string[] { @"c:\src\tmp", "dummy.txt" , "20", "THIS_IS_SAMPLE THIS_ISNOT_SAMPLE THIS_IS_SAMPLE hoge" }); lsv.Items.AddRange(new CustomComponent.ListViewItemOwnerDraw[] { listViewItem1, listViewItem2, listViewItem3 }); lsv.SetWord("THIS_IS_SAMPLE"); lsv.SetSubIndex(3); Controls.Add(lsv); } [STAThread] static void Main() { Application.Run(new SampleUsage()); } } namespace CustomComponent { public class ListViewItemOwnerDraw : ListViewItem { public bool _InvalidationFlag; void _init() { } public ListViewItemOwnerDraw(String[] a1, String a2, Color a3, Color a4, Font a5):base(a1,a2,a3,a4,a5){_init();} public ListViewItemOwnerDraw(String[] a1, Int32 a2, Color a3, Color a4, Font a5):base(a1,a2,a3,a4,a5){_init();} public ListViewItemOwnerDraw(ListViewItem.ListViewSubItem[] a1, String a2):base(a1,a2){_init();} public ListViewItemOwnerDraw(String[] a1, String a2):base(a1,a2){_init();} public ListViewItemOwnerDraw(String[] a1, Int32 a2):base(a1,a2){_init();} public ListViewItemOwnerDraw(String a1, String a2):base(a1,a2){_init();} public ListViewItemOwnerDraw(String a1, Int32 a2):base(a1,a2){_init();} public ListViewItemOwnerDraw(String[] a1):base(a1){_init();} public ListViewItemOwnerDraw(String a1):base(a1){_init();} public ListViewItemOwnerDraw(ListViewItem.ListViewSubItem[] a1, Int32 a2):base(a1,a2){_init();} } public class ListViewOwnerDraw : ListView { string _word; int _subIndex; public void SetWord(string word) { _word = word; } public void SetSubIndex(int subIndex) { _subIndex = subIndex; } public ListViewOwnerDraw() { this.View = View.Details; this.OwnerDraw = true; this.DrawItem += this_DrawItem; this.DrawSubItem += this_DrawSubItem; this.DrawColumnHeader += this_DrawColumnHeader; this.MouseUp += this_MouseUp; this.MouseMove += this_MouseMove; this.ColumnWidthChanged += this_ColumnWidthChanged; this.Invalidated += this_Invalidated; } // Selects and focuses an item when it is clicked anywhere along its width. // The click must normally be on the parent item text. private void this_MouseUp(object sender, MouseEventArgs e) { ListViewItem clickedItem = this.GetItemAt(5, e.Y); if (clickedItem != null) { clickedItem.Selected = true; clickedItem.Focused = true; } } static void MyDrawFocusRectangle(DrawListViewItemEventArgs e) { Rectangle rect = e.Bounds; int w = rect.Width-1; int h = rect.Height-2; if(w<0){w=0;} if(h<0){h=0;} rect = new Rectangle(rect.X,rect.Y,w,h); Pen pen = new Pen(Color.Black, 1.0f); pen.DashStyle = DashStyle.Dot; e.Graphics.DrawRectangle(pen, rect); } void MyFillMatchedRect(DrawListViewSubItemEventArgs e, Font fnt, StringFormat sf) { var cr = CalcCharRanges(e); sf.SetMeasurableCharacterRanges(cr); Region[] regions = e.Graphics.MeasureCharacterRanges(e.SubItem.Text, fnt, e.Bounds, sf); foreach(Region sr in regions) { RectangleF rect = sr.GetBounds(e.Graphics); rect.Y++; rect.Height-=3; e.Graphics.FillRectangle(Brushes.Yellow, rect); } } // Draws the backgrounds for entire ListView items. private void this_DrawItem(object sender, DrawListViewItemEventArgs e) { if ((e.State & ListViewItemStates.Selected) != 0) { // Draw the background and focus rectangle for a selected item. //e.DrawBackground(); e.Graphics.FillRectangle(Brushes.LightBlue, e.Bounds); //e.DrawFocusRectangle(); MyDrawFocusRectangle(e); } else { e.DrawBackground(); } // Draw the item text for views other than the Details view. // if (this.View != View.Details) { e.DrawText(); } } // Draws subitem text and applies content-based formatting. private void this_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) { TextFormatFlags flags = TextFormatFlags.Left; using (StringFormat sf = new StringFormat()) { switch (e.Header.TextAlign) { case HorizontalAlignment.Center: sf.Alignment = StringAlignment.Center; flags = TextFormatFlags.HorizontalCenter; break; case HorizontalAlignment.Right: sf.Alignment = StringAlignment.Far; flags = TextFormatFlags.Right; break; } if (e.ColumnIndex == _subIndex) { MyFillMatchedRect(e, this.Font, sf); } else if ((e.ItemState & ListViewItemStates.Selected) == 0) { e.DrawBackground(); } if ( e.ColumnIndex == _subIndex ) { e.Graphics.DrawString(e.SubItem.Text, this.Font, Brushes.Black, e.Bounds, sf); } else { e.DrawText(flags); } } } CharacterRange[] CalcCharRanges(DrawListViewSubItemEventArgs e) { var ret = new List<CharacterRange>(); if ( _word != null && _word.Length >= 1 ) { string s = e.SubItem.Text; for ( int i = 0 ; i < s.Length ; i += _word.Length ) { i = s.IndexOf(_word, i); if ( i < 0 ) { break; } ret.Add(new CharacterRange(i,_word.Length)); } } return ret.ToArray(); } // Draws column headers. private void this_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e) { using (StringFormat sf = new StringFormat()) { switch (e.Header.TextAlign) { case HorizontalAlignment.Center: sf.Alignment = StringAlignment.Center; break; case HorizontalAlignment.Right: sf.Alignment = StringAlignment.Far; break; } e.DrawBackground(); e.Graphics.DrawString(e.Header.Text, this.Font, Brushes.Black, e.Bounds, sf); } return; } // Forces each row to repaint itself the first time the mouse moves over // it, compensating for an extra DrawItem event sent by the wrapped // Win32 control. This issue occurs each time the ListView is invalidated. private void this_MouseMove(object sender, MouseEventArgs e) { ListViewItemOwnerDraw item = this.GetItemAt(e.X, e.Y) as ListViewItemOwnerDraw; if (item != null && !item._InvalidationFlag) { this.Invalidate(item.Bounds); item._InvalidationFlag = true; } } // Resets the item tags. void this_Invalidated(object sender, InvalidateEventArgs e) { foreach (ListViewItem itemTmp in this.Items) { ListViewItemOwnerDraw item = itemTmp as ListViewItemOwnerDraw; if (item == null) return; item._InvalidationFlag = false; } } void this_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e) { this.Invalidate(); // to repaint } } }参考サイト
- 投稿日:2019-11-24T11:45:48+09:00
Unity玉転がしチュートリアル 3-4.ゲームのビルド(終)
この記事の対象者
- Unity入門したい人
- 最初の一歩が踏み出せない人
OSとか環境とか
- Windows 10 Pro
- macOS Mojave
- Unity 2019.2.8f1
- Rider 2019.2.2
補足
- 公式動画にて利用しているのはMacなので、Windowsユーザーはある程度脳内変換して見る事
- 筆者はWindows、Macの両方の環境で確認。Ubuntuとかでは検証してない。
- 基本Unityは英語メニューで利用
- 間違いがあったらツッコミ大歓迎
公式
ゲームのビルド
そんなに書くことが無い
Build Setttingsウィンドウから行う
ビルドターゲットは現在はPCなので、PCのままでOK
AndroidとかiOS等に出力したい場合は今回は割愛ビルドするゲームに必要なScenesを取り込みます。
Add Open Scenesから追加しても良いし、直接Projectタブからドラッグして追加しても良いとりあえず変更せずにBuildボタンを押下
フォルダを選択するとビルドがはじまるので終わるまで待機
ビルドが終わるとプレイに必要なファイル群がフォルダに生成される
生成された「アプリ名.exe」を実行するとゲームが動くはずです!最後に
という感じでUnity超基本デビューは幕を閉じるのであった。
折角ある程度触ったし、次は自分でAssetStoreとか漁って小さくてもいいから形にしようと思う今日この頃でした。
オラタンか格ゲーが作りたいね!なおここまでのソースは下記にアップしておりますのでご自由にどうぞ。
- 投稿日:2019-11-24T11:17:25+09:00
Unity玉転がしチュートリアル 3-3.スコアとテキストの表示
この記事の対象者
- Unity入門したい人
- 最初の一歩が踏み出せない人
OSとか環境とか
- Windows 10 Pro
- macOS Mojave
- Unity 2019.2.8f1
- Rider 2019.2.2
補足
- 公式動画にて利用しているのはMacなので、Windowsユーザーはある程度脳内変換して見る事
- 筆者はWindows、Macの両方の環境で確認。Ubuntuとかでは検証してない。
- 基本Unityは英語メニューで利用
- 間違いがあったらツッコミ大歓迎
公式
https://learn.unity.com/tutorial/collecting-scoring-and-building-the-game#5c7f8529edbc2a002053b78a
アイテムのカウント
PlayerControllerスクリプトにアイテムのカウント機能を追加
単純にスクリプトにint型のcount変数を準備するだけ
ただしこのままでは内部的に数値が増加するだけでプレイヤーに伝わらないUnityでのテキスト表示
UnityのUIツールセットを利用する
UI>Textを追加
追加するとTextの親としてCanvas、EventSystemが追加されているこれはUnityの仕様で、全てのUI要素はCanvasの子要素として機能させなければならないという鉄の掟がある(多分)
Textを「Count Text」とリネーム
追加されたが、色が黒くて地味で見ずらいので、白に変更
Textオブジェクトには他のオブジェクトと違いRect Transformが存在する
スコア表示の位置調整
Anchor Presetsを表示して
Shift + Altキーを押しながら、一番左上のアイコンを押下
画面の左上にテキストが配置されるが、このままだとテキストがギリギリ過ぎてめり込んで見えてしまう
なのでPosX,PosYを調整して空白をもたせるとそれっぽくなるcount値の反映
PlayerControllerスクリプトの中でUnityEngine.UIのTextを用いて紐付ける
これでPlayerController側にフィールドが増えるので、そこにCountTextをドラッグして紐付ける
※最終的なソースは最後に書きますので動きはそちらを参考にする事↓紐付けるとこの様になる
ゲームの終了
UI>Textで終了用のテキストオブジェクトを追加
自動的にCanvasの下に追加されるので、下記のように調整
要素 値 Pos Y 75 Font Size 24 色 白 Text Win Text これで下記の状態になるので、実際に終了条件を満たした際に「Win Text」が更新されるスクリプトを実装
スクリプトを実装したら、PlayerオブジェクトのWin Textフィールドにドラッグして紐付ければ完成
最終的なPlayerController.cs
PlayerController.csusing System; using UnityEngine; using UnityEngine.UI; public class PlayerController : MonoBehaviour { public float speed; public Text countText; public Text winText; private Rigidbody rb; private int count; // See also:https://docs.unity3d.com/ja/2019.1/Manual/ExecutionOrder.html private void Start() { rb = GetComponent<Rigidbody>(); count = 0; SetCountText(); winText.text = ""; } private void FixedUpdate() { float moveHorizontal = Input.GetAxis("Horizontal"); float moveVertical = Input.GetAxis("Vertical"); Vector3 movement = new Vector3(moveHorizontal,0.0f,moveVertical); rb.AddForce(movement * speed); } private void OnTriggerEnter(Collider other) { if (other.gameObject.CompareTag("Pick Up")) { other.gameObject.SetActive(false); count++; SetCountText(); } } private void SetCountText() { countText.text = "Count :" + count.ToString(); if (count >= 12) { winText.text = "You Win!"; } } }
- 投稿日:2019-11-24T09:16:58+09:00
Span<T> を DllImport に渡す
// c の定義 SAMPLE_EXPORT void SAMPLE_NumberSequence(unsigned char *bytes, int length);[DllImport(DllName, EntryPoint = "SAMPLE_NumberSequence")] public static extern void SAMPLE_NumberSequence_UseRef(ref byte bytes, int length);と書いておいて
static void StackSample() { Span<byte> span = stackalloc byte[10]; SampleImport.SAMPLE_NumberSequence_UseRef(ref span[0], span.Length); Console.WriteLine(ToString(span)); }
ref span[0]
で先頭要素へのリファレンスを渡すことでできた。実験コード
- 投稿日:2019-11-24T09:16:58+09:00
Span を DllImport に渡す
// c の定義 SAMPLE_EXPORT void SAMPLE_NumberSequence(unsigned char *bytes, int length);[DllImport(DllName, EntryPoint = "SAMPLE_NumberSequence")] public static extern void SAMPLE_NumberSequence_UseRef(ref byte bytes, int length);と書いておいて
static void StackSample() { Span<byte> span = stackalloc byte[10]; SampleImport.SAMPLE_NumberSequence_UseRef(ref span[0], span.Length); Console.WriteLine(ToString(span)); }
ref span[0]
で先頭要素へのリファレンスを渡すことでできた。実験コード
- 投稿日:2019-11-24T06:54:13+09:00
Visual Studio 2019のC#・Android環境にて、指定した画像中に任意のファイルを埋め込む。コードのサンプルです。
表題の通り
Visual Studio 2019のC#にて、
Visual Studio 2019のC#にて、直接バイナリを叩いて指定した画像中に任意のファイル情報を埋め込む。
ステガノグラフィーに関する。コードのサンプルでございます。特段、NuGetなどで他のプラグインや
別途外部dllを必要としないようにしています。概要
任意のファイルを、画像に埋め込む際に
バイナリを以下のように、直接叩いています。1.指定した画像のバイナリを一旦すべて読み込む。
2.埋め込む任意のファイルのバイナリも、すべて読み込む。
3.画像の各バイナリを8桁の2進数にして、11111111とのようにする。
4.8桁の2進数の内、下位4桁をバッサリと切り捨て、1111----とのようにする。
5.埋め込むファイルの各バイナリを8桁の2進数にして、00000000とのようにする。
6.8桁の2進数の内、上位4桁と下位4桁に分割して、0000/0000とのようにする。
7.画像の各バイナリを上位4桁の2進数に、
埋め込むファイルの下位4桁を結合して、11110000とのようにする。
8.上記のように、画像の2バイトにつき、埋め込むファイル1バイトが埋まっていく。
9.埋め込みが終わった画像バイナリから、画像に戻す。
10.可逆圧縮であるpng形式で、出力する。そして、埋め込んだファイルを取り出す際は、
この逆をしています。今回は、キリ良く4ビットの深度で埋め込んでいます。
4ビット深度なら、(幅x高さx4)/2 バイトの情報が入る。はずなのですが
550kBを超えると、下記のコードでは問題が生じるので、修正中です。尚、1ビット深度~7ビット深度で、好みの深さで埋め込めます。
深度が浅いほど、容量が減る半面、画像が荒くならないです。
1ビット深度なら、荒さが目立たず、入っているとは、気づきにくいです。また、今回は、サンプルなので
ARGBすべてのビットに対して埋め込んでいますが、
必要に応じて、必要な部分だけ選択して埋め込むことも可能です。また、この方法により、
画像しか送受信できない環境や、
exeなど直接メールに添付できないような環境でも、
画像に一旦、埋め込むことで、やり取りすることが可能になります。※ただし、やり取りの過程で画像が加工されたりしてしまうと、
内部の情報が壊れるため、
画像が加工されずに、そのままやり取りできる環境が必要になります。注意事項
※この記事でのコードは、
最低限の記述にとどめているため、解放など不十分な部分や、
記述を省略している箇所があります。※コード中のURLは、参考にさせていただきましたサイト様のものでございます。
勝手ながら、参考にさせていただいたサイト様には、この場にて厚く御礼致します。下準備
記事本題のC#のコードを使用するために、
下記のxmlレイアウトを使用しています。GM_world.xml<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:id="@+id/back1" > <Button android:id="@+id/btn1" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/btn2" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>要は、埋め込み用のボタンと
取り出し用のボタン2つを
下図のように用意致したレイアウトとしております。
本題
標題にある機能のための
Visual Studio 2019 C#での
Android開発環境でのコードは、以下の通りでございます。MainActivity.csusing System; using System.Collections.Generic; using System.IO; using System.Text; using Android.App; using Android.Content; using Android.Graphics; using Android.OS; using Android.Support.V7.App; using Android.Widget; namespace App2 { [Activity(Label = "@string/app_name", Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)] public class MainActivity : AppCompatActivity { readonly string Output_DirName = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDcim).ToString(); Bitmap BaseImage = null; //基礎側のバイト配列の現在位置を格納 int base_cnt = 0; public async System.Threading.Tasks.Task Output_Sukashi_Bitmap_Making(Android.Net.Uri fileUris) { //★指定ファイルを画像に埋めて暗号化するところ //出力画像を組み立てるところ string err_str = "0"; //どこでエラーになったかを把握するための文字列 try { //基礎となる画像のロード if (BaseImage == null) { err_str = "WebClient"; using (System.Net.WebClient webClient = new System.Net.WebClient()) { //今回は、ネット空間からDLする。 err_str = "DownloadDataTaskAsync"; byte[] imageBytes = await webClient.DownloadDataTaskAsync(new Uri("https://blog-imgs-110.fc2.com/o/y/k/oyk3865b/2017_05_03_290.jpg")); err_str = "imageBytes : " + (imageBytes == null); if (imageBytes != null && imageBytes.Length > 0) { err_str = "DecodeByteArrayAsync"; BaseImage = await BitmapFactory.DecodeByteArrayAsync(imageBytes, 0, imageBytes.Length); Array.Clear(imageBytes, 0, imageBytes.Length); } } } //色設定をARGB8888に統一する。 //https://stackoverflow.com/questions/7320392/how-to-draw-text-on-image Android.Graphics.Bitmap.Config bitmapConfig = Android.Graphics.Bitmap.Config.Argb8888; BaseImage = BaseImage.Copy(bitmapConfig, true); //Bitmapのバイナリ置き換えの準備 //https://qiita.com/aymikmts/items/7139fa6c4da3b57cb4fc err_str = "byteBuffer"; Java.Nio.ByteBuffer byteBuffer = Java.Nio.ByteBuffer.Allocate(BaseImage.ByteCount); err_str = "CopyPixelsToBuffer"; BaseImage.CopyPixelsToBuffer(byteBuffer); err_str = "Flip"; byteBuffer.Flip(); err_str = "bmparr"; //基礎Bitmapのバイナリへの置き換え byte[] bmparr = new byte[byteBuffer.Capacity()]; err_str = "Get"; byteBuffer.Duplicate().Get(bmparr); err_str = "Clear"; byteBuffer.Clear(); //埋め込むファイルの名前の取得 string filename = Get_FileName_From_Uri(fileUris); //埋め込むファイルのバイナリ格納 List<byte> bs = new List<byte>(); //埋め込むファイルのバイナリを格納 //https://stackoverflow.com/questions/2436385/android-getting-from-a-uri-to-an-inputstream-to-a-byte-array/2436413 err_str = "Err_3 " + filename; using (Stream inputStream = ContentResolver.OpenInputStream(fileUris)) { err_str = "Err_4 " + filename; int bufferSize = 1024; using (Java.IO.BufferedInputStream stream = new Java.IO.BufferedInputStream(inputStream, bufferSize)) { //ファイルを開く int len = 0; //読み込み用バッファの準備 //https://stackoverflow.com/questions/2436385/android-getting-from-a-uri-to-an-inputstream-to-a-byte-array/2436413 byte[] buffer = new byte[bufferSize]; //ファイルを読み込む //http://d.hatena.ne.jp/curest/20090829/1251532479 err_str = "Err_5 " + filename; while ((len = stream.Read(buffer, 0, buffer.Length)) != -1) { if (len <= 0) { break; }//終端で出る else if (len == bufferSize) { //1024満願の場合 bs.AddRange(buffer); //埋め込むデータのバイナリをそのまま全てリストに読み込む } else { //終端手前の場合 byte[] temp_buf = new byte[len]; Array.Copy(buffer, 0, temp_buf, 0, len); bs.AddRange(temp_buf); //埋め込むデータのバイナリを必要分だけリストに読み込む Array.Clear(temp_buf, 0, temp_buf.Length); } } //閉じる err_str = "Err_6 " + filename; stream.Close(); } err_str = "Err_7 " + filename; inputStream.Close(); } //冒頭には、埋め込んだファイル情報を入れる byte[] name_data = Encoding.UTF8.GetBytes(filename); //名前のサイズ4バイト byte[] size_data = BitConverter.GetBytes((Int32)name_data.Length); //基礎の埋め込み開始位置の初期化 base_cnt = 0; //※※※今回は、下位4ビットに埋め込んでいくとする。※※※ //→つまり、4バイト埋めるのには、2倍の8バイト領域が必要 err_str = "埋め込んでいく"; for (int i = 0; i < (size_data.Length); i++) { data_umekomi(ref bmparr[base_cnt], ref bmparr[base_cnt + 1], size_data[i]); } //続いて、名前バイナリを埋め込む err_str = "name_data"; for (int i = 0; i < (name_data.Length); i++) { data_umekomi(ref bmparr[base_cnt], ref bmparr[base_cnt + 1], name_data[i]); } //いったん初期化 err_str = "Array.Clear"; Array.Clear(size_data, 0, size_data.Length); //次に、埋め込むファイル本体のサイズ err_str = "BitConverter.GetBytes"; size_data = BitConverter.GetBytes((Int32)bs.Count); err_str = "size_data"; for (int i = 0; i < (size_data.Length); i++) { data_umekomi(ref bmparr[base_cnt], ref bmparr[base_cnt + 1], size_data[i]); } //埋め込み側のバイナリ本体を埋め込む err_str = "byteary.Length"; for (int i = 0; i < (bs.Count); i++) { //安全装置 if (base_cnt + 1 > bmparr.Length - 1) { Toast.MakeText(Application.Context, "埋め込められる容量 " + i.ToString() + "B を超えました。", ToastLength.Long).Show(); break; } data_umekomi(ref bmparr[base_cnt], ref bmparr[base_cnt + 1], bs[i]); } //string check_filepath = System.IO.Path.Combine(Output_DirName, "check1.txt"); //await System.IO.File.WriteAllBytesAsync(check_filepath, bs.ToArray()); //set_SendBroadcast_state(check_filepath); bs.Clear(); //最後に適当に乱数を埋める err_str = "System.Random()"; System.Random rnd = new System.Random(); //乱数を配列に埋め込む Array.Clear(size_data, 0, size_data.Length); size_data = new byte[bmparr.Length - base_cnt]; rnd.NextBytes(size_data); for (int i = 0; i < (size_data.Length); i++) { //安全装置 if (base_cnt + 1 > bmparr.Length - 1) { break; } data_umekomi(ref bmparr[base_cnt], ref bmparr[base_cnt + 1], size_data[i]); } //埋め込んだバイナリを再びBitmapに戻す準備 err_str = "CreateBitmap"; Bitmap bitmap = Bitmap.CreateBitmap(BaseImage.Width, BaseImage.Height, bitmapConfig); //埋め込んだバイナリを再びBitmapに戻す err_str = "Wrap"; byteBuffer = Java.Nio.ByteBuffer.Wrap(bmparr); err_str = "CopyPixelsFromBuffer"; bitmap.CopyPixelsFromBuffer(byteBuffer); //埋め込み前画像の解放 BaseImage.Recycle(); BaseImage.Dispose(); BaseImage = null; //解放 byteBuffer.Clear(); byteBuffer.Dispose(); //出来上がった画像の保存パスの作成 err_str = "bitmap_save " + (bitmap == null) + " " + bmparr.Length; //+ " " + byteBuffer.Capacity(); string filepath = System.IO.Path.Combine(Output_DirName, "kikikanri.png"); //出来上がった画像の保存出力 err_str = "bitmap_save"; bitmap_save(bitmap, filepath, true, this); //出来上がった画像を開く err_str = "open_start_file"; open_start_file(filepath, this); //できた画像の解放 bitmap.Recycle(); bitmap.Dispose(); Array.Clear(bmparr, 0, bmparr.Length); } catch { //エラー時の表示 Toast.MakeText(Application.Context, err_str, ToastLength.Long).Show(); } } public async System.Threading.Tasks.Task Input_Sukashi_Bitmap_Making(Android.Net.Uri filePath) { //★埋めたファイルを復号するところ string err_str = "Input_Sukashi_Bitmap_Making"; try { //指定した画像を開く err_str = "DecodeStream"; Bitmap bitmap = null; using (var inputStream = ContentResolver.OpenInputStream(filePath)) { // URI指定の場合 //http://www.united-bears.co.jp/blog/archives/909 bitmap = BitmapFactory.DecodeStream(inputStream, null, null); inputStream.Close(); } //Bitmapのバイナリ置き場の準備 //https://qiita.com/aymikmts/items/7139fa6c4da3b57cb4fc err_str = "byteBuffer3"; Java.Nio.ByteBuffer byteBuffer = Java.Nio.ByteBuffer.Allocate(bitmap.ByteCount); err_str = "CopyPixelsToBuffer3 " + (bitmap == null) + " " + bitmap.ByteCount; bitmap.CopyPixelsToBuffer(byteBuffer); err_str = "Flip3"; byteBuffer.Flip(); err_str = "bmparr3"; //基礎Bitmapのバイナリの格納 byte[] bmparr = new byte[byteBuffer.Capacity()]; err_str = "Get3"; byteBuffer.Duplicate().Get(bmparr); err_str = "Clear3"; byteBuffer.Clear(); bitmap.Recycle(); bitmap.Dispose(); //埋め込むファイルのバイナリ格納用リスト err_str = "List<byte>"; List<byte> bs = new List<byte>(); //各初期化 bs.Clear(); base_cnt = 0; err_str = "名前のサイズを取得"; do { //まずは、埋め込んだファイルの名前のサイズを取得[Int32 = s4バイト固定] bs.Add(data_toridashi(bmparr[base_cnt], bmparr[base_cnt + 1])); } while (bs.Count < 4); //4バイト貯まるまでループ err_str = "名前領域の取り出し"; Int32 data_size = BitConverter.ToInt32(bs.ToArray()); bs.Clear(); do { //埋め込んだファイルの名前を取得 bs.Add(data_toridashi(bmparr[base_cnt], bmparr[base_cnt + 1])); } while (bs.Count < data_size); //先に取得したサイズまでループ //UTF8にてバイナリを文字列に戻す err_str = "出力先パスの完成"; string load_filepath = System.Text.Encoding.UTF8.GetString(bs.ToArray()); load_filepath = System.IO.Path.Combine(Output_DirName, load_filepath); err_str = "本体バイナリのサイズを取得"; bs.Clear(); do { //埋め込んだファイルのサイズの取得[4バイト固定] bs.Add(data_toridashi(bmparr[base_cnt], bmparr[base_cnt + 1])); } while (bs.Count < 4); data_size = BitConverter.ToInt32(bs.ToArray()); err_str = "本体バイナリの取得"; bs.Clear(); do { //埋め込んだファイルのバイナリの取得 bs.Add(data_toridashi(bmparr[base_cnt], bmparr[base_cnt + 1])); if (bs.Count == data_size) { break; } } while (bs.Count < data_size); //先に取得したファイルサイズが貯まるまで周回 //string check_filepath = System.IO.Path.Combine(Output_DirName, "check2.txt"); //await System.IO.File.WriteAllBytesAsync(check_filepath, bs.ToArray()); //set_SendBroadcast_state(check_filepath); err_str = "ファイルに書き出す"; await System.IO.File.WriteAllBytesAsync(load_filepath, bs.ToArray()); bs.Clear(); //復号したファイルを開く err_str = "open_start_file3"; open_start_file(load_filepath, this); //復号したファイルをPCからも見えるようにする。 err_str = "set_SendBroadcast_state3"; set_SendBroadcast_state(load_filepath); } catch { //エラー時の表示 Toast.MakeText(Application.Context, err_str, ToastLength.Long).Show(); } } public string Get_FileName_From_Uri(Android.Net.Uri uri) { //Uriから、ファイルバスを取得する。 //https://qiita.com/CUTBOSS/items/3476e164b86a63b02b2e //安全装置 if (null == uri) { return null; } //スキームの取得 string scheme = uri.Scheme; //スキームによる分岐 string fileName = null; switch (scheme) { case "content": String[] projection = { Android.Provider.MediaStore.MediaColumns.DisplayName }; Android.Database.ICursor cursor = this.ContentResolver.Query(uri, projection, null, null, null); if (cursor != null) { if (cursor.MoveToFirst()) { fileName = cursor.GetString( cursor.GetColumnIndexOrThrow(Android.Provider.MediaStore.MediaColumns.DisplayName)); } cursor.Close(); } break; case "file": fileName = new Java.IO.File(uri.Path).Name; break; default: break; } return fileName; } private void data_umekomi(ref byte base1, ref byte base2, byte umekomi_byte) { //指定されたバイト数値にデータを埋め込む所 //基礎画像の下位4ビットをカットする。 string base1_str = Convert.ToString(base1, 2).PadLeft(8, '0'); //8桁の二進数にする。 base1_str = base1_str.Substring(0, 4); //上位4ビットのみ取得。 string base2_str = Convert.ToString(base2, 2).PadLeft(8, '0'); //8桁の二進数にする。 base2_str = base2_str.Substring(0, 4); //上位4ビットのみ取得。 //空いた下位4ビットにデータを埋め込む string umekomi_str = Convert.ToString(umekomi_byte, 2).PadLeft(8, '0'); //8桁の二進数にする。 base1_str = base1_str + umekomi_str.Substring(0, 4); //下位4ビットに埋め込む base1 = Convert.ToByte(base1_str, 2); //2進数からbyteに変換 base2_str = base2_str + umekomi_str.Substring(4, 4); //下位4ビットに埋め込む base2 = Convert.ToByte(base2_str, 2); //2進数からbyteに変換 //基礎側のバイナリは2つ進む base_cnt += 2; } private byte data_toridashi(byte base1, byte base2) { //指定されたバイト数値からデータを取り出す所 //基礎画像の上位4ビットをカットする。 string base1_str = Convert.ToString(base1, 2).PadLeft(8, '0'); //8桁の二進数にする。 base1_str = base1_str.Substring(4, 4); //下位4ビットのみ取得。 string base2_str = Convert.ToString(base2, 2).PadLeft(8, '0'); //8桁の二進数にする。 base2_str = base2_str.Substring(4, 4); //下位4ビットのみ取得。 //空いた下位4ビットにデータを埋め込む string toridashi_str = base1_str + base2_str; //基礎側のバイナリは2つ進む base_cnt += 2; return Convert.ToByte(toridashi_str, 2); //2進数からbyteに変換 } public void bitmap_save(Bitmap bitmap, string filePath, bool SendBroadcast_flg, Activity ac) { //指定場所に画像の保存 try { if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(filePath))) { //保存先フォルダがない場合 //作る System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(filePath)); } if (bitmap.Width <= 0 || bitmap.Height <= 0) { //大きさのない画像の場合 return; //出る } //ファイル書き込み using (System.IO.FileStream fos = System.IO.File.Create(filePath)) { if (System.IO.Path.GetExtension(filePath).ToLower() == ".jpg" || System.IO.Path.GetExtension(filePath).ToLower() == ".tmp") { //JPEG保存の場合 bitmap.Compress(Bitmap.CompressFormat.Jpeg, 100, fos); } else if (System.IO.Path.GetExtension(filePath).ToLower() == ".png") { //PNG保存の場合 bitmap.Compress(Bitmap.CompressFormat.Png, 100, fos); } else { bitmap.Compress(Bitmap.CompressFormat.Webp, 100, fos); } fos.Close(); }; //PCからも見られるように設定する場合。 if (SendBroadcast_flg && (System.IO.Path.GetExtension(filePath).ToLower() != ".tmp")) { //一時ファイルでない場合 set_SendBroadcast_state(filePath); } //↓★ここで解放してはならない //bitmap.Dispose(); } catch { } } public void set_SendBroadcast_state(string Output_Path) { //PCからも見られるように設定する。 //https://qiita.com/wasnot/items/ae1e6282d2c33626b604 //https://developer.xamarin.com/api/member/Android.Content.Context.SendBroadcast/p/Android.Content.Intent/ //https://bluefish.orz.hm/sdoc/android_file.html#%E6%96%B0%E8%A6%8F%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E4%BD%9C%E6%88%90 try { if (System.IO.File.Exists(Output_Path)) { //安全措置 using (Java.IO.File j_file = new Java.IO.File(Output_Path)) { Android.Net.Uri uri = Android.Net.Uri.FromFile(j_file); Intent mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile, uri); Application.Context.SendBroadcast(mediaScanIntent); mediaScanIntent.Dispose(); uri.Dispose(); } } } catch { } } public void open_start_file(string filepath, Activity ac) { //指定のファイルを開く int err_flg = 0; try { //ファイルが存在するかの確認 if (System.IO.File.Exists(filepath)) { err_flg = 100; string application = "image/jpeg"; //初期は画像 Android.Net.Uri uri; using (Java.IO.File file = new Java.IO.File(filepath)) { file.SetReadable(true); err_flg = 103; if (System.IO.Path.GetExtension(filepath).ToLower().Contains(".pdf")) { //PDFの場合 application = "application/pdf"; } else if (System.IO.Path.GetExtension(filepath).ToLower().Contains(".zip")) { //zipの場合 application = "application/zip"; } else if (System.IO.Path.GetExtension(filepath).ToLower().Contains(".xlsx")) { //xlsxの場合 //https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types application = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; } else if (System.IO.Path.GetExtension(filepath).ToLower().Contains(".ods")) { //odsの場合 application = "application/vnd.oasis.opendocument.spreadsheet"; } else if (System.IO.Path.GetExtension(filepath).ToLower().Contains(".png")) { //pngの場合 application = "image/png"; } err_flg = 1; // ストレージとカメラの権限の確認 if (Build.VERSION.SdkInt >= BuildVersionCodes.M) { //Android6.0以上の場合 //作ったファイルを開きたい //https://www.petitmonte.com/java/android_fileprovider.html //https://stackoverflow.com/questions/40462245/fileprovider-xamarin-not-displaying-file //https://stackoverflow.com/questions/50072057/android-proper-way-to-share-public-files-between-apps err_flg = 100; uri = Android.Support.V4.Content.FileProvider.GetUriForFile( Application.Context, Application.Context.PackageName + ".provider", file); err_flg = 101; Intent intent = new Intent(Intent.ActionView); intent.SetDataAndType(uri, application); intent.SetFlags(ActivityFlags.NoHistory); intent.SetFlags(ActivityFlags.GrantReadUriPermission); err_flg = 102; Intent intent2 = Intent.CreateChooser(intent, "Open File"); if (intent2.ResolveActivity(ac.PackageManager) != null) { err_flg = 103; Application.Context.StartActivity(intent2); } else { //開けるファイルがない場合 Toast.MakeText(Application.Context, "この環境には、" + System.IO.Path.GetExtension(filepath).ToLower() + "形式ファイルを開けるアプリがありません。" + Convert.ToChar(13) + Convert.ToChar(10) + "アプリをインストールするか、他の環境で開いてください。", ToastLength.Long).Show(); } } else { //Android6.0未満の場合 uri = Android.Net.Uri.FromFile(file); Intent intent = new Intent(Intent.ActionView); err_flg = 2; intent.SetDataAndType(uri, application); intent.SetFlags(ActivityFlags.NoHistory); intent.SetFlags(ActivityFlags.ClearWhenTaskReset | ActivityFlags.NewTask); err_flg = 4; //ちゃんと開けるアプリがあるのかを確認 //https://developer.android.com/guide/components/intents-filters?hl=ja if (intent.ResolveActivity(ac.PackageManager) != null) { err_flg = 5; //実行 Application.Context.StartActivity(intent); } else { //開けるファイルがない場合 Toast.MakeText(Application.Context, "この環境には、" + System.IO.Path.GetExtension(filepath).ToLower() + "形式ファイルを開けるアプリがありません。" + Convert.ToChar(13) + Convert.ToChar(10) + "アプリをインストールするか、他の環境で開いてください。", ToastLength.Long).Show(); } } } } } catch { //エラー時は無視 Toast.MakeText(Application.Context, "エラー発生:" + err_flg.ToString(), ToastLength.Long).Show(); } } //権限がなければ上がる bool no_Permission_flg = false; protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); // Set our view from the "main" layout resource SetContentView(Resource.Layout.activity_main); // ストレージ権限の確認 try { if (Build.VERSION.SdkInt >= BuildVersionCodes.M) { //Android6.0以上の場合のみ string[] Manifest_Permissions = { Android.Manifest.Permission.WriteExternalStorage, Android.Manifest.Permission.ReadExternalStorage, Android.Manifest.Permission.Internet }; //各権限をループ foreach (System.String Permission_str in Manifest_Permissions) { //https://docs.microsoft.com/ja-jp/xamarin/android/app-fundamentals/permissions?tabs=windows //https://www.petitmonte.com/java/android_fileprovider.html if (ApplicationContext.CheckCallingOrSelfPermission(Permission_str) != Android.Content.PM.Permission.Granted) { //許可されていない場合 no_Permission_flg = true; // ストレージの権限の許可を求めるダイアログを表示する if (Android.Support.V4.App.ActivityCompat.ShouldShowRequestPermissionRationale(this, Permission_str)) { //Android.Support.V4.App.ActivityCompat.RequestPermissions(this, // new string[] { Permission_str }, (int)Android.Content.PM.RequestedPermission.Required); Android.Support.V4.App.ActivityCompat.RequestPermissions(this, Manifest_Permissions, (int)Android.Content.PM.RequestedPermission.Required); } else { Toast toast = Toast.MakeText(ApplicationContext, "アプリ実行の権限が必要です", ToastLength.Long); toast.Show(); Android.Support.V4.App.ActivityCompat.RequestPermissions(this, Manifest_Permissions, (int)Android.Content.PM.RequestedPermission.Required); } } } } Button btn1 = FindViewById<Button>(Resource.Id.btn1); btn1.Text = "ファイル埋め込み"; btn1.Click += delegate { if (!no_Permission_flg) { //権限がなければ無効 //https://developer.xamarin.com/recipes/android/data/files/selecting_a_gallery_image/ using (Intent imageIntent = new Intent(Intent.ActionGetContent)) { //埋め込みファイル選択 imageIntent.SetType("*/*"); //複数画像選択可能で固定 //https://stackoverflow.com/questions/19585815/select-multiple-images-from-android-gallery imageIntent.PutExtra(Intent.ExtraAllowMultiple, false); imageIntent.SetAction(Intent.ActionGetContent); StartActivityForResult( Intent.CreateChooser(imageIntent, "画像に埋め込むファイルを選択してください。"), 0); } } }; Button btn2 = FindViewById<Button>(Resource.Id.btn2); btn2.Text = "ファイル取り出し"; btn2.Click += delegate { if (!no_Permission_flg) { //権限がなければ無効 //https://developer.xamarin.com/recipes/android/data/files/selecting_a_gallery_image/ using (Intent imageIntent = new Intent(Intent.ActionGetContent)) { //埋め込みファイル選択 imageIntent.SetType("image/png"); //複数画像選択可能で固定 //https://stackoverflow.com/questions/19585815/select-multiple-images-from-android-gallery imageIntent.PutExtra(Intent.ExtraAllowMultiple, false); imageIntent.SetAction(Intent.ActionGetContent); StartActivityForResult( Intent.CreateChooser(imageIntent, "埋め込んだ画像ファイルを選択してください。"), 1); } } }; } catch { } } protected override async void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); string err_str = "0"; LinearLayout back1 = FindViewById<LinearLayout>(Resource.Id.back1); try { if (requestCode == 0 || requestCode == 1) { //◆◆◆以下、画像選択時のイベント◆◆◆ if (resultCode == Result.Ok) { //OK(ズドン) //複数画像を選択した場合。 back1.SetBackgroundColor(Color.Indigo); await System.Threading.Tasks.Task.Delay(5); //URIの記憶 //サムネイルにしたい画像のURIの格納 Android.Net.Uri selected_fileuri = null; //初期化 err_str = "selected_fileuris.Clear"; if (data.ClipData != null) { //複数選択された場合 if (data.ClipData.ItemCount > 0) { selected_fileuri = data.ClipData.GetItemAt(0).Uri; } } else if (data.Data != null) { //1つだけの選択時 selected_fileuri = data.Data; } err_str = "selected_fileuri"; //ポジフィルムの作成時 if (requestCode == 0) { //URI指定の場合 await Output_Sukashi_Bitmap_Making(selected_fileuri); } else { //今度は、埋め込んだファイルを復号する。 await Input_Sukashi_Bitmap_Making(selected_fileuri); } back1.SetBackgroundColor(Color.Ivory); await System.Threading.Tasks.Task.Delay(5); } } } catch { //全体を通しての、エラー時 Android.Widget.Toast.MakeText(Application.Context, err_str, Android.Widget.ToastLength.Long).Show(); } } } }なお、埋め込んだファイルが確実に復号できているかを確認するために
CRC-32等のハッシュ値を併せて埋め込んでおくと、より確実に思います。サンプル
上記のコードで、
画像に、私作成であり
Vector様のサイトで公開している
『オフタイマー弐式』という、
Windowsソフトの、exeファイルを
埋め込んでおります。
exeファイルは、直接だったり
zipに圧縮してメールでやり取りしようとしても、
セキュリティにて、止められたりしますが画像に埋め込んであるので
途中で加工さえされなければ、
そこは、すり抜けます。人に見られたくないファイルの
保存にも向きますが、消えると困る大切なファイルの埋め込みには向かないと
私は感じております。
- 投稿日:2019-11-24T02:57:50+09:00
[C#]OpenCvSharp3を使ってPictureBoxのImageに矩形を描画すると色が消える問題
問題
OpenCvSharp3を使ってビットマップ化したPictureBoxのImageに矩形を描画をすると、再度ビットマップ化したときに色情報が落ちてしまう。
PNGをビットマップ化した場合は問題なかったので違いを調べてみた。コード(修正前)
Bitmap pngBmp = new Bitmap(@"test.png"); Bitmap pictImageBmp = new Bitmap(this.pictureBox1.Image); Mat pngMat = BitmapConverter.ToMat(pngBmp); Mat pictImageMat = BitmapConverter.ToMat(pictImageBmp); // 緑色の矩形を描画 Rect rect = new Rect(10, 20, 50, 300); Cv2.Rectangle(pngMat, rect, new Scalar(0, 255, 0), 2); Cv2.Rectangle(pictImageMat, rect, new Scalar(0, 255, 0), 2); // 正しい色で矩形が表示される this.pictureBox1.Image = BitmapConverter.ToBitmap(pictImageMat); // 矩形が白くなる this.pictureBox1.Image = BitmapConverter.ToBitmap(pictImageMat);解決
OpenCvSharp3でMat変換すると、カラータイプが以下の通りになっていた。
PNG → BGR
PictureBoxのImage → BGRAOpenCvSharp3ではBGRで描画処理をしているため、BGRAでビットマップ化すると色情報が落ちてしまう。
※どうしてそうなるのかは調べきれていません、すみません。コード(修正後)
// Mat pictImageMat = BitmapConverter.ToMat(pictImageBmp); // アルファチャンネル付きBGRからBGRに変換 Mat pictImageMat = BitmapConverter.ToMat(pictImageBmp).CvtColor(ColorConversionCodes.BGRA2BGR);
- 投稿日:2019-11-24T00:41:25+09:00
PackageManagerから必要なPackageをインポートする
はじめに
はやく民主化して
こんにちは、アドベントカレンダー19日目担当の避雷です。unitypackageを配布したことのある人の中には、相手方と自分のプロジェクトに入っているリソースの乖離に苦しんだことがある人もいると思います。
例えばこっちがPPSv2でイイ感じの画を提供していても、インポート先のProjectにPackageManagerのPostProcessingが入っていなければその魅力を伝えることが出来ません。
今回はEditor拡張を用いてPackageManagerを操作する方法を調べてみましょう。PackageManagerを操作する
PackageManagerに対する基本的な操作は
UnityEditor.PackageManager
で行います。
https://docs.unity3d.com/ja/2017.4/ScriptReference/PackageManager.Client.htmlインポートされているパッケージの一覧を取得
インポートされているパッケージの一覧を取得するには、
Client.List
を使います。Listの返り値はListRequestです。
https://docs.unity3d.com/ja/2017.4/ScriptReference/PackageManager.Client.List.html
返り値の名称からもわかるように、この関数は非同期で実行されます。ListRequestはStatus,Resultなどの変数を持つので、これらを参照して非同期的に書きましょう。
↓サンプルコード(インポートされたすべてのpackageを出力)using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using UnityEditor; using UnityEditor.PackageManager; using UnityEngine; public class ListPackage { [MenuItem("PackageManager/List")] static async void List() { var listRequest = Client.List(); while (listRequest.Status == StatusCode.InProgress) { await Task.Delay(100); } if (listRequest.Status == StatusCode.Success) { var result = ""; foreach (var package in listRequest.Result) { result += package.packageId + "\n"; } Debug.Log(result); } if (listRequest.Status == StatusCode.Failure) { Debug.Log("Error!"); } } }MenuItemアトリビュートによってUnityEditorのメニューバーにPackageManagerの欄が追加されていると思うので、Listをクリックしてみましょう。一瞬のディレイの後、以下のようなログが出力されます。
Hoge@huga
と言うのがそれぞれのpackageのpackageIDです。
@より前がpackageの名前、@より後がpackageのversionです。
現在はcom.unity.hoge
となっている(つまりUnity公式の)packageしかありませんが、将来的にPackageManagerが使えるようになれば、他の人がPackageをインポートすることが出来るようになるはずです。パッケージを指定してインポートする
新しくpackageをインポートするときは
Client.Add
を使います。Addの返り値はAddRequestです。
これを実行するとプログレスバーが表示され、通常の方法でのpackageのinstallと同じようにインポートされます。
https://docs.unity3d.com/ja/2017.4/ScriptReference/PackageManager.Client.Add.html
Listと同様非同期で処理されます。引数のpackageIdOrNameは、nam(@の前)だけを指定すると最新版をインポートしてくれるようになっています。packageIDによってバージョン(@の後ろ)まで指定すれば、古いバージョンのpackageをインポートしてくれます。
↓サンプルコード(適当なpackageをインポートする)同様にメニューバーにAddがあるはずなので押してみます。今回はPostProcessingを直打ちして最新版をインストールするようにしています。しばらく経過すると下記のようなプログレスバーが出てきて、インポートの進捗を伝えてくれます。
更に暫くすれば、AddRequest.StatusがSuccessとなり、以下のようなログが出力されます。
Packageを見るとちゃんとPostProcessingがインストールされていることが分かります。
既にインストールされているpackageについては何も起こりません。(特に処理が行われず、Succeededとなる)
unity packageをインポートした直後にPackageManagerを呼びたい
[InitializeOnLoad]
アトリビュートを付けることによって、UnityEditor起動時とそのクラス自身のコンパイル時にクラスのコンストラクタが走るようになります。
ここに先ほどの処理を書けばunitypackageインポート直後にpackageをインストールすることが出来ます(UnityPackageとPackageManagerのpackageで紛らわしいですね…)
ただし、コンストラクタ自身はasyncを付けることが出来ないため、コンストラクタ内で別の非同期メソッドを走らせるようにしましょう。おわりに
他のIDEにおけるNugetのような役割を果たしているPackageManagerですが、現在はpublicなrepositoryをjson直書きで登録できるみたいですが、そのうち民主化されて楽にPackageが公開されるようになるといいですね。
個人的にはPackageで追加されるアセット類はgitで共有せずに済むところが気に入っています。
- 投稿日:2019-11-24T00:09:30+09:00
円形のゲージを自力で作る
はじめに
この記事は2013年頃に、uGUIもなく、NGUIにお金を出すのも厳しかった時に、どうにかして円形のゲージを作りたくて作ったプログラムを、投稿用に手直しをしたものになります。
現在のUnityではuGUIでお手軽に実装できるのでそちらを使ったほうが確実に良いです。
UIではなく3D空間に表示したいだとか、Unityじゃない他のプログラムで実装するのには少し役立つかもしれません。丸い画像はいらすとやさんからお借りしました。
お世話になってます。
丸のマークのイラスト「○」実装
CircleGauge.csusing UnityEngine; [RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))] [ExecuteInEditMode] public class CircleGauge : MonoBehaviour { private const float TWO_PI = Mathf.PI * 2; private static readonly int[] Triangles = new int[] { 4,3,5, 5,3,0, 3,2,0, 0,2,1, 5,0,6, 6,0,7, 0,9,7, 7,9,8, }; private static readonly int[] TrianglesClockwise = new int[] { 2,3,1, 1,3,0, 3,4,0, 0,4,5, 9,0,8, 8,0,7, 0,5,7, 7,5,6, }; private enum StartPosition { Right = 0, Top, Left, Bottom, } private Mesh _mesh = null; private Vector3[] _vertices = null; private Vector2[] _uv = null; [SerializeField, Range(0.0f, 1.0f)] private float _value = 0f; [SerializeField] private StartPosition _startPosition = StartPosition.Right; [SerializeField] private bool _clockwise = false; [SerializeField] private Texture2D _texture = null; [SerializeField] private bool _isUpdate = false; void Start() { CreateMesh(); } void Update() { if (_isUpdate) { _value += Time.deltaTime / 5; if (_value > 1) { _value = _value - Mathf.Floor(_value); } } UpdateMesh(); } /// <summary> /// Mesh情報更新 /// </summary> private void UpdateMesh() { for (int i = 0; i < _vertices.Length; i++) { if (i != 0) { var val = Mathf.Clamp(_value, 0, 0.125f * (i - 1)); var rad = val * TWO_PI * (_clockwise ? -1 : 1) + Mathf.PI * ((int)_startPosition * 0.5f); // normalized rad rad = rad - TWO_PI * (int)(rad / TWO_PI); if (rad < 0.0f) { rad += TWO_PI; } // rad in top if (ValueInRange(rad, Mathf.PI * 0.25f, Mathf.PI * 0.75f)) { _vertices[i].y = 0.5f; _vertices[i].x = _vertices[i].y / Mathf.Tan(rad); } // rad in left else if (ValueInRange(rad, Mathf.PI * 0.75f, Mathf.PI * 1.25f)) { _vertices[i].x = -0.5f; _vertices[i].y = Mathf.Tan(rad) * _vertices[i].x; } // rad in bottom else if (ValueInRange(rad, Mathf.PI * 1.25f, Mathf.PI * 1.75f)) { _vertices[i].y = -0.5f; _vertices[i].x = _vertices[i].y / Mathf.Tan(rad); } // rad in right else { _vertices[i].x = 0.5f; _vertices[i].y = Mathf.Tan(rad) * _vertices[i].x; } } _uv[i].x = _vertices[i].x + 0.5f; _uv[i].y = _vertices[i].y + 0.5f; } _mesh.vertices = _vertices; _mesh.uv = _uv; _mesh.triangles = _clockwise ? TrianglesClockwise : Triangles; } /// <summary> /// 値がmin~maxの範囲内にあるかチェック。 /// value が min, max と同じ値の場合もtrue。 /// </summary> private bool ValueInRange(float value, float min, float max) { return min <= value && value <= max; } /// <summary> /// 描画用Mesh生成 /// </summary> [ContextMenu("Reset Mesh")] private void CreateMesh() { var renderer = gameObject.GetComponent<MeshRenderer>(); var meshFilter = gameObject.GetComponent<MeshFilter>(); int length = 10; _vertices = new Vector3[length]; _uv = new Vector2[length]; var material = new Material(Shader.Find("Mobile/Particles/Alpha Blended")) { name = "material" }; material.SetTexture("_MainTex", _texture); _mesh = meshFilter.sharedMesh = new Mesh(); renderer.sharedMaterial = material; UpdateMesh(); } }解説
考え方
まず円形の画像を円形に表示したい場合はどうしたら良いかと考えたときに、中心点と円周上を360に分割した点でポリゴンを作れば良いかとも考えました。
ただ、少し重そうな気がしたのと、そのポリゴンに収まる余白のある元画像を作る必要がありそうだったのでもっと簡素化できないかと考えたときに、画像は四角形に描画されるので、四角形の外周を円運動と同じように角度によって等速で移動できないかと考えました。
上記のプログラムはそれをその通りに実装したものです。四角形の外周を角度によって移動するには
※四角形のサイズは縦横1(0を中心としたxy共に-0.5~0.5の範囲)とします。
※角度は正規化されている(0~360°内にある)ものとします。角度によってx,yのどちらかの値が決まる
まずは現在の角度によって、xまたyのどちらかの値が確定します。
- 角度が0°~45°、または225°~360°の時 : xは右辺上(0.5)に固定
- 角度が45°~135°の時 : yは常に上辺上(0.5)に固定
- 角度が135°~225°の時 : xは常に左辺上(-0.5)に固定
- 角度が225°~315°の時 : yは常に下辺上(-0.5)に固定
確定したxまたはyの値から、確定していないほうのx,yの値を算出
これにはTangentを用います。
覚えていますか?Tangent。
45°、135°、225°、315°に近いほど1または-1に近づき、水平に近いほど0、垂直に近いほど∞に近づきます。
式はtanθ = y / x
です。
つまり
x が確定している場合 :y = tanθ * x
y が確定している場合 :x = y / tanθ
という風に確定した値から確定しいない方を算出します。
これで角度によって四角形のどの外周上にいるか算出できました。Meshの作成と操作
必須コンポーネント
MeshRendererやMeshFilterなどのコンポーネントを用いて、自前でMeshの操作を行います。
[RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]で描画に必要なコンポーネントが必ず付随するようにします。
Meshの作成
verticesとuvは、中心点+外周を移動する9つの点の計10点で構成します。
位置は後々計算で算出されるので初期化時はすべてVector3.zero(Vector2.zero)で大丈夫です。
こんなイメージです。
※振っている番号にも意味があります。
verticesの各点の位置
※右側を開始点とした場合の説明になります。
四角形の外周上の位置を角度によって決めることができましたが、次はそれをverticesの各点に反映します。
中心点は必ず(0,0)なので計算はスキップします。
それ以外の点ですが、入力された角度が何度であろうと各点の範囲は決まっているので、各点の計算時に角度をその点の最大値に制限する必要があります。
①の点は必ず0°、②の点は0°~45°、③の点は0°~90°... といった具合です。
それがvar val = Mathf.Clamp(_value, 0, 0.125f * (i - 1));の部分です。
入力されるゲージの値_value
の値を制限することで、自動的に角度についても値が制限されることになります。uvの各点の位置
uvについてはverticesの同じindexのx,y値に、それぞれ0.5をプラスした値が0~1になるのでそれで大丈夫です。
_uv[i].x = _vertices[i].x + 0.5f; _uv[i].y = _vertices[i].y + 0.5f;もしTexture全体ではなく、特定の範囲を使いたい等といった場合には、この値に範囲を掛け合わせて計算すればいけるはずです。
triangles
trianglesはカメラ側から見た際に、各ポリゴンが時計回りになるようになるように設定されていれば順番は特に関係ありません。
とりあえず左上から1枚ずつ三角形を構成するような作りにしています。
このコンポーネントでは反時計回り、時計回りを選べるようにしていますが、verticesの計算上、同じTrianglesを使ってしまうとゲージを時計回りにしたときにポリゴンが反時計回り(裏向き)になってしまうので、別々のTrianglesを用意しています。
その他余談的なもの
isUpdateフラグについて
_isUpdateのチェックはデバッグ用なので実際に使うときは消してください。
角度の正規化
rad = rad - TWO_PI * Mathf.Floor(rad / TWO_PI);とすれば1行で書けますがほんの少しだけ処理速度が劣ります。
と言っても誤差程度なのでシビアな状況じゃなければこちらの方がスマートだと思います。コメント
// rad in right
がif分の最後の分岐になっている理由角度が右辺の範囲にあるかどうかの判定ですが、これをifで記述すると
if(ValueInRange(rad, Mathf.PI * 0, Mathf.PI * 0.25f) || ValueInRange(rad, Mathf.PI * 1.75f, Mathf.PI * 2.0f))のようにこれだけ2回チェックになってしまうので意図的にelseにしてます。
最後に
最初にも書きましたが、UIとして使いたい場合はuGUIなどを使った方が良いです。
そちらだと360°以外にも180°、90°、Horizontal、Verticalなど色々なモードが選べます。