20200801のUnityに関する記事は8件です。

Hololens2でMapBox SDK (Maps for unity)を使う

はじめに

Hololens2で3Dマップを使用する際、BingMapやGoMAP等いくつかの選択肢がありますが、
今回はMapBoxのUnity用SDK(Maps for Unity)を使用する方法をご紹介したいと思います。

尚、下記がMapBoxを採用した理由です

  • 無料である程度の規模まで使える
    https://www.mapbox.com/pricing/

  • 大きなサイズの地図を表示できる
    BingMapも試しましたが、地図の大きさが最大3x3 UnityUnitに制限されているようです。
    MapBoxは最大10x10UnityUnitまで表示可能です

  • タイルの品質
    BingMapでは私の使用する拡大率(ZoomLevel 7~13)でのタイル(テクスチャ)が芳しくありませんでした。

BingMapではタイルの解像度が宜しくない
5d963bf1ae0452d43fe8c674251670e0.jpg

Maps for unityでは良好な解像度が得られる
a8e2360786afc03867bfda89f0aea448.jpg

注意点

Maps for UnityはIL2CPPでのビルドに対応していません。
よってScriptingBackendに.Netを指定する必要がありますので、Unityのバージョンは2018.4系を使用する必要があります。

MapBox アカウント作成

Maps for Unityを使用する為にはmapboxのアカウントが必要です。
https://account.mapbox.com/auth/signup/
からアカウントを作りログインしておきます

Maps for Unityダウンロード

https://www.mapbox.com/install/unity/
よりSDK(Maps for Unity)をダウンロードします

MRTKダウンロード

https://github.com/Microsoft/MixedRealityToolkit-Unity/releases
よりMRTK(Unity.Foundation.2.4.0.unitypackage)をダウンロードします。

プロジェクト作成 & MRTKインポート

適当なサンプルプロジェクトを作り、MRTKをインポートします。
前述していますがUnityのバージョンは2018.4系を使って下さい。

プロジェクトを作成後、Assets => Import Package => Custom Package から先ほどダウンロードしたMRTKをインポートします。
途中で表示されるインポートするファイルの指定では、全てのファイルをインポートします。

インポートが終了するとUnityのメニューバーにMixed Reality Toolkitという項目が追加されますので、
ここからAdd Scene and Configure...を実行します。
この段階で、Hierarchyは画像のようになっているはずです。
0f461e21152b49838171afa326dc1643.png

MRTK 設定

Project ViewからMixedReality Toolkitを選択し、インスペクターでMixed Reality ToolKitコンポーネントの設定を編集します。
Configuration Profileを DefaultHololens2ConfigurationProfileに変更します。

Buildセッティング

Hololens2向けにビルドセッティングを行います。
File => BuildSettingを選択してBuildSettingダイアログを表示し、PlatformからUniversal Windows Platformを選択後、Switch Platformボタンを押してプラットフォームを切り替えます。
途中MRTK Project Configuratorが表示される場合は、Enable MS Buildのチェックを外してApplyしてください。
f7a409f6cf027b534fe25f8bbb6b304b.png

Player設定

続いてBuildSettingダイアログでPlayerSettingボタンを押し、Player設定画面を表示します。

XR Settingsタブの設定および確認

  • Virtual Reality Supportedがチェックされている
  • Virtual Reality SDKsにWindows Mixed Realityが追加されている
  • Depth Formatを16bit depthに変更
  • Enable Depth Buffer Sharingがチェックされている
  • Stereo Rendering ModeでSingle Pass Instancedが選択されている d1c488942077db96de7cbfc723f07255.png

Publish Settingsの設定および確認

  • Packagingをプロジェクトにふさわしい内容で適当に書き換えてください。

Other Settingsの設定および確認

  • Configuration内のScripting Runtime Versionが.Net 4.x Equivalentが選択されている
  • Scripting Backendを.NETに変更
  • Api Compatiblility Levelを.NET Standard2.0に変更

Maps for Unityのインストール及び設定

Assets => Import Package => Custom Package から先ほどダウンロードしたMaps for Unityをインポートします。
途中で表示されるインポートするファイルの指定では
・Mapbox
・ThirdpartyAssets
のみをインポートします。

インポート中にMapbox Setupダイアログが表示されますので、Access Tokenの欄にアクセストークンを入力し、submitを押します。
アクセストークンは下記URLより取得して下さい。
https://www.mapbox.com/install/

またこのダイアログから各種サンプルプログラムにアクセスできます。
今回はサンプルプログラムは使わないので、アクセストークン設定後はそのままダイアログを消してください。

998647cababf26326a2ff70c5750a853.png

Project ViewからAssets\Mapbox\Core\Plugins\Mapbox\MapboxAccounts\net4xのMapboxAccountsUnity.dllを選択し、
インスペクターよりSelect Platforms for pluginセクションのWSAPlyerのチェックをONにします。

地図の設置

Assets\Mapbox\Prefabs内のMapをHierarchyにドラッグした後に、インスペクターでMapを表示します。
インスペクターでAbstract Mapコンポーネントに対し設定を行う事で地図の表示内容を制御します。
表示する地点や拡大率、衛星写真・地図の切り替え等はここで行います。
今回は下記内容で設定してください

GENERAL/Locationセクション

  • Latitude Longitude : 35.3611236, 138.7266352
  • Zoom : 14

GENERAL/Extent Optionsセクション

  • Extent Options : Range Around Center
  • West , North , East , South : 10
  • Initialization On Start : ON

GENERAL/Othersセクション

  • Unity Tile Size : 1

IMAGEセクション

  • Data Source : Mapbox Satellite
  • Use Retina : ON
  • Use Compression : OFF
  • Use Mip Map : ON

TERRAINセクション

  • Data Source : Mapbox Terrain
  • Elevation Layer Type : Terrain With Elevation
  • Add Collider : OFF
  • Exaggeration Factor : 1

また、MapオブジェクトのTransformのPosition:Yを-2程度にすると見やすくなるかもしれません。

上記設定後、UnityのPlayボタンを押してGameViewに富士山が表示されればOKです。
初期表示では視点位置により表示内容が分かり難い場合があります。
その場合はWASDキーと、右クリック+マウス操作で視点を変更して下さい。

初期表示
982f64504fde6077749ede580af59421.png

視点変更後
d869a3fc3fc0a93822228bd4936c3083.png

UnityでのBuild

File=>Build Settingsを選び、Build Settingsダイアログを表示します。

Add Open Sceneを押してビルド対象に現在のシーンを追加します。

Buildを押しビルド結果の出力ディレクトリを指定するとビルドが開始されます。
ビルド終了後、Console Viewでエラーが発生していないことを確認して下さい。

VisualStudio2019でのBuild

Unityでのビルドが終了後、ビルド結果の.slnファイルをVisualStudio2019で開き下記設定を行います。
- 構成をDebugからReleaseまたはMasterに変更する
- 構成をARM またはARM64に変更する
- 構成をデバイスに変更する
457615f638bd5a32dd118be42f62ebd0.png

ソリューションエクスプローラーからUniversal Windowsプロジェクトを選び、Nugetパッケージの管理を選びSQLite.Universalをインストールして下さい。

デバッグ => デバッグ無しで開始
を選ぶとビルド後に実機にデプロイされ、自動的に起動するはずです。

全てが正しく行われていれば、目の前に富士山が表れると思います。

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

【Windows版】Unityインストール方法 2020/08

概要

今回はWindowsのPCでUnityをインストールする際に、自分が経験した躓いたことや注意を踏まえた流れを書いたので是非参考にしてください。
この記事で、そのまま書いてある手順を踏めば、あら不思議。勝手に自分の手元にはUnityが扱えている状態になっていることでしょう。
所々の画像がMacになってたりしますが、やることは変わらないので気にせず進めてください。

まずは公式HPへ

Unityをいれるために、まずはインストーラーを用意します。
こちらのUnity公式HPに飛んでください。

UnityHubをいれよう

下の画像のようなページに飛ぶので、「Unity Hubをダウンロード」を押しましょう。
スクリーンショット 2020-05-17 20.44.31.png

そしたら、何かいろいろ出てくると思うので、はいとか同意する的なニュアンスの方をポチポチしていってください。
無事Unity Hubがダウンロードできたら早速起動してください。

すると環境設定の画面のライセンスという画面に飛びます。
もし画像の画面になっていなかったら右上の歯車マークを押すと行けると思います!
image.png

写真にはモザイクをかけてますが、入れたばかりの皆さんはまだ何も入ってないはずです。

UnityHubからUnityのアカウントを作成

次にライセンスを取得しましょう!!!…って言いたところですが、ライセンスを取得するためにはまずUnityにアカウントを作ってログインする必要があります。なので右下の『ログイン』というボタンからアカウントの作成を行いましょう。
下にボタンがない方は右上の歯車の隣にある人マークを押してアカウント作成まで進んでください。
そしたら下のような画像になるので『IDを作成』をポチります。
image.png

各項目のメールアドレス、パスワード、ユーザーネーム、フルネームを入力して利用規約のチェックボックスにチェックを入れてください。その下のチェックは付けなくていいです。(いわゆるメルマガを受け取るかどうかの話です。)
また、パスワードは頭文字が大文字なので注意してください!
image.png

無事入力が済んだら、『UnityIDアカウントを作成』をポチッ。

image.png
すると、登録しようとしてるメールアドレス宛てにUnityから本人確認のメールが届くと思うので、本人確認を行ってください。
無事確認が済んだら、早速ログインしてください。

image.png

ログインが完了すると、『新規ライセンスの認証』のボタンがアクティブ状態になりますので、そこをクリックしてライセンスを獲得しましょう。
選択肢はUnityPersonalを選びます。するとまた選択肢が増えるので、個人利用の場合は下を、どこか団体に所属している場合は上を選択します。
image.png

Unityを入れよう

ここからやっとUnityを入れることができます。
UnityHubの最初の画面に戻りましょう。そしたら、左の欄に『インストール』があるのでそこに移り、右上の『インストール』というボタンを押してください。
image.png

するとこんな画面が出ると思うので、バージョンを選びます。最新正式リリースの中の『Unity 2019.4.6f1 LTS版』というものを選びましょう。こちらはバグが少ないのでおすすめです。迷ったらこれを選べば間違い無いです。
また、この記事は現在2020年の8月に更新していますが、LTSのバージョンが複数あったり、画像とはバージョンの値が全く異なるものが出てくることがあります。
特に理由がなければ、最も新しいバージョンのLTS版を選びましょう!(画像の場合、真ん中の項目を選択。)
スクリーンショット 2020-08-01 20.59.32.png

ここで注意です!こんな画面に移ると思うので、モジュールというものを追加していきます。これが無いとビルドというものをするときに大変なことになってしまうのでしっかり確認しながら行ってください。(ぶっちゃけ後からでもできますが…)
WechatIMG12.jpeg

いろんなモジュールがありますが、用途に合わせて選んでください、AppStoreやGooglePlayにリリースするのであれば、AndroidとiOSにチェックを入れましょう!
そして、一番上にある『Microsoft Visual Studio Community 2017』は必ずチェックを入れてください!

そして次へを押すと、こんな画面が出ると思うので同意をして実行しましょう!早速Unityのダウンロードが始まります。
image.png

と同時にVisualStudioもダウンロードされます。途中でこんな画面が出ると思いますが、画像と同じものにチェックが入っていればOKです。(このダウンロードの時間の速さは人によって個人差があります。お菓子でも食べながら気長に待ちましょう。)
image.png

無事ダウンロードが終わるとこんな感じで表示れます。下のアイコンはiOSとAndroidのモジュールが入っている証拠になります。
image.png

早速Unityを起動してみよう!

ここまでお疲れ様でした。インストールの話はここまでですが、
一応おまけということでプロジェクトの作り方を軽く解説していきます。
下の画像のようにプロジェクトから『新規作成』を押します。
image.png

するとこんな画面になると思うので、作りたいゲームに合わせて2Dや3Dなどを選び、プロジェクト名を自分で入力します。保存先はわかりやすい場所やお好きな場所にどうぞ。
image.png

Visual Studio でエラー出たら。。。

Unity画面で上タブにある「Unity」の「Preference」をクリック
image.png

次に右タブの「External Tools」を選択して「External Script Editor」を『Visual Studio』にしましょう。
image.png

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

UnityのBoltをさわってみたので超簡単なUIサンプルを書いてみた【初心者向け】

はじめに

無料化&公式アセット化で最近話題のBoltをさっそくいじってみました!
まだいじって一日目ですが「あーBolt完全に理解した」という心意気で、とりあえず簡単なサンプルを記事にしてみようと思います。

ちなみに、がっつりしたチュートリアルはちゃんと公式にあって一通りやってみましたが「もっと気軽にBoltってみたいなー」と思ったのでこの記事を書いてみました。

『Unity入門チュートリアル 「玉転がし」(Roll-a-Ball) ビジュアルスクリプティング版』
https://learn.unity.com/project/bolt-roll-a-ball-tutorial?uv=2019.4

やりたいこと

・テキストとボタンがある
・ボタンを押すとテキストが「クリックしました」に変わる

前提情報

Unity2020.1.0f1
Bolt Version 1.4.12

新規プロジェクトの作成

2Dプロジェクトで「BoltTest01」という名前にしました。

Boltのインストール

公式のチュートリアルがあるので、こちらでお願いします。
https://learn.unity.com/tutorial/bolt-install?uv=2019.4&projectId=5f0e8e30edbc2a1549c23a2c

オブジェクトの配置と設定

以下のオブジェクトを配置、設定します。

1.Textオブジェクト「TextMessage」

ヒエラルキービューの「+」からUI>Textでテキストオブジェクトを配置し、名前を「TextMessage」とします。
Bolt_UI_Text.png
RectTransformを以下のように設定します。
Bolt_RectTransform.png
Textコンポーネントを以下のように設定します。
・Font Sizeを「28」
・Colorを白(FFFFFF)に設定
Bolt_Text.png

2.Buttonオブジェクト「ButtonMessage」

ヒエラルキービューの「+」からUI>Buttonでボタンオブジェクトを配置し、名前を「ButtonMessage」とします。
RectTransformを以下のように設定します。
Bolt_Button_RectTransform.png

3.Emptyオブジェクト「GameManager」

ヒエラルキービューの「+」からCreate Emptyでオブジェクトを配置し、名前を「GameManager」とします。
AddComponentで「Bolt>Flow Machine」を選択してFlow Machineコンポーネントを追加します。
AddComponent_Bolt.png Bolt_AddComponent_FlowMachine.png

NEWボタンを押し、ファイル名を「GameManager」にしてGameManager.assetを保存します。
Bolt_FlowMachine.png
Bolt_FlowMachine_Component.png
このコンポーネントにBoltスクリプトを作っていきます。

ButtonのOnClick()設定

ButtonMessageのButtonコンポーネントにあるOnClick()部分を設定します。
このボタンを押した際にGameManagerにOnClickイベントが飛ぶように設定します。
Bolt_OnClick_List_is_Empty.png
「+」を押すと下の画像のようになります。
Bolt_OnClick_02.png
この「None(Object)」にヒエラルキービューのGameManagerをドラッグ&ドロップして設定します。
Bolt_OnClick_03.png
「No Function」をクリックして「FlowMachine>TriggerUnityEvent」を選択します。
その下の入力欄に「OnClick」と入力します。
Bolt_OnClick_04.png
これで、このボタンをクリックするとGameManagerに向けてOnClickイベントを発火させることができます。

GameManagerに変数の設定

GameManagerにGameObject変数「TextMessage」を設定します。
GameManagerのVariableコンポーネントにある入力欄「(New Variable Name)」に「TextMessage」と入力します。
Bolt_GameManager_Variables.png
Typeを「GameObject」に設定します。
Value欄にヒエラルキービューの「TextMessage」をドラッグ&ドロップして設定します。
Bolt_GameManager_Variables02.png
Boltでテキストを変更する際に、この変数を使います。

Boltスクリプティング

いよいよBoltをいじります!
GameManagerのFlow Machineコンポーネントにある「Edit Graph」を押すと、Boltスクリプトの編集画面が開きます。
Bolt_FlowMachine_Component.png
初期状態で「Start」ユニットと「Update」ユニットがあります。
UE4のBlueprintではノードと言いますが、Boltではユニットと言うそうです。
Bolt_Script01.png
これはStart()とUpdate()と同じ働きをするもの、と直感的にわかると思います。
今回は、どちらのユニットも使いません。

1.UnityEventユニット

グリッド上の背景を右クリックすると下の画像のようなユニットリストが出てきます。
虫メガネの検索欄に「UnityEvent」と入力して「UnityEvent (in Events)」を選択すると、画面上にUnityEventユニットが配置されます。
Bolt_UnityEvent.png
配置されたユニットの入力欄に「OnClick」と設定します。これでOnClickイベント発火時にこのユニットから処理が始まります。
Bolt_UnityEvent_OnClick.png

2.GetVariableユニット

ヒエラルキービューにあるGameManagerのVariableコンポーネントで設定した変数「TextMessage」の「=」部分をBoltスクリプト編集画面にドラッグ&ドロップすると、自動的にGetVariableユニットが配置されます。
Bolt_GetVariable2.png

3.Textユニット

右クリックして検索欄に「Text.text」と入力して「Text.text(set)」ユニットを配置します。
Bolt_Text.text.png
オレンジの丸の入力欄に「クリックしました」と変更後のテキストを直接入力します。
Bolt_Text.text2.png

4.各ユニットの接続

3つのユニットを下の画像のように接続します。
Bolt_ユニットの接続.png
・UnityEventでOnClickを検知したらtext(set)を処理します
・text(set)を処理する際、入力値としてTextMessageを参照します
これでBoltスクリプトは完成です!実行してみましょう!

実行してみる

実行すると最初は画面に「New Text」が表示されています。
Bolt_実行画面1.png
「Button」をクリックすると、テキストが「クリックしました」に変わります。
Bolt_実行画面2.png

追記:こんな方法もありました

Bolt_OnButtonClick.png
「OnButtonClick」ユニットを使えば、OnClickイベントを設定しなくても同じことができました。
・GameManagerにGameObject変数「ButtonMassage」を宣言する
・ButtonMassageを入力値にOnButtonClickユニットをText.text(set)ユニットに接続する
後から追記しておいてなんですが、個人的にはこちらの方がわかりやすいかな、と…(お好みでどうぞ)

おわりに

主に自分用に書いた記事ですが、参考になれば幸いです。

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

【Unity】Macでバージョン管理ができる? SnailSVNを解説

これまでWindowsで利用していたリポジトリをMacで利用する必要が出てきたので
今回はMacでSubversion利用するためのソフトの紹介や設定方法
Windowsリポジトリをチェックアウトするところまでを解説したいと思います。

本文はこちらです。
https://tedenglish.site/how-to-manage-unity-project-svnmac/

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

Unity(C#)でWINAPIを使う

はじめに

Windowsのデスクトップアプリで枠(BORDER)とかタイトルバーを消したいというのはよくあることですが、WINAPIのドキュメントを引用しながら方法を教えてくれるページがそんなに無いようなので作ることにしました。つまりは下記あたりを見るんですね。
https://docs.microsoft.com/en-us/windows/win32/api/winuser/

そもそもQiitaでWINAPI(WindowsAPI)のタグがない? みんな使っていると思うんですが興味ないんですかね……。
だらだら説明を書くので、方法だけを知りたい人は別のページをググると良いです。

背景

「NICT インターネット時刻供給サービス」を利用するUnity(2018.4.24f1)製のデスクトップ時計を作成しました。
https://github.com/hakua-doublemoon/NetClock
なんでこんなものを作ったかといえば、私の富士通製タブレット型PCの時計が半年に一回ぐらい狂いまくる時期が来るからです。(RTCとかがおかしいのかな……)
ついでについなちゃんの声で時報してもらってます。

環境

項目名
OS Windows10
Unity 2018.4.24f1

内容

基本的な考え方

Windowsのアプリケーションですので、WindowsのAPIを使ってWindow(=アプリの表示)を変えることができるようです。
このWINAPIは基本的にC/C++のコードですが、DLLImportすることでC#でも使えます。DLLImportについては他の記事が詳しいのではと思います。(これに関してはもう調べてない。)

事前: Windowの取得

BorderやTitleの表示はWindowの属性として決まっています。なのでWindowを取得し、現在の属性を取得し、必要な部分だけ変更して設定しなおします。

Window(ハンドラー)の取得: FindWindow

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-findwindowa
とにかく第二引数に指定した名前のWindow(アプリ)を見つけたい場合は第一引数をNULLにするようです。

WindowController.cs
    [DllImport("user32.dll", EntryPoint = "FindWindow")]
    public static extern IntPtr FindWindow(System.String className, System.String windowName);

    string windowName = "net_clock"; // Unityで設定するアプリ名とそろえる。
    var window = FindWindow(null, windowName);

Window属性の取得: GetWindowLong

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowlonga
第一引数で指定したハンドラーのWindowの属性(情報)を取得します。ただしWindowの属性は何種類もあり、内部仕様があるのか知りませんが32bitで収まっていません。そこで第二引数で取得/変更したい属性のインデックスを指定します。
(すなわち、特定の属性を変更したい場合はそれに対応したIndexで取得、設定する必要があるということです)

WindowController.cs
    [DllImport("user32.dll")]
    public static extern int GetWindowLong(IntPtr hWnd, int nIndex);

ほかのAPIについてはそれぞれのユースケースに沿って述べていくことにします。

枠(BORDER)とタイトルバーを消す

BORDERの属性: WS_CAPTION

BORDERの属性があるのでそれを変更します。すなわち下記です:

https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles

WS_CAPTION 0x00C00000L
The window has a title bar (includes the WS_BORDER style).

WS_BORDER と WS_DLGFRAME を合わせてこれにしているコードが散見されますが、あまり意味がないようです。
上のGetWindowLongで書きましたが、Windowの属性は多数あり、取得・設定する際はそれに対応したIndexを使用する必要がありますが、どれなのかはGetWindowLongなどの説明に書いてあります。

GWL_STYLE -16
Retrieves the window styles.

したがって下記のようなコードで取得ができます。

WindowsController.cs
            const int GWL_STYLE = -16;
            int style = GetWindowLong(window, GWL_STYLE);

属性の変更: SetWindowLong

属性の変更は、現在の属性をGetWindowLongで取得し、SetWindowLongで一部変更したものを設定しなおします。

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowlonga

説明のとおり、引数はGetWindowLongと同様で、第三引数に設定する値を渡します。

WindowsController.cs
    [DllImport("user32.dll")]
    public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

            style &= ~WS_CAPTION;
            SetWindowLong(window, GWL_STYLE, style);

Window位置の変更: SetWindowPos

BORDERを消すと容易にWindowの位置を変えられなくなります。そこでSetWindowPosで適当な位置に移動させてやります。

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos

第二引数はWindowの順序の操作のようです。こだわりがなければHWND_TOPでよいでしょう。前面に出てきます。
第七引数は(ざっくり言うと)細かい動きを制御できるようです。特にこだわりがなければ0でよさそうです。

WindowsController.cs
    [DllImport("user32.dll", EntryPoint = "SetWindowPos")]
    private static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);

            const int HWND_TOP = 0;
            SetWindowPos(window, HWND_TOP, x, y, width, height, 0);

ここまででこういう感じなります。

image.png

背景を透明にする

せっかく枠を消したら背景も消したくなるでしょう。背景の削除は下記の手順で行います。

  1. Layered Window属性の設定
  2. 背景色へのAlpha値の適用

Layered Window属性: WS_EX_LAYERED

https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles

WS_EX_LAYERED 0x00080000
The window is a layered window. This style cannot be used if the window has a class style of either CS_OWNDC or CS_CLASSDC.

Layered Windowについて:
https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#layered-windows

visual effects for a window ... wishes to use alpha blending effects. The system automatically composes and repaints layered windows and the windows of underlying applications
Alpha Blending 効果を使用したいウィンドウの視覚効果。システムは自動的にLayered Windowと下にあるアプリケーションのウィンドウを構成し再描画します。

これだって感じですね。WS_EX_LAYERED属性もSetWindowLongで設定できますが、Indexは-20になります。

GWL_EXSTYLE -20
Sets a new extended window style.

WindowsController.cs
            const int GWL_EXSTYLE = -20;
            const int WS_EX_LAYERED = 0x80000;

            int style = GetWindowLong(window, GWL_EXSTYLE);
            SetWindowLong(window, GWL_EXSTYLE, style | WS_EX_LAYERED);

Layered Windowの属性の設定: SetLayeredWindowAttributes

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setlayeredwindowattributes
第三引数が難しくて正直に言って理解できていません。
単純に背景色を透明にするだけなら、第四引数をLWA_COLORKEYにすれば、第二引数(crKey)を透明な色としてくれます。つまりUnityの方で背景色を適当な色にして、そのカラーコードを第二引数に書けばいいです。

WindowsController.cs
    [DllImport("user32.dll", EntryPoint = "SetLayeredWindowAttributes")]
    private static extern Boolean SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);

            const int LWA_COLORKEY = 1;
            SetLayeredWindowAttributes(window, 0x00000000, 0, LWA_COLORKEY);

これでこうなります。

image.png


いじょ。
この記事を書きながら改めて各APIを調査し、コードを整理できました。やはり正しい知識が妥当なコードを書く鍵だと思います。

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

unity standard講座 50%OFFセール中です。

私の販売している unity3Dゲームstandard講座ですが 
50%OFFセールにしました。 

こちらはNOTE限定のセールになります。 

8月いっぱいまで 

https://note.com/zazizuzezo22334/n/n90340339a00c

約6時間の講座なので、ボリュームたっぷりの内容となっています。 

実際にゲームを作り、それをGoogleplayに登録してリリースするところまで 
解説しています。 

更にコードのPDFもプレゼントしています。
そのまま コピペして やることもできるので 初心者でも大丈夫ですよ。

PS 

新しいunity講座ですが、 多分来月にはリリースします。 

こちらの講座の姉妹講座になる感じですね。 

普段の情報はNOTEに書いているので 
よかったらフォローしてください。

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

Unityでゴエモンインパクト戦を作ってみる Part1

目的

  • 設計の速さやそれを実装する速さを測定したい
  • 良いアウトプットがしたい
  • 良いフィードバックがほしい
  • 人が楽しめるゲームを作りたい

動機

  • 就職活動の強みとなるように、Qiitaに定期的に投稿したい
  • 昔からの夢は3Dゲーム制作(ソシャゲ開発はしていますが、このままだと一生2Dソシャゲ制作になると思った
  • コンシューマ開発したい(いずれはUEに以降したい
  • とある知り合い実況者が復活したので自分も負けないよう行動する

結果

  • Profilerで見たところ250fps以上出ている
  • 割と思い通りに形に出来た
  • 合計18時間で作成できた(約:3日

2020/07/23 16:00 ~ 23:00
Terrain(仮背景いずれは出現する敵によって動かしたりする)
カメラ
弾管理
弾方向

2020/07/24 14:00 ~ 19:00
弾の衝突
敵の当たり判定
敵被弾後の無敵時間
敵の喰らいアニメーション
UI(仮)
MVPに変更

2020/07/24 15:00 ~ 21:00
左パンチ(ジャブ)
右パンチ(ストレート)
百烈パンチ

コード

※GitHubは準備中

MVPは端折っています

ゴエモンインパクト

GoemonImpact.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
using Random = UnityEngine.Random;

namespace Goemon.Impacts
{
    /// <summary> Goemon Impact </summary>
    public class GoemonImpact : MonoBehaviour
    {
        /// <summary> インパクトのカメラ 操縦席の目 </summary>
        [SerializeField] private Camera _camera = default;
        /// <summary> 弾やパンチを出す場所 </summary>
        [SerializeField] private Transform actionTransform = default;
        /// <summary> 左手 パンチはカメラTransformの下から出すようにした </summary>
        [SerializeField] private ImpactHandLeft impactHandLeft = default;
        /// <summary> 右手 </summary>
        [SerializeField] private ImpactHandRight impactHandRight = default;
        /// <summary> 100烈出す場所 </summary>
        [SerializeField] private Transform oneHandredPunchTransform = default;


        /// <summary> 計算用差分カメラ回転値 </summary>
        private Vector3 _presentCamRotation;
        /// <summary> カメラ回転制限のためのカメラ初期値 </summary>
        private Quaternion initCameraRotation;
        /// <summary> 初期化 </summary>
        private bool initialze = false;
        /// <summary> モデル </summary>
        private GoemonImpactBattleModel model = null;
        /// <summary> 小判生成リスト </summary>
        private List<ImpactKoban> kobanList = new List<ImpactKoban>();
        /// <summary> 100烈パンチ生成リスト </summary>
        private List<GameObject> oneHandredPunchList = new List<GameObject>();
        /// <summary> 100烈パンチのカウント </summary>
        private int punchCounter = 0;

        /// <summary> 初期化 </summary>
        public void Initialize(GoemonImpactBattleModel battleModel)
        {
            this.model = battleModel;
            initialze = true;
            initCameraRotation = _camera.transform.rotation;
            kobanList = new List<ImpactKoban>();
            oneHandredPunchList = new List<GameObject>();
            impactHandLeft.onEndPunch += LeftPunchOnEnd;
            impactHandRight.onEndPunch += RightPunchOnEnd;
            punchCounter = 0;
        }

        /// <summary>
        /// 更新
        /// SceneManagerでUpdateする
        /// </summary>
        /// <param name="deltaTime">進んだ時間</param>
        public void ManualUpdate(float deltaTime)
        {
            // 初期化済みなら処理開始
            if (initialze)
            {
                // 小判生成リストがあれば小判の位置によって削除処理
                if (kobanList.Count > 0)
                {
                    var copyKobanList = new List<ImpactKoban>(kobanList);
                    foreach (var koban in copyKobanList)
                    {
                        // 小判を消す範囲
                        if (koban.gameObject.transform.position.x > actionTransform.position.x + 50.0f ||
                            koban.gameObject.transform.position.x < actionTransform.position.x - 50.0f ||
                            koban.gameObject.transform.position.y > actionTransform.position.y + 50.0f ||
                            koban.gameObject.transform.position.y < actionTransform.position.y - 50.0f ||
                            koban.gameObject.transform.position.z > actionTransform.position.z + 50.0f) 
                        {
                            kobanList.Remove(koban);
                            Destroy(koban.gameObject);
                        }
                    }
                }

                // 百烈パンチのオブジェクトを削除
                if (oneHandredPunchList.Count > 0)
                {
                    var copyPunchList = new List<GameObject>(oneHandredPunchList);
                    foreach (var punch in copyPunchList)
                    {
                        // パンチオブジェクトのAnimationが終わっていたら削除
                        if (!punch.GetComponent<Animation>().IsPlaying("100PunchParts"))
                        {
                            oneHandredPunchList.Remove(punch);
                            Destroy(punch);
                        }
                    }
                }

                // 百烈パンチのオブジェクト生成
                if (model.IsOneHundredPunch.Value)
                {
                    var resource = Resources.Load<GameObject>("Impacts/100PunchParts");
                    var obj = Instantiate(resource, oneHandredPunchTransform);
                    // 百烈を出す場所をランダムで決める
                    float positionX = Random.Range(-2.0f, 2.0f);
                    float positionY = Random.Range(-2.0f, 2.0f);
                    obj.transform.localPosition = new Vector3(positionX, positionY, 0.0f);
                    oneHandredPunchList.Add(obj);
                    punchCounter++;
                    if (punchCounter == 100)
                    {
                        punchCounter = 0;
                        model.SetOneHandredPunch(false);
                    }
                }

                // パンチしてないならカメラが動かせる
                if (model.ImpactLeftPunchCoolTime.Value == 0 && model.ImpactRightPunchCoolTime.Value == 0)
                {
                    // カメラ回転計算
                    _presentCamRotation.x = _camera.transform.eulerAngles.x;
                    _presentCamRotation.y = _camera.transform.eulerAngles.y;

                    float rotateX = _presentCamRotation.x;
                    float rotateY = _presentCamRotation.y;

                    if (Input.GetKey(KeyCode.UpArrow))
                    {
                        _presentCamRotation.x = _presentCamRotation.x - ConstImapct.CAMERA_X_MOVE;
                    }

                    if (Input.GetKey(KeyCode.DownArrow))
                    {
                        _presentCamRotation.x = _presentCamRotation.x + ConstImapct.CAMERA_X_MOVE;
                    }

                    if (Input.GetKey(KeyCode.LeftArrow))
                    {
                        _presentCamRotation.y = _presentCamRotation.y - ConstImapct.CAMERA_Y_MOVE;
                    }

                    if (Input.GetKey(KeyCode.RightArrow))
                    {
                        _presentCamRotation.y = _presentCamRotation.y + ConstImapct.CAMERA_Y_MOVE;
                    }

                    rotateX = Mathf.Clamp(Mathf.DeltaAngle(initCameraRotation.eulerAngles.x, _presentCamRotation.x),
                        -ConstImapct.ROTATE_X_LIMIT, ConstImapct.ROTATE_X_LIMIT);
                    rotateY = Mathf.Clamp(Mathf.DeltaAngle(initCameraRotation.eulerAngles.y, _presentCamRotation.y),
                        -ConstImapct.ROTATE_Y_LIMIT, ConstImapct.ROTATE_Y_LIMIT);

                    // カメラが向いている方向をUpdate
                    _camera.transform.rotation = Quaternion.Euler(rotateX, rotateY, 0);
                }
            }
        }

        /// <summary> 小判追加 </summary>
        /// <param name="addKoban">追加小判数</param>
        public void KobanAdd(int addKoban)
        {
            model.SetKobanCount(model.KobanCount.Value + addKoban);
        }

        /// <summary> 後更新処理 </summary>
        private void LateUpdate()
        {
            // パンチしてないならパンチと小判が打てる
            if (!model.IsOneHundredPunch.Value && model.ImpactLeftPunchCoolTime.Value == 0.0f && model.ImpactRightPunchCoolTime.Value == 0.0f)
            {
                // Xボタンで弱パンチ攻撃
                if (Input.GetKeyDown(KeyCode.X))
                {
                   // 敵にあたったら手を戻すフレームを数フレーム早くする
                   impactHandLeft.Punch();
                   //model.SetImpactLeftPunchCoolTime(impactHandLeft.PunchCoolTime);
                }

                // Zボタンで強パンチ攻撃
                if (Input.GetKeyDown(KeyCode.Z))
                {
                    // 敵にあたったら手を戻すフレームを数フレーム早くする
                    impactHandRight.Punch();
                    //model.SetImpactRightPunchCoolTime(impactHandRight.PunchCoolTime);
                }

                // Cボタンで弾発射
                if (Input.GetKeyDown(KeyCode.C))
                {
                    if (model.KobanCount.Value > 0)
                    {
                        if (kobanList.Count < ConstImapct.KOBAN_MAX)
                        {
                            // 弾上限値を超えていなければ弾を生成(プール処理がいいかな)
                            var kobanResource = Resources.Load<ImpactKoban>("Impacts/ImpactKoban");
                            var obj = Instantiate<ImpactKoban>(kobanResource, actionTransform);
                            // ListからRemoveしたいので弾の削除処理をImpactで登録
                            obj.onDestroy += KobanOnDestroy;
                            // カメラの向いている方向に弾を飛ばす為、カメラのTransformを渡す
                            obj.SetMoveVector3(_camera.transform);
                            // 生成リストに追加
                            kobanList.Add(obj);
                            model.SetKobanCount(model.KobanCount.Value - 1);
                        }
                        else
                        {
                            Debug.Log("打ちすぎ " + ConstImapct.KOBAN_MAX + "個まで");
                        }
                    }
                    else
                    {
                        Debug.Log("小判ねーぞ");
                    }
                }

                if (Input.GetKeyDown(KeyCode.V))
                {
                    model.SetOneHandredPunch(true);
                }
            }
        }

        // アクションに入れる処理
        /// <summary> 小判を削除するための登録用関数 </summary>
        /// <param name="koban">小判クラス</param>
        private void KobanOnDestroy(ImpactKoban koban)
        {
            kobanList.Remove(koban);
            Destroy(koban.gameObject);
        }

        /// <summary> 左パンチ終わり </summary>
        private void LeftPunchOnEnd()
        {
            model.SetImpactLeftPunchCoolTime(0.0f);
        }

        /// <summary> 右パンチ終わり </summary>
        private void RightPunchOnEnd()
        {
            model.SetImpactRightPunchCoolTime(0.0f);
        }

        /// <summary> インパクト(GameObject)削除 </summary>
        private void OnDestroy()
        {
            initialze = false;
            _camera = null;
            actionTransform = null;
        }
    }
}

巨大ボス

GiantEnemy.cs
using UnityEngine;

namespace Goemon.Impacts
{
    /// <summary> 巨大ボスクラス </summary>
    public class GiantEnemy : MonoBehaviour
    {
        [SerializeField] private BoxCollider boxCollider = default;
        [SerializeField] private Animator animator = default;

        private GoemonImpactBattleModel model = null;

        /// <summary>
        /// 初期化
        /// </summary>
        /// <param name="model"></param>
        public void Initialize(GoemonImpactBattleModel model)
        {
            this.model = model;
        }

        /// <summary>
        /// 更新処理(当たり判定などの物理演算用)
        /// </summary>
        /// <param name="deltaTime">frameの時間</param>
        public void ManualFixedUpdate(float deltaTime)
        {
            var isInvincibilityTime = false;
            if (model.EnemyInvincibilityTime.Value > 0)
            {
                isInvincibilityTime = true;
                model.SetEnemyInvincibilityTime(model.EnemyInvincibilityTime.Value - deltaTime);
                if (model.EnemyInvincibilityTime.Value < 0)
                {
                    model.SetEnemyInvincibilityTime(0);
                }
            }

            Collider[] colliders = Physics.OverlapBox(boxCollider.transform.position, boxCollider.transform.localScale);
            foreach (var collider in colliders)
            {
                // 小判を食らった場合
                if (collider.GetComponent<ImpactKoban>() != null)
                {
                    var koban = collider.GetComponent<ImpactKoban>();

                    Debug.Log("ログ対決!これは「敵」だよ!!!!!");

                    koban.onDestroy.Invoke(koban);

                    if (!isInvincibilityTime)
                    {
                        Damage(koban.Damage, 1.0f);
                    }
                }

                // ジャブを食らった場合
                if (collider.GetComponentInParent<ImpactHandLeft>() != null)
                {
                    var impactLeftPunch = collider.GetComponentInParent<ImpactHandLeft>();

                    Debug.Log("ジャブヒット!");

                    impactLeftPunch.PunchEnd();

                    if (!isInvincibilityTime)
                    {
                        Damage(impactLeftPunch.Damage, 0.2f);
                    }
                }

                // ストレートを食らった場合
                if (collider.GetComponentInParent<ImpactHandRight>() != null)
                {
                    var impactRightPunch = collider.GetComponentInParent<ImpactHandRight>();

                    Debug.Log("ストレートヒット!");

                    impactRightPunch.PunchEnd();

                    if (!isInvincibilityTime)
                    {
                        Damage(impactRightPunch.Damage, 1.0f);
                    }
                }

                if (collider.GetComponentInParent<ImpactOneHundredPunchParts>() != null)
                {
                    var OneHundredPunch = collider.GetComponentInParent<ImpactOneHundredPunchParts>();

                    Debug.Log("百烈ヒット!");

                    OneHundredPunch.OnEnd();

                    if (!isInvincibilityTime)
                    {
                        Damage(OneHundredPunch.Damage, 0.01f);
                    }
                }
            }
        }

        /// <summary> ダメージを受けた際の処理 </summary>
        /// <param name="damage">ダメージ数</param>
        /// <param name="invincibilityTime">ダメージを受けた後の無敵時間</param>
        private void Damage(int damage, float invincibilityTime)
        {
            model.SetEnemyHP(model.EnemyHp.Value - damage);
            if (model.EnemyHp.Value <= 0)
            {
                // 死んだら移動させる
                transform.position = new Vector3(0,0,-100);
            }
            else
            {
                model.SetEnemyInvincibilityTime(invincibilityTime);
                animator.SetTrigger("Damage");
            }
        }
    }
}

課題

  • 当たり判定を敵だけに実装しているため、オブジェクト指向にしたい
    • たまに弾は当たったのに、敵が当たっていない判定になるのが面倒くさいので敵にまとめてしまった
    • これだと、インパクトの喰らい判定どうするのという問題が出てくるため、いずれ変更したい
  • 百烈パンチを生成でやっているので、予め用意したオブジェクトのONOFFでやりたい
  • コックピットをUIから3Dモデルにしたい
  • 弾を打ち出すところをカメラの下にしたい

次回

敵AIを実装してみる(ワクワク)

参考文献

インパクトの大きさ
https://dic.nicovideo.jp/b/a/%E3%82%B4%E3%82%A8%E3%83%A2%E3%83%B3%E3%82%A4%E3%83%B3%E3%83%91%E3%82%AF%E3%83%88/31-

3Dゲームで壁抜けしないコリジョンを実装してみた
https://qiita.com/HiShiG/items/a49ea295254318625c35

【Unity】”FPSみたいなカメラの実装”
https://qiita.com/Nekomasu/items/0a6aaf1f595cf05fbd0d

UnityのTerrainで大地を創る
https://qiita.com/yando/items/ef76c200bb50005170d5

進行方向に弾を発射する
https://qiita.com/Eureka/items/e0ca38903d7b56a29dcc

動画

https://www.youtube.com/watch?v=HeuuuUme3Jc

※がんばれゴエモンシリーズは Konami Degital Entertainment の登録商品です

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

プログラミング初心者がUnityでゲームを作ってみる その(4)VisualStudioのインストール

スクリプトを書くためには、エディタが必要なのですが、まだ何も入っていなかったので
VidualStudioをダウンロードします。。。

公式サイトの「Professional」無料試用版をダウンロードします。
image.png

インストールして、さて使おうとしましたが、、、
Unityでエディタを開こうとすると何度やってもメモ帳が出てきてしまいます。
調べたところ、インストール前に以下のワークロードの選択が必要なようでした。
ゲーム→Unityにチェックを入れて、もう一度「ダウンロードしながらインストール」します。

image.png

これで使えるようになりました!!
image.png

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