最新情報(固定記事)

※※※※※※※※※※※※※※※※※※※※※※
計画中!
★ルビーのスパイ作戦
カードバトルxアドベンチャー
製作の最新情報はCi-enにて!

発売中!
えびげん、ターニャのラインディフェンス(NEW!)
エヴァいじり
弄ってイかせよう。シミュレーションゲーム!
・キャッスルえびる登録情報ページ
・スーサイダー登録情報ページ
・スペルマスター登録情報
・パツィーノの幸せの鳥登録情報
・ミナーヴァ登録情報

ダンジョンオブエロチックマスター発売中!(プログラム担当)
------------------------------------------------------------
★更新情報(2018/11/01)

※※※※※※※※※※※※※※※※※※※※※※

プログラミングメモ

ブログのページに書くと埋もれてしまうような、小さい話はここに溜めていきます。

(2013/8/14)上の方から追加していく方式に変えました。

 以前書いた「見下ろし型…」~「時計」までについては、下に新しいものを追加していますので、途中から見た人は順番に気をつけて下さい。

 日付をつけるようにしました。


■TODO/AI管理の薦め
(2013/8/14)
AI(ActionItem)

■ヘッダとリンクエラー

(2013/8/14)
初心者から中級者まで、幅広く悩ませるヘッダとリンクエラー。問題点と解決法をまとめてみる。
リンクエラーはエラー一覧をダブルクリックしても該当箇所がわからないので、初心者に厳しいエラー。

ほかでも書いているが、解説は間違ってる可能性がある。

解決法は実際に解決できているので、とりあえず「こうしたら動いた」というレベルで見てもらえれば良い。

・既に宣言されている

問題

ヘッダーを2個以上のCPPからインクルードしていると発生。

解決法

超基本問題。とりあえず2回以上読まれなければよい。
#pragma once
または
#ifdef _HOGE_HEADER_
#define _HOGE_HEADER_
brah brah
#endif

1個目はVisual C++など、最近のコンパイラであればまず通る。

昔のUNIXなどはこれが無く、2番目のやつにしないといけなかった。
今のg++は使えるかもしれない。

・~はすでに実体を持っている。

問題1

ヘッダのグローバル領域に変数や関数を「修飾語をつけずに実体を持たせる」と発生。
その関数や変数を二つ以上のCPPファイルが使っているとまずい。

なお、後述の修飾語(static/extern)をつけたり、関数なら実体をヘッダではなくCPPに書けば問題ない。

筆者もこれが起きる起きないの境界が良くわからないので、いろいろ試したほうが良い。

なお、下記はライブラリを別に用意してビルドした時に発生。
ライブラリからも、本体プログラムからもヘッダを呼んでいる。


hoge.h
#pragma once

namespace Hoge {
    void hoge() {}
}

main.cpp
#include "hoge.h"
piyo.cpp
#include "hoge.h"

解決法

関数にはstaticをつける。
変数にはexternをつける。

関数は、プロジェクトの規模が小さければstatic無くても動くことが多い。
なお、もともとグローバル関数は扱いが面倒なので、クラスにしてしまったほうが良いと思う。

変数はstaticでも使えるが、ライブラリをまたいだときに内容が保持されないようだ。
実際、ライブラリの関数でstatic変数の値を設定したにもかかわらず、実際使った時には中身は未初期化状態だった。

具体的には
HPLMath.h
static SIN_DEGS[360];
void setupSinDegs();
と宣言しておいて、
(setupSinDegs()でSIN_DEGSにsin(θ)の値を入れる。)

main.cpp
#include "HPLMath.h"
void setup() {
    setupSinDegs();
}


hoge.cpp
#include "HPLMath.h"
void func() {
    float fSin30 = SIN_DEGS[30];
}
としていた。
これをやるとfSin30には未初期化変数が入る。(デバッグモードなら0)

同じプロジェクト内ならstaticで十分だが、ライブラリなど、別プロジェクトにして参照する場合はexternが必須となってくる。

externつけたら、CPPに実体書くのを忘れずに。

問題2

「○○は××ですでに…」の○や×が、まったく見覚えのないものである場合。
(例:__***()はすでにmsvcrt.libで宣言されています)

解決法

これはコード生成方式が関係する。

プロジェクトで設定するコード生成にはDLL/DLLなしがあり、参照しているライブラリが異なると上記のようなエラーが出る。

プロジェクトを初めて作った時などに多い。幸いDXLibは解決法を細かく解説してくれているので、書いてある手順をやれば、必ず解決する。


・~の実体が無い

問題

ヘッダに 関数を書いて、実体を書いていないと発生。
問題はコンパイル時にはエラーとならないこと。
リンクエラーをダブルクリックしても、該当箇所に飛ばないこと。

解決法

CPPにちゃんと該当する関数の実体を書く。
なお、CPPはプロジェクトに入ってないと意味がない。


■見下ろし型/ベルトアクションの基礎設計

(2013/8/14)更新

1.擬似的な3Dに近い→Z軸座標を持つ必要がある
2.当たり判定は、見た目に一致する場合(下図の寝転び型)と、立っているZ座標±⊿Z場合とで計算が異なる。
立ち型と寝転び型の影
3.上の図キャプションにあるように、影は足もとに合わせる。寝転びタイプだと変になるので、ドットで表現するか、別の影表示方法を考える必要有り。

4.当たり判定はXZ軸(地面)で見るか、XY(画面と並行)で見るかの違いがあるが、基本は両方を駆使する必要がある。

 押しのける判定
  ブロック⇔オブジェクト:基本XY領域で見るとよい。
        XZだと壁にめり込んだり妙なことになってしまう。

        ただし落とし穴が壁のようにひっかかるという変な状況が起こる可能性がある。
        幅を持たせたり、穴や壁など、自然に見えるようにチップ配置を工夫する必要がある。

   ショット⇔オブジェクト、敵:オブジェクト側はどっちでもいいが、見下ろし型ショット(地面にぺったり張り付いたように見えるショット)は「XY固定チェック」のほうが良い。

        XZの縦幅は小さくなる傾向にあるので、ほとんど敵に当たらずイライラしてしまう。

        例外として、見下ろし型ではなく立ち型のショット(爆発など)は、XZチェックでやったほうが自然(XY固定だと、上方向の遠い敵にまでヒットしてしまう)

   ショット、敵⇔プレイヤー:これはちゃんとXY/XZ駆使する。頭上を通り過ぎると思った弾がヒットするのはイライラする。

■オブジェクトの追加

1.うちの場合
list<オブジェクト名*> lstObjects
で管理しているが、ループ中にlstObjects.push_back とかすると整合性が取れなくなる。
そこで、
list<オブジェクト名> lstReserve
とか別のリストを用意して一旦そこに追加するようにしてる。

2.Shotループ中にShot追加するならReserveに入れるのだが、Enemyループ中だったらShotを直接増やしても問題無い。…と思ってたが、下記現象が発生する事が有る。処理が確定しているなら直接でも良いが、呼び出しが良くわからくならないよう注意が必要。
AはReserveに一旦入れているので問題なし。BはEnemies側での処理だからリスト直接追加でいいか(B)と思って書いているが、実はShotsから呼ばれていた…とかだと、不整合となる。

というか、もう全部Reserveに追加する方式でも良いと思う。追加のタイミングには注意。
ただし、Reserveにモノが残ってるとやっぱり不整合になるし、間違えて消しちゃってたりすると追加されなくなる。
こうやればOKって方法が無いので、とりあえずプログラマ側でそこら辺整理する必要が有る。

■バイナリ操作

C++でiostream使う場合は下記参照。
バイナリファイルの読み込みと書き込み C++ 
当然double以外も可能。下記はintの例。
int nNum = 1;
fout.write( (char*)&nNum, sizeof( nNum ) );
つまりどんな型でもchar=byte型にして渡せばOK。
1byteを下回る型は無いので、sizeofでサイズを取得すればよい。

注意なのは、"&"を忘れてもコンパイルが通っちゃうこと。ポインタもLONG LONG型なので、誤認する。
当然結果はメモリエラー。

■バイナリ操作(.NET)

当然.NETでもバイナリは使える。
バイナリ・ファイルを読み書きするには?[C#、VB]

やり方、というか基本的に上のURLに有る方法で十分だが、一点注意がある。

サンプルでは下記のようにオフセットを進めているが、C++.NETではこれをしてはいけない。というか、C++.NETに限らない話だと思うので、URLの説明が合ってるのか疑問。
readSize = fs.Read(buf, bufPos, Math.Min(1024, remain));
bufPos += readSize;
FileStreamの中で勝手にオフセットを進めているので、更にオフセットを先に指定してしまうと、あっという間にファイルの終端を超えてしまう。

結局下記のようにすればおk。
readSize = fs.Read(buf, 0, Math.Min(1024, remain));
サイズは固定長であればsizeof( double)とかで良い。
ちなみに…
byte[]
はC#の書き方。C++.NETだとこれでは怒られるので、…
cli::array^
として扱う。

出てきたバイト列は、BitConverterクラスを使って変換する。

バイト列と数値を変換するには?


■C++.NETとC#.NETの違い

C#.NETと違って、C++.NETの資料は極端に少ない。
使いやすいんだろうけど、既存ソースを流用し辛いのは致命的と言える。

・配列

String::Split
など、C#は配列でそのまま扱う事が可能だが、C++.NETは既存コードとの共存のため、…
cli::array
とかいうので置き換えている。
cli::array^ ary = strText->Split(_T(','));
サンプルで…
hogehoge[]
とか有ったら、大抵…
cli::array
に置き換えれば流用可能。

・ポインタ

C#はGCが頑張るので気にしない。書式も思いっきりJava風。

一方C++.NETは既存のC++コードと同居できるので、ポインタ(のようなもの)で扱うのが基本となる。

Hoge^ hoge = gcnew Hoge();
逆に、通常の書式…
Hoge hoge = Hoge();
のような形式は滅多に使わない。

・シングルトン/グローバル変数

これも厄介で、基本グローバル変数は使えないと思った方が良い。

ref class の中で
static Hoge^ hoge;
とかかいて、クラスのコンストラクタでgcnewする…とかは可能。

シングルトンというか、共通データインスタンスを持たせたい場合は、

[GStaticData.h]
public ref class GStaticData {
public:
static GStaticData^ staticData;
};

[Form1.h]
Form1(void)
{
  GStaticData::staticData = gcnew GStaticData();
}

などと書くのが暫定解になる。もっと良い方法が有るかも知れない。

■.NETでステータスラベルが真っ黒になる

画面例や詳しい内容は下記。私の環境では発生するが、AeroがON/32bit Color/WinXP以前の環境いずれかでは発生しない可能性が高い。
Windows Phone Questions
プログレスバーをもつステータスバーがタスクバーに隠れて表示される時のステータスバーのラベルの背景

要約:
・ステータスが、ある要因で真っ黒になる事がある。
 要因は恐らく上に書いてあるディスプレイの表示形式。
・タイミングはウィンドウ切り替え時やタスクバーなどに隠れたとき…etc
・対処策:白背景にしてborderなしにする

今のところ上の対処策で問題ナシ。

■文字列の結合@.NET

Concat(String^,String^)または+= String^

■算術関数のスピードアップ

参考URL:
まだ完成の域に達していないが、いくらか対処方法が有る。

・FPUを直接呼び出す。

FPUにある関数を直接呼び出すことで、ある程度短縮可能。
 FPU(浮動小数演算ユニット)は486あたりから標準で載っているので、特に気にする問題は無い。
 アセンブリを使うのでちょっとびっくりするが、コピペで問題無い。

 簡単な分、下のテーブル方式よりは遅い。


・テーブルを使う

例えばsin/cosなら…
0<=θ<360 p="">
の値を360個配列として持っておけば、(当然だが)ほぼ0Fに近い勢いで答えを出せる。
 上記数値以外が入ったときに正規化するなど必要だが、実現も簡単だし、多用しているなら是非導入したい。

 ただし、atanなどは256x256のサイズにしたり、角度によって出し方を変えるなど、工夫が必要。面倒なので今回は見送り。

■実行時間の計測

上のような実行時間を計るにはどうすれば良いか。
ぐぐれば大体出てくるが、まとめると下記のようなものが一般的か。
WinAPIもあるが割愛。


clock_t c = clock();
コンマ秒数は ( clock() - c ) / CLOCKS_PER_SEC で出せる。
こいつは完全にクロック処理時間を出すので、他の処理が隣で走ってると、もろに影響を受けた値になるので、実際の時間かというと疑問。


time_t t = time(NULL);
秒数は difftime( time(NULL), t ) で出せる。
time_tは1秒単位までしか出せない。1秒未満の処理時間が計れないのはちょっと困る。

DXLib
int nT = GetNowCount();
フレーム数(ステップ数)が GetNowCount() - nT で出せる。
標準で60F/secなので、1/60してやれば大体のコンマ秒数が出せる。


■独自アイコンを設定

ぶっちゃけDXLib本家に載ってるが、細かい情報がばらけてたり、やり方が若干違うように見えるので、まとめる。

ちなみに製品版ではこんな面倒なことをする必要は無い。Express版限定のお話。

256x256のアイコンとかは不明。

アイコンで変えるべき箇所は…
  1. デスクトップ上の32x32(透明色指定可能)
  2. 起動時、ウィンドウ左上のアイコン
の2箇所。

1.は下記に有るとおりの方法でOK。
自作ソフトにオリジナルアイコンを付ける

すなわち…
Resource.rcを空のテキストファイルとして作成し、プロジェクトに登録。
中に下記を書く
#include "Resource.h"
MAINICON ICON DISCARDABLE "ICON.ico"
MAINICON:コレに設定することで、アプリケーションのアイコンになる。
ICON:リソースタイプ
DISCARDABLE:詳しくはここ。デフォルトの設定なので、書かなくてもOK

Resource.hには下記を書く
#pragma once
#define MAINICON 100
実はヘッダを書かなくてもアイコンは変わるのだが、上で言っていた2(左上の小さいアイコン)を設定する上で必要。

DXLibを使っているなら…
SetWindowIconID( MAINICON );
と書けば完成。ウィンドウ設定など、諸々の処理よりも前に書けば良い。

■時計

時計の相互変換
time_t→年月日
time_t t = time(NULL);
struct tm *lpTime = localtime(&t);

参考URL

年月日→time_t
struct tm m;
memset(&m, 0, sizeof(m));
mに日付時刻をセット
mktime(&m);

参考URL

0 件のコメント:

コメントを投稿