プチメタ3.0

刺激を受けた物事に対する感想や考察、資産運用や英語学習、自己成長に関することなど。


目標までの距離をもとに放物線の最適な発射角度を求める計算方法


ゲームにはさまざまな武器が登場するが、
銃弾は直線的な軌道として処理することが多い。


現実には弾丸も重力の影響を受けて放物線を描くのだが、
一般的なイメージとして銃の弾が徐々に落下するという印象が薄いし、
そこまで計算して敵に狙いをつけるのは
ゲームとしての面白みに欠けるからだ。


しかし、戦車砲やグレネードランチャー、弓矢などは
山なりに飛んでいく印象が強いため、
重力を無視して飛ばしてしまうと不自然に見えてしまう。




そこで発射された弾は、重力によって
徐々に下に落ちていくように処理する。

移動量は2つに分解して処理する


プログラム的には垂直方向の移動処理と
水平方向の移動処理を別々に管理した方が楽なので、
発射時の角度をもとに三角関数を使って
移動量を水平方向と垂直方向に分解する。


このうち水平方向の移動量は減衰せず、
毎回同じ分だけ弾の座標に足し込んでいく。
空気抵抗を考えても誰も得しないので普通は無視する。


垂直方向の移動量は徐々に減少していって
いずれマイナス値になるのだ。
つまり、弾は下降していくことになる。


さて、主人公が発射する武器はプレイヤーが操作をするので
発射時の向きに合わせて弾を飛ばすだけでいい。
弾の着弾位置から発射方向を修正する作業がゲーム性につながる。


問題は敵が弾を発射する場合だ。




敵はプレイヤーが操作するわけではないので
主人公にうまく当たるように弾を撃たせる必要があるが、
発射する角度によって弾の飛距離が変わるので、
目標までの距離に応じて角度を求める必要がある。


しかし重力によって垂直方向の移動量が変化するため
どんな角度で撃ち出せば
ちょうど目標地点に落ちるかという計算はなかなか難しい。
今回はこの計算方法を紹介してみる。

着弾するまでの滞空時間を求める

まず手順としては、弾が地面に着くまでの時間を求める。
そのために垂直方向の移動量を抜き出す必要があるが、
これには三角関数を使う。




直角三角形の角度と斜辺の長さがわかれば
コサインを使って底辺の長さとを求めることができる




つまり、角度θの方向にvの速度で発射された弾は
水平方向にv×コサインθ 、
垂直方向にv×サインθ の力で撃ち出された考えられる。


次に高校物理で出てくる等加速度運動の公式を使う。


上方向をプラスとした場合、
初速度 {v_0} 、重力加速度 {g} に対する時間 {t} のときの高さ {y}


 {\displaystyle\
y=v_0 t-\dfrac{1}{2}\ gt^2
}


となるので、この {v_0}
先ほどの垂直方向の発射速度 {vsin\theta} を当てはめると


 {\displaystyle\
y=vsin\theta\ t-\dfrac{1}{2}\ gt^2
}


になる。


弾が地面に着くということは {y}{0} になるときなので


 {\displaystyle\
0=vsin\theta\ t-\dfrac{1}{2}\ gt^2
}


になるときの {t} の値を求めればいいわけだ。


この式を並び替えると


 {\displaystyle\dfrac{1}{2}\ gt^2\ -vsin\theta\ t+0=0}


となり、2次方程式の


 {\displaystyle\ ax^2\ +bx+c=0}


と同じ形になる。
{x}{t}{a}{\dfrac{1}{2}\ g}{b}{-vsin\theta}{c}{0} ということだ。


2次方程式なら中学数学で習う


 {\displaystyle\ x=\dfrac{-b\pm\sqrt{b^2-4ac}}{2a}}


解の公式が使える。


この式にそれぞれを当てはめると


 {\displaystyle\ t=\dfrac{-({-vsin\theta})\pm\sqrt{({-vsin\theta})^2-4\times{\dfrac{1}{2}\ g}\times\ 0}}{2\times{\dfrac{1}{2}\ g}}}


となるが、これを整理していくと


 {\displaystyle\ t=\dfrac{{vsin\theta}\pm\ {vsin\theta} }{g}}


と、かなりシンプルにすることができて


 {\displaystyle\ t=0 ,  \biggl( \dfrac{2\times{vsin\theta}}{g}\biggr)}


±の関係で2つの解が出る。
つまり弾の高さが0になる時間は {0}{\biggl(\dfrac{2\times{vsin\theta}}{g}\biggr)} ということだ。


このうち時間 {0} というのは発射の瞬間を表しているので、
発射角度 {\theta} における着弾までの時間


 {t=\dfrac{2\times{vsin\theta}}{g}}


となる。


サイン値が最大となるのは90度なので
{\theta} が90に近づくほど {t} の値も大きくなる。
要するに、上向きに撃つほど
滞空時間が長くなるという納得の結果になっている。

滞空時間をもとに飛距離を求める


角度θの方向にvの速度で発射された弾は
水平方向にはv×コサインθ だけ進む。
こちらは重力には左右されないので速度は変化しない。


等速運動の移動量は等加速度運動の公式である


 {\displaystyle\
x=v_0 t+\dfrac{1}{2}\ at^2
}


の加速度 {a}{0} になっている状態なので、


 {\displaystyle\ x=v_0 t}


となるが、この {v_0} の部分に {vcos\theta} を当てはめれば


 {\displaystyle\ x=vcos\theta\ t}


となり、弾が水平方向に対して
どのぐらい移動するかを求めることができる。


この式の {t} に先ほど求めた滞空時間 {\biggl( \dfrac{2\times{vsin\theta}}{g}\biggr)} を当てはめれば


 {\displaystyle\ x=vcos\theta\times\dfrac{2\times{vsin\theta}}{g}}


となり、少し式を整理すると


 {\displaystyle\ x=\dfrac{cos\theta\sin\theta\times\ 2\times\ v^2}{g}}


という形になる。


三角関数の加法定理に


 {\displaystyle\sin( \alpha\ + \beta\ )=sin \alpha\cos \beta\ +cos \alpha\sin \beta}


があるが、 {\alpha\ = \beta} の場合に置き換えてみると


 {\displaystyle\sin( \alpha\ + \alpha\ )=sin \alpha\cos \alpha\ +cos \alpha\sin \alpha}


となり、


 {\displaystyle\sin(\alpha\times\ 2 )=cos \alpha\sin \alpha \times\ 2}


とすることができる。


ということは先ほど作った式、


 {\displaystyle\ x=\dfrac{cos\theta\sin\theta\times\ 2\times\ v^2}{g}}


の「 {\cos\theta\sin\theta\times\ 2} 」の部分を
そっくり「 {\sin(\theta\times\ 2)} 」に置き換えることができるので、


 {\displaystyle\ x=\dfrac{\sin(\theta\times\ 2)\times\ v^2}{g}}


という式になる。
これが角度 {\theta} で弾を発射したときの飛距離だ。


放物線で移動する物体は高さと推進力のバランスから
45度で発射されたときが一番よく飛ぶわけだが、
上記の {\theta} に45が入ると、サイン値の最大である
90度が使われるようになっているのがわかる。


また、発射速度 {v} が大きくなれば飛距離 {x} が増え、
重力加速度 {g} が大きくなれば飛距離 {x} が小さくなることからも
計算として正しいことが感覚的にわかるだろう。


45度で発射したときの飛距離より遠くには
どう撃っても絶対に弾は届かないので、
敵のAI処理ではまず上記の式の {\theta} に45を代入して射程距離 {x} を求め、
目標がそれより近いときだけ攻撃するようにすべき
だ。

飛距離から逆算して発射角度を割り出す

さて、目標が射程範囲内にいる場合は
適切な発射角度を求めて攻撃する必要があるため、


 {\displaystyle\ x=\dfrac{\sin(\theta\times\ 2)\times\ v^2}{g}}


先ほどたどり着いたこの式を少し変形して


 {\displaystyle\ sin(\theta\times\ 2)=\dfrac{xg}{v^2}}


左辺にサイン値がくるようにする。
これを見ると {\dfrac{xg}{v^2}} がサイン値と等しいことがわかる。


角度からサイン値を求める場合はサイン関数を使うが、
サイン値から角度を求める場合は
サインの逆関数であるアークサインを使う。


 {\displaystyle\theta\ = arcsin(\dfrac{xg}{v^2})\div\ 2}


ようやくたどり着いた。
これが目標との距離 {x} 、重力加速度 {g} 、発射速度 {v} をもとに
適切な発射角度を割り出す計算式
となる。


ここまでたどり着くのに

  • 三角関数
  • 等加速度運動
  • 加法定理
  • 解の公式
  • 逆三角関数

といった数学知識を活用する必要があり、
中学高校での勉強が大切なことを実感する。

実践での工夫


もともとこの処理は18年ほど前に作った
自作ゲーム「ネイビーミッション」で
艦砲射撃をしてくる敵の攻撃AIとして必要だったのだ。
当時はかなり四苦八苦して上記の結論に達したのを覚えている。


さらに後半のステージで登場する強めの敵では
途中に出てきた発射角度から滞空時間を求める式


 {t=\biggl( \dfrac{2\times{vsin\theta}}{g}\biggr)}


を使って、敵弾が飛んでいる間に
自機が進むであろう位置を先読みして
未来の座標に向けて発射するようにもした。




いわゆる偏差(へんさ)射撃というやつだが、これによって
「とりあえず移動していれば当たらない」という序盤の敵に対して
「攻撃してきたら移動方向を変えないと喰らう」という強敵が作れた。


開発中にややこしい計算をした感触だけは覚えていたので
当時の資料を掘り起こしてきちんと記録にまとめてみた。
(数式をブログ上で表現するTeX記法の練習にもなった)


同じような計算が必要になるプログラマーに
少しでも役立てばと思う。



mclover.hateblo.jp

mclover.hateblo.jp

見出しや説明文を配置するときはグルーピングを意識すべき


Facebookで流れてきたとある動画だが、
これを見てすぐに問題がわかるだろうか。




中央段にある見出しが
上下どちらのイラストのものなのかが
パッと判断できないのだ。


特に「Better」は上側のイラストとはピッタリくっついているのに
下側のイラストとはスキ間が空いているため、
上の方が結合具合が高く、見出しの対象を誤解してしまう


最上段に「Basic」「Simple」という見出しがあるため、
それを順繰りに割り当てていってはじめて
「Better」と「Perfect」が下段の見出しだとわかるのだが、
そういう消去法でやっと判断できるレイアウトはよくない。



liginc.co.jp


項目同士を適切に区分けする「グルーピング」は
デザインや資料作りの基本的な考え方なのだが、
学生たちが作るゲームのUIや説明書では
そのあたりが意識できていない例をよく見る。


余白や枠線、位置関係によって
いちいち頭を働かせなくても
誤解しないレイアウトを心がけて欲しい。



mclover.hateblo.jp

mclover.hateblo.jp

【ゲーム業界志望者向け】プログラミングWeb試験に必要な知識まとめ


ゲームプログラマーを目指す学生が企業に応募するときは
自作のゲームプログラムを求められることがほとんどだが、
これに加えて筆記試験を受けさせられることがよくある。


最近はWeb試験の一種であるコーディングテストがよく使われていて、
ブラウザ上で直接プログラミングし、
指示された動きをするかをテスト実行した上で提出する。


ただ、このシステム自体に割とクセがあり、
3Dゲームをバリバリ作れる技術があったとしても
コーディングテストで脱落することが多い。


これは普段触っているゲームプログラミングと比べて
あまりに触り心地が違うのが原因と思われ、
その試験で本来問われている内容ではなく
もっと根本的な部分で失敗していたりする。


そこでそういったコーディングテストに挑む前に
準備しておく方がいい基礎知識についてまとめておく。

原始的な入出力処理に慣れておく

ゲームプログラムの出力は画像が原則であり、
タイトルロゴやスコアのような文字も
そういう画像を用意して表示しているだけだ。


また、プレイヤーからの入力はキーボードやマウス、
コントローラーなどを経由して行われるため、
特定のキーが押されているかどうかの判定しかしない。


しかしコーディングテストでは
std::coutとstd::cinあたりの原始的な入出力処理が使われており、
入学当初の授業で扱ったような手法を求められると
使い慣れていないだけにかなり戸惑う。


そこでcinとcoutの基本的な使い方を
しっかり復習しておくことが重要になる。


#include <iostream>
void main()
{
	int age;
	std::cin >> age;
	if(age >= 18)
	{
		std::cout << "大人です" << std::endl;
	}else{
		std::cout << "未成年です" << std::endl;
	}
}
  • 「iostream」のインクルードが必要
  • 「std::cin >> 変数」でキー入力された情報を変数に代入
  • 「std::out << 文字列」「std::out << 変数」で指定した内容を文字表示
  • 「std::endl」を出力すると改行

 

#include <iostream>
using namespace std;
void main()
{
	int age;
	cin >> age;
	if(age >= 18)
	{
		cout << "大人です" << endl;
	}else{
		cout << "未成年です" << endl;
	}
}

いちいち「std::」と書かなくていいように
「using namespace std;」が宣言されていることも多い。


データの入出力がうまくできないと
問題の本質部分にまったく挑戦できないので
cinとcoutの使い方をおさらいしておく方がよい。


paiza.jp


模擬的なデータが自動でプログラムに渡される

実際にプログラムを実行したときは
数値などのデータを人間が入力する必要があるが、
コーディングテストではそこが自動的に行われるようになっている。


たとえば前述のプログラムは
入力された年齢が成人かどうかを判別するものだが、
この年齢入力の部分が自動で行われるのだ。




また、いろいろな値を入力したパターンが試されるため、
特定の数値のときだけ期待通りの結果になってもダメで、
考えられるいろいろな入力データに対応できている必要がある。

最初に書かれているプログラムを使う必要はない

問題に取り掛かった段階で、ある程度のプログラムが
最初から書かれていることがよくある。


#include <iostream>
#include<string>
using namespace std;
void main()
{
	//ここにプログラムを書きましょう!
	string str;
	getline(cin, str);
	cout << "文字が表示されます" << endl;
}

なんとなく、ここに追記するばかりで
もともとのプログラムを書き替えてはいけないような気になるが、
それだと記述できる内容が限定されて逆に苦労する。


#include <iostream>
#include<string>
using namespace std;
void main()
{
	string str;
	getline(cin, str);
	cout << atoi(&str[0]) * 10 << endl;
}

たとえば数値が入力されるのにstring型で受け取ると
そのままでは数値として利用できなくなる。
そうなると文字列を数値に変換するatoi系の関数や
string型をchar型配列として扱うための
ポインタ表現(またはc_str関数)などを思い出す必要があり、負担が増える。


もともと書かれているのはただのサンプル表記であって
特に気にせず削除してしまって構わないし、
その方が圧倒的に楽に解決することがある。


#include <iostream>
using namespace std;
void main()
{
	int no;
	cin >> no;
	cout << no * 10 << endl;
}

入力されるものが数値に限られるなら
そのままint型の変数で受け取った方が楽だ。
余計なことで悩まなくて済むよう、
最初に書いてある内容は自由に削除していいと認識すべきだ。

複数の入力データの扱いに慣れておく

ひとつの数値だけが入力されるのではなく、
複数のデータがスペース区切りで入力されることがある。
その際はcin関数に複数の変数を指定することができる。


#include <iostream>
using namespace std;
void main()
{
	int math, sci, eng;
	cin >> math >> sci >> eng;
	
	cout << "合計得点は" << ( math + sci + eng ) << endl;
}

これだけで3つの値が3つの変数に入ってくれる。


下手にgetline関数などで受け取ってしまうと、
3つの数値を含む連結された文字列を
スペースごとに区切って取り出す必要が出てくるので
それだけでかなり複雑な処理になってしまう。
単にcinを連続して使うだけでいいことは覚えておいた方がよい。

入力データの個数が変化する場合への対応

よくあるのが入力データの個数が変わる場合だ。
たとえば最初に人数を入力させ、
そのあと各自のデータを入力させる場合などは
可変長配列クラスであるstd::vectorなどを利用するとよい。


#include <iostream>
#include <vector>
using namespace std;
void main()
{
	//人数を取得
	int num;
	cin >> num;
	
	//人数分のデータを取得
	vector<int> income(num);
	for(int i = 0; i < num; i++)
	{
		cin >> income[i];
	}
	
	//ソート
	for(int i = 0; i < num - 1; i++)
	{
		for(int k = i + 1; k < num; k++)
		{
			if(income[i] < income[k])
			{
				int tmp = income[i];
				income[i] = income[k];
				income[k] = tmp;
			}
		}
	}

	cout << "高い順に表示" << endl;
	for(int i = 0; i < num; i++)
	{
		cout << income[i] << endl;
	}
}
  • 「vector」のインクルードが必要
  • 「vector<型> 変数名(個数)」で指定された型の配列を指定された個数分宣言できる

ソート処理が必要になる問題も多いので、
一番簡単なバブルソートの処理を覚えておくと役立つ。

文字列の扱いにも慣れておく

入力データが文字列であることも多いので
std::stringの扱いにも慣れておく方がよい。


#include <iostream>
#include <string>
using namespace std;
void main()
{
	string name;
	cin >> name;
	
	//逆から表示
	for(int i = name.size() - 1; i >= 0; i--)
	{
		cout << name[i];
	}
}
  • 「string」のインクルードが必要
  • 文字数はsize関数で取得
  • 末端を示す「¥0」のために1文字使われる
  • 入力データは半角文字限定の場合が多い

 

#include <iostream>
#include <string>
using namespace std;
void main()
{
	int num;
	cin >> num;

	string str;
	str = "入力値は" + to_string(num) + "です";

	cout << str;
}
  • 数値をstring型として扱う場合はto_string関数を使う

 

#include <iostream>
#include<string>
using namespace std;
void main()
{
	string str;
	cin >> str;

	int num;
	num = atoi(&str[0]);
	num *= 10;

	cout << num << endl;
}
  • string型を数値扱いしたい場合は「&変数名[0]」でchar型ポインタを取り出してatoi関数に渡す

 

項目別の集計が必要な場合への対応

入力された項目の出現数を数えるなど、
項目別の管理が必要な場合は
連想配列クラスであるstd::mapを利用するとよい。


#include <iostream>
#include <map>
using namespace std;
int main(void){
    int num;
    cin >> num;
    
    map<int, int> list; //キーと値の型は自由に設定可能
    for(int i = 0; i < num; i++)
    {
        int tmpNo;
        cin >> tmpNo;

        if(list.find(tmpNo) == list.end()) //キーが存在しない
        {
            list[tmpNo] = 1; //新たなキーを追加
        }else{
            list[tmpNo]++;
        }
    }
    
    for(auto itr = list.begin(); itr != list.end(); ++itr)
    {
        cout << itr->first << "の出現数は" << itr->second << endl;
    }
}
  • 「map」のインクルードが必要
  • find関数でend()が返されたら該当項目は記録されていない
  • 各項目を順に処理していくときはbegin関数でイテレータを取得する
  • イテレータのfirstがキー(項目名)、secondが値

 

Visual Studioにコマンドプロンプトが付属している

実際にこういった原始的なプログラムを試したい場合、
Microsoft Visual Studioをインストールすれば
セットでコマンドプロンプトも付いてくる。




Windowsのスタートメニューの中のVisual Studioフォルダにある
「Developer Command Prompt for VS」を起動。




真っ黒なウィンドウが開くので
cdコマンドで適当なフォルダに移動する。


C:\cpp>cl/EHsc test.cpp

Microsoft(R) C/C++ Optimizing Compiler Version 19.34.31933 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

test.cpp
Microsoft (R) Incremental Linker Version 14.34.31933.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:test.exe
test.obj

C:\cpp>test
40
大人です

C:\cpp>

あとは「cl/EHsc ファイル名」で対象ファイルをコンパイル。
エラーが出なければ実行ファイルが作られるので、
実行した上で適当な値を入力して結果を確認できる。

paizaなら無料で実践トレーニングができる


paiza.jp


コーディングテストのシステムに慣れるためにも
実践環境で訓練するのが一番だが、
無料で試せる中ではpaizaが最適だろう。


会員登録は必要だが、大量の問題が用意されており、
さまざまなプログラミング言語に対応している。


Dランク問題は非常に単純なプログラムで済むので
まずはこれでシステムに慣れたあと、
就活生ならとりあえずCランク問題まで解けるようになっておきたい。

まとめ

私自身がpaizaの問題を数十問ほど解いてみて
その中で必要になった知識やポイントをまとめてみた。


問題が問おうとしている本質的な部分がわからないならともかく、
単にコーディングテストに不慣れなせいで
本来の能力が発揮できずに不合格になるのはもったいないので、
ゲームプログラマー志望であっても
上記のような知識を身につけておく方が安全だろう。

char型の文字列をワイド文字(Unicode)に対応させる方法


C言語やC++系のプログラミングを学び始めると
文字データを入れる変数はchar型だと習うが、
半角文字を1バイト、全角文字を2バイトという
特殊な考え方は「マルチバイト文字」と呼ばれ、
日本語用のShift_JISなど特定の言語だけを想定した設計になっている。


そうなると外国語環境では文字化けしてしまうため、
世界標準の統一規格であるUnicodeで管理する方が望ましい。
Unicodeならどの環境であっても日本語は日本語として表示される。


Unicodeは1文字を表す情報量を増やしたワイド文字という仕様の一種で、
プログラム上ではchar型からwchar_t型に変わる。


今回、ウゴツールをUnicodeに対応するにあたって
必要になった知識を備忘録としてまとめておく。
(この情報が最初からあれば作業時間は10分の1で済んだ)

プロジェクトの文字セットを変更


プロジェクトのプロパティ内「詳細」にある
「文字セット」を「Unicode」に切り替える。


これによって文字列を扱うWindowsAPIは
すべて自動的にUnicodeを使うものに切り替わる。


これはUNICODEマクロが定義されているかどうかで
同じSendMessage関数でも
SendMessageWとSendMessageAのどちらを呼び出すかが変わるよう
条件コンパイルが指定されているためだ。


切り替えた直後はあちこちでビルドエラーが起きるため、
それぞれをワイド文字(Unicode)版に置き換えていく。

文字列の変換

■メッセージボックス■

MessageBox ( hwnd, "おはよう", "挨拶", MB_OK ) ;

    ↓    ↓    ↓

MessageBox ( hwnd, L"おはよう", L"挨拶", MB_OK ) ;

ダブルクォーテーションで囲んで直接文字列を記述している箇所は
「L」を頭に付けることでワイド文字として扱われるようになる。


■char型 ⇒ ワイド文字の変換■

char mbText [ ] = "おはよう" ;
wchar_t uText [ sizeof ( mbText ) ] ;
MultiByteToWideChar ( CP_ACP, 0, mbText, -1, uText, sizeof ( uText ) / sizeof ( wchar_t ) ) ;

char型として作られた文字列は
MultiByteToWideChar関数を使うことで
ワイド文字に変換することができる。


■ワイド文字 ⇒ char型の変換■

wchar_t uText [ ] = L"ありがとう" ;
char mbText [ sizeof ( uText ) ] ; //半角文字があると余りが発生する
WideCharToMultiByte ( CP_ACP, 0, uText, -1, mbText, sizeof ( mbText ) , NULL, NULL ) ;

逆にWindowsAPIなどから受け取ったワイド文字を
通常のchar型として扱いたい場合は
WideCharToMultiByte関数で変換できる。


これで互いの文字セットが行き来できるため
理屈としては上記の方法だけでもすべて対応できるのだが、
うまく関数を置き換えれば
ワイド文字のまま操作できるので変換の手間が省ける。

標準ライブラリ関数の置き換え

■文字列のコピー■

char src [ ] = "おはよう" ;
char dest [ 100 ] ;
strcpy_s ( dest, sizeof ( dest ) , src ) ;

    ↓    ↓    ↓

wchar_t src [ ] = L"おはよう" ;
wchar_t dest [ 100 ] ;
wcscpy_s ( dest, sizeof ( dest ) / sizeof ( wchar_t ), src ) ;
第2引数はバイト数ではなく文字数であることに注意

文字数指定のstrncpy_sとwcsncpy_sも同様。


■文字数を測る■

char mbText [ ] = "おはよう" ;
int len;
len = strlen ( mbText ) ;

    ↓    ↓    ↓

wchar_t uText [ ] = L"おはよう" ;
int len;
len = wcslen ( uText ) ;


■文字列を連結する■

char mbText [ 100 ] = "おはよう" ;
strcat_s ( mbText, sizeof ( mbText ), "ございます" ) ;

    ↓    ↓    ↓

wchar_t uText [ 100 ] = L"おはよう" ;
wcscat_s ( uText, sizeof ( uText ) / sizeof ( wchar_t ), L"ございます" ) ;
第2引数はバイト数ではなく文字数


■文字列を比較する■

char mbText1 [ 100 ] = "おはよう" ;
char mbText2 [ 100 ] = "こんにちは" ;
if ( strcmp ( mbText1, mbText2 ) == 0 )
{

}

    ↓    ↓    ↓

wchar_t uText1 [ 100 ] = L"おはよう" ;
wchar_t uText2 [ 100 ] = L"こんにちは" ;
if ( wcscmp ( uText1, uText2 ) == 0 )
{

}


■文字列を成形する■

char mbText [ 100 ] ;
int no = 13;
sprintf_s ( mbText, sizeof ( mbText ) , "テスト%d回目", no ) ;

    ↓    ↓    ↓

wchar_t uText [ 100 ] ;
int no = 13;
swprintf_s ( uText, sizeof ( uText ) / sizeof ( wchar_t ) , L"テスト%d回目", no);
第2引数はバイト数ではなく文字数


■テキストファイルに書き込む■

FILE* fp ;
fopen_s ( &fp, "mbFile.txt", "w" ) ;
int no = 13 ;
char mbText [ ] = "おはよう" ;
fprintf_s ( fp, "データ %d , %s", no, mbText ) ;
fclose ( fp ) ;

    ↓    ↓    ↓

FILE* fp ;
_wfopen_s ( &fp, L"uFile.txt", L"w, ccs = UNICODE" ) ;
int no = 13 ;
wchar_t uText [ ] = L"おはよう" ;
fwprintf_s ( fp, L"データ %d , %s", no, uText) ;
fclose ( fp ) ;


■テキストファイルから読み込む■

FILE* fp ;
int no ;
char mbText [ 100 ] ;
fopen_s ( &fp, "mbFile.txt", "r" ) ;
fscanf_s ( fp, "%d,%s", &no, mbText, sizeof ( mbText ) ) ;
fclose ( fp ) ;

    ↓    ↓    ↓

FILE* fp ;
int no ;
wchar_t uText [ 100 ] ;
_wfopen_s ( &fp, L"uFile.txt", L"r, ccs = UNICODE" ) ;
fwscanf_s ( fp, L"%d,%s", &no, uText, sizeof ( uText ) / sizeof ( wchar_t ) ) ;
fclose ( fp ) ;
文字列変数のサイズはバイト数ではなく文字数

 

まとめ

TCHAR型やTEXT()マクロなどを使えば
マルチバイト文字にもワイド文字にも対応した記述もできるが、
そもそもUnicode対応したプログラムを
元に戻す必要性がほとんどないので、
可能なら最初からUnicode前提ですべて記述した方がいいだろう。


文字セットの違いに悩むプログラマーにとって
この記事が少しでも役立つことを願う。



mclover.hateblo.jp

総アクセス数