簡易タイルマップでお店風の背景を表示

自作RPGのリリースに向けて開発を進めていきたいと思います。 まずは店風の背景を表示してみます。

使う素材は、BrowserQuestのtilesheet.pngです。

この画像は、32x32のタイルが縦に20個、横に98個並んだものです。 扱いやすくするためにそれぞれ番号を振ります。

せっかくなのでこれもSprite Kitでやります。

static const CGFloat TILE_SIZE = 32.0f;
static const CGFloat SCALE = 0.75f;

static NSString * const BG_NAME = @"bg";

- (void)createSceneContents {
    self.backgroundColor = [SKColor darkGrayColor];
    
    SKTexture *tilesheet = [SKTexture textureWithImageNamed:@"tilesheet"];
    
    SKSpriteNode *bgSprite = [SKSpriteNode spriteNodeWithTexture:tilesheet];
    bgSprite.xScale = bgSprite.yScale = SCALE;
    bgSprite.anchorPoint = CGPointMake(0, 0);
    bgSprite.name = BG_NAME;
    [self addChild:bgSprite];
    
    NSInteger cols = tilesheet.size.width / TILE_SIZE;
    NSInteger rows = tilesheet.size.height / TILE_SIZE;
    
    for (int i = 0; i < cols; i++) {
        for (int j = 0; j < rows; j++) {
            CGPoint position = CGPointMake(i * TILE_SIZE, j * TILE_SIZE);
            
            SKLabelNode *pointLabel = [SKLabelNode labelNodeWithFontNamed:@""];
            pointLabel.text = [NSString stringWithFormat:@"%d", i + j * cols];
            pointLabel.position = CGPointMake(position.x + TILE_SIZE / 2.0f, position.y + TILE_SIZE / 2.0f);
            pointLabel.fontSize = 14.0f;
            pointLabel.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
            [bgSprite addChild:pointLabel];
        }
    }
}

透過されている部分がわかりやすいようにSceneに背景色をつけています。

そして、tilesheet.pngからSKSpriteNodeを作成し、anchorPointを左下に設定してSceneに追加します。 そのままだと大きくて見づらいので、xScale・yScaleで調整しています。

その後、32px毎にSKLabelNodeで番号を表示していきます。 これで以下のような画面になります。


各タイルに番号を

それでは、この番号を使ってマップを作成します。

まずは、マップデータです。 今回はCSVファイルで表現します。

504,505,506,506,506,507,507,507,508,509
484,485,486,486,486,487,487,487,488,489
464,465,466,466,466,467,467,467,468,469
464,465,466,466,466,467,467,467,468,469
464,465,466,466,466,467,467,467,468,469
464,465,466,466,466,467,467,467,468,469
444,445,446,446,446,447,447,447,448,449
444,445,446,446,446,447,447,447,448,449
444,445,446,446,446,447,447,447,448,449
444,445,446,446,446,447,447,447,448,449
444,445,446,446,446,447,447,447,448,449
424,425,426,-1,446,447,-1,427,428,429
404,405,406,-1,-1,-1,-1,407,408,409

-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
-1,-1,285,286,-1,-1,-1,-1,-1,-1
-1,-1,265,266,-1,-1,-1,-1,-1,-1
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
-1,-1,1124,1125,1125,1125,1125,1126,-1,-1
-1,-1,1104,1105,1105,1105,1105,1106,-1,-1
-1,-1,1104,1105,1105,1105,1105,1106,-1,-1
-1,-1,1104,1105,1105,1105,1105,1106,-1,-1
-1,-1,1084,1085,1085,1085,1085,1086,-1,-1
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
-1,-1,-1,345,346,346,347,-1,-1,-1
-1,-1,-1,325,326,326,327,-1,-1,-1
-1,-1,-1,305,306,306,307,-1,-1,-1

あとはこれを読み込んで表示するだけです。

- (void)createSceneContents {
    
    NSString *shop = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"shop" ofType:@"csv"]  encoding:NSUTF8StringEncoding error:nil];
    
    SKTexture *tilesheet = [SKTexture textureWithImageNamed:@"tilesheet"];
    
    NSArray *layers = [shop componentsSeparatedByString:@"\n\n"];
    
    for (NSString *layer in layers) {

        NSArray *rows = [[[layer componentsSeparatedByString:@"\n"] reverseObjectEnumerator] allObjects];
        for (int i = 0; i < rows.count; i++) {
            NSString *row = rows[i];
            NSArray *cols = [row componentsSeparatedByString:@","];
            for (int j = 0; j < cols.count; j++) {
                
                NSInteger col = [cols[j] integerValue];
                
                if (col > -1) {
                    CGFloat x = col % (NSInteger)MAP_COLS * TILE_SIZE / tilesheet.size.width;
                    CGFloat y = col / (NSInteger)MAP_COLS * TILE_SIZE / tilesheet.size.height;
                    CGFloat w = TILE_SIZE / tilesheet.size.width;
                    CGFloat h = TILE_SIZE / tilesheet.size.height;
                    
                    CGRect rect = CGRectMake(x, y, w, h);
                    SKTexture *tile = [SKTexture textureWithRect:rect inTexture:tilesheet];

                    SKSpriteNode *tileSprite = [SKSpriteNode spriteNodeWithTexture:tile];

                    CGPoint position = CGPointMake(j * TILE_SIZE, i * TILE_SIZE);
                    tileSprite.anchorPoint = CGPointMake(0, 0);
                    tileSprite.position = position;

                    [self addChild:tileSprite];
                    
                }
            }
        }

    }
}

やっていることは単純でCSVを空行区切りでレイヤーにわけて、 あとは順番に、番号に合うタイルをtextureWithRect:inTexture:使って表示しているだけです。

なお、-1は何も表示しないという意味にしています。

これで以下のように表示できます。


お店?

ゲームに使うには、各タイルの通行可否フラグなど保持したりしないといけないので、 TileクラスやMapクラスが必要になってくると思います。

今日のところは表示するところまで。

ソースコードは、sj-prototype-apps/SJRolePlaying at master · tnantoka/sj-prototype-appsにあります。

rectの計算で割り切れないことなどが原因で、ノイズが出てしまうのが気になるところ。
解消できたらまた書きます。

コメント

大きなサイズのテクスチャが真っ黒になる

Xcode 5.0で遭遇した問題。

BrowserQuestのtilesheetを使って、マップを表示しようとしてたんですが、何度やってもうまくいかなくて困りました。

いろいろ試していて判明したのは、大きなサイズ(容量ではなくピクセル数)を指定したSKTextureが真っ黒になるということでした。


試したサイズ


4096は表示される


4097は真っ黒になる


幅を広げても4096は問題なし


幅を小さくしても4097はやっぱりダメ

こんな感じで、4097ピクセル以上になると表示されなくなってしまうようです。 幅を変えても関係なさそうだったので、1辺の長さが問題になる模様。

SKTexture Class Referenceには特に何も書いていない気がしますし、エラーログなども出ていません。

サイズを小さくするしかない?
そもそも全ての環境で発生する問題なのかも不明ですが…。

ソースコードは、
sj-posts-apps/SJLargeTexture at master · tnantoka/sj-posts-apps
に置いてありますので、ご自由にどうぞ。

何かわかったらまた書きます。


Hirohito Katoさんから、コメント欄で原因を教えていただきました。
GPUの制限により4096x4096までのサイズしか扱えないためのようです。

参考

情報提供ありがとうございました!

コメント

チュートリアルのEPUB版を公開しました

Gumroadからダウンロードできます。

iBooksで読むとこのようになります。
アニメーションGIFもちゃんと動くので、インタラクティブな感じで楽しめるかもしれません。


0円以上に設定しているため、無料でもダウンロード可能ですのでお気軽にご利用ください。

コメント

Apple公式のSprite Kit関連情報まとめ

WWDC

ガイド

サンプルコード

リファレンス

今のところ、公式の情報はこれで全てだと思います。
プログラミングガイドの日本語訳が待ち遠しいですね。

iOS 7 Tech Talksの資料も公開されるといいんですが。

コメント

Sprite Kit関連の日本語サイトまとめ

おすすめ

その他

今後も参考になるサイトを見つけたら紹介していきたいと思います。1

  1. 2013-9-19 以前に公開されているものは除外しています。 

コメント

SpriteKitではじめる2Dゲームプログラミング Swift対応 (Smart Game Developer)