20190714のC#に関する記事は7件です。

AzurePlayFabのとても便利なマッチメイキング(マッチング)機能を使ってみた

まえがき

マッチメイキングの処理はオンラインゲームにはかかせないですよね。

でもマッチメイキングを実装しようとすると、やりたいことはプレイヤーに適切なルームやバトルのIDを渡したいだけなんですが、様々なルール(プレイヤーのレベルやレートの考慮、1vs1 or チームバトル or バトルロイヤル)や排他制御を考える必要があって大変です。

そんなマッチメイキングを PlayFab を使えば簡単に実装できると聞いたので試してみました。

※今回は複雑なルールは取り上げません。
※マッチメイキングは Public Preview の機能です。

作ってみたもの

matchmakingsample.gif

PlayFab を使ったマッチングの流れ

  • 事前に GameManager でマッチング処理用のキューを作り、バトルの人数やマッチングする条件を定義します。
  • Player が上記のキューに MatchmakingTicket というチケットを投げます。
  • キューにバトルの人数分のチケットが貯まるとマッチングされます。
  • Player が投げたチケットをポーリングしておけば MatchID が取得できるので、同じMatchID の Player を集めてバトルを開始したりします。

使用する PlayFab の API

1. 事前準備(GameManager でマッチング処理用のキューを作っておく)

GameManager へログインし、マルチプレイヤー > マッチメイキング > 新しいキューをクリックします。
image.png

キューの構成を変更してキューを作成します。
必須項目は キュー名対戦人数 です。
今回は 1vs1 のマッチングを作りますので、それっぽいキュー名を指定し、対戦人数は必ず2人なので 2to2 に設定しておきます。
image.png

キューができました。
GameManager での事前準備はここまでです。
image.png

2. 最低限のコードでシンプルなマッチングを動かしてみる

Unity でこんなコードを書いてみました。
これを動かすと先に張ったサンプルのようにシンプルな1vs1のマッチングを行うことができます。

using PlayFab;
using PlayFab.ClientModels;
using PlayFab.MultiplayerModels;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class SampleSceneController : MonoBehaviour
{
    // 処理中のメッセージは雑に全部これに表示します。
    [SerializeField] Text textBox;

    public void Start()
    {
        textBox.text = "ログイン中...\n";

        // PlayFabにいつも通りログインします。
        var request = new LoginWithCustomIDRequest { CustomId = "MyCustomId", CreateAccount = true };
        PlayFabClientAPI.LoginWithCustomID(request, OnLoginSuccess, OnFailure);

        void OnLoginSuccess(LoginResult result)
        {
            textBox.text += "ログインしました!\n\n";

            // ログインできたので続けてマッチングの処理を呼びます。
            Matchmaking();
        }
    }

    private void Matchmaking()
    {
        textBox.text += "マッチメイキングチケットをキューに積みます...\n";

        // プレイヤーの情報を作ります。
        var matchmakingPlayer = new MatchmakingPlayer
        {
            // Entityは下記のコードで決め打ちで大丈夫です。
            Entity = new PlayFab.MultiplayerModels.EntityKey
            {
                Id = PlayFabSettings.staticPlayer.EntityId,
                Type = PlayFabSettings.staticPlayer.EntityType
            }
        };

        var request = new CreateMatchmakingTicketRequest
        {
            // 先程作っておいたプレイヤー情報です。
            Creator = matchmakingPlayer,
            // マッチングできるまで待機する秒数を指定します。最大600秒です。
            GiveUpAfterSeconds = 30,
            // GameManagerで作ったキューの名前を指定します。
            QueueName = "1vs1Battle"
        };

        PlayFabMultiplayerAPI.CreateMatchmakingTicket(request, OnCreateMatchmakingTicketSuccess, OnFailure);

        void OnCreateMatchmakingTicketSuccess(CreateMatchmakingTicketResult result)
        {
            textBox.text += "マッチメイキングチケットをキューに積みました!\n\n";

            // キューに積んだチケットの状態をマッチングするかタイムアウトするまでポーリングします。
            var getMatchmakingTicketRequest = new GetMatchmakingTicketRequest
            {
                TicketId = result.TicketId,
                QueueName = request.QueueName
            };

            StartCoroutine(Polling(getMatchmakingTicketRequest));
        }
    }

    IEnumerator Polling(GetMatchmakingTicketRequest request)
    {
        // ポーリングは1分間に10回まで許可されているので、6秒間隔で実行するのがおすすめです。
        var seconds = 6f;
        var MatchedOrCanceled = false;

        while (true)
        {
            if (MatchedOrCanceled)
            {
                yield break;
            }

            PlayFabMultiplayerAPI.GetMatchmakingTicket(request, OnGetMatchmakingTicketSuccess, OnFailure);
            yield return new WaitForSeconds(seconds);
        }

        void OnGetMatchmakingTicketSuccess(GetMatchmakingTicketResult result)
        {
            switch (result.Status)
            {
                case "Matched":
                    MatchedOrCanceled = true;
                    textBox.text += $"対戦相手が見つかりました!\n\nMatchIDは {result.MatchId} です!";
                    return;

                case "Canceled":
                    MatchedOrCanceled = true;
                    textBox.text += "対戦相手が見つからないのでキャンセルしました...";
                    return;

                default:
                    textBox.text += "対戦相手が見つかるまで待機します...\n";
                    return;
            }
        }
    }

    void OnFailure(PlayFabError error)
    {
        Debug.Log($"{error.ErrorMessage}");
    }
}

3. プレイヤーのレートやレベル差を考慮したマッチングを動かしてみる

実際のゲームではレートやレベル差によって、初心者と上級者のマッチングを分けたりしますよね。

PlayFab ではどのように制御すればよいのか試してみました。

3.1. キューにルールを追加する

先程キューを作ったときは最低限の内容しか定義しませんでした。
今回は以下のようなルールを追加してみます。

このルールを追加することで、Rate の差が 101 以上のプレイヤー同士はマッチングしなくなります。
image.png

3.2. プレイヤーの Rate 情報をチケットに含める

先程のコードではチケット内の MatchmakingPlayer には Entity 情報しか含んでいませんでした。
今回は Rate の情報をもたせるために Attributes を使用します。

        // マッチングさせるプレイヤーの情報を作ります。
        var matchmakingPlayer = new MatchmakingPlayer
        {
            // Entityは下記のコードで決め打ちで大丈夫です。
            Entity = new PlayFab.MultiplayerModels.EntityKey
            {
                Id = PlayFabSettings.staticPlayer.EntityId,
                Type = PlayFabSettings.staticPlayer.EntityType
            },
            // これ以下を追記
            Attributes = new MatchmakingPlayerAttributes
            {
                // このプレイヤーは Rate 900~1100 のプレイヤーとしかマッチングしない
                DataObject = new { Rate = 1000 }
            }
        };

これだけでレートやレベル差を考慮したマッチングを実装することができました。
これは楽で良いですね。

あとがき

今回はマッチメイキングの基本の部分を触ってみました。

まだ公式ドキュメントを読み切れていないのですが、PlayFab のマッチメイキング機能はもっと複雑なマッチングルールやチームバトルにも対応しているということですので、引き続き触ってみようと思います。

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

それは「else if」ではない「else」と「if」だ

 言語によっては elseif なんていうのもありますが、「else if」と言われているものは、elseのときに実行する処理がif~だという超小ネタです。

「if や else は必ず {} で囲め」というコーディング規約に書いているのに、そのコーディング規約の中でもelseやifを……

if (/*条件*/) {
  // 略
} else if (/*条件*/) {
  // 略
}

……みたいに書かれていたりすると「こいつ分かってないな」と思ってしまいます。

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

C#入門【第2講・変数と定数と関数】

前回の復習

  • C#はオブジェクト指向プログラミング言語である.
  • オブジェクト指向は,クラスを使ってオブジェクトを作成していく.
  • オブジェクトは属性(フィールドまたはプロパティ)と操作(メソッド)から成る.
  • クラスはオブジェクトの設計図であり,名前空間の中に記述される.
  • 名前空間はusingディレクティブを用いて表記を省くことができる.

変数と定数

データには,変数定数が存在します.意味は,数学での用法とほぼ同じです.
変数は,数学のxみたいなもので,その代入する値を自由に変えることができます.
定数は,変えることができない値です.

ここではデータのについて説明をしましょう.
型,とは値の種類のことで,型によってデータの扱われ方が変わります.
どういうことかというと,例えば数値同士でないと足し算はできません.
(数値と文字の足し算はできない,ということ)
コンピュータが「このデータは数値ですよ」とわからないと,行うべき処理がわからないのです.

まず,データは組み込み型ユーザー定義型の二種類に分けられます.

組み込み型

組み込み型は,プリミティブ型とも呼ばれます.
組み込み,というように元から言語仕様に組み込まれている型です.
次の表は,筆者が考える最も基本的な6種類の型です.

種類 名称 サイズ
整数型 int 32bit
整数型 long 64bit
実数型 double 64bit
論理型 bool 8bit
文字型 char 16bit
文字列型 string

例えば,int型の変数は,32bitの範囲の整数値を代入することができます.
32bitの範囲というのは,

-2^{31} 〜 2^{31}-1

の範囲に該当する整数値です.

ユーザー定義型

あらかじめ言語仕様に含まれている組み込み型に対して,ユーザが自由に定義できるのがユーザー定義型です.

  • 列挙型
  • 構造体
  • 配列

がこれに該当します.
これらに関してはそれぞれ使う時に説明します.

変数の宣言

int foo = 1;
double bar = 3.5;
char baz = 'あ';
string bin = "Hello World";

このように,変数を宣言するときにはその変数の型を指定しないといけません.
他の言語では指定しなくてもいい場合もあります.
C#のように,型指定しないといけないことを静的型付けといいます.

また,char型に値を代入するときはシングルクォーテーション(' ')を,
string型に値を代入するときはダブルクォーテーション(" ")を使う必要があります.

定数の宣言

const int foo = '1';
const double bar = 3.5;
const char baz = 'あ';
const string bin = "Hello World";

先頭に const をつけるだけで定数として宣言できます.
定数として宣言したデータは,値を変更することができません.

メソッドと関数

上述したように,オブジェクトは属性(フィールドまたはプロパティ)と操作(メソッド)から成ります.
以降は,特別な注意書きや記載をしない限り属性のことをフィールド,操作のことをメソッドと呼びます.
メソッドはオブジェクトの中にある関数のことを,特別にいったものです.
C#は,関数をオブジェクトの内部にしか定義できないため,必然的に全てメソッドになります.

関数 function は,数学の関数と同じ概念で,
値を入力すると,関数で定義している処理を行い,その結果を出力する機能のことを言います.

f(x)=ax^2+bx+c

この式は2次方程式の一般式です.
これをC#で書くと,

quadraticEquation.cs
using System;

namespace CSharpPractice {
    class FunctionExample {
        static void Main() {
            Console.WriteLine(QuadraticEquation(a, b, c, x));
        }

        static double QuadraticEquation(double a, double b, double c, double x) {
            return a * Math.Pow(x, 2) + b * x + c;
        }
    }
}

となります.
このメソッドを元に解説を進めていきます.

名前空間の宣言

前回,グローバル名前空間とSystem名前空間についての説明をしました.
クラスやメソッドの名前を自由につけることができるように,
quadraticEquiation.csの3行目みたいに,namescapeと書くことで,名前空間も自由に命名できます.

命名規約

名前空間,クラス,フィールド,メソッドはすべて自由に命名できます.
しかし,特定のキーワードをその名前にすることはできません(C#の場合エラーになる).
例えば usingstaticreturn など,あらかじめ役割が決まってる言葉は,名前にできません.
こういったキーワードのことを予約語といいます.

また,大文字と小文字は区別されますし,
C#でクラスやメソッドの先頭は大文字ではじめる規則があります.

より正しく言えば,大文字の使用規則 | Microsoft Docs によると,
パラメータ(引数のこと,後述)以外は PascalCase で記述する規則のようです.

PascalCase は,単語の先頭を大文字にしてつなげて記述する表記法です.
例えば CSharpPracticeFunctionExample のようにです.

対して,最初の単語の先頭のみ小文字にし,それ以外の単語の先頭を大文字にする記法が CamelCase です.

引数と戻り値

上述したように,関数には入力される値と出力される値があり,
入力された値をどう変換するかという機能のことを関数といいます.

プログラミングにおける入力される値のことを引数またはパラメータといい,
出力される値のことを戻り値または返り値といいます.

メソッドの種類によっては引数や戻り値を持たないものもあります.

メソッドの宣言

前回の記事で説明した通り,メソッドの宣言には

静的か動的か 戻り値の型 メソッド名(引数)

の四つの情報が必要です.
メソッド名は上述のように PascalCase で書きます.

今回は戻り値の型と引数について説明します.

メソッドの引数

引数はメソッド名の後の () の中に記述します.
() の中身が空のときには引数が存在しませんが,その場合でも () は書く必要があります.

quadraticEquiation.csを例にすると,
quadraticEquiationメソッドの引数は double a double b double c double x の四つです.
このとき,全ての引数の型は double であり,
整数のみならず64bitで表現可能の範囲における実数をとることがわかります.

メソッドを宣言する時には,()の中で,型と引数名を同時に指定します.
複数指定するにはカンマで区切ります.
メソッドを宣言する時に指定された引数のことを仮パラメータと呼びます.

宣言したメソッドの中では,仮パラメータの名前を使って処理を記述します.

メソッドの戻り値

メソッドを宣言するときには,戻り値の型を指定する必要があります.
すなわち,戻り値が数値なのか,文字なのか,文字列なのかなどを指定しておくのです.

returnのあとに戻り値を書くことで,つまり

return 戻り値

とすることで,関数に戻り値を持たせることができます.

static double QuadraticEquation(double a, double b, double c, double x) {
    return a * Math.Pow(x, 2) + b * x + c;
}

をみると,引数に a b c x があり,
それらの値を演算した結果を戻り値として返しています.
この a * Math.Pow(x, 2) + b * x + c の結果が小数になることもあるため,
戻り値の型をdoubleとしておく必要があります.

戻り値を持たない場合,戻り値の型の代わりにvoidと書きます.
つまりMainメソッドはvoidを指定しているので,戻り値を持たないことがわかります.

戻り値の型がvoid以外の時,つまり何かしらの型が指定されているとき,
必ず return 戻り値 と書いた文が必要になります.
returnのあとに戻り値を書かず return単独で使用すると,その時点でなんの戻り値も返さず処理を終了します.
このreturn文はvoidを指定した場合でも使えます.

Mathクラス

最後に数学的な処理を行うことができる,Mathクラスについての説明です.
これもまたSystem名前空間の中で定義されています.
今回の例ではPowメソッドを使っていますが,これは累乗計算のメソッドで,
一つ目の引数を,二つ目の引数の数だけ累乗するメソッドです.

この例の場合,xを2乗します.

他にも切り上げや切り捨て,乱数など様々な数学的処理を行うメソッドが定義されています.

まとめ

  • データには変数と定数があり,データの型を指定する必要がある.
  • 型は組み込み型とユーザー定義型に分けられる.
  • constを使うと定数として宣言でき,これは再代入ができなくなる.
  • すべての関数はメソッドで,引数と戻り値を持つ場合があり,その型を指定する必要がある.
  • returnで値を戻すことができるが,returnのみで使うと処理を終了する.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity(C#)】オブジェクトに当たると途切れるレーザーの実装方法

レーザー

RayLineRendererで実装します。

Rayがオブジェクトに当たったかどうかの判定を行い、LineRendererの終点の位置を変えることで
レーザーの途切れを表現します。

そのためにはRayLineRendererの始点、終点を合わせておく必要があります。

RayとLineRendererの位置を合わせる
 void OnRay()
    {      
        float lazerDistance = 10f;
        Vector3 direction = hand.transform.forward * lazerDistance;
        Vector3 pos = hand.transform.position;

        RaycastHit hit;
        Ray ray = new Ray(pos, hand.transform.forward);
        lineRenderer.SetPosition(0,hand.transform.position);

        if (Physics.Raycast(ray, out hit, lazerDistance))
        {
            lineRenderer.SetPosition(1, pos + direction);
        }

        Debug.DrawRay(ray.origin, pos + direction, Color.red, 0.1f);
    }

画像のようにぴったりと合わせることができました。(赤い線=Ray,青い線=LineRenderer)
ray_Line.PNG

貫通してしまう

先程のコードのままでは画像のようにオブジェクトを貫通してしまいます。
Penetration.PNG

この問題を解決するうえで、RayLineRendererをぴったりと合わせたことが鍵となります。

Rayはオブジェクトとの衝突判定を行うことができます。
また、オブジェクトと衝突した座標を取得することもできます。

つまり、LineRendererの終点にRayが衝突した座標を当てはめれば
オブジェクトに当たった箇所でレーザー(LineRenderer)が途切れているように見せることができる
ということです。

コードに落とし込むとこうです。

        if (Physics.Raycast(ray, out hit, lazerDistance))
        {
            hitPos = hit.point; //オブジェクトとの衝突座標を取得
            lineRenderer.SetPosition(1, hitPos); //LineRendererの終点に当てはめる
        }

デモ

OculusQuestでビルドしたデモになります。
Ray_LineRenderer.gif

遮蔽物でレーザーが途切れているように見えます。ばっちりです。

デモにおいて、始点も少しずらしているので
始点のずらし方に関しても最終的なコードと合わせて記述していきます。

最終的なコード

using UnityEngine;

[RequireComponent(typeof(LineRenderer))]
public class Lazer : MonoBehaviour
{
    [SerializeField]
    GameObject hand;

    LineRenderer lineRenderer;
    Vector3 hitPos;
    Vector3 tmpPos;

    float lazerDistance = 10f;
    float lazerStartPointDistance = 0.15f;
    float lineWidth = 0.01f;

     void Reset()
    {
        lineRenderer = this.gameObject.GetComponent<LineRenderer>();
        lineRenderer.startWidth = lineWidth;
    }

    void Start()
    {
        lineRenderer = this.gameObject.GetComponent<LineRenderer>();
        lineRenderer.startWidth = lineWidth;
    }


    void Update()
    {
        OnRay();      
    }

    void OnRay()
    {      
        Vector3 direction = hand.transform.forward * lazerDistance;
        Vector3 rayStartPosition = hand.transform.forward*lazerStartPointDistance;
        Vector3 pos = hand.transform.position;
        RaycastHit hit;
        Ray ray = new Ray(pos+rayStartPosition, hand.transform.forward);

        lineRenderer.SetPosition(0,pos + rayStartPosition);

        if (Physics.Raycast(ray, out hit, lazerDistance))
        {
            hitPos = hit.point;
            lineRenderer.SetPosition(1, hitPos);
        }
        else
        {      
            lineRenderer.SetPosition(1, pos + direction);
        }

        Debug.DrawRay(ray.origin, ray.direction * 100, Color.red, 0.1f);

    }
}

始点をずらす

始点をずらしているのは下記の箇所です。

    Vector3 rayStartPosition = hand.transform.forward*lazerStartPointDistance;
    Ray ray = new Ray(pos+rayStartPosition, hand.transform.forward);
    Vector3 pos = hand.transform.position;
    lineRenderer.SetPosition(0,pos + rayStartPosition);

Shaderで始点をずらしているようにみせる1こともできますが、
今回は始点そのものの座標を調節してます。

手にコライダーが付いている状態で手のTransformを参照してRayを出した場合、
手のPivot、コライダーの大きさによっては
Rayが手のコライダーに衝突してしまって判定が取れない場合があります。
そもそものRayの始点をずらすことで意図しない衝突を回避できます。

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

log4netのConfig

久々にC#を使って、LOGを出力する際にいろいろ調べたのがめんどくさかったので
自分なりに使いやすいLOG4NET用のコンフィグを残しておく。

アセンブリに関する情報

AssemblyInfo.csに以下を追記。
一番下でOK

[assembly: log4net.Config.XmlConfigurator(ConfigFile = @"Log4net.Config.xml", Watch = true)]

Configの実際の内容

以下の内容のLog4net.Config.xmlを作成

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <log4net>

    <!-- 通常ログ -->
    <appender name="InfoLogAppender" type="log4net.Appender.RollingFileAppender">
      <File value=".\\Logs\\Info_" />
      <!-- ファイル名は日付ごと -->
      <param name="DatePattern" value='yyyyMMdd".log"' />

      <param name="RollingStyle" value="date" />
      <param name="StaticLogFileName" value="false" />
      <param name="MaximumFileSize" value="10MB" />
      <param name="MaxSizeRollBackups" value="10" />

      <param name="AppendToFile" value="true" />
      <filter type="log4net.Filter.LevelRangeFilter">
        <param name="LevelMax" value="WARN" />
        <param name="LevelMin" value="INFO" />
      </filter>

      <layout type="log4net.Layout.PatternLayout">
        <ConversionPattern value="%date [%thread] [%-5level] %class %method (L%line) - %message%n" />
      </layout>
    </appender>

    <!-- エラーログ -->
    <appender name="ErrorLogAppender" type="log4net.Appender.RollingFileAppender">
      <File value=".\\Logs\\Error_" />
      <!-- ファイル名は日付ごと -->
      <param name="DatePattern" value='yyyyMMdd".log"' />

      <param name="RollingStyle" value="date" />
      <param name="StaticLogFileName" value="false" />

      <param name="AppendToFile" value="true" />

      <filter type="log4net.Filter.LevelRangeFilter">
        <param name="LevelMax" value="FATAL" />
        <param name="LevelMin" value="ERROR" />
      </filter>

      <param name="MaximumFileSize" value="10MB" />
      <param name="MaxSizeRollBackups" value="10" />

      <layout type="log4net.Layout.PatternLayout">
        <ConversionPattern value="%date [%thread] [%-5level] %class %method (L%line) - %message%n" />
      </layout>
    </appender>

    <!-- デバッグ用:分割ファイル出力 -->
    <appender name="DebugLogAppender" type="log4net.Appender.RollingFileAppender">
      <File value=".\\Logs\\Trace_" />
      <!-- ファイル名は日付ごと -->
      <param name="DatePattern" value='yyyyMMdd".log"' />

      <!-- ファイル名は日付ごと -->
      <param name="RollingStyle" value="date" />
      <param name="StaticLogFileName" value="false" />

      <param name="AppendToFile" value="true" />

      <filter type="log4net.Filter.LevelRangeFilter">
        <param name="LevelMax" value="FATAL" /> 
        <param name="LevelMin" value="TRACE" />
      </filter>

      <param name="MaximumFileSize" value="10MB" />
      <param name="MaxSizeRollBackups" value="10" />

      <layout type="log4net.Layout.PatternLayout">
        <ConversionPattern value="%date [%thread] [%-5level] %class %method (L%line) - %message%n" />
      </layout>
    </appender>

    <!-- コンソール -->
    <appender name="ColoredConsole"  type="log4net.Appender.ColoredConsoleAppender">
      <mapping>
        <level value="FATAL" />
        <foreColor value="White" />
        <backColor value="Red" />
      </mapping>
      <mapping>
        <level value="ERROR" />
        <foreColor value="White" />
        <backColor value="Purple" />
      </mapping>
      <mapping>
        <level value="WARN" />
        <foreColor value="Purple" />
        <backColor value="White" />
      </mapping>
      <mapping>
        <level value="INFO" />
        <foreColor value="White" />
        <backColor value="Blue" />
      </mapping>
      <mapping>
        <level value="DEBUG" />
        <foreColor value="White" />
        <backColor value="Green" />
      </mapping>

      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%d[%t] %p - %m%n"/>
      </layout>
    </appender>

    <root>
      <!-- TRACE以上のログを記録 -->
      <level value="TRACE" />
      <!-- 使用する Appender -->
      <appender-ref ref="InfoLogAppender" />
      <appender-ref ref="ErrorLogAppender" />
      <appender-ref ref="DebugLogAppender" />
      <appender-ref ref="ColoredConsole" />
    </root>

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

【C#開発環境構築】Visual Studio 2019インストールからHello Worldまで

C#の勉強用に環境構築をしたので、その備忘録です。

環境

Windows 10

Visual Studioインストール

MicrosoftのページからCommunity版をダウンロードします。

01.png

ダウンロードしたインストーラーを起動します。

インストールするコンポーネントを選択する画面が表示されます。

とりあえず、「.NETデスクトップ開発」にだけチェックを付けてインストールします。

02.png

プロジェクトの作成

Visual Studioを起動し、新しいプロジェクトを作成します。

03.png

コンソールアプリ(.NET Framework)を選択し、次へいきます。

04.png

プロジェクト名、保存場所等を設定し、作成します。

Hello World表示

Main関数の中に、

Console.WriteLine("Hello World!");

と打ち込みます。

05.png

Ctrl+F5もしくは、デバッグ->デバッグなしで開始からプログラムを実行します。

06.png

無事、コンソール上にHello World!が表示されました。

07.png

追加のコンポーネントをインストールするとき

新しいプロジェクトの作成画面から、「さらにツールと機能をインストールする」をクリックすると、Visual Studio Installerが立ち上がり、必要な機能をインストールできます。

08.png

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

C# でgRPC サーバーの開発環境を作る

概要

C# でgRPC サーバーの開発環境を作成するための手順です。
Visual Studio 2019 を使ってます。
だいたいこれの通りです。
https://docs.microsoft.com/ja-jp/aspnet/core/grpc/basics?view=aspnetcore-3.0
https://grpc.io/docs/quickstart/csharp/

C# のプロジェクトを作成する

「コンソールアプリ(.NET Core)」のプロジェクトを作成します。
image.png

パッケージをインストールする

NuGet で以下のパッケージをインストールします。
* Grpc.Core
* Grpc.Tools
* Google.Protobuf

プロジェクトファイルを編集する

プロジェクトファイルに、次のコードを追加します。
proto ファイルからC# のソースコードを生成するための設定です。

  <ItemGroup>
    <Protobuf Include="**/*.proto" OutputDir="%(RelativePath)" CompileOutputs="false" GrpcServices="Server" />
  </ItemGroup>

上記はサーバーサイドのファイルを生成する場合です。クライアントサイドの場合はGrpcServicesClient にします。
サーバー・クライアント両方のファイルを生成する場合は、Both にします。

proto ファイルを追加する

例として次のようなproto ファイルを追加します。

helloworld.proto
syntax = "proto3";

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

追加すると、自動的にC# のファイルが生成されます。

サーバー起動

下のようなコードでサーバーが動きます。

using Grpc.Core;
using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class GreeterImpl : Greeter.GreeterBase
    {
        // Server side handler of the SayHello RPC
        public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
        {
            return Task.FromResult(new HelloReply { Message = "Hello " + request.Name });
        }
    }

    class Program
    {
        const int Port = 50051;

        public static void Main(string[] args)
        {
            Server server = new Server
            {
                Services = { Greeter.BindService(new GreeterImpl()) },
                Ports = { new ServerPort("localhost", Port, ServerCredentials.Insecure) }
            };
            server.Start();

            Console.WriteLine("Greeter server listening on port " + Port);
            Console.WriteLine("Press any key to stop the server...");
            Console.ReadKey();

            server.ShutdownAsync().Wait();
        }
    }
}

おわり。

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