Sunday, July 17, 2011

LynxEngine Update : Script System

很久一段時間都沒有新文章了, 因為前一陣子一直在忙著做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

3 comments:

R2D2 said...

LynxEngine越來越強了! Mono和Lua在速度及memory footprint上比較起來差別如何?

fallingCAT said...

Lua基本上memory footprint還是比較有優勢的, 但是以執行速度來說, Mono就比Lua快上許多(PC version, 5xx fps v.s. 3xx fps). Mono比較大的問題是iOS, Android並無免費版本, 必須購買Mono Touch才能執行Mono runtime,這點不知Unity是如何解決的?

R2D2 said...

一般Unity/Unity Pro license並沒有包含Android或iOS的支援, 要另外多付US$400才有.
MonoTouch Pro的定價是US$399, 我猜Unity那個$400就是付MonoTouch的license fee.