2017年11月28日火曜日

【Storyboard】段位認定の背景表示【Storybrew】

ほぼ自分用のメモ。
こちらの段位認定のSBで使った背景表示のスクリプトです。曲名表示は別。
手動でやると大体20分くらいはかかりますが半自動化してしまえば5分以下で終わるので、スクリプトを書く時間を含めても大幅な時間短縮になります。

大まかな流れ
1. diffごとに異なる項目の配列を作る
2. 読み込む
3. いつもの感じで設定・出力

時間設定は手書きだと面倒なのでbookmarkで読み込み。
BGのファイル名は全て1.jpg, 2.jpg, 3.jpgで統一。

using StorybrewCommon.Scripting;
using StorybrewCommon.Storyboarding;
using System.Linq;

namespace StorybrewScripts
{
    public class Background : StoryboardObjectGenerator
    {
        public override void Generate()
        {

            var layer = GetLayer("");
            var layer2 = GetLayer("2");
            var diff = Beatmap.Name;

            //基本設定
            var dummy = layer.CreateSprite(Beatmap.BackgroundPath, OsbOrigin.Centre);
            dummy.Fade(0, 0);
            int[] x = {0, 0, 0};
            int[] c = {210, 90, 0};
            double[] b = {1.0, 1.0, 1.0};
            double[] o = {1.0, 1.0, 1.0};
            string d = "0";

            //diff固有設定
            if(diff == "1st Dan"){
                d = "01";
                x[0] = 140;
                x[1] = 120;
                x[2] = 180;
                c[1] = 250;
                b[1] = 0.3;
            }

            if(diff == "2nd Dan"){
                d = "02";
                x[0] = 120;
                x[1] = 210;
                x[2] = 160;
                c[0] = 30;
                c[1] = 180;
                b[1] = 0.6;
                c[2] = 50;
            }

            if(diff == "3rd Dan"){
                d = "03";
                x[0] = 120;
                x[1] = 255;
                x[2] = 190;
                c[0] = 30;
                c[1] = 0;
                c[2] = 350;
                b[2] = 0.5;
            }

             if(diff == "4th Dan"){
                d = "04";
                x[0] = 190;
                x[1] = 140;
                x[2] = 170;
                c[0] = 40;
                b[0] = 0.3;
                c[1] = 70;
                c[2] = 330;
                b[2] = 0.3;
            }

            if(diff == "5th Dan"){
                d = "05";
                x[0] = 210;
                x[1] = 60;
                x[2] = 60;
                c[0] = 220;
                b[0] = 0.5;
                c[1] = 35;
                b[1] = 0.5;
                c[2] = 5;
                b[2] = 0.8;
            }

            if(diff == "6th Dan"){
                d = "06";
                x[0] = 60;
                x[1] = 150;
                x[2] = 125;
                c[0] = 220;
                b[0] = 0.4;
                c[1] = 250;
                b[1] = 0.5;
                c[2] = 20;
                b[2] = 0.65;
            }


            //読み込み設定
            int[] time = {0, 0, 0, 0};
            var bg = new OsbSprite[4];
            var i = 0; var n = 0;

            var line = layer2.CreateSprite("BG/line.png", OsbOrigin.CentreLeft);
            line.Additive(0,0);
            line.Move(0, 597, 122);
            double linelength = 0.237;
            
            //時間設定
            foreach (var bookmark in Beatmap.Bookmarks)
            {
                time[n] = bookmark - 1500;
                n++;
            }
            time[3] = (int)Beatmap.HitObjects.Last().EndTime + 1000;


            //背景読み込み
            foreach (var bookmark in Beatmap.Bookmarks)
            {
                i++;
                string j = "" + i; 
                string path = "BG/"+ d + "/" + j +".jpg";
                int startTime = time[i-1];
                int endTime = time[i];
                int FadeTime = 500;

                bg[i] = layer.CreateSprite(path, OsbOrigin.Centre);
                bg[i].Fade(startTime, startTime + FadeTime, 0, 1);
                bg[i].Fade(endTime, endTime + FadeTime, 1, 0);

                bg[i].Scale(startTime, 480/768.0);
                bg[i].MoveY(startTime, x[i-1] + 240);

                line.Fade(startTime + FadeTime, 1);
                line.ScaleVec(startTime + FadeTime, endTime, 0, 0.5, linelength, 0.5);
                line.Fade(endTime - 200 - 1000, endTime, 1, 0);
                line.ColorHsb(startTime, c[i-1], b[i-1], o[i-1]);

            }

            //Taiko_Bar
            var layer_bar = GetLayer("Taiko_bar");
            var bar = layer_bar.CreateSprite("BG/black.png", OsbOrigin.TopCentre);
            
            bar.Fade(time[0] - 500, time[0], 0, 1);
            bar.Fade(time[3] + 1000, time[3] + 2000, 1, 0);
            bar.ScaleVec(time[0], 3, 3.2);
            bar.MoveY(time[0], 0);

            var st = layer_bar.CreateSprite("BG/stage.png", OsbOrigin.Centre);
            st.Fade(time[0] - 500, time[0], 0, 1);
            st.Fade(time[3] + 1000, time[3] + 2000, 1, 0);
            st.ScaleVec(time[0], 0.4, 0.3);
            st.Move(time[0], 680, 105);

        }

    }
}

beatmap Dataの情報がwikiにあまり載ってなかったので色々初心者なりに模索。
Beatmap.Nameで開いているdiffの名前を読み取れることが分かったので、それと一致を条件にdiffを判別。dでBGのあるフォルダを指定。

前述の通り時間設定は基本bookmark。ただし終了地点だけは最後のノートの位置を条件に。

BG設定はxがy座標(ただのミス), cが右上の曲の長さを表すバーの色、b/oがその濃度関係。他はwikiの情報をもとに書いたので特に書くこと無し


2017年11月25日土曜日

ModdingV2 変更点

Rank過程

従来のStar Priorityが廃止され、Hype Trainが導入された。

現在新たに投稿される譜面ではModding / DiscussionのリンクをクリックするとModdingV2のページへと移動します。
すると次のような画面が出てきます。

これはHype Trainと呼ばれていて、従来のStar Priorityに対応しています。Hypeの意味は辞書には"広告"などと載っていますが、ネット上ではスラングとして用いられることが大半で、日本語としては「期待、盛り上げ」が近いのではないかと思います。Hypeは一人につき1回することが出来、その人数が12人を超えるとBeatmap NominaterによってNominateされることが可能となります。


(NominateボタンはBeatmap Nominaterのみに表示されます)
Nominateを押した人数が2人に達すると譜面は晴れてQualifiedとなります。以前はBubble, Qualifyと複数のアイコンがあり、初心者はwiki等から情報を集めなければなりませんでしたが、今回の変更によってどうすれば譜面がランクするのか視覚的に分かりやすくなったのではないかと思います。

Hypeの方法ですが、最初の画像の"Hype Beatmap"を押すと次の画面へと移動します。

下の英文を意訳すると、「Praise♡のついたPostは譜面のHype値(Hype Train)を上昇させ、譜面のNominateを可能にします。」です。先ほど説明したとおりですね。
ここにコメントを入力し、右下のPraiseを押すことでHypeが完了します。ちなみにPraiseのついたポストは何度でもできますが、1人1譜面につき1回しかHype値は上昇しません。また、Hypeをするには対価は不要です。


Hype値が上昇しました。


ModdingV2ではGeneral (All Difficulties), General, Timeline, Historyの4つに分かれており、Hype値が上昇するのはGeneral (All Difficulties)にPraiseのポストをしたときのみです。GeneralやTimelineでPraiseのポストをしてもHypeは上昇しません。
ポスト時のアイコンの意味は次の通りです。

Praiseは称賛するときに使用します。
SuggestionはModdingで提案をする際に使用します。
Problemは譜面に問題点があるときに使用します。
自分の譜面でコメントを残す場合はNoteとなります。これは特定の場所/配置に対してmodderに説明をするときなどに活用が出来ます。

ちなみに、譜面をWork in Progressに投稿した場合はHype値が12以上でもNominate Statusは表示されません。

Modding関連

Timelineが表示され、どの場所に対する指摘なのかわかりやすくなった

Timestamp (00:15:186 - のような形式) を含めると自動で指摘した場所にアイコンが付きます。これらは上記のポスト時のアイコンに対応しています。


✔は問題が解決したことを意味します。返信をする際に"Resolved"にチェックを入れることでアイコンが変化します。
このResolvedはMapperと権限を持った人にしか出来ないため、現状ゲスト譜面を含むときは投稿者が改めてmodに対して返信をする必要があります。


特定箇所に対する指摘でない場合はGeneralにPostしましょう。

Modに対するe-mail通知が廃止され、Modding Watchlistが導入された。

Moddingページの左上にあるWatchボタンを押すとこちらのページに譜面が追加され、"Resolved"となっていないPostの数が表示されるようになります。

Kudosuの付与方法が変わった

Modに対してUpvote(右画像, 左)を押すことで付与されます。今まではModの量, 質を問わず1 Postにつき1-2kudosuでしたが、今回の変更により"modの内容量に応じて合計kudosu数が変化"し、"譜面を改善しなければkudosuが付与されない"ようになり、内容の薄いmodでkudosuの荒稼ぎが出来なくなりました。個人的には良い変更だと思っています。

ただ、Upvoteをする際は"本当に譜面を改善したか"を必ず確認してください。仮に変更を加えたとしてもd? k?のような意義の薄いPostに対してはUpvoteをすることは推奨できません。






今後ModdingV2関係のガイドラインが充実してくると思うので、そちらを参考にしていくと良いかと思います。それでは良いマッピングライフを














2017年7月25日火曜日

【Storyboard】立体的演出【Storybrew】

難易度: Advanced
範囲: Parameter, Vector Scale, Loop等

初めに.
そもそも、osuのStoryboardはXY平面上で動作しているため、立体的な処理が出来ません。ですが、ちょっとした工夫をすればある程度立体的な表現をすることが出来るようになります。

Paramete (FlipH, FlipV)画像を反転させる働きをします。
Vector Scale (ScaleVec):X, Yそれぞれの拡大率を設定します。

演出01.
これらのコマンドを組み合わせてみましょう。

        public override void Generate()
        {
      var layer = GetLayer("");
            var BG = layer.CreateSprite("SB/bg_1.jpg", OsbOrigin.TopLeft);

            var startTime = 175627;
            var endTime = 233912;
            var beat = Beatmap.GetTimingPointAt(startTime).BeatDuration;

            BG.Scale(startTime - beat, endTime, 480/768.0 * 1.1, 480/768.0);
            BG.Fade(startTime - beat, startTime, 0, 1); 
            BG.Fade(endTime, endTime + beat, 1, 0);
            BG.Move(startTime, -107, 0);

            var n_sakura = 500;
            var sakura = new OsbSprite[n_sakura];

            for (var i = 0; i < n_sakura; i++){
                if (i % 2 == 0){sakura[i] = layer.CreateSprite("SB/sakura_1.png");}
                else {sakura[i] = layer.CreateSprite("SB/sakura_2.png");}

                var sakuraStart = Random(startTime - beat, endTime - beat);
                var sakuraTime = Random(3000, 8000);
                var sakuraEnd = sakuraStart + sakuraTime;
                var Ys = Random(50, 600);
                var Ym = Random(-400, -50);
                double Ss = Random(40, 75); Ss /= 100.0;
                double Srx = Random(-6, 6) * Math.PI;
                var Srz = Random(1, 8);

                sakura[i].Fade(sakuraStart, sakuraEnd, 1, 1);
                sakura[i].Scale(sakuraStart, Ss);
                sakura[i].Rotate(sakuraStart, sakuraEnd, 0, Srx);
                sakura[i].MoveX(sakuraStart, sakuraEnd, -120, 800);
                sakura[i].MoveY(sakuraStart, sakuraEnd, Ys, Ys + Ym);
                sakura[i].Additive(sakuraStart, sakuraStart);

                if(Srz > 0)
                {
                    sakura[i].StartLoopGroup(sakuraStart, Srz);
                    sakura[i].ScaleVec(OsbEasing.InSine, 0, (sakuraTime / Srz) / 2, Ss, Ss, Ss, 0);
                    sakura[i].FlipV((sakuraTime / Srz) / 2, (sakuraTime / Srz) /2);
                    sakura[i].ScaleVec(OsbEasing.OutSine, (sakuraTime / Srz) / 2, (sakuraTime / Srz), Ss, 0, Ss, Ss);
                    sakura[i].EndGroup();
                }
            }


先ほどの2コマンドとRotateを組み合わせることによって二軸回転をさせています。現実的にはこのような動きはしないのですが、SBの演出としては十分でしょう。
最後のIF内にあるLoop処理が特徴的です。LoopGroupの記述方法は公式wikiを参照してください。




演出02.
Scaleを第三の軸, Z座標として扱うことが可能です。サイズを大きくすれば近づく、小さくすれば遠くなるのと同じような見え方をします。遠近法的なアレです。
具体例は割愛。

演出03.
Animationに頼る方法。その方が楽な場合もあるので覚えていても良いと思います。


補足.
var beat = Beatmap.GetTimingPointAt(startTime).BeatDuration;
公式wikiにはBeatmap Dataのページに載っています。
Beatmap.GetTimingPointAt(time)でtimeにおけるデータを取得します。今回の場合、BeatDurationと続けることで一拍の長さを変数"beat"に入れています。

var BG = layer.CreateSprite("SB/bg_1.jpg", OsbOrigin.TopLeft);
OsbOriginは重心の設定だと導入編で説明しました。今回の場合はTopLeft, 左上が重心となっています。





2017年6月13日火曜日

【Storyboard】雪エフェクト【Storybrew】

難易度: Basic
雪エフェクトの使用例: EMILIA (CV: Rie Takahashi) - Stay Alive

範囲: Move, Scale, for構文, Random

今回はこの譜面で進めていきます。

雪の画像はこちらからどうぞ。


STEP 01.
public override void Generate()
        {
            int number = 200;

     var l = GetLayer("");
            var SnowPath = "SB/dot_0.png";
            var Snow = new OsbSprite[number];

        }

number: 雪の個数を指定。
SnowPath: 雪画像のパスを指定。自分のmapsetに合わせて変更すること。
Snow = New Osbsprite[数]: 同じ画像を複数回用いるときに使う。

STEP 02.
public override void Generate()
        {
            int number = 200;

            var l = GetLayer("");
            var SnowPath = "SB/dot_0.png";
            var Snow = new OsbSprite[number];

            int startTime = 55246;
            int endTime = 76116;

            for (var i = 0; i < number; i++){

                var snowStart = Random(startTime, endTime);
                Snow[i] = l.CreateSprite(SnowPath, OsbOrigin.Centre);
            }

        }

for (---; ---; ---){}: 超重要。適当に解説しているサイトを見ると良い。
int startTime / endTime: 雪の降り始める時間の範囲を指定。
snowStart: startTime~endTimeの間のランダムな数値が入る。これによりそれぞれの雪画像が別々の時間に降り始めるような設定が出来るようになる。
snow[i]: 先ほどforの中身がnumber回繰り返されると説明したが、i値はの1週するたびに1ずつ増えていくため、Snow[0], Snow[1], Snow[2], ...., Snow[199]と同じ役割を果たす。

STEP 03
public override void Generate()
        {
            int number = 200;

            var l = GetLayer(""); 
            var SnowPath = "SB/dot_0.png";
            var Snow = new OsbSprite[number];

            int startTime = 55246;
            int endTime = 76116;

            for (var i = 0; i < number; i++){

                var snowStart = Random(startTime, endTime - 4000);
                var startX = Random(-120, 840);
                var MoveX = Random(-50, 50);
                var FallTime = Random(4000, 8000);
                Double sScale = Random(40, 80); sScale /= 100.0;

                Snow[i] = l.CreateSprite(SnowPath, OsbOrigin.Centre);

                Snow[i].MoveX(snowStart, snowStart + FallTime, startX, startX + MoveX);
                Snow[i].MoveY(snowStart, snowStart + FallTime, -20, 500);
                Snow[i].Scale(snowStart, sScale);

            }

        }


Double sScale: Scaleは小数になるため、varではなくdoubleを使用している。40~80のランダムの数値を生成→100で割るという手順を踏むことで0.01単位の0.40~0.80の範囲のランダムな数値を得ることが出来る。
MoveX: 一直線に落ちてくるだけだと見栄えが良くないため左右の揺れ幅を追加。範囲をRandom(20, 100)のようにすると風が吹いているかのような演出にすることが出来る。


以上で雪エフェクトの作成完了です。

Additiveコマンドを入れるとより綺麗に見えると思います。
自分なりに数値をいじったり、画像を変更したりして是非応用してみてください。




2017年6月12日月曜日

【Storyboard】基礎 導入編【Storybrew】

本記事ではStoryboardの作成ツールである『Storybrew』について解説します。
注意 公式wikiに載っている情報は基本的に省略します。

対象: Storyboardの作成に興味がある方

SBの例

1. 準備編
【必要なもの】
・GIMP2などの画像編集ソフト
・Visual Studio

・論理的思考力
・数学/英語への耐性
・自己解決能力

一通りそろえたらstorybrew公式サイトのGetting Startedの3番まで進めてください。

2. 導入編
今回はこちらの譜面を使用します。


(1). 画像のようにProject Name,  Mapset Pathの設定が終わったらStartを押してください。
(2). Effects > New Script を押し、Script Nameを"Background"にしてください。するとVisual Studio Codeが立ち上がるはずです。
public override void Generate(){}のカッコ内がこれから記入していく部分になります。試しに次のように打ち込んでみてください。
        public override void Generate()
        {
            var layer = GetLayer("TEST");
            var BG = layer.CreateSprite("osu!cysmix-bgtiny.jpg", OsbOrigin.Centre);

        }
次にStorybrewに戻り、右下のLayersを押してみてください。

このように"Script名 1 (test)" と表示されています。ちなみに今の段階では画面には何も映りません。

GetLayer: ()内の名前のレイヤーを作成します。複雑なものを作るときは複数のレイヤーを使用することもあります。
layer.CreateSprite: レイヤー、画像等のファイルパス、重心(OsbOrigin.Centre)を設定します。

(3). BGを画面に表示させます。
public override void Generate()
        {
            var layer = GetLayer("");
            var BG = layer.CreateSprite("osu!cysmix-bgtiny.jpg", OsbOrigin.Centre);

            BG.Scale(0, 960.0/1680);
            BG.Fade(1357, 1785, 0, 1);
            BG.Fade(173214, 173643, 1, 0);
        }
Scale: 画像の大きさを指定する。今回は背景画像の横幅が1680であるから, 数値は960.0/1680と指定すれば良い。
Fade: 画像の不透明度を指定する。2つの時間を設定することで徐々に変化させることが可能。今回の場合は1357ms~1785msにかけて0%~100%, 173214ms~173643msにかけて100%~0%へと変化している。
時間の指定はStorybrewで時間を合わせてctrl+c, ctrl+vで済む。手打ちの必要無し。

これにより、画面に背景画像が表示されるようになりました。
Scale, Fade等の記述方法はこちらを参照してください。→Sprite Methods

(4). Storyboardを出力します。
Storybrewの右下にあるパズルのピースのようなアイコンを押してみてください。これによりosbファイルが作成されます。
Mapsetのフォルダを開き確認してみましょう。


以上で導入編は終了です。