之前写了一个 python 版本的回合制战棋游戏,最近学习了一下 javascript ES6 语法,因为 ES6 新增加了 class 语法糖,可以比较方便的将 python 的 class 实现移植过来。使用 html5 canvs 绘制游戏图像,利用 javascript ES6 重新实现了这个游戏。
python 战棋游戏代码实现
游戏实现了类似英雄无敌3 中战斗场景的回合制玩法:
游戏截图如下:
图1图1中,是游戏的开始页面,‘start game’ 是一个按钮,点击开始运行游戏。
图2图2中,目前轮到行动的生物是我方的左下角背景为浅蓝色的步兵,可以看到背景为深蓝色的方格为步兵可以行走的范围。背景为绿色的方格为目前选定要行走到得方格。鼠标指向敌方生物,如果敌方生物背景方格颜色变成黄色,表示可以攻击,可以看到允许攻击斜对角的敌人。图中还有石块,表示不能移动到的地图方格。
游戏实现代码的 github 链接 战棋游戏
这边是 csdn 的下载链接 战棋游戏
为了更好的管理,将游戏的资源,配置文件和代码分成了多个目录进行保存。
images
目录:存放游戏中用到的生物和地图格子图片。data
目录:存放游戏会用到的配置文件,保存关卡地图配置的 entity_data.js
,保存生物属性配置的 entity.js
。js
目录:存放游戏实现的 js 文件。index.html
:在浏览器中打开这个 html 文件来运行游戏。1.支持的浏览器
2.运行
直接用浏览器执行代码根目录下的 index.html 文件。
canvas 使用 JavaScript 在网页上绘制图像。canvas 画布是一个矩形区域,可以在这个矩形上以像素为单位绘制各种图形(图片,文字,矩形等)。canvas 提供了绘制线段、矩形、圆形、文字和图像的方法。
绘制图形前,需要先创建一个 canvas 对象,设置 canvas 矩形区域的宽度和高度,调用 getContext 函数返回一个对象 ctx,然后就可以用 ctx 对象来绘制图形。
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = MAP_WIDTH;
canvas.height = MAP_HEIGHT;
document.body.appendChild(canvas);
在一个游戏中需要绘制图片,文字和图形。图形可以有线段,矩形,复杂点的有多边形,椭圆等。
HTML5 canvas 提供了函数来绘制线段、矩形、字符和图像,可以通过下面链接看下函数使用介绍:
HTML 5 Canvas 参考手册
但是 canvas 提供的绘制功能不是简单的一个函数就能实现,需要设置各种参数,下边是游戏中提供的封装函数,代码在 js/tool.js
文件中:
function drawLine(ctx, color, start_x, start_y, end_x, end_y) {
ctx.strokeStyle = color;
ctx.beginPath();
ctx.moveTo(start_x, start_y);
ctx.lineTo(end_x, end_y);
ctx.stroke();
}
function drawImage(ctx, img, source_rect, dest_rect) {
ctx.drawImage(img, source_rect[0], source_rect[1], source_rect[2], source_rect[3],
dest_rect[0], dest_rect[1], dest_rect[2], dest_rect[3]);
}
function drawRect(ctx, color, x, y, width, height) {
ctx.fillStyle = color;
ctx.fillRect(x, y, width, height);
}
function drawText(ctx, color, str, font, x, y) {
ctx.font = font;
ctx.fillStyle = color;
ctx.fillText(str, x, y);
}
在游戏中每个生物都有一个图片,需要先将图片加载完成后,才能在 canvas 中绘制,不然浏览器执行时会报错。一个图片对象可以在 canvas 上绘制多个相同类型的生物,所以我们可以用一个 Map 对象来预先加载所有的图片对象,然后在创建具体的类时(比如生物类),获取这个图片对象。
所有图片的名称和资源位置定义在 js\contants.py
文件中,IMAGE_SRC_MAP Map 对象保存了图片名称和资源位置的对应关系。
// IMAGE NAME
const GRID_IMAGE = 'tile.png';
const DEVIL = 'devil';
const FOOTMAN = 'footman';
const MAGICIAN = 'magician';
const EVILWIZARD = 'evilwizard';
const FIREBALL = 'fireball';
var IMAGE_SRC_MAP = new Map([
[GRID_IMAGE, 'images/tile.png'],
[DEVIL, 'images/devil.png'],
[FOOTMAN, 'images/footman.png'],
[MAGICIAN, 'images/magician.png'],
[EVILWIZARD, 'images/evilwizard.png'],
[FIREBALL, 'images/fireball.png']
]);
加载图片的代码在 js/tool.js
文件中:
function loadAllGraphics(img_src_map) {
for(let key of img_src_map.keys()) {
let tmp = {'img':new Image(), 'ready':false};
IMAGE_MAP.set(key, tmp);
tmp.img.onload = function() {
let tmp = IMAGE_MAP.get(key);
tmp.ready = true;
};
tmp.img.src = img_src_map.get(key);
}
}
function getMapGridImage() {
let grid_rect = new Map([
[MAP_STONE.toString(), [0, 21, 20, 20]],
[MAP_GRASS.toString(), [0, 0, 20, 20]]
]);
let grid_image_map = new Map();
for(let key of grid_rect.keys()) {
let img = new ImageWrapper(GRID_IMAGE, grid_rect.get(key));
grid_image_map.set(key, img);
}
return grid_image_map;
}
function getLevelData(level_num) {
let level = 'level_' + level_num;
return LEVEL_MAP.get(level);
}
var IMAGE_MAP = new Map();
loadAllGraphics(IMAGE_SRC_MAP);
var GRID_IMAGE_MAP = getMapGridImage();
在 js\contants.py
文件中提供了一个图片的封装类,
class ImageWrapper{
constructor(name, rect) {
this.img = IMAGE_MAP.get(name);
this.rect = rect;
}
draw(ctx, dest_rect) {
if(this.img.ready) {
drawImage(ctx, this.img.img, this.rect, dest_rect);
}
}
}
看下图片封装类的实际应用,在 js\entity.py
文件中实现了远程生物的火球类,在 FireBall 类的构造函数中,调用 loadImage 函数创建了一个 ImageWrapper 类对象,这样在 draw 函数中绘制火球图片时,可以不用考虑图片是否加载完成的问题。
class FireBall{
constructor(x, y, enemy, hurt) {
this.loadImage();
this.pos = {'x':x, 'y':y};
...
}
loadImage() {
let rect = [0, 0, 14, 14];
this.img = new ImageWrapper(FIREBALL, rect);
}
getRect() {
return [this.pos.x - 7, this.pos.y - 7, 14, 14];
}
draw(ctx) {
this.img.draw(ctx, this.getRect());
}
}
动画显示
生物在空闲,行走和攻击状态时,在游戏中一般会有一个动画效果的显示,本游戏中只实现行走状态的动画效果,方法很简单。利用生物的两个相似的图形,按照一定的时间间隔来循环显示这两个图形,就可以展示出生物行走的动画效果。
在 js\entity.py
文件中生物 Entity 类中,下面几个函数实现了行走动画效果,省略了不相关的代码:
images/footman.png
。可以看到这个图片上有八个小的生物图形,我们目前只需要其中右上方的两个小图形。rect_list 数组保存了右上方两个小图形在图片中的位置和大小,用来创建两个 ImageWrapper 图片对象。class Entity{
constructor(group, name, map_x, map_y, data) {
...
this.imgs = [];
this.img_index = 0;
this.loadImages(name);
this.img = this.imgs[this.img_index];
...
}
loadImages(name) {
let rect_list = [[64, 0, 32, 32], [96, 0, 32, 32]];
for(let i in rect_list) {
this.imgs.push(new ImageWrapper(name, rect_list[i]));
}
}
update(current_time, ctx, level) {
this.current_time = current_time;
if(this.state == WALK) {
if((this.current_time - this.animate_timer) > 200) {
if(this.img_index == 0) {
this.img_index = 1;
}
else {
this.img_index = 0;
}
this.animate_timer = this.current_time;
}
...
}
...
}
draw(ctx) {
this.img = this.imgs[this.img_index];
this.img.draw(ctx, this.getRect());
...
}
}