触れた人と会話する

前回から少し時間が開いてしまいましたが、今回はキャラクターに話をさせてみます。

event.jsonファイルでデータを定義しています。

{
    "c1" : {
        "type" : "message",
        "message" : {
            "en" : "hello, world.",
            "ja" : "よくきた、○○よ。待って追ったぞ。ここは××研究所。これから旅に出るお主に、託したいものがあって呼んだのじゃ。その宝箱の中身を持って行くがよい。世界の平和を頼んだぞ。"
        }
    }
}

接触時のdelegateメソッドで、playerNodeがc1という名前のNodeに触れた時に、メッセージの内容を表示するようにしています。

- (void)didBeginContact:(SKPhysicsContact *)contact {
    SKPhysicsBody *firstBody, *secondBody;
    
    if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
        firstBody = contact.bodyA;
        secondBody = contact.bodyB;
    } else {
        firstBody = contact.bodyB;
        secondBody = contact.bodyA;
    }
    
    if ((firstBody.categoryBitMask & playerCategory) != 0) {
        if ((secondBody.categoryBitMask & characterCategory) != 0) {
            SJCharacterNode *node = (SJCharacterNode *)secondBody.node;
            NSString *name = node.name;
            NSDictionary *e = [self event][name];
            if ([e[@"type"] isEqualToString:@"message"]) {
                _state = SJShopSceneStateMessage;
                [self messageNode].message = e[@"message"][[SJUtilities lang]];
                [self messageNode].hidden = NO;
            }
        }
    }
}

メッセージの表示は、新たに追加したSJMessageNodeに担当させています。

SKLabelNodeが複数行表示に対応していないため、行数分並べて表示するという泥臭い実装になっています。 特別なことをしていない上に無駄に長いため、引用はやめておきます。

無事、キャラクターに話しかけることができるようになりました。


唐突な会話

次は他のシーンへの遷移を実装します。

ソースコード: sj-prototype-apps/SJRolePlaying at master · tnantoka/sj-prototype-apps

コメント

Spkite Kitの物理エンジン向けデバッグドロー用ライブラリ「PhysicsDebugger」

Sprite Kitには物理エンジンが組み込まれており、簡単に物理シミュレーションが利用できて便利です。
ただ、デバッグ表示用の機能がないため、どこにPhysicsBodyを追加したか、見た目では知ることができません。

そんな不満を解決してくれるのが、PhysicsDebuggerです。

早速使ってみます。

インストール

CocoaPodsに対応してるので簡単です。

# ターミナルを起動してプロジェクトのフォルダまで移動

# CocoaPodsをインストールしてない人
$ gem install cocoapods
$ pod install 

# Podfile
platform :ios, '7.0'

pod 'PhysicsDebugger'

# インストール
$ pod
Analyzing dependencies
Downloading dependencies
Installing PhysicsDebugger (1.0.0)
Generating Pods project
Integrating client project

[!] From now on use `SJRolePlaying.xcworkspace`.

CocoaPodsを初めて使う場合、これ以降.xcodeprojではなく、.xcworkspaceを開く必要があることに注意してください。

利用方法

ヘッダをインポートして、描画対象のノードが作成される前に、initを呼び、 drawPhysicsBodiesを呼びます。

今回は、SJMapNodeに追加してみました。

#import "YMCPhysicsDebugger.h"
#import "YMCSKNode+PhysicsDebug.h"

- (void)createNodeContents {
    
    [YMCPhysicsDebugger init];

    /* ノードの作成・追加 */
    
    [self drawPhysicsBodies];
}

これで実行すると、以下のようにPhysicsBodyに赤枠がついて可視化されます。


赤枠が表示される

感想

手軽に使えて便利でした。

ただ、ちょっとコードに手を加える量が多いかなぁ、という印象。 まぁそれでも、毎回PhysicsBodyと同じ大きさのShapeNodeを作成して追加する、とかに比べればはるかに綺麗ですが。

あと、このライブラリに限った話ではないですが、デバッグドローのぶんNodeの数が増えて動作がもっさりになるので、常に使用しながらの開発は厳しいかもしれません。

しばらく使って様子を見てみようと思います。

コメント

お店の中に他のキャラクターを登場させる

今日はマップ内に他のキャラクターを表示してみます。

前回はSJCharacgterNodeの中に定数でいろいろと持たせていましたが、 characters.jsonというファイルで定義するようにしました。1

{
    "default" : {
        "stop_time" : 0.6,
        "walk_time" : 0.3,
        "speed": 0.2
    },
    "c0" : {
        "name" : "clotharmor",
        "stop_row" : 0,
        "stop_cols" : 2,
        "walk_row" : 1,
        "walk_cols" : 4,
        "up_row" : 3,
        "right_row" : 6,
        "left_row" : 6,
        "size": 64
    },
    "c1" : {
        "name" : "scientist",
        "stop_row" : 0,
        "stop_cols" : 2,
        "walk_row" : 0,
        "walk_cols" : 2,
        "up_row" : 0,
        "right_row" : 0,
        "left_row" : 0,
        "size": 48
    },
    "c2" : {
        "name" : "chest",
        "stop_row" : 0,
        "stop_cols" : 1,
        "walk_row" : 0,
        "walk_cols" : 1,
        "up_row" : 0,
        "right_row" : 0,
        "left_row" : 0,
        "size": 32
    }
}

マップデータでどこに配置するか指定します。

-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,c2,c1,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,c0,-,-,-,-,-

SJMapNode内で、cから始まるデータの場合はキャラクターを生成するようにします。

} else if ([col hasPrefix:@"c"]) {
    tileSprite = [[SJCharacterNode alloc] initWithCharacterNamed:col];

    tileSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(TILE_SIZE, TILE_SIZE)];
    tileSprite.physicsBody.affectedByGravity = NO;
    tileSprite.physicsBody.allowsRotation = NO;

    tileSprite.name = col;

} 

SJCharacterNodeではjsonから設定を読み取ります。 この値を定数の代わりに使います。

- (id)initWithCharacterNamed:(NSString *)name {
    
    NSString *path = [[NSBundle mainBundle] pathForResource:CHARACTERS_NAME ofType:@"json"];
    NSData *data = [NSData dataWithContentsOfFile:path];
    NSError *error = nil;
    NSDictionary *characters = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
    if (error) {
        NSLog(@"%@", [error localizedDescription]);
    }
    NSMutableDictionary *character = [characters[@"default"] mutableCopy];

    [character addEntriesFromDictionary:characters[name]];

    CGFloat size = [character[@"size"] floatValue];
    
    if (self = [super initWithColor:nil size:CGSizeMake(size, size)]) {
        _character = character;
        [self createNodeContents];
    }
    return self;
}

完成です。


ひとりじゃない

相変わらず全然お店ではないですが、見た目はゲームっぽくなってきました。

次は会話を実装する予定です。

ソースコード: sj-prototype-apps/SJRolePlaying at master · tnantoka/sj-prototype-apps

  1. Objective-C的にはplistを使うのが定石だと思いますが、個人的にplistが苦手なのでここではJSONにしています。 

コメント

Mac OS X 10.9 Mavericksがリリースされました

ついにリリースされましたね。

正直なところ、そこまで魅力は感じていなかったんですが、まさかの無料ということで早速アップグレードしました。 今まで作ったサンプルが、最新版のXcodeでも問題なく動いていて一安心です。

残念ながら、このタイミングでも、日本語ドキュメント - Apple DeveloperにSprite Kit Programming Guideは追加されませんでした。 こうなると、しばらくは翻訳されなさそうです…。

NDA期間も終わったことだし、今後は機会があればMac向けの情報も扱っていきたいと思います。

コメント

物理エンジンを使ってお店の中を歩く

今回は、キャラクターをマップ内で歩かせてみます。

以下が完成イメージです。 ややぎこちないですが、リアルタイムバトルをやるわけでもないので、よしとします。


お店の中を歩く

通行状態とプレイヤーの位置を設定するために、マップデータにレイヤーを追加しています。

x,x,x,x,x,x,x,x,x,x
x,x,x,x,x,x,x,x,x,x
x,x,x,x,o,o,o,o,x,x
x,x,o,o,o,o,o,o,x,x
x,x,o,o,o,o,o,o,x,x
x,x,o,o,o,o,o,o,x,x
x,x,o,o,o,o,o,o,x,x
x,x,o,o,o,o,o,o,x,x
x,x,o,o,o,o,o,o,x,x
x,x,o,o,o,o,o,o,x,x
x,x,o,o,o,o,o,o,x,x
x,x,x,x,o,o,x,x,x,x
x,x,x,x,o,o,x,x,x,x

-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,-,-,-,-,-,-
-,-,-,-,p,-,-,-,-,-

oは通行可で何もしません。xは通行不可なので、phsyicsBodyを設定したSKNodeを配置します。
pがプレイヤーの場所でphysicsBodyを指定したNodeを配置します。-は何もなしです。

以下がその部分のコード抜粋です。

if ([col isEqualToString:@"o"]) continue;
if ([col isEqualToString:@"-"]) continue;

SKNode *tileSprite;

if ([col isEqualToString:@"x"]) {
    tileSprite = SKNode.new;
    
    tileSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(TILE_SIZE, TILE_SIZE)];
    tileSprite.physicsBody.dynamic = NO;
    
} else if ([col isEqualToString:@"p"]) {
    tileSprite = [SJCharacterNode characterNode];
    tileSprite.name = kPlayerName;

    tileSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(TILE_SIZE, TILE_SIZE)];
    tileSprite.physicsBody.affectedByGravity = NO;
    tileSprite.physicsBody.allowsRotation = NO;
    
}

なお、今回から、マップの処理をSJMapNodeという専用クラスに任せるように変更しています。 上のコードもSJMapNode内のものです。

プレイヤーは新たに追加したSJCharacterNodeを使っています。 アニメーションはチュートリアルのサンプルゲームとほぼ同じロジックなので、ここでは移動の処理を抜粋します。

といっても、特に特別なことはやっておらす、画面がタップされたら、x軸・y軸の順でその方向を向いて歩いていくだけです。移動はタイルサイズ(32px)の単位で行なうにしています。

アニメーションはもちろんSKActionを利用。
また、物理エンジンの衝突を利用することで、通行状態を考慮する必要がく、コードがシンプルになっています。1

- (void)moveTo:(CGPoint)location {
    
    NSMutableArray *actions = @[].mutableCopy;
    CGPoint diff = CGPointMake(floor((location.x - self.position.x) / TILE_SIZE), floor((location.y - self.position.y) / TILE_SIZE));
    
    CGFloat x = diff.x * TILE_SIZE;
    CGFloat y = diff.y * TILE_SIZE;
    
    SKAction *moveX = [SKAction moveByX:x y:0 duration:abs(diff.x) * SPEED];
    SKAction *moveY = [SKAction moveByX:0 y:y duration:abs(diff.y) * SPEED];
    
    SKAction *walk = [SKAction runBlock:^{
        [self walk];
    }];
    SKAction *stop = [SKAction runBlock:^{
        [self stop];
    }];
    
    SKAction *turnX = [SKAction runBlock:^{
        if (diff.x > 0) {
            _direction = SJCharacterDirectionRight;
        } else if (diff.x < 0){
            _direction = SJCharacterDirectionLeft;
        }
    }];
    SKAction *turnY = [SKAction runBlock:^{
        if (diff.y > 0) {
            _direction = SJCharacterDirectionUp;
        } else if (diff.y < 0){
            _direction = SJCharacterDirectionDown;
        }
    }];
    
    [actions addObject:turnX];
    [actions addObject:walk];
    [actions addObject:moveX];

    [actions addObject:turnY];
    [actions addObject:walk];
    [actions addObject:moveY];
    
    [actions addObject:stop];
    
    SKAction *sequence = [SKAction sequence:actions];
    
    [self runAction:sequence withKey:MOVE_KEY];
}

これでキャラクターが歩けるようになりました。

ソースコードは、sj-prototype-apps/SJRolePlaying at master · tnantoka/sj-prototype-appsです。

まだまだ続きます。

  1. physicsBodyを設定した物体同士はデフォルトで衝突するため、プレイヤーはxのマスに移動できない。 

コメント

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