Creating 2d platforms using JavaScript

会有一股神秘感。 提交于 2020-01-14 04:37:07

问题


I'm developing a HTML5 Canvas game using EaselJS and I've written a function that allows me to create "blocks" just by setting one or more images, size and position.

and by "blocks", what I mean is:

I'm doing this using two methods:

First method:

With this method the blocks are created in the available space inside the location I've set, using the images randomly.

Second method:

The blocks are created inside the location I've set using specific images for the top left corner, top side, top right corner, left side, center, right side, bottom left corner, bottom side and bottom right corner, and there can be more than a single image for each one of those parts (so the system uses a random one to avoid repeating the same image multiple times).

Ok, but what's the problem?

This function uses a zillion 77 lines (131 lines counting with the collision-detection-related part)! I know there's a better way of doing this, that will take about a half or less lines than it's taking now, but I don't know how to do it and when someone show me, I'll use the "right way" for the rest of my life. Can you help me?

What I want:

A possible way to use less lines is to use a single "method" that allows me to create blocks that are compound by blocks that are compound by the 9-or-more images (I just don't know how to do it, and I know it's difficult to understand. Try to imagine the third image being used 9 times). // This part of the question makes it on-topic!

Note that this question isn't subjective, since the goal here is to use less lines, and I'm not using the EaselJS tag because the question isn't EaselJS-specific, anyone with JavaScript knowledge can answer me.

Here's my incredibly big JavaScript function:

var Graphic = function (src, blockWidth, blockHeight) {
    return {
        createBlockAt: function (x, y, blockGroupWidth, blockGroupHeight, clsdir, alpha) {
            for (var blockY = 0; blockY < blockGroupHeight / blockHeight; blockY++) {
                for (var blockX = 0; blockX < blockGroupWidth / blockWidth; blockX++) {

                    var obj = new createjs.Bitmap(src[Math.floor(Math.random() * src.length)]);

                    obj.width = blockWidth;
                    obj.height = blockHeight;
                    if (typeof alpha !== 'undefined') {
                        obj.alpha = alpha; // While debugging this can be used to check if a block was made over another block.
                    }

                    obj.x = Math.round(x + (blockWidth * blockX));
                    obj.y = Math.round(y + (blockHeight * blockY));

                    stage.addChild(obj);
                }
            }
        }
    }
}
var complexBlock = function (topLeft, topCenter, topRight, middleLeft, middleCenter, middleRight, bottomLeft, bottomCenter, bottomRight, blockWidth, blockHeight) {
    return {
        createBlockAt: function (x, y, blockGroupWidth, blockGroupHeight, clsdir, alpha) {
            for (var blockY = 0; blockY < blockGroupHeight / blockHeight; blockY++) {
                for (var blockX = 0; blockX < blockGroupWidth / blockWidth; blockX++) {
                    if (blockY == 0 && blockX == 0) {
                        var obj = new createjs.Bitmap(topLeft[Math.floor(Math.random() * topLeft.length)]);
                    }
                    if (blockY == 0 && blockX != 0 && blockX != (blockGroupWidth / blockWidth - 1)) {
                        var obj = new createjs.Bitmap(topCenter[Math.floor(Math.random() * topCenter.length)]);
                    }
                    if (blockY == 0 && blockX == (blockGroupWidth / blockWidth - 1)) {
                        var obj = new createjs.Bitmap(topRight[Math.floor(Math.random() * topRight.length)]);
                    }

                    if (blockY != 0 && blockY != (blockGroupHeight / blockHeight - 1) && blockX == 0) {
                        var obj = new createjs.Bitmap(middleLeft[Math.floor(Math.random() * middleLeft.length)]);
                    }
                    if (blockY != 0 && blockY != (blockGroupHeight / blockHeight - 1) && blockX != 0 && blockX != (blockGroupWidth / blockWidth - 1)) {
                        var obj = new createjs.Bitmap(middleCenter[Math.floor(Math.random() * middleCenter.length)]);
                    }
                    if (blockY != 0 && blockY != (blockGroupHeight / blockHeight - 1) && blockX == (blockGroupWidth / blockWidth - 1)) {
                        var obj = new createjs.Bitmap(middleRight[Math.floor(Math.random() * middleRight.length)]);
                    }

                    if (blockY == (blockGroupHeight / blockHeight - 1) && blockX == 0) {
                        var obj = new createjs.Bitmap(bottomLeft[Math.floor(Math.random() * bottomLeft.length)]);
                    }
                    if (blockY == (blockGroupHeight / blockHeight - 1) && blockX != 0 && blockX != (blockGroupWidth / blockWidth - 1)) {
                        var obj = new createjs.Bitmap(bottomCenter[Math.floor(Math.random() * bottomCenter.length)]);
                    }
                    if (blockY == (blockGroupHeight / blockHeight - 1) && blockX == (blockGroupWidth / blockWidth - 1)) {
                        var obj = new createjs.Bitmap(bottomRight[Math.floor(Math.random() * bottomRight.length)]);
                    }

                    obj.width = blockWidth;
                    obj.height = blockHeight;
                    if (typeof alpha !== 'undefined') {
                        obj.alpha = alpha; // While debugging this can be used to check if a block was made over another block.
                    }

                    obj.x = Math.round(x + (blockWidth * blockX));
                    obj.y = Math.round(y + (blockHeight * blockY));

                    stage.addChild(obj);
                }
            }
        }
    }
}

var bigDirt = complexBlock(["http://i.imgur.com/DLwZMwJ.png"], ["http://i.imgur.com/UJn3Mtb.png"], ["http://i.imgur.com/AC2GFM2.png"], ["http://i.imgur.com/iH6wFj0.png"], ["http://i.imgur.com/wDSNzyc.png", "http://i.imgur.com/NUPhXaa.png"], ["http://i.imgur.com/b9vCjrO.png"], ["http://i.imgur.com/hNumqPG.png"], ["http://i.imgur.com/zXvJECc.png"], ["http://i.imgur.com/Whp7EuL.png"], 40, 40);

bigDirt.createBlockAt(0, 0, 40*3, 40*3);

Okay... Lots of code here, how do I test?

Here we go: JSFiddle


回答1:


I don't see an easy way to reduce the number of lines given the nine possible branches, but you can substantially reduce the repetition in your code:

function randomImage(arr) {
    return new createjs.Bitmap(arr[Math.floor(Math.random() * arr.length)]);
}

if (blockY == 0 && blockX == 0) {
    var obj = randomImage(topLeft);
} // etc

Re: the nine possible branches, you should note that they are mutually exclusive, so should be using else if instead of just if, and that they are also naturally grouped in threes, suggesting that they should be nested.

EDIT in fact, there is a way to reduce the function size a lot. Note that for X and Y you have three options each (nine in total). It is possible to encode which image array you want based on a two-dimensional lookup table:

var blocksHigh = blockGroupHeight / blockHeight;
var blocksWide = blockGroupWidth / blockWidth;
var blockSelector = [
    [topLeft, topCenter, topRight],
    [middleLeft, middleCenter, middleRight],
    [bottomLeft, bottomCenter, bottomRight]
];

for (var blockY = 0; blockY < blocksHigh; blockY++) {
    var blockSY = (blockY == 0) ? 0 : blockY < (blocksHigh - 1) ? 1 : 2;
    for (var blockX = 0; blockX < blocksWide; blockX++) {
        var blockSX = (blockY == 0) ? 0 : blockY < (blocksWide - 1) ? 1 : 2;
        var array = blockSelector[blockSY][blockSX];
        var obj = randomImage(array);

        ...
     }
}

Note the definitions of blocksHigh and blocksWide outside of the loop to reduce expensive repeated division operations.

See http://jsfiddle.net/alnitak/Kpj3E/




回答2:


Ok, it's almost a year later now and I decided to come back here to improve the existing answers. Alnitak's suggestion on creating a "2-dimensional lookup table" was genius, but there's a even better way of doing what I was asking for.

Sprite Sheets

The problem core is the need for picking lots of separated images and merge them in order to create a bigger mosaic. To solve this, I've merged all images into a sprite sheet. Then, with EaselJS, I've separated each part of the platform (topLeft, topCenter, etc) in multiple animations, and alternative images of the same platform part that would be used randomly are inserted within it's default part animation, as an array (so topLeft can be five images that are used randomly).

This was achieved by making a class that creates an EaselJS container object, puts the sprite sheet inside this container, moves the sprite sheet to the correct position, caches the frame and updates the container cache using the "source-overlay" compositeOperation — which puts the current cache over the last one — then it does this again until the platform is finished.

My collision detection system is then applied to the container.

Here's the resulting JavaScript code:

createMosaic = function (oArgs) { // Required arguments: source: String, width: Int, height: Int, frameLabels: Object
    oArgs.repeatX = oArgs.repeatX || 1;
    oArgs.repeatY = oArgs.repeatY || 1;

    this.self = new createjs.Container();
    this.self.set({
        x: oArgs.x || 0,
        y: oArgs.y || 0,
        width: ((oArgs.columnWidth || oArgs.width) * oArgs.repeatX) + oArgs.margin[1] + oArgs.margin[2],
        height: ((oArgs.lineHeight || oArgs.height) * oArgs.repeatY) + oArgs.margin[0] + oArgs.margin[3],
        weight: (oArgs.weight || 20) * (oArgs.repeatX * oArgs.repeatY)
    }).set(oArgs.customProperties || {});
    this.self.cache(
        0, 0,
        this.self.width, this.self.height
    );

    var _bmp = new createjs.Bitmap(oArgs.source);
    _bmp.filters = oArgs.filters || [];
    _bmp.cache(0, 0, _bmp.image.width, _bmp.image.height);

    var spriteSheet = new createjs.SpriteSheet({
        images: [_bmp.cacheCanvas],
        frames: {width: oArgs.width, height: oArgs.height},
        animations: oArgs.frameLabels
    });

    var sprite = new createjs.Sprite(spriteSheet);
    this.self.addChild(sprite);

    for (var hl = 0; hl < oArgs.repeatY; hl++) {
        for (var vl = 0; vl < oArgs.repeatX; vl++) {
            var _yid = (hl < 1) ? "top" : (hl < oArgs.repeatY - 1) ? "middle" : "bottom";
            var _xid = (vl < 1) ? "Left" : (vl < oArgs.repeatX - 1) ? "Center" : "Right";

            if(typeof oArgs.frameLabels[_yid + _xid] === "undefined"){
                oArgs.frameLabels[_yid + _xid] = oArgs.frameLabels["topLeft"];
            } // Case the expected frameLabel animation is missing, it will default to "topLeft"
            sprite.gotoAndStop(_yid + _xid);

            if (utils.getRandomArbitrary(0, 1) <= (oArgs.alternativeTileProbability || 0) && oArgs.frameLabels[_yid + _xid].length > 1) { // If there are multiple frames in the current frameLabels animation, this code choses a random one based on probability
                var _randomPieceFrame = oArgs.frameLabels[_yid + _xid][utils.getRandomInt(1, oArgs.frameLabels[_yid + _xid].length - 1)];
                sprite.gotoAndStop(_randomPieceFrame);
            }

            sprite.set({x: vl * (oArgs.columnWidth || oArgs.width), y: hl * (oArgs.lineHeight || oArgs.height)});
            this.self.updateCache("source-overlay");
        }
    }
    this.self.removeChild(sprite);
    awake.container.addChild(this.self);
};

Usage:

createMosaic({
    source: "path/to/spritesheet.png",
    width: 20,
    height: 20,
    frameLabels: {
        topLeft: 0, topCenter: 1, topRight: 3,
        middleLeft: 4, middleCenter: [5, 6, 9, 10], middleRight: 7,
        bottomLeft: 12, bottomCenter: 13, bottomRight: 15
    },
    x: 100,
    y: 100,
    repeatX: 30,
    repeatY: 15,
    alternativeTileProbability: 75 / 100
});

I would recommend using the "createMosaic" as a function returned by a constructor that passes the required arguments to it, so you'll not need to write the source image path, width, height and frameLabels every time you want to create a dirt platform, for example.

Also, this answer may have more LoC than the others that came before, but it's made this way in order to have more structure.



来源:https://stackoverflow.com/questions/23826694/creating-2d-platforms-using-javascript

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!