2018年2月28日水曜日

SpriteKit で UserDefaults に独自に定義したクラスをセットする方法

概要

過去に SpriteKit 上で UserDefaults を使う方法を紹介しました
UserDefaults は Objective-c の機能になり NSString や NSInteger 型のデータははそのまま使うことができるのですが Swift で定義した独自のクラスのオブジェクトなどを set/get する場合には少し工夫が必要でした
今回はその方法を紹介します

環境

  • macOS 10.13.3
  • Xcode 9.2 (9C40b)

宣言する独自クラスに NSObject と NSCoding を継承する

例えば以下のような感じです

import Foundation

class GameConfig: NSObject, NSCoding {
...
}

Foundation がないと NSObject が継承できないので追加します
またただ継承しただけではダメで NSCoding 用のメソッドを定義しなければなりません

init と encode メソッドを追加する

先ほど継承したクラスを実装すると以下のようになります

import Foundation

class GameConfig: NSObject, NSCoding {
    var roomId = ""
    var player = ""
    var name = ""
    var msg = ""

    init(roomId: String) {
        self.roomId = roomId
        self.msg = "The room was lost..."
    }

    required convenience init(coder aDecoder: NSCoder) {
        let roomId = aDecoder.decodeObject(forKey: "roomId") as! String
        self.init(roomId: roomId)
        let player = aDecoder.decodeObject(forKey: "player") as! String
        let name = aDecoder.decodeObject(forKey: "name") as! String
        self.player = player
        self.name = name
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(roomId, forKey: "roomId")
        aCoder.encode(player, forKey: "player")
        aCoder.encode(name, forKey: "name")
        aCoder.encode(msg, forKey: "msg")
    }
}

重要なのは required convenience init と encode メソッドになります
どちらも UserDefaults の set/get をするときに自動で呼ばれるのですがこれがないと「Use of undeclared type 'NSObject' in Xcode」や「class 'GameConfig' does not implement methodSignatureForSelector」などのエラーが発生します

また required convenience init でエンコードしたデータをフィールドにセットする場合に既存のコンストラクタで全フィールドに対して値をセットできない場合は、その後でちゃんと各フィールドに対して値とセットしないと取り出したときに値がない状態になってしまいます

UserDefaults にセットする

あとは独自クラスから作成したオブジェクトをセットするだけですがここでも少し工夫が必要です

let defaults = UserDefaults.standard
defaults.set(NSKeyedArchiver.archivedData(withRootObject: config), forKey: "GameConfig")

config は上記で説明した GameConfig クラスのオブジェクトになります
NSKeyedArchiver.archivedData を使ってセットすることでデータを NSData 側に変更しています
こうすることで独自で定義したクラスを UserDefaults にセットできるようにしています

UserDefaults から取り出す

取り出すときも少し工夫が必要です
例えば以下のように取り出します

let defaults = UserDefaults.standard
// UserDefaults のデータがある場合
if let data = defaults.value(forKey: "GameConfig") as? NSData {
    // NSData から GameConfig に変更
    let config = NSKeyedUnarchiver.unarchiveObject(with: data as Data) as! GameConfig
    // これで参照できる
    print(config.name)
}

セットしたデータが存在するかしないかを確認してもし存在するならば NSKeyedUnarchiver.unarchiveObject を使ってデータを取り出し GameConfig クラスにキャストしてあげます
こうすることで元のクラスのオブジェクトとして扱うことができます

最後に

SpriteKit + UserDefaults で独自のクラスのオブジェクトを扱う方法を紹介しました
独自のクラスになったとたん急に複雑になってしまいます
これが面倒であればクラスの特定のフィールドだけを String などで共有する方が簡単なケースもあるかもしれません

そもそも SpriteKit で UserDefaults を使うケースですが自分は SKScene 側で生成したデータを AppDelegate.swift の applicationWillTerminate で扱うために UserDefaults を使いました
UserDefaults は永続的なデータの保存にはあまり向いていないのでアプリのライフサイクル内でのみ共有したいデータがあれば使うようにする感じが良いと思います

2018年2月23日金曜日

Cygwin 上でコマンドを管理者権限で実行する方法

概要

例えば Windows 上の hosts ファイルを Cygwin 上で編集したい場合などに使えます

環境

  • Windows 7 64bit
  • Cygwin 2.9.0

コマンド

  • cygstart.exe --action=runas mv hosts hosts.back

コマンドを実行する際に先頭に cygstart.exe --action=runas を付与するだけで OK です
実行すると Windows のダイアログが表示されて本当に管理者権限で実行していいか確認が表示されます

2018年2月21日水曜日

天空トラップタワーの湧き効率を上げるために頑張ったこと

概要

前回スライムトラップタワーが湧かない問題について考えました
今回は天空トラップタワーでモンスターが湧かない問題を対処してみました

環境

  • macOS X 10.13.2
  • Minecraft 1.12.2

天空トラップタワー

作った天空トラップタワーは水流タイマー式のトラップタワーになります
湧き層は全部で 9 層あり一番下の湧き層が Y 軸 225 の位置にあります
そこから 9 層作成しました
待機場所は Y 軸 184 の場所で湧き層とは 41 も差があります (ここが失敗だったかもしれません、、、)
地上の高さは 56 の位置から開始しています
地面ではなく川の上に作っているので地面の開始位置の高さが若干低くなっています

作成完了後待機場所で待っているのですが湧きが非常に悪い感じです
落下してくるモンスターのタイミングとしては 30 秒の 1 体という感じです
これが良いのか悪いのか何ともわからないのですが、他の紹介動画などを見ているともっと湧いても良い印象です

この湧き効率を何とか上げるべく頑張ってみました

試したこと

以下いろいろと模索したときのメモです

待機場所をいろいろと模索

前回のスライムトラップタワーでもいろいろと模索したので今回もまずは待機場所を模索してみました
はじめは Y 軸 184 の場所でじっと待機していたのですが湧き効率が良くなかったのでいろいろな場所で検証してみました

湧き層から平行方向に 24 マス離れた場所で待機したりあえて湧き層に密着して待機したりしてみましたが劇的に変わるものはありませんでした

そしてスライムトラップタワーのときにも試したトロッコで動き続ける待機場所も作成してみたのですがこれも劇的に改善することはありませんでした

結局落ち着いたのは待機場所 Y 軸 184 の場所から 13 ブロックおいた Y 軸 197 の場所で待機する方法でした
これがとりわけ一番効率が良かったわけではないですが「何となく湧き続けている感」があったのでここにしています
それでも 10 分間で 10 - 30 アイテムほどしかドロップしません

描画距離を調整

描画距離が短すぎると 128 ブロック離れた mob がデスポーンしないという情報があったので試しに描画距離を 18 チャンクに伸ばしてみたのですが特に変わりませんでした

トラップドアを使ってクリーパーのみ湧くようにした

もうひとつ検証として試してみたのが湧き層の天井にトラップドアを設置してクリーパーのみスポーンするようにしてみたことです
しかもこれを 9 層いきなりやるのではなく下位層から徐々に設置していきどの層になったときにクリーパーのみになるかを調査してみました

というのもいろいろと調べてみると「高度によってスポーン効率が悪くなる」という情報を見たので、それが本当かどうか試してみました
ようするにクリーパー以外も湧いているのであればその層の Y 軸では mob がスポーンするということになり、逆にクリーパーのみ湧くようになったのであればその層以降の Y 軸では mob が湧かないということになります

で結果としてどうだったかというと

  • 10 層全部スポーンしているのが確認できた

でした
根拠は下から 9 層はトラップドラを設置して最後の上部 1 層だけはトラップドアを設置しない状態で湧きを確認したのですが、クリーパー以外も湧いたからです
ただ、この検証で明確になっていないのは

  • 上位の 10 層や 9, 8, 7 層での湧き効率が 1 層に比べて良いかまではわからない

という点があります
厳密にやればわかるかもしれませんが、自分は検証しきれていません

ここまで検証して自分の考察としては 255 でもおそらく mob は湧くが地上や地下洞窟に比べて湧き効率が非常に悪いと推測しました
なので湧き効率を上げる 1 つの方法としてもっと低いところに湧き層を作るというのが解決策になるかもしれません

ただ、湧き層を低い位置に作るとその分地上 or 地下の湧き潰しが必要になるのでもしそれが面倒なのであればやはり無いもない海の上を待機場所にしてそこから 128 離れたあたり (63 + 128 = 191) を湧き層にするのが良いかもしれません
それでも海の下にもし洞窟があれば結局そこが影響して湧きが悪くなるというのはあります

レッドストーン回路の変更

今回参考にしたサイトのタイマー回路を見るとタイマーから出た信号をラッチ回路で単発の信号にしてディスペンサーから水を出し、その後レッドストーンリピーターの遅延を使って水を回収するという仕組みにしているようでした

これをそのまんまやってみたところうまく動きませんでした
具体的にうまく動かなかったのは水をまく初めの信号が 1 層目の信号を受け取る 1 つ目の部分にしか伝わっていませんでした
本来であればブロックを経由して伝わってきた信号がトーチタワーと伝わって上位層にも行くのですが行っていませんでした
つまり本来水を回収する信号で水をまいてしまっており、水を回収するタイミングが次のタイマーでの信号になるので、湧き層に水がまかれている時間が長くなってしまい、その分湧きが悪くなっているということに気が付きました

なので回路を以下のように変更して水をまくための信号がしっかり全てのトーチタワーに伝わるようにしてみました
具体的にはラッチ回路で単発の信号を作る部分を手前に持ってきてそこから水をまく信号と水を回収する信号を分岐するようにしています
minecraft_tenku_tt1.png

あともう一つ改善してみたのがタイマーの切り替えのタイミングです
紹介サイトではアイテムを 21 個入れていたのですがこれを 32 個に増やしてみました
要するにこれも水をまいていない時間を増やすことで mob の湧き効率を上げようという作戦になります

で、この回路の変更で結局どうなったかというとこれもそこまで湧き効率が改善した感じはありませんでした、、、

待機場所の足場を極力なくす

スポーンするブロックを探す段階で足場が少ないほど湧きやすくなると思い待機場所の足場をほぼなくしました
が、結果は変わらずでした

湧きの挙動がおかしい

待機場所ではただただ立っているだけなのですが、湧きの挙動も少しおかしいので記載しておきます
どうおかしいかと言うと時間が経過すると全く湧かなくなるということです
待機し始めは湧きが悪いものの 2, 3 匹の mob が落下するのを確認することができます
が、1, 2 時間すると落下する mob がほぼいなくなります
試しに 8 時間ほど放置してみたところやはり、ある時間から全く湧いていないことがわかりました
湧いていないということは何かしらのスポーン条件に当てはまっていない可能性があるとは思いますが、その原因はさっぱりわかりません

最後に

マインクラフト v1.12.2 で天空トラップタワーの湧き効率を何とかあげてみようと模索してみました
結果的にはほぼ上げることができませんでした

今回紹介した中でまだ試していない施策として

  • 湧き層を下に増やす
  • 地上の湧き潰しをしてみる (たぶん意味なし)
  • 別のタイプの天空トラップタワーを作ってみる

があるかなーと思います
正直天空トラップタワーを作るのにはかなり苦労するのでまずは湧き層を増やすことをやってみようかなと思います
ただ、これもすでに処理層などがあるのを考えるとそれを一度崩す必要が出てくるので大変かなと思います

あと今回の作業で一番なぞだったのが「Y 軸 200 以上の高度での mob のスポーン効率」です
もしこれが本当にそうなのであればそもそも湧き層を作る位置をもっと下げないといけなくなります
いろいろと調べたのですが明確な情報が出てこなかったので詳細は不明です

2018年2月20日火曜日

docker-compose で load-balancer を使う方法

概要

docker が公式で load-balancer 用のイメージを出しているのでそれを使います
dockercloud-haproxy というやつです

環境

  • macOS 10.13.2
  • Docker version 18.02.0-ce, build fc4de44
  • docker-compose version 1.19.0, build 9e633ef

docker-compose.yml

version: '2'
services:
  web:
    image: kakakikikeke/debug_container
    ports:
      - 4567
  lb:
    image: dockercloud/haproxy
    ports:
      - 80:80
    links:
      - web
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
  • docker-compose up -d
  • curl -s localhost | jq '.hostname'

こんな感じでアクセスするとコンテナ ID が取得できます

スケールしてみる

  • docker-compose scale web=3

でスケールできます
これで何度か curl を叩いてみましょう

  • for i inseq 1 10; do curl -s localhost | jq '.hostname'; done
"7a6fd1982372"
"d35f25b865a1"
"57236b7f536d"
"7a6fd1982372"
"d35f25b865a1"
"57236b7f536d"
"7a6fd1982372"
"d35f25b865a1"
"57236b7f536d"
"7a6fd1982372"

こんな感じで別のコンテナにアクセスしてくれていることがわかると思います
ローカルで動作させる場合には volumes で指定している /var/run/docker.sock を haproxy にマウントしていないとちゃんとバランシングしてくれないので注意してください

最後に

dockercloud-haproxy を使ってコンテナのロードバランシングをしてみました
scale するだけでバランシングしてくれるので haproxy の再起動が不要なのが嬉しい点です
haproxy 自体の多重構成もできるはずです
AWS などには ALB など専用のロードバランサがありますが、そこまでアクセスがないのであれば、コンテナで簡単に LB を立てるのもありかなと思います
本番運用するのであれば Swarm でクラスタ化してちゃんと affinity ルールを考える必要はありそうですが

参考サイト

2018年2月19日月曜日

docker 17.06 で swarm 機能を試してみた

概要

docker 17.06 で swarm の使い方が変わっていたので試してみました
かなり簡単に使える印象です
Manager と Worker 2 つのノードを作成してどちらも dockerd を起動しておきましょう
ついでなので overlay ネットワークも試してみました

環境

  • Ubuntu 16.04
  • Docker version 17.06.2-ce, build cec0b72

Manager となるノードで初期化する

  • docker swarm init

以下で実行する join 用のコマンドが表示されます

Worker となるノードで join する

  • docker swarm join --token SWMTKN-1-4n6elansbxnaz6iubmy2i171dptprfpflykqn3vjx53ef7kivy-65jhododu1mnkps3s95jfl86j 192.168.100.20:2377

Manager ノードで overlay ネットワークを作成する

  • docker network create -d overlay --attachable overlay1

attachable を指定すると作成したネットワークを手動でアタッチすることができるようになります

  • docker network ls

で作成した overlay ネットワークを確認しましょう
Manager では表示されると思います
Worker 側では表示されません、が普通に使うことができるので試してみます

ちなみに ingress というデフォルトで作成されるネットワークは Manager にも Worker 側にもあります

overlay ネットワークを試してみる

Manager 側でコンテナを起動します

  • docker run -d --net overlay1 -p 80:80 --name nginx nginx

これで overlay ネットワーク上で起動するコンテナが 1 台作成されました
続いて Worker 側のホストでコンテナを起動してみます

  • docker run --rm --net overlay1 --link nginx alpine ping -c 5 nginx

これで実行すると Manager 側のコンテナにアクセスできると思います

Swarm 状態を解除する

まず Worker 側で

  • docker swarm leave

します
Manager 側で

  • docker node ls

を実行してみて leave した Worker 側のホストのステータスが Down になるのを待ちます
Down になったら Manager 側で

docker node rm 7jug2spgk3c1i335shlnmz885

することで Worker を切り離すことができます
Worker 側で docker info を実行すると Swarm の状態が表示されなくなります

最後に Manager 1 台になったら Manager で以下のコマンドを実行すると Manager を切り離せます

  • docker swarm leave --force

ちなみに swarm 状態が解除されると overlay ネットワークも勝手に削除してくれました

最後に

比較的新しめの docker で Swarm 機能を試してみました
かなり簡単に使えるようになっている印象です
昔は Swarm を管理する agent コンテナが起動する必要があったのですがそれはなくても動作するみたいです
KVS を管理する必要があると思うのでどこかで管理しているとは思いますが

2018年2月17日土曜日

マイクラを DropBox を使ってロールバックする方法

概要

放置プレイ中に予期せぬ自体で死んでしまいアイテムをロストするなんてことがあると思います
そんな場合にセーブデータをロールバックする方法を紹介します

環境

  • macOS 10.13.2
  • Minecraft 1.12.2

やり方

前提としてセーブデータを Dropbox で管理している必要があります
DropBox でなくても OK ですがバージョン管理できていないとダメです
今回は DropBox でロールバックする方法を紹介します
マイクラを起動している場合は停止しましょう

まず DropbBox にログインしてセーブデータ配下のディレクトリに移動します
そして level.dat のバージョン履歴を開きます
minecraft_rollback1.png

level.dat は現在のキャラクターの状態を保存しているファイルです
ほぼ毎分更新履歴があるので死ぬ前、ロストする前の時間に遡って復元を選択します
minecraft_rollback2.png

これで状態が戻りました
再度マイクラを開いて見ると死ぬ前の状態になっていると思います
もし、戻りすぎた or まだ戻っていない場合は再度マイクラを停止して復元する箇所を変えてみてください

同じようなファイル名で _old とついたファイルがありますがそいつは特に復元しないで大丈夫です

他にも .json ファイルや .log, .mca ファイルなどが更新されていますがそれも復元しないで OK です

最後に

マイクラのデータをロールバックする方法を紹介しました
前提としてバージョン管理されていることが条件なので、バージョン管理していない場合は諦めてください

Dropbox はファイルに更新があるたびに同期するので自分の好きなタイミングで履歴を取りたい場合などは git などを使ったほうが良いと思います
コミットログも残せるので

DropBox も git もよくわからんという場合はセーブデータのディレクトリを丸ごとコピーするでも良いと思います

2018年2月16日金曜日

framework not found GoogleToolboxForMac 対策

概要

iOS11 の実機 (iPhone6) でビルドすると問題ないのですが iOS10 の実機 (iPhone5) でビルド発生するようになりました
対策したので紹介します

環境

  • macOS 10.13.2
  • Xcode 9.2 (9C40b)

Build Active Architecture Only を NO にする

プロジェクトの Build Settings と Pods の Build Settings の Build Active Architecture Only をすべて NO にすることで解決しました

Targets の Build Settings
not_found_framework3.png

Projects の Build Settings
not_found_framework2.png

Pods の Build Settings
not_found_framework1.png

一度 Clean してから iPhone5 上でビルドしてみてください
当然ですが Deployment Target は iOS10 以上を指定するようにしてください

参考サイト