很久一段時間都沒有新文章了, 因為前一陣子一直在忙著做iPhone的遊戲, 引擎也就很少進展了. 但是由於人力, 以及時間等等因素, 案子進展的的很不順利. 接著六月開始我又得一個人帶小孩, 上下學接送, 洗澡樣樣得自己來所以忙到連iPhone遊戲的案子都沒時間作了. 於是只能趁小孩睡覺後的一兩個小時, 試試引擎的script system. 其實引擎的script系統早就玩成了, 之前是使用Lua為主, 但是從來沒用script來做過任何遊戲demo, 所以這一兩個小時的時間就正好用來試試script系統以及補強一些功能. 
在修改了一些功能後, LnxEngine可以僅依靠Lua script完成整個遊戲的介面以及遊戲gameplay. 這次試作了一個橫向卷軸遊戲, 下面是遊戲的影片, 背景都是2D的sprite, 只有人物是3D的. 之後會繼續作作其他類型遊戲來試試看script system的能力.
之後上網看了一下Mono, 才發現Mono runtime 其實可以直接內嵌到應用程式中, 所以任何符合.Net 的bytecode, 就可以藉由Mono runtime來執行. 花了幾天時間上網查了一些資料, 發現整合Mono沒想像中的難, 不過原本的 C#+C++/CLI+Native的作法在Mono上卻行不通於是只好改用類似swig的作法來橋接Managed code跟Native code, 缺點是要花多一點時間來寫wrapper. 之後使用了C#來完成了之前用Lua作出的卷軸遊戲. 使用Mono最大的好處是, 任何能編譯成.Net bytecode的語言都可以用來作為引擎的script語言, C#, VB, JavaScript, Java都沒問題, 甚至Lua都有人作出bytecode編譯器, 所以彈性可以說是非常的高. 目前Lua與Mono是併存於LynxEngine中, 但將來應該會慢慢轉移到以Mono為主.
以下就列出C#以及Lua的code 作個比較,  相較起來Mono還是更有彈性.
C# script list
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Diagnostics;
using LynxEngine;
//----------------------------------------------
//  說明:   
//----------------------------------------------
public class CBGLayer
{
    public float ObjectWidth, ObjectHeight;
    public uint ObjectInterval;
    public float PosY;
    public uint NumObjects;
    public float ScrollingSpeed;
    public CDynamicObj[] Objects = null;
    public float[] Pos = null;
    public CBGLayer()            
    {
    }
    public void CreateObjects(
        CScene scene, 
        int n, 
        String name, 
        String filename, 
        CRenderableObj.DEPTHLAYERTYPE dl)
    {
        Objects = new CDynamicObj[n];
        Pos = new float[n];
        for (uint i=0; i < Objects.GetLength(0); i++)
        {
            Objects[i] = 
                scene.CreateDynamicEntity();
            Objects[i].CreateSprite(
                name, 
                filename, 
                ObjectWidth, 
                ObjectHeight);
            Objects[i].UpdateTransform();
            Objects[i].SetDepthLayer(dl);
            scene.AddDynamicObj(Objects[i]);
        }
    }
}
//----------------------------------------------
//  說明:   
//----------------------------------------------
class CPlayer : CDynamicObj
{
    public enum STATE
    {
        RUN = 0,
        JUMP,
        ATTACK1
    };
    public class CState        
    {
        public CSound Sound = null;
        public CAnimation Animation = null;
    }
    
    public STATE State;
    public CState [] States = null;
    public CPlayer(IntPtr ptr)
        : base(ptr)
    {
    }
    public void SetState(STATE state)
    {
        States[(uint)State].Sound.Stop();
        
        SetCurrentAnimation(
            States[(uint)state].Animation);
        States[(uint)state].Sound.Play();
        State = state;
    }
}
//----------------------------------------------
//  說明:   
//----------------------------------------------
public class CScrollingGamePage : CUIPage
{
    bool GameConsoleIsRunning = false;
    CBGLayer[] BGLayers = new CBGLayer[4];
    CPlayer Player;
    CSound BGM;
    CVector2 [] BackgroundUV = new CVector2 [2];
    CScene Scene;
    
    new public void OnCreate()
    {
        Scene = GlobalVar.SceneSystem.AddScene();
        Scene.SetName("Scrolling Game Scene") ;
    
        CCamera Camera = Scene.CreateCamera();
        Camera.Create();
        Scene.SetCurrentCamera(Camera);
        CCameraContainer CameraContainer = 
            (CCameraContainer)Camera.GetContainer();
        CVector3 CameraPos = 
            new CVector3(10, 10, -35);
        CameraContainer.SetPosition(CameraPos);
        CVector3 CameraLookPos = 
            new CVector3(10, 10, 0);
        CameraContainer.LookAt(CameraLookPos);
        CameraContainer.UpdateProjectionMatrix(
            (float)(GlobalVar.GraphicsSystem.GetBackbufferWidth()) / 
            (float)(GlobalVar.GraphicsSystem.GetBackbufferHeight()));
        CameraContainer.UpdateViewMatrix();
        
        Scene.LoadBackgroundTexture(
            "../texture/scene/map00/background0.tga");
        BackgroundUV[0] = new CVector2();
        BackgroundUV[1] = new CVector2();
        BackgroundUV[0].x = 0.0f;   BackgroundUV[0].y = 0.0f;
        BackgroundUV[1].x = -0.5f;  BackgroundUV[1].y = 0.0f;
        Scene.SetBackgroundTextureUVOffset(
            BackgroundUV[0].x, BackgroundUV[0].y, 
            BackgroundUV[1].x, BackgroundUV[1].y);
        Player = new CPlayer(
            Scene.CreateDynamicEntity().GetPtr());                        
        Player.CreateModel(
            "Player", 
            "../model/dynobj/boy/boy.mdl");             
                       
        Player.SetDepthLayer(
            CRenderableObj.DEPTHLAYERTYPE.DEPTH_LAYER_3);
        Player.Yaw(-90.0f, MATHORDER.LYNX_MATH_POST);
        Scene.AddDynamicObj((CDynamicObj)Player);            
        Player.States = new CPlayer.CState[3];
        Player.States[(uint)CPlayer.STATE.RUN] = 
            new CPlayer.CState();
        Player.States[(uint)CPlayer.STATE.RUN].Animation = 
            Player.LoadAnimation("../model/dynobj/boy/run.ani");
        Player.States[(uint)CPlayer.STATE.RUN].Sound = 
            GlobalVar.SoundSystem.LoadSound(
                "Run", 
                "../sound/run.wav");
        Player.States[(uint)CPlayer.STATE.RUN].Sound.SetLoops(-1);
        Player.States[(uint)CPlayer.STATE.JUMP] = 
            new CPlayer.CState();
        Player.States[(uint)CPlayer.STATE.JUMP].Animation = 
            Player.LoadAnimation("../model/dynobj/boy/jump.ani");
        Player.States[(uint)CPlayer.STATE.JUMP].Sound = 
            GlobalVar.SoundSystem.LoadSound(
                "Jump", 
                "../sound/jump.wav");
        Player.States[(uint)CPlayer.STATE.ATTACK1] = 
            new CPlayer.CState();
        Player.States[(uint)CPlayer.STATE.ATTACK1].Animation = 
            Player.LoadAnimation("../model/dynobj/boy/attack1.ani");
        Player.States[(uint)CPlayer.STATE.ATTACK1].Sound = 
            GlobalVar.SoundSystem.LoadSound(
                "Attack1", 
                "../sound/attack1.wav");
        
        BGLayers[0] = new CBGLayer();
        BGLayers[0].ObjectWidth = 160;
        BGLayers[0].ObjectHeight = 320;
        BGLayers[0].ObjectInterval = 110;
        BGLayers[0].PosY = 320-35-320;    
        BGLayers[0].ScrollingSpeed = 1;
        BGLayers[0].CreateObjects(
            Scene, 6, 
            "Tree", 
            "../texture/scene/map00/tree_23.tga", 
            CRenderableObj.DEPTHLAYERTYPE.DEPTH_LAYER_5);
        BGLayers[1] = new CBGLayer();
        BGLayers[1].ObjectWidth = 256;
        BGLayers[1].ObjectHeight = 256;
        BGLayers[1].ObjectInterval = 256;
        BGLayers[1].PosY = 320-35-256;    
        BGLayers[1].ScrollingSpeed = 2.5f;
        BGLayers[1].CreateObjects(
            Scene, 3, 
            "Tree2", 
            "../texture/scene/map00/tree_01.tga", 
            CRenderableObj.DEPTHLAYERTYPE.DEPTH_LAYER_4);
        BGLayers[2] = new CBGLayer();
        BGLayers[2].ObjectWidth = 128;
        BGLayers[2].ObjectHeight = 64;
        BGLayers[2].ObjectInterval = 110;
        BGLayers[2].PosY = 320 - 22 - 64;
        BGLayers[2].ScrollingSpeed = 6.0f;
        BGLayers[2].CreateObjects(
            Scene, 6, 
            "Bush", 
            "../texture/scene/map00/bush_08.tga", 
            CRenderableObj.DEPTHLAYERTYPE.DEPTH_LAYER_3);
        BGLayers[3] = new CBGLayer();
        BGLayers[3].ObjectWidth = 64;
        BGLayers[3].ObjectHeight = 64;
        BGLayers[3].ObjectInterval = 64;
        BGLayers[3].PosY = 320 - 40;
        BGLayers[3].ScrollingSpeed = 6.0f;
        BGLayers[3].CreateObjects(
            Scene, 9, 
            "GroundTile", 
            "../texture/scene/map00/ground.tga", 
            CRenderableObj.DEPTHLAYERTYPE.DEPTH_LAYER_2);           
        BGM = GlobalVar.SoundSystem.LoadSound(
            "BGM", 
            "../sound/BGM.wav");
        BGM.SetLoops(-1);
        Scene.SetRenderMode(CScene.RENDERMODE.RENDER_SIMPLE);            
        Scene.SetSortMode(CScene.SORTMODE.SORT_BY_DEPTH_LAYER);
        Scene.Setup();
    }
    //----------------------------------------------
    //  說明:   
    //----------------------------------------------
    new public void OnInit()
    {
        GlobalVar.SystemMouse = 
            (CMouse)(GlobalVar.InputSystem.FindDevice("System Mouse"));
        GlobalVar.Engine.DeleteLastUIPage();
        Player.SetState(CPlayer.STATE.RUN);
        for (int i = 0; i < BGLayers.GetLength(0); i++)
        {
            for (int j=0; j < BGLayers[i].Objects.GetLength(0); j++)                
            {
                BGLayers[i].Pos[j] = (int)(j*BGLayers[i].ObjectInterval);
            }
        }
    
        BGM.SetVolume(0.6f);
        BGM.Play();            
    }
    //----------------------------------------------
    //  說明:   
    //----------------------------------------------
    void AnimationLoop()      
    {
        if (GlobalVar.SystemMouse.ButtonStatus(CMouse.RBUTTON) && 
            Player.State == CPlayer.STATE.RUN)
        {
            Player.SetState(CPlayer.STATE.JUMP);                
        }    
        
        if (GlobalVar.SystemMouse.ButtonStatus(CMouse.LBUTTON) && 
            Player.State == CPlayer.STATE.RUN)
        {
            Player.SetState(CPlayer.STATE.ATTACK1);                
        }
        if (Player.State != CPlayer.STATE.RUN)
        {
            if (Player.IsCurrentAnimationStopped())
            {
                Player.SetState(CPlayer.STATE.RUN);                
            }
        }
    }
    //----------------------------------------------
    //  說明:   
    //----------------------------------------------
    new public void OnLoop(float step)
    {
        GlobalVar.SystemMouse.Poll();
        if (GlobalVar.GameConsoleSystem.IsRunning())
        {
            GameConsoleIsRunning = true;
            Player.States[(int)Player.State].Sound.Pause();
        }
        else
        {
            AnimationLoop();
            if (GameConsoleIsRunning)
            {
                if (!Player.States[(int)Player.State].Sound.IsPlaying())
                {
                    Player.States[(int)Player.State].Sound.Play();
                }
                GameConsoleIsRunning = false;
            }
        
            if (Player.State == CPlayer.STATE.RUN || 
                Player.State == CPlayer.STATE.JUMP)
            {
                BackgroundUV[0].x += (step * 0.0003f);
                BackgroundUV[1].x += (step * 0.0003f);
                Scene.SetBackgroundTextureUVOffset(
                    BackgroundUV[0].x, BackgroundUV[0].y, 
                    BackgroundUV[1].x, BackgroundUV[1].y);
                for (int i = 0; i < BGLayers.GetLength(0); i++)
                {
                    for (int j = 0; 
                         j < BGLayers[i].Objects.GetLength(0); j++)
                    {
                        BGLayers[i].Pos[j] -= 
                            (BGLayers[i].ScrollingSpeed * step);
                        if (BGLayers[i].Pos[j] < -BGLayers[i].ObjectWidth)
                            BGLayers[i].Pos[j] += 
                                (BGLayers[i].Objects.GetLength(0)*
                                 BGLayers[i].ObjectInterval);
                        CVector3 Pos = 
                            new CVector3(BGLayers[i].Pos[j], 
                                         BGLayers[i].PosY, 
                                         0);
                        BGLayers[i].Objects[j].SetPosition(Pos);
                        BGLayers[i].Objects[j].UpdateTransform();
                    }
                }
            }
        }
    }
    //----------------------------------------------
    //  說明:   
    //----------------------------------------------
    new public void OnRender()
    {
        GlobalVar.Engine.Render();            
    }
    //----------------------------------------------
    //  說明:   
    //----------------------------------------------
    new public void OnQuit()
    {
        base.OnQuit();
    }
}
Lua script list
Player  = {DynObj = 0, State = 0, States = {}}
Player.STATE = {}
Player.STATE.RUN     = 0
Player.STATE.JUMP    = 1
Player.STATE.ATTACK1 = 2
Scene = nil
BGLayer = {}
BackgroundUV = {u0 = 0.0, v0 = 0.0, u1 = -0.51, v1 = 0.0}
BGM = nil
GameConsoleIsRunning = false
------------------------------------------------
--  說明:   
------------------------------------------------
Player.SetState = function (state) 
    CSound_Cast(Player.States[Player.State].Sound):Stop()
    
    CDynamicObj_Cast(Player.DynObj):SetCurrentAnimation(
        Player.States[state].Animation)
    CSound_Cast(Player.States[state].Sound):Play()    
    Player.State = state 
end
------------------------------------------------
--  說明:   
------------------------------------------------
function BGLayer_Create (scene, layer, n, name, filename, depthlayer) 
    BGLayer[layer].NumObjects = n
    for i=0,BGLayer[layer].NumObjects-1 do
        DynamicObj = CDynamicObj_Cast(scene:CreateDynamicEntity())
        DynamicObj:CreateSprite(
            name, 
            filename, 
            BGLayer[layer].ObjectWidth, 
            BGLayer[layer].ObjectHeight)        
        DynamicObj:UpdateTransform()
        DynamicObj:SetDepthLayer(depthlayer)
        scene:AddDynamicObj(GetObjectPointer(DynamicObj))  
        BGLayer[layer][i] = {}        
        BGLayer[layer][i].DynObj = GetObjectPointer(DynamicObj)
        BGLayer[layer][i].Pos = i*BGLayer[layer].ObjectWidth
    end    
end
------------------------------------------------
--  說明:   
------------------------------------------------
CScrollingGamePage.OnCreate = function () 
    CSampleScriptGame:ComputeScreenRatio()  
       
    Scene = CScene_Cast(CSceneSystem:AddScene())
    Scene:SetName(L"Scrolling Game Scene")  
        
    Camera = CCamera_Cast(Scene:CreateCamera())
    Camera:Create()
    Scene:SetCurrentCamera(GetObjectPointer(Camera))
    CameraContainer = CCameraContainer_Cast(Camera:GetContainer())
    CameraContainer:SetPosition(CVector3(10, 10, -35))
    CameraContainer:LookAt(10, 10, 0)
    CameraContainer:UpdateProjectionMatrix(
        CGraphicsSystem:GetBackbufferWidth()/
        CGraphicsSystem:GetBackbufferHeight())
    CameraContainer:UpdateViewMatrix()       
            
    Scene:LoadBackgroundTexture(
        L"../texture/scene/map00/background0.tga")
    Scene:SetBackgroundTextureUVOffset(
        BackgroundUV.u0, BackgroundUV.v0, 
        BackgroundUV.u1, BackgroundUV.v1) 
    
    DynamicObj = CDynamicObj_Cast(Scene:CreateDynamicEntity())
    DynamicObj:CreateModel(L"Player", L"../model/dynobj/boy/boy.mdl")    
    DynamicObj:SetDepthLayer(CRenderableObj.DEPTHLAYERTYPE.DEPTH_LAYER_3)
    DynamicObj:Yaw(-90.0, 1)        
    Scene:AddDynamicObj(GetObjectPointer(DynamicObj))    
    
    Player.States[Player.STATE.RUN] = {}
    Player.States[Player.STATE.RUN].Animation = 
        DynamicObj:LoadAnimation(L"../model/dynobj/boy/run.ani")
    Player.States[Player.STATE.RUN].Sound = 
        CSoundSystem:LoadSound(L"Run", L"../sound/run.wav")
    CSound_Cast(Player.States[Player.STATE.RUN].Sound):SetLoops(-1)
    Player.DynObj = GetObjectPointer(DynamicObj)
    
    Player.States[Player.STATE.JUMP] = {}
    Player.States[Player.STATE.JUMP].Animation = 
        DynamicObj:LoadAnimation(L"../model/dynobj/boy/jump.ani")
    Player.States[Player.STATE.JUMP].Sound = 
        CSoundSystem:LoadSound(L"Jump", L"../sound/jump.wav")
    
    Player.States[Player.STATE.ATTACK1] = {}
    Player.States[Player.STATE.ATTACK1].Animation = 
        DynamicObj:LoadAnimation(L"../model/dynobj/boy/attack1.ani")
    Player.States[Player.STATE.ATTACK1].Sound = 
        CSoundSystem:LoadSound(L"Attack1", L"../sound/attack1.wav")      
    
    BGLayer[0] = {}
    BGLayer[0].ObjectWidth = 160    
    BGLayer[0].ObjectHeight = 320    
    BGLayer[0].ObjectInterval = 110    
    BGLayer[0].PosY = 320-35-320        
    BGLayer[0].ScrollingSpeed = 1
    BGLayer_Create(
        Scene, 0, 6, 
        L"Tree", 
        L"../texture/scene/map00/tree_23.tga", 
        CRenderableObj.DEPTHLAYERTYPE.DEPTH_LAYER_5)
    
    BGLayer[1] = {}
    BGLayer[1].ObjectWidth = 256
    BGLayer[1].ObjectHeight = 256    
    BGLayer[1].ObjectInterval = 256      
    BGLayer[1].PosY = 320-35-256          
    BGLayer[1].ScrollingSpeed = 2.5  
    BGLayer_Create(
        Scene, 1, 3, 
        L"Tree2", 
        L"../texture/scene/map00/tree_01.tga", 
        CRenderableObj.DEPTHLAYERTYPE.DEPTH_LAYER_4)    
    
    BGLayer[2] = {}
    BGLayer[2].ObjectWidth = 128  
    BGLayer[2].ObjectHeight = 64  
    BGLayer[2].ObjectInterval = 110     
    BGLayer[2].PosY = 320-22-64     
    BGLayer[2].ScrollingSpeed = 6
    BGLayer_Create(
        Scene, 2, 6, 
        L"Bush", 
        L"../texture/scene/map00/bush_08.tga", 
        CRenderableObj.DEPTHLAYERTYPE.DEPTH_LAYER_1)        
            
    BGLayer[3] = {}
    BGLayer[3].ObjectWidth = 64 
    BGLayer[3].ObjectHeight = 64    
    BGLayer[3].ObjectInterval = 64 
    BGLayer[3].PosY = 320-40     
    BGLayer[3].ScrollingSpeed = 6  
    BGLayer_Create(
        Scene, 3, 9, 
        L"GroundTile", 
        L"../texture/scene/map00/ground.tga", 
        CRenderableObj.DEPTHLAYERTYPE.DEPTH_LAYER_1)        
    
    BGM = (CSoundSystem:LoadSound(L"BGM", L"../sound/BGM.wav"))
    CSound_Cast(BGM):SetLoops(-1)
        
    Scene:SetRenderMode(CScene.RENDERMODE.RENDER_SIMPLE)    
    Scene:SetSortMode(CScene.SORTMODE.SORT_BY_DEPTH_LAYER)           
    Scene:Setup()
end
------------------------------------------------
--  說明:   
------------------------------------------------
CScrollingGamePage.OnInit = function ()          
    CEngine:DeleteLastUIPage()
    Player.SetState(Player.STATE.RUN)
    
    for i=0,3 do
        for j=0, BGLayer[i].NumObjects-1 do
            BGLayer[i][j].Pos = j*BGLayer[i].ObjectInterval
        end
    end  
        
    CSound_Cast(BGM):SetVolume(0.6)
    CSound_Cast(BGM):Play()
end
------------------------------------------------
--  說明:   
------------------------------------------------
MouseDevice = CMouse_Cast(CInputSystem:FindDevice(L"System Mouse"))
------------------------------------------------
--  說明:   
------------------------------------------------
CScrollingGamePage.AnimationLoop = function ()          
    if (MouseDevice:ButtonStatus(RBUTTON) == TRUE and 
        Player.State == Player.STATE.RUN) then
        Player.SetState(Player.STATE.JUMP)        
    end
    
    if (MouseDevice:ButtonStatus(LBUTTON) == TRUE and 
        Player.State == Player.STATE.RUN) then
        Player.SetState(Player.STATE.ATTACK1)        
    end
    
    if (Player.State ~= Player.STATE.RUN) then
        if (CDynamicObj_Cast(
            Player.DynObj):IsCurrentAnimationStopped() == TRUE) then
            Player.SetState(Player.STATE.RUN)        
        end
    end    
end
------------------------------------------------
--  說明:   
------------------------------------------------
CScrollingGamePage.OnLoop = function (step)      
    MouseDevice:Poll()
    
    if (CGameConsoleSystem:IsRunning() == TRUE) then
        GameConsoleIsRunning = true
        CSound_Cast(Player.States[Player.State].Sound):Pause()
    else
        CScrollingGamePage:AnimationLoop()    
        
        if (GameConsoleIsRunning == true) then
            if (CSound_Cast(
                Player.States[Player.State].Sound):IsPlaying() == FALSE) then
                CSound_Cast(Player.States[Player.State].Sound):Play()
            end
            GameConsoleIsRunning = false
        end
        
        if (Player.State == Player.STATE.RUN or 
            Player.State == Player.STATE.JUMP) then    
            BackgroundUV.u0 = BackgroundUV.u0 + (step * 0.0003)
            BackgroundUV.u1 = BackgroundUV.u1 + (step * 0.0003)
            Scene:SetBackgroundTextureUVOffset(
                BackgroundUV.u0, BackgroundUV.v0, 
                BackgroundUV.u1, BackgroundUV.v1)
            
            for i=0,3 do
                for j=0, BGLayer[i].NumObjects-1 do
                    BGLayer[i][j].Pos = 
                        BGLayer[i][j].Pos - BGLayer[i].ScrollingSpeed*step
                    if (BGLayer[i][j].Pos < -BGLayer[i].ObjectWidth) then
                        BGLayer[i][j].Pos = 
                            BGLayer[i][j].Pos + 
                            BGLayer[i].NumObjects*BGLayer[i].ObjectInterval 
                    end
                    DynObj = CDynamicObj_Cast(BGLayer[i][j].DynObj)
                    DynObj:SetPosition(
                        CVector3(BGLayer[i][j].Pos, BGLayer[i].PosY, 0))
                    DynObj:UpdateTransform()
                end
            end   
        end
    end
end
------------------------------------------------
--  說明:   
------------------------------------------------
CScrollingGamePage.OnRender = function ()           
    CEngine:Render()
end
------------------------------------------------
--  說明:   
------------------------------------------------
CScrollingGamePage.OnQuit = function ()      
    CUIPage:OnQuit();
end