もちっとメモ

もちっとメモ

もぐりのエンジニアが日々の中で試してみたことを気が向いたときに書き連ねていきます

Unityで外部テキストを読んで表示するノベルが作りたい

手っ取り早くUnityでノベルゲームを作りたい時は宴とかを使えばいいんでしょうが、お試しで軽くノベルゲーム的なのを作って見たいだけなので今回は見送り。

madnesslabo.net

じゃあデフォルトのAssetだけで作れんもんかと調べたらありました。やはり先人は偉大です。下記の記事にしたがって組んで見たら、おお!!すごい。テキストファイルに書いた内容がノベルゲームっぽく表示されてる!!しかも、ちゃんと@brでテキストが区切られていて//でコメントアウトされている。感動です。早速お借りさせていただきます。

tsubakit1.hateblo.jp

基本はこちらに書かれている通りに進めればできますが、個人的につまづいた事を備忘録的に記しておきます。

  1. Q.作ったスクリプトは何にアタッチすればいいの?
    A.スクリプトを動かしたいシーン上に適当に空のオブジェクトを作成して`ScenarioManager.cs`をアタッチします。すると自動的に`TextController.cs`も付随してアタッチされるはずです。あとはInspectorビューで`ScenarioManager.cs`のLoadFileNameにシナリオを書いたテキストのファイル名(拡張子は不要)を入力します。`TextController.cs`にはUiTextをアタッチします。これはその1と同じです。

    tsubakit1.hateblo.jp

  2. Q.`ScenarioManager.cs`のUpdate関数の`m_commandController.PreloadCommand (m_scenarios [m_currentLine])`あたりで`NullReferenceException`が出て動かないんだけど。(その2のコメントにもあったやつです)
    A.commandControllerのインスタンスが見つからない的な事を言っていると思われます。なので`commandController.cs`をシーン上のオブジェクトにアタッチして見たら動きました。アタッチするオブジェクトは何でもいいんだと思いますが私は先ほどの`ScenarioManager.cs`をアタッチしたのと同じオブジェクトにアタッチしました。

せっかくなのでもう一工夫いじらせていただきます。我流魔改造なので出来具合はご愛嬌という事で。

メッセージが表示されるとポポポポというSEを鳴らす

kan-kikuchi.hatenablog.com

オーディオ制御のコアの部分はこちらで紹介されているAudioManagerを利用させていただいてます。こちらのAudioManagerを使って`TextController.cs`のテキストが送られる部分をこんな感じに改造します。


    public void SetNextLine(string text){
		AudioManager.Instance.StopChargeSE ();//すでにポポポポがなっていたらの停止
		AudioManager.Instance.PlayChargeSE ("serifSE");//新しくポポポポを鳴らす
		currentText = text;
		timeUntilDisplay = currentText.Length * intervalForCharactorDisplay;
		timeElapsed = Time.time;
		lastUpdateCharacter = -1;
	}

上で紹介されているAudioManagerではSEの途中停止はなかったのでStopBGMを参考にStopSEを自作しています。ほぼBGMをまるっとコピーして作っています。つまりStop可能なSEは一つだけですね。以下`AudioManager.cs`に足したStop可能SE部分を抜粋(名前がChargeSEになっているのは、はじめに作った時にチャージショット様に作ったためです。stopableSEとかにしておけばよかったですね)


    //ChargeSEのフェードアウト時間	
	public const float CHARGE_SE_FADE_SPEED_RATE_MAX = 0.9f;
	public const float CHARGE_SE_FADE_SPEED_RATE_MIN = 0.3f;
	//BGM用とSE用それぞれのオーディオソース
	private AudioSource _chargeSESource;
	//全オーディオを保持
	private SortedDictionary<string, AudioClip> _bgmDic, _seDic, _chargeSEDic;
	//作成したAudioSourceを取得し、変数にセットする。ボリュームもセットする
    for (int i = 0; i < audioSourceArray.Length; i++) {
		audioSourceArray [i].playOnAwake = false;

		if (i == 0) {
			audioSourceArray [i].loop = true;
			_bgmSource = audioSourceArray [i];
			_bgmSource.volume = PlayerPrefs.GetFloat (BGM_VOLUME_KEY, BGM_VOLUME_DEFAULT);
		} else if (i == 1) {
			_chargeSESource = audioSourceArray [i];
			_chargeSESource.volume = PlayerPrefs.GetFloat (SE_VOLUME_KEY, SE_VOLUME_DEFAULT);
		} else {
			_seSourceList.Add (audioSourceArray [i]);
			audioSourceArray [i].volume = PlayerPrefs.GetFloat (SE_VOLUME_KEY, SE_VOLUME_DEFAULT);
		}
	}

	//リソースフォルダから全てのBGM,SEを読み込み、セットする
	_chargeSEDic = new SortedDictionary<string, AudioClip> ();
	object[] chargeSEList = Resources.LoadAll (SE_PATH);

	/*
	* ChargeSE
	* 指定したファイル名のChargeSEを流す
	* ただし、すでに流れている場合は前の曲をフェードアウトさせてから流す
	* 第二引数のfadeSpeedRateにフェードアウトスピードの割合を入れる
	*/ 
	public void PlayChargeSE(string chargeSEName,float fadeSpeedRate = CHARGE_SE_FADE_SPEED_RATE_MAX){
		if (!_chargeSEDic.ContainsKey (chargeSEName)) {
			Debug.Log (chargeSEName + " can not be found");
			return;
		}
		//現在、再生されていない場合はそのまま再生
		if (!_chargeSESource.isPlaying) {
			_chargeSESource.clip = _chargeSEDic [chargeSEName] as AudioClip;
			_chargeSESource.Play ();
		}
		//違うChargeSEが流れているときは、流れているChargeSEをフェードアウトさせてから次を再生
		//同じChargeSEが再生中はpass
		else if (_chargeSESource.clip.name != chargeSEName) {
			FadeOutChargeSE (fadeSpeedRate);
		}
	}

	/*
	 * ChargeSEの停止
	 */ 
	public void StopChargeSE(){
		_chargeSESource.Stop ();
	}

	/*
	 * 現在再生中のSEをフェードアウト
	 * fadeSpeedrateでスピードを調整
	 */ 
	public void FadeOutChargeSE(float fadeSpeedRate = CHARGE_SE_FADE_SPEED_RATE_MIN){
		_isFadeOut = true;
	}

	/*
	 * 音量調整
	 * BGMとSEのボリュームを別々に変更and保存
	 */ 
	public void ChangeVolume(float BGMVolume,float SEVolume){
		_chargeSESource.volume = SEVolume;
	}

	public void ChangeSEVolume(float SEVolume){
		_chargeSESource.volume = SEVolume;
	}

これでメッセージと一緒にSEが流れる様になったのですが、テキストが表示され切った時点でSEを止めたいのでもう一手間加えます。テキストが表示され切ったかは`ScenarioManager.cs`の`if(m_textController.IsCompletaDisPlayText)`の部分で判定しているのでここに先ほどのStopSEを挟んでやります。


    void Update(){
	if (m_textController.IsCompleteDisplayText) {
		AudioManager.Instance.StopChargeSE ();
		if (m_currentLine < m_scenarios.Length) {
			if (!m_isCallPreload) {
    (以下略)

これでテキストが表示され切った時点でSEが止まる様になりました。

おまけでBGMも鳴らしちゃいましょう。BGMはシーンのスタート時に鳴らします。bgmはpublicで定義しておいて指定すればいいのですがたくさんシーンを作ると毎回打つのが面倒なので中に埋め込んじゃいます。その代わり辞書型で定義しておいてシーン名に応じて切り替えられる様にしました。


    private Dictionary<string,string> bgmList = new Dictionary<string, string> () {
		{ "Story1", "BGM1" },
		{ "Story2", "BGM2" },
		{ "Story3", "BGM3" }
	};

    void PlayBGM(){
		AudioManager.Instance.StopBGM ();
		AudioManager.Instance.PlayBGM (bgmList [SceneManager.GetActiveScene ().name]);
	}

    void Start(){
		PlayBGM ();
		m_textController = GetComponent ();
		m_commandController = GetComponent ();

		UpdateLines (LoadFileName);
		RequestNextLine ();
	}

シーンの切り替えエフェクトをつける

以前紹介したこちらを使ってやります。この記事の元ネタもテラシュールブログさんでしたね。いつもお世話になっています。

t-n-clark.hatenadiary.jp

tsubakit1.hateblo.jp

以前と同じ様に`ScenarioManager.cs`に`Fadeout`用の関数を作ります。 遷移時のSEはInspectorViewから指定できる様にpublicで定義しておきます。


	[SerializeField] Fade fade = null;
    public string changeSE;

    public void Fadeout(){
		AudioManager.Instance.PlaySE (changeSE);
		fade.FadeIn (1, () => {
			Invoke ("ChangeNextScene", 0.5f);
		});
	}
	
	public string nextScene;//次のシーンの名前

	void ChangeNextScene(){
		SceneManager.LoadScene (nextScene);
	}

そして、テキストファイルの中身が全て読み終わった時点で次のシーンに飛ばします。


    void Update(){
		if (m_textController.IsCompleteDisplayText) {
			AudioManager.Instance.StopChargeSE ();
			if (m_currentLine < m_scenarios.Length) {
				if (!m_isCallPreload) {
					m_commandController.PreloadCommand (m_scenarios [m_currentLine]);
					m_isCallPreload = true;
				}
				if (Input.GetKeyDown (KeyCode.N)) {
					RequestNextLine ();
				}
			} else {//テキストの中身が全て読みこまれていて、かつNのキーが押された時
				if (Input.GetKeyDown(KeyCode.N)) {
					Fadeout ();
				}
			}
		} else {
			if(Input.GetKeyDown(KeyCode.N)){
				m_textController.ForceCompleteDisplayText ();
			}
		}
	}

これでテキストが全て読み終わった後にフェードアウトして次のシーンに遷移できる様になりました。 これでだいぶノベルゲームっぽくなりました。RPGのシナリオシーンにも使えるんじゃないかな。

C++でYAMLが読みたいんや...読めました

最近、C++のコードをちまちまいじっていたところ、設定ファイルとかの外部から渡すファイルって階層構造にしたほうが作りやすいし、読みやすいんだよな、これをそのままC++で読ませたいな...と思い立って調べました。

結論から言うと無事読めるようになったのですが、結構苦労したので備忘録を残しておきます。

今回はこちらのyaml-cppを使わせていただきました。提供方法はバイナリファイルではなくソースコードなので自分の環境でビルドしてバイナリ化してから使う必要があります。本当は最新版を使いたかったのですが、ビルドがうまくいかなかったのでver0.3.0を使いました。ver0.3とver0.6では使い方が結構違うようなのでご注意を。

github.com

基本的なインストール方法は公式の通りです。日本語の解説記事はこちらが参考になります。

www.mk-mode.com

yomi322.hateblo.jp

C++ から YAMLファイルを読み込む « Stop Making Sense

 

ついでに私が行った手順を大雑把に手順を書いておきます。

  1. GitHubリポジトリからファイルをダウンロードする。今回はver-0.3.0を使用する(ver-0.6.0はうまくビルドできなかった)
  2. CMakeをダウンロードし、インストールする。今回はver-3.0.0-rc2を使用する。

  3. ダウンロードしてきたcpp-yamlの圧縮ファイルを解凍する
  4. 解凍したフォルダ内にbuildという名前の空フォルダを作成する(この中にcamkeしたバイナリを保存します)
  5. cmakeでsourceにCMakeList.txtが入ったフォルダを指定してcmakeする。今回はGUIでcmakeした。このとき、どのコンパイラでcmakeするかによって32bitか64bitかが決まるので注意
    1. GUIを使った場合
    2. srcにcpp-yamlのルートフォルダを出力先にbuildフォルダを指定してConfigureボタンをクリックする
    3. コンパイラとして何を使うか聞かれるので選択する。今回はVisualStudio2012がインストールされている環境で64bit版で使用したいのでVisual Studio 11 2012 Win64を選択
    4. 無事完了し、Configuring doneが表示されればOKなので、次にGenerateボタンをクリックする
    5. 無事Generating doneが表示されればOK
  6. buildフォルダ内にソリューションファイル.slnが生成されてるはずなのでVSでこれを開く
  7. プラットフォームがx64になっていることを確認し、ビルドモードをRelWithDebInfoに設定してビルドする。このときビルド対象にInstallを含めると適当な位置にライブラリを配備してくれるらしい。試していないので不明。
  8. 無事ビルドが成功し、Installを指定しなかった場合は、RelWithDebInfoフォルダ内の.libファイルを取り出してC++でスタティックライブラリを読めるように設定する。わからなければ以下を参考にする

    corgi-lab.com

    qiita.com

  9. include内のyaml-cppフォルダをごそっと組み込みたいソフトで読める位置に設定する。より具体的にはVSのヘッダーファイルの設定をする
  10. あとは公式にそって書いていけばよい。

これで下ごしらえは終わりです。早速実際に使ってみようと思うのですが、使い方にも癖があってymlファイルは特定の形式で書かれていないとうまく読んでくれないみたいです。まず、このような形式のymlファイルを用意します。内容は適当で意味はありませんです。 ファイル名も適当にinfo.ymlとでもしておきます。


- enemy:
    - name: enemy0
      hp: 100
      atk: 10
    - name: enemy1
      hp: 50
      atk: 20 
    - name: enemy2
      hp: 200
      atk: 30
    world: japan
    player: sashimimochi

次にymlの形式に合わせてC++を実装します。 基本的には参考にさせていただいたページの通りですが、複数の要素を持つ場合にループで回す部分を改良しています。 インクルードはするとして、


#include "yaml-cpp\yaml.h"

まず、受け取る情報に合わせて構造体を実装します。ymlのデータが階層構造になっていれば階層分作成します。


struct Enemy{
  string name;
  int hp;
  int atk;
} 
struct Info{ 
  vector<enemy> enemy;
  string world;
  string player; 
}

これを処理するオペレーターを用意します。オペレーターも作成した構造体分作成します。


void operator (const YAML::Node& node, Enemy& enemy){ 
  node["name"] >> enemy.name;
  node["hp"] >> enemy.hp; 
  node["atk"] >> enemy.atk; 
} 
void operator >> (const YAML::Node& node, Info& info){ 
  const YAML::Node& enemies = node["enemy"];
  for(int i=0;i<enemies.size();i++){ 
    Enemy enemy; enemies[i] >> enemy;
    info.enemy.push_back(enemy); 
  }
  node["world"] >> info.world;
  node["player"] >> info.player;
} 

そしてこれを読み込む関数を実装します。


void loadYMLFile(string ymlpath){ 
  string name;
  int hp; 
  int atk; 
  string world; 
  string player; 
  try{ 
    ifstream fin(ymlpath);
    YAML::Parser parser(fin);
    YAML::Node doc; parser.GetNextDocument(doc);
    Info info;
    for(int i=0;i<doc.size();i++){ 
      Info info; doc[i] >> info;
      for(int j=0;j<info.enemy.size();j++){
        name = info.enemy[j].name;
        hp = info.enemy[j].hp;
        atk = info.enemy[j].atk;
        cout << "name:" << name << "\n" << "HP:" << hp << "\n" << "ATK:" << atk << endl;
      } 
      world = info.world; 
      player = info.player;
      cout << "world:" << world << "\n" << "player:" << player << endl;
    }
  }catch(YAML::ParserException& e){
    cerr << e.what() << endl;
  } 
} 

無事ymlファイルに入力した情報がコンソールに出力されれば成功です。 まとめて書くとこうなる。 cpp-yaml-reader.cpp


#include "yaml-cpp\yaml.h"
#include <string>
#include <fstream>


struct Enemy{ 
    string name;
    int hp;
    int atk;
}
struct Info{ 
    vector<enemy> enemy;
    string world;
    string player;
}


void operator (const YAML::Node& node, Enemy& enemy){
    node["name"] >> enemy.name;
    node["hp"] >> enemy.hp;
    node["atk"] >> enemy.atk;
}
void operator >> (const YAML::Node& node, Info& info){
    const YAML::Node& enemies = node["enemy"];
    for(int i=0;i<enemies.size();i++){
        Enemy enemy; enemies[i] >> enemy;
        info.enemy.push_back(enemy);
    } 
    node["world"] >> info.world;
    node["player"] >> info.player;
} 


void loadYMLFile(string ymlpath){ 
    string name;
    int hp; 
    int atk;
    string world;
    string player;
    try{ 
        ifstream fin(ymlpath);
        YAML::Parser parser(fin);
        YAML::Node doc; parser.GetNextDocument(doc);
        Info info;
        for(int i=0;i<doc.size();i++){
            Info info; doc[i] >> info;
            for(int j=0;j<info.enemy.size();j++){
                name = info.enemy[j].name;
                hp = info.enemy[j].hp;
                atk = info.enemy[j].atk;
                cout << "name:" << name << "\n" << "HP:" << hp << "\n" << "ATK:" << atk << endl;
            } 
            world = info.world;
            player = info.player;
            cout << "world:" << world << "\n" << "player:" << player << endl;
        }
    }catch(YAML::ParserException& e){ 
        cerr << e.what() << endl;
    }
} 

int main(int argc, char* argv[]){
    string ymlpath = argv[1];
    loadYMLFile(ymlpath);
    return 0;
} 

これをコンパイルして引数渡しで先ほどのymlファイルのパスを渡してあげると表示してくれる形にしてみました。

cpp-yaml-reader.exe info.yml

これでめでたくC++でymlファイルが扱えるようになりました。設定情報をcsvファイルとかを使って頑張って階層構造を組まなくていいようになりました。マジックナンバーではなくキーで指定できるのも地味にうれしいです。

MattermostでTwitterの更新情報を自動で呟いてくれるbot的なのを作った

はじめに

最近、職場でOSSの環境を乱立しまくっているのですが、そのひとつとしてMattermostも立ち上げました。

釈迦に説法かとは思いますが、Mattermostは早い話が、オンプレミスで動くSlackです。うちもセキュリティが厳しいので、流行りのツールを使おうにもオンプレで動くものでないと使えなかったりします。

qiita.com

で、立ち上げたのはいいのですが、早速言われたのが、「これ何に使うの?」。うちは独自のチャットツールがあるので、わざわざMattermostを使わなくてもいいじゃんという考えの人が多いようです。そこで、よくチェックする記事を自動で呟いてくれるbotを作れば需要があるんじゃなかろうかということで作りました。

使っている環境はMattermostとJenkinsとpythonです。構成としてはこんな感じになっています。

f:id:T-N-Clark:20180614000213p:plain

即興で作ったのでガバガバなのはご愛嬌ということで。Jenkinsはただのスケジューラーとして使っているので、こうして見返してみるとJenkinsである必要ないですね。まあ、Mattermostと繋げてビルドに失敗したときに通知が飛ばせるから(震え声)。あったので使った程度です。

今回はこちらのTwitterアカウントの更新情報を取得させていただくことにします。機械学習などの最新の論文情報をまとめてくださっているサイトです。アブストの日本語訳などもあるので、さっとチェックしたいときにとても参考になります。

twitter.com

実装

では、早速実装していきます。まずは、Twitterの更新情報を取得します。色々方法はあると思うのですが、今回はこちらを利用しました。

TwitRSS.me - rss of twitter user feeds by screenscraping with perl

次に、Mattermost側で情報を受け取るURLを用意しておきます。今回は情報を受け取るだけなので、内向きウェブフックを使います。

qiita.com

いよいよ、RSSpythonで取得、データベースに保存、json形式に変換、Mattermostに送信部分を組んでいきます。最後に最終的に実装したコードを載せていますので、説明はいいという方は飛ばしてください。長いので処理内容毎に分けて説明します。といっても浅い理解なので間違っていたらすみません。コードもとりあえず、動くことだけしか考えてないので出来の悪い実装は適宜修正してください。ベースはこちらのサイトを参考にさせていただきました。

qiita.com

requestsで指定したURLからRSSの情報を取得します。requestライブラリについては、こちらをどうぞ。

qiita.com

プロキシの設定が必要な場合はこちらが参考になります。

qiita.com

無事、RSSの情報が取ってこれたら、feedparserでcontentから必要な情報を取ってきます。(これなんでfeedparserだけじゃだめなんだっけ?プロキシのせいだったかな。忘れてしまった。)この中のentryの中に欲しいデータがあるみたいなのでpandas形式に変換しておきます。そこまでできたらこちらを参考にTwitRSSでとってきた中から必要情報を抜き出します。

qiita.com

今回必要なのは、idとtitleとlinkです。idはツイート毎のユニークな値です。これを使って既出か否かの判別をします。titleはツイートの内容にあたるのでこれをMattermostのメッセージにします。linkはそのツイートへのリンクです。今回はMattermostでメッセージをクリックするとTwitterにリンクするようにしておきます。

取ってきたRSSの情報をデータベースに格納します。今回はそこら辺はサボってCSVに保存しています。(ちなみにこの安易な考えで後に少々面倒なことになりました)

qiita.com

全体通して書くとこうなります。



import requests,feedparser

import pandas as pd

import os

import json



def getNewFeed(rss_data,already_printed_feeds,filepath):

    feeds_info = []

    feed = feedparser.parse(rss_data.content)

    entries = pd.DataFrame(feed.entries)

    if already_printed_feeds.empty:

        #全て新着Feed

        new_entries = entries

    else:

        #既出のFeedは除く

        new_entries = entries[~entries['id'].isin(already_printed_feeds['id'])]

    if not new_entries.empty: #新着Feedがあれば

        for key, row in new_entries.iterrows():

            title = row['title'].split('http')[0]

            #Mattermostに投稿されるメッセージ.ここではmarkdown形式でリンクになるように書いている.

            feedinfo = '[**%s**](%s)' % (title, row['link'])

            feeds_info.append(feedinfo)

        #新着データがあれば既存のリストに追加する

        already_printed_feeds = already_printed_feeds.append(new_entries)

        #データベース(csv)に保存

        if os.path.exists(filepath):

            new_entries.to_csv(filepath,encoding='utf-8',mode='a',header=False)

        else:

            new_entries.to_csv(filepath,encoding='utf-8')

    else: #新着Feedが無ければ

        print('not found new entries')

    return feeds_info



#既出のfeed情報の取得

def getAlreadyPrintedFeeds(filepath):

    if os.path.exists(filepath):

        already_printed_feeds = pd.read_csv(filepath)

    else:

        already_printed_feeds = pd.Series()

    return already_printed_feeds



def setPostMessage(feedinfo,username):

    payload = {

        'text':feedinfo,

        'username':username,

    }

    return payload



#データベース(csv)へのパス

filepath = 'entries.csv'

#proxyの設定

proxies = {

    'http':'http://id:passward@proxyadress:port',

    'https':'http://id:passward@proxyadress:port'

}

#RSSFeed取得先のURL

url = 'http://twitrss.me/twitter_user_to_rss/?user=arxivtimes'

rss_data = requests.get(url,proxies=proxies)

already_printed_feeds = getAlreadyPrintedFeeds(filepath)

feedinfo = getNewFeed(rss_data,already_printed_feeds,filepath)



#Mattermostの内向きウェブフック

mattermosturl = 'http://localhost/hooks/***'

#Mattermostのつぶやき時に表示される名前(好きな名前をつける)

username = 'FeedBot'

header = {'content-Type':'application/json'}

#新着Feedを順にMattermostに投げる

for i in range(len(feedinfo)):

    payload = setPostMessage(feedinfo[i])

    resp = request.post(mattermosturl,

                        header=header,

                        data=json.dumps(payload))

最後にこれを定期的に実行するためにJenkinsで実行スケジュールを組みます。と言ってもやることは単純で1時間に1回ジョブを実行させるだけです。確か今回採用しているRSSの取得方法が1時間単位に更新された気がするのでこんなもんでいいでしょう。こちらを参考にさせていただきながら、スケジュールを設定していきます。

qiita.com

ということで最低限飛ばせるようになりました。

ただ、ここで2つ問題が起きました。

  1. 一つはこのままじゃ複数ページから取ってこれないのでもうちょっとだけいじります。といってもurlをリストにして順々に実行するだけです。なので大したことないです。
  2. もう一つは取ってきたRSS情報のcolumn数が揃ってないだと…どういう条件かまでは見てないのですが、ツイートによっては特定のcolumnのデータがあったりなかったりするみたいです。NaNになるならいいのですが、CSVへの書き出し部分がいけないのかcolumnそのものが違って保存されてしまっているようです。そのせいで既出情報を読み出す際にcolumn数が合わないとエラーを食らいました。ダサいですが、`pandas`で読み込む際に`names`を指定して読み込むことにします。

    nekoyukimmm.hatenablog.com

    pandasでカラムサイズが一定でないcsv/tsvを読み込む : mwSoft blog

import requests,feedparser
import pandas as pd
import os

def getNewFeed(rss_data,already_printed_feeds,filepath):
    feeds_info = []
    feed = feedparser.parse(rss_data.content)
    entries = pd.DataFrame(feed.entries)

    if already_printed_feeds.empty:
        #全て新着Feed
        new_entries = entries
    else:
        #既出のFeedは除く
        new_entries = entries[~entries['id'].isin(already_printed_feeds['id'])]
    if not new_entries.empty: #新着Feedがあれば
        for key, row in new_entries.iterrows():
            title = row['title'].split('http')[0]
            #Mattermostに投稿されるメッセージ.ここではmarkdown形式でリンクになるように書いている.
            feedinfo = '[**%s**](%s)' % (title, row['link'])
            feeds_info.append(feedinfo)
        #新着データがあれば既存のリストに追加する
        already_printed_feeds = already_printed_feeds.append(new_entries)
        #データベース(csv)に保存
        if os.path.exists(filepath):
            new_entries.to_csv(filepath,encoding='utf-8',mode='a',header=False)
        else:
            new_entries.to_csv(filepath,encoding='utf-8')
    else: #新着Feedが無ければ
        print('not found new entries')

    return feeds_info

#既出のfeed情報の取得
def getAlreadyPrintedFeeds(filepath):
    if os.path.exists(filepath):
      col_names = ['no',
                    'author',
                    'author_detail',
                    'authors',
                    'guidislink',
                    'id',
                    'link',
                    'links',
                    'published',
                    'published_parsed',
                    'summary',
                    'summary_detail',
                    'title',
                    'title_detail',
                    'twitter_place',
                    'twitter_source']
        already_printed_feeds = pd.read_csv(filepath)

    else:
        already_printed_feeds = pd.Series()

    return already_printed_feeds

import json

def setPostMessage(feedinfo,username):
    payload = {
        'text':feedinfo,
        'username':username,
    }

    return payload



def postForMattermost(feedinfo):
    #Mattermostの内向きウェブフック
    mattermosturl = 'http://localhost/hooks/***'
    #Mattermostのつぶやき時に表示される名前(好きな名前をつける)
    username = 'FeedBot'
    header = {'content-Type':'application/json'}

    #新着Feedを順にMattermostに投げる
    for i in range(len(feedinfo)):
        payload = setPostMessage(feedinfo[i])
        resp = request.post(mattermosturl,
                            header=header,
                            data=json.dumps(payload))


def main():
    #proxyの設定
    proxies = {
        'http':'http://id:passward@proxyadress:port',
        'https':'http://id:passward@proxyadress:port'
    }

    #データベース(csv)へのパス
    filepath = 'entries.csv'
    already_printed_feeds = getAlreadyPrintedFeeds(filepath)

    #RSSFeed取得先のURL
    urls = ['http://twitrss.me/twitter_user_to_rss/?user=arxivtimes',
            'http://twitrss.me/twitter_user_to_rss/?user=a_i_news',
            'http://twitrss.me/twitter_user_to_rss/?user=ai_m_lab'
    ]


    for i in range(len(urls)):
        #RSS情報の取得
        rss_data = requests.get(urls[i],proxies=proxies)
        #データベースへの登録
        feedinfo = getNewFeed(rss_data,already_printed_feeds,filepath)
        #Mattermostへの送信
        postForMattermost(feedinfo)

if __name__ == "__main__":
    main()

多少整理もしたのでさっきよりは見やすくなったのではないでしょうか。Cの頃の癖なのか気づくと思考停止でインデックスでforループ回してますね。これはひどい

長いわりに稚拙な内容でしたが参考になれば幸いです。

後半で追加したサイト

twitter.com

twitter.com

【Unity】シーンの切り替えエフェクト

【2018/7/14追記】作成したスクリプトにFadeCanvasをアタッチする部分が抜けていたので追記しました。

RPGでマップ画面から戦闘画面に切り替わるときとかにあるあれです。既にたくさんの方が書かれているので今さらですが、個人的にはまったので、備忘録として書いておきます。

手っ取り早くやりたいなら

github.com

bibinbaleo.hatenablog.com

複雑なエフェクトをかけたいなら

tsubakit1.hateblo.jp

使い方の例:ボタンクリックでエフェクトをかけながら次のシーンに移る

  1. シーンを2つ用意する
  2. ルール画像を取得する4you.bz

  3. 取得したルール画像をUnityのProjectにインポートする。フォルダー構成は任意。
  4. ルール画像のInspectorを変更する。TextureType=Default,TextureShape=2D,sRGB=on,AlphaSource=FromGrayScale,AlphaIsTransparency=on
    にしてApplyを押す。
  5. シーン切り替えエフェクト用のパッケージを取得する。

    github.com

  6. ダウンロードしたパッケージを[Assets]->[Import Package]->[Custom Package]からインポートする。インポート対象はallで良い。
  7. シーン移動する前のシーンにインポートしたパッケージフォルダ[Fade]->[FadeCanvas]のプレハブをシーンに置く
  8. FadeCanvasにアタッチされている[FadeImage]でエフェクトの様子を調整できる。Materialを変更することで変化の様子を調整する。[UI-Fade-Cutout]にすると円を描くようなエフェクトになる。MaskTextureを変更するとエフェクトの模様を変更できる。ここに3.でインポートしたルール画像をアタッチする。
  9. Canvas下に[UI]->[Button]を置く。
  10. ButtonにFade用のスクリプトをアタッチする。今回は以下のようなスクリプトを自作して与えた。
    using System.Collections;
    
    using System.Collections.Generic;
    
    using UnityEngine;
    
    using UnityEngine.SceneManagement;
    
    
    
    public class FadePractice : MonoBehaviour {
    
    	[SerializeField]
    
    	Fade fade = null;
    
    
    
    	public void Fadeout()
    
    	{
    
    		//fade.FadeIn (1, () => {
    
    		//	fade.FadeOut (1);
    
    		//});
    
    		fade.FadeIn (1, () => {
    
    			Invoke("LoadScene",0.5f);
    
    		});
    
    	}
    
    	public void LoadScene(){
    
    		SceneManager.LoadScene ("NextScene");
    
    	}
    
    }
  11. ButtonのOnClickに先ほどButtonにアタッチした[FadePractice]をアタッチする。この時[FadePractice]はInspector上のものをアタッチすること。アタッチできたら、[Runtime Only],関数名:[FadePractice.Fadeout]を選択する。
  12. FadePracticeにシーン上のFadeCanvasをアタッチする
  13. シーンをSaveしてゲームを実行する。ボタンをクリックして、フェードアウトエフェクトがかかりながら次のステージに遷移できたら完成。

あとはお好みのエフェクトに変更したり、ステージの遷移になるように調整してください。

FadeCanvasのSortingLayerをいじることで描画順序も調整できるようです。Materialによっても見え方がことなるので適宜変更してください。デフォルトだと[UI-Fade-Alpha]になっているので下にobjectがあると透過して下のobject見えるっぽいです。[UI-Fade-Cutout]に変更したら透過しなくなりました。

【GitBucket】社内用HPページを作って情報共有してみる

 以前、Hugoを使って簡単ホームページの作り方をご紹介したかと思います。

t-n-clark.hatenadiary.jp

しかし、作ったのはいいのですが、あんまり外に見せたくない、社内への情報共有程度に使えればいいんだけどな、オンプレミスで使えるGitHub Pases的なのがあればなあと思っていたら便利なものがありました。ちなみにGitHub Pasesとはこんな感じのものです。

www.tam-tam.co.jp

GitHubだと社内の情報セキュリティ的に...という場合に使えるオンプレミスで動くGitBucketというものがあるのですが、これのプラグインGitHub Pages的なものがあったのでこれを使うことにしました。

gitbucket.github.io

github.com

こちらで紹介されている通り、特定の名前のブランチに上げるとホームページとして見ることができます。
GitBucketを補助するプラグイン4選 | 株式会社INDETAIL - インディテール

Hugoを使う場合はpublicフォルダをGitBucketに上げます。うん、これで無事作ったホームページが公開できました。満足満足。

年度が変わったので新しいメンバーに自分のプロジェクトの紹介とかに使ってはいかがでしょうか。