炸弹人游戏开发系列(3):显示地图
前言
上文我们进行了初步的高层设计,现在我们将实现图片预加载和显示地图的功能需求。我采用TDD开发,大家可以看到在实现的过程中我们会修改设计,修改设计后又会修改对应的实现代码,这是一个不断迭代的过程。在有测试套件的保护下,我们可以放心地重构。
本文目的
掌握地图绘制的技术。
本文主要内容
- 开发策略
- 预加载图片
- 显示地图
- 重构
- 继续实现显示地图
- 本文最终领域模型
- 高层划分
- 演示
- 本文参考资料
回顾上文与显示地图相关的领域模型
开发策略
使用我的控件YPreLoadImg来实现图片预加载,结合progressBar插件,能够显示出加载进度条。
只建一个ShowMap类,用这个类来进行实验,找到显示地图的方法,然后在重构的时候再分离出Main类和MapData数据类。
原因
- 因为我对canvas很不熟悉,需要先集中精力熟悉canvas的使用,而不是把精力放在架构设计上。
- 因为我是采用TDD开发,因此可以安全地进行重构。可以在实现“显示地图”功能后,再在重构时提炼出Main和MapData类。
开发策略也是迭代修正的
开发策略只是就当前的知识指定的大概计划,会随着开发的进行而进行细化和修正。如现在的开发策略并没有考虑到在重构时会增加Game类。
预加载图片
预加载的目的
将图片下载到本地内存中。
为什么要预加载
- 必须等到图片完全加载后才能使用canvas对图片进行操作。如果试图在图片未完全加载之前就将其呈现到canvas上,那么canvas将不会显示任何图片。
- 如果不使用预加载,则在使用图片之前需要先下载图片到本地。即使有浏览器优化,第一次使用图片时也需要先下载图片,这样在第一次使用图片时,会有卡的感觉。
因此,在游戏开始之前,先要进行游戏初始化,预加载游戏所需的图片。
如何进行预加载
基本示例:
var img = new Image(); //创建一个图片对象 img.src = "test.png"; //下载该路径的图片 img.onload = function () { //图片下载完毕时异步调用callback函数。 callback(img); };
预加载网上教程
- js图片预加载
- js 实现图片预加载
- 浏览器重图片的预加载问题onload事件
显示进度条
YPreLoadImg控件结合进度条插件progressbar,可以显示出进度条。
新增ShowMap类并实现预加载图片
新增ShowMap类,使用TDD开发并实现预加载图片,需要先写测试用例。
测试代码
describe("showMap.js", function () {describe("init", function () {beforeEach(function () {//不执行onloadspyOn(showMap, "onload").andCallFake(function () {});});afterEach(function () {});it("传入的参数为数组,数组元素包含id和url属性", function () {var urls = [];var temp = [];var i = 0, len = 0;spyOn(window.YYC.Control, "PreLoadImg");temp = [{ id: "ground", url: "ground.png" },{ id: "wall", url: "wall.png" }];for (i = 0, len = temp.length; i < len; i++) {urls.push({ id: temp[i].id, url: "../../../../Content/Bomber/Image/Map/" + temp[i].url });}showMap.init();expect(YYC.Control.PreLoadImg).toHaveBeenCalled();expect(YYC.Control.PreLoadImg.calls[0].args[0]).toBeArray();expect(YYC.Control.PreLoadImg.calls[0].args[0][0].id).toBeDefined();expect(YYC.Control.PreLoadImg.calls[0].args[0][0].url).toBeDefined();});});describe("onload", function () {var dom = null;function insertDom() {dom = $("<div id='progressBar'></div>");$("body").append(dom);};function removeDom() {dom.remove();};beforeEach(function () {insertDom();});afterEach(function () {removeDom();});it("加载完毕后,隐藏加载进度条", function () {expect($("#progressBar").css("display")).toEqual("block");showMap.onload();expect($("#progressBar").css("display")).toEqual("none");});}); });
实现代码
var showMap = (function () {var _getImg = function () {var urls = [];var temp = [];var i = 0, len = 0;temp = [{ id: "ground", url: "ground.png" }, { id: "wall", url: "wall.png" }];for (i = 0, len = temp.length; i < len; i++) {urls.push({ id: temp[i].id, url: "../../../../Content/Bomber/Image/Map/" + temp[i].url });}return urls;};return {init: function () { this.imgLoader = new YYC.Control.PreLoadImg(_getImg(), function (currentLoad, imgCount) {$("#progressBar_img_show").progressBar(parseInt(currentLoad * 100 / imgCount, 10)); //调用进度条插件}, YYC.Tool.func.bind(this, this.onload));},onload: function(){$("#progressBar").css("display", "none");alert("complete!");}};window.showMap = showMap; }());
补充说明
YYC.Tool.func.bind是我的一个工具类方法,作用是将onload中的this指向showMap。
预加载完成后,调用onload,隐藏进度条,弹出对话框,提示“complete”。
领域模型
运行
在页面上调用init方法
$(function () {showMap.init();});
显示效果
加载图片中
加载完成
重构
识别出config类,放到辅助操作层
为什么要增加Config类?
ShowMap中预加载图片的url是相对于当前页面的,所以在不同的页面预加载图片时(如在测试页面和实际页面),url的前缀可能不一样。
因此我希望url的前缀是可以配置的,这样当在不同的页面调用ShowMap时,只需要改动配置文件就好了。
领域模型
具体内容
所以我增加Config全局配置类,在Config中配置url的前缀。该类属于辅助操作层。
Config
var bomberConfig = {url_pre: {//showMap.jsSHOWMAP: "../../../../Content/Bomber/"} };
ShowMap
var _getImg = function () { ...for (i = 0, len = temp.length; i < len; i++) {urls.push({ id: temp[i].id, url: bomberConfig.url_pre.SHOWMAP + temp[i].url });}return urls; };
显示地图
开发策略
现在我就要开始着手显示游戏地图了。考虑到我没有canvas的使用经验,因此我先进行试验,熟悉canvas中与显示地图相关的api;然后对代码和领域模型进行重构,提炼出新的显示地图的模型;最后再具体实现“显示地图”的功能。
因为要使用canvas显示图片,先要对该图片进行预加载。因此先在showMap.onload中进行试验。
注意:对onload的测试为异步测试(要等待图片加载完成后才能使用canvas显示图片)。
用drawImage显示空地图片
因为canvas中使用drawImage来绘制图片,因此需要先掌握该API的用法。
测试代码
it("绘制一张空地图片", function () {spyOn(showMap, "getContext").andCallFake(function (canvas) {showMap.context = canvas.getContext("2d");spyOn(showMap.context, "drawImage");});showMap.init();//延迟100ms测试 testTool.asynRun(function () {expect(showMap.context.drawImage).toHaveBeenCalledWith(showMap.imgLoader.get("ground"), 0, 0);}, 100);});
实现代码
var showMap = (function () {var _createCanvas = function () {// 创建canvas,并初始化 (我们也可以直接以标签形式写在页面中,然后通过id等方式取得canvas)var canvas = document.createElement("canvas");//设置宽度、高度canvas.width = 600;canvas.height = 400;document.body.appendChild(canvas);return canvas;};var _getImg = function () {var urls = [];var temp = [];var i = 0, len = 0;temp = [{ id: "ground", url: "ground.png" }, { id: "wall", url: "wall.png" }];for (i = 0, len = temp.length; i < len; i++) {urls.push({ id: temp[i].id, url: bomberConfig.url_pre.SHOWMAP + "image/map/" + temp[i].url });}return urls;}; return {context: null,imgLoader: null,init: function () {var self = this;var canvas = _createCanvas();//为了方便测试,将“取得2d绘图上下文”封装到方法中this.getContext(canvas); this.imgLoader = new YYC.Control.PreLoadImg(_getImg(), function (currentLoad, imgCount) {$("#progressBar_img_show").progressBar(parseInt(currentLoad * 100 / imgCount, 10)); //调用进度条插件}, YYC.Tool.func.bind(this, this.onload));},onload: function(){$("#progressBar").css("display", "none");this.context.drawImage(this.imgLoader.get("ground"), 0, 0);},getContext: function (canvas) {this.context = canvas.getContext("2d");}};window.showMap = showMap; }());
补充说明
这里canvas是动态创建的,但是这样创建canvas会有drawImage中的dx、dy和clearRect中的x、y按比例缩放的问题。在第5篇博文中,我们会碰到这个问题,到时候我再详细说明。
运行效果
用createPattern显示空地图片区域
在游戏开发中,可能需要绘制一片相同图片的区域,该区域由一张图片在x、y方向重复绘制而成,需要用到createPattern、fillStyle、fillRect。因此先进行实验。
测试代码
describe("画出地图", function () {function spyOnContext(func) {spyOn(showMap, "getContext").andCallFake(function (canvas) {showMap.context = canvas.getContext("2d");func();});};it("获得pattern", function () {spyOnContext(function () {spyOn(showMap.context, "createPattern");});showMap.init();//延迟100ms测试 testTool.asynRun(function () {expect(showMap.context.createPattern).toHaveBeenCalledWith(showMap.imgLoader.get("ground"), "repeat");expect(showMap._pattern).not.toBeNull();}, 100);});it("缓存pattern", function () {spyOnContext(function () {spyOn(showMap.context, "createPattern");});showMap.init();expect(showMap.context.createPattern.calls.length).toEqual(1);//*延迟100ms后,执行“showMap.init();”。//*然后再延迟100ms后,如果showMap.context.createPattern没有被调用,就验证pattern被缓存了“ testTool.asynRun(function () {showMap.init();}, 100);testTool.asynRun(function () {expect(showMap.context.createPattern.calls.length).toEqual(1);}, 100);});it("画出200*200的空地图片区域,ie、ff显示正常", function () {spyOnContext(function () {spyOn(showMap.context, "fillRect");});showMap.init();testTool.asynRun(function () {expect(showMap.context.fillStyle).toEqual(showMap._pattern);expect(showMap.context.fillRect).toHaveBeenCalledWith(0, 0, 200, 200);}, 100);}); });
实现代码
onload: function(){$("#progressBar").css("display", "none");if (!this._pattern) {this._pattern = this.context.createPattern(showMap.imgLoader.get("ground"), "repeat");}this.context.fillStyle = this._pattern;this.context.fillRect(0, 0, 200, 200);},
运行效果
重构
现在需要停一下,对已有的代码进行梳理。
创建Bitmap类,并进行对应重构
我发现,在ShowMap的onload方法中,多次使用到了图片对象:showMap.imgLoader.get("ground"),这个对象是预加载图片后的Image对象。
考虑到在游戏中需要对图片进行操作,那为什么不能提出“图片类”的概念,将与图片本身相关的内容都放到该类中呢?
因此,我提炼出Bitmap类,该类有以下职责:
- 包含图片的信息(如图片对象image、width、height等)
- 包含图片的基本操作(如剪切、缩放等)
进一步思考
- 绘制图片的draw方法应不应该放到Bitmap类中呢?
考虑到Bitmap类是图片的包装类,包含与图片本身密切相关的属性和方法。而绘制图片方法的职责是读取图片的属性,使用canvas的api进行操作。Bitmap类不需要知道自己是如何被调用的,因此绘制图片的职责应该放到调用Bitmap的类,即放到ShowMap类中。
- 为什么不创建图片的精灵类?
因为图片不是独立的个体,它属于数据的概念,是精灵的一个属性,在概念上它并不是精灵。
- Bitmap应该具体有哪些成员?
属性:
应该包含预加载后的图片对象、宽度和高度、图片的坐标。
方法:
目前来看,不需要对图片进行操作,因此不需要如剪切等方法。
领域模型
测试代码
describe("Bitmap.js", function () {var bitmap = null;beforeEach(function () {});afterEach(function () {});describe("构造函数Init", function () {var dom = null;function insertDom() {dom = $("<img id='test_img'>");$("body").append(dom);};function removeDom() {dom.remove();};beforeEach(function () {insertDom();});afterEach(function () {removeDom();});it("获得预加载后的图片对象、宽度、高度、图片的坐标", function () {bitmap = new Bitmap($("#test_img")[0], 2, 3, 4, 5);expect(bitmap.img).not.toBeNull();expect(bitmap.width).toEqual(2);expect(bitmap.height).toEqual(3);expect(bitmap.x).toEqual(4);expect(bitmap.y).toEqual(5);});}); });
实际代码
(function () {var Bitmap = YYC.Class({Init: function (img, width, height, x, y) {var judge = YYC.Tool.judge;this.img = img;this.width = width;this.height = height;this.x = x;this.y = y;},Private: {},Public: {img: null,width: 0,height: 0,x: 0,y: 0}});window.Bitmap = Bitmap; }());
重构Bitmap
Bitmap构造函数的参数太多了,因此使用一个对象直接量来包装参数:
(function () {var Bitmap = YYC.Class({Init: function (data) {this.img = data.img;this.width = data.width;this.height = data.height;this.x = data.x;this.y = data.y;},Private: {},Public: {img: null,width: 0,height: 0,x: 0,y: 0}});window.Bitmap = Bitmap; }());
运行测试,测试失败。
修改测试代码,使测试通过:
it("获得预加载后的图片对象、宽度、高度、图片的坐标", function () {bitmap = new Bitmap({img: $("#test_img")[0],width: 2,height: 3,x: 4,y: 5});expect(bitmap.img).not.toBeNull();expect(bitmap.width).toEqual(2);expect(bitmap.height).toEqual(3);expect(bitmap.x).toEqual(4);expect(bitmap.y).toEqual(5);});
创建BitmapFactory类
因为在ShowMap类中需要创建Bitmap实例,因此需要增加Bitmap的工厂BitmapFactory类。
理由如下:
- Bitmap构造函数有3个参数,比较复杂,需要将创建实例这个过程封装起来。因此需要工厂类来负责和管理创建过程。
领域模型
测试代码
describe("bitmapFactory.js", function () {describe("createBitmap", function () {var dom = null;function insertDom() {dom = $("<img id='test_img'>");$("body").append(dom);};function removeDom() {dom.remove();};beforeEach(function () {insertDom();});afterEach(function () {removeDom();});it("方法存在", function () {expect(bitmapFactory.createBitmap).toBeDefined();});it("如果参数为1个(HTMLImg对象),则bitmap的width、height为HTMLImg的width、height", function () {var bitmap = null,width = 0,height = 0;bitmap = bitmapFactory.createBitmap($("#test_img")[0]),width = $("#test_img").width(),height = $("#test_img").height();expect(bitmap.width).toEqual(width);expect(bitmap.height).toEqual(height);});it("如果参数为3个(HTMLImg对象、width、height),则bitmap的width、height为传入的width、height", function () {var bitmap = null;bitmap = bitmapFactory.createBitmap($("#test_img")[0], 100, 200),expect(bitmap.width).toEqual(100);expect(bitmap.height).toEqual(200);});}); });
实际代码
(function () {var bitmapFactory = {createBitmap: function (img, width, height) {if (arguments.length == 1) {return new Bitmap(img, img.width, img.height);}else if (arguments.length == 3) {return new Bitmap(img, width, height);}}};window.bitmapFactory = bitmapFactory; }());
showMap.onload对应改变
onload: function(){$("#progressBar").css("display", "none");if (!this._pattern) {this._pattern = this.context.createPattern(bitmapFactory.createBitmap(showMap.imgLoader.get("ground")).img, "repeat");}this.context.fillStyle = this._pattern;this.context.fillRect(0, 0, 200, 200); },
重构测试
现在让我们来回头看下drawImage和createPattern的测试,在测试中都需要异步测试。
每增加一个测试用例就需要延迟测试,这样增加了很多重复代码,为什么不能把延迟测试分离出去,从而在测试中把精力放到我们的主要任务-即如何测试行为上呢?
因此,我把init方法放到测试页面SpecRunner上调用,然后在测试页面上对整个测试进行延迟,这样就能保证整个测试都是在图片预加载成功后进行的了。
测试页面相关代码:
<body> <script type="text/javascript"> (function () {//图片预加载 main.init()//清除“main.init()”创建的多余html元素 function clear() {$("body").children().not("script").remove();};var jasmineEnv = jasmine.getEnv();jasmineEnv.updateInterval = 1000;var htmlReporter = new jasmine.HtmlReporter();jasmineEnv.addReporter(htmlReporter);jasmineEnv.specFilter = function (spec) {return htmlReporter.specFilter(spec);};var currentWindowOnload = window.onload;//延迟300ms执行测试(等待预加载完后执行) setTimeout(function () {clear();if (currentWindowOnload) {currentWindowOnload();}execJasmine();}, 1000);function execJasmine() {jasmineEnv.execute();}})(); </script> </body>
现在测试用例中就不再需要“testTool.asynRun”进行异步测试了。
重构ShowMap
现在,让我们再来看看showMap类,发现该类做了三件事:
- 游戏初始化:图片预加载
- 游戏逻辑
- 显示地图
增加Main类
根据单一职责原则,一个类只应该有一个职责,只有一个原因引起变化。再结合之前给出的领域模型,Main类是游戏入口,负责控制游戏的主循环,调用相关的操作。因此,将showMap中负责游戏初始化的init方法移到Main中。
增加Game类
showMap中还包含了游戏逻辑,如在onload中隐藏进度条,然后显示地图。根据以往游戏开发经验,知道游戏逻辑会越来越复杂,因此可以将游戏逻辑提取出来形成新类Game,这样ShowMap只负责调用canvas的API显示地图了。
重构后的领域模型
相关代码
Main
var main = (function () {var _getImg = function () {var urls = [];var temp = [];var i = 0, len = 0;temp = [{ id: "ground", url: "ground.png" }, { id: "wall", url: "wall.png" }];for (i = 0, len = temp.length; i < len; i++) {urls.push({ id: temp[i].id, url: bomberConfig.url_pre.SHOWMAP + "image/map/" + temp[i].url });}return urls;};return {imgLoader: null,init: function () {var game = new Game();this.imgLoader = new YYC.Control.PreLoadImg(_getImg(), function (currentLoad, imgCount) {$("#progressBar_img_show").progressBar(parseInt(currentLoad * 100 / imgCount, 10)); //调用进度条插件 }, YYC.Tool.func.bind(game, game.onload));}};window.main = main; }());
Game
(function () {var Game = YYC.Frame.MyClass({Init: function () {this.showMap = new ShowMap();},Private: {},Public: {showMap: null,onload: function () {$("#progressBar").css("display", "none");this.showMap.drawMap();}}});window.Game = Game; }());
重构showMap
改成类的形式
将ShowMap改成类的形式。
提出Layer
结合领域模型分析和第2篇博文中分层渲染的概念,我增加Layer类,将图片类Bitmap装入Layer,然后使用Layer来统一绘制图片。
Layer包含canvas属性,canvas由Game创建并通过Layer的构造函数将其注入到Layer中。
Layer有一个draw方法,负责调用canvas的API来绘制图片。
重构Layer
提出Collection
考虑到Layer是集合类,因此可以将集合这个概念提出,形成新的Collection类,把集合的相关操作和集合的容器_childs放到Collection中,让Layer继承Collection。从而Layer就具有了集合类的功能。
进一步思考
为什么这里选择继承的方式复用,而不是选择组合的方式复用呢?
- 通过继承来复用比起组合来说更方便和优雅,可以减少代码量。
- 从概念上来说,Collection和Layer都是属于集合类,应该属于一个类族。Collection是从Layer中提炼出来的,它是集合类的共性,因此Collection作为父类,Layer作为子类。
领域模型
继续实现显示地图
重构到这里就告一段落,现在继续实现“显示地图”。
增加MapData
根据领域模型,增加MapData类,它是一个二维数组,用来保存地图数据。
MapData
(function () {var ground = 1,wall = 2;var mapData = [[ground, wall, ground, ground],[ground, wall, ground, ground],[ground, wall, ground, ground],[ground, wall, ground, ground]];window.mapData = mapData; }());
增加MapDataOperate
ShowMap不应该直接操作MapData,因为:
- MapData在后续的迭代中可能会变化,因此需要封装这个变化,使得MapData变化时不会影响到ShowMap
- 根据分层的结果,应该由数据操作层的类来操作MapData
因此增加数据操作层的MapDataOperate,它负责获得MapData。
领域模型
相关代码
MapDataOperate
(function () {var mapDataOperate = {getMapData: function () {return YYC.Tool.array.clone(mapData);}};window.mapDataOperate = mapDataOperate; }());
显示地图的实现
实现drawMap
在ShowMap中,通过调用MapDataOperate的getMapData方法,就可以获得地图数据,然后再根据数据向Layer中加入对应的Bitmap类即可。
相关代码
ShowMap
_getMapImg: function (i, j, mapData) {var img = null;switch (mapData[i][j]) {case 1:img = main.imgLoader.get("ground");break;case 2:img = main.imgLoader.get("wall");break;default:break}return img; } ... drawMap: function () {var i = 0,j = 0,width = 34,height = 34,row = 4,col = 4,bitmap = null,mapData = mapDataOperate.getMapData(),x = 0,y = 0,img = null;this._createLayer();for (i = 0; i < row; i++) {//注意!//y为纵向height,x为横向widthy = i * height;for (j = 0; j < col; j++) {x = j * width;img = this._getMapImg(i, j, mapData);bitmap = bitmapFactory.createBitmap({ img: img, width: width, height: height, x: x, y: y });this.layer.appendChild(bitmap);}}this.layer.draw(); }
重构
重构MapData
将MapData的ground与wall设为枚举值,增加可读性。
将枚举值放到Config类中。
相关代码
Config
map: { ...type: {GROUND: 1,WALL: 2}},
MapData
var ground = bomberConfig.map.type.GROUND,wall = bomberConfig.map.type.WALL;
重构drawMap
config增加bomberConfig.map配置
目前地图大小是在drawMap写死了:
大小为4*4,单元格宽度和高度为34px。
考虑到地图大小可能在后期的开发中不断变化,因此将其放到Config中进行统一配置。
相关代码
Config
map: {//方格宽度WIDTH: 34,//方格高度HEIGHT: 34,ROW: 4,COL: 4,type: {GROUND: 1,WALL: 2}},
ShowMap
_getMapImg: function (i, j, mapData) {var img = null,type = bomberConfig.map.type;switch (mapData[i][j]) {case type.GROUND:img = window.imgLoader.get("ground");break;case type.WALL:img = window.imgLoader.get("wall");break;default:break}return img; } ... drawMap: function () {var i = 0,j = 0,map = bomberConfig.map,bitmap = null,mapData = mapDataOperate.getMapData(),x = 0,y = 0,img = null;this._createLayer();for (i = 0; i < map.ROW; i++) {//注意!//y为纵向height,x为横向widthy = i * map.HEIGHT;for (j = 0; j < map.COL; j++) {x = j * map.WIDTH;img = this._getMapImg(i, j, mapData);bitmap = bitmapFactory.createBitmap({ img: img, width: map.WIDTH, height: map.HEIGHT, x: x, y: y });this.layer.appendChild(bitmap);}}this.layer.draw(); }
本文最终领域模型
高层划分
重构层
增加数据操作层
本文增加了MapDataOperate类,对应增加数据操作层。该层负责对数据进行操作。
分析
Bitmap放到哪?
我们来看下Bitmap的职责:
- 包含图片的信息(如图片对象image、width、height等)
- 包含图片的基本操作(如剪切、缩放等)
从中得出Bitmap应该放到数据操作层。
层、领域模型
演示
演示地址
本文参考资料
js图片预加载
js 实现图片预加载
欢迎浏览上一篇博文:炸弹人游戏开发系列(2):初步设计
欢迎浏览下一篇博文:炸弹人游戏开发系列(4):炸弹人显示与移动
转载于:https://www.cnblogs.com/chaogex/p/3175811.html
炸弹人游戏开发系列(3):显示地图相关推荐
- 炸弹人游戏开发系列(6):实现碰撞检测,设置移动步长
前言 上文中我们实现了"玩家控制炸弹人"的功能,本文将实现碰撞检测,让炸弹人不能穿过墙.在实现的过程中会发现炸弹人移动的问题,然后会通过设置移动步长来解决. 说明 名词解释 具体状 ...
- 炸弹人游戏开发系列(8):放炸弹
前言 上文中我们加入了1个敌人,使用A*算法寻路.本文会给我们的炸弹人增加放炸弹的能力. 说明 名词解释 xx类族 是指以xx为基类的继承树上的所有类. 本文目的 实现"放炸弹"功 ...
- 炸弹人游戏开发系列(2):初步设计
前言 有了上文的初步需求分析后,就可以进入初步设计阶段了. 本文目的 得到初步的领域模型,对层和模块进行初步的划分. 本文主要内容 技术选择 开发环境 外部依赖 代码结构 开发方法 初步领域模型 高层 ...
- Cocos2dx游戏开发系列笔记7:一个简单的跑酷游戏《萝莉快跑》的消化(附下载)
懒骨头(http://blog.csdn.net/iamlazybone QQ124774397 青岛 ) 或许有天 我们羡慕和崇拜的人 因为我们的努力 也会来了解我们 说不定 还会成为好友 骨头喜 ...
- Cocos2dx游戏开发系列笔记7:一个简单的跑酷游戏《萝莉快跑》的消化(附下载)...
2019独角兽企业重金招聘Python工程师标准>>> 或许有天 我们羡慕和崇拜的人 因为我们的努力 也会来了解我们 说不定 还会成为好友 骨头喜欢这样与哲哲共勉 多少个夜晚 一张长 ...
- Cocos2dx游戏开发系列笔记11:解刨《战神传说》完结篇
懒骨头(http://blog.csdn.net/iamlazybone QQ:124774397 ) 两点: 1 感谢 net19880504 同学,在上篇提到:想让骨头继续写<战神传说> ...
- HTML5游戏开发系列教程5(译)
原文地址:http://www.script-tutorials.com/html5-game-development-lesson-5/ 最终我决定准备下一篇游戏开发系列的文章,我们将继续使用can ...
- unity2d游戏开发系列教程:四、一个2D游戏所需要的主要功能(游戏框架)
目录 unity2d游戏开发系列教程:一.环境安装 unity2d游戏开发系列教程:二.新建工程并熟悉Unity编辑器常用功能 unity2d游戏开发系列教程:三.场景布置,增加怪物和机关 原文下载 ...
- unity2d游戏开发系列教程:三、场景布置,增加怪物和机关
目录 unity2d游戏开发系列教程:一.环境安装 unity2d游戏开发系列教程:二.新建工程并熟悉Unity编辑器常用功能 第一节.场景草地布置 先查看一下资源文件里都有什么,一会就要用到的 打开 ...
最新文章
- lower_boundupper_bound
- 文思创新深圳招聘biztalk
- 动态规划训练11 [String painter HDU - 2476]
- VC2008以资源形式实现多语言版本
- U盘安装CentOS 7错误 /dev/root does not exist, could not
- Python学生信息管理系统的开发
- java设计模式2--工厂模式
- AdaBoost--从原理到实现
- oracle的unload,Oracle 业务数据unload恢复过程
- JVM初识之类加载过程
- java后台调用webservice接口常用方式
- c++数组、结构体数组和对象数组的初始化方式
- 深圳经济特区新居住证将实施签注制度
- CSS mask遮罩
- chrome浏览器打开网址找不到服务器IP
- Android远程桌面助手(B1309)
- 制作7Z格式可安装程序包
- Windows7下按装使用vagrant踩坑总结及Vagrant常见镜像简单配置
- UVa Problem 10001 Garden of Eden (伊甸园)
- 真正意义上能够全部抓取昵图网全站图片