今回は、ブロック崩しの全機能をコーディングしていきたいと思います。
ブロック崩しは、私自身も初めて作成してみましたが、全4回で完了し、思ったよりシンプルにできて、ゲーム作成の練習としては、よい題材だったと今更ながら感じています。
(あまり計画なく始めたことがバレバレですね)
それでは、頑張って一緒にやっていきましょう。
ブロックの追加とインスタンス化
ブロックとして使用するのは、将来的に画像に置き換えて高級感も出せるように「ImageView」を使用したいと思います。
今回も、ストーリーボード上で、作業をしていきたいと思います。
もし、やり方を忘れてしまった場合やわからないという方は、SwiftでiPhoneゲームアプリを作ろう!〜ストーリーボード編をもう一度見直してみてくださいね。
ストーリーボードでImageViewを追加してサイズを50x20pxに設定します。
バックグラウンドカラーも変更して好きな色に設定してブロックの色を決めましょう。
更に、ストーリーボードとコードを接続します。が、今回は、複数のブロックを一つの変数にまとめたいので、「Referencing Outlet Collections」で接続します。
ここまで行うとまず一つのブロックを準備できた状態となります。
そのあとに、ブロックをコピーしていきます。私は、6列x4行のブロックをコピーしました。
コードとの接続は、先に作った変数につなげていきます。多くのブロックがあるのでまとめて接続処理を行いたいと思い色々試してみましたがうまくいきませんでしたので、一つ一つ接続していきます。
もし、今後、ブロックを多くしていく場合は、一つ一つ手作業で行うと、すごく手間がかかりますので、ストーリーボードを使用せず、コード側で、全てのブロックを作成し、順番に並べていくという方法もありますのが、現時点では、そういう方法もあるんだなーぐらいに思っておいてもらっても大丈夫です。
ViewController上に下記のコードができているでしょうか?
あと、全てのブロックを消した時に表示する「UILabel」も追加しましょう。
@IBOutlet var blocks: [UIImageView]!
@IBOutlet weak var compLabel: UILabel!
ブロックとボールが当たった時の処理
ブロックの変数が準備できましたので、ブロックとボールが当たった時の処理を書いていきましょう。
ボールが移動するたびに当たり判定を行いたいので、MoveBall関数の中に記述をしていきます。
全てのブロックとボールの当たりの確認を行います。
その確認方法には、「for in」を使用します。
わからない方は、Swift簡単入門〜繰り返し編|XcodeのPlaygroundで楽しい学びの世界を確認してみてください。
下記のコードは、「blocks」の配列から一つずつ「block」に順番に代入して当たっているかの判定を行っていきます。
肝心な当たり判定は、ボールとブロックの中心座標の差がボール幅とブロック幅の和の半分より小さくかつ表示されている場合という条件としています。
更に、ブロックの下から当たった場合と上から当たった場合で反射の仕方を変えています。
本物のブロック崩しの動きが、下から当たるときはボールが来た方向にそのまま帰って、上から当たる時は、普通の反射になっていたと記憶していますのでそうしました。
当たった場合は、ブロックを非表示とする処理を加えています。
これらの処理を行うコードは下記の状態となります。
for block in blocks {
if abs(ballImage.center.x - block.center.x) <
(ballImage.bounds.width + block.bounds.width) / 2
&& abs(ballImage.center.y - block.center.y) <
(ballImage.bounds.height + block.bounds.height) / 2
&& !block.isHidden{
if vectBall.y > 0 { //下からブロックに当たった場合
vectBall.x = vectBall.x * -1
vectBall.y = vectBall.y * -1
} else { //上からブロックに当たった場合
vectBall.y = vectBall.y * -1
}
block.isHidden = true //ブロックを非表示化
}
}
ゲーム性のアップ
ラケットの当たる位置によって反射するパターンを変えるようにしまいした。
ラケットの端近くに当たると、ボールが来た方向に反射するという動作です。
この部分は、本物もそうなっていますので、ゲーム性を上げるためにその挙動も再現してみました。
端に当たった時の処理は下記のようにしてみました。
//ラケットの端に当たった場合の処理
if abs(ballImage.center.x - racketImage.center.x) >
racketImage.bounds.width / 4 {
//更に、ボールが向かってくる側の端の場合は、X軸も弾き返す
//(ボールが右から来た場合と左から来た場合)
if (ballImage.center.x - racketImage.center.x < 0
&& vectBall.x > 0)
|| (ballImage.center.x - racketImage.center.x > 0
&& vectBall.x < 0) {
vectBall.x = vectBall.x * -1
}
}
あと、ラケットとボールの当たり判定を、前回より読みやすくなるように変更をしてみました。
その部分のコードは下記となります。
//ラケットとのあたり判定
if abs(ballImage.center.x - racketImage.center.x) <
(ballImage.bounds.width + racketImage.bounds.width) / 2
&& abs(ballImage.center.y - racketImage.center.y) <
(ballImage.bounds.height + racketImage.bounds.height) / 2
&& vectBall.y < 0 {
vectBall.y = vectBall.y * -1
//ラケットの端に当たった場合の処理
if abs(ballImage.center.x - racketImage.center.x) >
racketImage.bounds.width / 4 {
//更に、ボールが向かってくる側の端の場合は、X軸も弾き返す
//(ボールが右から来た場合と左から来た場合)
if (ballImage.center.x - racketImage.center.x < 0
&& vectBall.x > 0)
|| (ballImage.center.x - racketImage.center.x > 0
&& vectBall.x < 0) {
vectBall.x = vectBall.x * -1
}
}
}
ブロック崩し全体のコード
それでは、全体のコードを載せておきたいと思います。
上記では詳細の説明をしていませんが、ブロックを全て消すと「Game Complete」と表示されるようなコードも追加しました。
ただ、細かな部分の処理は省略していますので、ボールを打ち返し損ねても、そのままゲームが続いたり、全てのブロックを消して終了しても、ボールは相変わらず動き続けます。
この辺りは、本題のブロック崩しの動作とは別の処理となりますので、あまり深く追求していませんので、ご了承ください。
もし、ご要望等があれば、ご連絡をお願いいたいます。続きを考えてみたいと思います。
//
// ViewController.swift
// BreakBlock
//
// Copyright © 2019年 BRS. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var racketImage: UIImageView!
@IBOutlet weak var ballImage: UIImageView!
@IBOutlet weak var compLabel: UILabel!
@IBOutlet var blocks: [UIImageView]!
//ボールの進むベクトル値
var vectBall = CGPoint(x: 1, y: 1)
//消したブロックの数
var hittedBlockCount = 0
override func viewDidLoad() {
super.viewDidLoad()
//ラケットとボールのy軸の値を画面サイズに合わせて設定
racketImage.center.y = self.view.bounds.maxY - 100
ballImage.center.y = self.view.bounds.maxY - racketImage.bounds.maxY - 100
//ボールスピードタイマー
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 old = touchevent.previousLocation(in: self.view)
let new = touchevent.location(in: self.view)
racketImage.center.x += new.x - old.x
//ラケットの移動範囲を外面サイズ+ラケット幅までに制限する
if racketImage.center.x < -racketImage.bounds.maxX
|| racketImage.center.x > self.view.bounds.maxX
+ racketImage.bounds.maxX {
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
}
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 abs(ballImage.center.x - racketImage.center.x) <
(ballImage.bounds.width + racketImage.bounds.width) / 2
&& abs(ballImage.center.y - racketImage.center.y) <
(ballImage.bounds.height + racketImage.bounds.height) / 2
&& vectBall.y < 0 {
vectBall.y = vectBall.y * -1
//ラケットの端に当たった場合の処理
if abs(ballImage.center.x - racketImage.center.x) >
racketImage.bounds.width / 4 {
//更に、ボールが向かってくる側の端の場合は、X軸も弾き返す
//(ボールが右から来た場合と左から来た場合)
if (ballImage.center.x - racketImage.center.x < 0
&& vectBall.x > 0)
|| (ballImage.center.x - racketImage.center.x > 0
&& vectBall.x < 0) {
vectBall.x = vectBall.x * -1
}
}
}
//ブロックとのあたり判定
for block in blocks {
if abs(ballImage.center.x - block.center.x) <
(ballImage.bounds.width + block.bounds.width) / 2
&& abs(ballImage.center.y - block.center.y) <
(ballImage.bounds.height + block.bounds.height) / 2
&& !block.isHidden{
if vectBall.y > 0 {
vectBall.x = vectBall.x * -1
vectBall.y = vectBall.y * -1
} else {
vectBall.y = vectBall.y * -1
}
block.isHidden = true
hittedBlockCount += 1
if blocks.count == hittedBlockCount {
compLabel.text = "ゲームコンプリート"
}
}
}
}
}
あと、出来上がった画面はこんな感じになりました。(クリックで拡大)
まとめ
ブロック崩しを全4回に渡り、一緒に作成して来ましたが、いかがだったでしょうか。
最初は難しかったですかね。
if文の判定の部分はとても複雑な感じに見えると思いますが、そのほかの部分は比較的、シンプルなコマンドとなっていますので、じっくりと一つ一つのコマンドを確認していくことで、理解ができるようになってくるのではと思っています。
xcodeは、ストーリーボードがあり、視覚的にも作りやすいうえに、コーディングとの連携もしやすいため、初心者の方でも比較的すぐになじめたのではないでしょうか。
今回は省略しましたが、ゲームの完了画面等を作ったり、ゲームセンターの画面を作ったりと複数ページの遷移が必要になってくると、ストーリーボードのありがたみが分かって来ます。
本当に便利な機能なので色々改造をして試してみてくださいね。
それでは、以上でブロック崩しアプリの作成を終了したいと思います。
また、今回の内容でも全体を通しての内容でも結構ですので、ご質問等があれば、コメントやお問い合わせよりお気軽にご連絡をいただければと思います。
また、こんなゲームを作って欲しいなどのご要望がある場合もコメントをいただけると作り方を紹介したいと思います。
それでは、お疲れ様でした。
それと、ここまでできた方は、初心者の方ではないと思いますので、是非とも、もっと高度なプログラミングに挑戦してみてはいかがでしょうか。
今は、プログラミングですごく稼げる時代となっています。この波に乗り遅れないように、いますぐ、無料で始めるプログラミングスクールで頑張ってください。
無料でプログラミングを学ぶ
コメント
タイトル画面やゲームオーバー画面への画面遷移の方法も知りたいです
タイトル画面やゲームオーバー画面への遷移の方法も知りたいのでお願いします。
コメントありがとうございます。
画面の遷移に関しましても、ご紹介させていただきたいと思います。
申し訳ありませんが、すぐに取り掛かれそうにありませんので、swiftに関する他の記事も併せてご確認をいただけますと幸いです。
ありがとうございます
いつ頃になりそうでしょうか?
今月中にやって頂きたいのですが、可能でしょうか