2017年5月15日 星期一

Unity內用C#尋找比對在Dictionary內Structure的屬性資料

Dictionary可以塞各種不同格式的資料,我比較常用的是放Structure,這樣可以把一個個體的各種屬性資料都放在一塊;但當要條件式比對或尋找裡面的某一個屬性資料時,就有點麻煩,因此在這裡做一個記錄。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public class XXX : MonoBehaviour {

    public struct playerDataStruct
    {
        public string nickName;
        public int age;
        public int score;
    }

    private Dictionary<string, playerDataStruct> playerDataDictionary = new Dictionary<string, playerDataStruct>();
    private playerDataStruct tempPlayerDataStruct = new playerDataStruct();

    void Start ()
    {
        tempPlayerDataStruct.nickName = "Warrior";
        tempPlayerDataStruct.age = 18;
        tempPlayerDataStruct.score = 1000;

        playerDataDictionary.Add("John", tempPlayerDataStruct);

        tempPlayerDataStruct.nickName = "Fighter";
        tempPlayerDataStruct.age = 20;
        tempPlayerDataStruct.score = 2000;

        playerDataDictionary.Add("Peter", tempPlayerDataStruct);

        //----------------------------------------------------------------------------------------------------------------

        if (playerDataDictionary.Values.Any(x => x.age >= 20) == true)
        {
            Debug.Log("本遊戲有大於20歲以上的玩家。");
        }

        Debug.Log("本遊戲內小於20歲的玩家第一人為:" + playerDataDictionary.Where(x => x.Value.age < 20).Select(x => x.Key).FirstOrDefault());
    }

    void Update ()
    {

    }
}
一般比較常見的情況有:
1.使用Value去逆向尋找Key。
2.判斷此Dictionary內是否包含該Key或是該Value。
3.直接尋找或比對Structure內的某一個屬性。

自己所知的對應方式:

依據條件把找到Key的全都取出
playerDataDictionary.Where(x => x.Value.age < 20).Select(x => x.Key)

取出找到的第一個Key
playerDataDictionary.Where(x => x.Value.age > 20).Select(x => x.Key).FirstOrDefault())
playerDataDictionary.FirstOrDefault(x => x.age >= 20).Key

判斷有無該Key包含在內
playerDataDictionary.ContainsKey("Peter")

判斷有無該Value的屬性包含在內
playerDataDictionary.Values.Any(x => x.age >= 20)

2017年5月9日 星期二

Unity內使用桌面的虛擬鍵盤

虛擬鍵盤一般用在觸控螢幕上,也就是觸控電視和手機平板等,當需要讓使用者輸入的時候就很方便,因此在這記錄一下呼叫虛擬鍵盤的做法,之前在網路上找到並在專案中拿來使用的。

首先是創造一個C#的Script,裡面內容直接如下,可以直接複製貼上:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

public class VirtualKeyboardController : MonoBehaviour {

    void Start ()
    {

    }

    void Update ()
    {

    }

    public class VirtualKeyboard
    {
        [DllImport("user32")]
        static extern IntPtr FindWindow(String sClassName, String sAppName);

        [DllImport("user32")]
        static extern bool PostMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);

        private static Process _onScreenKeyboardProcess = null;

        //Show the touch keyboard (tabtip.exe).
        public void ShowTouchKeyboard()
        {
            ExternalCall("C:\\Program Files\\Common Files\\Microsoft Shared\\ink\\tabtip.exe", null, false);
            //ExternalCall("TABTIP", null, false);
        }

        //Hide the touch keyboard (tabtip.exe).
        public void HideTouchKeyboard()
        {
            uint WM_SYSCOMMAND = 274;
            int SC_CLOSE = 61536;
            IntPtr ptr = FindWindow("IPTip_Main_Window", null);
            PostMessage(ptr, WM_SYSCOMMAND, SC_CLOSE, 0);
        }

        //Show the on screen keyboard (osk.exe).
        public void ShowOnScreenKeyboard()
        {
            //ExternalCall("C:\\Windows\\system32\\osk.exe", null, false);

            if (_onScreenKeyboardProcess == null || _onScreenKeyboardProcess.HasExited)
                _onScreenKeyboardProcess = ExternalCall("OSK", null, false);
        }

        // Hide the on screen keyboard (osk.exe).
        public void HideOnScreenKeyboard()
        {
            if (_onScreenKeyboardProcess != null && !_onScreenKeyboardProcess.HasExited)
                _onScreenKeyboardProcess.Kill();
        }

        /// <summary>
        /// Set size and location of the OSK.exe keyboard, via registry changes.  Messy, but only known method.
        /// </summary>
        /// <param name='rect'>
        /// Rect.
        /// </param>
        public void RepositionOnScreenKeyboard(Rect rect)
        {
            ExternalCall("REG", @"ADD HKCU\Software\Microsoft\Osk /v WindowLeft /t REG_DWORD /d " + (int)rect.x + " /f", true);
            ExternalCall("REG", @"ADD HKCU\Software\Microsoft\Osk /v WindowTop /t REG_DWORD /d " + (int)rect.y + " /f", true);
            ExternalCall("REG", @"ADD HKCU\Software\Microsoft\Osk /v WindowWidth /t REG_DWORD /d " + (int)rect.width + " /f", true);
            ExternalCall("REG", @"ADD HKCU\Software\Microsoft\Osk /v WindowHeight /t REG_DWORD /d " + (int)rect.height + " /f", true);
        }

        private static Process ExternalCall(string filename, string arguments, bool hideWindow)
        {
            ProcessStartInfo startInfo = new ProcessStartInfo();
            startInfo.FileName = filename;
            startInfo.Arguments = arguments;

            // if just command, we do not want to see the console displayed
            if (hideWindow)
            {
                startInfo.RedirectStandardOutput = true;
                startInfo.RedirectStandardError = true;
                startInfo.UseShellExecute = false;
                startInfo.CreateNoWindow = true;
            }

            Process process = new Process();
            process.StartInfo = startInfo;
            process.Start();

            return process;
        }
    }
}
然後在其他地方來做呼叫或關閉,當然虛擬鍵盤呼叫出來後,也可以直接按鍵盤右上角的X來關閉:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class XXX : MonoBehaviour {

    void Start ()
    {
        VirtualKeyboardController.VirtualKeyboard keyboard = new VirtualKeyboardController.VirtualKeyboard();

        keyboard.ShowOnScreenKeyboard();

        keyboard.HideOnScreenKeyboard();
    }

    void Update ()
    {

    }
}
這樣就可以呼叫和關閉螢幕虛擬鍵盤了,網路上有人說這招在Win 10後就沒辦法叫了,因為Microsoft不再放出虛擬鍵盤的控制權,我自己在Win 10使用是無礙啦......


2017年4月10日 星期一

Unity內使用C#來Email寄信

現在用程式發Email這種事已基本到是一種必備功能了,但我就是沒有去實做過,所以趁這次工作上有需要用,借助Google大神的力量,自己也成功地在Unity內手動寄出第一封Mail,趕快記錄一下寫法。

我是以自己的Gmail做寄信人,所以SmtpClient的部分是參考用Gmail時的寫法,用其他家的Mail時設定都會不一樣的樣子。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net;
using System.Net.Mail;

public class EmailController : MonoBehaviour {

    void Start ()
    {
    }

    void Update ()
    {
    }

    void EmailAction()
    {
        MailMessage mailMessage = new MailMessage ();
        SmtpClient smtpClient = new SmtpClient ("smtp.gmail.com");
        Attachment attachment = new Attachment (@"Assets/A.png");    //指定要夾帶的物件路徑

        mailMessage.From = new MailAddress ("寄信人信箱", "寄信人名字", System.Text.Encoding.UTF8);
        mailMessage.To.Add ("收信人信箱1");
        mailMessage.To.Add ("收信人信箱2");
        mailMessage.CC.Add ("收信人信箱3");
        mailMessage.Bcc.Add ("收信人信箱4");

        mailMessage.Subject = "送給你一張好圖片";
        mailMessage.Body = "這是我精挑細選、要送給你的一張好圖片。";
        mailMessage.SubjectEncoding = System.Text.Encoding.UTF8;
        mailMessage.BodyEncoding = System.Text.Encoding.UTF8;
        mailMessage.Attachments.Add (attachment);
        mailMessage.Priority = MailPriority.High;

        smtpClient.Port = 587;
        smtpClient.Credentials = new System.Net.NetworkCredential ("寄信人信箱", "寄信人信箱密碼") as ICredentialsByHost;
        smtpClient.EnableSsl = true;

        ServicePointManager.ServerCertificateValidationCallback = delegate(object sender,                   
                                        System.Security.Cryptography.X509Certificates.X509Certificate certificate,
                                        System.Security.Cryptography.X509Certificates.X509Chain chain,
                                        System.Net.Security.SslPolicyErrors sslPolicyErrors)
                                        {
                                             return true;
                                        };

        smtpClient.Send (mailMessage);

        Debug.Log ("寄信完成!!");
    }
}
在使用此方式時,作為SMTP的信箱帳戶必須要將安全防護性降低,才能順利寄出信件。

2017年4月5日 星期三

Unity內使用C#清除暫存記憶體的方式

最近在做專案時,忘記了加入釋放記憶體的機制,導致程式開了幾個小時就會當掉,故在此為粗心的自己記錄所知的釋放方式;這些方式有的是正規、有的是經驗、有的還是自己亂七八糟的觀念,所以不敢說絕對正確,主要是當需要釋放記憶體時,可用這些資訊作為起頭,直接使用或是以此去尋找更完整的資訊。

--------------------------------------------------------------------------------------------------------

首先是一般使用的情況:
GameObject x = GameObject.Find( "cube" );
x = null;
藉由設為null而釋放掉x。

--------------------------------------------------------------------------------------------------------

使用Unity內的WWW方式下載物件時:
WWW wwwObject;

wwwObject.Dispose();
wwwObject = null;
--------------------------------------------------------------------------------------------------------

使用Resources載入物件時:
Resources.UnloadUnusedAssets ();
--------------------------------------------------------------------------------------------------------

使用GC機制時,單純的方式:
GC.Collect();
依照現有的所有層代來逐一釋放:
for( int k = 0; k <= GC.MaxGeneration; k++ )
{
    GC.Collect (k);
    GC.WaitForPendingFinalizers ();
}
關於GC.Collect的說明:
https://msdn.microsoft.com/zh-tw/library/y46kxc5e(v=vs.85).aspx

關於GC.WaitForPendingFinalizers的說明:
https://msdn.microsoft.com/zh-tw/library/system.gc.waitforpendingfinalizers(v=vs.110).aspx

有一點在意的地方是,網路上有人說若是GC.Collect()使用頻繁過多,會造成效能過度消耗和程式變得奇怪運作。

--------------------------------------------------------------------------------------------------------

GC機制還有一種使用方式:
public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}
關於GC.SuppressFinalize的說明:
https://msdn.microsoft.com/zh-tw/library/ms182269.aspx

當在看說明時,會發現此方式會有正確和不正確的情況,但是看Mircosoft官方的範例會注意到差別只有在不正確為GC.SuppressFinalize(true);,而正確為GC.SuppressFinalize(this);這兩個地方。

--------------------------------------------------------------------------------------------------------

最後是Mircosoft官方對於自動記憶體管理的說明,簡單來講是設定變數為null和使用GC.Collect():
https://msdn.microsoft.com/zh-tw/library/aa691138(v=vs.71).aspx

2016年9月10日 星期六

Unity內使用WebClient和Stream方式來下載遠端檔案

一般Unity下載遠端檔案,大家都是用WWW的方式來做;但是WWW的結果是將下載的檔案直接匯入到Unity內,沒有辦法放在外面,要放在外面還得自己事後存出去,像是文檔或圖片等,那如果下載的是AssetBundle怎麼辦?光想就覺得很麻煩......

當然Unity有WWW.LoadFromCacheOrDownload這樣的方式,但我想要的是更直接地把下載檔案放在外部,之後自己比對是否需要再次下載覆蓋或可直接本地端匯入,因此就來尋找了一般的下載方式。

找到的有WebClient和Stream兩種方式。WebClient很簡單,可說是一口氣就能搞定的輕鬆方式,但是如果是在Unity內用的話,當下載檔案的量大時,程式就會卡在那沒法做其它事了,所以我個人會比較傾向使用Stream的方式,只是程式碼就沒那麼簡便了。

--------------------------------------------------------------------------------------------------------

WebClient:
using UnityEngine;
using System.Collections;
using System.Net;

public class WebClientController : MonoBehaviour {

    void Start ()
    {
        WebClient webClient = new WebClient();

        try
        {
            webClient.DownloadFile( "http://localhost:8080/xxx.assetbundle", "D:/xxx.assetbundle" );
        }
        catch( Exception ex )
        {
            Debug.Log( ex );
        }
    }
}
雖然我想一併使用下載過程的檢視,但是這部分的效果就是出不來,只好先記錄程式碼,日後有空再來試。
webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler( WebClient_DownloadProgressChanged );
webClient.DownloadDataCompleted += new DownloadDataCompletedEventHandler( WebClient_DownloadDataCompleted );

void WebClient_DownloadProgressChanged( object sender, DownloadProgressChangedEventArgs e )
{
    Debug.Log( "下載" + e.ProgressPercentage + "%" );
}

void WebClient_DownloadDataCompleted (object sender, DownloadDataCompletedEventArgs e)
{
    Debug.Log( "下載完成" );
}
--------------------------------------------------------------------------------------------------------

Stream:
using UnityEngine;
using System.Collections;
using System.Net;

public class WebClientController : MonoBehaviour {

    HttpWebRequest httpWebRequest;
    HttpWebResponse httpResponse;
    System.IO.Stream dataStream;
    byte[] buffer = new byte[8192];
    int size = 0;
    float downloadMemory = 0;
    bool completeCheck = false;

    void Start ()
    {
        try
        {
            httpWebRequest = (HttpWebRequest)WebRequest.Create( "http://localhost:8080/xxx.assetbundle" );
            httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
            dataStream = httpResponse.GetResponseStream();
            fileStream = new FileStream( "D:/xxx.assetbundle", FileMode.Create, FileAccess.Write );

            downloadMemory = 0;
        }
        catch( Exception e )
        {
            Debug.Log( e );
        }
    }

    void Update ()
    {
        if( completeCheck == false )
        {
            try
            {
                size = dataStream.Read( buffer, 0, buffer.Length );

                if( size > 0 )
                {
                    fileStream.Write( buffer, 0, size );
                    downloadMemory += size;
                    Debug.Log( "正下載 " + ( downloadMemory / 1048576 ).ToString( "f1" ) + "MB / " +
                                ((float)httpResponse.ContentLength / 1048576 ).ToString( "f1" ) + "MB == " +
                                (( downloadMemory / (float)httpResponse.ContentLength ) * 100 ).ToString( "f1" ) + "%" );
                }
                else
                {
                    fileStream.Close();
                    httpResponse.Close();
                    buffer = new byte[8192];

                    Debug.Log( "下載完成!" );

                    completeCheck = true;
                }
            }
            catch( Exception ex )
            {
                Debug.Log( ex );
            }
        }
    }
}
--------------------------------------------------------------------------------------------------------

Unity內用WWW載入AssetBundle

大家都知道,AssetBundle是Unity用來打包資源的方式。
大家也知道,AssetBundle對於Script的不方便,一定要在Project內事先放入相同的Script,才能讓AssetBundle上的Script相對應到。

但這都不是重點,本篇的重點是將AssetBundle匯入到Unity程式內使用。
using UnityEngine;
using System.Collections;

public class AssetBundleLoadController : MonoBehaviour {

    void Start ()
    {
        StartCoroutine( LoadAssetBundle() );
    }

    private IEnumerator LocalLoad()
    {
        WWW fileBundle = new WWW( "file://D:/xxx.assetbundle" );
        yield return fileBundle;

        GameObject tempObject = null;
        yield return tempObject = Instantiate( fileBundle.assetBundle.mainAsset ) as GameObject;
        tempObject.name = tempObject.name.Replace( "(Clone)", string.Empty );

        fileBundle.assetBundle.Unload( false );
        Resources.UnloadUnusedAssets();
    }
}
這樣就能使用AssetBundle的內容物了。

2016年9月2日 星期五

開發時,Java的版本設定......

在使用Eclipse、Tomcat等時,很怕Java的JDK、JRE等版本不相對,或是出現"Unsupported major.minor version 52.0"之類的訊息,這時就得去挖所有相關的環境設定,我也被這情況弄過幾次,所以在此記錄一下。

確認執行Tomcat的電腦使用的Java版本,通常可以從控制台的解除安裝程式來看,但是也許你會安裝好幾個版本的Java版本在裡面,所以用CMD來看比較妥當,這樣才能確認電腦到底是使用哪個版本的Java,分別輸入"java -version"和"javac -version",就可以看到結果:


再來是Eclipse內,一般大家都知道的是Project內的Library引用設定,在Project上用滑鼠右鍵開起選單,選擇"Properties":


然後在左邊選擇"Java Build Path",來觀看Libraries內的JRE版本:


但其實還有其它地方也和Java版本有關係,一個是"Java Complier"的"Compiler compliance level":


另一個是"Project Facets"的設定,這些都和Java版本有關:


以後應該不會再被Java版本這種設定給弄到了。