• 検索結果がありません。

NPCA部誌2018

N/A
N/A
Protected

Academic year: 2021

シェア "NPCA部誌2018"

Copied!
103
0
0

読み込み中.... (全文を見る)

全文

(1)
(2)

NPCA

部誌

2018

NPCA

 著

(3)

部長挨拶

こんにちは.灘校パソコン研究部(NPCA)部長の73回生、sheyasutakaです.当部の部誌2018年度版を読んでいただき,誠 にありがとうございます. 昨年度,前部長の niimiから部長を引き継ぎました.慣れない役職で苦労しましたが,優秀な会計ともどもここまで漕ぎ着ける ことができました.部長の癖に頼りない私を支えて下さった,先輩や部員の方々に,この場を借りて深くお礼を申し上げます. さて,部誌は去年に引き続きRe:VIEWによって書かれており,当日のカタログも去年から受け継がれたものです.しかし展示 に関しては,当日来てくださった方は,何か大きな変化を目の当たりにしたでしょう.今年の文化祭では新しい試みを,2つご用意 しました.1つは「ポスター街道」です.部員たちの,部誌に書くほどでもないような小さな学びを,2ページのポスターという形 でお見せします.もう1つは,ステージ企画「CombNada」です.これは,部員たちが各々の興味ある分野でライトニング・トー クをするというものです.ひとたび語りを浴びれば,ポスターに囲まれる以上の刺激が得られることでしょう. もしこれを読んでいるのが文化祭当日で,時間に余裕があれば,是非灘校文化祭に訪れて楽しんで頂けると嬉しいです. では,部員たちが苦労しながらも書き上げた記事をどうぞお楽しみください.

執筆者の表記について

各記事にはタイトルと執筆者、そして執筆者の学年が表記されています。 ほとんどの記事には、本名ではなくハンドルネームが記載されています。これは、部内では本名を呼ぶことがめったにないため です。多くの部員は入部時にハンドルネームを決めますし、そうでない人も部で活動するうちに気が付けばハンドルネームが自然 発生して定着することが多いです。 また、灘校の文化に従い、本来の学年ではなく何回生かが記されています。2018年5月現在、各回生は次の学年に相当します。 • 72回生: 高校2年生 • 73回生: 高校1年生 • 74回生: 中学3年生

(4)

目次

部長挨拶 i 執筆者の表記について . . . i 第1章 ARKit 1 1.1 自己紹介 . . . 1 1.2 ARKitとは . . . 1 1.3 ARKitを使う . . . 3 1.4 ARKitの機能 . . . 9 1.5 おわりに . . . 22 1.6 参考文献 . . . 22 第2章 写真のデータを読み込もう 23 2.1 はじめに . . . 23 2.2 どのようにデータが保存されているか . . . 23 2.3 実際の読み方 . . . 26 第3章 すばやい文字列の探しかた 28 3.1 はじめに . . . 28 3.2 約束. . . 28 3.3 とりあえず力技で . . . 28 3.4 KMP法 . . . 29 3.5 接尾辞配列 . . . 30 3.6 おわりに . . . 33 第4章 簡単!VR3(00)分クッキング 34 4.1 始まりと終わりのプロローグ . . . 34 4.2 概要. . . 34 4.3 どんなゲーム? . . . 34 4.4 下拵え . . . 35 4.5 モデルが無いと始まらないよ! . . . 35 4.6 どこに置くの? . . . 36

4.7 No Programming No Creating game ~とある似非プログラマーの死~ . . . 37

4.8 終わりと始まりのプロローグ . . . 44 第5章 ぱそこんであそぼ 46 5.1 自己紹介などなど . . . 46 5.2 環境構築 . . . 46 5.3 Pythonのプログラムを実行する . . . 47 5.4 プログラムからパソコンにアクセス . . . 48 5.5 Webから情報を取得する . . . 49

(5)

目次 5.6 さいごに . . . 57 5.7 参考文献 . . . 58 第6章 マインクラフトのサーバー管理を楽で便利なものにしてみる話 59 6.1 誤植訂正 . . . 59 6.2 初めに&自己紹介 . . . 59 6.3 プラグインって? . . . 59 6.4 実際に作ってみる前の下準備 . . . 60 6.5 「何もしない」プラグインを作ってみる. . . 60 6.6 コマンドを作る. . . 63 6.7 configファイルを作ってみる . . . 69 6.8 おわりに . . . 71 第7章 簡単blenderモデリング 72 7.1 初めに . . . 72 7.2 3DCG is easy! . . . 72 7.3 おわりに . . . 75 第8章 深層学習の基礎のキソ 76 8.1 単層・多層パーセプトロン . . . 76 8.2 順伝播型ネットワーク . . . 79 8.3 勾配法 . . . 82 8.4 誤差逆伝播法 . . . 84 8.5 あとがき . . . 91 第9章 JOI参戦記 92 9.1 予選のお話 . . . 92 9.2 本選のお話 . . . 93 編集後記 97

(6)

1

ARKit

73

回生

object

1.1

自己紹介

みなさんこんにちは、73回生(高1)のobjectです。今年はARKitのことについて部誌を書きました。

1.2

ARKit

とは

さて、多くの人はARKitというのが何なのかわからないと思いますが、なんとなくAR関連の何かだということはわかるでしょ う。じゃあそのARとは何なのでしょうか。

AR

ってなに

AR(拡張現実)とは、人が知覚する現実環境をコンピュータにより拡張する技術、およびコンピュータにより拡張された現実 環境そのものを指す言葉。(Wikipediaより抜粋) ARの定義を言葉で言い表すのは難しいですが、わかりやすく言うと現実空間に仮想のものをくっつける技術です。 社会現象にもなったPokemon GO(もう二年近く前)で有名になりましたが、それよりも前からARは存在します。

(7)

第1章ARKit 1.2 ARKitとは 図1.1: 現実にポケモンがいるように見える VRとよく混同されますが、ARとVRは全く違う技術です。 VR 現実をまるごと作り変える(全部仮想) AR 現実と仮想を組み合わせる(現実と仮想のハイブリッド)

ARKit

ってなに

ARが何なのかわかったところで、次はARKitの説明をします。ARKitというのはAppleが発表したiOS用のARフレーム ワークです。

もっとわかりやすく言うとARKitはARを使ったiOSアプリを簡単に作るためのキットで、いままでとても難しかったiOSで

のAR開発が劇的に簡単になるという画期的なものです。

ARKitはA9チップ以降を搭載し、iOS11が入ったiOS端末(つまりiPhoneなら6s以降)さえあれば特別なカメラ等が必要な く、誰でも簡単に体験できるのが特徴です。

(8)

第1章ARKit 1.3 ARKitを使う

1.3

ARKit

を使う

ARKit

の凄さ

ARKitを使えばiOSでのAR開発が劇的に簡単になるとか言いましたが、実際にどれだけ簡単なのかというと、数行コードを 書くだけでただのアプリをARアプリにできるレベルです。 それでは実際に数行コードを書くだけでARアプリを作ってみましょう。

三行

AR

Hello,World!

ARKitは実質三行でARが実装できます。早速Hello,World!を表示するARアプリを作ってみましょう。 環境構築

まず、iOSアプリを作るにはXcodeというアプリが必要です。そして、XcodeはMacでしか使えません。つまりiOSアプリ開 発にはMacが必要です。

Macが準備できたら、次にXcodeをインストールします。Xcodeはここからインストールできます。 また、表示する3Dモデルを作るためにBlenderを使うのでここからインストールしておきましょう。 モデルの作成 インストールしたBlenderを開くと立方体のオブジェクトが出てきますが、使わないのでXキーで削除しておきます。 次に、左側のツールシェルフの作成タブからテキストを選択し、Tabキーで編集モードにした後、予め表示されたテキストをす べて消してHello,World!と打ち込みます。 図1.2: Hello,World!を打ち込んだ状態 次に右側にある[ジオメトリ]プロパティの[押し出し]で厚さを調整します。また、[フォント]プロパティではフォントを変更す ることもできます。

(9)

第1章ARKit 1.3 ARKitを使う 図1.3: 厚さとフォントを変更した状態 UV展開ができるように左下のオブジェクト→変換でテキストをメッシュに変換します。 次にテクスチャを貼ります。左上のスクリーンレイアウトをDefaultからUV Editingに変更し、テキストを編集モードで選択 した状態でUキーを押してスマートUV展開を選択します。 今回は全部白くていいので白い正方形の画像を用意して左下の[開く]から用意した画像を開きましょう。 図1.4: UV展開後画像を開いた状態 できたらスクリーンレイアウトをDefaultに戻し、右のマテリアル(丸のアイコン)からマテリアルを追加し、その横のテクス チャで[新規作成]を押して下の[画像]のところでさっきの画像を開きます。 下にあるシェーディングのところでソリッドからテクスチャに切り替えて白くなっていればモデル作成は成功です。

(10)

第1章ARKit 1.3 ARKitを使う

図1.5: テクスチャが反映された状態

次に、作成したモデルをXcodeで使えるようにエクスポートします。Xcodeで使える3Dモデルの拡張子は.scnと.daeと.obj です。メニューバーからエクスポートを選択し、Collada(.dae)でエクスポートします。

Xcodeでの作業

インストールしておいたXcodeを開きます。。"create a new project"のところを選択するとこのような画面が出てきます。

図1.6: アプリテンプレート選択

ここでアプリのテンプレートを選択することができます。中央上にAR用のテンプレートがあるのでそれを選びたくなります

が、AR用テンプレートを選ぶと最初からARが実装されてしまっているので、ARKitの凄さを伝えるためにあえてSingle View Appを選びます。

名前を適当に入れてアプリを作ったら、info.plistを開き、"Information Properly List"に"Privacy - Camera Usage Description" を追加します。

(11)

第1章ARKit 1.3 ARKitを使う 図1.7: storyboard 次に先程作成し、エクスポートしたモデルとテクスチャをプロジェクト内に追加します。モデルのサイズが合わないので右上の ところで調整しておきましょう 図1.8: Xcodeでの表示 次にARを表示するためのコードを書きます。 まず最初に右上のベン図みたいなマークを押してMain.storyboardとViewController.swiftを並べ、Ctrlキーを押しながら ARSCNViewをViewController.swiftの最後の中括弧の上にドラッグ&ドロップし、sceneViewという名前でIBOutlet接続し ます。

ViewController.swift

// IBOutlet接続

@IBOutletvar sceneView: ARSCNView!

次に、ARKitをインポートします。

ViewController.swift

//ARKitのインポート

importARKit

(12)

第1章ARKit 1.3 ARKitを使う

ViewController.swift

// シ ー ン の 生 成

sceneView.scene =SCNScene(named: "helloworld.dae")!

// セ ッ シ ョ ン の コ ン フ ィ ギ ュ レ ー シ ョ ン 生 成

letconfiguration =ARWorldTrackingConfiguration()

// セ ッ シ ョ ン 開 始

sceneView.session.run(configuration)

これでARアプリを作ることができました。早速動かしてみましょう。

ARKitの起動にはカメラが必要なのでシュミレータではなく実機を接続してシュミレーションします。

(13)

第1章ARKit 1.3 ARKitを使う

図1.9: Hello,World!

(14)

第1章ARKit 1.4 ARKitの機能 図1.10: 違う方向から ね?簡単でしょ? モデルをいちいち作ったりしたので若干面倒でしたが、肝心のARの実装は非常に簡単にできました。

1.4

ARKit

の機能

さっきの説明

次のアプリを作る前にさっきのHello,World!の説明をします。

(15)

第1章ARKit 1.4 ARKitの機能

ViewController.swift importUIKit

importARKit

classViewController: UIViewController { override func viewDidLoad() {

super.viewDidLoad()

sceneView.scene =SCNScene(named: "helloworld.dae")! letconfiguration = ARWorldTrackingConfiguration()

sceneView.session.run(configuration) }

override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() }

@IBOutlet varsceneView: ARSCNView! }

コードを書き加えるとこのようになっているはずです。

最初から書かれているコードは無視して、まず2行目の import ARKit というのはアプリ内でARKitを使えるようにするため のコードです。

次に、8行目の sceneView.scene = SCNScene(named: "helloworld.dae")!はシーン(表示するもの) を生成して、AR-SCNViewにセットするコードで、この場合はhelloworld.daeという名前の3Dモデルを表示しています。

10行目の let configuration = ARWorldTrackingConfiguration() はコンフィギュレーションの作成です。

ARWorldTrackingConfiguration というのはデバイスの向き等をトラッキングし、現実世界の面を検出するコンフィギュレー ションです。 12行目の sceneView.session.run(configuration) はセッションの開始、つまり現実世界の認識やデバイスのモーション の解析を開始するということです。 また、session.pause() メソッドでセッションを中断することもできます。

水平、垂直面検出

ARKitには平面検出及び垂直面検出の機能があります。*1 デフォルトではこの機能が無効になっているので、使いたい場合は自分で設定する必要があります。 まずは平面を検出して、可視化するアプリを作りましょう。 Xcodeを開いてARテンプレートでアプリを作った後、ViewController.swiftをこのように変更します。 ViewController.swift importUIKit importARKit

classViewController: UIViewController,ARSCNViewDelegate,ARSessionDelegate{

@IBOutletvar sceneView:ARSCNView! override func viewDidLoad() {

super.viewDidLoad()

// デ リ ゲ ー ト を セ ッ ト

sceneView.delegate =self

sceneView.session.delegate =self

// シ ー ン を セ ッ ト

sceneView.scene =SCNScene()

// コ ン フ ィ ギ ュ レ ー シ ョ ン の 生 成

let configuration =ARWorldTrackingConfiguration()

// 平 面 検 出 を 有 効 化

configuration.planeDetection = .horizontal

// セ ッ シ ョ ン 開 始

sceneView.session.run(configuration) }

(16)

第1章ARKit 1.4 ARKitの機能

// 追 加 さ れ た と き

funcrenderer(_ renderer: SCNSceneRenderer,didAdd node: SCNNode, foranchor:ARAnchor) { guard let planeAnchor =anchor as?ARPlaneAnchor else{fatalError()}

print("anchor:\(anchor), node: \(node), node geometry: \(String(describing: node.geometry))")

// 平 面 ジ オ メ ト リ を 作 成

let geometry =SCNPlane(width: CGFloat(planeAnchor.extent.x),

height:CGFloat(planeAnchor.extent.z))

geometry.materials.first?.diffuse.contents = UIColor.yellow.withAlphaComponent(0.5)

// 平 面 ジ オ メ ト リ を 持 つ ノ ー ド を 作 成

let planeNode= SCNNode(geometry:geometry)

//x- z 平 面 に 合 わ せ る

planeNode.transform= SCNMatrix4MakeRotation(-Float.pi / 2.0, 1, 0, 0)

DispatchQueue.main.async(execute: {

// 検 出 し た ア ン カ ー に 対 応 す る ノ ー ド に 子 ノ ー ド と し て 持 た せ る

node.addChildNode(planeNode) })

}

// 更 新 さ れ た と き

funcrenderer(_ renderer: SCNSceneRenderer,didUpdate node: SCNNode, foranchor:ARAnchor) { guard let planeAnchor =anchor as?ARPlaneAnchor else{fatalError()}

DispatchQueue.main.async(execute: {

// 平 面 ジ オ メ ト リ の サ イ ズ を 更 新

for childNodein node.childNodes{

guard letplane =childNode.geometry as?SCNPlane else{continue}

plane.width = CGFloat(planeAnchor.extent.x)

plane.height =CGFloat(planeAnchor.extent.z) break

} }) }

// 削 除 さ れ た と き

funcrenderer(_ renderer: SCNSceneRenderer,didRemove node: SCNNode, foranchor:ARAnchor) {

print("\(self.classForCoder)/" + #function) }

// ARSessionDelegate // 追 加 さ れ た と き

funcsession(_ session:ARSession, didAdd anchors: [ARAnchor]) {

print("\(self.classForCoder)/" + #function) }

// 更 新 さ れ た と き

funcsession(_ session:ARSession, didUpdate anchors: [ARAnchor]) {

print("\(self.classForCoder)/" + #function) }

// 削 除 さ れ た と き

funcsession(_ session:ARSession, didRemove anchors: [ARAnchor]) {

print("\(self.classForCoder)/" + #function) }

}

(17)

第1章ARKit 1.4 ARKitの機能

図1.11: 平面検出

まず平面検出を有効化するためには、コンフィギュレーションの planeDetection プロパティに.horizontal を追加する必要 があります。

(18)

第1章ARKit 1.4 ARKitの機能

図1.12: 垂直面検出

// 平 面 検 出 の 有 効 化

configuration.planeDetection = .horizontal

ARAnchor

ARAnchorはARの空間内に何らかのオブジェクトを配置するための位置、向きを表すクラスです。

これによって、デバイスを動かしてもオブジェクトはアンカー(錨)で固定されているように表示されます。

また、ARAnchorにはARPlaneAnchorというサブクラスがあり、平面,垂直面検出時に得られるアンカーはこの型で動きます。 ARPlaneAnchorには平面の中心を表すcenterプロパティと大きさを表すextentプロパティ、アラインメントを示すalignment

(19)

第1章ARKit 1.4 ARKitの機能

プロパティがあります。

center、extentともにvector_float3型ですが、extentは平面の大きさを示すのでx,zのみで大きさは決まり、yの値は常にゼ ロです。

ARSCNViewDelegate

ARSCNViewDelegateではアンカーと対応するノードが追加、更新、削除されるときにメソッドを呼ぶことができます。

// ノ ー ド が 追 加 さ れ た と き

funcrenderer(_ renderer: SCNSceneRenderer,didAdd node: SCNNode, foranchor:ARAnchor)

// 更 新

funcrenderer(_ renderer: SCNSceneRenderer,didUpdate node: SCNNode,for anchor:ARAnchor)

// 削 除

funcrenderer(_ renderer: SCNSceneRenderer,didRemove node: SCNNode,for anchor:ARAnchor)

ARSessionDelegate

ARSessionDelegateではアンカーが追加、更新、削除されたときにメソッドを呼ぶことができます。

// ア ン カ ー が 追 加 さ れ た と き

funcsession(_ session:ARSession,didAdd anchors: [ARAnchor])

// 更 新

funcsession(_ session:ARSession,didUpdate anchors: [ARAnchor])

// 削 除

funcsession(_ session:ARSession,didRemove anchors: [ARAnchor])

平面に物を置く

最後に、平面を検出した後、そこにオブジェクトを置くアプリを作ってみましょう。 ViewController.swift

importUIKit

importARKit

classViewController: UIViewController,ARSCNViewDelegate, ARSessionDelegate{

@IBOutletvar sceneView:ARSCNView! override func viewDidLoad() {

super.viewDidLoad()

sceneView.delegate= self

sceneView.session.delegate =self

// シ ー ン を 生 成 し て ARSCNViewにセット

sceneView.scene= SCNScene()

// セ ッ シ ョ ン の コ ン フ ィ ギ ュ レ ー シ ョ ン を 生 成

let configuration =ARWorldTrackingConfiguration()

configuration.planeDetection = .horizontal

// セ ッ シ ョ ン 開 始

sceneView.session.run(configuration) }

// MARK: - ARSCNViewDelegate

funcrenderer(_ renderer: SCNSceneRenderer,didAdd node: SCNNode, foranchor:ARAnchor) { guard let planeAnchor= anchor as?ARPlaneAnchor else{fatalError()}

print("anchor:\(anchor), node: \(node), node geometry: \(String(describing: node.geometry))")

// 平 面 ジ オ メ ト リ を 作 成

let geometry =SCNPlane(width:CGFloat(planeAnchor.extent.x),

height:CGFloat(planeAnchor.extent.z))

geometry.materials.first?.diffuse.contents =UIColor.yellow.withAlphaComponent(0.5)

// 平 面 ジ オ メ ト リ を 持 つ ノ ー ド を 作 成

let planeNode=SCNNode(geometry:geometry)

planeNode.transform=SCNMatrix4MakeRotation(-Float.pi / 2.0, 1, 0, 0)

DispatchQueue.main.async(execute: {

// 検 出 し た ア ン カ ー に 対 応 す る ノ ー ド に 子 ノ ー ド と し て 持 た せ る

node.addChildNode(planeNode) })

(20)

第1章ARKit 1.4 ARKitの機能

let scene =SCNScene(named:"art.scnassets/ship.scn")!

// オ ブ ジ ェ ク ト の ノ ー ド 作 成

let virtualObjectNode= SCNNode()

// 別 の ノ ー ド に 載 せ 替 え る

for child in scene.rootNode.childNodes{

virtualObjectNode.addChildNode(child) }

// オ ブ ジ ェ ク ト を 載 せ る

node.addChildNode(virtualObjectNode) }

funcrenderer(_ renderer: SCNSceneRenderer,didUpdate node: SCNNode, foranchor:ARAnchor) { guard let planeAnchor= anchor as?ARPlaneAnchor else{fatalError()}

DispatchQueue.main.async(execute: {

// 平 面 ジ オ メ ト リ の サ イ ズ を 更 新

for childNodein node.childNodes{

guard letplane =childNode.geometry as?SCNPlane else{continue}

plane.width= CGFloat(planeAnchor.extent.x)

plane.height=CGFloat(planeAnchor.extent.z) break

} }) }

funcrenderer(_ renderer: SCNSceneRenderer,didRemove node: SCNNode, foranchor:ARAnchor) {

print("\(self.classForCoder)/"+ #function) }

// MARK: - ARSessionDelegate

funcsession(_ session:ARSession, didAdd anchors: [ARAnchor]) {

print("\(self.classForCoder)/"+ #function) }

funcsession(_ session:ARSession, didUpdate anchors: [ARAnchor]) {

print("\(self.classForCoder)/"+ #function) }

funcsession(_ session:ARSession, didRemove anchors: [ARAnchor]) {

print("\(self.classForCoder)/"+ #function) }

}

(21)

第1章ARKit 1.4 ARKitの機能 図1.13: 平面検出とオブジェクト 平面の上に3Dモデルが乗っているのがわかります。 内容は簡単で、先程は平面のアンカーに対応したノードを作りましたが、そこに別の、オブジェクトのノードを載せただけです。

デバッグオプション

ARSCNViewにはdebugOptionsというプロパティがあり、その名の通りデバッグに関する機能を利用できます。 2018年5月現在、debugOptionsの型であるSCNDebugOptionsの持つオプション一覧は以下のとおりです。 ARKitではSCNDebugOptionsに加えて、ARSCNView用のARSCNDebugOptionsが用意されています。

(22)

第1章ARKit 1.4 ARKitの機能 表1.1: SCNDebugOptions一覧 オプション名 内容 showBoundingBoxes 任意のノードのバウンディングボックス(境界を示す箱)を可視化する showWireframe シーンのジオメトリをワイヤーフレーム付きで描画する renderAsWireframe ジオメトリをワイヤーフレームだけで描画する showSkeletons ボーンを可視化する showCreases 細分化した状態で表示する#@#英語がよくわからなかったので消すかも showConstraints シーン内のノードに作用する制約オブジェクトを可視化する showCameras カメラを可視化する showLightInfluences シーン内のSCNLightの位置を可視化する showLightExtents 各SCNLightに影響を受ける領域を可視化する showPhysicsShapes 各ノードに追加されているSCNPhysicsBodyを可視化する showPhysicsFields 各SCNPhysicsBodyに影響を受ける領域を可視化する 表1.2: ARSCNDebugOptions一覧 オプション名 内容 showWorldOrigin AR空間の座標軸を可視化する showFeaturePoints ARKitが検出した特徴点群を可視化する 実際に適用する まずはshowBoundingBoxesオプションを適用してみましょう。

sceneView.debugOptions = [.showBoundingBoxes]

このコードをセッションを開始するコードの前に記述して動かすとこうなります。

他のオプションを使う場合は.showBoundingBoxes を.showWireframe 等に変更します。

(23)

第1章ARKit 1.4 ARKitの機能

図1.14: .showBoundingBoxes

以下、オプションを適用した場合の画像を載せます。

(24)

第1章ARKit 1.4 ARKitの機能

図1.16: .renderAsWireframe

図1.17: .showSkeletons

(25)

第1章ARKit 1.4 ARKitの機能

図1.18: .showCameras

図1.19: .showLightInfluences

(26)

第1章ARKit 1.4 ARKitの機能

図1.20: .showLightExtents

(27)

第1章ARKit 1.5おわりに 図1.22: .showFeaturePoints

1.5

おわりに

最後まで読んでいただき、ありがとうございます。 この記事ではARKitの基本的な機能を説明したつもりですが、それはARKitの機能のほんの一部に過ぎません。 この記事を機にARおよびARKitに興味を持っていただけたら幸いです。 それでは、また来年お会いしましょう!(部誌を書くとは言ってない)

1.6

参考文献

ARKit公式ドキュメント(英語) iOS11 Programing

(28)

2

写真のデータを読み込もう

73

回生

2lu3

2.1

はじめに

C言語を使って、BitMapという画像ファイルを読み込みます。 BitMapというのは、pngやjpeg,gifなどと同じようなものです。それぞれの違いは、どのようにしてデータを保存するかです。 例えば、BitMapはドット(点)ずつに色のデータが集まっています。

■コラム

: C

言語とは

プログラミングをするための方法の一つです。パソコンの細かい動作までプログラミングをすることができますが、何かを するのにたくさん書かないといけないので敬遠されることもあります。詳しくはWiki検索をおねがいします。

2.2

どのようにデータが保存されているか

パソコンのデータの全ては、0と1で表現されているのはご存知でしょうか? ですが、001001010110なんていうデータは人間 には非常にわかりにくいです。 そこで、いくつかの数字を塊として考えるようにします。具体的には、8個をひとかたまりにした1byte(バイト)というものが 使われています。8個の桁それぞれで0か1を選ぶことができるので、2の8乗個、つまり256通りものパターンを表すことがで きます。さて、下の図は1byteずつBitMapの画像ファイルのデータを前から順番に読んでいったときの図です。ちなみに、2進 数での1桁をビット(bit)といいます。つまり、1byteは8bitです。

(29)

第2章 写真のデータを読み込もう 2.2どのようにデータが保存されているか さて、最初に[42]があり、次に[4D]があります。この[4D]は何でしょうか? 正解は、16進数で表した数 です。今年は2018 年ですが、この2018という数字は10進法で表されています。10貯まるごとに、1つ位を上げるから10進数というわけです。じゃ あ、16進数はというと、16貯まるごとに1つ位を上げます。しかし、10という数字を1桁で表すことはできません。そこで、0∼ 9に加えてA∼Fのアルファベットを使って16個を表します。 さて、16進数のままだとわかりにくいので、文字or10進数に直した図を下に載せました。 2つの画像の色は対応しています。これから、それぞれについて解説をしていきます。

(30)

第2章 写真のデータを読み込もう 2.2どのようにデータが保存されているか

ファイルタイプ

BitMapなので「BM」というわけです。

ファイルサイズ

言葉の通り、ファイルの大きさをbyteで表します。このBitMap画像の場合、72,480,822byte、つまり約72Mbyteになります。

予約領域

1

2

おまじないのようなものです。

画像データまでの距離

ファイル部分と情報部分のバイト数を全て足してみて下さい。54になります。要するに、一番最初から画像データ部分まで何バ イトあるかという話です。

情報部分のサイズ

情報部分のバイト数をすべて足すと、40になりますね。

画像の幅

ピクセルというのはご存知でしょうか? 簡単にいえば、1ピクセルに1つ色があって、ピクセルが集まることによって画像にな ります。 近くからみた蟻の列は途切れているように見えても、遠くから見た蟻の列は一本の線に見えます。つまり、近くから見たらてん でばらばらな色があるようにしか見えなくても、遠くからみたら一枚の写真に見えます。 さて、話が脱線しましたが、画像の横方向にピクセルがいくつあるのかがここに入っています。

画像の高さ

縦方向にピクセルがいくつあるのかがここに入っています。

プレーン数

これは、よくわかりませんが 現在では使われなくなった概念ですby Wiki(意訳) とのことなので、気にする必要はありません。ちなみに、常に1です。

1

画素あたりの色数

正確には、色ビット数と呼びます。24のときは、1677万色が使えるそうです。正直、全部使うことはないでしょう。

圧縮形式

どのようにして圧縮をするかです。

画像データのサイズ

画像データ部分のデータの大きさです。画像データのサイズと画像データまでの距離を足すと、ファイルサイズになることをお 確かめ下さい。

(31)

第2章 写真のデータを読み込もう 2.3実際の読み方

水平解像度・垂直解像度

水平解像度(すいへいかいぞうど)とは、アナログ放送時代のテレビ・ビデオなどの映像機器の、画質の指標のひとつであ る。 参照元:Wiki とのことです。

パレットの色数

1画素あたりの色数が1,4,8のときに使うらしいです。上で書いたとおり、正確には色ビット数と呼びます。それぞれ、1bit : 2 種類4bit : 16種類8bit : 256種類色を登録することができます。

重要なパレットのインデックス

0の場合もあるらしいです。使ったことはありません。

青・緑・赤の輝度

色の三原色はご存知でしょうか? 赤(Red)・緑(Green)・青(Blue)で、RGBとまとめて呼ばれることもあります。そして、

RGBのそれぞれの色の濃さによって色を表現することができます。ここでは、1ピクセルごとに色を決めています。

2.3

実際の読み方

C言語でやります。 やることは、順番にfread()で読み込んでいくだけです。下にサンプルコードをおいておきます。 #include<math.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<time.h> #include<Windows.h> #definetrue 1 #definefalse 0

#defineHEADER_SIZE 54//heder size 54 = 14 + 40

#defineMAX_WIDTH 3000//upper limit of width(pixel)

#defineMAX_HEIGHT 3000//upper limit of height(pixel)

#defineCOLOR_CATEGORY_NUM 1000

#definePLUS_MINUS(a, b, c) ((a) + (c) >= b && (a) - (c) <= b) #defineSIZE 10

// r,g,b values of one pixel

typedef struct{ unsigned char r; unsigned char g; unsigned char b; intcolor_type;// ど の 色 か intcolor_num;// c o l o r _ c a t e g o r y の 場 合 、 ど れ だ け の ピ ク セ ル が そ の 色 に 入 っ て い る の か intsum;// r + g + b; }color; typedef struct{ longheight; longwidth;

color data[MAX_HEIGHT][MAX_WIDTH]; intcolor_num;// 色 の 数

color color_category[COLOR_CATEGORY_NUM]; }img;

intReadBmp(char *file_name,img *imgp) {

unsigned char bmp_header_buf[HEADER_SIZE]; // this store header data of bmp

charbmp_type[2]; longbmp_height; longbmp_width;

unsigned short bmp_color; longreal_width; unsigned char *bmp_data;

FILE*bmp_file;

(32)

第2章 写真のデータを読み込もう 2.3実際の読み方

if(bmp_file == NULL) {

printf("\n\n\n\nFailed to open bmp file\n"); return 0;

}

// ヘ ッ ダ ー( 両 方) を 取 得

fread(bmp_header_buf,sizeof(unsigned char), HEADER_SIZE,bmp_file);

// フ ァ イ ル の 最 初 が B M か ど う か

memcpy(&bmp_type,bmp_header_buf,sizeof(bmp_type)); if(strncmp(bmp_type,"BM", 2) != 0) {

printf("Error: %s is not a bmp file.\n", file_name); return 0;

}

memcpy(&imgp->width,bmp_header_buf + 18, sizeof(bmp_width));

memcpy(&imgp->height,bmp_header_buf + 22, sizeof(bmp_height));

memcpy(&bmp_color,bmp_header_buf + 28, sizeof(bmp_color)); if(bmp_color!= 24) {

printf("Error: bmp_color = %d is not implemented in this program.\n", bmp_color); return 0;

}

if(imgp->width >MAX_WIDTH) {

printf("Error: bmp_width = %ld > %d = MAX_WIDTH\n",imgp->width,MAX_WIDTH); return 0;

}

if(imgp->height>MAX_HEIGHT) {

printf("Error: bmp_height = %ld > %d = MAX_HEIGHT\n",imgp->height,MAX_HEIGHT); return 0;

}

if(imgp->height< 0) {

printf("Error hight is under 0\n"); return 0;

}

printf("Finished checking file format\n");

real_width=imgp->width * 3 + imgp->width % 4;//calculate real width to fit 4 byte border

if((bmp_data = (unsigned char *)calloc(real_width,sizeof(unsigned char))) ==NULL) {

printf("Eroor: memory allocation failed for bmp_data\n"); return 0;

}

printf("Reading data...\n");

for(int i= 0; i<imgp->height;i++) {

fread(bmp_data, 1,real_width,bmp_file); for(intj = 0;j<imgp->width;j++) {

imgp->data[imgp->height-i-1][j].b= bmp_data[j*3];

imgp->data[imgp->height-i-1][j].g= bmp_data[j*3+1];

imgp->data[imgp->height-i-1][j].r= bmp_data[j*3+2];

imgp->data[imgp->height-i-1][j].sum =bmp_data[j*3] +bmp_data[j*3+1] +bmp_data[j*3+2]; }

}

printf("Finished Reading data\n");

printf("Please wait a few seconds...\n");

free(bmp_data); fclose(bmp_file); return1; } intmain() { FILE*color_data_file; FILE*out_put_data_file; img*receiver_data;

receiver_data = (img*)malloc(sizeof(img)); charfile_name[100];

// Get file name

printf("Drag and drop a bmp picture which you want to convert\n");

printf("And push enter key if I don’t say start opening\n");

// If the name of picture have \n or ", it will be failed.

scanf("%s", file_name);

if(!ReadBmp(file_name, receiver_data)) {

//unsuccess return -1; } return0; } // ご 苦 労 さ ま

(33)

3

すばやい文字列の探しかた

72

回生

Hinata

3.1

はじめに

皆さんこんにちは。72回生のHinataです。ちょっと前まで中学生だったのに、いつのまにか高校2年生になってしまいました。 入学してから4年が経ちますが、自分が成長している気が全くせず厳しい気持ちでいます。今年はJOI(情報オリンピック)も落ち てしまいましたし。 さて、今回のタイトルは「すばやい文字列の探しかた」です。どっかで聞いたことあるようなタイトルですね。あまり上手なパ ロディではないですが、そこはご容赦ください。ちなみに冴えカノは見たことも読んだこともありません。読みたいとは思ってる んですけどお金がないんですよ。ラノベに限らず読みたい本が多すぎてつらいので、通りすがりの石油王がいらっしゃいましたら 寄付をお願いします。 Webサイトを見ているとき、あるいはテキストエディタで文章(あるいはコード)を書いているとき、ブラウザやテキストエディ タの検索機能を使えば、長い文章やサイトでも一瞬で探し当ててくれます。では、この処理はどうやって実現されているのでしょ うか。大多数の人は詳しく知らないでしょう。私もつい最近まで知りませんでした。 そこで、この記事では、文章から文字列(文字の列、すなわち単語や文章など)を高速に検索するアルゴリズムをいくつか紹介・ 実装してみます。使う言語はRubyです。

3.2

約束

この記事において、文字列Sの長さは|S|と表記します。 また、文字列Sの部分列のうち、i文字目から|S|文字目までを取り出した文字列を接尾辞と呼ぶことにします。例えば"ABCDEF" の接尾辞には"CDEF"や"BCDEF"などがあります。反対に、1文字目からi文字目までを取り出した文字列を接頭辞と呼ぶことに します。例えば"ABCDEF"の接頭辞には"ABC"や"ABCDE"などがあります。

3.3

とりあえず力技で

文字列を検索するアルゴリズムのうち、もっとも単純なのが総当たり(ブルートフォース)です。 長い文字列Sと検索したい文字列Wがあるとします。SとWを1文字ずつずらして比較し、全て同じであれば成功です。途中 で異なる文字があった場合は、Sの開始位置を1つずらして同じことをします。 一回の比較にO(|W |)かかり、その処理を|S| 回繰り返すため、全体の計算量はO(|S||W |)です。 実際に、Sに含まれるWの開始位置を配列で返す関数を実装してみます。 List 3.1: ブルートフォース defbrute_force(s,w) match_index = []

0.upto(s.size-w.size)do |i|

match =true

0.upto(w.size- 1)do |j| if s[i+j] !=w[j]

match =false break

(34)

第3章 すばやい文字列の探しかた 3.4 KMP法

end

match_index.push(i)if match

end match_index end

3.4

KMP

総当たりは無駄が多いです。 例えば、Sが"BABABABABDC"でWが"ABABD"の場合を考えます。

B

A

B

A

B

A

B

A

B

D

C

1

A

2

A

B

A

B

D

3

A

4

A

B

A

B

D

5

A

6

A

B

A

B

D

7

A

8

A

B

A

9

A

10

A

11

A

図3.1: ブルートフォース 後戻りしてすでに探索した部分から比較を始めている部分がありますね。この比較をどうにか省いて高速化できないでしょうか。 ここで、1から|W |までの全てのiに対して、「i文字目で終わり、Wの接頭辞のひとつと一致するWの部分文字列のうち、最 長のもの」を考えます。ただし、文字列そのものは含みません。

例えば、iが4だとします。3文字目から始まり4文字目で終わる部分文字列"AB"は先頭の2文字"AB"と同じですね。今回はこ れ以外に条件を満たす文字列がないため、最長のものは"AB"となります。 全てのiについて最長の文字列の長さを計算すると、次のようになります。 表3.1: 「ずらし表」 i 1 2 3 4 5 文字列長 0 0 1 2 0 では、実際に探索するとどうなるか考えてみましょう。ずらし表においてiに対応する文字列長をt[i]と定義します。 例えば、"ABAB"まで一致して"D"が一致しなかった場合を考えます。t[4]は2ですが、これは"ABAB"の最後2文字が先頭2文 字と等しいということなので、次の比較は途中で終わり2回先の比較でようやく"AB"と一致するわけです。

(35)

第3章 すばやい文字列の探しかた 3.5接尾辞配列

B

A

B

A

B

A

B

A

B

D

C

1

A

2

A

B

A

B

D

3

A

B

A

B

D

4

A

B

A

B

D

5

A

図3.2: KMP法 灰色の部分は実際には実行されませんが、分かりやすさのために書いています。 実際のコードはこうなります。 List 3.2: KMP法(検索部) defkmp(s, w) match_index = [] # 「 ず ら し 表 」 を 作 成 す る t=create_table(w) i= 0 # S の 比 較 開 始 位 置 j= 0 # W の 比 較 位 置 while i+j <s.size if s[i +j] ==w[j] # 一 致 し た 場 合 j += 1 if j == w.size # 検 索 成 功 match_index.push(i) # i = i + j - t[j - 1] # j = t[j - 1] i, j=i +j-t[j- 1], t[j - 1] end elsif j== 0 # 1文 字 目 で 比 較 失 敗 i += 1 else i,j =i+ j-t[j - 1], t[j- 1] end end match_index end

3.5

接尾辞配列

文字列Sの接尾辞を全て列挙することを考えます。開始位置iが0から|S| − 1まで動き、1つのiに対して接尾辞1つが対応す るので、Sの接尾辞は|S|種類存在します。 さて、|S|種類の接尾辞を辞書順に並べた配列があったとします。これが接尾辞配列です。例えば、Sが"abracadabra"の場合は 次のようになります。 "bra"が出現する位置を全て探すということは、つまり"bra"から始まる接尾辞を探索することです。配列はソートされているの で、二分探索を利用することで効率的に探すことができます。この場合、"bra"が出てくるのは2文字目と9文字目からです。 素朴な実装は次のようになります。 List 3.3: キャプション classSuffixArray attr_reader :sa definitialize(str) @str= str

@sa =Array.new(str.size) { |i|i}

@sa.sort! { |i,j|str[i..-1] <=>str[j..-1] } end

(36)

第3章 すばやい文字列の探しかた 3.5接尾辞配列 表3.2: 接尾辞配列 開始位置 接尾辞 11 a 8 abra 1 abracadabra 4 acadabra 6 adabra 9 bra 2 bracadabra 5 cadabra 7 dabra 10 ra 3 racadabra

p SuffixArray.new("abracadabra").sa # [10, 7, 0, 3, 5, 8, 1, 4, 6, 9, 2]

初めに[0,1,2,3,...]という配列を生成して、それをソートしています。 さて、このコードの計算量はどうなっているでしょうか。ソートの計算量自体は O(|S|log|S|)ですが、1回の比較はO(|S|) な ので、接尾辞配列を構築する処理の計算量はO(|S|2log|S|)となります。これは、さすがに使い物になりません。実際、16.75万文 字の文章でこのコードを実行したところ、私のパソコンでは5.3秒かかりました。多少高速化はできますが、計算量が大きすぎて 焼け石に水です。 しかし、実際にはSA-IS法という高速な構築法があり、O(|S|) で計算することができます。しかし、筆者のアルゴリズムを理 解し実装する能力はとても低く、実装することも理解することもできませんでした。

Multi-key quicksort

というわけで、今回はMulti-key quicksortというアルゴリズムを実装してみます。これは文字列の比較に特化したクイックソー トです。なぜこのアルゴリズムかというと、私は比較的再帰アルゴリズムが得意だからです。あと9時間で文化祭始まっちゃうの に、苦手な実装をしている余裕はないんですよ。(ヤバイ) "abracadabra"の接尾辞は以下です。 abracadabra bracadabra racadabra acadabra cadabra adabra dabra abra bra ra a ランダムに基準の文字列(今回はadabra)を取ります。そして、1文字目がaより前か、同じか、後ろかで分別します。 ---abracadabra acadabra adabra abra a ---bracadabra racadabra cadabra dabra bra ra それぞれの区域をソートすれば、全体としてもソートできたことになりますね。 まず、aから始まる文字列をソートします。この場合、2文字目で比較します。abracadabraを選択すると

(37)

第3章 すばやい文字列の探しかた 3.5接尾辞配列 [a] ---[a]bracadabra [a]bra ---[a]cadabra [a]dabra この後も再帰的にソートしていきます。 さて、初めの文字がaよりも後ろだった文字列のソートはどうなるでしょうか。まだ1文字目は揃っていないので、もう一度1 文字目で分別することになります。基準にbracadabraを選択すると ---bracadabra bra ---racadabra cadabra dabra ra これも、それぞれの区域をソートすることで全体をソートできます。 文字列の比較は当然 O(1)なので、全体の計算量はO(|S|log|S|)です。多分。 コード書いた方が分かりやすいと思うので、サンプルコードを載せます。なお、このコードは速度を考慮していないため、さっ きの力技ソートより遅いです。 List 3.4: キャプション classString

defat_banpei(index) if self[index].nil?

"\0" else self[index] end end end classSuffixArray attr_reader :sa definitialize(str) @str= str

@sa =Array.new(str.size) { |i|i}

@sa =qsort_sa(0, @sa) end

# n は 何 文 字 目 を 比 較 す る か

defqsort_sa(n,array) if array.size== 0 return array end if array.size== 1 return array end

pivot =array.sample before = []# 辞 書 順 が 前

same= [] # n 文 字 目 が 同 じ

after = []# 辞 書 順 が 後 ろ

array.eachdo |index|

if @str.at_banpei(n+ index) < @str.at_banpei(n +pivot)

before.push(index)

elsif @str.at_banpei(n+ index) [email protected]_banpei(n+pivot)

same.push(index) else

after.push(index) end

end

qsort_sa(n,before).concat(qsort_sa(n + 1,same), qsort_sa(n,after)) end

end

p SuffixArray.new("abracadabra").sa

結局、「ある文字列を基準に3つに分類して、1つ目と3つ目はもう一度ソートし、2つ目は比較する文字の位置を1つ進めて ソートする」という処理を再帰的にやっているだけです。

(38)

第3章 すばやい文字列の探しかた 3.6おわりに

3.6

おわりに

時間が足りず大変雑な記事になってしまいました。編集者として部誌の校正とか記事の整形とかLaTeXとの格闘とかもしない といけなかったし、ウェブサイトの管理者として文化祭ページを更新しないといけなかったし、仕方ないですね。はい。 文字列のアルゴリズムは大変奥が深いです。この記事を読んで、ちょっとでも楽しいと思っていただけたら、ぜひ自分で学んで みてください。

(39)

4

簡単

!VR3(00)

分クッキング

74

回生

harady

4.1

始まりと終わりのプロローグ

こんにちは。74回生のharady(原井)です。今回初めて部誌を書くので多分初めましてだと思います。偉大なる先輩方の精進っ ぷりを指をくわえて眺めていたら、気づいたらもう中3になってました。嘘やろ...。時がたつのは早いので皆さん気を付けま しょう。 今回の文化祭に向けて僕は、VRゲーム「寿司(が)打」を作りました。この部誌では、このゲームをUnity+Oculusで3(00*4 以上)分かけて作った様子をまとめました。一応ツールなどは小冊子にまとめたのですがここにも書いておくと、材料は • スペックの高いパソコン • VR機器(Oculus,VIVE...) • 開発ソフト(Unity,Unreal Engine...) • 技術 • やる気 です。VRゲームはおろか、普通のゲームですらUnityで完成させたことがなかったのでUnityの使い方として良くない点があ るかもしれませんが、大目に見て下さい。また作り始めたのが4月に入ってからなので、随所に手抜き工事の痕跡が残っています。 許ちて。それと表記揺れがあるかもしれませんが、見逃して下さい。 それでは「寿司(が)打」制作記の始まり始まり∼

4.2

概要

まず、何をするにしても、行動計画を立てることは重要です。このゲームの制作過程を数段階に分けて書くと、 • どんなゲームにするか • モデル用意 • 配置&テクスチャ決め • プログラミング といった感じになるかと思います。以降ではこれに従って書いていきます。

4.3

どんなゲーム

?

このゲームのコンセプトは初めから決まっていました。「寿司の気持ちになるゲーム」です。回転寿司の寿司の気持ちになるVR ゲームを作ったら面白いんじゃね?w、というネタ会話からすべては始まりました。 作る前から決めていた仕様は • プレイヤーは皿の上 • 回る

(40)

第4章 簡単!VR3(00)分クッキング 4.4下拵え

• 捕食者が取りに来る

ぐらいでしょうか。書いてから気付きましたが、ただの回転寿司ですね。これらの実装はプログラミングの項にまとめます。

4.4

下拵え

制作を開始する前に少し準備が必要です。まずVR対応するUnityのProject側の設定をしなくてはなりません。これは簡単 で、上のメニューバーからEdit→ProjectSettings→Player →Other Settings→Virtual Realty supportedにチェックを入れ

るだけです。Oculusを接続していたらおそらく自動で認識されます。

あとOculusの公式が配布しているOculusPlatformとOVRとOvrAvatarをUnityにインポートして下さい。これらの詳しい 使い方はググって...。一応OvrAvatarの使い方だけ説明すると、OvrAvatar→Content →Prefabs→ LocalAvatarをヒエラル キーにコピーすればよいです。カメラにはOVR Camera RigとOVR Managerをアタッチします。

4.5

モデルが無いと始まらないよ

!

いざ制作スタート! ...モデルが、無い...。3Dモデルが無いゲームを3Dゲームと呼んでいいのか?断じて否だ!

はい、というわけでまずモデルを用意するところから始めましょう。自分の欲しい3Dモデルは、WebのフリーモデルやUnity

のAsset Storeを探せば大抵見つかりますが、探すのに時間が掛かるし、目的のモデルが見つかるとも限りません。そんな時は自

分でモデルを用意してしまいましょう!(嘘つけ絶対こっちのほうが時間掛かるやろ)

モデル制作はBlenderというフリーソフトを使って行いました。Blenderの使い方はkota氏が部誌にまとめていますが皿とレー ンの作り方を大雑把にまとめると、

断面の半分を作ってスクリューモディファイアで1周し、それに再分割曲面モディファイアを適用した...んだと思います。覚え

(41)

第4章 簡単!VR3(00)分クッキング 4.6どこに置くの?

レーン

直線部分と円周部分を分けて作りました。円周部分の作り方は皿と同じです。テクスチャはめんどくさかったのでUV展開せ

ず、Blenderのマテリアル割り当て機能を使いました。

完成したらオブジェクトの原点?(Pivot)をCtrl+Alt+Shift+Cで重心に動かしましょう。これを怠るとUnityでの作業がとて もやりにくくなります。

Blender を使う上で覚えておいたほうが良いショートカットがいくつかあります。Aキーや UnDoのCtrl+Z やReDoの

Ctrl+Shift+Zなどは必須だと思います。他にもたくさんあるのでググるなりなんなりして下さい。

できた3Dモデルを.fbxでメッシュのみエクスポートしておきます。(.blendのままでも良いみたいなのですが、ライトなどが くっついてきて邪魔だったので.fbxにしました)。ここで問題になるのが座標系です。Blenderは右手座標系のZ-up、Unityは左 手座標系のY-upなのでBlenderで作ったモデルをそのままエクスポートするとUnityにインポートする際に向きが変わってしま

います。エクスポートするときに左下のメニューで前方をZ、上をYに設定し、トランスフォームを適用にチェックを入れましょ

う。これをしないと後々少しめんどくさいことになります。

4.6

どこに置くの

?

さぁUnityを使うぞ! ...どうやって画面を作るんだ...

Unityを使ったゲーム制作は、まずモデルを配置するところから始まります。まずはUnityのメニューバーのGameObject → 3D Object→Cubeで立方体を作りましょう。こいつを拡大して床にします。このままでは白色1色で味気ないのでテクスチャを 設定します。Asset Storeで見つけたフリーの木の床のテクスチャを使いました。 床ができたら次はレーンの配置です。先程作ったレーンをUnityにインポートして(0,~,0)に置きます。Y座標は上下を表すの で適宜調整しましょう。インポートする際はモデルとテクスチャを一緒にインポートしましょう。そうしないとモデルにテクス チャがつきません(多分)。 レーンが置けたら次は皿を設置します。皿を動かすプログラムの関係で円周部と直線部の境目に置きました。ここで僕は気付き ました。なんか皿がのっぺりしてる...

(42)

第4章 簡単!VR3(00)分クッキング 4.7 No Programming No Creating game ~とある似非プログラマーの死~

この問題の解決には少し時間がかかりました。Blender側でマテリアルをいじっても全然反映されないのです。考えてみるとこ

れは当たり前で、Blenderで設定しているのはあくまでBlenderでレンダリングする際の設定なので、この設定がUnityに反映さ

れる訳がないのです。という訳でUnity側でマテリアルを編集しましょう。 めでたく皿が光るようになりました。やれやれ、やっとできた...ダメでした。ちょっと前に書いたBlenderのエクスポート設 定を知らなかった自分は普通に皿を回転させて使っていたのです。ここに大きな罠が潜んでいて、Unityにおいて回転されたオブ ジェクトのx,y,z軸は回転する前と角度が変わってしまうのです。モデルをもう一度エクスポートし直せばいいのですが、そうと 知らなかった当時の僕は皿のモデルの親にEmptyを作り、Emptyを回転させることで解決しました。この問題はこのあと腕を実 装するときにも再び発生します。何はともあれひとまず問題は解決しました。 あとは適当に壁や天井を作りましょう。ここは本質ではないので手抜きです。 天井を設置すると太陽光(最初からあるDirectional Light,平行光線)が通らなくなり画面が暗くなります。そこで天井の中心付 近にPoint Lightを1個設置しました。 これで一通り配置できました。お次はプログラミングです。一番面倒、というかこれが本編かもしれません。

4.7

No Programming No Creating game ~

とある似非プログラマーの死

~

おい、このゲーム動かねぇぞ! 当たり前だろ、プログラム1個も書いてないんだぞ! はい。プログラムのないゲームが動くはず

がないのでプログラミングしていきましょう。ちなみに言語はC#です。

まずはプレイヤーの移動スクリプトです。回転寿司ですのでレーンの上をレーンに沿って動く必要があります。ここからはスク

(43)

第4章 簡単!VR3(00)分クッキング 4.7 No Programming No Creating game ~とある似非プログラマーの死~

都合上スクリプトの名前をゲーム内での名前と変えている場合があります。本来スクリプトの名前とclassの後に書くものは同じ

でなければなりません。

移動スクリプト

Move.cs usingSystem.Collections;

usingSystem.Collections.Generic; usingUnityEngine;

public classmoveOther: MonoBehaviour {

Vector3 center;

Vector3 center2; float starttime;

// Use this for initialization

voidStart() {

center= GameObject.Find("center").transform.position;

center2=center;

center2.x= -center.x;

starttime=Time.time; }

// Update is called once per frame

voidUpdate () {

if (transform.position.x> 4f) {

transform.RotateAround(center2,Vector3.up, -180f / 14f *Time.deltaTime); }else if(transform.position.x< -4f) {

transform.RotateAround(center,Vector3.up, -180f/ 14f*Time.deltaTime); }

else if(transform.position.z< 0) {

transform.position += newVector3(0.5f *Time.deltaTime, 0, 0); }

else{

transform.position += newVector3(-0.5f* Time.deltaTime, 0, 0); } } } もっと短くなるかもしれませんが、これをもとに説明していきます。 最初の3行はおまじないだと思ってて下さい。"名前空間"でググると大体わかると思います。 次の public~もUnityでスクリプトを作ると最初から書いてある部分で、変更してはなりません。 実際の処理は次の行からです。最初の数行では処理に使う変数を定義しています。変数とは色々な型の値を取り扱うもので、座 標を表す変数や整数を表す変数など様々なものがあります。ここでは移動する為に必要な座標を表す変数と時間を記録する変数を 定義しています。 ’//’はコメントアウトです。//をかいた行は無視されるのでコメントを書くことができます。

次の Void Start() で Start 関数を定義しています。Start のあとの’{’から、数行下の’}’までが Start 関数です。関数とい うのは、足し算引き算等の様々な処理をまとめて記述したもので、関数を呼び出すだけで、関数内で書かれた処理がすべてまとめ て行われます。Start 関数というのはUnityがスクリプトを実行するときに、最初に1回だけ実行される関数で、ここで値の代入 やオブジェクトの初期配置等の処理を行います。今回のスクリプトでは皿を動かすときの目印となる地点の座標と、タイマーの最 初の時間を決めています。

次の Void Update() で Update 関数を定義しています。Update 関数はUnityが毎フレーム実行する関数です。フレームとい

うのはすべての処理を始めてから終わるまでの時間で、1秒間に数十から数百回フレームが加算されます。フレーム数はプログラ ムの重さによって変わります。重いゲームでは、処理に時間がかかるので、必然的にフレーム数は落ちます。秒間フレーム数は常 に変化するので、移動用の関数などを Update 関数内で使う場合には後述する処理が必要です。 では Update() 関数内の移動処理を解説していきます。はじめのifは条件分岐です。()内の条件を満たしていると{}内の処理が 行われます。その次のelse内に書かれた処理は直前のif文の条件を満たしていない場合に実行されます。else ifは直前のif文の条 件を満たしていない、かつ()内の条件を満たしているときに実行されます。詳しく見ていきましょう。 3 つの()内で皿が今どこにいるかを判断しています。直線部なのか円周部なのか、みたいな感じです。直線部なら

trans-form.position(皿の場所)のX座標を1秒に0.5ずつ増やし、円周部では transform.RotateAround() 関数でcenterという座標 を中心に毎秒180/14°回しています。Vector3というのは3次元ベクトルを表す型で、Time.deltaTime は先程書いた Update 関数の問題点を解決する為に書いています。Time.deltaTime でそのフレームを処理するのにかかった時間(秒)を取得すること

(44)

第4章 簡単!VR3(00)分クッキング 4.7 No Programming No Creating game ~とある似非プログラマーの死~ 粋して載せていこうと思います。 このゲームでは腕が飛んでくる仕様なのでお次は腕を生成するスクリプトと生成した腕を動かすスクリプト。その前に腕の説明 をしておくと、現状Unityの標準の3DモデルであるCylinderを利用しています。文化祭本番では腕のモデルになってる...かも しれません。まずは生成から

腕を生成 eatSushi.cs public classeatsushi :MonoBehaviour {

Transform arm_point;

Transform dish;

public GameObject arm_pf; float time;

// Use this for initialization

voidStart() {

arm_point=transform.Find("arm");

dish=GameObject.Find("Main Camera").transform;

Vector3 add= arm_point.transform.position;

add.x =add.x +Random.Range(-0.5f, 0.5f) - 0.5f;

add.y += Random.Range(-0.5f, 0.5f);

arm_point.transform.position =add;

arm_point.transform.LookAt(dish);

time= 0;

Instantiate(arm_pf,arm_point.transform.position,arm_point.transform.rotation); }

// Update is called once per frame

voidUpdate() {

time+= Time.deltaTime; if (time > 5f) {

Instantiate(arm_pf,arm_point.transform.position, arm_point.transform.rotation);

time = 0; } } } ちょっと長いですね...では解説していきます。 まずこのスクリプトは捕食者に対してアタッチします。 はじめの Transform はオブジェクトの位置や角度などをまとめて記憶する型です。座標と角度を指定して使う関数の引数に指 定できます。GameObject は、1つのオブジェクトの持つ情報をすべて格納できる型です。 Start 関数内では生成すべき腕を読み込み、所定の座標に乱数を足した座標を生成し、Instantiate() で生成しています。ま た、一定時間ごとに生成する為にタイマーを設定しています。

Update 関数内で5秒ごとに生成する処理を記述しています。Start 関数内で処理を開始した時間を記録しておき、Time.time

で取得した今の時間との差が5を超えたときにタイマーを0にセットし、腕を生成すればいいわけです。

お次は腕を動かすスクリプト。命名が適当だけど許ちて。

腕を飛ばす

Shot.cs public classmomove: MonoBehaviour {

Transform dish;

// Use this for initialization

float starttime; int attack;

voidStart () {

dish=GameObject.Find("TrackingSpace/CenterEyeAnchor").transform;

//transform.Rotate(new Vector3(1, 0, 0) * 180);

transform.LookAt(dish);

starttime=Time.time;

attack= Random.Range(0, 4); }

// Update is called once per frame

voidUpdate() {

if (attack == 0) normalAttack(); else if(attack== 1) fastAttack(); elseballisticAttack();

}

voidnormalAttack() {

transform.LookAt(dish);

(45)

第4章 簡単!VR3(00)分クッキング 4.7 No Programming No Creating game ~とある似非プログラマーの死~

transform.Translate(Vector3.up *Time.deltaTime * 0.75f); }

voidfastAttack() {

transform.LookAt(dish);

transform.Rotate(newVector3(1, 0, 0) * 90);

transform.Translate(Vector3.up *Time.deltaTime * 1f); }

voidballisticAttackfh() {

transform.Translate(transform.up *Time.deltaTime* 1.5f); }

voidballisticAttacklh() {

transform.LookAt(dish);

transform.Rotate(newVector3(1, 0, 0) * 90);

transform.Translate(Vector3.up *Time.deltaTime * 2f); }

voidballisticAttack() {

if (Time.time-starttime < 3f) {

ballisticAttackfh(); } else{ ballisticAttacklh(); } } } 長い...。この関数は腕自体にアタッチします。生成するスクリプトから制御しようと思ったのですが、複数生成されたオブジェ クトのそれぞれに個別の処理をすることができなかったので、腕自体にアタッチしました。結果的にこのほうがコードが書きやす くなってると思います。

腕には3種類の飛ばし方を設定しました。Start 関数内でランダムに切り替えています。また Transform 型の dish にVRの 目を指定し、そこをめがけて腕が飛ぶようにしています。この仕様によって目の前に腕が迫ってくる演出ができます。

3種類それぞれの説明をする前に1つだけ。各関数のどれも、移動する前にRotateしていたり、移動の方向もオブジェクトに対

する前方ではなく上方になっています。ここには大きな罠(?)が潜んでいて、Unityの円柱はもともと前方が側面になっているの

です(多分)。腕の親にEmptyを設定して、このEmptyを回せば解決する気がするのですが、実験している時間がなかったのでこ

図 1.9: Hello,World!
図 1.11: 平面検出
図 1.12: 垂直面検出
図 1.14: .showBoundingBoxes
+7

参照

関連したドキュメント

高さについてお伺いしたいのですけれども、4 ページ、5 ページ、6 ページのあたりの記 述ですが、まず 4 ページ、5

○池本委員 事業計画について教えていただきたいのですが、12 ページの表 4-3 を見ます と、破砕処理施設は既存施設が 1 時間当たり 60t に対して、新施設は

○齋藤部会長

○齋藤部会長 ありがとうございました。..

○杉田委員長 ありがとうございました。.

〇齋藤会長代理 ありがとうございました。.

きも活発になってきております。そういう意味では、このカーボン・プライシングとい

QRされた .ino ファイルを Arduino に‚き1む ことで、 GUI |}した ƒ+どおりに Arduino を/‡((スタンドアローン})させるこ とができます。. 1)