炸彈人遊戲開發系列(8):放炸彈

前言css

上文中咱們加入了1個敵人,使用A*算法尋路。本文會給咱們的炸彈人增長放炸彈的能力。html

說明

名詞解釋

  • xx類族
    是指以xx爲基類的繼承樹上的全部類。

本文目的

實現「放炸彈」功能算法

增長1個敵人,即一共有2個敵人追蹤炸彈人canvas

本文主要內容

 

回顧上文更新後的領域模型

查看大圖設計模式

對領域模型進行思考

Layer類族的render方法更名爲run

Layer的render方法負責統一調用Layer的方法,在概念上屬於Actor,所以將其更名爲run。數組

開發策略app

首先實現「放炸彈」功能。把這個功能分解成不少個子功能,一個一個地實現子功能。ide

而後再加入1個敵人。實際上就是在Game中往EnemyLayer集合中再加入一個EnemySprite實例,SpriteData增長第2個敵人的數據,SpriteFactory增長工廠方法createEnemy2。函數

放炸彈流程

 

功能分解

顯示炸彈和火焰oop

顯示炸彈

首先來實現「地圖上顯示炸彈」的功能,目前最多顯示1個炸彈,玩家、敵人不能穿過炸彈。若是玩家處於炸彈方格中,則敵人會原地等待,玩家離開後,敵人繼續追蹤。

增長圖片

增長圖片bomb.png:

增長BomberSprite

增長炸彈精靈類BomberSprite:

(function () {
    var BombSprite = YYC.Class(Sprite, {
        Init: function (data, bitmap) {
            this.base(null, bitmap);
        },
        Public: {
            draw: function (context) {
                context.drawImage(this.bitmap.img, this.x, this.y, this.bitmap.width, this.bitmap.height);
            },
            clear: function (context) {
                    context.clearRect(0, 0, bomberConfig.canvas.WIDTH, bomberConfig.canvas.HEIGHT);
            }
        }
    });

    window.BombSprite = BombSprite;
}());

增長BombLayer

在畫布上增長炸彈層。同時增長對應的BombLayer類,它的集合元素爲BombSprite類的實例。

將玩家、敵人畫布Canvas的zIndex設爲3,炸彈畫布的zIndex設爲1,使得,炸彈畫布位於地圖畫布(zIndex爲0)之上,玩家和敵人畫布之下。

BomberLayer

(function () {
    var BombLayer = YYC.Class(Layer, {
        Private: {
            ___hasBomb: function () {
                return this.getChilds().length > 0;
            },
            ___render: function () {
                if (this.___hasBomb()) {
                    this.clear();
                    this.draw();
                }
            }
        },
        Public: {
            setCanvas: function () {
                this.P__canvas = document.getElementById("bombLayerCanvas");
                var css = {
                    "position": "absolute",
                    "top": bomberConfig.canvas.TOP,
                    "left": bomberConfig.canvas.LEFT,
                    "z-index": 1
                };

                $("#bombLayerCanvas").css(css);
            },
            draw: function () {
                this.P__iterator("draw", this.P__context);
            },
            clear: function () {
                this.P__iterator("clear", this.P__context);
            },
            run: function () {
                this.___render();
            }
        }
    });

    window.BombLayer = BombLayer;
}());

增長工廠方法

SpriteFactory增長建立炸彈精靈類實例的工廠方法。

LyaerFactory增長建立炸彈層實例的工廠方法。

SpriteFactory

       createBomb: function (playerSprite) {
            return new BombSprite(playerSprite, bitmapFactory.createBitmap({ img: window.imgLoader.get("bomb"), width: bomberConfig.WIDTH, height: bomberConfig.HEIGHT }));
        },

LayerFactory

    createBomb: function () {
            return new BombLayer();
        },

修改PlayerSprite

PlayerSprite增長createBomb方法:

           bombNum: 0,
...
            createBomb: function () {
                if (this.moving || this.bombNum === 1) {
                    return null;
                }

                var bomb = spriteFactory.createBomb();

                bomb.x = this.x;
                bomb.y = this.y;

                this.bombNum += 1;

                return bomb;
            }

修改PlayerLayer

PlayerLayer增長getBomb和createAndAddBomb方法:

            bombLayer: null,
...
            getBomb: function (bombLayer) {
                this.bombLayer = bombLayer;
            },
            createAndAddBomb: function () {
                var bomb = this.getChildAt(0).createBomb();
                if (!bomb) {
                    return false;
                }
                this.bombLayer.appendChild(bomb);
            }

監聽空格鍵

空格鍵用於炸彈人放炸彈。

KeyCodeMap增長空格鍵枚舉值:

    var keyCodeMap = {
        Left: 65, // A鍵
        Right: 68, // D鍵
        Down: 83, // S鍵
        Up: 87, // W鍵
        Space: 32   //空格鍵
    };

    keyState[keyCodeMap.A] = false;
    keyState[keyCodeMap.D] = false;
    keyState[keyCodeMap.W] = false;
    keyState[keyCodeMap.S] = false;
    keyState[keyCodeMap.Space] = false;

而後在PlayerLayer中對KeyState的空格鍵進行斷定:

            run: function () {
                if (keyState[keyCodeMap.Space]) {
                    this.createAndAddBomb();
                    keyState[keyCodeMap.Space] = false;
                }
                this.base();
            }

領域模型

  

顯示火焰

火力範圍設爲1格,分爲上下左右四個方向。地圖的牆對火焰有阻斷做用。

增長圖片

爆炸中心爲圖片boom.png:

火焰爲圖片explode.png:

增長FireSprite

增長火焰精靈類。

增長FireLayer

在畫布上增長火焰畫布,同時對應的FireLayer類。

該畫布位於地圖和炸彈畫布之上,玩家和敵人畫布之下。

增長工廠方法

SpriteFactory增長建立爆炸中心火焰精靈類實例和建立火焰精靈類實例的工廠方法。

LayerFactory增長建立火焰層實例的工廠方法。

領域模型

 

相關代碼

Sprite

(function () {
    var Sprite = YYC.AClass({
        Init: function (data, bitmap) {
            this.bitmap = bitmap;

            if (data) {
                this.x = data.x;
                this.y = data.y;

                this.defaultAnimId = data.defaultAnimId;
                this.anims = data.anims;
            }
        },
        Private: {
            //更新幀動畫
            _updateFrame: function (deltaTime) {
                if (this.currentAnim) {
                    this.currentAnim.update(deltaTime);
                }
            }
        },
        Public: {
            bitmap: null,

            //精靈的座標
            x: 0,
            y: 0,

            //精靈包含的全部 Animation 集合. Object類型, 數據存放方式爲" id : animation ".
            anims: null,
            //默認的Animation的Id , string類型
            defaultAnimId: null,

            //當前的Animation.
            currentAnim: null,

            //設置當前Animation, 參數爲Animation的id, String類型
            setAnim: function (animId) {
                this.currentAnim = this.anims[animId];
            },
            //重置當前幀
            resetCurrentFrame: function (index) {
                this.currentAnim && this.currentAnim.setCurrentFrame(index);
            },
            //取得精靈的碰撞區域,
            getCollideRect: function () {
                return {
                    x1: this.x,
                    y1: this.y,
                    x2: this.x + this.bitmap.width,
                    y2: this.y + this.bitmap.height
                }
            },
            Virtual: {
                //初始化方法
                init: function () {
                    //設置當前Animation
                    this.setAnim(this.defaultAnimId);
                },
                // 更新精靈當前狀態.
                update: function (deltaTime) {
                    this._updateFrame(deltaTime);
                },
                //得到座標對應的方格座標
                getCellPosition: function (x, y) {
                    return {
                        x: x / bomberConfig.WIDTH,
                        y: y / bomberConfig.HEIGHT
                    }
                },
                draw: function (context) {
                    context.drawImage(this.bitmap.img, this.x, this.y, this.bitmap.width, this.bitmap.height);
                },
                clear: function (context) {
                    //直接清空畫布區域
                    context.clearRect(0, 0, bomberConfig.canvas.WIDTH, bomberConfig.canvas.HEIGHT);
                }
            }
        },
        Abstract: {
        }
    });

    window.Sprite = Sprite;
}());
View Code

FireSprite

(function () {
    var FireSprite = YYC.Class(Sprite, {
        Init: function (data, bitmap) {
            this.base(null, bitmap);
        }
    });

    window.FireSprite = FireSprite;
}());

BombSprite

(function () {
    var BombSprite = YYC.Class(Sprite, {
        Init: function (playerSprite, bitmap) {
            this.playerSprite = playerSprite;

            this.base(null, bitmap);
        },
        Protected: {
        },
        Private: {
            __createFire: function () {
                var fires = [],

                    up = null,
                    down = null,
                    left = null,
                    right = null;

                this.__createCenter(fires);
                this.__createUp(fires);
                this.__createDown(fires);
                this.__createLeft(fires);
                this.__createRight(fires);

                return fires;
            },
            __createCenter: function (fires) {
                var center = spriteFactory.createExplode();

                center.x = this.x;
                center.y = this.y;
                fires.push(center);
            },
            __createUp: function (fires) {
                this.__createOneDir(fires, this.x, this.y - bomberConfig.HEIGHT);
            },
            __createDown: function (fires) {
                this.__createOneDir(fires, this.x, this.y + bomberConfig.HEIGHT);
            },
            __createLeft: function (fires) {
                this.__createOneDir(fires, this.x - bomberConfig.WIDTH, this.y);
            },
            __createRight: function (fires) {
                this.__createOneDir(fires, this.x + bomberConfig.WIDTH, this.y);
            },
            __createOneDir: function (fires, x, y) {
                var fire = null;
                var position = this.getCellPosition(x, y);

                if (this.__isNotBorder(position) && this.__isGround(position)) {
                    fire = spriteFactory.createFire();
                    fire.x = x;
                    fire.y = y;
                    fires.push(fire);
                }
            },
            __isNotBorder: function (position) {
                if (position.x < 0 || position.y < 0) {
                    return false;
                }

                if (position.x >= window.mapData[0].length || position.y >= window.mapData.length) {
                    return false;
                }

                return true;
            },
            __isGround: function (position) {
                return window.mapData[position.y][position.x] === window.bomberConfig.map.type.GROUND;
            },
            __changeTerrainData: function () {
                var pass = bomberConfig.map.terrain.pass,
                position = this.getCellPosition(this.x, this.y);

                window.terrainData[position.y][position.x] = pass;
            }
        },
        Public: {
            playerSprite: null,

            explode: function () {
                this.playerSprite.bombNum -= 1;
                this.__changeTerrainData();
                return this.__createFire();
            }
        }
    });

    window.BombSprite = BombSprite;
}());
View Code

PlayerSprite

(function () {
    var PlayerSprite = YYC.Class(MoveSprite, {
        Init: function (data, bitmap) {
            this.base(data, bitmap);

            this.P__context = new Context(this);
        },
        Private: {
            __allKeyUp: function () {
                return window.keyState[keyCodeMap.A] === false && window.keyState[keyCodeMap.D] === false
                     && window.keyState[keyCodeMap.W] === false && window.keyState[keyCodeMap.S] === false;
            },
            __judgeAndSetDir: function () {
                if (window.keyState[keyCodeMap.A] === true) {
                    this.P__context.walkLeft();
                }
                else if (window.keyState[keyCodeMap.D] === true) {
                    this.P__context.walkRight();
                }
                else if (window.keyState[keyCodeMap.W] === true) {
                    this.P__context.walkUp();
                }
                else if (window.keyState[keyCodeMap.S] === true) {
                    this.P__context.walkDown();
                }
            },
            __changeTerrainData: function () {
                var stop = bomberConfig.map.terrain.stop,
                position = this.getCurrentCellPosition();

                window.terrainData[position.y][position.x] = stop;
            }
        },
        Public: {
            //已放置的炸彈數
            bombNum: 0,

            move: function () {
                this.P__context.move();
            },
            setDir: function () {
                if (this.moving) {
                    return;
                }

                if (this.__allKeyUp()) {
                    this.P__context.stand();
                }
                else {
                    this.__judgeAndSetDir();
                }
            },
            createBomb: function () {
                if (this.moving || this.bombNum === 1) {
                    return null;
                }

                var bomb = spriteFactory.createBomb(this);

                bomb.x = this.x;
                bomb.y = this.y;

                this.bombNum += 1;

                this.__changeTerrainData();

                return bomb;
            }
        }
    });

    window.PlayerSprite = PlayerSprite;
}());
View Code

Layer

//層類(抽象類)
//職責:
////負責層內組件的統一draw
(function () {
    var Layer = YYC.AClass(Collection, {
        Init: function () {
        },
        Private: {
            __state: bomberConfig.layer.state.CHANGE,   //默認爲change

            __getContext: function () {
                this.P__context = this.P__canvas.getContext("2d");
            }
        },
        Protected: {
            //*共用的變量(可讀、寫)

            P__canvas: null,
            P__context: null,

            //*共用的方法(可讀)

            P__isChange: function () {
                return this.__state === bomberConfig.layer.state.CHANGE;
            },
            P__isNormal: function () {
                return this.__state === bomberConfig.layer.state.NORMAL;
            },
            P__setStateNormal: function () {
                this.__state = bomberConfig.layer.state.NORMAL;
            },
            P__setStateChange: function () {
                this.__state = bomberConfig.layer.state.CHANGE;
            },
            P__iterator: function (handler) {
                var args = Array.prototype.slice.call(arguments, 1),
                    nextElement = null;

                while (this.hasNext()) {
                    nextElement = this.next();
                    nextElement[handler].apply(nextElement, args);  //要指向nextElement
                }
                this.resetCursor();
            },
            P__render: function () {
                if (this.P__isChange()) {
                    this.clear();
                    this.draw();
                    this.P__setStateNormal();
                }
            }
        },
        Public: {
            addElements: function (elements) {
                this.appendChilds(elements);
            },
            Virtual: {
                init: function () {
                    this.__getContext();
                },
                //更改狀態
                change: function () {
                    this.__state = bomberConfig.layer.state.CHANGE;
                }
            }
        },
        Abstract: {
            setCanvas: function () {
            },
            clear: function () {
            },
            //統一繪製
            draw: function () { },
            //遊戲主線程調用的函數
            run: function () { }
        }
    });

    window.Layer = Layer;
}());
View Code

FireLayer

(function () {
    var FireLayer = YYC.Class(Layer, {
        Private: {
            ___hasFire: function(){
                return this.getChilds().length > 0;
            }
        },
        Public: {
            setCanvas: function () {
                this.P__canvas = document.getElementById("fireLayerCanvas");
                var css = {
                    "position": "absolute",
                    "top": bomberConfig.canvas.TOP,
                    "left": bomberConfig.canvas.LEFT,

                    "z-index": 2
                };

                $("#fireLayerCanvas").css(css);
            },
            draw: function () {
                this.P__iterator("draw", this.P__context);
            },
            clear: function () {
                this.P__iterator("clear", this.P__context);
            },
            change: function () {
                if (this.___hasFire()) {
                    this.base();
                }
            },
            run: function () {
                this.P__render();
            }
        }
    });

    window.FireLayer = FireLayer;
}());

BombLayer

(function () {
    var BombLayer = YYC.Class(Layer, {
        Private: {
            ___hasBomb: function(){
                return this.getChilds().length > 0;
            },
            ___removeBomb: function (bomb) {
                //*注意順序!

                this.clear();
                this.remove(bomb);
            },
            ___removeAllFire: function () {
                //*注意順序!

                this.fireLayer.clear();
                this.fireLayer.removeAll();
            }
        },
        Public: {
            fireLayer: null,

            setCanvas: function () {
                this.P__canvas = document.getElementById("bombLayerCanvas");
                var css = {
                    "position": "absolute",
                    "top": bomberConfig.canvas.TOP,
                    "left": bomberConfig.canvas.LEFT,
                    "z-index": 1
                };

                $("#bombLayerCanvas").css(css);
            },
            draw: function () {
                this.P__iterator("draw", this.P__context);
            },
            clear: function () {
                this.P__iterator("clear", this.P__context);
            },
            getFire: function (fireLayer) {
                this.fireLayer = fireLayer;
            },
            explode: function (bomb) {
                var self = this;

                this.fireLayer.addElements(bomb.explode());
                this.___removeBomb(bomb);
                //定時清空fireLayer(火焰消失)
                setTimeout(function () {
                    self.___removeAllFire();
                }, 300);
            },
            change: function(){
                if (this.___hasBomb()) {
                    this.base();
                }
            },
            run: function () {
                this.P__render();
            }
        }
    });

    window.BombLayer = BombLayer;
}());

SpriteFactory

        createFire: function () {
            return new FireSprite(null, bitmapFactory.createBitmap({ img: window.imgLoader.get("fire"), width: bomberConfig.WIDTH, height: bomberConfig.HEIGHT }));
        },
        createExplode: function () {
            return new FireSprite(null, bitmapFactory.createBitmap({ img: window.imgLoader.get("explode"), width: bomberConfig.WIDTH, height: bomberConfig.HEIGHT }));
        }

LayerFactory  

        createFire: function () {
            return new FireLayer();
        }

「顯示炸彈和火焰」演示

演示地址

使用觀察者模式

觀察者模式介紹

詳見Javascript設計模式之我見:觀察者模式

應用場景

牆被炸掉後,會變成空地。

實現思路

Maplayer的changeSpriteImg負責更改地圖圖片,BombSprite的explode負責處理爆炸邏輯。

須要在explode中調用Maplayer的changeSpriteImg。

所以,決定在Game中訂閱Maplayer的changeSpriteImg方法,而後在BombSprite的explode方法中發佈。   

爲何此處用觀察者模式

由於MapLayer的Layer類族在BombSprite的Sprite類族的上層,我不但願下層BombSprite與上層MapLayer耦合。

所以,採用觀察者模式來解除二者的耦合。

領域模型

使用觀察模式前

 

使用觀察模式後

相關代碼

Subject

(function () {
    if (!Array.prototype.forEach) {
        Array.prototype.forEach = function (fn, thisObj) {
            var scope = thisObj || window;
            for (var i = 0, j = this.length; i < j; ++i) {
                fn.call(scope, this[i], i, this);
            }
        };
    }

    if (!Array.prototype.filter) {
        Array.prototype.filter = function (fn, thisObj) {
            var scope = thisObj || window;
            var a = [];
            for (var i = 0, j = this.length; i < j; ++i) {
                if (!fn.call(scope, this[i], i, this)) {
                    continue;
                }
                a.push(this[i]);
            }
            return a;
        };
    }

    Subject = function () {
        this._events = [];
    }

    Subject.prototype = (function () {

        return {
            //訂閱方法
            subscribe: function (context, fn) {
                if (arguments.length == 2) {
                    this._events.push({ context: arguments[0], fn: arguments[1] });
                }
                else {
                    this._events.push(arguments[0]);
                }
            },
            //發佈指定方法
            publish: function (context, fn, args) {
                var args = Array.prototype.slice.call(arguments, 2);    //得到函數參數
                var _context = null;
                var _fn = null;

                this._events.filter(function (el) {
                    if (el.context) {
                        _context = el.context;
                        _fn = el.fn;
                    }
                    else {
                        _context = context;
                        _fn = el;
                    }

                    if (_fn === fn) {
                        return _fn;
                    }
                }).forEach(function (el) {  //指定方法可能有多個
                        el.apply(_context, args);       //執行每一個指定的方法
                    });
            },
            unSubscribe: function (fn) {
                var _fn = null;
                this._events = this._events.filter(function (el) {
                    if (el.fn) {
                        _fn = el.fn;
                    }
                    else {
                        _fn = el;
                    }

                    if (_fn !== fn) {
                        return el;
                    }
                });
            },
            //所有發佈
            publishAll: function (context, args) {
                var args = Array.prototype.slice.call(arguments, 1);    //得到函數參數
                var _context = null;
                var _fn = null;

                this._events.forEach(function (el) {
                    if (el.context) {
                        _context = el.context;
                        _fn = el.fn;
                    }
                    else {
                        _context = context;
                        _fn = el;
                    }

                    _fn.apply(_context, args);       //執行每一個指定的方法
                });
            },
            dispose: function () {
                this._events = [];
            }
        }
    })();

    YYC.Pattern.Subject = Subject;
})();
View Code

MapLayer

            //改變指定精靈類的img對象
            //參數:
            //x:x座標(方格對應值);y:y座標(方格對應值);img:要替換的img對象
            changeSpriteImg: function (x, y, img) {
                var index = y * window.bomberConfig.map.COL + x;
                this.getChildAt(index).bitmap.img = img;
            },

BombSprite

         __destroyOneDir: function (x, y) {
...
                    window.observer.publishAll(null, position.x, position.y, groundImg);
...
            },

Game

    //觀察者全局實例
    window.observer = null

    var Game = YYC.Class({
        Init: function () {
            window.observer = new YYC.Pattern.Observer();
        },
...
    init: function () {
...
        //觀察者模式 -> 訂閱                
        window.observer.subscribe(this.layerManager.getLayer("mapLayer"), this.layerManager.getLayer("mapLayer").changeSpriteImg);
    },

重構

增長TerrainDataOperate

增長TerrainData地形數據操做類TerrainDataOperate

領域模型

重構前

重構後

相關代碼

TerrainDataOperate

(function () {
    var terrainDataOperate = {
        getTerrainData: function () {
            return YYC.Tool.array.clone(window.terrainData);
        },
        setTerrainData: function (x, y, data) {
            window.terrainData[y][x] = data;
        }
    };

    window.terrainDataOperate = terrainDataOperate;
}());

增長火力範圍

將範圍從1格改成2格,方便演示遊戲。

增長遊戲全局狀態GameState

在Game的run方法中,須要判斷敵人是否抓住了玩家(是否與玩家碰撞):

    run: function () {
        if (this.layerManager.getLayer("enemyLayer").collideWidthPlayer()) {
            YYC.Tool.asyn.clearAllTimer(this.mainLoop);
            alert("Game Over!");
            return;
        }
...
    }

這裏注意到,Game須要知道EnemyLayer的collideWidthPlayer方法:

但Game類只應該知道LayerManager,而不該該知道Layer(見「炸彈人遊戲開發系列(1):準備工做」中的概念層次結構)。

所以,增長遊戲全局狀態GameState,在Game的run判斷GameState,而後把與炸彈人的碰撞檢測的任務放到EnemyLayer的run方法中:

重構後相關代碼

Config

    game: {
        state: {
            NORAML: 1,
            OVER: 2
        }
    },

Game

    //遊戲全局狀態
    window.gameState = window.bomberConfig.game.state.NORMAL;
...
    run: function () {
        if (window.gameState === window.bomberConfig.game.state.OVER) {
            this.gameOver();
            return;
        }
...
    }
    ,
    gameOver: function () {
        YYC.Tool.asyn.clearAllTimer(this.mainLoop);
        alert("Game Over!");
    }

EnemyLayer

            run: function () {
                if (this.collideWidthPlayer()) {
                    window.gameState = window.bomberConfig.game.state.OVER;
                    return;
                }
...

炸彈能夠炸死炸彈人和敵人

在炸彈爆炸時,判斷與炸彈人、敵人是否碰撞並進行相應處理。

領域模型

相關代碼

BombLayer

___collideFireWithPlayer: function (bomb) {
    if (bomb.collideFireWithCharacter(this.playerLayer.getChildAt(0))) {
        window.gameState = window.bomberConfig.game.state.OVER;
    }
},
___collideFireWithEnemy: function (bomb) {
    var i = 0,
        len = 0,
        enemySprites = this.enemyLayer.getChilds();

    for (i = 0, len = enemySprites.length ; i < len; i++) {
        if (bomb.collideFireWithCharacter(enemySprites[i])) {
            this.___removeEnemy(enemySprites[i]);
        }
    }
},
___removeEnemy: function (enemy) {
    //*注意順序!

    this.enemyLayer.clear();
    this.enemyLayer.remove(enemy);
},
___handleCollid: function (bomb) {
    //判斷與炸彈人碰撞
    this.___collideFireWithPlayer(bomb)
    //判斷與每一個敵人碰撞
    this.___collideFireWithEnemy(bomb);
}
...
enemyLayer: null,
playerLayer: null,
...
explode: function (bomb) {
    var self = this,
        result = null;

    //處理碰撞
    this.___handleCollid(bomb);
...

移動時放炸彈

由於炸彈人移動時,根據炸彈人狀態的不一樣,炸彈放置的座標策略也不一樣(即若是炸彈人往上走,則炸彈放在炸彈人所在方格的上面相鄰方格;若是往左走,則炸彈放在炸彈人所在方格的左側相鄰方格)。因此將PlayerSprite的createBomb方法委託給狀態類處理。具體來講,就是把createBomb方法移到狀態類的WalkState類和Stand類中來分別處理。

領域模型

分析

由於PlayerSprite、EnemySprite都使用了狀態類,所以二者都與BombSprite耦合。但只有PlayerSprite須要使用createBomb方法,EnemySprite並不須要使用該方法。因此此處違反了迪米特法則。

目前這種狀況在能夠接受的範圍以內。若是在後面的開發中EnemySprite與BombSprite耦合得很嚴重,再來考慮解耦。

放置多個炸彈

能夠最多放3個炸彈,炸彈爆炸時會引爆在火力範圍內的炸彈。

不能在一個方格疊加多個炸彈

在狀態類WalkState類族、StandState類族的createBomb中判斷方格是否有炸彈(判斷地形數據TerrainData來實現)。

改變地圖

炸掉牆

若是牆處於火焰範圍內,則修改MapData,將牆的圖片換成空地圖片,同時對應修改TerrainData,將牆所在的方格設成可經過。

刷新地圖

在炸掉牆後,在BombLayer中須要調用MapLayer的setStateChange方法,將MapLayer的state設爲change,從而可以在遊戲的下一個主循環中,刷新地圖,從而顯示爲空地。

領域模型

相關代碼

BombLayer

___mapChange: function (mapChange) {
    if (mapChange) {
        this.mapLayer.setStateChange();
    }
}

小結

如今咱們就完成了「放炸彈」的功能,來看下成果吧~

「放炸彈」演示

演示地址

相關代碼

FireSprite

(function () {
    var FireSprite = YYC.Class(Sprite, {
        Init: function (data, bitmap) {
            this.base(null, bitmap);
        }
    });

    window.FireSprite = FireSprite;
}());

FireLayer

(function () {
    var FireLayer = YYC.Class(Layer, {
        Private: {
            ___hasFire: function(){
                return this.getChilds().length > 0;
            }
        },
        Public: {
            setCanvas: function () {
                this.P__canvas = document.getElementById("fireLayerCanvas");
                var css = {
                    "position": "absolute",
                    "top": bomberConfig.canvas.TOP,
                    "left": bomberConfig.canvas.LEFT,
                    "z-index": 2
                };

                $("#fireLayerCanvas").css(css);
            },
            draw: function () {
                this.P__iterator("draw", this.P__context);
            },
            clear: function () {
                this.P__iterator("clear", this.P__context);
            },
            change: function () {
                if (this.___hasFire()) {
                    this.setStateChange();
                }
            },
            run: function () {
                this.P__render();
            }
        }
    });

    window.FireLayer = FireLayer;
}());
View Code

PlayerSprite

(function () {
    var PlayerSprite = YYC.Class(MoveSprite, {
        Init: function (data, bitmap) {
            this.base(data, bitmap);

            this.P__context = new Context(this);
        },
        Private: {
            __allKeyUp: function () {
                return window.keyState[keyCodeMap.A] === false && window.keyState[keyCodeMap.D] === false
                     && window.keyState[keyCodeMap.W] === false && window.keyState[keyCodeMap.S] === false;
            },
            __judgeAndSetDir: function () {
                if (window.keyState[keyCodeMap.A] === true) {
                    this.P__context.walkLeft();
                }
                else if (window.keyState[keyCodeMap.D] === true) {
                    this.P__context.walkRight();
                }
                else if (window.keyState[keyCodeMap.W] === true) {
                    this.P__context.walkUp();
                }
                else if (window.keyState[keyCodeMap.S] === true) {
                    this.P__context.walkDown();
                }
            },
            __changeTerrainData: function () {
                var stop = bomberConfig.map.terrain.stop,
                position = this.getCurrentCellPosition();

                terrainDataOperate.setTerrainData(position.x, position.y, stop);
            }
        },
        Public: {
            //已放置的炸彈數
            bombNum: 0,

            move: function () {
                this.P__context.move();
            },
            setDir: function () {
                if (this.moving) {
                    return;
                }

                if (this.__allKeyUp()) {
                    this.P__context.stand();
                }
                else {
                    this.__judgeAndSetDir();
                }
            },
            createBomb: function () {
                if (this.bombNum === 3) {
                    return null;
                }

                return this.P__context.createBomb();
            }
        }
    });

    window.PlayerSprite = PlayerSprite;
}());
View Code

BomberSprite

(function () {
    var BombSprite = YYC.Class(Sprite, {
        Init: function (playerSprite, bitmap) {
            this.playerSprite = playerSprite;

            this.base(null, bitmap);
        },
        Protected: {
        },
        Private: {
            //返回火焰範圍
            //返回順序爲[center、[up]、[down]、[left]、[right]]
            __getFireAllRange: function () {

                return [
                    { x: this.x, y: this.y },
                    [
                        { x: this.x, y: this.y - bomberConfig.HEIGHT },
                        { x: this.x, y: this.y - bomberConfig.HEIGHT * 2 }
                    ],
                    [
                        { x: this.x, y: this.y + bomberConfig.HEIGHT },
                        { x: this.x, y: this.y + bomberConfig.HEIGHT * 2 }
                    ],
                    [
                        { x: this.x - bomberConfig.WIDTH, y: this.y },
                        { x: this.x - bomberConfig.WIDTH * 2, y: this.y }
                    ],
                    [
                        { x: this.x + bomberConfig.WIDTH, y: this.y },
                        { x: this.x + bomberConfig.WIDTH * 2, y: this.y }
                    ]
                ];
            },
            __getCenterEffectiveRange: function (effectiveRange, center) {
                effectiveRange.center = { x: center.x, y: center.y };
            },
            __getFourDirEffectiveRange: function (effectiveRange, allRange) {
                var i = 0,
                    j = 0,
                    len1 = 0,
                    len2 = 0,
                    firePos = null,
                    cellPos = null,
                    groundRange = [],
                    wallRange = [];

                for (i = 0, len1 = allRange.length; i < len1; i++) {
                    for (j = 0, len2 = allRange[i].length; j < len2; j++) {
                        firePos = allRange[i][j];
                        cellPos = this.getCellPosition(firePos.x, firePos.y);

                        if (this.__isNotBorder(cellPos)) {
                            if (this.__isGround(cellPos)) {
                                groundRange.push(firePos);
                            }
                            else if (this.__isWall(cellPos)) {
                                wallRange.push(firePos);
                                break;
                            }
                            else {
                                throw new Error("未知的地圖類型");
                            }
                        }
                    }
                }
                effectiveRange.groundRange = groundRange;
                effectiveRange.wallRange = wallRange;
            },
            __createFire: function (effectiveRange) {
                var fires = [];

                this.__createCenter(fires, effectiveRange);
                this.__createFourDir(fires, effectiveRange);

                return fires;
            },
            __createCenter: function (fires, effectiveRange) {
                var center = spriteFactory.createExplode();

                center.x = effectiveRange.center.x;
                center.y = effectiveRange.center.y;
                fires.push(center);
            },
            __createFourDir: function (fires, effectiveRange) {
                var i = 0,
                    len = 0,
                    fire = null,
                    groundRange = effectiveRange.groundRange;

                for (i = 0, len = groundRange.length; i < len; i++) {
                            fire = spriteFactory.createFire();
                            fire.x = groundRange[i].x;
                            fire.y = groundRange[i].y;
                            fires.push(fire);
                }
            },
            __isNotBorder: function (position) {
                if (position.x < 0 || position.y < 0) {
                    return false;
                }

                if (position.x >= window.mapData[0].length || position.y >= window.mapData.length) {
                    return false;
                }

                return true;
            },
            __isGround: function (position) {
                return window.mapDataOperate.getMapData()[position.y][position.x] === window.bomberConfig.map.type.GROUND;
            },
            __bombPass: function () {
                var pass = bomberConfig.map.terrain.pass,
                position = this.getCellPosition(this.x, this.y);

                terrainDataOperate.setTerrainData(position.x, position.y, pass);
            },
            __destroyWall: function (effectiveRange) {
                var i = 0,
                len = 0,
                mapChange = false,
                wallRange = effectiveRange.wallRange,
                cellPos = null,
                      ground = bomberConfig.map.type.GROUND,
                    groundImg = window.imgLoader.get("ground"),
                    wall = bomberConfig.map.type.WALL,
                    pass = bomberConfig.map.terrain.pass,
                    stop = bomberConfig.map.terrain.stop;

                for (i = 0, len = wallRange.length; i < len; i++) {
                    cellPos = this.getCellPosition(wallRange[i].x, wallRange[i].y);
                    window.mapDataOperate.setMapData(cellPos.x, cellPos.y, ground);
                                window.terrainDataOperate.setTerrainData(cellPos.x, cellPos.y, pass);
                                //觀察者模式 -> 發佈
                                //調用mapLayer.changeSpriteImg,改變地圖層對應精靈類的img對象
                                window.observer.publishAll(null, cellPos.x, cellPos.y, groundImg);
                                if (!mapChange) {
                                    mapChange = true;
                                }
                }

                return mapChange;
            },
            __isWall: function (position) {
                return window.mapDataOperate.getMapData()[position.y][position.x] === window.bomberConfig.map.type.WALL;
            },
            __isInEffectiveRange: function (effectiveRange) {
                var range = null;

                range = effectiveRange.groundRange.concat(effectiveRange.wallRange);
                range.push(effectiveRange.center);

                if (this.__isInRange(range)) {
                    return true;
                }
                else {
                    return false;
                }
            },
            __isInRange: function (range) {
                var i = 0,
                    len = 0;

                for (i = 0, len = range.length; i < len; i++) {
                    if (range[i].x === this.x && range[i].y === this.y) {
                        return true;
                    }
                }

                return false;
            }
        },
        Public: {
            playerSprite: null,
            //是否已爆炸標誌
            exploded: false,

            explode: function () {
                var fires = null,
                    mapChange = false,
                    effectiveRange = [];

                this.playerSprite.bombNum -= 1;
                this.exploded = true;
                this.__bombPass();
                effectiveRange = this.getFireEffectiveRange();
                fires = this.__createFire(effectiveRange);
                mapChange = this.__destroyWall(effectiveRange);


                return {
                    fires: fires,
                    mapChange: mapChange
                };
            },
            //檢測火焰與玩家人物、敵人的碰撞
            collideFireWithCharacter: function (sprite) {
                var effectiveRange = this.getFireEffectiveRange(),
                range = [],
                fire = {},
                obj2 = {},
                i = 0,
                len = 0;

                //放到數組中
                range.push(effectiveRange.center);
                range = range.concat(effectiveRange.groundRange, effectiveRange.wallRange);

                for (i = 0, len = range.length; i < len; i++) {
                    fire = {
                        x: range[i].x,
                        y: range[i].y,
                        width: this.bitmap.width,
                        height: this.bitmap.height
                    };
                    obj2 = {
                        x: sprite.x,
                        y: sprite.y,
                        width: sprite.bitmap.width,
                        height: sprite.bitmap.height
                    };
                    if (YYC.Tool.collision.col_Between_Rects(fire, obj2)) {
                        return true;
                    }
                }

                return false;
            },
            //返回有效範圍。(考慮牆、邊界阻擋等問題)
            //返回值形如:{center: {x: 1,y: 1}}, {groundRange: [{{x: 1,y: 1}]}, {wallRange: [{{x: 1,y: 1}]}
            getFireEffectiveRange: function () {
                var effectiveRange = {},
                    allRange = this.__getFireAllRange();

                this.__getCenterEffectiveRange(effectiveRange, allRange.shift());
                this.__getFourDirEffectiveRange(effectiveRange, allRange);

                return effectiveRange;
            },
            isInEffectiveRange: function (bomb) {
                return this.__isInEffectiveRange(bomb.getFireEffectiveRange());
            }
        }
    });

    window.BombSprite = BombSprite;
}());
View Code

Sprite

(function () {
    var Sprite = YYC.AClass({
        Init: function (data, bitmap) {
            this.bitmap = bitmap;

            if (data) {
                this.x = data.x;
                this.y = data.y;

                this.defaultAnimId = data.defaultAnimId;
                this.anims = data.anims;
            }
        },
        Private: {
            //更新幀動畫
            _updateFrame: function (deltaTime) {
                if (this.currentAnim) {
                    this.currentAnim.update(deltaTime);
                }
            }
        },
        Public: {
            bitmap: null,

            //精靈的座標
            x: 0,
            y: 0,

            //精靈包含的全部 Animation 集合. Object類型, 數據存放方式爲" id : animation ".
            anims: null,
            //默認的Animation的Id , string類型
            defaultAnimId: null,

            //當前的Animation.
            currentAnim: null,

            //設置當前Animation, 參數爲Animation的id, String類型
            setAnim: function (animId) {
                this.currentAnim = this.anims[animId];
            },
            //重置當前幀
            resetCurrentFrame: function (index) {
                this.currentAnim && this.currentAnim.setCurrentFrame(index);
            },
            //取得精靈的碰撞區域,
            getCollideRect: function () {
                var obj = {
                    x: this.x,
                    y: this.y,
                    width: this.bitmap.width,
                    height: this.bitmap.height
                };

                return YYC.Tool.collision.getCollideRect(obj);
            },
            Virtual: {
                //初始化方法
                init: function () {
                    //設置當前Animation
                    this.setAnim(this.defaultAnimId);
                },
                // 更新精靈當前狀態.
                update: function (deltaTime) {
                    this._updateFrame(deltaTime);
                },
                //得到座標對應的方格座標(向下取值)
                getCellPosition: function (x, y) {
                    return {
                        x: Math.floor(x / bomberConfig.WIDTH),
                        y: Math.floor(y / bomberConfig.HEIGHT)
                    }
                },
                draw: function (context) {
                    context.drawImage(this.bitmap.img, this.x, this.y, this.bitmap.width, this.bitmap.height);
                },
                clear: function (context) {
                    //直接清空畫布區域
                    context.clearRect(0, 0, bomberConfig.canvas.WIDTH, bomberConfig.canvas.HEIGHT);
                }
            }
        }
    });

    window.Sprite = Sprite;
}());
View Code

PlayerLayer

(function () {
    var PlayerLayer = YYC.Class(CharacterLayer, {
        Init: function (deltaTime) {
            this.base(deltaTime);
        },
        Private: {
            ___keyDown: function () {
                if (keyState[keyCodeMap.A] === true || keyState[keyCodeMap.D] === true
                    || keyState[keyCodeMap.W] === true || keyState[keyCodeMap.S] === true) {
                    return true;
                }
                else {
                    return false;
                }
            },
            ___spriteMoving: function () {
                return this.getChildAt(0).moving
            },
            ___spriteStand: function () {
                if (this.getChildAt(0).stand) {
                    this.getChildAt(0).stand = false;
                    return true;
                }
                else {
                    return false;
                }
            }
        },
        Public: {
            bombLayer: null,

            setCanvas: function () {
                this.P__canvas = document.getElementById("playerLayerCanvas");

                $("#playerLayerCanvas").css({
                    "position": "absolute",
                    "top": bomberConfig.canvas.TOP,
                    "left": bomberConfig.canvas.LEFT,
                    "border": "1px solid red",
                    "z-index": 3
                });
            },
            init: function (layers) {
                this.bombLayer = layers.bombLayer;

                this.base();
            },
            change: function () {
                if (this.___keyDown() || this.___spriteMoving() || this.___spriteStand()) {
                    this.base();
                }
            },
            createAndAddBomb: function () {
                var bomb = this.getChildAt(0).createBomb();
                var self = this;

                if (!bomb) {
                    return false;
                }

                this.bombLayer.appendChild(bomb);
                //3s後炸彈爆炸
                setTimeout(function () {
                    if (!bomb.exploded) {
                        self.bombLayer.explode(bomb);
                    }
                }, 3000);

                return bomb;
            },
            run: function () {
                if (keyState[keyCodeMap.Space]) {
                    this.createAndAddBomb();
                    keyState[keyCodeMap.Space] = false;
                }
                this.base();
            }
        }
    });

    window.PlayerLayer = PlayerLayer;
}());
View Code

BomberLayer

(function () {
    var BombLayer = YYC.Class(Layer, {
        Private: {
            ___hasBomb: function(){
                return this.getChilds().length > 0;
            },
            ___removeBomb: function (bomb) {
                //*注意順序!

                this.clear(bomb);
                this.remove(bomb);
            },
            ___removeAllFire: function () {
                //*注意順序!

                this.fireLayer.clear();
                this.fireLayer.removeAll();
            },
            ___removeEnemy: function (enemy) {
                //*注意順序!

                this.enemyLayer.clear(enemy);
                this.enemyLayer.remove(enemy);
            },
            ___mapChange: function (mapChange) {
                if (mapChange) {
                    this.mapLayer.setStateChange();
                }
            },
            ___collideFireWithPlayer: function (bomb) {
                    if (bomb.collideFireWithCharacter(this.playerLayer.getChildAt(0))) {
                        window.gameState = window.bomberConfig.game.state.OVER;
                    }
            },
            ___collideFireWithEnemy: function (bomb) {
                    var i = 0,
                        len = 0,
                        enemySprites = this.enemyLayer.getChilds();

                    for (i = 0, len = enemySprites.length ; i < len; i++) {
                        if (bomb.collideFireWithCharacter(enemySprites[i])) {
                            this.___removeEnemy(enemySprites[i]);
                        }
                    }
            },
            ___handleCollid: function (bomb) {
                //判斷與玩家人物碰撞
                this.___collideFireWithPlayer(bomb)
                //判斷與每一個敵人碰撞
                this.___collideFireWithEnemy(bomb);
            },
            ___explodeInEffectiveRange: function (bomb) {
                var eachBomb = null;

                this.resetCursor();
                while (this.hasNext()) {
                    eachBomb = this.next();
                    if (eachBomb.isInEffectiveRange.call(eachBomb, bomb)) {
                        this.explode(eachBomb);
                    }
                }
                this.resetCursor();
            }
        },
        Public: {
            fireLayer: null,
            mapLayer: null,
            playerLayer: null,
            enemyLayer: null,

            setCanvas: function () {
                this.P__canvas = document.getElementById("bombLayerCanvas");
                var css = {
                    "position": "absolute",
                    "top": bomberConfig.canvas.TOP,
                    "left": bomberConfig.canvas.LEFT,
                    "z-index": 1
                };

                $("#bombLayerCanvas").css(css);
            },
            init: function(layers){
                this.fireLayer = layers.fireLayer;
                this.mapLayer = layers.mapLayer;
                this.playerLayer = layers.playerLayer;
                this.enemyLayer = layers.enemyLayer;

                this.base();
            },
            draw: function () {
                this.P__iterator("draw", this.P__context);
            },
            explode: function (bomb) {
                var self = this,
                    result = null;

                //處理碰撞
                this.___handleCollid(bomb);

                result = bomb.explode();
                this.fireLayer.addSprites(result.fires);
                this.___mapChange(result.mapChange);
                this.___removeBomb(bomb);


                //炸彈爆炸時會引爆在火力範圍內的炸彈。
                this.___explodeInEffectiveRange(bomb);

                //定時清空fireLayer(火焰消失)
                setTimeout(function () {
                    self.___removeAllFire();
                }, 300);

            },
            change: function(){
                if (this.___hasBomb()) {
                    this.setStateChange();
                }
            },
            run: function () {
                this.P__render();
            }
        }
    });

    window.BombLayer = BombLayer;
}());
View Code

Layer

(function () {
    var Layer = YYC.AClass(Collection, {
        Init: function () {
        },
        Private: {
            __state: bomberConfig.layer.state.CHANGE,   //默認爲change

            __getContext: function () {
                this.P__context = this.P__canvas.getContext("2d");
            }
        },
        Protected: {
            //*共用的變量(可讀、寫)

            P__canvas: null,
            P__context: null,

            //*共用的方法(可讀)

            P__isChange: function(){
                return this.__state === bomberConfig.layer.state.CHANGE;
            },
            P__isNormal: function () {
                return this.__state === bomberConfig.layer.state.NORMAL;
            },
            P__iterator: function (handler) {
                var args = Array.prototype.slice.call(arguments, 1),
                    nextElement = null;

                while (this.hasNext()) {
                    nextElement = this.next();
                    nextElement[handler].apply(nextElement, args);  //要指向nextElement
                }
                this.resetCursor();
            },
            P__render: function () {
                if (this.P__isChange()) {
                    this.clear();
                    this.draw();
                    this.setStateNormal();
                }
            }
        },
        Public: {
            remove: function (sprite) {
                this.base(function (e, obj) {
                    if (e.x === obj.x && e.y === obj.y) {
                        return true;
                    }
                    return false;
                }, sprite);
            },
            addSprites: function(elements){
                this.appendChilds(elements);
            },
            //設置狀態爲NORMAL
            setStateNormal: function () {
                this.__state = bomberConfig.layer.state.NORMAL;
            },
            //設置狀態爲CHANGE
            setStateChange: function () {
                this.__state = bomberConfig.layer.state.CHANGE;
            },
            Virtual: {
                init: function () {
                    this.__getContext();
                },
                clear: function (sprite) {
                    if (arguments.length === 0) {
                        this.P__iterator("clear", this.P__context);
                    }
                    else if (arguments.length === 1) {
                        sprite.clear(this.P__context);
                    }
                }
            }
        },
        Abstract: {
            setCanvas: function () {
            },
            //判斷並更改狀態
            change: function () {
            },
            //統一繪製
            draw: function () { },
            //遊戲主線程調用的函數
            run: function () { }
        }
    });

    window.Layer = Layer;
}());
View Code

SpriteFactory

        createBomb: function (playerSprite) {
            return new BombSprite(playerSprite, bitmapFactory.createBitmap({ img: window.imgLoader.get("bomb"), width: bomberConfig.WIDTH, height: bomberConfig.HEIGHT }));
        },
        createFire: function () {
            return new FireSprite(null, bitmapFactory.createBitmap({ img: window.imgLoader.get("fire"), width: bomberConfig.WIDTH, height: bomberConfig.HEIGHT }));
        },
        createExplode: function () {
            return new FireSprite(null, bitmapFactory.createBitmap({ img: window.imgLoader.get("explode"), width: bomberConfig.WIDTH, height: bomberConfig.HEIGHT }));
        }

LayerFactory

        createBomb: function () {
            return new BombLayer();
        },
        createFire: function () {
            return new FireLayer();
        }

加入1個敵人

往EnemyLayer集合中再加入一個EnemySprite實例,SpriteData增長第2個敵人的數據,SpriteFactory增長工廠方法createEnemy2。

相關代碼

Game

           _createEnemyLayerElement: function () {
                var element = [],
                    enemy = spriteFactory.createEnemy(),
                    enemy2 = spriteFactory.createEnemy2();

                enemy.init();
                enemy2.init();

                element.push(enemy);
                element.push(enemy2);

                return element;
            },

SpriteData

enemy2: {
    //初始座標
        x: bomberConfig.WIDTH * 10,
    //x: 0,
        y: bomberConfig.HEIGHT * 10,
    //定義sprite走路速度的絕對值
        walkSpeed: bomberConfig.enemy.speed.NORMAL,

    //速度
        speedX: 1,
        speedY: 1,

        minX: 0,
        maxX: bomberConfig.canvas.WIDTH - bomberConfig.player.IMGWIDTH,
        minY: 0,
        maxY: bomberConfig.canvas.HEIGHT - bomberConfig.player.IMGHEIGHT,

        defaultAnimId: "stand_left",

        anims: {
            "stand_right": new Animation(getFrames("enemy", "stand_right")),
            "stand_left": new Animation(getFrames("enemy", "stand_left")),
            "stand_down": new Animation(getFrames("enemy", "stand_down")),
            "stand_up": new Animation(getFrames("enemy", "stand_up")),
            "walk_up": new Animation(getFrames("enemy", "walk_up")),
            "walk_down": new Animation(getFrames("enemy", "walk_down")),
            "walk_right": new Animation(getFrames("enemy", "walk_right")),
            "walk_left": new Animation(getFrames("enemy", "walk_left"))
        }
}

SpriteFactory

        createEnemy2: function () {
            return new EnemySprite(getSpriteData("enemy2"), bitmapFactory.createBitmap({ img: window.imgLoader.get("enemy"), width: bomberConfig.player.IMGWIDTH, height: bomberConfig.player.IMGHEIGHT }));
        },

炸死全部敵人後,提示遊戲勝利

GameState增長WIN枚舉值。在BombLayer中判斷是否將敵人都炸死了,若是都炸死了則設置GameState爲WIN。在Game中判斷GameState,調用相應的方法。

領域模型

相關代碼

BombLayer

           ___collideFireWithEnemy: function (bomb) {
                    var i = 0,
                        len = 0,
                        enemySprites = this.enemyLayer.getChilds();

                    for (i = 0, len = enemySprites.length ; i < len; i++) {
                        if (bomb.collideFireWithCharacter(enemySprites[i])) {
                            this.___removeEnemy(enemySprites[i]);
                        }
                    }

                    //若是敵人都被炸死了,則遊戲勝利!
                    if (this.enemyLayer.getChilds().length === 0) {
                        window.gameState = window.bomberConfig.game.state.WIN;
                    }
            },

Game

_judgeGameState: function () {
    switch (window.gameState) {
        case window.bomberConfig.game.state.NORMAL:
            break;
        case window.bomberConfig.game.state.OVER:
            this.gameOver();
            break;
        case window.bomberConfig.game.state.WIN:
            this.gameWin();
            break;
        default:
            throw new Error("未知的遊戲狀態");
    }
    return;
}
...
run: function () {
    this._judgeGameState();

    this.layerManager.run();
    this.layerManager.change();
},

本文最終領域模型

查看大圖

高層劃分

炸彈層和炸彈精靈、火焰層和火焰精靈應該放到哪一個包?

炸彈層和玩家層、炸彈精靈和玩家精靈緊密關聯,火焰層和火焰精靈與炸彈層和炸彈精靈緊密關聯,所以將炸彈層和炸彈精靈、火焰層和火焰精靈移到人物包中。

新增包

  • 全局包
    GameState
  • 觀察者模式包
    Subject
  • 炸彈實現包
    BombSprite、FireSprite、BombLayer、FireLayer

層、包

對應領域模型

  • 輔助操做層
    • 控件包
      PreLoadImg
    • 配置包
      Config
  • 用戶交互層
    • 入口包
      Main
  • 業務邏輯層
    • 輔助邏輯
      • 工廠包
        BitmapFactory、LayerFactory、SpriteFactory
      • 事件管理包
        KeyState、KeyEventManager
      • 抽象包
        Layer、Sprite、Hash、Collection
      • 全局包
        GameState
    • 遊戲主邏輯
      • 主邏輯包
        Game
    • 層管理
      • 層管理包
        LayerManager
    • 實現
      • 人物實現包
        PlayerLayer、MoveSprite、PlayerSprite、EnemySprite、CharacterLayer、PlayerLayer、EnemyLayer、Context、PlayerState、WalkState、StandState、WalkState_X、WalkState_Y、StandLeftState、StandRightState、StandUpState、StandDownState、WalkLeftState、WalkRightState、WalkUpState、WalkDownState
      • 炸彈實現包
        BombSprite、FireSprite、BombLayer、FireLayer
      • 地圖實現包
        MapLayer、MapElementSprite
      • 算法包
        FindPath
      • 動畫包
        Animation、GetSpriteData、SpriteData、GetFrames、FrameData
      • 觀察者模式包
        Subject
  • 數據操做層
    • 地圖數據操做包
      MapDataOperate、TerrainDataOperate
    • 路徑數據操做包
      GetPath
    • 圖片數據操做包
      Bitmap
  • 數據層
    • 地圖包
      MapData、TerrainData
    • 圖片路徑包
      ImgPathData

本文參考資料

深刻理解JavaScript系列(32):設計模式之觀察者模式

《設計模式之禪》

歡迎瀏覽上一篇博文:炸彈人遊戲開發系列(7):加入敵人,使用A*算法尋路

歡迎瀏覽下一篇博文:炸彈人遊戲開發系列(9):總結

相關文章
相關標籤/搜索