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

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

なんとなく,今までしてこなかったことをしてみたくなったので都度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でした。

MacでBluetoothをオフにした状態で再起動した際にログイン画面でBT無線キーボードが認識されずに詰んだ時の対処法

表題の通り、普段利用しているiMacにペアリングされたBluetooth無線キーボードを他のデバイスで利用しようと,一旦iMacのBluetoothをオフにした状態でうっかり再起動したら詰んだというお話です。 僕が持っているキーボードはHHKB BTモデル1つのみで、USBで接続するタイプのキーボードは持っていませんでした。 Windowsはログイン画面でもスクリーンキーボードを利用してパスワードを入力できるので、同じようにそれで解決できると思っていたのですが、Macではできないようです。

スマートな解決方法

Bluetoothオフのままログインを求められ、文字入力が出来ず困っています

過去に同じような問題に陥ってしまった人がいたようです。USB機器を全部外した後に再起動する となにやらセットアップウィザードが出るとのこと。。。ちょっとよくわからないですが,試す価値はありそうですね。(僕は試していません)

それよりも,もしかしたら所謂 PRAMリセット というやつでなおるかもしれません。

support.apple.com

1. Mac をシステム終了します。
2. キーボードで「command (⌘)」「option」「P」「R」の各キーの場所を確認します。
3. Mac の電源を入れます。
4. 起動音が聞こえたらすぐに、「command + option + P + R」キーを同時に押し、そのまま押し続けます。
5. コンピュータが再起動し、2 度目の起動音が聞こえるまで、キーを押したままにします。
6. キーを放します。

NVRAM がリセットされた後で、必要に応じて、スピーカーの音量、画面解像度、起動ディスクの選択、時間帯の情報などを設定し直してください。

MacのBluetoothをオフにしてもブート時のキー操作は認識するようで,optionキーを押してBootcampを起動することができました。なのでこれはBluetoothオフ時でも試すことができそうです。 僕はこのPRAMリセットによる解決方法も試していないので直るという保証はできません。なぜならもっと確実で乱暴な解決方法を採ったからです。

乱暴な解決方法

有線キーボードを買ってくる。

シンプルで素晴らしい解決方法だと思います。この問題が発生して真っ先に買いに行きました。少しは調べろよ・・・^^; 幸いなことに最寄りの西友の家電コーナーに1200円ほどの安いキーボードが売っていたので,ものの20分で問題が解決しました。 f:id:azaraseal_7:20161219014410j:plain

このキーボードは最早要らないので売るなり誰かにあげるなりしたいところですが、エンジニア的には確実に繋がるキーボードのバックアップを一つくらい持っておいてもいいかもしれませんね。。

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

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

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

developers.cyberagent.co.jp