存档

2015年2月 的存档

Unity3d 杂七杂八小技巧

2015年2月28日 没有评论



用了半年多的Unity3d,零零碎碎的记了点东西,先前记在个人博客里 ,后来网站改版给删了,现在发这吧 都是我实际用过的,至少在某个版本力是杠杠给力的 Unity3d 杂七杂八小技巧 ,谁用谁知道 Unity3d 杂七杂八小技巧

1、自定义编辑器

1.1、自定义菜单栏

using UnityEditor

[MenuItem ("aaa/bbb")] //必须有子菜单
static void function_called()
{

}

[MenuItem ("aaa/bbb"),true] //已经选中目标为true
static void called_again()
{

}

1.2、自定义组件菜单

using UnityEngine;
using UnityEditor;

//添加该脚本至组件菜单栏中
[AddComponentMenu("aaa/bbb")]
class menuExt : MonoBehaviour {
    
    void Update()
    {
        //自身旋转
        transform.Rotate(0.0f,Time.deltaTime * 200,0.0f);   
    }
}

2、角色控制器

第一人称 & 第三人称 import Package->Character Controller

控制器组件 component->physics->Character Controller然后使用GetComponent<CharacterController>()

控制器方法:

//平面移动
controller.SimpleMove();
//立体移动
controller.Move();

//碰撞中的状态
controller.collisionFlgas == CollisionFlags.Sides

//碰撞会回调
void OnControllerColliderHit(ControllerColliderHit hit)
{
    //碰撞的游戏对象为 hit.gameObject
}

3、工具类

3.1时间

void OnGUI()
{
    GUILayout.Label("当前游戏时间:" + Time.time);
    GUILayout.Label("上一帧所消耗的时间:" + Time.deltaTime);
    GUILayout.Label("固定增量时间:" + Time.fixedTime);
    GUILayout.Label("上一帧所消耗固定时间:" + Time.fixedDeltaTime);
}

3.2等待

waitForScends()可以直接让游戏主线程等待,不过需要修改返回值为IEnumerator。这岂不是很烂?

3.3随机数

Random.Range(startNum,endNum);

4、射线

Ray ray = new Ray(startPos,endPos)
//从摄像机到鼠标之间的射线
Ray ray = Camera.main.ScreenPointToRay(Input.mousePositon)

RaycastHit hit;

//与游戏中的对象相交返回true
if(Physics.Raycast(ray,out hit,num))
{
}

5、点击事件

鼠标键位:左键0,右键1,中建2

//按下
Input.GetMouseButtonDown(num)
//取位置
Input.mousePosition
//抬起
Input.GetMouseButtonUp(num)
//长按
Input.GetMouseButton(num)

6、自定义按键事件

按键事件 Input.GetAxis(InputName)

Edit->Project Settings->Input更改数量Input.GetButton(SelfInputName),返回同点击事件。

7、取子节点

foreach (Transform child in obj.transform) 
{
    child.gameObject.SetActive(b);
}

如果你觉得这篇文章对你有帮助,可以顺手点个,不但不会喜当爹,还能让更多人能看到它… Unity3d 杂七杂八小技巧

分类: unity3d 标签:

Unity3d 在 iOS 上推送( push notification ) 编写

2015年2月26日 没有评论



配置证书

先按照这个生成一大堆证书 http://www.cnblogs.com/gpwzw/archive/2012/03/31/apple_push_notification_services_tutorial_part_1-2.html

其中输入的命令有些错误,可以参考 http://www.tuicool.com/articles/vy2MbmZ

openssl x509 -in aps_development.cer -inform der -out PushChatCert.pem
openssl pkcs12 -nocerts -out PushChatKey.pem -in PushChatKey.p12
cat PushChatCert.pem PushChatKey.pem > ck.pem

配置好证书后,在UnityAppController.mm中的 didFinishLaunchingWithOptions函数添加:

    #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
        if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) {
            // use registerUserNotificationSettings
            [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
            
            [application registerForRemoteNotifications];
        } else {
            // use registerForRemoteNotifications
            [application registerForRemoteNotificationTypes:
             (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound)];
        }
    #else
        // use registerForRemoteNotifications
        [application registerForRemoteNotificationTypes:
         (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound)];
    #endif

这样就完成了对注册的配置,对于Unity3d自带的注册,应该是不兼容IOS8系统

	function Start() {
			NotificationServices.RegisterForRemoteNotificationTypes(RemoteNotificationType.Alert | 
										RemoteNotificationType.Badge | 
										RemoteNotificationType.Sound);
		}
	function Update () {
		if (!tokenSent) {
			var token : byte[] = NotificationServices.deviceToken;
			if (token != null) {
				// send token to a provider
				var hexToken : String = "%" + System.BitConverter.ToString(token).Replace('-', '%');
				new WWW("http://"+address+"/?token="+hexToken);
				tokenSent = true;
			}
		}
	}

如果使用Unity3d可以在didRegisterForRemoteNotificationsWithDeviceToken函数中加入

NSString *tokenStr = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
tokenStr = [tokenStr stringByReplacingOccurrencesOfString:@" " withString:@""];
UnitySendMessage("Bridge", "onPushID",tokenStr.UTF8String);
另外就是使用update来处理token也是不可行的。

获取token成功后,更改didRegisterForRemoteNotificationsWithDeviceToken函数,添加一个消息发送回unity3D,将结果发送给服务器。 我觉得可以使用一个单例,把整个这一套东西抽象出来,回头有功夫整理一下。

如果你觉得这篇文章对你有帮助,可以顺手点个,不但不会喜当爹,还能让更多人能看到它… Unity3d 在 iOS 上推送( push notification ) 编写

分类: unity3d 标签: ,

『HTML5梦幻之旅』 – 炫酷的节日贺卡

2015年2月26日 没有评论

刚过完春节,想必大家收到了各种祝福和贺卡吧~Y某我今年也为同学和家人准备了贺卡。不一样的是,我的贺卡可不是made from树,而是一行行代码凝聚而来的。

考虑到本次开发需要的功能不多,所以就没有用库件了,利用纯Html5 Canvas API来完成本次梦幻之旅:节日贺卡。虽然用到的Canvas API不多,但是效果还是蛮理想的~

首先上截图吧:

『HTML5梦幻之旅』 - 炫酷的节日贺卡

『HTML5梦幻之旅』 - 炫酷的节日贺卡

『HTML5梦幻之旅』 - 炫酷的节日贺卡

哎呀,看到了截图,各位是不是领悟了传说中的炫酷华丽(luàn qī bā zāo)?

测试地址:http://wyh.wjjsoft.com/demo/greeting_card/

大家可以先到测试地址里体验一下玩法,顺便观察一下这些小正方形所组成的文字有什么特点。

一,原理

每次写博客和大家分享技术的时候,我都会先把原理介绍给大家,因为这样一来,大家对下文中的代码理解起来就快多了。所以原理很重要,得作为第一个研究话题。

无论是在测试地址里还是截图中,都不难发现这些文字的特点:是由小正方形拼接而成的,如果说一个小方块是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://wyh.wjjsoft.com/demo/greeting_card/export_array_tool.html

使用方法很简单,就是在画板上用鼠标点击格子。灰黑色的格子代表在demo中有粒子,白色则相反。

『HTML5梦幻之旅』 - 炫酷的节日贺卡

上图就代表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 渲染函数

最后,奉上源代码下载地址:

http://wyh.wjjsoft.com/downloads/greeting_card.zip

本章就到此为止了。欢迎大家交流~

—————————————————————-

欢迎大家转载我的文章。

转载请注明:转自Yorhom’s Game Box

http://blog.csdn.net/yorhomwang

欢迎继续关注我的博客

分类: html5 标签:

Mac Yosemite 配置Apache

2015年2月3日 没有评论



基本知识

下面是Apache的一些基本设置 参考这篇文章

打开“终端(terminal)”,输入

sudo apachectl -v

可显示Apache的版本

接着输入 sudo apachectl start 可以启动Apache。打开Safari浏览器地址栏输入 “http://localhost”,可以看到内容为“It works!”的页面。其位于“/Library(资源库)/WebServer/Documents/”下,这就是Apache的默认根目录。

Apache的安装目录在:/etc/apache2/,etc默认是隐藏的。有三种方式查看:

  1. dock下右键Finder,选择"前往文件夹",输入"/etc"
  2. 在finder下----》前往---》前往文件夹,然后输入/etc
  3. 可以在terminal 输入 "open /etc"

设置虚拟主机

  1. 在终端运行“sudo vi /etc/apache2/httpd.conf”,打开Apche的配置文件
  2. 在httpd.conf中找到“#Include /private/etc/apache2/extra/httpd-vhosts.conf”,去掉前面的“#”,保存并退出。
  3. 运行“sudo apachectl restart”,重启Apache后就开启了虚拟主机配置功能。
  4. 运行“sudo vi /etc/apache2/extra/httpd-vhosts.conf”,就打开了配置虚拟主机文件httpd-vhost.conf,配置虚拟主机了。需要注意的是该文件默认开启了两个作为例子的虚拟主机:
<VirtualHost *:80>
     ServerAdmin webmaster@dummy-host.example.com
     DocumentRoot "/usr/docs/dummy-host.example.com"
     ServerName dummy-host.example.com
     ErrorLog "/private/var/log/apache2/dummy-host.example.com-error_log"
     CustomLog "/private/var/log/apache2/dummy-host.example.com-access_log" common
</VirtualHost>


<VirtualHost *:80>
     ServerAdmin webmaster@dummy-host2.example.com
     DocumentRoot "/usr/docs/dummy-host2.example.com"
     ServerName dummy-host2.example.com
     ErrorLog "/private/var/log/apache2/dummy-host2.example.com-error_log"
     CustomLog "/private/var/log/apache2/dummy-host2.example.com-access_log" common
</VirtualHost>

而实际上,这两个虚拟主机是不存在的,在没有配置任何其他虚拟主机时,可能会导致访问localhost时出现如下提示.

Forbidden
You don't have permission to access /index.php on this server

最简单的办法就是在它们每行前面加上#,注释掉就好了,这样既能参考又不导致其他问题。

增加如下配置

<VirtualHost *:80>
    DocumentRoot "/Library/WebServer/Documents"
    ServerName localhost
    ErrorLog "/private/var/log/apache2/localhost-error_log"
    CustomLog "/private/var/log/apache2/localhost-access_log" common
</VirtualHost> 
 
<VirtualHost *:80>
    DocumentRoot "/Users/fansy/Sites"
    ServerName mysites
    ErrorLog "/private/var/log/apache2/sites-error_log"
    CustomLog "/private/var/log/apache2/sites-access_log" common
    <Directory />
        Options Indexes FollowSymLinks MultiViews
        AllowOverride None
        Order deny,allow
        Allow from all
    </Directory>
</VirtualHost>

保存退出,并重启Apache。

运行sudo vi /etc/hosts,打开hosts配置文件,加入127.0.0.1 mysites,这样就可以配置完成sites虚拟主机了,可以访问http://mysites了,在10.8之前Mac OS X版本其内容和“http://localhost/~[用户名]”完全一致。

注意,记录log的“ErrorLog "/private/var/log/apache2/sites-error_log"”也可以删掉,但记录日志其实是一个好习惯,在出现问题时可以帮助我们判断。如果保留这些log代码,一定log文件路径都是存在的,如果随便修改一个不存在的,会导致Apache无法服务而没有错误提示,这个比较恶心。

Forbidden

经过了上面的设置后 依旧报错 :

Forbidden
You don't have permission to access / on this server.

查询原因是 Yosemite 权限策略更改了。需要更改一些配置,我参考了这篇文章

核心就是打开libphp5.somod_userdir.so这两个库的加载,启用httpd-userdir.conf配置。当然我们刚才配置好的虚拟主机也不耽误,能够一起使用。

分类: 未分类 标签:

微信开发(四)收发消息

2015年2月2日 没有评论



原理

这篇文章主要介绍如何读取用户从手机端发出的信息。当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。以文本为例,XML数据包结构如下:

 <xml>
 <ToUserName><![CDATA[toUser]]></ToUserName>
 <FromUserName><![CDATA[fromUser]]></FromUserName> 
 <CreateTime>1348831860</CreateTime>
 <MsgType><![CDATA[text]]></MsgType>
 <Content><![CDATA[this is a test]]></Content>
 <MsgId>1234567890123456</MsgId>
 </xml>

我们可以使用在线工具 调试这个接口。

wechat

下午在找解决方案的时候,突然找到了这个项目 。搞了半天,有大神已经有这方面的研究了。估计Node.js用这个就成,要是有啥问题,也可以看源码改。可以直接用npm安装:

$ npm install wechat

踏破铁鞋无觅处,得来全不费工夫 微信开发(四)收发消息

分类: 未分类 标签:

微信开发(三)创建菜单

2015年2月2日 没有评论



Overview

如果选择了为微信制定开发版,就不能使用功能中的自定义菜单工具,需要使用代码手动添加 。这篇文章主要介绍如何通过http请求添加自定义菜单。

原理与工具

如果希望添加自己的菜单,需要向微信的一个URL发送创建菜单消息。只需发送一次,即可保存更改。

微信提供了一个调试工具可以直接将编辑好的信息发送给对应的网址,工具的地址在这里

创建菜单

首先取得AccessToken,不知如何操作的童鞋可以参考这里

取得token后将其填写到工具的"access_token"后面,然后在下面的body输入框中输入一个格式如下的json文本:

{
     "button":[
     {  
          "type":"click",
          "name":"今日歌曲",
          "key":"V1001_TODAY_MUSIC"
      },
      {
           "name":"菜单",
           "sub_button":[
           {    
               "type":"view",
               "name":"搜索",
               "url":"http://www.soso.com/"
            },
            {
               "type":"view",
               "name":"视频",
               "url":"http://v.qq.com/"
            },
            {
               "type":"click",
               "name":"赞一下我们",
               "key":"V1001_GOOD"
            }]
       }]
 }

添加的菜单按钮有很多类型,常用的也就3、4种吧,更多用法可以参考这里 的官方文档。

发送添加

填写好后,点选下面的按钮提交即可完成添加。据说是有24小时的延迟,我是发送后直接就生效了。另外,还遇到了一个报错,返回码为40001 过了一会,什么都没改,却能提交成功了,估计是哪里有点小Bug : )

分类: 未分类 标签: