開発中のソースコードを晒すことにした

なんとなく,今までしてこなかったことをしてみたくなったので都度githubに上げることにしました。開発中のコードは荒削りなところがあってあまり晒したくない派ですが,まぁ誰もみないしいいかなと。

去年ちょっと開発していたSwift製macOS向けゲームを,一度白紙に戻して作り直しています。

最初はほぼSpriteKitだけで作っていたのですが,最近iOSのアプリ開発を業務でやっていて知った良さそうなライブラリを色々組み込みたくなったのと,作りかけのコードの規模も大きくなかったし,しばらくコードを触れていなかったという理由があってガラっと作り変えてみました。活躍する場面があるのかちょっと謎ですが,調子に乗ってRxも入れてみました。

ひとまず3月末までに完成させる目標で黙々と仕上げていきます。(仕事の新規開発サービスも3月着地目標だったなぁ…また修羅場りそう)

今年はアプトプットをちゃんと見える形で残していく年にしたい。

github.com

SpriteKitでシーンにクリック可能なオブジェクトを配置するための仕組み

qiita.com qiita.com

上記の記事はiOS向けの記事です。 OSX用のゲームを作っている場合,touchesBegan はないはずなのでどうするのだろうと少し考えてみました。

前述の記事はオブジェクトに名前をつけてdelegateで処理させる戦略のようです。 名前を付けてSwitchで分岐させたり,delegateを用意するのはちょっと大げさだなぁという気もしなくもない…。 onClickedという名前のクロージャを一度渡せばおしまい,というような仕組みがあればいいなと思ったので簡単に実装してみました。

import SpriteKit

protocol Clickable {
  var onClicked: ((SKNode) -> Void)? { get set }
}

class TextButton: SKLabelNode, Clickable {
  var onClicked: ((SKNode) -> Void)?
}

class BasicSene: SKScene {
  override func mouseDown(with event: NSEvent) {
    let location = event.location(in: self)
    
    let clicked = nodes(at: location)
    clicked.forEach {
      if let node = $0 as? Clickable {
        node.onClicked?($0)
      }
    }
  }
}

class DebugScene: BasicSene {

  override func didMove(to view: SKView) {
    scaleMode = .aspectFill

    let label = TextButton(text: "test")
    label.onClicked = { _ in
      print("label is clicked")
    }
    label.position = CGPoint(x: 200.0, y: 200.0)
    addChild(label)
  }

  override func mouseDown(with event: NSEvent) {
    // 独自実装があればここに記述,その際superは必ず呼ぶ
    super.mouseDown(with: event)
  }
}

Clickableプロトコルを用意するのでSpriteKitが最初から用意しているクラスをクリック可能にすることはできませんが,それなりの規模のゲームを作る場合はどうせ独自クラスを作りたくなるし,実用性はそこそこありそうです。 というかこのくらいのことはどこも当たり前にやってるはずなので今更感あるのですが,もしかしたらクロージャを渡しておしまいという仕組みがSpriteKitに最初からあるかもしれないという期待を込めてググってみても,Qiitaのような情報しかでてこなかったので一応。

同じ階層に重なってるオブジェクトもClickableであれば,上のオブジェクトから順に発火されます。また階層構造になっている場合は子要素から順に発火されるようです。これは node(at:) の仕様であり,バージョンや環境によって異なるかもしれません。 クロージャの返り値をBoolにしてpropagationの制御もできればなお良かったのですが,簡易実装ということでご勘弁を。

2017年やりたいことリスト

  • 開発中のゲームを完成させる
  • 次に作りたい作品があるので今年の冬コミ目指して制作する
  • お仕事的にそろそろWeb界隈に行きたい(戻りたい?)
  • 枚数こなして絵を上達させたい

とりあえずやりたいことというか目標です。 去年は割と仕事中心という生活だったのでまずは自分のための時間を作る土俵作りから…。

MacでHID準拠USBゲームコントローラの値を取得したい

コントローラがどんな種類の入力をもっているかスキャンできるコードを紹介します。 このプログラムの管理下にあるときは任意のタイミングで入力の状態がどうなっているかもチェックできます。 startメソッド の無限ループはチェックのためにその場におきました。よきに取り除いてください。

Criteriaには検出したいデバイスの条件を設定して下さい。

// HIDDeviceManager
#import <Foundation/Foundation.h>
#import <IOKit/hid/IOHIDLib.h>
#import "HIDDeviceManager.h"

static const int numOfDetection = 2;
static const NSDictionary* criteria = @{@kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop),
                                         @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_Joystick)};

static void CFSetApplier(const void *value,void *context)
{
  CFArrayAppendValue((CFMutableArrayRef)context,value);
}

@interface HIDDeviceManager (){}
  @property(nonatomic) IOHIDManagerRef manager;
  @property(nonatomic) IOHIDDeviceRef* devices;
  @property(nonatomic) int deviceNum;
@end

@implementation HIDDeviceManager

- (void) start {
  self.manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);

  IOHIDManagerSetDeviceMatching(self.manager, (__bridge CFDictionaryRef)criteria);
  IOHIDManagerOpen(self.manager, kIOHIDOptionsTypeNone);
  
  CFSetRef copyOfDevices = IOHIDManagerCopyDevices(self.manager);
  CFMutableArrayRef hidDevices = CFArrayCreateMutable(kCFAllocatorDefault, 0 ,&kCFTypeArrayCallBacks);
  CFSetApplyFunction(copyOfDevices, CFSetApplier, (void*)hidDevices);
  
  CFRelease(copyOfDevices);

  IOHIDDeviceRef shoudlManage[numOfDetection];
  int deviceCount = (int)CFArrayGetCount(hidDevices);

  for (int i=0; i<deviceCount && i < numOfDetection; i++) {
    IOHIDDeviceRef hidDevice = (IOHIDDeviceRef)CFArrayGetValueAtIndex(hidDevices, i);
    if (hidDevice == NULL) {
      continue;
    }

    shoudlManage[self.deviceNum++] = hidDevice;
  }
  
  self.devices = shoudlManage;
  CFRelease(hidDevices);
  
  while(1) {
    time_t t0;
    t0=time(NULL);
    while(t0==time(NULL))
    {
    }

    for (int i=0; i<self.deviceNum; i++) {
      CFArrayRef elements = IOHIDDeviceCopyMatchingElements(self.devices[i], NULL, 0);
      int elementCount = (int)CFArrayGetCount(elements);
      
      for (int j=0; j<elementCount; j++) {
        IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, j);
        IOHIDElementType elementType = IOHIDElementGetType(element);
        unsigned int usage = IOHIDElementGetUsage(element);
        unsigned int usagePage = IOHIDElementGetUsagePage(element);
        
        printf("element %d: type: %d usage: %d value: ", j, elementType, usagePage);
    
        switch(elementType) {
          case kIOHIDElementTypeInput_ScanCodes:
          case kIOHIDElementTypeInput_Misc:
          case kIOHIDElementTypeInput_Button:
          case kIOHIDElementTypeInput_Axis:
          {
            IOHIDValueRef valueRef;
            IOHIDDeviceGetValue(self.devices[i], element, &valueRef);
            
            int value = (int)IOHIDValueGetIntegerValue(valueRef);
            printf("%d", value);
          }
          default: {
            printf("\n");
            break;
          }
            
        }
      }

      CFRelease(elements);
    }
  }
}

@end

今回はObjective-Cのコードを紹介しますが,Swift3上のコードでも実現できます。やることは各型のRefという接尾辞やCFReleaseを取り除いたり,引数のコールバックをクロージャあるいは関数のUnsafePointerで渡すといった変更を加えていけばObjective-Cレスでスッキリします。別の実装ではSwiftを使っていますが,今回はUnsafePointer <-> Tの相互変換が面倒くさかったので無理にSwiftを使う必要はないなという判断です。C++の関数を呼び出してるので,こっちの方が相性良さそう。

それにしてもI/OKit面倒臭いですね。

値が変更されたタイミングにその値をコールバックで受け取って処理したいならもっとスッキリとした実装があります。 qiita.com

SpriteKitのSKSceneでShiftKey(装飾キー)をハンドリングする方法

SKSceneはNSResponderを継承しているはずなのに,なぜかfunc flagsChanged(with event: NSEvent) を継承してもコールバックが呼ばれないので,extensionで解決してみました。

extension SKView {
  override open func flagsChanged(with event: NSEvent) {
    scene?.flagsChanged(with: event)
  }
}
class DebugScene: SKScene {
  override func flagsChanged(with event: NSEvent) {
    if event.modifierFlags.contains(NSShiftKeyMask) {
      print("Shift key is pressed")
    }
  }
}

SKSceneは必ずSKViewのメンバsceneとして保持されるので,SKViewでせき止められているイベントをオーバーライドして明示的発火させてあげたところ無事にイベントが渡って来ました。 通常のキーイベントは何もしなくてもわたってきます。装飾キーのイベントだけが渡って来ないのには何かしらの理由がありそうなので,なにか副作用があるかもしれません。

または

class DebugScene: SKScene {
  override func didMove(to view: SKView) {
    view.nextResponder = self
  }

  override func flagsChanged(with event: NSEvent) {
    if event.modifierFlags.contains(NSShiftKeyMask) {
      print("Shift key is pressed")
    }
  }
}

didMoveで自身を保持するviewの参照が渡って来るので,nextResponderに自分の参照をいれることでも解決します。SKViewのnextResponderにセットされてるNSViewがなんなのかはわからないのですが,階層構造的にもSKView->Sceneという順番で処理できた方がシンプルで上の実装よりResponder Chainのあるべき姿な気がします。なんならSKViewのpresentSceneをオーバーライドして必ずnextResponderにシーンの参照をセットするようにしてもいいかもしれません。

ちょっと心配なのが元のNSViewを迂回する必要性があるのはか不明なところ。たぶんNSViewの実装でflagChangedが止まってる気がします。

東方とかBMSでもShiftキーはバリバリ使うので,似たようなゲームを実装したいとなったときにハマるかもしれませんね。 ググっても解決策が全然でてこないくらいには人気のないSpriteKitくんのTipsでした。

AndroidのGLSurfaceViewに描画する内容を動画として吐き出す方法

ゲームの録画機能をCocos2d-xで動くアプリの中に実装しました、というお話を社内ブログに書きました。 Cocos2d-xが絡むからこそ難しくなったりする点もあることにはあるのですが、それなりに汎用性のあるお話です。

当初ハードウェアに依存しないソフトウェアエンコーダでいくつもりが、それだと地獄のような重さを回避できないことを知り、以下の実装に落ち着きました。

developers.cyberagent.co.jp

iPad Pro13インチ+AstroPadが液タブとして優れている話

iPad Pro13インチを購入して3ヶ月が経過しました。最初は大した目的もなく購入し、リフレクビートがゲーセンに近い環境でプレイできたり、Kindleアプリで漫画が見開きで読めたりといった大画面タブレットの恩恵を享受できる楽しみかたに感動していたのですが、Apple Pencilと同時に利用するもっと面白い活用方法が存在することに気づきました。

AstroPadという液タブ化ソフトが素晴しかった

ずばりiPadProの液タブ化です。 AstroPadというアプリをiOS、Mac双方にインストールすることで、Macで見ている画面をiPadに映し出すことができます。更にApple Pencilを使うとPhotoshopやClipStudioPaintに対応した液晶タブレットとして活用することができます。接続方法はライトニングケーブル、Wifiの2種類があります。どちらも動作に違いが無いように感じますが、安定しているであろうライトニングケーブルを接続する方式で利用しています。

astropad.com

その描き味ですが、精度も良く、遅延もそれほどストレスに感じないレベルで快適そのものです。もちろん筆圧やペンの傾き具合もきっちり反映されており、感動しました。そもそも液タブを利用すること自体初めてなので、思った通りの線が紙に描くように引けるという液タブそのものの魅力による補正があると思うのですが、これをきっかけに定期的にTwitterでワンドロを始めるようになりました。

AstroPadは$29.99とそこそこお値段のする有料アプリです。Mac側にインストールするアプリは無料です。

Astropad Graphics Tablet

Astropad Graphics Tablet

  • Astro HQ
  • 仕事効率化
  • ¥3,600

お絵描きを快適にするAstroPadの便利な機能

仮想ファンクションキー

AstroPadには通常の板タブや液タブのようなショートカットを登録できるファンクションキーをソフトウェア的に提供しています。ここにUndoやRedo、ペン/消しゴムの切り替えなどのお好きなショートカットを割り当てるとより快適にお絵描きが楽しめるようになります。

f:id:azaraseal_7:20161215003314p:plain

ジェスチャによるキャンバスの操作

更に、AstroPadはClipStudioPaintやPhotoshopに正式対応しておりキャンバスに対してピンチイン/ピンチアウトをするとキャンバスの拡大縮小ができます。また、回転ジェスチャでキャンバスが回転したり、2本指でドラッグするとキャンバスの位置を調整できます。通常のタブレットはタッチ可能モデルとそうで無いものが別物として売られていますが、AstroPad+iPadは限定的ではあるものの特定のソフトウェアに対するタッチ操作がデフォルトで可能です。 昔Bambooという名前で売られていたWacomのタッチ可能板タブを触ったのですが、認識精度が悪くストレスが溜まるだけで使い物になりそうにないという印象を受けました。AstroPadのタッチ機能にはそのような印象を受けず、むしろこれが無いとお絵描きにおいてストレスがたまってしまうのでは?と思うほど快適です。現行モデルだと改善されているのでしょうか。

お絵描き目的なら液タブの購入をオススメします

ここまでAstroPad+iPad Proによる液タブをべた褒めしてきましたが、やはり遅延が気になって仕方がないという方もいます。なので、初めからiPadを液タブ目的で購入すると痛い目に遭うかもしれません。同じ価格帯でWacomの13インチの液タブが手に入るので素直にそちらを購入しましょう。 お絵描きが趣味で、別の目的でiPadを買ったものの、液タブとしても活用してみたいという方には是非オススメします。

僕は今の所画面がちょっと狭いかな?と思う程度の不満以外は感じていないので、このままiPadでお絵描きを続けます。iPadProはなかなか良い買い物だったと思います。