原生JS超级马里奥(第一天)

Posted ainuo5213

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原生JS超级马里奥(第一天)相关的知识,希望对你有一定的参考价值。

这段时间心血来潮,在网上找了一大堆前端关于游戏制作的博客,诸如飞机大战、魂斗罗等,但是个人思维限制,可能想的不是很多、很全面,最终放弃了个人独立开发的打算。皇天不负有心人,最终在youtube上找到了前端关于超级马里奥的视频,链接见Super Mario In Javascript

打算两周时间,每天花点时间看一到两集,并将所看视频整理出思路和代码供大家参考,当然也会讲解一些自己探寻出来的原理,并写明注释

github链接见:ainuo5213的超级马里奥,可以从该链接获取源码和素材,我争取做到每天更新直至结束,小伙伴们也可以自己再此基础上发挥或重构

第一张实现效果图如下:

目录结构

 目录文件讲解:

1-1.json:目前存储关卡的数据,比如背景渲染的一些东西

loader.js:导入配置相关比如图片资源、关卡数据资源等

main.js:入口文件

UnitStyleSheet.js:渲染背景的类文件

Canvas裁剪图片

在此之前我需要先讲解一下canvas裁剪图片的原理,canvas.drawImage,通常传递5个数据,分别是image/video/canvas、x、y、width、height,但是另外还可以传递4个参数,用于裁剪图片,详情见HTML5 canvas drawImage() 方法,举个例子:

const canvas = document.getElementById("screen");
const context = canvas.getContext("2d");

loadImageAsync("/src/assets/tiles.png")
    .then(image => 
        canvas.getContext("2d")
            .drawImage(
                image,
                0,
                0,
                16,
                16,
                0,
                0,
                16,
                16);
    );

上述方法描述为:从(0, 0)裁剪一个16x16的图像并放置到(0,0)且16x16的一个方块内,图像显示为

 原图:

 可以看到我们能够成功将第一个方块剪切出来。

导入配置相关:

// 异步导入图片
export function loadImageAsync(url) 
    return new Promise(resolve => 
        const img = new Image();
        img.onload = function () 
            resolve(img);
        
        img.src = url;
    );


// 异步导入关卡数据
export function loadLevelAsync(name) 
    return fetch(`/src/levels/$name.json`)
        .then(r => r.json());

单位图像样式对象

export default class UnitStyleSheet 

    /**
     * 创建一个图像样式定义对象
     * @param htmlImageElement image 背景图像
     * @param number width 单位图像宽度
     * @param number height 单位图像高度
     */
    constructor(image, width, height) 
        this.image = image;
        this.width = width;
        this.height = height;
        this.tiles = new Map();
    

    /**
     * 存储需要裁剪的图片
     * @param string name 画布名称
     * @param number x 需要裁剪的图片x与单位高度倍数
     * @param number y 需要裁剪的图片y与单位高度倍数
     */
    define(name, x, y) 
        const canvas = document.createElement("canvas");
        canvas.width = this.width;
        canvas.height = this.height;

        // 裁剪图片,并存储
        canvas.getContext("2d")
            .drawImage(
                this.image,
                x * this.width,
                y * this.height,
                this.width,
                this.height,
                0,
                0,
                this.width,
                this.height);
        this.tiles.set(name, canvas);
    

    /**
     * 画图像
     * @param string name 画布名称
     * @param any context 上下文对象
     * @param number x 需要画图像的x坐标
     * @param number y 需要画图像的y坐标
     */
    draw(name, context, x, y) 
        const canvas = this.tiles.get(name);
        context.drawImage(canvas, x, y, this.width, this.height); // drawImage第一个参数接收类型:图像、视频、画布
    

    /**
     * 画单元格
     * @param string name 画布名称
     * @param any context 上下文对象
     * @param number x 需要画图像的x坐标(倍数)
     * @param number y 需要画图像的y坐标(倍数)
     */
    drawTile(name, context, x, y) 
        this.draw(name, context, x * this.width, y * this.height)
    

入口函数:

import UnitStyleSheet from "./UnitStyleSheet.js";
import  loadImageAsync, loadLevelAsync  from "./loader.js";

// 用于绘制背景,双重循环的x和y依赖于关卡配置文件
function drawBackground(background, context, unitStyleSheet) 
    background.ranges.forEach(([x1, x2, y1, y2]) => 
        for (let x = x1; x < x2; x++) 
            for (let y = y1; y < y2; y++) 
                unitStyleSheet.drawTile(background.tile, context, x, y);
            
        
    )


const canvas = document.getElementById("screen");
const context = canvas.getContext("2d");

loadImageAsync("/src/assets/tiles.png")
    .then(image => 
        // 创建单位图像样式对象,设置单位长度,并定义裁剪起始位置来裁剪图像
        const unitStyleSheet = new UnitStyleSheet(image, 16, 16);
        unitStyleSheet.define("ground", 0, 0);
        unitStyleSheet.define("sky", 3, 23);
        loadLevelAsync('1-1')
            .then(level => 
                level.backgrounds.forEach(background => 
                    drawBackground(background, context, unitStyleSheet);
                )
            );
    );

当然油管UP主写的代码很好,思路很清晰,我自己第一次看到也觉得需要好好的理解一番,当然各位小伙伴可以去看看视频,雀食不错。

以上是关于原生JS超级马里奥(第一天)的主要内容,如果未能解决你的问题,请参考以下文章

历史上的今天9 月 13 日:计算机先驱诞生日;第一台装载硬盘的超级计算机;《超级马里奥兄弟》发布

JS原生第一天 (帅哥)

10年代码经验程序员UP主复刻“阴间”超级马里奥,获赞27万,马里奥:我头呢?

儿童节,和 AI 一起通关 “超级马里奥兄弟”

儿童节,和 AI 一起通关 “超级马里奥兄弟”

Vue-第一天vue.js与MVVM设计模式