Oculus Rift Advent Calendar 2017の7日目の記事です。
自分はエンジニアではないのですが、趣味でOculus Rift用のTwitterクライアント的なアレを開発してまして、「ツイッター、ユニティちゃんが通知してくれたら、素敵やん?」と思った結果が以下になります
通知にユニティちゃんの反応をつけつつAnimatorの勉強中… #VR用Twitterクライアント pic.twitter.com/uHPdAp3knW
— 人生 (@toofu__) July 8, 2017
フォローされたりツイートがいいねされたりすると喜んで教えてくれ、逆にいいねを外されたりすると悲しみながら伝えてくれます。
ユニティちゃんも好きですが、SDユニティちゃんが可愛くて好きです。
はじめに
実装としては自前のTwitterAPIラッパーを使い、TwitterのUser Streamに接続しています。
世の中ではTwitterが有料APIを発表したり、User Streamの廃止がアナウンスされていたりしていますが、魂はまだUser Streamを求めています。
なお、動作確認はUnity 2017.1.0f3で行っており、中途半端に古くて申し訳無さがあります。
やりかた
こんな風に書くのが良いのかどうかよくわかりませんが、全体のクラス関係はこのような感じです。
- TweetEventHandlerがUser Streamへの接続を行い、
- 返ってきたレスポンスに応じてイベントメッセージをNotificationHandlerに渡す
- NotificationHandlerはイベントメッセージに応じてNotificationPanelとNotificationUnitychanのpublicメソッドを叩く
という流れでユニティちゃんから通知をもらいます。ちなみにフキダシをユニティちゃんに直接紐付けなかったのは、ユニティちゃんの動きに沿ってフキダシが動いたりするのが読みづらかったからです。
User Streamに接続する
GitHubのReadMeに書いてあるとおりです。
Twity.Stream stream;
void StartMyStream()
{
stream = new Twity.Stream(Twity.StreamType.User);
Dictionary<string, string> streamParameters = new Dictionary<string, string>();
StartCoroutine(stream.On(streamParameters, OnStream);
}
private void OnStream(string response, Twitter.StreamMessageType messageType)
{
// 後述
}
User Streamから流れてきたメッセージを判別する
ここもGitHubのReadMeに書いてあるとおりなんですが一応説明すると、User Streamのコールバックにはレスポンス本文(string)とメッセージの種類(Twitter.StreamMessageType)が引数として渡されます。
https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/streaming-message-types
public enum StreamMessageType
{
Tweet, // A Tweet has been posted.
StatusDeletionNotice, // A given Tweet has been deleted.
LocationDeletionNotice, // Geolocated data must be stripped from a range of Tweets.
LimitNotice, // A filtered stream has matched more Tweets than its current rate limit allows to be delivered.
WithheldContentNotice, // Either the indicated Tweet or indicated user has had their content withheld.
DisconnectMessage, // Streams may be shut down for a variety of reasons.
StallWarning, // current health of the connection (required "stall_warnings" parameter)
FriendsList, // Upon establishing a User Stream connection
FriendsListStr,
DirectMessage, // send or receive Direct Messages
StreamEvent, // Notifications about non-Tweet events. Check "event_name".
None // Error or no response
}
主に返ってくるのはFriendsList
、Tweet
、StreamEvent
の3種類です。
- FriendsList:Userstreamの接続が成功したときに返ってきます。型は
Twitter.FriendsList
。 - Tweet:フォローの誰かがつぶやいたときに返ってきます。型は
Twitter.Tweet
。 - StreamEvent:自分が誰かをフォローしたり誰かにフォローされたりツイートがいいねされたり、いろんなシチュエーションで返ってきます。型は
Twitter.StreamEvent
。
このTwitter.StreamMessageType
に応じて返ってきたstringをJsonUtilityでオブジェクトにします。
void StartMyStream()
{
// 先述
}
private void OnStream(string response, Twity.StreamMessageType messageType)
{
try
{
// Tweetが送られてきたときはそれをパネルにして表示する
if(messageType == Twity.StreamMessageType.Tweet)
{
Twity.Tweet tweet = JsonUtility.FromJson<Twity.Tweet>(response);
// 詳しく述べませんが、ツイートを表示する処理
if (!TweetPanelManager.isCurrentDisplayed(tweet))
{
TweetPanelManager.AddToCurrentList(tweet);
GenerateTweetCard(tweet);
}
}
// StreamEventが送られてきたときはNotificationHandlerに送る
else if (messageType == Twity.StreamMessageType.StreamEvent)
{
Twity.StreamEvent streamEvent = JsonUtility.FromJson<Twity.StreamEvent>(response);
GameObject.Find("NotificationHandler").GetComponent<NotificationHandler>().ShowNotification(streamEvent);
}
// FriendsListが送られてきたときは特に何もしない(ちゃんと返ってきたかどうかだけ確認する)
else if (messageType == Twity.StreamMessageType.FriendsList)
{
Twity.FriendsList friendsList = JsonUtility.FromJson<Twity.FriendsList>(response);
}
}
catch (System.Exception e)
{
Debug.Log(e.ToString());
}
}
NortificationHandler
というクラスで通知に関する諸々を制御しているので、そこのpublicメソッドであるShowNotification
にTwitter.StreamEvent streamEvent
を渡します。
StreamEventに応じてユニティちゃんに通知をもらう
通知は「フキダシ」と「ユニティちゃんの動き」の2つで表現します。どの種類のStreamEventを渡されたかによって、フキダシは表示するテキストが変わり、ユニティちゃんは喜ぶか悲しむかが変わります。
StreamEventの種類も https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/streaming-message-types に載っています。いっぱいありますが、今回はfavorite
、unfavorite
、follow
の3つを考えます。
- favorite:自分が誰かのツイートをいいねした、または誰かが自分のツイートをいいねした
- unfavorite:自分が誰かのツイートをからいいねを外した、または誰かが自分のツイートからいいねをはずした
- follow:自分が誰かをフォローした、または誰かが自分をフォローした
ちなみにunfollow
は自分が誰かのフォローを外した場合だけが取得でき、自分へのフォローが外されたことはなぜか取得できません。また、リツイート関係も取得できません。
フキダシのテキスト
フキダシはそれ用のPrefabを作っておいて、通知があるたびにそれを生成する形にしています。
public void ShowNotification(StreamEvent streamEvent)
{
GameObject notificationPanel = Instantiate(
notificationPanelPrefab, // フキダシっぽいパネルをPrefab化しておく。NotificationPanelというクラスをAttachしておく
transform.localPosition, // NotificationHandlerはフキダシ出したい場所あたりに配置しておく
transform.localRotation
);
//フキダシの初期化処理
notificationPanel.transform.SetParent(transform);
notificationPanel.transform.Translate(new Vector3(0.16f, 1f, 0));
notificationPanel.GetComponent<NotificationPanel>().streamEvent = streamEvent;
notificationPanel.GetComponent<NotificationPanel>().Init();
// ユニティちゃん(後述)
// ...
}
ちなみに、StreamEventは「自分が誰かをフォローした」というような自分の行動も通知として流れ込んできます。自分の行動は通知してもらってもしかたないので除外する必要があります。
{
"event_name":"EVENT_NAME",
"created_at": "Sat Sep 4 16:10:54 +0000 2010",
"target": TARGET_USER, // event_nameに記載される行為の対象となった人。followならフォローされた人
"source": SOURCE_USER, // event_nameに記載される行為を行った主体。followならフォローを行った本人
"target_object": TARGET_OBJECT // event_nameに記載される行為の対象物。favoriteならTweetが入る。followならnull
}
StreamEventの中身自体はこうなっているので、source
が自分の場合は何も通知しないようにします。
(さらにちなみにですが、TwitterのUser Streamで返ってくるメッセージ自体はevent_name
ではなくevent
というキー名になっています。しかしC#ではevent
が予約語のため、このライブラリではevent_name
という名前に置換して使っています)
public Twity.StreamEvent streamEvent;
public void Init()
{
bool isMyAction = streamEvent.source.id == "自分のid" ? true : false; // 自分の行動かどうかの判別
if (streamEvent.event_name == null) return;
if (isMyAction) return; // 自分の行動だった場合は何もしない
Tween(true); // フキダシが出てくるときの拡大の動き。ご自由に
transform.Find("Panel/Text").GetComponent<Text>().text = notificationText(streamEvent); // GameObjectの親子関係は適宜直してください
}
private string notificationText(Twity.StreamEvent streamEvent)
{
return String.Format(
eventNameDictionary[streamEvent.event_name],
streamEvent.source.name,
streamEvent.source.screen_name
);
}
private Dictionary<string, string> eventNameDictionary = new Dictionary<string, string>()
{
{"favorite", "あなたのツイートが{0}(@{1})さんからいいねされたよ!"},
{"unfavorite", "あなたのツイートがいいねからはずされちゃった…"},
{"follow", "{0}(@{1})さんにフォローされたよ!"}
};
これでユニティちゃんが通知してくれるフキダシができました(上の動画ではフォローしてくれた相手などは表示していませんでしたが、このコードだと表示されます)。
ユニティちゃんの動き
ユニティちゃんにはNotificationUnitychan
というよくわからない名前のクラスをattachしています。
動きとしては簡単(というかデフォルトの)ジャンプと悲しみの動作に絞って、StreamEventがfavorite
、follow
のときはジャンプして喜び、unfavorite
のときは悲しむようにしました。それぞれの場合に"Jump"、"Sad"というStateをNotificationHandlerからNotificationUnitychanに送ります。
public void ShowNotification(Twity.StreamEvent streamEvent)
{
// フキダシ(先述)
// ...
// ユニティちゃん
unitychan.GetComponent<NotificationUnitychan>().Response(CheckUnitychanResponse(streamEvent));
}
// streamEventに応じて"Jump"か"Sad"のどちらかを返す
private string CheckUnitychanResponse(Twity.StreamEvent streamEvent)
{
List<string> eventNameForUnitychanJump = new List<string>() { "favorite", "follow" };
List<string> eventNameForUnitychanSad = new List<string>() { "unfavorite" };
if (eventNameForUnitychanJump.IndexOf(streamEvent.event_name) != -1)
{
return "Jump";
}
else if (eventNameForUnitychanSad.IndexOf(streamEvent.event_name) != -1)
{
return "Sad";
}
else
{
return null;
}
}
ユニティちゃん側の制御はたぶん普通のやりかた。
private Animator anim;
private AnimatorStateInfo currentBaseState;
static int idleState = Animator.StringToHash("Base Layer.Idle");
static int locoState = Animator.StringToHash("Base Layer.Locomotion");
static int jumpState = Animator.StringToHash("Base Layer.Jump");
static int sadState = Animator.StringToHash("Base Layer.Sad");
private void Start()
{
anim = GetComponent<Animator>();
anim.speed = animSpeed;
}
private void Update()
{
currentBaseState = anim.GetCurrentAnimatorStateInfo(0);
if (currentBaseState.fullPathHash == jumpState)
{
anim.SetBool("Jump", false);
}
else if (currentBaseState.fullPathHash == sadState)
{
anim.SetBool("Sad", false);
}
}
public void Response(string responseType)
{
if (!anim.IsInTransition(0))
{
anim.SetBool(responseType, true);
}
}
Animator自体はユニティちゃんに初めからついてくるものをちょこちょこいじっている程度です。そのあたりの知見は無です。
まとめ
こんな感じでユニティちゃんから通知をもらえるようになりました。正直Oculus Riftというか単なるUnityの記事なんですが、ただ画面上で通知をもらうのに比べると、目の前でユニティちゃんが喜んだりしてくれるのは圧倒的な"良さ"があります。
VR空間上でのUIは色々と試行錯誤がなされている感じですが、キャラクターを媒介としたUIも良いものだなと思いました。誰かの本で読みましたが、「人間は人間のためのインターフェースとして残っていく」ってやつは本当にありえると思います。
なお、VR用のTwitterクライアントは細々と作り続けている趣味プロジェクトなので、もしご指摘ご意見などあればどしどしいただけると嬉しいです。
なんとなく #VR用Twitterクライアント のプレイ動画を簡単だけど改めてつくった https://t.co/rSamWpzbHa
— 人生 (@toofu__) September 14, 2017
*この記事はユニティちゃんライセンス条項の元に作成されています。