文档章节

12、开源游戏-“胡子”地图绘制和游戏主循环设计

青青小树
 青青小树
发布于 2014/04/08 20:53
字数 2446
阅读 1233
收藏 25

在前面中我们初始化了游戏的资源,这次我们来说下地图的绘制和游戏主循环设计。

地图绘制

    以前说过地图是用tiled画好,导出为图片形式的,所以地图的绘制,就是把这个图片绘制到canvas的过程。这样绘制地图就简单了,使用drawImage方法绘制即可。

    这里有个2问题,1是地图的大小一般肯定是大于canvas的,所以我们只是把地图的一部分绘制到了canvas上,2是地图的移动。1中的地图的复制位置是根据2中地图的移动距离来确定的。我们的思路如下:记录鼠标移动的xy坐标值,然后根据xy值和canvas边缘做比较,当靠近边缘时,我们就移动地图一段距离,重复这个过程,直到地图绘制完。

    其实我们上面的思路中,就是在改变drawImage方法的参数过程,那么来看下drawImage方法:

定义和用法

drawImage() 方法绘制一幅图像。

语法

drawImage(image, x, y)
drawImage(image, x, y, width, height)
drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight)
image

所要绘制的图像。这必须是表示 <img> 标记或者屏幕外图像的 Image 对象,或者是 Canvas 元素。

x, y
要绘制的图像的左上角的位置。
width, height
图像所应该绘制的尺寸。指定这些参数使得图像可以缩放。
sourceX, sourceY
图像将要被绘制的区域的左上角。这些整数参数用图像像素来度量。
sourceWidth, sourceHeight
图像所要绘制区域的大小,用图像像素表示。
destX, destY
所要绘制的图像区域的左上角的画布坐标。
destWidth, destHeight
图像区域所要绘制的画布大小。

描述

drawImage() 方法有 3 个变形。第一个变形把整个图像复制到画布,将其放置到指定点的左上角,并且将每个图像像素映射成画布坐标系统的一个单元。第二个变形也把整个图像复制到画布,但是允许您用画布单位来指定想要的图像的宽度和高度。第三个变形则是完全通用的,它允许您指定图像的任何矩形区域并复制它,对画布中的任何位置都可进行任何的缩放。

传递给 drawImage() 方法的图像必须是 Image 对象Canvas 元素。一个 Image 对象能够表示文档中的一个 <img> 标记或者使用 Image() 构造函数所创建的一个屏幕外图像。

再看一下这个图:

我们使用该方法的最后一种变形,代码如下:

game.bgContext.drawImage(game.taskMapImage,game.offsetX,game.offsetY,game.canvasWidth,game.canvasHeight, 0,0,game.canvasWidth,game.canvasHeight);


我仔细说明一下:

  • bgContext是我们背景canvas的绘图环境,同样我们在初始化时将它保存了起来,这样我们就可以使用2d的绘图环境。代码如下:


game.bgCanvas = $('#bgcanvas');
game.bgContext = game.bgCanvas.getContext('2d');
  • game.taskMapImage为当前任务的地图,现在所有任务都是一个地图,而且也没有障碍物,我打算在做胡子第二版时用一个框架来实现这个障碍物碰撞检测
  • offsetX和offsetY,是指地图移动的偏移量,默认未移动时为0。当地图向左侧移动了20像素后,offsetX变为20,同理offsetY也是如此计算。这个20就是我们说的移动一段距离的值。这2个值决定了,我们从地图图片哪开始复制,结合他后面的2个参数,就是我从哪开始复制,复制多宽、多高的图片。
  • game.canvasWidth和game.canvasHeight为背景canvas的宽高,即复制地图中canvas宽高的地图。
  • 0,0为背景canvas左上角的坐标,即将地图图片,从这开始绘制。
  • game.canvasWidth,game.canvasHeight 绘制地图的大小,没说的肯定是背景canvas的宽高了。



那我们怎么判断是否需要移动地图呢?我们根据鼠标的xy位置和一个阀值做比较,即鼠标x值距离背景canvas的左右边界小于阀值像素时,我们移动一次地图,鼠标y同理。

阀值我们为10像素,每次移动20像素的地图距离。代码如下:

if(mouse.x<=10){
 if (game.offsetX>=20){
 game.offsetX -= 20; 
 }else if (game.offsetX>0){
 game.offsetX = 0; 
 
 }
} else if (mouse.x>= game.canvasWidth - 10){
 if (game.offsetX + game.canvasWidth + 20 <= game.currentMapImage.width){
 game.offsetX += 20;
 }else{
 game.offsetX += game.currentMapImage.width-(game.offsetX + game.canvasWidth);
 }
}

下面代码中,我们判断鼠标的x值,如果x距离左侧边界小于等于10时,判断是否已经移动了超过20像素,是我们就减去20,不足时我们恢复成0,同理我们判断鼠标x距离右侧边界时的情况。类似完成鼠标y的判断。

通过上面的代码,当面的条件满足时,我们改变offsetX和offsetY的值,然后即调用drawImage方法绘制新的地图,来实现地图的移动。


game.bgContext.drawImage(game.taskMapImage,game.offsetX,game.offsetY,game.canvasWidth,game.canvasHeight, 0,0,game.canvasWidth,game.canvasHeight);


小节

我们使用鼠标的xy和背景canvas边界做比较,来判断是否需要移动地图,是的话就根据移动的距离,重新绘制地图。这里对鼠标移动的监听没有说明,主要使用jquery事件:


$(document).mousemove(function(e){
		mouse.x = e.pageX;
		mouse.y = e.pageY;
	});

这里还需要减去背景canvas相对左和顶部的距离(如果有的话),这个都不细说。在上面中我们还有一个问题,就是谁来触发这个判断动作,由鼠标事件来做?如果这样这个鼠标事件js就有点不单存了,我想用游戏的主循环触发这个判断,当然不是直接去做,那样更不好。


设计游戏主循环

    在前面时,我们提到过游戏的基本原理就是“绘制 擦除 绘制”循环它。我感觉这个很重要!这个循环控制着或说触发着游戏的一系列事件的产生执行。这个和flash好像啊,如果了解过flash的动画,应该知道弄flash动画,都弄很多小的影片剪辑(movieclip),然后将它们组合或放在主时间轴上,当主时间轴播放时,这些小的影片剪辑也在同时播放着自己的循环(这里可能不太恰当,因为主时间轴可以是停止的,呵呵)

    那我们可不可以这样想,所谓的游戏开发其实和开发网站、mis系统、bi软件(为啥说他呢,因为我是开发这个的,呵呵)这些系统是一样的,都是把系统分解成许多个小功能,然后组合其中一部分,或根本不用组合,就成了一个系统了。

    游戏也是,分解成许多个小功能,尽量模块话(一个稍复杂的游戏不这样弄,真难想象如何控制代码和维护),然后用主循环去组合或执行他们, 不论游戏还是系统,触发动作的一般都是人,如用户点击按钮登录,玩家使用技能,只不过系统中用户触发后一般就是立马执行,而游戏却不同,一般是由循环去执行他,或者说是在下一次循环中执行他。

    不知道我说的乱不乱,呵呵。我整理下我的思路:开发一个游戏时,把游戏分解成许多个小功能,然后分别实现他们,我想这里最难的就是如何分解和他们的关系了。(等胡子游戏第一版开发完时,我会发一下代码的类图,到时欢迎大家指出我们的错误和不足呵呵,)。然后主循环中调用它们或调用几个,然后这几个中又调用其他的(他们的关系)。

    以前觉得开发游戏时,不知道从何下手,现在我把它和我熟悉的bi开发做了对号或找不同,感觉不再像当初那么迷糊了。下面说主循环的代码设计。

代码设计

    按照前面的废话,主循环只有“绘制 擦除 绘制”就可以了,那就是绘制所有的游戏单元(建筑、车辆、人员),如果是一个没有动画的游戏,我想应该是的,但是胡子有动画,就是精灵图(其实这么说是不准确的,游戏里的一切都精灵),就是每个单元都有自己的一个小的动画循环,如车辆行走,转向和建筑的生产等,如下图的坦克和天电的发电图:

坦克行走图


天电 发电图

就目前来说我们的主循环要干2建事,1绘制所有的游戏单元,2绘制游戏单元的动画。代码如下:

$(window).load(function() {
    game.init();
});

var game = {
    init: function(){
        mouse.init();
	commandbar.init();
	sounds.init();
	data.init();

	...
    },
    start:function(){
	 commandbar.init();
        
	 ...
    },
    spiritLoop:function(){
		//调用所有游戏单元的动画方法
		for (var i = 0;i<game.items.length;i++){
			game.items[i].spiritAnimate();
		};
		setInterval(game.spiritLoop,100);
    },	
    drawLoop:function(){
		if (判断是否需要移动地图){
			game.bgContext.drawImage(game.taskMapImage,game.offsetX,game.offsetY,game.canvasWidth,game.canvasHeight, 0,0,game.canvasWidth,game.canvasHeight);
		}
		//调用所有游戏单元的绘制方法
		for (var i = 0;i<game.items.length;i++){
			game.items[i].draw();
		};
		requestAnimationFrame(game.drawLoop);							
    }
}

这里我们主要设计,不细说代码。

总结

我们按照游戏的原理,来设计代码的实现,就像我们开发web系统时按照系统设计来开发系统一样。下一次我们来说游戏单元的绘制,再上一次的总结(11中)中我们提到,使用继承的方式实现绘制。

© 著作权归作者所有

青青小树

青青小树

粉丝 74
博文 110
码字总数 33227
作品 4
长春
程序员
私信 提问
加载中

评论(4)

背你进京赶考
背你进京赶考
胡子 !大胡子!! 瞧那个大胡子!! 碎碎念中....
随风流逝de昨天
写开发思路的文章真的很少啊,赞一个。
青青小树
青青小树 博主
13
青青小树
青青小树 博主
:-)
09、开源游戏-“胡子”开始游戏前03-总结

这几天我们干了很多事,我们总结一下。 首先,设计了游戏的选择界面、操作主界面,虽然很粗糙,但是只要你看了就知道他代表的意思,以后在具体实现时我们会在雕琢一下。 接着,我们做了主页i...

青青小树
2014/03/24
180
0
01、开源游戏-“胡子”项目启动

如果你看过之前的文章,你就会明白我们什么这么写,如果你没看,也没必要解释了。 开源游戏“胡子”,今天正式开始设计。 “胡子”是一款以html5开发的一款即时战略游戏,即大家说的RTS,可以...

青青小树
2014/03/15
222
0
用JSGF游戏框架开发支持HTML4的JS游戏!

JSDK是一个新发布的JS框架。在开源中国社区上的项目首页:http://www.oschina.net/p/jsdk 完整教程,请访问JSDK网站:http://jsdk2.sourceforge.net/ JSGF游戏开发课程(一):游戏开发原理与...

冯伯约
2012/04/12
1K
6
04、开源游戏-“胡子”界面设计

遵循一些RTS游戏通用界面设计,胡子界面设计如下,效果图只附上一张主界面的,其他会在游戏开发时贴上。 主界面设计 情节界面设计 大当家选择界面 单人多人加载界面

青青小树
2014/03/17
200
0
从0开始:开发自己的游戏[0]

我是Lem0,自学倡导者,执迷于“不务正业”,被批评“旁门左道”。我注册并使用简书,希望能够记录一些我记不住的事情,或者与大家一起共享知识,共同学习。 在「从0开始:开发自己的游戏」中...

Lem0
2017/03/08
0
0

没有更多内容

加载失败,请刷新页面

加载更多

ReentrantLock的可重入特性

在自旋分布式锁实现 中我们已经分析了ReentrantLock的自旋特性,现在我们来分析一下它的可重入特性。 可重入特性其实说白了就是当获得锁的线程解锁后,重新来获取锁的时候会判断自己以前是否...

算法之名
48分钟前
6
0
js如何控制table中的某一行动态置顶

两行代码搞定: $('#'+item.roadCode).fadeOut().fadeIn();//获取到需要置顶的行 $(".table").prepend($('#'+item.roadCode)); 其中,fadeOut()方法 作用 --- 从可见到隐藏 如下: prepend(......

码妞
今天
4
0
四种解决Nginx出现403 forbidden 报错的方法

我是在在本地用虚拟机中通过yum安装nginx的,安装一切正常,但是访问时报403, 于是查看nginx日志,路径为/var/log/nginx/error.log。打开日志发现报错Permission denied,详细报错如下: 1....

dragon_tech
今天
3
0
获取RestResultResponse返回的值

Springboot项目,需要调其他服务的接口,返回值类型是RestResultResponse 打断点的结果集是这个 打印出来的getData(): [{id=3336b624-8474-4dd9-bd5b-c7358687c877, paraNo=104, para=Postpo...

栾小糖
今天
4
0
【小学】 生成10以内的加减法

#!/usr/bin/env python# coding: utf-8from random import randrange# 题目的最大数值R_MAX = 10# 生成的题目的数量R_PAGE = 70# 生成减法列表def get_sub_list():...

Tensor丨思悟
今天
11
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部