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

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

「縦シューティング」【Xcode5, iOS7】

f:id:tadakazu1972:20140202173444p:plain
たまには、こういうのもいいんじゃないでしょうか。
ゼビ○スに衝撃を受けた世代としては、一度は自分で作りたいと思ったものです。
背景の「逆スクロール」(死語)がキモですね。
画像ファイルは爆風などがあってそれなりに数が必要ですが…。
ここから敵の種類の追加、敵が弾を打つようになる、パワーアップなど追加するとニギヤカにできるかと。
あ、当たり判定がプログラム的にはずぼらかましたせいで計算量多くなってますので、敵の数を100機とかにふやすとiPhone5sでもモッサリしはじめてしまいます。スピードアップさせるためには前回エントリーの矩形で判定のアルゴリズムを採用するほうがよさげですね;

StarphenixView.h

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

#import <UIKit/UIKit.h>
#define N 5 //アステロイドの数
#define M 20 //敵の数

@interface StarphenixView : UIView {
    int WIDTH;
    int HEIGHT;
    CGContextRef context;
    NSMutableArray* draw;
    
    CGPoint lastPoint;
    
    float px; // prayer x position
    float py; // prayer y position
    float vx;
    float vy;
    int   hp;
    
    struct shot {
        float x;
        float y;
        float vx;
        float vy;
        int   visible;
        int   type;
        int   power;
    };
    struct shot s[16];
    int shotcount;
    
    struct explosion {
        float x;
        float y;
        int   visible;
        float count;
    };
    struct explosion e[16];
     
    struct asteroid {
        float x;
        float y;
        float vx;
        float vy;
        int   star;
    };
    struct asteroid b[N];
    
    struct alien {
        float x;
        float y;
        float vx;
        float vy;
        float wx;
        float wy;
        int   dd;
        float xtr;
        float ytr;
        float qtr;
        float dr;
    };
    struct alien a[M];
    int alien_speed;

    int MAP[20][10];
    float mapx;
    float mapy;
    
    int touch_direction; // 0:stop 1:up 2:right 3:down 4:left
    int time;
    int time2;
    int gs;
    int highscore;
    int score;
}

@end

StarphenixView.m

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

#import "StarphenixView.h"
#define SPEED 2.0f
#define R 2
#define APPLE_SPEED 0.5f
#define STAR 16
#define WX 100
#define WY 100

@implementation StarphenixView

- (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:@"phenix.png"]];  //0
        [ draw addObject:[UIImage imageNamed:@"asteroid.png"]];//1
        [ draw addObject:[UIImage imageNamed:@"alien01.png"]]; //2
        [ draw addObject:[UIImage imageNamed:@"alien01.png"]]; //3
        [ draw addObject:[UIImage imageNamed:@"green.png"]];   //4
        [ draw addObject:[UIImage imageNamed:@"earth.png"]];   //5
        [ draw addObject:[UIImage imageNamed:@"sea.png"]];     //6
        [ draw addObject:[UIImage imageNamed:@"shot.png"]];    //7
        [ draw addObject:[UIImage imageNamed:@"bang01.png"]];  //8
        [ draw addObject:[UIImage imageNamed:@"bang02.png"]];  //9
        [ draw addObject:[UIImage imageNamed:@"bang03.png"]];  //10
        [ draw addObject:[UIImage imageNamed:@"bang04.png"]];  //11
        [ draw addObject:[UIImage imageNamed:@"bang05.png"]];  //12
        [ draw addObject:[UIImage imageNamed:@"star.png"]];    //13
        // タイマーの準備
        [NSTimer scheduledTimerWithTimeInterval:0.01f target:self selector:@selector(onTick:) userInfo:nil repeats:YES];
        // タイマー(自機の弾発射)の準備
        [NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(onShot:) userInfo:nil repeats:YES];
        // 自機の準備
        px=160.0f;   //最初のX座標
        py=400.0f; //最初のY座標
        vx=0.0f;   //最初のX座標の移動量
        vy=0.0f;  //最初のY座標の移動量
        hp=10;     //自機のHP
        // 自機の弾の準備
        for (int si=0;si<16;si++) {
            s[si].x=px;
            s[si].y=py-16.0f;
            s[si].vx=0.0f;
            s[si].vy=-10.0f;
            s[si].visible=1;
            s[si].type=0; // 0:normal
            s[si].power=1;
        }
        shotcount=0;
        // 爆風の準備
        for (int ei=0;ei<16;ei++) {
            e[ei].x=0.0f;
            e[ei].y=0.0f;
            e[ei].visible=0;
            e[ei].count=0.0f;
        }
        // アステロイドの準備
        for (int i=0;i<N; i++) {
            b[i].x=rand()%320;
            b[i].y=rand()%100;
            //float dist = sqrt((px-b[i].x)*(px-b[i].x)+(py-b[i].y)*(py-b[i].y));
            //if (dist==0) dist=5; // distが0の時は次の割り算ができなくなるためその予防
            //b[i].vx=(px-b[i].x)/dist*SPEED;
            //b[i].vy=(py-b[i].y)/dist*SPEED;
            b[i].vx=0.0f;
            b[i].vy=(float)(rand()%3+1)*SPEED;
            b[i].star=rand()%STAR;
        }
        // 敵の準備
        for (int j=0;j<M; j++) {
            a[j].x=rand()%320;
            a[j].xtr=(-1*M_PI)/2;
            a[j].y=rand()%100;
            a[j].ytr=(-1*M_PI)/2;
            a[j].dd=R;
            a[j].wx=WX;
            a[j].wy=WY;
            a[j].dr=M_PI*a[j].dd/180;
        }
        alien_speed=R;
        // 背景の準備
        for (int my=0;my<20;my++) {
            for (int mx=0;mx<10;mx++) {
                MAP[my][mx]=rand()%5;
            }
        }
        mapx=0.0f;
        mapy=576.0f;
        // その他変数 初期設定
        touch_direction=0;
        time=0;
        time2=0;
        gs=1;
        score=0;
        // ハイスコアの準備
        highscore=0;
        // 呼び出し
        NSNumber* _highscore=[[NSUserDefaults standardUserDefaults] objectForKey:@"highscore"];
        if (_highscore!=nil) highscore=[_highscore intValue];
    }
    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));
    // iPhone5以降の時は、画面の下にバナーを出すようにする。(もともとiphone4Sの画面サイズでつくってたから、すきまができてしまう)
    if ([[UIScreen mainScreen] bounds].size.height == 568){
        //[[p_bmp objectAtIndex:5] drawAtPoint:(CGPointMake(0,455))];    //バナーを描画
    }
//通常ゲーム処理****************************************************************************************
    if (gs==1) {
        // 時間の処理
        time=time+1;
        time2=time2+1;
//背景の処理********************************************************************************************
        // 背景の描画
        for (int my=0;my<20;my++) {
            for (int mx=0;mx<10;mx++) {
                int mapid=MAP[my][mx];
                if (mapid==0||mapid==1||mapid==2) [[draw objectAtIndex:4] drawAtPoint:(CGPointMake(mx*32,my*(-32)+mapy))];
                if (mapid==3) [[draw objectAtIndex:5] drawAtPoint:(CGPointMake(mx*32,my*(-32)+mapy))];
                if (mapid==4) [[draw objectAtIndex:6] drawAtPoint:(CGPointMake(mx*32,my*(-32)+mapy))];
            }
        }
        mapy=mapy+1.0f;
        if (mapy>608.0f) {
            mapy=576.0f;
            for (int my2=1;my2<20;my2++) {
                for (int mx2=0;mx2<10;mx2++) {
                    MAP[my2-1][mx2]=MAP[my2][mx2];
                }
            }
            //最終列の配列新規設定
            for (int mx3=0;mx3<9;mx3++) MAP[19][mx3]=rand()%5;
        }
//アステロイド処理********************************************************************************************
        // アステロイド描画
        for (int i=0; i<N; i++) {
            if (b[i].star==0) {
                [[draw objectAtIndex:13] drawAtPoint:(CGPointMake(b[i].x,b[i].y))];
            } else {
                [[draw objectAtIndex:1] drawAtPoint:(CGPointMake(b[i].x,b[i].y))];
            }
            // アステロイド移動
            b[i].x=b[i].x+b[i].vx;
            b[i].y=b[i].y+b[i].vy+time2/1000+time/5000;
            
            if (b[i].x <0.0f || b[i].x>320.0f || b[i].y>536.0f) {
                b[i].x=(int)rand()%320;
                b[i].y=0.0f;
                
                //float dist = sqrt((px-b[i].x)*(px-b[i].x)+(py-b[i].y)*(py-b[i].y));
                //if (dist==0.0f) dist=SPEED; // distが0の時は次の割り算ができなくなるためその予防
                //b[i].vx=(px-b[i].x)/dist*SPEED;
                //b[i].vy=(py-b[i].y)/dist*SPEED;
                b[i].vx=0.0f;
                b[i].vy=(float)(rand()%3+1)*SPEED;
                b[i].star=rand()%STAR;
            }
            // 自機とのぶつかりチェック
            if (((px+15.0f)-(b[i].x+15.0f))*((px+15.0f)-(b[i].x+15.0f))+((py+15.0f)-(b[i].y+15.0f))*((py+15.0f)-(b[i].y+15.0f)) < (10+10)*(10+10)) {
                if (b[i].star==0) { //星に願いを
                    hp=hp+3;
                } else {
                    // 自機をどん!
                    py=py+32.0f;
                    if (py>536.0f) py=536.0f;
                    // 自機のHPを減らす
                    hp=hp-1; if (hp<1) gs=2;
                    // 爆風設定
                    e[i].visible=1;
                    e[i].x=b[i].x;
                    e[i].y=b[i].y;
                }
                // ぶつかったアステロイドは再登場
                b[i].x=(int)rand()%320;
                b[i].y=0.0f;
                b[i].star=rand()%STAR;
            }
            // 自機の弾とのぶつかりチェック
            for (int si=0;si<16;si++) {
                if (((s[si].x+15.0f)-(b[i].x+15.0f))*((s[si].x+15.0f)-(b[i].x+15.0f))+((s[si].y+15.0f)-(b[i].y+15.0f))*((s[si].y+15.0f)-(b[i].y+15.0f)) < (10+10)*(10+10)) {
                    s[si].visible=0;
                    s[si].x=px;
                    s[si].y=py-16.0f;
                }
            }
        }
//敵の処理********************************************************************************************
        // 敵描画
        for (int j=0; j<M; j++) {
            [[draw objectAtIndex:2] drawAtPoint:(CGPointMake(a[j].x,a[j].y))];
            // X移動量計算
            a[j].qtr=a[j].xtr;
            a[j].xtr=a[j].xtr+a[j].dr;
            a[j].vx=a[j].wx*(cosf(a[j].xtr)-cosf(a[j].qtr));
            // Y移動量計算
            a[j].vy=a[j].wy*(sin(a[j].xtr)-sin(a[j].qtr));
            // 移動量を足し込む
            a[j].x=a[j].x+a[j].vx;
            a[j].y=a[j].y+a[j].vy+APPLE_SPEED;
            // 画面の一番下にいったら再設定
            if (a[j].y>568 ) {
                a[j].x=rand()%320;
                a[j].xtr=M_PI/2;
                a[j].y=0;
                a[j].ytr=(-1*M_PI)/2;
                a[j].dd=alien_speed;
                a[j].wx=WX;
                a[j].wy=WY;
                a[j].dr=M_PI*a[j].dd/180;
            }
            // 自機とのぶつかりチェック
            if (((px+15.0f)-(a[j].x+15.0f))*((px+15.0f)-(a[j].x+15.0f))+((py+15.0f)-(a[j].y+15.0f))*((py+15.0f)-(a[j].y+15.0f)) < 400.0f) {
                // 自機をどん!
                if (vx>0) {px=px-30.0f; if (px<0.0f) px=0.0f;} else {px=px+30.0f; if(px>288.0f)px=288.0f;}
                if (vy>0) {py=py-30.0f; if (py<0.0f) py=0.0f;} else {py=py+30.0f; if(py>536.0f)py=536.0f;}
                // 爆風設定
                e[j].visible=1;
                e[j].x=a[j].x;
                e[j].y=a[j].y;
                // 自機のHPを減らす
                hp=hp-1;
                if (hp<1) gs=2;
                // ぶつかった敵は再設定
                a[j].x=rand()%320;
                a[j].xtr=M_PI/2;
                a[j].y=0;
                a[j].ytr=(-1*M_PI)/2;
                a[j].dd=alien_speed;
                a[j].wx=WX;
                a[j].wy=WY;
                a[j].dr=M_PI*a[j].dd/180;
            }
            // 自機の弾とのぶつかりチェック
            for (int si=0;si<16;si++) {
                if (((s[si].x+15.0f)-(a[j].x+15.0f))*((s[si].x+15.0f)-(a[j].x+15.0f))+((s[si].y+15.0f)-(a[j].y+15.0f))*((s[si].y+15.0f)-(a[j].y+15.0f)) < (10+10)*(10+10)) {
                    // 得点
                    score=score+100;
                    // 爆風設定
                    e[si].visible=1;
                    e[si].x=s[si].x;
                    e[si].y=s[si].y;
                    // 弾再設定
                    s[si].visible=0;
                    s[si].x=px;
                    s[si].y=py-16.0f;
                    // ぶつかった敵は再設定
                    a[j].x=rand()%320;
                    a[j].xtr=M_PI/2;
                    a[j].y=0;
                    a[j].ytr=(-1*M_PI)/2;
                    a[j].dd=alien_speed;
                    a[j].wx=WX;
                    a[j].wy=WY;
                    a[j].dr=M_PI*a[j].dd/180;
                }
            }
        }
//自機の処理********************************************************************************************
        // 自機の描画
        [[draw objectAtIndex:0] drawAtPoint:(CGPointMake(px,py))];
        // 自機移動処理
        // タッチ操作の方向に応じて移動量を代入しておく
        if (touch_direction==1) { vy=-3.0f; vx=0.0f; }
        if (touch_direction==2) { vx= 3.0f; vy=0.0f; }
        if (touch_direction==3) { vy= 3.0f; vx=0.0f; }
        if (touch_direction==4) { vx=-3.0f; vy=0.0f; }
        px=px+vx;
        if (px<0.0f) px=0.0f;
        if (px>288.0f) px=288.0f;
        py=py+vy;
        if (py<0.0f) py=0.0f;
        if (py>536.0f) py=536.0f;
//弾の処理********************************************************************************************
        // 弾の描画と移動
        for (int si=0;si<16;si++) {
            if (s[si].visible==1) {
                [[draw objectAtIndex:7] drawAtPoint:(CGPointMake(s[si].x,s[si].y))];
                // 移動処理
                s[si].y=s[si].y+s[si].vy;
                if (s[si].y<-32) { s[si].y=py; s[si].visible=0; }
            } else {
            //弾の移動処理
            s[si].x=px;
            s[si].y=py-16.0f;
            }
        }
//爆風の処理********************************************************************************************
        // 爆風の描画
        for (int ei=0;ei<16;ei++) {
            if (e[ei].visible==1) {
                int explosionid=(int)e[ei].count+8;
                [[draw objectAtIndex:explosionid] drawAtPoint:(CGPointMake(e[ei].x,e[ei].y))];
                // 爆風アニメーション処理
                e[ei].count=e[ei].count+0.3f;
                 if (e[ei].count>5.0f) {
                     e[ei].count=0;
                     e[ei].visible=0;
                 }
            }
        }
//得点表示**************************************************************************************************
        // スコアの描画
        NSString* s1=[NSString stringWithFormat:@"SCORE %d", score];
        UIFont* font = [UIFont boldSystemFontOfSize:20];
        [s1 drawAtPoint:CGPointMake(100,20) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName,[UIColor yellowColor],NSForegroundColorAttributeName,nil]];
        // ハイスコアの描画
        NSString* s3=[NSString stringWithFormat:@"HIGH SCORE %d", highscore];
        UIFont* font3 = [UIFont boldSystemFontOfSize:15];
        [s3 drawAtPoint:CGPointMake(100,0) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font3, NSFontAttributeName,[UIColor redColor],NSForegroundColorAttributeName,nil]];
        // HPの描画
        NSString* s2=[NSString stringWithFormat:@"HP=%d", hp];
        UIFont* font2 = [UIFont boldSystemFontOfSize:20];
        [s2 drawAtPoint:CGPointMake(250,20) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font2, NSFontAttributeName,[UIColor whiteColor],NSForegroundColorAttributeName,nil]];
//ゲームオーバー処理********************************************************************************************
    } else if (gs==2) {
        // 背景の描画
        for (int my=0;my<20;my++) {
            for (int mx=0;mx<10;mx++) {
                int mapid=MAP[my][mx];
                if (mapid==0||mapid==1||mapid==2) [[draw objectAtIndex:4] drawAtPoint:(CGPointMake(mx*32,my*(-32)+mapy))];
                if (mapid==3) [[draw objectAtIndex:5] drawAtPoint:(CGPointMake(mx*32,my*(-32)+mapy))];
                if (mapid==4) [[draw objectAtIndex:6] drawAtPoint:(CGPointMake(mx*32,my*(-32)+mapy))];
            }
        }
        // 自機の描画
        [[draw objectAtIndex:0] drawAtPoint:(CGPointMake(px,py))];
        // TEXT
        NSString* s1=[NSString stringWithFormat:@"SCORE %d", score];
        UIFont* font2 = [UIFont boldSystemFontOfSize:20];
        [s1 drawAtPoint:CGPointMake(100,20) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font2, NSFontAttributeName,[UIColor yellowColor],NSForegroundColorAttributeName,nil]];
        // TEXT
        NSString* s2=[NSString stringWithFormat:@"GAME OVER"];
        NSString* s3=[NSString stringWithFormat:@"TOUCH RESTART!"];
        UIFont* font3 = [UIFont boldSystemFontOfSize:30];
        [s2 drawAtPoint:CGPointMake(80,200) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font3, NSFontAttributeName,[UIColor redColor],NSForegroundColorAttributeName,nil]];
        [s3 drawAtPoint:CGPointMake(40,260) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font3, NSFontAttributeName,[UIColor yellowColor],NSForegroundColorAttributeName,nil]];
        // ハイスコア処理
        if (score>=highscore) {
            highscore=score;
            // 記録更新メッセージ
            NSString* s4=[NSString stringWithFormat:@"HIGH SCORE!"];
            UIFont* font4 = [UIFont boldSystemFontOfSize:30];
            [s4 drawAtPoint:CGPointMake(60,100) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font4, NSFontAttributeName,[UIColor greenColor],NSForegroundColorAttributeName,nil]];
        }
        // 保存
        [[NSUserDefaults standardUserDefaults] setObject:@(highscore) forKey:@"highscore"];
        [[NSUserDefaults standardUserDefaults] synchronize];
        if (touch_direction==0) {
            // 自機再設定
            px=160.0f;   //最初のX座標
            py=400.0f; //最初のY座標
            vx=0.0f;   //最初のX座標の移動量
            vy=0.0f;  //最初のY座標の移動量
            hp=10;
            // アステロイド再設定
            for (int i=0;i<N; i++) {
                b[i].x=(int)rand()%320;
                b[i].y=(int)rand()%100;
                //float dist = sqrt((px-b[i].x)*(px-b[i].x)+(py-b[i].y)*(py-b[i].y));
                //if (dist==0) dist=5; // distが0の時は次の割り算ができなくなるためその予防
                //b[i].vx=(px-b[i].x)/dist*SPEED;
                //b[i].vy=(py-b[i].y)/dist*SPEED;
                b[i].vx=0.0f;
                b[i].vy=(float)(rand()%3+1)*SPEED;
                b[i].star=rand()%STAR;
            }
            // その他変数 初期設定
            touch_direction=0;
            time=0;
            time2=0;
            gs=1;
            score=0;
            alien_speed=R;
        }
    }
}

// タイマー
-(void)onTick:(NSTimer*)timer {
    [self setNeedsDisplay];
}

// タイマー(自機の弾の発射)
-(void)onShot:(NSTimer*)timer {
    // 弾の発射処理
    s[shotcount].visible=1;
    shotcount=shotcount+1;
    if (shotcount>15) shotcount=0;
}

// タッチパネル操作
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
    lastPoint = [[touches anyObject] locationInView:self.superview];
    if (gs==2) touch_direction=0;
}

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

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

-(void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
    [self touchesEnded:touches withEvent:event];
}


@end

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