JogoShugh / SpaceMiner

SpaceMiner
http://spaceminer.azurewebsites.net
Other
4 stars 4 forks source link

Support multiple custom sprites per type within a level #81

Closed JogoShugh closed 9 years ago

JogoShugh commented 9 years ago

Now that #78 brings basic support for uploading custom sprites for a given sprite type (tile, enemy, player, coin, gem), we need to able to support multiple custom sprites per type.

It's tricky to load these because we don't want to pre-load every custom image before use. Here's how I think it would work:

At the code level, we can modify the short-cut helpers such that you can pass in a custom name, not just refer to the built-in ones.

Example

Currently, tiles, enemies, players, coins, and gems are Objects, with properties that simplify producing the parameters needed to specify a given built-in sprite option:

let _spr = type => val => () => ({sprite:type, asset: val});
let _c = _spr('coin');

let coins = {
  blue: _c('blue'),
  brown: _c('brown'),
  gold: _c('gold'),
  green: _c('green'),
  light: _c('light'),
  pink: _c('pink')
};
window.coins = coins;

We can modify this by turning each of those into function objects that can have the name of a custom sprite for the given type to be supplied, but still support dotted notation for the built-in items without breaking any existing levels. Like this:

let _e = _spr('enemy');
let enemies = function(customSpriteName) { return () => ({sprite:'enemy', asset:`enemy|${customSpriteName}.cspr`}); };
enemies.blue = _e('brainBlue');
enemies.pink = _e('brainPink');
enemies.red = _e('cyclopsRed');
enemies.yellow = _e('cyclopsYellow');
enemies.green = _e('goonGreen');
enemies.purple = _e('goonPurple');
window.enemies = enemies;

After doing this, we will be able to author worlds like this:

Assuming we've uploaded an enemy named John.png, a tile named Grass.png, and a coin named silver.png, then:

let worldName = "The World"

start(
    box(enemy),
    box(enemies('John'), at(10,1)),
    wall(at(1,10), coins('silver'),
    wall(at(1,11), coins.pink),
    wall(tiles.fiery, at(1, 12)),
    block(tiles('Grass'), at(9,5)),
    sprite(player, at(10,10))
)

The trick will be to preload each of those custom images just-in-time, and produce the correct spriteSheet / animation for each, because they could have multiple frames and we should support that.

I think the code for doing so will end up similar to what we now have for the one-to-one custom sprite support:

      let customSprites = obj.customSprites;
      if (customSprites && _.isObject(customSprites)) {
        let customSpritesPaths = [];
        for(let key in customSprites) {
          customSpritesPaths.push(customSprites[key]);
        }
        Q.load(customSpritesPaths, function() {
          for(let key in customSprites) {
            var customSprite =  customSprites[key];
            if (customSprite !== '') {
              Q.sheet(`custom-${key}Sheet`, customSprite, {tileW:32, tileH: 32});
              let frames = Array.from(range(Q.sheets[`custom-${key}Sheet`].w/32));
              Q.animations(key, { move: { frames, rate: 1, loop: true } });
            }
          }
          finishLoadingLevel();
        });
      }

But instead of building a sheet naively as custom-${key}Sheet, which resolves to custom-enemySheet, etc, we should be more specific, like custom-enemy-John-Sheet, and so forth. Similarly, the key for the new animation should be more specific.