今回は、いよいよ、ブロック崩しのパーツであるボールとラケットの部分を作っていきたいと思います。
前回までをまだ見られていない方は、 SwiftでiPhoneゲームアプリを作ろう!〜ストーリーボード編 も参考にしてください。
それでは、前回に配置したボールとラケットを動くようにしていきたいと思います。
始める前に、一つ注意して欲しい内容としまして、iPhoneの画面は左上が原点となっています。
X軸は右にいくほど大きくなりますが、Y軸は、下にいくほど大きくなりますので、座標を扱う際には注意してくださいね。
ブロック崩しのラケットを動かす
ラケットは、スワイプの動作に連動して動くようにしたいと思います。
本物のブロック崩しはダイアルでラケットを動かしていましたが、早く動かせば早く動き、ゆっくり動かせばゆっくり動くといったように、操作スピードに追従していましたので、スワイプで操作することにより、同じような状態が再現できると思います。
スワイプに連動するためには、イベントを使います。
操作に対して処理しますよというイベント処理を書くことにより、指の操作をプログラムに伝えることができるようになります。
今回使用するイベント処理として「UIResponder」を紹介します。
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
// ここにタッチした時の処理を書きます
}
「UIResponder」に処理を書いていきたいと思います。 タッチした座標を取得し、スワイプで移動した距離を計算して、その移動した距離に対してラケットを移動するようにします。
この処理は、「ViewController」の中に記述します。
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touchevent = touches.first! //タッチした座標を取得します
let old = touchevent.previousLocation(in: self.view) //タッチしたviewの一つ前の座標値を取得します
let new = touchevent.location(in: self.view) //タッチしたviewの今回の座標値を取得します
// X座標に対して、今回の座標から一つ前の座標を引くことにより移動量を計算し、ラケットをその移動量分動かします。
racketImage.center.x += new.x - old.x
}
それでは早速、実行して見ましょう。
画面をスワイプしてみてください。いい感じで動いていますね。
ブロック崩しのボールを動かす
それでは、次に、ボールが画面内を反射しながら動くコード書いていきたいと思います。
最初に、画面の端で跳ね返るようにしたいと思います。
最初にボールを動かすようにしたいのですが、方法として、定期的に座標を加算していくという方法で行いたいと思います。
定期的に処理するためにタイマーを準備したいと思います。
このタイマーは、アプリが起動した時に動いていて欲しいので、「viewDidLoad()」関数(viewが呼び出された時に実行する関数)の中に記述をします。
override func viewDidLoad() {
super.viewDidLoad()
//ボールスピードタイマー 5/1000秒毎にselectorを実行します
Timer.scheduledTimer(timeInterval: 0.005, target: self, selector: #selector(MoveBall), userInfo: nil, repeats: true)
}
これでタイマーがセットできましたので、selectorで実行するための関数を書いていきたいと思います。
最初にボールの進む方向を指定する変数を作成したいと思います。
この変数は、「ViewController」の中に記述します。
//ボールの進むベクトル値
var vectBall = CGPoint(x: 1, y: 1) //とりあえず45度に進むようにしておきます
次に実行するための関数を書いていきます。「ViewController」の中に記述します。
@objc func MoveBall(){
ballImage.center.x += vectBall.x //ボールの座標にvectBall.xで決めた値を加える
ballImage.center.y -= vectBall.y //ボールの座標にvectBall.yで決めた値を加える
// ボールの端が画面の端に到達したら方向を逆転します。
// 方向を逆転する方法は、それぞれのベクトル値の符号を変えることで行います。
if ballImage.center.x >= view.bounds.maxX - ballImage.bounds.maxX / 2 {
vectBall.x = vectBall.x * -1
} else if ballImage.center.x <= ballImage.bounds.maxX / 2 {
vectBall.x = vectBall.x * -1
} else if ballImage.center.y >= view.bounds.maxY - ballImage.bounds.maxY / 2 {
vectBall.y = vectBall.y * -1
} else if ballImage.center.y <= ballImage.bounds.maxY / 2 {
vectBall.y = vectBall.y * -1
}
//ボールがラケットに当たった時に弾き返す処理
//前から当たった時のみ弾き返します。また、ボールの端とラケットの端の場合も弾き返します。
if ballImage.center.x + ballImage.bounds.maxX / 2 > racketImage.center.x - racketImage.bounds.maxX / 2 && ballImage.center.x - ballImage.bounds.maxX / 2 < racketImage.center.x + racketImage.bounds.maxX / 2 && ballImage.center.y + ballImage.bounds.maxY / 2 > racketImage.center.y - racketImage.bounds.maxY / 2 && ballImage.center.y - ballImage.bounds.maxY / 2 < racketImage.center.y - racketImage.bounds.maxY / 2 && vectBall.y < 0 {
vectBall.y = vectBall.y * -1
}
}
この状態で、実行してみましょう。
ブロック崩しの基本の動作のボールが画面の中で反射して、ラケットで打ち返すという動作ができるようになったと思います。
ボールを徐々に早くする処理
次に、難易度を上げるために、ボールのスピードを徐々に早くしていく処理を追加してみたいと思います。
この処理は最初に作ったタイマーのボールを動かすのと同じ要領で行います。
下記のように先ほど記述したところにスピードアップのタイマーを追加します。
override func viewDidLoad() {
super.viewDidLoad()
//ボールスピードタイマー 5/1000秒毎にselectorを実行します
Timer.scheduledTimer(timeInterval: 0.005, target: self, selector: #selector(MoveBall), userInfo: nil, repeats: true)
//スピードアップ 5秒毎にselectorを実行します
Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(ChangeSpeed), userInfo: nil, repeats: true)
}
更に、タイマーでの処理するコードを記述します。
@objc func ChangeSpeed() {
//xとyのベクトル値を1.1倍にすることでスピードアップを実現します
vectBall.x *= 1.1
vectBall.y *= 1.1
}
これで、動作させると、徐々にボールのスピードが上がってきますので、ゲームぽくなってきたと思います。
コードの全体
これで、今回考えていた機能をすべて実現しましたので、全体のコードを載せておきたいと思います。
//
// ViewController.swift
// BreakBlock
//
// Copyright © 2019年 BRS. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var racketImage: UIImageView!
@IBOutlet weak var ballImage: UIImageView!
//ボールの進むベクトル値
var vectBall = CGPoint(x: 1, y: 1)
var ballSpeed = 5
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//ボールスピードタイマー
Timer.scheduledTimer(timeInterval: 0.005, target: self, selector: #selector(MoveBall), userInfo: nil, repeats: true)
//スピードアップ
Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(ChangeSpeed), userInfo: nil, repeats: true)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touchevent = touches.first!
// let view = touchevent.view!
let old = touchevent.previousLocation(in: self.view)
let new = touchevent.location(in: self.view)
// view.frame.origin.x += (new.x - old.x)
// view.frame.origin.y += (new.y - old.y)
racketImage.center.x += new.x - old.x
}
@objc func ChangeSpeed() {
vectBall.x *= 1.1
vectBall.y *= 1.1
}
@objc func MoveBall(){
ballImage.center.x += vectBall.x
ballImage.center.y -= vectBall.y
if ballImage.center.x >= view.bounds.maxX - ballImage.bounds.maxX / 2 {
vectBall.x = vectBall.x * -1
} else if ballImage.center.x <= ballImage.bounds.maxX / 2 {
vectBall.x = vectBall.x * -1
} else if ballImage.center.y >= view.bounds.maxY - ballImage.bounds.maxY / 2 {
vectBall.y = vectBall.y * -1
} else if ballImage.center.y <= ballImage.bounds.maxY / 2 {
vectBall.y = vectBall.y * -1
}
//ラケットとのあたり判定
if ballImage.center.x + ballImage.bounds.maxX / 2 > racketImage.center.x - racketImage.bounds.maxX / 2 && ballImage.center.x - ballImage.bounds.maxX / 2 < racketImage.center.x + racketImage.bounds.maxX / 2 && ballImage.center.y + ballImage.bounds.maxY / 2 > racketImage.center.y - racketImage.bounds.maxY / 2 && ballImage.center.y - ballImage.bounds.maxY / 2 < racketImage.center.y - racketImage.bounds.maxY / 2 && vectBall.y < 0 {
vectBall.y = vectBall.y * -1
}
}
}
おまけ
実は、上記の完成版のコードにはバグがあります。
長時間実行していると発生するんですが、ボールがちょうど角の部分に当たった時に、画面外に行ってしまったり、変な動きをしたりします。
ですので、それを自力で直してみて欲しいと思います。
このバグは意図して作ったんではないんですが、実際の開発と同じように作っている中で発覚したバグで、挙動から原因を考え、修正を行いました。
そのあたりをみなさんにも体感してもらうには、良い機会だと思いましたので、バグありのコードを最終形として掲載させていただきました。
決して、意地悪をしているわけではありませんので、頑張って考えてみてくださいね。
まとめ
いかがだったでしょうか。
だいぶゲームっぽくなってきましたね。
今回の中では、ボールを丸くしたり、ラケットが端まで行ったら止まる処理とかは行っていませんが、ご興味があればそのあたりも自分で改造してみても楽しいと思います。
基本のコードから、少しずつ自分のコードを足して動作をアレンジしてみたり、機能を追加したりすることで、プログラミングが楽しくなりますので、ぜひ、チャレンジしてみてください。
ボールとラケットの初期の配置をコード上で指定するということをやってみるのもいいかもしれませんね。
画面サイズをから、ラケットの位置を決めるようにすると、様々な画面サイズのiPhoneに対応できるようになりますね。
もしわからないようであればコメントやお問い合わせでご質問をしてください。
コメント