獣は月夜に何を見る...

【iPhone】Swiftでアプリ開発 | SimpleButtonApp

f:id:tukumosanzou:20180815202335j:plain

開発環境
iOS 12
Xcode 10
Swift 4.2



f:id:tukumosanzou:20180819003000g:plain


動画で確認できます





プロジェクトをつくる

XcodeでSingle View Appを選び、新しいプロジェクトをつくります。


SimpleButtonAppとでもしておいてください。


iPhoneアプリはUIKitを使用する場合は、Single View Appを使用することが多いです。



UIKitとはボタンやテキストや画像などを画面上に作成・配置できるものと思ってください。




f:id:tukumosanzou:20180815203245p:plain
f:id:tukumosanzou:20180815203322p:plain

Product Name プロジェクト名になります。
Team Apple IDを入力します、これがないとエラーがでます。
Organization Name 任意の名前でよいです。
Organization Identifier アプリの公開に必要な組織の識別名ですが公開するわけでなければ適当で良いです、com. 〜とするのが流行り?のようです。
Bundle Identifier Organization IdentifierとProduct Nameで自動で作成されます。
Language Swiftを選びます、Objective-Cも選べます。
Use Core Data アプリ内のデータベースを扱うためのフレームワークです、データベースを使わなければ必要ありません。
include Unit Tests 機能テスト用のファイルが作成されますが、初心者のうちは必要ありませんのでオフにしましょう。
include UI Tests UIテスト用のファイルが作成されますが、初心者のうちは必要ありませんのでオフにしましょう。




下の図のView as:の部分をクリックして、デバイスiPhone Xに変更します。


f:id:tukumosanzou:20180815223600j:plain


画像をAssetsフォルダにコピーする

Kenney.nlから使用する画像をダウンロードします。


ダウンロードしたファイルのPNGフォルダにpngファイルがありますので、今回はそこから使用します。


dog.pngとduck.pngを下の図の枠内にドラッグしてコピーします。


f:id:tukumosanzou:20180817131958j:plain


ボタンとラベルを追加する

Buttonを追加します。

f:id:tukumosanzou:20180816011227p:plain

①右上のアイコンをクリックして、ライブラリーパネルを表示します。
Xcode 10ではXcode9までとは表示の仕方が変更されてます。

②buttonで検索して、Buttonを View Controller上にドラッグして追加します。


Buttonを追加すると、黄色い三角マークでエラーが出た場合、これはButtonが画面のレイアウトで位置が指定されていないので、デバイスの種類が変わると位置が移動して、他のオブジェクト(表示するであろうテキストやボタンなど)と重なってしまいますよと警告が出ているわけです。


これを回避するには、Buttonの位置を指定する必要があります。


以下の図のように、Buttonを選択して4つのアイコンの左から2番目をクリックすると、ポップアップでAdd New Alignment Constraintsと出ますので、下の2つの項目をチェックしてください。


f:id:tukumosanzou:20180817135151j:plain



Buttonが画面の中央の正確な位置に表示されます、そうすれば警告マークが消えると思います。


ラベル(Label)を追加します。

ライブラリーパネルでLabelを検索し、View Controller上に追加します。

右のサイドバーのattributes inspectorで、Textを”START VIEW”にFontをBoldに、文字サイズを38に、Alignmentをセンタリングにします。

Labelを追加すると黄色い三角の警告がでます。

これもボタンと同じく、View ontroller上の位置が設定されていないためです。

Add New Alignment Constraintsを表示して、今回はHorizontally is Containerを選択します、そうするとセンタリングされます。

次にSTART VIEWのテキストをクリックして、CTRキーを押しながら上方向にドラッグして離すとプロパティーがでますので、Top Space to Safe Areaと選択します。

これで、画面上部からの位置が決まりました。

警告が消えると思います。

ボタンに画像を配置する

Buttonに現時点でテキストが表示されていますが、これを画像に変更します。


追加したButtonを選択して、右側のattributes inspectorでButtonのテキストを削除します。


続いて、imageの項目でタブをクリックしてDogを選択すると、ButtonにDog.pngが追加されます。


f:id:tukumosanzou:20180817132302p:plain


2つ目の画面を作成する

Dogの画像をクリックしたら、画面が切り替わるようにします。


新しい画面用のView Controllerを追加します。


下の図のように、libraryパネルからView Controllerをストーリーボード上にドラッグして追加します。


f:id:tukumosanzou:20180817140508p:plain


このように、なにもまだ追加されていないView Controllerが追加されます。

f:id:tukumosanzou:20180817140906j:plain


ここでも、ボタンとラベルを追加します。


方法は、最初のView Controllerと同じです、今度は画像はDuckです。


それでもまだ、黄色い三角マークの警告がでると思います。


今回はなぜかというと、新たにView Controllerを追加したのですが、要するに最初の画面と次の画面との接続ができていないと警告が出ているわけです。


これを回避するには、下の図のようにDocの画像をクリックしてCTRキーを押しながら次の画面にドラッグします。



f:id:tukumosanzou:20180817142032j:plain


プロパティーが出ますので、Action SegueのPresent As Popoverを選択します。


これは、画面が切り替わる時のアクション(アニメーション)を設定してます。


今回は、下から次の画面が飛び出すアクションです。


2つ目のView Controllerの処理を書くファイルをつくる

ScondViewController.swiftを作ったらView Cotrollerと紐つけます。

storyboard上の2つ目のView Controllerをクリックして、Identity inspectorのCustom ClassをSecondViewControllerにします。

@IBOutletと@IBActionを定義する

@IBOutletを設定します。
@IBOutlet --- View ControllerとSwiftコードのプロパティ(変数)を接続するもの。


ラベルをクリックして、CTRキーを押しながらドラッグします。

f:id:tukumosanzou:20180819142648j:plain




プロパティがでますので設定します。


f:id:tukumosanzou:20180819143319j:plain

Connection --- 変数との接続ですから、Outletのままです。
Object --- この変数がある場所(View Controller)がどこなのか。
Name --- この部分に変数の名前を入力します。
Type --- この変数のタイプ
Storage --- 今回はStrongでよいです。


StrageがStrongかWeakなのかについては、この辺りが参考になるかもしれません。

IBOutletにおける weak vs strong

Swiftの循環参照問題におけるunownedとweakの使い分けについて




@IBActionもやり方は同じです。
@IBAction --- View ControllerとSwiftコードのアクション(関数)を接続するもの。


Duckの画像をクリックしてドラッグするとプロパティが出ます。

f:id:tukumosanzou:20180820222054j:plain

Nameの部分にアクション(変数)名を入れます。

他の部分は、そのままで大丈夫です。

Swiftコードを書く。

varは変数を宣言するのに使います。


var changeLabel: UILabel!のchangeLabelの部分が変数名になります。


UILabel!は、その変数のなかに入れるもののタイプ(種類)が何であるかを表します、変数名と「:」で区切ります、後についている「!」は必ずそのタイプである事を指定しているような意味です。


Swiftでは、この部分がすこし理解がムズカシイ部分ですので、最初のうちはそのようなものとおもっててください。


この辺りが参考になるかもしれません。
どこよりもわかりやすいSwiftの”!”と”?” - Qiita




SecondViewController.swift

import UIKit

class SecondViewController: UIViewController {
    
    //START VIEWと紐ついた変数
    @IBOutlet var changeLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        
    }
    
    //Buttonと紐ついた関数。
    @IBAction func clickDuck(_ sender: Any) {
        
        //テキストを入れ替える
        changeLabel.text = "Duck! Duck!"
        
    }
    
}





UIKit | Apple Developer Documentation

UIButton - UIKit | Apple Developer Documentation





いかがだったでしょうか?。

ストーリボードとSwiftコードを紐つけてできる、かんたんなアプリケーションのつくりかたでした。



【iPhone】Swiftでアプリ開発 | UITabBarApp

f:id:tukumosanzou:20180811115351j:plain



今回は、TabBarControllerという下側にナビゲーションバーがでてくるやつです。



ナビゲーションのアイコンをクリックする事で画面が切り替わるようになります。



iOSアプリケーションの開発では、ストーリーボードでかんたんにつくれます。

開発環境
iOS 12
Xcode 10
Swift 4.2


f:id:tukumosanzou:20180822091017g:plain


動画で確認できます








ストーリーボードでかんたんにつくれます

Xcodeでsingle viewの新しいプロジェクトをつくります。


プロジェクト名は、UITabBarAppとでもしてしださい。


Main.storyboardを開いて、既にあるView controllerを図のように選択して削除してください。


f:id:tukumosanzou:20180811085832p:plain

画面の部分をクリックで選択して、deleteキーで削除する。


グレーになってView Controllerと文字がでたら、もういちどdeleteキーを押すと削除できます。


f:id:tukumosanzou:20180811090523p:plain

すると、Main.storyboardが真っ白になります。


f:id:tukumosanzou:20180811090657p:plain


右下のObject LibraryからTab Bar Controllerを選択して、Main.storyboard上にドラッグします。


f:id:tukumosanzou:20180811091713p:plain


追加できれば、下のような図が表示されます。


f:id:tukumosanzou:20180811092408p:plain


Tab Bar Controllerには、初期設定として2つのBar Itemで構成されています。


右側に矢印で別れて2つのView Controllerが表示されていますが、これが各Bar ItemごとのView Controllerになります。


ここで、いちどプロジェクトをビルドしてみてください(今回はiPhone xで行います)。


Failed to instantiate the default view controller for UIMainStoryboardFile Main-perhaps the designnated entry point is not set?


要するに、初期のView Controller(最初に表示する画面のこと)が設定されていないためです。


きちんと設定されて入れば下のような矢印が表示されていないといけません。


f:id:tukumosanzou:20180811093845p:plain


今の段階では、表示されていないのでこれを設定したいと思います。


Tab Bar Controllerをクリックして、ユーティリティパネルの属性オプション(attributes inspector)に移動します。


f:id:tukumosanzou:20180811094811p:plain


initial View Controller(最初に表示される画面のこと)の部分がチェックされていないので、図のようにチェックしてください。


f:id:tukumosanzou:20180811094854p:plain


プロジェクトをビルドすると、今度は正常に動作すると思います。


※実は、この→(矢印)はドラッグして移動することができます。

Main.storyboardを見ると、Tab Bar controllerには、各タブにひとつずつ、View Controllerがあらかじめ追加されていますが、Tab Bar ControllerからタブごとのView Controllerが矢印でつながっていますが、このつながりを「Segues(セグ)」といいます。


ここでは「Segues(セグ)」について詳細は省きますがそういうものだと覚えておいてください。


つぎに、2つの新しいView ControllerをTab Bar Controllerに追加したいと思います。


2つあるView Controllerの下側の分を図のように移動して、さらに2つのView ControllerをMain.storyboardに追加します。


f:id:tukumosanzou:20180811101559j:plain


下の図のようになればOKです。


f:id:tukumosanzou:20180811101646p:plain


Tab Bar Contrpllerをクリックしてcotrolキーをおしながら追加したView Controllerまでドラッグすると、下の図のようなオプションが出ますのでRelationship SegueのView Controllerを選択します。


f:id:tukumosanzou:20180811103003p:plain


これを、2つのView Controller毎に行うと、図のようになります。


「Segues(セグ)」が正しく設定できれば、Tab Bar Controllerと2つのView Controllerの間に矢印ができていると思います。


f:id:tukumosanzou:20180811103213p:plain


Main.storyboardの表示がiPhone xの外観になっていないときはView as:の部分をクリックするとデバイスのアイコンが出るのでiPhon xを選択すると切り替わります。


うまくいかないときは、別のデバイスを選択してからiPhone xを選択するとよいです。


f:id:tukumosanzou:20180811104321p:plain


追加した2つのView Controllerは属性インスペクタ(attributes inspector)からテキストとアイコンをカスタマイズできます。


Object libraryからTab Bar ItemをView Controllerのタブのアイコンまでドラッグします、緑色のプラスマークが表示されたらOKです。


f:id:tukumosanzou:20180811105514p:plain


View ControllerのTab Barを選択して、属性インスペクタ(attributes inspector)からSystem Itemをクリックするとアイコンのタイプが選べるのでDownloadsとします。


f:id:tukumosanzou:20180811112303p:plain


View ControllerのViewの部分をクリックして属性インスペクタ(attributes inspector)のBackgroundで背景色が変更できます。


f:id:tukumosanzou:20180811112547p:plain


テキストも変更できます。


下の図のように左端のアイコンをクリックして属性インスペクタ(attributes inspector)のTitleでView Controllerのタイトルが変更できます。


f:id:tukumosanzou:20180811112651p:plain


アイコンのカスタマイズとテキストと背景色の変更を残りのView Controllerにも行ってください。


残りは「Contacts」、「 Favorites」、「 Search」です、背景色はなんでもよいです。


完成すると、図のようになると思います。


f:id:tukumosanzou:20180811113635p:plain


プロジェクトをビルドすると図のようになります。


f:id:tukumosanzou:20180808120727j:plain


ストーリーボードではコードを書くこともなく、ここまでできてしまいます、慣れるまでXcodeはムズカシイ感じがしますが、いちど慣れてしまうとかなり楽できますね。


ストーリーボードを使った、4つのタブを持つかんたんなアプリケーションをつくりました、いかがだったでしょうか?。


今回は、これにて終了です。





【iPhone】Swiftでアプリ開発 | SKShapeNodeAnimation

f:id:tukumosanzou:20180826223315j:plain




今回は、SkShapeNodeをつかった、かんたん泡泡なアニメーションの作り方です。


開発環境
iOS12
Swift 4,2
Xcode 10 Beta


f:id:tukumosanzou:20180826223441g:plain


プロジェクトをつくります

新しいプロジェクトをつくります。


プロジェクト名はSKShapeNodeAnimationとでもしましょう。


GameScene.swiftを開いて、余分なコードを削除して以下のようにします。


import SpriteKit

class GameScene: SKScene {
    
    override func didMove(to view: SKView) {
        
    }
    
    
    override func update(_ currentTime: TimeInterval) {
       
    }
}




プロジェクト作成時にintegrate GamePlayKitのチェックを入れているとimport GamePlayKitの一文があると思いますが、今回は使わないので削除して良いです。



では、先ず背景をつくるcreateBackground()を作成します。

class GameScene: SKScene {
    
    //背景用の変数を作成。
    var background: SKShapeNode!
    
    override func didMove(to view: SKView) {
        
   //関数の呼び出し
        createBackground()

    }
    
    
    func createBackground() {

        //画面と同じ大きさの四角形を作成する。
        let rect = CGRect(x: frame.minX - 1, y: frame.minY - 1, width: frame.width, height: frame.height + 2)

        //四角形をSKShapeNodeとして登録する。
        background = SKShapeNode(rect: rect, cornerRadius: 0.01)

        //四角形の面の部分の色を決める。
        background.fillColor = SKColor.darkGray

        //z軸方向の重なり順を決める。
        background.zPosition = 2

        //GameSceneに追加する。
        self.addChild(background)
    }
}




特に変わったことはしていません、今回は画像を使わずに背景となる四角形を作成するようにしました。



frame.midXは画面サイズのx軸のちょうど真ん中、frame.heightは画面サイズの高さの値をそれぞれ表してます。



frame.minX - 1、frame.minY - 1としているのは、そのままだと上下に少し隙間ができるため1px分下げて下側の隙間を埋めて、その分frame.height + 2で上の隙間を埋めてます。



ほかにも、midY minX minY maxX maxY width などがあります。



SKShapeNode - SpriteKit | Apple Developer Documentation
CGRect - Core Graphics | Apple Developer Documentation



シミュレーターで見てみると以下のようになってれば大丈夫です。
f:id:tukumosanzou:20180806072742j:plain

泡をつくる、createBubble()を作成します。



createBackground()の直前に追加します。

class GameScene: SKScene {
    
    var background: SKShapeNode!
    
    override func didMove(to view: SKView) {
        
        createBackground()
    }
    
    
    override func update(_ currentTime: TimeInterval) {

        //関数の呼び出し。
        createBubble()
    }
    
    
   func createBubble() {
        let bubble = SKSpriteNode(color: SKColor.white, size: CGSize(width: 10, height: 10))
        let startPosition = CGPoint(x: frame.width / 2, y: frame.minY - bubble.size.width)
        bubble.position = startPosition
        bubble.zPosition = 3
        background.addChild(bubble)
    }
    
    
    func createBackground() {
    }
}



関数の呼び出しはoverride update()の中で行います。


これでシミュレーターで見てみると特に変わっていないのですが、それは泡の発生する位置が画面の外になっているからです。

stratPosition- bubble.size.widthで泡の大きさ分画面の外のなっているので試しにその部分を消してみると画面の一番下に出てくると思います。



floatBubbles()を作ります。

いまのままだと、同じ位置にしか泡ができないので徐々に浮上するようにしたいと思います。

呼び出しを、createBubble()の直後に追加します。

floatBubbles()createBubble()の直前に追加します。

class GameScene: SKScene {
    
    var background: SKShapeNode!
    
    var activeBubbles: [SKSpriteNode] = []
    
    override func didMove(to view: SKView) {
        
        createBackground()
    }
    
    
    override func update(_ currentTime: TimeInterval) {
        createBubble()

   //関数の呼び出し。
        floatBubbles()
    }
    
    
    func floatBubbles() {

        //for inでループさせる。
        for i in background.children {

            //泡をx軸方向に少しだけ動かすための値。
            let xOffset = CGFloat(arc4random_uniform(20)) - 10.0

    //泡をy軸方向に動かすための値。
            let yOffset = CGFloat(20.0)

            //移動先の値を決める
            let newLocation = CGPoint(x: i.position.x + xOffset, y: i.position.y + yOffset)

            //移動先へ0.2秒間隔で動かす。
            let newAction = SKAction.move(to: newLocation, duration: 0.2)

            //アクションを実行する。
            i.run(newAction)
         }
    }

    func createBubble() {




arc4random_uniform()は乱数を生成します。

CGFloat(arc4random_uniform(20))では、現在表示されている泡を上に浮かべるために、フレームごとにxの値に0〜19の幅で乱数をつくります。


CGFloat(arc4random_uniform(20)) - 10.0は、結局のところ-10~9の間で乱数をつくり、それを泡のx軸方向の値に足すことでx軸方向の位置を変化させるという事になります。

つまり、泡が左右に動くことになります。

yの位置に関しては20を追加する事で、一定間隔で上に浮かぶようにします。

これで、ゆらゆらとしながら泡が浮かんでいくように見えます。




removeExcessBubbles()を作ります。

最後に、画面の上部に到達した後に泡を取り除かなければ、泡がいつまでも増え続けるのは、好ましくないので、画面外に出たら削除するようにしたいと思います。

以下のようにします。

呼び出しをupdate()内のfloatBunnles()の直後に、関数removeExcessBubbles()func floatBubbles()の直前に追加します。




最後に、全体のコードで表示します。

import SpriteKit

class GameScene: SKScene {
    
    var background: SKShapeNode!
    
    override func didMove(to view: SKView) {
        
        createBackground()
    }
    
    
    override func update(_ currentTime: TimeInterval) {
        createBubble()
        floatBubbles()

        //関数呼び出し。
        removeExcessBubbles()
    }
    
    
    func removeExcessBubbles() {

        //for inでループさせる。
        for i in background.children {

            //画面の最上部より上になったら
            if i.position.y > frame.maxY {

                //泡を削除する。
                i.removeFromParent()
            }
        }
    }


    func floatBubbles() {
        for i in background.children {
            let xOffset = CGFloat(arc4random_uniform(20)) - 10.0
            let yOffset = CGFloat(20.0)
            let newLocation = CGPoint(x: i.position.x + xOffset, y: i.position.y + yOffset)
            let newAction = SKAction.move(to: newLocation, duration: 0.2)
            i.run(newAction)
         }
    }

    
    func createBubble() {
        let bubble = SKSpriteNode(color: SKColor.white, size: CGSize(width: 10, height: 10))
        let startPosition = CGPoint(x: frame.width / 2, y: frame.minY - bubble.size.width)
        bubble.position = startPosition
        bubble.zPosition = 3
        background.addChild(bubble)
    }
    
    
    func createBackground() {
        let rect = CGRect(x: frame.minX - 1, y: frame.minY - 1, width: frame.width, height: frame.height + 2)
        background = SKShapeNode(rect: rect, cornerRadius: 0.01)
        background.fillColor = SKColor.darkGray
        background.zPosition = 2
        self.addChild(background)
    }
}







今回は、基本的なSpriteKitでのアニメーションの紹介でした。 では、また次回。





今のスマホ時代、ブルーライト対策は必要!

わたしも、「pcめがね」を使ってます。 使わないと、違いはわからない!。 「J!NS SCREEN」、デザインがおしゃれでわたしは好きです。


JINS PC」は、疲れと戦うメガネ「JINS SCREEN」に生まれ変わりました! 眠りが浅い、寝つきが悪い、その原因はブルーライトかもしれません。ブルーライトを防いで、快適な眠りを体験してみませんか?





【 iPhone 】Swiftでアプリ開発 | UserNotifications

ローカル通知の覚書

iOSにおいて、アプリが起動していなくても、スケジュール等を通知してくれるアレ。

Main.storyboardにNavigation Controllerを追加。


f:id:tukumosanzou:20180803014128j:plain

ViewController.swift

import UIKit
import UserNotifications

//受信通知と通知関連の操作を処理するためのインタフェース。UNUserNotificationCenterのdelegateプロパティを使うのに必要。
class ViewController: UIViewController, UNUserNotificationCenterDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        //ナビゲーションバーの左ボタンを追加する。
        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Register", style: .plain, target: self, action: #selector(registerLocal))

        //ナビゲーションバーの右ボタンを追加する。
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Schedule", style: .plain, target: self, action: #selector(scheduleLocal))
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    @objc func registerLocal() {

        //通知センターの取得、アプリまたはアプリ拡張の共有ユーザー通知センターオブジェクトを返します。
        let center = UNUserNotificationCenter.current()

        //ユーザーのデバイスにローカルおよびリモートの通知が配信されたときに、ユーザーと対話するための認可を要求します。
        //options: UNAuthorizationOptions = []
        center.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
            if granted {
                print("Yay!")
            } else {
                print("D'oh")
            }
            
        }
    }
    
    @objc func scheduleLocal() {
        registerCategories()
        
        let center = UNUserNotificationCenter.current()

        //すべての保留中の通知要求のスケジュールを解除します。
        center.removeAllPendingNotificationRequests()

        //通知の編集可能なコンテンツ。
        let content = UNMutableNotificationContent()

        //通知アラートに表示するためのローカライズされた文字列を返します。
        content.title = NSString.localizedUserNotificationString(forKey: "Late wake up call", arguments: nil)
        content.body = NSString.localizedUserNotificationString(forKey: "The early bird catches the worn, but the second mouse gets chees.", arguments: nil)

        //通知のタイプを表すカテゴリオブジェクトの識別子。
        content.categoryIdentifier = "alarm"

        //通知に関連付けられたカスタム情報の辞書。
        content.userInfo = ["customData": "fizzbuzz"]

        //通知が配信されたときに再生されるサウンド。
        content.sound = UNNotificationSound.default()

        //このオブジェクトの作成に使用された日付コンポーネント。
        var dateComponents = DateComponents()
        dateComponents.hour = 10
        dateComponents.minute = 30

        //指定された時間が経過した後に通知を配信するトリガー条件。
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)

        //特定の日時に通知を配信するトリガー条件。
//        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)

        //ローカル通知をスケジュールする要求。通知の内容と配信のトリガー条件が含まれます。
        let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
        center.add(request)
    }
    
    //func scheduleLocal()内部で最初に呼び込む。
    func registerCategories() {
        let center = UNUserNotificationCenter.current()

        //受信通知と通知関連の処理を処理するオブジェクト、UNUserNotificationCenterDelegateを継承する必要がある。
        center.delegate = self

        //配信された通知に応答して実行するタスク。
        let show = UNNotificationAction(identifier: "show", title: "Tell me more ", options: .foreground)

        //あなたのアプリがサポートしている通知のタイプとそれを使って表示するカスタムアクション。
        let category = UNNotificationCategory(identifier: "alarm", actions: [show], intentIdentifiers: [])

        //アプリケーションの通知タイプと、それがサポートするカスタムアクションを登録します。
        center.setNotificationCategories([category])
    }
    //デリゲートに、配信された通知に対するユーザーの応答を処理するように要求します。
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        let userInfo = response.notification.request.content.userInfo
        
        if let customData = userInfo["customData"] as? String {
            print("Custom data received: \(customData)")

            //ユーザーが選択したアクションの識別子文字列。
            switch response.actionIdentifier {

            //ユーザーが通知インターフェイスを右にスライドさせてからアプリを開いたことを示すアクション
            case UNNotificationDefaultActionIdentifier:
                
                print("Default identifier")
              
            //ユーザーが通知インターフェイスを左にスライドさせてからアプリを開いたことを示すアクション
            //UNUserNotificationActionのidentifierプロパティの値
            case "show":
                
                print("Show more information...")
                
            default:
                break
            }
        }

        //終了時には、必ず補完ハンドラを呼び出します。
        //ユーザーの応答を処理した後、このブロックを実行して、完了したことをシステムに知らせる必要があります。
        completionHandler()
    }
}




UNUserNotificationCenter | Apple Developer Documentation

 func registerCategories() {
        let center = UNUserNotificationCenter.current()

        //受信通知と通知関連の処理を処理するオブジェクト、UNUserNotificationCenterDelegateを継承する必要がある。
        center.delegate = self

delegate = self とするためにはUNUserNtificationCenterDelegateをclassに追加する必要がある。

import UIKit
import UserNotifications

//受信通知と通知関連の操作を処理するためのインタフェース。UNUserNotificationCenterのdelegateプロパティを使うのに必要。
class ViewController: UIViewController, UNUserNotificationCenterDelegate {
 //ユーザーが通知インターフェイスを右にスライドさせてからアプリを開いたことを示すアクション
            case UNNotificationDefaultActionIdentifier:
                
                print("Default identifier")
              
            //ユーザーが通知インターフェイスを左にスライドさせてからアプリを開いたことを示すアクション
            //UNUserNotificationActionのidentifierプロパティの値
            case "show":
                
                print("Show more information...")



右スライドでOpenをクリック。 f:id:tukumosanzou:20180803021934j:plain コマンドパレットには以下のように表示される。 f:id:tukumosanzou:20180803024252j:plain



左スライドでViewをクリック。 f:id:tukumosanzou:20180803022005j:plain 表示がこうなるので、「Tell me more」をクリック。 f:id:tukumosanzou:20180803025056j:plain コマンドパレットには以下のように表示される。 f:id:tukumosanzou:20180803025125j:plain




@objc func registerLocal() {

        //通知センターの取得、アプリまたはアプリ拡張の共有ユーザー通知センターオブジェクトを返します。
        let center = UNUserNotificationCenter.current()

        //ユーザーのデバイスにローカルおよびリモートの通知が配信されたときに、ユーザーと対話するための認可を要求します。
        //options: UNAuthorizationOptions = []
        center.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
            if granted {
                print("Yay!")
            } else {
                print("D'oh")
            }
            
        }
    }

UNUserNotificationCenter | Apple Developer Documentation

あなたのアプリやアプリの拡張子のすべての通知関連行動を管理するために共有UNUserNotificationCenterオブジェクトを使用します。

requestAuthorization(options:completionHandler:) | Apple Developer Documentation

Parameters options --- UNAuthorizationOptionsを継承。 completionHandler granted error ローカルおよびリモートの通知がユーザーのデバイスに配信されたときに、ユーザーと対話するための承認を要求します。




//func scheduleLocal()内部で最初に呼び込む。
    func registerCategories() {
        let center = UNUserNotificationCenter.current()

        //受信通知と通知関連の処理を処理するオブジェクト、UNUserNotificationCenterDelegateを継承する必要がある。
        center.delegate = self

        //配信された通知に応答して実行するタスク。
        let show = UNNotificationAction(identifier: "show", title: "Tell me more ", options: .foreground)

        //あなたのアプリがサポートしている通知のタイプとそれを使って表示するカスタムアクション。
        let category = UNNotificationCategory(identifier: "alarm", actions: [show], intentIdentifiers: [])

        //アプリケーションの通知タイプと、それがサポートするカスタムアクションを登録します。
        center.setNotificationCategories([category])
    }

UNNotificationAction | Apple Develper Documentation

UNNotificationActionオブジェクトを使用して、配信された通知に応じてアプリケーションが実行できるアクションを定義します。

あなたのアプリがサポートするアクションを定義します。

たとえば、ミーティングのアプリケーションは、ミーティングの招待を受け入れるか拒否するかのアクションを定義します。

アクションオブジェクト自体には、アクションボタンとボタンの外観に表示するタイトルが含まれています。

アクションオブジェクトを作成したら、それらをUNNotificationCategoryオブジェクトに追加し、カテゴリーをシステムに登録します。

UNNotificationActionOptions | Apple Developer Documentation

.foreground --- このアクションにより、アプリはフォアグラウンドで起動します。

ユーザーがこのオプションを含むアクションを選択すると、システムはアプリケーションをフォアグラウンドに持ち込み、ユーザーに必要に応じてデバイスのロックを解除するように要求します。

このオプションは、ユーザーがあなたのアプリをさらに操作する必要がある操作に使用します。

単にこのオプションを使用してアプリケーションをフォアグラウンドに持ってこないでください。

Udemy(ユーデミー)とは?評価と評判 | お得な使い方まとめ!

f:id:tukumosanzou:20180801152711j:plain
更新日:2021-5-15

機械学習」を学ぶのにお世話になった、動画学習サービスのUdemy (ユーデミー)についてわかりやすく紹介させていただきます。

けっ..けっ..けっ..、決して流行りにのったわけではないですよ。


OpenCV」っていう、画像認識のライブラリが使いたかったんです。


ま、それはさておき

何か技能を習得したいと思ったとき、手軽に始められたら良いのになと思いますよね?。

そんな望みをかなえられるのが、Udemy(ユーデミー)なのです!。


Udemy (ユーデミー)をうまく使えば、プロ並みの技術を習得することもできるんです。


実際、私もWebデザイン、Webアプリケーション、データサイエンスを学ぶことができました、もちろん格安で!


そこでこの記事は、Udemy (ユーデミー)とはどういったサービスなのか・どういった使い方がいいのか等をまとめてみたいと思います。


           


この記事の目次

Udemy(ユーデミー)はどんなサービス?|料金は?

世界最大級のオンライン学習プラットフォームである「Udemy(ユーデミー)」です。 米国シリコンバレー発祥のUdemyは、学びたい人と教えたい人をつなぐCtoCの学習マーケットプレイスです。

なんだか、イマイチわからない人もいるかとおもいます。

「オンライン学習プラットフォーム...、CtoCの学習マーケットプレイス...」

?... 結局、なんのことかな?

大ざっぱに言うと、インターネットを使って動画でいろんな教材を学習しよう、ということです。

少し前までは、本や学習教室でしか学べなかった様々なものが、場所に関係なく学べるということですね。

Udemy(ユーデミー)で何が学べるのか



開発
ビジネススキル
ITとソフトウェア
仕事の生産性
パーソナルライフ&ファミリー
デザイン
マーケティング
趣味・実用・ホビー
写真
ヘルス&フィットネス
講師向けトレーニン
音楽
学問・教養
言語
入試・資格

現時点で、カテゴリーとしては上記のようになってます、各カテゴリーごとにはサブカテゴリーが存在します。

例えば、「開発」のカテゴリーではさらに

全ての開発
ウェブ開発
モバイルアプリ
プログラミング言語
ゲーム開発
データベース
ソフトウェアテスト
ソフトウェアエンジニアリング
開発ツール
Eコマース

となってまして、その中の「ウェブ開発」だとさらに

全てのウェブ開発
JavaScript
Angular
React
Node.Js
CSS
PHP
HTML
Reduxフレームワーク

と言語ごとにカテゴリーが存在します。

ですが、検索機能がありますのでキーワードで検索することもできるようになってます。


Udemy(ユーデミー)の料金プラン

気になる価格についてですがUdemy(ユーでミー)では講座ごとに価格が違います。

無料のものから2〜3万円ぐらいまで、いろいろです。

ですが、Udemy (ユーデミー)で講座の動画を購入すると料金がかかるのは最初だけで、あとは購入した動画に関しては無制限に視聴することができるので月払いで利用するわけではないので、お得だと思います。


Udemy(ユーデミー)のおすすめ動画

最近流行りの「データサイエンス(機械学習)」関連もありますね。

「データサイエンス」の基礎から学べるコースです。 とにかく長いですので、少しづつやるのがコツです。
世界で34万人が受講】データサイエンティストを目指すあなたへ〜データサイエンス25時間ブートキャンプ〜

機械学習ライブラリの「scikit-learn」を使って学習しますが、できれば書籍等で基礎学習してからの方が理解しやすいと思います。
Pythonで機械学習:scikit-learnで学ぶ識別入門

こちらも、初学者に人気のコースです。
機械学習」の初歩を数学を交えて教えてくれます。
スタートダッシュにはもってこいかもです。
【キカガク流】人工知能・機械学習 脱ブラックボックス講座 - 初級編 -

こちらは、上の続きの中級編です。 「初級編」は単純な単回帰でしたが、こちらは変数が複数ある重回帰という、ちょっとレベルが上の分析方法を学べます。
【キカガク流】人工知能・機械学習 脱ブラックボックス講座 - 中級編 -

Pythonやるなら、酒井さんというシリコンバレーで現役のエンジニアのコースが人気です。
基礎からわかりやすく説明してくれるので、最近話題のコースです。
現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイル

最近話題の「データサイエンス(機械学習)」関連が日本語で学べる、質の高い動画をいくつか紹介しましたが、これ以外にもたくさんあるのでチェックしてみてください。

人気のあるものには「ベストセラー」のタグがついています、一つの目安にはなると思います。



Udemy(ユーデミー)のメリット|評判と評価は?

f:id:tukumosanzou:20180801193815j:plain

・内容が思ったより、しっかりしている
・価格が安い
・いろんなジャンルがある
・本を買うより安く、それ以上の内容のものが得られる
・動画なので覚えやすい

比較的、良い印象を受けました。

Udemy(ユーデミー)は動画の内容のクオリティが高いです。

動画には購入者がレビューやコメントができるシステムで評価する制度がキチンとあり、講師の方も売り上げを上げるためにはいい加減なものをだせないようになってます。

こうしたレビューやコメントはだれでも見れますので、購入するときの目安にもなり安心です。

スマートフォンタブレット用のアプリがある。

Udemy (ユーデミー)ではスマートフォンタブレット用にアプリケーションが準備されていますiPhoneandroid共にありますのでご安心を!

このアプリを使うと、購入した動画をダウンロードしてインターネット環境の無い場所でも視聴できます。

休憩の時間や、ちょっとした移動時間に学習が可能です。




Udemy(ユーデミー)のデメリット&注意点

f:id:tukumosanzou:20180801194224j:plain

動画には翻訳機能がない?

Udemy (ユーデミー)は米国発のサービスなので、まだまだ日本語での動画が少ないです。


だからと言って、質が悪いのではなく日本の場合は逆に専門家の方がほとんどですので、クオリティは高いものが多いです。


動画には字幕機能はあるのですが、翻訳はできないので英語の場合はそのまま英語でしか利用できません。


※現時点では、自動翻訳による自動生成字幕で、英語音声の動画に関しては日本語が表示できるようです。


参考: 自動生成字幕に関するよくある質問


でも動画とは、見て学習するわけですから大抵は大丈夫だと思います。


実際、私も困ることはほとんどありませんでした、いざとなれば字幕をgoogle翻訳でっていうのも、今はできますからね。





Udemy(ユーデミー)を格安で使う方法!

クーポンを利用するとお得に購入できる!



Udemy (ユーデミー)で動画を購入すると、講師から不定期にクーポンコードがメールで送られてきます、もちろんその講師が作成した動画にしか使えませんがかなり安く買えたりするので欲しい動画のときは利用すると良いですね。


または、Udemy(ユーでミー)のことを取り扱ったりしたWebやテック系のサイトなどでクーポンコードを発行してくれてたりすることもあります。


情報は常にチェックしておくと、ラッキーなことがあるかもしれませんよ。




キャンペーン・セールを利用するとお得に購入できる!



Udemy (ユーデミー)では、かなり頻繁にキャンペーン・セールが行われます。

Webサイト版・アプリ版のどちらとも同じセールがあることもあれば、Webサイト版のみだったりアプリ版のみだったりするので、そこは注意してください。

購入さえすれば、Webサイトでもアプリでも同じ動画が使用できるようになりますので、ご心配なく。


新学期就職時期夏休み年末ブラックフライデー等にはかなりお得になることがあります。


それ以外でも、キャンペーン・セールは行われるのでしっかりチェックしてお得に購入しましょう。




キャンペーン・セール情報


2021 5/18から、今年最大のセールがあるようです
価格は1270円〜です
ほしいコースは、普段からチェックしておいてセールの時に買うのがベストです。


私もよくチェックしていますよ。



今回の年末・年始はステイホームの方が多いのでは無いでしょうか?、こんな時には学習してみるのも良いかもですよ。

まとめ

アプリを使って、好きな時間・場所で快適に学習しましょう!。


クーポンコードで、気になった動画をお得に購入しましょう。


セールを見極めて、お得に購入しましょう。


世界最大級のオンライン学習サイトUdemy とはどういったサービスなのかをザクっとまとめさせていただきました!、いががでしたでしょうか?


「Udemy(ユーデミー)ってなんだろう?」と思っていた人もいるかと思います。 実際、私も利用するまではそうでした。


でも、利用してみると知りたかった技術や知識を得ることができました。


思っているだけで、行動しないのはもったいないです。


今は、知りたいことが手軽に知ることができる時代になりました、ぜひこの機会に世界最大級のオンライン学習サイトUdemy を利用してみてくださいませ!。




          

SpriteKitでゲーム その1- SPACE SHOOTER⑨

f:id:tukumosanzou:20180702205113p:plain

前回までで、完成してますが今回はおまけを、すこし追加したいと思います。

せっかくですから、LaunchScreen.storyboard を調整してゲームのオープニング画面を作ります。


LaunchScreen.storyboard は既にあるので開いてください。

以下のような、感じに変更していきます。
f:id:tukumosanzou:20180728001821p:plain


右下の Object Library から Labelview に追加して以下の図の様に変更します。
f:id:tukumosanzou:20180728002028p:plain


"SPACE" "SHOOTER" の各テキストをクリックして図の部分の白い四角形内の赤い線が太くなっていたら線自体をクリックして図の状態にして Autoresizing の設定をオフにします。

各テキストごとに行ってください。
f:id:tukumosanzou:20180728002333p:plain


ちょっとわかりづらいので、拡大しますね。
f:id:tukumosanzou:20180728002507p:plain

赤い太線をクリックすると、以下のようになります。 そうすると、右側の赤い四角が中央に来ると思います。
f:id:tukumosanzou:20180728002522p:plain
これで、テキストがデバイスのサイズに合わせて自動で中央に表示できるようになります。



以上で終了です、全てはうまく説明仕切れてないかもですがなんとなく感じが掴めてもらえれば幸いです。

ありがとうございました。

完成した各ファイルの全コードを載せておきます。

GameScene.swift

import SpriteKit
import GameplayKit

var gameScore = 0

class GameScene: SKScene, SKPhysicsContactDelegate {
    
    var logoLabel: SKLabelNode!
    var gameState = GameState.startGame
    
    var livesImages = [SKSpriteNode]()
    var lives = 3
    
    var player: SKSpriteNode!
    
    var enemy: SKSpriteNode!
    var enemyArray = ["ufoBlue", "ufoRed", "ufoGreen", "ufoYellow"]
    
    var getTimer: Timer!
    
    var scoreLabel: SKLabelNode!
    var score = gameScore {
        didSet {
            scoreLabel.text = "SCORE: \(score)"
        }
    }
    
    var backgroundMusic = SKAudioNode()
    let musicURL = Bundle.main.url(forResource: "music", withExtension: "m4a")
    let laserSound = SKAction.playSoundFileNamed("LaserSoundEffect.mp3", waitForCompletion: false)
    let explosionSound = SKAction.playSoundFileNamed("explosion.wav", waitForCompletion: false)
    
    
    struct PhysicsCategories {
        static let none: UInt32 = 0
        static let player: UInt32 = 0x1 << 1 //1
        static let laser: UInt32 = 0x1 << 2 //2
        static let enemy: UInt32 = 0x1 << 3 //3
    }
    
    enum GameState {
        case startGame
        case endGame
    }
    
    
    override func didMove(to view: SKView) {
        
        physicsWorld.gravity = CGVector(dx: 0, dy: 0)
        physicsWorld.contactDelegate = self
        
        createBackground()
        createPlayer()
        createScore()
        createLives()
        
        if getTimer == nil {
            getTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)
        }

        
        backgroundMusic = SKAudioNode(url: musicURL!)
        addChild(backgroundMusic)
    }
    
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        guard let touch = touches.first else { return }
        
        switch gameState {
        case .startGame:
            let location = touch.location(in: self)
            
            
            if location.x < player.position.x {
                player.position.x -= 50
            } else if location.x > player.position.x {
                player.position.x += 50
            }
            
            if player.position.x > frame.maxX - player.size.width {
                player.position.x = frame.maxX - player.size.width / 2
                
            }
            
            if player.position.x < frame.minX + player.size.width {
                player.position.x = frame.minX + player.size.width / 2
            }
            
            createLaser()
            
        case .endGame:
            break
        }
    }
    
    
    override func update(_ currentTime: TimeInterval) {
       
    }
    
    
    func didBegin(_ contact: SKPhysicsContact) {
        
        if contact.bodyA.node?.name == "laser" || contact.bodyB.node?.name == "laser" {
            if contact.bodyA.node?.name == "enemy" {
                contact.bodyA.node?.removeFromParent()
                contact.bodyB.node?.removeFromParent()
                
                if let explosion = SKEmitterNode(fileNamed: "ExplosionEffect") {
                    explosion.position = (contact.bodyA.node?.position)!
                    addChild(explosion)
                }
                
            } else {
                contact.bodyA.node?.removeFromParent()
                contact.bodyB.node?.removeFromParent()
                
                if let explosion = SKEmitterNode(fileNamed: "ExplosionEffect") {
                    explosion.position = (contact.bodyB.node?.position)!
                    addChild(explosion)
                }
            }
            
            run(explosionSound)
            
            gameScore += 1
            score = gameScore
            
            return
        }
        
        guard contact.bodyA.node != nil && contact.bodyB.node != nil else { return }
        
        if contact.bodyA.node?.name == "player" || contact.bodyB.node?.name == "player" {
            if let explosion = SKEmitterNode(fileNamed: "ExplosionEffect") {
                explosion.position = player.position
                addChild(explosion)
            }
            
            run(explosionSound)
            
            contact.bodyA.node?.removeFromParent()
            contact.bodyB.node?.removeFromParent()
            
            subtractLife()
            createPlayer()
            
        }
    }
    
    
    func changeScene() {
        
        let scene = GameOverScene(size: frame.size)
        scene.scaleMode = self.scaleMode
        let transition = SKTransition.crossFade(withDuration: 1.5)
        view?.presentScene(scene, transition: transition)
    }
    
    
    func runGameOver() {
        if getTimer != nil {
            getTimer.invalidate()
            getTimer = nil
            
        }
        
        let changeSceneAction = SKAction.run(changeScene)
        let waitToChangeScene = SKAction.wait(forDuration: 2.5)
        let changeSceneSequence = SKAction.sequence([waitToChangeScene, changeSceneAction])
        run(changeSceneSequence)
    }
    
    
    func subtractLife() {
        lives -= 1
        
        var life: SKSpriteNode
        
        if lives == 2 {
            life = livesImages[0]
        } else if lives == 1 {
            life = livesImages[1]
        } else {
            life = livesImages[2]
        }
        
        life.removeFromParent()
        
        if lives == 0 {
            gameState = .endGame
            runGameOver()
        }
    }
    
    
    func createLives() {
        for i in 0 ..< lives {
            let spriteNode = SKSpriteNode(imageNamed: "player")
            spriteNode.position = CGPoint(x: frame.minX + 40 + CGFloat(i * 99) * 0.25, y: UIScreen.main.bounds.height * 0.90)
            spriteNode.setScale(0.25)
            addChild(spriteNode)
            
            livesImages.append(spriteNode)
        }
    }
    
    
    func createScore() {
        scoreLabel = SKLabelNode(fontNamed: "Optima-ExtraBlack")
        scoreLabel.fontSize = 24

        scoreLabel.position = CGPoint(x: frame.minX + 80, y: UIScreen.main.bounds.height * 0.92)
        scoreLabel.text = "SCORE:0"
        scoreLabel.fontColor = UIColor.white
        
        addChild(scoreLabel)
    }
    
    
    @objc func createEnemy() {
        
        enemyArray = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: enemyArray) as! [String]
        print(enemyArray)
        let enemyTexture = SKTexture(imageNamed: enemyArray[0])
        enemy = SKSpriteNode(texture: enemyTexture)
        
        enemy.physicsBody = SKPhysicsBody(circleOfRadius: max(enemyTexture.size().width / 2, enemyTexture.size().height / 2))
        enemy.physicsBody!.categoryBitMask = PhysicsCategories.enemy
        enemy.physicsBody!.collisionBitMask = PhysicsCategories.none
        enemy.physicsBody!.contactTestBitMask = PhysicsCategories.player | PhysicsCategories.laser
        
        enemy.zPosition = -10
        enemy.name = "enemy"
        enemy.setScale(0.5)
        
        let randomDistribute = GKRandomDistribution(lowestValue: Int(enemy.size.width), highestValue: Int((frame.size.width) - enemy.size.width))
        enemy.position = CGPoint(x: CGFloat(randomDistribute.nextInt()), y: frame.size.height)
        addChild(enemy)
        
        let moveEnemy = SKAction.moveTo(y: -self.size.height + enemy.size.height, duration: 5)
        let moveReset = SKAction.removeFromParent()
        let moveSequence = SKAction.sequence([moveEnemy, moveReset])
        let moveForever = SKAction.repeatForever(moveSequence)
        
        enemy.run(moveForever)
    }
    

    func createLaser() {
        let laser = SKSpriteNode(imageNamed: "laser")
        
        laser.physicsBody = SKPhysicsBody(circleOfRadius: max(laser.size.width / 2, laser.size.height / 2))
        laser.physicsBody!.categoryBitMask = PhysicsCategories.laser
        laser.physicsBody!.collisionBitMask = PhysicsCategories.none
        laser.physicsBody!.contactTestBitMask = PhysicsCategories.enemy
        
        laser.zPosition = 5
        laser.name = "laser"
        laser.position = CGPoint(x: player.position.x, y: player.position.y + player.size.height * 0.8)
        addChild(laser)
        
        let moveLaser = SKAction.moveTo(y: self.size.height + laser.size.height, duration: 1)
        let moveReset = SKAction.removeFromParent()
        let moveSequence = SKAction.sequence([laserSound, moveLaser, moveReset])
        
        laser.run(moveSequence)
    }
    
    
    func createPlayer() {
        let playerTexture = SKTexture(imageNamed: "player")
        player = SKSpriteNode(texture: playerTexture)
        
        player.physicsBody = SKPhysicsBody(circleOfRadius: max(player.size.width / 2, player.size.height / 2))
        player.physicsBody!.categoryBitMask = PhysicsCategories.player
        player.physicsBody!.collisionBitMask = PhysicsCategories.none
        player.physicsBody!.contactTestBitMask = PhysicsCategories.enemy
        
        player.setScale(0.5)
        player.zPosition = 10
        player.name = "player"
        player.position = CGPoint(x: frame.midX, y: UIScreen.main.bounds.height * 0.05)
        addChild(player)
    }

    
    func createBackground() {
        let backgroundTexture = SKTexture(imageNamed: "background")
        
        for i in 0 ... 1 {
            let background = SKSpriteNode(texture: backgroundTexture)
            background.zPosition = -30
            background.anchorPoint = CGPoint.zero
            background.position = CGPoint(x: 0, y: (backgroundTexture.size().height * CGFloat(i)) - CGFloat(1 * i))
            addChild(background)
            
            let moveDown = SKAction.moveBy(x: 0, y: -backgroundTexture.size().height, duration: 20)
            let moveReset = SKAction.moveBy(x: 0, y: backgroundTexture.size().height, duration: 0)
            let moveLoop = SKAction.sequence([moveDown, moveReset])
            let moveForever = SKAction.repeatForever(moveLoop)
            
            background.run(moveForever)
        }
    }
}



GameViewController.swift

import UIKit
import SpriteKit

class GameViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        if let view = self.view as! SKView? {
            if let scene = SKScene(fileNamed: "GameScene") {
                if (UIDevice.current.model.range(of: "iPad") != nil) {
                    scene.scaleMode = .resizeFill
                } else if UIScreen.main.nativeBounds.height == 2436.0 {
                    scene.scaleMode = .resizeFill
                } else {
                    scene.scaleMode = .aspectFill
                }
                view.presentScene(scene)
            }
            
            
            view.ignoresSiblingOrder = true
            
            view.showsFPS = true
            view.showsNodeCount = true
            view.showsPhysics = true
        }
    }

    override var shouldAutorotate: Bool {
        return true
    }

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        if UIDevice.current.userInterfaceIdiom == .phone {
            return .allButUpsideDown
        } else {
            return .all
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Release any cached data, images, etc that aren't in use.
    }

    override var prefersStatusBarHidden: Bool {
        return true
    }
}



GameOverScene.swift

import Foundation
import SpriteKit

class GameOverScene: SKScene {
    
    let restartLabel = SKLabelNode(fontNamed: "AppleSDGothicNeo-Bold")
    
    override func didMove(to view: SKView) {
        
        let background = SKSpriteNode(imageNamed: "background")
        background.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
        background.zPosition = 0
        addChild(background)
        
        
        let gameeOverLabel = SKLabelNode(fontNamed: "Helvetica-Bold")
        gameeOverLabel.text = "GAME OVER"
        gameeOverLabel.fontSize = 60
        gameeOverLabel.fontColor = SKColor.white
        gameeOverLabel.position = CGPoint(x: frame.size.width * 0.5, y: frame.size.height * 0.7)
        gameeOverLabel.zPosition = 1
        addChild(gameeOverLabel)
        
        
        let scoreLabel = SKLabelNode(fontNamed: "AppleSDGothicNeo-Bold")
        scoreLabel.text = "SCORE: \(gameScore)"
        scoreLabel.fontSize = 30
        scoreLabel.fontColor = SKColor.white
        scoreLabel.position = CGPoint(x: frame.size.width / 2, y: frame.size.height * 0.55)
        scoreLabel.zPosition = 1
        addChild(scoreLabel)
        
        
        let defaults = UserDefaults()
        var highScoreNumber = defaults.integer(forKey: "highScoreSaved")
        
        if gameScore > highScoreNumber {
            highScoreNumber = gameScore
            defaults.set(highScoreNumber, forKey: "highScoreSaved")
        }
        
        
        let highScoreLabel = SKLabelNode(fontNamed: "AppleSDGothicNeo-Bold")
        highScoreLabel.text = "HIGH SCORE: \(highScoreNumber)"
        highScoreLabel.fontSize = 30
        highScoreLabel.fontColor = SKColor.white
        highScoreLabel.zPosition = 1
        highScoreLabel.position = CGPoint(x: frame.size.width / 2, y: frame.size.height * 0.45)
        addChild(highScoreLabel)
        
        
        restartLabel.text = "Restart"
        restartLabel.fontSize = 30
        restartLabel.fontColor = SKColor.white
        restartLabel.zPosition = 1
        restartLabel.position = CGPoint(x: frame.size.width / 2, y: frame.size.height * 0.3)
        addChild(restartLabel)

    }
    
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        for touch: AnyObject in touches {
            let pointOfTouch = touch.location(in :self)
            
            if restartLabel.contains(pointOfTouch) {
                let scene = GameScene(size: self.size)
                scene.scaleMode = self.scaleMode
                let transition = SKTransition.crossFade(withDuration: 0.5)
                view?.presentScene(scene, transition: transition)
            }
        }
    }
}




SpriteKitでゲーム その1- SPACE SHOOTER⑧

f:id:tukumosanzou:20180702205113p:plain

SpriteKit での作業も終盤にさしかかってきました、つたない説明が続きますが、お付き合いくださいませ。


今回は lives = 0 でゲームオーバーになる時の処理も追加していきます。


それにまだ player の移動処理を追加していないのでそれも追加します。


ついでにゲームの状態によってディスプレイの表示を管理したいと思います。

ゲームの状態を管理する変数をつくります。

var livesImages = SKSpriteNode の直前に追加してください、以下のようになります。

//ゲームの状態を管理する変数。
var gameState = GameState.startGame

var livesImages = [SKSpriteNode]()

var lives = 3

var player: SKSpriteNode!



次の列挙型を override func didMove() の直前に追加します。

enum GameState {
    case startGame
    case endGame
}

列挙型(enum)とは関連性のある事柄、データを一つにまとめた定数のようなものです。


switch文の

列挙型の内部データにアクセスするには ” . " で区切って”GameState.startGame " と書きます。

Enumerations — The Swift Programming Language (Swift 4.2)


override func touchesBegan() を以下のように変更します。


playerゲームオーバーの移動処理を追加していきます。

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    //画面にタッチしたかどうかの判定。
    guard let touch = touches.first else { return }

    //ゲームの状態によって処理を分ける。
    switch gameState {
    
    //gameStateがstartGameのとき。
    case .startGame:

        //タッチした位置を取得する。
        let location = touch.location(in: self)

        //タッチした位置がplayerのx軸の位置より小さい(左側)場合。
        if location.x < player.position.x {

            //playerの位置を-50移動する(左に50)
            player.position.x -= 50

        //タッチした位置がplayerのx軸の位置より大きい(右側)場合。
        } else if location.x > player.position.x {

            //playerの位置を+50移動する(右に50)
            player.position.x += 50
        }
    
        //playerの右方向の移動範囲の最大値を決める。
        if player.position.x > frame.maxX - player.size.width {
            player.position.x = frame.maxX - player.size.width / 2
        }

        //playerの左方向の移動範囲の最大値を決める。
        if player.position.x < frame.minX + player.size.width {
            player.position.x = frame.minX + player.size.width / 2
        }

        createLaser()

    //gameStateがendGameのとき。
    case .endGame

        //何も処理をしない。
        break
    }
}

player の中心点、つまり SpriteNode の anchorPoint より右側をタッチすれば右方向に+50移動し、左側をタッチすれば左に-50移動します。

しかし移動範囲は画面の幅以内にしたいので、以下の部分で

//playerの右方向の移動範囲の最大値を決める。
if player.position.x > frame.maxX - player.size.width {
    player.position.x = frame.maxX - player.size.width / 2
}

//playerの左方向の移動範囲の最大値を決める。
if player.position.x < frame.minX + player.size.width {
    player.position.x = frame.minX + player.size.width / 2
}

としていますが、playeranchorPoint(0.5, 0.5) つまり中心なので player の大きさの半分の幅を移動範囲の最大・最小から取り除かないと player の半分が画面からはみ出してしまいます。


ゲームオーバー時の処理に移ります。

以下のコードを func subtractLife() に追加します。

//残機数がゼロならば。
if lives == 0 {

    //gameStateをendGameに変更する。
    gameState = .endGame

    //ゲームオーバーの処理を実行する。
    runGameOver()
}



追加する位置は、以下のようになります。

func subtractLife() {
    
    //初期値から-1する。
    lives -= 1

    //関数内部で使う変数を設定。
    var life: SKSpriteNode

    if lives == 2 {
        life = livesImages[0]
    } else if lives == 1 {
        life = livesImages[1]
    } else {
        life = livesImages[2]
    }

    //GameSceneから削除する。
    life.removeFromParent()

    //残機数がゼロならば。
    if lives == 0 {

        //gameStateをendGameに変更する。
        gameState = .endGame

        //ゲームオーバーの処理を実行する。
        runGameOver()
    }

}



ゲームオーバーの処理を行う func runGameOver() func subtractLife() の直前に追加します。

func runGameOver() {

    //Timerがまだ有効になっているかチェックする。
    if getTimer != nil {

        //タイマーを破棄する
        getTimer.invalidate()

        //Timerを無効にする。
        getTimer = nil
    }

    //func changeSceneを実行するアクションを作成。
    let changeSceneAction = SKAction.run(changeScene)

    //2.5秒のアイドル状態のアクションを作成します。
    let waitToChangeScene = SKAction.wait(forDuration: 2.5)

    //アクションのコレクションを順番に実行するアクションを作成。
    let changeSceneSequence = SKAction.sequence([waitToChangeScene, changeSceneAction])

    //アクションを実行する。
    run(changeSceneSequence)
}

getTimerTimer.scheduledTimer(timeInterval:target:selector:userInfo:repeats:) を格納しています、パラメーターを repeats: true にしてあるので無効になるまでタイマーを繰返しますのでゲームオーバー時には無効にする必要があります。


先ずその処理を行い、次に2.5秒おいて画面をゲームオーバーの画面に切り替える処理を行います。

Timer.scheduledTimer(timeInterval:target:selector:userInfo:repeats:) wait(forDuration:) - SKAction | Apple Developer Documentation sequence(_:) - SKAction | Apple Developer Documentation


画面をゲームオーバーの画面に切り替える処理を作成します。

func changeScene() func runGameOver() の直前に追加します。

func changeScene() {
    
    //sceneにGameOverSceneを指定しサイズを端末の画面にします。
    let scene = GameOverScene(size: frame.size)

    //シーンを提示するビューにシーンがどのようにマッピングされるかを定義します。
    scene.scaleMode = self.scaleMode

    //画面遷移のアニメーションを作成する。
    let transition = SKTransition.crossFade(withDuration: 1.5)

    //現在のシーンから新しいシーンに移行します
    view?.presentScene(scene, transition: transition)
}

この処理でゲームオーバー時に表示する画面に切り替えます。


移動先の画面を指定し、アニメーションで1.5秒かけて徐々に画面が現れるように調整します。
SKScene - SpriteKit | Apple Developer Documentation scaleMode - SKScene | Apple Developer Documentation SKTransition - SpriteKit | Apple Developer Documentation crossFade(withDuration:) - SKTransition | Apple Developer Documentation presentScene(_: transition:) - SKView | Apple Developer Documentation


以下のような画面に切り替わるようにしたいと思います。
f:id:tukumosanzou:20180727234533p:plain


では、ゲームオーバーの画面を作成していきます。


新しくファイルをつくります。

File / New / File / ios / Swift File で GameOverScene.swift を作成しましょう。

コードは以下のようになります。

import Foundation
import SpriteKit

class GameOverScene: SKScene {
    

    //テキストラベルを表示するノードを作成し、フォントを指定する。
    let restartLabel = SKLabelNode(fontNamed: "AppleSDGothicNeo-Bold")
    
    override func didMove(to view: SKView) {
        
        //背景のノードを作成。
        let background = SKSpriteNode(imageNamed: "background")
        //位置を指定。
        background.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
        //重なり順を指定。
        background.zPosition = 0
        //GameOverSceneに追加。
        addChild(background)
        

        //GAME OVERのテキストラベルを表示するノードを作成し、フォントを指定する。
        let gameeOverLabel = SKLabelNode(fontNamed: "Helvetica-Bold")
        //表示するテクストを指定。
        gameeOverLabel.text = "GAME OVER"
        //フォントのサイズを指定。
        gameeOverLabel.fontSize = 60
        //フォントのカラーを指定。
        gameeOverLabel.fontColor = SKColor.white
        //位置を指定。
        gameeOverLabel.position = CGPoint(x: frame.size.width * 0.5, y: frame.size.height * 0.7)
        //重なり順を指定。
        gameeOverLabel.zPosition = 1
        //GameOverSceneに追加。
        addChild(gameeOverLabel)
        
        //SCOREのテキストラベルを表示するノードを作成し、フォントを指定する。
        let scoreLabel = SKLabelNode(fontNamed: "AppleSDGothicNeo-Bold")
        //表示するテクストを指定。
        scoreLabel.text = "SCORE: \(gameScore)"
        //フォントのサイズを指定。
        scoreLabel.fontSize = 30
        //フォントのカラーを指定。
        scoreLabel.fontColor = SKColor.white
         //位置を指定。
        scoreLabel.position = CGPoint(x: frame.size.width / 2, y: frame.size.height * 0.55)
        //重なり順を指定。
        scoreLabel.zPosition = 1
        //GameOverSceneに追加。
        addChild(scoreLabel)
        
        //ユーザーのデフォルトデータベースとのインターフェイス。
        let defaults = UserDefaults()
        //保存するデータと関連づけるためのキーを作成。
        var highScoreNumber = defaults.integer(forKey: "highScoreSaved")
        //得点が過去の得点より高いならば。
        if gameScore > highScoreNumber {
            //ハイスコアをゲームスコアで更新する。
            highScoreNumber = gameScore
            //デフォルト・データベースに格納するオブジェクトとデフォルトキーの値を設定します。 
            defaults.set(highScoreNumber, forKey: "highScoreSaved")
        }
        
        //ハイスコア(HIGH SCORE)用。
        let highScoreLabel = SKLabelNode(fontNamed: "AppleSDGothicNeo-Bold")
        highScoreLabel.text = "HIGH SCORE: \(highScoreNumber)"
        highScoreLabel.fontSize = 30
        highScoreLabel.fontColor = SKColor.white
        highScoreLabel.zPosition = 1
        highScoreLabel.position = CGPoint(x: frame.size.width / 2, y: frame.size.height * 0.45)
        addChild(highScoreLabel)
        
        //再スタート(Restart)用。
        restartLabel.text = "Restart"
        restartLabel.fontSize = 30
        restartLabel.fontColor = SKColor.white
        restartLabel.zPosition = 1
        restartLabel.position = CGPoint(x: frame.size.width / 2, y: frame.size.height * 0.3)
        addChild(restartLabel)

    }
    
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        for touch: AnyObject in touches {
            //タッチした位置を検出。
            let pointOfTouch = touch.location(in :self)
            //タッチした位置が"Restart"の位置に含まれるかチェックする。
            if restartLabel.contains(pointOfTouch) {
                let scene = GameScene(size: self.size)
                scene.scaleMode = self.scaleMode
                let transition = SKTransition.crossFade(withDuration: 0.5)
                view?.presentScene(scene, transition: transition)
            }
        }
    }
}

GAME OVER, SCORE, HIGH SCORE, Restart のそれぞれのテキストを表示するだけです。


ポイントは得点(SCORE)を表示する部分とハイスコア(HIGH SCORE)を毎回リセットする部分とタッチするとゲームを再開(Restart)するリスタートの部分です。


SCOREgameScoreGameScene.swiftグローバル変数として定義しているので GameOverScene.swift からでも取得できるので簡単です。


HIGH SCORE は取得した SCORE より大きければ デフォルトキーを関連付けてデフォルトデータベースHIGH SCORE として格納し使用します。


Restartゲームオーバー画面に遷移する GameScene.swiftfunc changeScene() の内部処理と同じです、違う点は override touchesBegan() の内部で呼び出しを行い Restart のテキストをタッチしたら GameScene.swift を呼び出しゲームが再開することです。

SKLabelNode - SpriteKit | Apple DeveloperDocumentation SKSpriteNode - SpriteKit | Apple DeveloperDocumentation UseDefault - Foundation | Apple DeveloperDocumentation integer(forKye:) - UserDefaults | Apple Developer Documentation set(_: forKey:) - UserDefaults | Apple Developer Documentation

UITouch - UIKit | Apple Developer Documentation location(in:) - UITouch | Apple Developer Documentation


最後にデバイスごとの表示の調整をします。

GameViewController.swift を開きます。

以下の部分を見つけてください。

if let view = self.view as! SKView {
    //Load the SKScene from 'GameScene.sks'
    if let Scene = SKScene(fileNamed: "GameScene") {
        //Set the scale mode to scale to fit the window
        scene.scaleMode = .aspectFill

        //Present the scene
        view.presentScene(scene)
    }

    view.ignoresSiblingOrder = true

    view.showsFPS = true
    view.showsNodeCount = true
    view.showsPhysics = true
}

そしてこのように変更します。

if let view = self.view as! SKView? {
    //シーンにGameSceneがセットされているならば
    if let scene = SKScene(fileNamed: "GameScene") {
        //デバイスがiPadならば。
        if (UIDevice.current.model.range(of: "iPad") != nil) {
            //シーンのサイズが常にビューのサイズと一致するように、自動的に変更されます。
            scene.scaleMode = .resizeFill
        //デバイスの画面サイズの高さのピクセルが2436.0であるならば。
        } else if UIScreen.main.nativeBounds.height == 2436.0 {
            //シーンのサイズが常にビューのサイズと一致するように、自動的に変更されます。
            scene.scaleMode = .resizeFill
        //上記いがいならば
        } else {
            //シーンのサイズが画面に収まるように切り取ります。
            scene.scaleMode = .aspectFill
        }
        //現在のシーンにセットする。
        view.presentScene(scene)
    }

    view.ignoresSiblingOrder = true
    
    view.showsFPS = true
    view.showsNodeCount = true
    view.showsPhysics = true
}

シーンの遷移のときにでてきた部分にデバイスの判別を追加しています。

1番目にデバイスiPadなのか識別し、2番目は iPhone X 用ですデバイスの画面の高さをピクセルで取得し iPhone X の画面の高さと同じならばシーンのサイズを自動的に調整します。

3番目はiPhone 5s 以降で iPhone X 以外の iPhone 用になります、シーンの大きさはアスペクト比の大きい方をもとにスケーリングされるので切り取られる場合があります。

showsFPSshowsNOdeCount はゲームをプレイしているときの画面の右下に出ている nodefps の表示の切り替えです、false にすると消えます。

showsPhysics は player やその他の物理ボディのまわりの青い円の表示を切り替えます、false にすると消えます。

UIDevice - UIKit |Apple Developer Ducumentation
SKSceneScaleMode - SpriteKit | Apple Developer Documentation
presentScene(_:) - SKView | Apple Dveloper Documentation
showsFPS - SKView | Apple Developer Documntation
showsNodeCount - SKView | Apple Developer Documentation
showsPhysics - SKView | Apple Developer Documentation



以上で、シューティングゲームとしての最低限のものは完成したと思います。

今回は以上です。


swiftを学ぶなら!
TechAcademy オンラインブートキャンプ iPhoneアプリコース