Net 框架目前逐步在普及了,仍然有很多人在寻找如何让.NET程序脱离.NET框架的方法。
经过我实验后,可行的方式有以下两种:
- 利用飞信的框架来加载.NET程序
- 使用Salamander .Net Linker 编译成本地代码
下面我详细说明下这两种方法的具体实现:
一、利用飞信的框架来加载.NET程序
1、下载一个飞信的程序安装。在安装目录中会有 VMDotNet 目录。这个就是.Net框架虚拟环境需要的文件。
直接把整个目录提取出来。
如何利用这个虚拟环境运行
自己的 .Net 程序呢?
很简单,一个命令行搞定。
2、在 VMDotNet 目录里面会找到一个 FetionVM.exe 。这个是虚拟环境的loader。
使用方法: FetionVM.exe CookieTool.exe。就是直接把你的 .Net 程序
作为命令行参数传递给 FetionVM.exe 就可以在这个虚拟环境中执行你的。net程序了。
3、最好再自己写一个启动程序,首先检查系统是否安装了。Net框架,如果有直接运行。没有调用虚拟环境运行。
这个启动程序飞信也有,如果不想自己写就直接拿飞信安装目录中的 Feition.exe,但是这样的话
你自己的.Net程序的名字必须叫 FeitionFX.exe 。把它和Feition.exe 放在同一目录下,直接执行 Feition.exe 即可。
4、如果你对.Net程序使用了加密保护,需要注意一下,目前市面上的大部分。Net加密工具加密后的程序集不能在这个虚拟环境中运行的。
二、使用Salamander .Net Linker 编译成本地代码
Salamander .Net Linker ,Remotesoft的一个产品,这里使用一个破解版本,在公司:"ftp/学习培训/技术类/开发/.NET/脱离.NET Framework需要工具/"可行下载到相关软件。移动的飞信软件就利用了它的核心。
1、首先安装Remotesoft.NET
2、然后安装Linker_Evaluation
3、启动LinkerPatch.exe选中Linker_Evaluation安装目录的mini.exe进行破解破解 Linker_Evaluation
4、下面启动Remotesoft,进行设置

5、在Remotesoft打开你要脱离.NET Framework的.NET程序,注意该程序不能在桌面
6、选择Mark as Manifest
7、选择MiniDeploy进行脱离.NET Framwok处理,选择保存目录
8、进行过程会报好多次错误,点击关闭不用理睬
9、最后就生成了脱离.NET Framework的.NET程序
总结:
其实这些方式的实现思路都是基于建立一个虚拟的.NET运行环境,有点像虚拟机。虽然这样可以脱离.NET Framework运行,但是不论性能上合功能上都会受到一定程度的影响。
posted @ 2011-05-12 13:37 藏积 阅读(212) 评论(0)
编辑
前言
在编程武林中,Java派成立较久底子雄厚,虽然新掌门Oracle准备重现Java神威,但镇山之技的Java语言已经被后进的新秀.NET派的C#压得喘不过气来,甚至有时候Ocacle老大还得跑到.NET派潜伏学艺。但是百足之虫,死而不僵,一众Java派的拥趸们自认虽然Java渐渐技不如人,但是Java派成立日久,从Java演化过来的七十二门绝技绝非武林暴发户.NET派所能比拟,其中几大支派如apache,springsource各有绝技,而衍生出的帮会、黑社会等等更是不计其数,.NET派望尘莫及。
然而江湖传言有不世神功叫北冥神功,"北冥有鱼,其名为鲲,鲲之大,不知其几千里也……",能够容纳几千里的大鱼必定是非常广阔的海洋,因而北冥神功正是寓含了广大恢宏之意,也体现了神功的威力。"可以吸取他人的内力以供己用,是迅速提升功力的捷径。内力既厚,天下武功无不为我所用,犹如北冥,大舟小舟无不载,大鱼小鱼无不容。"
.NET派的几位高人闭关苦练,竟然悟出北冥神功,此神功后曰:IKVM.NET.
江湖后辈小子Ray Linn偶习此神功,得心得一二,不敢自珍,特此记之,以壮大我.NET门派,千秋万代,一统江湖。
那日Ray偶来到apache支派,却看到Apache弟子们各施绝技,好不热闹. Ray对Apache绝技手痒已久,想来得习IKVM.NET已有时日,斗胆上前叫阵。迎战者哪Apache派中的小弟子,江湖人称:commons.collection.
二人拳脚来去,Ray却懒得与之多动手脚,随即默念真言:
ikvmc -assembly:commons -target:library
-version:1.0.0.0 commons-collections-3.2.1.jar
collection陡然萎靡在地,想是一身内功尽被Ray所吸去,Apache派人等尽皆失色,"我等苦练十余载,内力尽为汝一夕取去",莫敢上前。
Ray回转.NET派,试练collection的神功,借助IKVM.OpenJDK.Core之神器,神功即成,试演如下:
static void Main(string[] args)
{
String name = "Tim";
Predicate nameJohn = new EqualPredicate( "John" );
Predicate nameTim = new EqualPredicate( "Tim" );
Predicate instanceString = new InstanceofPredicate(typeof(String));
Predicate instanceDouble = new InstanceofPredicate(typeof(Double));
Console.Out.WriteLine( "Is Name John?: " + nameJohn.evaluate(name ) );
Console.Out.WriteLine("Is Name Tim?: " + nameTim.evaluate(name));
Console.Out.WriteLine( "Is this a String?: " + instanceString.evaluate( name ) );
Console.Out.WriteLine( "Is this a Double?: " + instanceDouble.evaluate( name ) );
}
相较原有神功:
String name = "Tim";
Predicate nameJohn = new EqualPredicate( "John" );
Predicate nameTim = new EqualPredicate( "Tim" );
Predicate instanceString = new InstanceofPredicate( String.class );
Predicate instanceDouble = new InstanceofPredicate( Double.class );
// Testing all predicates for "Tim"
System.out.println( "Is Name John?: " + nameJohn.evaluate( name ) );
System.out.println( "Is Name Tim?: " + nameTim.evaluate( name ) );
System.out.println( "Is this a String?: " + instanceString.evaluate( name ) );
System.out.println( "Is this a Double?: " + instanceDouble.evaluate( name ) );
竟然绝无二致。
偌大Java江湖,从此为我.NET所用,哇哈哈。
IKVM.NET是一个针对Mono和微软.net框架的java实现,其设计目的是在.NET平台上运行java程序。本文将比较详细的介绍这个工具的原理、使用入门(如何java应用转换为.NET应用、如何在java中开发.NET应用),希望能给大家带来惊喜。
介绍
IKVM.NET是一个针对Mono和微软.net框架的java实现,其设计目的是在.NET平台上运行java程序。它包含了以下的组建:
* 一个用.NET实现的java虚拟机
* 一个java类库的.NET实现
* 致力于在java和.NET之间交互的工具
IKVM.NET的组件
- IKVM.Runtime.dll: VM运行时和所有支持代码。它包括以下的功能: Byte Code JIT 编译器和验证器: 使用JIT将Java Byte Code编译为CIL(C中间语言)。
- 对象模式映射结构: 将.NET中的System.Object,System.String,System.Exception映射为java代码中的java.lang.Object, java.lang.String,java.lang.Throwable。
- 管理本地方法(在Classpath中)的.NET重新实现。
- *IKVM.GNU.Classpath.dll: 被编译的GNU Classpath版本,它是由自由软件基金会实现的java类库和一些IKVM.NET附加代码组成的。注意:这里的GNU Classpath不是IKVM.NET的一部分,但是前者被用在IK.VM.NET中。
- IKVM.JNI.[Mono|CLR-Win32].dll: 通过实现JNI接口管理C++汇编。作为一个可选部分,只在程序使用自己的本地库时才被用到。而对于纯java程序来讲是不会被用到的。
- ikvm.exe: 与java.exe很类似的启动执行程序(动态模式)。
- ikvmc.exe: 静态编译器,被用来编译java类和jar使其成为.NET汇编(静态模式)。
- ikvmstub.exe: 一个从.NET汇编生成存根类的工具,就如javap一样反编译.NET汇编。IKVM.NET了解如何存根并用实际的.NET类型引用替换对存根的引用。
- IKVM.AWT.WinForms.dll: 非常有限的零散AWT实现。
项目状态
此项目目前正在开发,将最大化实现与JDK1.4的兼容,但是仍存在一些漏洞(尤其在Classpth API中)。
- AWT和Swing还未有功能。
- 安全性是IKVM平台的一个大遗漏。此问题将依靠.NET平台提供的旧有的、但功能强大的安全模式而被解决。 虽然这样,目前项目已具备能成功运行大型java项目的能力。
IKVM原理
1.如何替换JVM
IKVM应用包含了采用.NET实现的java虚拟机。在一些场合,我们可以用它替换掉java。例如: java -jar myapp.jar 将被替换为 ikvm -jar myapp.jar。
2.在.NET应用中使用java类库
IKVM.NET包含ikvmc,这个在java bytecode与.NET中间语言的转换器。如果我们使用一个被用在.NET平台的java库的话, 运行ikvmc –target:library mylib.jar(mylib.jar在这里指代我们的jar文件)来生成mylib.dll。例如apache FOP项目是一个开源的XSL-FO处理器项目,它使用java语言编写的用于从xml生成PDF文档。使用IKVM.NET技术,我们可以将apache FOP用在任何的.NET应用中。这样在开发.NET应用的同时利用IKVM便可以使用java开源项目这个免费的软件仓库。尽管在IKVM.NET没有提供在.NET中使用的java编译器,但是我们可用开源的Jikes编译器将java源代码编译为JVM bytecode,然后使用ikvmc –target:exe myapp.jar来生产.NET执行文件。我们甚至可以通过包含ikvmstub应用的方式在我们的java代码中用.NET API。
四、IKVM使用入门
1.系统准备
Windows平台:Microsoft .NET Framework 1.1 SDK
Windows或者Linux平台:Mono Framework 1.0
2.开始安装
在Windows和Linux平台上安装过程是相同的,在下载二进制发布版后,将文件解压缩。打开命令或者shell窗口,cd进入ikvm\bin目录,执行ikvm。如果我们操作正确的话,我们将看到以下的输出:
usage: ikvm [-options] <class> [args...] (to execute a class) or ikvm -jar [-options] <jarfile> [args...] (to execute a jar file) ...
为了方便使用,我们可将ikvm\bin目录加入到系统path。现在我们将使用IKVM就像使用JVM一样,并不需要配置。如果我们需要在.NET或者Mono环境下使用IKVM,请仔细阅读下面的文字说明:
- 首先下载Jikes编译器。如果我们计划开发在java中开发代码 运行于.NET环境的话,我们将要一个java编译器。IKVM.NET没有提供这个编译器,所以我们需要能生成标准java类文件的编译器。Jlikes是一个好选择,它是一个优秀的开源项目,并应用在多种平台。当然Sun提供的jdk也很好。
- 在Windows的全局汇编缓冲区中安装IKVM dll。在Windows中运行基于IKVM dll的.NET应用程序时,.NET框架必须定位这些dll的位置。系统首先在全局汇编缓冲区中查找,然后再当前目录中查找。如果我们想不在当前目录中安装这些dll文件的话,我们就要将它们安装在全局汇编缓冲区中:在Windows控制面板中访问.NET框架配置,增加一个汇编缓冲区。我们至少要安装IKVM.GNU.Classpath.dll和IKVM.Runtime.dll。
3.设置环境
在我们开始编写代码之前需要准备一下我们的环境,添加以下路径到系统PATH环境变量中:
- 包含IKVM执行文件的目录。
- 包含C#编译器(在Windows/Mono中为csc)的目录,通常在Windows中为C:\WINDOWS\Microsoft.NET\Framework\v2.0.5372。
* 包含java编译器(javac或者jikes)的目录。
4.动态执行java应用程序
IKVM.NET包括了一个C#实现的java虚拟机。我们可以从一个例子开始——进入IKVMROOT\samples\hello目录,编译示例程序:
Javac Hello jar cfm hello.jar manifest.mf Hello.class
现在,在使用javac编译了Hello类后,我们使用IKVM运行此类:
ikvm Hello
这个命令将启动IKVM,IKVM查找名为Hello.class的文件。如果找到,则将其装载并动态执行bytecode。此时Hello程序将要求我们输入名字,之后我们将看到一个问候信息。
如果在上面的过程中发生问题,请检查下面的地方:
- 检查命令行: ikvm像java一样需要我们输入正确的类名。
- 如果ikvm报告ClassNotFoundException,请检查CLASSPATH环境变量是否被设置。如果被设置,请清除CLASSPATH或者将当前目录加入到CLASSPATH中以使ikvm能够在当前目录中找到类。
如果运行jar文件,我们可键入:
ikvm -jar hello.jar
提示:详细的命令行选项可以参考ikvm手册。
5.将java程序转换为.NET程序
IKVM.NET包含ikvmc这个能够将jar文件转换为.NET的dll库文件和exe应用的工具。下面我们将学习如何将java应用转换为一个.NET执行文件:
进入IKVMROOT\samples\hello目录输入以下命令:
ikvmc hello.jar
注意:当我们使用Mono时,我们需要告诉ikvmc如何找到GNU Classpath dll,例如:
ikvmc -reference:/usr/lib/IKVM.GNU.Classpath.dll hello.jar
在命令执行完后,我们将发现hello.exe已被生成在当前目录:
- 在Windows/.NET环境下,如果我们得到了FileNotFound的异常,请记住检查.NET框架希望在当前目录或是全局汇编缓冲区中寻找dll文件。我们可以通过上面讲的方法将dll安装到全局汇编缓冲区中,或者将dll文件直接复制到当前目录。
- 在Linux/Mono环境下,我们使用下面的命令执行hello.exe:
mono hello.exe
6.在java中开发.NET应用
首先进入IKVMROOT\samples\usenetapi目录,找到ShowDir.java文件,这个java应用使用了.NET API来显示当前目录下的文件列表。打开这个文件,我们会发现其导入的包名以cli开头,这些并不是java API包,它们是映射到.NET命名空间的"伪"包。需要查看更多信息请看IKVM
的开发者手册。
第一步:生成java存根文件
IKVM没有提供java编译器,所以我们可以使用标准的java编译器。由于java编译器只能编译使用了java API的应用程序,而不是使用.NET API的应用。所以我们在这里需要"愚弄"一下java编译器使其相信名为cli.System.IO的包是一个真正的java包。帮助我们完成这项工作的是ikvmstub程序。它从.NET dll生成jar文件,这个被生成的jar文件包含了与.NET类对应的java类和接口,但是并不包含真正的代码,只包含一些映射信息。这样做便会通过java编译器的检查和编译:
ikvmstub mscorlib.dll
注意:在Linux Mono环境下,我们必须输入dll文件的完整路径,例如:
ikvmstub /usr/lib/mscorlib.dll
在编译完成后,我们将在当前目录下发现一个名为mscorlib.jar的文件。
第二步:编译java源代码
现在我们将编译java源代码,如果使用javac的话,可输入以下命令:
javac -classpath mscorlib.jar ShowDir.java
在命令完成后,ShowDir.class文件将出现当前目录下。
第三步:生成.NET执行文件
最后我们将转换java class文件为.NET应用程序,正如前面讲的:
ikvmc ShowDir.class
注意:在Linux Mono环境下,我们需要使用前面提到的-reference选项:
ikvmc -reference:/usr/lib/IKVM.GNU.Classpath.dll ShowDir.class
这时我们便可以看到ShowDir.exe出现在当前目录。
posted @ 2011-05-12 13:36 藏积 阅读(107) 评论(0)
编辑
介绍
DevExpress 简介
DevExpress(DXperience) 是 Developer Express Inc.公司针对 .NET 平台开发的一整套组件解决方案。如果你想将更多的时间放在你所开发的系统的业务上,而又想它同时具备良好的用户交互界面,那么 DevExpress(DXperience) 将帮助你实现这种可能。DevExpress(DXperience) 提供了功能完备的可视化组件,整套组件将帮助你模拟当今最流行的UI,同时组件都是用Visual C# 编写,它们中的大多都是对VS自带的控件功能进行了扩展,并都是直接继承至所要扩展的控件本身,因此整套控件具有很好的兼容性。无论你是用DevExpress(DXperience) 进行全新的开发,还是将以前控件改为新的控件,它都能帮你很好的完成。并且组件已针对 .NET Framework 和所有 .NET 语言进行了全面的优化。在 UI 漂亮实用的同时,又具有相当好的运行速度。
DevExpress 安装你可以在 http://www.devexpress.com/Downloads/NET/index.xml 直接下载评估版,进行安装试用。
安装成功后,将在VS的工具箱里出现相应的控件如图:
Coolite Toolkit
是一个支持ASP.NET AJAX的Web控件。
Coolite Toolkit是基于跨浏览器的http://www.coolite.com
http://www.codepub.com/d/tag.php?n=1&tag=CooliteExtJS 库开发而来的,并且简化了开发步骤,并且包含有丰富的Ajax运用。
Coolite Toolkit和 ExtJS 都是开源的。
可能通过SVN直接获取Coolite 的代码。
Coolite Toolkit非常适合做web应用程序的开发,它提供了很多专业的Asp.net输入/验证/显示控件,和页面布局的框架,同时完全支持ajax,因为它是所有的组件是居于ExtJS上封装出来,让开发人员在可视化的设计器内进行方面的属性配置。
如图所示是Coolite一个官方的Demo站点,从页面布局和使用的控件TextBox,Combox,Button,ToolBar,StateBar,Panel,TabPanel,ExplorerBar,MenuBar,PictureBox都用统一的样式非常方便的配置,基本不需要额外美工处理,同时支持ajax无刷新效果。
另外就是支持多窗体(MDI)功能,我们知道在C/S的应用程序中很容易实现MDI应用,在一个主窗体中打开多个子窗体,方便客户在不同的窗体间进行切换,支持多任务的操作,但是在B/S的环境下要实现MDI的效果,一种了借助IE,firefox之类的浏览器实现,问题是的你还得让每个URL的Redirect加上Target的属性,每次谈出新窗口,自然不是很好的解决办法,但是Coolite的页面布局实现了这样的功能,你可以在多个自窗体之间进行切换。
该套控件不同于一般的第三方空间如devexpress,netAdvantage,虽然提供非常多功能,但运行起来特别占用资源,时间一长服务器/客户端都会非常忙,但是Coolite采用的是纯javascript开发的,所有控件脚本图片资源加起来6M,所以性能非常好。
下如是一个客服中心服务平台的应用。同时打开多个子页面,导航览支持缩紧,页面loading有动画效果,动态修改样式。
一些控件使用效果;
DropDownList支持多列显示,动态检索,分页非常实用的效果
类似ajax autocomplete功能测试文档
详细比较
易用性比较
两者易用性上不相上下,DevExpress在于对已有ASP.NET控件的基础上扩展,Coolite在于对Ext的封装。
服务器后置代码:都可以调用设置前段控件的属性和事件。
AJAX交互:DevExpress只限于对已有的控件内部封装好的Ajax方法的调用,Coolite的优势在于在前段也能很方便地调用Ajax方法,你唯一呀做的是在方法特性上加上AjaxMethods。
开发效率
在展现数据和交互操作上两者开发还是比较方便的。
在布局上Coolite依托于Ext的强大布局处理,所以无论效果还是效率上都很方便。
Coolite的设计目标是使Web开发人员不用写js和css,它做到了,使用它开发真的很方便。
显示效果
Coolite具有Ext一致的效果,主题方面也可以利用Ext提供的大量主题资源。
适用范围:
DevExpress贯穿于WinForm、WebFrom、WPF等,而Coolite只能用于Web开发。
拓展性
Coolite是开源的,很多东西可以很方便地扩展。
posted @ 2011-05-12 13:32 藏积 阅读(389) 评论(1)
编辑
IDE 在ASP.NET 开发上的增强
代码片段(Code Snippets):
代码段是预先开发的代码模板,可以节省我们对有关语法思考的时间。在VS 2005和VS 2008中,已经有建立了很多代码段。不过,这些只适用于隐藏代码(code behind)。在VS 2010中代码片段支持JScript,HTML以及asp.net标记。在下面画面,展示了JScript和HTML片段的快捷菜单。
在JS中:
在Html中
:
Generate From Usage:
在ASP.Net以前的版本,微软推出了从现有的代码中进行代码重构来生成方法,在In ASP.NET 4.0 中, Generate From Usage 有了新概念,能基于现有的代码来生成属性,方法,类和的其它类型。
写一些代码,选中它,右击最右侧的字符,你将得到去提取属性和方法的选项。此选项仅当你没有定义过这个标识符才显示。对于下面的例子智能感应不会显示选项来提取属性,例如,如果您右键点击变量 i 。
MultiMonitor:
Visual Studio 2010 给我们提供能将IDE的窗体移动 visual studio IDE之外,将它放至在桌面上。它还支持多面去地去看不同的IDE窗体。如果我们关闭Visual Studio,并再次打开它,我们会发现所有的窗体在我们最后一次放置的地方。
ASP.NET 完善性改进、SEO增强
EnablePersistedSelection:
当我们选择像datalist 或者gridview 控件中的一行时,如果我们移动到另一个网页,在新的页上,它选择同编号行,虽然我们只在第一页选择了它。
为了避免这个,ASP.Net 4.0为这些控件推出了一种新的属性,这是EnablePersistedSelection。如果设置为true,在其他网页中,将不能选择同一编号。例如,导航到原始网页,第一页将显示选定的最初选定的行。
URL Routing:
现在,我们看到网站的URL是更具体的SEO。不显示任何描述性信息,例如,不是显示网址 : http://mywebsite.com/userprofiles.aspx?userid=1 ,开发者更愿意显示: http://mywebsite.com/mydetails。在asp.net 2.0的URL映射为我们提供了一个选项,在一定程度上实现此功能。我们可以提供确切的网址导航和URL显示给用户。
Url routing在asp.net 3.5中引入。开发者创建不同的route处理类取决于网站url routings的数量。由于有url routing选项,回发的问题能被解决。
在asp.net 4.0中你不需要为每个单独的处理程序类来定义routing。建立一个辅助函数MapPageRoute,帮助您实现更快速的routing。这些routes在Application_Start中注册。对于这个在Global.asax SetRouting方法的示例中设置routing,第一个参数是routing的友好名称,第二个参数是检查URL进行模式匹配,第三个是在aspx页将这一功能为用户实施。
Global.asax中是:
protected void Application_Start(object sender, EventArgs e)
{
SetRouting(System.Web.Routing.RouteTable.Routes);
}
private void SetRouting(System.Web.Routing.RouteCollection routeCollection)
{
routeCollection.MapPageRoute("RouteFriendlyName", "MyWebsite/RequestParameterName",
"~/Book.aspx");
// you can add other page routes with different names and combinations here
}
现在Book.aspx将使用下面的代码进行进一步处理。
string Parameter = Page.RouteData.Values["RequestParameterName"].ToString();
if (Parameter=="Chemistry"){....}
else if (Parameter=="Physics"){....}
现在,如果条件满足,如果我们将首先浏览到localhost / MyWebsite /Chemistry 。
Compressing Session Values
ASP.NET session外的进程的状态值保存在数据库或服务器上。这些都是以序列化格式保存。发送到服务器时候,更多更大的session将消耗更多的资源 。现在,这些可以被压缩在一个新的构建compressionEnabled属性中。这种对sessionState元素属性,可以在web.config这样被声明。
<sessionState
mode="SQLServer" stateConnectionString="connectionstring goes here"
compressionEnabled="true"/>
此选项将用于session外的进程。
Meta Tags:
HtmlMeta类可用于动态添加HTML meta的标签和HTMLMeta的名称,内容属性可以被用来在运行时动态添加任何继标记名称和它的值。
在asp.net 4.0 Page类中有两个属性MetaDescription和MetaKeywords。这些可以用来在运行时在html中为description和keyword元素添加 meta 的值。这些可以在HTML中提及页面属性或以这种方式:
Page.MetaDescription = "this is meta description";
Page.MetaKeywords = "this is a meta keyword";
如果这样我们看到这样的HTML生成:
<head>
<meta name="description" content="this is meta description " />
<meta name="keywords" content="this is a meta keyword" />
</head>
Generating Client IDs:
web控件的客户端ID是根据父控件ID动态的产生。
所以,如果你在一个用户控件中使用textbox,我们必须查看为它生产什么样ID,才能在客户端的脚本中直接使用。
有时这些是动态的改变用户控件的名称也更改它的子控件ID。在asp.net 4.0中,这个问题是可以由页面的ClientIDMode属性解决。它有如下可能的值:AutoID , Static, Predictable 和Inherit。
AutoID: 是webpage 的默认值,功能和以前 ASP.NET的版本一样。
Static: 为控件设置一个静态的ID,所以我们不需要当心父控件的ID,我们也能使用javascript引用它。
例如一个用户控件里面的 textbox ,它外面胡一个ClientIDMode 设置为static
<asp:TextBox ID="textbox1" runat="server" clientIDMode="Static"></asp:TextBox>
<uc1:WebUserControl ID="WebUserControl1" runat="server" />
这个用户控件包含了textbox2
<asp:TextBox ID="TextBox2" ClientIDMode="Static" runat="server"></asp:TextBox>
这个代码将产生下面的HTML:
<input name="ctl00$MainContent$textbox1" type="text" id="textbox1" />
<input name="ctl00$MainContent$WebUserControl1$TextBox2" type="text" id="TextBox2" />
如果我们移除ClientIDMode="static" ,输出胡HTML将会是:
<input name="ctl00$MainContent$textbox1" type="text" id="MainContent_textbox1" />
它将产生象以前asp.net版本的textbox的ID
Predictable: 如果gridview 或者listview控件中我们设置ClientIDMode的值为Predictable ,它将会串连控件ID和Gridview的ID ,column的值在ClientIDRowSuffiex 属性上提到:
Gridview ID ="GridView1"
ClientIDMode="Predictable"
ClientIDRowSuffix="ItemID"
如果在gridview的模板列中的有一个ID为 Label1的label控件
,它被绑定在ItemID 列上。
它的ID将会是: ID= GridView1_Label1_200。
如果我们用数据绑定控件之外使用web控件,设置ClientIDMode等于Predictable:
<asp:TextBox ID="TextBox1" runat="server" ClientIDMode="Predictable"></asp:TextBox>
<asp:Panel ID="panel1" runat="server">
<asp:TextBox ID="TextBox2" runat="server" ClientIDMode="Predictable"></asp:TextBox>
</asp:Panel>
你可以看到webcontrols在一个面板输出和另外一个再面板以外输出。<input name="ctl00$MainContent$TextBox1" type="text" id="MainContent_TextBox1" />
<div id="MainContent_panel1">
<input name="ctl00$MainContent$TextBox1" type="text" id="MainContent_TextBox1" />
</div>
它将产生所有web控件的ID,是panel的页面的ID加上子控件的ID。
Inherit: WEB控件默认是继承父容器的ID。 控件能覆盖它父控件的属性。所以我能为ClientModeID设置不同的值。
Permanent Redirect :
在有些情况下,如果当前页面已经过时,我们希望将用户重定向到新开发的网页。 我们可以使用Response.Redirect转移页到新的页面。但是,在这种情况下,搜索引擎保存搜索索引的网页的旧信息。
另外,如果用户浏览到这个网页,他将被重定向到这个网页,然后用命令的response.redriect帮助新打开的一页。
但在ASP.NET 4.0中一个新的命令Response.RedirectPermanent可用于更改服务器页上的头信息。因此,如果用户去为他直接重定向到新的一页,并为他节省了双行页面。在该网站和网页更新的发现头和更新其索引信息,展示最新的信息重新搜索索引同样的搜索引擎。
New Browser Definitions:
在ASP.net 3.5近几年,一些浏览器已经更新,其中包括Google chrome和那些支持blackberry智能手机。 ASP.Net 4.0的HttpBrowserCapabilities类被更新符合支持新浏览器。
页面标记<%%>说起
在Asp.Net4.0中<%符号表达式%>有四种形式,分别为<%="hello world"%>,<%$expression%>,<%#expression%>,<%:abc%> 我们分别来看一下
<%:expression%>它是一个新增的表达式方式,表示将expression HtmlEncode后输出,如果这个表达式的类型时IhtmlString,它将通过IhtmlString接口的ToHtmlString()方法来做UrlEncode,.Net 4.0中HtmlString类通过实现这个接口,避免重复做HtmlEncode
例如:
需要做HtmlEncode时使用表达式
<%: new HtmlString("<h1>I'a a test</h1>")%> |
将输出
这个很酷的feature在Asp.Net Mvc 2.0中被广泛使用。
这些表达式都出现在aspx页面上,在编译aspx页面时编译程序会使用正则比表达式将这些表达式解析成后台代码来执行
ASP.NET 语言、语法层面
Optional Parameters
在asp.net 4之前,为了实现可选的参数,我们创建重载函数。但现在在C#中,可选参数没有更多的限制。但是象VB的可选参数必须放置到最后。例如:
public void FunctionOptionalParam(string Name, int Age, string Country = "")
我们可以不提要求的可选参数的值。
FunctionOptionalParam("My Full Name",20);
Named Parameters:
命名的参数可以忽略的参数顺序,在不同顺序的使用带名称的参数。例如:
public void FunctionNamedParam(int x, int y , int z)
在函数调用将是:
FunctionNamedParam(x:1, z:3, y:2);
在函数声明的顺序之前,虽然我们为参数设置了Z的值,但这些仍然等同于x = 1,y = 2,z=3。
posted @ 2011-05-12 13:25 藏积 阅读(167) 评论(0)
编辑
自从2000年微软.NET平台问世以来,全球已经有超过四百万开发人员使用.NET平台进行软件开发。对于.NET来说,这无疑是一个巨大的成功。这不仅仅体现在商业上的成功,其核心价值在于.NET为基于微软Windows平台的软件开发过程提供了一种新颖、高效的编程模型。在该模型下,开发人员能够更容易地将精力集中在其特定的开发情景中,而不用过多地关注消息循环、窗口过程等操作系统底层的处理。
另一方面,由于历史的原因,在.NET出现之前,开发人员已经编写了大量经过严格测试且可复用的非托管代码。它们以C库函数、C++类库以及COM组件的形式存在于诸多应用程序和框架之中,并承担着非常重要的角色。但由于在托管和非托管对象模型之间,数据类型、方法签名和错误处理机制都存在很大差异,从而使两种编程模型之间的代码互用和移植更加复杂。因此,在很长一段时期内,开发人员必须面对.NET与久经考验的"遗留代码(legacy code)"长期并存的局面。
公共语言运行库(Common Language Runtime,简称CLR)提供了一系列能够使托管代码与非托管代码进行交互操作的解决方案。其中主要包含3类互操作技术:
平台调用技术(P/Invoke):主要用于处理在托管代码中调用C库函数及Win32 API函数等非托管函数的情形。
C++ Interop:适用于在托管代码与C++类库、核心算法库之间进行高效、灵活的互操作过程。一方面托管代码可以通过包装类机制使用C++类库,另一方面非托管代码可以通过包装模板机制使用托管对象。
COM Interop:该技术用于处理托管代码与COM之间的交互过程。托管代码通过运行库可调用包装(RCW)使用非托管COM组件。反过来,非托管COM客户端可以通过COM可调用包装(CCW)使用托管程序集。
1平台调用技术(P/Invoke)
Win32 API是C语言(注意,不是C++语言,尽管C语言是C++语言的子集)函数集。C#语言与C语言是完全不同的(除了语法上比较像),所以,要想用C#语言调用C语言的Win32 API,要费上一番周折。首先我们就要准备一些基础知识。
Win32 API函数是Windows的核心,比如我们看到的窗体、按钮、对话框什么的,都是依靠Win32函数"画"在屏幕上的,由于这些控件(有时也称组件)都用于用户与Windows进行交互,所以控制这些控件的Win32 API函数称为"用户界面"函数(User Interface Win32 API),简称UI函数;还有一些函数,并不用于交互,比如管理当前系统正在运行的进程、硬件系统状态的监视等等……这些函数只有一套,但是可以被所有的Windows程序调用(只要这个程序的权限足够高),简而言之,API是为程序所共享的。为了达到所有程序能共享一套API的目的,Windows采用了"动态链接库"的办法。之所以叫"动态链接库",是因为这样的函数库的调用方式是"随用随取"而不是像静态链接库那样"用不用都要带上"。
Win32 API函数是放在Windows系统的核心库文件中的,这些库在硬盘里的存储形式是.dll文件。我们常用到的dll文件是user32.dll和kernel32.dll两个文件,还有其它一些dll文件也非常重要,大家要在实践中多积累经验。
我们知道Win32 API函数是放在dll文件中了,但新问题又来了——我们怎么调用它们呢?这些dll文件是用C语言写的,源代码经C语言编译器编译之后,会以二进制可执行代码形式存放在这些dll文件中,就好像苹果被打碎机打成果酱后装在罐子里一样——你再也分不清哪个是你GF给你的,哪个是你老妈给你的一样。为了能让程序使用这些函数,微软在发布每个新的操作系统的时候,也会放出这个系统的SDK。
现在,我们已经找到了问题的关键点:如何用.NET平台上的C#语言来调用Win32平台上的dll文件。答案非常简单:使用DllImport特性。
下面,就让我们写一个小程序,试一试如何用C#语言和DllImport特性来调用Win32 API。
using System;
using System.Runtime.InteropServices;
class Program
{
[DllImport("User32.dll")]
public static extern int MessageBox(int h, string m, string c, int type);
static int Main()
{
MessageBox(0, "Hello Win32 API", "水之真谛", 4);
Console.ReadLine();
return 0;
}
}
2 C++调用
c++经过这么多年的发展已经积累了大量的动态连接库,如果能够在.net环境里应用这些函数库,
可以很大的提高整个应用的开发速度。
使用c++编程的人员肯定对指针不会感到陌生,由于c++中的函数接口好多都可能定义成位指针,
而c#中只有在声明为unsafe code中才能够使用指针。如果想让c++的DLL支持在C#中调用,
那么在C++接口的声明中需要使用下面的这种格式:
extern "C" __declspec(dllexport) void __stdcall popMessage(char* message)
{
MessageBox(NULL, message, "C message from C#!", MB_OK);
}
并且在c#类声明中使用如下的导入编译好的DLL,例如:
[ DllImport( "test.dll", CallingConvention=CallingConvention.Cdecl )]
public static extern void Message(string theMessage);
3 COM调用
.NET framework 是从COM的一种自然地进步,因为这两个模型共享了许多中心的主题,包括组件重用和语言中立。为了支持向后兼容,COM interop提供了不需要修改现有组件而能访问现有COM组件的方法。可以通过使用COM interop工具导入相关的COM类型来合并COM组件到.NET Framework的应用中。一旦导入,COM的类型就可以使用了。
COM interop 同时也提供了向前兼容使得COM的客户可以像访问其他的COM对象一样访问托管的代码,COM interop又一次的提供了所谓的无缝从程序集中导出元数据(metadata)到类型库并且像传统COM组件一样注册托管组件的方法。无论是导出还是导入工具处理的结果都与COM规范一致。在运行时,如果需要的话common language runtime在COM对象和托管代码之间列集(marshals)数据
以下演示一个封装好的com调用类
public class ComManage
{
public string CLSID { set; private get; }
public Dictionary<string,object> Properties { set; private get; }
public object[] Arguments { set; get; }
private Type _dynamicType;
private object _dynamicObject;
/// <summary>
/// 方法调用
/// </summary>
/// <param name="methodName"></param>
/// <returns></returns>
public object MethodInvoke(string methodName)
{
if (_dynamicType == null)
{
_dynamicType = Type.GetTypeFromCLSID(new Guid(CLSID));
}
if (_dynamicObject == null)
{
_dynamicObject = Activator.CreateInstance(_dynamicType);
}
//给属性赋值
foreach (KeyValuePair<string, object> property in Properties)
{
_dynamicType.InvokeMember(property.Key, BindingFlags.SetProperty, null, _dynamicObject, new object[] { property.Value });
}
//调用方法
object ret = _dynamicType.InvokeMember(methodName,BindingFlags.InvokeMethod, null, _dynamicObject, Arguments);
return ret;
}
}
具体调用
ComManage comManage = new ComManage()
{
CLSID = clsid,
Properties = new Dictionary<string, object>()
{
{"Host","ydtf-127"},
},
Arguments = new object[]
{
"测试单位",
"",
""
}
};
object ret = comManage.MethodInvoke("PKILogin");
4 .NET 4.0 调用
C#4.0新特性对.NET互操作的影响
说道C#的新版本对.NET互操作的影响就不得不先说一下C#4.0的新特性。
Dynamically Typed Objects.
Optional and Named Parameters.
Improved COM Interoperability.
Safe Co- and Contra-variance.
这其中第2、3条都和互操作有关系。第2点的可选参数和命名参数并不是什么新概念了。主要在于编译器的支持。像VB.NET早就支持可选参数了。这几年C#社区对这个特性的呼声太高了,看来终于起作用了。
4.1 可选参数
有很多COM方法都接受可选参数。在调用此类方法时,可以根据具体需要为可选参数传递指定的值,或者忽略此参数而使用该参数的默认值。在使用托管代码调用COM方法时,根据不同的.NET语言,调用的复杂度也有所差异。由于Visual Basic .NET本身就支持可选参数(Optional关键字),它能够以可选方式使用该参数。但如果使用C#,情况就会大为不同。由于C#不支持可选参数,因此就必须为方法中的每个参数传递值,比如可以为可选参数传递System.Type.Missing以设置该参数的默认值。由于必须为所有的可选参数传递值,因此使用C#调用带有可选参数的COM方法,就不如Visual Basic .NET方便和灵活。
使用过Office PIA的朋友,在操作word文档时一定遇到过下面的例子:
object fileName = "Test.docx";
object missing = System.Reflection.Missing.Value;
doc.SaveAs(ref fileName,
ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing);
为了调用SaveAs方法,你不得不为填写全部不必要的参数。这就是由于老的C#版本不支持可选参数的原因。
在C#4.0出现后,情况就大不一样了。比如上面的代码可以写成:
doc.SaveAs("Test.docx");
4.2对COM互操作的改进支持
新特性的第3点提到了特别针对COM互操作的改进。这包括:
1.在同一进程中host多个版本的CLR。这样可以为托管COM组建选择它所需(编译时)的运行时版本。
2. 不再必须使用PIA(Primary Interop Assembly)于COM组建交互。在过去,当你发布一个COM组建时,微软建议你随该组建发布一个PIA。这个附带的程序集PIA用来被托管应用程序客户端引用。在.NET Framework 4.0中PIA将被弱化。C#和VB编译器会判断你的程序具体使用了哪一部分COM API,并只把这部分包装成IA(互操作程序集),直接加入到你自己的应用程序集里面。
3. 重定义QueryInterface。你可以使用System.Runtime.InteropServices.ICustomQueryInterface接口自定义由托管代码实现的IUnknown::QueryInterface方法。应用程序可以用它返回特定的接口。
4.3 P/Invoke 调用
dynamic user32 = new DynamicDllImport("user32.dll",
callingConvention: CallingConvention.Winapi);
user32.MessageBox(0, "Hello World", "Platform Invoke Sample", 0);
DynamicDllImport是Mono发布的一个类,利用它我们可以直接调用WinAPI,这里调用了MessageBox函数。
posted @ 2011-05-12 13:23 藏积 阅读(134) 评论(0)
编辑
随着多核CPU的普及和互联网的迅速发展,计算已经进入并行的时代,这种并行计算有两种主要的形式,一种着眼于充分挖掘单台计算机的硬件潜力,通常以多线程协作的方式完成指定的工作任务;另一种着眼于利用互联的计算机所共同拥有的计算能力,将一个工作任务分发到多台计算机上同时处理,通过多台计算机的相互协作完成单台计算机所无法完成的工作任务。
第一种计算形式在过去一直都是使用线程来实现的,而在.NET 4.0中,又在线程的基础上向软件工程师提供了一个"并行扩展(Parallel Extensions)",从一个更高的抽象层次简化多线程应用程序的开发,这也是本章要介绍的主要内容。
第二种计算形式依赖于多台计算机的相互协作,本质上是一种分布式的软件系统,在.NET平台上,WCF是开发这类型软件系统的强大工具。
并行计算引例
请大家仔细查看一下示例程序SequentialvsParalled的源码。此程序完成了一个非常典型的数据处理工作:递增一个整数数组的每个元素值。 示例程序将数组大小设定为1000000,然后对数组中的每个元素进行100次操作,每次操作都将元素值加1,因此,完成整个数据处理工作需要108次操作。
以下是串行代码:
//依次给一个数组中指定部分的元素执行OperationCounterPerDataItem次操作
static void IncreaseNumberInSquence(int[] arr,int startIndex,int counter)
{
for (int i = 0; i <counter; i++)
for (int j = 0; j < OperationCounterPerDataItem; j++)
arr[startIndex+i]++;
}
上述代码在T400笔记本电脑上执行时花费了776毫秒。
现在,使用.NET 4.0所提供的任务并行库让上述操作并行执行:
//将任务划分为TaskCount个子任务,然后并行执行
static void IncreaseNumberInParallel(int[] arr)
{
int counter = DataSize / TaskCount;
Parallel.For(0, TaskCount, i =>
{
int startIndex = i * counter;
IncreaseNumberInSquence(arr, startIndex, counter);
}
);
}
测试结果为419毫秒,并行加速系数约为1.85。
再改算法,将对每个元素的每个操作设定为一个任务,然后再并行执行:
static void IncreaseNumberInParallel2(int[] arr)
{
//为每个数据项创建一个任务
Parallel.For(0, arr.Length, i =>
{
Parallel.For(0, OperationCounterPerDataItem, j => arr[i]++);
}
);
}
测试结果为10057毫秒,并行加速系数为0.08,比串行算法慢多了!
并行计算带来的复杂性
上面所介绍的例子非常清晰地展示出并行程序设计的特殊性,并不是"并行"总比"串行"快的,到底怎样才能获得最大的并行加速系数,需要仔细地设计并行算法,并且应该在多个典型的软硬件环境中进行对比测试,最终才能得到理想的并行设计方案。 开发并行程序的关键在于要找到一个合适的任务分解方案,并行总要付出一定的代价,比如线程同步、线程通讯、同步缓冲数据等都是开发并行程序必须认真考虑的问题。
下表对比了并行程序与串行程序的主要差别:
项目 | 串行程序 | 并行程序 |
程序行为特性 | 可以预期的,相同运行环境下总可以得到相同的结果 | 如果没有提供特定的同步手段,则程序执行的结果无法预期 |
内存访问 | 独占访问内存单元,数据可靠 | 有可能因多线程同时存取同一内存单元而引发数据存取错误 |
锁 | 不需要 | 必须为共享资源加锁 |
死锁 | 不可能出现 | 可能出现,需要仔细考虑程序中可能出现的种种情况予以避免 |
测试 | 使用代码覆盖的测试方法可以检测出绝大多数BUG
| 由于多个线程同时并行,仅使用代码覆盖的测试方法无法检测出程序中隐藏的BUG,并行程序的测试变得很复杂 |
调试
| 相对简单,可以随时停止程序运行,单步跟踪定位到每条语句和每个变量的值
| 由于多个线程同时运行,当你暂停一个线程进行调试时,其他线程可能还在运行中,因此无法保证调试环境的一致性,并行程序的调试非常困难。 |
正因为并行程序开发、测试和调试都比串行程序要困难,所以一般都是先编写程序的串行版本,等其工作正常之后再将其升级替换为并行版本。
何时使用"并行计算"?
根据前面的介绍,大家一定对"并行计算"有了一个总体的认识,由于"并行"需要付出代价,因此,不是所有的程序都需要转换为并行的,当要处理的数据量很大,或者要执行的数据处理任务繁重,并且这些任务本身就可以分解为互不相关的子任务时,使用并行计算是合适的。 对于哪些规模较小的数据处理任务,比如你要编写一个"通讯簿"小程序来保存和检索好友信息,就不必考虑并行处理了,因为要处理数据量不会很大,串行算法的性能就可以满足需求,还用"并行处理"就显得是"牛刀杀鸡"。除了增加程序开发难度之外没有什么好处。
.NET 4.0中的并行计算组件
由于并行计算是将一个工作任务进行分解以并发执行,因此,任何一个支持并行计算的软件开发与运行平台都必须解决这些并发执行的子任务之间的相互协作问题,比如:
- 一个子任务需要等待其它子任务的完成,多个子任务完成之后才允许执行下一个子任务(即所谓fork-join)。
- 一个子任务结束后自动启动多个下级子任务的执行。
- 允许一个任务中途取消。
- ""
.NET 4.0通过对已有的基类库进行扩充和增强,满足了上述需求。
如图所示,.NET 4.0给 "System.Threading" 命名空间增加了一些新的类,同时对部分已有类也进行了调整和优化。另外,针对中途取消线程或作务执行这一实际开发中非常普遍的需求,提供了一个线程统一取消模型最大的变化是.NET为基类库提供了多个与并行计算密切相关的类,并将它们统一称之为"并行扩展(Parallel Extensions)"。
如图,NET 4.0"并行扩展"的主要包括以下几个部分:
1、并行语言集成查询(PLINQ,Parallel Language Integrated Query),这是.NET 3.0引入的LINQ to Object的换代"产品",让查询操作可以并行执行。
2、任务并行库(TPL,Task Parallel Library):将开发并行程序的抽象级别从"线程(thread)"提升到"任务(Task)",只需规定好计算机要执行的任务,然后由.NET去管理线程的创建和同步等问题。
3、同步的数据结构(CDS,Coordination Data Structures):包括一组线程安全的常用数据结构,比如线程安全的队列、堆栈等,在并行程序中访问这些数据结构,可以不需要显式地使用lock。
4、任务调度器(Task Scheduler):负责任务的创建、执行、暂停等管理工作。
5、线程池:.NET 4.0对原有的托管线程池功能进行了大幅度的增强,通过给其集成一个任务调度器,线程池中的线程可以高效地并行执行各种任务。
上述五个组成部分当中,PLINQ是建立在TPL之上的,而Task Scheduler是并行计算的核心,是一个Runtime,它与线程池相集成,负责将任务分派给线程池中的各个线程执行。
任务并行库原理及应用
任务并行库(TPL:Task Parallel Library)是.NET 4.0为帮助软件工程师开发并行程序而提供的一组类,位于System.Threading和System.Threading.Tasks这两个命名空间中,驻留在3个.NET核心程序集mscorlib.dll、System.dll和 System.Core.dll里。使用这些类,可以让软件工程师在开发并行程序时,将精力更关注于问题本身,而不是诸如线程的创建、取消和同步等繁琐的技术细节。
使用TPL开发并行程序,考虑的着眼点是"任务(task)"而非"线程"。
一个任务是一个Task类的实例,它代表某个需要计算机执行的数据处理工作,其特殊之处在于:
在TPL中,任务通常代表一个可以被计算机并行执行的工作。
任务可以由任何一个线程执行,特定的任务与特定的线程之间没有绑定关系。在目前的版本中,TPL使用.NET线程池中的线程来执行任务。 负责将任务"分派"到线程的工作则由"任务调度器(Task Scheduler)"负责。任务调度器集成于线程池中。 换言之,对于应用软件开发工程师而言,使用TPL开发并行程序,在编程方式上没有任务变化,只不过是编程时多了几个类可用,并且处理数据时需要使用并行算法。
使用任务并行库实现并行处理
上面介绍了基于线程编码实现并行处理的技术要点,可以看到还是比较繁琐的。但使用.NET 4.0的并行库可以简化开发工作。我们略微详细一点地介绍一下示例程序中是如何使用任务并行库实现并行计算的。
其中的一个关键函数是ForRange()函数,先来看看它的声明:
public static ParallelLoopResult ForRange(
int fromInclusive, int toExclusive, Action<int, int> body);
前两个参数代表要计算的数据在数组中的起始和结束索引,第3个参数是一个Action委托,它引用一个将被并行执行的处理函数。 在并行计算程序中,任务的分解方式是一个需要仔细考虑的问题,有一种常用的方案就是依据本机所包容的处理器个数来决定并行处理的任务数,可以直接调用.NET基类库中的类来获取这一信息。
int numberOfPartitions = System.Environment.ProcessorCount;
确定了要分解的任务数,就可以算出每个子任务负责处理的数据项数:
// 获取要计算的数据范围
int range = toExclusive - fromInclusive;
//计算出每个并行任务要计算的数据个数
int stride = range / numberOfPartitions;
if (range == 0) numberOfPartitions = 0;
现在到了关键的部分,我们不是使用线程来执行每个子任务,而是直接调用.NET 4.0任务并行库中的Parallel类来完成这一个工作:
return Parallel.For(0, numberOfPartitions, i =>
{
int start = i * stride;
int end = (i == numberOfPartitions - 1) ? toExclusive : start + stride;
body(start, end);
}
);
Parallel.For()是一个静态方法,它的第3个参数是类型为Action<int>的委托,在这里,我们直接使用Lambda表达式来将一个函数直接"内联"作为For()方法的参数。 For()方法有一个ParallelLoopResult类型的返回值,可以通过此返回值的IsCompleted属性了解For()方法启动的所有任务是否运行结束。
让查询执行得更快——Parallel LINQ
LINQ的出现对于.NET平台而言是一件大事,它使用一种统一的模式查询数据,并且可以紧密地与具体编程语言直接集成。LINQ语句的编写方式是"动态组合"和"递归"的,这与函数式编程语言(如F#)类似,这种编写方式的优点在于代码量小,通过动态组合一些典型的查询运算符,可以实现相当复杂的数据处理逻辑,而同样的功能如果采用传统的编码方式实现,将耗费不少的力气写代码。
.NET 4.0引入的PLINQ是LINQ的"升级换代"技术,它允许以并行方式执行LINQ查询。 使用PLINQ技术的最大好处之一是当计算机处理器个数增加时,不需要修改(或仅需少量修改)源代码,程序性能就可以得到相应的提升。
PLINQ概述
PLINQ主要用于并行执行数据查询,而它本身又是.NET 4.0所引入的并行扩展的有机组成部分,因此,它与LINQ和TPL都有着密切的联系。
LINQ,是英文词组"Language-Integrated Query" 的缩写,中文译为"语言集成的查询",分为LINQ to Object,LINQ to SQL,LINQ to XML,LINQ to DataSet等几个有机组成部分。
在目前的版本中,PLINQ只实现了LINQ to Object的并行执行,换句话说,PLINQ实现了对"内存"中的数据进行并行查询。如果数据来自于数据库或文件,您需要将这些数据加载到内存中才能使用PLINQ。
标准的LINQ查询运算符是由"System.Linq.Enumerable"类所封装的扩展方法实现的,类似地,PLINQ也为所有标准的LINQ查询运算符(如where和select等)提供了并行版本,这些并行的PLINQ查询运算符实现为.NET 4.0新增的"System.Linq.ParallelEnumerable"类的扩展方法。
将LINQ查询转换为PLINQ非常简单,在许多情况下只需简单地添加一个AsParallel子句就行了,例如,以下代码将把整数集合中的偶数挑出来:
//创建一个100个元素的整数集合,保存从1到100的整数.
var source = Enumerable.Range(1, 100);
var evenNums = from num in source.AsParallel()
where num % 2==0
select num;
可以看到,PLINQ查询除了多一个AsParallel子句之外,与标准LINQ的查询并没有什么不同,原有的绝大多数LINQ编程方法仍然继续适用。 当.NET语言编译器"看到"一个查询中包含AsParallel子句代码时,它会在编译期间引用System.Concurrency.dll程序集,将相应的标准LINQ查询运算符替换为对ParallelEnumerable类相应静态方法的调用,同时"悄悄地"将查询的返回值修改为相应的并行版本(比如许多PLINQ查询返回一个ParallelQuery<T>类型的数据集合)。由于ParallelQuery<T>派生自IEnumerable<T>,而后者是许多标准LINQ查询运算符的返回数据类型,因此,PLINQ利用多态性保证了它与原有LINQ代码的最大兼容性。与LINQ类似,PLINQ也具有"延迟执行"的特性,只有对查询集合调用foreach迭代、或者调用ToList之类方法时,PLINQ查询才会真正执行。
设计者在设计PLINQ,追求的一个目标是:PLINQ绝不能比它的前辈--LINQ to Object运行得更慢!如果在某个地方做不到,它就采用串行方式执行。
在真实的应用程序中,要确定到底性能有无提升,请直接运行LINQ和PLINQ的两个版本进行对比测试以决定取舍。
一般来说,对于小数据量的数据集而言,优先选择LINQ而不是PLINQ。
并行计算的未来之路
当前计算机中普遍装备了"双核"CPU,一些新购置的计算机更是装备了"四核"CPU,随着CPU在"多核化"之路上越走越远,并行计算已成为软件技术确定无疑的发展方向。
与CPU多核化趋抛同时出现的是计算机网络的"无孔不入",由此可知,分布式的软件系统也将成为软件技术发展的另一个方向,而分布式的软件系统"天生"就是"并行"的,因此,未来的软件系统一定同时兼具有"并行"和"分布"两大特点。
posted @ 2011-05-12 13:22 藏积 阅读(196) 评论(0)
编辑
.
dynamic元类型
C# 4.0将通过新的元类型"dynamic"来添加对后期绑定的支持。任何直接声明为这种类型的变量,或者从函数中返回这种类型的值,都将自动地视为后期绑定。这类似于在Visual Basic中把变量声明为"object",不过它现在可以支持任何类型系统了,不仅仅是CTS(通用类型规范)和COM。
一个重要之处是,这个特性的目标就是为了支持后期绑定,以及更多地为了支持近来流行的动态绑定。动态类型明显不是C#的一个特性,不过是为了支持动态绑定的一个后果。
还要着重注意的一点是,反射并不是一种很好的替代方案。使用反射的问题在于,需要处理各种各样的类型。使用Reflection命名空间调用方法的方式和在ScriptObject上调用方法的方式并不相同。尤其,Ruby/Python方法这样的第三方方法。
dynamic ExpandoObject
熟悉js的朋友都知道js可以这么写 :
var t = new Object();
t.Abc = "something";
t.Value = 243;
现在这个js动态语言的特性,我们也可以在c#中使用了,前提是将一个变量声明为ExpandoObject类型。如下例:
static void Main(string[] args)
{
dynamic t = new ExpandoObject();
t.Abc = "abc";
t.Value = 10000;
Console.WriteLine("t's abc = {0},t's value = {1}", t.Abc, t.Value);
Console.ReadLine();
}
C# 4.0中新增了一个命名空间System.Dynamic来实现对此应用的支持,这种用法的意义何在,现在我还不太清楚,也是是c#向动态语言过渡的一种试探吧。以下展示的是dynamic可变类型特性
dynamic d = 1;
Console.WriteLine(d.ToString());
d = new DateTime();
Console.WriteLine(d.ToString());
d = "szl";
Console.WriteLine(d.ToString());
dymanic应用场景
按New features in CSharp 4的说法,dymanic主要应用于下面的场景:
1、自动反射
2、COM组件互操作
3、混合编程,例如IronRuby和IronPython
4、处理Html DOM对象
如果有处理过上面这些工作的朋友们,应该不难理解了吧。
有了dynamic的支持让我们看下更强大的功能吧:
反射调用
Type type = Type.GetType("ConsoleApplication1.Test");
dynamic t = Activator.CreateInstance(type);
t.Print("你好,szl,欢迎使用 CSharp 4.0!");
这里我们之间调用了ConsoleApplication1.Test类型中的Print方法,就行创建实例后调用一样,是不是很方便。
P/Invoke 调用
dynamic user32 = new DynamicDllImport("user32.dll",
callingConvention: CallingConvention.Winapi);
user32.MessageBox(0, "Hello World", "Platform Invoke Sample", 0);
DynamicDllImport是Mono发布的一个类,利用它我们可以直接调用WinAPI,这里调用了MessageBox函数。详细内容我会在下一篇文章《.NET4.0新特性之互操作》进行介绍。
动态语言运行
动态语言运行时 (DLR) 是一种运行时环境,它将一组适用于动态语言的服务添加到公共语言运行时 (CLR)。借助于 DLR,可以更轻松地开发要在 .NET Framework 上运行的动态语言,而且向静态类型化语言添加动态功能也会更容易。
动态语言可以在运行时标识对象的类型,而在类似 C# 和 Visual Basic 的静态类型化语言中(当您使用 Option Explicit On 时),您必须在设计时指定对象类型。动态语言的示例有:Lisp、Smalltalk、JavaScript、PHP、Ruby、Python、ColdFusion、Lua、Cobra 和 Groovy。
大多数动态语言都会向开发人员提供以下优点:
可以使用快速反馈循环(REPL 或读取-计算-打印循环)。这样,您就可以在输入几条语句之后立即执行它们以查看结果。
同时支持自上而下的开发和更传统的自下而上的开发。例如,当您使用自上而下的方法时,可以调用尚未实现的函数,然后在需要时添加基础实现。
更易于进行重构和代码修改操作,原因是您不必在代码中四处更改静态类型声明。
利用动态语言可以生成优秀的脚本语言。利用新的命令和功能,客户可以轻松地扩展使用动态语言创建的应用程序。动态语言还经常用于创建网站和测试工具、维护服务器场、开发各种实用工具以及执行数据转换。
DLR 的目的是允许动态语言系统在 .NET Framework 上运行,并为动态语言提供 .NET 互操作性。在 Visual Studio 2010 中,DLR 将动态对象引入到 C# 和 Visual Basic 中,以便这些语言能够支持动态行为,并且可以与动态语言进行互操作。
DLR 还可帮助您创建支持动态操作的库。例如,如果您具有一个使用 XML 或 JavaScript 对象表示法 (JSON) 对象的库,则对于使用 DLR 的语言,您的对象可以显示为动态对象。这使库用户能够编写语法更简单且更自然的代码,以便操作对象和访问对象成员。
DLR 体系结构
下图显示了动态语言运行时的体系结构。
DLR 向 CLR 中添加了一组服务,以便更好地支持动态语言。这些服务包括:
- 表达式树。DLR 使用表达式树来表示语言语义。为此,DLR 对 LINQ 表达式树进行了扩展,以便包括控制流、工作分配以及其他语言建模节点。有关更多信息,请参见表达式树(C# 和 Visual Basic)。
- 调用站点缓存。动态调用站点是代码中用于对动态对象执行类似 a + b 或 a.b() 的操作的位置。DLR 将缓存 a 和 b 的特性(通常是这些对象的类型)以及有关操作的信息。如果之前已执行过此类操作,则 DLR 将从缓存中检索所有必需的信息,以实现快速调度。
- 动态对象互操作性。DLR 提供一组表示动态对象和操作的类和接口,可供语言实施者和动态库的作者使用。这些类和接口包括IDynamicMetaObjectProvider、DynamicMetaObject、DynamicObject 和 ExpandoObject。
DLR 通过在调用站点中使用联编程序,不仅可以与 .NET Framework 通信,还可以与其他基础结构和服务(包括 Silverlight 和 COM)通信。联编程序将封装语言的语义,并指定如何使用表达式树在调用站点中执行操作。这样,使用 DLR 的动态和静态类型化语言就能够共享库,并获得对 DLR 支持的所有技术的访问权。
C#与Python混合编程
下面演示在C#调用python代码,以下是python.py文件中的python代码
#类
class MyService(object):
def GetData(self, value):
return "hello" + value
#占位pass
pass
#方法
def MyFunction(name):
return "hello " * 8 + name
#读取文件
def read(file):
path=file
f=open(path,"r")
for line in f:
print("每一行的数据是:%s"%line)
f.close()
#默认值
def add(a,b=2):
return str(a+b)
下面是一直相对应的C#调用代码
//直接编写python
var engine = Python.CreateEngine();
engine.Execute("print(23)");
//调用文件中的python
var python = Python.CreateRuntime();
dynamic script = python.UseFile("python.py");
//调用Python里的类
var service = script.MyService();
var result = service.GetData("aaa");
Console.WriteLine(result);
//调用中的函
result = script.MyFunction("aaa");
//默认值
result = script.add(2);
Console.WriteLine(result);
//读取文件
script.read("python.py");
posted @ 2011-05-12 13:18 藏积 阅读(224) 评论(0)
编辑
一、应用程序兼容性和部署
除了一些在安全、标准遵从、正确性、可靠性及性能等方面的改进之外,.NET4与基于早期.NET版本构建的应用程序高度兼容。
.NET4并不会自动使用当前版本的公共语言运行库来运行使用早期版本的.NET构建的应用程序。为了在.NET4下运行以前的应用程序,你必须使用在您的Visual Studio项目的属性中指定的目标.NET版本重新编译您的应用程序,或者在应用程序的配置文件中使用元素来指定支持的运行时刻库。
二、内核新功能及改进
(一)诊断和性能
早期版本的.NET并没有提供一种方法来确定一个特定的应用程序域是否正在影响其他应用程序域,因为操作系统的API及相关工具(如Windows任务管理器)都被精确设计到进程级。从.NET4开始,你可以在每一个应用程序域中获取处理器及内存的使用估测信息。
您可以监控每一个应用程序域的CPU和内存使用情况。可以通过托管和本机宿主API以及Windows事件跟踪(ETW)来进行应用程序域资源的监控。当启用此功能后,它能够在进程的整个生命周期过程中收集所有应用程序域的统计信息。有关此主题,你也可以参考新属性AppDomain.MonitoringIsEnabled的介绍。
现在,您可以为了诊断目的而访问ETW事件以提高性能。
(二)垃圾收集
.NET4支持后台垃圾收集。此功能取代了以前版本中的并行垃圾收集,从而提供更好的系统性能。
(三)代码契约
代码契约功能允许你指定那些仅通过方法或类型的签名尚不能描述的契约信息。新命名空间System.Diagnostics.Contracts中包含的类提供了一种独立于语言的方法并使用前置条件、后置条件和对象不变量等形式来表达编码假设。这些契约通过运行时检查能够改进测试,支持静态契约校验,并支持文档生成。
(四)仅使用设计时互操作程序集
如今,你不再需要携带主互操作程序集(PIA)来部署与COM对象互操作的应用程序。在.NET4中,编译器可以从互操作程序集中嵌入类型信息,而且能够仅选择那些应用程序(例如,一个插件)实际使用的类型。类型安全由公共语言运行时来保障。
(五)动态语言运行时
动态语言运行时(DLR)是一个新的运行时环境,它把一组针对动态语言的服务添加到CLR中。DLR使开发运行于.NET上的动态语言更加容易,而且有利于把动态特性添加到静态类型的语言中。为了支持DLR,.NET中添加了一个新的命名空间System.Dynamic。
通过引入新的描述控制流的类型,例如System.Linq.Expressions.LoopExpression和System.Linq.Expressions.TryExpression,表达式树功能被进一步扩展。注意,这些新的类型仅为动态语言运行时(DLR)所使用,而不能够由LINQ使用。
下面示例:C#调用python代码
static void Main(string[] args)
{
Action action = () => Console.WriteLine("Hello World");
ScriptRuntime runtime = Python.CreateRuntime();
dynamic script = runtime.UseFile("script.py");
dynamic decorator = script.decorator(action);
decorator.execute();
}
}
}
在创建的工程的bin\debug目录下,添加script.py.代码如下,一个很简单的wrapper,仅供验证之用。
def wrapper(function):
def inner(self):
print "Decorator... "
return function(self)
return inner
class decorator(object):
def __init__(self, function):
self.action = function
@wrapper
def execute(self):
self.action()
(六)协变与反变
有几种泛型接口和委托现在支持协变与反变功能。
(七)BigInteger和复数
新的System.Numerics.BigInteger结构是一个任意精度的整数数据类型,它支持所有标准的整数运算,其中包括位操作。BigInteger可以用于任何.NET语言中。此外,一些新的.NET语言(如F#和IronPython)都已内置了对此结构的支持。
新的System.Numerics.Complex结构用于描述一个复数,而且支持复数的算术运算和三角运算。
(八)元组
.NET4引入了System.Tuple类,用于创建包含结构化数据的元组对象。另外,还提供了泛型元组类以便支持具有1至8个组件的元组。为了支持有9个或更多组件的元组对象,提供了一个泛型元组类,此类使用7个类型参数,而第8个参数可以是任何的元组类型。
(九)文件系统枚举功能方面的改进
.NET4又增加了新的文件枚举方法,从而进一步提高访问巨型文件目录或遍历大文件的应用程序的性能。
(十)内存映射文件
.NET现在支持内存映射文件。你可以使用内存映射文件来编辑非常大的文件,还可以为进程间通信创建共享内存。
(十一)64位操作系统与进程
您可以使用Environment.Is64BitOperatingSystem和Environment.Is64BitProcess属性来标识64位的操作系统和进程。
当你打开基键时,您可以使用Microsoft.Win32.RegistryView枚举来指定一个32位或64位的注册表视图。
三、托管扩展框架
托管扩展性框架(MEF)是.NET4中的一个新库,帮助您构建可扩展的和可组合式应用程序。MEF可以让您指定在一个应用程序中的扩展点,给其他扩展的应用程序提供服务,以及创建可扩展应用程序所使用的部件等。MEF还支持很容易地基于元数据搜索到可用的部件,而无需为这些部件加载相应的程序集。
四、并行计算
.NET 4针对编写多线程和异步代码引入了一个新的编程模式,从而极大地简化了应用程序和库开发者的编程。此新的模式可以使开发人员以一种自然的方式来编写高效的,良好粒度的,可扩展的并行代码,而不必直接使用线程或线程池等。新的System.Threading.Tasks命名空间和其他相关类型支持这种新模式。并行LINQ(PLINQ),作为LINQ to Objects的一种并行实现,能够通过声明性语法支持类似的功能。
代码示例:
//并行化的Linq,只需杂集合类型后加上.AsParallel()
var q = from p in people.AsParallel()
where p.Name == queryInfo.Name &&
p.State == queryInfo.State &&
p.Year >= yearStart &&
p.Year <= yearEnd
orderby p.Year ascending
select p;
//并行化的for
Parallel.For(0, 16, i =>
{
Console.WriteLine("TID={0}, i={1}",
Thread.CurrentThread.ManagedThreadId,
i);
});
五、网络编程
网络编程方面的改进包括以下内容:
针对Windows身份验证的安全改进体现在几个类中,包括System.Net.HttpWebRequest,System.Net.HttpListener,System.Net.Mail.SmtpClient,System.Net.Security.SslStream和 System.Net.Security.NegotiateStream。在Windows 7和Windows Server 2008 R2中运行的应用程序可以使用扩展保护功能。
六、Web开发
ASP.NET版本4在以下几个方面引入了一些新特点:
核心服务,包括一个新的允许您扩展缓存的API,支持压缩会话状态数据和一个新的应用程序预加载管理器(自动启动功能)。
Web窗体方面,其中包括为ASP.NET路由提供的更加集成化的支持,对Web标准的增强支持,更新的浏览器支持,为数据控件增加了一些新功能,以及为视图状态管理增加了一些新的功能。
Web窗体控件方面,提供了一个新的图表控件。
MVC框架方面,包括针对视图的新的辅助方法,对分区的MVC应用程序的支持,并提供了异步控制器。
动态数据方面,包括针对现有Web应用程序的支持,全对多对多关系和继承的支持,对字段模板和属性的支持,以及增强的数据过滤功能。
在Microsoft AJAX库中增加了针对客户端中心型Ajax应用程序的额外支持。
Visual Web Developer中,包括了改进的针对JScript的智能感知支持,针对HTML和ASP.NET标记的新的自动完整代码片段支持,以及增强的CSS兼容性支持。
部署方面,为自动化的典型的部署任务增加了新的支持工具。
多目标方面,包括针对无法在.NET的目标版本中可用功能的更好的过滤支持。
七、客户端开发
Windows Presentation Foundation
在.NET4中,Windows Presentation Foundation(WPF)在许多方面都发生了变化并进行了改进,包括控件、图形和XAML等等。
八、数据
(一)ADO.NET
ADO.NET的实体框架中提供了新功能,包括持久化透明对象(Persistence-Ignorant Objects,译者注:这种对象具有不必事先在DBMS中建立实体信息就可以利用实体框架的DDL生成功能将POCO对象结构转换成实体信息结构以存入信息库中的能力。),在LINQ查询功能和自定义对象层代码生成等。有关该内容的更多的信息,请参阅《ADO.NET新特征》。
(二)动态数据
对于ASP.NET 4来说,动态数据支持得到进一步增强,从而给你以更强大的功能快速建立数据驱动的网站。这包括以下内容:
基于在数据模型定义的约束的自动验证功能。
通过使用已成为动态数据项目的一部分的字段模板,能够方便地改变在GridView和DetailsView控件中针对相应字段生成的标记。
九、通信和工作流
Windows通信基础(WCF)实现了消息管理方面的增强并提供与Windows工作流基础(WF)的无缝集成。WF提供的改进体现在性能、可扩展性、工作流建模以及一个更新的可视化设计器等方面。
posted @ 2011-05-12 13:17 藏积 阅读(72) 评论(0)
编辑
这一篇我们主要讲Android游戏开发中常用的开发技巧,并写了一个简单的游戏框架。
一、设置全屏以及绘画简单的图形
package cn.szl;
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
public class MainActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
//隐去电池等图标和一切修饰部分(状态栏部分)
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
// 隐去标题栏(程序的名字)
setContentView(new MyView(this));
}
}
注意: 隐去标题(应用的名字) 此设定必须要写在setContentView之前,否则会有异常!
对于设置全屏,主要就两点:
一点是设置隐去状态栏部分,包括电池等图标,第二点无疑就是把我们应用的名字也隐去不显示,这样一来就全屏了。
package cn.szl;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.View;
public class MyView extends View {
private Paint paint ;
public MyView(Context context) {
super(context);
paint = new Paint();
paint.setAntiAlias(true);//设置画笔无锯齿(如果不设置可以看到效果很差)
this.setKeepScreenOn(true);//设置背景常亮
paint.setColor(Color.RED);
}
@Override
public void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);//设置刷屏颜色
Rect rect = new Rect(30,30,50,50); //这里最后两个参数不是宽高、而是矩形右下角的坐标
canvas.drawRect(rect, paint);
RectF rectF = new RectF(70f,30f,90f,90f);//RectF 只是矩形 float形式 只是跟Rect精确度不一样
canvas.drawArc(rectF, 0, 360, true, paint);
canvas.drawCircle(150, 30, 20, paint);//这也是画圆 第三个参数为半径
float[] points =new float[]{200f,10f,200f,40f,300f,30f,400f,70f};
canvas.drawLines(points, paint);
// canvas.drawLines(points, 1, 4, paint);//选取特定点数组中两点来画出一条直线
canvas.drawText("Szl", 230, 30, paint);
}
}
设置横竖屏也可以在AndroidManifest.xml中定义:
android:theme="@android:style/Theme.NoTitleBar" 隐去标题栏android:theme="@android:style/Theme.NoTitleBar.Fullscreen" 隐去状态栏
二、剖析游戏开发用view还是sarfaceView
在Android游戏当中充当主要的除了控制类外就是显示类,在J2ME中我们用Display和Canvas来实现这些,而Google Android中涉及到显示的为view类,Android游戏开发中比较重要和复杂的就是显示和游戏逻辑的处理。
这里我们说下android.view.View和android.view.SurfaceView。SurfaceView是从View基类中派生出来的显示类,直接子类有GLSurfaceView和VideoView,可以看出GL和视频播放以及Camera摄像头一般均使用SurfaceView,到底有哪些优势呢? SurfaceView可以控制表面的格式,比如大小,显示在屏幕中的位置,最关键是的提供了SurfaceHolder类,使用getHolder方法获取,相关的有Canvas lockCanvas()
Canvas lockCanvas(Rect dirty) 、void removeCallback(SurfaceHolder.Callback callback)、void unlockCanvasAndPost(Canvas canvas) 控制图形以及绘制,而在SurfaceHolder.Callback 接口回调中可以通过重写下面方法实现。
使用的SurfaceView的时候,一般情况下要对其进行创建,销毁,改变时的情况进行监视,这就要用到 SurfaceHolder.Callback.
class XxxView extends SurfaceView implements SurfaceHolder.Callback {
public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}
//看其名知其义,在surface的大小发生改变时激发
public void surfaceCreated(SurfaceHolder holder){}
//同上,在创建时激发,一般在这里调用画图的线程。
public void surfaceDestroyed(SurfaceHolder holder) {}
//同上,销毁时激发,一般在这里将画图的线程停止、释放。
}
对于Surface相关的,Android底层还提供了GPU加速功能,所以一般实时性很强的应用中主要使用SurfaceView而不是直接从View构建,同时后来做android 3d OpenGL中的GLSurfaceView也是从该类实现。
SurfaceView和View最本质的区别在于,surfaceView是在一个新起的单独线程中可以重新绘制画面而View必须在UI的主线程中更新画面。
那么在UI的主线程中更新画面 可能会引发问题,比如你更新画面的时间过长,那么你的主UI线程会被你正在画的函数阻塞。那么将无法响应按键,触屏等消息。
当使用surfaceView 由于是在新的线程中更新画面所以不会阻塞你的UI主线程。但这也带来了另外一个问题,就是事件同步。比如你触屏了一下,你需要surfaceView中thread处理,一般就需要有一个event queue的设计来保存touch event,这会稍稍复杂一点,因为涉及到线程同步。
所以基于以上,根据游戏特点,一般分成两类。
1 被动更新画面的。比如棋类,这种用view就好了。因为画面的更新是依赖于 onTouch 来更新,可以直接使用 invalidate。 因为这种情况下,这一次Touch和下一次的Touch需要的时间比较长些,不会产生影响。
2 主动更新。比如一个人在一直跑动。这就需要一个单独的thread不停的重绘人的状态,避免阻塞main UI thread。所以显然view不合适,需要surfaceView来控制。
Android中的SurfaceView类就是双缓冲机制。因此,开发游戏时尽量使用SurfaceView而不要使用View,这样的话效率较高,而且SurfaceView的功能也更加完善。
考虑以上几点,所以一般选用 SurfaceView 来进行游戏开发。
三、剖析Surface Callback以及SurfaceHolder
之前我们对view和surfaceview 做了比较和取舍,最后我们发现surfaceview更加的适合运作与游戏开发中,那么下面就让我们来看看这个surfaceview的结构吧;
先上一段代码:
package cn.szl;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.SurfaceHolder.Callback;
import android.view.animation.Animation;
public class MySurfaceView extends SurfaceView implements Callback, Runnable {// 备1
private SurfaceHolder sfh;
private Thread th;
private Canvas canvas;
private Paint paint;
private int ScreenW, ScreenH;
public MySurfaceView(Context context) {
super(context);
th = new Thread(this);
sfh = this.getHolder();
sfh.addCallback(this); // 备注1
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
this.setKeepScreenOn(true);// 保持屏幕常亮
}
@Override
public void startAnimation(Animation animation) {
super.startAnimation(animation);
}
public void surfaceCreated(SurfaceHolder holder) {
ScreenW = this.getWidth();// 备注2
ScreenH = this.getHeight();
th.start();
}
private void draw() {
try {
canvas = sfh.lockCanvas(); // 得到一个canvas实例
canvas.drawColor(Color.WHITE);// 刷屏
canvas.drawText("Szl", 100, 100, paint);// 画文字文本
canvas.drawText("这就是简单的一个游戏框架", 100, 130, paint);
} catch (Exception ex) {
} finally { // 备注3
if (canvas != null)
sfh.unlockCanvasAndPost(canvas); // 将画好的画布提交
}
}
public void run() {
while (true) {
draw();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
}
}
代码很简单,我们继承继承surfaceview类,并且使用回调callback接口以及线程runnable接口。那么这里我简单的说下Callback接口和SurfaceHolder 类的作用;
//备注1
callback接口:
只要继承SurfaceView类并实现SurfaceHolder.Callback接口就可以实现一个自定义的SurfaceView了,SurfaceHolder.Callback在底层的Surface状态发生变化的时候通知View,SurfaceHolder.Callback具有如下的接口:
surfaceCreated(SurfaceHolder holder):当Surface第一次创建后会立即调用该函数。程序可以在该函数中做些和绘制界面相关的初始化工作,一般情况下都是在另外的线程来绘制界面,所以不要在这个函数中绘制Surface。
surfaceChanged(SurfaceHolder holder, int format, int width,int height):当Surface的状态(大小和格式)发生变化的时候会调用该函数,在surfaceCreated调用后该函数至少会被调用一次。
SurfaceHolder 类:
它是一个用于控制surface的接口,它提供了控制surface 的大小,格式,上面的像素,即监视其改变的。
SurfaceView的getHolder()函数可以获取SurfaceHolder对象,Surface 就在SurfaceHolder对象内。虽然Surface保存了当前窗口的像素数据,但是在使用过程中是不直接和Surface打交道的,由SurfaceHolder的Canvas lockCanvas()或则Canvas lockCanvas()函数来获取Canvas对象,通过在Canvas上绘制内容来修改Surface中的数据。如果Surface不可编辑或则尚未创建调用该函数会返回null,在 unlockCanvas() 和 lockCanvas()中Surface的内容是不缓存的,所以需要完全重绘Surface的内容,为了提高效率只重绘变化的部分则可以调用lockCanvas(Rect rect)函数来指定一个rect区域,这样该区域外的内容会缓存起来。在调用lockCanvas函数获取Canvas后,SurfaceView会获取Surface的一个同步锁直到调用unlockCanvasAndPost(Canvas canvas)函数才释放该锁,这里的同步机制保证在Surface绘制过程中不会被改变(被摧毁、修改)。
// 备注2
我没有在该surfaceview的初始化函数中将其 ScreenW 与 ScreenH 进行赋值,这里要特别注意,如果你在初始化调用ScreenW = this.getWidth();和ScreenH = this.getHeight();那么你将得到很失望的值 全部为0;原因是和接口Callback接口机制有关,当我们继承callback接口会重写它的surfaceChanged()、surfaceCreated()、surfaceDestroyed(),这几个函数当surfaceCreated()被执行的时候,真正的view才被创建,也就是说之前得到的值为0 ,是因为初始化会在surfaceCreated()方法执行以前执行,view没有的时候我们去取屏幕宽高肯定是0,所以这里要注意这一点;
//备注3
这里我把draw的代码都try起来,主要是为了当画的内容中一旦抛出异常了,那么我们也能 在finally中执行该操作。这样当代码抛出异常的时候不会导致Surface出去不一致的状态。
四、Android 游戏框架
其实上面分析surfaceview的内容就是一个简单的游戏框架了,当然这里再强调一下,简单的游戏框架,所以不要高手们不要乱喷。
这个Demo是一个对图片操作以及按键处理,游戏简单框架的一个demo,这里放出给大家分享。
package cn.szl;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.SurfaceHolder.Callback;
public class MySurfaceView extends SurfaceView implements Callback, Runnable {
private Thread th = new Thread(this);
private SurfaceHolder sfh;
private int SH, SW;
private Canvas canvas;
private Paint p;
private Paint p2;
private Resources res;
private Bitmap bmp;
private int bmp_x = 100, bmp_y = 100;
private boolean UP, DOWN, LEFT, RIGHT;
private int animation_up[] = { 3, 4, 5 };
private int animation_down[] = { 0, 1, 2 };
private int animation_left[] = { 6, 7, 8 };
private int animation_right[] = { 9, 10, 11 };
private int animation_init[] = animation_down;
private int frame_count;
public MySurfaceView(Context context) {
super(context);
this.setKeepScreenOn(true);
res = this.getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.enemy1);
sfh = this.getHolder();
sfh.addCallback(this);
p = new Paint();
p.setColor(Color.YELLOW);
p2 = new Paint();
p2.setColor(Color.RED);
p.setAntiAlias(true);
setFocusable(true); //备注1
}
public void surfaceCreated(SurfaceHolder holder) {
SH = this.getHeight();
SW = this.getWidth();
th.start();
}
public void draw() {
canvas = sfh.lockCanvas();
canvas.drawRect(0, 0, SW, SH, p); //备注2
canvas.save(); //备注3
canvas.drawText("Szl", bmp_x-2, bmp_y-10, p2);
canvas.clipRect(bmp_x, bmp_y, bmp_x + bmp.getWidth() / 13, bmp_y+bmp.getHeight());
if (animation_init == animation_up) {
canvas.drawBitmap(bmp, bmp_x - animation_up[frame_count] * (bmp.getWidth() / 13), bmp_y, p);
} else if (animation_init == animation_down) {
canvas.drawBitmap(bmp, bmp_x - animation_down[frame_count] * (bmp.getWidth() / 13), bmp_y, p);
} else if (animation_init == animation_left) {
canvas.drawBitmap(bmp, bmp_x - animation_left[frame_count] * (bmp.getWidth() / 13), bmp_y, p);
} else if (animation_init == animation_right) {
canvas.drawBitmap(bmp, bmp_x - animation_right[frame_count] * (bmp.getWidth() / 13), bmp_y, p);
}
canvas.restore(); //备注3
sfh.unlockCanvasAndPost(canvas);
}
public void cycle() {
if (DOWN) {
bmp_y += 5;
} else if (UP) {
bmp_y -= 5;
} else if (LEFT) {
bmp_x -= 5;
} else if (RIGHT) {
bmp_x += 5;
}
if (DOWN || UP || LEFT || RIGHT) {
if (frame_count < 2) {
frame_count++;
} else {
frame_count = 0;
}
}
if (DOWN == false && UP == false && LEFT == false && RIGHT == false) {
frame_count = 0;
}
}
@Override
public boolean onKeyDown(int key, KeyEvent event) {
if (key == KeyEvent.KEYCODE_DPAD_UP) {
if (UP == false) {
animation_init = animation_up;
}
UP = true;
} else if (key == KeyEvent.KEYCODE_DPAD_DOWN) {
if (DOWN == false) {
animation_init = animation_down;
}
DOWN = true;
} else if (key == KeyEvent.KEYCODE_DPAD_LEFT) {
if (LEFT == false) {
animation_init = animation_left;
}
LEFT = true;
} else if (key == KeyEvent.KEYCODE_DPAD_RIGHT) {
if (RIGHT == false) {
animation_init = animation_right;
}
RIGHT = true;
}
return super.onKeyDown(key, event);
}
/* (non-Javadoc)
* @see android.view.View#onKeyUp(int, android.view.KeyEvent)
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (DOWN) {
DOWN = false;
} else if (UP) {
UP = false;
} else if (LEFT) {
LEFT = false;
} else if (RIGHT) {
RIGHT = false;
}
return super.onKeyUp(keyCode, event);
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
draw();
cycle();
try {
Thread.sleep(100);
} catch (Exception ex) {
}
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// TODO Auto-generated method stub
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
}
}
备注1
此方法是用来响应按键!如果是自己定义一个继承自View的类,重新实现onKeyDown方法后,只有当该View获得焦点时才会调用onKeyDown方法,Actvity中的onKeyDown方法是当所有控件均没有处理该按键事件时,才会调用.
备注2
这里也是对屏幕进行刷屏操作,其实这也只是一种,之前文章里我也用到drawRGB的方法同样实现,当然也可以用fillRect等来刷屏。
那么这里我想说下,在继承view中,因为onDraw方法是系统自动调用的,不像在surfaceview这里这样去在run里面自己去不断调用,在view中我们可以抵用 invalidate()/postInvalidate() 这两种方法实现让系统调用onDraw方法,这里也是和surfaceview中的不同之一!
备注3
这里canvas.save();和canvas.restore();是两个相互匹配出现的,作用是用来保存画布的状态和取出保存的状态的。这里稍微解释一下,
当我们对画布进行旋转,缩放,平移等操作的时候其实我们是想对特定的元素进行操作,比如图片,一个矩形等,但是当你用canvas的方法来进行这些操作的时候,其实是对整个画布进行了操作,那么之后在画布上的元素都会受到影响,所以我们在操作之前调用canvas.save()来保存画布当前的状态,当操作之后取出之前保存过的状态,这样就不会对其他的元素进行影响
对于 canvas.save();和canvas.restore(); 还有不少人不懂,OK、我再补充点:
代码段1:
public void draw() {
Canvas canvas = sfh.lockCanvas();
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(bmp1, 0,0,paint);
canvas.save();
canvas.scale(5f, 5f);
canvas.restore();
canvas.drawBitmap(bmp2, 0,0,paint);
sfh.unlockCanvasAndPost(canvas);
}
代码段2:
public void draw() {
Canvas canvas = sfh.lockCanvas();
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(bmp1, 0,0,paint);
canvas.scale(5f, 5f);
canvas.drawBitmap(bmp2, 0,0,paint);
sfh.unlockCanvasAndPost(canvas);
}
上面这两个代码片段中我们都假设有两张图片 bmp1和bmp2,并且都画在画布上!
那么代码段1和代码段2的不同:
代码段1中我们进行画布缩放的之前保存了画布状态,做了缩放操作之后又取出之前保存的状态,这样做是为了保证bmp2正常画出来不受到缩放的影响!
代码段2里,画了bmp1后就执行了缩放操作,并且没有保存状态!紧接着画了bmp2,那么bmp2也会一样受到缩放的影响!!
所以我们如果单独处理一张图片的时候,而且不想影响其他部分的绘制,那么应该如下来做:
view plaincopy to clipboardprint?
public void draw() {
Canvas canvas = sfh.lockCanvas();
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(bmp1, 0,0,paint);
canvas.save();
canvas.scale(5f, 5f);
canvas.drawBitmap(bmp2, 0,0,paint);
canvas.restore();
sfh.unlockCanvasAndPost(canvas);
}
posted @ 2011-05-12 12:04 藏积 阅读(734) 评论(0)
编辑
这一篇我们主要讲Android游戏开发中涉及到的数学、物理、AI知识,有了这些基础我们能更好地进行游戏的开发。
一、数学
两点间的距离
在人工智能程序中,智能体可以通过判断敌人与自己的距离来决定发动攻击的时机。
2D场景中的距离公式:
设点P1(x1, y1)和P2(x2, y2)分别为线上的点,他们的距离d的计算方法如下:
3D场景中的距离公式:
在碰撞检测中的应用
可以在游戏中利用圆或球的边界进行碰撞检测。当然也可以利用其他图形。不过圆和球都可以方便地进行数学计算,它们在检测的速度上也优于其他图形。虽然精确度不高,但是可以作为外围检测。
两圆之间,如果两圆心的距离小于两圆的半径和,即发生碰撞。
设两圆方程分别为:(x – h1)2 + (y – k1)2 = r12和(x – h2)2 + (y – k2)2 = r22。如果:
,则两圆发生碰撞。
由于开方运算会占用大量的处理器资源,所以建议使用平方进行比较。
利用圆边界进行碰撞检测是一种较快的方法,但是极有可能会产生错误的碰撞检测结果,所以避免错误的方法是寻找一种更适合的图形来检测。只要这个图形可以用数学公式表示出来。也可以使用多重圆形进行多重检测,先检测外面的圆,如果发生碰撞则检测内部的圆,减小错误判断的几率。但多重检测会消耗更多的CPU时间。
三角函数
所有的三角函数都是直角三角形中定义的。

常用角度的三角函数值
(角度)
| (弧度)
| sin | cos | tan |
0 | 0 | 0 | 1 | 0 |
30 | /6
| 0。5 | = 0。866
| = 0。5774
|
45 | /4
| =0。7071
| =0。7071
| 1 |
60 | /3
| = 0。866
| 0。5 | 
|
90 | /2
| 1 | 0 | 不可算 |
120 | 2 /3 | = 0。866
| -0。5 | - |
180 | 
| 0 | -1 | 0 |
270 | 3 /2 | -1 | 0 | 不可算 |
360 | 0 | 0 | 1 | 0 |
在真正进入游戏主循环之前,可以建立一个三角函数的查找表,这样在游戏中需要用到三角函数值的时候就不用重新计算,只需进行查表工作就可以,大大加快运行速度。
角的正弦值在第一、第二象限是正值;
角的余弦值在第一、第四象限是正值;
角的正切值在第一、第三象限是正值;
对于所有的反三角函数,如果传入的参数是正值,那么它们的返回值都是正,即意味着该角位于第一象限;如果传入的参数为负数,那么反正弦asin()和反正切atan()的返回角度将位于第四象限,而反余弦acos()的返回角度位于第二象限。
二、物理
一维空间运动
物体只要移动就会有速率,速率是用来表示物体运动的快慢。相应的,如果一个物体有速率,那么它就会有速度,速度是速率的向量形式。即速度是有方向的速率。
匀速运动的公式:
位移 = 速度 × 时间 (D = v * t)
路程 = 速率 × 时间 (S = v * t)
平均速度:
加速度
加速度用来衡量速率的变化快慢程度。
vf表示末速度,vi表示初速度。
tf表示末速度对应的时间,vi表示初速度对应的时间。
如果a的方向和v一致,那么表示加速运动,如果相反,表示减速运动。
力
当我们准备用程序来模拟物体的运动时,首先要对我们所要移动的物体进行受力分析。物体所受力的总和决定了它的运动模式。
重力
物体运动的竖直分量加速度为-g或-9。8m/s2。我们可以利用重力加速度和物体的质量求出物体所受重力。重力是一个向量,方向指向地心。
重力公式:
w = mg 其中m是物体质量,g是重力加速度。
如果在游戏编程中,在不同星球之间切换,要考虑各个星球的不同重力加速度。
重力的单位是牛顿,记为N,1N = 1kg×m/s2。
支持力
支持力作用在物体表面,抵消重力并保证它不向下落。支持力是正交的,总是垂直于物体表面。
对于斜面上的物体,支持力减小了物体竖直方向的加速度。
摩擦力
摩擦力分为两种形式,静摩擦力和滑动摩擦力。
静摩擦力可以使物体保持稳定的状态,而滑动摩擦力则可以使物体减速。
如果物体所受的其他力总和小于静摩擦力,那么物体保持静止。一旦其他的力大于静摩擦力,物体开始运动,静摩擦力就变成滑动摩擦力。两种摩擦力都取决于它们的接触面。接触面越光滑,摩擦力越小。
要计算摩擦力,就必须知道摩擦系数。
静摩擦力:
其中N为支持力。
滑动摩擦力:
其中N为支持力。
牛顿定律
牛顿第一定律:当物体所受合力为0时,它将保持原有的运动状态不变。
牛顿第二定律:Fnet = ma。F是合力,m是物体质量,a是物体的加速度。
即,① 一个物体受到合力越大,速度改变得越快。
② 如果两个物体受到的合力相同,那么质量小的物体速度改变更快。
牛顿第三定律:对于每个力,都有一个与之方向相反、大小相同的反作用力。
动量和碰撞
跟静止物体的碰撞
和静止物体的碰撞可以用向量反射来研究物体的运动,这种运动存在着一种对称性,球射入的角度必然等于它射出的角度,即入射角等于反射角。
向量的轴平行反射
如果边是竖直方向的,则vf = [-vix, viy];
如果边是水平方向的,则vf = [vix, -viy];
入射向量:vi = [vix, viy]。
向量的非轴平行反射
如右图:球的入射方向为vi,求射出方向vf
先给公式:vf = 2·P + vi
其中vi是初速度,P为-vi基于分界线法线的发射向量。
写出边界B的矩阵[Δx Δy];
求出边界的垂直向量N = [Δy -Δx];
将N单位化得N' = [Δy / ||N|| -Δx/||N||];
求发射向量P,P=(-vi·N')·N'
计算反射向量vf,vf = 2·P + vi
动量和冲量
动量
P = mv,其中m是物体的质量,v是物体的速度。
如果物体的速度用矩阵表示,那么物体的动量也用矩阵表示。
冲量
冲量 = Ft = Δp,其中F是合力,t是时间,Δp是动量增量。
碰撞建模
动量定理的变形:
m1v1i + m2v2i = m1v1f + m2v2f
下标1表示第一个物体,下标2表示第二个物体。
每个碰撞的情况都介于弹性碰撞和非弹性碰撞之间。弹性碰撞是一种没有动量损失的碰撞。但现实中的碰撞往往伴随着能量损失,因此可以用还原系数ε来表示能量损失的大小。
(v1f – v2f) = -ε(v1i – v2i) 0<ε<1
三、AI
AI的定义
首先我们需要明白AI是什么,AI全称是Actificial Intelligence人工智能。其实这一词汇并非为游戏制作而产生,对于它的研究是广而且深的,它包含着机器视觉到专家系统等一大系列,当然我们没必要去细细了解它,我们仅仅需要了解的就是它对游戏制作有关的着一部分。
游戏规则分为三大部分,规则对象,规则事件和相应规则。而我们所述说的对象主要是玩家控制的角色,当然非控制角色也有着自己的规则和事件,这时就需要我们赋予它们以类似于人类的智能。所以我们可以简化为一句话:让游戏中的NPC获得分析,判断的能力,并进行相应行为的设计,我们称之为游戏AI设计。
游戏中AI设计的目的
首先我们可以看一个GDC(Game Development Conference)上对游戏业界中AI技术的调查年份游戏公司中拥有专门负责AI小组的公司百分比 游戏中用于AI运算的CPU资源
2007 | 24% | 5% |
2008 | 46% | 10% |
2009 | 80% | 25% |
从中可见,游戏中AI发展十分迅猛,游戏的AI更加受到各个游戏制作公司的重视。而它为什么这么受到亲睐和重视呢?AI对游戏起到了什么作用呢?
1、增加玩家的挑战性
在许多以前的老游戏中,大家可以发现其中并没有很多的AI痕迹。例如<超级玛丽>吧,里面的怪物出现种类,顺序,行走方式丝毫没有受到玩家的行为改变而做出相应的改变,大家若是能花一定的时间去熟悉这款游戏,了解了其中的怪物的出现点,出现时间,游戏将很容易通关。(当然游戏设计者为避免这样的情况,设计了极其精巧的地图来弥补AI的不足)。而现在很多游戏中,敌人NPC并非一成不变的按照已定的路线进行移动,他们根据玩家的行为做出相应的判断并且行动,在更大的范围内他们拥有着一定的"自主性"。例如CS中的NPC。
对玩家来说,对付复杂AI的敌人需要更高的技巧,挑战性有一定程度的增加。
2、创造更真实的虚拟世界
不清楚大家是否玩过原版的CS,其实原本CS中的NPC还是比较愚蠢的,他们只会走相对固定的路线,对突发事件的反应能力也相对较差,通常只要我们在背后游戏了某一NPC,即使你枪法较差,它们也很难有存活的机会,或许,它们根本就不会回头。而经过一系列的发展修正后,现在CS中MPC不再那么好对付了,他们在背后受到攻击后,会很快时间内做出回头攻击的行为,甚至还会"说话"发出信息告诉其他NPC请求援军,这更类似于玩家的行为。
我们在翻以前的游戏时或许能想起某些游戏中,NPC角色会被卡在一块石头或树木前,傻傻的撞着树走,而不会饶个圈子走过去,这将使玩家看低这款游戏,"太假了,无聊"我们可能会发出这样的感叹而宣告这款游戏的卸载。在现在游戏则很少见到这种情况,多种"寻路法"的计算已是游戏程序员们最基本的一种要求。
3、增加游戏的可玩性
为什么CS,魔兽争霸等游戏,人们更喜欢上游戏平台,战网与人对战,因为与人对战有更多的风格,有更多的不可预料性,玩家在追求这种未知的结果。如果一部电影在开始时我们就已知其中结局,我们对它的热情就会丧失,我们游戏也是一样的。
使NPC更加具有AI,更模拟人类的思维,行为,可以使玩家拥有更多的不可预料性,给玩家一种惊奇感,新鲜感。而实际上我们的做法是给NPC的一定的AI套路选择,例如:根据自身HP的多少,来进行"勇猛直前,边战边退,逃命要紧"等各类的AI算法的选择,将使玩家无法准确的推测NPC的行动,可玩性得到了发挥。
4、辅助其他功能
举个例子:假若我们玩一款游戏,控制角色进入了一个城市,而发现这里的敌人和其他地方的敌人AI截然不同,他们即使HP很少也会努力冲锋攻击着玩家,我们玩家会产生一种疑问,到底是什么驱使他们如此疯狂?这容易以另一种手法来激发出我们的好奇心,之后再以其他信息来告诉玩家这个村子之前遭受过外族攻击屠戮,所以人们都变的仇视侵略,勇于牺牲等,我们玩家也更加容易接受。AI设计间接的对剧情做出了介绍。
又如<三国志>系列,人物分为勇猛,果断,冷静等特性,在平时,这将影响他们的忠诚度变化,在战斗时将影响它们自动战斗的风格,当然也根据他们的AI不同,玩家进行某种举措时,武将也将给予不同的反映,客观上又体现了角色的特性。
AI设计的类型
1、FSM(Finite State Machine)有限状态机。这是最简单也最古老的AI技术,如果懂得程序的人员,可以将其简单的理解为一套if,else或者 switch,case构成的条件判断。
我这里依旧拿原先的例子来说:
例:角色种类:熊类
case1:当与玩家距离50Pixel范围内,警戒(警戒当然是种状态,属于行为规则)
case2:当与玩家距离10Pixel范围内,主动攻击最近者
case3:当受到攻击,立即攻击攻击者
case4:攻击过程中,优先攻击对方队伍中最少HP人员
case5:当攻击目标消失或死亡,则攻击10Pixel范围内最近者
case6:当与攻击目标距离10Pixel内,追随攻击
case7:当与攻击目标距离10Pixel外,警戒
case8:当与攻击目标距离50Pixel外,返回初始点,在一定范围内随机移动
这可以说是比较典型的FSM设计,无论玩家做出什么行为,都有一套AI规则进行判断和行为,而且,仅仅是一套。不会有一种行为对应两套规则的可能。当然,这样即使加入更多的判断可能,我们也很容易觉得单调,因为确定性太强了,这就需要我们更多的引入随机性,即我们下面说的
2、FuSM(Fuzzy State Machine)模糊状态机。对于它的名词解释或许会很复杂饶口,而我们没必要去关系那些,我们可以最简单的理解其真意:就是在FSM中加入随机特性。
随机特性很早就在D&D中有过体现,在原先的帖子中有朋友评论说我举例不当,D&D不仅仅只有一个世界设定,我想他说的是正确的,D&D的最大特色还有一个就是骰子系统,这个系统我更愿意说成是随机系统。在FSM中加入随机系统,将减少了其规范性,而加大了游戏的不确定性。我这里拿上面的例子加以补充,
case2:当与玩家距离10Pixel范围内,80%几率主动攻击最近者,20%几率逃跑至玩家反方向10Pixel并警戒。
当然可以加入更多的百分比判断可能,我这里仅做出最简单的FuSM设计,
你可能觉得这并没什么,不就是多加入一个判断么?
然而扩展的来想,假如一个NPC是90%几率攻击,那么我们可以确定它为勇猛的(可设置老虎),若是60%几率逃跑,那么我们可以确定它是狡猾的或者怯懦的(可设置狐狸兔子),这样将更大程度上突出NPC的特点。
当然,我们可以再加入新的判断,当HP低于30%时,有60%几率放弃战斗逃跑,当HP低于10%时,将有90%几率放弃战斗逃跑,这也将大大加大游戏的可玩性。
细心的朋友会发现,我上面无论是逃跑还是攻击,都是给定的一个百分比,而实际上我们可以采取更模糊的方法,就是使用随机数,大部分游戏设计中都会使用一个函数"Random",无论是C++,JAVA,Ruby我们都有这个系统函数,它将生成一个一定范围内的数,我们用它来控制NPC的行为几率的话,更会有更大的不确定性。
而且使用FuSM还有一个最大的好处,它将简化我们的策划工作。 没错,是简化,我们没有必要去专门为每一类的NPC设置各自不同的规范了,我们需要做的仅仅是调整其随机数的范围,调整其百分比就可以获得性格种类迥异的NPC了。
3、可扩展性AI。这个概念可能会很大程度上引起的兴趣,而实际上它并非那么神秘,我们RM使用者实际上一直在接触着它。看这帖子的朋友里我相信有相当一部门并不懂得编程,而实际上,程序员们不关心游戏中的平衡问题,他们需要做的仅仅是实现游戏的功能,至于以如何使用百分比来控制游戏进程,他们一点也不管。那么我们开始罗嗦了这么久的FSM,FuSM是给谁看的?呵呵,是给策划看的,是给我们自己看的。是的,这样做仅仅是方便我们的调整和逻辑判断。通常,程序员们会制作一套工具来让我们进行设置和测试,而这套工具就是可扩展性AI。
当然,我们RM整个工具,我们可以说是引擎,而它若拥有了关于NPC行为规定的百分比摸版的话,那么那部分就是可扩展性AI。然而就我对RM的理解来说,在不修改RGSS的原则下,我们纯靠事件来制作FuSM设计是比较困难的,更期待Ruby高手能够加入相应的支持。
4、神经元网络。它是生理学上的真实人脑神经网络的结构和功能,以及若干基本特性的某种理论抽象、简化和模拟而构成的一种信息处理系统 。从我的理解上来说,它偏靠程序方面的东西实在太多,误差反传训练算法,BP算法,RFNN都非三言两语能够说明的,我在此依旧不再赘述,有兴趣的朋友可以上网自行查找。
AI流程图说明
我们是说策划文档的,当然就这方面要多说一些。
通常,我们做AI部分的设计说明时,很少象我这样使用条目型的文字说明,因为它过于浪费时间,而且条理性不足,我们更多的使用的流程图,这也是开始我要求大家尽可能的使用Visio的原因。当然,大家也可以使用Word来制作流程图,但个人认为过于复杂,不如Visio好用。
posted @ 2011-05-12 12:01 藏积 阅读(451) 评论(0)
编辑