Ever needed to loop a background forever in your 2D game and wondered the best way to do it? That’s exactly what we are going to look at today, with all the code samples done using HaxeFlixel, which I recently covered in this tutorial series if you wish to learn more. One key aspect is to have a background that is seamless. That is the ending edge and beginning edge need to blend seamlessly together. For this example I am using this graphic (click for the full resolution version).
There are two approaches we are going to cover today. The first one is the simplest, essentially we update our camera when we get within half a screens height of the edge and loop it to the bottom, like so:
Let’s take a look at the code.
import flixel.FlxG; import flixel.FlxSprite; import flixel.FlxState; import flixel.FlxObject; class PlayState extends FlxState { var bg:FlxSprite; var camTarget:FlxObject; override public function create():Void { super.create(); bg = new FlxSprite(0,0,AssetPaths.BloodRed__png); add(bg); camTarget = new FlxObject(); camTarget.setPosition(FlxG.width/2, FlxG.height/2); FlxG.camera.target = camTarget; add(camTarget); } override public function update(elapsed:Float):Void { super.update(elapsed); camTarget.y+=25; // Move the camera to loop forever if(camTarget.y > bg.height - FlxG.height/2){ camTarget.setPosition(FlxG.width/2, FlxG.height/2); } } }
The downside to this approach is you also need to reset the Y coordinate of all the entities in your scene as your camera resets.
Another option is to use two instances of the background and as you approach the edge you raise the lower most version to the top and repeat forever. Like so:
Here is the code for this approach.
import flixel.FlxG; import flixel.FlxSprite; import flixel.FlxState; import flixel.FlxObject; class PlayState extends FlxState { var bg:FlxSprite; var bg2:FlxSprite; var camTarget:FlxObject; override public function create():Void { super.create(); bg = new FlxSprite(0,0,AssetPaths.BloodRed__png); bg2 = new FlxSprite(0,bg.height,AssetPaths.BloodRed__png); add(bg); add(bg2); camTarget = new FlxObject(); camTarget.setPosition(FlxG.width/2, FlxG.height/2); FlxG.camera.target = camTarget; add(camTarget); } override public function update(elapsed:Float):Void { super.update(elapsed); camTarget.y+=25; var greater,lesser; if(bg.y > bg2.y){ greater = bg; lesser = bg2; } else{ greater = bg2; lesser = bg; } if(camTarget.y > greater.y + bg.height - FlxG.height/2){ trace("Flip"); lesser.y = greater.y + bg.height; } } }
This approach has the advantage of enabling you to keep coordinates consistent, but has the downside of requiring a second identical sprite. Since the second sprite is simply an instance of the same texture though, the memory impact should be almost non-existent.