2011年9月22日 星期四

UNITY3D - Javascript繼承

善用繼承可以讓遊戲有更多的變化且減少無意義的COPY&PASTE(?)

U3D的JS繼承用法如下

class Parent {
function Start () {
print("parent's Start");
A();
}

function A () {
print("parent's A");
B();
}

function B () {
print("parent's B");
C();
}


function C () {
print("parent's C");
}

}


class Children extends Parent {

function A () {
print("Children's A");
B();
}


function C () {
super.C();
print("Children's C");
}
}

如果在GameObject上附加Children時會發現此GameObject的執行結果為
parent's Start
Children's A
parent's B
parent's C
Children's C

在寫怪物AI時用繼承的方式會輕鬆很多,底層先寫好每隻怪都會做的動作(移動、攻擊等等),繼承後就可以按不同需求附加個別處理如(特殊攻擊)。

2011年9月15日 星期四

UNITY3D - 圖層觀念

U3D裡面有2D物件也有3D物件,因此要注意物件之間是否會造成顯示異常。

2D世界其實跟3D世界不屬於同一個世界...
可以把2D世界當成是貼在鏡頭前,所以2D物件一定會在介面上擋到3D物件。
而2D世界中的圖片會依據transform.position.z來當作圖層設定,z軸越大表示圖越上面。

例:
A圖片z軸為0
B圖片z軸為1
C圖片z軸為2

呈現出來就會變成
┌───────┐
│        ┌───────┐
│ A    │  ┌───────┐
│        │ B    │        │
└───│  │ C      │
           └───│                   │
                      └───────┘

要注意的是在OnGUI底下畫出來的東西圖層是最高的,所以2D物件也會被OnGUI的東西蓋到喔。

2011年8月31日 星期三

UNITY3D - 2D圖動畫工具


遊戲中常常會用到一些2D連續圖來做UI特效,但是UNITY 3D本身不支援GIF格式的檔案,基於這些需求而寫了以下這隻程式。
目前的功能有:
1.播放一次
2.循環播放
3.淡入
4.淡出
5.放大
6.縮小
7.上升
8.下降

變數功能:
aniplayers :動畫詳細設定
size:有幾段動畫要播放
aniTexture:放連續圖的位置,需依序放入。
playTime:這段動畫的播放時間長度
frequency:時間內播放次數(淡入、淡出、放大、縮小、上升、下降無作用)
playMode:播放模式(播放一次、循環播放、淡入、淡出、放大、縮小、上升、下降)
waitTime:播放後等待時間(循環播放的作用為多久循環一次)
offset:偏移量,放大、縮小、上升、下降的依據
sync:同步,勾選後會跟下一段動畫同步播放
destroy:動畫播放完是否銷毀此GameObject

注意事項:
若勾選destroy時,至少要有一段動畫不能勾選sync

=======================================================================

enum AniPlayMode {PlayOnce,PlayCycle,FadeIn,FadeOut,Magnify,Minify,Up,Down};
class ANIPLAYER {
var aniTexture : Texture[];
@HideInInspector var startTime : float;
var playTime : float;
var frequency : int;
var playMode : AniPlayMode;
var waitTime : float;
var offset : float;
var sync : boolean;
}

var aniplayers : ANIPLAYER[];
var destroy : boolean;

@script RequireComponent (GUITexture)

function OnEnable () {
Play();
}

function Play () {
for(var aniplayer : ANIPLAYER in aniplayers)
{
if(aniplayer.playMode == AniPlayMode.PlayOnce)
{
if(aniplayer.sync)
PlayOnce(aniplayer);
else
yield PlayOnce(aniplayer);
}
else if(aniplayer.playMode == AniPlayMode.PlayCycle)
{
if(aniplayer.sync)
PlayCycle(aniplayer);
else
yield PlayCycle(aniplayer);
}
else if(aniplayer.playMode == AniPlayMode.FadeIn)
{
if(aniplayer.sync)
FadeIn(aniplayer);
else
yield FadeIn(aniplayer);
}
else if(aniplayer.playMode == AniPlayMode.FadeOut)
{
if(aniplayer.sync)
FadeOut(aniplayer);
else
yield FadeOut(aniplayer);
}
else if(aniplayer.playMode == AniPlayMode.Magnify)
{
if(aniplayer.sync)
Magnify(aniplayer);
else
yield Magnify(aniplayer);
}
else if(aniplayer.playMode == AniPlayMode.Minify)
{
if(aniplayer.sync)
Minify(aniplayer);
else
yield Minify(aniplayer);
}
else if(aniplayer.playMode == AniPlayMode.Up)
{
if(aniplayer.sync)
Up(aniplayer);
else
yield Up(aniplayer);
}
else if(aniplayer.playMode == AniPlayMode.Down)
{
if(aniplayer.sync)
Down(aniplayer);
else
yield Down(aniplayer);
}
}
if(destroy)
{
Destroy(gameObject);
}
}

function PlayOnce (aniplayer : ANIPLAYER) {
var index : int;
aniplayer.startTime = 0.0f;
while(aniplayer.startTime <= aniplayer.playTime)
{
index = (aniplayer.startTime * (aniplayer.aniTexture.Length / aniplayer.playTime) * aniplayer.frequency) % aniplayer.aniTexture.Length;
guiTexture.texture = aniplayer.aniTexture[index];
aniplayer.startTime = aniplayer.startTime + Time.deltaTime;
yield;
}
yield WaitForSeconds(aniplayer.waitTime);
}

function PlayCycle (aniplayer : ANIPLAYER) {
var index : int;
aniplayer.startTime = 0.0f;
while(true)
{
while(aniplayer.startTime <= aniplayer.playTime)
{
index = (aniplayer.startTime * (aniplayer.aniTexture.Length / aniplayer.playTime) * aniplayer.frequency) % aniplayer.aniTexture.Length;
guiTexture.texture = aniplayer.aniTexture[index];
aniplayer.startTime = aniplayer.startTime + Time.deltaTime;
yield;
}
yield WaitForSeconds(aniplayer.waitTime);
aniplayer.startTime = 0.0f;
}
}

function FadeIn (aniplayer : ANIPLAYER) {
aniplayer.startTime = 0.0f;
guiTexture.color.a = 0;
while(aniplayer.startTime <= aniplayer.playTime)
{
aniplayer.startTime = aniplayer.startTime + Time.deltaTime;
guiTexture.color.a = guiTexture.color.a + (Time.deltaTime / aniplayer.playTime);
yield;
}
yield WaitForSeconds(aniplayer.waitTime);
}

function FadeOut (aniplayer : ANIPLAYER) {
aniplayer.startTime = 0.0f;
while(aniplayer.startTime <= aniplayer.playTime)
{
aniplayer.startTime = aniplayer.startTime + Time.deltaTime;
guiTexture.color.a = guiTexture.color.a - (Time.deltaTime / aniplayer.playTime);
yield;
}
yield WaitForSeconds(aniplayer.waitTime);
}

function Magnify (aniplayer : ANIPLAYER) {
var scale : float = guiTexture.pixelInset.width / guiTexture.pixelInset.height;
aniplayer.startTime = 0.0f;
while(aniplayer.startTime <= aniplayer.playTime)
{
aniplayer.startTime = aniplayer.startTime + Time.deltaTime;
guiTexture.pixelInset.width = guiTexture.pixelInset.width + Time.deltaTime * aniplayer.offset * scale;
guiTexture.pixelInset.height = guiTexture.pixelInset.height + Time.deltaTime * aniplayer.offset;
guiTexture.pixelInset.x = guiTexture.pixelInset.x - Time.deltaTime * aniplayer.offset * scale / 2;
guiTexture.pixelInset.y = guiTexture.pixelInset.y - Time.deltaTime * aniplayer.offset / 2;
yield;
}
yield WaitForSeconds(aniplayer.waitTime);
}

function Minify (aniplayer : ANIPLAYER) {
var scale : float = guiTexture.pixelInset.width / guiTexture.pixelInset.height;
aniplayer.startTime = 0.0f;
while(aniplayer.startTime <= aniplayer.playTime)
{
aniplayer.startTime = aniplayer.startTime + Time.deltaTime;
guiTexture.pixelInset.width = guiTexture.pixelInset.width - Time.deltaTime * aniplayer.offset * scale;
guiTexture.pixelInset.height = guiTexture.pixelInset.height - Time.deltaTime * aniplayer.offset;
guiTexture.pixelInset.x = guiTexture.pixelInset.x + Time.deltaTime * aniplayer.offset * scale / 2;
guiTexture.pixelInset.y = guiTexture.pixelInset.y + Time.deltaTime * aniplayer.offset / 2;
yield;
}
yield WaitForSeconds(aniplayer.waitTime);
}

function Up (aniplayer : ANIPLAYER) {
aniplayer.startTime = 0.0f;
while(aniplayer.startTime <= aniplayer.playTime)
{
aniplayer.startTime = aniplayer.startTime + Time.deltaTime;
guiTexture.pixelInset.y = guiTexture.pixelInset.y + Time.deltaTime * aniplayer.offset;
yield;
}
yield WaitForSeconds(aniplayer.waitTime);
}

function Down (aniplayer : ANIPLAYER) {
aniplayer.startTime = 0.0f;
while(aniplayer.startTime <= aniplayer.playTime)
{
aniplayer.startTime = aniplayer.startTime + Time.deltaTime;
guiTexture.pixelInset.y = guiTexture.pixelInset.y - Time.deltaTime * aniplayer.offset;
yield;
}
yield WaitForSeconds(aniplayer.waitTime);
}

2011年7月24日 星期日

蚊語錄

記錄自己說過,且還不錯的觀念

經典遊戲之所以經典是因為它的獨特性
當延續好幾代都差不多或者看起來感覺都一樣,那它就爛掉了

2011年7月20日 星期三

UNITY3D - 圖片淡入淡出

圖片淡入淡出其實跟GameObject逐漸透明消失的做法類似
只要改圖片本身的透明度就好了
要記得一點是顏色跟透明度的值是(x/256)的float值,不要以為直接塞1~256的值就可以...

圖片淡入
=========================================================================
function Start ()
{
guiTexture.color.a = 0; //讓圖片一開始為透明
}

function Update ()
{
if(guiTexture.color.a < 1)
{
guiTexture.color.a = guiTexture.color.a + Time.deltaTime; //讓圖片隨時間變不透明
}
}


=========================================================================

畫面淡出
=========================================================================

function Start ()
{
guiTexture.color.a = 1; //讓圖片一開始為不透明
}

function Update ()
{
if(guiTexture.color.a < 1) 
{
guiTexture.color.a = guiTexture.color.a - Time.deltaTime; //讓圖片隨時間變透明
}
}

=========================================================================

2011年7月13日 星期三

UNITY3D - 連續貼圖

U3D不支援顯示gif檔案,但是我們可以利用程式來將一連串的靜態圖片顯示得跟動態圖片無異。
首先建立一個empty object然後加入下面component script。

=======================================================================
var aTexture : Texture[]; //靜態圖片組
var framePerSecond : int; //每秒播放張數
var x : int; //x軸位置(以pixel為單位)
var y : int; //y軸位置(以pixel為單位)
private var startTime : float;
function Start () {
    startTime = Time.time; //記錄開始時間,確保圖從第一張開始放。
}
function OnGUI () {
    var index : int = ((Time.time - startTime) * framePerSecond) % aTexture.Length;
    GUI.DrawTexture(Rect(x, y, aTexture[index].width, aTexture[index].height), aTexture[index], ScaleMode.StretchToFill, true, 0);
}

=======================================================================
加入component後將靜態圖按順序設給aTexture以及設定framePerSecond與XY軸就可以看到在設好的(x,y)上顯示靜態圖片組成的動畫了。

手機測試

測試手機版blogger效果...上課太無聊,如果效果不錯之後就上課寫教學好了。

2011年7月3日 星期日

UNITY3D - GameObject逐漸透明消失

private var startTime : float;
private var alpha : float = 255.0f;

function Start () {
startTime = Time.time;
}

function Update () {
    if((Time.time - startTime) > 5)
    {
    var render = gameObject.GetComponentInChildren(Renderer);
    alpha = alpha - 10.0f;
    render.material.color.a = alpha / 255.0f;
    if(render.material.color.a <= 0)
    Destroy(gameObject);
    }
}
用處:用在怪被打死後逐漸消失。

以上程式功能為物件產生出來後經過5秒,然後將render的透明度逐漸遞減,當透明度低於0則清除此物件。注:Material的Shader必須用允許透明的Shader。

2011年6月30日 星期四

UNITY3D - 畫面震動效果

遊戲設計其實跟電影拍攝的概念很類似,構成電影最基本的元素是導演、攝影師、劇本、演員,而構成遊戲的最基本元素是鏡頭(導演、攝影師)、角色與NPC設定(劇本、演員)。

角色與NPC設定屬於控制與AI的設計,有賴於程設的構思去完善。相信很多人都會忽略鏡頭的重要性,但其實鏡頭就如同導演一般,要負責的東西多又繁雜且細膩。簡單來說...畫面上看到的東西都是鏡頭負責的(好像是廢話),舉凡玩家視角、遊戲介面、選單、畫面特效(震動、畫面變色)等等都是用鏡頭來呈現。

畫面震動的效果其實說穿了也沒什麼...不就是鏡頭晃一下就好了?以下是javascript code,附加到鏡頭上試試效果吧。
var shakePower : float = 2.0f; //設定震動的幅度
var shakeDelay : float = 0.05f; //設定震動的間隔時間

function UpDownShake () //上下震動
{
transform.Translate(Vector3.up * shakePower);
yield WaitForSeconds(shakeDelay);
transform.Translate(Vector3.up * -shakePower);
}

function LeftRightShake () //左右震動

{
transform.Translate(Vector3.right * -shakePower);
yield WaitForSeconds(shakeDelay);
transform.Translate(Vector3.right * shakePower);
}

function ForwardBackShake () //前後震動
{
transform.Translate(Vector3.forward * shakePower);
yield WaitForSeconds(shakeDelay);
transform.Translate(Vector3.forward * -shakePower);
}

function XRotateShake () //X軸晃動
{
transform.Rotate(Vector3.right * shakeAngle);
yield WaitForSeconds(shakeDelay);
transform.Rotate(Vector3.right * -shakeAngle);
}

function YRotateShake () //Y軸晃動
{
transform.Rotate(Vector3.up * shakeAngle);
yield WaitForSeconds(shakeDelay);
transform.Rotate(Vector3.up * -shakeAngle);
}

function ZRotateShake () //Z軸晃動
{
transform.Rotate(Vector3.foward * shakeAngle);
yield WaitForSeconds(shakeDelay);
transform.Rotate(Vector3.foward * -shakeAngle);
}

function Update ()
{
if(Input.GetKeyDown(KeyCode.Keypad1)) { StartCoroutine("UpDownShake"); }
if(Input.GetKeyDown(KeyCode.Keypad2)) { StartCoroutine("LeftRightShake"); }
if(Input.GetKeyDown(KeyCode.Keypad3)) { StartCoroutine("ForwardBackShake"); }
if(Input.GetKeyDown(KeyCode.Keypad4)) { StartCoroutine("XRotateShake"); }
if(Input.GetKeyDown(KeyCode.Keypad5)) { StartCoroutine("YRotateShake"); }
if(Input.GetKeyDown(KeyCode.Keypad6)) { StartCoroutine("YRotateShake"); }
}

UNITY3D - 控制輸入設定

輸入控制是遊戲最基本的部分,那U3D要怎麼設定輸入控制呢?
首先我們開啟Edit->Project Settings->Input就可以看到Inspector視窗呈現類似的畫面
Axes底下就是各按鍵設定,如果有想要增加按鍵數量的話可以加大Size就會看到下面的按鍵數量增加。
每個按鍵下面都有以下屬性:
Name:用來定義按鍵的名稱,關係到程式呼叫時的對應。
Descriptive Name:玩家在更改輸入控制畫面看到的名字。
Descriptive Negative Name:玩家在更改輸入控制畫面看到的名字(通常為方向控制)。
Negative Button:此輸入所對應的按鍵(通常為方向控制才有用到),程式中取到的值為-1~0。
Positive Button:此輸入所對應的按鍵,程式中取到的值為0~1。
Alt Negative Button:此輸入所對應的替代按鍵(通常為方向控制才有用到),程式中取到的值為-1~0。
Alt Positive Button:此輸入所對應的替代按鍵,程式中取到的值為0~1。
Gravity:設置按鍵恢復的速度。
Dead:調節類比訊號的敏感度(如類比搖桿)。
Sensitivity:調節數位訊號的敏感度。
Snap:勾選後,按反向鍵時會先將數值歸零。
Invert:勾選後,對應的按鍵會相反(通常用於飛行遊戲)。
Type:按鍵的輸入型態(若要改用遊戲手把記得更改此項)。
Axis:此按鍵的軸向。
Joy Num:設定為第幾個手把(若是用遊戲手把的話)。


程式部分:

Input.GetKey (name : string) : bool

用於檢查某特定按鍵是否被按,跟Input的設定無關,會被連續偵測到。

Input.GetKeyDown (name : string) : bool


用於檢查某特定按鍵是否被按下,跟Input的設定無關,只有在被按下的時候才觸發,按鍵不放開重按則無效。

Input.GetKeyUp (name : string) : bool

用於檢查某特定按鍵是否被按下,跟Input的設定無關,只有在被放開的時候才觸發。

Input.GetButton (buttonName : string) : bool

一樣是用來檢查按鍵是否被按,buttonName是Input所設定的名字,會被連續偵測到。

Input.GetButtonDown (buttonName : string) : bool

一樣是用來檢查按鍵是否被按,buttonName是Input所設定的名字,只有在被按下的時候才觸發,按鍵不放開重按則無效。

Input.GetButtonUp (buttonName : string) : bool

一樣是用來檢查按鍵是否被按,buttonName是Input所設定的名字,只有在被放開的時候才觸發。

Input.GetAxis (axisName : string) : float

通常用於方向鍵偵測,回傳值為-1~1,跟Input的設定息息相關。

還有其他的函式可以用,但相信這些就很夠了...

2011年4月1日 星期五

程式設計-從C開始之99乘法表

  1. #include <stdio.h>
  2. main()
  3. {
  4.     int i = 1;
  5.     int j;
  6.     for(j = 1;i <= 9;j++)
  7.     {
  8.         if(j <= 9)
  9.         {
  10.             printf("%d*%d=%2d  ", i, j, i * j);
  11.         }
  12.         else
  13.         {
  14.             printf("\n");
  15.             i = i + 1;
  16.             j = 0;
  17.         }
  18.     }
  19.     system("pause");
  20. }

這段程式碼可以用Dev-C來編譯,編譯出來的結果是一個9*9的乘法表。很簡單的程式但我認為是很好入門的程式。

語句解說:

  1. 第1行是載入C程式的標頭檔,但我們今天先不解釋,照打就好。
  2. 第2行的main()是C程式必寫,用來通知系統執行程式時要從這邊開始,有更複雜的用法以後有機會再提。
  3. 第3行的{跟第20行的}是一對,表示第4行到第19行的內容都是屬於main()。
  4. 第4行int i是指宣告一個變數i為int整數型態並且設為1,型態宣告以後再另開篇幅說明,這裏就先知道i只能用來表示數字,後面的;符號是用來表示一句敘述句結束就跟中文的。一樣。
  5. 第5行跟第4行不一樣在於宣告時沒有設定j是多少,就像過年拿紅包(j)你不知道裡面有多少錢(有可能有一百萬也有可能只有一塊錢),跟直接給你錢(i)的差別。
  6. 第6行的for是C程式的重複結構,以for(A;B;C)為例:
    • A為一般敘述如j = 1
    • B為條件判斷,判斷的結果為真則重複做{}裡面的內容動作,結果為否則離開重複結構,上面程式的B為i <= 9表示當i小於或等於9的時候進入重複結構否則離開重複結構。
    • C也是一般敘述,跟A不同的是A只有在for開始時執行一次,C則是每執行完結構中內容就會執行一次,上面程式的C為j++等同於j = j + 1。
  7. 第7行跟第18行的{}是for的架構標示,表示第8行到第17行是for的重複架構內容。
  8. 第8行的if是C程式的條件結構,根據小括號內容判斷真假,若為真則執行結構中動作否則不執行,上面程式則是判斷j是否小於等於9。
  9. 第9行跟第11行的{}是if的架構標示。
  10. 第10行的printf("%d*%d=%2d  ", i, j, i * j);是列印一串字在螢幕上,列印的內容在兩個"引號裡面,在這裡我們要印出變數i、*符號、變數j、=符號、變數i乘變數j的結果,而不是印出i*j=i*j這樣的一串字,於是寫成("%d*%d=%2d  ", i, j, i * j),%d跟%2d的意思都是顯示"引號結束後以逗號隔開的變數值(還有%s、%f等等可以使用,有機會再提),第一個%d就會呈現變數i的值,第二個%d就會呈現變數j的值,最後的%2d則是呈現變數i乘變數j的值,%2d的2表示如果顯示的數字小於二位數就會在前面補空格,可以嘗試更改2為其它數字試試,"引號後面的逗號區隔的可以是單一變數也可以是一句敘述,想呈現越多的變數值就用越多的逗號隔開,相反的只要顯示"引號內容的話就不用逗號了。
  11. 第12行的else必須搭配if來使用,舉個例子來說:如果肚子餓則吃飯否則睡覺,可以寫成:
    if(肚子餓)
    {
        吃飯
    }
    else
    {
        睡覺
    }
    else就是處理「否則」要做的事情。
  12. 第14行的printf("\n");是將\n顯示到螢幕上,但因為電腦會將\n當成斷行符號,所以這只是將顯示的部分斷行而已。
  13. 第15行的i = i + 1;跟16行的j = 0;只是一般的敘述句。
  14. 第19行的system("pause");是將目前的程式及畫面停住,避免程式執行太快沒看清楚就結束被windows關掉,在Mac或Linux就不需要這句。

邏輯解說:
  • 因為printf只能做橫向列印,所以我們必須先思考怎麼印出1*1=1  1*2=2  1*3=3....1*9=9
  • 要印出這麼一行只需要用:
    for(j = 1;j <= 9;j++)
    {
        printf("1*%d=%2d  ", j, 1 * j);
    }
  • 但仔細觀察後可以發現我們必須執行上面類似的動作9次才能順利印出完整的99乘法表,而這次數決定在於i,所以我們把上面的for修改成這樣:
    for(j = 1;i <= 9;j++)
    {
        printf("%d*%d=%2d  ", i, j, i * j);
    }
  • 但這樣就形成了無窮迴圈,因為在迴圈內i永遠不會變動也就永遠不會大於9,於是我們再增加if條件架構來處理讓j大於9時就斷行且將i加1於將j歸零(因為j會因為迴圈結束時會再執行j++):
    for(j = 1;i <= 9;j++)
    {
        if(j <= 9)
        {
            printf("%d*%d=%2d  ", i, j, i * j);
        }
        else
        {
            printf("\n");
            i = i + 1;
            j = 0;
        }
    }
  • 如此99乘法表就大致完成了。