Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

How would level scrolling be implemented with haxe and nme?

I started plying with haxe and currently the only way to display things on screen is by using nme.display.Sprite.

I don't think changing the position of all sprites to scroll the level is the most efficient way of doing things.

I can't find any comprehensive example of how this should be done with haxe.

Any pointers?

share|improve this question
    
There's nothing specific about Haxe that you need to do. The usual way is to keep information about all sprites, and draw only those that are visible on screen. –  ashes999 Jan 31 '13 at 18:42
    
I'm learning the ins and outs of the nme library and I couldn't figure out how to use the built-in scrolling features of nme.display.Sprite –  Coyote Jan 31 '13 at 21:10
add comment

1 Answer

up vote 3 down vote accepted

Basically you want to have a "camera" concept to show exactly what you want in your game. In this case the camera may follow your hero, so that it tries to keep the hero centered on the screen as much as possible (similar to how the original super mario bros. game worked).

In order to do so you would add all of your sprites to a parent sprite so your display hierarchy looks like this:

  • Stage
    • Game Container
      • Game Sprites

So now your "camera" is in the Game Container layer and you simply use scrollRect() to show exactly what you want. The scrolling parts come into play by having your scrollRect() follow the hero. You could do this with a listener or just a function call, something like:

var oldHeroXPos = hero.x;
var oldHeroYPos = hero.y;

//move your hero in game
hero.moveTo(newHeroXPos,newHeroYPos);

//make the camera follow the hero..i.e. move same amount as the hero
camera.moveOffset(newHeroXPos-oldHeroXPos,newHeroYPos-oldHeroYPos);

The camera.moveOffset() function would look something like this:

public function moveOffset(inXDiff:Float,inYDiff:Float):Void
{
    gameContainer.scrollRect.x += inXDiff;
    gameContainer.scrollRect.y += inYDiff;
}

After you get this working you will have an issue where scrolling isn't smooth, so you will have to do additional work to change the camera position over time...maybe give it some elasticity to make it look nicer.

Note if you use a game engine such as flixel, or awe6 that this type of thing is already taken care of for you, and unless you are interested in learning exactly what it takes to implement a smooth camera I always suggest using a tried and true game library to help get your game out quicker, but the end goals are up to you.

Keep in mind that whatever game logic you use to move your enemies would apply regardless of the camera, and you would probably want a slightly larger box (bigger than the camera) that would be used to indicate if the game entity was "alive" i.e. needs its AI run. If you did something like this then you could have enemies walk into your screen even if the hero stands still (camera not moving).

drawTiles

Instead of using the build in nme.display.Sprite class you can roll your own game entities and render with drawTiles().

In drawTiles() you will optimally have only one layer for game content, so that you only need to call drawTiles() once per frame. In order to do this you need to build up a class that has everything you need in it (ex: GameEntity). nme.display.Sprite has x/y, width/height, rotation, scale, graphics, etc and you will need to replicate all of that in your GameEntity class. Handling the graphics yourself is the major hurdle since you will need to plan out how you layout your graphics before you load a level in your game (using a Tilesheet).

Note: The libraries Tilelayer or gm2d (look in the blit folder) handle the drawTiles() abstraction nicely. TileLayer uses TileSprite to replace nme.display.Sprite, while gm2d uses Tile for the nme.display.Sprite functionality

Your main game loop would something like this:

class GameMain
{
    public var renderArray:Array<Float>;
    public var gameEntities:Array<GameEntity>;

    public function new():Void
    {
         renderArray = new Array<Float>();
         gameEntities = new Array<GameEntity>();
         //Here we update all game entities and then render
         Lib.current.stage.addEventListener(Event.ENTER_FRAME,onUpdate);
    }

    private function onUpdate(inEvent:Event):Void
    {
        //Reset render array...see TileLayer for most efficient way to handle
        renderArray = [];

        for (entity in _gameEntities)
        {
            entity.update(); //update x,y position
        }

        //we now have all the graphics at the correct positions...one batch render!
        Lib.current.stage.graphics.drawTiles(renderArray);
    }
}

You game entity would look something like this (only x/y and graphics shown for simplicity):

class GameEntity
{
    //drawTiles tile ID
    private var _tileID:Int;
    private var _x:Float;
    private var _y:Float;
    private var _gameMain:GameMain;

    public function new (inTileId:Int,inGameMain:GameMain):Void
    {
        //get ref to main game so we can update render array
        _gameMain = inGameMain;

        //This is the graphic we show every frame
        _tileID = inTileId; 

        _x = _y = 0.0;

    }

    //Call me on each ENTER_FRAME event!
    public function update():Void
    { 
         //Assign arbitrary values to x and y 
        _x = Std.random(100);
        _y = Std.random(100);

        //Update render array with this entities information
        //order for drawTiles() is id,x,y,...etc
        _gameMain.renderArray.push(_tileID);
        _gameMain.renderArray.push(_x);
        _gameMain.renderArray.push(_y);
    }
}

At this point the code still won't work, and that's because the tileID is meaningless at this point...i.e. what does the tileID refer to? It needs to represent something inside of a nme.display.Tilesheet. A tilesheet is just a large image made up of smaller images...that's it. Each small image occupies some space in the large image and that "occupied space" is what the tileID represents.

Example:

  • tileID=0 represents the image at x=0,y=0,width=100,height=100
  • tileID=1 represents the image at x=101,y=0,width=300,height=100

So now you need to setup your tilesheet...and with that you could use a tool like texturepacker that will generate a large image (from many small .pngs,.jpgs,etc) and will ALSO generate rectangle data...and that rectangle data correlates 1:1 with the tile IDs. The TileLayer library is already setup to read in texturepacker sparrow output so all you have to do is open the tool drop in some images and export the .png and .xml file and then TileLayer will autocreate your tilesheet data.

If you want to roll your own solution you can just create a blank bitmap (i.e. new BitmapData(2048,2048)) and then start adding smaller BitmapData to the larger image and track the rectangles yourself...but then you need an algorithim on how you are going to stack the rectangles..i.e. a binning or packing algorithim like repack or the algorithim I added for gm2d use here.

So as you can see your todo list grows very quickly...I highly recommend TileLayer and Texturepacker if you want to use drawTiles() Tilelayer is efficient, well written, well commented, and should have you up and running 100x quicker then rolling your own solution.

share|improve this answer
    
Hi! Thank you for the answer... I didn't know about the scrollRec this is great! I just tried it and I have an issue. Changing the scrollRec like this scrollRect.x = scrollRect.x + 1; from within the gameContainer doesn't update the rec. Are there any specific limitations during the Event.ENTER_FRAME event? –  Coyote Jan 31 '13 at 20:53
    
I got it running by replacing the rect like so: scrollRect = new nme.geom.Rectangle(scrollRect.x + 1, 0, stageWidth_, stageHeight_); I think this is not efficient and doesn't help the garbage collector. –  Coyote Jan 31 '13 at 20:56
    
Yeah probably not the most efficient but I wouldn't worry about that at this point, you can always optimize later once you start profiling and I bet this action will have <.1% impact on your game overall. Also, for max performance you want to use drawTiles() to batch your graphics call so you could just use a rectangle as your camera, and then you wouldn't need to follow the limitations of sprite.scrollRect(). –  ajdiperna Jan 31 '13 at 21:45
    
Could you add another answer with a snippet on how to use drawTiles()? –  Coyote Jan 31 '13 at 22:10
    
Great answer! Thank you, with these pointers I will solve the question for good! If I could up-vote your answer more I would :) –  Coyote Feb 2 '13 at 8:47
show 1 more comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.