用Html5結合Qt製作一款本地化EXE遊戲-太空大戰(Space War)

本次來說一說如何利用lufylegend.js引擎製作一款html5遊戲後將其通過Qt轉換成EXE程序。步驟其實非常簡單,接下來就一步步地做一下解釋和說明。

首先我們來開發一個有點類似於太空大戰的遊戲,遊戲截圖如下:


遊戲介紹:這個遊戲原本是七十一霧央前輩用Cocos2d-x開發的android小遊戲。由於我看到這個遊戲實現起來比較簡單,因此就把apk下載下來,並且在霧央的指導下,把它當rar壓縮文件解開了,把素材偷走了……嘿嘿。由於我最近的開發涉及html5領域,因此就用html5+lufylegend.js把這個遊戲移植到瀏覽器平臺上了。當然,效率不能和霧央的原版遊戲比,因爲html5的效率衆所周知是很低的。

操作說明:用鼠標點擊界面,發出子彈攻擊迎面飛來的敵人。

遊戲目標:不放過任何一個迎面飛來的敵人。

遊戲測試地址:

http://www.cnblogs.com/yorhom/articles/3274940.html

注:演示地址中沒有背景音樂,是因爲我覺得音樂太佔空間了,所以去掉了。下載包裏含有音樂,各位可以欣賞一下。另外也感謝一下霧央兄弟,感謝他給我提供那麼好,那麼多的素材。


接下來就來說說這個遊戲的製作步驟。


準備工作

首先你需要下載lufylegend.js遊戲引擎。這個引擎是一個html5開源庫件,利用他可以仿照了As 3.0的語法進行html5開發,使用起來非常方便。當然,你說你不是flasher,不懂As 3.0,那也無妨,可以參照官方API文檔進行學習。具體的介紹還是去官方網站看看吧,免得lufy說我亂介紹他的引擎,嘿嘿~

引擎官方網站:

http://lufylegend.com/lufylegend

引擎API文檔:

http://lufylegend.com/lufylegend/api

因爲本次開發要用到這個引擎,所以各位先看看這個引擎的一些API介紹吧,避免文中用到的一些API大夥看不懂。


製作過程

首先要讀取一下游戲中的數據。本次開發要用到的數據如下:

/**加載變量*/
var loadData = [
	{path:"./js/Bullet.js",type:"js"},
	{path:"./js/Plain.js",type:"js"},
	{path:"./js/Background.js",type:"js"},
	{name:"bullet",path:"./images/bullet.png"},
	{name:"sky.1",path:"./images/gamebg0.png"},
	{name:"sky.2",path:"./images/gamebg1.png"},
	{name:"over_text",path:"./images/gameover.png"},
	{name:"player",path:"./images/hero.png"},
	{name:"monster",path:"./images/monster.png"},
	{name:"over_bg",path:"./images/overbg.png"},
	{name:"start_bg",path:"./images/startbg.png"},
	{name:"start_button_normal",path:"./images/startNormal.png"},
	{name:"start_button_selected",path:"./images/startSelected.png"}
];
由於加載完成後要保存這些加載好的數據,所以還要用一個變量:

var datalist;
接下來把一些定義的變量放在下面,都寫了註釋,大家慢慢看喔~

/**層變量*/
var backLayer,
plainLayer,
enemyLayer,
bulletLayer,
textLayer,
loadingLayer;

/**分數變量*/
var score;

/**頻率變量*/
var maxFrame = 30;
var frameIndex = 0;

/**遊戲進行時間*/
var gameTime;

/**對象變量*/
//顯示分數對象
var scoreText;
//玩家
var player;
//音樂對象
var startMusic,
overMusic,
playingMusic,
dieMusic;

然後用到init初始化遊戲,因爲遊戲是要跨平臺的,所以要在手機上全屏顯示,爲了實現這些,我們在Main.js頂部加入如下的代碼:

//設置全屏
LSystem.screen(LStage.FULL_SCREEN);
//初始化遊戲
init(30,"mylegend",800,480,main);
init的用法和LSystem.screen的用法都可以參照API文檔。

接下來我們來看看main函數,這個函數是用來加載圖片和設置一些信息用的,比如開啓debug模式等,代碼如下:

function main(){
	//設置debug模式
	LStage.setDebug(true);
	//如果是移動端,就將body標籤margin調爲0px 0px 0px 0px
	if(LStage.canTouch == true){
		document.body.style.margin = "0px 0px 0px 0px";
	}
	
	//初始化加載層
	loadingLayer = new LoadingSample3();
	addChild(loadingLayer);
	
	//加載遊戲數據
	LLoadManage.load(
		loadData,
		function(progress){
			//顯示加載進度
			loadingLayer.setProgress(progress);
		},
		gameInit
	);
}
上面的代碼中,用了庫件中的LLoadManage類讀取,這個類可以讀取js文件和圖片、音頻文件等,大家可以自己去看看API文檔。

接下來看看gameInit裏的代碼,這個函數是用來保存加載數據,加入音樂,加入開始場景用的。代碼如下:

function gameInit(result){
	//保存加載的數據
	datalist = result;
	//清空加載層
	removeChild(loadingLayer);
	
	//加入地板層
	backLayer = new LSprite();
	addChild(backLayer);
	
	//初始化音樂
	initMusic();
	
	//加入開始界面
	addStartPage();
}
其中調用到的函數代碼分別如下:

function initMusic(){
	//開場音樂
	startMusic = new LSound("./music/startbg.mp3");
	//結束音樂
	overMusic = new LSound("./music/overbg.mp3");
	//遊戲開始後的音樂
	playingMusic = new LSound("./music/gamebg.wav");
	//死亡後的音樂
	dieMusic = new LSound("./music/die.wav");
}
function addStartPage(){
	//播放音樂
	startMusic.play(0,100000000000000000000000000000000);
	//加入背景
	var bitmapData = new LBitmapData(datalist["start_bg"]);
	var bitmap = new LBitmap(bitmapData);
	backLayer.addChild(bitmap);
	
	//按鈕普通時的樣式
	var normalBtnStyleData = new LBitmapData(datalist["start_button_normal"]);
	var normalBtnStyle = new LBitmap(normalBtnStyleData);
	//按鈕盤旋時的樣式
	var selectedBtnStyleData = new LBitmapData(datalist["start_button_selected"]);
	var selectedBtnStyle = new LBitmap(selectedBtnStyleData);
	//加入開始按鈕
	var startBtn = new LButton(normalBtnStyle,selectedBtnStyle);
	startBtn.x = (LStage.width-startBtn.getWidth())*0.5;
	startBtn.y = (LStage.height-startBtn.getHeight())*0.5;
	backLayer.addChild(startBtn);
	
	//加入開始事件
	startBtn.addEventListener(LMouseEvent.MOUSE_DOWN,startGame);
}
代碼都加上了註釋,可以參照API文檔看看。在上面的addStartPage代碼中,加入一個開始按鈕後,我們給這個按鈕加了一個鼠標事件,這個事件是用來觸發遊戲開始用的。開始遊戲我們用的是startGame函數,代碼如下:

function startGame(event){
	//清空界面
	backLayer.removeAllChild();
	
	//分數調零
	score = 0;
	//遊戲時間調零
	gameTime = 0;
	
	//停止開始界面的音樂音樂
	startMusic.close();
	//播放遊戲進行中的音樂
	playingMusic.play(0,100000000000000000000000000000000);
	
	//加入滾動背景
	var background = new Background();
	backLayer.addChild(background);
	
	//初始化層變量
	initLayer();
	//加入玩家飛機
	player = new LBitmap(new LBitmapData(datalist["player"]));
	player.x = 20;
	player.y = (LStage.height-player.getHeight())*0.5
	plainLayer.addChild(player);
	
	//加入分數文字
	addText();
	
	//加入事件
	backLayer.addEventListener(LMouseEvent.MOUSE_DOWN,onmousedown);
	backLayer.addEventListener(LEvent.ENTER_FRAME,onframe);
}

這個函數中我們首先初始化一些值,比如分數和遊戲時間。在這裏順便說說這個遊戲時間有什麼用:因爲我們的遊戲隨着時間的推進,難度應該越來越難,所以我們要保存下這個時間,方便以後的計算。

在startGame中,我們爲了加入一個滾動的背景,我們用到了一個Background類。這個類代碼如下:

/**
* Background.js
* @author Yorhom
* @date 2013/8/10/23:42
*/

function Background(){
	var s = this;
	base(s,LSprite,[]);
	
	//設置移動速度
	s.speed = 10;
	
	var skyBitmapData1 = new LBitmapData(datalist["sky.1"]);
	var skyBitmapData2 = new LBitmapData(datalist["sky.2"]);

	//記錄每塊背景的長度
	s.lastObjX = skyBitmapData1.image.width;
	
	//實例化第一塊背景
	s.skyBitmap1 = new LBitmap(skyBitmapData1);
	//實例化第二塊背景
	s.skyBitmap2 = new LBitmap(skyBitmapData2);
	//將第二塊背景移動到第一塊的後面
	s.skyBitmap2.x = s.lastObjX; 
	
	//加入兩塊背景
	s.addChild(s.skyBitmap1);
	s.addChild(s.skyBitmap2);
	
	//加入時間軸事件
	s.addEventListener(LEvent.ENTER_FRAME,s.run);
}
Background.prototype.run = function(s){
	//將背景向前移動
	s.skyBitmap1.x -= s.speed;
	s.skyBitmap2.x -= s.speed;
	
	//如果第一塊背景移除到屏幕之外就移到另一塊的後面
	if(s.skyBitmap1.x < -1 * s.lastObjX){
		s.skyBitmap1.x = s.skyBitmap2.x + s.lastObjX;
	}
	//如果第二塊背景移除到屏幕之外就移到另一塊的後面
	if(s.skyBitmap2.x < -1 * s.lastObjX){
		s.skyBitmap2.x = s.skyBitmap1.x + s.lastObjX;
	}
};
原理很簡單,就是先將第二塊背景接到第一塊背景的後面,如果第一塊背景已經移除屏幕了,那就把第一塊背景加到第二塊後面,以此類推,就實現了畫面不段移動的感覺。

在startGame函數中,我們還用到了實例化層的函數initLayer(),這個函數代碼如下:

function initLayer(){
	//加入飛機層
	plainLayer = new LSprite();
	backLayer.addChild(plainLayer);
	//加入敵機層
	enemyLayer = new LSprite();
	backLayer.addChild(enemyLayer);
	//加入子彈
	bulletLayer = new LSprite();
	backLayer.addChild(bulletLayer);
	//加入文字層
	textLayer = new LSprite();
	backLayer.addChild(textLayer);
}
另外還有個addText函數,負責顯示分數文字用的。

function addText(){
	//實例化LTextField對象
	scoreText = new LTextField();
	scoreText.font = "Tekton Pro";
	scoreText.size = 20;
	scoreText.text = "Score: " + score;
	scoreText.x = LStage.width - scoreText.getWidth() - 20;
	scoreText.y = 20;
	//加到顯示層中
	textLayer.addChild(scoreText);
}
還有就是在startGame中加的兩個事件:時間軸事件,鼠標點擊事件。鼠標事件觸發的函數代碼如下:

function onmousedown(event){
	//計算子彈飛出的角度
	var height = (player.y + player.getHeight()*0.5) - event.offsetY;
	var width = event.offsetX - (player.x + player.getWidth()*0.5);
	var angle = Math.atan2(height,width);
	//實例化一個子彈
	var bullet = new Bullet(angle);
	bulletLayer.addChild(bullet);
}
代碼很簡單,就是先取出當前點擊位置離人物的寬度與高度,然後通過Math.atan2算出這個點與玩家飛機的直線距離和通過玩家飛機的水平直線的夾角度數,並將這個值當參數傳入Bullet類中。Bullet類代碼如下:

/**
* Bullet.js
* @author Yorhom
* @date 2013/8/12/21:14
*/

function Bullet(angle){
	var s = this;
	base(s,LSprite,[]);
	
	//計算子彈角度
	s._angle = angle * 180 / Math.PI;
	//保存子彈移動速度
	s._speed = 10;
	//保存當前子彈到玩家飛機的距離
	s._r = 0;
	
	//計算出初始位置
	s.x = player.x + player.getWidth()*0.5;
	s.y = player.y + player.getHeight()*0.5;
	
	//保存初始位置
	s._startX = s.x;
	s._startY = s.y;

	//添加子彈對象
	var bitmapData = new LBitmapData(datalist["bullet"]);
	s.bitmap = new LBitmap(bitmapData);
	s.addChild(s.bitmap);
	
	//添加射擊時的音頻對象
	var attackMusic = new LSound("./music/attack.wav");
	attackMusic.play();
}
Bullet.prototype.onframe = function(){
	var s = this;
	//更改當前子彈到玩家飛機的距離
	s._r += s._speed;
	
	//計算y軸移動距離
	var speedy = Math.sin(s._angle * Math.PI / 180) * s._r;
	//計算x軸移動距離
	var speedx = Math.cos(s._angle * Math.PI / 180) * s._r;
	//更改子彈位置
	s.x = s._startX + speedx;
    s.y = s._startY - speedy;
};
這個類主要負責顯示一個子彈,並且讓子彈往點擊的方向飛去。顯示一個子彈就是用一個LBitmap來實現。移動子彈的原理就是先把子彈到玩家飛機的直線距離設置爲0,然後每當要移動子彈時,就將這個距離先加上移動速度,找到要到的位置,然後通過傳進來的那個角度參數配合Math.cos和Math.sin算出要到的位置的x,y座標,然後讓子彈移動到那個位置上去。這個對於大夥兒應該很簡單,但對於我這個只有初二水平的學生來說,連cos和sin都沒學過,查了很多資料才搞出來的。

上面還提到了時間軸事件,觸發的函數如下:

function onframe(){
	//增加遊戲時間
	gameTime ++;
	//添加敵人
	if(frameIndex > maxFrame){
		var enemy = new Plain();
		enemyLayer.addChild(enemy);
		frameIndex = 0;
	}else{
		frameIndex ++;
	}
	//移除敵人
	for(var key in enemyLayer.childList){
		if(enemyLayer.childList[key].mode == "die"){
			enemyLayer.removeChild(enemyLayer.childList[key]);
			//增加分數
			score += 10;
			//顯示新分數
			changeText();
			return;
		}
		if(enemyLayer.childList[key].mode == "complete"){
			gameOver();
			enemyLayer.removeChild(enemyLayer.childList[key]);
		}
	}
	//移除飛出屏幕的子彈
	for(var key in bulletLayer.childList){
		bulletLayer.childList[key].onframe();
		if(
			bulletLayer.childList[key].x > LStage.width
			|| bulletLayer.childList[key].x < 0
			|| bulletLayer.childList[key].y < 0
			|| bulletLayer.childList[key].y > LStage.height
		){
			bulletLayer.removeChild(bulletLayer.childList[key]);
		}
	}
}
每段代碼都加了註釋,結合API文檔看一些就能明白的。其中有個Plain類,這個是一個用來實現敵機的類,包括敵機移動,檢測碰撞等,代碼如下:

/**
* Plain.js
* @author Yorhom
* @date 2013/8/15/12:10
*/

function Plain(){
	var s = this;
	base(s,LSprite,[]);
	
	//設置飛機移動速度
	s.speed = Math.floor(gameTime/100) + 7;
	s.mode = "";
	
	//添加敵人的圖片
	var bitmapData = new LBitmapData(datalist["monster"]);
	s._bitmap = new LBitmap(bitmapData);
	s.x = LStage.width + s._bitmap.getWidth();
	s.y = Math.floor(Math.random()*(LStage.height-s._bitmap.getHeight()));
	s.addChild(s._bitmap);
	
	//通過時間軸事件實現不斷移動
	s.addEventListener(LEvent.ENTER_FRAME,s.run);
}
Plain.prototype.run = function(s){
	//移動飛機對象
	s.x -= s.speed;
	
	//檢測碰撞
	s.checkHit();
	
	//判斷是否移除屏幕。如果是,就將mode屬性設置爲"complete"
	if(s.x < -1 * s.getWidth()){
		s.mode = "complete";
	}
};
Plain.prototype.checkHit = function(){
	var s = this;
	
	//判斷碰撞
	for(var key in bulletLayer.childList){
		if(LStage.hitTestArc(s,bulletLayer.childList[key])){
			//將mode屬性改爲"die"
			s.mode = "die";
			//移除碰撞子彈
			bulletLayer.removeChild(bulletLayer.childList[key]);
		}
	}
};

可以在上面的構造器代碼中看到,我們通過遊戲時間變量gameTime計算了飛機移動的速度,達到改變遊戲的難度。其他的代碼就直接看註釋和API文檔就能看懂。

實現了這個類,我們的遊戲基本上就搞定了。不過還有些細節部分不可忽視。

爲了移除一些對象避免效率低下,我們在onframe中加入了移除對象的功能。爲了實現這個功能,我們遍歷了每個飛機對象,然後判斷遍歷到的飛機對象的mode屬性是否爲die,如果是,就移除掉。在onframe中,實現這個效果的代碼如下:

//移除敵人
for(var key in enemyLayer.childList){
	if(enemyLayer.childList[key].mode == "die"){
		enemyLayer.removeChild(enemyLayer.childList[key]);
		//增加分數
		score += 10;
		//顯示新分數
		changeText();
		return;
	}
	if(enemyLayer.childList[key].mode == "complete"){
		gameOver();
		enemyLayer.removeChild(enemyLayer.childList[key]);
	}
}
//移除飛出屏幕的子彈
for(var key in bulletLayer.childList){
	bulletLayer.childList[key].onframe();
	if(
		bulletLayer.childList[key].x > LStage.width
		|| bulletLayer.childList[key].x < 0
		|| bulletLayer.childList[key].y < 0
		|| bulletLayer.childList[key].y > LStage.height
	){
		bulletLayer.removeChild(bulletLayer.childList[key]);
	}
}

爲了及時更改分數,我們在時間軸事件中還加入了調用changeText函數。代碼如下:

function changeText(){
	//更改顯示文字
	scoreText.text = "Score: " + score;
	//更改文字座標
	scoreText.x = LStage.width - scoreText.getWidth() - 20;
}
還有就是遊戲結束時調用的代碼,如下:

function gameOver(){
	//消除事件
	backLayer.die();
	
	var bitmap;
	//加入遊戲結束層
	var gameOverLayer = new LSprite();
	backLayer.addChild(gameOverLayer);
	//加入背景
	bitmap = new LBitmap(new LBitmapData(datalist["over_bg"]));
	gameOverLayer.addChild(bitmap);
	//加入文字
	bitmap = new LBitmap(new LBitmapData(datalist["over_text"]));
	bitmap.x = (LStage.width - bitmap.getWidth()) * 0.5;
	bitmap.y = (LStage.height - bitmap.getHeight()) * 0.5;
	gameOverLayer.addChild(bitmap);
	
	//將遊戲結束層移除屏幕
	gameOverLayer.y = -1 * gameOverLayer.getHeight();
	//通過緩動將遊戲結束層移到屏幕上
	LTweenLite.to(gameOverLayer,0.7,{
		y:0,
		ease:Quad.easeInOut,
		onComplete:function(){
			//加入鼠標事件,來應對遊戲重開
			backLayer.addEventListener(LMouseEvent.MOUSE_DOWN,restart);
		}
	});
	
	//關掉遊戲進行中的音樂
	playingMusic.close();
	//播放遊戲結束時候的音樂
	dieMusic.play(0,1);
	overMusic.play(0,100000000000000000000000000000000);
}
最後就是遊戲重開函數:

function restart(){
	//清除界面
	backLayer.die();
	backLayer.removeAllChild();
	//關掉遊戲結束時候的音樂
	overMusic.close();
	//開始遊戲
	startGame();
}

上面基本上把整個遊戲製作過程簡略地講了一遍,代碼講解有點不詳細,大家可以結合註釋看看。另外如果有感興趣的朋友,可以到下面鏈接裏下載。下載包裏還有我打包好的apk文件,大家可以在手機上玩玩。打包apk的話,可以看看這篇文章:《用HTML5來開發一款android本地化App遊戲-寶石碰碰》

源代碼下載地址:http://files.cnblogs.com/yorhom/SpaceWar.rar


結合Qt實現本地化EXE遊戲

下載和安裝Qt等一些基礎的東西這裏就不多說了,Google一下或者百度一下就可以了。接下來就直接講方法。

首先你需要在Qt Creater中創建一個Qt項目,創建項目的方法如下。

首先點開File->New File or Project,出現以下對話框,選擇如圖所示的幾個選項:


點擊Choose...按鈕,進入如下界面:


上面的信息隨便添就可以。點擊Next,出現如下界面:


上面是在選擇配置,根據自己下載的選擇一些就ok,點擊Next繼續。出現如下界面


按照上面的添法填寫好後,再繼續按下Next,進入下一個界面。


然後按下Finish就已經創建好項目了。得到以下的目錄樹,大家可以看看操作對沒有:


然後把裝有html5遊戲的文件夾複製到執行文件目錄下。如下圖:


注意:貌似遊戲加了音樂就會運行不出來了,把有用到音樂的地方全部刪掉就ok沒事了

打開mainwindow.h,寫入以下代碼:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

class MainWindow : public QMainWindow
{
	Q_OBJECT
	
public:
	MainWindow(QWidget *parent = 0);
	~MainWindow();
};

#endif // MAINWINDOW_H

接着在mianwindow.cpp加入以下代碼:

#include <QtWebKit/QWebView>
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
	: QMainWindow(parent)
{
	setWindowTitle(QString(""));
	setMaximumSize(QSize(800, 480));
	setMinimumSize(QSize(800, 480));
	showMaximized();
	setWindowIcon(QIcon("./SpaceWar/images/logo.jpg"));

	QWebView *pWebView = new QWebView(this);
	setCentralWidget(pWebView);

	pWebView->load("file:///"+QUrl(QFileInfo("./SpaceWar/index.html").absoluteFilePath()));
}

MainWindow::~MainWindow()
{
	
}

注意,QUrl裏面的路徑要是絕對路徑,並且要在取出的絕對路徑前面加上file:///。

再打開main.cpp,加入以下代碼:

#include <QApplication>
#include "mainwindow.h"

int main(int argc, char *argv[])
{
	QApplication a(argc, argv);
	MainWindow w;
	w.show();
	
	return a.exec();
}

按下如圖所示的按鈕運行程序:


運行效果如下:


最後要發佈的時候,要找到幾個dll,如下:

  1. libgcc_s_dw2-1.dll
  2. libstdc++-6.dll
  3. QtCore4.dll
  4. QtGui4.dll
  5. QtNetwork4.dll
  6. QtWebKit4.dll
把這幾個dll放在執行文件目錄下面,就可以了。

如下圖所示:


over,exe就打包完成了。是不是很簡單?

本次講解就到這裏了,歡迎大家捧場~~支持就是最大的鼓勵!

如果文中有疏漏的地方或者大家有任何疑問都歡迎在文章下面留言。


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

歡迎大家轉載我的文章。

轉載請註明:轉自Yorhom's Game Box

http://blog.csdn.net/yorhomwang

歡迎繼續關注我的博客

發佈了67 篇原創文章 · 獲贊 45 · 訪問量 93萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章