読者です 読者をやめる 読者になる 読者になる

A Day In The Life

とあるプログラマの備忘録

SpriteKitでUIScrollView的なスクロール処理を実装する

ios swift ゲーム spritekit

SpriteKit でゲームやアプリを開発していると UI 系の部品がなくて困ることがあります。SpriteKit にはラベルしかなくボタンすらないです。ましてやスクロールビューなんて贅沢なものは当然ありません。現在開発しているアプリでどうしても必要になったので自作してみました(プログラムが複雑になるので横スクロールのみ実装しています)。

慣性処理のないスクロールノー

まずはじめに一番単純な実装からいきます。指で動かした分だけラベルが横にスクロールします。慣性の処理がないので touchesBegan: withEvent: と touchesMoved: withEvent: の処理だけで実現できます。

import SpriteKit

class ScrollNode: SKSpriteNode {
    
    // スクロールさせるコンテンツを配置するためのノード
    private var contentNode = SKNode()
    private var startX: CGFloat = 0.0
    private var lastX: CGFloat = 0.0
    
    init(size: CGSize) {
        super.init(texture: nil, color: SKColor.clearColor(), size: size)

        self.userInteractionEnabled = true

        self.contentNode.position = CGPoint(x: 0, y: 0)
        self.addChild(self.contentNode)
        
        // スクロールさせるコンテンツ
        let myLabel = SKLabelNode(fontNamed: "Helvetica")
        myLabel.text = "scroll"
        myLabel.fontSize = 20
        self.contentNode.addChild(myLabel)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        // store the starting position of the touch
        let touch = touches.first
        let location = touch!.locationInNode(self)
        startX = location.x
        lastX = location.x
    }
    
    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let touch = touches.first
        let location = touch!.locationInNode(self)
        
        // set the new location of touch
        let currentX = location.x
        
        // スクロールのスピード。好みで変えてください
        let scrollSpeed: CGFloat = 1.0        
        let newX = self.contentNode.position.x + ((currentX - lastX) * scrollSpeed)

        // 左と右端の設定
        let limitFactor: CGFloat = 0.3 // 好みで変えてください
        let leftLimitX = self.size.width * (-limitFactor)
        let rightLimitX = self.size.width * limitFactor

        // 移動処理
        if newX < leftLimitX {
            self.contentNode.position = CGPointMake(leftLimitX, self.contentNode.position.y)
        } else if newX > rightLimitX {
            self.contentNode.position = CGPointMake(rightLimitX, self.contentNode.position.y)
        } else {
            self.contentNode.position = CGPointMake(newX, self.contentNode.position.y)
        }
        
        // Set new last location for next time
        lastX = currentX
    }   
}

使い方

使い方は簡単で適当なサイズを設定して SKScnene に配置してやるだけです。

class GameScene: SKScene {
    override func didMoveToView(view: SKView) {
        let scrollNode = ScrollNode(size: CGSize(width: 200, height: 44))
        self.addChild(scrollNode)
    }
}

参考記事

慣性処理に対応したスクロールノー

慣性処理のないスクロールノードでもとりあえずスクロールの動きはできていますがやっぱり UIScrollView のように慣性スクロールしたほうが自然だし使いごごちも良いです。というわけで慣性処理を追加します。 慣性処理は update メソッドの中で行います。 SKSpriteNode オブジェクトは SKScene オブジェクトのように自動で update メソッドが呼び出されないので自前でタイマーを仕込むか SKScene の update メソッドと連動して動かす必要があります。

import SpriteKit

class ScrollNode: SKSpriteNode {
    
    private var contentNode = SKNode()
    private var startX: CGFloat = 0.0
    private var lastX: CGFloat = 0.0
    // タッチされているかどうか
    private var touching = false
    // 少しずつ移動させる
    private var lastScrollDistX: CGFloat = 0.0
    
    init(size: CGSize) {
        super.init(texture: nil, color: SKColor.clearColor(), size: size)

        self.userInteractionEnabled = true

        self.contentNode.position = CGPoint(x: 0, y: 0)
        self.addChild(self.contentNode)
        
        // スクロールさせるコンテンツ
        let myLabel = SKLabelNode(fontNamed: "Helvetica")
        myLabel.text = "scroll"
        myLabel.fontSize = 20
        self.contentNode.addChild(myLabel)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        self.touching = true
        // store the starting position of the touch
        let touch = touches.first
        let location = touch!.locationInNode(self)
        startX = location.x
        lastX = location.x
    }
    
    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let touch = touches.first
        let location = touch!.locationInNode(self)
        
        // set the new location of touch
        let currentX = location.x
        
        lastScrollDistX =  lastX - currentX
        
        self.contentNode.position.x -= lastScrollDistX
        
        // Set new last location for next time
        lastX = currentX
    }
    
    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        self.touching = false
    }
    
    override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
        self.touching = false
    }
    
    func update(currentTime: CFTimeInterval) {
        // タッチされてたら
        guard !touching else {
            return
        }
       
        // 左と右端の設定
        let limitFactor: CGFloat = 0.3
        let leftLimitX: CGFloat = self.size.width * (-limitFactor)
        let rightLimitX: CGFloat = self.size.width * limitFactor
        if self.contentNode.position.x < leftLimitX {
            // 行き過ぎたから戻す
            self.contentNode.position.x = leftLimitX
            lastScrollDistX = 0.0
            return
        }
        if self.contentNode.position.x > rightLimitX {
            // 行き過ぎたから戻す
            self.contentNode.position.x = rightLimitX
            lastScrollDistX = 0.0
            return
        }
            
        // 慣性処理
        var slowDown: CGFloat = 0.98
        if fabs(lastScrollDistX) < 0.5 {
            slowDown = 0.0
        }
        lastScrollDistX *= slowDown
        self.contentNode.position.x -= lastScrollDistX
    }
}

使い方

はじめに説明しましたが SKScnene に配置してやるだけでは updata メソッドが実行されないので SKScene の update メソッドと連動させてやる必要があります。

class GameScene: SKScene {

    var scrollNode: ScrollNode!
    
    override func didMoveToView(view: SKView) {
        self.scrollNode = ScrollNode(size: CGSize(width: 200, height: 44))
        self.addChild(scrollNode)
    }

    override func update(currentTime: CFTimeInterval) {
        // scrollNodeのupdateメソッドを呼ぶ
        self.scrollNode.update(currentTime)
    }
}

参考記事

慣性処理をなめらかにしたい

慣性スクロールには対応できましたが少し動きが硬いです。そこでアニメーションを入れてなめらかに動くように修正してみます。

class ScrollNode: SKSpriteNode {
    :
    : 省略
    :
    func update(currentTime: CFTimeInterval) {
        // タッチされてたら
        guard !touching else {
            return
        }
        
        let limitFactor: CGFloat = 0.3
        let leftLimitX: CGFloat = self.size.width * (-limitFactor)
        let rightLimitX: CGFloat = self.size.width * limitFactor
        if self.contentNode.position.x < leftLimitX {
            // アニメーションさせる
            if !self.contentNode.hasActions() {
                let move = SKAction.moveToX(leftLimitX, duration: 0.2)
                move.timingMode = .EaseInEaseOut
                self.contentNode.runAction(move)
            }
            lastScrollDistX = 0.0
            return
        }
        if self.contentNode.position.x > rightLimitX {
            // アニメーションさせる
            if !self.contentNode.hasActions() {
                let move = SKAction.moveToX(rightLimitX, duration: 0.2)
                move.timingMode = .EaseInEaseOut
                self.contentNode.runAction(move)
            }
            lastScrollDistX = 0.0
            return
        }
            
        var slowDown: CGFloat = 0.98
        if fabs(lastScrollDistX) < 0.5 {
            slowDown = 0.0
        }
        lastScrollDistX *= slowDown
        self.contentNode.position.x -= lastScrollDistX
    }
}

React と Redux を使った Electron 環境を作ってみました

javascript electron

最近、デスクトップアプリ開発界隈で流行ってる Electron の環境を ReactRedux を組み合わせて作成してみました。Redux は Swift にも移植されたたりしているので前から気になってました。せっかくなんでオリジナル Redux 触ってみようと思った次第です。

最終成果物的なソースコードはこちら

Electron + React + Redux の最小構成 Boilerplate を作成しました。Boilerplate ってのは「再利用を意図した標準的な文例集」という意味で、必要最低限これだけあれば Electron + React + Redux 開発ができるよってことを意識してあります。ソースコードはこちらに置きました。 github.com

インストールと Electron の実行

以下の手順だけで Electron アプリが起動します。babel-core/register を使っているので ES6 のコンパイルは不要です。

$ git clone https://github.com/glassonion1/electron-react-redux-boilerplate.git
$ cd electron-react-redux-boilerplate
$ npm install
$ electron app

ディレクトリ構成

ディレクトリ構成は以下のような感じにしました。

$ cd ~/electron-react-redux-boilerplate
$ tree -I node_modules
.
├── app
│   ├── actions
│   │   └── counter.js
│   ├── components
│   │   ├── Root.js
│   │   └── main.js
│   ├── css
│   │   └── style.css
│   ├── index.html
│   ├── index.js
│   └── reducers
│       ├── counter.js
│       └── index.js
└── package.json

使用した Node.js のライブラリ

Electron 0.3系、React 0.14系、Redux 3.3系で構築しています。ReactDOM は React 0.14系から必須になったみたいなので追加してます。 ES6 から ES5 へのトランスパイラは Babel 6系を使うことにしました。Babel 5と6で設定が違ったのですこし手こずりました。

{
    "name": "electron-boilerplate",
    "version": "1.0.0",
    "description": "electron app",
    "main": "index.js",
    "author": "glassonion1",
    "license": "MIT",
    "repository": {
        "type": "git",
        "url": "https://github.com/glassonion1/electron-react-redux-boilerplate.git"
    },
    "keywords": [
        "electron",
        "react",
        "redux",
        "babel",
        "boilerplate"
    ],
    "dependencies": {
        "babel-core": "^6.6.5",
        "babel-preset-es2015": "^6.3.13",
        "babel-preset-react": "^6.3.13",
        "electron-prebuilt": "^0.31.0",
        "react": "^0.14.2",
        "react-dom": "^0.14.2",
        "react-redux": "^4.0.0",
        "redux": "^3.3.1"
    },
    "devDependencies": {
        "react": "^0.14.2",
        "react-redux": "^4.0.0",
        "redux": "^3.3.1"
    }
}

ES6 の自動トランスパイル

babel-core/register を使うと ES6 をgulp なんかでわざわざトランスパイルしなくても自動でトランスパイルしてくれます。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Hello Electron!</title>
        <link href="css/style.css" rel="stylesheet" type="text/css">
    </head>
    <body>
        <div id="root"></div>
        <script>
            window.onload = function() {
                require("babel-core/register")({
                    "presets": ["es2015", "react"]
                });
                require("./components/main.js");
            }
        </script>
    </body>
</html>

詳しくはこちらの記事を参照してください。 - ES2015でコマンドラインツールを書くときは、require hookで提供するべきか、事前にバベるべきか? - Koa入門 - Part2: babel/registerでES6を自動コンパイル

参考にしたサイト

Unity5.3のインストールからプロジェクト作成までの流れ

unity ゲーム

一昨年(2014年)の10月にスマホのネイティブゲームの開発をはじめたのですが、そこからずっと Cocos2d-x を使っています。同じゲームエンジンを使い続けると知識が偏るし ゲームエンジンのシェア的には Unity の方が大きそうなんで Unity にもチャレンジすることにしました。手始めとして昨年末にリリースされた Unity 5.3のインストールからプロジェクト作成までの手順をまとめてみました。

Unity 5.3のインストーラーをダウンロードする

こちらから Unity5.3のインストーラーをダウンロードします。

Unity 5.3のインストール

インストーラーの起動

ダウンロードしたファイルをクリックして Unity インストーラーを起動します。 Unityのインストーラーの起動 起動画面にある「Unity Download Assistant」ボタンをクリックすると以下の「Download And Install Unity」というタイトルのウインドウが立ち上がります。 Download And Install Unityウインドウ

コンポーネントのインストール

ライセンスに同意して「Unity component selection」まで進めます。この画面でインストールするコンポーネントを聞かれます。スマホゲーム(iOS/Android)の開発を行う場合は以下のように「Android Build Support」と「iOS Build Supoort」のチェックを追加して進めます。 Unityのインストール画面 以下のようにインストール先を選択する画面になるのでインストール先を選んで次に進めるとコンポーネントのダウンロードが始まります。 インストール先を選ぶ コンポーネントのダウンロード(通信環境によってはかなり時間がかかります)が終わったら自動でインストールが始まります。以下の画面が表示されればインストール成功です。 インストール完了 Unity はアプリケーションフォルダの Unity 以下にインストールされます。

Unity アカウントの作成

インストールが終わったので Unity を立ち上げます。起動すると Unity アカウントを作成しろと言われるので作成します。以下のような Unity アカウントの作成ページに飛ぶと思うので名前、ユーザ名、メールアドレス、国、パスワード、セキュリティー質問を入力します。 Unityアカウントの作成 Create account ボタンを押すと次のページに進んで幾つか質問(開発者の人数とかとのデバイス向けに開発するのとか)があるので適当に答えます。Webサイト上での作業はこれで終わりです。次に登録したメールアドレスにアカウントの確認メールがくるので、メール本文のリンクを押してアカウントを有効にします。

Pro 版か Personal 版を選択する

作成したアカウントの有効化が終わったら、Unity の作業に戻ります。Unity を立ち上げるとログイン画面になるので先ほど作成したアカウントでログインします。次に Professional 版か Personal 版を選べと言われるので Personal 版を選びます(Pro版のライセンスキーをお持ちの方は方はPro版でもOKです)。ここまでの手続きが終わると以下の画面が表示され新規プロジェクトを作成できるようになります。 新規プロジェクト作成画面

プロジェクトの作成

Unity のインストールが終わったのでプロジェクトを作成します。先ほどの画面の「New project」ボタンを押すと以下の画面が表示されるのでプロジェクト名と保存場所を指定してプロジェクトを作成します(3D、2Dどちらかを選択してください。例では2Dを選びました)。 プロジェクト作成画面 今回プロジェクト名は「MyFirstUnityGame」としました(適宜変更してください)。「Create project」ボタンを押すとプロジェクトが作成されて以下のように Unity IDE が立ち上がります。 UnityIDE 以上で Unity のインストールとプロジェクトの作成は終わりです。

ゲーム開発をはじめる前に読んでおきたい本

c++ おすすめ本 ゲーム

1年半ほど前から本格的にブラウザじゃないゲーム開発(ネイティブゲームって言うんですかね)を始めたのですが、Web の知識が全くと言っていいほど役に立たずほとんど一から勉強するみたいな感じでかなり苦労しました。自分は Cocos2d-x を使って開発していたのですが、機能を追加すればするほどどんどんゲームの動きが遅くなっていく...C++で開発すれば速いんじゃなかったっけ???みたいな状態でした。半年かけてなんとかゲーム開発できるかなぐらいになったものの、自分のやり方が正解だったのか全くわからずこれで良かったのかと悩んでいたときに出会ったのがこの本「Game Programming Patterns」です。

ゲーム開発は言語化されてなかったり、ゲーム開発経験のあるプログラマしか知らないノウハウやパターンがあり、ネットや本では見つからない情報が多いです。この本はそんな現場のノウハウ的なものがぎゅっと1冊にまとめられています。

続きを読む

6年前に開発したObjcのアプリをSwiftで書き直してみた

swift ios appstore

約6年前に LinkedWord という英英辞書アプリを開発しました。リリースからしばらく(2年ぐらい、時期的には iOS4とか5の時代)は機能アップデートやバグ修正をしていたのですが、本の執筆やら他のアプリの開発やらで忙しくなりしばらく放置してました。その間に iPhoneは5系や6系が発売され画面サイズが増えたり、iOS7で UI が刷新されたり、とわりと大きな変化がありました。本の執筆も落ち着いたし Swift を使ってアプリを1本ちゃんと開発したいと思ったので過去に開発した LinkedWord を作り直してみることにしました。以下やったことをつらつらと上げていきます。
from Objc to Swift

続きを読む

OpenGL ES入門 その2 -三角形の描画とシェーダーの仕組み-

opengl c objective-c/objc

OpenGL ES入門 その1 -描画の仕組みとバッファ-の続きです。今回は OpenGL ES を使って三角形を描画してみます。三角形を描くだけなら簡単そうな気もしますが、三角形を描くにはシェーダーを用意しきゃいけないという少し面倒くさい作業があるのでその辺の説明になります。

続きを読む

OpenGL ES入門 その1 -描画の仕組みとバッファ-

opengl c objective-c/objc

最近 Cocos2d-x を触っているとどうしても細かいところで OpenGL の知識がないと理解できないみたいなことが多いです。いままで避けてきた感がある OpenGL の勉強をそろそろ始めてみようかなと思い少しづつ日記的にまとめていこうと思います。OpenGL と言っても自分はモバイルゲームの開発に興味があるので OpenGL ES について勉強していくことにしました。参考図書として購入した本はこちらです。

わりと新しく出た本ってのと解説がものすごくわかりやすかったので選びました。第1回は OpenGL ES の基本的なところから画面を色で塗りつぶすまで解説します。

続きを読む