20190730のC#に関する記事は9件です。

VRMMOゲーム開発:リアルタイム通信

如何にして90fpsを達成するか

VRMMOにおいてはリアルタイム性が重視されます。
ボイスチャット然り、動作反映然り。
その指標となる、90fpsの達成方法について考察します。
90回通信/秒より、一回の所要時間は10^-2程度、同時接続1000人とすると、負荷を考慮して1000で割って、10^-5の通信速度が求められます。

jsonデータ

ゲームオブジェクトは、何らかの値を常時持っており、更新されます。これを、クライアント間で共有せねばなりません。
その為には、ゲームオブジェクトを識別可能として、値を文字列に変換する事が要と思われます。
構造化データ形式は限られており、unityの基本はゲームオブジェクト。そこで、ゲームオブジェクトの json化をします。 json utilityを用います。(他の方法が思いつきませんでした)
また、mysqlが jsonをサポートしている事もあります。

通信

同期というのは恐らく手間がかかります。通信速度に影響します。
そこで、同期をやめます。
何かというと、ソケット通信時、c++ 変数に 文字列jsonを指定し、都度新しい jsonを丸ごと代入してあげます。これで、予想が正しければ、解析の時間が無くなります。その後、新しい jsonを、自分以外のポートに伝送します。(やり方はまだ分かりません)
プロトコルは、正確さが求められる場合はTCP、速さが求められる場合はUDPを用います。独自プロトコルについては、検討します。

大事なこと

全体としての手続きを簡略化する事が重要です。速さと正確さの担保となります。
クライアント、サーバ双方で、常に何らかの処理をさせる、遊ばせない、これを重視したく思います。

最後に

当方、超がつくほどの初心者です。
結局、根本にあるのは、unityはc++ で出来ているし、c++ はcの発展系だから、何とかなるだろうという考えです。ビジョンが先にあって、エンジンやプログラミング言語を用いて調節、最適化しながら、目標を目指します。

当方、unity、c、c++ 、c#について全くの初心者です。これから勉強してゆきます。

追記

magic onionも検討中です。オープンソースライブラリということで、後でgithubにて確認します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】 テキストファイルを読み取り、自動でスクロールコンテンツを生成してくれるInfiniteScroll+α

まずはじめに

 前提として、ある程度Unityの操作に慣れていて、用語がそれなりに分かっている事を前提条件としているため、全くの初心者が読む記事ではありません。

 この記事は、tsubaki様製作のInfinite Scrollを使用、改変しています。
Ifinite Scrollの使い方が記載されたブログ記事

 このスクリプトがなければ出来なかったです、この場をお借りしてtsubaki様にお礼申し上げます。

Step1、Inifite ScrollをDLし、LimitedのSceneを開く

  • まず初めにInfinit ScrollをDLする。
    スクリーンショット (11)_LI.jpg

  • DLが完了したら、ファイルを解凍し、UnityのOpen Projectから解凍したフォルダを選択して開く。
    スクリーンショット (12).png

  • Projectが開いたら、Infinite Scrollフォルダ内にあるLimitedを開き、ダブルクリックしてシーンを開く。
    (左下の警告はバージョンの互換による警告なので無視してよい)
    スクリーンショット (15)_LI.jpg

これでひとまず、シーンを再生すると99個までスクロールできるスクロールが生成されるようになった。

Step2、文字を読み取るための下準備

  • Inifite Scrollはプレハブ化されたベースとなるオブジェクトを量産し、Itemスクリプトによりベースの中身をそれぞれ書き換えることでコンテンツを量産しています。
    スクリーンショット (16)_LI.jpg

  • まずは読み取り先のテキストファイルを作成していきます。ProjectのAseetsフォルダ内にCreateからFolderを選択し、名前をResourcesとします。
    このフォルダの中に、テキストファイルを入れます。テキストファイルはUnityでは作成できないので、メモ帳などの機能を使って作ったものをドラッグ&ドロップで入れてください。
    スクリーンショット (19).png

  • テキストファイルの中身は後に改行ごとに切り分けて一行分のデータとして保存されるので、表示する際に二行にしたい場合はHTMLタグの<br>をテキストファイル内で記述するとできます。

  • 次にItemスクリプトに代わるスクリプトを作成します、このスクリプトでテキストファイルのデータを読み取り、コンテンツに代入して表示します。

    MyItem.cs (click here)
    MyItem.cs
    using UnityEngine;
    using UnityEngine.EventSystems;
    using UnityEngine.UI;
    
    public class MyItem : UIBehaviour 
    {
        [SerializeField]
        Text uiText = null;
    
        [SerializeField]
        Text MaineText = null;
        static Text mein;
    
        public string[] textMessage; //テキストの加工前の一行を入れる変数
        public string[,] textWords; //テキストの複数列を入れる2次元配列 
    
        public static int rowLength; //テキスト内の行数を取得する変数 @staticに変更
        private int columnLength; //テキスト内の列数を取得する変数
    
        int i; //@外側で宣言
        int n; //@外側で宣言
    
        new private void Start()
        {
            TextAsset textasset = new TextAsset(); //テキストファイルのデータを取得するインスタンスを作成
            textasset = Resources.Load("Test", typeof(TextAsset) )as TextAsset; //Resourcesフォルダから対象テキストを取得
            string TextLines = textasset.text; //テキスト全体をstring型で入れる変数を用意して入れる
    
            //Splitで一行づつを代入した1次配列を作成
            textMessage = TextLines.Split('\n'); 
    
            //行数と列数を取得
            columnLength = textMessage[0].Split('\t').Length;
            rowLength = textMessage.Length;
    
            //2次配列を定義
            textWords = new string[rowLength, columnLength];
    
            for( i = 0; i < rowLength; i++)
            {
                string[] tempWords = textMessage[i].Split('\t'); //textMessageをカンマごとに分けたものを一時的にtempWordsに代入
    
                for ( n = 0; n < columnLength; n++)
                {
                    textWords[i, n] = tempWords[n]; //2次配列textWordsにカンマごとに分けたtempWordsを代入していく
                }
            }
        }
    
        //プレハブ化された表示形式に以下の処理で文字と画像を順番に割り当てていく
        public void UpdateItem(int count) 
        {
            Start();
            n = 0;
    
            //アイテムナンバー、0から始まるので+1をして1から表記
            uiText.text = (count + 1).ToString("00");
    
            //テキストの行数を超えないように制限、テキストをカウントに併せて変更。
            if(count < rowLength)
            {
                MaineText.text = (textWords[count, 0]);
            }       
        }
    }
    
    

     

    処理内容の解説をすると非常に長くなるので、重要な部分のみ説明します。

    textasset = Resources.Load("Test", typeof(TextAsset) )as TextAsset;

     この一文がリソースフォルダからテキストファイルを読み取る処理となっています。
    ""で区切られた部分を対応するテキストファイルの名前にすることで、読み取るテキストファイルを変えることができます。

     画像を読み取り&変更したい場合は元のItemスクリプトを参照してください、Commonフォルダの中にスクリプトがあります。

    Step3、文字を表示するベースオブジェクトの作成

    次に文字を表示するためのベースとなるオブジェクトをHierarchy上で作成し、プレハブ化します。
    (hierarchyのオブジェクトをProjectにドラッグ&ドロップ)

    ベースを作成する際の重要な部分ですが、オブジェクトをまとめている親オブジェトのRectTransformが以下の画像のようになっている必要があります。これができていないと、後にシーンを再生してもコンテンツが下の方に生成され、ゲームビュー内に移ら映らないない場合があります。
    Heightの部分は子オブジェクトの大きさによって左右されるため、適宜調整してください。

    この時、先ほど作ったスクリプトをGameObject(親)にアタッチし、それぞれのインスペクターに対応したオブジェクトをアタッチします。
    プレハブ化が済んだらHierarchyに残っているベースは消します。

    スクリーンショット (24)_LI.jpg

    Hierarchy
     GameObject(空のオブジェクト)-MyItem.csをアタッチ
      -Image(背景の白)
      -NumberText(順に番号が割り振られる、画像の00にあたる)-MyItem.csのUiTextにアタッチ
      -MaineText(読み取った文字を書き込むテキスト、画像のtestにあたる)-MyItem.csのMaineTextにアタッチ
    

    このプレハブをInifite Scroll.csのItem Prototypeにドラッグ&ドロップします。
    スクリーンショット (23)_LI.jpg

    この状態でシーンを再生しても、まだ量産体制が整っていないので、ただベースのオブジェクトが表示されるだけです。
    ScrollContentにアタッチされているItem Controller LimitedスクリプトをRemoveComponentし、代わりに以下のスクリプトをアタッチします。

    MyItemControllerLimited.cs (click here)
    MyItemControllerLimited.cs
    using UnityEngine;
    using UnityEngine.EventSystems;
    using UnityEngine.UI;
    
    [RequireComponent(typeof(InfiniteScroll))]
    public class MyItemControllerLimited :  UIBehaviour, IInfiniteScrollSetup
    {
    
        [SerializeField, Range(1, 999)]
        private int max = 30;
    
        public void OnPostSetupItems()
        {
            max = MyItem.rowLength;
            var infiniteScroll = GetComponent<InfiniteScroll>();
            infiniteScroll.onUpdateItem.AddListener(OnUpdateItem);
            GetComponentInParent<ScrollRect>().movementType = ScrollRect.MovementType.Elastic;
    
            var rectTransform = GetComponent<RectTransform>();
            var delta = rectTransform.sizeDelta;
            delta.y = infiniteScroll.itemScale * max;
            rectTransform.sizeDelta = delta;
        }
    
        public void OnUpdateItem(int itemCount, GameObject obj)
        {
            if(itemCount < 0 || itemCount >= max) {
                obj.SetActive (false);
            }
            else {
                obj.SetActive (true);
    
                var item = obj.GetComponentInChildren<MyItem>();
                item.UpdateItem(itemCount);
            }
        }
    }
    

     

    これでシーンを再生すると、文字を読み取ってコンテンツに表記し、自動生成してくれるようになりました。
    あとはテキストファイルの中身を変更したり、ベースオブジェクトを変更することで自在に使用できます。

    スクロール作成.gif

    さいごに

    ここまで読んでいただき、ありがとうございました。

    おそらくこの記事通りにこなせば、最後のGIF通りの結果となるはずですが、万が一記述漏れなどがあるかもしれないので、うまくいかなかった場合はコメントしてもらえると対応出来るかもしれません。

    お疲れ様でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】メールをHTML形式で送信【OutLook】

概要

タイトルまんま。
OutLookをC#で動かして、HTML形式でメールを送る。
(ハイパーリンクを使いたかったのだが、検索してもなかなか使い方がわからなかった。検索の仕方が悪いのか…)
自分用の備忘録に

コード

using System;
using OutLook = Microsoft.Office.Interop.Outlook;

namespace HTMLsample
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                OutLook.Application outlookApp = new OutLook.Application();
                OutLook.MailItem mail = outlookApp.CreateItem(OutLook.OlItemType.olMailItem) as OutLook.MailItem;
                mail.Subject = "HTMLsample";
                OutLook.AddressEntry User = outlookApp.Session.CurrentUser.AddressEntry;

                String sHtml;
                sHtml = "<HTML>\n" +
                   "<HEAD>\n" +
                   "<TITLE>Sample GIF</TITLE>\n" +
                   "</HEAD>\n" +
                   "<BODY>\n" +
                   "<p><a href=\"hogehoge\">foofoo</p>\n" +
                   "</BODY>\n" +
                   "</HTML>";
                mail.HTMLBody = sHtml;

                mail.Recipients.Add("送信先");
                mail.Recipients.ResolveAll();
                mail.Send();
            }
            catch
            {
                Console.WriteLine("メールは送信できませんでした");
            }
        }
    }
}

以上。
テキストエディタでHTML書いて、それをコピペしても上手くいかなかった。
この書き方なら反映される。なぜ???

↓参考にした
https://support.microsoft.com/en-us/help/310262/how-to-use-the-microsoft-outlook-object-library-to-send-an-html-format

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】メールをHTML形式で送信【Outlook】

概要

タイトルまんま。
OutlookをC#で動かして、HTML形式でメールを送る。
(ハイパーリンクを使いたかったのだが、検索してもなかなか使い方がわからなかった。検索の仕方が悪いのか…)
自分用の備忘録に

コード

using System;
using Outlook = Microsoft.Office.Interop.Outlook;

namespace HTMLsample
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Outlook.Application outlookApp = new Outlook.Application();
                Outlook.MailItem mail = outlookApp.CreateItem(Outlook.OlItemType.olMailItem) as Outlook.MailItem;
                mail.Subject = "HTMLsample";
                Outlook.AddressEntry User = outlookApp.Session.CurrentUser.AddressEntry;

                String sHtml;
                sHtml = "<HTML>\n" +
                   "<HEAD>\n" +
                   "<TITLE>Sample GIF</TITLE>\n" +
                   "</HEAD>\n" +
                   "<BODY>\n" +
                   "<p><a href=\"hogehoge\">foofoo</a></p>\n" +
                   "</BODY>\n" +
                   "</HTML>";
                mail.HTMLBody = sHtml;

                mail.Recipients.Add("送信先");
                mail.Recipients.ResolveAll();
                mail.Send();
            }
            catch
            {
                Console.WriteLine("メールは送信できませんでした");
            }
        }
    }
}


以上。
テキストエディタでHTML書いて、それをコピペしても上手くいかなかった。
この書き方なら反映される。なぜ???

↓参考にした
https://support.microsoft.com/en-us/help/310262/how-to-use-the-microsoft-outlook-object-library-to-send-an-html-format

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VRMMO:ボイスチャットの実装

jsonデータの取り扱い

jsonデータは以下の経路で伝送します。

1.クライアントよりnginxへ
httpプロトコルにて伝送、送信にはc++ を使用

2.nginxよりfcgiを用いてmysqlへjson格納
c++ を使用します。
(web VRを意識)

3.mysqlよりjsonデータをhttpプロトコルにてクライアントに伝送
c++ を使用します。

4.クライアント-サーバ間通信にはudpまたは上位プロトコルを使用、c++ を使用します。(速さ重視)

今考えているのは、変数jsonを定義しjsonを再代入する手法です。

ゲームオブジェクトの更新

各種コンポーネント及びスクリプトを追加したゲームオブジェクトを生成します。
元データはmysqlよりjsonデータにて伝送。

流れとしては

json utility.from json

json utility.to json

json utility.from json overwrite

ボイスチャット

マイク入力は

microphoneを用いてaudioclip化

audio sourceで再生します。

on audio filterreadで録音してゲームオブジェクトに反映します。(リップシンク)

(恐らく、同じ要領で、各種ファイル形式を扱えます)

音量表示、音量調節がしたいです。

最後に、 audio listenerコンポーネントでスピーカー出力出来ます。

steam audio sdkを用いることで、立体音響を再現出来ます。

注)理論段階です。あしからず。
ソースコードは、unity内部はc#、外部及びインターフェイスにはc++ を使用予定です。
ソースコードのビルド、テスト、デバッグが終わり次第、githubにて公開予定です。

よろしくお願いします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VRMMOゲーム開発:ボイスチャットの実装

jsonデータの取り扱い

jsonデータは以下の経路で伝送します。

1.クライアントよりnginxへ
httpプロトコルにて伝送、送信にはc++ を使用

2.nginxよりfcgiを用いてmysqlへjson格納
c++ を使用します。
(web VRを意識)

3.mysqlよりjsonデータをhttpプロトコルにてクライアントに伝送
c++ を使用します。

4.クライアント-サーバ間通信にはudpまたは上位プロトコルを使用、c++ を使用します。(速さ重視)

今考えているのは、変数jsonを定義しjsonを再代入する手法です。

ゲームオブジェクトの更新

各種コンポーネント及びスクリプトを追加したゲームオブジェクトを生成します。
元データはmysqlよりjsonデータにて伝送。

流れとしては

json utility.from json

json utility.to json

json utility.from json overwrite

ボイスチャット

マイク入力は

microphoneを用いてaudioclip化

audio sourceで再生します。

on audio filterreadで録音してゲームオブジェクトに反映します。(リップシンク)

(恐らく、同じ要領で、各種ファイル形式を扱えます)

音量表示、音量調節がしたいです。

最後に、 audio listenerコンポーネントでスピーカー出力出来ます。

steam audio sdkを用いることで、立体音響を再現出来ます。

注)理論段階です。あしからず。
ソースコードは、unity内部はc#、外部及びインターフェイスにはc++ を使用予定です。
ソースコードのビルド、テスト、デバッグが終わり次第、githubにて公開予定です。

よろしくお願いします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity(C#)】ParticleSystemのややこしいところまとめ(の予定)

ParticleSystem(シュリケン)

Unityの超便利機能です。
誰でもお手軽にエフェクトの作成が行えます。

しかし!一歩踏み込んでScriptで変更しようと思うと一手間も二手間もかかります!

(の予定)??

Particleを一度利用したことがある人には理解して頂けるかと思いますが、
パラメーターの数が半端じゃなく多いです。(私のような初心者にとっては)

なので、私が利用しようとして詰まったとこを随時更新していきます。
みなさんの方でもこんなの見つけられっこないよ!ってので半日つぶしたなんてエピソードをお持ちでしたら
編集リクエストやコメントでの支援をお願いします。

ぜひ、手を取り合ってParticleを攻略しましょう。(他力本願)

MainModule

Durationの変更はParticleSystemのプレイ中には不可のようです。
duration.PNG

MainModuleをいろいろ変更するサンプル
using UnityEngine;

public class ParticleTest : MonoBehaviour
{
    float durationTimeValue = 5;
    float startLifeTimeValue=3;

    ParticleSystem _ParticleSystem;
    ParticleSystem.MainModule _MainModule;
    ParticleSystem.MinMaxCurve _MinMaxCurve;
    AnimationCurve _AnimationCurve = new AnimationCurve(new Keyframe(0, 0), new Keyframe(1, 1));

    void Start()
    {
        _ParticleSystem = this.gameObject.GetComponent<ParticleSystem>();
        _MainModule = _ParticleSystem.main;
        _MinMaxCurve = _MainModule.startLifetime;
        _AnimationCurve = _MainModule.startLifetime.curve;

        //Durationの変更はParticleSystemのプレイ中には不可のようです
        //_MainModule.duration = durationTimeValue;

        //Loopのオンオフ
        _MainModule.loop = false;

        //StartLifeTimeの値の変更(Constantの場合)
        _MinMaxCurve =startLifeTimeValue;
        _MainModule.startLifetime = _MinMaxCurve;

        //StartLifeTimeのモードの変更(Constant→Curve)
        ParticleSystemCurveMode _ParticleSystemCurveMode = ParticleSystemCurveMode.Curve;
        _MinMaxCurve.mode = _ParticleSystemCurveMode;

        //StartLifeTimeの値の変更(Curveの場合)
        _MinMaxCurve.curve = _AnimationCurve;
        _MainModule.startLifetime = _MinMaxCurve;
    }
}

Emission

これけっこう頻繁に使いますよね。(私だけ?)
任意の処理に応じてパーティクルの量を調節できるのは便利です。

Emissionをだんだん減らして消滅したように見せるサンプル
using UnityEngine;

public class ParticleTest : MonoBehaviour
{
    [SerializeField]
    float deleteSpeed = 0.01f;

    ParticleSystem _ParticleSystem;
    ParticleSystem.EmissionModule _EmissionModule;
    ParticleSystem.MinMaxCurve _MinMaxCurve;

    void Start()
    {
        _ParticleSystem = this.gameObject.GetComponent<ParticleSystem>();
        _EmissionModule = _ParticleSystem.emission;
        _MinMaxCurve = _EmissionModule.rateOverTime;
    }

    void Update()
    {
        //だんだんと消滅させる
        _MinMaxCurve.constant -= deleteSpeed;
        _EmissionModule.rateOverTime = _MinMaxCurve;
    }
}

PSE.gif

粒子のヒットした場所を取得

このサンプル内では粒子は一つしか存在しない状態(Max Particles = 1)にしてあります。

粒子のヒットした場所にキューブが出現するサンプル
using System.Collections.Generic;
using UnityEngine;

public class ParticleHit : MonoBehaviour
{
    [SerializeField]
    GameObject obj;

    List<ParticleCollisionEvent> particleCollisionEventList = new List<ParticleCollisionEvent>();

    ParticleSystem _ParticleSystem;

    void Start()
    {
        _ParticleSystem = this.gameObject.GetComponent<ParticleSystem>();
    }

    //パーティクルの当たった箇所でオブジェクト出現
    void OnParticleCollision(GameObject other)
    {
        _ParticleSystem.GetCollisionEvents(other, particleCollisionEventList);

        Vector3 collisionHitPos = particleCollisionEventList[0].intersection;

        Instantiate(obj,collisionHitPos,Quaternion.identity);
    }
}

PSC.gif

今のところ氷山の一角という表現も適さないボリュームですが、よく使い、よく忘れるので記録しときます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

計算量O(n)で噂のスターリンソートを実装してみた

寝る前にTwitterを見てたらTLにこんなツイートが↓

寝る前に笑わせんなよ寝れねぇじゃねぇか!!ということで実装してみることに

Qiitaには既に先駆者様がいたようですが、自分Haskell読めないです。。。ん~~どう実装したらいいんだろ?とりあえず粛清しながらソートすればいいかソートされてない要素を無視するソートにしとけばいいか、という感じの安直な実装したので間違ったソートなら指摘してくださいm(__)m

CSharp

C#erなのでまずはC#から

Program.cs
using System;
using System.Collections.Generic;

#nullable enable
namespace CSharp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Hello Stalin!");
            WriteStalinSort(new[] { 4 });
            WriteStalinSort(new[] { 6, 2, 5, 7, 3, 8, 8, 4 });
            WriteStalinSort(new[] { 5, 3, 7, 8, 9, 5, 3, 5, 7 });
            /**
             * Hello Stalin!
             * Input: 4
             * StalinBy: 4
             * StalinByDescending: 4
             * Input: 6, 2, 5, 7, 3, 8, 8, 4
             * StalinBy: 6, 7, 8, 8
             * StalinByDescending: 6, 2
             * Input: 5, 3, 7, 8, 9, 5, 3, 5, 7
             * StalinBy: 5, 7, 8, 9
             * StalinByDescending: 5, 3, 3
             */
        }

        public static void WriteStalinSort(int[] source)
        {
            Console.WriteLine($"Input: {string.Join(", ", source)}");
            Console.WriteLine($"StalinBy: {string.Join(", ", source.StalinBy(x => x))}");
            Console.WriteLine($"StalinByDescending: {string.Join(", ", source.StalinByDescending(x => x))}");
        }
    }

    public static class StalinSort
    {
        public static IReadOnlyList<TSource> StalinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
        {
            return source.StalinBy(keySelector, Comparer<TKey>.Default);
        }

        public static IReadOnlyList<TSource> StalinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
        {
            return stalinSort(source, keySelector, comparer, false);
        }

        public static IReadOnlyList<TSource> StalinByDescending<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
        {
            return source.StalinByDescending(keySelector, Comparer<TKey>.Default);
        }

        public static IReadOnlyList<TSource> StalinByDescending<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
        {
            return stalinSort(source, keySelector, comparer, true);
        }

        private static IReadOnlyList<TSource> stalinSort<TSource, TKey>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer, bool descending)
        {
            if (source is null)
            {
                throw new ArgumentNullException(nameof(source));
            }
            if (keySelector is null)
            {
                throw new ArgumentNullException(nameof(keySelector));
            }
            if (comparer is null)
            {
                throw new ArgumentNullException(nameof(comparer));
            }

            IList<TSource> result = new List<TSource>();
            using IEnumerator<TSource> enumerator = source.GetEnumerator();

            if (enumerator.MoveNext())
            {
                TSource element = enumerator.Current;
                TKey lastKey = keySelector(element);

                result.Add(element);

                while (enumerator.MoveNext())
                {
                    element = enumerator.Current;
                    TKey newKey = keySelector(element);
                    int compare = descending ? comparer.Compare(lastKey, newKey) : comparer.Compare(newKey, lastKey);

                    if (0 <= compare)
                    {
                        result.Add(element);
                        lastKey = newKey;
                    }
                }
            }

            return (IReadOnlyList<TSource>)result;
        }
    }
}

Kotlin

StalinSort.kt
fun main(args: Array<String>) {
    println("Hello Stalin!")
    writeStalinSort(intArrayOf(4))
    writeStalinSort(intArrayOf(6, 2, 5, 7, 3, 8, 8, 4))
    writeStalinSort(intArrayOf(5, 3, 7, 8, 9, 5, 3, 5, 7))
    /**
     * Hello Stalin!
     * Input: 4
     * StalinBy: 4
     * StalinByDescending: 4
     * Input: 6,2,5,7,3,8,8,4
     * StalinBy: 6,7,8,8
     * StalinByDescending: 6,2
     * Input: 5,3,7,8,9,5,3,5,7
     * StalinBy: 5,7,8,9
     * StalinByDescending: 5,3,3
     */
}

private fun writeStalinSort(source: IntArray) {
    println("Input: ${source.joinToString(",")}")
    println("StalinBy: ${source.asSequence().stalinBy().joinToString(",")}")
    println("StalinByDescending: ${source.asSequence().stalinByDescending().joinToString(",")}")
}

fun <T> Sequence<T>.stalinBy(): List<T> where T : Comparable<T> {
    return stalinSort(this, false)
}

fun <T> Sequence<T>.stalinByDescending(): List<T> where T : Comparable<T> {
    return stalinSort(this, true)
}

private fun <T> stalinSort(source: Sequence<T>, descending: Boolean): List<T> where T : Comparable<T> {
    val iterator = source.iterator()
    val result = mutableListOf<T>()

    if (iterator.hasNext()) {
        var lastElement = iterator.next()
        result.add(lastElement)

        while (iterator.hasNext()) {
            val element = iterator.next()
            val compare = when (descending) {
                true -> element <= lastElement
                false -> lastElement <= element
            }
            if (compare) {
                result.add(element)
                lastElement = element
            }
        }
    }

    return result
}

Crystal

最近少しずつ学び始めてるCrystal(C#, Kotlinより雑いのは許して)

stalin_sort.cr
# Crystal 0.27.2 [60760a546] (2019-02-05)
# LLVM: 4.0.0
# Default target: x86_64-unknown-linux-gnu

puts "Hello Stalin!"
write_stalin_sort [4]
write_stalin_sort [6, 2, 5, 7, 3, 8, 8, 4]
write_stalin_sort [5, 3, 7, 8, 9, 5, 3, 5, 7]

# Hello Stalin!
# Input: [4]
# StalinBy: [4]
# StalinByDescending: [4]
# Input: [6, 2, 5, 7, 3, 8, 8, 4]
# StalinBy: [6, 7, 8, 8]
# StalinByDescending: [6, 2]
# Input: [5, 3, 7, 8, 9, 5, 3, 5, 7]
# StalinBy: [5, 7, 8, 9]
# StalinByDescending: [5, 3, 3]

def write_stalin_sort(source : Array(Int32))
  puts "Input: #{source}"
  puts "StalinBy: #{stalin_by source}"
  puts "StalinByDescending: #{stalin_by_descending source}"
end

def stalin_by(source : Array(Int32))
  stalin_sort source, false
end

def stalin_by_descending(source : Array(Int32))
  stalin_sort source, true
end

def stalin_sort(source : Array(Int32), descending : Bool)
  if source.size <= 1
    return source
  end

  result = Array(Int32).new
  lastElement = source[0]
  result << lastElement

  i = 1
  while i < source.size
    element = source[i]
    compare = if descending
                element <= lastElement
              else
                lastElement <= element
              end
    if compare
        lastElement = element
        result << element
    end

    i += 1
  end

  result
end

Repository

ソースはGitHubに置いてます

GitHubにリポジトリー置いてるときにTopic登録されてるなーと思ったら更なる先駆者がいた模様

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windowsフォームでの高DPI(Hi-DPI)対応のコツ

はじめに

Windowsフォームアプリをなるべく簡単に高DPI(Hi-DPI)対応させるコツをまとめました。
Windowsフォームの自動スケーリング機能を活用します。

今回やらないこと

下記機能が.NET Framework 4.7でサポートされましたが、実装コストが高いので対応はしません。

  • 起動中のDPI変更サポート (Dynamic DPI)
  • モニタ別のDPIサポート (Per monitor DPI)

対応方法

アプリケーションマニフェストの設定をする

プロジェクトにアプリケーションマニフェストファイルapp.manifestを追加し、下記の設定を記入します。
この設定でWindowsが高DPIサポート済と認識してくれるので、表示がぼやけることが無くなります。

app.manifest
<application xmlns="urn:schemas-microsoft-com:asm.v3">
  <windowsSettings>
    <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
  </windowsSettings>
</application>

フォームに高DPI対応のための一連の設定をする

フォームのデザイン時にはVisual StudioをDPI 100%モードで起動する

高DPI環境下で開発をしている場合、フォームデザイナを開いたとき、下記のような表示が出ます。
必ず毎回「100% のスケールでVisual Studio を再起動します」を選択してください
でないと、コントロールの配置が高DPI前提となり、DPI 100%環境で表示が崩れてしまいます。
※DPI 100%環境で開発している場合には何もしなくてOKです
image.png

AutoScaleModeプロパティをDpiモードにする

フォームのAutoScaleModeプロパティをDpiモードに変更しておきます。
AutoScaleModeプロパティは、Windowsフォームに備わっている自動スケーリング機能の挙動を制御するものです。
デフォルトのFontモードでは、DPIの比率ではなくフォントの大きさを基準にスケーリングが行われます。
ピクセル単位の座標を扱う処理がある場合、Fontモードは不適です。
デザイナの挙動にも変な影響があり(後述)、使わないほうがよいです。

フォームのFontはYu Gothic UIかMeiryo UIにする

MS UI Gothic、MS Pゴシック等、ビットマップ部分を含むフォントは高DPI環境下でレイアウトが崩れる原因になります。
Yu Gothic UI、Meiryo UIのようなビットマップ部分を含まないフォントを使いましょう。
フォントを変更する際、AutoScaleModeFontのままだと、フォーム全体のサイズが変わってしまうのでご注意ください。

ピクセル単位の操作をする箇所には係数をかける

ここまでの設定で、殆どのコントロールは自動でDPIの制御してくれますが、一部のコントロール(DataGridViewの列幅等)や、描画処理では個別に高DPI対応をする必要があります。
下記のようなコードで96dpiに対する現在のDPI比率を宣言しておき、補正が必要な個所で乗算します。
対応個所を洗い出すには、トライアンドエラーが必要になるかもしれません。

static readonly float DpiScale = ((new System.Windows.Forms.Form()).CreateGraphics().DpiX) / 96;

まとめ

Windowsフォームアプリは高DPI非対応と見なされることが多いですが、
コツをつかめば標準機能任せでも、いい感じに対応できることが分かりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む