前回でメインループの基本形が出来たので、このまま次はプレイヤーを動かしてみたいと思います。プレイヤーを動かすのに、上下左右では味気がないので、ここはベクトルを使ってアナログ的に移動させてみます。内容に関しては今回も、前回の続きとなりますので、直接こちらに来てしまった場合は、前回の記事を参照してからお読みください。

本記事では説明のために struct 定義を書いてますが、実際には既に C# 側で用意された Vector2 があります。そのため、struct 定義は不要となります。※ デザイン振り子時計です。これも電波時計で手間いらず。時計はカシオやセイコーといった日本製を選んでしまいます…
※数あるからくり系の壁掛け時計の中でも最も飽きが小なさそうなのをチョイス。通常時は普通の時計に見えるのがポイント。暗いと鳴らないのも配慮が効いてます。
--
- キーボード入力
C# でのキーボード入力ですが、実はリアルタイムキー入力はあまり充実していないというか不親切です。以前は GetKeyState 関数が用意されていて、これを使うと簡単にキーボード入力が取れたのですが、今は標準ではサポートされていません。そこで、Windows dll から、user32.dll をインポートして、GetKeyState を無理やり使える状態にしてしまいます。
まずは使用する関数の直上か、プロジェクトの最初らヘンに以下の定義を記述します。
[System.Runtime.InteropServices.LibraryImport("user32.dll")] private static partial short GetKeyState(int vKey);
この2行を記述することで、user32.dll の中から GetKeyState を使用する宣言となります。引数に与える vKey はバーチャルキー定数です。これは残念ながら自分で定義する必要があります。キーボード入力とキーコードの関係はこちらを見てください。
この中から使用するバーチャルキーを定義します。#define を使って一つずつ定義しても良かったのですが、めんどくさかったので私は以下のように定義しました。
これで、(int)VK.LEFT とすれば、0x25 が拾えるようになります。#define より簡単だと思います。さて、引数はこれで良いとして、戻り値です。short の最上位ビットが 1 なら押されていることになります。なので、ビット判定せず < 0 とマイナス判定すれば押されているかどうかが分かります。例えば…
public enum VK { SPACE = 0x20, PRIOR, NEXT, END, HOME, LEFT, UP, RIGHT, DOWN, }
if (GetKeyState((int)VK.SPACE) < 0) { Console.WriteLine("SPACEが押されている。"); }
こんな使い方で OK です。簡単ですよね?
※ ちょっとかっこいい壁掛け時計。これなら視認性も良さそうです。当然電波時計なので、自動時刻修正。- ベクトルで定義する
まず、自分の座標を示す型を Point ではなく Vector2 に変更します。Vector2 は他にも Vector3 等もあります。簡単に言ってしまえば、Vector2 が X,Y の2次元、Vector3 が X,Y,Z の3次元を表します。
private static Vector2 vecPlayer; // プレイヤーの位置
この Vector2 は System.Numerics 空間にありますので、Vector2 と VS2022 に入力した時点で using System.Numerics; を記述せよとメッセージが表示されます。そのまま、using System.Numerics; の部分を左クリックするだけで、自動的にコードの最初に using の記述が追加されます。便利ですよねw

さて、これで既にベクトルを使う準備は出来ています。ところでベクトルってなんでしょうか。私の時代ですと高校ぐらいの数学で学んでますが、要は大きさと方向を持つ数値の塊です。例えば 5 という数値は大きさは 5 だと分かりますが、方向が分かりません。ベクトルは各軸方向の成分毎の大きさを個別で持つことにより、大きさに加えて方向を表すことが出来る単位なのです。
大きさと方向は、普通に考えたら
こんな構造にしてしまいがちですが、これだと実はとても使いづらいです、理由は先にも述べたとおり、各軸成分の大きさが分からないので、加減算がやりにくいためです。ベクトルは正しくは
struct Vector2 { public int Length; // 大きさ public float Angle; // 角度 }
struct Vector2 { public float X; // X軸方向の大きさ public float Y; // Y軸方向の大きさ }
このような構造を持ちます。そして、各軸成分を同時に加減算するとベクトルの合成が出来ます。この各成分の合成をまとめて面倒見てくれるのが Vector2 クラスとなります。なお、ベクトルのお勉強についても書こうかと悩んだのですが、それだけで単独の記事になりそうなボリュームになるので、本記事では端折ります。気になる人はこちらをご参照ください。
本記事では説明のために struct 定義を書いてますが、実際には既に C# 側で用意された Vector2 があります。そのため、struct 定義は不要となります。
- ベクトルで動かす
さて、vecPlayer が現在の位置を示していますが、折角キャラを動かすので、速度と方向もプロパティに作り、その値を元に動かすようにしましょうか。
private static float speed; // プレイヤーの移動速度 private static float direction; // プレイヤーの移動方向
仕様としては、以下のような内容としてみます。
[←][→] 移動方向の左右変更 [↑] 加速 [↓] 減速/ブレーキ [Home] 元の状態に戻す
ハンドルの切れ角、加速性能、減速性能は、定数定義しておきます。
const float turnAngle = 5f; const float accelerator = 0.5f; const float brake = 1f;
この定数を元にプレイヤーの移動速度や方向を変更していきます。
ハンドルの左右の向き
加速と減速
if (GetKeyState((int)VK.LEFT) < 0) { // 左方向 direction -= turnAngle; } if (GetKeyState((int)VK.RIGHT) < 0) { // 右方向 direction += turnAngle; }
リセット
if (GetKeyState((int)VK.UP) < 0) { // アクセル speed += accelerator; } if (GetKeyState((int)VK.DOWN) < 0) { // ブレーキ speed -= brake; if (speed < 0) { speed = 0; } }
これで角度と速度の変更がキー入力で出来ましたので、あとは、その角度と速度の情報に従って、自分の座標(ベクトル)を更新します。ベクトルの合成です。速度はベクトルの大きさ、角度はベクトルの方向になります。三角関数の SIN で Y軸方向の成分分解、COS で X時期成分の分解ができます。精度が問題にならないのであれば、Math ではなくて、MathF を使います。理由はそのほうが全然処理が速いためです。
if (GetKeyState((int)VK.HOME) < 0) { // 元の位置に戻す vecPlayer = new(SCR_WIDTH / 2f, SCR_HEIGHT / 2f); speed = cntPlayer = 0; }
さて、ここでちょっとだけ変わったことをしています。それは、最初の行。加速値の5倍より、今の移動速度が大きくなったら、cntPlayer を +1 しています。つまりは移動速度が高くなったら始めてキャラのアニメをするということです。あと、三角関数の角度はラジアンで指定するのですが、我々が通常扱うのは一周を360度とするデグリーです。そのため、デグリーからラジアンに変換します。一周 = 2πr という公式を覚えている人もいるかと思いますが、デグリー360度 = ラジアン 2πr なので、ラジアンに直すには 2πr ÷ 360 を掛けることになります。2で約分できますので πr ÷ 180 ですね。毎回この計算を書くのも面倒なので、コードの冒頭に
if (speed > accelerator * 5f) { ++cntPlayer; } Vector2 vecMove = new( MathF.Cos(direction * Deg2Rad) * speed,
MathF.Sin(direction * Deg2Rad) * speed); vecPlayer += vecMove;
このように定数定義しておけば、あとは角度の値に Deg2Rad を乗算するだけでラジアン変換が完了するというワケです。最後に vecPlayer の位置にプレイヤーを表示して終了となります。
public const float Deg2Rad = MathF.PI / 180f;
※数あるからくり系の壁掛け時計の中でも最も飽きが小なさそうなのをチョイス。通常時は普通の時計に見えるのがポイント。暗いと鳴らないのも配慮が効いてます。
- クリッピング(範囲外判定)
さて、これで動かすことは出来るのですが、このままだとたぶんあっという間に画面外に飛び出して、行方不明になってしまうと思います。だからリセットなんて機能を付けてはいるのですが…。
移動した結果、その座標が画面範囲外の場合は、それ以上、数値が画面から離れていかないように制限します。これがクリッピングです(厳密には用語の使い方が違いますが今はこれで許容願います)。今回は単純に判定することにします。
表示は足元左右中央を起点としています。そのため、一番上だと、単純に値が 0 未満なら 0 とすると結局見えなくなってしまいます。そこで、表示するキャラの高さを plHeight という変数に入れておいて、その値より小さくなったら移動を止めることにしました。plHeight はコンストラクタで定義する readonly メンバとしています。
if (vecPlayer.X < 0) { vecPlayer.X = 0; } if (vecPlayer.Y < plHeight) { vecPlayer.Y = plHeight; } if (vecPlayer.X >= SCR_WIDTH) { vecPlayer.X = SCR_WIDTH - 1; } if (vecPlayer.Y >= SCR_HEIGHT) { vecPlayer.Y = SCR_HEIGHT - 1; }
Vector2 クラスを使うと割とあっさりとコーディングが完了してしまいますよね。今回もサンプルを下記からダウンロードできるようにしておきます。
/// <summary> /// コンストラクタ /// </summary> private static readonly float plHeight; static Program() { plHeight = bmPlayer[0].Height; // キャラの高さ clGameStage = Color.LimeGreen; // 背景の色 vecPlayer = new(SCR_WIDTH / 2f, SCR_HEIGHT / 2f); // 初期座標 cntPlayer = 0; // アニメカウンタ speed = 0f; // 移動速度 direction = 90f; // 向き }
2023.10.24.1530



コメント