くまちゃんのiOS/Androidゲームプログラミング

マイペースでゲームつくってます。

ドラクエ的RPGの基礎 マップの描画&当たり判定【Xcode5, iOS7】

f:id:tadakazu1972:20140223112342p:plain
以前にもエントリしましたが、以下の対応をしました。
・Xcode5, iOS7対応
・画面スクロール対応(画面端から2キャラ分先へ移動しようとすると上下左右スクロールする)
・当たり判定(海や山は移動できない)

これでいくらでもやる気になれば広大な世界を創造できますね。
ダンジョンや塔、城などはマップの配列をもう1次元追加すればよいだけですし。

DragonHackerView.h

//
//  DragonHackerView.h
//  DragonHacker
//
//  Created by 中道 忠和 on 2014/02/22.
//  Copyright (c) 2014年 中道 忠和. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface DragonHackerView : UIView {
    int WIDTH;
    int HEIGHT;
    CGContextRef context;
    NSMutableArray* draw;
    
    CGPoint lastPoint;

    int MAP[20][20];
    float mapx;
    float mapy;
    struct mapcollision {
        float l;
        float r;
        float t;
        float b;
    };
    struct mapcollision m;
    
    int touch_direction; // 0:stop 1:up 2:right 3:down 4:left
    
    struct mycharacter {
        float x;
        float y;
        float vx;
        float vy;
        float wx; //world x
        float wy;
        float l;
        float r;
        float t;
        float b;
    };
    struct mycharacter p;
    
    int gs;
    
}
@end

DragonHackerView.m

//
//  DragonHackerView.m
//  DragonHacker
//
//  Created by 中道 忠和 on 2014/02/22.
//  Copyright (c) 2014年 中道 忠和. All rights reserved.
//

#import "DragonHackerView.h"

@implementation DragonHackerView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
    }
    return self;
}

-(id)initWithCoder:(NSCoder*)coder {
    self=[super initWithCoder:coder];
    if (self) {
        CGRect bounds=[[UIScreen mainScreen] bounds];
        WIDTH = bounds.size.width;
        HEIGHT= bounds.size.height;
        // 画面の準備
        context=NULL;
        // キャラクターの絵を準備
        draw=[NSMutableArray array];                      //配列を設定
        [ draw addObject:[UIImage imageNamed:@"banner.png"]];  //0
        [ draw addObject:[UIImage imageNamed:@"kumako01.png"]];//1
        [ draw addObject:[UIImage imageNamed:@"sea.png"]];     //2
        [ draw addObject:[UIImage imageNamed:@"mountain.png"]];//3
        [ draw addObject:[UIImage imageNamed:@"green.png"]];   //4
        [ draw addObject:[UIImage imageNamed:@"earth.png"]];   //5
        //マイキャラの準備
        p.x=160.0f; //最初のX座標
        p.y=240.0f; //最初のY座標
        p.vx=0.0f;  //最初のX座標の移動量
        p.vy=0.0f;  //最初のY座標の移動量
        p.wx=160.0f;
        p.wy=240.0f;
        p.l=p.x; //当たり判定の設定
        p.r=p.x+31.0f;
        p.t=p.y;
        p.b=p.y+31.0f;
        // マップの準備
        [self setMap];
        // その他変数 初期設定
        touch_direction=0;
        gs=1;
        // 画面再描画タイマーの準備
        [NSTimer scheduledTimerWithTimeInterval:0.01f target:self selector:@selector(onTick:) userInfo:nil repeats:YES];
    }
    return self;
}

- (void)drawRect:(CGRect)rect
{
    // 画面の準備(まあ、呪文のようなものです。アブダカダブラ。)
    if (context!=NULL) {
        CGContextRelease(context);
        context=NULL;
    }
    context=UIGraphicsGetCurrentContext();
    CGContextRetain(context);
    // 画面を真っ黒に塗って、全部消す。(それからあとで背景とキャラクターを高速で描くから、アニメーションに見える)
    CGContextSetRGBFillColor(context,0,0,0,1);
    CGContextSetRGBStrokeColor(context,0,0,0,1);
    CGContextFillRect(context,CGRectMake(0,0,WIDTH,HEIGHT));
//通常ゲーム処理****************************************************************************************
    if (gs==1) {
        //マップの描画
        [self drawMap];
        //マイキャラの描画
        [self drawMycharacter];
        
//データ表示**************************************************************************************************
        // iPhone5以降の時は、画面の下にバナーを出す
        if ([[UIScreen mainScreen] bounds].size.height == 568){
            //[[draw objectAtIndex:0] drawAtPoint:(CGPointMake(0,481))];    //バナーを描画
        }
    } else if (gs==2) {
        
    }
}

//マップ設定
-(void)setMap {
    int map[20][20]={
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,2,2,2,3,2,2,2,2,0,0,2,2,1,1,1,1,1,1,0,
        0,2,2,2,3,2,2,2,2,0,0,2,1,1,1,2,2,1,0,0,
        0,2,2,2,3,3,2,2,2,2,0,2,1,3,3,2,2,1,0,0,
        0,2,2,2,2,3,3,2,2,2,0,2,1,1,3,3,3,1,0,0,
        0,0,2,2,2,2,3,2,2,2,0,2,1,1,3,3,3,1,1,0,
        0,0,2,2,2,2,3,2,2,2,0,2,1,2,3,3,3,1,1,0,
        0,0,2,2,2,2,3,1,1,1,0,2,1,2,3,3,3,1,1,0,
        0,2,2,2,2,2,3,1,1,1,0,2,2,2,2,3,3,3,1,0,
        0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,
        0,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,0,
        0,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,0,
        0,0,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,0,
        0,0,0,0,0,0,0,0,2,2,2,2,3,3,3,3,1,1,1,0,
        0,0,1,1,1,1,1,0,2,2,3,3,3,3,3,3,3,0,0,0,
        0,1,1,1,1,1,1,0,2,2,3,3,3,3,3,3,3,0,0,0,
        0,1,2,2,2,2,2,1,0,2,3,3,3,3,3,3,3,0,0,0,
        0,1,2,1,1,1,2,2,2,2,2,3,3,3,3,3,3,1,0,0,
        0,1,2,0,0,1,1,1,0,0,0,0,3,2,1,1,1,1,1,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    };
    for (int j=0;j<20;j++) {
        for (int i=0;i<20;i++)
            MAP[j][i]=map[j][i];
    }
    mapx=0.0f;
    mapy=0.0f;
    m.l=p.x;
    m.r=p.x+31.0f;
    m.t=p.t;
    m.b=p.b;
}

//マップ描画
-(void)drawMap {
    for (int j=0;j<20;j++) {
        for (int i=0;i<20;i++) {
            int mapid=MAP[j][i];
            if (mapid==0) [[draw objectAtIndex:2] drawAtPoint:(CGPointMake(i*32+mapx,j*32+mapy))];
            if (mapid==1) [[draw objectAtIndex:3] drawAtPoint:(CGPointMake(i*32+mapx,j*32+mapy))];
            if (mapid==2) [[draw objectAtIndex:4] drawAtPoint:(CGPointMake(i*32+mapx,j*32+mapy))];
            if (mapid==3) [[draw objectAtIndex:5] drawAtPoint:(CGPointMake(i*32+mapx,j*32+mapy))];
        }
    }
}

//マイキャラ処理
-(void)drawMycharacter {
    //マイキャラ描画
    [[draw objectAtIndex:1] drawAtPoint:(CGPointMake(p.x,p.y))];
    //マイキャラ移動処理
    //タッチ操作の方向に応じて移動量を代入しておく
    //if (touch_direction==0) { p.vx= 0.0f; p.vy=0.0f; }
    if (touch_direction==1) { p.vy=-1.0;  p.vx=0.0f; }
    if (touch_direction==2) { p.vx= 1.0f; p.vy=0.0f; }
    if (touch_direction==3) { p.vy= 1.0f; p.vx=0.0f; }
    if (touch_direction==4) { p.vx=-1.0f; p.vy=0.0f;}
    //まず当たり判定を移動させる(ワールド座標を使用することに留意)
    p.l=p.wx+p.vx; p.r=p.wx+31.0f+p.vx;
    p.t=p.wy+p.vy; p.b=p.wy+31.0f+p.vy;
    //当たり判定用マップ座標を計算
    m.l=p.l/32.0f; if (m.l<0)  m.l=0;  //配列添字オーバー防止
    m.r=p.r/32.0f; if (m.r>19) m.r=19;
    m.t=p.t/32.0f; if (m.t<0)  m.t=0;
    m.b=p.b/32.0f; if (m.b>19) m.b=19;
    //カベ判定
    if (MAP[(int)m.t][(int)m.l]<2 || MAP[(int)m.t][(int)m.r]<2 || MAP[(int)m.b][(int)m.l]<2 || MAP[(int)m.b][(int)m.r]<2) {
        //カベがあるので移動量をリセット
        p.l=p.wx-p.vx; p.r=p.wx+31.0f-p.vx;
        p.t=p.wy-p.vy; p.b=p.wy+31.0f-p.vy;
        p.vx=0.0f; p.vy=0.0f;
    }
    //マップスクロール処理
    p.x=p.x+p.vx;
    if (p.x<64.0f)  { p.x=64.0f;  mapx++; if (mapx>64.0f)   mapx=64.0f; }
    if (p.x>224.0f) { p.x=224.0f; mapx--; if (mapx<-384.0f) mapx=-384.0f; }
    p.y=p.y+p.vy;
    if (p.y<64.0f)  { p.y=64.0f;  mapy++; if (mapy>64.0f)   mapy=64.0f; }
    if (p.y>384.0f) { p.y=384.0f; mapy--; if (mapy<-224.0f) mapy=-224.0f; }
    //ワールド座標処理
    p.wx=p.wx+p.vx;
    if (p.wx<0.0f) p.wx=0.0f;
    if (p.wx>640.0f) p.wx=640.0f;
    p.wy=p.wy+p.vy;
    if (p.wy<0.0f) p.wy=0.0f;
    if (p.wy>640.0f) p.wy=640.0f;
}

// 画面再描画
-(void)onTick:(NSTimer*)timer {
    [self setNeedsDisplay];
}

// タッチ操作
-(void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
    // タッチした座標を取得
    CGPoint point=[[touches anyObject] locationInView:self.superview];
    // タッチ操作量はX方向とY方向のどちらが多いか絶対値で比較する(lastPointとpの座標を取得する構造がゆえに)
    float temp_vx, temp_vy;
    temp_vx=abs(lastPoint.x-point.x);
    temp_vy=abs(lastPoint.y-point.y);
    if (temp_vx > temp_vy) {
        if (lastPoint.x-point.x<0) {
            touch_direction=2;
        } else {
            touch_direction=4;
        }
    } else {
        if (lastPoint.y-point.y<0) {
            touch_direction=3;
        } else {
            touch_direction=1;
        }
    }
    // さっき取得したタッチの座標をラストポイントに書き換えておく。
    lastPoint = point;
}

-(void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
    touch_direction=0;
}

-(void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
}

@end

https://itunes.apple.com/jp/app/kumachanjanpu2/id789138227?mt=8&uo=4&at=10l8JW&ct=hatenablog