TECH PLAY

株式会社LIFULL

株式会社LIFULL の技術ブログ

652

こんにちは、上津原です。今回はCouchbaseネタです。 前回のCouchbase MeetUpに引き続き、今回も弊社ネクストで開催いたしました。 今回は、このブログでも何度か紹介していたモバイル向けのNoSQLデータベース、Couchbase Liteのハンズオンを行いました。 今回は、私が前に立って来ていただいた皆さんにCouchbaseLiteを動かしてCRUDをしてCloudantとSyncするまでをやってもらいました。 その際のスライドとプロジェクトを公開します。 スライド&プロジェクト Couchbase liteハンズオン from ssd kfk 課題プロジェクト 解答プロジェクト 応用プロジェクト これらのプロジェクトの中に入っているSync(Replication)の部分ですが、私のCloudantのURLが入っています。さすがにここでIDとパスワードを公開するわけにも行かないので、利用の際はご自身でCloudantのアカウントを作成して利用いただければと思います。 あと、ハンズオンということもあって詳しい説明は省かれています。詳しいことをしえいたい方はぜひ私に声をかけてもらえればと思います。 第2回も予定しています。 昨日のハンズオンではCloudant(CouchDB)とのSyncでしたが、次回はCouchbaseとSyncGatewayを利用したSyncができればと思っています。 初のハンズオンということで人数も少なくやりましたが、次回は多分ちょっと人数も増やせるかな、と思っています。また、こういう内容でやって欲しいとか、もう一度このハンズオンをやって欲しいなどありましたらご意見お寄せ頂ければと思います。
アバター
こんにちは、上津原です。 相変わらずUnrealEngine4に翻弄されて頭から煙を出しています。 そろそろ機関車トーマスの異名を得てもいい頃かもしれません。 さて、今回は丸1日半ハマったHTTP通信がやっとこさ動いたので残しておこうと思います。 BluePrintで出来なかったからC++だ! 最初はBluePrintでさくっとできるんだろう、だって今21世紀だぜ?とか思っていたのですが甘かったです。そんなノードは用意されていませんでした。 そういうわけでコードで書くしかなくなり、1年半ぶりにC++を触ることとなりました。 とりあえず クイックスタート を一通りやって、さあ実践!そして撃沈。Objective-C→cocos2d-x→Unityとやってきましたが、同じC++でもcocos2dxとは全く違う書き方。甘く見ていました。 本題のコード コードは以下のようにすれば動きました。 Actorを元に、HelloWorldというクラスを作成しています。 ↓長くなっちゃったのでクリックして見てください。 ハマりポイント あまり理解しきれていないので、詳しいことは何ともですが、 「Http.h」をincludeするためにBuild.csに PrivateDependencyModuleNames.AddRange(new string[] { "HTTP" }); を追加する必要があった という点が一番ハマりポイントで、どうやったらパスが通るのかわからず、ず~~っと止まっていました。やっぱりハマるポイントは些細なことなのね…。 とりあえずこのように書けば、HTTP通信が通る、ということになります。 この後JSONのパースも行ったのですが、それはまた別の記事で紹介しますね。 お礼 最後にこれらは https://www.facebook.com/groups/unrealuserj/permalink/566215020144037/?stream_ref=2 https://answers.unrealengine.com/questions/31079/http%E9%80%9A%E4%BF%A1%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6.html などのコミュニティで助言をいただきました。助言いただいたみなさんありがとうございました!
アバター
古川です。 search component plugin 後編です。 search component plugin 前編 で作成した MyQueryComponent.java にスコア計算をするための処理を追加していきます。 Collector の作成 以前の記事 で紹介しましたが、luceneのcollectorクラスを使うと、ソートのためのスコア計算を柔軟に定義することができます。 そこで、フィールドx、フィールドy の値を使って、 score = a*x*x + b*x*y + c*y*y + d*x + e*y + f というスコア値を計算する MyCollectorクラスを作成します。 MyCollector.java package jp.co.homes.searchcomponent; import java.io.IOException; import java.util.Collections; import java.util.ArrayList; import java.util.Comparator; import org.apache.lucene.index.IndexReader; import org.apache.lucene.search.Collector; import org.apache.lucene.search.Scorer; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.FieldCache; import org.apache.lucene.util.PriorityQueue; import org.apache.lucene.index.AtomicReader; import org.apache.lucene.index.AtomicReaderContext; public class MyCollector extends Collector { private static final String FIELD_X = "x"; private static final String FIELD_Y = "y"; private int docBase; private int totalHits; private FieldCache.Floats x; private FieldCache.Floats y; private HitQueue pq; private ScoreDoc pqTop; private float maxScore; private float[] coefficients; public MyCollector(float[] _coefficients, int maxLen) { this.pq = new HitQueue(maxLen, true); this.pqTop = this.pq.top(); this.maxScore = Float.NEGATIVE_INFINITY; this.coefficients = _coefficients; } @Override public void setNextReader(AtomicReaderContext context) throws IOException { AtomicReader reader = context.reader(); this.docBase = context.docBase; this.x = FieldCache.DEFAULT.getFloats(reader, FIELD_X, false); this.y = FieldCache.DEFAULT.getFloats(reader, FIELD_Y, false); } @Override public void setScorer(Scorer socorer){} @Override public boolean acceptsDocsOutOfOrder(){ return true; } @Override public void collect(int doc) { totalHits++; float x = (float)this.x.get(doc); float y = (float)this.y.get(doc); float score = this.coefficients[0]*x*x; score += this.coefficients[1]*x*y; score += this.coefficients[2]*y*y; score += this.coefficients[3]*x; score += this.coefficients[4]*y; score += this.coefficients[5]; if (score < pqTop.score) { // 現在の最低点より小さいスコアの // ドキュメントは無視 return; } if (this.maxScore < score) { this.maxScore = score; } int docid = this.docBase + doc; // 今先頭にあるスコアを、新しい値に交換 this.pqTop.doc = docid; this.pqTop.score = score; // 新しい値のスコアを登録しHitQueueを再構築 // 一番小さい値を持つオブジェクトがキューの先頭になる pqTop = pq.updateTop(); } public int getTotalHits() { return this.totalHits; } public float getMaxScore() { return this.maxScore; } public ScoreDoc[] getResults() { int queueSize = this.pq.size(); ScoreDoc[] results = new ScoreDoc[queueSize]; for (int i=0; i<queueSize; i++) { results[i] = this.pq.pop(); } return results; } // プライオリティーキュー class HitQueue extends PriorityQueue<ScoreDoc> { HitQueue(int size, boolean prePopulate) { super(size, prePopulate); } @Override public ScoreDoc getSentinelObject() { return new ScoreDoc(Integer.MAX_VALUE, Float.NEGATIVE_INFINITY); } @Override public final boolean lessThan(ScoreDoc hitA, ScoreDoc hitB) { return hitA.score < hitB.score; } } } Search Component Plugin 実装 前回作成した、MyQueryComponent.java を、このMyCollectorクラスを使ってスコア計算するよう変更します。 MyQueryComponent.java package jp.co.homes.searchcomponent; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.Filter; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.ConstantScoreQuery; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrException; import org.apache.solr.common.params.*; import org.apache.solr.common.util.NamedList; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.ResultContext; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.schema.FieldType; import org.apache.solr.search.QParser; import org.apache.solr.search.QParserPlugin; import org.apache.solr.search.QueryParsing; import org.apache.solr.search.ReturnFields; import org.apache.solr.search.DocSet; import org.apache.solr.search.DocList; import org.apache.solr.search.DocSlice; import org.apache.solr.search.SolrIndexSearcher; import org.apache.solr.search.SolrReturnFields; import org.apache.solr.search.SyntaxError; import org.apache.solr.handler.component.SearchComponent; import org.apache.solr.handler.component.ResponseBuilder; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; public class MyQueryComponent extends SearchComponent { public static final String COMPONENT_NAME = "my_query" ; @Override public void prepare(ResponseBuilder rb) throws IOException { SolrQueryRequest req = rb.req; SolrParams params = req.getParams(); if (!params.getBool(COMPONENT_NAME, true )) { return ; } SolrQueryResponse rsp = rb.rsp; ReturnFields returnFields = new SolrReturnFields( req ); rsp.setReturnFields( returnFields ); int flags = 0 ; if (returnFields.wantsScore()) { flags |= SolrIndexSearcher.GET_SCORES; } rb.setFieldFlags( flags ); String defType = params.get(QueryParsing.DEFTYPE, QParserPlugin.DEFAULT_QTYPE); String queryString = rb.getQueryString(); if (queryString == null ) { queryString = params.get( CommonParams.Q ); rb.setQueryString(queryString); } try { QParser parser = QParser.getParser(rb.getQueryString(), defType, req); Query q = parser.getQuery(); if (q == null ) { q = new BooleanQuery(); } rb.setQuery( q ); rb.setSortSpec( parser.getSort( true ) ); rb.setQparser(parser); rb.setScoreDoc(parser.getPaging()); String[] fqs = req.getParams().getParams(CommonParams.FQ); if (fqs!= null && fqs.length!= 0 ) { List<Query> filters = rb.getFilters(); filters = filters == null ? new ArrayList<Query>(fqs.length) : new ArrayList<Query>(filters); for (String fq : fqs) { if (fq != null && fq.trim().length()!= 0 ) { QParser fqp = QParser.getParser(fq, null , req); filters.add(fqp.getQuery()); } } if (!filters.isEmpty()) { rb.setFilters( filters ); } } } catch (SyntaxError e) { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e); } } @Override public void process(ResponseBuilder rb) throws IOException { SolrQueryRequest req = rb.req; SolrQueryResponse rsp = rb.rsp; SolrParams params = req.getParams(); if (!params.getBool(COMPONENT_NAME, true )) { return ; } SolrIndexSearcher searcher = req.getSearcher(); SolrIndexSearcher.QueryCommand cmd = rb.getQueryCommand(); // スコア計算なしで、ヒットするドキュメントID一覧を取得 DocSet ds = null ; if (cmd.getFilterList() == null ) { ds = searcher.getDocSet(cmd.getQuery()); } else { List<Query> newList = new ArrayList<Query>(cmd.getFilterList().size() + 1 ); newList.add(cmd.getQuery()); newList.addAll(cmd.getFilterList()); ds = searcher.getDocSet(newList); } // ヒット件数を取得 DocList dl = null ; int numHits = ds.size(); int requestOffset = cmd.getOffset(); int requestLen = cmd.getLen(); if (numHits < requestOffset) { dl = new DocSlice( 0 , 0 , new int [ 0 ], null , numHits, 0.0f ); } else { int queueSize = requestOffset + requestLen; if (numHits < queueSize) { queueSize = numHits; } // result セットされている値を docset をluceneFilter // 独自スコア計算用のcollector として、 // 独自collectorを使ってスコア計算 Query luceneQuery = new ConstantScoreQuery(ds.getTopFilter()); float [] coefficients = parseMyParmas(params); // 必要なサイズだけ MyCollector collector = new MyCollector(coefficients, queueSize); searcher.search(luceneQuery, null , collector); // 結果を取得 ScoreDoc[] docs = collector.getResults(); float maxScore = collector.getMaxScore(); // レスポンス用に加工 int [] ids = new int [queueSize]; float [] scores = new float [queueSize]; for ( int i= 0 ; i<queueSize; i++) { ScoreDoc sc = docs[i]; // 小さい順に取り出してるので大きい順に並べ替え int index = queueSize - 1 - i; ids[index] = sc.doc; scores[index] = sc.score; } dl = new DocSlice( 0 , queueSize, ids, scores, numHits, maxScore); dl = dl.subset(requestOffset, queueSize - requestOffset); } ResultContext ctx = new ResultContext(); ctx.docs = dl; ctx.query = rb.getQuery(); rsp.add( "response" , ctx); rsp.getToLog().add( "hits" , numHits); } private float [] parseMyParmas(SolrParams params) { float [] coefficients = new float [ 6 ]; String buf = params.get( "myparams" ); String[] myparams = buf.split( "," ); for ( int i= 0 ; i<myparams.length; i++) { coefficients[i] = Float.parseFloat(myparams[i]); } return coefficients; } @Override public String getDescription() { return "my query component" ; } @Override public String getSource() { return "$URL: dummy $" ; } } コンパイル・設定 前回同様なので省略します。 動作確認 solr を起動して、前回作成したmyfunc を使ったクエリと、今回作成したmy_select の検索結果が同じであることを確認します。 myfunc(function query plugin) http://localhost:8983/solr/collection1/select?echoParams=none&q=*:*&fq=x:[0 TO 500]&fl=id,x,y,myfunc(x,y,1,2,3,4,5,6)&sort=myfunc(x,y,1,2,3,4,5,6) desc&rows=3 <result name = "response" numFound = "500773" start = "0" > <doc> <str name = "id" > id789132 </str> <float name = "x" > 500.0 </float> <float name = "y" > 999.0 </float> <float name = "myfunc(x,y,1,2,3,4,5,6)" > 4250004.0 </float> </doc> <doc> <str name = "id" > id96466 </str> <float name = "x" > 499.0 </float> <float name = "y" > 999.0 </float> <float name = "myfunc(x,y,1,2,3,4,5,6)" > 4247003.0 </float> </doc> <doc> <str name = "id" > id882478 </str> <float name = "x" > 499.0 </float> <float name = "y" > 999.0 </float> <float name = "myfunc(x,y,1,2,3,4,5,6)" > 4247003.0 </float> </doc> </result> my_select(search component plugin) http://localhost:8983/solr/collection1/my_select?echoParams=none&q=*:*&fq=x:[0 TO 500]&fl=id,x,y,score&myparams=1,2,3,4,5,6&rows=3 <result name = "response" numFound = "500773" start = "0" maxScore = "4250004.0" > <doc> <str name = "id" > id789132 </str> <float name = "x" > 500.0 </float> <float name = "y" > 999.0 </float> <float name = "score" > 4250004.0 </float> </doc> <doc> <str name = "id" > id96466 </str> <float name = "x" > 499.0 </float> <float name = "y" > 999.0 </float> <float name = "score" > 4247003.0 </float> </doc> <doc> <str name = "id" > id882478 </str> <float name = "x" > 499.0 </float> <float name = "y" > 999.0 </float> <float name = "score" > 4247003.0 </float> </doc> </result> scoreが同じ場合のソート順が微妙に違うため、ときどき順序が前後しますが、score値は同じで、どちらも大きい順に並んでいることが分かります。 速度評価 スコア計算に関連する引数を変更しながら、5回クエリを実行してQTimeの平均値を計算してみました。 myfunc(function query plugin) 15.4ms my_select(search component plugin) 11.6ms search component plugin でも高速なスコア計算が実現できることが確認できました。 まとめ search component plugin を使った独自ソートの実現方法を紹介しました。実際に使う場合には、きちんとしたエラー処理や SolrIndexSearcher.java の中で行われているようなクエリキャッシュの仕組みを追加していく必要がありますが、大体こんな感じでいけそうです。 function query plugin で実現できないソート用スコア計算が必要になった時は、search component plugin を使った方法を検討してみても良いかもしれません。
アバター
先日おもしろい記事に出会いました。" The design decisions behind the tech industry’s beloved anonymous Secret app " という題名なのですが、この"Secret"というのが形容詞だと思っていたので文意をとるのに時間がかかりました。これは"Secret"というアプリの名称なのですね。 記事を読むと、デザインが特徴的らしいのですがいまいちイメージが湧かない。 日本語のApp Storeで検索をしてもでてこないので Facebook Paper のときのように米国サイトからダウンロードしてみると... 追記:2014/5/27 日本のApp Storeからもダウンロードできるようになりました。 いや、これ面白いわ。(読者をおいてけぼりにした自己満足) 仕組みが極めてシンプルで アカウントは登録するが、Secretの投稿、コメントはすべて匿名。(投稿者の住んでいる都市名だけは表示される) 電話番号を登録すれば、それを手がかりに「友達」と繋がることはできる。ただ「友達」が誰かはわからない。 Secretは画面に一列に表示。写真+コメントで縦スクロールだけで閲覧できる。 それぞれのSecretには、ハートマークをクリックするか、コメントをすることができる。ただしコメントができるのは「友達」だけ Secretにつけられたコメントには、ランダムにアイコンが割り当てられる(そのSecretの中では共通) Secretは最初「友達」だけに公開され、「友達」がだれかハートマークをつければ、より多くの人に公開される。 日本語の記事 はこちらにあります。元記事を読むと、「Tech Industryでの買収とかレイオフの噂で賑わっている」らしいのですが *1 使ってみた限り実にいろいろなSecretが投稿されています。 こういう感動的なのや 「母親は”いついい男と結婚するの?”といつも聞く。私はレズビアンだと何度も言っているのに」 という考えさせられるものもあり。かと思えば 「今は昼。シャツの中に靴下が片方はいっていることに今気がついた」 という脱力系のSecretもあります。もっともこうした「匿名の噂アプリ」は他者に対する無責任な中傷を広めてしまう危険性を持っていることにも注意する必要がある。いや、すでにTwitterがそうした役割を担っている、という議論もあるでしょうし、そもそもインターネット自体が,,という意見もあるでしょう。 というような「アプリの位置づけ」に対する議論とは別に、このアプリのUIは良く考えられており,,という詳細な考察は Design Details: Secret for iOS を見ていただくとして、このアプリのリードデザイナーChrys Bader氏が挙げた「プロダクトデザインの原則」と「ソーシャルのホームランを打つための3つのルール」を紹介します。 Secret以前にもこうした「匿名で書き込みができる」掲示板もアプリもあったのですが、なぜSecretはこれほど話題になっているのか?そうした疑問に対する答えの一端がそれらから伺えると思うからです。 まずプロダクトデザインの原則ですが Start from emotion — This isn’t a new concept , says Bader, but Apple’s design team embodies this and I agree with them. Tell a story — The user should be able to fit the whole product in his or her mind at once. You should be able to think about what Secret is and not spend more than one brain cycle piecing it together. Keep a balance — Positive experiences need to outweigh the negative ones (which can be prevalent on an anonymous app). The experience needs to be a net positive. Live by constraints — It’s great to use “blue-sky thinking” to brainstorm and come up with ideas, but when it comes time to implementing design, set constraints. via: The design decisions behind the tech industry’s beloved anonymous Secret app — Tech News and Analysis 感情から始める: これは新しい話ではないがAppleのデザインチームは実践しておりそれに同意 ストーリーを語る: ユーザはそのアプリが何をするものか瞬時に理解できなくてはならない バランスを保つ: ポジティブな要素がネガティブな要素を上回っていなければならない。 制約を意識する: ブレストの時は制約を外して考えるのがよいが、実装段階では制約を意識しなくては これらのうち「ストーリーを語る」についてこのアプリのデザインは特に印象的です。上のようにコンセプトを文字で説明しようと思えばかなり苦労するのですが、ダウンロードして触り、投稿されているSecretをみればたちまち「このアプリが何をするものなのか」が理解できる。 さて、次は「ソーシャルのホームランを打つための3つのルール」 Allow for a novel form of self expression Make it stupidly simple to express yourself Make it rewarding. 引用元: The design decisions behind the tech industry’s beloved anonymous Secret app — Tech News and Analysis 自分を表現する新しい方法を提供する 馬鹿げているくらい簡単に自分を表現できるようにする 投稿した甲斐があるようにする 匿名でなければ告白できないような秘密を、文章と背景画像だけで表現させる。これは「馬鹿げているくらい簡単に自分を表現できる」「自分を表現する新しい方法を提供する」こと。そしてそれに対してこれまた匿名の人達からフィードバックがもらえる。これによって投稿者は「投稿した甲斐があった」と感じることができる。 原則は言葉でしかないが、実際にアプリに具現化させているところには感服せざるを得ません。 Facebook paperとかこのアプリを使っていると、 「コンテンツが主役」 というデザイン原則が徹底していることに気がつきます。それとともにやはりiPhoneアプリはスワイプ(つまり画面をすい、っとすること)操作を基本にしたほうが自然である、とも。 またそれぞれのアプリの詳細に凝らされた工夫、こだわりには感嘆させられます。それがただの「自己満足」ではなく使う側の楽しさ、満足感につながっている。 iPhoneが発表されてから7年。モバイルアプリのUIはまだまだ進化し続けています。いや、楽しい。 *1 : NikeのFuel Band開発チームレイオフの噂も Secret から広まったようです
アバター
こんにちは、UE4に悪戦苦闘中の上津原です。 Rayの作成ができたので記事にしておきます。 Rayってなんじゃ? Rayとは 指定した一定距離内にオブジェクトがあるかどうか確かめるもの です。 オブジェクトががあればそのオブジェクトを取得できます。 これを応用すれば、遠くにあるものを動かしたりできるのです!ヒューカックイイ! 実際のBluePrint 実際作ったものはこちらです。 大きい画像はこちら ※今回のBPはFPSの状態を基本としています。 簡単な説明 基本として「Single Line Trace for Object」を利用します。これらに「どこから(Start)」「どこまで(End)」オブジェクトがあるか確認をするかを指定したり「何を認識するか」を指定したりします。 まずは「どこから」「どこまで」を作ります。First Person Cameraの位置を取得し作成します。 開始地点はカメラのWorld Locationで問題ありません。 最終地点は、Forward Vectorを取得しそれを乗算します。今回は500となっていますが、この乗算値がどれくらいの距離でRayを打つかが決まるので、任意で変えれば遠くでも近くにでも変更ができます。そしてその値をWorld Locationに足す。そうしないとカメラの位置からRayが飛ばされなくなってしまいます。 そして、Object Typesを指定しなくてはいけません。取得したい「Object Type」のArrayをセットします。今回は「WorldDynamic」を指定していますが、壁とか動かないモノを取得したい場合は「WorldStatic」にすれば検出するようです。 そして最後に、DrawDebugTypeを「ForDuration」に指定します。これは別にやらなくてもいいんですが、発射したRayが見えるようになるので開発中は出しておくと分かりやすいです。 ReturnValueには、成功、失敗のBoolが返ってきます。OutHitには、ヒット時の各種情報がまとまって返ってきます。これはそのままでは使えないのでBrakeして情報を分割しています。 これでRayは完成 これでPlayしてみると、以下のような変な赤い串のようなものが表示されるはずです。 ↓これは一度出してからわかりやすいように横から見ています。 この赤いラインがRayです。やったね! ここから持ち上げもついでに入れてみる 持ち上げるためのBlue Printは以下のようにしてみました。 大きい画像はこちら カメラにアタッチしたりデタッチしたりするだけです。 そうすると、こんな風になります。 まだ持ち上げているハコにPhysicsはつけてないのでちょっと不自然ですがこんな感じで動かせるようになりました。ちなみに、Physicsを持っているものはAttachできないので場だもう少し付け足しが必要になります。 Blue Print難しいけど面白い Blue Printって、理解しやすいやらそうじゃないやらわからない感じもしますけど、慣れてくるとなかなか楽しいもんですね~。 何かまた報告できるものができたら記事にします。それでは~。
アバター
こんにちは、上津原です。 最近Unityに加え、Unreal Engine4( https://www.unrealengine.com/ )も触り始めました。 Unityと使い勝手が違うので、チュートリアルを見て勉強しながら操作しているのですが、忘れないうちに記事にしてしまおうと思います。 Unreal Engineとは、 Unreal Engine(アンリアルエンジン)は、Epic Gamesより開発されたゲームエンジンである。1998年にファーストパーソン・シューティングゲーム (FPS) である『Unreal』で初めて実装された。 Wikipwdia はい、3Dゲームエンジンです。 僕自身、Epic Gamesの「 Gears of war 」がしたくてXbox360を買ったクチなので、使う前から存在自体は知っていました。 そのころから「スゲーな~、かっこいいな~」と思っていて、前職でスマホゲームとかに関わっていたときに「アンリアルとか使えたらかっこいいですよね」と気軽に言ったら「あんな高いもん使えるか!」と一蹴されたのはいい思い出です。 そんな「高くて使えるか!」と一蹴されていたUnreal Engineですが、なんと今なら 「毎日コーヒー1杯我慢すれば買える価格」 約月額2000円で使えてしまうのです(!) Unreal Engineのパワー 「でも2000円のゲームエンジンなんて、機能制限してるんじゃない?」なんて思われるかもしれませんがそんなことはなく、むしろ2000円でソースコードまで読めちゃう、ほんとにこれ2000円でいいの?と思っちゃうほどの充実っぷりです。 どんなクオリティのものができるのかというと、こんなかんじ Unreal Engine 4 Features Trailer -- GDC 2014 - YouTube ヒュー!ワンダフル! この性能が月額2000円だなんて本当にびっくりです! チュートリアルが動画なのが点数高い チュートリアルが動画で公開されています。 現時点で(2014/4/21)65本ものビデオチュートリアルが! https://www.youtube.com/playlist?list=PLZlv_N0_O1gaCL2XjKluO7N2Pmmw9pvhE 英語がわからない人でも、動画を見ながら同じようにマネをすれば、使い方が把握できます。 字幕も出しておけば「今なんて言った?」というのも読めばなんとなくわかるので非常に役立ちます。 そしてチュートリアルをやってみた結果がこちら これはチュートリアルの一番上にある「Creating a Level」を一通りやっていじくったものです。 これが触り始めて1日目でできちゃうのがすごい。ワンダフル! ほかにも面白い機能が いじくってて感動したのは、Directional Light(太陽)の角度を変えると、勝手に夕日などの表現がされることです。 左上の電球がDirectional Lightです。ライトの矢印は右下を指しています。 この状態でプレイをすると… このように空は青く、上の方に太陽が描画されます。 では今度は太陽の矢印を真横に向けて… プレイをしてみると まあきれいな夕焼け! 適当ににいじくってたらいきなりこれになったので、最初すごく感動しました。 UnityでRoom VRを作ったときはSkyBoxを変更していたのですが、その必要もなくなるのか?とちょっと期待できちゃいます。 豊富な機能 どんな機能があるのかなー?などは、以下のスライドが非常に役立つので見てみてください。 はじめてのUnreal Engine 4 from Shun Sasaki 私自身理解の及ばない機能が目いっぱいありますw どこまで使うかはさておき、全力で土下座するレベルなのはなんとなく伝わります。 Unityとのちがい そして気になるポイントは、Unityとどう違う?どっちが使いやすい?だと思います。 私の個人的な感触では以下のような感じです。 Unreal Engine 4が優れている点 光の表現がUnityより手軽に美しい表現ができる。 Blue Printの採用により、非プログラマでもプログラムが可能に。 安い!!! Unityが優れている点 コードでプログラムが書けるので、学習コストが低い。 コミュニティ活動が活発で資料が多い 無料版がある 簡単にまとめて言うと、 UE4は高性能で安いけど無料版はない。そして資料(特に日本語の)が少ないからちょっと苦労するかも。 Unityは無料版があって資料も多くてとっつきやすい。でもプロ版は16万円。 といった感じ。 使用感は慣れればいいと思うので、その辺は割愛します。 Unityはこれから5がリリース予定なのでそこでまた新たな局面も見えそうです。 そんな感じで今回はここまでです。 この値段で最高峰の3Dゲームエンジンが触れるのはとってもお買い得だと思うので、ぜひ気になってるなって人は使ってみるとよいと思います!
アバター
こんにちは 上津原です。 今回から実装編に入っていきます。 基本的に実装編では、各機能がどんなふうにできていったかを紹介していきたいと思います。 今回は 「朝~夜の日照のシミュレーション」 について話をしていきます。 OculusRiftでSkyboxがぶれて見える件 普通にUnityでやるようにSkyboxを設定すると、Riftを通してみたとき、タブって見えます。 これは、OculusRiftのSDKでは、立体になるように左右の目でちょっとずれた映像を出しているのですが、Skyboxに関しては左右が全く同じ映像が出ているため起きるようです。 ↑左右の立方体の市場微妙に違うが、空の表示はだいたい同じになっている。これだと立体視されない。 それの対応のため Stereoarts Homepage さんの、「SkyboxMesh.cs」を利用させていただきました。 これを利用することで、Skyboxがブレることなく表示することができました。 ↑左右の雲の描画にずれが生まれ、Riftでみてもぶれなくなっている。 使い方も非常に簡単で、カラのGameObjectを作成して、Skyboxmesh.csをそいつにドロップ。そうしたらInspector内にあるSkyboxの部分に表示したいSkyboxを設定すれば表示が可能です。この際、あらかじめ設定していたSkyboxの設定は抜いておきましょう。 ちなみに、ここでSkyboxを指定した時点ではSceneでSkyboxは表示されません。実行時に描画がされるので何も見た目が変わらなくても大丈夫です。 時間帯の変更について 今回のアプリケーションでは、朝、昼、夕方、夜の4つの時間帯の体験が可能です。これらはDirectional Lightの向き、強さ、色変更と、Skyboxの変更を行っています。 つくりは非常に簡単で、Directional Light用のScriptを作り、そこから向き、強さ、色の変更と、上で紹介したSkymapmeshへとアクセスし光の具合を変更しています。 こんな感じであとからでも把握しやすいように各マテリアルはインスペクタに表示されるよう作っています。 ちなみに角度はそれぞれ固定となっています。 最終的には、太陽の動きを日付や時間帯を指定することでリアルな太陽光のシミュレーションができるようにしたいと思っています。 影の描画について 今回は、PCスペックの都合上影の描画はDirectional Lightのみとしており、室内の光に関しては影の生成が行われない作りとなっていました。 その結果、いまいちリアルな描写ができていないという現状があるため、ここもPCスペックの見直しを行い、室内光源からの影の描写なども対応予定です。 最も苦労した点 Skyboxmeshへのアクセスでした。SkyboxmeshはC#で書かれており、こちらのスクリプトはJavaScriptだったため、どのようにアクセスをすればいいか?という点が一番の問題でした。 そこで、Skyboxmeshに以下のコードを追加させていただきました。こうすることで外からSkyboxマテリアルを渡すことでSkyboxを変更できるように対応しました。 gist9952286 気を付けるべき点は、「skymesh」というタグがついたオブジェクトをいったんすべて削除しなくてはならない点です。 削除せずに変更をしようとすると見た目は変わってくれませんので、そこに気をつけなくてはいけないです。 これがでができてしまえばあとは簡単で、Javascriptから、 var skyboxMeshObj : GameObject = GameObject.FindGameObjectWithTag("SkyBox"); var skyBoxMesh : SkyboxMesh; skyBoxMesh = skyboxMeshObj.GetComponent("SkyboxMesh"); skyBoxMesh.Change(noonSkybox); などしてやれば変更ができます。(Skyboxmeshの入っているオブジェクトには「Skybox」タグがついています) 日あたりは部屋にとって大事なステータス みなさんも賃貸などを選ぶときに、日当たりは重視されるかと思います。 ここがよりリアルになれば、VRでの物件選びへの道が大きく前進すると思います。今後日当たりはより現実に近いものにしていきたいですね。
アバター
Apple原理主義者の大坪です。 さて、Facebookが公開した「インタラクションデザインのPhotoshop」Origamiについて 使い方を書いてみた ものの 「そもそも誰が読んでくれるのか」 と疑問に思っていたのも事実。少なくとも一人は読んで、動かしてしかも初期バージョンの間違いまで指摘してくれたことに意を強くし、次には 「もう少し実際的な例を説明しなくていいのか」 という強迫観念にとらわれ始めたわけです。 というわけでOrigamiのサンプル第2弾は「コンテンツをスクロールして、一番上までいくとツールバーがにゅいっと隠れたり出たりする」です。具体的にはこんなのです。 ちょっと縦横比が狂ってくる気がしますが、気のせいです。細かい事を気にしすぎると折角の春の訪れに気がつかないかもしれませんよ(言い訳)こういう風に動くものを作れると 「ほら、こうやってスクロールしていくとこのメニューがにゅいっと隠れて、下にスクロールするとまたでてきて。いや、これは”にゅいっ”じゃなくて”すっ”でちょっとイメージが違う」 とか不毛な言葉によるやりとりをしなくてもいいと思うのです。 さて、あとで学位を返上しろとか言われると困るので最初に白状しますが、今回説明する内容は Prototyping with Facebook Origami にほんの少し修正を加えたものです。自分なりにアレンジしようとかあれこれ考えたのですけど、元の動画が非常に良くできており、変な事やるとかえって読んでくれた方の理解を損なうと判断しました。 というわけでお前のわけのわからん文章など読んでられるか、というかたは 元コンテンツ を参照していただくことにして、やおら説明を始めるのです。 とにかく画像を表示 OrigamiをインストールしたQuartz Composerを立ち上げましょう。それは何のことだ?というかたは 前回の記事 を参照してください。 でもって以下の図のところまで同様に作ってください。空のiPhoneイメージが表示されてるけど、これからRender in Imageパッチの中身を作らなくちゃね、というところまでです。 ここまでできたら、Render in Imageパッチをダブルクリックし、中身を作って行きましょう。 まず表示する画面のパーツを作ります。最初にClearパッチを追加します(これを忘れると妙なことが起こります) 今回作る物は三つの部品から構成されています。 Header(一番上の部分です) Toolbar(Headerの下にあり、コンテンツをスクロールすると出たりかくれたりする部分) Feed(Facebookのタイムラインみたいで、上下にスクロールする部分) Github においてあるファイルの中のassetsフォルダを観ると、その中に三つの画像があります。Render in ImageをダブルクリックしたEditor画面にHeader.pngファイルをドラッグ&ドロップしましょう。するといきなりImageパッチとそれに接続されたLayerパッチが現れ、ViewerのほうにはHeaderの画像が真ん中に表示されます。 ををこれは簡単だ、というわけで調子にのって残る二つの画像をドラッグ&ドロップします。また忘れないうちにそれぞれのLayerパッチの名前を部品の名前に変更しておきましょう。パッチの一番上にある"Layer"という文字をダブルクリックすると名前が変更できます。 こんな風になるはずです(ドラッグ&ドロップの順番によっては、表示が少し異なるかもしれませんが、気にすることはありません) なんだか景気よく画像が表示されましたが問題が二つ。第一に位置がみんな真ん中になっている。第二に最初に追加したはずのHeaderはどこに行ってしまったのか。 まず2番目の問題から直しましょう。これは一回目にも書いた「表示順」の問題です。(画面クリアを除いて)一番下(というか裏側)に表示されてほしいものから順番に番号を設定して行く必要がある。この場合一番下にあるべきはFeedです。というわけで Clear:1 Feed:2 Toolbar:3(Toolbarは後でHeaderの下に隠れてほしいのでこの順番です) Header:4 という風に、パッチ左上の数字を設定します。するとHeaderの画像は確かにFeedの上に表示されるようになったけど、まだ真ん中にごちゃっとしている。 というわけで、各パーツの表示位置を修正していきます。さてどこをどう設定すればよいでしょう? それを調べるために、ツールバーの"Parameters"というボタンを押します。すると画面右側にこんな画面が現れる。 今編集しているのは、Render in Imageパッチの中身です。でもってこの表示を信じれば、そのサイズは幅640,縦1136らしい。これはiPhone5の画面サイズのそのものです。 Quartz composerでは画面の中央が座標の原点(0,0)で、画像の位置は中心座標で指定するようです。Header画像の大きさは640 x 128ですから、これが一番上に表示されるためには,,, (1136 ÷ 2)- (128 ÷ 2) = 504 というわけで、Headerを表示しているLayerパッチの Y Positionに504を設定しましょう。するとめでたくHeadrの画像が一番上に行く筈です。ToolBarはHeaderの下にあり、かつ高さが88だから 504 - (128÷2) - (88÷2) = 396 というわけでToolbarのY Positionに396を設定します。これでめでたくそれっぽい表示になりました。 画像を眺めながらしばし満足感に浸ります。ああ、長い道のりだった。しかし遠からず 「まだ全然動かないじゃないか」 という厳然たる事実に直面することになる。ではここから動きをつけましょう。 とにかくスクロール まずFeed画面をスクロールさせましょう。そのためにPatch LibraryからScrollというパッチを探してEditorに追加します。 それでもって以下のようにつなぎます。 Scrollパッチの右上にある○からFeedパッチの左上にある○に。 FeedにつながっているImageパッチのImageからScrollのImageに ScrollのY PositionからFeedのY Positionに。 つなぎ終わるとこん感じになります。 この段階でViewerを表示させ、Feedのところをマウスでドラッグしてみましょう。ちゃんとスクロールするはずです。 今回はY方向(つまり縦方向)だけのスクロールなので線を一カ所だけつなぎましたが、例えば縦横方向に自由にスクロールさせたければ、ScrollのX PositionからFeedのY Positionにつなげばよいです。:すいません。この部分記述が間違ってました。お詫びして削除します。 なんでImageの出口を二手に分かれてつなぐ必要があるのかなあ、と30秒ほど悩みましたが、多分Scrollの内部でイメージの大きさを調べてよしなにスクロールさせているのでしょう。 さて、ここで私が2回やった失敗です。Quartz Composer字が小さいですよね。少なくとも私のような年の人間にとっては細かい。目が見えなくなってくると「だいたいこんなんでいいんでないかい」と適当にやる作業が増えます。というわけでScrollのY Positionから出た線をFeedの "Y Position" ではなく "Y Rotation" につないでしまったのでした。すると何が起こるか?上下にスクロールさせようとドラッグするとFeedの絵がぐりんと回転します。慌てて正しい入り口につなぎ直すがまだ表示がおかしいまま。こういう時は、Feedパッチを選んだ状態でPatch Inspectorを開き、Y Rotationの値を0に設定しましょう。 Toolbarをにゅいっと隠す さて、めでたくFeedがスクロールするようになったので次には「スクロールしていくとツールバーがにゅいっと隠れる」動きを実現しまししょう。 まず一回目でも使ったTransitionパッチを追加します。このTransitionパッチというのは、Progressという値を0から1に設定するとそれに応じて望んだ範囲の値を出力してくれるものでした。今はToolBarの位置を合わせるためにY Positionに396を設定していました。この値をTransitionのStart Valueに設定します。ToolBarイメージの高さは88ですから、End Valueを484にしましょう。そしてTransitionのValue出口をtoolBarのY Positionにつなぎます。 この状態で一つ実験してみましょう。Transitionを選んだ状態でPatch Inspectorを開きProgressという値の横にある丸いものをぐりぐり回してみましょう。Progressの値が変化するとともに、Viewerの中のToolBarの位置が変わることがわかると思います。 つまりToolBarを動かす為には、「何かのきっかけで0と1の値を切り替える」ことができればよいことになります。さてどうしよう。 ここでConditionalパッチをEditorに追加しましょう。Patch Inspectorを使って中身をみると "First ValueがSecond Valueに対して<いろいろな条件>ならば" と書いてあるような気がします。条件が変わればConditionalパッチの出力が変わるのでしょう。よし、っというわけででConditionalのResultをTransitionのProgressにつなぎます。ちょと待て、ResultをProgressにつないでいいのか?なんだか言葉違うけど。 パッチの説明を読むとConditionalパッチの出力は真(True)か偽(False)からしいのですが細かい事は気にしない。計算機が本当にやっているのは2進数の計算でその世界では1が真で0が偽。だったら細かい事には目をつぶってつなげるはず。ではどういう条件が満足された時にこの値を切り替えるか。 やらんとしていることは「Feedがスクロールして行って上の方にいったらToolbarを隠す」ことです。この「スクロールして上の方に行ったら」とは具体的にどういうことか。実際にやってみましょう。Feedパッチを選んだ状態でPatch Inspectorを開く。Viewerの中でFeedを上下にやってPatch InspectorのY Position欄の値が変わるのを観ましょう。 Feedが上にいくとY Positionの値が増えることがわかると思います。というわけでなんとなく「ここらへんでToolbarが引っ込んでほしい」と思う値を覚えておきます。 さて、Conditionalパッチに戻ります。ScrollからY PositionをひっぱっていってConditional パッチのFirst Valueにつなぎます。そしてSecond Valueには先ほど覚えた「ここらへんでひっこんでの値」(ここでは-520にしています)を。そしてConditionのところには"Is Greater Than"を選びます。ScrollのY Positionの値が-520より大きくなったら出力の値が1になるわけです。 でもってConditionalパッチの出口をTransitionパッチのProgressにつないでやる。Viewerでタイムラインをスクロールさせてみましょう。タイムラインが上にいくとToolBarが隠れ、下に戻すと再度表示されることがわかると思います。 わーいできた、と喜ぶのは少し早い。確かにToolBarは出たり隠れたりするのですが急に出たり消えたりする。やりたかったのは「にゅいっ」と出たり入ったりすることでした。ではどうしよう。ここで「にゅいっ」を実現してくれるClassic Animationというパッチを追加しましょう。 これをさっきエンジニア的良心の呵責を覚えながら直接つないだConditionalとTransitionの間にいれてやります。具体的には ConditionalパッチのResultをClassic AnimationパッチのNumberにつなぐ Classic AnimationパッチのProgressをTransitionパッチのProgressにつなぐ これが今回の最終形です。 Viewerを使ってスクロールしてみましょう。さっきは「がっこん」と出たり消えたりしていたToolbarが「にゅいっ」と出たり入ったりするはずです。 さきほどいれたClassic Animationというパッチを選んでPathc Inspectorを開きます。DurationとCurveという値が設定できることに気がつきます。Durationというのはアニメーションが行われる時間。たとえばこれを2にするとものすごくゆっくりToolbarがでたりはいったりします。Curveはアニメーションの「にゅいっ」具合を変えるものです。正直それぞれの言葉にどういう意味があるかわかっていません。私はこのパラメータをあまりいじった事がありませんが、きっと良いデザイナーはこのCurveの種類にこだわるのだと想像します。 というわけで、いつも作業しているマシンがお亡くなりになってしまった失望のあまり書いてしまったOrigamiの使い方講座その2でございましたがいかがでしょう。今回くらい動いてくれるとなんだか 「これでインタラクションデザインができる!」 という気分になりますよね。ね。ね。(ウザい) エンジニアとしては時々「こんなに苦労してOrigami覚えるなら、Objective-Cで書いたほうが早い!」と叫びたい衝動にも駆られるわけですが、そこはぐっと我慢。きっとこのOrigamiはインタラクションデザイナーとエンジニアの共通言語に成りうると信じたい訳です。 「えーっ、これ想像していた動きと違う!」 というデザイナーの叫びと 「なら最初からそう言ってよ!」 というエンジニアの怨嗟の声が少しでも減らせるのではないか。とはいっても叫び声自体の回数は大して減らず内容が 「えーっ、この動きOrigamiのと違う!」by デザイナー と 「なら自分でプログラムしてみろよ!」by プログラマー に代わるだけかもしれませんが。
アバター
Apple原理主義者の大坪です。 「Google先生に"Facebook Paper Origami"+日本語記事でお伺いをたてた結果が何故これほど少ないのか」 と疑問に思う今日この頃です。ひょっとして私一人だけが錯乱しているのでしょうか。 という根本的な疑問からは目をそらして、昨日見つけた文章について紹介します。 Paperでもっともわかりやすい「新インタラクション」はiPhoneを傾けることで、画面より大きな画像を閲覧する“tilt-to-explore”でしょう。(下の動画の52秒あたりから) それが初めて披露された時の様子は以下のようだったとのこと。 “Everyone’s jaws just dropped,” remembers Michael Reckhow, who was sitting beside Matas that afternoon. “Everyone started exchanging these glances that were like: ‘What did he just do?’” 引用元: Facebook Paper Has Forever Changed the Way We Build Mobile Apps | Wired Enterprise | Wired.com いいかげんな訳:Matasの隣に座っていたMichael Rechhowはこう語る。「みんな顎が外れるほど驚いた。お互い顔を見合わせて”今彼はなにをやったんだ?”と言わんばかりだった」 しかしここで驚くべきはその結果だけではない。"tilt-to-explore"を開発したMike Matasはプログラマーではなく、Objective-Cは知らない。彼はOrigamiを使ってそのプロトタイプを作り上げたのです。 プロトタイプ作成にOrigamiを使うことのメリットは、このように表現されています。 “With your typical programming language, you have to type in a bunch of code and hit ‘compile,’ and a minute later, you see what you built,” he says. “It’s almost like you’re trying to learn how to play the piano and you have a piano where you hit a couple of keys and then hit a compile button and a minute later you hear what you played.” Origami changes this. 引用元:前掲記事と同じ 普通のプログラミング言語では、山ほどコードを書いて「コンパイルボタン」を押す。すると一分後にその結果をみることができる。これはまるでピアノの練習をしているのに、鍵盤を叩いてから一分後に音が聞こえるようなもんだ。Origamiはこれを変えた。 OrigamiはQuartz Composerで作られており、パラメータやパッチ(「何か」をしてくれる単位です)の結合やパラメータを変更すれば、即座にその結果が動作に反映される。このツールを使っての"tilt-to-explore"の開発はこんな風に行われたとのこと。 Matas had the idea one evening at home — after attempts to add an automatic “Ken Burns effect” to Paper failed to, well, pan out — and the next morning, he spent a few hours mocking it up with Origami. 引用元:前掲記事と同じ 自動的に Ken Burns Effect がつくような機能を実現しようとして失敗した後、 Matasは家で良いアイディアを思いついた。そして次の朝数時間作業してOrigamiのtilt-to-exploreのモックを作り上げた。 さて、ここからは 「そうかー。デザイナーの皆さん、Origami使ってがんばってねー」 と他人ごとのように考えていたエンジニアの皆さんの顔が青くなるような話がでてきます。 Paperの実装方法を考えていて気がついたのですが、あのアプリは見かけよりずっと複雑にできている。少なくともApple標準部品を持ってきて「はいできました」という類のものではない。 *1 Engineer達は、そのアプリを作り上げるためにOrigamiとは別のTool-Tweakという名前-を作り上げたそうです。(このツールはまだ公開されていません) There are times when the app runs literally dozens of physics simulations that all work in concert — Grant worked on an animation that involved 42 virtual springs — and Tweaks provides a way of instantly changing the behavior of each and every one of these simulations. 引用元:前掲記事と同じ アプリ内部ではお互いに協調しあう物理シミュレーションが何ダースも同時に動いていることがある。例えばGrantは仮想的なバネが42個必要なアニメーションを作ったことがある。Tweakを使えば、そうした計算のパラメータを即座に変更することができる。 初期バージョンのPhoto Viewerは、車の中で使うと振動やら加速度で変な動きをしたとのこと。 “When we first built it, it felt pretty good in your hand,” he says. “But we noticed that, the more places we took it, it began to fall apart.” With Tweaks, as he rode home on the Facebook shuttle, he could instantly adjust and readjust the filters used to eliminate any irrelevant movement, identifying what worked and what didn’t without having to rebuild and recompile. 引用元:前掲記事と同じ 最初に作った時、普通に使うぶんには上手く動いた。しかしいろんな場所で使ってみると問題が見つかった。帰宅するために乗っていたFacebookシャトルバスの中で、Tweakを使いコンパイルなしでフィルターの調整を行い、何がうまくいって何がうまくいかないかを見つけることができた。 シャトルバスの中でそんなデバッグしたら車酔いするじゃないか、とか言っている場合ではありません。まだ観ぬtweakを使えば、どこでもパラメータチューニングができてしまうのです。つまり彼らは 「こんな複雑なモデルのデバッグは大変だから、モデルを簡単にしよう」 ではなく 「こんな複雑なモデルのデバッグは大変だから、デバッグを簡単にするツールをつくろう」 と考え実際に作ったわけです。 Ok, エンジニア同士諸君。話はまだ終わっていない。 このように、Paperの動作は、かなり複雑な計算の上に成り立っているので通常のアプリの作り方ではうまく動かないとのこと。UIに関連するタスクは全て同一CPUコアで動かし、他のタスクは別のコアに任せる必要があります。そうした「タスク毎のCPUコアへの割当」を柔軟に行うため、彼ら自身でアプリエンジンを開発したそうです。(名前はないそうですが) この(名無しのアプリエンジン)を使えば、どのタスクをどのコアに割り当てるかが細かく制御できる。これによって、写真をスクロールしていくときに、前の写真がまだ開き終わってないうちに処理を中断し、別のCPUコアでデコードした画像をスムーズに表示できるとのこと。つまり彼らは 「このインタラクションは今のCPUじゃスムーズに動かないから、止めよう」 ではなく 「このインタラクションは今のCPUじゃスムーズに動かないから、動かす為のエンジンを作ろう」 と考え、実行した訳です。 彼らが作り上げたツールはOrigamiだけではなかったのですね... などと書いているとだんだん気が滅入ってきます。あれですよ。 マリアナ沖海戦 の前に日本の造船所で一生懸命空母を作っちゃいるけれど、太平洋の向こう側では一週間に1隻空母が竣工していたような状況を思い浮かべたりするわけですよ。 いや、大丈夫。別に我々はFacebookと喧嘩をしているわけではない。Tweakや(名無しのアプリエンジン)もいつかきっとOpen Sourceとして公開されることでしょう。それらを使い、デザイナーとエンジニアがideaと知恵を合わせれば、きっとPaperチームが「公開するんじゃなかった」と後悔するようなインタラクションが実装できるに違いない,,と無理矢理かつ前向な言葉をもってこの文章を締めくくるのでした。 *1 : 具体的にどう複雑かは こちらのページ を参照してください。
アバター
古川です。 前回 から少し時間がたってしまいましたが、独自ソートを実現の続きで、search component plugin を使って実現する方法を、二回に分けて紹介します。solr が持っている検索の機能をすべて満たした実装は難しいので、 他のコンポーネントとの組み合わせ グルーピング処理 分散検索 はあきらめて、とにかく検索にヒットしたドキュメントを望みの順番で返すことができるsearch component を作成することを目的とします。 今回はまず、solr の search component plugin の作り方を紹介します。前回と違い、とりあえずこうすれば動いたという感じなので、間違えている点あれば、ご指摘いただければ幸いです。 Search Component の作成 solr におけるデフォルトの検索コンポーネントは、SearchComponent クラスを継承して実装されています。 solr/core/src/java/org/apache/solr/handler/component/SearchComponent.java SearchComponent.java のコードを見ると、以下4つの関数を実装すればよいことが分かります。 public abstract void prepare(ResponseBuilder rb) throws IOException; public abstract void process(ResponseBuilder rb) throws IOException; public abstract String getDescription(); public abstract String getSource(); 次に、Solr の検索コンポーネントの本体 QueryComponent.java をベースに、分散と、グルーピングに関連する部分を除いたクラス MyQueryComponent.java を作成します。 solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java package jp.co.homes.searchcomponent; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrException; import org.apache.solr.common.params.*; import org.apache.solr.common.util.NamedList; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.ResultContext; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.schema.FieldType; import org.apache.solr.search.QParser; import org.apache.solr.search.QParserPlugin; import org.apache.solr.search.QueryParsing; import org.apache.solr.search.ReturnFields; import org.apache.solr.search.SolrIndexSearcher; import org.apache.solr.search.SolrReturnFields; import org.apache.solr.search.SyntaxError; import org.apache.solr.handler.component.SearchComponent; import org.apache.solr.handler.component.ResponseBuilder; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; public class MyQueryComponent extends SearchComponent { public static final String COMPONENT_NAME = "my_query" ; @Override public void prepare(ResponseBuilder rb) throws IOException { SolrQueryRequest req = rb.req; SolrParams params = req.getParams(); if (!params.getBool(COMPONENT_NAME, true )) { return ; } SolrQueryResponse rsp = rb.rsp; ReturnFields returnFields = new SolrReturnFields( req ); rsp.setReturnFields( returnFields ); int flags = 0 ; if (returnFields.wantsScore()) { flags |= SolrIndexSearcher.GET_SCORES; } rb.setFieldFlags( flags ); String defType = params.get(QueryParsing.DEFTYPE, QParserPlugin.DEFAULT_QTYPE); String queryString = rb.getQueryString(); if (queryString == null ) { queryString = params.get( CommonParams.Q ); rb.setQueryString(queryString); } try { QParser parser = QParser.getParser(rb.getQueryString(), defType, req); Query q = parser.getQuery(); if (q == null ) { q = new BooleanQuery(); } rb.setQuery( q ); rb.setSortSpec( parser.getSort( true ) ); rb.setQparser(parser); rb.setScoreDoc(parser.getPaging()); String[] fqs = req.getParams().getParams(CommonParams.FQ); if (fqs!= null && fqs.length!= 0 ) { List<Query> filters = rb.getFilters(); filters = filters == null ? new ArrayList<Query>(fqs.length) : new ArrayList<Query>(filters); for (String fq : fqs) { if (fq != null && fq.trim().length()!= 0 ) { QParser fqp = QParser.getParser(fq, null , req); filters.add(fqp.getQuery()); } } if (!filters.isEmpty()) { rb.setFilters( filters ); } } } catch (SyntaxError e) { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e); } } @Override public void process(ResponseBuilder rb) throws IOException { SolrQueryRequest req = rb.req; SolrQueryResponse rsp = rb.rsp; SolrParams params = req.getParams(); if (!params.getBool(COMPONENT_NAME, true )) { return ; } SolrIndexSearcher searcher = req.getSearcher(); if (rb.getQueryCommand().getOffset() < 0 ) { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "'start' parameter cannot be negative" ); } long timeAllowed = ( long )params.getInt( CommonParams.TIME_ALLOWED, - 1 ); SolrIndexSearcher.QueryCommand cmd = rb.getQueryCommand(); cmd.setTimeAllowed(timeAllowed); SolrIndexSearcher.QueryResult result = new SolrIndexSearcher.QueryResult(); searcher.search(result,cmd); rb.setResult( result ); ResultContext ctx = new ResultContext(); ctx.docs = rb.getResults().docList; ctx.query = rb.getQuery(); rsp.add( "response" , ctx); rsp.getToLog().add( "hits" , rb.getResults().docList.matches()); } @Override public String getDescription() { return "my query component" ; } @Override public String getSource() { return "$URL: dummy $" ; } } コンパイル 以下のjarファイルにpathを通して、上記ファイルをコンパイルして、homes-serch-component.jar を作成します(jar ファイル名は何でも構いません) solr-4.6.1/example/solr-webapp/webapp/WEB-INF/lucene-core-4.6.1.jar solr-4.6.1/example/solr-webapp/webapp/WEB-INF/solr-core-4.6.1.jar solr-4.6.1/example/solr-webapp/webapp/WEB-INF/solr-solrj-4.6.1.jar 設定 前回同様、作成したjarファイルをcollection1フォルダのlib以下にコピーします。 cp homes-function-search-component.jar solr-4.6.1/example/solr/collection1/lib solrconfig.xml の、hilighting コンポーネント設定の後に、今回作成した MyQueryComponent にアクセスするための設定を追加します。これで、 http://localhost:8983/solr/collection1/my_select にアクセスするとMyQueryComponentにクエリが渡されるようになります。 <searchComponent class = "solr.HighlightComponent" name = "highlight" > <highlighting> <!-- 省略 実際にはhighligの設定がある--> </highlighting> </searchComponent> <!-- 追加分 --> <searchComponent name = "MyQueryComponent" class = "jp.co.homes.searchcomponent.MyQueryComponent" /> <requestHandler name = "/my_select" class = "solr.SearchHandler" > <arr name = "components" > <str> MyQueryComponent </str> </arr> </requestHandler> 動作確認 solr を起動します。solr には前回投入したインデックスが既に存在していると仮定します。 cd ./solr-4.6.1/example java -jar start.jar & 通常のsolr検索selectと、新しく作成したmy_selectの検索結果が同じであることを確認します。 select http://localhost:8983/solr/collection1/select?echoParams=none&q=*:*&fl=x,y,myfunc(x,y,1,2,3,4,5,6)&sort=x asc,y desc <result name = "response" numFound = "1000006" start = "0" > <doc> <str name = "id" > id828205 </str> <float name = "x" > 0.0 </float> <float name = "y" > 999.0 </float> </doc> <doc> <str name = "id" > id91438 </str> <float name = "x" > 0.0 </float> <float name = "y" > 998.0 </float> </doc> <doc> <str name = "id" > id628349 </str> <float name = "x" > 0.0 </float> <float name = "y" > 998.0 </float> </doc> </result> my_select http://localhost:8983/solr/collection1/my_select?echoParams=none&q=*:*&fl=x,y,myfunc(x,y,1,2,3,4,5,6)&sort=x asc,y desc <result name = "response" numFound = "1000006" start = "0" > <doc> <str name = "id" > id828205 </str> <float name = "x" > 0.0 </float> <float name = "y" > 999.0 </float> </doc> <doc> <str name = "id" > id91438 </str> <float name = "x" > 0.0 </float> <float name = "y" > 998.0 </float> </doc> <doc> <str name = "id" > id628349 </str> <float name = "x" > 0.0 </float> <float name = "y" > 998.0 </float> </doc> </result> グルーピングや、分散検索以外は同じはずなので、当たり前ですが同じ結果が取得できています。 次に、グルーピングを行うクエリを実行してみます。 select http://localhost:8983/solr/collection1/select?group=true&group.field=x&echoParams=none&q=*:*&fl=x,y,myfunc(x,y,1,2,3,4,5,6)&rows=2 <lst name = "grouped" > <lst name = "x" > <int name = "matches" > 1000006 </int> <arr name = "groups" > <lst> <float name = "groupValue" > 1.0 </float> <result name = "doclist" numFound = "997" start = "0" > <doc> <float name = "x" > 1.0 </float> <float name = "y" > 4.0 </float> <float name = "myfunc(x,y,1,2,3,4,5,6)" > 87.0 </float> </doc> </result> </lst> <lst> <float name = "groupValue" > 2.0 </float> <result name = "doclist" numFound = "975" start = "0" > <doc> <float name = "x" > 2.0 </float> <float name = "y" > 5.0 </float> <float name = "myfunc(x,y,1,2,3,4,5,6)" > 138.0 </float> </doc> </result> </lst> </arr> </lst> </lst> my_select http://localhost:8983/solr/collection1/my_select?group=true&group.field=x&echoParams=none&q=*:*&fl=x,y,myfunc(x,y,1,2,3,4,5,6)&rows=2 <result name = "response" numFound = "1000006" start = "0" > <doc> <float name = "x" > 1.0 </float> <float name = "y" > 4.0 </float> <float name = "myfunc(x,y,1,2,3,4,5,6)" > 87.0 </float> </doc> <doc> <float name = "x" > 2.0 </float> <float name = "y" > 5.0 </float> <float name = "myfunc(x,y,1,2,3,4,5,6)" > 138.0 </float> </doc> </result> グルーピング関連の部分を削除したので、my_select の方は、groupを指定しても、通常の検索が行われていることが分かります。 まとめ グルーピングや分散処理を除けば、SearchComponent は意外とシンプルな構造でした。次回は、MySearchComponent.java に 以前 紹介した 自作のCollectorクラスを使う方法を組み込んで、独自ソートを実現します。
アバター
サムです。前回の「 GameController.frameworkをつかってG550ゲームパッドとSpriteKitゲームを接続してみよう 」に引き続き今回は、接続したG550のボタンに応じたイベントを取得してみます。 0. 前回まで GameController.frameworkをつかってG550ゲームパッドとSpriteKitゲームを接続してみよう 1. ポーズ&リスタート(一時停止と再開)のイベント まず、すべてのゲームコントローラには、ポーズボタンがあります。 このボタンはほとんどのゲームでは、ゲームの一時停止、および再開というイベントが走ると思います。 このイベントには、コントローラオブジェクトの controllerPausedHandler プロパティにブロック処理を追加することで可能になります。 GameController.framework自体には、ゲームの一時停止におけるユーザインターフェースは提供されていません ポーズボタンを押したときのゲーム停止などは、自分たちで実装する必要があります。 2. A/B/X/Yボタン、Dパッド、L/Rトリガーのイベント 今回使用しているゲームコントローラは G550 です。このコントローラは GCGamepad クラスを使います。 他にも GCExtendedGamepad クラスというGCGamepadクラスと比べると、ボタン数が多いゲームコントローラもあります。 すべてのボタンのイベントを取得するには、 GCGamePad オブジェクトの valueChangedHandler プロパティにブロック処理を追加するだけです。 この処理では、ボタンが押されるたびにイベントが発生して、押されたボタンの処理が実行されます。 ボタンの同時押しなどのイベントを設定したいときにつかいましょう ボタンごとのイベントを設定したい場合は、ボタンオブジェクト自体に valueChangedHandler プロパティにブロック処理を追加するだけです。 これで、ボタンのステータスを見張ることが出来ます。 まとめ ゲームコントローラは、 GCGamePadクラスだけではなくGCExtendedGamepadクラスも必ず実装 しましょう 何のコントローラを使うかは、ユーザが決めることですからね。 これらのソースコードは GitHubで公開 しております。 参考、引用元 Apple Developer - Game Controller Programming Guide
アバター
こんにちわ、社内でアジャイル推進をしている非エンジニアな鈴木です。 2014/3/19 第2回「 Scrum Masters Night(スクラムマスターズナイト) 」の参加レポートです。 今回はヤフー株式会社さんのオフィスで開催、同会場で行われた懇親会もスポンサー付きで「タダ!」これは参加するしかありません。ということで行ってきました! 基本的に 第一回目 と同じオープンスペース方式で行われました。 今回は最初のテーマとして、 チームのメトリクスについて スプリントゴール(ストーリー)どうしてますか? スクラムマスターのキャリアパス チームがプロセスを気にするためにどうすればよいか? が選ばれました。 どれも気になるテーマではあったのですが、「スクラムマスターのキャリアパス」を選択しました。 スクラムマスターのキャリアパス まずテーマの背景としてあったのが、同じような役割の人を増やしたいが、スクラムマスターの先のキャリアが見えない。 キャリアを重ねた先に個人として、会社として何があるのか?という疑問です。 スクラムマスターには次の2タイプがいると思います。 チーム中からスクラムマスターという役割をまかされたタイプ 推進役やコーチとしてスクラムマスターを専任しているタイプ どちらも初期段階はチームを成功に導き、スクラム開発を導入していく。 そして、少しずつスケールを大きくしていく。コーチであれば、後任者を育てるなどなど。 けれど、そうして導入が済み自己組織化が完全にされたら、スクラムマスターの仕事はなくなります。 とすると、元のエンジニアやプランナーに戻るのか?また新たな現場を求めてさまよい歩くのか? (完全な自己組織化というのがあり得るかはおいといて・・・。) なんだか、やっぱり先が見えませんスクラムマスター。 スクラムマスターの役割 スクラムマスターはどんなチームや組織で役割を果たしているのか、何をしているかという点についても話しをしました。 スクラムマスターは手法が目的にならないように、 スクラムのプロセスを守る人 チームを新しい方向に導いて行く人 チームがよい仕事が出来るようにする人 外的要因からチームを守る人 などと、一般的に言われています。 スクラムマスターは、チームの実行力や自己組織化を高めていくのが役割です。 ※但し、チーム内でリーダーシップを発揮してはならない。 そして、「※」の部分が重要で、従来型のリーダーとは異なったファシリテーション能力とコミュニケーション能力が求められます。また、なにか具体的な成果物というものは生み出しません。 スクラムマスターのスキルパス そんなスクラムマスターに求められるスキルパスはどんなものでしょうか? スクラム開発を含めたさまざまなフレームワークに関する知識 CIや開発基盤を改善する手法に関する知識 Fearless changeや組織パターンなどの組織に関する知識 コーチング、ティーチング、メンタリング、ファシリテーションの能力 謙虚さ 人たらし 組織やチームからの信頼 などなど、プロジェクト管理に必要な知識、相手を気持ちよく動かすことができる能力と気持ちを引き出すコミュニケーション能力が必要であるという意見が出てきました。 最終ゴールはマリッサ・メイヤー? では、そんなスクラムマスターのゴールは何でしょう? Dreamforce 2013の記事 でYahoo!CEO マリッサ・メイヤーが、自身の仕事についてこんな風に語っていたのが印象に残っています。 なにか大きなプランを立てて、それを社員にやらせるということではなく、どんなアイディアがあるのかを聞き、アイディアを実行するように許可するのが私の役割。 道をクリアにし、障害物を取り除き、仕事がやりやすいような環境を作ること。チームが攻撃をして、エグゼクティブがディフェンスをすることが理想の姿 なんだかこれってスクラムマスター的ではありませんか? スクラムマスターをつきつめていくと、チームのあり方だけではなく組織のあり方というところも考えなくてはなりません。 CEOは究極ですが、人材育成、評価制度、組織変更といった「組織のデザイン」まで行うのが最終目標かもしれません。 やはり社外の方との交流は楽しい いろんなバックグラウンドの方と同じテーマで語ると、それまでとは違った視点を見つけることができたり、自分の考えの整理や裏付けができます。 この経験をその場限りのものにせず、次のステップへつなげていくことができるようになればと思いました。 スポンサーの耳元でささやいてくださった 高橋さん 、運営者のみなさま、参加者のみなさま、楽しい時間をありがとうございました! 次回は、4月21日に開催予定。 もちろん次回も参加したいです。ですが、いつか弊社でも開催したいっ!などと、次のステップへつなげるための発言を残しつつ、今回のレポートを終わりたいと思います。
アバター
サムです。今日はGameController.frameworkについて軽く触れます。 GameController.frameworkは、MFiのiPhone/iPod/iPadとBluetoothまたは物理接続したコントローラの入力をアプリケーション側に受信できるものです。 そこで、2013/12/24にロジクールから発売された「G550 パワーシェル コントローラ + バッテリー」を使って、SpriteKit + GameController.frameworkについて書きます。 0. 今回の環境 OS X Version: 10.8.5 Xcode Version: 5.0.2 Development Target: 7.0 Devices: iPhone 5S, G550 1. GameController.frameworkの追加 Xcodeのプロジェクトから TARGET > General > Linked Frameworks and Libraries で GameController.framework を追加します。 2. import SKSceneクラスを拡張してあるシーンにGameControllerを追加します。 3. ゲームパッド用のプロパティを宣言 まず、ゲームパッドのクラスオブジェクトには2タイプあります。 GCGamepad Class こちらはとてもシンプルなゲームパットクラスで、次のコントロールを結びつけています。 Two shoulder buttons. Four face buttons arranged in a diamond pattern. One directional pad (D-pad). GCExtendedGamepad 一方のGCExtendedGamepadクラスは、GCGamepadクラスとは事なり、より多くのコントロールに関連しています。 Two shoulder buttons. Two triggers. Four face buttons arranged in a diamond pattern. One directional pad. Two thumbsticks. G550はGCGamepadクラス用のゲームパッド なので、次のように宣言します。 4. ゲームパッドの接続、切断を検出を通知するコードを追加 ゲームパッドは、Bluetoothで接続されても、ケーブルを介して直接接続されても、ゲーム上では同じように動作されます。 ゲームパッドが接続または切断されたときは、GameController.frameworkからNotificationが発生するので、これを受け取るセレクターを用意します。 この通知は、ゲームコントローラの接続/切断が変更されるたびに呼び出されます。 4-2. ワイヤレスゲームパッドの場合 ワイヤレスコントローラは、通常ゲームの外で接続されます(Bluetoothゲームパッドとかを利用したとき?) そのため、ゲームの起動前に接続しておく(または、接続した後にゲームを再起動する)必要があります。 けれども startWirelessControllerDiscoveryWithCompletionHandler クラスメソッドを使うことで、ゲーム内でゲームパッド(優先、無線含む)を直接接続することもできます。 この検出プロセスを実行すると、新しいコントローラが自動的に検出され、ゲームパッドとして接続されます。 検出プロセスは自動的にタイムアウト(30秒くらい?)されますが stopWirelessControllerDiscovery クラスメソッドを呼び出すことで、現在実行されている検索プロセスを停止させることができます。 これらのクラスメソッドは、 アプリケーションで適切なユーザインターフェースを設計 して、提供する必要があります。 ※例えば、ゲーム設定とかに「ワイヤレスコントローラと接続する」的な項目を追加したりとか まとめ 以上で、GameController.frameworkを使ってG500ゲームパッドに接続することができます。 次回は、ボタンのイベントを取得してみようと思います。 これらのソースコードは GitHubで公開 しております。 参考、引用元 Apple Developer - Game Controller Programming Guide
アバター
Apple原ry) 大坪です。 さて、 前回 はとにかくOrigamiを動かしたところで終わっていました。今回は「ではRender in Imageの中では一体何をしているのか」について書きます。とはいえここで書くことはかなりの推測が混じっていますので、間違っている点についてはご指摘いただけるとうれしいのです。Render in Imageをダブルクリックするとこんな画面になっていたのでした。 ここで描画したものが、iPhoneの画面中に表示されています。ではここでは何をしているのか。右下のほうから見て行きましょう。 まずClearパッチの順番が1になっているので、最初に画面がクリアされます 次に順番が2であるところの"Layer"パッチの内容が描画されます。 "Layer"パッチのImageというところには"Image"パッチから線がつながっています。前回の最後ではこの"Image”パッチに好きな画像を指定したのでした。 ここだけみると、指定した画像がiPhoneの中に表示されるだけです。ではどのように「クリックで拡大、縮小」しているのかを見て行きましょう。 一番左に"Interaction 2"というパッチがあり青い線がLayerまで伸びています。このパッチはLayerに対してユーザが行った操作(クリックとかドラッグとか)を検知して教えてくれるもののようです。でもってそのClickから線が伸びて"Switch"の"Flip"にはいっている。つまりここに何かが入ってくる度に、スイッチがOn/Offが切り替わる、ということなのでしょう。On/Offという出口から今度は"Bouncy Animation"のNumberという口に線がつながっている。 ここからは想像ですが、このパッチはNumberにはいった値を元に「びよよーん」という値変化をつけてくれるものではないか。Patch Inspectorを使って中を見るとFriction,Tensionという2つの値があります。これを変えることで「びよよーん」の様子が変わります。 でもってその出口は"Transition"につながっている。説明を読めば、Transitionは0から1の間の値を受け取って、それをStart ValueからEnd Valueまでの値に変換するとのこと。Bouncy Animationは0から1の間で値をびよんびよんさせるのですが、画像の大きさを表すのにそれだけではこまる。というわけでこのTransitionを使って必要な範囲の値を取り出すわけです。 というわけで、"Transition"の出口が"Layer"の"Scale"につながって、ようやくこれで 「画面をタップするたび、画像がびよよーん、と大きくなったり小さくなったりする」 が実現できたわけです。 理解を深めるために、ここからいじってあれこれやってみるといいかもしれません。なんといっても「操作をすると何かの反応がある」のは楽しい。 例えばScaleパッチの出口のつなぎ先をLayerパッチのSaleからAlphaにかえると何がおこるか?画像をタップする度に明るくなったりぼんやりしたりするはずです。じゃあタップすると同時に明るさと大きさを、、と考えはじめるとFacebookが提供しているAlpha And Scaleに戻ってくるわけでした。ああ、長い道のりでした。 ここまで読んで、実際に何か手を動かしてくれる人がいるかどうかはなはだ疑問に思っているわけですが、次の事実は動かしようがない。 Paperで使われているほとんどのアニメーションは、Origami上でプロトタイプされ、そのあと実際のアプリ開発を行うプログラマーに渡された。 引用元: Facebook Develops A Photoshop For Interaction Design, And It's Free For Anyone To Use | Co.Design | business + design Facebook Paperを使っていると、細部にまで考慮が行き届いていることに驚きます *1 。もちろん「あら」を探すことは可能です。しかしそれより良いものをデザインすることができるか、と問うのはおそらく無駄ではない。 またエンジニアとしては、その実装に驚くほどの工夫がなされていることも感じ取れる。 Cocoacontrols を見れば、いくつかPaperに触発されたモジュールがあることがわかる。しかしそれらを使えばそのままPaperができるわけではもちろんない。それどころか、Paperまでの距離の遠さを思い知らされることになります。 実はそうした推測が間違いではないことを示す記事を見つけ、愕然としているところです。一人で愕然としているのは「もったいない」ので次はその内容について紹介できれば、と。 *1 : 具体的には、 こちらのサイト で詳しく考察されています
アバター
株式会社ネクストの藤原です。 今からちょうど3ヶ月ほど前、「 クリエイターの日、ただいま開催中 - 株式会社ネクスト エンジニアBlog 」にてご紹介しました「 クリエイターの日 」ですが、ただいま、第4四半期(1月~3月)の合宿期間中で、今回は12チーム、34人のクリエイターが、新たなサービス・技術へのチャレンジをしています。 さて、「クリエイターの日」の活動の趣旨ですが、 「既存サービス・技術の枠組みを飛び越えた自由な発想からイノベーションの創造」 「各メンバーの興味あるサービス・技術へのチャレンジを通しての個人の成長とネクストの創出力向上」 この2つを目的として、最大7日間を費やして研究・開発を行っています。 それでは、各チームの活動状況の一部をご紹介します。 Node.jsチームはまずは二人でスタート。圧倒的な速度を目指します。 こちらではArduino+Unityで光の検知とシミュレーションをやっているようです。 センサーに手をかざすと画面上には・・・? iPhoneアプリを作成中!何かゲームをしているようにも見えますが・・・ もう一つiPhoneアプリ。前回に引き続きUXにこだわりを持ったアプリの開発中です。 開発中のものを見せていただきましたが、すぐにでも使ってみたいと思いました! こちらは黙々と作業中ですね。 テーマは「レスポンシブウェブ」とのこと。 ホームズくんの新しいコンテンツ作成中!どんなものが出来上がるんでしょう! こちらは、「クリエイターの日」サイトのリニューアルチーム。 近々新しいコンテンツを皆さんにも見ていただけるようにしたいですね!  こんな感じで各チームそれぞれ思い思いの方法で、新しいサービスを生み出すことや、新しい技術の取り組みに全力を注いでいます。今月末には成果報告会を実施する予定で、どんなものが出来上がるのか、どんな新しいモノが見られるのか、大変楽しみにしています。
アバター
Apple原理主義者の大坪です。 Facebookが「インタラクションデザインのPhotoshop」を公開したぞーと 驚いたのが2月の始め 。さて使ってみようと思ってはみたけれど肝心のQuartz Composerの使い方を完全に忘れている。とはいえ 「インタラクションデザイナーと名乗るからにはこれくらい使えなくちゃね」 などと言いっぱなしで終わるのはいかがなものか(そもそもお前はいつからインタラクションデザイナーについて語れるようになったのだ、という根本的は疑問は無視) というわけで、まずOrigamiを使うまでの手順を書きます。 自分が「正しいコンピュータ」を持っていることを確認する。「正しいコンピュータ」とはMacです。はて、Windows PCってなんのことですか?(錯乱) Origamiのページ に行く。ここからページの下半分に書いてある手順です。 Appleの開発者登録を行う。( Apple Developer Registration - Apple Developer )登録だけなら無料。 下のほうにあるSign Inを選びます。もしiPhoneとかを持っていて、既にApple IDを作っていればそれを入力するだけで登録できます(多分) ここをクリック してQuartz Composerをダウンロードしてインストールする。(さっき登録した開発者IDとパスワードの入力が必要) ダウンロードしたディスクイメージをダブルクリックすると、Graphics Toolsというフォルダが開きます。この中のQuartz Composerというアプリをアプリケーションフォルダにコピーします。 Origamiのインストール:最初のページの リンクの「3」番をクリック するだけで、ファイルがダウンロードされます。でもってそれをダブルクリックするとインストーラーが開いてOrigamiがインストールされる。その後サンプルのページが開き、かつQuartz Composerが起動します。 というわけで、ようやくOrigamiを使う準備ができました。 Origamiのサンプルページ にはいくつかの例が載っています。その中で一番簡単な"Alpha and Scale"を一部だけ(Scaleのみ)「写経」しつつ、何をしているのかについて分かったことを書きます。 とはいってもQuartz Composerの使い方というのは少し変わっている。まずはいくつかの基本用語を抑えないことにはなんともなりません。以下の2つのサイトが参考になると思います。 Quartz Composerの基本的な使い方 ) Quartz Composer入門  良いサイトですが、内容が9年前なのが惜しい。 上記ページを見てからでもよいですし、なんなら飛ばして後から見てもよいです。とにかくQuartz Composerを立ち上げます。するとTemplate Chooserという画面が表示されます。Basic Compositionを選んで、右下のChooseというボタンを押す。 すると2つのウィンドウが開きます。一つはEditorとタイトルがついていて、なにやらClearという文字がついた青い四角がでている。もう一つのウィンドウはViewerで真っ黒です。 Editorの中に表示される四角いものは、「パッチ:Patch」と呼ばれます。「何かをしてくれる」ものです。Quartz Composerのプログラミングは、このパッチを並べて、必要な情報をつなぐことで行います。 真っ暗のViewerをみていても、楽しくないのでとりあえずiPhoneを表示させましょう。Editorの右上にある"Patch Library"というボタンを押します。するとこんな画面がでてきます。 下の方にある検索窓に"Phone”と入力するとこんな感じになります。 でもってPhoneのところをダブルクリックしてください。するとEditorにPhoneが追加されます。それと同時にViewerにもiPhoneの外枠が表示されます。 わーい、できたできたー、と喜びたいところですが、まだ外枠が表示されただけだということを思い出しましょう。先は長いのです。 とはいえここで一休みしてそもそも何をやっているかを考えるのは無駄ではないでしょう。 青い色をしたパッチは、「スクリーンに何かを表示する」役目を持っています。今はPhoneとClearの2つの青いパッチがある。スクリーンに何かを表示してくれるものが2つあるとき何がおこるか?一つが書き込んだ上に、もう一つが書き込むという動作をします。その順番を示しているのが、右肩にある小さな数字。数字が小さいものほど下にある。そこをクリックすると順番を変えられます。ちなみにここでClearを2にすると、画面に何も表示されなくなります。Phoneが一生懸命フレームを書いても、上からクリアされてしまうわけですね。 さて、もし青いパッチの順番を変えていたとしたら元に戻して...(Clearパッチを1,Phoneパッチを2) ここから2つパッチを追加します。まずTemplate Chooserから"Phone Dimensions"を選んでEditorに追加。もう一つは"Render in Image"です。 追加した"phone Dimensions"を選んで、メニューバーにある"Parameters"を選ぶと、右側にパッチで設定できるパラメータを入力する画面がでます。iPhone/Android/Windows Phoneが選べるはずです。 Render in Imageパッチを選択すると、なにやら英語で説明がどわーっと出ます。細かいところは別として読んでみると「このパッチのサブパッチが描画したものを、Imageとして出力するよん」と書いてあるような、書いてないような。というわけで、今存在しているパッチを接続しましょう。 Phone DimensionsのPixels Wideをクリックして、ボタンを押したままずりずるとどらっぐすると糸が伸びます。その糸を、Render in ImageのPixels Wideにつなぐ。同様にして Phone DimensiosのPixels High→Render in ImageのPixels High Render in ImageのImage→PhoneのScreen Image つなぎ終わるとこんな感じに見えます。ここまでで何をやっているかというと、 画面のサイズをPhone DimensionsからRender in Imageに渡す Render in Imageは描画した結果を(まだ何も作ってないけど)Phoneに渡す。 Quartz Composerはひたすら画面をクリアした後に、Render in Imageから渡された結果を描画する。 というわけで、あとはRender in Imageの中身さえちゃんと作ればきっと何かができるはずさ!とまるで駅前で歌っている若者のようなことを言ったところで次に行きます。 Render in Imageのパッチをダブルクリックします。ここで"Render in image"の文字をクリックするとタイトルが編集できるモードになってしまいますので下の方をダブルクリックしましょう。するといきなり画面がまっさらになります。 うぎゃー、今まで作ったのが全部消えた!と叫ぶ前に深呼吸をしましょう。大丈夫。メニューバーのオレンジ色のメニュー"Edit Parent"を押せばさっきの画面に戻るはずです。つまるところはRender in Imageの中にはいっていたのでした。 というわけでもしEdit Parentを押していれば、もう一度Render in imageをダブルクリックしてまっさら画面に戻ります。心を一旦落ち着けて次の図のようにひたすらパッチを入力+パッチの間をつなぎましょう。ヒント:最初に"Image"を追加すると、一緒に"Layer"も接続され+追加されるのでお得です。 ここで何をしている(と思われるか)は後で説明します。ひたすら写経の精神でつなぐのです。でもって3点だけ設定が必要な部分があります。 まず右下のほうにある"Transition"パッチですが、選んだ状態でPatch Inspectorを開くと"Start Value"とEnd Valueという欄があり、値が0と1になっています。このStart Valueのほうを0.5にしてください。(これを忘れると、最初の画面が真っ黒になります) 次にBouncy Animationパッチを選びPatch Inspectorを開いてください。Tensionという値を100にします。(これを忘れると、拡大したときにあまり「びよーん」と動かずつまらない思いをします) 最後にImageパッチをクリックした状態でメニューバーの"Patch Inspector"を押しましょう。でもって開いた画面の上部にあるオプションメニューの"Settings"を選択します。でもってここの"import from file"というボタンを押すと、ファイルを選択する画面が開くはずです。ここで画像ファイルを選択します。なんでもいいのですが、このあと何度も見ることになるのである程度の大きさがあって、かつ何度も見たくなるような画像がいいと思います。 選択が終わったら、どきどきしながらメニューバーのViewerボタンをおしましょう。すると先ほど選択した画像がiPhoneの中に表示されているはずです。 でもってiPhoneの画面をマウスでクリックすると画像が大きくなったり、小さくなったりします。ああ、長い道のりだった。感動しませんか?しますよね?ね?(うざい) というわけでしばし感動しながら画像を大きくしたり小さくしたりしましょう。エンジニアの怨嗟の言葉を聴くことなしにiPhoneアプリの動きを表現することができたのです。画像を拡大するだけの動きになんの意味がある、という根本的な疑問は聞かなかったことにして、「では今何をやっていたのか」の説明は後編で。 今回説明しているプログラムのファイルは GitHub においてあります。GitHubとはなんだ?という人は周りにいるソフトウェアエンジニアを捕まえてダウンロードしてもらいましょう *1 。GitHubを知らない、などという人はそもそもソフトウェアエンジニアではありません。(暴言) *1 : あるいはリンク先のページで、画面右下のほうにある"Download Zip"というボタンを押しましょう
アバター
どうも上津原です。 今回は、実装時に初心者だから(だと思う)つまずいたポイントと、どう解決したかをUnity操作、Oculus Rift対応それぞれに分けて紹介していきたいと思います。 教える立場の人は、完全初心者が3Dゲームに手を出すとどういうところにぶつかるのか? これから始める人は、ぶつかる前にどこを押さえておけばいいのか? それらの参考になれば嬉しいです。 では、Unity操作編、始まりです! 3Dゲームの基本 3Dゲームには、Z軸や重力、当たり判定、ライティング、カメラ…いろいろな要素が絡みます。 そんな中、盛大に転んだものを3つお伝えします。 オブジェクトが地面を通り抜けて落ちていく! 本をひと通り試した私は、ある程度もう知ってるもんね〜という心持ちでプロジェクトを作っていました。 そこで一番最初のつまづきがきました。 「この3Dモデル配置してRigitbodyで重力付きの物が作れるんでしょ?簡単簡単!」 そしてひたすら落下し続けるキャラクター! 「 アイエエエエ!ノラナイ!?ノラナイナンデ!? 」 地面にオブジェクトが乗らない!!! 読んだ本ではこんなことはなかったので、かなり頭を悩ませました。まさかこんな落とし穴があるとは(うまい!) Unity内で生成したもの(CubeとかShpereとか)なら、予め当たり判定が付いているのでうまく動いていました。しかし、ただの3Dデータを外部から持ってきて配置したらコライダーがついていません。 モノの上に乗るには「 コライダー 」が必要。 そして当時私は、 Rigitbodyをつけることが「ものに乗れる条件」 だと思っていました。まぎれも無いバカです。 なので私は 「どちらともにコライダーがなければものの上に乗らない」ということに気がついていなかった し、なのでそもそも 「上に乗ること」を「衝突」 だとも考えもしませんでした。 コライダー?メッシュ?シェーダー?なにそれ。 沢山出てくる専門用語。みんなアプリ開発では聞きもしませんでした。 「Unity すりぬける」でググった私が出会った言葉、それが「コライダー」でした。 「コライダー」と聞くと、ライダーの方に意識が取られて、ライダー?とか思ってました。 そして「コライダーとは」でググッた結果、それが 「あたり判定」 だと知りました。普段ゲームをやるにあたって「あたり判定」という言葉は馴染みがあったので、この言葉を知ってやっとピンときました。 どうやら2つのオブジェクトお互いにコライダーがないと、ものには乗れないらしい。それを知り実践し「なるほど、コライダー」とやっと理解したのでした。 ほかにも、メッシュって?シェーダーって?レンダラー?トランスフォーム??それぞれの専門用語を一個一個調べ、何なのかを理解していきました。 代表的な専門用語は、予め調べておく コライダー メッシュ シェーダー レンダラー トランスフォーム 解説記事にもついていけなくなるのでせめて上記のこれらくらいは把握していたほうがいいと思います。 画像検索で出てくるようなライティングが出来ません! 検索して出てくる、Unityで出来たゲーム画面画像を見ては「これ、俺の持ってるUnityと違う…」状態でした。 ライトを配置し影を描画してもダサイ感じ…。「Proの無料体験版だから、じつは機能制限されてるとか!?」「特殊なプラグイン買わないと出来ないとかじゃないの?」とも考えました。 理想 現実 (画像: Unity Manual より) そこで出た答えが「 Lightmapping 」 ライトマッピングはまだ全然把握できていないし、使いこなせていないのでここでは偉そうなことは書けませんが、Lightmappingを利用することでやわらかな影の描写や、動作を軽くすることなどが実現できるようになりました。 コード iOSのアプリ開発をやっている中、考えもしない要素がコードにも山積みです。そもそもコードはどういう立場にいるのか?コードでしか出来ないことは? 4つのつまづきポイントを紹介します。 UnityScriptイコールJavaScriptではない!? そして、舞台はコード実装へ。 UnityはJavaScriptで開発できるんだよ!すごいでしょ!という触れ込みと、最初に読んだ本のコードがJavaScriptだった事もあり、それを使って開発を進めていくとすぐに気が付きました。 「これ、俺が知ってるJavaScriptと違う…。」 (またかよ) 「型宣言もしなくちゃいけないし、WebのJavaScriptで使ってた関数を使えない時あるし、 そういえば俺、そもそも知ってたのjQueryだわ。 」 UnityScriptはUnityScript JavaScriptちょっとかじってたしなんとかなるだろ、と思って安易にJavaScriptを選んだけど、結局はきちんとUnityScriptとしてJavaScriptを覚え直す事になりました。 僕は本当にかじった程度だったのでもっとちゃんと知っている人ならばそうではないのかもしれませんが、僕にとっては違うものに感じました。 遠隔にあるものを認識するには? ものの持ち上げ判定を得るために、視線の先に対象のものがあるのかを確認する必要がありました。 そこで必要になるのが 「Ray」 です。 どこのページを見ても「Rayというのは、見えないレーザーみたいなものです」という説明があり「レーザー?レーザー????なんでレーザーなの?」とか考えていました。 しかし動かしてみるとピンときました。設定した長さの棒をだして、それに当たったものを認識するんだな?見えないコリジョンだな?そうなんだな?と。 Rayは見えない物体認識棒 Rayという名前ゆえに「レーザー」と言われるのだと思いますが、僕は「棒」と思ったほうが現実味が合ってしっくりしました。そうして、遠隔のものを認識する、という術を手に入れました。 他オブジェクト内のPublicな変数はどうやって取り出すんだ?? コードを書いていくと、当然この問題にぶつかります。 例えば、Objctive-Cのコードでは、別クラスのPublicな値を手に入れるには EXObject exObject = [オブジェクト呼び出し]; BOOL flag = exObject.flag; というふうに書けばいいのですが、これはUnityだしUnityScript。当然同じように書いてみても取れません。 そもそも「オブジェクトにスクリプトを配置する」という行為自体に理解が追いついていないのです。某書籍では、「static publicにしてアクセスすればいいんだよ」という風に書いてあったけど、それだと明らかに柔軟性に欠ける。なにかいい方法はないのか。 GameObject go = GameObject.Find(“exObject “); EXScript exScript = go.GetCompornent(EXScript); bool flag = exScript.flag; となる事を知る。 スクリプトはいちコンポーネント扱い アプリ開発をしている時、コードこそが本体だった自分としては、コンポーネントの1つとしてScriptがある、という考えにいまいち至らずこの方法を知るまで、static publicで頑張ったり、1つのコードのまとめちゃったりなどをやっていました。 今考えれば馬鹿なことをしていたなあ…。 JavaScriptから、C#のコードにアクセスできるの? 結論から言うと、出来なくないらしいけど僕は出来ませんでした! とある人曰く「Pluginsフォルダを作ってそこに入れれば、アクセスできるよ」という情報をもらいました。 しかし、やってみるとコンソールで赤が出まくり、「あーもうやだ!アクセスする部分はC#で書くわ!」となりました。 その結果、JavaScriptとC#が混同するプロジェクトとなってしまったのがとても残念です…。 現状のものはデモなので、きちんとしたものを作る場合はC#で統一しよう、と固く心に誓っています。 壁はあれども、使いやすいUnity まだまだたくさんあるのですが、強く覚えているものは以上です。 知識のなさゆえにぶつかることも沢山ありますが、Unityは使いやすく、作ってゆくのが楽しい開発環境です。 この僕の転んだ例を見て、スムーズな滑り出しができることを祈ります。
アバター
こんにちは、上津原です。 Oculus Riftというヘッドマウントディスプレイがあります。 以前紹介しました。( http://nextdeveloper.hatenablog.com/entry/2013/12/05/102157 ) それを利用し3Dデータで作られた部屋をバーチャルリアリティで内見できるアプリケーション「Room VR」を開発しました。 オプションとして 目線の高さ変更 建物の高さ変更 朝昼夕晩の光さし方の確認 物の移動、配置、削除 などの機能も盛り込んでおり、これを利用することにより、子供目線の確認、モノの搬出入、光のさし具合のシミュレーション、部屋の高さによる風景のシミュレーションなどが可能になります! それを開発するにあたって、Unityや3D完全初心者の私がアプリケーションを開発する間、何を調達して、何を学び、どうすれば作れたかをまとめていきたいと思います。 開発期間は2ヶ月半。前提のスキルはiOSアプリ開発とPhotoshopです。 まずは準備編ということで、環境と必要なもの、参考になるものを挙げていきます。 これからOculusRiftで開発をする人の参考になれば幸いです! 開発環境 実際開発に使った環境は以下です Windows7 OculusRift Unity Pro XboxController(コード付き) HDMI - USB変換ディスプレイアダプタ ひとつひとつについて説明していきます。 Oculus Rift(約¥36,000) https://www.oculusvr.com/order/ 公式サイトで申込みます。$300+送料がかかります。 購入方法は、以下を見ながら買うとわかりやすいです。 http://www.ocufes.jp/2013/10/175/ 私が購入した時は、3週間位で届きましたが、最近はもう少し早く届くようです。 Unity Pro(¥157,500) https://store-jp.unity3d.com/ OculusRiftを購入すると、UnityProの体験版コードがが4ヶ月分ついてきます。 なので、開発を始めるには購入する必要はありません。 体験期間が切れても開発を続ける場合は、Unity Proの購入が必要です。 Xbox360 Controller(¥2,982) http://www.amazon.co.jp/gp/product/B004DL20UU Windowsの場合、OculusのSDKがXbox360コントローラに対応しています。 Macの場合は対応していないので注意が必要です。 Xbox360コントローラを利用する場合、有線のものを買う必要があります。 無線のXbox360コントローラを有線にするようなものが売っていますが、あくまで電池充電器であって、USB接続コードではありません! 実際に間違って買って動かないのを確認しました…。無駄な出費…。 利用の場合は、きちんと有線のものを買いましょう。 HDMI - USB変換ディスプレイアダプタ(¥6,800) http://direct.sanwa.co.jp/ItemPage/500-KC007 DVI端子がなかったため、購入しました。 fpsが落ちるかな?と思ったのですが、今のところ違和感なく動いています。 DVI端子、またはHDMI端子があるPCには必要ありません。 初期費用合計は ¥45,782となりました。(UnityProは購入してません) コントローラもUSB変換も使わないよ、という場合は、Oculusの¥36,000だけになりますね。 言語 経験があったのでJavaScriptを使いました。 ただ、OculusのSDKはC#で書かれているため、途中で「C#で書けばよかったな」と思いました。 JavaScriptでも開発はできますが、C#の理解があったほうがいいには変わりないです。 学習 まずは本で学習し、そこから必要な物をWebで調べながら実装する感じで作りました。 読んだ本 Unity入門 ~高機能ゲームエンジンによるマルチプラットフォーム開発~ http://www.sbcr.jp/products/4797365337.html Unityの基本の動きを確認するためにひと通り動かしました。 参考にしたサイト Unity公式ドキュメント http://japan.unity3d.com/developer/document/ 詳しく内容を知りたい場合やチュートリアルなどお世話になりました。 ActinScript入門Wiki http://www40.atwiki.jp/spellbound/pages/1303.html 検索するとしょっちゅう上に出てくる。 基本的な部分を押さえるのにお世話になりました。 助けてもらったサイト Unityユーザー助け合い所 https://www.facebook.com/groups/unityuserj/ ほんとうにわからない部分はこちらで質問しました。 必ず回答が来るというわけではないですが、助けていただける率は高いと思います。 3Dモデリング 素材では足りないものも多かったため、一文のオブジェクトはモデリングソフトを使って作りました。 メタセコイア http://metaseq.net/jp/ プロ版の体験版にFBX出力があるので、それを利用しモデリング。 素材 開発段階では簡単な素材で作るのが楽で良い感じ。 Sketch Up 3Dギャラリー http://sketchup.google.com/3dwarehouse/ からColladaデータをダウンロードして FBX Converter http://usa.autodesk.com/adsk/servlet/pc/item?siteID=123112&id=10775920 を使ってFBXに変換。 以上が準備したものです。 一気に並べると結構たくさんあるように見えますね。 とりあえずは、Oculusを注文して、届くまでの間にUnityの勉強を…というのがいいと思います。 メタセコイアは、実装が殆ど済んでから最後に触り始めたので、最初の段階では必要ないかと思います。 まずは素材を使って実装をし、そこから思ったものを表現したいときに、メタセコイアなどは触り始めればいいと思います。 さて、これで準備ができたら、次はレッツ実装編です!
アバター
大坪と申します。2/27から行われました インタラクション2014 というカンファレンスに参加しました。ここではそのうちのインタラクティブ発表(1日目及び2日目)について「なにがしか書きたくなったもの」について書きます。     ちなみに私は人気のあるデモに並んででも体験する、という忍耐力を持ち合わせないので例えば論文賞を受賞した研究のデモは遠目に観ただけで体験しておりません。結果としてここにも書けないのでした。他の人気があった発表も以下同文です。またひたすら勉強になったものについても書いておりません。発表者については論文のFirst Authorの方のみ挙げております。   --- SenseChairを用いた眠気検出に関する検討:阪大 宮崎さん   椅子にセンサーを仕込んでおいて、ユーザの姿勢変化を検出しそこから眠気を察してやろう、という研究。「椅子にセンサー仕込む研究っていくつもあったような気がするけどどこが新しいですか」と聞いててみればなんと「実際に研究室の学生の椅子に設置し、居眠りしている学生の姿勢をデータとして取得。その上で識別特徴量とかアルゴリズムを設定」とのことでした。   いや、すばらしい。私のようないい加減な人間がこうした研究を行うと   「被験者10名について、”居眠りしたつもりになって”もらいデータを取得しました。判定精度は、、」   とかやりがちです。しかしそれはあくまでもfakeでしかない。私はこのように現実に接地した研究に感動します。でもって気になったので   「学生さん、寝てくれましたか?」   と聞くと「なかなか寝てくれませんでした。一人よく寝る学生がいたので...」と率直に語ってくれたも印象的。   --- 交通事故低減のための運転者状況共有システム :ATR 内海さん   車内にカメラおいて、映像解析結果から運転手の状況を推定しようという研究は多くあります。この研究はそこから一歩進み、例えば   「今ドライバーは眠くなってますよー」   といった情報を車の外に対して提示してやろう、というもの。たとえば車の上に目玉がついていて、それが眠そうな目になるとか。   ユーザにアンケートとったところ、そうしたドライバーの情報について周りと共用することについて結構皆さん抵抗が無い、ということでした。では実際に事故とか起こった時不利な証拠として採用される可能性もあるのではないか、それでも情報を共有したいと思うのか、と聞いた所   「どんな相手と共有するか、というのを細かくアンケートと撮ったところ、警察相手というと受容度が下がった」   とのこと。やはりこうした試みが現実世界にでていくといろいろあるんだろう、と想像しました。   --- マグネイル:爪装着型磁石を用いたインタラクション:お茶の水女子大 門村さん   個人的には一番面白かった発表。女性はネイルアートというかとにかく爪にあれこれつけてデコレーションすることがあります。そのデコレーションに磁石を仕込んでやる。   でもってスマホの地磁気センサーの情報を「ものすごくがんばって」解析処理すると磁石を仕込んだ爪がどのあたりにあるのかがわかる。その情報を使えば、たとえば中指が伸びているか曲がっているかでお絵描きソフトの「描画モード」と「消去モード」を切り替えるなんてことができる、というもの。   何よりも既存のスマホに何も付加する必要がなく、かつ操作側にも(ネイルアートに抵抗がない人であれば)あまり負担をかけることがなく新しい入力を実現しているところがすばらしい。しかし一番感動したのは、学生といっしょに自らつけ爪をつけデモをしていた椎尾教授(50代♂)の姿でした。   --- Do-seiさん: 「やっておいたよ」メッセージの書き置きによる架空エージェントの存在感演出:京都工繊大 髙島さん   人間とコミュニケーションをするエージェントに関する研究は多い。でもたいていの場合それらのエージェントはなんらかの姿を持っている。(男性が作るときは、目の巨大な女性にすることが多かったりする)しかしあえてそうした姿を持たせず、エージェントは相手が帰宅したときに「置き書き」だけを残す。そうして情報を絞ることによりユーザの妄想を誘う、というのが狙いの研究。   私が説明を聞いているとある人が「例えば、夜遅く帰ってきたのだったら”大丈夫?疲れていない”とかメッセージに含めると面白いのではないか」とコメントをしていました。こういう話を聞くと私は意見を述べずにはいられない。   「そうした”状況に応じた人間との対話”は一見面白そうに見えるし、こういうデモの場では受けるのだが、長い間使うとユーザは飽きてしまう。バリエーションがないことがわかるからだ。人間を飽きさせない「自然な会話」というのはそれだけで大変な話。逆にもっと情報を削り、ユーザの妄想をかき立てるほうがいいのではないか。一番怖い物は形のない恐怖だと言う。それは形を持たせないことにより、人間が勝手に妄想を膨らませるからだ。同じように考えると、置書きとして文章を出すより例えば単語だけ提示してユーザに妄想させるのがいいのではないか」   概略このようなことを述べました。発表された方は大変素直かつ柔軟性のある方で、このように見学者が(好き勝手に)真逆の意見を述べていても、きちんと聞いて対応してくれたのが印象的。   --- SoundShare: アドホックな情報共有のためのグルーピングの手法:東京工科大 依田さん   「グループで簡単に写真を共有する」ためにはなんらかの方法で「共有場所」の情報をそれぞれのスマホ(でなくてもいいのだけど)に教える必要がある。実際世の中にはそうしたシステムはいくつもあるのだけどなかなかちゃんと動かない。やれパスワードを忘れた、やれ無線がつながらない、そもそもBluetoothって何のこと、とか。   というわけで一番「原始的」ともいえる「一台のスマホから共有場所の情報を乗せた音を出す。他のスマホはその音を解析することで共有場所の情報を得る」というもの。ピーヒャラーと鳴る音を聞いていると、昔なつかし「モデムの音」を思いだす。そう感想を述べると、この研究の発表者自身はその音を聞いた事は無いが、同じような感想を述べた「おじさん」がいたとのことでした。   いろいろ突っ込みどころはあるかもしれませんが、着眼点は素晴らしい。これだけWebサービスだなんだ、があるにも関わらず「集まったグループで写真を簡単に共有する」というのは未だに解決されていない問題です。解決策は世の中に山ほど提案されているがどれも肝心な時に動作しない。結局現実世界で一番確実に動くのは「原始的な手法」ではなかろうか。確かに電波の悪い場所であっても、スマホである限り「音を出し、かつそれを取得する」ことは可能なはず。またQRコードを使うのに比べても、一度に複数の端末に情報を転送できる、というメリットがあります。   説明を聞いているうち、3年前を思い出しました。インタラクション2011の二日目、東日本大震災の時に無線接続のインターネット、電話は全く役に立たなくなりました。それまでカンファレンスで聞いていた「素晴らしいシステム」はもちろん、数々のWebサービスは使えなくなり結局「時代遅れ」の公衆電話に長い列ができたわけです。私が新しいシステムに対して「現実世界に接地している」ことを強く意識するようになったのはあの時から,,というのは嘘です。   --- Pay4Say: 貨幣制度を導入したビデオ会議システム:北陸先端大 永井 さん   会議で「声が大きい人だけが発言する」という現象は大学でも会社でも起こりうるわけです。でもってそれを平準化するため   ・参加者は発言コインを持っている   ・実際に発言すると、発言コインが、誰かに贈与できるコインに変換される   ・贈与コインは(匿名で)誰にでもあげることができる。贈与コインも発言によって消費される   というシステム。面白いと思ったのは   「声だけ大きくて内容がからっぽの発言をする人」   が、このシステムを使うと可視化されるのではないか、と思ったから。上司とか教授がこのシステムであっというまに発言を封じられたりすると、結構複雑な人間関係を浮き彫りにさせられるような気がします。   そういう具体例はないのか?と尋ねたところ、あまりない、との返答。おそらくこの研究をされた方の研究室には理性的な人がそろっているのでしょう。   --- CCC: 振動機能を応用した携帯端末での個人認証における覗き見攻撃対策手法の提案:電通大石塚さん     携帯端末のパスワード入力は覗き見されると容易にばれてしまう。それを防ぐため、端末の振動機能を使おうという試み。   聞いていて   「そういえば、サッカーワールドカップの予選組み合わせ抽選も全世界にTV中継される中で行われるけど、温度を使ったり、振動を使ったりいろいろ不正な行為があると聞いた。それと通じるものがあるなあ」   と思いました。逆に「サッカーワールドカップ抽選から発想を得た”覗き見されない”認証手段」なんて研究も有りかもしれません。   --- というわけで、最近とみにインタラクティブ発表が好きな私です。なんといっても研究を実際に行った人と様々な議論ができるのがうれしい。などと喜んでいると   「踊るなんとかに観るなんとか」   という言葉が頭をよぎる。というわけで次にインタラクションに参加することがあれば、何か持って行く可能性がないとは必ずしも言えないのでした(弱気)
アバター
大坪と申します。人前でプレゼンテーションをする機会というのは、私が若いころに比べてずいぶん増えたように思います。 でもって「プレゼンのコツ」なるものも世の中に山ほど出回っている。フォントは24ポイントだか40ポイントだとか、あるいは箇条書きにしろとか、スライドのデザインはスッキリしろとか。 しかし 私見ではそれらは末端の些事にすぎない。フォントを40ポイントで統一しようが、高い金を払ってプロのデザイナーに「スライド」を作ってもらおうが、プレゼンテーションの基本原則を考えていないプレゼンは駄目プレゼンです。(きっぱり) PowerPointだかそれに類するソフトウェアを立ち上げる前に、一歩引いて考えましょう。そもそもプレゼンテーションは何のためにするのでしょうか?ネットで検索すると、 良い言葉にいくつも出会えます がここでは以下の定義を使います。 定義:プレゼンの目的は「売り込み」である つまり聴衆が「あなたが売りたい物について話しを聞く気にさせる」のがプレゼンの目的です。ここで「売る」というのは別に金銭の移動を意味しているわけではない。「これはよい」「面白い」と思ってもらえれば、売り込みは成功です。しかしここでプレゼンと似て非なる「説明」というものの存在について考えなくてはならない。 「説明」とは何か。それは既に「買う気になっている相手」に細かい条件等を話すことです。主たる目標は情報を伝えることであり、「売り込み」ではない。この区別は時として曖昧になりがちであり、ほとんどの場合は両者が混じっています。しかしこの定義を疎かにすると後の「良いプレゼンを行うための行動」が全て的はずれなものになってしまう。 貴様ごときチンピラエンジニアが何を言うか、と思われる方もいると思うので別の方の言葉を引用しましょう。 これらの事の本質は実にシンプルで「自分のいいたいことが相手に伝わり、共感してもらえなければ、そもそもプレゼンする価値がない」という至極当たり前のことである。その観点にたてば、それまでの自分の説明というのはおおむね「聞いて理解しようと努力してくれる人にたいして、各種データを提供していただけ」といことを、その後ベインでいろいろなプロジェクトに関わり、たくさんのプレゼンをこなす中で、徐々に理解するにいたるのであった。 スティブージョブス驚異のプレゼン P386 解説外村仁氏 上記引用文中で「自分のいいたいことが相手に伝わり、共感してもらえる」というのが「売り込み」であり、「聞いて理解しようと努力してくれる人にたいして、各種データを提供する」のが「説明」です。 最近聴力がだいぶ衰えてきたので、異論は聞かなかったことにし上記の定義を元に考えましょう。例えばあなたが感動的なスライドの束を作り上げたとして、それをどこかに置いておけば皆がそれを読み、あなたのところに「もっと詳しい話を聞かせてほしい」と連絡してくるでしょうか? 例えば ポアンカレ予想を証明したペレルマンの論文 はそうしたものだったかもしれません。彼はインターネットサイトに論文を投稿するだけで、驚くほどの「売り込み」に成功したのです。しかしもしあなたがポアンカレ予想よりは多くの人に着目されていない問題に対して、ペレルマンの論文ほど画期的でない事柄を述べようとするのなら、別の方法を考える必要がある。 別の方法とはなにか?プレゼンテーションです。プレゼンテーションを行う機会を与えられた、ということは、単に「だれでもアクセスできる場所に文章を置いた」のとは違い、忙しい聴衆があなたの「売り込み」を聞くことに貴重な時間をコミットしてくれた、ということです。 であれば プレゼンターは「自分の言葉で語りかける」必要がある。人間に対して売り込みができるのは人間だけです。紙や画面に映しだされた文字ではない。 つまり プレゼンテーションは「プレゼンターが聴衆に語りかける場」なのです。聴衆が配布された印刷物を読む場でも、画面に映しだされた文字を読む場でも、ましてやその文字を朗読するのを聞かされる場所でもない。聴衆は「プレゼンターから言葉を聴くために」きているのです。 こう考えると次の事がわかります。 プレゼンは朗読会ではない=「しゃべる内容」を画面に書いてしまうこと、これはプレゼンテーションを行う上で根本的な間違い 大事なことなのでもう一度書きます。プレゼンは朗読会ではない。あなたがいかに美声の持ち主であり、すばらしい声の表現力を持っていたとしても、画面にかかれた文字を朗読することで「なるほど、これは素晴らしい内容だ」と考える聴衆はいません。読んでわかる内容なら、聴衆は時間をコミットする必要はない。資料を送ってもらう(そしてほとんどの場合そのまま削除する)だけで十分です。 たとえばよく見かけるこんな「スライド」。これは「売り込み」という観点からは間違っている。 なぜ間違いか?このスライドがあればプレゼンターは要らないからです。 いや、そういっても書いておけば何かいいことあるかもしれない、と考える人もいるでしょうがそうではない。「しゃべる内容を書いた画面」を目にした途端聴衆はプレゼンターの言葉を聞かず自分勝手なペースでその文字を読み出します。さらに悪いことに、ほとんどの人はプレゼンターが朗読するより早く文字を読み終えますから、 「もうわかったからさっさと次の話しを聞かせてくれ」 と内心いらいらしながら朗読が終わるのをまつことになる。 このことから、私は次のように主張したい。 「スライド」を印刷して配布したり、SlideShareにアップロードしてすむようなら、人前でしゃべる資格はない これについては少し補足が必要です。喋った後に「より細かい資料」を公開して読んでもらうのはとてもいいことだと思います。しかしそれとてプレゼンという売り込みが成功してからの話。つまり「プレゼンテーションを印刷」とか「プレゼンテーションをアップロード」というのは(プレゼンテーションの音声も合わせて記録した動画、あるいはそれに類する形式 *1 でない限りそもそも意味を持たない行為なのです。 さらに次の事実も合わせて考える必要がある。 事実:聴衆がプレゼンの間に受け取れる情報量は信じられないほど少ない まずプレゼンの定義を思い出しましょう。プレゼンを行う時点では、そもそも聴衆はあなたが売り込もうとする内容について「ふーん」くらいの期待しか持っていないわけです。よくてどんよりした目を前方に向けている、悪ければ寝ているかもしれません。 そういう姿勢でいる相手がどれくらいの情報量を受け取れるか、というのはお昼ごはんの後の講義で、例えば15分の間にどれくらい先生の話が頭に残っていたかを思い出せばぼんやり想像できると思います。 あるいはこう考えてもいいかもしれません。私が昨年参加したWISSというワークショップでは、六頁の論文を書きます。プレゼンの時間は15分。この論文を最初から朗読するとどれくらい時間がかかるでしょうか?一度やったことがあるのですが、初めて聴く聴衆が理解できるようにゆっくり読み上げると軽く2倍の時間がかかります。つまりあなたが文章で表現したい内容に対して最高でも半分しかしゃべることができない。聴衆の記憶に残り、興味を持ってもらうためにはそれ以上に内容を削らなければならない。 ですから 「プレゼンテーションの準備」をする際にまず考えるべきなのは 自分は何を売り込みたいのか それをわかってもらうために、エッセンスをどう抜き出し、どのように語ればよいか これらをきちんと考えることが第一に必要。それを飛ばして「プレゼンの準備をしよう」といって最初にPowerPointを立ち上げたとすれば、その時点で失格。そもそも自分が何を売り込みたいのかわかっていないのに、「スライド」をいくら作っても、誰も聞いてはくれません。 ここまで書いたことを踏まえ、最後にやりがちな間違いについて書いておきます。 自分がやったことを順序良く説明するのは、プレゼンテーションではない。 それは「説明」です。もっとも相手が「そうした説明」を期待している場合は別ですが。いや、俺(もしくは私)はこんなに苦労して結果をだしたのだから、その過程を克明に語る資格があるし是非聞いてもらいたい、というなどと考えたとすれば、それは間違っています。以下の文章を読みましょう。 ──ではよくある“ダメなプレゼン”はなんでしょう? 自分のことばかりを語るプレゼンですね。最初に触れた通り、自分が話したいことではなく「相手が聞きたいこと」を心に響くストーリーに仕立てて話すことがいちばん重要なことです。 引用元: 五輪招致戦略コンサルタント、ニック・バーリー:「世界を動かすプレゼン力を伝授します」 « WIRED.jp あなたがものすごい有名人であるか、あるいは観客全員が何らかの理由によりあなたに恋しているのでなければ、誰も「あなたがどんな人で何を考えているか」なんかに興味を持っていません。観客は自分が聞きたい事を聴くために、そうした「自己中心的」な理由のためにそこにいるのです。相手が何を聞きたいと思っているのか。それを考えることが第一歩。 以上が私が考えるプレゼンテーションの「原則」です。原則は原則であって、全ての場合の真理ではない。Apple原理主義者といえどもWindows上でC#でプログラムを書かざるを得ないこともある。上司の求めに応じ、PowerPointで作った「説明資料」を読み上げなくてはならない場合もあるでしょう。 しかしながら 原則とその応用形を混同してはならない。プレゼンテーションの原型はプレゼンターが聴衆にしゃべりかけ、どうしても必要な場合だけビジュアルエイドと呼ばれる図表を用いるものでした。しかしソフトウェアを用いてスライドを作ることが容易に行われるようになった今、プレゼンテーションの主役がプレゼンターではなく「スライド」になっているのではないか。 「明日のプレゼンの用意全然できてないよ。早くスライド作らなくちゃ」 という言葉を聞く事は多い。この言葉は暗黙のうちに「プレゼン=スライド」という等式を想定しているかのようです。しかし私見ではこんなことを言っている時点でプレゼンは聞くに値しないものになっている可能性が高い。 私はPowerPointとかの「スライド」作成ソフトの使用を否定するものではありませんが、それらがもたらす問題についてはよく考える必要があります。ではそれは何なのか、については以下次号(次号があればの話ですが) *1 : 任天堂がサイトに掲載している 決算説明 がこの例です
アバター