上一章讲述部分代码重构,本章讲koopa和goomba的生成,和他们的部分动作

本章的提交ID:97bf843711642e39f52649399f2ca0138de0c104

 github地址:ainuo5213的超级马里奥

本节目录

python的超级马里奥 超级马里奥源码_小游戏

目录讲解:

        删除了mario.js、VelocityTrait.js

        1. entities/Goomba.js:蘑菇怪的实体,继承自Entity

        2. entities/Koopa.js:乌龟的实体,继承自Entity

        3. entities/Mario.js:原mario.js,继承自Entity

        4. WalkTrait.js:用于行走的特征,继承自Trait

        5. BoundingBox.js:用于计算每一个实体的边界,例如top、bottom、left、center等

        6. entities.js:批量加载各种实体,避免在入口文件中一一加载

        7. sprites/goomba.json:用于定义goomba的切片和动画帧

        8. sprites/koopa.json:用于定义koopa的切片和动画帧

 实现效果

python的超级马里奥 超级马里奥源码_python的超级马里奥_02

 入口文件改动

入口文件移除了createMario方法,导入了loadEntities方法,用于批量加载已生成的实体 ,并添加到level.entities

python的超级马里奥 超级马里奥源码_超级玛丽_03

 Entity.js改动

新增定义了Sides的方向,用于x方向的碰撞检测边

Entity抽象类新增offset和bounds,前者用于在碰撞检测时考虑偏移量,后者用于当前实体的边界对象取值,例如top、left、bottom、right、center等

python的超级马里奥 超级马里奥源码_python的超级马里奥_04

layer.js改动

layer对于绘制碰撞检测的layer时,使用bounds对象的值,考虑偏移量 

python的超级马里奥 超级马里奥源码_前端_05

 TileCollider.js改动

TileCollider改动由于本人将注释删掉,写法重构了一次,这里以代码进行展示,逻辑改动不多。

在计算碰撞检测的时候,替换原本x、y等,加入bounds,考虑偏移量的问题

import TileResolver from "./TileResolver.js"
import { Sides } from "./Entity.js"

export default class TileCollider {
    constructor(tileMatrix) {
        this.tiles = new TileResolver(tileMatrix);
    }

    checkX(entity) {
        let x;
        if (entity.vel.x > 0) {
            x = entity.bounds.right;
        } else if (entity.vel.x < 0) {
            x = entity.bounds.left;
        } else {
            return;
        }

        const matches = this.tiles.searchByRange(
            x, x,
            entity.bounds.top, entity.bounds.bottom);

        matches.forEach(match => {
            if (match.tile.type !== 'ground') {
                return;
            }

            if (entity.vel.x > 0) {
                if (entity.bounds.right > match.x1) {
                    entity.bounds.right = match.x1;
                    entity.vel.x = 0;

                    entity.obstruct(Sides.RIGHT);
                }
            } else if (entity.vel.x < 0) {
                if (entity.bounds.left < match.x2) {
                    entity.bounds.left = match.x2;
                    entity.vel.x = 0;

                    entity.obstruct(Sides.LEFT);
                }
            }
        });
    }

    checkY(entity) {
        let y;
        if (entity.vel.y > 0) {
            y = entity.bounds.bottom;
        } else if (entity.vel.y < 0) {
            y = entity.bounds.top;
        } else {
            return;
        }

        const matches = this.tiles.searchByRange(
            entity.bounds.left, entity.bounds.right,
            y, y);

        matches.forEach(match => {
            if (match.tile.type !== 'ground') {
                return;
            }

            if (entity.vel.y > 0) {
                if (entity.bounds.bottom > match.y1) {
                    entity.bounds.bottom = match.y1;
                    entity.vel.y = 0;

                    entity.obstruct(Sides.BOTTOM);
                }
            } else if (entity.vel.y < 0) {
                if (entity.bounds.top < match.y2) {
                    entity.bounds.top = match.y2;
                    entity.vel.y = 0;

                    entity.obstruct(Sides.TOP);
                }
            }
        });
    }
}

sprites/mario.json改动

加入了马里奥的动画,用于创建马里奥的时候,去除在代码里面定义的动画和distance等(hard code) 

python的超级马里奥 超级马里奥源码_小游戏_06

新增entities/Goomba.js

用于定义Goomba对象 (蘑菇小怪)

import { Entity } from "../Entity.js"
import { WalkTrait } from "../traits/WalkTrait.js"
import { loadSpriteSheet } from "../loader.js";


export function loadGoomba() {
    return loadSpriteSheet("goomba")
        .then(createGoombaFactory)
}


function createGoombaFactory(goombaSprite) {
    // 从配置里获取帧
    const walkAnime = goombaSprite.animations.get("walk");

    function drawGoomba(context) {
        goombaSprite.draw(walkAnime(this.lifetime), context, 0, 0);
    }

    return function createGoomba() {
        const goomba = new Entity();

        goomba.size.set(16, 16);
        goomba.vel.x = -30;

        // 添加行走的特征
        goomba.addTrait(new WalkTrait());

        goomba.draw = drawGoomba;


        return goomba;
    }
}

新增entities/Koopa.js

用于定义Koopa对象(小乌龟怪)

import { Entity, Sides } from "../Entity.js"
import { WalkTrait } from "../traits/WalkTrait.js"
import { loadSpriteSheet } from "../loader.js";

export const LOW_SPEED = 1 / 1500;
export const HIGH_SPEED = 1 / 6000;


export function loadKoopa() {
    return loadSpriteSheet("koopa")
        .then(createKoopaFactory)
}


function createKoopaFactory(koopaSprite) {
    // 从配置里获取动画
    const walkAnime = koopaSprite.animations.get("walk");

    function drawKoopa(context) {
        koopaSprite.draw(walkAnime(this.lifetime), context, 0, 0, this.vel.x < 0);
    }

    return function createKoopa() {
        const koopa = new Entity();

        koopa.size.set(16, 16);
        koopa.vel.x = -30;
        // 设置y方向向下偏移8px
        koopa.offset.y = 8;

        // 添加walk特征
        koopa.addTrait(new WalkTrait());

        koopa.draw = drawKoopa;


        return koopa;
    }
}

新增entities/Mario.js

原mario.js,迁移到了该文件,并对其中做出了部分重构(闭包)

import { Entity } from "../Entity.js"
import { JumpTrait } from '../traits/JumpTrait.js'
import { GoTrait } from '../traits/GoTrait.js'
import { loadSpriteSheet } from "../loader.js";
import { createAnim } from "../animation.js";

export const LOW_SPEED = 1 / 1500;
export const HIGH_SPEED = 1 / 7000;


export function loadMario() {
    return loadSpriteSheet("mario")
        .then(createMarioFactory)
}


function createMarioFactory(marioSprite) {
    const getFrame = marioSprite.animations.get("run");

    // 马里奥运动时取得其动画帧名字进行对应帧动画对应的切片,进行渲染
    function routeFrame(mario) {
        if (mario.jump.isFalling) {
            return "mario-jump";
        }
        // break的帧需要马里奥当前速度方向和当前运动方向相反即可
        if (mario.vel.x * mario.go.dir < 0) {
            return "mario-break"
        }
        if (mario.go.distance > 0) {
            return getFrame(mario.go.distance);
        }
        return "mario";
    }

    function drawMario(context) {
        marioSprite.draw(routeFrame(this), context, 0, 0, this.go.heading < 0);
    }

    function setTurboState(turboOn) {
        this.go.dragFactor = turboOn ? HIGH_SPEED : LOW_SPEED;
    }


    return function createMario() {
        const mario = new Entity();

        mario.size.set(14, 16);

        mario.draw = drawMario;

        // 添加马里奥的特征:1、跳跃;2、方向(第五节删除了速度,因为碰撞检测的时候才会对速度特征进行修改,而非update的时候进行修改)
        mario.addTrait(new GoTrait());
        mario.addTrait(new JumpTrait());
        mario.turbo = setTurboState;
        mario.turbo(false);


        return mario;
    }
}

新增trait/WalkTrait.js

用于定义行走的特征

import { Sides, Trait } from '../Entity.js'

export class WalkTrait extends Trait {
    constructor() {
        super('walk');
        this.speed = -30;
    }

    update = (entity) => {
        // 当前实体的x方向速度是当前的速度
        entity.vel.x = this.speed;
    }

    obstruct = (entity, side) => {
        // 如果在x方向碰撞了,反向
        if (side === Sides.LEFT || side === Sides.RIGHT) {
            this.speed = -this.speed;
        }
    }
}

新增BoundingBox.js

用于定义某个实体当前所在的位置在其x、y方向上,考虑偏移量的情况下,计算其left、top、bottom、right、center等

import { Vector } from "./math.js";

export default class BoundingBox {
    constructor(pos, size, offset) {
        this.pos = pos;
        this.size = size;
        this.offset = offset;
    }

    overlaps(box) {
        return this.bottom > box.top
            && this.top < box.bottom
            && this.left < box.right
            && this.right > box.left;
    }

    getCenter() {
        return new Vector(this.meridian, this.equator);
    }

    setCenter(vec2) {
        this.meridian = vec2.x;
        this.equator = vec2.y;
    }

    get meridian() {
        return this.pos.x + this.offset.x + this.size.x / 2;
    }

    set meridian(c) {
        this.pos.x = c - (this.size.x / 2 + this.offset.x);
    }

    get equator() {
        return this.pos.y + this.offset.y + this.size.y / 2;
    }

    set equator(c) {
        this.pos.y = c - (this.size.y / 2 + this.offset.y);
    }

    get bottom() {
        return this.pos.y + this.size.y + this.offset.y;
    }

    set bottom(y) {
        this.pos.y = y - (this.size.y + this.offset.y);
    }

    get top() {
        return this.pos.y + this.offset.y;
    }

    set top(y) {
        this.pos.y = y - this.offset.y;
    }

    get left() {
        return this.pos.x + this.offset.x;
    }

    set left(x) {
        this.pos.x = x - this.offset.x;
    }

    get right() {
        return this.pos.x + this.size.x + this.offset.x;
    }

    set right(x) {
        this.pos.x = x - (this.size.x + this.offset.x);
    }
}

新增entities.js

用于简化main函数中create实体的Promise.all方法,批量创建实体并导出到main函数进行使用。

import { loadMario } from "./entities/Mario.js";
import { loadKoopa } from "./entities/Koopa.js";
import { loadGoomba } from "./entities/Goomba.js";

export function LoadEntities() {
    const entityFactory = {};

    function addAs(name) {
        return factory => entityFactory[name] = factory
    }

    return Promise.all(
        [loadMario().then(addAs('mario')),
        loadGoomba().then(addAs('goomba')),
        loadKoopa().then(addAs('koopa'))])
        .then(() => entityFactory);
}

新增sprites/goomba.json

用于定义蘑菇小怪的切片和动画配置

{
    "tileImgUrl": "/src/assets/characters.png",
    "tileWidth": 16,
    "tileHeight": 16,
    "frames": [
        {
            "name": "walk-1",
            "rect": [296, 187, 16, 16]
        },
        {
            "name": "walk-2",
            "rect": [315, 187, 16, 16]
        },
        {
            "name": "flat",
            "rect": [277, 53, 16, 16]
        }
    ],
    "animations": [
        {
            "name": "walk",
            "frameLength": 0.15,
            "frames": [
                "walk-1",
                "walk-2"
            ]
        }
    ]
}

新增sprites/koopa.json

用于定义乌龟小怪的切片和动画配置

{
    "tileImgUrl": "/src/assets/characters.png",
    "tileWidth": 16,
    "tileHeight": 24,
    "frames": [
        {
            "name": "walk-1",
            "rect": [296, 206, 16, 24]
        },
        {
            "name": "walk-2",
            "rect": [315, 206, 16, 24]
        }
    ],
    "animations": [
        {
            "name": "walk",
            "frameLength": 0.15,
            "frames": [
                "walk-1",
                "walk-2"
            ]
        }
    ]
}

本节不难,希望大家能懂起