从零开始编写自己的C#框架(13)——T4模板在逻辑层中的应用(二)
最近这段时间特忙,公事私事,忙得有时都没时间打开电脑了,这两周只能尽量更新,以后再将章节补回来。
直接进入主题,通过上一章节,大家明白了怎么使用模板类编写T4模板,本章进的是一些简单技巧的应用
1、首先创建一个Test2.tt模板
2、然后修改模板内容为下面代码
这些代码与上一章最后面的那个差不多,只是修改了输出文件名、命名空间、类名、类属性(partial)和一个单例获取函数
<#@ template debug="false" hostspecific="True" language="C#" #>
<#@ output extension=".cs" encoding="utf-8" #>
<#@ include file="SQLServer.ttinclude" #>
<#@ include file="MultipleOutputHelper.ttinclude"#> <#
//获取所有表与视图
var tables = LoadTables();
//创建多文件生成实体
var manager = Manager.Create(Host, GenerationEnvironment); //遍历所有表
foreach(var tbl in tables){
//判断当前表名是否是禁用名称(禁用的名称可以在Settings.ttinclude文件的ExcludeTables字符串数据中进行添加)
if(!ExcludeTables.Contains(tbl.Name))
{
// 设置输出的文件名
manager.StartNewFile(tbl.ClassName+"Bll.cs");
#>
using System; namespace Solution.Logic { public partial class <#=tbl.CleanName#>Bll { #region 单例模式
//定义单例实体
private static <#=tbl.Name#>Bll _<#=tbl.Name#>Bll = null; /// <summary>
/// 获取本逻辑类单例
/// </summary>
/// <returns></returns>
public static <#=tbl.Name#>Bll GetInstence() {
if (_<#=tbl.Name#>Bll == null) {
_<#=tbl.Name#>Bll = new <#=tbl.Name#>Bll();
}
return _<#=tbl.Name#>Bll;
}
#endregion } } <#
// 输出文件结束
manager.EndBlock();
} //if(!ExcludeTables.Contains(tbl.Name)) 判断结束 }// end foreach // 执行编译,生成文件
manager.Process(true);
#>
运行模板,测试看看效果
从上面添加函数的方法可以看出,对于常用的函数,都可以在模板中进行添加后,直接生成出来,这样就减少了程序员很大的工作量了
3、用上面方法确实可以解决很大部分的问题,但对于一些特殊的函数直接这样写就不行了,比如我们想生成一个删除指定外键Id的函数,由于有的表有外键有的没有,那么就要用上一些简单的判断来处理了
<#@ template debug="false" hostspecific="True" language="C#" #>
<#@ output extension=".cs" encoding="utf-8" #>
<#@ include file="SQLServer.ttinclude" #>
<#@ include file="MultipleOutputHelper.ttinclude"#> <#
//获取所有表与视图
var tables = LoadTables();
//创建多文件生成实体
var manager = Manager.Create(Host, GenerationEnvironment); //遍历所有表
foreach(var tbl in tables){
//判断当前表名是否是禁用名称(禁用的名称可以在Settings.ttinclude文件的ExcludeTables字符串数据中进行添加)
if(!ExcludeTables.Contains(tbl.Name))
{
// 设置输出的文件名
manager.StartNewFile(tbl.ClassName+"Bll.cs");
#>
using System;
using Solution.DataAccess.DataModel;
using Solution.DataAccess.DbHelper; namespace Solution.Logic { public partial class <#=tbl.CleanName#>Bll { #region 单例模式
//定义单例实体
private static <#=tbl.Name#>Bll _<#=tbl.Name#>Bll = null; /// <summary>
/// 获取本逻辑类单例
/// </summary>
/// <returns></returns>
public static <#=tbl.Name#>Bll GetInstence() {
if (_<#=tbl.Name#>Bll == null) {
_<#=tbl.Name#>Bll = new <#=tbl.Name#>Bll();
}
return _<#=tbl.Name#>Bll;
}
#endregion <#
foreach(var col in tbl.Columns){
//判断字段名称中是否包含“_Id”这个字符串,且字段类型为int或long的,则生成对应的删除函数
if (col.CleanName.IndexOf("_Id") >= && (col.SysType == "int" || col.SysType == "long"))
{
#>
#region 删除<#=tbl.Name#>表指定<#=col.CleanName#>的字段值记录
/// <summary>
/// 删除<#=tbl.Name#>表指定<#=col.CleanName#>的字段值记录
/// </summary>
/// <param name="id">记录的主键值</param>
public void DeleteBy<#=col.CleanName#>(int id) {
//删除
<#=tbl.Name#>.Delete(x => x.<#=col.CleanName#> == id);
} /// <summary>
/// 删除<#=tbl.Name#>表指定<#=col.CleanName#>的字段值记录
/// </summary>
/// <param name="id">记录的主键值</param>
public void DeleteBy<#=col.CleanName#>(int[] id) {
if (id == null) return;
//将数组转为逗号分隔的字串
var str = string.Join(",", id); //设置Sql语句
var sql = "delete from <#=tbl.Name#> where <#=col.CleanName#> in (" + str + ")"; //删除
var deleteHelper = new DeleteHelper();
deleteHelper.Delete(sql);
}
#endregion <#
}
}
#>
} } <#
// 输出文件结束
manager.EndBlock();
} //if(!ExcludeTables.Contains(tbl.Name)) 判断结束 }// end foreach // 执行编译,生成文件
manager.Process(true);
#>
运行模板,测试看看效果
4、同理,我们可以通过判断,生成获取指定名称的字段值和生成删除图片函数等,这些都可以根据你的需要去生成
<#@ template debug="false" hostspecific="True" language="C#" #>
<#@ output extension=".cs" encoding="utf-8" #>
<#@ include file="SQLServer.ttinclude" #>
<#@ include file="MultipleOutputHelper.ttinclude"#> <#
//获取所有表与视图
var tables = LoadTables();
//创建多文件生成实体
var manager = Manager.Create(Host, GenerationEnvironment); //遍历所有表
foreach(var tbl in tables){
//判断当前表名是否是禁用名称(禁用的名称可以在Settings.ttinclude文件的ExcludeTables字符串数据中进行添加)
if(!ExcludeTables.Contains(tbl.Name))
{
// 设置输出的文件名
manager.StartNewFile(tbl.ClassName+"Bll.cs");
#>
using System;
using Solution.DataAccess.DataModel;
using Solution.DataAccess.DbHelper; namespace Solution.Logic { public partial class <#=tbl.CleanName#>Bll { #region 单例模式
//定义单例实体
private static <#=tbl.Name#>Bll _<#=tbl.Name#>Bll = null; /// <summary>
/// 获取本逻辑类单例
/// </summary>
/// <returns></returns>
public static <#=tbl.Name#>Bll GetInstence() {
if (_<#=tbl.Name#>Bll == null) {
_<#=tbl.Name#>Bll = new <#=tbl.Name#>Bll();
}
return _<#=tbl.Name#>Bll;
}
#endregion <#
foreach(var col in tbl.Columns){
//判断字段名称中是否包含“_Id”这个字符串,且字段类型为int或long的,则生成对应的删除函数
if (col.CleanName.IndexOf("_Id") >= && (col.SysType == "int" || col.SysType == "long"))
{
#>
#region 删除<#=tbl.Name#>表指定<#=col.CleanName#>的字段值记录
/// <summary>
/// 删除<#=tbl.Name#>表指定<#=col.CleanName#>的字段值记录
/// </summary>
/// <param name="id">记录的主键值</param>
public void DeleteBy<#=col.CleanName#>(int id) {
//删除
<#=tbl.Name#>.Delete(x => x.<#=col.CleanName#> == id);
} /// <summary>
/// 删除<#=tbl.Name#>表指定<#=col.CleanName#>的字段值记录
/// </summary>
/// <param name="id">记录的主键值</param>
public void DeleteBy<#=col.CleanName#>(int[] id) {
if (id == null) return;
//将数组转为逗号分隔的字串
var str = string.Join(",", id); //设置Sql语句
var sql = "delete from <#=tbl.Name#> where <#=col.CleanName#> in (" + str + ")"; //删除
var deleteHelper = new DeleteHelper();
deleteHelper.Delete(sql);
}
#endregion <#
}
//判断字段名称中是否包含“Name”这个字符串,且字段类型为string
else if (col.CleanName.IndexOf("Name") >= && col.SysType == "string")
{
#>
#region 获取<#=col.CleanName #>字段值
/// <summary>
/// 获取<#=col.CleanName #>字段值
/// </summary>
/// <param name="pkValue">主键Id</param>
/// <returns></returns>
public string Get<#=col.CleanName #>(int pkValue)
{
//从数据库中查询
var model = <#=tbl.Name#>.SingleOrDefault(x => x.Id == pkValue);
return model == null ? "" : model.<#=col.CleanName #>;
}
#endregion <#
}
//判断字段名称中是否包含“Img”这个字符串,且字段类型为string
else if (col.CleanName.IndexOf("Img") >= && col.SysType == "string")
{
#>
#region 删除<#=col.CleanName #>字段存储的对应图片
/// <summary>删除<#=col.CleanName #>字段存储的对应图片</summary>
/// <param name="pkValue">主键Id</param>
public void Del<#=col.CleanName #>(int pkValue) {
try {
//添加删除语句
}
catch (Exception e) {
//出现异常,保存出错日志信息
//添加保存出错日志语句
}
}
#endregion <# }
}
#>
} } <#
// 输出文件结束
manager.EndBlock();
} //if(!ExcludeTables.Contains(tbl.Name)) 判断结束 }// end foreach // 执行编译,生成文件
manager.Process(true);
#>
运行模板,测试看看效果
5、有时候我们会存在一些特殊的需求,有些表或字段要进行过滤操作,这时我们就可以使用一些简单的过滤判断处理
比如我们对于Manager_Id与Manager_Name这两个字段是不需要生成对应函数的,那么我们就可以加个过滤处理
首先在Settings.ttinclude文件中创建一个字符串数组变量,并赋值
然后在模板中的循环语句中添加判断
运行模板,测试看看效果
对比第4点的图就可以看到,已经少了两个函数了
而表名过滤,在上一章节的内容中已经包含了,请看下图
只要在Settings.ttinclude文件中的ExcludeTables变量中添加你想过滤的表名称就可以了
当然我们还可以写出更多的扩展,这些需要发挥你的想象力,生成更多常用函数,使你从复制粘贴中解放出来,当然函数有变动时,也只需要改一下模板就可以了,方便快捷
6、对于常用功能来说,前面的生成方式都可以解决,但有时候有些功能直接生成的方式解决不了,那么父类(基类)与虚函数的运用可以帮我们解决很多程序调用的问题。
比如我们使用IIS缓存,在对记录进行添加、删除与修改操作时,必须同步删除缓存。看到这个需求,可能有的朋友就会说,这很简单啊,直接生成一个缓存删除函数就可以了。是的,这是一种处理方法,但还会存在很多特殊情况,有些时候,我们在自定义函数中也会用到一些缓存,这些缓存并不存在模板中,那么想要模板里的程序在清空模板缓存后,也能自动帮我们清除自定义缓存的话该怎么实现呢?不可能要让我们在相关程序调用的方法中手动添加吧,如果调用的地方太多的话,就很容易忘记了。而我们有一种比较好的解决方式,那就是使用父类(基类),在基类中实现一个删除缓存的虚函数,模板类继承基类后,可以在那些执行添加、删除与修改的函数中直接调用虚函数,而对于这些个性化的删除操作,我们只需要使用override修饰符重写该函数,就可以实现自动删除缓存的功能了。
具体操作方法请看下面步骤:
首先创建一个基类
创建一个虚函数
然后再模板中让模板类继承基类,并实现添加、修改与删除方法,在方法中调用删除缓存函数
<#@ template debug="false" hostspecific="True" language="C#" #>
<#@ output extension=".cs" encoding="utf-8" #>
<#@ include file="SQLServer.ttinclude" #>
<#@ include file="MultipleOutputHelper.ttinclude"#> <#
//获取所有表与视图
var tables = LoadTables();
//创建多文件生成实体
var manager = Manager.Create(Host, GenerationEnvironment); //遍历所有表
foreach(var tbl in tables){
//判断当前表名是否是禁用名称(禁用的名称可以在Settings.ttinclude文件的ExcludeTables字符串数据中进行添加)
if(!ExcludeTables.Contains(tbl.Name))
{
// 设置输出的文件名
manager.StartNewFile(tbl.ClassName+"Bll.cs");
#>
using System;
using System.Linq.Expressions;
using Solution.DataAccess.DataModel;
using Solution.DataAccess.DbHelper; namespace Solution.Logic
{ public partial class <#=tbl.CleanName#>Bll : LogicBase
{ #region 单例模式
//定义单例实体
private static <#=tbl.Name#>Bll _<#=tbl.Name#>Bll = null; /// <summary>
/// 获取本逻辑类单例
/// </summary>
/// <returns></returns>
public static <#=tbl.Name#>Bll GetInstence()
{
if (_<#=tbl.Name#>Bll == null)
{
_<#=tbl.Name#>Bll = new <#=tbl.Name#>Bll();
}
return _<#=tbl.Name#>Bll;
}
#endregion #region 添加与编辑<#=tbl.Name#>表记录
/// <summary>
/// 添加与编辑<#=tbl.Name#>记录
/// </summary>
/// <param name="model"><#=tbl.Name#>表实体</param>
public void Save(<#=tbl.Name#> model)
{
try {
//保存
model.Save(); //删除缓存
DelCache(); //添加用户访问记录
//UseLogBll.GetInstence().Save("{0}" + (model.Id == 0 ? "添加" : "编辑") + "<#=tbl.Name#>记录成功,ID为【" + model.Id + "】");
}
catch (Exception e) {
//var result = "执行<#=tbl.Name#>Bll.Save()函数出错!"; //出现异常,保存出错日志信息
//CommonBll.WriteLog(result, e, false);
}
}
#endregion #region 删除<#=tbl.Name#>表记录
/// <summary>
/// 删除<#=tbl.Name#>表记录
/// </summary>
/// <param name="id">记录的主键值</param>
public void Delete(int id) {
//设置Sql语句
var sql = "delete from <#=tbl.Name#> where Id = " + id; //删除
var deleteHelper = new DeleteHelper();
deleteHelper.Delete(sql); //删除缓存
DelCache(); //添加用户操作记录
//UseLogBll.GetInstence().Save("{0}删除了<#=tbl.Name#>表id为【" + id + "】的记录!");
} /// <summary>
/// 删除<#=tbl.Name#>表记录
/// </summary>
/// <param name="id">记录的主键值</param>
public void Delete(int[] id) {
if (id == null) return;
//将数组转为逗号分隔的字串
var str = string.Join(",", id); //设置Sql语句
var sql = "delete from <#=tbl.Name#> where Id in (" + str + ")"; //删除
var deleteHelper = new DeleteHelper();
deleteHelper.Delete(sql); //删除缓存
DelCache(); //添加用户操作记录
//UseLogBll.GetInstence().Save("{0}删除了<#=tbl.Name#>表id为【" + str + "】的记录!");
} /// <summary>
/// 获取数据表中的某个值——从数据库中查询,如果使用了缓存,删除成功后会清空本表的所有缓存记录,然后重新加载进缓存
/// </summary>
/// <param name="expression">条件语句</param>
/// <returns></returns>
public void Delete(Expression<Func<<#=tbl.Name#>, bool>> expression)
{
//执行删除
<#=tbl.Name#>.Delete(expression); //删除缓存
DelCache(); //添加用户操作记录
//UseLogBll.GetInstence().Save(page, "{0}删除了<#=tbl.Name#>表记录!");
}
#endregion <#
foreach(var col in tbl.Columns)
{
//进行过滤判断,指定的字段名称不做处理
if (ExcludeFields.Contains(col.CleanName))
continue; //判断字段名称中是否包含“_Id”这个字符串,且字段类型为int或long的,则生成对应的删除函数
if (col.CleanName.IndexOf("_Id") >= && (col.SysType == "int" || col.SysType == "long"))
{
#>
#region 删除<#=tbl.Name#>表指定<#=col.CleanName#>的字段值记录
/// <summary>
/// 删除<#=tbl.Name#>表指定<#=col.CleanName#>的字段值记录
/// </summary>
/// <param name="id">记录的主键值</param>
public void DeleteBy<#=col.CleanName#>(int id)
{
//删除
<#=tbl.Name#>.Delete(x => x.<#=col.CleanName#> == id);
} /// <summary>
/// 删除<#=tbl.Name#>表指定<#=col.CleanName#>的字段值记录
/// </summary>
/// <param name="id">记录的主键值</param>
public void DeleteBy<#=col.CleanName#>(int[] id)
{
if (id == null) return;
//将数组转为逗号分隔的字串
var str = string.Join(",", id); //设置Sql语句
var sql = "delete from <#=tbl.Name#> where <#=col.CleanName#> in (" + str + ")"; //删除
var deleteHelper = new DeleteHelper();
deleteHelper.Delete(sql);
}
#endregion <#
}
//判断字段名称中是否包含“Name”这个字符串,且字段类型为string
else if (col.CleanName.IndexOf("Name") >= && col.SysType == "string")
{
#>
#region 获取<#=col.CleanName #>字段值
/// <summary>
/// 获取<#=col.CleanName #>字段值
/// </summary>
/// <param name="pkValue">主键Id</param>
/// <returns></returns>
public string Get<#=col.CleanName #>(int pkValue)
{
//从数据库中查询
var model = <#=tbl.Name#>.SingleOrDefault(x => x.Id == pkValue);
return model == null ? "" : model.<#=col.CleanName #>;
}
#endregion <#
}
//判断字段名称中是否包含“Img”这个字符串,且字段类型为string
else if (col.CleanName.IndexOf("Img") >= && col.SysType == "string")
{
#>
#region 删除<#=col.CleanName #>字段存储的对应图片
/// <summary>删除<#=col.CleanName #>字段存储的对应图片</summary>
/// <param name="pkValue">主键Id</param>
public void Del<#=col.CleanName #>(int pkValue)
{
try
{
//添加删除语句
}
catch (Exception e)
{
//出现异常,保存出错日志信息
//添加保存出错日志语句
}
}
#endregion <# }
}
#>
} } <#
// 输出文件结束
manager.EndBlock();
} //if(!ExcludeTables.Contains(tbl.Name)) 判断结束 }// end foreach // 执行编译,生成文件
manager.Process(true);
#>
运行模板,测试看看效果
然后我们创建一个与模版中同名的类
实现虚函数
这样模板函数在执行相关操作时,如果我们重写了清空缓存这个函数,那么程序就会自动执行清空缓存函数了,而对于那些不需要该功能的类则没有任何影响
这里要注意的是,我们的模板类与这个自定义类都有一个统一的修饰符partial
大家先消化上面内容,才好理解下一章节中模板调用内容,下章会将写好的模板函数全部贴出来,让大家直接一步到位,生成Web层所需要的绝大部分调用函数,减少这一部分不必要的编码工作。
下载地址:
版权声明:
本文由AllEmpty原创并发布于博客园,欢迎转载,未经本人同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。如有问题,可以通过1654937@qq.com 联系我,非常感谢。
发表本编内容,只要主为了和大家共同学习共同进步,有兴趣的朋友可以加加Q群:327360708 ,大家一起探讨。
更多内容,敬请观注博客:http://www.cnblogs.com/EmptyFS/
从零开始编写自己的C#框架(13)——T4模板在逻辑层中的应用(二)的更多相关文章
- 从零开始编写自己的C#框架(14)——T4模板在逻辑层中的应用(三)
原本关于T4模板原想分5个章节详细解说的,不过因为最近比较忙,也不想将整个系列时间拉得太长,所以就将它们整合在一块了,可能会有很多细节没有讲到,希望大家自己对着代码与模板去研究. 本章代码量会比较大, ...
- 从零开始编写自己的C#框架(12)——T4模板在逻辑层中的应用(一)(附源码)
对于T4模板很多朋友都不太熟悉,它在项目开发中,会帮我们减轻很大的工作量,提升我们的开发效率,减少出错概率.所以学好T4模板的应用,对于开发人员来说是非常重要的. 园子里对于T4模板的介绍与资料已经太 ...
- 从零开始编写自己的C#框架(1)——前言
记得十五年前自学编程时,拿着C语言厚厚的书,想要上机都不知道要用什么编译器来执行书中的例子.十二年前在大学自学ASP时,由于身边没有一位同学和朋友学习这种语言,也只能整天混在图收馆里拼命的啃书.而再后 ...
- 从零开始编写自己的C#框架 ---- 系列文章
目录: 从零开始编写自己的C#框架(1)——前言从零开始编写自己的C#框架(2)——开发前的准备工作从零开始编写自己的C#框架(3)——开发规范从零开始编写自己的C#框架(4)——文档编写说明从零开始 ...
- 从零开始编写自己的C#框架(17)——Web层后端首页
后端首页是管理员登陆后进入的第一个页面,主要是显示当前登陆用户信息.在线人数.菜单树列表.相关功能按键和系统介绍.让管理员能更方便的找到息想要的内容. 根据不同系统的需要,首页会显示不同的内容,比如显 ...
- 从零开始编写自己的C#框架(26)——小结
一直想写个总结,不过实在太忙了,所以一直拖啊拖啊,拖到现在,不过也好,有了这段时间的沉淀,发现自己又有了小小的进步.哈哈...... 原想框架开发的相关开发步骤.文档.代码.功能.部署等都简单的讲过了 ...
- 从零开始编写自己的C#框架(15)——Web层后端登陆功能
对于一个后端管理系统,最重要内容之一的就是登陆页了,无论是安全验证.用户在线记录.相关日志记录.单用户或多用户使用帐号控制等,都是在这个页面进行处理的. 1.在解决方案中创建一个Web项目,并将它设置 ...
- 从零开始编写自己的C#框架(11)——创建解决方案
这段时间一直在充电,拜读了园子中大神们的博文(wayfarer的<设计之道>.TerryLee的<.NET设计模式系列文章>.卡奴达摩的<设计模式>还有其他一些零散 ...
- 从零开始编写自己的C#框架(2)——开发前准备工作
没想到写了个前言就受到很多朋友的支持,大家的推荐就是我最大的动力(推荐得我热血沸腾,大家就用推荐来猛砸我吧O^-^O),谢谢大家支持. 其实框架开发大家都知道,不过要想写得通俗点,我个人觉得还是挺吃力 ...
随机推荐
- C#高性能TCP服务的多种实现方式
哎~~ 想想大部分园友应该对 "高性能" 字样更感兴趣,为了吸引眼球所以标题中一定要突出,其实我更喜欢的标题是<猴赛雷,C#编写TCP服务的花样姿势!>. 本篇文章的主 ...
- Android权限管理之Android 6.0运行时权限及解决办法
前言: 今天还是围绕着最近面试的一个热门话题Android 6.0权限适配来总结学习,其实Android 6.0权限适配我们公司是在今年5月份才开始做,算是比较晚的吧,不过现在Android 6.0以 ...
- SQL Server-聚焦在视图和UDF中使用SCHEMABINDING(二十六)
前言 上一节我们讨论了视图中的一些限制以及建议等,这节我们讲讲关于在UDF和视图中使用SCHEMABINDING的问题,简短的内容,深入的理解,Always to review the basics. ...
- Gradle 实现 Android 多渠道定制化打包
Gradle 实现 Android 多渠道定制化打包 版权声明:本文为博主原创文章,未经博主允许不得转载. 最近在项目中遇到需要实现 Apk 多渠道.定制化打包, Google .百度查找了一些资料, ...
- OSGi规范的C#实现开源
这是大约在3-4年前完成的一个C#实现的OSGi框架,实现的过程参照了OSGi规范与与一些实现思路(感谢当时的那些资料与项目),此框架虽然仅在几个小型项目有过实际的应用,但OSGi的规范实现还是相对比 ...
- 一个软件开发者的BPM之路
我是小林,一名普通的软件工程师,从事BPM(业务流程管理)软件开发工作.我没有几十年的技术底蕴,无法像大牛们一样高谈阔论,品评BPM开发之道:也不是资深的流程管理专家,能与大家分析流程管理的时弊.我只 ...
- Java实现FTP文件与文件夹的上传和下载
Java实现FTP文件与文件夹的上传和下载 FTP 是File Transfer Protocol(文件传输协议)的英文简称,而中文简称为"文传协议".用于Internet上的控制 ...
- SpringMvc中的数据校验
SpringMvc中的数据校验 Hibernate校验框架中提供了很多注解的校验,如下: 注解 运行时检查 @AssertFalse 被注解的元素必须为false @AssertTrue 被注解的元素 ...
- Linux文件查找.md
Linux 文件查找 在Linux系统的查找相关的命令: which 查看可执行文件的位置 whereis 查看文件的位置 locate 配合数据库查看文件位置 find 实际搜寻硬盘查询文件名称 w ...
- 山寨Unity3D?搜狐畅游的免费开源游戏引擎Genesis-3D
在CSDN上看到了<搜狐畅游发布3D游戏引擎Genesis-3D 基于MIT协议开源>(http://www.csdn.net/article/2013-11-21/2817585-cha ...