Author Topic: FlxTilemap for iOS  (Read 4567 times)

initials

  • Contributor
  • ****
  • Posts: 378
  • Karma: +0/-0
  • Initials
    • View Profile
    • Initials Blog. Code and other things.
FlxTilemap for iOS
« on: Mon, Aug 22, 2011 »
Ok, so this isn't exactly a port of FlxTilemap but here's what I do instead.

Firstly, FlxTilemap creates a single block for each pixel or "1" in your map or CSV. My way looks at a pixel of a png and then uses the RED channel to determine width, GREEN channel to determine height, and BLUE is an arbitrary ID. You can use it give certain blocks an ID of "5" and then query that ID to determine if it's a collapsable block or similar property. Just as an example. Anyway, the code:

FlxImageInfo.h

Code: [Select]
#import <Flixel/FlxObject.h>

@interface FlxImageInfo : NSObject
{

}

+ (NSData *) readImage:(NSString *)imageToRead;


@end



FlxImageInfo.m

Code: [Select]
#import "FlxImageInfo.h"

@implementation FlxImageInfo


+ (NSData *) readImage:(NSString *)imageToRead
{   
    UIImage* image = [UIImage imageNamed:imageToRead]; // An image
    NSData* pixelData = (NSData*) CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
   
    return pixelData;
   
   
}


@end

How to use it in your playstate:

Code: [Select]
    NSData* pixelData = [FlxImageInfo readImage:@"level1_map.png"];
    unsigned char* pixelBytes = (unsigned char *)[pixelData bytes];
    int j = 0;
    for(int i = 0; i < [pixelData length]; i += 4) {
        //if pixel is black
        if (pixelBytes[i]==0 && pixelBytes[i+1]==0 && pixelBytes[i+2]==0 && pixelBytes[i+3]==255) {
            //NSLog(@"Found a black Pixel");
            bl = [FlxTileblock tileblockWithX:( (j)%48)*10 y:((j) / 48) * 10 width:10 height:10]; 
            [bl loadGraphic:ImgGrapeTiles empties:0 autoTile:YES];
            [playerPlatforms add:bl];
            [enemyPlatforms add:bl];
        }
       
        //if pixel is red, use red value to make a HORIZONTAL block of that length
       
        else if (pixelBytes[i]>0 && pixelBytes[i+2]>0 && pixelBytes[i+3]==255) {
            //NSLog(@"Found a red+blue Pixel");
            int w = pixelBytes[i]*10;
            int h = pixelBytes[i+2]*10;
            bl = [FlxTileblock tileblockWithX:( (j)%48)*10 y:((j) / 48) * 10 width:w height:h]; 
            [bl loadGraphic:ImgGrapeTiles empties:0 autoTile:YES];
            [playerPlatforms add:bl];
            [enemyPlatforms add:bl];
        }
       
        //NSLog(@"PIXEL DATA %d %hhu %hhu %hhu %hhu %d %d", i, pixelBytes[i], pixelBytes[i+1], pixelBytes[i+2], pixelBytes[i+3], (j%48)*10, (j / 48) * 10 );
        j++;
    }
   

You'll also need to edit FlxTileblock to get a similar result to FlxTimemap.AUTO which I have done but I'll need to post that later.

« Last Edit: Mon, Aug 22, 2011 by initials »
Initials: Super Lemonade Factory, Super Lemonade Factory Part Two, Above The Clouds, Revvolvver, Four Chambers of the Human Heart

John Hutchinson (Johntron247)

  • Commodore 256
  • Contributor
  • ****
  • Posts: 392
  • Karma: +1/-0
  • I can has lazerz?
    • View Profile
    • Level X Games
Re: FlxTilemap for iOS
« Reply #1 on: Mon, Aug 22, 2011 »
This is very interesting work, Initials!  I'm curious, what's the reasoning behind doing it this way?

initials

  • Contributor
  • ****
  • Posts: 378
  • Karma: +0/-0
  • Initials
    • View Profile
    • Initials Blog. Code and other things.
Re: FlxTilemap for iOS
« Reply #2 on: Mon, Aug 22, 2011 »
Sorry this example isn't very good.
I'll try to post a complete project of it working.

The reason is that 1. I have no idea what I'm doing in Objective-C and 2. when I tried to make discreet squares like Flash does, it slowed down horribly. So rather than have each tile as a separate entity, I thought if you're going to have one long platform, might as well make it one long solid FlxTileblock.

I am getting a handle on Objective-C more and more so I might find that the slowdown I experienced can be reduced in other ways. It isn't optimal to be drawing a tile map and going "Ok, 10 across, 2 down, and I want this to be moving so R=10,G=2,B=10" and then sample the pixel in your map in an editor, then re-draw.

Are you after an example project of this?
Initials: Super Lemonade Factory, Super Lemonade Factory Part Two, Above The Clouds, Revvolvver, Four Chambers of the Human Heart

John Hutchinson (Johntron247)

  • Commodore 256
  • Contributor
  • ****
  • Posts: 392
  • Karma: +1/-0
  • I can has lazerz?
    • View Profile
    • Level X Games
Re: FlxTilemap for iOS
« Reply #3 on: Tue, Aug 23, 2011 »
Not at the moment, but thank you!  I just thought that your idea and implementation was really interesting and that I may be able to use a similar technique later on down the road.  I'm using AIR to target multiple platforms and so far the performance on Android and iOS is horrible (display architecture problem between the way that Flixel does it and the way that mobile devices prefer it set up... 8-15fps just will not do!), so I'm keeping my eyes open for potential optimizations.

initials

  • Contributor
  • ****
  • Posts: 378
  • Karma: +0/-0
  • Initials
    • View Profile
    • Initials Blog. Code and other things.
Re: FlxTilemap for iOS
« Reply #4 on: Tue, Aug 23, 2011 »
I agree. I found that a simple Flash game exported for iOS using various methods just didn't have the frame rate needed for an action game.
So I dove in to Objective-C. But learning ObjectiveC for targeting different platforms with one build isn't going to be helpful.
These mobile devices, while getting more and more powerful, still have limited memory, so you really do have to optimize things.
Initials: Super Lemonade Factory, Super Lemonade Factory Part Two, Above The Clouds, Revvolvver, Four Chambers of the Human Heart

jimmypaulin

  • Member
  • **
  • Posts: 13
  • Karma: +0/-0
    • View Profile
Re: FlxTilemap for iOS
« Reply #5 on: Sat, Mar 17, 2012 »
Hi, I have an alternative iOS FlxTilemap that might be of interest, I basically use a big FlxTileblock with proper tile indexing (i.e. not random, or autotiled) and read the data from CSV. I have a blog post about it http://superfluidgames.com/?p=56

I'd be very happy to share the method if anyone is interested

FYI I'm porting my Flash prototype (also in Flixel) -  http://forums.flixel.org/index.php/topic,5831.0.html

ATB

initials

  • Contributor
  • ****
  • Posts: 378
  • Karma: +0/-0
  • Initials
    • View Profile
    • Initials Blog. Code and other things.
Re: FlxTilemap for iOS
« Reply #6 on: Sat, Mar 17, 2012 »
That's a really nice looking game. I couldn't play it with a trackpad properly, but when I plug a mouse in I'll give it another shot.

I'd really love to see your version of FlxTilemap, it would be a great addition to the Flixel iOS community.
Initials: Super Lemonade Factory, Super Lemonade Factory Part Two, Above The Clouds, Revvolvver, Four Chambers of the Human Heart

jimmypaulin

  • Member
  • **
  • Posts: 13
  • Karma: +0/-0
    • View Profile
Re: FlxTilemap for iOS
« Reply #7 on: Sun, Mar 18, 2012 »
Thanks for the feedback! Yeah it's very much optimised for wasd + mouse; I'm going for a twin-stick approach for the iOS port, rather like minigore I think.

I'll post the tilemap code when I get a little further with it. It's just graphical tilemap data at the moment so no collision detection or pathfinding. Not sure how best to implement that yet- multiple invisible rectangles vs some kind of custom system using a quadtree, depends on performance I guess.

Very impressed with flixel iOS port so far, boards seem to have been a quiet recently...

Cheers

initials

  • Contributor
  • ****
  • Posts: 378
  • Karma: +0/-0
  • Initials
    • View Profile
    • Initials Blog. Code and other things.
Re: FlxTilemap for iOS
« Reply #8 on: Tue, Mar 20, 2012 »
Look forward to seeing your tilemap code.

If you are after a twin stick control scheme for iOS you'll need to make some modifications because Flixel iOS has no multi touch controls.

I have made a virtual control pad for the Mode demo. I've made some edits to FlxGame and FlxTouches to get that working.

https://github.com/initials/Mode-iOS

Also, it include iCade support if you are interested.

If you find ways to improve any of my code let me know.
Initials: Super Lemonade Factory, Super Lemonade Factory Part Two, Above The Clouds, Revvolvver, Four Chambers of the Human Heart

jimmypaulin

  • Member
  • **
  • Posts: 13
  • Karma: +0/-0
    • View Profile
Re: FlxTilemap for iOS
« Reply #9 on: Sun, Mar 25, 2012 »
Here is the code I use to generate tile maps for the top-down game I'm currently working on

FlxTilemap.h

Code: [Select]
   

@interface FlxTilemap : FlxObject
{
    // .... same as FlxTileblock, with these extra variables

    NSMutableArray *tiles;
   
    unsigned int collideIndex;
   
}

+ (id) tilemap;
- (id) init;   
- (id) loadMap:(NSString *)CSVFile
   tileGraphic:(NSString *)TileGraphic
  collideIndex:(unsigned int)CI;

- (void) parseTilemapData:(NSString *)MapData;

// ... same as FlxTileblock


FlxTilemap.m

Code: [Select]

// ... as FlxTileblock, with couple of tweaks and an extra function

- (id) loadMap:(NSString *)CSVFile
   tileGraphic:(NSString *)TileGraphic
  collideIndex:(unsigned int)CI;
{
    collideIndex=CI;
    if (TileGraphic == nil) {
        NSLog(@"can't load a null graphic");
        return self;
    }
   
    if (texture != nil) {
        [texture release];
        texture = nil;
    }
   
    NSString *MapData = [NSString stringWithContentsOfFile:CSVFile encoding:NSASCIIStringEncoding error:nil];

    if (MapData == nil) {
        NSLog(@"tilemap string is null");
        return self;
    }
   
    texture = [[FlxG addTextureWithParam1:TileGraphic param2:NO] retain];
    if (texture == nil) {
        NSLog(@"couldn't find texture");
        return self;
    }
   
    [self parseTilemapData:MapData];
   
    // TODO from parsed tilemap data
    widthInTiles=40;
    heightInTiles=30;
   
   
    tileSize = texture.height;
    //widthInTiles = ceil(width*1.0/tileSize);
    //heightInTiles = ceil(height*1.0/tileSize);
    width = widthInTiles * tileSize;
    height = heightInTiles * tileSize;
    //unsigned int numTiles = widthInTiles*heightInTiles;
    //unsigned int numGraphics = texture.width/tileSize;

   
    // Create single graphic large enough to hold entire tilemap
    // It will be composed of individual triangles, 2 per tile
    int m = widthInTiles;
    int n = heightInTiles;
   
    vertexCount = 4*m + (2 + 4*m)*(n-1);
    unsigned int newByteCount = sizeof(GLshort)*vertexCount*2*2;
    if (verticesUVs != NULL && newByteCount > byteCount)
        free(verticesUVs);
    if (verticesUVs == NULL || newByteCount > byteCount) {
        byteCount = newByteCount;
        verticesUVs = malloc(byteCount);
    }
   
    uShort = [FlxGLView convertToShort:tileSize/texture.paddedWidth];
    vShort = [FlxGLView convertToShort:tileSize/texture.paddedHeight];
   
    //for (unsigned int i = 0; i < numTiles; ++i) {
    int vi = 0;
    for (unsigned int j=0; j<n; ++j) {
        for (unsigned int i=0; i<m; ++i) {
            if (i == 0) {
                if (j != 0) { //add in 'stitching'
                    verticesUVs[vi] = (GLshort)(m*tileSize);
                    verticesUVs[vi+1] = (GLshort)(j*tileSize);
                    verticesUVs[vi+4] = (GLshort)(0);
                    verticesUVs[vi+5] = (GLshort)(j*tileSize);
                    //doesn't matter what uvs are set to
                    verticesUVs[vi+2] = verticesUVs[vi+3] = verticesUVs[vi+6] = verticesUVs[vi+7] = 0;
                    vi += 8;
                }
            }
            verticesUVs[vi] = (GLshort)(i*tileSize);
            verticesUVs[vi+1] = (GLshort)(j*tileSize);
            verticesUVs[vi+4] = (GLshort)(i*tileSize);
            verticesUVs[vi+5] = (GLshort)((j+1)*tileSize);
            verticesUVs[vi+8] = (GLshort)((i+1)*tileSize);
            verticesUVs[vi+9] = (GLshort)(j*tileSize);
            verticesUVs[vi+12] = (GLshort)((i+1)*tileSize);
            verticesUVs[vi+13] = (GLshort)((j+1)*tileSize);
            if (true/*FlxU.random * (numGraphics + Empties) > Empties || Empties == 0*/) {
               
                // Choose appropriate tile for this location
                int gi = [[tiles objectAtIndex:(i+j*widthInTiles)] intValue];
               
                if (texture.atlasTexture) {
                    verticesUVs[vi+2] = (GLshort)(gi*uShort*texture.atlasScale.x + texture.atlasOffset.x + 0.5);
                    verticesUVs[vi+3] = (GLshort)(0 + texture.atlasOffset.y + 0.5);
                    verticesUVs[vi+6] = (GLshort)(gi*uShort*texture.atlasScale.x + texture.atlasOffset.x + 0.5);
                    verticesUVs[vi+7] = (GLshort)(vShort*texture.atlasScale.y + texture.atlasOffset.y + 0.5);
                    verticesUVs[vi+10] = (GLshort)((gi+1)*uShort*texture.atlasScale.x + texture.atlasOffset.x + 0.5);
                    verticesUVs[vi+11] = (GLshort)(0 + texture.atlasOffset.y + 0.5);
                    verticesUVs[vi+14] = (GLshort)((gi+1)*uShort*texture.atlasScale.x + texture.atlasOffset.x + 0.5);
                    verticesUVs[vi+15] = (GLshort)(vShort*texture.atlasScale.y + texture.atlasOffset.y + 0.5);
                } else {
                    verticesUVs[vi+2] = gi*uShort;
                    verticesUVs[vi+3] = 0;
                    verticesUVs[vi+6] = gi*uShort;
                    verticesUVs[vi+7] = vShort;
                    verticesUVs[vi+10] = (gi+1)*uShort;
                    verticesUVs[vi+11] = 0;
                    verticesUVs[vi+14] = (gi+1)*uShort;
                    verticesUVs[vi+15] = vShort;
                }
            } else {
                //blank entry
                verticesUVs[vi+2] = verticesUVs[vi+3] = verticesUVs[vi+6] = verticesUVs[vi+7] =
                verticesUVs[vi+10] = verticesUVs[vi+11] = verticesUVs[vi+14] = verticesUVs[vi+15] = 0;
            }
            vi += 16;
        }
    }
    return self; 
}



- (void) parseTilemapData:(NSString *)MapData
{
   
   
    NSScanner *scanner = [NSScanner scannerWithString:MapData];
    [scanner setCharactersToBeSkipped:
     [NSCharacterSet characterSetWithCharactersInString:@"\n, "]];
    tiles = [NSMutableArray array];
    NSInteger tileType = 0;
   
   
    while ( [scanner scanInteger:&tileType])
    {

        [tiles addObject:[NSNumber numberWithInt:tileType]];
        //NSLog(@"Added tile %d", tileType);
    }
   
    NSLog(@"Loaded %d tiles", tiles.length);
 
   
   
}


And then in the PlayState.m

Code: [Select]

    NSString * rootPath = [levelName stringByAppendingString:@"_terrain"];
    NSString *filePath = [[NSBundle mainBundle] pathForResource:rootPath ofType:@"csv"];

    FlxTilemap * tileMap = [FlxTilemap tilemap];
    [tileMap loadMap:filePath tileGraphic:ImgTiles collideIndex:5];
    [self add:tileMap]


For the collision I recover all the "collidable" tiles from the tilemap by returning the tile location vectors in an NSMutableArray, and then I add a FlxObject to represent these. It is certainly not the most efficient way to do it but with quad tree collisions (has anyone (initials ;) ) got those working yet btw?) it should be fast enough.

In the current game I have three 40x30 tile tile maps (16x16 tiles) and a single 40x30 collision tile map and the framerate is fine (is it possible to enable debug mode and show FPS easily in the iOS flixel port?)

A good future optimisation would be to render the tile map into a texture and then draw to screen using a single quad (two tri's). In the current implementation a triangle-strip with all 1200 tiles (2400 triangles) is sent down the render pipeline - definitely not most efficient way for such static data as a tile map.

Hope that's of some use.

If you are after a twin stick control scheme for iOS you'll need to make some modifications because Flixel iOS has no multi touch controls.

I have made a virtual control pad for the Mode demo. I've made some edits to FlxGame and FlxTouches to get that working.

https://github.com/initials/Mode-iOS

Also, it include iCade support if you are interested.

If you find ways to improve any of my code let me know.

Thanks for that. I think I started with the mode fork rather than canabalt :D It wasn't too hard to enable multitouch, just added some lines to FlxGame

Code: [Select]
        FlxGLView * glView = [[FlxGLView alloc] initWithFrame:CGRectMake(0,0,window.bounds.size.width/2,window.bounds.size.height/2)];
       
        // ...
       
        glView.multipleTouchEnabled = YES;
        NSLog(@"multitouch: %d", glView.multipleTouchEnabled);

Picked up Super Lemonade Factory, excellent work!