A Day In The Life

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

UnityでRedux.NETを使ってみた

フロントエンド開発と iOS アプリ開発でステート管理に Redux(iOSはReSwift)を使っています。Unity でも Redux 使えないかなと探していたところ Redux.NET というC♯の実装を見つけたので使ってみることにしました。

手順

Redux.NET を使うためには Reactive Extensions というやつをインストールしないといけないのですが今のところ(2016年6月現在) Unity には対応していません。なので代わりに Reactive Extensions の Unity 実装である UniRx をインストールします。

UniRxのインストール

Asset Store からUniRxをインストールする

Redux.NETのインストール

Redux.NET は100行にも満たないシンプルなコードなのでインストールというほど大げさなものではないです。

GuillaumeSalles/redux.NET から Store.cs と IAction.cs をコピーして Unity プロジェクトの Scripts フォルダに追加してください。 github.com

Redux.NETをUnityで動くように修正する

Redux.NET はそのままではコンパイルエラーになるので以下のように Store.cs を修正します。

using System;
//using System.Reactive.Subjects;
using UniRx;

namespace Redux
{
    ...その他コード
}

以上で準備は完了です。

使い方

カウンターを使った簡単なサンプルを例に使い方をみてみましょう。

アクションの作成

初めにアクションを定義します。

public class IncrementAction : IAction { }

public class DecrementAction : IAction { }

ストアの作成

次にストアを定義します。

public class State
{
  public int Counter { get; set; }
}

Reducerの作成

最後に Reducer を作成します。

public static class CounterReducer
{
  public static State Execute(State state, IAction action)
  {
    if (action is IncrementAction)
    {
      return new State {
        Counter = state.Counter + 1
      }
    }
    else if (action is DecrementAction)
    {
      return new State {
        Counter = state.Counter - 1
      }
    }
    return state;
  }
}

ステートの監視

アクションの発行とステートを監視してみます。

using UnityEngine;
using UniRx;
using Redux;

public class Hoge : MonoBehaviour
{
  public IStore<State> Store { get; private set; }
  void Start()
  {
    // ストアの生成
    store = new Store<State>(Reducers.Hoge, initialState);
    store.Subscribe(state => {
      // stateが変わったら呼ばれる
      print(state.Counter);
    });
    // ボタンのイベント(ボタンが2つ配置されていると仮定)
    var buttons = GetComponentsInChildren<Button>();
    foreach (var button in buttons)
    {
      var name = button.name;
      button.onClick.AddListener(() => OnButtonClick(name));
    }
  }
  void OnButtonClick(string name)
  {
    if (name == "Increment")
    {
      // アクションの発行
      this.Store.Dispatch(new IncrementAction());
    }
    else if (name == "Decrement")
    {
      // アクションの発行
      this.Store.Dispatch(new DecrementAction());
    }
  }
}

注意点

一つだけ注意点があります。UniRx が iOS の Mono2x に対応していないため iOS アプリのビルドをする場合は IL2CPP に設定をしてから実行する必要があります。

おすすめUnity本

自作ライブラリをBitcodeに対応させる

Swiftで開発した自作ライブラリを Bitcode に対応させるためには Xcode の Build Settings 項目に以下を設定する必要があります。

  • Enable BitcodeをYesにセット
  • Other C Flagsに-fembed-bitcodeをセット(Other C++ Flagsには自動でセットされます)
  • User-DefinedにBITCODE_GENERATION_MODEを追加してbit codeをセット

f:id:glass-_-onion:20160602215450p:plain

上記を設定しないとアーカイブアップロード時に

ITMS-90668
Invalid Bundle Executable.
The executable file 'ライブラリ名' contains incomplete bitcode.
To compile binaries with complete bitcode, open Xcode and choose Archive in the Product menu.

というエラーが出て App Store にアップできなくなります。

参考記事

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

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 環境を作ってみました

最近、デスクトップアプリ開発界隈で流行ってる 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のインストールからプロジェクト作成までの流れ

一昨年(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 のインストールとプロジェクトの作成は終わりです。

おすすめUnity本

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

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

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

続きを読む

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

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

続きを読む