『HTML5夢幻之旅』 - 炫酷的節日賀卡

剛過完春節,想必大家收到了各種祝福和賀卡吧~Y某我今年也爲同學和家人準備了賀卡。不一樣的是,我的賀卡可不是made from樹,而是一行行代碼凝聚而來的。

考慮到本次開發需要的功能不多,所以就沒有用庫件了,利用純Html5 Canvas API來完成本次夢幻之旅:節日賀卡。雖然用到的Canvas API不多,但是效果還是蠻理想的~

首先上截圖吧:






哎呀,看到了截圖,各位是不是領悟了傳說中的炫酷華麗(luàn qī bā zāo)?

測試地址:http://yuehaowang.github.io/demo/greeting_card/index.html

大家可以先到測試地址裏體驗一下玩法,順便觀察一下這些小正方形所組成的文字有什麼特點。


一,原理

每次寫博客和大家分享技術的時候,我都會先把原理介紹給大家,因爲這樣一來,大家對下文中的代碼理解起來就快多了。所以原理很重要,得作爲第一個研究話題。

無論是在測試地址裏還是截圖中,都不難發現這些文字的特點:是由小正方形拼接而成的,如果說一個小方塊是1px,那麼這裏的文字像不像像素遊戲裏的文字?

如何實現這樣的文字呢?我們不妨先從畫像素圖說起。首先,我們知道圖片都是由一像素一像素組成的,如果4px*4px的一張白色圖片可以看成數組,那麼這個數組可以表示爲這樣:

[
[#FFFFFF, #FFFFFF, #FFFFFF, #FFFFFF],
[#FFFFFF, #FFFFFF, #FFFFFF, #FFFFFF],
[#FFFFFF, #FFFFFF, #FFFFFF, #FFFFFF],
[#FFFFFF, #FFFFFF, #FFFFFF, #FFFFFF]
]

如果把第一排第一列的那個像素塗成黑色,那麼數組變成:

[
[#000000, #FFFFFF, #FFFFFF, #FFFFFF],
[#FFFFFF, #FFFFFF, #FFFFFF, #FFFFFF],
[#FFFFFF, #FFFFFF, #FFFFFF, #FFFFFF],
[#FFFFFF, #FFFFFF, #FFFFFF, #FFFFFF]
]
爲了簡化開發,我們設黑色時數組裏表示爲true,白色爲false,那麼上述數組又變成:

[
[true, false, false, false],
[false, false, false, false],
[false, false, false, false],
[false, false, false, false]
]

如果在這張圖上畫個11的字樣,則數組可以表示爲

[
[true, false, false, true],
[true, false, false, true],
[true, false, false, true],
[true, false, false, true]
]

可見如果我們通過數組存取文字的各像素顏色,然後遍歷這個數組來獲取哪些點畫黑色哪些點畫白色,並根據得到的顏色在界面上繪製,就能得出想要的圖案。本次開發的原理就是和此類似。在demo中,粒子(在這裏定義爲由多個小正方形組成帶有拖尾或者正在原地旋轉的一個顯示對象)的顏色是我隨機取出的,所以在數組裏,我們只用記載哪裏該有一個粒子,哪裏不該有。然後在添加噴射出的粒子時,我們記錄下點擊的位置並在剩餘需要有粒子的位置列表中隨機找到這個粒子該到的位置,再隨機取出一個顏色並按照該顏色調用Canvas API進行渲染即可。

在本次開發中,記載哪裏該有粒子哪裏該是空白的數組保存在list.js中(true表示有粒子,false表示沒有粒子):

var list = [[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,true,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,true,true,true,true,true,false,true,true,true,true,true,false,true,true,false,false,false,true,false,false,false,false],[false,false,true,false,false,false,true,false,false,false,false,true,false,true,false,false,false,false,true,false,false,false,true,false,true,false,false,false,true,false,false,true,false,false,false,true,false,false,false,false],[false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,true,false,false,false,true,false,false,true,false,false,false,true,false,false,false,false],[false,false,true,true,true,true,true,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false,true,false,true,false,false,false,true,false,false,true,false,false,false,true,false,false,false,false],[false,false,true,false,false,false,true,false,false,true,true,true,true,true,true,true,false,false,true,true,true,true,true,false,true,true,true,true,true,false,false,true,true,true,true,true,false,false,false,false],[false,false,true,false,false,false,true,false,false,true,false,false,false,false,false,true,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false],[false,false,true,false,false,false,true,false,true,true,false,false,false,false,false,true,true,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,true,false,false,false,true,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,true,true,true,true,true,false,false,false,false],[false,false,false,false,true,false,false,false,false,true,false,true,true,true,true,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,true,false,true,false,false,false,true,false,true,false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false],[false,false,false,true,false,true,false,false,true,false,false,true,true,true,true,false,false,true,false,true,false,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false],[false,false,true,false,false,false,true,false,true,false,false,true,false,false,false,false,false,true,false,true,false,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false],[false,false,true,false,false,false,false,true,false,false,false,true,true,true,true,false,false,false,true,false,true,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false],[false,false,true,false,false,true,false,true,true,true,true,false,false,false,false,true,false,false,false,false,true,true,true,true,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false],[false,false,true,false,false,true,false,true,false,false,false,false,false,false,true,false,true,false,false,false,true,false,false,true,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false],[false,false,true,true,true,true,false,true,true,true,true,false,false,true,false,false,false,true,false,false,true,true,true,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,true,false,true,false,false,false,false,false,true,true,true,true,true,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,true,true,true,true,false,true,true,true,true,false,true,true,false,false,false,true,true,false,true,false,true,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,true,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false]];
這麼大一堆數組就完成了“Happy New Year!”的字樣。

爲了方便,我做了一個編輯文字的工具,在線使用地址:

http://yuehaowang.github.io/demo/greeting_card/export_array_tool.html

使用方法很簡單,就是在畫板上用鼠標點擊格子。灰黑色的格子代表在demo中有粒子,白色則相反。


上圖就代表demo中的粒子需要構成“2015”的字樣。點擊“Export”按鈕生成數組,然後把數組複製粘貼到list.js中,並保存到list變量下即可。

Ok,萬事俱備只欠代碼了。

二,代碼一覽


(1) index.html、common.js和Main.js

先來看html代碼:

<!DOCTYPE html>
<html>
<head>
	<title>Greeting Card</title>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
	<script type="text/javascript" src="./Particle.js"></script>
	<script type="text/javascript" src="./Sprite.js"></script>
	<script type="text/javascript" src="./Txt.js"></script>
	<script type="text/javascript" src="./Stage.js"></script>
	<script type="text/javascript" src="./Main.js"></script>
	<script type="text/javascript" src="./list.js"></script>
	<script type="text/javascript" src="./common.js"></script>
</head>
<body style="margin: 0px; padding: 0px; font-size: 0px">
	<canvas id="mycanvas"></canvas>
</body>
</html>

爲了方便上文所提到的文字編輯器和demo相通(如畫布的大小和文字畫板大小相同,粒子中小正方形大小和畫板格子大小相同),我們準備個common.js來保存這些常量:

var particleW = particleH = 20,
angleToRad = Math.PI / 180,
stageW = 800,
stageH = 480;

particleW和particleH分別代表小正方形寬度、高度;angleToRad是角度和弧度換算率;stageW,stageH是指舞臺的寬度和高度。

然後再是Main.js。Main.js主要是用來處理事件,循環渲染,全屏顯示以及實例化舞臺和文本並提供了獲取哪些地方該有粒子哪些地方不該有粒子的函數接口,當然還有其他的功能不一一列舉了,大夥就直接拿着代碼啃吧。代碼如下:

window.addEventListener("load", main, false);

var canvasTag, ctx;
var canvasStyleWidth, canvasStyleHeight, marginLeft = 0, marginTop = 0;
var isFirefox = true, mobile = false;
var instructionsTxt;
var instructionsIndex = 0, instructionsContents = [
	"Tap to open my greeting card~",
	"Well, continue~",
	"Don't stop tapping until you know my meaning ^_^"
];
var showList = new Array();
var positionList = new Array();

(function (n) {
	isFirefox = (n.toLowerCase().indexOf('firefox') >= 0);

	if (
		n.indexOf("iPhone") > 0
		|| n.indexOf("iPad") > 0
		|| n.indexOf("iPod") > 0
		|| n.indexOf("Android") > 0
		|| n.indexOf("Windows Phone") > 0
		|| n.indexOf("BlackBerry") > 0
	) {
		mobile = true;
	}
})(navigator.userAgent);

function main () {
	canvasTag = document.getElementById("mycanvas");
	canvasTag.width = stageW;
	canvasTag.height = stageH;
	ctx = canvasTag.getContext("2d");

	fullScreen();
	addStage();
	addInstructions();
	getParticlesPosition();
	canvasTag.addEventListener(
		"mouseup",
		function (e) {
			if (instructionsIndex++ >= instructionsContents.length - 1) {
				instructionsTxt.visible = false;
			} else {
				instructionsTxt.text = instructionsContents[instructionsIndex];
			}

			if (e.offsetX == null && e.layerX != null) {
				e.offsetX = e.layerX;
				e.offsetY = e.layerY;
			}

			var startX = scaleOffsetX(e.offsetX),
			startY = scaleOffsetY(e.offsetY);

			for (var i = 0; i < 5; i++) {
				addParticle(startX, startY);
			}
		},
		false
	);

	setInterval(function () {
		loop();
	}, 1000/60);
}

function fullScreen () {
	var w = stageW, h = stageH, ww = window.innerWidth, wh = window.innerHeight;

	if (mobile) {
		if (ww / wh > stageW / stageH) {
			h = wh;
			w = stageW * wh / stageH;
		} else {
			w = ww;
			h = stageH * ww / stageW;
		}
	}

	canvasTag.style.width = w + "px";
	canvasTag.style.height = h + "px";
	canvasTag.style.marginLeft = (ww - w) / 2 + "px";
	canvasTag.style.marginTop = (wh - h) / 2 + "px";

	canvasStyleWidth = w;
	canvasStyleHeight = h;

	if (isFirefox) {
		marginLeft = parseInt(canvasTag.style.marginLeft);
		marginTop = parseInt(canvasTag.style.marginTop);
	}
}

function addStage () {
	var stage = new Stage();
	showList.push(stage);
}

function addInstructions () {
	instructionsTxt = new Txt(instructionsContents[instructionsIndex]);
	showList.push(instructionsTxt);
}

function getParticlesPosition () {
	for (var i = 0, l = list.length; i < l; i++) {
		var item = list[i];

		for (var j = 0, n = item.length; j < n; j++) {
			if (item[j]) {
				positionList.push({x : j * particleW, y : i * particleH});
			}
		}
	}
}

function addParticle (startX, startY) {
	var index = Math.floor(Math.random() * (positionList.length - 1)),
	pos = positionList[index];

	if (!pos) {
		return;
	}

	var particle = new Particle(startX, startY, pos.x, pos.y);
	showList.push(particle);

	positionList.splice(index, 1);
}

function scaleOffsetX (v) {
	return (v - marginLeft) * stageW / canvasStyleWidth;
}

function scaleOffsetY (v) {
	return (v - marginTop) * stageH / canvasStyleHeight;
}

function loop () {
	ctx.clearRect(0, 0, canvasTag.width, canvasTag.height);

	for (var i = 0, l = showList.length; i < l; i++) {
		showList[i].loop();
	}
}

由於要放到移動端運行,所以在針對移動端做了點處理。首先介紹幾個全局變量:

canvasTag 通過document.getElementById取出的一個canvas標籤對象

ctx canvasTag.getContext獲取的一個CanvasRenderingContext2D對象

canvasStyleWidth、canvasStyleHeight 這兩個變量分別記載canvasTag的style屬性中這是的width和height;用於處理全屏拉伸後,鼠標事件失靈的問題

marginLeft、marginTop 記載canvasTag的style中marginLeft,marginTop;用於處理Firefox等瀏覽器中,在canvasTag的位置移動後鼠標事件取出的layerX和layerY不是相對畫布左上角座標的問題(換句話說就是讓點擊的位置成爲粒子發射的位置)

isFirefox、mobile 這倆是用來判斷是否是Firefox瀏覽器和移動端的變量

instructionsTxt 這個是一個Txt對象,負責顯示說明文本(說明文本是什麼?@_@!就是demo一開始那個蠱惑你點擊屏幕的傢伙)

instructionsIndex、instructionsContents 前者是記載說明到了那一步,後者是記載說明內容的一個數組

showList 顯示列表,把需要渲染的對象扔進這個數組,就可以使該對象重複渲染了

positionList 記載哪些地方可以出現粒子

呼~全局變量終於介紹完了,繼續往下看:

(function (n) {
	isFirefox = (n.toLowerCase().indexOf('firefox') >= 0);

	if (
		n.indexOf("iPhone") > 0
		|| n.indexOf("iPad") > 0
		|| n.indexOf("iPod") > 0
		|| n.indexOf("Android") > 0
		|| n.indexOf("Windows Phone") > 0
		|| n.indexOf("BlackBerry") > 0
	) {
		mobile = true;
	}
})(navigator.userAgent);
在這個匿名函數裏給isFirefox和mobile賦值。

在接下來的main函數中,任勞任怨的main需要調用全屏顯示函數,加入舞臺函數,加入說明文本函數,獲取粒子位置函數,加入事件函數,並且還要驅動循環渲染。

值得一看的是事件部分:

canvasTag.addEventListener(
	"mouseup",
	function (e) {
		if (instructionsIndex++ >= instructionsContents.length - 1) {
			instructionsTxt.visible = false;
		} else {
			instructionsTxt.text = instructionsContents[instructionsIndex];
		}

		if (e.offsetX == null && e.layerX != null) {
			e.offsetX = e.layerX;
			e.offsetY = e.layerY;
		}

		var startX = scaleOffsetX(e.offsetX),
		startY = scaleOffsetY(e.offsetY);

		for (var i = 0; i < 5; i++) {
			addParticle(startX, startY);
		}
	},
	false
);
在某些瀏覽器中獲取點擊位置不是用offsetX和offsetY而是layerX和layerY。所以在這裏我們需要統一一下。由於在移動端我做了全屏拉伸處理,所以用scaleOffsetX和scaleOffsetY來矯正鼠標位置。


(2) Stage.js和Txt.js

Stage這個類在Main.js中得到實例,並加入顯示列表。Stage類的代碼如下:

function Stage () {
	var s = this;

	s.width = canvasTag.width;
	s.height = canvasTag.height;
	s.bgColor = ctx.createRadialGradient(s.width / 2, s.height / 2, 10, s.width / 2, s.height / 2, s.width * 0.6);
	s.bgColor.addColorStop(0.3, "#CCCCCC");
	s.bgColor.addColorStop(1.0, "#FFFFFF");
}

Stage.prototype = {
	loop : function() {
		var s = this;

		ctx.save();
		ctx.beginPath();
		ctx.fillStyle = s.bgColor;
		ctx.rect(0, 0, s.width, s.height);
		ctx.fill();
		if (s.isShowInstructions) {
			ctx.fillStyle = "black";
			ctx.font = "bold 20px sans-serif";
			ctx.textAlign = "center";
			ctx.textBaseline = "middle";
			ctx.fillText("Tap to open greeting card~", stageCenterX, stageCenterY);
		}
		ctx.restore();
	}
};
在loop函數中,我們進行渲染,把舞臺渲染出來。

再是Txt.js:

function Txt (text) {
	var s = this;

	s.x = stageW / 2;
	s.y = stageH / 2;
	s.text = text || "";
	s.visible = true;
}

Txt.prototype = {
	loop : function() {
		var s = this;

		if (!s.visible) {
			return;
		}

		ctx.save();
		ctx.fillStyle = "black";
		ctx.font = "bold 20pt sans-serif";
		ctx.textAlign = "center";
		ctx.textBaseline = "middle";
		ctx.fillText(s.text, s.x, s.y);
		ctx.restore();
	}
};
和Stage類似,通過loop來進行渲染。不同的是多了個visible屬性來控制是否顯示。畢竟Txt對象在demo中是可以消失的。


(3) Particle.js和Sprite.js

前面也介紹了,Particle是指許多小正方形組成的一個有拖尾的顯示對象。而小正方形就是Sprite了。

先來看看Particle的代碼:

function Particle (startX, startY, endX, endY) {
	var s = this;

	s.x = startX;
	s.y = startY;
	s.rotation = 0;
	s.endX = endX;
	s.endY = endY;
	s.displacement = Math.sqrt((startX - endX) * (startX - endX) + (startY - endY) * (startY - endY));
	s.stepLength = 7;
	s.stepNum = s.displacement / s.stepLength;
	s.stepIndex = 0;
	s.stopAddSprite = false;
	s.moveCos = (endX - startX) / s.displacement;
	s.moveSin = (endY - startY) / s.displacement;
	s.childList = new Array();
	s.removeList = new Array();
	s.color = Particle.COLOR_LIST[Math.round(Math.random() * (Particle.COLOR_LIST.length - 1))];
}

Particle.COLOR_LIST = [
	"#990000",
	"#FF0000",
	"#CC3300",
	"#CC6600",
	"#CC0033",
	"#FFFF00",
	"#33FF00",
	"#33CC00",
	"#0066FF",
	"#00FF99",
	"#330099",
	"#990033",
	"#000099"
];

Particle.prototype = {
	loop : function () {
		var s = this;

		s.loopChild();
		s.clearRemoveList();
		s.updateShowProperites();
		s.addChildSprite();
	},

	loopChild : function () {
		var s = this;

		for (var i = 0, l = s.childList.length; i < l; i++) {
			var o = s.childList[i];

			if (!o) {
				continue;
			}

			o.loop();

			if (o.mode == Sprite.MODE_DISAPPEAR) {
				s.removeList.push(o);
			}
		}
	},

	clearRemoveList : function () {
		var s = this;

		for (var j = 0, m = s.removeList.length; j < m; j++) {
			var toRemoveObj = s.removeList[j];

			for (var k = 0, n = s.childList.length; k < n; k++) {
				if (s.childList[k].index == toRemoveObj.index) {
					s.childList.splice(k, 1);

					break;
				}
			}
		}

		s.removeList.splice(0, s.removeList.length);
	},

	updateShowProperites : function () {
		var s = this;

		s.x += s.stepLength * s.moveCos;
		s.y += s.stepLength * s.moveSin;
		s.rotation += 10;
	},

	addChildSprite : function () {
		var s = this;

		if (s.stopAddSprite) {
			return;
		}

		if (++s.stepIndex >= s.stepNum) {
			s.x = s.endX;
			s.y = s.endY;

			var sprite = new Sprite(s.x, s.y, s.rotation, true);
			sprite.color = s.color;
			s.childList.push(sprite);

			s.stopAddSprite = true;

			return;
		}

		var sprite = new Sprite(s.x, s.y, s.rotation, false);
		sprite.color = s.color;
		s.childList.push(sprite);
	}
};
這個類就相對於前幾個要複雜一些了,接受四個參數,分別是[開始x座標, 開始y座標, 終點x座標, 終點y座標]。首先是對其屬性的介紹:

x、y、rotation 分別表示x座標,y座標,旋轉角度

endX、endY 記錄粒子要到的位置

displacement 傳說中物理學裏的位移!!!(根據高中物理必修一的知識,位移是矢量,但是這裏我直接賦值爲標量了,祈禱俺的物理老師沒有看到這裏吧)

stepLength 粒子每次移動時,移動的長度

stepNum 計算一下粒子移動到目的地需要多少步

stepIndex 粒子已經移動了多少步,用於判斷粒子是否該停下了

stopAddSprite 用於判斷是否停止添加拖尾小正方形

moveCos、moveSin displacement代表起始點和終點中點連線的長度,stepLength的方向也是沿着該線的。我們移動對象只能移動x,y座標,所以計算出該線的cos和sin值以便算出x方向上的增量和y方向上的增量,從而達到按任意角度移動的對象。

childList 成員列表,裝載小正方形拖尾的數組

removeList 小正方形的透明度降爲0或小於0後,需要移除這些小正方形,所以把這些小正方形加到移除列表removeList中,然後等渲染完畢後遍歷移除列表,從childList中移除需要移除的對象。

color 粒子的顏色,是從Particle.COLOR_LIST中隨機取出的


Ok,再來看看成員函數介紹,具體的代碼大家對照看吧,我只介紹一下函數執行的邏輯和功能:

loop 這個函數作爲其他函數的入口

loopChild 從childList中取出小正方形Sprite對象進行渲染,並把透明度爲0或小於0的小正方形放入removeList中。需要注意的是,我判斷小正方形是否需要移除使用的是Sprite的mode屬性,其實這個mode屬性在Sprite透明度變爲0或小於0時就會變成Sprite.MODE_DISAPPEAR,具體的代碼見下文Sprite部分。

clearRemoveList 進行清理需要移除的小正方形。爲什麼不直接在loopChild函數裏進行移除操作而要準備個移除列表在渲染完成後進行移除呢?那是因爲你在循環渲染時,是在遍歷childList,如果立刻刪除需要刪除的元素,就會破壞開始遍歷時childList的結構,這樣一來就可能出現小正方形閃爍的現象。

updateShowProperites 更新粒子的位置和旋轉的角度

addChildSprite 加入小正方形實現拖尾效果

至此,Particle介紹完畢。由代碼可知,Particle並沒有進行渲染,而是通過childList中的Sprite對象來進行的。所以該到介紹Sprite的時候了:

function Sprite (x, y, rotation, cannotDisappear) {
	var s = this;

	s.x = x;
	s.y = y;
	s.index = Sprite.INDEX++;
	s.rotation = rotation;
	s.alpha = 1;
	s.mode = Sprite.MODE_APPEAR;
	s.cannotDisappear = cannotDisappear;
	s.color = "#FFFFFF";
	s.startDrawX = -particleW / 2;
	s.startDrawY = -particleH / 2;
}

Sprite.INDEX = 0;

Sprite.MODE_APPEAR = "appear";
Sprite.MODE_DISAPPEAR = "disappear";

Sprite.prototype = {
	loop : function () {
		var s = this;

		ctx.save();
		ctx.beginPath();
		ctx.translate(s.x, s.y);
		ctx.rotate(s.rotation * angleToRad);
		ctx.globalAlpha = s.alpha;
		ctx.rect(s.startDrawX, s.startDrawY, particleW, particleH);
		ctx.fillStyle = s.color;
		ctx.fill();
		ctx.restore();

		if (s.cannotDisappear) {
			s.rotation += 5;

			return;
		}

		s.alpha -= 0.05;

		if (s.alpha <= 0) {
			s.mode = Sprite.MODE_DISAPPEAR;
		}
	}
};

參數介紹:

x 繪製的x座標

y 繪製的y座標

rotation 旋轉角度

cannotDisppear 是否減少透明度並可以被移除。如果該Sprite作爲的是拖尾中的小正方形那麼這個參數爲false,如果是停止後原地旋轉的小正方形則爲true


屬性介紹:

x、y、rotation 同Particle類的x、y、rotation

index 對象編號,移除Sprite時會用到,相當於對象的身份證

alpha 透明度

mode 爲Sprite.MODE_APPEAR、Sprite.MODE_DISAPPEAR;前者表示正常顯示,後者表示透明度降爲0或0以下,需要移除此對象

color 小正方形顏色,由裝載它的Particle的color決定

startDrawX、startDrawY 由於小正方形旋轉時是按中心旋轉的,所以繪製矩形時其實座標不能爲0,而是由這兩個屬性決定矩形的起始點

cannotDisppear 見參數cannotDisppear


函數介紹:

loop 渲染函數

最後,奉上源代碼Github地址:

https://github.com/yuehaowang/greeting_card


本章就到此爲止了。歡迎大家交流~

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

歡迎大家轉載我的文章。

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

http://blog.csdn.net/yorhomwang

歡迎繼續關注我的博客

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章