2015年3月30日月曜日

図形描画

昔のパソコンみたいにLine文とか書いて簡単にドローグラフィックが描けないのが、ちょいめんどくさい。

基本的には、UIViewのサブクラスを自作してやり、その中のdrawRectメソッドで書き換え処理を行うこととなる。

作ったサブクラスは、Storyboardで画面全体に貼ってあるViewのCustom Classにくくりつけてやれば画面全体をドローキャンバスにできるし、StoryboardにUIViewを貼ってそれにくくりつけてやれば画面の一部だけをドローキャンバスにできる。

以下、RestOfTimeViewというUIViewのサブクラスにドロー画像を表示させる場合。
画面全体に貼ってあるViewにRestOfTimeViewをくくりつけてる。


サブクラス側には
- (void)drawRect:(CGRect)rect {}
メソッドのひな形がコメントアウトされた状態で書かれてるので、コメントを外して使う。
このメソッドがiOSが決めた画面更新のタイミングに従って呼ばれ、書き換えられるわけだ。
このメソッドを直接呼び出すことは禁止されてるので、プロパティを使って中の値にアクセスするとともに、呼出元のViewControllerから
setNeedsDisplayメソッドを使って画面更新を指示する。

RestOfTimeViewにrestOfTimeというプロパティがあった場合、そこにViewControllerからアクセスするには、RestOfTimeViewを#importした後に、以下のようにする。
ともに(RestOfTimeView *)を頭につけて()でくくり、自作カスタムクラスにキャスト(変換)してやらないとエラーになっちゃうことに注意。
キャストしないとself.viewもしくは_restOfTimeは、ただのUIViewクラスのままだからだそうだが、よくわからん。

その1:画面全体に貼ってあるUIViewにくくりついてる場合
((RestOfTimeView *)self.view).restOfTime = 30;

その2:画面の一部に新たに貼られたUIView(restOfTimeプロパティ)にくくりついてる場合
((RestOfTimeView *)(_restOfTime)).restOfTime = 30;

そのままではすぐに画面更新してくれないので、setNeedsDisplayメソッドをviewに送ってサブクラス中のdrawRectメソッドの実行を要求し、画面を更新させる。

その1の場合
[self.view setNeedsDisplay];


その2の場合
[_restOfTime setNeedsDisplay];


さて、実際のサブクラスの中身。
Storyboardを使った場合と、コードでViewをalloc、initした場合とではイニシャライザが違う。
drawRectメソッドの中では、この例では
UIBezierPath *rectangle = [UIBezierPath bezierPathWithRect:self.bounds];
で四角形のパスを作り、

setStrokeで線の色を、setFillで塗りつぶしの色を、setLineWidthで線の太さをそれぞれ指定し、fillstrokeでそれぞれ線の描画、塗りつぶし描画をしてる。
他にも線をつなげた描いたり、円を描いたり、角の丸い四角を描いたり、ベジェ曲線を描いたりいろいろできる。

@implementation RestOfTimeView

//StoryboardからViewを読み込む場合
- (id)initWithCoder:(NSCoder *)aDecoder {
    if ((self = [super initWithCoder:aDecoder])) {
        self.backgroundColor = [UIColor yellowColor];
    }
    return self;
}

//コードでViewを作る場合
- (id)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
    //initialization code
        self.backgroundColor = [UIColor purpleColor];
    }
    return self;
}

- (void)drawRect:(CGRect)rect {
    // Drawing code
    //ベースの色
    [[UIColor blueColor] setStroke];
    [[UIColor redColor] setFill];
    UIBezierPath *rectangle = [UIBezierPath bezierPathWithRect:self.bounds];
    [rectangle setLineWidth:2];
    [rectangle fill];
    [rectangle stroke];
    
    //残り時間
    CGFloat restTimeWidth = self.bounds.size.width / 30 * _restOfTime;
    CGRect restTimeRect = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, restTimeWidth, self.bounds.size.height);
    rectangle = [UIBezierPath bezierPathWithRect:restTimeRect];
    [[UIColor blueColor] setFill];
    [rectangle fill];
}

@end

重ね重ね、8ビットパソコンの頃よりめんどくさいんだけど、ガマンガマン。

2015年3月26日木曜日

処理を一定時間止める

同期処理で、一定時間処理を待たせたり、数秒間何かを表示してから消すとかそういう時に使える。

以下2秒間処理停止させる書き方をいくつか。

・NSThreadを使う方法
[NSThread sleepForTimeInterval:2.0f];
Objective-Cでの基本的な書き方はこれ。

Swiftだと
NSThread.sleepForTimeInterval(2.0)


・sleep関数を使う方法
sleep(2);
でも同じことができる。こっちはC言語の関数なんだろう。

上の2つは結果は一緒みたいで、画面更新まで止まるようだ。直前に書き換えた画面は停止が明けないと更新されない。
処理的にはどっちでもいいんだろうけど、NSThread使ったほうがiOS的には推奨だったりするのかな?

・NSRunLoopを使う方法
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0f]];

ちなみにSwiftだと
NSRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 2.0))
NSRunLoopってのは要するにアプリのバックで回り続けてる入出力とかの監視をしてるプロセスのようだ。よくわかってないけど。
こっちは画面更新は止まらないので、直前に書き換えたものをちゃんと表示してくれる。
逆に、書いた部分のコードの進行以外は実行されちゃうようなので、注意。

直前の画面書き換えを反映させてから処理を停止させる方法
画面更新はOSが定期的(数十分の1秒ごととか)にUIViewのdrawRectメソッドを実行することで行われるので、画面書き換え(ラベルのテキスト変更とかも)処理直後に[NSThread sleepForTimeInterval:2.0f];なんて呼ぶと更新される間もなく停止されちゃうのだね。

だから、
_label.text = @"hogehoge";
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.05f]];
で画面更新のタイミング分コードの進行を待たせた後に
[NSThread sleepForTimeInterval:2.0f];
で処理を停止させてやればいいのだね。
めんどっちーけど。

なお、
[NSThread sleepForTimeInterval:2.0f];
を使った停止中でも、画面をいじるとその操作がバッファされてるようで、停止明けに実行されちゃったりするので、ボタンとかをイネーブルにするなど、別途対策を取られたし。

2015年3月20日金曜日

効果音を鳴らす

正解したら「ピンポ〜ン」、不正解なら「ブッ!」といった簡単なSEを鳴らしたい場合。
21世紀にもなったのに、やり方がちょっととっても面倒なのでまとめる。
なお、Swift 2.0もわかった点だけ後述

鳴らすためのサウンドファイルはどっかから用意すること。GarageBandあたりで作ってもよし。

ファイルをXcodeにドラッグしてプロジェクトに登録するまでは画像なんかと一緒。
しかし次が重要
ドラッグして追加した場合、Build PhasesのCopy Bundle Resourcesで、「+」ボタンを押してサウンドファイルを指定してやらないと、コード中でpathが掴めなくてエラーになってしまう。
ただし、ファイルメニューの「Add Files to "プロジェクト名"」で追加した場合は自動的にCopy Bundle Resourcesに追加されるので、不要のようだ。こっちの方が楽だな。

サウンドファイルを選んでAddを押す。

これでちゃんとサウンドファイルが認識されるようになる。

必要なFrameworkはAVFoundation.frameworkなので、インポートしとくこと。


ヘッダーファイルのinterface部でAVAudioPlayerDelegateの設定が必要。
@interface GameViewController : UIViewController <AVAudioPlayerDelegate>

実装ファイルの方でも工夫が必要。
サウンド用のインスタンス変数を用意。(後述)
NSMutableArray *soundArray; //SEのインスタンスを一時的にしまっておく
AVAudioPlayer *soundPlayer; //SE再生用

AVAudioPlayerってので再生するのだが、ローカル変数では、ARCで動かすせいで、メソッドを抜けるとすぐに変数が解放されてしまうため、音を鳴らすところまでいってくれないのだ。
ということでサウンドのインスタンスはインスタンス変数にしまって保持するのだが、一つのインスタンス変数だけでは、今度は前の音が終了するまで次の音が鳴らなくなってしまう。ギギギ…(-_-;)
そこで、NSMutableArrayに同時に鳴らす数だけのインスタンス変数をしまって保持するようにする。
そして音が鳴り終わった時に呼ばれるありがたいdelegateメソッドによって、不要なインスタンス変数をNSMutableArrayから消すのだ。

//サウンドの再生
- (void)playSoundWithResource:(NSString *)resource ofType:(NSString *)type{
    NSString *path = [[NSBundle mainBundle]pathForResource:resource ofType:type];
    
    soundPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:path] error:nil];
    soundPlayer.delegate = self;
    soundPlayer.volume = 0.4;
    soundPlayer.numberOfLoops = 0; //連続再生回数。0で1回、1で2回…、-1で無限ループ
    [soundPlayer prepareToPlay]; //あらかじめメモリに展開するなど、すぐ再生できるよう準備
    [soundPlayer play];
    [soundArray insertObject:soundPlayer atIndex:0]; //ARCでメソッド終了後に消されないようにインスタンス変数の配列に格納
}

//鳴り終わったら呼ばれるdelegateメソッド
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
    //鳴り終わったSEのインスタンスを消す
    [soundArray removeObject:player];
}

上記のplaySoundWithResource:〜メソッドは自作なので、実際に音を鳴らす際は、どっかから以下のように呼び出す。
[self playSoundWithResource:@"correct" ofType:@"m4a"];

いちいちめんどくせえだろ? 8ビットパソコンでBEEPとだけ書けば良かったものが、この手間の数。世の中間違っとるよね。Swiftではどうなのか知らんけど。


BGMを鳴らす場合

ゲームなんかでBGMを鳴らしっぱなしにする際は、
soundPlayer.numberOfLoops = -1;
にしてやりゃいい。

BGMをGarageBandで作ると無音部分が追加される

共有でiTunesに書き出して、そのファイルを使うことになるわけだけど、共有メニューからiTunesやディスクに書き出すと、曲の最後に1秒程度の無音部分が勝手に追加されてしまう。
おかげで、BGM2巡目に入る前に音が途切れることとなり甚だよろしくない。
音の切り貼りができるソフト(PRO版のQuickTime7 Playerとか)を使って残響音のところを消してしまえば問題はないが、めんどくさいし、QTPlayerだと波形が見られないからちょうどいいところで削除できなかったり、AIFFかWaveなどでしか保存できないみたい。

40秒という制限はあるが、GarageBandの共有から着信音として書き出すと、連続再生に向いた、無音部分のないものが書き出せる。

なお、Xcodeにとっかえひっかえ曲のファイルを入れて試していたら、新しく追加した無音部分なしファイルが認識されず、差し替えたはずの無音部分付きファイルが再生されることがあって、やっぱりダメなのかと迷ったことがあった。
プロダクトのクリーン、Xcode再起動で直ったが、こういうことはXcodeではよくあるね。

Swift 2.0の場合

・AVFoundation.frameworkのインポートは必要
import AVFoundation

・ヘッダー部分でのAVAudioPlayerDelegateの記述は必要
class ViewController: UIViewController, AVAudioPlayerDelegate {

・インスタンス変数を定義しておくのも必要
    var soundPlayer:AVAudioPlayer? //SE再生用

・Swift 2.0の仕様変更でエラー処理の仕方が変わったため、AVAudioPlayer使用時に
do 〜 catch内にtry文を書く必要がある
よく知らんのだけど、try後に書いたメソッドでエラーが発生したら、catch後の処理を実行するのだね。
    func playSoundWithResource(resource:String, type:String) {
        let path = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(resource, ofType: type)!)
        do {
            soundPlayer = try AVAudioPlayer(contentsOfURL: path)
            soundPlayer!.delegate = self
            soundPlayer!.volume = 0.4
            soundPlayer!.numberOfLoops = 0 //0で1回再生
            soundPlayer!.prepareToPlay()
            soundPlayer!.play()
        } catch {
            print("sound再生でエラー発生")
        } 
    }

tryのところは以下でもいいみたい。
try soundPlayer = AVAudioPlayer(contentsOfURL: path)

まあ後はテキトーに調べて書こう。

2015年3月19日木曜日

オブジェクトのタッチ位置検出とドラッグ

単純に位置を得る場合
タッチイベントの検出は、以下のメソッドで。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    CGPoint dustPoint = [[touches anyObject]locationInView:self.view];
}

[touches anyObject]
はNSSetからUITouchのインスタンスを取り出している。よくわからんけど、そうなるの。
んで、
[[touches anyObject]locationInView:self.view];
によって、自分のviewの座標の位置に変換している。これもよくわからんけど、そうなるからいいの。
よって、dustPointを見ればタッチした位置がわかる。

touchesBegan:〜はタッチした瞬間の位置なので、
指を離した時、
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
タッチしたまま指を動かした時、
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
はそれぞれ別メソッドで。


オブジェクトをドラッグしたりする場合
複数のカスタムクラスによるUIオブジェクトの、タッチしたものだけドラッグしたりする場合は、delegate使う。
カスタムクラス側でdelegateの記述を書く。
例)
Dust.h
@protocol DustDelegate
- (void)dustTouchMoved:(Dust *)dust;
@end

@interface Dust : UIImageView
@property (weak) id delegate; //独自delegate
@property (assign) CGPoint touchPoint; //位置
@end

Dust.m
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    //タッチされた位置をpropertyに入れてdelegateされる
    _dustPoint = [[touches anyObject]locationInView:nil];

    if ([self.delegate respondsToSelector:@selector(dustTouchMoved:)]) {
        [self.delegate dustTouchMoved:self];
    }
}
プロパティのtouchPointにタッチ位置を入れておいてやれば、delegateで渡された先でも位置がわかる。
locationInView:nil
nilになってることに注意。self.viewもしくはself(viewを持たない部品の場合)のままでは自分のUIの左上からの座標になってしまう。

ただし、nilを指定したまま画面の縦横を変えると(landscape、portrate)座標が左下からになってしまうという情報も? 自分で試してみたところならなかったみたいだけど、念のため注意。

参考サイト:Cocoaの日々

んで、delegate先に以下のメソッドを書いておけば、指の位置にズルズルとUIオブジェクトがくっついてくるぞ。
(カスタムクラスdustをallocした際に、dust.delegate = self; とやっとくのは当然)
- (void)dustTouchMoved:(Dust *)dust
{
    //property dustPointにタッチされた位置が入ってる
    dust.center = dust.touchPoint;
}

2015年3月17日火曜日

Objective-Cの多次元配列

Objective-Cでの2次元配列の使い方

配列に設定
2次元配列の場合
NSArray *dustName = 
          [NSArray arrayWithObjects:@[@"A"@"B"@"C"],
                                    @[@"D"@"E"@"F"]nil];
これで3×2の2次元配列ができる。[]がそれぞれ1次元。中のA, B, Cなどがそれぞれ2次元。
中身へのアクセスは、
NSLog(@"%@",dustName[0][1]); //結果は「B」
などとなる。
古い書き方で
NSLog(@"%@",[[dustName objectAtIndex:0] objectAtIndex:1]);
とも書けるが益はない。



3次元配列の場合
NSArray *dustName
      [NSArray arrayWithObjects:@[@[@"A", @"B"], @[@"C", @"D"]],
                                @[@[@"E", @"F"], @[@"G", @"H"]],nil];
これで2×2×2の3次元配列ができる。
[]がそれぞれ1次元。[]がそれぞれ2次元。中のA, Bなどがそれぞれ3次元。
中身へのアクセスは、
NSLog(@"%@",dustName[0][1][0]); //結果は「C」
NSLog(@"%@",dustName[0][1][1]); //結果は「D」
古い書き方では
NSLog(@"%@",[[[dustName objectAtIndex:0] objectAtIndex:1] objectAtIndex:1]);

このように多次元配列はできるけど、定義も使用もややこしいので、あまりデータ量が多くなるなら、カスタムクラスを使うなど別の方法を考えたほうがいいかも。

Delegateの仕組み

「委譲」などと訳されるObjective-Cのdelegate。(辞書で引くと「派遣」と出た)
正式な仕組みや意味的なものはともかく、何かのイベントが起こった時にそのメソッドが実行されるので、イベントハンドラーのようなものと考えてればいい。
書き方はわかったけど、根本的仕組みがよくわからんのでまとめ。

Objective-Cの場合

基本的使い方

ヘッダファイル.h のinterfaceのところで、○○Delegateと宣言してやらないといけないことが多い。
これは後で知ったんだけど、delegateメソッドを宣言してるプロトコルってやつなんだね。詳しくはプロトコルのところで。
例)
@interface DetailTableViewController : UITableViewController <MFMailComposeViewControllerDelegate, ADBannerViewDelegate, UITextFieldDelegate, UITextViewDelegate, UIActionSheetDelegate, CLLocationManagerDelegate, MKMapViewDelegate>

実装ファイル.m の中では、あらかじめ関連するクラスのdelegateプロパティにselfを指定する。(delegateメソッドの場所がその実装ファイル内にあるということ)
例)
    //returnキーでキーボードを隠すためのdelegate
    _titleTextField.delegate = self;
    _detailTextView.delegate = self;
    _mapView.delegate = self;
    
    //iAddelegate
    _iAdView.delegate = self;

同じ実装ファイル内にイベント発生時に呼ばれるdelegateメソッドを記述する。
メソッド名はそれぞれ決まっているので、どのイベントで何が呼ばれるかはあらかじめ調べておかないといけない。
例)
//textFieldreturnが押されたらキーボードを隠すdelegateメソッド
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    [textField resignFirstResponder];
    return YES;
}

//textView編集終了時、キーボードを隠し、元データを更新するdelegateメソッド
- (BOOL)textViewShouldEndEditing:(UITextView *)textView
{
    _incidentData.text = _detailTextView.text; //元データ更新
    //ファイル保存
    [appDelegate writeFile];

    [textView endEditing:YES];
    return YES;
}

TableView関係のdelegateなどは、ヘッダファイルでの宣言、実装ファイルでのdelegateプロパティにselfの指定などは省略できるようだ。たぶんどこか見えないところでされてるんだとは思うが。
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.objects.count;
}

カスタムクラスでの独自Delegateの実装方法

DustというImageViewの独自クラスをタッチしたら、メインのGameViewControllerでラベルにメッセージを表示するコード。

Dustのヘッダファイルの記述

protocolの宣言と、delegateメソッド名の設定、delegateプロパティの設定をする。
(protocolを外部ファイルにしてある場合は、それを#importする)
//独自delegateの設定
@class Dust;
@protocol DustDelegate
- (void)dustTouched:(Dust *)dust;
@end

@interface Dust : UIImageView
@property (weak) id delegate; //独自delegate
@end

Dustの実装ファイルのdelegateメソッドを発生させるメソッドに、GameViewController内にdelegateメソッドが実装されていたらそれを呼ぶコードを書く。
respondsToSelector:メソッドは、:@selector()で指定したメソッドが存在するかの判定。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //delegate先(GameViewController)にdustTouched:メソッドが実装されていれば、
    //自分(Dust)を引数にそいつを呼ぶ
    if ([self.delegate respondsToSelector:@selector(dustTouched:)]) {
        [self.delegate dustTouched:self];
    }
}

GameViewControllerのヘッダファイルでは特にdelegateに関する宣言なし。

実装ファイル内でDustのインスタンスを作る際にdelegateにself(GameViewController)を指定する。
    //Dustクラスを作る
    Dust *dust = [[Dust alloc]initWithImage:image];
    dust.delegate = self; //touch時に呼ばれるdelegateメソッドの場所の指示

実装ファイル内にdelegateメソッドを書く。以上。
//Dustがタッチされたら呼ばれるdelegate
- (void)dustTouched:(Dust *)dust
{
    _dustNameLabel.text = @"タッチされた";
}


仕組みの解説


Dust.hのprotocolの設定で、メソッドの引数に自分自身を指定していることと、プロパティで汎用のid型を使い、あらゆるクラスから呼ばれてもいいようにしているのが肝。

GameViewController.mで
dust.delegate = self;
としているので、dustのdelegateプロパティにはGameViewController自身が入る(②)

GameViewController内でDustインスタンスが作られ、画面でタッチされると、Dust内のtouchesBegan:〜メソッドが動く。(③)
if ([self.delegate respondsToSelector:@selector(dustTouched:)]) {〜}
によるチェックで、delegateプロパティに入っているのはGameViewController自身なので、当然dustTouched:メソッドは存在することになる。(④)

そしてDust.mのtouchesBegan:〜メソッド中の
[self.delegate dustTouched:self];
において、self(Dust)のdelegateプロパティに入っているクラス(GameViewController)内のdustTouched:メソッドを実行させている。(⑤)
引数にself(Dust)を渡してあるので、dustTouched:メソッドの中でもDustのいろいろなプロパティを使用することができる。(ここでは記述してないが)

つまり、GameViewControllerを親、Dustを子とした場合、親を指定して子を呼び、子の中で親にdelegateメソッドがあるかどうかをチェックし、あれば親の中のそのdelegateメソッドを実行しているわけだ。

dust.delegate = self;
というコードが単に「delegateメソッドの場所の指示」というわけではなく、dustのプロパティdelegateにGameViewController自身(正確にはそのポインタ)を入れているわけだ。
いろいろややこしいが。

他のイベント時に別のdelegateメソッドを発生させたかったら、Dustクラス中の記述(ヘッダ、実行ファイルとも)と、GameViewController中の対応するdelegateメソッドを記述すればいいわけだ。


Swiftの場合

protocolを宣言し、delegate変数にselfを入れてメソッドを呼び出すとかはObjective-Cと一緒。
以下はSpriteNode Coinをタッチしたら親ノードに書かれたdelegateメソッドを呼び出す例。

//delegate用プロトコルの宣言
protocol CoinDelegate: class {
    //delegateメソッドの定義
    func touchCoinBegan(coin: Coin)
    func touchCoinEnded(coin: Coin)
}

class Coin: SKSpriteNode {
    override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
        delegate?.touchCoinBegan(self)
    }
    
    override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
        delegate?.touchCoinEnded(self)
    }
}

なお、コードの中で
delegate?.touchCoinBegan(self)
は、Objective-Cの
if ([self.delegate respondsToSelector:@selector(dustTouched:)]) {
        [self.delegate dustTouched:self];
    }
にあたる部分。
やってることはちょっと違うんだけどね。
SwiftにもrespondsToSelectorメソッドはあるんだけど、まだ俺が書き方がわかんないし、オプショナル「?」で、変数delegateがnilじゃないか調べ、nilじゃなければtouchCoinBeganメソッドを実行させる方が簡単でSwiftらしい書き方のようだ。
プロトコル実装してればdelegate先にそのメソッドがないことはあり得ないしね。


んで、delegateを呼ぶ親ノード側。
ちゃんと最初にCoinDelegateプロトコルを使う旨最初に宣言。
Objective-Cの時は、なんか書かなくても動いちゃったけど、Swiftだと書かないとダメみたい。
class GameScene: SKScene, CoinDelegate { //自作delegateのプロトコルを宣言
    var 開始時刻:CFTimeInterval = 0
    var 射出力:CGFloat = 0
    var coin:Coin = Coin()
    
    override func didMoveToView(view: SKView) {
        coin = Coin(imageNamed: "10yen")
        coin.userInteractionEnabled = true 
        coin.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMaxY(self.frame))
        coin.setScale(0.5)
        coin.physicsBody = SKPhysicsBody(circleOfRadius: coin.size.width / 2)
        coin.delegate = self //delegate先をselfに
        self.addChild(coin)
    }

    //こっからがプロトコルに定義されたdelegateメソッド
    func touchCoinBegan(coin: Coin) {
        開始時刻 = CFAbsoluteTimeGetCurrent() //現在の時刻を得る(200111日からの経過秒数)
        射出力 = 0
    }
    
    func touchCoinEnded(coin: Coin) {
        let 経過時間:CGFloat = CGFloat(CFAbsoluteTimeGetCurrent() - 開始時刻)
        coin.physicsBody?.applyForce(CGVectorMake(経過時間, 0))
    }
}