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 の環境を React と Redux を組み合わせて作成してみました。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 Download Assistant」ボタンをクリックすると以下の「Download And Install Unity」というタイトルのウインドウが立ち上がります。
コンポーネントのインストール
ライセンスに同意して「Unity component selection」まで進めます。この画面でインストールするコンポーネントを聞かれます。スマホゲーム(iOS/Android)の開発を行う場合は以下のように「Android Build Support」と「iOS Build Supoort」のチェックを追加して進めます。 以下のようにインストール先を選択する画面になるのでインストール先を選んで次に進めるとコンポーネントのダウンロードが始まります。 コンポーネントのダウンロード(通信環境によってはかなり時間がかかります)が終わったら自動でインストールが始まります。以下の画面が表示されればインストール成功です。 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 が立ち上がります。 以上で Unity のインストールとプロジェクトの作成は終わりです。
おすすめUnity本
ゲーム開発をはじめる前に読んでおきたい本
1年半ほど前から本格的にブラウザじゃないゲーム開発(ネイティブゲームって言うんですかね)を始めたのですが、Web の知識が全くと言っていいほど役に立たずほとんど一から勉強するみたいな感じでかなり苦労しました。自分は Cocos2d-x を使って開発していたのですが、機能を追加すればするほどどんどんゲームの動きが遅くなっていく...C++で開発すれば速いんじゃなかったっけ???みたいな状態でした。半年かけてなんとかゲーム開発できるかなぐらいになったものの、自分のやり方が正解だったのか全くわからずこれで良かったのかと悩んでいたときに出会ったのがこの本「Game Programming Patterns」です。
ゲーム開発は言語化されてなかったり、ゲーム開発経験のあるプログラマしか知らないノウハウやパターンがあり、ネットや本では見つからない情報が多いです。この本はそんな現場のノウハウ的なものがぎゅっと1冊にまとめられています。
続きを読むOpenGL ES入門 その2 -三角形の描画とシェーダーの仕組み-
OpenGL ES入門 その1 -描画の仕組みとバッファ-の続きです。今回は OpenGL ES を使って三角形を描画してみます。三角形を描くだけなら簡単そうな気もしますが、三角形を描くにはシェーダーを用意しきゃいけないという少し面倒くさい作業があるのでその辺の説明になります。
続きを読む