Swiftで初めて動きのあるゲームを開発する人向けチュートリアル(なんとかストライクを例にStep by Step解説)
なんとかストライクとは
ひっぱって敵にぶつけて倒す例のゲームもどきです。
これの作り方を通して、SpriteKitを利用したiOSアプリでの画面要素の移動/回転/拡大縮小、衝突、接触処理、ドラッグ(panジェスチャー)への対応、画面遷移を含む開発の第一歩を踏み出すためのチュートリアルです。
iPhoneアプリ開発環境であるXcodeの準備とSwiftのとっかかりとしては、こちらのチュートリアルをご覧下さい。
プロジェクト開始〜
前回のSwiftゲーム開発チュートリアル
こちらを参考に、「新しいゲームプロジェクトの開始」から「よけいなコードを消す」までの作業を行いましょう。
ただし、Project Nameのところは、NantokaStrikeにしておきましょう。
ひっぱり(ドラッグ、panジェスチャー)の動作に対応する
ひっぱり的な操作に対応するには、Pan Gesture Recognizerを使います。
- Project NavigatorでMain.storyboardをクリックして開く
- 右下の検索ボックスにpanと入力
- Pan Gesture Recognizerが表示されるので、ドラッグ&ドロップする
- タキシードに蝶ネクタイっぽいアイコンをクリックしてAssistan Editorを開く
- controlを押しながらTap Gesture Recognizerをドラッグして、コード中のGameViewControllerの中にドロップする
- 出てきたダイアログで下記の通り入力して、connectボタンを押す
- Connection: Action
- Name: didPan
- Type: UIPanGestureRecognizer
- Assistant Editorを使い終わったら閉じておきます
うまく行くとdidPanのコードが生成されます
@IBAction func didPan(sender: UIPanGestureRecognizer) { }
- Pan Gesture Recognizerを右クリック
- 出てきたダイアログで、Outletsの下にあるdelegateの右の○をドラッグして、Game View Controllerにドロップ
今の操作で、
UIPanGestureRecognizerとGameViewControllerにdelegateの関係を追加した
ので、コードにもこれを反映します。
- GameViewController.swiftを開きます
- クラスのプロトコルにUIGestureRecognizerDelegateを追加します
class GameViewController: UIViewController, UIGestureRecognizerDelegate {
ひっぱり操作に対して、delegeteしたクラスのdidPanメソッドが呼び出されるので、この中に処理を書きます
var panPointReference: CGPoint? @IBAction func didPan(sender: UIPanGestureRecognizer) { let currentPoint = sender.translationInView(self.view) if let originalPoint = panPointReference { println("currentPoint: \(currentPoint)") } else if sender.state == .Began { println(".Began") panPointReference = currentPoint } if sender.state == .Ended { println(".Ended") panPointReference = nil } }
- didPanの中では、sender.stateの値を調べることで、ひっぱり操作の状態を確認することが出来ます。
- 引っぱり中であることの確認と引っぱり開始位置との相対位置を計算するために、ひっぱり開始位置をクラス変数panPointReferenceに入れておくことにします。
- このような基本構造にすることにして、まずは、三つの状態ごとにログを出力するようにしてみます。
ここで一旦、⌘+Rでアプリを動かして、Xcodeにログが出力されることを確認しましょう
- 左上の再生ボタンを押す
- あるいは、⌘ + R (コマンドキーを押しながらRキーを押す)
シミューレーター上で何回かドラッグしてみてログが想定通りに出力されましたか?
※Xcodeのログ出力はShift+⌘+Yでshow/hideができます
Xcodeに戻って、アプリを停止しましょう
- 左上の停止ボタンを押す
- あるいは、⌘+.(コマンドキーを押しながらピリオドキーを押す)
引っぱり操作を表す矢印と自キャラを追加する
画面上でひっぱり操作を表示する矢印を追加します
- この画像を、arrow.pngという名前で保存しましょう
- 保存した場所をFinderで開いてProject NavigatorのSupporting Filesにドラッグ&ドロップする
- Choose options for adding these filesダイアログ
- Copy items if necessaryにチェック
- finishをクリック
ひっぱる対象のキャラクターも追加しておきます
- この画像を、nantoka.pngという名前で保存しましょう
- 残りはarrow.pngと同じ要領で。
コード上でこの二つの画像を表示するようにします。
GameScene.swiftを開いて、GameSceneクラスを次のように書き換えましょう。
class GameScene: SKScene { var arrow: SKSpriteNode var nantoka: SKSpriteNode required init(coder aDecoder: NSCoder) { fatalError("NSCoder not supported") } override init(size: CGSize) { println("\(size)") arrow = SKSpriteNode(imageNamed: "arrow") arrow.alpha = 1 arrow.position = CGPoint(x: 160, y: 160) arrow.xScale = 0.2 nantoka = SKSpriteNode(imageNamed: "nantoka") nantoka.size = CGSizeMake(40, 40) nantoka.position = CGPoint(x: 120, y: 180) super.init(size: size) backgroundColor = SKColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1.0) anchorPoint = CGPoint(x: 0, y: 0) addChild(arrow) addChild(nantoka) } override func update(currentTime: CFTimeInterval) { /* Called before each frame is rendered */ } }
ついでに、SKSceneクラスのbackgroundColorを指定することで背景色も変更しました。
ここで一旦、動作を確認しておきましょう。
ひっぱり操作の処理を実装する
矢印が目的の角度と大きさになるようにアニメーションさせるメソッドturnArrowを定義しましょう。
GameScene.swiftを開いてGameSceneクラスにこのコードを追加します。
func turnArrow(angle: CGFloat, scale: CGFloat) { let rotateAction = SKAction.rotateToAngle(angle, duration: 0.1) let scaleAction = SKAction.scaleXTo(scale, y: 1, duration: 0.1) arrow.runAction(SKAction.group([rotateAction, scaleAction])) }
- SKAction.scaleXToを使って、ひっぱりの大きさに合わせて矢印画像を拡大します
- scaleは拡大率を指定します。
- 1が原寸大
- 大きくしたい場合は、1より大きい値
- 小さくしたい場合は、1より小さい値
- durationは動作にかける時間を指定します
- scaleは拡大率を指定します。
- SKAction.rotateToAngleを使って、ひっぱりの向きに合わせて矢印画像を回転します
- angleはX軸から反時計回りの角度をラジアン(一回転が2πです)で指定します
- 角度angleは度ではなく、ラジアンで指定します
- durationは動作にかける時間を指定します
次に、GameViewController.swiftを開いて、didPan中からturnArrowを呼び出すようにします。
@IBAction func didPan(sender: UIPanGestureRecognizer) { let currentPoint = sender.translationInView(self.view) if let originalPoint = panPointReference { println("currentPoint: \(currentPoint)") let v = sub(currentPoint, p1: originalPoint) let len = length(v) scene.arrow.alpha = 1 scene.arrow.position = scene.nantoka.position let scale = 0.2 * len / CGFloat(5.0) scene.turnArrow(vector2radian(v), scale: scale) } else if sender.state == .Began { println(".Began") panPointReference = currentPoint } if sender.state == .Ended { println(".Ended") panPointReference = nil scene.arrow.alpha = 0 } }
didPanメソッドの中では、
- ひっぱり中の位置と引っぱり開始の位置から矢印の向きと大きさを計算します(subメソッド)
- ひっぱり開始で矢印が表示され、引っぱり終了で矢印が見えなくなるようにalpha値を変更します
- ひっぱり距離を計算(lengthメソッド)し、それに比例した矢印の拡大率をscaleとします
- ベクトルの向きをvector2radianで角度(ラジアン)に変換したものとscaleでturnArrowを呼び出します
ここで使った三つのメソッドも追加しておきます。
(内容を解説すると物理や数学のお勉強になってしまうので、説明は省略します。大学の物理を勉強し直しましょう→大学生のための 力学入門)
func sub(p0: CGPoint, p1: CGPoint) -> CGPoint { return CGPoint(x: p0.x - p1.x, y: p0.y - p1.y) } func length(v: CGPoint) -> CGFloat { return sqrt(v.x * v.x + v.y * v.y) } func vector2radian(v: CGPoint) -> CGFloat { let len = length(v) let t = -v.y / v.x let c = v.x / len if v.x == 0 { return acos(c) } else { var angle = CGFloat(atan(t)) return angle + CGFloat(0 < v.x ? M_PI : 0.0) } }
最後にGameViewControllerからこのシーンを表示するようにします。
var scene: GameScene! override func viewDidLoad() { super.viewDidLoad() let skView = view as SKView skView.multipleTouchEnabled = false scene = GameScene(size: skView.bounds.size) scene.scaleMode = .AspectFill skView.presentScene(scene) }
ここで一旦、ゲームを動かしてみて、矢印の動きを確認しましょう。
矢印の出始めや、回転が12時の位置を通過する時の動作が変ですが、余裕があれば改修してみて下さい。
飛ばす
SpriteKitで画面要素を動かすためには、アニメーションを使う方法の他に、物理演算を使うこともできます。
物理演算で物体を動かすと、衝突判定などが簡単になるので、キャラクターの動作には、物理演算を使うことにします。
では、GameScene.swiftを修正しましょう。
次に、キャラクターの物体としての特徴を設定するために、
super.init呼び出しの上にこのコードを追加します。
nantoka.physicsBody = SKPhysicsBody(circleOfRadius: 16) nantoka.physicsBody?.dynamic = true
- 物理演算の対象にするSKSpriteNodeのphysicsBodyプロパティを設定します
- physicsBody: プロパティを物体としての形を指定したSKPhysicsBodyにします
- dynamic: 重力や衝突によって物体が動くかどうかを指定します
- true: 動く
- false: 固定される
シーンのphysicsWorldの設定をするために、
super.init呼び出しの次にこのコードを追加します。
physicsWorld.gravity = CGVectorMake(0, 0)
- 今回のゲームでは、キャラクターの動きに重力を考慮する必要はないので、gravityにはゼロベクトルを設定しておきます。
GameViewController.swiftを開き、didPanの中のひっぱり終了のコードブロックの中に、このコードを追加します。
scene.nantoka.physicsBody?.velocity = acceleration(currentPoint, point: panPointReference!)
- 速度を与えるために、velocityプロパティにCGVectorを設定しています
ここで使ったaccelerationメソッドも追加しておきましょう。
func acceleration(o: CGPoint, point: CGPoint) -> CGVector { let leverage = CGFloat(50) return CGVectorMake(leverage * (point.x - o.x), leverage * (o.y - point.y)) }
- ここでは、引っぱりを表すベクトルの大きさを50倍にしたものを速度として与えるようにしました。
では、ここで一旦、飛ばす処理の動作確認をしましょう
ぶつかる対象の追加
ぶつかる対象として壁を追加します。
壁は同じ特徴を持った四つの物体を上下左右に配置するだけなので、一つの壁を追加するメソッドを用意します。
このコードをGameSceneクラスに追加しましょう。
func addWall(size: CGSize, position: CGPoint) { var wall: SKSpriteNode wall = SKSpriteNode(color: UIColor.brownColor(), size: size) wall.position = position wall.anchorPoint = CGPoint(x: 0, y: 0) wall.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(size.width * 2, size.height * 2)) wall.physicsBody?.dynamic = false addChild(wall) }
上下左右の壁の大きさと位置を指定して、このメソッドを呼び出せば壁の追加は終了です。
ここで、ついでに、後ほど利用する敵キャラのライフ値ゲージも追加しておきます
var enemyLifeGuage: SKSpriteNode let enemyLifeGuageBase: SKSpriteNode
- GameSceneのクラス変数としてライフ値の表示になるenemyLifeGuageを定義します。
- 敵ライフ値が減ったときに黒塗りが残るようにするための画面要素としてenemyLifeGuageBaseを追加します
initの中で、super.init(size: size)の上にこのコードを追加します。
nantoka.physicsBody?.usesPreciseCollisionDetection = true nantoka.physicsBody?.friction = 0 enemyLifeGuage = SKSpriteNode(color: UIColor.yellowColor(), size: CGSizeMake(size.width, 20)) enemyLifeGuage.position = CGPoint(x: 0, y: size.height - enemyLifeGuage.size.height) enemyLifeGuage.anchorPoint = CGPoint(x: 0, y: 0) let enemyLifeGuageBase = SKSpriteNode(color: UIColor.blackColor(), size: CGSizeMake(size.width, 20)) enemyLifeGuageBase.position = CGPoint(x: 0, y: size.height - enemyLifeGuage.size.height) enemyLifeGuageBase.anchorPoint = CGPoint(x: 0, y: 0)
- クラス変数/定数は super.init呼び出しより先に初期化する必要があります
- usesPreciseCollisionDetectionをtrueにしておくと、精度の高い衝突判定が行われるようになります
- 自キャラの摩擦係数frictionをゼロにしておきます。衝突や接触したときに自キャラがくるくるしてしまわないようにするためです。
- sizeはこのシーンの大きさが入っているので、画面の上下左右からつめてゲージや壁を配置するために利用します
initの中で、super.init(size: size)の下にこのコードを追加します
let wallThickness: CGFloat = 10 let fieldSize = CGSizeMake(size.width - wallThickness * 2, 400) let fieldBottomY = size.height - (enemyLifeGuage.size.height + wallThickness + fieldSize.height) // upper wall addWall(CGSize(width: fieldSize.width, height: wallThickness), position: CGPoint(x: wallThickness, y: enemyLifeGuage.position.y - wallThickness)) // bottom wall addWall(CGSizeMake(fieldSize.width, wallThickness), position: CGPoint(x: wallThickness, y: fieldBottomY - wallThickness)) // right wall addWall(CGSizeMake(wallThickness, fieldSize.height), position: CGPoint(x: wallThickness + fieldSize.width, y: fieldBottomY)) // left wall addWall(CGSizeMake(wallThickness, fieldSize.height), position: CGPoint(x: 0, y: fieldBottomY)) addChild(enemyLifeGuageBase) addChild(enemyLifeGuage)
- 親クラスのプロパティにアクセスするためには(ここではaddChild)、super.init呼び出し後に書く必要があります
- 壁の厚さwallThicknessと壁が納まる領域の大きさfieldSizeを定義します
- 領域の幅は画面ぴったりにして、高さは適当に選びました
- SpriteKitでは画面左下が座標の原点(0,0)なので、それに合わせたpositionでそれぞれの壁を配置しています
では、ゲームらしくなってきたところで、動作確認をしてみましょう。
動いている途中でもまたひっぱれちゃったりしますが、やる気のある人はその辺の制御も入れてみて下さい。
跳ね返る位置も若干おかしいですが、各自で調整してみて下さい。
GameViewControllerのviewDidLoadメソッドの中にこのコードを追加しておくと、物体としての輪郭が表示されます。
skView.showsPhysics = true
他にも、デバッグに役立つ情報を表示するために、このようなプロパティ設定も追加しておいてもよいでしょう。
skView.showsDrawCount = true skView.showsFPS = true skView.showsNodeCount = true skView.showsFields = true skView.showsQuadCount = true
敵キャラと接触時の処理を追加する
壁にぶつかったときには単に反射するだけで良いのに対して、
敵にぶつかったときには敵のライフ値を減らす処理も入れる必要があります。
ここでは接触判定を利用して、その実装をします。
接触判定と衝突は独立した概念なので、
物体同士がすり抜けたときに接触判定を行うことも出来ます。
たとえば、フィールドに配置されたアイテムをゲットする時にも使えます。
ここで、敵キャラを配置の準備をしましょう
- 敵キャラ画像としてこの画像をenemy.pngという名前で保存して下さい
- 自キャラ画像の取り込みと同じ手順で取り込みます
接触判定では、まず、Spriteのカテゴリを設定することになります。
カテゴリは接触の相手ごとに処理を切り替えるためのUInt32型の値です。
このコードをGameSceneのクラス変数として追加しましょう。
let categoryS: UInt32 = 0x1 << 0 let categoryE: UInt32 = 0x1 << 1 var enemyLifePoint = 100
- 自キャラをcategoryS、敵キャラをcategoryEというカテゴリ分けにしました
- ビット演算をするので、各ビットが各クラスを表すようにします
- ついでに、敵のライフ値を表すクラス変数も追加しておきます
クラスが接触に関する処理を受け持つことを示すために、次のように、クラスにSKPhysicsContactDelegateプロトコルを追加します。
class GameScene: SKScene, SKPhysicsContactDelegate {
このコードをsuper.init呼び出しの下に入れましょう。
physicsWorld.contactDelegate = self nantoka.physicsBody?.categoryBitMask = categoryS nantoka.physicsBody?.contactTestBitMask = categoryE let enemy = SKSpriteNode(imageNamed: "enemy") enemy.position = CGPoint(x: 160, y: 400) enemy.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(80, 80)) enemy.physicsBody?.dynamic = false enemy.physicsBody?.categoryBitMask = categoryE enemy.physicsBody?.contactTestBitMask = categoryS enemy.physicsBody?.usesPreciseCollisionDetection = true addChild(enemy)
- contactDelegateをselfに設定することで、接触が判定されたときにGameSceneのdidBeginContactが呼び出されるようになります
- categoryBitMaskには自分のカテゴリを表す
- collisionBitMaskに衝突の相手となるカテゴリを示す値を設定します。
- 複数ある場合は、|(ビットOR演算子)区切りで並べます
contactTestBitMaskを設定したSKSpriteNodeが接触すると呼び出されるdidBeginContactを定義します。
このコードをGameSceneクラスに追加しましょう。
func didBeginContact(contact: SKPhysicsContact) { println("didBeginContact") let (starBody, other) = contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask ? (contact.bodyA, contact.bodyB) : (contact.bodyB, contact.bodyA) switch other.categoryBitMask { case categoryE: enemyLifePoint -= 10 enemyLifeGuage.size = CGSizeMake(enemyLifeGuageBase.size.width * CGFloat(enemyLifePoint) / CGFloat(100.0), enemyLifeGuageBase.size.height) default: break // nothing to do } }
- 引数のSKPhysicsContactには接触した二つの物体を表すSKPhysicsBodyのbodyAとbodyBとして渡されます。
- どちらがどのカテゴリの物体なのかは予め分かりませんので、それぞれのcategoryBitMaskを利用して、それぞれ適切な変数に入れ直します。
- 今回は接触する片方が必ず自キャラであることと、自キャラのcategoryBitMaskが一番小さい数値に対応していることを利用しています。
- 自キャラをnantokaBodyに、接触相手をotherに入れ直しています。
- 今回は接触する片方が必ず自キャラであることと、自キャラのcategoryBitMaskが一番小さい数値に対応していることを利用しています。
- 接触相手の種類に応じた処理にするために、other.categoryBitMaskでswitchしています。
- 例えば、アイテムを導入してゲットの処理を追加する場合などには、ここのcaseを増やして行くことになります。
- 敵に接触した場合は、とりあえず敵ライフ値を10減らすことにしました
- ライフ値の減少に合わせて、ライフ値ゲージの表示も変更します
完成です!
おめでとうございます!
では、さっそく⌘+Rでゲームを起動して、遊んでみましょう
おわりに
お疲れ様でした!
皆様が面白いゲームを開発して、楽しい人生をおくるきっかけ作りにこの記事が役立てば、これに勝る喜びはありません。
Swiftの記事のまた次のネタも仕込み中です。
あとがき
土日に作業をしていて疲れたら、ふらっと電車に乗って適当な街に散歩に行きます。
最初から散歩行くつもりなら散歩ガイドでも持って行くんですが、ふらっと行くときも多いので、こんなアプリを使っています。
https://itunes.apple.com/jp/app/supotto-jiantsukaru!fotopot/id813779149?mt=8&uo=4&at=10l8JW&ct=hatenablog
自分の居る場所周辺で話題になっているモノや店が分かって超便利です。
散歩ガイドは英語の勉強もかねて洋書を使います。楽しいです。
Swiftで初めてiPhoneゲーム開発する人向チュートリアル(マルバツゲームを例にStep by Step解説)
マルバツゲームとは
二人でマルとバツを交互に書いて行って、先に三つ並べた方が勝ちっていう例のやつです。
これの作り方を通して、「Swift分かんない」「iPhoneアプリ作ったことない」という人がiPhoneゲーム開発の第一歩を踏み出すことを目的としたチュートリアルです。
他のプログラミング言語の経験も全くない方でも一応出来ると思いますが、専門用語がちょっと難しいかもしれません ^^;
その辺は、まずは目をつぶって頂いて、とりあえず書いてある通りにやってみて下さい。
iPhoneアプリ開発環境であるXcodeの準備とSwiftのとっかかりとしては、こちらのチュートリアルをご覧下さい。
続編も公開中
新しいゲームプロジェクトの開始
プロジェクトの開始方法は2通りあるので、どちらかで
- Welcome to Xcode画面でCreate a new Xcode projectを選択する
- もしくは、既に起動している場合は、メニューからFile > New > Project
プロジェクトの作成
新しいプロジェクトの画面で
- 左側のiOSの下のApplicationをクリック
- Gameをクリック
- Nextをクリック
Choose options for your project画面で
- 以下の通り入力
- Product Name: OXGame
- Organization Name: x
- 本来はちゃんと考えるべきだが、練習なので適当な文字列にしておく
- Organization Identifier: x
- 本来はちゃんと考えるべきだが、練習なので適当な文字列にしておく
- Language: Swift
- Game Technology: SpriteKit
- Device: iPhone
- Nextをクリック
SpriteKitは、2Dゲームを作るためのフレームワーク(Apple純正)です。
Save画面
- 好きなディレクトリを選択
- create Git repositoryにチェックを入れておきましょう
- Createをクリック
新しいプロジェクトの画面
まっさらな状態ですが、アプリを動かしてみましょう
- 左上の再生ボタンを押す
- あるいは、⌘ + R (コマンドキーを押しながらRキーを押す)
シミューレーターが起動して、何らかのアプリが動きましたね
Xcodeに戻って、アプリを停止しましょう
- 左上の停止ボタンを押す
- あるいは、⌘+.(コマンドキーを押しながらピリオドキーを押す)
よけいなファイルを消す
- Project Navigatorを表示します
- 再生ボタン下のフォルダアイコンをクリック
- もしくは、⌘ + 1 (コマンドキーを押しながら1キー)を押す
- GameScene.sksを右クリック > delete
- 確認ダイアログ > Move to Trash
よけいなコードを消す
左側のProject NagivatorでGameScene.swiftをクリックして開く
- didMoveToViewメソッドを削除
- touchesBeganメソッドを削除
GameScene.swiftのコードはこうなるはずです
import SpriteKit class GameScene: SKScene { override func update(currentTime: CFTimeInterval) { /* Called before each frame is rendered */ } }
GameViewController.swiftを開きます
- class GameViewControllerの中の
- viewDidLoadのsuper.viewDidLoad()以外を全部消す
- shouldAutorotateを消す
- supportedInterfaceOrientationsを消す
- didReceiveMemoryWarningを消す
GameViewController.swiftのコードはこうなるはずです
import UIKit import SpriteKit class GameViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } override func prefersStatusBarHidden() -> Bool { return true } }
背景画像をプロジェクトに追加する
- この画像を保存して下さい
- 名前はbg.pngにすること
- 保存した場所をFinderで開いてProject NavigatorのSupporting Filesにドラッグ&ドロップする
- Choose options for adding these filesダイアログ
- Copy items if necessaryにチェック
- finishをクリック
背景画像を表示するコードを書く
まずは、GameScene.swiftからです。
GameSceneクラスの中に以下の2つのinitを追加します。
(Swiftではinitがクラスのコンストラクタです)
required init(coder aDecoder: NSCoder) { fatalError("NSCoder not supported") }
override init(size: CGSize) { super.init(size: size) anchorPoint = CGPoint(x: 0, y: 0) let background = SKSpriteNode(imageNamed: "bg") background.position = CGPoint(x: 0, y: 0) background.anchorPoint = CGPoint(x: 0, y: 0) addChild(background) }
SpriteKitの座標はCGPointで表すことになっていて、原点(0, 0)は画面の左下隅です。
単純に静止画像を表示するために、SKSpriteNodeを使います。
画像の位置を設定して
シーンに(自分自身に)画像のオブジェクトをaddChildして表示されます。
続いて、GameViewController.swiftです。
GameViewControllerクラスに、scene変数を追加します。
var scene: GameScene!
viewDidLoadの中身を追加して次のコードのようにします。
override func viewDidLoad() { super.viewDidLoad() let skView = view as SKView skView.multipleTouchEnabled = false scene = GameScene(size: skView.bounds.size) scene.scaleMode = .AspectFill skView.presentScene(scene) }
上で記述したGameSceneクラスのinitを使って、GameSceneを生成します。
SpriteKitビューのpresentSceneにGameSceneを渡せば、シーンが表示されます。
⌘+Rで背景画像が表示されることを確認しましょう
タップに反応させる
- Project NavigatorでMain.storyboardをクリックして開く
- 右下の検索ボックスにtapと入力
- Tap Gesture Recognizerが表示されるので、ドラッグ&ドロップする
- タキシードに蝶ネクタイっぽいアイコンをクリックしてAssistan Editorを開く
- controlを押しながらTap Gesture Recognizerをドラッグして、コード中のGameViewControllerの中にドロップする
- 出てきたダイアログで下記の通り入力して、connectボタンを押す
- Connection: Action
- Name: didTap
- UITapGestureRecognizer
- Assistant Editorを使い終わったら閉じておきます
うまく行くとdidTapのコードが生成されます
@IBAction func didTap(sender: UITapGestureRecognizer) { }
- Tap Gesture Recognizerを右クリックする
- 出てきたダイアログで、
- Outletsの下にあるdelegateの右の○をドラッグして、Game View Controllerにドロップ
- 出てきたダイアログで、
今の操作でUITapGestureRecognizerとGameViewControllerにdelegateの関係を追加したので、
コードにもこれを反映します。
GameViewController.swiftを開きます。
GameViewControllerクラスにUIGestureRecognizerDelegateプロトコルを追加して次のようにします。
class GameViewController: UIViewController, UIGestureRecognizerDelegate {
画面をタップするとdidTapの中身が実行されることになるので、ひとまずタップ位置の座標を出力することにしておきます。
@IBAction func didTap(sender: UITapGestureRecognizer) { let location = sender.locationInView(view) println("tapped at \(location.x), \(location.y)") }
⌘+Rでアプリを起動してみましょう。
シミュレーターのどこかをタップしてみると、
コンソールにタップ位置が出力されます。
コンソールが表示されていない場合は、メニューのView > Debug Area > Activate Consoleで表示してから、タップしてみて下さい
タップされた升目に○×を画像を配置する
まずは、背景画像をプロジェクトに取り込んだのと同じ要領で、次の2つのマルとバツの画像をSupporting Filesフォルダに取り込みます。
ファイル名として、
○画像は、o.png
×画像は、x.png
にします
GameScene.swiftを開きます。
下の説明の通り、最終的にGameScene.swiftのコードがこうなるようにしましょう。
import SpriteKit class GameScene: SKScene { var turn_o = true required init(coder aDecoder: NSCoder!) { fatalError("NSCoder not supported") } override init(size: CGSize) { super.init(size: size) anchorPoint = CGPoint(x: 0, y: 0) let background = SKSpriteNode(imageNamed: "bg") background.position = CGPoint(x: 0, y: 0) background.anchorPoint = CGPoint(x: 0, y: 0) addChild(background) } override func update(currentTime: CFTimeInterval) { /* Called before each frame is rendered */ } func transform(w: CGFloat) -> CGFloat { switch w { case let w where 0 < w && w < 80: return 0 case let w where 80 < w && w < 160: return 80 case let w where 160 < w && w < 240: return 160 default: return -80 } } func mark(location: CGPoint) { let imageName = turn_o ? "o" : "x" let sign = SKSpriteNode(imageNamed: imageName) sign.position = CGPoint(x: transform(location.x), y: 160 - transform(location.y - 320)) sign.anchorPoint = CGPoint(x: 0, y: 0) addChild(sign) turn_o = !turn_o } }
- 「マルの番かバツの番か」を管理する変数turn_oをGemeSceneクラスに追加します。
- turn_oがtrueなら○の番、falseなら×の番を表していることにします
- タップされた場所から、画像を配置する正確な位置を計算する補助のtransformメソッドを、GameSceneクラスに追加します
- ○×記号の画像を升目に配置して表示するmarkメソッドをGameSceneクラスに追加します
GameViewController.swiftを開きます。
import UIKit import SpriteKit class GameViewController: UIViewController { var scene: GameScene! override func viewDidLoad() { super.viewDidLoad() let skView = view as SKView skView.multipleTouchEnabled = false scene = GameScene(size: skView.bounds.size) scene.scaleMode = .AspectFill skView.presentScene(scene) } override func prefersStatusBarHidden() -> Bool { return true } @IBAction func didTap(sender: UITapGestureRecognizer) { let location = sender.locationInView(view) println("tapped at \(location.x), \(location.y)") scene.mark(location) } }
- 上で定義したmarkメソッドを呼び出すように、didTapメソッドの最後の一行を追加しましょう
完成です
おめでとうございます!
では、⌘+Rでゲームを起動して遊んでみましょう!
プログラムの構成に関する注意点
このプログラムでは、説明を簡単にするために、
ゲームの進行とルールに関わること(turn_oによるどちらの番かの管理)を、GameSceneの中で行っていますが、
本来はそういったことを管理するクラスを作ってその中でまとめた方がよいです。
おまけ
背景色を変える
GameSceneのbackgroundColorプロパティを設定すると、背景色が変更できます。
このコードを(例えば、GameSceneクラスのinitの中に)追加してみましょう。
backgroundColor = SKColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
おすすめツール
おわりに
お疲れ様でした!
皆様が面白いゲームを開発して、楽しい人生をおくるきっかけ作りにこの記事が役立てば、これに勝る喜びはありません。
私も面白いゲームを作ろうと思って、色々本を読んでみて、アイデアがわき上がってきました。
スポンサーリンク
あとがき
Swiftでテトリス作る英語記事読みましたけど、長過ぎで疲れましたよ、ほんとに・・・。
最近は、仕事のストレスも結構溜まるので、ストレスを和らげるためにお酒を良く飲むようになりました。
カンパリを買って、自分でソーダで割って飲んでます。
Dr.Pepperが好きな方はカンパリソーダもきっと好きになると思います。是非試してみて下さい!
運転免許の更新で新宿に行ったレポート(写真付き道順案内、時間、注意点)
新宿駅から会場まで
ロータリーは左側を回って動く歩道のある通路を行きます。
トンネルを抜けてしばらく進み、このオブジェクトで左に入ります。
中に入るとそこは1階なので、右奥のエレベーターで2階に行きます。
手続きや写真撮影が終わると、一旦ホールに出て反対側の講習室で講習の受付をします。
講習が終わったら、新しい免許証を受け取って、コンピューターで本籍地の確認をして、全ておしまいです。
時間は意外とかからない
午前中に行きました(9:40ぐらいにJR新宿駅着)が、
受付の列はたいして並んでいませんでした(たしか5人以下)。
受付や手続きは、
めちゃめちゃ合理化されているので、待ち時間はほとんどなく、
15分ぐらいで全て終わりました。
講習はきっかり30分間です。多分、1時間ごとにやってます。
(待ち時間でトイレに行きましょう)
ということで、平日に行った場合、
- タイミングが良ければ1時間以内で終わるでしょう
- タイミングが悪くても1時間半ぐらいだと思います
写真は持参不要です
手続きの最後に写真を撮ります。
1回撮って終わりです。
取り直しさせてもらう余裕は基本的にありません。
奇麗に写りたい人は、鏡を持って行って、その場で色々整えましょう。
(一旦、トイレに行って戻ってくることも出来ますが、時間がもったいない)
- 出版社/メーカー: ヤマムラ
- メディア: ヘルスケア&ケア用品
- 購入: 2人 クリック: 8回
- この商品を含むブログを見る
ヤフー株式会社株主総会2014参加レポート【お土産画像あり】
スポンサーリンク
会場
会場は、例年通り、東京国際フォーラムのホールAです。
山手線有楽町駅の目の前にあります。
ホールがAからDまでありますが、カラフルな案内に従ってAホールまで行きます。
開場まで時間があったので、Aホール付近の神戸屋で簡単な朝ご飯を食べました。この時間帯は、ゆったりとして、とても気持ちのいいカフェですね。
スポンサーリンク
受付にて
お土産を頂きました。
高級なペンとメモ帳、ヤフーの書籍でした。
書籍は一般に販売されているものですね。
10倍挑戦、5倍失敗、2倍成功!?: ちょっとはみだし もっとつながる 爆速ヤフーの働き方
- 作者: 長谷川琢也,ヤフー
- 出版社/メーカー: 東洋経済新報社
- 発売日: 2014/06/20
- メディア: 単行本
- この商品を含むブログ (1件) を見る
開始
- 宮坂さんの挨拶があり、
- 吉井さんの監査報告
- 決算報告
- などなど
質疑
どの株主総会でも同じですが、質疑応答がいちばん楽しいです。
以下、簡単なメモです。正確な詳細は、公式IRページから動画をご覧下さい。
株主総会 - 株式情報 - IR情報 - ヤフー株式会社
Q:30代サラリーマン風の男性
国際会計基準の導入
▶︎検討中
▶︎爆速で感動
▶︎導入の目的教えて。
A:
M&Aを行う
暖簾
Q:おじいさん
広告について
積極的に銀座のイメージに関わってほしい
すずきそのこの写真もなくなったし。
電光ニュースもなくなったし。
銀座の土地が一番安かったのは、1967年(?)
銀座を利用して、スパイスとして
A:
地域の情報を有効に利用していく
Q:おばちゃん1547さん
孫さんにお願い
隣国が日本中の土地を買いまくっている
日本で現金をもっているのは孫さん
孫さんが
ヤフーが株上がんなくてもいいので。
ヤフーの株は処分したけど、これだけは孫さんにお願いしたくて、ここに来た
A:
情報革命が本業なので土地を買うのはどうかと思うが、検討する
Q:おっさん
孫会長に。
孫さんの持ち株が0。
知恵袋見ても書いてない。
A:孫さん
ソフトバンクの株は20%持ってる
ソフトバンクを通じてヤフーの株を持っている
全面支援している。運命共同体。
Q:
CSR関係の質問をしたい。
3Dプリンターで感動した。
復興はどういう体制で、どういう支援をするのか。
どういう風に意思決定しているのか
A:宮坂
CSRを考えている部門がある
インターネットならではのこと。
募金や寄付
チャリティーをやる
6人石巻に引越して常駐している。
大儲けはしなくていいので、
黒字になれば、拡大できる。ビジネス感覚は持って拡大再生産できるように。
Q:
久々の参加。新しい社長を見たかった。一生懸命なイメージがある。頑張ってほしい。
業績は上がってる。株価も2、3年は上がっている。基準値に満たしてない。
普通は業績が良ければ、高値は更新するはず。
御社は基準を満たしていない。なんで株は人気ないのか。
A:宮坂
業績からするともっと評価されていいはず。
M&Aとか頑張る。
還元も成長とのバランスを考えながらやっていく。
IR活動にもっと時間を費やす。
株主がわくわくする夢のある挑戦をしていく。株主も従業員もわくわくする。
株価が上がるように頑張る。
Q:
取り締まり役が7人になった背景は?
A:宮坂
人員数、定款では10人。
今までヤフーからは自分だけ。
Q:わかいひと
Y!mobileについて。
Y!mobileの立ち位置としてソフトバンクとカニバラないように、
どのような戦略をとるのか。
アメリカとも通信料無料とか。
A:宮坂
イーアクセスのプランが発表されていないので、ご容赦下さい
ヤフーを使い易いように
ご期待下さい。
Q:
じちたいのりすくは?こすとをかけてもりえきがあがらない?
じゃぱんねっと銀行のセキュリティ
A:
だれもがものをうれるせかいにしたい。
きがるにうれるようになった。
やふーはぜんこくにえいぎょうにはいけない。
じちたいはじもとのしょうひんをよくしっている。
ふたんのないようにやってもらえてる。
セキュリティは盤石の
攻撃者も新しい手口。いたちごっこになる。
地味だけどセキュリティに
Q:
内容は素晴らしい。この調子で。
ビッグデータの解析による先手を打つ。
解析についてデータサイエンティストは不足している。
品質がおちている。間違えた結果に基づいて戦略を打っては行けない。
人工知能、バーチャルサイエンティストの導入は?
やっぱり人間?
A:
3つ重要。量はNo.1。多様性もたぶんNo1。鮮度。
日本で有数。おそらくNo1。
データサイエンティストを採用するアドバンテージになっている。
qubitalと人を出し合って、クライアントに提供するサービスをやっている
リーダーシップを発揮できるようにやっていく
Q:
株価が上がらない理由。
ヤフーはソフトバンクの財布に成っているからという懸念だと思う。
モバイルの買収も。
リーマン以来、3000億ぐらい融通している。
暖簾の消却をしないでほしい。
ヤフーらしさを追求してほしい。
ソフトバンクの株主でもある。ヤフーのお金をあてにしないでほしい。
子会社でも独自の判断をしなければいけない。
A:宮坂
事実関係。
イーアクセスの買収は自分から、ソフトバンクの孫さんに言った。「やっぱり買いません」
孫さんは「どっちなんだ」と。
別々の会社なので独立性を維持しながらやっていく。
情報開示も気を付ける。
世界のインターネットの会社はソフトウェアに閉じていない。
企業価値を最大化するためには、幅広いことに挑戦するかもしれない。
孫
本当に宮坂から爆速で打診がきた。
普段からお金足りない、借金しながらやってるのが疑われるもと。不徳の致すところ。
ボーダフォンを買収するときにお金に困ったときがある。
事業を証券化してお金を得た。
当時はヤフーがモバイルに参入できなかった。ヤフーボタンを付けたかった。
優先株
その時は僕から頼んだ。
ヤフーのアメリカからもチェックが入った。
全額返済できた。
ソフトバンクとの連携を深めるとヤフーにもメリットがある。
疎遠になることはない。
我田引水してないか、さらに襟を正していく
Q:1579の人
会長に。
ソフトバンクも野球も会社も順調。
集団的自衛権など、我々に対する教訓、何でもいいので、話をきいて帰ります!
A:
情報革命は始まったばかり。
人類が生まれて、3つ革命があった。
農業、産業、情報。
産業界は300年間伸び続けている。筋肉より頭の方が人間にとって重要。
ヤフーは情報革命のソフト・コンテンツ面で
日本に中核はヤフー。
株価は低迷していてもソフトバンクの中核はヤフーだ。
ヤフーが伸びるとソフトバンクグループにものすごい恩恵がある。
両者の協調関係は深まる。
日本ではイー・ベイに勝った。
ヤフージャパンがあって、アリババがある。ノウハウと経験がアリババの3度目の挑戦に役立った。
ヤフーが伸びるのはソフトバンクの世界戦略の起点。
ヤフージャパン自体の成功ではなくて、
孝行息子。
集団的自衛権は、コリンパウエルと対談したが、核兵器はゼロにすべきといっていたが、
どこの国とか肌の色とかを越えて、心を通じ合わせて、
幸せになることを願っている
airweave買って1ヶ月が経ったので再評価
腰痛は軽減した
airweaveに変えてからは朝起きたときに腰(というか背中)が痛いということは無くなった(腰痛体質はもちろんこれだけでは治らないけど)。ただし、肩こりはたまにバキバキするが、これはairweaveの問題ではなくではなく枕とか寝る前のストレッチで改善すべきだと思われる。
とにかく蒸れなくて快適
とにかく蒸れない。これだけでも高い金払ってairweave買った価値があったと思う。
干す必要がなく楽チン
一人暮らしだと干したくても干せなかったりするし、特に布団干すのが面倒くさい自分のような人には、とてもありがたいことだと思われる。
カバーの肌触りが良い
意外だったけど、標準装備のカバーは地味に気が利いている。
今のところ夏用の面をつかってるけど、サラサラ感が絶妙。
airweaveが高反発というのかは疑問
自分に取っては反発してる感じというより、むしろいい感じに体の形を支えてくれている感じがする
枕はやっぱりいまいちかな
しょうがない
ちなみに、買ったairweaveはこちら
airweave(エアウィーヴ) 夏蒸れず、冬暖かい厚さ6.5cm エアウィーヴ四季布団 シングル
- 出版社/メーカー: エアウィーヴ
- メディア: ホーム&キッチン
- この商品を含むブログを見る
ピュアSwiftコード100行でLispインタープリター作ろうとしたけど185行になったよ
English version of this page: knj4484: Lisp interpreter implemented in 185 lines of Swift
背景
このLispインタープリターで出来ること
(+ 0 (* 11 22) (- 333 30 3)) (first (1 2 3)) (let (a 2 b 3 c 4) (+ a b c)) (if (< 1 3) 2 3) (map (list 1 2) (\ (x) (* 2 x))) (defun calc (a b) (+ a b)) (calc 4 8)
- 整数の算術演算 + - * / %
- リスト操作 list quote first rest
- ローカル変数 let
- 比較演算 < > =
- if
- プロシージャー \
- map
- 関数定義 defun
使い方
- Xcode 6 betaをダウンロードして、インストールする
- GitHubからXcodeプロジェクトのリポジトリをクローンする
- Xcode 6でプロジェクトを開く
- command+Rでプロジェクトを実行する
- Lispの式を一つずつ、入力ボックスに入力し、evalボタンを押す
※エラー処理を一切していませんので、実行できないLispプログラムを実行しようとすると、アプリが固まります。再起動して下さい
サンプルプログラム
フィボナッチ数列の計算もちゃんとできます
(defun f (x) (if (= x 0) 1 (if (= x 1) 1 (+ (f (- x 1)) (f (- x 2)))))) (map (list 1 2 3 4 5 6 7 8 9 10) (\ (x) (f x)))
結論
knj4484$ wc -l Sweme2/{Evaluator,Expression}.swift 153 Sweme2/Evaluator.swift 32 Sweme2/Expression.swift 185 total
少なくとも現時点の自分の技術力では、100行以内のSwiftコードでLispインタープリターを作ることは無理でしたが、Swiftはコードを非常に簡潔に書くことが出来る言語だと分かりました。
考察
Swiftの良いところ
Swiftのいまいちなところ
※まだβ版なので、正式リリースでは改善されているかもしれません
- 文字列の処理が貧弱(このせいでTokenizerの実装がとても大変だった)
- n番目の文字を取り出すのも一苦労
- defaultが必須なのはやりすぎ
- クロージャーでreturn省けるのなら、そもそも普通の関数でもreturn省けていいんじゃないか
- ifの条件ではoptional bindingができるのだから、caseラベルでも出来て欲しかった
- case let x = f(): x
- [1,2,3][1..<2]の型が[Int]じゃなくてSliceなのがきつい
- これはエラーメッセージも分かりにくかったし、ハマった
- 文字列のインデックスが演算できなくてadvance関数を使わなければいけない
今後の展望
- マクロ展開機能を実装する
- 文字列と小数の演算を実装する
参考
- 計算機プログラムの構造と解釈[第2版]
- この本は、読むのが結構大変ですが、Lispの動作原理、実装方法などが分かりました。
- きつねさんでもわかるLLVM ~コンパイラを自作するためのガイドブック~
Swiftのビルトイン関数の使い方リファレンス
スポンサーリンク
abs(x: T) -> T
- 絶対値を返す
abs(-10) // r4 : Int = 10 abs(46.89) // r6 : Double = 46.89
assert(condition: @auto_closure () -> Bool, message: StaticString, file: StaticString, line: UWord)
- 条件が成り立っていることをチェックする
- Xcodeから実行した場合は、どのassertで終了したのかわかり、メッセージが出力される
let x = 3 assert(x == 3) assert(1 == 3) assertion failed: file <REPL Input>, line 1 Segmentation fault: 11
contains(sequence: S, item: S.GeneratorType.Element)
- sequenceの中にitemが含まれているか
let favoriteLanguages = ["c++", "Ruby", "Lisp", "Swift"] contains(favoriteLanguages, "Java") // r0 : Bool = false
contains(sequence: S, predicate: (S.GeneratorType.Element) -> L)
- sequenceの中にpredicateを真にする要素が含まれているか
let favoriteLanguages = ["c++", "Ruby", "Lisp", "Swift"] contains(favoriteLanguages) { $0.hasPrefix("S") } // r0 : Bool = true
countElements(x : T) -> T.IndexType.DistanceType
- xの中の要素数を返す。
- IndexTypeが
- RandomAccessIndexなら、O(1)
- でなければ、O(N)
(swift) let favoriteLanguages = ["c++", "Ruby", "Lisp", "Swift"] // favoriteLanguages : Array<String> = ["c++", "Ruby", "Lisp", "Swift"] (swift) countElements(favoriteLanguages) // r0 : Int = 4
distance(start: T, end: T) -> T.DistanceType
- 2点の距離を返す
- abs(start - end)と同じ
- 1次元の距離しか出来ないみたい
(swift) distance(1,2) // r1 : Int = 1 (swift) distance(-1,2) // r2 : Int = 3 (swift) distance((-1,-1),(2,2)) <REPL Input>:1:1: error: cannot convert the expression's type '$T2' to type '_BuiltinIntegerLiteralConvertible' distance((-1,-1),(2,2)) ^~~~~~~~~~~~~~~~~~~~~~~ (swift) distance([-1,-1],[2,2]) <REPL Input>:1:1: error: cannot convert the expression's type '$T2' to type '_BuiltinIntegerLiteralConvertible' distance([-1,-1],[2,2]) ^~~~~~~~~~~~~~~~~~~~~~~ (swift) distance("a", "b") <REPL Input>:1:1: error: cannot convert the expression's type '$T2' to type '_BuiltinIntegerLiteralConvertible' distance("a", "b")
スポンサーリンク
dropFirst(sequence: Seq)
- sequenceから最初の要素を除いた新しいシーケンス(配列など)を返す
var src = [1, 2, 3] dropFirst(src) // r1 : Slice<Int> = [2, 3] src // src : Array<Int> = [1, 2, 3]
dropLast(sequence: Seq)
- sequenceから最後の要素を除いた新しいシーケンス(配列など)を返す
var src = [1, 2, 3] // src : Array<Int> = [1, 2, 3] (swift) dropLast(src) // r0 : Slice<Int> = [1, 2] (swift) src // src : Array<Int> = [1, 2, 3]
dump(obj: T, name: String?, indent: Int, maxDepth: Int, maxItems: Int)
- オブジェクトの中身を標準出力に出力する
(swift) let favoriteLanguages = ["c++", "Ruby"] // favoriteLanguages : Array<String> = ["c++", "Ruby"] (swift) dump(favoriteLanguages) ▿ 2 elements - [0]: c++ - [1]: Ruby // r1 : Array<String> = ["c++", "Ruby"]
enumerate(seq : Seq)
- seqをイテレートするときに使う
- インデックスと値のタプルを返す
(swift) let favoriteLanguages = ["c++", "Ruby", "Lisp", "Swift"] // favoriteLanguages : Array<String> = ["c++", "Ruby", "Lisp", "Swift"] (swift) for (index, languageName) in enumerate(favoriteLanguages) { println(languageName) }
equal(sequence1: S1, sequence2: S2)
- sequence0とsequence1が各要素に同じ値をもつならtrue
let favoriteLanguages = ["c++", "Ruby"] let availableLanguage = ["c++", "PHP"] (swift) equal(favoriteLanguages, availableLanguages) // r2 : Bool = false (swift) equal("c++", "c+-") // r1 : Bool = false
filter(source: C, includeElement: (C.GeneratorType..Element) -> Bool)
- sequenceの要素の中でクロージャーincludeConditionがtrueを返すものから成るシーケンスを返す
let filtered = filter([1,2,3,4]) { $0 % 2 == 0 } for item in filtered { dump(item); } - 2 - 4
find(domain: C, value: C.GeneratorType.Element) -> C.IndexType?
- domainの中で(最初の)valueのインデックスを返す。なければ、nil
find(["a", "b", "c", "b"], "b") // r6 : Int? = 1 find(["a", "b", "c"], "x") // r7 : Int? = nil
スポンサーリンク
indices(seq : Seq)
- seqのインデックス列のRange
オブジェクトを返す
(swift) for index in indices(favoriteLanguages) { println("#\(index): " + favoriteLanguages[index]) } #0: c++ #1: Ruby
join(separator : C, seq : S) -> C
- シーケンスseqの要素の間にseparatorを差し込んだ結果の型Cのコレクションを返す
(swift) join(":", ["x", "y"]) // r3 : String = "x:y" (swift) join([0], [[1],[2],[3]]) // r2 : Array<Int> = [1, 0, 2, 0, 3]
map(source : C, transform : (C.GeneratorType.Element) -> T) -> [T]
- sourceの各要素にプロシージャーtransformを適用した結果の配列を返す
(swift) for converted in map([1,2,3], { $0 * $0 }) { println(String(converted)) } 1 4 9
max(x: T, y: T, z: T, rest: T...) -> T
- 引数の中の最大値を返す
max(4.0, 9.0, 5.0, 5.2) // r16 : Double = 9.0 max("a", "b", "c") // r18 : String = "c"
maxElement(range: R) -> R.GeneratorType.Element
- 最大の要素を返す
- 空のsequenceはエラーになる(バグかも)
(swift) let x1 = [1,3,2] // x1 : Array<Int> = [1, 3, 2] (swift) maxElement(x1) // r0 : Int = 3 (swift) let x2 : Int[] = [] // x2 : Int[] = [] (swift) maxElement(x2) // コケる
min(x: T, y: T, z: T, rest: T...)
- 引数の中の最小値を返す
(swift) min(4.0, 9.0, 5.0, 5.2) // r20 : Double = 4.0 (swift) min("a", "b", "c") // r21 : String = "a"
minElement
- 最小の要素を返す
- 空のsequenceはエラーになる(バグかも)
(swift) minElement(["a", "b", "c"]) // r22 : String = "a" (swift) let x2: Int[] = [] // x2 : Int[] = [] (swift) minElement(x2) fatal error: Can't unwrap Optional.None
- 説明省略
println
- 説明省略
reduce(sequence: S, initial: U, combine: (U, S.GeneratorType.Element) -> U)
- 説明が難しいのですが、等価な関数を書くとすれば、こんな感じです。
func reduce(sequence, seed, proc) var x = seed for item in sequence { x = proc(x, item) } return x }
(swift) let sum = reduce([1, 2, 3], 0, { $0 + $1 }) // sum : Int = 6 (swift) let max = reduce([2, 3, 1], Int.min, { $0 < $1 ? $1 : $0 }) // max : Int = 3
reverse(source: C) -> [C.GeneratorType.Element]
- 要素を逆順にした配列を返す
(swift) for item in reverse(["a", "b", "c"]) { println(item) } c b a
startsWith(s: S0, prefix: S1)
- sがprefixの要素の並びから始まればtrue
(swift) startsWith("grapefruits", "grape") // r25 : Bool = true (swift) startsWith([4, 6, 8, 9], [4, 6]) // r26 : Bool = true
sort()
- 説明省略