2019年11月29日 | ブログ記事

[AdC2019 30日目] C#の記法とか書いたゲームとかの話

b1smuth

前置き

はじめまして。一応HNはb1smuthとしていますが適当です。traPの民としては珍しくTwitterやってません。
なかなかtraP内での活動ができていないのにこんなときだけ参加するというのも(面倒くさい)少々気が引けるというのはあるのですが、AdCについては19なら基本参加ということだったので、私が一番好きなプログラミング言語のC#について書こうかなと。そういうわけで、少し前(とは言っても3Q内ですが)にとある科目(○○○○○○○○)の為に作ったゲームの話とコードを交えながら、C#の言語特徴でより好きになったところを3つだけ話そうかと思います。(色々ありますが頑張って絞ってみました)。
注:著者はプログラミングが特別得意な訳ではありません。趣味程度なので誤りがあるかも。

結論

登場して19年経つ今も仕様が追加され発展し続けるC#。
とてもいい言語なので人気ランキングももっと上昇して欲しいななど思ってます。

開発環境

さて、2DゲームをC#で作ったというのは先述したとおりなのですが、より正しく言い表せば、C#(.net Framework 4.5)でライブラリとしてDX Libraryを使用してシューティングゲームを書いた、という感じです。
Unityならともかく、なぜC# × DX Libraryなんて組み合わせで書いているの?CかC++でいいじゃんみたいな質問はやめてください。色々言い訳はできなくもないですが、正直なところを言えば私が(CとかC++は殆ど)書けないというだけです。はい。
(特に今回は色んな事情で約1ヶ月で一からコードを書かないといけなかったので、CやC++を学んでいる時間はありませんでした…。いつか学びたいですね)

C#固有の機能が役に立ったこと3つ

これもまた先述したように、あんまりダラダラ書くわけにも行かないので特にこの機能のおかげで楽にコーディングできたなって機能3つに絞って書きます。LINQもとてもいい機能ではありますが、話し始めるとかなりの量&n番煎じっぽくなりそうなので今回は言及しません。

1. 多次元配列の使いやすさ

まずはコードの例を示します。

score.csstatic string[,] strs = new string[16, 5]
                { { "あ", "い", "う", "え", "お" },
                { "か", "き", "く", "け", "こ" },
                { "さ", "し", "す", "せ", "そ" },
                { "た", "ち", "つ", "て", "と" },
                { "な", "に", "ぬ", "ね", "の" },
                { "は", "ひ", "ふ", "へ", "ほ" },
                { "ま", "み", "む", "め", "も" },
                { "や", " ", "ゆ", " ", "よ" },
                { "ら", "り", "る", "れ", "ろ" },
                { "わ", "を", "ん", "゛", "゜" },
                {"A", "B", "C", "D", "E" },
                { "F", "G", "H", "I", "J" },
                { "K", "L", "M", "N", "O" },
                { "P", "Q", "R", "S", "T" },
                { "U", "V", "W", "X", "Y" },
                { "Z", "!", "?", "&", "@" } };

もしこれが、例えばJavaだった場合:

score.javaString [][] strs = new String[16][5]
strs = { { "あ", "い", "う", "え", "お" },
{ "か", "き", "く", "け", "こ" },
{ "さ", "し", "す", "せ", "そ" },
{ "た", "ち", "つ", "て", "と" },
{ "な", "に", "ぬ", "ね", "の" },
{ "は", "ひ", "ふ", "へ", "ほ" },
{ "ま", "み", "む", "め", "も" },
{ "や", " ", "ゆ", " ", "よ" },
{ "ら", "り", "る", "れ", "ろ" },
{ "わ", "を", "ん", "゛", "゜" },
{"A", "B", "C", "D", "E" },
{ "F", "G", "H", "I", "J" },
{ "K", "L", "M", "N", "O" },
{ "P", "Q", "R", "S", "T" },
{ "U", "V", "W", "X", "Y" },
{ "Z", "!", "?", "&", "@" } };

と書くようになります。
これだけだとC#の記法の優位性がまだわかりにくいかもしれませんが、例えばstrsがn+1次元配列(nは大きい0以上の整数)としたとき次は同値:
C#: strs[n, n-1, ..., 0]
Java: strs[n][n-1]...[0]

こうしてみると、[]をいちいち書くのって、地味に面倒に感じませんでしょうか?一度だけ使う変数ならまだしも、何度か扱う変数だとすると面倒さが数割増しで感じられます。
その上、見やすさといった観点からも明らかに前者の方が扱いやすいのではないかと思います。

さて、このような二次元配列を何に使ったのか実例として画像を載せてみます。
なお、コードは次のとおりです:

score.csusing static DXLibDll.DX;
static void Draw()
{
    for (int i = 0; i < 16; i++)
    {
        for (int j = 0; j < 5; j++)
        {
            if (i == selectingstr[0] && j == selectingstr[1])
            {
                DrawString(580 - 35 * i, 200 + 35 * j, strs[i, j], GetColor(255,0,0));

            }
            else
            {
                DrawString(580 - 35 * i, 200 + 35 * j, strs[i, j], GetColor(255,255,255);
            }

        }
    }
}

---2

画像のようなランキング登録画面が上のような簡単なコードで描画出来ました。二次元配列を使うことの便利さがわかるコードだと思います。
まあ今回の使用例では二次元配列で比較的見やすいのでstrs[i][j]となってもさほど問題はないかもしれませんが、やはり私は[i, j]の記法の方が好みです。

2. プリプロセッサ

C#のプリプロセッサの中で一番始めに使うようになるのは、おそらく、#region~#endregionでしょうか(ソースはないです)。コードをまとめるのにまあまあの頻度で僕も使ったり、使われているのを見かけたりします。ですが、他にも便利なものがいくつもあります。
特に、今回はゲームという性質上、デバッグ用の表示とか関数(チート用)も色々と用意するひつようがありました。ですが、実際に使用してもらうときにはそれらの機能は当然伏せないといけないわけです。今回はそれを楽に実装するために#define, #if~#else~#endif プリプロセッサが役に立ちました。

開発の時には、普通は(Releaseビルドではなく)Debugビルドを用います。
このことを考えると、Debugビルドのとき(あるいは#define DEBUGを次のコードの前に置いた時)のみ呼び出したいコードを特定の場所で呼ぶということができたらとても便利ですよね。実際、これは#if~#else~#endif プリプロセッサを用いて、例えば次のように簡単に実装できます:

#if DEBUG
    funcDebug(); //デバッグ用関数
#endif

もちろん、このプリプロセッサ機能を持っている言語はC#だけではないのですが(仕様は違いますがCとかにもあります)、Javaのように実装されていない言語も多くあります。多用は可読性が下がるので良くないですが、必要な程度で使えばコーディングは楽になりますし便利な機能と思います。
また、#region~#endregionのように、指定した範囲のコードを簡単にIDE上で折り畳んでくれる言語はおそらくなかった気がします(まあこれに限ってはプログラムの実行には何も及ぼさないプリプロセッサですし、Visual StudioみたいにほぼIDE一択って言語でないと実装の意味が少ないのかもしれませんけどね…)。ですが、これも適度に使う分には作業性も上がる便利な機能の一つだと思います。

3. null演算子

NullPointerException(ぬるぽ)って言葉を聞いたことのある方もいるかと思います。ぬるぽはJavaの例外なのですが、C#でこれと同等の例外に、NullReferenceExceptionというものがあります。
さて、この例外はその名の通りnullなObjectを参照しようとすると発生する例外なのですが、参照したいObjectがnullでないときは値を返し、nullなときは(例外も発生させず)何もしないということをたった1文字"?"をつけるだけで可能になります。例えば(test1, 2は共存不可):

test.csEnemyObject eo; //eoはnullとする

#region test1(例外発生)
    string name = "";
    name = eo.name; //ここでNullReferenceException
#endregion

#region test2(例外回避)
    string name = "default";
    name = eo?.name; //例外は発生しない
    Console.WriteLine(name); //出力結果は"default"
#endregion

のような実行結果になります。
"?."演算子はif文でも使えますので、nullになる可能性のあるObjectのプロパティの値を比較する時、

if (eo != null && eo.name =="Bob") {Console.WriteLine("Enemy is Bob!"); }

みたいな冗長なチェックをするまでもなく、

if (eo?.name =="Bob") {Console.WriteLine("Enemy is Bob!"); }

と書くことができるというわけです。

この記法があるだけで随分とコードが見やすく、かつ楽に書けると思いませんか?
三項演算子は嫌いな方でもこの記法は受け入れられるのではないでしょうか(ちなみに、私は三項演算子はとても嫌いです。)

最後に

紹介した以外の演算子や、LINQの記法、言語仕様など、C#には魅力がとても紹介しきれないくらいにたくさんあります。Visual Studioという最高のIDEも学生ならEnterprise版すら無料で使えますし、Unityも今やJavaScriptやBooは廃止されC#一択ですよね。まだC#を触ったことがない方も、ぜひ触れてみてはどうでしょうか。C#を始めて深みにハマる方が一人でもいれば嬉しいです。

一応、ゲームを作ったという話をしたので、少しだけゲーム画面(Debugビルド時)を載せてみます。
※この画面内で使用している画像はPublic Domainのもの, 著作権表記が不要のフリー素材, 私または他の開発メンバーが作成したもののいずれかです。

---3

拙い感想ですが、リサージュ曲線とか描いてみたり、安置をなくすために乱数で微妙に振ったりとかはじめ色々調整して作るのはとてもおもしろかったです。
とはいえ、一ヶ月でゲームを書くような無茶は二度としたくないなと思いました。できればプログラム要員がもう一人いればよかったなとか色々思うことはありますが、他人と共有できるほど丁寧にコード書いてもいないですし、そもそも3ヶ月くらい猶予があったはずだったのでまあ自己責任ですね。

お読みいただきありがとうございました。

=======================
明日の記事担当はtemmaさんです。
お楽しみに!

この記事を書いた人
b1smuth

19生命の一人です。

この記事をシェア

このエントリーをはてなブックマークに追加

関連する記事

2019年12月11日
円周率が無理数であることの証明【AdC2019 42日目】
Tarara
2019年12月11日
ぷよぷよってナンですか?【AdC2019 42日目】
arahi10
2019年12月10日
ブログ投稿ツイートを無理やり自動化した話【AdC2019 41日目】
xecua
2019年12月10日
理工系科目英語クラスについて【AdC2019 41日目】
kano
2019年12月9日
大学生活を豊かにする自己管理ツール集【AdC2019 40日目】
Deka
2019年12月8日
ハッカソンに参加して素材を作った話
NABE

活動の紹介

カテゴリ

タグ