马宁的嵌入式开发研究

Windows Phone, XNA, Windows Embedded, Windows Mobile
posts - 80, comments - 676, trackbacks - 17, articles - 0

2011年10月3日

作者:马宁

源代码下载地址:

http://files.cnblogs.com/aawolf/OpenXLiveAnalytics.zip

 

在Windows Phone中很多应用需要数据查询功能,来确定每天的用户数和应用使用次数等关键信息。在iOS和Android中已经有非常多的类似应用了,比如Flurry, 友盟等,但是Windows Phone上只有Flurry提供了此类功能。但由于很多开发者来说,没有中文支持的Flurry有些可望而不可即。

其实,在OpenXLive也提供数据分析的支持,只是之前更多是用于游戏数据的统计和查询上。在这篇文章里,我们将介绍,如何将OpenXLive的数据分析功能应用在Windows Phone的应用程序开发中。

创建OpenXLive应用

因为所有的统计数据都是保存在OpenXLive的云端存储服务器中,所以,我们要在OpenXLive网站上创建一个应用。

首先,登录OpenXLive网站(http://www.openxlive.com/),如果没有帐号,我们需要创建一个相应的帐号,通过电子邮件激活帐号之后,我们还需要升级成为开发者。

在激活邮件地址后,我们重新回到OpenXLive的网站,输入用户名和密码后,我们会进入用户资料编辑界面,在这里,我们可以填写完整的个人信息,使得朋友在网络中更加容易地找到你。用户资料中最后一项为“Developer Information”。

image

点击“Developer Information”,进入开发者申请界面:

image

在这个页面,我们要填写开发者类型(个人/公司/学生)、国家、城市、地址、电话、IM等信息。在填写完成后,我们会看到下面的开发者注册成功界面。

image

在这个界面中,我们可以进入OpenXLive开发者首页,查看开发指南,也可以直接点击“Create New Game”,进入开发者后台。

进入开发者后台界面之后,如果您之前已经创建过游戏,会显示已创建游戏的列表,如上图所示。在用户图标的下方,有Create New Game的按钮,点击,进入创建新游戏的界面。

image

添加OpenXLive引用

为了引用OpenXLive的程序集,我们必须首先下载OpenXLive SDK并且完成安装。下载地址是:http://developer.openxlive.com/sdk/download/ 目前的版本是0.9.6.

创建Windows Phone Silverlight应用后,我们要获取OpenXLive的程序集。在开始菜单中,找到OpenXLive应用,其中OpenXLive目录下包括XNA和Silverlight两个目录。我们从Silverlight目录中取出OpenXLive.dll程序集,拷贝至我们的应用所在的目录。

然后在Solution Explorer中,右键单击References,选择Add Reference。在对话框中选择Browse页面,然后找到OpenXLive.dll,将其添加到工程中。

因为我们不需要OpenXLive的界面,所以只需要添加OpenXLive.dll即可。

image

在添加完引用之后,我们就可以添加代码了。

创建Session

接下来,我们要为工程添加OpenXLive的引用代码。打开工程中的App.xaml.cs文件,首先找到Application_Launching方法:

private void Application_Launching(object sender, LaunchingEventArgs e)
{
    if (XLiveGameManager.CurrentSession == null)
    {
        GameSession session = XLiveGameManager.CreateSession("xxxxxxxx");
        session.CreateSessionCompleted += new AsyncEventHandler(session_CreateSessionCompleted);
        session.Open();
    }
}

我们在这个方法里判断是否已经有Game Session存在,如果没有的话,则调用XLiveGameManager的CreateSession方法来创建一个Game Session,需要传递的参数,是我们在OpenXLive网站上获得的Secret Key。

然后,我们可以添加一个CreateSessionCompleted事件处理函数,来获取Game Session是否被创建的事件。当然,这个事件处理函数是可选的,我们可以不添加。

最后,调用Game Session的Open方法,来打开Game Session就可以了。

然后是关闭这个Game Session,在Application_Closing方法里调用Close方法。

private void Application_Closing(object sender, ClosingEventArgs e)
{
    if (XLiveGameManager.CurrentSession != null)
    {
        XLiveGameManager.CurrentSession.Close();
    }
}

如果我们不显式调用Close方法的话,Game Session在创建24小时后会自动过期,所以也不会造成太大的影响,唯一的问题是游戏时间将失效。

为了支持墓碑机制,我们还要特意在Application_Activated和Application_Deactivated函数中,添加对于墓碑机制的处理。

 

private void Application_Activated(object sender, ActivatedEventArgs e)
{
    XLiveGameManager.Activated();
}

private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
    XLiveGameManager.Deactivated();
}

 

好了,到这里我们就大功告成了,够简单吧?

查看数据结果

当我们的Windows Phone软件发布之后,就可以在OpenXLive网站上查看应用程序的在线数据了。

首先,我们访问OpenXLive网站http://www.openxlive.com/ ,在网页右上角点击Login登录,成功登录之后,我们会在OpenXLive首页右上角的位置看到下面的网页:

image

点击My Game,可以进入游戏后台管理的Dashboard中。如果用户登录后,访问OpenXLive开发者网站(http://developer.openxlive.com/),在右侧的Dashboard中选择“Managed your games”也可以进入开发者后台管理界面。

image

上图所示,包括我们创建的所有游戏和应用,点击应用图标会进入应用的产业页面,点击应用标题,则进入应用的管理界面。

image

点击管理界面中的Statistics按钮,进入数据分析界面。

在数据分析界面中,我们可以查看一些通用信息,比如创建Session的数量、使用人数等信息。点击View,则进入详细数据分析界面。

image

在详细数据分析界面中,我们可以考到用户数、Session数的时间分布,按照月、周、日来进行显示等。

image

写在最后

好了,到这里,我们就介绍完OpenXLive目前的数据分析功能。下一步,OpenXLive还会加入自定义事件、地理分布信息等功能。

posted @ 2011-10-03 17:36 马宁 阅读(1262) 评论(6) 编辑

2011年8月25日

作者:马宁

终于可以坐下来继续这个系列了,在微博和博客园里,已经有无数人催过了,实在抱歉,一个人的精力毕竟是有限的,但我会抽出一切可用的时间,尽可能尽快完成。

这一章我们来说说景深数据Depth Data,这是Kinect的Depth Camera为我们提供的全新功能,以往的技术只能够通过图像识别来完成的一些工作,我们可以借助景深来帮我们完成了。比如,前景与背景的分离,以前只能将背景设置为蓝屏或者绿屏,但是现在有了景深数据,我们就可以很容易地将前景物体从背景中分离出来。当然,需要特别说明的是,Depth Camera技术是由以色列公司PrimeSense提供的。

程序布局

在这一章里,我们要完成的工作非常简单,根据物体距离Kinect的远近,设置成不同的颜色。首先,我们要创建一个新的WPF工程,然后添加一个Image控件:

<Image Height="240" HorizontalAlignment="Left" Margin="62,41,0,0" Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="320" />

然后是MainWindow.xaml.cs中的核心代码,这部分代码与之前的代码大体一致,所以不做过多解释了:

//Kinect RuntimeRuntime 

Runtime nui = new Runtime(); 

private void Window_Loaded(object sender, RoutedEventArgs e)

{

//UseDepthAndPlayerIndex and UseSkeletalTracking

nui.Initialize(RuntimeOptions.UseDepthAndPlayerIndex | RuntimeOptions.UseSkeletalTracking);

//register for event

nui.DepthFrameReady += new EventHandler<ImageFrameReadyEventArgs>(nui_DepthFrameReady);

//DepthAndPlayerIndex ImageType

nui.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution320x240,

ImageType.DepthAndPlayerIndex);

}

private void Window_Closed(object sender, EventArgs e)

{

nui.Uninitialize();

}

 

唯一需要大家注意的是,我们在初始化函数中传递的参数是RuntimeOptions.UseDepthAndPlayerIndex,这表示景深信息中会包含PlayerIndex的信息。接下来,我们就要花点时间来理解DepthAndPlayerIndex的实际结构。

理解DepthAndPlayerIndex

先来看DepthFrameReady事件处理函数,如下:

void nui_DepthFrameReady(object sender, ImageFrameReadyEventArgs e)

{

// Convert depth information for a pixel into color information 

byte[] ColoredBytes = GenerateColoredBytes(e.ImageFrame); 

// create an image based on the colored bytes 

PlanarImage image = e.ImageFrame.Image;

image1.Source = BitmapSource.Create( 

image.Width, image.Height, 96, 96, PixelFormats.Bgr32, 

null, ColoredBytes, image.Width * PixelFormats.Bgr32.BitsPerPixel / 8); 

}

 

DepthFrameReady事件会返回一个ImageFrame对象,其中会包含一个PlanarImage对象。PlanarImage对象会包含一个byte[]数组,这个数组中包含每个像素的深度信息。这个数组的特点是,从图像左上角开始、从左到右,然后从上到下。

该数组中每个像素由两个bytes表示,我们需要通过位运算的办法来获取每个像素的位置信息。

如果是Depth Image Type,将第二个bytes左移8位即可。

Distance (0,0) = (int)(Bits[0] | Bits[1] << 8 );
 

如果是DepthAndPlayerIndex Image Type,第一个bytes右移三位去掉Player Index信息,第二个bytes左移5位。

Distance (0,0) =(int)(Bits[0] >> 3 | Bits[1] << 5);
 

DepthAndPlayerIndex image类型的第一个bytes前三位包括Player Index信息。Player Index最多包含六个可能值:

0, 没有玩家;1,玩家1;2,玩家2,以此类推。

我们可以用下面的方法来获取Player Index信息:

private static int GetPlayerIndex(byte firstFrame)

{

//returns 0 = no player, 1 = 1st player, 2 = 2nd player...

return (int)firstFrame & 7; 

}

 

设置颜色

接下来,我们创建一个bytes数组,用来存储颜色值。

if (distance <= 900)

{

//we are very close

colorFrame[index + BlueIndex] = 255;

colorFrame[index + GreenIndex] = 0;

colorFrame[index + RedIndex] = 0;

}

if (GetPlayerIndex(depthData[depthIndex]) > 0)

{

//we are the farthest

colorFrame[index + BlueIndex] = 0;

colorFrame[index + GreenIndex] = 255;

colorFrame[index + RedIndex] = 255;

}

当然,我们最好是设置几个边界值,来定义不同距离的不同颜色:

//equal coloring for monochromatic histogram

var intensity = CalculateIntensityFromDepth(distance);

colorFrame[index + BlueIndex] = intensity;

colorFrame[index + GreenIndex] = intensity;

colorFrame[index + RedIndex] = intensity;

const float MaxDepthDistance = 4000; // max value returned

const float MinDepthDistance = 850; // min value returned

const float MaxDepthDistanceOffset = MaxDepthDistance - MinDepthDistance;

public static byte CalculateIntensityFromDepth(int distance)

{

//formula for calculating monochrome intensity for histogram

return (byte)(255 - (255 * Math.Max(distance - MinDepthDistance, 0) / (MaxDepthDistanceOffset)));

}

最后,把所有代码放在一起,我们设定的颜色值为:

蓝色,小于900;900到2000,绿色;大于2000,红色

代码如下:

        private byte[] GenerateColoredBytes(ImageFrame imageFrame)
        {    
            int height = imageFrame.Image.Height;    
            int width = imageFrame.Image.Width;     
            
            //Depth data for each pixel    
            Byte[] depthData = imageFrame.Image.Bits;      
            
            //colorFrame contains color information for all pixels in image    
            //Height x Width x 4 (Red, Green, Blue, empty byte)    
            Byte[] colorFrame = new byte[imageFrame.Image.Height * imageFrame.Image.Width * 4];     
            
            //Bgr32  - Blue, Green, Red, empty byte    
            //Bgra32 - Blue, Green, Red, transparency     
            //You must set transparency for Bgra as .NET defaults a byte to 0 = fully transparent     
            //hardcoded locations to Blue, Green, Red (BGR) index positions           
            const int BlueIndex = 0;    
            const int GreenIndex = 1;    
            const int RedIndex = 2;     
            var depthIndex = 0;    
            
            for (var y = 0; y < height; y++)    
            {                
                var heightOffset = y * width;         
                
                for (var x = 0; x < width; x++)        
                {            
                    var index = ((width - x - 1) + heightOffset) * 4;             
                    
                    //var distance = GetDistance(depthData[depthIndex], depthData[depthIndex + 1]);            
                    var distance = GetDistanceWithPlayerIndex(depthData[depthIndex], depthData[depthIndex + 1]);             
                    
                    if (distance <= 900)            
                    {                
                        //we are very close                
                        colorFrame[index + BlueIndex] = 255;                
                        colorFrame[index + GreenIndex] = 0;                
                        colorFrame[index + RedIndex] = 0;                   
                    }            
                    else if (distance > 900 && distance < 2000)            
                    {                
                        //we are a bit further away                
                        colorFrame[index + BlueIndex] = 0;                
                        colorFrame[index + GreenIndex] = 255;                
                        colorFrame[index + RedIndex] = 0;            
                    }            
                    else if (distance > 2000)            
                    {                
                        //we are the farthest                
                        colorFrame[index + BlueIndex] = 0;                
                        colorFrame[index + GreenIndex] = 0;                
                        colorFrame[index + RedIndex] = 255;            
                    }             
                    
                    //Color a player            
                    if (GetPlayerIndex(depthData[depthIndex]) > 0)            
                    {                
                        //we are the farthest                
                        colorFrame[index + BlueIndex] = 0;                
                        colorFrame[index + GreenIndex] = 255;                
                        colorFrame[index + RedIndex] = 255;            
                    }             
                    
                    //jump two bytes at a time            
                    depthIndex += 2;        
                
                }    
            }     
            
            return colorFrame;
        }

程序的现实效果如下图:

clip_image001

写到最后

到这里,我们就将Kinect SDK中NUI的基本内容介绍完了,接下来,我们要介绍Audio部分,或者借助一些实际的例子,来看一下Kinect SDK的实际应用。

posted @ 2011-08-25 22:26 马宁 阅读(3154) 评论(9) 编辑

2011年8月3日

作者:马宁

前边介绍Push Notification时,其实已经谈到了Tile Notification。在Windows Phone 7.1中,Smart Tile得到了极大的提高。我们不但可以控制Tile的动画显示、内容和背景切换,而且还能够为同一个应用提供两个Tile,比如一个天气预报的应用程序,就可以在手机的首页上显示多个Tile,一个是北京的天气,另一个是上海的天气等。

实现Application Tile

MSDN上的文章写的又臭又长,其实挺简单的事情,弄得那么复杂。我试着改写了一下例子,加入到应用的一个Button点击事件里:

            // Application Tile is always the first Tile, even if it is not pinned to Start.
            ShellTile TileToFind = ShellTile.ActiveTiles.First();

            // Set the properties to update for the Application Tile.
            // Empty strings for the text values and URIs will result in the property being cleared.
            StandardTileData NewTileData = new StandardTileData
            {
                Title = "Title",
                BackgroundImage = new Uri("Background.png", UriKind.Relative),
                Count = 2,
                BackTitle = "Back Tilte",
                BackBackgroundImage = new Uri("Background2.png", UriKind.Relative),
                BackContent = "This is BackContent"
            };

            // Update the Application Tile
            TileToFind.Update(NewTileData);

首先,我们要引用Microsoft.Phone.Shell命名空间,然后通过ShellTile.ActiveTiles.First()方法来获取应用最主要的ShellTile.每个应用至少会有一个Tile,所以不用担心该对象为空。然后我们创建一个StandardTileData对象,为其中的属性赋值。属性分为两组,每组都会有背景图片、标题和内容,显示位置如下图所示。如果设置两组Tile属性,则Tile在显示一段时间后会自动切换。最后,我们调用ShellTile对象的Update方法,将StandardTileData对象传递进去,就完成了新Tile的设置。BackgroundImage和BackBackgroundImage指定的是图片的URI,可以是本地的图片,也可以是来自网络的图片。我们使用的两个图片,都是以Content方式加入到工程中的图片。

clip_image002

当我们将程序部署到设备或模拟器上时,首先会在Application List里出现对应的图标。我们长按图标,会出现一个菜单,选择Pin to start,会将应用程序的图标显示到手机首页上。

运行应用程序,点击Button后,Tile会被更新成新的式样。两张背景和内容会交替显示,显示效果如下图所示:

clip_image004 clip_image006 clip_image008

实现 Secondary Tile

在完成了Application Tile的显示之后,我们接下来要实现更复杂的Secondary Tile,当然,我们可以添加多个Tile。在实现Secondary Tile时,有两个技术点需要实现,一个是Tile的添加与显示;另一个则是,程序启动时如何区分是由哪个Tile点击启动应用的。

首先,我们来看如何添加Secondary Tile的代码:

            // Look to see whether the Tile already exists and if so, don't try to create it again.
            ShellTile TileToFind = ShellTile.ActiveTiles.FirstOrDefault(x => x.NavigationUri.ToString().Contains("DefaultTitle=FromTile"));

            // Create the Tile if we didn't find that it already exists.
            if (TileToFind == null)
            {
                // Create the Tile object and set some initial properties for the Tile.
                // The Count value of 12 shows the number 12 on the front of the Tile. Valid values are 1-99.
                // A Count value of 0 indicates that the Count should not be displayed.
                StandardTileData NewTileData = new StandardTileData
                {
                    BackgroundImage = new Uri("Background.png", UriKind.Relative),
                    Title = "Secondary Tile",
                    Count = 12,
                    BackTitle = "Back of Tile",
                    BackContent = "Welcome to the back of the Tile",
                    BackBackgroundImage = new Uri("Background2.png", UriKind.Relative)
                };

                // Create the Tile and pin it to Start. This will cause a navigation to Start and a deactivation of our application.
                ShellTile.Create(new Uri("/MainPage.xaml?DefaultTitle=FromTile", UriKind.Relative), NewTileData);
            }
            else
            {
                TileToFind.Delete();
            }

首先调用ShellTile.ActiveTiles.FirstOrDefault方法,获取在NavigationUri属性中包含“DefaultTitle=FromTile”字样的ShellTile对象,如果获取到的ShellTile对象为空,则创建Secondary Tile,否则调用ShellTile的Delete方法,删除这个Tile。

创建Secondary Tile的过程,首先创建StandardTileData对象,将显示的各个参数进行赋值,这一步与Application Tile相同;然后,调用ShellTile的Create方法,第一个参数是URI,即点击该Tile后,调用Page的命令行,可以包含参数,第二个参数是StandardTileData对象,用于指定显示式样。

需要注意的是,如果Page命令行指定不对,会引起用户点击Tile时,应用直接退出,由于无法调试,第一次接触这个问题时,会找不到具体的原因。

Secondary Tile显示效果如下:

clip_image010 clip_image012

接下来,我们就要处理区分不同Tile点击的事件了,需要重载MainForm的OnNavigatedTo方法:

        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            string value = null;

            if (this.NavigationContext.QueryString != null)
            {
                this.NavigationContext.QueryString.TryGetValue("DefaultTitle", out value);
                if (value != null)
                {
                    textBoxTitle.Text = value;
                }
                else
                {
                    textBoxTitle.Text = "FromMain";
                }
            }

            base.OnNavigatedTo(e);
        }

通过NavigationContext.QueryString的TryGetValue方法来获取DefaultTitle参数的值,如果无法获取到该值,则认为该点击来自第一个Tile,相反则来自第二个Tile。我们将参数显示在主页面的文本框里,当然,实际应用中可以有更加复杂的业务逻辑。界面显示效果如下:

clip_image014

到这里,我们简单介绍了如何添加Tile、修改Tile内容,除此之外,还有如何修改Tile的初始值,指定Tile更新的时间间隔等。在这里,我们就不介绍了,大家可以参考又臭又长的MSDN文档,这部分不长,我可以保证。

写在最后

好了,到这里,我们正式将Windows Phone 7.1中的Tile编程的内容介绍完了。Windows Phone采用的是HUB方式,对于功能进行分类,所以,就要求Tile具备更丰富的显示方式和展现形式。而且,多个应用可以对应于一个Tile,同样一个应用也可以对应多个Tile,这样极大提高了主页与应用之间交互的丰富程度。

当然,具体的Tile,还要在实际的开发中使用,才能真正了解其优缺点。

OpenXLive杯Windows Phone游戏开发大赛

clip_image016

OpenXLive杯Windows Phone游戏开发大赛,是由OpenXLive联合国内知名的开发者社区:DevDiv、智机网、WPMind、Silverlight银光中国和XNA游戏世界,一起举办的针对Windows Phone游戏开发的比赛。

http://www.openxlive.net/posts/news/40

posted @ 2011-08-03 02:36 马宁 阅读(3507) 评论(7) 编辑

2011年8月2日

作者:马宁

Push Notification并不是Windows Phone 7.1的新功能,但是之前的文章里对这部分都缺少详细的分析,所以姑且就把Push Notification放到这部分里吧。

很多iOS开发者都将WP7里的Push Notification说成抄袭iOS的产物,孰不知,微软才是Push Notification技术的先行者,Windows Mobile时代的Push Mail技术可以说是独步天下,连Symbian也要授权使用相关的技术。

Push Notification的技术为什么越来越重要,其实这跟移动设备的特点紧密相关,移动设备网络的不稳定性,决定了以Socket为代表的强连接方式并不适用,所以大家更多选择HTTP协议作为主要的通讯方式。但是HTTP的特点是,设备找服务器很容易,通过URL就可以了,但服务器找设备就难上加难了,因为设备会随时切换移动网络,IP地址之类的经常性失效。当然设备端轮询的方式可以解决这个问题,但移动设备的电源、网络都是稀缺资源。所以,OS级别的Push Notification技术就变成了一种珍贵的战略资源,而且,在封闭式的操作系统中,只有OS厂商提供的Push Notification才能够获得最好的效率。

Push Notification简介

目前,Windows Phone支持三种Push Notification方式:Toast Notifications、Tile Notifications和Raw Notifications,我不想翻译成中文名字了,因为“吐司”之类的翻译无法帮助理解。

Toast Notifications,当我们的程序没有运行时,我们希望有一种形式可以通知用户,并且让用户调用对应的应用,就像收到SMS时,调用Messaging程序一样。运行效果如下图,当用户点击Toast时,可以调用对应的程序。

clip_image002

Tile Notifications,用于更新启动界面上的Tile,可以通过Tile Notifications设置Tile的背景图片、Count和Title等属性,各属性显示位置如下图所示。我们可以通过该技术来提示用户,我们的应用有新的事件发生,比如SNS上有多少新的回复等。

clip_image004

Raw Notifications,最简单,当你的应用程序运行时,可以接收Raw Notification信息,如果程序没有运行,则接受不到Raw Notification。

接下来,我们会以Toast Notifications为例详细介绍,Push Notification的原理和使用方法。

clip_image006

首先,还是引用这张广为流传的图示吧,确实是目前能够找到的最详细的示意图了。下面是步骤叙述:

1-3,应用通过调用HttpNotificationChannel接口,向Microsoft Push Notification Service (MPNS)请求一个URI,这个URI会在服务器端发送Notification消息时,作为验证发送目标的唯一标识;这样做的一个好处是,当同一个设备上有多个应用在监听Notification时,不会相互干扰。

4. 应用会将获得的URI传到自己的服务器上,这个URI会存储在自己的服务器上,用于发送Push Notification时调用。URI的传递和存储方式,由开发者自己决定,不过要保证传输过程的加密,以及存储时的安全性。接下来的实例里,传递和存储URI的方式就很有创意。

5-6. 当服务器端有信息要通知应用时,需要向MPNS发起一个Http请求,而MPNS将请求转发到相应的设备上,设备上的应用接到Push Notification的消息后,进行相应的处理。

其实过程挺简单,被MSDN文档说得神乎其神。接下来,我们挨个看一下各个类型的Notification。偷点懒,不自己写Sample Code了,请大家去下面的链接里下载。不过注意Sample Code的更新,最近有同学因为更新不及时,而影响了开发进度。

http://msdn.microsoft.com/en-us/library/ff431744(VS.92).aspx

初始化 Notifications

首先是Toast Notifications,这种Notification在实际应用中最为有效,不但可以提示用户有新的消息,而且还让用户选择是否打开当时的应用。不过比起iOS的Notifications来,Toast Notifications能够容纳的信息量实在有限。所以,开发者要适当“浓缩”需要推送的消息。

我们按照调用的顺序,依次来分析代码,首先来看客户端部分ToastNotificationClient的代码:

            /// Holds the push channel that is created or found.
            HttpNotificationChannel pushChannel;

            // The name of our push channel.
            string channelName = "ToastSampleChannel";

            InitializeComponent();

            // Try to find the push channel.
            pushChannel = HttpNotificationChannel.Find(channelName);

            // If the channel was not found, then create a new connection to the push service.
            if (pushChannel == null)
            {
                pushChannel = new HttpNotificationChannel(channelName);

                // Register for all the events before attempting to open the channel.
                pushChannel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(PushChannel_ChannelUriUpdated);
                pushChannel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(PushChannel_ErrorOccurred);

                // Register for this notification only if you need to receive the notifications while your application is running.
                pushChannel.ShellToastNotificationReceived += new EventHandler<NotificationEventArgs>(PushChannel_ShellToastNotificationReceived);

                pushChannel.Open();

                // Bind this new channel for toast events.
                pushChannel.BindToShellToast();

            }
            else
            {
                // The channel was already open, so just register for all the events.
                pushChannel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(PushChannel_ChannelUriUpdated);
                pushChannel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(PushChannel_ErrorOccurred);

                // Register for this notification only if you need to receive the notifications while your application is running.
                pushChannel.ShellToastNotificationReceived += new EventHandler<NotificationEventArgs>(PushChannel_ShellToastNotificationReceived);

                // Display the URI for testing purposes. Normally, the URI would be passed back to your web service at this point.
                System.Diagnostics.Debug.WriteLine(pushChannel.ChannelUri.ToString());
                MessageBox.Show(String.Format("Channel Uri is {0}",
                    pushChannel.ChannelUri.ToString()));

            }
 
 

 

代码比较多,我们逐步来看,首先要声明一个HttpNotificationChannel的变量,命名空间为Microsoft.Phone.Notification。接下来,声明该Notification Channel的名称,该名称是Channel的唯一标识名,不能与其他Channel重复。接下来,我们调用HttpNotificationChannel的静态方法Find来查找,当前的Channel是否存在,如果存在则返回HttpNotificationChannel对象实例,不存在则返回空。如果该程序从未在当前设备上运行过,则HttpNotificationChannel对象为空,只要运行过一次,则能够通过Find方法来获取对象实例。

先来说未创建Channel的情况,创建HttpNotificationChannel的对象,并将Channel名称作为参数传递进去。然后处理该对象的两个事件:ChannelUriUpdated和ErrorOccurred。当URI发生变化时,会触发ChannelUriUpdated事件,我们在创建Channel对象时,会触发该事件,另外一个ErrorOccurred事件,则用于错误处理。

当程序已经在运行时,默认是接收不到Toast Notification消息的,所以,我们还需要处理ShellToastNotificationReceived事件,当程序运行时,也可以接收到相应的消息。

然后调用HttpNotificationChannel的Open方法,打开该Channel,最后是BindToShellToast方法,通知Shell,该Notification的Channel已经被创建。

获取Channel后的代码与之大体类似,但我们可以通过HttpNotificationChannel的ChannelUri属性来获取Channel的URI,该URI值就是需要传递给我们自己服务器的唯一值,标记了该设备上的该Channel。

而创建Channel后,我们也可以获取URI的值,不过我们需要在ChannelUriUpdated事件处理函数里获取该URI值:

        void PushChannel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
        {

            Dispatcher.BeginInvoke(() =>
            {
                // Display the new URI for testing purposes.   Normally, the URI would be passed back to your web service at this point.
                System.Diagnostics.Debug.WriteLine(e.ChannelUri.ToString());
                MessageBox.Show(String.Format("Channel Uri is {0}",
                    e.ChannelUri.ToString()));

            });
        }

到此为止,我们已经完成了Notification Channel的创建,并且获得了唯一标识的URI。

触发Notification

接下来,我们来看Server的代码。我们自己的Server与Microsoft Push Notification Server(MPNS)之间是通过Http协议,使用HttpWebRequest来发送请求。理论上来说,我们也可以使用客户端程序向MPNS发送Notification请求。为什么要用ASP.NET来编写,最主要的原因还是在于客户端应用要将URI传递给Server端,显然基于ASP.NET的Web应用在这方面是最容易的。

不过我们的实例中,没有客户端将URI传递给服务器端的代码,而是采用了最简单的办法:由开发者直接将URI拷贝到ASP.NET的Web应用中。

clip_image008

接下来,我们来看Server端的代码:

            try
            {
                // Get the Uri that the Microsoft Push Notification Service returns to the Push Client when creating a notification channel.
                // Normally, a web service would listen for Uri's coming from the web client and maintain a list of Uri's to send
                // notifications out to.
                string subscriptionUri = TextBoxUri.Text.ToString();


                HttpWebRequest sendNotificationRequest = (HttpWebRequest)WebRequest.Create(subscriptionUri);

                // We will create a HTTPWebRequest that posts the toast notification to the Microsoft Push Notification Service.
                // HTTP POST is the only allowed method to send the notification.
                sendNotificationRequest.Method = "POST";

                // The optional custom header X-MessageID uniquely identifies a notification message. 
                // If it is present, the // same value is returned in the notification response. It must be a string that contains a UUID.
                // sendNotificationRequest.Headers.Add("X-MessageID", "<UUID>");

                // Create the toast message.
                string toastMessage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
                "<wp:Notification xmlns:wp=\"WPNotification\">" +
                   "<wp:Toast>" +
                        "<wp:Text1>" + TextBoxTitle.Text.ToString() + "</wp:Text1>" +
                        "<wp:Text2>" + TextBoxSubTitle.Text.ToString() + "</wp:Text2>" +
                        "<wp:Param>/Page2.xaml?NavigatedFrom=Toast Notification" + TextBoxSubTitle.Text.ToString() + "</wp:Param>" +
                   "</wp:Toast> " +
                "</wp:Notification>";

                // Sets the notification payload to send.
                byte[] notificationMessage = Encoding.Default.GetBytes(toastMessage);

                // Sets the web request content length.
                sendNotificationRequest.ContentLength = notificationMessage.Length;
                sendNotificationRequest.ContentType = "text/xml";
                sendNotificationRequest.Headers.Add("X-WindowsPhone-Target", "toast");
                sendNotificationRequest.Headers.Add("X-NotificationClass", "2");


                using (Stream requestStream = sendNotificationRequest.GetRequestStream())
                {
                    requestStream.Write(notificationMessage, 0, notificationMessage.Length);
                }

                // Send the notification and get the response.
                HttpWebResponse response = (HttpWebResponse)sendNotificationRequest.GetResponse();
                string notificationStatus = response.Headers["X-NotificationStatus"];
                string notificationChannelStatus = response.Headers["X-SubscriptionStatus"];
                string deviceConnectionStatus = response.Headers["X-DeviceConnectionStatus"];

                // Display the response from the Microsoft Push Notification Service.  
                // Normally, error handling code would be here.  In the real world, because data connections are not always available,
                // notifications may need to be throttled back if the device cannot be reached.
                TextBoxResponse.Text = notificationStatus + " | " + deviceConnectionStatus + " | " + notificationChannelStatus;
            }
            catch (Exception ex)
            {
                TextBoxResponse.Text = "Exception caught sending update: " + ex.ToString();
            }

Server端程序的主体是调用HttpWebRequest对象,来实现HTTP POST的方法。发送的消息以XML形式呈现,在上面代码中表现为toastMessage字符串,wp:Toast指定Notification类型,wp:Text1指定标题,wp:Text2指定子标题,wp:Param指定传递的参数。

请大家注意wp:Param,我们可以认为该参数相当于调用了客户端应用中NavigationService的Navigate方法,必须确认客户端有指定的页面可以进行跳转。当用户点击Toast Notification的提示条时,客户端应用会调用wp:Param,从而跳转到指定界面上。

我们可以通过该参数来传递一些参数,比如标识本次Notification是由哪条消息引发的。不过该参数中不应该包括敏感信息、安全信息,因为无法保证这些信息的安全。最好的办法是,传递一个ID值,当客户端访问服务器时,由服务器端判断该ID是否有效。

接下来,我们还要为HTTP 头添加自定义字段,这部分参考Sample Code就可以了。

接收Notification

当Server端代码完成后,我们回到客户端,看一下当MPNS找到设备后,如何触发我们的客户端代码。首先是Page2.xaml的OnNavigatedTo方法:

        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            //  If we navigated to this page
            // from the MainPage, the DefaultTitle parameter will be "FromMain".  If we navigated here
            // when the secondary Tile was tapped, the parameter will be "FromTile".
            textBlockFrom.Text = "Navigated here from " + this.NavigationContext.QueryString["NavigatedFrom"];
        }

通过NavigationContext.QueryString属性来获取自定义参数,试过传两个参数,后一个收到不到,目前成功的是传递一个参数的情况。

还有一种情况是,当客户端程序运行时,接收到Toast Notification时,该如何处理:

        void PushChannel_ShellToastNotificationReceived(object sender, NotificationEventArgs e)
        {
            StringBuilder message = new StringBuilder();
            string relativeUri = string.Empty;

            message.AppendFormat("Received Toast {0}:\n", DateTime.Now.ToShortTimeString());

            // Parse out the information that was part of the message.
            foreach (string key in e.Collection.Keys)
            {
                message.AppendFormat("{0}: {1}\n", key, e.Collection[key]);

                if (string.Compare(
                    key,
                    "wp:Param",
                    System.Globalization.CultureInfo.InvariantCulture,
                    System.Globalization.CompareOptions.IgnoreCase) == 0)
                {
                    relativeUri = e.Collection[key];
                }
            }

            // Display a dialog of all the fields in the toast.
            Dispatcher.BeginInvoke(() => MessageBox.Show(message.ToString()));

        }

该方法是HttpNotificationChannel的ShellToastNotificationReceived事件处理函数,具体内容不详细解释了。

Tile Notifications

Tile Notification是另一种Notification,主要用于在Windows Phone的首页上显示Smart Tile。很多内置的WP应用可以显示Tile上的自定义显示,比如Messaging程序里可以显示当前未读的短信有多少。Tile Notification可以帮助我们实现类似的功能,通常的一个用法是,首先发送一条Toast Notification,如果用户不理睬,可以发送一条Tile Notification,在Smart Tile上显示提示,让用户在方便时,再处理该条请求。

Tile Notification的实现代码与Toast Notification类似,所以,我们不在这里列出全部源代码了,只是列出与上面不同的部分代码。

首先来看Server部分的代码:

                // Create the tile message.
                string tileMessage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
                "<wp:Notification xmlns:wp=\"WPNotification\">" +
                    "<wp:Tile>" +
                      "<wp:BackgroundImage>" + TextBoxBackgroundImage.Text + "</wp:BackgroundImage>" +
                      "<wp:Count>" + TextBoxCount.Text + "</wp:Count>" +
                      "<wp:Title>" + TextBoxTitle.Text + "</wp:Title>" +
                      "<wp:BackBackgroundImage>" + TextBoxBackBackgroundImage.Text + "</wp:BackBackgroundImage>" +
                      "<wp:BackTitle>" + TextBoxBackTitle.Text + "</wp:BackTitle>" +
                      "<wp:BackContent>" + TextBoxBackContent.Text + "</wp:BackContent>" +
                   "</wp:Tile> " +
                "</wp:Notification>";

                // Sets the notification payload to send.
                byte[] notificationMessage = Encoding.Default.GetBytes(tileMessage);

                // Sets the web request content length.
                sendNotificationRequest.ContentLength = notificationMessage.Length;
                sendNotificationRequest.ContentType = "text/xml";
                sendNotificationRequest.Headers.Add("X-WindowsPhone-Target", "token");
                sendNotificationRequest.Headers.Add("X-NotificationClass", "1");

TileMessage与ToastMessage大体类似,标签变为wp:Tile,而其中包括的数据分为两组:BackgroundImage、Title和Content,显示位置可以参考文章前面的示意图。BackgroundImage可以是本地的资源,也可以是远程的图片链接。

接下来是客户端的部分代码:

                pushChannel = new HttpNotificationChannel(channelName);

                // Register for all the events before attempting to open the channel.
                pushChannel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(PushChannel_ChannelUriUpdated);
                pushChannel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(PushChannel_ErrorOccurred);

                pushChannel.Open();

                // Bind this new channel for Tile events.
                pushChannel.BindToShellTile();

Tile Notification无需添加任何额外的代码,在调用HttpNotificationChannel的Open方法后,还需要调用BindToShellTile的方法,通知Shell绑定该Tile Notification。

Raw Notifications

Raw Notification的用法最简单,在程序运行时,收到Raw Notification时,更新应用程序界面上的某些元素。

首先,我们来看Server端的代码:

                // Create the raw message.
                string rawMessage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
                "<root>" +
                    "<Value1>" + TextBoxValue1.Text.ToString() + "<Value1>" +
                    "<Value2>" + TextBoxValue2.Text.ToString() + "<Value2>" +
                "</root>";

                // Sets the notification payload to send.
                byte[] notificationMessage = Encoding.Default.GetBytes(rawMessage);

                // Sets the web request content length.
                sendNotificationRequest.ContentLength = notificationMessage.Length;
                sendNotificationRequest.ContentType = "text/xml";
                sendNotificationRequest.Headers.Add("X-NotificationClass", "3");

RawMessage非常简单,只需要指定Value1, Value2就可以了,这些值是自定义的,然后指定HTTP头的X-NotificationgClass的值为3,代表Raw Notification。

接下来是客户端代码的片段:

pushChannel = new HttpNotificationChannel(channelName);
pushChannel.HttpNotificationReceived += new EventHandler<HttpNotificationEventArgs>(PushChannel_HttpNotificationReceived);
pushChannel.Open();


        void PushChannel_HttpNotificationReceived(object sender, HttpNotificationEventArgs e)
        {
            string message;

            using (System.IO.StreamReader reader = new System.IO.StreamReader(e.Notification.Body))
            {
                message = reader.ReadToEnd();
            }


            Dispatcher.BeginInvoke(() =>
                MessageBox.Show(String.Format("Received Notification {0}:\n{1}",
                    DateTime.Now.ToShortTimeString(), message))
                    );
        }

HttpNotificationChannel创建完成后,只需要调用Open方法即可,不需要绑定到Shell上。另外,还需要处理HttpNotificationChannel的HttpNotificationReceived事件处理函数,具体的代码不解释了。

写在最后

好了,到这里,我们正式将Windows Phone 7.1中的Push Notification介绍完了。为了增加用户黏性,Push Notification可能是一种非常有效的方式,但是请开发者谨慎使用,如果太多的垃圾信息影响用户体验,那是得不偿失的。另外,我们还需要为Push Notification搭建一个Web服务器,这对于普通开发人员来说,也是一个很难接受的成本。

OpenXLive杯Windows Phone游戏开发大赛

clip_image010

OpenXLive杯Windows Phone游戏开发大赛,是由OpenXLive联合国内知名的开发者社区:DevDiv、智机网、WPMind、Silverlight银光中国和XNA游戏世界,一起举办的针对Windows Phone游戏开发的比赛。

http://www.openxlive.net/posts/news/40

posted @ 2011-08-02 01:24 马宁 阅读(3473) 评论(13) 编辑

2011年6月21日

作者:马宁

我们的Kinect SDK开发开始渐入佳境了,Skeleton Tracking(骨骼追踪)是Kinect的核心技术,正因为有了这项技术,很多有趣的功能才得以实现。

首先,我们来看一下骨骼追踪的具体实现。Kinect最多可以追踪20个骨骼点,而且目前只能追踪人体,其他的物体或者动物就无能为力了。下图介绍了Kinect骨骼点的分布情况:

7

初始化代码

接下来,我们来看一下骨骼追踪的代码是如何编写的。首先,我们要创建一个新的Visual C#的工程,叫做“SkeletonTracking”,添加Kinect程序集和Coding4Fun程序集的工作,可以参考上一篇“Kinect for Windows SDK开发初体验(二)操作Camera”的内容,在这里不再重复。

首先我们还是创建Runtime对象,在初始化时,指定UseSkeletalTracking的RuntimeOptions,然后在SkeletonFrameReady事件中添加处理函数。

Runtime nui; 

private void Window_Loaded(object sender, RoutedEventArgs e)

{

nui = new Runtime();

nui.Initialize(RuntimeOptions.UseSkeletalTracking); 

nui.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(nui_SkeletonFrameReady);

}

接下来,窗体关闭时的事件处理函数:

private void Window_Closed(object sender, EventArgs e)

{

nui.Uninitialize();

}

如果这个时候,我们在空的nui_SkeletonFrameReady事件处理函数中,添加一个断点:

void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) 

{

}

正确连接Kinect设备,并且站在Kinect前,让Kinect能够正确识别人体时,SkeletonFrameReady事件将被触发。

我们可以通过下图看到返回的事件处理参数,其中比较重要的是SkeletonFrame和Skeletons两个对象。

image[11]

添加代码

接下来,我们要准备WPF的界面,在界面上添加五个小球,来跟踪头部、双手和膝盖的位置。在MainPage.xaml中,添加下列代码:

<Canvas Name="MainCanvas">
<Ellipse Canvas.Left="0" Canvas.Top="0" Height="50" Name="headEllipse" Stroke="Black" Width="50" Fill="Orange" />
<Ellipse Canvas.Left="50" Canvas.Top="0" Height="50" Name="rightEllipse" Stroke="Black" Width="50" Fill="SlateGray" />
<Ellipse Canvas.Left="100" Canvas.Top="0" Fill="SpringGreen" Height="50" Name="leftEllipse" Stroke="Black" Width="50" />
<Ellipse Canvas.Left="150" Canvas.Top="0" Height="50" Name="KneeRightEllipse" Stroke="Black" Width="50" Fill="Salmon" />
<Ellipse Canvas.Left="200" Canvas.Top="0" Fill="Navy" Height="50" Name="KneeLeftEllipse" Stroke="Black" Width="50" />
</Canvas>

然后,我们在SkeletonFrameReady事件处理函数中添加捕捉SkeletonData的方法:

void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) 

{

SkeletonFrame allSkeletons = e.SkeletonFrame;

//get the first tracked skeleton

SkeletonData skeleton = (from s in allSkeletons.Skeletons

where s.TrackingState == SkeletonTrackingState.Tracked

select s).FirstOrDefault();

}

我们使用了LINQ来获取TrackingState等于Tracked的SkeletonData数据。在SkeletonData对象的Joints属性集合中保存了所有骨骼点的信息。每个骨骼点的信息都是一个Joint对象,其中的Position的X、Y、Z表示了三维位置。其中X和Y的范围都是-1到1,而Z是Kinect到识别物体的距离。

我们可以用下面的代码,将Joint的位置缩放到合适的比例:

Joint j = skeleton.Joints[JointID.HandRight].ScaleTo(640, 480, .5f, .5f);

最后两个参数为原始大小的最大值和最小值,上面的语句相当于将-0.5到0.5的范围扩大为0到640的范围。

我们封装了一个函数,将获取到的SkeletonData数据,转换为屏幕上的某一个圆圈:

private void SetEllipsePosition(FrameworkElement ellipse, Joint joint) 

{ 

var scaledJoint = joint.ScaleTo(640, 480, .5f, .5f);

Canvas.SetLeft(ellipse, scaledJoint.Position.X);

Canvas.SetTop(ellipse, scaledJoint.Position.Y); 

}

最后,我们SkeletonFrameReady事件的处理方法会是这样的:

void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) 

{

SkeletonFrame allSkeletons = e.SkeletonFrame;

//get the first tracked skeleton

SkeletonData skeleton = (from s in allSkeletons.Skeletons

where s.TrackingState == SkeletonTrackingState.Tracked

select s).FirstOrDefault();

SetEllipsePosition(headEllipse, skeleton.Joints[JointID.Head]); 

SetEllipsePosition(leftEllipse, skeleton.Joints[JointID.HandLeft]); 

SetEllipsePosition(rightEllipse, skeleton.Joints[JointID.HandRight]);

SetEllipsePosition(KneeLeftEllipse, skeleton.Joints[JointID.KneeLeft]);

SetEllipsePosition(KneeRightEllipse, skeleton.Joints[JointID.KneeRight]);

}

最后,程序运行的效果如下,貌似膝盖的识别还是有些问题:

30

程序运行时,我们会发现小球运动时会有跳动的问题,为了减少这种情况,我们要设置SkeletonEngine引擎的TransformSmooth属性为true,并指定TransformSmoothParameters参数,根据应用的具体情况,该参数也应该被适当微调。

添加代码后的Load函数,代码如下:

private void Window_Loaded(object sender, RoutedEventArgs e)

{

nui = new Runtime();

nui.Initialize(RuntimeOptions.UseSkeletalTracking); 

//Must set to true and set after call to Initialize

nui.SkeletonEngine.TransformSmooth = true;

//Use to transform and reduce jitter

var parameters = new TransformSmoothParameters

{

Smoothing = 0.75f,

Correction = 0.0f,

Prediction = 0.0f,

JitterRadius = 0.05f,

MaxDeviationRadius = 0.04f

};

nui.SkeletonEngine.SmoothParameters = parameters;

nui.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(nui_SkeletonFrameReady);

}

写到最后

到这里, Kinect最精华的部分“骨骼追踪”已经介绍给大家了,大家可以去写一些有趣的应用了。接下来,我们会介绍另外一个Kinect的核心功能——Depth Data,景深数据。

 

OpenXLive杯Windows Phone游戏开发大赛

clip_image019

OpenXLive杯Windows Phone游戏开发大赛,是由OpenXLive联合国内知名的开发者社区:DevDiv、智机网、WPMind、Silverlight银光中国和XNA游戏世界,一起举办的针对Windows Phone游戏开发的比赛。

http://www.openxlive.net/posts/news/40

posted @ 2011-06-21 16:16 马宁 阅读(8922) 评论(31) 编辑

2011年6月18日

摘要:   作者:马宁 Kinect SDK出来之后,不到24小时,很多Geek们已经将自己的示例发布到网上去了。可见,好东西肯定会被大家认可的,不好的东西投入再多的宣传也没用。 这一篇我们就要正式进入Kinect的编程世界了,介绍我们如何从Camera获取图像信息。先来介绍一下Kinect的整体结构,省得大家在后边的介绍中被某些名词弄晕。 Kinect一共有三个Camera,其中中间的一个是RGB...阅读全文

posted @ 2011-06-18 19:05 马宁 阅读(11801) 评论(48) 编辑

2011年6月17日

摘要: 作者:马宁 万众期待的Kinect for Windows SDK终于在广大开发者的千呼万唤中发布了beta版,作为历史上销售最快的消费电子产品,早就有无数人想将其用于其他领域了。微软虽然在硬件接口上制造了一点小障碍,但并没有对Kinect的输出做任何加密。于是,基于Kinect的各种应用层出不穷,也有开源社区提供了针对Kinect的USB驱动程序,比如OpenKinect等。 微软从谏如流,推出了官方版的Kinect for Windows SDK,终于让广大开发者可以名正言顺地使用Kinect SDK了。我希望能够在第一时间为国内开发者提供Kinect开发相关的介绍,根据微软官方提供的指南阅读全文

posted @ 2011-06-17 03:25 马宁 阅读(16766) 评论(22) 编辑

摘要: 关于Windows Phone Mango真真假假的图片、视频已经在网上流传很久了,微软发布的Windows Phone 7.1 SDK可以让我们看到一部分Mango支持的功能。 普通用户对Mango的关注更多停留在多语言、多任务等方面,但对于开发者来说,Mango还有很多有趣的功能。我会试着将自己感兴趣的一些功能写出来,变成一个系列。 首先,大家要安装Windows Phone 7的SDK和Windows Phone 7.1 SDK的Beta 版,下载地址如下: http://www.microsoft.com/downloads/en/details.aspx?FamilyID=77586阅读全文

posted @ 2011-06-17 00:14 马宁 阅读(2847) 评论(11) 编辑

2011年4月2日

摘要: OpenXLive beta版发布已经有一个半月的时间了,得到了开发者和玩家的好评,目前已经有五款OpenXLive游戏进入Windows Phone Marketplace,其中的7bomb和Super Hoops都取得了非常骄人的成绩。 当OpenXLive在XNA游戏中大展身手的同时,我们也听到了一些来自Silverlight程序员的抱怨。在Windows Phone 7中虽然提供了XNA的游戏开发平台,但相当一部分的游戏是采用Silverlight开发的。毕竟对于显示性能不高的游戏来说,Silverlight是一个颇具魅力的快速开发工具。 幸好,我们在最初的架构设计上考虑了未来支持Si阅读全文

posted @ 2011-04-02 15:06 马宁 阅读(1359) 评论(6) 编辑

2011年3月31日

摘要: Game Center,移动游戏社交平台的勘探报告 作者:马宁 缘起 亲爱的朋友, 当你读到这封信的时候,我们已经在路上了。南加州发现了金矿,先到那里的人会赢得鲜花、掌声,以及足以笑傲未来十年的资本。是的,我们站在移动互联网的破晓之时,这与淘金时代是多么的相似,每个人都狂热地进入这个领域,宣称自己拥有最先进的设备,足以找到地下深埋的黄金。资本的追捧,也让耐心与沉着离我们而去。 作为一个赶上互联网泡沫结尾的程序员,在我的记忆中,如今的互联网巨头都是活过了严冬的幸存者,上一个世纪里那些为怎么花钱发愁的互联网公司已经变成了遥远而古老的回忆,甚至已经无人记起,宛如南加州沙漠里的一颗沙砾。 无论如何,移阅读全文

posted @ 2011-03-31 14:13 马宁 阅读(1741) 评论(4) 编辑