您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
HTML5游戏框架大军中的一乘轻骑Phaser
 
作者:郭华丰 来源:《程序员》杂志 发布于: 2015-10-10
   次浏览      
 

摘要:HTML5游戏以其“低门槛、低成本、简单有趣、易于分享”的特点受到了广大玩家的喜爱和业界人士的关注,本文将介绍HTML5游戏框架大军中的一乘轻骑——Phaser。

【编者按】HTML5游戏以其“低门槛、低成本、简单有趣、易于分享”的特点受到了广大玩家的喜爱和业界人士的关注。不论是新手还是企业专业团队,从无到有制作一个游戏并不总是一个好主意,选用适合自己的游戏引擎或者框架才是正确的途径。本文将介绍HTML5游戏框架大军中的一乘轻骑——Phaser。

Phaser简介

Phaser是一个开源HTML5游戏框架,由Photon Storm公司创建,在2011年发布1.0版本后,就进入稳定期,定期更新版本,很多新的功能和修复会快速完成。大约一个月发布一次稳定版——可以在GitHub上查看完整的更新记录。Phaser的设计目标是创建运行在桌面和移动设备网页浏览器上的游戏。近几年,人们越来越关注移动设备网页浏览器的性能,以及快速增长、非常重要的网页游戏领域。Photon Storm公司早在2011年就开始制作游戏工具与游戏,其技术积累,能帮助开发者提供游戏开发中的种种需求,直面游戏开发中的痛点。

Phaser主要特色

朴素的JavaScript编程风格

这听起来好像是一个奇怪的“特性”,但实际上这点相当有吸引力。Phaser内部不使用任何人造的OO风格编程。没有大量的继承链和组件系统,你也不必强迫对象设计为任何固定的类结构。只有简单直接的原型链,JavaScript最自然的使用方式。

这并不意味着你不能以结构化方式创建游戏。这只是意味着它不是强制性的。这也意味着Phaser内部可以轻松地改造。

简单易用的资源加载

Phaser内置的资源加载器可以处理:

  • 精灵表(固定尺寸帧)
  • 纹理图片集(包括Texture Packer、JSON Hash、JSON Array、Flash CS6/CC以及Starling XML格式)
  • 音频文件
  • 数据文件(XML、JSON和文本文件)
  • JavaScript文件(所以你能部分加载游戏或JS资源)
  • Tilemaps(CSV和Tiled地图格式)
  • Bitmap字体

我通常从Flash直接导出纹理图片集到Phaser游戏,并且完全支持修剪空白边缘的图片集。资源可以部分加载、缓存,以及从不同URLs(为了支持CDN)拉取,你只需要一行代码就能将任何精灵变成进度条。

渲染:WebGL和Canvas

Phaser内部使用Pixi.js进行渲染。Pixil是一个专注于Canvas和WebGL非常棒和快速的渲染库。Photon Storm公司宣称将持续资助这个库不断改进和发展。

对于你的游戏,这意味着如果浏览器支持WebGL,那么玩家就会获得更加流畅的游戏体验。WebGL在桌面平台已经非常普遍,但在移动平台还在发展初期,即便如此,这也是HTML5游戏发展的未来,所以支持它非常重要。最新发布的Phaser版本引入了WebGL着色器和过滤支持,并且即将发布的版本将实现法线贴图(normal map),所以你将能使用新工具诸如Sprite Lamp。

音频:网络音频和遗留音频

音频成为HTML游戏的软肋很多年了。仅仅几年前,还面临选择一个单声道高延迟的音频还是根本不使用音频而两难。还好时代变了,Web Audio API来拯救我们了。它支持基于节点的音频,多通道,节点路由和各种效果。毫无疑问,Phaser全面支持Web Audio。

然而,很多设备,尤其是Android设备仍有不支持Web Audio——所以Phaser也支持遗留音频以及使用Audio Sprite:打包一组声音到单个文件,然后使用回放标记跳转到不同的效果。Phaser将在两种方式自带切换,取决于设备的能力,并且也包含自动为你解锁音频自系统,它捕获了许多第一批移动开发者的芳心。

输入:多点触碰,键盘,指针,鼠标

当需要支持桌面和移动平台,有越来越多的不同数量的潜在输入选项。Phaser支持键盘、鼠标、触摸、MSPointer(目前Pointer在IE11下)以及它们的组合。例如,在Windows Surface设备上,你可以在鼠标和触摸两者之间切换,或同时使用两者。

对于触摸输入,Phaser能处理单点触碰和多点触碰环境。你可以定义多达10个触碰点,并且独立地追踪它们,使用它们的事件来处理Sprite交互,诸如拖拽、轻击和碰撞。

物理、补间动画和粒子

整合进核心库的是ArcadePhysics和ArcadeParticles系统。它们是简单的AABB轻量级库,允许你对任何精灵使用重力和运动,然后进行测试以便用于碰撞和分离。使用基于世界的四叉树有助于最小化碰撞测试,你能通过耗费很少的时间得到相当好的结果。

然而,内置的物理系统不可能适合所有类型的游戏,所以物理系统可以轻易地替换,并且没有物理属性绑定到实际的精灵(而是身体组件),所以可以被替换成诸如Box2D或p2.js。补间系统也被整合进,允许你轻易地补间对象或属性。如果游戏暂停,那么所有补间也将自动暂停,需要时再继续。

插件系统

Phaser的目标是最终稳定下来,并且趋于稳定而均衡,它不太可能涉足修复和浏览器更新之外的其他主题。同时又想要Phaser保持不断增长,为各种游戏提供功能,但是又不至于导致核心库爆炸式膨胀。为此,Photon Storm公司构建了一个插件系统。

Phaser插件可以注册自己到核心框架,在核心游戏循环中进行更新,从而执行各种有用的额外任务。一个极好的例子是最近发布的Photon Storm公司将自己发布插件,同时也期待未来有大量的插件来自社区。

社区评价

在知名的NK社区,活跃的开发者有许多有价值的交流和评论。

网友balazsdavid987:尝试了数个HTML5/JS游戏框架库之后,我敢肯定Phaser是最好的2D游戏框架库。它更新频率快,有优秀的使用文档和极好的社区支持。只是阅读Phaser的源代码就可以学习到大量的游戏开发技术。我们在iOS创建了一个完整的小游戏平台,上网友dtft :面都是phaser游戏。虽然它们算不上大作,但的确非常快速和具有挑战性。我们发现phaser对于快速开发和原型制作简直完美。

网友georgefrick:我们正在使用phaser制作91个应用(系列教育游戏/互动/故事书)。进展很顺利,并且已经完成了一半以上的应用。虽然Phaser有缺陷,但任何框架都会有错误。

网友HobbesDT :对我来说,Phaser非常容易学习,即使我没有很多计算机科学经验,并且没有费多大力气就可以在移动平台(至少是iOS)运行得很好。如果你不知道如何制作游戏,并且想要快速制作一个简单的游戏,这无疑是一个很棒的框架库。官方网站有非常丰富的教程和示例,并且还能在第三方网站上找到很多资料。

网友GavinAnderegg :大约一年前,我和一些朋友在一个12个小时的黑客马拉松活动中使用Phaser制作了一个小游戏。它是一个很好的小框架,虽然我们没有真正使用它的很多功能,但它很容易上手。

可以在 https://github.com/gavinanderegg/coffeeQuest查看我们制作的游戏。

设置开发环境

安装web server

因为浏览器的一些安全机制,最好使用本地web服务器如iis,apache来开发HTML5游戏,如果直接双击打开html页面,可能有些功能不能正常运行。

推荐初学者安装服务器套件安装包,它们以单个可执行文件的形式包含了流行的网络开发工具,例如Apache, PHP和MySQL。对于Windows平台,推荐WAMP Server或XAMPP。而对于OS X平台,强烈推荐MAMP。

选择编辑器

接下来要选择一个得心应手的编辑器来编写代码。如果你是个经验丰富的开发者,我相信你已经有自己偏爱的编辑器。如果是新手,我推荐Sublime Text。Phaser还支持TypeScript,所以还可以选择使用微软的Visual Studio作为编辑器。

下载Phaser

Phaser框架开源,源代码托管在GitHub。

测试环境是否就绪

到此,你已经安装好Web服务器,设置好编辑器,Phaser也已经下载好。现在是时候创建第一个测试项目,并且测试环境是否就绪。

定位到你的“Web Root”,这是一个文件夹,是Web服务器查找文件的地方。如果你在Windows上使用WAMP,可以鼠标左键点击系统图标中的WAMP图标,在弹出菜单中选择“www目录”,即可在文件浏览器中打开这个目录。

下载这个zip文件。它包含了一个hellophaser目录,里面有一个JavaScript文件、一个index.html和一个PNG文件。拷贝Hellophaser目录到Web服务器根目录。

打开网页浏览器,然后浏览本地服务器上的Hellophaser目录。通常是在浏览器中简单地输入localhost/hellophaser或127.0.0.1/hellophaser。如果一切运行正确,将会在中央显示黑色的游戏区域,以及一个Phaser logo。

如果某种原因导致不正确,你需要打开调试窗口查看错误输出。如果只是简单的文件丢失错误,检查你的目录名,然后刷新页面。如果是复杂的错误,可以在Phaser社区发帖求助,将会得到开发团队和热心人的帮助。

使用Phaser创建第一个游戏

在这个示例中,制作的游戏名字叫怪物要糖果。首先介绍项目的结构,以便你可以理解整个游戏玩法。我们将根据游戏运行的逻辑顺序依次讲解:装载图片资源、创建主菜单、真正的游戏循环。你可以点击链接先试玩怪物要糖果。

目设置及结构

项目目录中包含index.html文件(包括HTML5结构和所有必要的JS文件)。还有两个子目录:IMG目录,里面是美术资源,src目录,里面是游戏的源代码。

下面是目录结构预览:


图1 目录结构预览

在src目录,你会看到JavaScript文件。在本教程中,我将描述在该文件夹中的所有文件的内容和用途。你可以在GitHub看到每个文件的源代码。

index.html 文件

我们从index.html文件开始。它看起来像一个HTML5网站,但没有文本和HTML元素,我们初始化Phaser框架,它将渲染所有东西到Canvas元素。

我们在标签定义文档:字符集编码,网页标题以及CSS样式。通常我们会引用外部CSS文件,但在这里不需要,正如我前面提到的,一切都将在一个canvas元素中呈现,所以我们不会有任何的HTML元素。

要做的最后一件事是包含所有的JS文件:从phaser.min.js文件,包含Phaser框架所有源代码,到包含所有游戏代码的文件。为了减少浏览器的请求数,使游戏的载入速度更快,我们将在游戏需要的时候单独载入它们。

查看标签,在这里初始化框架并启动我们的游戏。自调用函数中的第一行代码如下:

此代码将初始化Phaser:

640是游戏的画布宽度,960是游戏的画布高度。

phaser.auto通知框架我们希望游戏如何渲染到画布上。这里有三个选项:CANVAS, WEBGL和AUTO。第一个将我们的游戏渲染在2D Canvas上;第二个使用WebGL在可能的情况下渲染游戏(现在主要用于桌面游戏,但是移动端支持也会越来越好);第三个通知框架,自动检查是否支持WebGL,从而决定游戏如何呈现,如果不支持WebGL,那么2D Canvas将被使用。

该框架初始化将被赋值给一个称为game的对象,它将在引用Phaser实例时使用。

下面几行代码都是加入状态到我们的游戏:

“Boot”是一个状态名,Candy.Boot是一个对象(在下面代码中定义),我们开始进入状态时将被执行。我们为Boot(配置),Preloader(加载资源),MainMenu(你猜对了,这是游戏主菜单)和Game(游戏主循环)添加状态。最后一行,game.state.start(“Boot”),启动Boot状态,Candy.Boot对象将被执行。

你可以看到,一个主要的JavaScript游戏对象已被创建。在游戏中,我们有Boot, Preloader, MainMenu,和Game对象,我们使用原型定义它们。这些对象中的一些特殊函数名被框架本身保留(preload(),create(),update(),和render()),但我们也可以定义自己的(startgame(),spawncandy(),managepause())。如果你不确定你能理解这一切,别担心我会使用示例代码解释这一切。

游戏

现在让我们忘记Boot Preloader和MainMenu。它们将在以后详细解释;此刻你要知道的是Boot状态决定游戏的基本配置,Preloader将加载所有的美术资源,而MainMenu将显示开始游戏菜单。

让我们关注游戏本身,查看Game代码。在解释game.js代码之前,让我们从开发者的角度来谈谈游戏概念本身。

Portrait模式

游戏是竖屏模式。

在这种模式下,屏幕的高度大于其宽度。有的游戏适合竖屏(像怪物要糖果),有的游戏适合横屏(包括平台游戏,比如Craigen),甚至某些类型的游戏在两种模式下都可以运行,通常这样的游戏比较难编写。

Game.js

在我们浏览game.js文件之前,先了解它的结构。我们创建一个游戏世界,有一个玩家角色在里面,它的工作就是吃糖果。

游戏世界:怪物后面的世界是静态的。背景中有一张糖果大陆的背景图片,怪物放置在前景中,还有一个用户界面。

玩家角色:这个演示非常简单而基础,所以小怪物除了等待糖果什么也不做。玩家的主要任务就是收集糖果。

糖果:游戏的核心机制是吃到尽可能多的糖果。糖果在屏幕的顶部边缘产生,玩家必须在它们掉落的时候轻击(或点击)。如果任何糖果掉出屏幕的底部,删除它,并且玩家将受到伤害。我们还没有实现生命值系统,因此,糖果掉出屏幕后,游戏立即结束,并显示适当的消息。

好吧,现在让我们查看game.js:

Candy.Game原型有三个函数:

create()初始化

managepause()暂停和继续游戏

update()管理游戏主循环

我们将创建一个叫做item的实用对象,它表示一颗糖果。它会有一些有用的方法:

spawncandy()增加新糖果

clickcandy()当用户点击时,糖果消失

removecandy()删除糖果

让我们浏览这些代码:

在这里,我们设置所有将使用的变量。

通过定义this._name,我们限制了变量使用的范围为Candy.Game。这意味着他们不能用在其他状态中——我们在其他地方不需要它们,所以为什么要暴露它们?

通过定义Candy._name,我们允许在其他状态和对象中使用这些变量,例如,Candy._score的数值可以被Candy.item.clickCandy()函数增加。

对象初始化为null,计算需要的变量初始化为零。

我们查看candy.game.prototype的代码:

在create()函数开始,我们设置了ARCADE物理系统──Phaser中有现成的,这是最简单的一种。之后,我们为游戏添加了重力。然后添加了三个图片:背景、怪物、以及得分UI的背景。第四个增加的元素是暂停按钮,注意我们使用的是candy.GAME_WIDTH和candy.GAME_HEIGHT变量,它们定义在Candy.Preloader(),但是在整个游戏代码中都可用。

然后我们创建怪物,玩家的虚拟形象。这是一个使用了帧的动画精灵——精灵表单。为了使之看起来像是站着在呼吸,我们为它添加动画。

animations.add()函数创建帧动画,需要四个参数:

动画的名称(可以在之后引用它)

包含所有帧的table(我们可以只使用其中一些)

帧速

一个决定动画是否循环的标志

当想要播放动画,我们必须使用animations.play()播放参数指定的动画。

我们将spawncandytimer设为0,并且怪物生命health设为10。

文本样式

接下来的两行代码,可以让我们在屏幕上显示文字。this.add.text()函数有四个参数:屏幕左侧和顶部的绝对位置,实际的文本字符串和配置对象。我们可以使用CSS那样设置文本格式。代码如下:

字体是Arial,40像素高,黄色,可以设置描边(颜色和厚度),文本中心对齐。

之后,我们定义candygroup和第一颗糖果。

暂停游戏

暂停函数看起来像这样:

每次暂停按钮被点击时,我们改变this.game.paused状态为true,显示相应的提示给玩家,并为玩家单击或点击屏幕的行为创建一个事件侦听器。当单击或点击被检测到,我们删除文本并设this.game.paused为false。

paused变量在game对象中是特殊的,因为它将暂停游戏中所有动画以及计算,所以一切都被冻结,直到我们取消暂停,游戏暂停状态被设置为false。

更新循环

update()函数名是Phaser保留的函数之一。当你用这个名字命名一个函数,它将在游戏的每一帧被执行。

我们用spawncandytimer变量来跟踪时间。if语句每秒检查一次,检查是否需要重置计时器,或者是在游戏世界中产生一个新糖果(也就是说,每次看到spawncandytimer相隔1000毫秒)。然后,我们用forEach遍历糖果组中的所有糖果对象(在屏幕上可以存在多个糖果),添加固定的数值到糖果的angle变量(存储在糖果对象中的rotateMe),使它们下落时以固定的速度旋转。我们做的最后一件事是检查health是否下落到0,如果这样的话,那么我们在屏幕上显示游戏结束并暂停游戏。

管理糖果事件

为了将糖果的逻辑与主Game分开,我们使用item定义糖果,包含的函数有:spawncandy(),clickcandy()和removecandy()。为了方便使用在Game保留一些糖果的变量,而其他的一些变量则只在item的函数中,以便达到更好的维护性。

函数首先定义三个值:

糖果随机下落x坐标(数值在0和游戏画面宽度之间)

糖果随机下落y坐标,基于糖果自身的高度

随机的糖果类型(糖果一共有五个不同的图像)

然后我们添加一个糖果作为精灵,其起始位置和图像根据上面的定义。我们还需要为糖果产生过程设定动画帧。

接下来我们用物理引擎使糖果自然地从屏幕顶部坠落。然后,我们启用糖果的点击输入,加入事件监听器。

为确保糖果离开游戏屏幕时被销毁,我们将checkWorldBounds设为true。糖果离开屏幕时,函数events.onOutOfBounds()将被调用;我们用它调用removecandy()函数。设置锚点在糖果上,使其绕轴线旋转。在这里我们设置了rotateMe变量,在update()循环中转动糖果;我们选择-2和+2之间的一个值。最后一行代码将新创建的糖果加入到糖果群组,这样我们就可以不停的遍历他们。

让我们查看下一个函数clickcandy():

这里需要将一个糖果作为参数,采用Phaser自带的方法kill()删除它。我们还增加了得分1,并更新得分的文本。

重置糖果也同样简短和简单:

当糖果被点击或掉出屏幕。removecandy()函数会被调用。candy对象会被删除,玩家失去10点生命。(游戏一开始玩家有10点生命,所以有一个糖果掉出屏幕游戏就结束了)

原型和游戏状态

我们已经了解了游戏机制,核心理念,以及游戏玩法。现在是时候去看代码的其他部分了:缩放屏幕、加载资源和管理按钮点击等等。

我们已经知道了游戏状态,让我们逐个查看他们:

Boot.js

boot.js是我们将定义主要游戏对象Candy(你可以取个你喜欢的名字)的JavaScript文件。下面是boot.js文件的源代码:

正如你所看到的,我们从var Candy = {}开始,为游戏创建了一个全局对象。所有对象都存储在其中,所以我们将不会使全局名字空间膨胀。

Candy.Boot = function(game){}代码创建一个新函数称为boot()(index.html有调用)将game对象作为参数(也在index.html中被框架创建)。

Candy.Boot.prototype = {}代码是使用原型定义Candy.Boot内容的一种方式。

Phaser有一些保留的函数名称,正如我之前提到;preload()和create()就是其中的两个。preload()用于加载所有资源;create()只调用一次(在preload()之后),所以你可以把代码当对象一样安排,就像定义变量或添加精灵。

我们的Boot对象包含这两个函数,这样他们就可以被Candy.Boot.preload()和Candy.Boot.create()调用。正如你所看到boot.js文件的完整的源代码,preload()函数加载了一个图像到框架中:

this.load.image()中第一个参数是我们给图像取的名字,第二个是图像文件的路径。

为什么我们要在boot.js文件中加载图像,用preload.js不行吗?好吧,因为我们需要一个加载条来显示所有资源(preload.js)的加载状态,因此它需要第一个被加载。

缩放选项

create()函数包含了一些Phaser特定的输入和缩放设置:

第一行,input.maxpointers设为1,我们的游戏不需要多点触摸。

scale.scalemode控制游戏的缩放。可用的设置有:EXACT_FIT,NO_SCALE和SHOW_ALL。EXACT_FIT将缩放游戏到所有可用的空间(100%宽和高,不等比缩放);NO_SCALE将禁用缩放;SHOW_ALL将确保游戏符合给定的尺寸,一切都会显示在屏幕上(按比例缩放)。

将scale.pagealignhorizontally和scale.pagealignvertically设为true ,将使游戏在水平和垂直方向居中。

调用scale.setScreenSize(true) “激活”缩放。

最后一行,state.start('Preloader'),执行下一个状态,即Preloader状态。

Preloader.js

preload函数中加载了大量的图片。与preload()函数相比,create()函数非常简单,因为create()函数只需要负责切换状态。

下面是preloader.js源代码:

};与boot.js比较像;定义了Preloader对象,然后添加了两个原型函数(preload()和create())。在Prototype对象中,我们定义了两个变量:Candy.GAME_WIDTH和Candy.GAME_HEIGHTt;它们设置游戏屏幕默认的宽度和高度。

preload()的前3行代码设置舞台的背景颜色(# b4d9e7,浅蓝色),显示游戏中的精灵,setPreloadSprite()函数将负责资源的加载进度显示。我们来看add.sprite()函数:

正如你所看到的,我们需要三个值:图像x轴绝对坐标(舞台宽度减去图像宽度再除以2),图像y轴绝对坐标(类似计算)以及图像的名称(我们已经在boot.js文件中加载)。

加载spritesheets

接下来的几行是使用load.image()(你已经看到了)加载所有的图形资源。

最后3行代码有点不同:

load.spritesheet()函数,不是加载单个的图片,而是spritesheet。两个额外的参数告诉函数单个图像的尺寸。

在这里candy.png我们有5种不同类型的糖果。图片尺寸410x98px,但是单个元素的大小是82x98px,在load.spritesheet()函数中定义。玩家spritesheet以同样的方式加载。

create()函数启动游戏的下一个状态MainMenu,当所有资源加载完毕就会显示游戏菜单。

MainMenu.js

在这里渲染图片,添加按钮以及游戏循环。

MainMenu没有preload()函数,因为资源已在Preload.js加载。

这里有2个函数,create(),startGame()。先看startGame()函数:

这个函数只负责启动游戏循环,但是它不会自动执行,我们需要通过按钮来触发它。

create()有3个add.sprite()函数,它们加载图片到舞台上。我们的主菜单在背景上,小怪物在角落里,还有游戏的标题。

按钮

还有一个我们早已在Game状态使用的对象,就是按钮:

这个按钮看起来比我们之前的代码都要复杂。我们通过八种不同的参数创建按钮:x轴位置,y轴位置,图像的名称(或精灵),单击该按钮时执行的函数,该函数执行环境,指定按钮使用的图片。

这是按钮的spritesheet,包含状态标签:

与candy.png非常相似,垂直排列。

记住最后三位数字传递给函数的意义—1 0 2-它们分别代表按钮的不同状态:over(鼠标悬停),out(正常),和down(触摸或点击)。在button.png我们有不同的图片代表他们。

现在你已经了解了Phaser游戏框架的基础。恭喜你!

游戏成品

本文中使用的demo游戏已经演变成一个完整的游戏,你可以在这里玩。如你所见,有生命、成就、得分,以及其他有趣的功能,他们中的大多数都基于你已经学到的知识。

这是一段关于怪物要糖果demo代码的漫长旅程,我希望能帮助你学习Phaser,在不久的将来你可以开发很酷的游戏。

作者:郭华丰,广州微美软件有限公司CTO,专注于移动平台(Android、iOS等)游戏应用开发,拥有超过10年图形和游戏开发经验,曾供职跨国企业Gameloft。主要作品有:狂野飙车(Asphalt 6)、近地联盟先遣队2(NOVA 2)、战谷、三国我为王等。出版了《iOS应用开发》、《Android数据库应用编程》。

 

   
次浏览       
 
相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
 
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
 
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]

十天学会DIV+CSS(WEB标准)
HTML 5的革新:结构之美
介绍27款经典的CSS框架
35个有创意的404错误页面
最容易犯的13个JavaScript错误
设计易理解和操作的网站
更多...   


设计模式原理与应用
从需求过渡到设计
软件设计原理与实践
如何编写高质量代码
单元测试、重构及持续集成
软件开发过程指南


东软集团 代码重构
某金融软件服务商 技术文档
中达电通 设计模式原理与实践
法国电信 技术文档编写与管理
西门子 嵌入式设计模式
中新大东方人寿 技术文档编写
更多...