今日は2015年12月17日!!つまり明日は2015年12月18日!!そう!STAR WARS EP7がついに公開する!!!!!予告、ハン・ソロの「戻ってきたぜ」にはしびれましたね・・・STAR WARSと言えばライトセーバーですが、なんで青とか緑とか赤とか色が違うのかなって不思議ですよね。あれ実はライトセーバーを作るときに使うアデガン・クリスタルという石に依存していて、ジェダイたちは青や緑の天然の石を使うんですが、シスは人工の石を使ってかつ邪悪な気を込めて作るから赤色になるらしいですよ!ちなみにライトセーバーでの戦闘には7つのフォームがありましt(ry
概要
というわけで今回は振るとブォンッと音がなるライトセーバーっぽいものを作っていきます。加速度を取得するCoreMotionや音を再生するAVFoundationを使ってiPhoneの動きに合わせて音がする機能を作っていきます。
完成がこちら
https://github.com/kentarohorie/HikariSaber
手順
- 準備
- 加速度を取得してiPhoneの動きを受け取る
- 音の出力の実装
- 音を連続で出せるようにする
- ボタンを押した時のブォォンッ
実装
さっそく作っていきましょう!
Xcode新規プロジェクトを作成する
今回はSingle View Applicationで作っていきます。
事前準備
デフォルトのViewControllerファイルにCoreMotion, AVFoundationを導入し、適当なボタンを作ります。
以下、ViewController.swiftファイルで作業...
import UIKit
import CoreMotion
import AVFoundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setUp()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func setUp() {
setButton()
}
func setButton() {
let button = UIButton()
button.frame.size = CGSize(44, 44)
button.center = view.center
button.setTitle("Tap", forState: .Normal)
button.setTitleColor(UIColor.blueColor(), forState: .Normal)
button.setTitleColor(UIColor.redColor(), forState: .Selected)
button.addTarget(self, action: "tapButton:", forControlEvents: .TouchUpInside)
view.addSubview(button)
}
func tapButton(sender: UIButton) {
sender.selected = true
print("tapButton")
}
}
加速度を取得してみる
さて、まずはiPhoneを振ったというアクションを受け取るために加速度を取得してデバックエリアに表示してみましょう。
CMMotionManagerのインスタンスを生成
let motionManager = CMMotionManager()
※インスタンスをメソッド内で生成するとうまく機能しません
IMPORTANT
An app should create only a single instance of the CMMotionManager class. Multiple instances of this class can affect the rate at which data is received from the accelerometer and gyroscope.
加速度の取得&出力
第1引数に作業するキューを指定します。今回はmainQueueと指定します(他に処理がある場合など、一般的にこういった作業をmainQueueに渡すことは推奨されていません)このクロージャ内の第1引数でx軸、y軸、z軸の加速度を、第2引数で加速度を取得している際のエラーを受け取ります。
func getAccelerometer() {
motionManager.accelerometerUpdateInterval = 1 / 100
motionManager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()) { (accelerometerData: CMAccelerometerData?, error: NSError?) in
print(accelerometerData)
}
}
データを取得更新する頻度を指定します。
motionManager.accelerometerUpdateInterval = 1 / 100
どうでしょうか・・・デバックエリアがものすごい勢いでスクロールしていることでしょう・・・
合成加速度を取得
今回はこの合成加速度がある値以上になった場合に音を出すような仕組みを作ります。これによりiPhoneを動かす速さによって音を出す機能が実現できます。またiPhoneを振る速さで場合分け・・・なんて考えたらいろんなアイデアが思い浮かびますね!
合成加速度は
上記の式で求められますが、今回は簡単にこの式の平方根を除いた式を使います。
また、これにより求められる値が5以上になったときに音を出すとします
func getAccelerometer() {
motionManager.accelerometerUpdateInterval = 1 / 30
motionManager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()) { (accelerometerData: CMAccelerometerData?, error: NSError?) in
guard error == nil else {
return
}
let x = accelerometerData!.acceleration.x // x軸の加速度
let y = accelerometerData!.acceleration.y // y軸の加速度
let z = accelerometerData!.acceleration.z // z軸の加速度
let synthetic = (x * x) + (y * y) + (z * z) // 合成加速度
if synthetic >= 5 {
print("sound")
}
}
}
音を出力する
次に音を出力してみましょう。
まずはAVAudioPlayerのインスタンスを生成しましょう。メソッドをまたいで使用するため、メソッドの外に定義しましょう。
var audioPlayer = AVAudioPlayer()
次はいよいよ音のセットです。今回はこちらから音をダウンロードして使いました。setSoundメソッドはviewDidLoadで呼び出しましょう。
func setSound() {
let soundData = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("light_saber3", ofType: "mp3")!) //NSBundleを使用して音声ファイルを取得
audioPlayer = try! AVAudioPlayer(contentsOfURL: soundData)
audioPlayer.prepareToPlay()
}
では音を実際に出す処理を書きましょう!
func getAccelerometer() {
motionManager.accelerometerUpdateInterval = 1 / 100
motionManager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()) { (accelerometerData: CMAccelerometerData?, error: NSError?) in
guard error == nil else {
return
}
let x = accelerometerData!.acceleration.x
let y = accelerometerData!.acceleration.y
let z = accelerometerData!.acceleration.z
let synthetic = (x * x) + (y * y) + (z * z)
if synthetic >= 5 {
self.audioPlayer.play()
}
}
}
試しにiPhoneを振ってみましょう!
どうでしょう・・・ジェダイの気分を味わえましたか・・・
音を連続で出力する
今のままでは一定のリズムでしか音が出ません。僕らはリズミカルセーバーを作ろうとしてるのではありません。僕らが目指してるのは・・・
さて、振る度に音を出すためにgetAccelerometerメソッドを以下のように書き換えます。
func getAccelerometer() {
var preBool = false
var postBool = false
motionManager.accelerometerUpdateInterval = 1 / 5
motionManager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()) { (accelerometerData: CMAccelerometerData?, error: NSError?) in
guard error == nil else {
return
}
let x = accelerometerData!.acceleration.x
let y = accelerometerData!.acceleration.y
let z = accelerometerData!.acceleration.z
let synthetic = (x * x) + (y * y) + (z * z)
if preBool {
postBool = true
}
if !postBool && synthetic >= 6 {
self.audioPlayer.currentTime = 0 // 再生中の音を止める
self.audioPlayer.play()
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate)) //バイブ
preBool = true
}
if postBool && synthetic >= 6 {
self.audioPlayer.currentTime = 0 // 再生中の音を止める
self.audioPlayer.play()
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate)) //バイブ
postBool = false
preBool = false
}
}
}
この二つの真偽値を使って”振った”という動作を判断しています。
var preBool = false
var postBool = false
ついでに振る度に振動させてみましょう。
AudioToolboxを導入し・・・
import AudioToolbox
たったこれだけ!
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
そして更新周期、音出力の閾値を微調整して・・・
かなりいい感触になってきたはずです・・・!
ボタンを押した時の効果音
さてそれでは臥龍点睛といきましょう・・・
音の出力と同じ要領で実装します。
新たにAVAudioPlayerのインスタンスを生成し、音データをセットしボタンのアクションに追加しましょう!
var startAudioPlayer = AVAudioPlayer()
func setStartSound() {
let soundData = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("electric_chain", ofType: "mp3")!)
startAudioPlayer = try! AVAudioPlayer(contentsOfURL: soundData)
startAudioPlayer.prepareToPlay()
ちなみに新たにインスタンスを生成せず、初めに生成したAVAudioPlayerインスタンスの中身を入れ替える形で実装するとうまくいきませんでした。おそらく音を出す処理の直後にインスタンスの中身が入れ替わっているからか・・・
// 以下のようにすると失敗する
func tapButton(sender: UIButton) {
let soundData = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("electric_chain", ofType: "mp3")!)
audioPlayer = try! AVAudioPlayer(contentsOfURL: soundData)
audioPlayer.play()
setSound()
getAccelerometer()
}
以上で完成です!!音量をMAXにして(サイレント解除も忘れず)振り回しましょう!!!!
さて・・・あなたの手元にあるサーベルは何色か・・・
May the force be with you