2017年11月27日月曜日

Mac 版の Minecraft を購入してみた

概要

今更ながら Mac 版の Minecraft を購入してみました
スクリーンショット付きで紹介したいと思います

環境

  • macOS X 10.13.1

購入する

まずは Minecraft の公式ページにアクセスしましょう
ここで「MINECRAFT を購入する」を選択します
minecraft1.png

すると MOJANG のアカウントを作成する画面になるので登録しましょう
すでにある場合はログインすれば OK です
入力できたらアカウントを作成します
minecraft2.png

すると登録したメールアドレスに認証用のコードが届くのそれを入力してアカウントをアクティベートします

次にプロファイルとクレジットカード情報を登録しましょう
登録が完了し情報が問題なければ購入完了画面に移動します
minecraft4.png

ちなみにメールにも受領書メールが来ます
そのまま「MACOS を獲得するためダウンロードする」ボタンを押して Minecraft をダウンロードしましょう
すると「Minecraft.dmg」というファイルがダウンロードできます

インストールする

ダウンロードできた dmg ファイルを展開してインストールしましょう
インストールは簡単で dmg ファイルを展開したら Minecraft.app を Applications フォルダに移動するだけです
minecraft5.png

起動してみる

LaunchPad やらアプリケーション一覧からインストールした Minecraft を起動しましょう
ランタイムのダウンロードが始まると思います
minecraft6.png

完了するとログイン画面になるので登録したメールアドレスとパスワードでログインしましょう
minecraft7.png

ログインが完了すると本体の jar ファイルをダウンロードできるボタンが表示されるのでクリックしてダウンロードしましょう (ダウンロードするものが多いですね、、、)
ついでに大量のメディアファイル (ogg ファイル) もダウンロードします (おそらくゲームに必要なファイルでしょう)
minecraft8.png

起動完了

すべてのダウンロードが完了し以下の画面になればゲームを開始することができます
minecraft9.png

とりあえず設定したほうが良さそうなこと

  • 設定 -> 言語設定 -> 日本語
  • 設定 -> 操作設定 -> で操作のキーバインドを設定
  • 設定 -> 操作設定 -> マウスの感度を 70% (3D 酔い防止)
  • 設定 -> ビデオ設定 -> 画面の揺れオフ (3D 酔い防止)
  • シングルプレイ -> ワールド新規作成 -> ワールド名を決定

あたりかなと思います
あとはプレイしながら好きなところを設定していきましょう

最後に

Mac で Minecraft を購入してインストールし起動するところまでやってみました
Runtime はどうやら Java らしいので Java がインストールされていない場合はインストールが必要かなと思います

これでマイクラデビューです
いろいろと試していきたいと思います
mod でプログラミングもしてみようと思います

2017年11月25日土曜日

LPIC202 4.5 に合格しました

概要

昨年 LPIC 201 に合格しました
今年は 202 を受験したので勉強方法などを紹介したいと思います

申し込みから受験までの流れ

前回と全く同じなので申込み方法などは前回の記事を参考にしてください
ピアソン VUE から申し込みを行う感じです

勉強方法

一点前回より大きく変わった点があります
それは LPIC の 202 のバージョンが 4.5 に変わりました

前回の 201 は 4.0 というバージョンだったのですが、今回は 4.5 というバージョンに変わりました
簡単に言うと出題される範囲が変わっています

で勉強方法ですが大きくは前回と変わりません
過去問をガンガン解いて 100% になるまで繰り返します
あとは実際にコマンドを叩いたりネットで調べて情報を補っていきました

まず参考書ですが前回使用した 4.0 のバージョンの参考書が手元にあるのでまずはこれで学習を進めました

これに出題範囲の問題集+模擬試験が付いているのでそれを 100% になるまで繰り返しました

個人的には「4.5 になったと言ってもマイナーバージョンアップだからそんなに変わらないだろ」と思っていました
とは言え「4.5 も少しはカバーしておきたい、でも 201 は合格しているので参考書を新しく買うのもなー」と思い探してみたら差分だけ紹介している本が手軽な値段で売っていたのでこれを購入して追加で勉強しました

こも模擬試験があったので模擬試験を 100% になるまで解きました

この 2 冊の参考書を完璧に覚えたたらあとはネットや help コマンドを使って知識を補いました

で実際はどうだったか

LPIC はご存知の通り回答の詳細や問題の持ち帰りはできないので記憶を辿ることしかできないのですが、一番感じたのは「勉強不足」だったなと思いました

特に以下の分野については紹介した参考書だけでは完全に知識不足だったなと思いました

  • IPv6 について
  • 電子メールの知識

前者や分野に関係なくすべての分野で IPv6 に関する知識が出題された記憶があります
後者に関しては参考書で得られる知識では完全に足りず実際に構築しないとわからなかったり運用してみないとわからない問題もあったなという印象です

もしかしたら 4.5 の参考書を素直に買っていたらそれらもカバーできていたかもしれないです
前回受験した 201 に比べ 202 は過去問から出題される内容が少ないなと思いました (まだ 4.5 になってから日が経っていないというのもあると思います)
なので、202 の方がより実践的な知識が必要になるなと思いました

成績表

点数配分は一応こんな感じでした
メールがダメダメでした
lpic202_ret.jpg

最後に

LPIC 202 4.5 に合格したので勉強方法について紹介しました
個人的には 201 よりも難しかった印象です

今になって思いますが 202 は DHCP や DNS, nfs, nginx などサーバとして構築できる技術が多いので実際に構築して挙動を確かめる方法が取れればそれが一番良いと思いました

次は下位ですが 101, 102 を一気に取得しようかなと思います

2017年11月17日金曜日

SpriteKit でカウントダウンする画面を作ってみた

概要

ゲームなどをスタートする場合に 5 からカウントダウンする場合があると思います
自分が作っているゲームアプリでも必要になりそうだったので試しに作ってみました

環境

  • macOS X 10.13.1
  • Xcode Version 9.1 (9B55)

GameScene.sks

こんな感じで背景とカウントする用のノードを追加します
背景は SKSpriteNode ですがカウントするのは SKLabelNode を使っています
カウントダウンするのに画像を使いたい場合はカウントダウン用のノードも SKSpriteNode にしたほうが良いかなと思います
spritekit_countdown1.png

Scene の設定で CustomClass に「GameScene」を設定しておきましょう

GameScene.swift

sks ファイル上に配置したノードを使って Swift 側でカウントダウン処理を実装します
で全容は以下の通りです
初めに言っておきますがとりあえず動けば良いと思って作ったのでソースコードはかなり適当です

import SpriteKit

class GameScene: SKScene {

    var background = SKSpriteNode()
    var count = SKLabelNode()

    override func didMove(to view: SKView) {
        // 各種ノードを初期化
        background = childNode(withName: "background") as! SKSpriteNode
        count = childNode(withName: "count") as! SKLabelNode
        // wait するアクションを定義
        let wait = SKAction.wait(forDuration: 1.0)
        // 5 から 1 までカウントダウンするアクションを定義
        let five = SKAction.run({
            self.count.text = "5"
        })
        let four = SKAction.run({
            self.count.text = "4"
        })
        let three = SKAction.run({
            self.count.text = "3"
        })
        let two = SKAction.run({
            self.count.text = "2"
        })
        let one = SKAction.run({
            self.count.text = "1"
        })
        // 背景とカウントダウンのラベルを非表示にするアクションを定義
        let hidden = SKAction.run({
            self.background.isHidden = true
            self.count.isHidden = true
        })
        // アクションを実行
        self.run(SKAction.sequence([wait, five, wait, four, wait, three, wait, two, wait, one, wait, hidden]))
    }
}

説明はコード内にコメントを記載してあります
基本はカウントダウンする SKAction を作成してそれを SKAction.sequence でつなぎ合わせて run するだけです

かなりコードが冗長なのでカウントダウンするアクションの部分はループなので書き直せると思います

もしカウントダウンするラベルが SKSpriteNode なのであれば .texture でカウントダウンごとに画像を変更すれば OK かなと思います

あと今回背景用のノードを一つ追加しています
カウントダウンが終了するまでゲームを始めたくない場合に背景用のノードを最前面に置いておくことでタップなどをさせないように制御することができます
背景用のノードを非表示にすることで実際にゲームする画面にタップすることができるようになるという感じです

動作確認

こんな感じで動きます
うーん、それっぽい

spritekit_countdown2.gif

最後に

SpriteKit でカウントダウンする画面を作成してみました
もしかするともっとかっこいい画面を簡単に作成できるライブラリがあるかもしれません
とりあえず簡単にかつ自作したいという場合に参考にしてみてはいかがでしょうか

参考サイト

2017年11月16日木曜日

SpriteKit の衝突判定について挙動をまとめてみた

概要

SpriteKit の物理エンジンで使える衝突判定の設定について実際に手を動かしながらどうさ確認してみました
今更感もありますが個人的な備忘録として残しておきます

環境

  • macOS X 10.13.1
  • Xcode Version 9.1 (9B55)

categoryBitMask を設定してみる

とりあえず categoryBitMask だけをそれぞれ設定して実行できるところまで準備しましょう
GameScene.sks ファイルには SKSpriteNode を縦に適当に 3 つ配置しておきます
ちなみに以下では青ノード (node1)、緑ノード (node2)、ピンクノード (node3) として扱います
またピンクノードを落下させて挙動を確認しています

import SpriteKit

class GameScene: SKScene, SKPhysicsContactDelegate {

    var node1 = SKSpriteNode()
    var node2 = SKSpriteNode()
    var node3 = SKSpriteNode()

    let Node1: UInt32 = 0x1 << 1
    let Node2: UInt32 = 0x1 << 2
    let Node3: UInt32 = 0x1 << 3

    override func didMove(to view: SKView) {
        self.physicsWorld.contactDelegate = self
        node1 = self.childNode(withName: "node1") as! SKSpriteNode
        node1.physicsBody = SKPhysicsBody(rectangleOf: node1.size)
        node1.physicsBody?.affectedByGravity = false
        node1.physicsBody?.isDynamic = true
        node1.physicsBody?.categoryBitMask = Node1
        node2 = self.childNode(withName: "node2") as! SKSpriteNode
        node2.physicsBody = SKPhysicsBody(rectangleOf: node2.size)
        node2.physicsBody?.affectedByGravity = false
        node2.physicsBody?.isDynamic = true
        node1.physicsBody?.categoryBitMask = Node2
        node3 = self.childNode(withName: "node3") as! SKSpriteNode
    }

    func touchDown(atPoint pos : CGPoint) {
        if let node = atPoint(pos) as? SKSpriteNode {
            if node == node3 {
                node3.physicsBody = SKPhysicsBody(rectangleOf: node3.size)
                node3.physicsBody?.affectedByGravity = true
                node3.physicsBody?.isDynamic = true
                node3.physicsBody?.categoryBitMask = Node3
            }
        }
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        for t in touches { self.touchDown(atPoint: t.location(in: self)) }
    }
}

friction や linearDumping, mass などは設定しないで OK です
この状態で実行すると以下のように衝突後 3 つのノードが下に落下します
contact_test1.gif

ちなみに categoryBitMask を適当に変更しても状況は変わりません
ノード間の衝突ロジックはデフォルトだと通過ではなく衝突になるようです
これを通過できるようにするのに collisionBitMask の設定をいじります

collisionBitMask を設定してみる

次に collisionBitMask を設定して挙動を確認してみます
まずは各ノードにそれぞれ設定します

node1.physicsBody?.collisionBitMask = Node1
node2.physicsBody?.collisionBitMask = Node2
node3.physicsBody?.collisionBitMask = Node3

でこれで挙動を確認すると以下のように変わります
contact_test2.gif

青 (node1) 、緑 (node2) ともに衝突せずピンク (node3) がすーっと通過していきました
なぜこうなるかというと

例えば、緑のカテゴリ (categoryBitMask) は 2進数表現で「100」です
ピンクの衝突フラグ (collisionBitMask) は 2進数表現で「1000」です
これの 2 つ値の論理積を取ると「0」になります
そう、0 になる 2 つノード間の categoryBitMask と collisionBitMask の論理積が 0 になると衝突せず通過となります
逆に論理積が 1 となると衝突します
一番始めのデモではそれぞれ衝突しましたが、デフォルトだと collisionBitMask はすべてのノードに衝突する設定になっているようです (print すると nil でした)

ではこれを踏まえて青 (node1) とピンク (node3) だけ衝突するようにしています

node3.physicsBody?.collisionBitMask = Node1

に変更しましょう
node1 側の collisionBitMask は変更しなくて OK です
これで実行すると以下のようになります
contact_test3.gif

青には衝突するようになりました
が青が動かなくなりました
isDynamic = true にしているのに止まります
もし衝突したときに青も動かしたい場合には node1 の collisionBitMask を 0 にします (もしくは設定しません)

node1.physicsBody?.collisionBitMask = 0

すると以下のような挙動になります
contact_test4.gif

緑で止まるのは青の collisionBitMask が全衝突判定 (0 or nil) になっているからです
ここで少し補足なのですが collisionBitMask が 0 or nil どちらでも同じ挙動になったということです
そして 0 or nil の場合は他と衝突した場合に isDynamic の設定の影響を受けるのですが明示的に設定している場合 (例えば今回のように node3.physicsBody?.collisionBitMask = Node1 などとした場合) は isDynamic が必ず false の挙動になるようです
これはおそらく仕様かなと思います (バグなような気もしますが、、、)

なので今回の場合、青に衝突してピンクと一緒に落下するけど緑はスルーしたいというケースはおそらく設定できないと思います
もし設定できる方法があれば教えていただきたいです

ちなみに青をスルーして緑に衝突させて停止させたい場合は以下のように設定します

node2.physicsBody?.collisionBitMask = Node2
node3.physicsBody?.collisionBitMask = Node2

さらにちなみに node2 の collisionBitMask の設定を削除すると緑も一緒に落下していきます

contactTestBitMask を設定してみる

これを設定することで衝突時にコールバックとして didBegin メソッドがコールされるようになります

didBegin メソッドは以下のような感じで実装しました

func didBegin(_ contact: SKPhysicsContact) {
    var firstBody: SKPhysicsBody
    var secondBody: SKPhysicsBody
    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        firstBody = contact.bodyA
        secondBody = contact.bodyB
    } else {
        firstBody = contact.bodyB
        secondBody = contact.bodyA
    }
    print(firstBody)
    print(secondBody)
}

上記が書けたら contactTestBitMask を各ノードに設定してみます
何も考えずそれぞれに設定してみます

node1.physicsBody?.contactTestBitMask = Node1
node2.physicsBody?.contactTestBitMask = Node2
node3.physicsBody?.contactTestBitMask = Node3

で実行しても didBegin メソッドはコールされないと思います
なぜか、node3.physicsBody?.contactTestBitMask = Node3 の設定の意味は

node3 が Node3 の categoryBitMask を設定しているノードに衝突したときに didBegin をコールする

という意味になります
今回の場合、node3 は node1 or node2 に衝突します
なので、以下のように書き換えてあげることがで didBegin メソッドがコールされるようになります

node3.physicsBody?.contactTestBitMask = Node1

これで node1 に衝突もしくは通過したときに didBegin メソッドがコールされるようになります
contact_test5.gif

見ての通り青は通過、緑で衝突するように collisionBitMask を設定しています
更に上記の通り contactTestBitMask は青を通過したときに didBegin がコールされるようにしています

ここでやりたくなるのが「緑と衝突したときも didBegin がコールされたい」だと思います
その場合は以下のように設定し直しましょう

node3.physicsBody?.contactTestBitMask = Node1 + Node2

これで実行すると緑と衝突したときもコールされるようになります
contact_test6.gif

こんな感じで衝突したいノードのカテゴリ情報を加算していけば OK です

firstBody と secondBody について

didBegin 内で衝突した 2 つノード情報を取得することができます
firstBody が衝突された側で secondBody が衝突した側になります
今回のサンプルで言うとピンクが second で 青や緑が first になります

青は緑に衝突する可能性があるので second になり得る可能性もあります

最後に

SpriteKit の衝突判定で使用する categoryBitMask, collisionBitMask, contactTestBitMask の挙動についてまとめてみました

categoryBitMask は名前の通りノードに設定するカテゴリみたいなものです
カテゴリに設定した情報を元に collisionBitMask や contactTestBitMask に設定した値と比較して衝突するのかスルーするのかコールバックメソッドをコールするのかしないのかを決定する感じです
基本は論理積を取って 0 は何もしない 1 ならアクションという感じです

正直ややこしいです
たぶん自分も今回の記事の説明を聞いただけではさっぱり理解できないと思います
なので、やはり手を動かしながら実際に挙動を確認して理解するのが良いかなと思います

SpriteKit の物理エンジンや衝突判定はややこしいですがこれを理解しないとおもしろいゲームは作れないと思うので頑張って理解してみてください

2017年11月14日火曜日

didBegin が複数呼ばれてしまう場合の対応方法

概要

didBegin は SKPhysicsContactDelegate を継承することで使える関数です
SpriteKit で物理エンジンを使ったノードが衝突したときにコールされるイベント関数です
本来なら一度しか衝突していないのですが、ノードのスピードや重力の影響などで関数が複数回呼ばれることがあります
例えば衝突時に敵のカウントなどを増やすような処理をしている場合に 1 体しか倒していないのにカウントが 3 や 4 に増えてしまいます
そんな場合の対応方法を紹介します

環境

  • macOS X 10.13.1
  • Xcode 9.1 (9B55)

コード

こんな感じで対応します

func didBegin(_ contact: SKPhysicsContact) {
    var firstBody: SKPhysicsBody
    var secondBody: SKPhysicsBody
    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        firstBody = contact.bodyA
        secondBody = contact.bodyB
    } else {
        firstBody = contact.bodyB
        secondBody = contact.bodyA
    }
    if (firstBody.node?.parent != nil && secondBody.node?.parent != nil) {
        // 処理する
    }
}

ずばり答えは firstBody.node?.parent の null チェックをします
理由はさっぱりですが、複数衝突した場合に 1 度だけ null ではない場合があります
なのでこれで衝突回数を 1 回にすることができます

最後に

didBegin が複数回呼ばれてしまう場合の対応方法を紹介しました
自分が遭遇したのが iPhone5 (iOS 10.3) です
で、実はシミュレータの iOS11 では発生しませんでした
なのでもしかすると iOS10 系のバグかもしれません

とりあえず処理は入れておいても iOS11 で問題なく動作はするので影響はないと思います

参考サイト

2017年11月13日月曜日

SpriteKit でボールを壁に当てて無限に反射させる

概要

SpriteKit の CGVector は指定方向に力を与えることができます
今回はノードに対して力を加え動かし壁に衝突したら永遠に反射しつづけるものを作成してみました

環境

  • macOS X 10.13.1
  • Xcode 9.1 (9B55)

コード

import SpriteKit

class GameScene: SKScene {

    var ball = SKSpriteNode()

    override func didMove(to view: SKView) {
        self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
        self.physicsBody?.affectedByGravity = false
        self.physicsBody?.isDynamic = false
        self.physicsBody?.linearDamping = 0
        self.physicsBody?.friction = 0
        self.physicsBody?.restitution = 1

        ball = self.childNode(withName: "ball") as! SKSpriteNode
        ball.physicsBody = SKPhysicsBody(texture: ball.texture!, size: ball.size)
        ball.physicsBody?.affectedByGravity = false
        ball.physicsBody?.linearDamping = 0
        ball.physicsBody?.restitution = 1
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        for t in touches { self.touchUp(atPoint: t.location(in: self)) }
    }

    func touchUp(atPoint pos : CGPoint) {
        print(abs(pos.x - ball.position.x))
        if (abs(pos.x - ball.position.x) <= 200 && abs(pos.y - ball.position.y) <= 200) {
            let vector = CGVector(dx: (pos.x - ball.position.x) / 10, dy: (pos.y - ball.position.y) / 10)
            ball.physicsBody?.applyImpulse(vector)
        }
    }
}

説明

.sks ファイルでは中央に 30x30 の円上のノードを 1 つ用意しています
それを childNode(withName:) で読み込んでいます
物理エンジンはコード上で設定します

今回壁に反射してもスピードが減衰しない角度が変わらないようにする必要があります
それを実現するための必須プロパティは以下の通りです

ball.physicsBody?.affectedByGravity = false
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.restitution = 1

重力の影響を受けないようにして直線移動時の減衰率を 0 にして反発率を 1 にすることで反射時にも減衰しないようにします
加えて衝突する壁にも以下の設定が必須となります

self.physicsBody?.affectedByGravity = false
self.physicsBody?.isDynamic = false
self.physicsBody?.linearDamping = 0
self.physicsBody?.friction = 0
self.physicsBody?.restitution = 1

重力の影響と衝突の影響を受けないようにします
そして直線の減衰と反発時の減衰が発生しないようにします
ボールの設定と違って friction = 0 も設定します
これは摩擦係数の値で 1 に近いほど摩擦が多くなります
なので 0 にすることで壁に衝突したときの減衰をなくしています

今回はタップした方向に対してボールに力を与えます
ボールとタップした距離が 200 以下の場合にタップした座標への距離の 1/10 の力をボールに加え移動させています
なぜ 1/10 にしているかというと距離分力を加えるとものすごいスピードでボールが動き出してしまいます
ある程度の速度なら問題ないですが速度が早すぎると物理エンジンの当たり判定がうまく動作しないケースがあったので速度を抑えるために 1/10 にしています
本当は速度を一定にするためにタップした座標との方向はそのままで距離を一手にするような計算処理を入れればいいのですがとりあえず今回はさっくり動かしたかったので 1/10 にしました

CGVector の情報はノードに対して applyImpulse を設定することで割り当てることができます

動作確認

実行すると以下のように動作すると思います
vector_sample1.gif

最後に

SpriteKit で CGVector を使って外部から力を加えてみました
SpriteKit では重力以外の力も加えることができます
今回の手法は主にビーム光線やブロック崩しなどに使われます

結構使える技なので覚えておいて損はないかなと思います

2017年11月12日日曜日

GarageBand で効果音を作成する方法

概要

GarageBand を使って簡単な効果音を作成する方法を紹介します
最終的に mp3 方式に書き出して使います
GarageBand のバージョンが 10 系なのでそれ以外やそれ以下のバージョンの場合は UI が異なる可能性があるので注意してください

環境

  • macOS X 10.13.1
  • GarageBand 10.2.0

新規でプロジェクトを作成する

garageband_sound1.png

空のプロジェクトを選択しましょう

トラックのタイプを選択する

garageband_sound2.png

ソフトウェア音源を選択しましょう

ライブラリから音源を選択

garageband_sound3.png

とりあえず今回は

Dlectronic Drum Kit -> Boutique 808

を選択しましょう
またソフトウェア鍵盤が表示れます
もし表示されない場合は Cmd + k で表示できます
表示されたらこの状態で鍵盤をクリックしたり対応するキーをタイプすることで音が出ます

録音開始

garageband_sound4.png

では録音してみましょう
上部の赤いボタンを押すと録音が開始されます
今回は効果音なので、適当な鍵盤をクリックして録音しましょう

確認

garageband_sound5.png

完了したら再度録音ボタンを押せば録音が終了します
再生ボタンを押して音が出るか確認してみましょう
もし出ない場合は音を左右に動かして音の先頭がちゃんと範囲に入るようにしてください

mp3 で書き出す

garageband_sound6.png

問題なく再生できたら mp3 を作成しましょう

共有 -> 曲をディスクに書き出す

を選択すると上のようなダイアログが表示されるので MP3 を選択して「書き出す」を選択しましょう
これで mp3 が出せるのであとは再生すれば録音した効果音が作成できます

最後に

GarageBand で簡単な効果音を作成する方法を紹介しました
GarageBand には他にも様々な音源があるので、いろいろな効果音が作成できます
今回は 1 音でのみ効果音を作成しましたが音を複合することもできます

ドラムやギター、ピアノといろいろあるので組み合わせて自分だけの効果音を作成してみてください

2017年11月11日土曜日

Mac OS High Sierra ににくまるフォントを追加する

概要

備忘録として残しておきます
フォントは別に何でも OK です

環境

  • macOS X 10.13.1
  • Font Book 8.0

フォントのダウンロード

http://www.fontna.com/blog/1651/

から手順にそってダウンロードしましょう
ダウンロードしたら解凍して .otf ファイルが手に入れば OK です

フォントを追加する

Font Book アプリを開きましょう
ミッションコントロールでもアプリ一覧からでも OK です
見つからない場合は Spotlight で検索してもいいと思います

開いたら「+」ボタンを押して先ほど手に入れた .otf ファイルを選択しましょう
mac_fond_add1.png

追加が完了するとユーザがインストールしたフォントの一覧に表示されると思います
mac_fond_add2.png

使ってみる

例えば FireAlpaca で使うときはこんな感じで表示されます
mac_fond_add3.png

フォント名を選択する場合名称は「07NikumaruFont」になるようです

最後に

Mac OS High Sierra にフォントを追加してみました
基本的にはこの手順でどんなフォントでも追加できると思います

参考サイト

2017年11月10日金曜日

SpriteKit で効果音を再生する方法

概要

SpriteKit で効果音を再生する方法を紹介します
ここで効果音は単発で再生する音のことを言います
例えばキャラがジャンプしたりアイテムを取得したときのような音です
ゲームの BGM のようにループでずっと流しておく感じではないです

いろいろとハマリポイントもあったのでその辺りも紹介します

環境

  • macOS X 10.13.1
  • Xcode 9.1 (9B55)
  • Swift3

SKAudioNode を使う

再生するためのコードは以下のような感じです

func play(music: String, loop: Bool) {
    if #available(iOS 9.0, *) {
        let play = SKAudioNode(fileNamed: music)
        play.autoplayLooped = loop
        self.addChild(play)
        self.run(
            SKAction.sequence([
                // SKAction.wait(forDuration: 0.1),
                SKAction.run {
                    play.run(SKAction.play())
                }
            ])
        )
    } else {
        let play = SKAction.playSoundFileNamed(music, waitForCompletion: true)
        self.run(play)
    }
}

まずポイントとしては iOS 9 以上と 8 以下で処理が変わるということです
iOS9 以上では SKAudioNode を使って再生できますが iOS8 以下では使えません
なので旧方式である SKAction を使って再生させます

SKAudioNode を使って再生する場合 addChild する必要があります
なので、音を再生するたびに SKScene 上にノードが追加されてしまうのでシーン上のノード数をカウントするようなアプリの場合には追加した音の数を考慮する必要があります

AVAudioPlayer を使う

以下のクラスを追加して使います
参考サイトにあるコードをほぼそのまま使っています

import Foundation
import SpriteKit
import AVFoundation

private let JKAudioInstance = JKAudioPlayer()

open class JKAudioPlayer {

    var musicPlayer: AVAudioPlayer!
    var soundPlayer: AVAudioPlayer!

    static var canShareAudio = false {
        didSet {
            canShareAudio ? try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
                : try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategorySoloAmbient)
        }
    }

    open class func sharedInstance() -> JKAudioPlayer {
        return JKAudioInstance
    }

    open func playMusic(_ fileName: String, withExtension type: String = "") {
        if let url = Bundle.main.url(forResource: fileName, withExtension: type) {
            musicPlayer = try? AVAudioPlayer(contentsOf: url)
            musicPlayer.numberOfLoops = -1
            musicPlayer.prepareToPlay()
            musicPlayer.play()
        }
    }

    open func stopMusic() {
        if musicPlayer != nil && musicPlayer!.isPlaying {
            musicPlayer.currentTime = 0
            musicPlayer.stop()
        }
    }

    open func pauseMusic() {
        if musicPlayer != nil && musicPlayer!.isPlaying {
            musicPlayer.pause()
        }
    }

    open func resumeMusic() {
        if musicPlayer != nil && !musicPlayer!.isPlaying {
            musicPlayer.play()
        }
    }

    open func playSoundEffect(named fileName: String) {
        if let url = Bundle.main.url(forResource: fileName, withExtension: "") {
            soundPlayer = try? AVAudioPlayer(contentsOf: url)
            soundPlayer.stop()
            soundPlayer.numberOfLoops = 0
            soundPlayer.prepareToPlay()
            soundPlayer.play()
        }
    }
}

これを追加して音を作成したいところで

let audio = JKAudioPlayer.sharedInstance()
audio.playSoundEffect(named: "sound1.mp3")

という感じで音を出します

こっちのやり方の方がノードを追加する必要もないので自分的にはオススメです
また iOS のバージョンも気にする必要はありません
クラスには playMusic という BGM を再生するようのメソッドも用意されているので BGM としてループするような音はそっちのメソッドを使うだけで実現できます

Tips

SpriteKit 上で音を再生するのは上記の方法でできます
が、それでもアプリと状況によっては「なぜか再生できない」という状況が発生します

自分が遭遇したケースでは音を再生する処理が終了する前に次のシーンに進むと音が出ないというケースがありました
また例えば音の再生を行っているメソッドがループするようなメソッドになっていて何度もコールされてしまい音がうまく再生されないというケースもありました
SKScene だと update メソッドや touchDown メソッドなどイベント系のメソッド内でコールするときには連続して音が再生されないような工夫が必要かなと思います

最後に

SpriteKit で音を再生してみました
自分は mp3 の効果音を再生しています
wav などでも再生できるらしいので拡張子は幅広くサポートしていると思います
タイミングなど結構考える点があり少しコツが必要かなと思いました

参考サイト

2017年11月9日木曜日

rbvmomi で GuestFileManager を使って VM 内のファイルやディレクトリを操作してみる

概要

前回 GuestProcessManager を使って VM 内のプロセス制御をしてみました
同じようにファイルやディレクトリを制御できる GuestFileManager という機能があるので今回はこれを使ってみました
ProcessManager に比べて操作できる機能が多いです

環境

  • Ubuntu 16.04.3
  • Ruby 2.3.1p112
  • gem 2.5.1
  • rbvmomi 1.11.3

ファイルの一覧を表示する

  • vim list_files.rb
require 'rbvmomi'

vim = RbVmomi::VIM.connect(
  host: '192.168.100.1',
  user: 'vcenter-user',
  password: 'vcenter-pass',
  insecure: 'true'
)

dc = vim.serviceInstance.find_datacenter('datacenter') || fail('datacenter not found')
vm = dc.find_vm('directory/path/to/vm') || fail('VM not found')

auth = RbVmomi::VIM::NamePasswordAuthentication(
  interactiveSession: false,
  username: "root",
  password: "password"
)

path = "/tmp/"
files = vim.serviceContent.guestOperationsManager.fileManager.ListFilesInGuest(
  vm: vm,
  auth: auth,
  filePath: path,
  # index: 0,
  # maxResults: 50,
  matchPattern: "^hoge$"
)

files.props[:files].each { |file|
  puts file.props[:path]
  puts file.props[:type]
  puts file.props[:size]
  puts file.props[:attributes].props[:modificationTime]
  puts file.props[:attributes].props[:accessTime]
  puts file.props[:attributes].props[:ownerId]
  puts file.props[:attributes].props[:groupId]
  puts file.props[:attributes].props[:permissions].to_s(8)
}

ListFilesInGuest を使います
パラメータに path, index, maxResults, matchPattern を指定できます
path はファイルでもいいですしディレクトリでも OK です
ファイルの場合は 1 つだけ取得しディレクトリの場合は指定したディレクトリ配下のファイル群を取得します
index はページネート用のインデックスです
デフォルトは 0 ページ目からの取得になります
maxResults は 1 ページで取得する最大の件数になります
デフォルトは 50 です
例えば取得件数が 100 ファイルある場合に 51 件目以降を取得する場合は index を 1 にしてそこから 50 件取得するか index を 0 のままで maxResults を 100 にするかになります

matchPattern は正規表現に合致したファイル群を取得するパラメータです
上記のサンプルのように指定した場合は hoge にマッチするファイルだけを取得します
これを ^hoge というふうにすると先頭が hoge というファイルにマッチするので hoge2 や hogefuga というファイルも検索結果に含まれます
使用できる正規表現は Perl の正規表現になります

検索結果は GuestListFileInfo というクラスのオブジェクトが返ってきます
この中のプロパティ :files にというものがあり、その中に各ファイルの情報を管理する GuestFileInfo という配列があります
あとは each とハッシュを使って必要な情報を取得するだけですがその中にファイルの権限を取得できる permissions があります
これがデフォルトだと 10 進数表記になっているのでわかりやすいように 8 進数表記に変換してあげると良いと思います

ファイルの属性情報を変更する

  • vim change_file_attrs.rb
require 'rbvmomi'

vim = RbVmomi::VIM.connect(
  host: '192.168.100.1',
  user: 'vcenter-user',
  password: 'vcenter-pass',
  insecure: 'true'
)

dc = vim.serviceInstance.find_datacenter('datacenter') || fail('datacenter not found')
vm = dc.find_vm('directory/path/to/vm') || fail('VM not found')

auth = RbVmomi::VIM::NamePasswordAuthentication(
  interactiveSession: false,
  username: "root",
  password: "password"
)

path = "/tmp/hoge"
attr = RbVmomi::VIM::GuestPosixFileAttributes(
  # groupId: 0
  # ownerId: 0
  permissions: 33279 # 100777
)

vim.serviceContent.guestOperationsManager.fileManager.ChangeFileAttributesInGuest(
  vm: vm,
  auth: auth,
  guestFilePath: path,
  fileAttributes: attr
)

ChangeFileAttributesInGuest を使います
guestFilePath で指定したファイルの属性情報を変更します
今回 Ubuntu VM 内のファイルの属性情報を変更するので GuestPosixFileAttributes を使用します
パラメータにはファイルの管理者情報とパーミッションを指定できます
上記のサンプルでは権限を変更しています
権限の指定方法は 10 進数で指定する必要があるので注意してください

返り値が void なので結果を確認したい場合は ListFilesInGuest を実行してファイルの内容を確認してください

テンポラリのディレクトリを作成する

  • vim create_tmp_dir.rb
require 'rbvmomi'

vim = RbVmomi::VIM.connect(
  host: '192.168.100.1',
  user: 'vcenter-user',
  password: 'vcenter-pass',
  insecure: 'true'
)

dc = vim.serviceInstance.find_datacenter('datacenter') || fail('datacenter not found')
vm = dc.find_vm('directory/path/to/vm') || fail('VM not found')

auth = RbVmomi::VIM::NamePasswordAuthentication(
  interactiveSession: false,
  username: "root",
  password: "password"
)

path = "/tmp"

ret = vim.serviceContent.guestOperationsManager.fileManager.CreateTemporaryDirectoryInGuest(
  vm: vm,
  auth: auth,
  prefix: "prefix-",
  suffix: "-suffix",
  directoryPath: path
)

puts ret

CreateTemporaryDirectoryInGuest を使います
パラメータにはディレクトリを作成するパス (directoryPath) と作成するディレクトリの prefix と suffix をオプションで指定することができます
作成されるディレクトリ名は vmware000 という形式で作成されます
000 の部分はランダムの数字が入ります
その前後に prefix と suffix で指定した文字列が付与されます

返り値は作成されたディレクトリのフルパス情報が String で返却されます

テンポラリのファイルを作成する

  • vim create_tmp_file.rb
require 'rbvmomi'

vim = RbVmomi::VIM.connect(
  host: '192.168.100.1',
  user: 'vcenter-user',
  password: 'vcenter-pass',
  insecure: 'true'
)

dc = vim.serviceInstance.find_datacenter('datacenter') || fail('datacenter not found')
vm = dc.find_vm('directory/path/to/vm') || fail('VM not found')

auth = RbVmomi::VIM::NamePasswordAuthentication(
  interactiveSession: false,
  username: "root",
  password: "password"
)

path = "/tmp"

ret = vim.serviceContent.guestOperationsManager.fileManager.CreateTemporaryFileInGuest(
  vm: vm,
  auth: auth,
  prefix: "prefix-",
  suffix: "-suffix",
  directoryPath: path
)

puts ret

CreateTemporaryFileInGuest を使います
挙動やパラメータは前述の CreateTemporaryDirectoryInGuest とほぼ同じです
ディレクトリを作成するかファイルを作成するかの違いになります
ンポラリファイルの権限は 600 で作成されるようです

ディレクトリの削除をする

  • vim delete_dir.rb
require 'rbvmomi'

vim = RbVmomi::VIM.connect(
  host: '192.168.100.1',
  user: 'vcenter-user',
  password: 'vcenter-pass',
  insecure: 'true'
)

dc = vim.serviceInstance.find_datacenter('datacenter') || fail('datacenter not found')
vm = dc.find_vm('directory/path/to/vm') || fail('VM not found')

auth = RbVmomi::VIM::NamePasswordAuthentication(
  interactiveSession: false,
  username: "root",
  password: "password"
)

path = "/tmp/prefix-vmware130-suffix"

vim.serviceContent.guestOperationsManager.fileManager.DeleteDirectoryInGuest(
  vm: vm,
  auth: auth,
  directoryPath: path,
  recursive: true
)

DeleteDirectoryInGuest を使います
パラメータは削除対象のディレクトリと再帰的に削除するかのフラグを渡します
recursive が false の場合に削除対象のディレクトリ配下に更にディレクトリがあるとエラーとなります

返り値は void になります

ファイルの削除をする

  • vim delete_file.rb
require 'rbvmomi'

require 'rbvmomi'

vim = RbVmomi::VIM.connect(
  host: '192.168.100.1',
  user: 'vcenter-user',
  password: 'vcenter-pass',
  insecure: 'true'
)

dc = vim.serviceInstance.find_datacenter('datacenter') || fail('datacenter not found')
vm = dc.find_vm('directory/path/to/vm') || fail('VM not found')

auth = RbVmomi::VIM::NamePasswordAuthentication(
  interactiveSession: false,
  username: "root",
  password: "password"
)

path = "/tmp/prefix-vmware136-suffix"

vim.serviceContent.guestOperationsManager.fileManager.DeleteFileInGuest(
  vm: vm,
  auth: auth,
  filePath: path
)

DeleteFileInGuest を使います
先程はディレクトリを削除しましたがこれはファイルを削除します
ディレクトリを指定した場合やファイルが存在しない場合にはエラーとなります
返り値は void です

ディレクトリを作成する

  • vim make_dir.rb
require 'rbvmomi'

vim = RbVmomi::VIM.connect(
  host: '192.168.100.1',
  user: 'vcenter-user',
  password: 'vcenter-pass',
  insecure: 'true'
)

dc = vim.serviceInstance.find_datacenter('datacenter') || fail('datacenter not found')
vm = dc.find_vm('directory/path/to/vm') || fail('VM not found')

auth = RbVmomi::VIM::NamePasswordAuthentication(
  interactiveSession: false,
  username: "root",
  password: "password"
)

path = "/tmp/fuga/hoge"

vim.serviceContent.guestOperationsManager.fileManager.MakeDirectoryInGuest(
  vm: vm,
  auth: auth,
  directoryPath: path,
  createParentDirectories: true
)

MakeDirectoryInGuest を使います
CreateTemporaryDirectoryInGuest とほぼ同じですがこちらの場合は作成するディレクトリ名に規則はありません
createParentDirectories は mkdir でいうところの -p オプションで上位のディレクトリがなかった場合に作成してくれます

ただ CreateTemporaryDirectoryInGuest とは違いなぜか返り値は void になっています
そして MakeFileInGuest はありません

ディレクトリを移動する

  • vim move_dir.rb
require 'rbvmomi'

vim = RbVmomi::VIM.connect(
  host: '192.168.100.1',
  user: 'vcenter-user',
  password: 'vcenter-pass',
  insecure: 'true'
)

dc = vim.serviceInstance.find_datacenter('datacenter') || fail('datacenter not found')
vm = dc.find_vm('directory/path/to/vm') || fail('VM not found')

auth = RbVmomi::VIM::NamePasswordAuthentication(
  interactiveSession: false,
  username: "root",
  password: "password"
)

src = "/tmp/fuga/hoge2"
dst = "/tmp/fuga/hoge"

vim.serviceContent.guestOperationsManager.fileManager.MoveDirectoryInGuest(
  vm: vm,
  auth: auth,
  srcDirectoryPath: src,
  dstDirectoryPath: dst
)

MoveDirectoryInGuest を使います
src にあるディレクトリを dst に移動します
dst 側にすでにディレクトリがある場合はエラーになります

返り値は void になります

ファイルを移動する

  • vim move_file.rb
require 'rbvmomi'

vim = RbVmomi::VIM.connect(
  host: '192.168.100.1',
  user: 'vcenter-user',
  password: 'vcenter-pass',
  insecure: 'true'
)

dc = vim.serviceInstance.find_datacenter('datacenter') || fail('datacenter not found')
vm = dc.find_vm('directory/path/to/vm') || fail('VM not found')

auth = RbVmomi::VIM::NamePasswordAuthentication(
  interactiveSession: false,
  username: "root",
  password: "password"
)

src = "/tmp/fuga/file1"
dst = "/tmp/fuga/file2"

vim.serviceContent.guestOperationsManager.fileManager.MoveFileInGuest(
  vm: vm,
  auth: auth,
  srcFilePath: src,
  dstFilePath: dst,
  overwrite: false
)

MoveFileInGuest を使います
移動前と移動後のファイルパスを指定します
移動前のファイルが存在しない場合はエラーになります
また移動後のファイル名がすでに存在する場合に上書きするオプションがあります
true で上書きします、false を指定した場合に移動後のファイル名がすでにある場合はエラーとなります

ホストから ESXi にファイルを転送する

  • vim transfer_from.rb
require 'rbvmomi'

vim = RbVmomi::VIM.connect(
  host: '192.168.100.1',
  user: 'vcenter-user',
  password: 'vcenter-pass',
  insecure: 'true'
)

dc = vim.serviceInstance.find_datacenter('datacenter') || fail('datacenter not found')
vm = dc.find_vm('directory/path/to/vm') || fail('VM not found')

auth = RbVmomi::VIM::NamePasswordAuthentication(
  interactiveSession: false,
  username: "root",
  password: "password"
)

path = "/tmp/fuga/file1"

ret = vim.serviceContent.guestOperationsManager.fileManager.InitiateFileTransferFromGuest(
  vm: vm,
  auth: auth,
  guestFilePath: path
)

puts ret.props[:url]

InitiateFileTransferFromGuest を使います
これは VM 上のファイルをから ESXi 上にアップロードすることができる機能になります

返り値は RbVmomi::VIM::FileTransferInformation になります
この中に url というフィールドが含まれておりこの URL にアクセスすると ESXi 上にアップロードされたファイルを確認することができます
URL は一度しかアクセスすることができません

ESXi からホストにファイルを転送する

  • vim transfer_to.rb
require 'rbvmomi'

vim = RbVmomi::VIM.connect(
  host: '192.168.100.1',
  user: 'vcenter-user',
  password: 'vcenter-pass',
  insecure: 'true'
)

dc = vim.serviceInstance.find_datacenter('datacenter') || fail('datacenter not found')
vm = dc.find_vm('directory/path/to/vm') || fail('VM not found')

auth = RbVmomi::VIM::NamePasswordAuthentication(
  interactiveSession: false,
  username: "root",
  password: "password"
)

path = "/tmp/fuga/file1"
attr = RbVmomi::VIM::GuestPosixFileAttributes(
  # groupId: 0
  # ownerId: 0
  permissions: 33188 # 100644
)

ret = vim.serviceContent.guestOperationsManager.fileManager.InitiateFileTransferToGuest(
  vm: vm,
  auth: auth,
  guestFilePath: path,
  fileAttributes: attr,
  fileSize: 26,
  overwrite: true
)

puts ret

InitiateFileTransferToGuest を使います
ローカルにあるファイルにアクセスするために ESXi 経由で取得できるURL を取得することができます
URL には一度しかアクセスできません

最後に

GuestFileManager を使って VM 内のファイルやディレクトリの操作をしてみました
GuesetProcessManager に比べいろいろな処理ができる印象です
ただ、相変わらず返り値は void が多いので確認には工夫が必要です

今回も認証はユーザ名/パスワードにしました
SSPI or Ticketed でも頑張れば認証する方法があるかもしれません

2017年11月8日水曜日

GarageBand に Magical 8bit Plug をインストールしてみた

概要

GarageBand でファミコン風のレトロ音楽を作成するには「Magical 8bit Plug」というプラグインを使用するのが定石っぽいのでインストールしてみました
軽く使ってみたのでそちらも紹介します

環境

  • macOS X 10.13.1
  • GrageBand 10.2.0

インストール方法

  • wget 'http://www.ymck.net/download/magical8bitplug/magical8bitPlug_for_mac_ver0.4.zip'
  • unzip magical8bitPlug_for_mac_ver0.4.zip
  • cp -r ~/Downloads/magical8bitPlug_for_mac_ver0.4/magical8bitPlug.component ~/Library/Audio/Plug-Ins/Components/

ダウンロードした zip ファイルを解凍して所定のディレクトリに配置するだけです
インストール方法はダウンロードしたファイル内の「お読みください.html」にも記載があります

GarageBand から使う

実際に GarageBand から使ってみましょう
開いたら画面下部に Smart Control を表示しておきます

magical_8bit_plug1.png

「トラック」を選択して下の方にスクロールすると「プラグイン」という選択ボックスがあります
そこから AU Instruments -> YMCK -> Magical 8bit Plug -> ステレオと選択しましょう
すると以下のように Magical 8bit Plug の設定画面が表示されます

magical_8bit_plug2.png

この状態で Cmd + k で鍵盤を表示しましょう
そしておもむろに鍵盤を叩くとレトロ調の音が出てくるようになると思います

magical_8bit_plug3.png

操作すると音の変化が実感できそうなのが

  • OscKind・・・種類を選択
  • Volume・・・音量の調整
  • Release・・・音が伸びる
  • SweepSwitch・・・ネガポジを設定
  • SweepTime・・・音が高くなったり低くなったりする

かなと思います
細かい部分はドキュメントを参考にしてください

あとは上部の録音ボタンを押して鍵盤を叩けば自分で曲が作成できます

最後に

GarageBand に Magical 8bit Plug をインストールして使えるところまで設定してみました
自分は作曲とかはさっぱりやったことがないのでコツなどはわかりません
付属のドキュメントに簡単なポイントなども載っているので調べながら曲を作ってみてください

参考サイト

2017年11月7日火曜日

rbvmomi で GuestProcessManager を使って VM 内のプロセスを操作してみる

概要

GuestProcessManager は VM のプロセスを制御することができる vSphere API です
VM に SSH ログインすることなくコマンドなどを実行することができます
今回は Ruby + rbvmomi を使ってプロセスの制御を行ってみました

環境

  • Ubuntu 16.04.3
  • Ruby 2.3.1p112
  • gem 2.5.1
  • rbvmomi 1.11.3

ライブラリインストール

  • bundle init
  • vim Gemfile
gem "rbvmomi"
  • bundle install --path vendor

プロセスの一覧を表示する

  • vim list_processes.rb
require 'rbvmomi'

vim = RbVmomi::VIM.connect(
  host: '192.168.100.1',
  user: 'vcenter-user',
  password: 'vcenter-pass',
  insecure: 'true'
)

dc = vim.serviceInstance.find_datacenter('datacenter') || fail('datacenter not found')
vm = dc.find_vm('directory/path/to/vm') || fail('VM not found')

auth = RbVmomi::VIM::NamePasswordAuthentication(
  interactiveSession: false,
  username: "root",
  password: "password"
)

processes = vim.serviceContent.guestOperationsManager.processManager.ListProcessesInGuest(
  vm: vm,
  auth: auth,
  pids: []
)

processes.each { |process|
  puts "name => #{process.props[:name]}, pid => #{process.props[:pid]}, owner => #{process.props[:owner]}, cmdLine => #{process.props[:cmdLine]}, startTime => #{process.props[:startTime]}"
}

ListProcessesInGuest を使います
引数には VM の情報と VM の認証情報を付与します
第 3 引数に pid の配列を指定することができ、指定のプロセス情報だけ取得することもできます
結果は RbVmomi::VIM::GuestProcessInfo の配列が返ってくるので each で回してプロパティにアクセスしています

VM の情報を取得するのに find_datacenter -> find_vm という流れを使っています
データセンターを取得したあとそのデータセンター配下にある VM を検索するのですが、フォルダで階層化されている場合はパス形式で指定する必要があるので注意してください

VM 情報取得 -> VM 認証情報作成の流れはこのあとの処理でも共通処理として登場します

環境変数の一覧を表示する

  • vim read_environment.rb
require 'rbvmomi'

vim = RbVmomi::VIM.connect(
  host: '192.168.100.1',
  user: 'vcenter-user',
  password: 'vcenter-pass',
  insecure: 'true'
)

dc = vim.serviceInstance.find_datacenter('datacenter') || fail('datacenter not found')
vm = dc.find_vm('directory/path/to/vm') || fail('VM not found')

auth = RbVmomi::VIM::NamePasswordAuthentication(
  interactiveSession: false,
  username: "root",
  password: "password"
)

env = vim.serviceContent.guestOperationsManager.processManager.ReadEnvironmentVariableInGuest(
  vm: vm,
  auth: auth,
  names: []
)

puts env

ReadEnvironmentVariableInGuest を使います
流れは先程のプロセスを取得するスクリプトとほぼ同じです
結果が String の配列で返ってくる部分が違います
第 3 引数に環境変数名を配列で指定すれば指定した環境変数のみ取得することも可能です

プロセスを起動する

  • vim start_program.rb
require 'rbvmomi'

vim = RbVmomi::VIM.connect(
  host: '192.168.100.1',
  user: 'vcenter-user',
  password: 'vcenter-pass',
  insecure: 'true'
)

dc = vim.serviceInstance.find_datacenter('datacenter') || fail('datacenter not found')
vm = dc.find_vm('directory/path/to/vm') || fail('VM not found')

auth = RbVmomi::VIM::NamePasswordAuthentication(
  interactiveSession: false,
  username: "root",
  password: "password"
)

spec = RbVmomi::VIM::GuestProgramSpec(
  programPath: "/bin/hostname",
  arguments: ""
)

pid = vim.serviceContent.guestOperationsManager.processManager.StartProgramInGuest(
  vm: vm,
  auth: auth,
  spec: spec
)

puts pid

StartProgramInGuest を使います
第 3 引数に実行するコマンド情報を指定する必要があります
コマンド情報は GuestProgramSpec から生成します
コマンド情報はフルパスで指定しましょう、PATH が通っているコマンドでもフルパスで指定しないと見つかりません

返り値は実行したコマンドのプロセス ID が返却されます
ここが少し残念なのですが実行したコマンドの標準出力などを取得することができません
なので結果がほしい場合はどこかのファイルのリダイレクトしてあとからそのファイルを取得するなどの工夫が必要になります

プロセスを停止する

  • vim terminate_process.rb
require 'rbvmomi'

vim = RbVmomi::VIM.connect(
  host: '192.168.100.1',
  user: 'vcenter-user',
  password: 'vcenter-pass',
  insecure: 'true'
)

dc = vim.serviceInstance.find_datacenter('datacenter') || fail('datacenter not found')
vm = dc.find_vm('directory/path/to/vm') || fail('VM not found')

auth = RbVmomi::VIM::NamePasswordAuthentication(
  interactiveSession: false,
  username: "root",
  password: "password"
)

pid = 3004
vim.serviceContent.guestOperationsManager.processManager.TerminateProcessInGuest(
  vm: vm,
  auth: auth,
  pid: pid
)

TerminateProcessInGuest を使います
第 3 引数には停止するプロセス ID を指定します
これも少し残念なのですが、返り値は void です
成功したかどうかの判断ができないので、もし判断したい場合はこのあとで ListProcessesInGuest などを呼び出してプロセスがなくなっているか確認するなどの工夫が必要になります

最後に

GuestProcessManager を使って vmware-tools 経由で VM 内のプロセスの制御をしてみました
VM に SSH する必要がないのが嬉しい点かなと思います
簡単なプロセス制御であれば GuestProcessManager で代用できるかなと思います
また実行条件としては vmware-tools が起動している必要があります

今回認証は VM のユーザ、パスワード認証を使いました
そもそもパスワード認証を設定していない場合もあるのでその場合は SSPIAuthentication や TicketedSessionAuthentication を使うのですがどうやらこれらだと OperationNotSupportedByGuest になってしまいアクセスできません
やり方が悪いのかもわからないのですが、ネット上でも同じように SSPI or Ticket 認証で GuestProcessManager を制御しようとしているようなのですが解決できてそうな情報は見つかりませんでした
(もし判明したら教えてほしいくらいです)

あと rbvmomi の場合ドットつなぎでオブジェクトを辿り API をコールします
次に何をつなぐかわからなくなるケースが多くなると思うのでその場合は vCenter の mob を見ながらやると次に何のメソッドがコールできるかわかると思います
本当はそ rbvmomi のコードを見ながらやりたいのですが、API の殆どが vmodl.db というバイナリファイルで定義されているため内容が確認するのが大変です

参考サイト

2017年11月6日月曜日

Super ATOK ULTIAS で定型文を登録する方法

概要

いつも忘れるので手順をメモしておきます

環境

  • Android 6.0.1
  • 端末 FUJITSU F-01J
  • Super ATOK ULTIAS

方法

  1. 設定アプリを開く

atok_ultias_snipet1.png

  1. 言語・文字・入力を選択

atok_ultias_snipet2.png

  1. Super ATOK ULTIAS を選択

atok_ultias_snipet3.png

  1. ユーティリティを選択

atok_ultias_snipet4.png

  1. 定型文ユーティリティを選択

atok_ultias_snipet5.png

  1. メニューから新規作成を選択

atok_ultias_snipet6.png

  1. 定型文として登録したい情報を入力して保存

atok_ultias_snipet7.png

で OK です
あとはキーボードから定型文を選択すれば新規登録した定型文を使用することができます

2017年11月3日金曜日

HandBrake で画像の解像度を変更して動画を出力する方法

概要

HandBrake は DVD のリッピングなどが行えるツールです
動画の再エンコードも可能でその際に解像度を変更して動画を出力することも可能です
今回はその方法を紹介したいと思います

環境

  • macOS X 10.13.1
  • HandBrake 1.0.7

方法

HandBrake を開いて以下のように設定しましょう
handbrake1

  1. Picture を選択します
  2. Anamorphic を Off に選択して Keep Aspect Ratio のチェックを外します
  3. Storage Size のサイズを任意のサイズに変更します
  4. 同様に Display Size のサイズを任意のサイズに変更します

これで OK です

自分は Apple Store で公開するための App Previews の動画サイズに合わせるために使っています
ちなみに好きなサイズとは言いましたが動画も元サイズより大きなサイズを指定することはできません

2017年11月2日木曜日

Windows 2016 に ansible を使ってみる

概要

Ansible を WindowsServer 2016 に使ってみました
WindowsServer の構築はこちらを参考にしてください
ansible コマンドを実行する環境は CentOS になります

環境

  • Windows 2016
  • CentOS 7.4.1708
    • ansible 2.4.0.0
    • pip 9.0.1
    • python 2.7.5

ansible 環境の準備

ansible は yum コマンドでインストールしています

  • yum -y install ansible

必要な python モジュール等があるので準備します

  • wget 'https://bootstrap.pypa.io/get-pip.py'
  • python get-pip.py
  • pip install xmltodict
  • pip install pywinrm

2 つのライブラリを追加しました

とりあえず ping を送ってみる

では ansible の playbook を作成していきます
とりあえず ansible 経由で ping を送信してみましょう

  • vim inventory.yml
[windows]
192.168.56.101

ホストの情報を設定します

  • mkdir group_vars
  • vim group_vars/windows.yml
ansible_user: winuser1
ansible_password: winpass123
ansible_port: 5985
ansible_connection: winrm
ansible_winrm_server_cert_validation: ignore

WindowsServer に接続する認証情報を記載します
ansible が WindowsServer に接続するのは packer 時と同様で winrm を使っています

では実行してみましょう

  • ansible windows -i inventory.yml -m win_ping

で以下の結果が返ってくれば OK です

192.168.56.103 | SUCCESS => {
    "changed": false, 
    "failed": false, 
    "ping": "pong"
}

winrm の有効化について

今回 WindowsServer 2016 の構築は packer を使用しました
packer を使って構築する場合 winrm の有効が必須になります
なので、packer 経由で WindowsServer を構築すると必然的に winrm が有効になっています

もし有効になっていない場合はコンソール画面を使って WindowsServer を操作します
Powershell のプロンプトを管理者権限で開いて以下のコマンドを実行することで有効にすることができます

  • winrm qc

で y/n を求められるので y を入力すれば有効にできます

他のプロビジョニングをしてみる

接続できたらいろいろと playbook を作成して実行してみましょう
ディレクトリ構成はベストプラクティスに沿って作成します

コマンドを実行して結果を出力する

  • mkdir -p roles/common/tasks/
  • vim roles/common/tasks/main.yml
- include_tasks: test.yml
  • vim roles/common/tasks/test.yml
- name: run ipconfig
  raw: ipconfig
  register: ip_info
- debug: var=ip_info

- name: test stat module on file
  win_stat: path="C:/Windows/win.ini"
  register: stat_file
- debug: var=stat_file

raw はコマンドを直接実行するモジュールです
register は実行した結果を指定した変数に格納することができるモジュールです
debug は指定した情報を出力するモジュールです
var= オプションを使うことで register で定義した変数を参照することができます

  • vim site.yml
---
- hosts: all
  roles: 
    - common
  • ansible-playbook -i inventory.yml site.yml

で実行すると IP アドレスの表示とファイルのチェックを行います
本当は stat_file を assert: を使って中身を確認するところまでやったほうが良いのですが今回はサンプルなので省略しています

Powershell を実行する

  • mkdir -p roles/common/files/
  • vim roles/common/files/test.ps1
Write-Host "test `n"
  • vim roles/common/tasks/powershell.yml
- name: execute powershell
  script: files/test.ps1
  register: echo_ret
- debug: var=echo_ret
  • vim roles/common/tasks/main.yml
- include_tasks: test.yml
- include_tasks: powershell.yml
  • ansible-playbook -i inventory.yml site.yml

こんな感じです
先ほどの test.yml も実行していますが不要であれば include_tasks から削除すれば OK です

WinRAR をインストールする

  • vim roles/common/files/winrar.ps1
$workdir = "c:\installer\"

If (Test-Path -Path $workdir -PathType Container)
{ Write-Host "$workdir already exists" -ForegroundColor Red}
ELSE
{ New-Item -Path $workdir  -ItemType directory }

$source = "http://rarlab.com/rar/winrar-x64-540.exe"
$destination = "$workdir\winrar.exe"
Invoke-WebRequest $source -OutFile $destination
Start-Process -FilePath "$workdir\winrar.exe" -ArgumentList "/S"

Start-Sleep -s 35
rm -Force $workdir\w*
  • vim roles/common/tasks/winrar.yml
- name: execute powershell
  script: files/winrar.ps1
  register: install_ret
- debug: var=install_ret
  • vim roles/common/tasks/main.yml
- include_tasks: test.yml
- include_tasks: powershell.yml
- include_tasks: winrar.yml
  • ansible-playbook -i inventory.yml site.yml

成功した後にプラグラムの一覧を確認すると WinRAR がインストールされているのか確認できると思います
win2016_with_ansible1.png

最後に

WindowsServer2016 に ansible を実行してみました
基本は WinRM 経由でいろいろとコマンドを実行する感じです
コマンドは shell などではなく Powershell がメインになります

Powershell を覚えさえすればいろいろとできるかなと思います

参考サイト