上一章讲述马里奥跳跃动作和刹车的动作,本章对Json文件改动较多,我会一一讲解,还涉及到了部分代码重构
本章的提交ID:7419c4a70c772a45795d20ceb6559c1e1e8d8e3a
github地址:ainuo5213的超级马里奥
本节目录
目录讲解:
1. loaders/level.js:原loadLevelAsync方法和loadTiles方法移动到了单独的文件,用于单独生成loadLevelAsync
实现效果
layers.js文件改动
layers.js将原本用于获取TileResolver从实例中改为了创建实例,并且使用外部传入的tiles,在redraw方法中用于清空当前临时创建的bufferContext的内容,避免重复渲染
Level.js改动
Level.js去掉了tiles、tileCollider的初始化操作,暴露一个方法用于设置马里奥碰撞检测实例和tiles
loader.js改动
loader.js删掉了loadTiles、loadLevelAsync方法,将loadJson暴露了出去
,供loader/level.js使用
TilCollider碰撞检测实例改动
因之前遗漏,这里改动一下name为type
1-1关卡数据json改动
1-1.json改动都标有注释,各位可以尝试理解一下
{
"spriteSheet": "overworld",
// 模式,用于16x16单位的多个元素拼凑而成的图片生成
"patterns": {
// 云,云被划分为了6个单位,上面3个,下面三个
"cloud-single": {
"tiles": [
{
"name": "cloud-1-1",
"ranges": [[0, 0]]
},
{
"name": "cloud-1-2",
"ranges": [[1, 0]]
},
{
"name": "cloud-1-3",
"ranges": [[2, 0]]
},
{
"name": "cloud-2-1",
"ranges": [[0, 1]]
},
{
"name": "cloud-2-2",
"ranges": [[1, 1]]
},
{
"name": "cloud-2-3",
"ranges": [[2, 1]]
}
]
},
// 水管体,水管体被划分为了2个单位,左边一个,右边一个
"pipe-section-vert": {
"tiles": [
{
"name": "pipe-vert-left",
"type": "ground",
"ranges": [
[0, 0]
]
},
{
"name": "pipe-vert-right",
"type": "ground",
"ranges": [
[1, 0]
]
}
]
},
// 水管帽子被划分为了2个单位,左边一个,右边一个
"pipe-cap-vert": {
"tiles": [
{
"name": "pipe-insert-vert-left",
"type": "ground",
"ranges": [
[0, 0]
]
},
{
"name": "pipe-insert-vert-right",
"type": "ground",
"ranges": [
[1, 0]
]
}
]
},
// 高度为2的水管,被划分为了2个,上边1个单位是水管帽,下边1个单位是水管体
"pipe-2h": {
"tiles": [
{
"pattern": "pipe-cap-vert",
"ranges": [
[0, 0]
]
},
{
"pattern": "pipe-section-vert",
"ranges": [
[
0, 1,
1, 1
]
]
}
]
},
// 高度为3的水管,被划分为了2个,上边1个单位是水管帽,下边2个单位是水管体
"pipe-3h": {
"tiles": [
{
"pattern": "pipe-cap-vert",
"ranges": [
[0, 0]
]
},
{
"pattern": "pipe-section-vert",
"ranges": [
[
0, 1,
1, 2
]
]
}
]
},
// 高度为4的水管,被划分为了2个,上边1个单位是水管帽,下边3个单位是水管体
"pipe-4h": {
"tiles": [
{
"pattern": "pipe-cap-vert",
"ranges": [
[0, 0]
]
},
{
"pattern": "pipe-section-vert",
"ranges": [
[
0, 1,
1, 3
]
]
}
]
}
},
// 背景渲染部分,整合了之前的backgrounds,并加入了水管(2h、3h、4h)和云
"layers": [
{
"tiles": [
{
"name": "sky",
"ranges": [
[
0, 212,
0, 13
]
]
},
{
"name": "ground",
"type": "ground",
"ranges": [
[
0, 212,
13, 2
]
]
},
{
"name": "sky",
"ranges": [
[
75, 2,
13, 2
],
[
92, 2,
13, 2
],
[
157, 2,
13, 2
]
]
},
{
"name": "ground",
"type": "ground",
"ranges": [
[
5, 3,
9, 1
],
[
29, 5
],
[
5, 7,
9
],
[
12, 6,
11, 1
],
[
2, 1,
11, 1
],
[
10, 2,
10, 1
],
[
10, 2,
10
],
[
9, 1,
0, 7
]
]
}
]
},
{
"tiles": [
{
"name": "bricks",
"type": "ground",
"ranges": [
[
27, 5,
9
],
[
83, 3,
9
],
[
86, 6,
5
],
[
96, 3,
5
],
[
99, 9
],
[
105, 2,
9
],
[
123, 5
],
[
126, 3,
5
],
[
132, 4,
5
],
[
133, 2,
9
],
[
171, 4,
9
]
]
},
{
"name": "chance",
"type": "ground",
"ranges": [
[2, 2],
[23, 9],
[28, 9],
[30, 9],
[29, 5],
[84, 9],
[99, 5],
[114, 5],
[111, 9],
[114, 9],
[117, 9],
[133, 2, 5],
[173, 9]
]
},
{
"name": "chocolate",
"type": "ground",
"ranges": [
[141, 1, 9],
[140, 2, 10],
[139, 3, 11],
[138, 4, 12],
[144, 1, 9],
[144, 2, 10],
[144, 3, 11],
[144, 4, 12],
[155, 2, 9],
[154, 3, 10],
[153, 4, 11],
[152, 5, 12],
[159, 1, 9],
[159, 2, 10],
[159, 3, 11],
[159, 4, 12],
[191, 2, 5],
[190, 3, 6],
[189, 4, 7],
[188, 5, 8],
[187, 6, 9],
[186, 7, 10],
[185, 8, 11],
[184, 9, 12]
]
},
{
"pattern": "pipe-2h",
"ranges": [
[35, 11],
[167, 11],
[182, 11]
]
},
{
"pattern": "pipe-3h",
"ranges": [
[45, 10]
]
},
{
"pattern": "pipe-4h",
"ranges": [
[53, 9],
[64, 9]
]
},
{
"pattern": "cloud-single",
"ranges": [
[2, 2],
[25, 2],
[35, 3],
[44, 2],
[64, 3],
[74, 2],
[80, 3],
[90, 2],
[108, 3],
[118, 2],
[128, 3],
[138, 2]
]
}
]
}
]
}
overworld.json地图切片数据改动
地图切片数据加入了6个单位的云和4个单位的水管,用于切不同的16x16的方格进行渲染,所以说云占6个单位,长3宽2等
loader/level.js
loader/level.js是之前loadTiles和loadAsync的整合,并进行部分优化,将每个循环拆分到了外部,降低主代码区的代码长度,利于维护和扩展
import { createBackgroundLayer, createrSpriteLayer } from "../layers.js";
import { Matrix } from "../Math.js";
import { Level } from "../Level.js";
import { loadJson, loadSpriteSheet } from "../loader.js";
export function loadLevelAsync(name) {
return loadJson(`/src/levels/${name}.json`)
.then(data => Promise.all([data, loadSpriteSheet(data.spriteSheet)]))
.then(([levelJson, backgroundSprite]) => {
const level = new Level();
// 加载level中的matrix每一个格子的数据到tiles
const mergedTiles = levelJson.layers.reduce((mergedTiles, layer) => {
return mergedTiles.concat(layer.tiles);
}, []);
// 动态设置level的碰撞检测类
const collisionGrid = createCollisionGrid(mergedTiles, levelJson.patterns);
level.setCollisionGrid(collisionGrid);
levelJson.layers.forEach(layer => {
const backgroundGrid = createBackgroundGrid(layer.tiles, levelJson.patterns);
const backgroundLayer = createBackgroundLayer(level, backgroundGrid, backgroundSprite);
level.compositor.layers.push(backgroundLayer);
})
// 创建马里奥图像的回调
const marioSpriteLayer = createrSpriteLayer(level.entities);
level.compositor.layers.push(marioSpriteLayer);
return level;
})
}
// 一个range范围内的x和y对象集合
function* expandSpan(xStart, xLen, yStart, yLen) {
const xEnd = xStart + xLen;
const yEnd = yStart + yLen;
for (let x = xStart; x < xEnd; x++) {
for (let y = yStart; y < yEnd; y++) {
yield { x, y };
}
}
}
// 展开一个range,原range.forEach...
function expandRange(range) {
// 修改渲染逻辑: 当配置中的range为4位数,则其分别为x位置开始xStart、x方向渲染长度xLen、y位置开始yStart、y方向渲染长度yLen
// 当配置中的range为2位数,则其分别为x位置开始xStart、y位置开始yStart,此时yLen、xLen均为1
// 当配置中的range为3位数,则其分别为x位置开始xStart、x方向渲染长度xLen、y位置开始yStart,此时yLen为1
if (range.length === 4) {
const [xStart, xLen, yStart, yLen] = range;
return expandSpan(xStart, xLen, yStart, yLen);
} else if (range.length === 2) {
const [xStart, yStart] = range;
return expandSpan(xStart, 1, yStart, 1);
} else if (range.length === 3) {
const [xStart, xLen, yStart] = range;
return expandSpan(xStart, xLen, yStart, 1);
}
}
// 展开多个ranges(原tile.ranges.forEach...)
function* expandRanges(ranges) {
for (const range of ranges) {
for (const item of expandRange(range)) {
yield item
}
}
}
// 展开tiles为制定格式的对象的数组(如果该元素是pattern,就继续展开pattern对应的那个对象知道展开到底)
function expandTiles(tiles, patterns) {
const expandedTiles = [];
function walkTiles(tiles, offsetX, offsetY) {
for (const tile of tiles) {
for (const { x, y } of expandRanges(tile.ranges)) {
const derivedX = x + offsetX;
const derivedY = y + offsetY;
if (tile.pattern) {
const tiles = patterns[tile.pattern].tiles;
walkTiles(tiles, derivedX, derivedY);
} else {
expandedTiles.push({
tile,
x: derivedX,
y: derivedY
});
}
}
}
}
walkTiles(tiles, 0, 0);
return expandedTiles;
}
// 循环遍历展开了的tiles,生成matrix
function createCollisionGrid(tiles, patterns) {
const matrix = new Matrix();
for (const { tile, x, y } of expandTiles(tiles, patterns)) {
matrix.set(x, y, {
type: tile.type,
name: tile.name
});
}
return matrix;
}
// 循环遍历展开了的tiles,生成matrix
function createBackgroundGrid(tiles, patterns) {
const matrix = new Matrix();
for (const { tile, x, y } of expandTiles(tiles, patterns)) {
matrix.set(x, y, {
type: tile.type,
name: tile.name,
});
}
return matrix;
}
本节代码重构较多,大家多多理解重构部分