Objectを大量に描画したいが…重い。なにかが悪い。

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
やっくる

Objectを大量に描画したいが…重い。なにかが悪い。

#1

投稿記事 by やっくる » 5年前

FC風の横スク2Dシューティングを製作しているのですが
FCグラディウスで有名な、こちらの様な
「障害物オブジェクトが多数設置されたステージ」を作ろうものなら
http://download1.getuploader.com/g/9%7C ... efault.jpg
すぐに画面のfpsが遅延状態になってしまいます。

現在、障害物オブジェクトは雑魚敵と同じクラスで扱っておりますが、
画面内に入ったらインスタンス化する様にして
なるべくギリギリまで生成されない様にしています。
その障害物のサイズも8×8と小さいです。
が、画面内に15個〜20個作られた時点で
画面が相当重くなってしまうのです。

今の時代で画面内にたかが20個や30個、小さなオブジェクトが
生成されたくらいでここまでfpsが下がるということに疑問を抱きますが、
なぜ自分は今、FC時代に出来ていることの再現が出来ないのでしょう?

画面内にオブジェクトを大量に作る場合
なんかしらテクニックが必要なのでしょうか?

想像で話しますが、
インスタンス化したクラスの中の処理数が重要なのでしょうか?
例えば、私の障害物オブジェクトはプレイヤーが壊せることから、
雑魚敵と同じクラスで作っています。
なので、処理の中には他の雑魚敵の処理も連ねていて、
大量のif文で実行処理を分けています。
本来そこは障害物オブジェクト用に、
座標とHPだけを持ち、HPがゼロになったら消えるって処理だけの
非常にシンプルな内容のクラスを
雑魚敵用のクラスとは別で作り、
インスタンス化する方が良いのでしょうか?

また、雑魚敵のクラスといっても
自分は癖で何種類もの雑魚敵をそのひとつのクラスで
回しています。
動きなどを、処理の中で細分化させて
すべての雑魚敵がそのクラスでインスタンス化するだけで
動く様に記載してしまっています。
こういうのはヤメた方がいいのでしょうか?

ですが、何種類もいる敵に合わせて
個別にクラス名.cppを作っていたら
それこそ、中の処理数は短いが、ファイル数は敵の種類だけあるので
膨大な数になると思うんですが、、、でも
重くなることを考えれば、そっちのが良いのでしょうか??

その辺の知識が独学の自分には欠けていますし、
得る方法がわかりませんので、こちらで質問させて頂きました。

昔pc-98でファーストクイーンとゲームがありました。
ここの回答者サマたちならご存知かと思います。
http://download1.getuploader.com/g/9%7C ... ss03-2.jpg
こんなのを将来作ってみたいとも思っていますので
今回の質問は重要かと考えます。
ぜひ、よろしくお願い致します。

というか、8bitのグラディウスや
パソコンとは言え旧時代のゲームのファーストクイーンですよ?
あんだけオブジェクト作っても処理速度は落ちてなかった(画像はちらついてましたが)のに
一体、どんな工夫をしていたのでしょう????
それとも、ただ自分が
あまりにもヘタクソな作り方してるだけなのでしょうか?

せと

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#2

投稿記事 by せと » 5年前

さすがにオブジェクトの20個や30個で、fpsがガタ落ちするようなことは普通ではありません。
プログラムにもよりますが、負荷が少なめのオブジェクトであれば、
1000個~2000個は軽く処理できるはずです。

ただし、大量のグラフィックスを同時に描画したい場合は、
描画処理のちょっとした知識と工夫がないと若干重くなりますが、
それでも100枚程度までなら問題ありません。

何か無駄な処理を繰り返してしまっているのではないでしょうか?
例えば、初回だけ実行すれば済む比較的重い処理を、毎フレーム実行してしまっている、とか。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#3

投稿記事 by softya(ソフト屋) » 5年前

たぶん、どこかで間違っています。
実証したいのなら、超簡単なプログラムで画面内に15個〜20個表示してみてください。
それで問題ないのなら、ロードが無駄に発生している、画面内に15個〜20個つもりが数千個表示している、どこかで過剰な負荷のかかる処理をしているのいずれかです。
自分のプログラムが間違っている発想でデバッグしないとデバッグできるものもできなくなりますよ。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
spaaaark・∀・
記事: 62
登録日時: 6年前
住所: 京都のどこかの学寮
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#4

投稿記事 by spaaaark・∀・ » 5年前

皆さんが色々な意見を投稿しておられますが、僕は処理に関する気になる所を攻めてみます。

まず、すべての敵・障害物で同じクラスを使っている件について。
これはいろいろとまずいです。比較で処理が遅くなってしまったり、あるいはコードが読みにくくなってしまったり。
これはポリモーフィズムを用いて、同じような機能を持ちながら別の処理を行なえるような処理にするべきです。
これを用いれば少なくとも障害物と敵との差別化は図れるはずです。
あとは画像の違いなど、軽い変更なら汎用化して外部データ化するのもありです。例えばcsvのような。
これでファイル数の増加も抑えることができます。
なお、どっちの方が見やすく、修正しやすいかは現状のコードと、分けて書いたときのコードを想像すれば一目瞭然のはずです。
[キーワード]ポリモーフィズム,インターフェイスクラス,継承,オーバーライド etc...

次に、インスタンスの管理方法です。
これはコードがないのではっきりとしたことが言えないのですが、もしC++でstd::vectorを使って管理している場合、
eraseの時の処理で時間がかかっている可能性が高いです。この場合、挿入と削除に強いstd::listを使う事を推奨します。
ただしこちらの場合基本的に添字ではなくイテレータを用いてアクセスすることになるので注意してください。
[キーワード]std::list イテレータ etc...

あとはソフト屋さん・せとさんが言うような、処理のミスを地道に探していくことが大切です。
デバッグしてみれば自分が思わないような処理をしていることが多々あるので、ゆっくりたどっていくことが大切になります。
クリエイティブな生活で刺激的な毎日を!

ISLe()

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#5

投稿記事 by ISLe() » 5年前

いまどきの弾幕シューティングだと同時に4000くらいのオブジェクトでも少ないほうだと思います。

投稿に書かれている内容についてわたしなりの回答をします。
以下はより高速にするための提案になります。
質問者さんのプログラムが重い原因は別にあるはずです。
それはコードを晒してもらわないことには分かりません。


FCのBGと同様に背景の描画はタイルマップを採用したほうが良いと思います。
破壊できる障害物については個別にインスタンスを作るのではなく、タイルマップを直接(あるいは内部に確保した状態マップを)チェックして『ある状態からある状態に変化させる』処理を対応させると良いでしょう。
そうすれば描画はタイルマップというひとつのオブジェクトで一括して行え、対応する処理は障害物の種類(且つ状態変化のあるものだけ)分で済みます。
スクロールに応じてタイルマップの画面に入ってくる端の分を更新していきます。
同時に障害物と状態変化処理の対応表も(必要であれば)更新していきます。


ゲームプログラムでコンテンツとしてのキャラの種類に応じてクラスを継承するようなことはむしろ非推奨なので、その辺は安心してください。
コンテンツとしてのキャラの種類は、動きを定義したデータを再生する処理(いわゆるスクリプト)を組んだりして実装します。

ただし何種類ものキャラの動きを直にコーディングしてifやswitchで分岐するようなものではありません。
どんなキャラにでもなれるクラスであって、いくつものキャラをまとめたクラスではありません。
#どんなキャラにでもなれるクラスとして、機能を継承するといったことはよくあります。

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#6

投稿記事 by やっくる » 5年前

せと さんが書きました:さすがにオブジェクトの20個や30個で、fpsがガタ落ちするようなことは普通ではありません。
1000個~2000個は軽く処理できるはずです。
何か無駄な処理を繰り返してしまっているのではないでしょうか?
例えば、初回だけ実行すれば済む比較的重い処理を、毎フレーム実行してしまっている、とか。
1000個~2000個は軽く、、ですか、良いですね、素晴らしい。
今回の問題が自分のせいだとわかり、安心しました。
がしかし、1000個~2000個は軽く処理出来るところを
20個やそこらでガタ落ちさせる処理に気付けない自分って
かなり致命的ですね。
もちろん初回だけ実行すれば済む処理を
毎フレーム処理している更新関数内には入れてないつもりなんですけど、、
でも今回のことでもう一度しっかり確認したいと思います。
(ソースを載せたいのはやまやまですが、更新関数内の量もハンパ無いので、、)

softya(ソフト屋) さんが書きました:たぶん、どこかで間違っています。
実証したいのなら、超簡単なプログラムで画面内に15個〜20個表示してみてください。
それで問題ないのなら、ロードが無駄に発生している、画面内に15個〜20個つもりが数千個表示している、どこかで過剰な負荷のかかる処理をしているのいずれかです。
→ロードが無駄に発生している
ロードの類いの処理は、コンストラクタか、INITIALIZE関数にしか記載しておらず、
一応その辺は守っているつもりです。

→画面内に15個〜20個つもりが数千個表示
自分はまだまだ知識が浅いので、そういうミスをたまに犯している場合もあります。
ので、注意をしています。発生処理については色々と確認しておりますので
今回に限っては、この辺も大丈夫なはずです。

なので、ソフト屋さんのご指摘で私が自信の持てない項目は
過剰な負荷のかかる処理ですね。
これは、先のせとさんのご指摘と同じかと思います。
やはり何か無駄な処理を繰り返してしまっているのかもしれません。

とりあえず、その雑魚敵用のクラスで作った
オブジェクトを画面内に増やせば、
すぐに画面が遅延するのでとても怪しいです。
思えば、アイテム類を構成する別のクラスで作った
オブジェクトは画面内に20個いくつ作ろうが、画面は遅延しませんでしたし。
雑魚クラスの更新関数が怪しくなって来ましたので
確認したいと思います。

せとさん、ソフト屋さん、ありがとうございます。

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#7

投稿記事 by やっくる » 5年前

spaaaark・∀・ さんが書きました: もしC++でstd::vectorを使って管理している場合、
eraseの時の処理で時間がかかっている可能性が高いです。
この場合、挿入と削除に強いstd::listを使う事を推奨します。
これは大丈夫です。
要素の追加や削除が頻繁に行われるものはlistで、と
勉強しましたので、ゲーム中に消去される様なオブジェクトはすべて
listで管理しております。
spaaaark・∀・ さんが書きました: これはポリモーフィズムを用いて、同じような機能を持ちながら
別の処理を行なえるような処理にするべきです。
そうですよね、実はこの辺が自分は弱いです。
多分、ISLe()さんも言う「どんなキャラにでもなれるクラス」も
同じ意味だと思うんですが

以前、プログラムの本で、雑魚敵をインスタンス化する際に、
毎回move関数をどっかから繋げてインスタンス化させるというのがありました。
うろ覚えですが、確か、別のところに様々な動きの処理の関数が用意されていて
それを各雑魚のmove関数にポリモフィズムで繋げて、
その雑魚の更新中はその処理をさせる、みたいな感じでした。
その時は自分が初歩の初歩でしたのでいまいち理解していませんでしたが
自分はそれを理解しなければならないところまで進んだようですね。
要は、今の私の処理では、ひとりひとりが処理を持ち
動いているわけですが、
ポリモフィズムをすることにより、ひとつの処理をみんなで使っている
といった感じでしょうか。
しかし、残念ながらポリモフィズム、すなわち多態性を利用した敵の生成を
解説しているサイトなんて、見当たりません。
ので、その本をもう一度探してみることにします。
ISLe() さんが書きました: FCのBGと同様に背景の描画はタイルマップを採用したほうが良いと思います。
はい、現在自分のシューティングの背景はタイルマップを採用しております。
それこそステージ開始時に描画タイル座標と描画タイル画像Noを持つ構造体で
vectorに詰めて描画しております。
確かに、そこにflgでも加え、背景が壊れる前、背景が壊れた後、で
描画する画像を設定する、のは良いかもしれません。
ただ現在、オブジェクト同士の判定と
背景とオブジェクトとの判定とは別のところで処理していまして、
背景vectorと動いてるオブジェクトたちとの衝突判定関数では
ぶつかると移動停止(押される等)の処理をやっているのですが、
そこにオブジェクト同士の判定と同じ様な判定内容も作る必要性が出て来ますから
なんだか、ややこしくなりそうで怖いです。

オブジェクトがタイルマップの上下左右それぞれのどの面に当たっただのの判定関数と、
オブジェクト同士の矩形領域の重なり判定とは別の箇所で処理しています。
なんだか、グラディウスでいうところの生き物みたいな障害物は
オブジェクト同士の矩形領域の重なり判定の方でやりたくなるんですが
ナンセンスなんでしょうか?
また、たまたま今回はマップのタイルサイズと障害物のサイズが同じなので
問題無いですが、障害物のサイズがタイルより小さい場合
タイル構造体がその障害物の座標なりサイズなり別で持つようにしなければならず
さらにややこしくなりそうな気がします。
あくまで想像ですが。

そもそも、ひとつのゲーム内で
キャラと背景に対しては上下左右の面との判定、
キャラ同士なら矩形のみで判定、とをしたい場合
判定関数を二種類作る、は正解なのでしょうか?
ISLe()さんなら、そこはキャラ同士でも上下左右の面との判定してやれば
ひとつの判定関数で済むじゃん、なんでしょうか?

とりあえず、
まずはクラス内で無駄な処理をしている可能性が大なので
そこを確認してみますが
ISLe()さんの言う、「より高速にするための提案」は
今後のために勉強すべきだとは思っています。いや、勉強したいので
ISLe() さんが書きました: コンテンツとしてのキャラの種類は、動きを定義したデータを再生する処理
についてもう少し詳しく教えて頂けませんか?
ただ、自分はC++以外は扱えないのでスクリプトを組むのは無理ですので
そこはポリモフィズムを利用して動きを定義したデータを再生する処理、
で御願い出来ればと思いますが、、、

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#8

投稿記事 by softya(ソフト屋) » 5年前

本当に問題ないのか、グローバル変数で良いので初期化と描画にカウント処理を埋め込んでみてください。
そのカウントを1フレームの最初でクリア、フレームの終わりでDrawFormatStringなどで描画します。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#9

投稿記事 by softya(ソフト屋) » 5年前

それと処理の時間計測をされていない様なので、まとまった単位で時間計測を行ってください。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#10

投稿記事 by やっくる » 5年前

softya(ソフト屋) さんが書きました:グローバル変数で良いので初期化と描画にカウント処理を埋め込んでみてください。
そのカウントを1フレームの最初でクリア、フレームの終わりでDrawFormatStringなどで描画します。
DrawFormatStringで確認するのはよくやりますが、
「そのカウントを初期化と描画に埋め込む。1フレームの最初でクリア、フレームの終わりで描画」
についてもう少し詳しくお願いします。

Initialize関数{
count=0;
count++;

DrawFormatString(割愛 count) 
}

Draw関数{
count=0;
count++;

DrawFormatString(割愛 count)
}

ってことでしょうか?
だとすれば自分は頭が悪く、これで一体なにを調べられるのか検討がつきません。
1フレームだけの変動ではDrawFormatStringで描画しても
0と1が凄いスピードで繰り返されるだけだと思うのですが、、、
ぜひこれによってなにがわかるのか、教えて頂けませんか?

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#11

投稿記事 by softya(ソフト屋) » 5年前

一番外側のwhileループ
while( ScreenFlip()==0 && ProcessMessage()==0 && ClearDrawScreen()==0 ){
この直後にcount=0;ですね。
DrawFormatString(割愛 count) は上のwhileのとじカッコ}の直前に入れます。
なので、InitializeとDrawは別カウント変数です。

こんな感じです。

コード:

#include "DxLib.h"

int WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR, int ) {
	ChangeWindowMode( TRUE ), DxLib_Init(), SetDrawScreen( DX_SCREEN_BACK ); //ウィンドウモード変更と初期化と裏画面設定

	int x = 0;
	int Handle;     // 画像格納用ハンドル
	Handle = LoadGraph( "画像/キャラクタ01.png" ); // 画像のロード

	// while( 裏画面を表画面に反映, メッセージ処理, 画面クリア )
	while( ScreenFlip() == 0 && ProcessMessage() == 0 && ClearDrawScreen() == 0 ) {
		initCount = 0;
		drawCount = 0;
		
		ほにゃらら。中でカウント。
		
		
		DrawFormatString(割愛 initCount,drawCount);
	}

	DxLib_End(); // DXライブラリ終了処理
	return 0;
}
1フレーム間に何回初期化と描画されたかを調べます。
変化が多すぎる場合は、平均とmaxを取る方向で。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

ISLe()

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#12

投稿記事 by ISLe() » 5年前

わたしが思っているFCのBGと同様のタイルマップというのは、単にグラフィックをセットしてタイル状に描画する仕組みであって、いわゆるタスク的なオブジェクトのかたまりではないです。
判定処理は別のところでまとめて行うという考え方は良いと思いますが、わたしが考えているのと同じなのかどうかは分かりません。
キャラクタがタイルマップのサイズと合わないのならそれはもうタイルマップではないのですが。

やっくるさんの説明を読んだ感想は、既にかなり面倒くさいことをやっていそうだなあということですね。
ちょっとした特殊化とパラメータ調整で実装できそうなことを大量にコードを書いて実装しているようです。
根本的に異なる考え方でプログラムを組み上げているようなので互いにいくら説明を繰り返しても噛み合わない気がします。


スクリプトというのは単なる数値の並びを命令と解釈して実行するものも含みます。
一般的な話となると書籍が一冊できるくらいのボリュームの内容になるので、
・CPUが命令を実行する仕組みのうちメモリとレジスタとプログラムカウンタあたりのごく基本部分
・アセンブラ
・Javaや.NETなどのバイトコードを実行する仮想マシンの仕組み
あたりを勉強してください。
言語としての完成度を上げるのならインタプリタやコンパイラについても勉強してください。

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#13

投稿記事 by やっくる » 5年前

softya(ソフト屋) さんが書きました: 1フレーム間に何回初期化と描画されたかを調べます
手ほどきを示して頂き、ありがとうございます。
やってみましたが、結果は
数値、その推移等、多分正常だと思います。

とにかく、一部?のクラスを利用したオブジェクトを
増やすと遅くなってしまいます。
例えば、オブジェクトのENEMYを作る際に利用するクラスがそうで、
エネミーを画面内に20も作れば、もうスクロールがかなり遅くなります。
その敵を倒して消して行けば、スクロールはもとのスピードに戻ります。
で、その敵のクラスなんですが、インスタンス化する際に
引数で敵の種類を渡します。
そして、初期化、更新、描画のすべての関数が
switch文でわけられていまして、
その敵専用の初期化、更新、描画を処理し続ける、といった感じにしてます。
この方法自体は問題ないんですよね?
もちろん、ソースが大量になるわけでミスを誘発することになったり、
本来なら委譲を利用したりと、もっと合理的な方法があるとは思うんですが
自分はまだ初心者ですので、、、これが精一杯でして。
で、そんな内容のクラスなんで敵によっては、
更新の内容がスカスカになってる敵もいるんです。
(地面から動かない敵とか)
で、その敵を20個ほど作っても同じ遅延状態になってしまうんです。
つまり振り分けている敵の各処理云々では無く、
このクラス自体の内容に問題がありそうです。

ちなみに、アイテムも同じようにインスタンス化させて
ステージに表示しているんですが、
こちらは20個作ろう(アイテム専用のクラスで作ってます)と
スクロールに遅延は置きませんので
オブジェクトを放り込んでいるlist等を扱う処理が
問題では無いと思われます。

しかし、ENEMYクラスをどれだけ眺めても
原因が突き止められません。
独学の自分が成長するかどうかかなり
重要なところな気がします。
恥を承知でそのENEMYクラスのcppをアップしますので
一度確認して頂けませんでしょうか?
怪しいところとか、意味不明なところがありましたら
ご指摘頂ければ幸いです。
http://u9.getuploader.com/nikoman/downl ... cEnemy.txt

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#14

投稿記事 by やっくる » 5年前

ISLe() さんが書きました: ちょっとした特殊化とパラメータ調整で実装できそうなことを大量にコードを書いて実装しているようです。
はい、まだプログラム歴の短い駆け出しなので、
無駄なことを大量にしていると思います。
非常に見苦しいソースですが、こちらを見て頂き、
アドバイスを頂けるとありがたいです。
http://u9.getuploader.com/nikoman/downl ... cEnemy.txt
このクラスのENEMYを画面内に20個も生成すると
遅延してしまいます。。。
処理が多過ぎるのでしょうか?
ISLe()さんがおっしゃいました、
ISLe() さんが書きました: どんなキャラにでもなれるクラスであって、いくつものキャラをまとめたクラスではありません。
の意味を理解出来れば、改善するのかな、と思うのですが
今の自分にはまだ理解出来ません。
しかし、自分は"委譲"というものに触れたことがあります。
それが近いものではないでしょうか?
で、調べてみると、ISLe()さんが
http://dixq.net/forum/viewtopic.php?t=14077
のトピックにてまさにこういう会話をしていらっしゃいます。
質問者 さんが書きました: SLeさんのおっしゃる翻訳単位で分割とは分割コンパイルのことで、移動用の関数のパターンはそれ専用のmovePattern.cppなどにまとめておけばいいということでしょうか。
ISLe() さんが書きました: 翻訳単位での分割はそのとおりです。
Enemyクラスがインターフェースとして働くことになるのでそれに関する部分は自ずと共通化されることになります。
各キャラクタの固有部分が分離されて、オブジェクト別に状況に応じた柔軟な設計が可能になります。
まさに、ここで触れています"委譲"を自分は覚えたいのです。
委譲とは、その部分の仕事を他のクラスに丸投げすること、と聞いてます。
とすれば、今回の自分の話で言えば、
ENEMYクラスの更新内のすべてがスカスカになるわけですよね?
つまり、ENEMYオブジェクトを増やそうと、重くなるはずがないと。
委譲を利用すれば、今回の様なことは起きないのかなと思っているのですが
そういうわけではないのでしょうか?
自分のENEMYクラスのこの状態ってISLe()さんから見たらどうなんでしょうか、、

アバター
へにっくす
記事: 628
登録日時: 7年前
住所: 東京都

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#15

投稿記事 by へにっくす » 5年前

やっくる さんが書きました:アドバイスを頂けるとありがたいです。
http://u9.getuploader.com/nikoman/downl ... cEnemy.txt
リンクは正しくはってください。
http://u9.getuploader.com/nikoman/downl ... cEnemy.txt
一応見てみましたが、見にくいです。
それぞれの関数の行数が、
CEnemy::Initialize (474行)
CEnemy::Update (878行)
CEnemy::Draw (276行)
と多すぎですので、それぞれの関数が100行以内におさまるように修正するのが先だと思います。
これじゃどこに問題があるかすぐに探せないでしょうねえ・・・
written by へにっくす

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#16

投稿記事 by softya(ソフト屋) » 5年前

もう一つ提案した時間計測の話はどうなってますか?

コードについては、問題点を拾い出しづらいです。
C言語で書かれた関数ポインタ方式の龍神録のほうがスッキリしている状態だと思いますので、switch case多用でC++の良さを失っています。

【補足】
気になるのは、弾一つ一つがクラスみたいなのでnew/deleteなどが重そうな印象。
あとnewでコンストラクタで画像ロードしていたら重そうですね。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#17

投稿記事 by やっくる » 5年前

へにっくす さんが書きました: 一応見てみましたが、それぞれの関数の行数が、
CEnemy::Initialize (474行)CEnemy::Update (878行)CEnemy::Draw (276行)
と多すぎですので、それぞれの関数が100行以内におさまるように
修正するのが先だと思います。
すみません、それはつまり
「そりゃ関数が100行を超えて来る内容のクラスをポコポコ作ってると
フレームレートはすぐ遅延状態になるよ」ってことでしょうか?
それとも、ただ単に「行数多くなると、ミスしやすい、
こうやってミスを探しにくいから
100行以内に収まる様にした方がいいよ」ということでしょうか?
気になります。
なぜなら、自分の実力ではこれらの動作を盛り込んで
各関数を100行以内に収めるのは正直不可能です。
もちろん、無駄な部分があるのは認めますが、
それを無くしても300、400行を縮めるのは無理です。
てっとり早く処理の行数を減らす方法として、
"委譲"を利用したいのですが、委譲を利用するには
自分自身勉強が必要ですので、今は無理です。
ただ、別の方法として、
ENEMYクラスを別に増やし、作る敵によって利用するクラスを分散させてやることにすれば、
ひとつのクラスの関数の行数を抑えるのは可能です。
「そうした方がいいんだよ」との結論でしたらそうしたいので
どちらの意味でアドバイスして頂けたのか、知りたいのです。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#18

投稿記事 by softya(ソフト屋) » 5年前

後者ですね。
バグるから・メンテが困難だから・バグが探せないから。
と言うことです。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#19

投稿記事 by やっくる » 5年前

softya(ソフト屋) さんが書きました:もう一つ提案した時間計測の話は
すみません、
softya(ソフト屋) さんが書きました:処理の時間計測をされていない様なので、
まとまった単位で時間計測を行ってください
これについては、やり方がわかりません。
処理の時間計測というのは、どういう方法で行うのでしょうか、、、。
初心者なので、処理の時間を計る、ということがどういうことなのかも
ちょっと想像つかない状態でして、、、お恥ずかしいです。
申し訳ありませんが、出来ましたら、処理時間計測をする重要性と
カウント処理の時の様に、簡単な手順を示して頂ければ非常に助かります。
softya(ソフト屋) さんが書きました:C言語で書かれた関数ポインタ方式の
龍神録のほうがスッキリしている状態だと思います
その言い様だと、龍神録は"委譲"を使われているのでしょうね。。。
softya(ソフト屋) さんが書きました: 弾一つ一つがクラスみたいなのでnew/deleteなどが重そうな印象。
あとnewでコンストラクタで画像ロードしていたら重そうですね。
もちろん弾もひとつひとつのクラスで作っています。
あ、確かに大量に弾を出す敵を作ると、
画面が遅延します。つまり、遅延する原因は同じみたいですね、、、、
ちなみに、画像ロードは各クラス内で個別に読み込んだりはしていません。
ゲームを最初に実行した時の初期化関数内でゲームに利用する画像はすべて読ませています。

弾を増やせば遅延のこともありますし、
結局、自分のフレームレート遅延は、ミスというものでは無く
クラスに処理を詰め込み過ぎ等の、
処理量が多いというものなんでしょうか?

しかし、自分は、ゲームで出現させる敵から弾から
各オブジェクトをクラスで作り、listに放り込んでいくことを
ある熟練プログラマからアドバイスされました。
(皆様から見て、どのくらいのレベルの方かは判断つきませんが)学びました。
これは、間違いなんでしょうか?
実際は、そうする方が、すごくラクなんですが。。。
softya(ソフト屋) さんが書きました: switch case多用でC++の良さを失っています。
しかし、自分の思った
「敵NO(15種類)を受け取り、そのキャラ専用の初期化、更新、描画の処理を実行させるクラス」
を実行するには、中でswitch caseを多用する他ありません。
"委譲"を利用すれば新しい考え方が出来る様な気がしますが
今はこれが自分の知識の限界です。
例えば、ソフト屋さんなら
「15種類の敵に、そのキャラ特有の初期化、更新、描画をさせる」
を実行しようとしたら、どう作られますか?
しかもそれぞれの弾にもまったく違う初期化、更新、描画を要します。
弾も敵もソフト屋さんはクラスでは作らないのでしょうか?
熟練の方のやり方を学びたいです。ぜひ教えて下さいませんか?

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#20

投稿記事 by やっくる » 5年前

softya(ソフト屋) さんが書きました:後者ですね。
バグるから・メンテが困難だから・バグが探せないから。
と言うことです。
やはり、そうですよね。
15種類の敵の処理をひとつのクラスでさせようと
今の時代、重くなるはずないですよね?
だとすれば、クラス内の処理では無く
もっと根本が、自分の構成の仕方に問題があるんでしょうか?

オブジェクトの管理について自分は現在、
ゲーム中、オブジェクトlistをひとつだけ作り、
そこにプレイヤーや敵、それらの弾、アイテムなど
すべてのオブジェクトをインスタンス化しては放り込んで
消滅するとflgを立て、毎フレーム最後に
list内から一括で削除しています。
衝突判定や、存在数を確認したりする場合
各オブジェクトが持つ変数の"ID"で判別、
イテレーターにて抽出して処理しています。

ちなみに、listを一つだけにしているのも
いちいちプレイヤーの弾用のlistとかエネミーの弾用のlistとか作る方が大変で
IDつけとけば判別出来るんだから
ひとつのlistで回せばいいとのアドバイスを受けたからです。

上記(赤フォント)のやり方なんですが
問題ありますか?

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#21

投稿記事 by softya(ソフト屋) » 5年前

>申し訳ありませんが、出来ましたら、処理時間計測をする重要性と
>カウント処理の時の様に、簡単な手順を示して頂ければ非常に助かります。

遅延して困っているわけですから、遅延している原因を探さないと行けませんよね。
それには、処理のあちこちで時間計測をして何処ネックか探さないといけません。
よほど経験を積まない限り、憶測だけで色々やってみても一向にケリが付きませんよ。
こちらもソースコードを見ていないので、想像で原因を書いてますが的中率はもちろん低いです。
それとやっくる さんの情報が正しいとも限りません。
思い込みを排除できない以上は、やっくる さんの報告は誤情報である可能性が常に付きまといます。

と言うことで、時間計測して現銀原因を探りましょう。
処理にかかっている時間で問題点を探します。
GetNowCount();
http://homepage2.nifty.com/natupaji/DxL ... .html#R7N1
で「Windows が起動してから経過 時間をミリ秒単位」で得られますので、処理開始と処理後の時間を得て差分を処理時間とします。
これで顕著に遅い部分を探しましょう。
最初はmain内のループなど大きな単位で、だんだん細かくメソッド関数まで降りてきます。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#22

投稿記事 by softya(ソフト屋) » 5年前

> その言い様だと、龍神録は"委譲"を使われているのでしょうね。。。

龍神録のC言語のコードを見られたことがないのでしょうか?
これです。
http://dixq.net/rp/12.html

【補足】
コードを直す件は、別途やりましょうか。
遅延バグ潰しとコードのリファクタリングの2つを一度にやるとろくな事になりません。
まず遅延の原因を探すことです。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#23

投稿記事 by やっくる » 5年前

softya(ソフト屋) さんが書きました: まず遅延の原因を探すことです。
そうですね、わかりました。
GetNowCount() で
関数の処理にかかっている時間を
調べてみたいと思います。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#24

投稿記事 by softya(ソフト屋) » 5年前

アルゴリズムの二分探索をご存知ですか?
これに似た考え方ですが、最初に関数まで絞り込むのではなく前半処理・後半処理と分けて時間計測で調査します。
それで前半が重ければ、更に前半の半分で調べます。
これを繰り返して絞り込んでいきます。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

うずら

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#25

投稿記事 by うずら » 5年前

開発環境になにを使ってるか知りませんが素直にプロファイラ使えばいいんじゃないでしょうか・・・

プロファイラを使わないほうがいい理由とかあったら教えてください。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#26

投稿記事 by softya(ソフト屋) » 5年前

うずら さんが書きました:開発環境になにを使ってるか知りませんが素直にプロファイラ使えばいいんじゃないでしょうか・・・

プロファイラを使わないほうがいい理由とかあったら教えてください。
expressなら初めからついていないってところでしょうか。
【補足】VC++Express系に無料で容易に導入できて、初心者でも使いやすいプロファイラがあったら教えてください。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

ISLe()

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#27

投稿記事 by ISLe() » 5年前

委譲といっても対象はいろいろあるので、けっきょくはどういうふうに実装するかの問題に帰結します。

複数の種類の敵をどうやって互いに影響し合うことなく実装するか、については簡単なサンプルコードを書いてみようと思います。

気長にお待ちください。

ソースコードのほうも。


ひとつのリストに放り込んで回すのはふつうだと思いますよ。
ただ、ヒープメモリの確保・解放はけっこう時間を食います。
フレームレートがひどく落ち込むほどではありませんが。

うずら

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#28

投稿記事 by うずら » 5年前

オフトピック
ソフト屋さん、回答ありがとうございます。

やっくるさんがご利用できるかはわかりませんが、もし学生ならMSのDreamSparkに入ってProfessional版を使えるようにするといいんじゃないでしょうか。
処理落ちの原因を探るとき、重い処理を探すときなんかはクリティカルに役に立ちます。いちいちデバッグ出力する手間が省けるうえに、
思ってもみなかったところの

国際学生証云々とか書いてありますが

マイクロソフトDreamSparkの学生向けページ
アカウント作成中の学生認証の設問「アカデミック資格アカウントをどの方法で検証しますか」で、「学校を経由して検証を受けます」を選択し、学校名で検索、提供された電子メール アドレスを入力して学生認証を受けて下さい。学校名が表示されない場合は「自分の学校が見当たりませんか」をクリックすることで市町村や機関別で検索ができます。それでも見つからない場合は「手動検証リクエスト」から手動で行って下さい。この場合、学生証のコピーと現在の成績証明書または課程一覧のコピーの両方を添付する必要があります。

僕は高校の学生証をスキャナで取り込んで送ったら認証が通りました。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#29

投稿記事 by softya(ソフト屋) » 5年前

うずら さんが書きました:
オフトピック
ソフト屋さん、回答ありがとうございます。

やっくるさんがご利用できるかはわかりませんが、もし学生ならMSのDreamSparkに入ってProfessional版を使えるようにするといいんじゃないでしょうか。
処理落ちの原因を探るとき、重い処理を探すときなんかはクリティカルに役に立ちます。いちいちデバッグ出力する手間が省けるうえに、
思ってもみなかったところの

国際学生証云々とか書いてありますが

マイクロソフトDreamSparkの学生向けページ
アカウント作成中の学生認証の設問「アカデミック資格アカウントをどの方法で検証しますか」で、「学校を経由して検証を受けます」を選択し、学校名で検索、提供された電子メール アドレスを入力して学生認証を受けて下さい。学校名が表示されない場合は「自分の学校が見当たりませんか」をクリックすることで市町村や機関別で検索ができます。それでも見つからない場合は「手動検証リクエスト」から手動で行って下さい。この場合、学生証のコピーと現在の成績証明書または課程一覧のコピーの両方を添付する必要があります。

僕は高校の学生証をスキャナで取り込んで送ったら認証が通りました。
やはり、DreamSparkで入手できるVisualStudioPro版が前提なのですね。
DreamSparkで入手した物では商用ソフト販売禁止(同人ソフトを含む)ですので、それさえ理解されていれば良いかと思います。
学生でない場合はDreamSparkが利用できません。

【補足】 正式認可を受けた教育機関の課程 (高等学校・専門学校・専修学校・高等専門学校・大学) となっているので小学生・中学生が対象外です。職業訓練校もかな?
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

たいちう
記事: 418
登録日時: 9年前

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#30

投稿記事 by たいちう » 5年前

色々アドバイスが寄せられていますが、質問者の当初の目的に合っているか疑問に思いましたので、
少し別の視点から書いてみます。ゲーム作成は未経験なので、プログラミングの一般論からです。


[まず前提の確認]

色んな実装方法があって、効率面でも一長一短あるでしょうけど、
やっくるさんの「遅い」という症状は、そんなレベルではないですよね?
何百行のメソッドがあろうが、1つのクラスで管理しようとしていようが、
そのこと自体が直接引き起こす性能上の問題は微々たるものだと思います。


[考えられる原因]

現状で遅くなっている原因については、ざっくりと2通り考えられます。
1つは、何らかの単純な不具合が直接の原因であり、プログラムが煩雑であるため、
やっくるさんがまだ原因を見つけることができていないという可能性。
この場合、原因を特定できさえすれば、今のプログラムからほとんど変えずに、
劇的に改善されるかもしれません。

もう1つは、プログラムの構造自体が遅い原因であるという可能性。
この場合は、改善は比較的手間がかかります。


[解決への方針(1)]

可能ならば、プログラム全体をアップロードしてみてはいかがでしょう。
実際に症状が発生しているプログラムが手元にあれば、
デバッグしてくれる上級者もいると思います。
逆に、プログラムの断片と、やっくるさんの説明のみでは、上級者でも原因特定は困難です。
(softyaさんが書いていますね。)

掲示板というものの性質上、望む回答が得られない可能性は理解しておいてください。
(期日までに絶対解決が必要なら、信頼できる友人や有償のサービスを探してください。)

アップロードする場合は、中間ファイルを削除したり圧縮したり、
ファイルサイズに気を付けましょう。方法がわからない場合は、質問すると良いでしょう。


[解決への方針(2)]

プログラムを公開できない場合、上級者がやってくれたかもしれないデバッグ作業を
やっくるさんが自分ですることになりますが、自分でデバッグできなかったからこそ、
ここで質問しているのだと思います。つまり、今のままでは不可能な方法です。

デバッグの一番の敵は、先入観だと思います。
自分を信用するのではなく、信頼できる手順を身に着けてください。
また、「よくわからないけど、こう変えたら治った」というのも禁物です。
学習の機会を逃しますし、より複雑になって再現することも多いです。

デバッグツールを使いこなせれば、速やかに鮮やかに原因特定することもできるかもしれませんが、
単純だけど強力な方法を紹介します。

『「不具合が発生する」という状態を保ったまま、プログラムをどんどん小さくする』です。
しっかりバックアップを取って、不具合と関係ない部分のプログラムを削除していきます。
例えば、BGMとか、2面以降とか、無関係と思われる部分を削除して、不具合が再現するか確認します。

不具合が再現したら、さらに別の部分を削ります。
再現しなくなったら、そのとき削除した部分を検討してみてください。
不具合の直接の原因かもしれないし、別の個所で発生していた不具合が表面化するきっかけだったりします。
少し考えてわからなければ一旦諦めて、プログラムの別の部分を削りましょう。

この過程で不具合の原因が判明することも多いし、
ある程度小さくなった段階で、公開可能になるかもしれません。


[解決への方針(3)]

「頭痛がするから痛み止めを飲む」のではなく、「規則正しい生活をする」という方法です。
ご自分でも自覚があるようですが、今のプログラムはあまり良くありません。
優秀な初心者が陥りやすい、といったら慰めになるでしょうか。
もっと楽をする方向に精進してください。
この方針を選ぶ場合に限り、色々ある実装方法のどれを選ぶか、
どの順番に改善していくかを考える必要があります。

最初に書いたとおり、これが不具合の直接の原因ではないと思いますが、
より良い、より管理のしやすいプログラムに書き換えることで、
直接の原因に気づいたり、不具合が自ずと解決したりすることも多いです。

ただし、深刻な頭痛だったら生活習慣を改める前に医者に行くように、
プログラムについても、明日提出(納品)とかいう場合には、悠長なことは言っていられません。



解決への方針を3案書きました。その他の案や複合的な案もあると思いますが、
行き詰った時などに、どの方法がベストか考えてみてはどうでしょうか。

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#31

投稿記事 by やっくる » 5年前

ソフト屋さん、突き止めました!!!!!
softya(ソフト屋) さんが書きました:アルゴリズムの二分探索をご存知ですか?
はい、知っています。真ん中で区切って探索し
的を絞って行く方法ですよね?
ただ、プログラムの前半後半とかよくわからないので
手間でもわかりやすい関数単位で調べるという方が
自分向きだと思いましたので時間食いましたが調べていました。

とりあえず、
TimeStart = GetNowCount(); // 現在時刻
〜ここに計測したい処理〜
TimeEnd = GetNowCount(); // 現在時刻
といった形で関数をはさんで、
TimeSpan = TimeEnd - TimeStart;
といった感じです。
(それぞれdouble型で宣言しております)

で、結果ですが、時間がかかっている処理をつきとめました。
それは、オブジェクト同士の衝突判定を行う処理(関数)でした。

ここから、重要な質問なのですが、
自分はHitCheckというクラスをシングルトンで生成しており
そのHitCheckクラスにて
オブジェクトlist内のオブジェクト同士の衝突判定をする
関数(名をhitcaseとします)を作っています。

ややこしいのですが、そのhitcase関数の中で扱っている
オブジェクトlistは、オブジェクトを管理させているObjectMgrクラスが持っています。
ObjectMgrクラスもシングルトンで作っているのですが
(そこではオブジェクト用のlistを作り、そこに作ったオブジェクトを入れたり、
中のオブジェクトを削除したり、オブジェクトの更新をさせたりしています)
そのhitcase関数をステージの更新関数内にて実行させているときに
オブジェクトが増えると遅延する様です。

しかし、ここで疑問があります。
オブジェクトlistのサイズがただ増加すれば
重くなるわけでありません。
処理時間計測で調べたのですが、
ObjectMgrクラスの更新関数や、描画関数でも
オブジェクトlist内の更新や描画処理をさせているので
イテレータを作ってまわしておりますが、
オブジェクトが増えたところで、
その処理速度はまったく変わりません。

目に見えて時間がかかるようになるのは
オブジェクトリストを扱っているhitcase関数だけなのです。
そしてここでも疑問があるのですが
そのhitcase関数内の衝突処理を空っぽに、つまりは↓

コード:

//HitCheckクラスの、オブジェクト同士の衝突判定をする関数
void HitCheck::hitcase(){

	list<sObject*>::iterator it, it2;//ふたつのイテレータ宣言

	for(it = ObjectMgr::getInstance()->objectlist.begin(); it !=ObjectMgr::getInstance()->objectlist.end(); ++it)
	{
			for(it2 = ObjectMgr::getInstance()->objectlist.begin(); it2 !=ObjectMgr::getInstance()->objectlist.end(); ++it2)
		{

     本来ここでオブジェクトを抽出して、
     ダラダラと衝突確認をしているわけですが、
     すべてを削除して、ここにはなにも無い、空っぽの状態にしています。

		}
	}
}
こういう状態にしても遅延が起きるのです。
つまり、これは
衝突処理の内容では無いということですよね?

そこでさらに、イテレータ宣言や、多重for文を削除して
本当に空っぽにすると↓

コード:

void HitCheck::hitcase(){
 なにも無し
}
遅延がまったく無くなるのです。。。

これは、どういうことでしょう???

オブジェクトlistに入ってる数が問題ならば
衝突判定処理だけで無く、オブジェクトlist更新処理なども
増加に比例し、時間がかかってくるはずです。
しかし衝突判定関数以外はすべて正常時間です。

また、衝突判定処理の内容が問題で
オブジェクトが増加とともに遅延が発生するなら
そのhitcase関数の中でイテレータ宣言と多重for文を残していようが
その中の衝突処理を削除すれば遅延は起きないはずです。
なのにオブジェクトが増えると遅延が起きます。

hitcase関数をコメントアウトするか、
hitcase関数内の全処理をコメントアウトすると
遅延はまったく発生しません。

つまり、犯人はこの部分です。
↓これはそんなに遅延を誘発するような処理なのでしょうか???

コード:

	for(it = ObjectMgr::getInstance()->objectlist.begin(); it !=ObjectMgr::getInstance()->objectlist.end(); ++it)
	{
			for(it2 = ObjectMgr::getInstance()->objectlist.begin(); it2 !=ObjectMgr::getInstance()->objectlist.end(); ++it2)
		{


                }
       }

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#32

投稿記事 by やっくる » 5年前

ISLe() さんが書きました: 複数の種類の敵をどうやって互いに影響し合うことなく実装するか、
については簡単なサンプルコードを書いてみようと思います。
気長にお待ちください。ソースコードのほうも。
ありがとうございます。
その辺を熟練者の方ならどのようにされるのか
非常に気になりますし、学びたいです。
自分の中では自分の様な形しか無いはずだと思っていますので
楽しみです。待ってますのでぜひお願い致します。
ISLe() さんが書きました: ひとつのリストに放り込んで回すのはふつうだと思いますよ。
ありがとうございます。
誰も答えて下さらなかったので助かります。
自分のこの部分の方法は間違っていないのですね。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#33

投稿記事 by softya(ソフト屋) » 5年前

2重forの内側で、大体何回ぐらいループしてますか?
list要素数次第では恐ろしい回数回りそうです。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#34

投稿記事 by やっくる » 5年前

softya(ソフト屋) さんが書きました:2重forの内側で、大体何回ぐらいループしてますか?
list要素数次第では恐ろしい回数回りそうです。
「2重forの内側で大体何回ぐらいループしているか」
すみません、これはどう調べればいいでしょうか?
ちなみにイテレーターの初期化はこのfor文の頭でしか行っておりません。

コード:

 for(it = ObjectMgr::getInstance()->objectlist.begin(); it !=ObjectMgr::getInstance()->objectlist.end(); ++it)
    {
            for(it2 = ObjectMgr::getInstance()->objectlist.begin(); it2 !=ObjectMgr::getInstance()->objectlist.end(); ++it2)
        {
    LoopCount++;   ←こんな感じで設置すればいいですか?

 
        }
    }
あと、オブジェクトlistに入っているオブジェクトを
2重for文を使って、比較対象を二つ抽出し、
衝突判定させるって普通は使わない方法なんでしょうか?
すごく驚かれた感じですが、、、

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#35

投稿記事 by softya(ソフト屋) » 5年前

> LoopCount++;   ←こんな感じで設置すればいいですか?

そんな感じですが、大体の数も予想出来ていませんか?
例えばリストに100個あれば100x100で10000回まわるわけですよね。

>衝突判定させるって普通は使わない方法なんでしょうか?
>すごく驚かれた感じですが、、、

やらないことは無いですよ。
ただ、恐ろしい回数行きそうなら敵と自分はリストを分けるなど工夫します。
ObjectMgr::getInstance()も毎回やらないて良いかなとは思います。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#36

投稿記事 by やっくる » 5年前

softya(ソフト屋) さんが書きました: そんな感じですが、大体の数も予想出来ていませんか?
調べました。
list内に約60以上、オブジェクトが入ると遅延して来ます。
調査ということでわざと100以上オブジェクトを突っ込んでみましたが
list内のオブジェクト数に比例してどんどん遅延します。

loop数ですが、これがまたすごいスピードで増加しまして、
これが処理として少ないのか多いのかわかりませんが
その遅延状態にはloop数が2,3秒もしないうちに
1000000以上も増加するレベルになってますね。
いや、そうでなくても普段から
loopの部分は相当回ってました。
1フレームに二重for文全部回るわけですから。
これが、二重for文の弊害なんですね。

完全に、今回の遅延は
これが原因の様です。

がしかし、ISLe()さんも言った通り
ひとつのlistにすべてのオブジェクトをまとめて
衝突判定させてやる方法は
ゲームプログラムにおいて
別に特殊でもなんでも無いと思うんです。
そして、ひとつのlistで管理するならば
抽出させる時に二重for文を利用するのは
避けられないはずです。

結局、今回の問題は
オブジェクトクラスの更新の量の問題でも無く、
自分のプログラム文のミスでも無く、
60以上ものオブジェクトを画面内に出現させる様なゲームで、
すべてのオブジェクトをひとつのlistに入れる方法を取った事が問題、
というのはちょっと現代の言語処理速度にガッカリな感じです。。
それは仕方ないとして、これも疑問なのですが
ソフト屋さんのアドバイス通り、弾や敵用のlistを別に増やし、
objectlistAと、objectlistBとでオブジェクトをわけて放り込んだとしても
結局は、こんな感じになりませんか?
大きく見れば、これも二重for文の様な気がするのですが
これで、解消されるのでしょうか?

コード:

 for(it = ObjectMgr::getInstance()->objectlistA.begin(); it !=ObjectMgr::getInstance()->objectlistA.end(); ++it)
    {
   if((*it)->ID==プレイヤー){   //プレイヤーを抽出
            
                 for(it2 = ObjectMgr::getInstance()->objectlistB.begin(); it2 !=ObjectMgr::getInstance()->objectlistB.end(); ++it2)
                 { 
       敵や敵の弾を抽出し、衝突判定
       〜割愛〜 
                 }
        }
    }
softya(ソフト屋) さんが書きました: ObjectMgr::getInstance()も毎回やらないて良いかなとは思います
待って下さい、シングルトンしたクラスの変数や関数を
他クラスで利用する場合
"ObjectMgr::getInstance()->"は必須記載事項じゃ無いんですか???

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#37

投稿記事 by やっくる » 5年前

補足します。

>list内に約60以上、オブジェクトが入ると遅延して来ます

厳密にはトピの質問内容通り、約30以上から遅延は始まっており、
60以上では完全にゲームプレイに支障をきたしているレベルの
遅延となっています。

ISLe()

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#38

投稿記事 by ISLe() » 5年前

やっくる さんが書きました:がしかし、ISLe()さんも言った通り
ひとつのlistにすべてのオブジェクトをまとめて
衝突判定させてやる方法は
ゲームプログラムにおいて
別に特殊でもなんでも無いと思うんです。
そして、ひとつのlistで管理するならば
抽出させる時に二重for文を利用するのは
避けられないはずです。
わたしの場合、衝突判定は更新処理の中にあって、衝突判定のための独立したループはありません。
衝突判定の相手も、そもそもする必要のない無駄な判定などを省きます。
総当りなんて無茶は最初から除外しますね。
コストの増加が常に線形になるように考えます。
リストそのものを分けなくても、グループ化のオブジェクトを作ればメインのリストはひとつのままで拡張できます。

No.20の赤字で書かれた部分は、わたしには
ひとつのリストで更新処理を一回だけ回す
という意味に見えました。
ことあるごとに何回もループを回すという発想はありませんでした。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#39

投稿記事 by softya(ソフト屋) » 5年前

>1000000以上も増加するレベルになってますね。

100要素だと3重ループ以上している計算になりますね。2重ループだと数学的に計算がおかしいのでコードの流れの見直しが必要かと思います。
100要素なら2重ループで100の2乗なので1万のはずです。

>すべてのオブジェクトをひとつのlistに入れる方法を取った事が問題、
>というのはちょっと現代の言語処理速度にガッカリな感じです。。
ちなみに速度にがっかりされるのは、Releaseビルドされてからのほうが良いと思います。こういうパターンは劇的に早くなるでしょう。
まぁ、パワーに任せて解決しても根本的にアルゴリズムに問題があると思うのでDebugビルドでも問題なく動くようにするのが鉄則です。
Releaseビルドに頼るのは最後の手段ですからね。
8bit時代なら、こんな事したら数秒に1コマも動かない激遅ゲームだと思いますよ。
コードを見直すことが必要です。

>待って下さい、シングルトンしたクラスの変数や関数を
>他クラスで利用する場合
>"ObjectMgr::getInstance()->"は必須記載事項じゃ無いんですか???

動作中にインスタンスが変化するはず無いので、forループ2つでgetInstance()が4回も出てくる必要性は無いんじゃないですか?
それとも動的に変化するのですか?
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#40

投稿記事 by やっくる » 5年前

ISLe() さんが書きました: No.20の赤字で書かれた部分は、わたしには
ひとつのリストで更新処理を一回だけ回すという意味に見えました。
ことあるごとに何回もループを回すという発想はありませんでした。
ことあるごと、の指している意味がわかりませんが、
二重for文にしても、当たり判定なのですから
無駄なループをさせているわけではないはずです。
そのオブジェクトが、
list内のすべてのオブジェクトと今衝突しているか判断する

それを毎フレーム行っているだけです。
それは縦横無尽にドット単位で動くシューティングにおいて
必ず行い続けなければならない処理のはずですよね?
ISLe() さんが書きました: 衝突判定の相手も、そもそもする必要のない無駄な判定などを省きます。
自分も抽出している時に、相手に条件をつけているので
それで違う対象は弾くわけですが、
それがISLe()さんのいう「する必要のない相手を省く」
ということにはならないのでしょうか、意味違っていますか?
同じlistにいる相手から条件に合う合わないと調べて抽出すること自体
「する必要のない無駄な判定」なのでしょうか?
listの中を対象だけに絞っていれてれば、そんな判定すらいりませんが
すべてのオブジェクトをいれたlistを扱う方法では、それでは無理な話なはずです。
ISLe() さんが書きました: ひとつのリストで更新処理を一回だけ回す
更新処理は当然として、衝突判定も
自分は、毎フレーム1回だけ回しています。
それが、二重for文だから、量が多い?というだけで
すべての要素を毎回通らせるのは、無駄な処理では無いと思うのですが
自分は思い違いをしているのでしょうか?
ISLe() さんが書きました: 衝突判定は更新処理の中にあって、衝突判定のための独立したループはありません。
listにあるオブジェクトたちの更新関数の中で衝突判定を???
それとも、ObjectMgrの更新の中ででしょうか?いや、
でもそれなら、HitCheckの更新でやろうと一緒ですよね、、
どいうことでしょうか。
別のところの更新ループを利用して
衝突相手のlistをまわすなんて、どういう仕組みなんでしょうか?

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#41

投稿記事 by やっくる » 5年前

softya(ソフト屋) さんが書きました: 100要素だと3重ループ以上している計算になりますね
2重ループだと数学的に計算がおかしいので
いや、すみません、すごいスピードでloop回数が増加するので
自分が計算苦手なのでアバウトに話しただけです。
3重ループはしてないでしょう、
for文は2カ所しかないのですし。
イテレータの初期化も最初だけですから、
毎回list内の要素をすべて通るだけですよね?
softya(ソフト屋) さんが書きました: ちなみに速度にがっかりされるのは、Releaseビルドされてからのほうが良いと思います。
こういうパターンは劇的に早くなるでしょう。
そうなんですか?
それだとありがたいです。。
softya(ソフト屋) さんが書きました: Releaseビルドに頼るのは最後の手段ですからね。
ですよね。
softya(ソフト屋) さんが書きました: 動作中にインスタンスが変化するはず無いので、
forループ2つでgetInstance()が4回も出てくる必要性は無いんじゃないですか?
それとも動的に変化するのですか?
えっと、ふたつのイテレータを宣言し、
for文の条件式にて、objectMgrのlistの
各イテレータの位置をlistの頭にセット(初期化)するのと
listの最後(終わり)の判断させるとで、それがイテレータ二つ分ということで
その4回はどうしようもないと思うんですが、、、、?

コード:

for(it = ObjectMgr::getInstance()->objectlist.begin(); it !=ObjectMgr::getInstance()->objectlist.end(); ++it)
    {
            for(it2 = ObjectMgr::getInstance()->objectlist.begin(); it2 !=ObjectMgr::getInstance()->objectlist.end(); ++it2)
        {

この四つの箇所は絶対必要じゃないですか?
わからなくてすみません。

うずら

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#42

投稿記事 by うずら » 5年前

判定をやっている最中終わり位置のイテレーターが変化しないなら(判定のチェック中にlistに項目の追加がない)
終了位置のイテレーターは最初に取得すれば使いまわせるということではないでしょうか。

僕もそこの改善でかなりループを軽くできたことがあります。

こんな感じに。(書きなれてるvectorで書いちゃいましたがlistでも同じように書けば大丈夫でしょう)
第一式のunsigned intのところをlist<type>::iteratorに書き換えれば。

コード:

vector<char> Array;

for (unsigned int i = 0, n = Array.size(); i < n; i++)
{
    // 処理
}

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#43

投稿記事 by softya(ソフト屋) » 5年前

そんな難し話じゃないですよ。

コード:

ループ中イテレータが変化する場合

list<sObject*>&loopObjectlist = ObjectMgr::getInstance()->objectlist;	
for(it = loopObjectlist.begin(); it !=loopObjectlist.end(); ++it) {
            for(it2 = loopObjectlist.begin(); it2 !=loopObjectlist.end(); ++it2) {

イテレータがループ中は固定の場合
list<sObject*>::iterator it_st, it_end;
it_st = ObjectMgr::getInstance()->objectlist.begin();
it_end = ObjectMgr::getInstance()->objectlist.end();
for( it=it_st ; it!=it_end ; ++it ) {
	for( it2=it_st ; it2!=it_end ; ++it2 ) {
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#44

投稿記事 by softya(ソフト屋) » 5年前

>いや、すみません、すごいスピードでloop回数が増加するので
>自分が計算苦手なのでアバウトに話しただけです。
>3重ループはしてないでしょう、
>for文は2カ所しかないのですし。

プログラムで確認できるのでループ数と付きあわせてみてください。
1フレーム辺りでリスト数xリストの数と等しくなるはずです。
こういう所は手を抜かないでくださいね。
※ 自分を信じないようにしましょう。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
usao
記事: 1565
登録日時: 6年前

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#45

投稿記事 by usao » 5年前

オフトピック
衝突判定って双方向
(A,B という二つのオブジェクトがあったら
A→B
B→A
みたく2回やる)
なんですか?
(速度がやたら遅いという話とは直接関係ない話です)

ISLe()

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#46

投稿記事 by ISLe() » 5年前

やっくる さんが書きました:ことあるごと、の指している意味がわかりませんが、
二重for文にしても、当たり判定なのですから
無駄なループをさせているわけではないはずです。
そのオブジェクトが、
list内のすべてのオブジェクトと今衝突しているか判断する

それを毎フレーム行っているだけです。
それは縦横無尽にドット単位で動くシューティングにおいて
必ず行い続けなければならない処理のはずですよね?
キャラの移動処理などを含む更新処理のループが別にあるのではないのですか?
そうであれば、わたしの感覚として、ことあるごとにループを組んでいるということになるのですが。

当たり判定には、『当たっているかどうか』『当たっていたらどうするか』の2段階あります。
『当たっているかどうか』を総当りで求めるのは無駄でしかありません。
「当たり判定 高速化」「当たり判定 軽量化」などでネットを検索してみてください。
空間分割などさまざまな手法が見付かるはずです。
メジャーなテーマなのです。

やっくる さんが書きました:同じlistにいる相手から条件に合う合わないと調べて抽出すること自体
「する必要のない無駄な判定」なのでしょうか?
listの中を対象だけに絞っていれてれば、そんな判定すらいりませんが
すべてのオブジェクトをいれたlistを扱う方法では、それでは無理な話なはずです。
既にいくつもヒントを出しました。

タイルマップは同じ大きさのマスが格子状に並んでいるので、どのマスと重なっているかは簡単な計算で求められます。
重なっているマスにあるオブジェクトとだけ当たり判定をすれば良いのです。

例えば、敵の弾グループというオブジェクトを用意します。
ひとつのリストに登録するということは共通のインターフェースを持っているはずですよね。
敵の弾は、敵の弾グループにリスト登録します。
敵の弾グループをメインのリストに登録して、敵の弾グループは自分に登録された敵の弾に処理を委譲します。
敵の弾同士は当たらないとすれば、敵の弾グループで弾くことができ、同様にグループ化することでループ回数を減らせます。

空間分割手法などを使う場合も同様です。
『誰と』当たっているかを求める方法はいくらでも工夫の余地があります。
工夫の余地が入るようにコーディングすべきだと思います。

やっくる さんが書きました:更新処理は当然として、衝突判定も
自分は、毎フレーム1回だけ回しています。
それが、二重for文だから、量が多い?というだけで
すべての要素を毎回通らせるのは、無駄な処理では無いと思うのですが
自分は思い違いをしているのでしょうか?
更新処理で回し、衝突判定で回しているわけですよね。
それぞれ1回ずつ。
わたしの感覚ではそれは無駄です。

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#47

投稿記事 by やっくる » 5年前

ソフト屋さん
listをわけて、衝突判定処理をNo: 36の方法に変えたところ
遅延がまったく起きなくなりました。問題自体は解決しました。
結局、総当たりさせる自分の衝突判定方法と
ひとつのlistでオブジェクトを管理するということの
相性の悪さで起きた遅延だったということですね。
勉強になりました。

usaoさん
いいえ、違います。
そういうことではありません。


ISLe()さん
ISLe() さんが書きました: キャラの移動処理などを含む更新処理のループが別にあるのではないのですか?
はい、別にあります。
オブジェクトlist管理のクラスにて、list内の更新をしているのです。
そこでついでにオブジェクト同士の衝突判定もしろ、ということでしょうか?
確かに、自分は癖というか、衝突判定するクラスを作るやり方を最初に覚えたので
なんの疑問も無く、すべての製作ゲームで衝突判定はクラスで作ってます。
そのメリットもあるわけでもありません、
衝突判定もlisit内の更新をするループを利用してやった方がいいですよね、、、
考えてみます。
ISLe() さんが書きました: 当たり判定には、『当たっているかどうか』『当たっていたらどうするか』の2段階あります。
『当たっているかどうか』を総当りで求めるのは無駄でしかありません。
空間分割は初めて聞きました。
こことか読んでみましたが、頭が爆発しそうです。
http://marupeke296.com/COL_2D_No8_QuadTree.html
ゲーム制作を続けて行く上で必要な知識だというのは
理解出来ますので勉強してみます。
ISLe() さんが書きました: タイルマップは同じ大きさのマスが格子状に並んでいるので、
どのマスと重なっているかは簡単な計算で求められます。
重なっているマスにあるオブジェクトとだけ当たり判定をすれば良いのです。
ああ、タイルマップで衝突の判断をするんですね。
衝突判定処理は、オブジェクトの座標はしったこっちゃ無くて
オブジェクトがいるマスを認識していればいいと。
イメージは見えましたが、碁盤感覚でシュミレーションゲームのイメージです。
それをアクションゲームやシューティングにも利用するイメージを
持つようにならなければならないのですね。
意識していこうと思います。

あと、No: 27の件をお待ちしております。
ISLe() さんが書きました: Enemyクラスがインターフェースとして働くことになるのでそれに関する部分は自ずと共通化されることになります。
各キャラクタの固有部分が分離されて、オブジェクト別に状況に応じた柔軟な設計が可能になります。
自分の中ではENEMYクラス内でswitch文で分けて処理する方法以外考えられないので
ソレ以外の方法?でどうされてくるのか楽しみです。

アバター
usao
記事: 1565
登録日時: 6年前

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#48

投稿記事 by usao » 5年前

何やらコードを修正したら現象が収まったとのことですが,
もともとの void HitCheck::hitcase() で,
listの要素数がNのとき,ループの中身は結局何回走ってたんですか? というのが読み取れないのですが…

本当に「1本のリストだったから」が原因だったのでしょうか?
まぁ,何かいじってるうちに解消して結果オーライと言う話も有りなのかもしれませんが…
オフトピック
単純比較はできないでしょうが,こんなのをdebugビルドで動かしても1秒もかからないわけで.

コード:

int Loop( int N )
{
	int counter = 0;
	for( int i=0; i<N; i++ )
	{
		for( int j=0; j<N; j++ )
		{
			++counter;
		}
	}
	return counter;
}

//
int main( int argc, char **argv )
{
	const int N = 1000;

	for( int i=0; i<60; i++ )
	{
		std::cout << i << ", " << Loop( N ) << std::endl;
	}

	std::cin.ignore();
	return 0;
}

アバター
usao
記事: 1565
登録日時: 6年前

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#49

投稿記事 by usao » 5年前

オフトピック
>usaoさん
>いいえ、違います。
>そういうことではありません。

そういうこと になっているように見えたのですが,
ループ内で何か判定して一方のパターンでしか処理しないようなことをしていたのでしょうか?

まぁ,言いたかったことは 単純に,

コード:

for( int i=0; i<N; i++ )
{
  for( int j=0; j<N; j++ )
  {
    i<j のときのみ,{i,j}の組み合わせについて処理
  }
}
とか書くのであれば,こう↓すればいいんじゃ? ってだけのことだったのですけど.

コード:

for( int i=0; i<N-1; i++ )
{
  for( int j=i+1; j<N; j++ )
  {
    {i,j}の組み合わせについて処理
  }
}

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#50

投稿記事 by やっくる » 5年前

usaoさん
自分は最初、あなたのいう↓の処理方法でやっていました。
ですが、それで遅延が起きていたんですよ。

コード:

for( int i=0; i<N-1; i++ )
  for( int j=i+1; j<N; j++ )
  {
    {i,j}の組み合わせについて処理
  }
}
厳密にはこう↓ですが、回し方は一緒ですよね?

コード:

for(it = ObjectMgr::getInstance()->objectlist.begin(); it !=ObjectMgr::getInstance()->objectlist.end(); ++it)
  for(it2 = ObjectMgr::getInstance()->objectlist.begin(); it2 !=ObjectMgr::getInstance()->objectlist.end(); ++it2)
  {
    {it,it2}の組み合わせについて処理
  }
}
それで遅延が起きていたんです。

itをメインとして、it2で対象を探すわけですが
it==A、it2==Bの衝突判定をしている場合は
もちろん重複するようなit==B、it2==Aの衝突判定はしていません。
Aに関係無いit==B、it2==Cはしますけど。

アバター
usao
記事: 1565
登録日時: 6年前

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#51

投稿記事 by usao » 5年前

offtopicな「2重ループの書き方」は,まぁわりとどうでもいいのですが,
【本題の側(本題だった側)の 遅延が起きていた理由 が,明確にされなかった】ように見受けるのですが,
その点はどうなんでしょうか?

No.31の書き込みによれば,「ループ内で何もしなくても遅延が起きていた」とのことでしたから,遅延の原因としては

(1)そこに書かれている
 ・ObjectMgr::getInstance()
 ・ObjectMgr::getInstance()->objectlist.begin()
 ・ObjectMgr::getInstance()->objectlist.end()
 のいずれかが問題を起こしていた

(2)この 特別何も仕事をしない2重ループ が単にとんでもない回数回っていて,結果として遅かっただけ

くらいが推測されるわけです.

で,後者側の回数を問われていたわけですが……
仮にこれが 「リスト要素が100個なら,100x100回ですよね?」 という問いかけ通りの回数であった のならば,
原因は(1)側にある可能性もあったわけで,
「リストが一本だった」ことが遅延の直接の原因ではなかった可能性もあるのでは…? と思うのです.


#…といった感じで,私には 何で遅延してたのか? が未だ明確でないように見えるのですが……
 そんなことはなくて,この点は既にしっかりと解明済み ということなのであれば,スルーしてください.

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#52

投稿記事 by softya(ソフト屋) » 5年前

私も2重ループが遅い原因でした!って片付けるのは何かが違うと言うかゴーストがささやくと言うか、もう少し具体的な数字などの情報がほしいです。
このトピック見ている人のためにも必要な情報だと思います。

2重ループの場合、NxNでM万回ループしていて処理時間はQmsでした。 → これはループだけの処理時間です、
ループ改善後は、M2回ループしていて処理時間はQ2msでした。
って感じで情報を出せませんでしょうか。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
usao
記事: 1565
登録日時: 6年前

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#53

投稿記事 by usao » 5年前

まぁ,元のソースがもうありません とかいう状況だったりすると今さら再検証とか無理なのかもしれませんが.

一応,補足しておきますと,

>loop数ですが、これがまたすごいスピードで増加しまして、
>これが処理として少ないのか多いのかわかりませんが
>その遅延状態にはloop数が2,3秒もしないうちに
>1000000以上も増加するレベルになってますね。

これが

「HitCheck::hitcase() という関数を【1回呼ぶと】,
 時間にして 2,3 秒ほどを要し, ループ回数は1000000以上」

ということだったのか,それとも

「プログラムを 2,3 秒 動かすと,(HitCheck::hitcase()は,その間に何回も呼ばれて)
 HitCheck::hitcase()内のループ回数の 総数 が1000000以上」

とかいうことだったのか がはっきりしない感じの書き方になってしまっているのですよね.
後者みたいな事柄であったならば,調査方法が的外れであったことになりますし,
前者であったならば,プログラムにバグがあったのだと思います.
(例えば,別スレッドが悪さしてるとか,ObjectMgr::getInstance()の実装がバグってるとか…?)
オフトピック
例えば,VGAサイズ画像をデータとして扱ったりすると
640x480回の2重ループが必要な全画素走査処理をN種類適用してどうの…とかを30fpsとかで普通にやるわけで,
100x100の空ループ1個書いたら遅延がやばい とかいう話は信じられないんですよね.

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#54

投稿記事 by やっくる » 5年前

usaoさん、ソフト屋さん

まず、
usao さんが書きました:(1)そこに書かれている
 ・ObjectMgr::getInstance()
 ・ObjectMgr::getInstance()->objectlist.begin()
 ・ObjectMgr::getInstance()->objectlist.end()
 のいずれかが問題を起こしていた
についてですが、
ソフト屋さんがNo.43で教えてくださった、

コード:

list<sObject*>&loopObjectlist = ObjectMgr::getInstance()->objectlist;   
for(it = loopObjectlist.begin(); it !=loopObjectlist.end(); ++it) {
            for(it2 = loopObjectlist.begin(); it2 !=loopObjectlist.end(); ++it2) {
を真っ先に導入したのですが、遅延状態は変わりませんでしたので
これはObjectMgr::getInstance()の書き方の問題では無いのだなと思いました。
(が、一応No.39でのソフト屋さんの「4回も出てくる必要性は無い」との言葉を重んじて
遅延状態の改善は見られなかったのですが、教えてもらったこの方法を使ってfor文は書いています)

で、僕が発言しました
>loop数ですが、これがまたすごいスピードで増加しまして、
>これが処理として少ないのか多いのかわかりませんが
>その遅延状態にはloop数が2,3秒もしないうちに
>1000000以上も増加するレベルになってますね。
についてですが、
ループ数を計るカウントはusaoさんのソースでいうところの

コード:

for( int i=0; i<N-1; i++ )
  for( int j=i+1; j<N; j++ )
  {
  LoopCount++; ← ここが通る数を計った
     {i,j}の組み合わせについて処理
  }
}
の場所です。
ですので、調べたのはHitCheck::hitcase()が呼ばれた数ではありません。
二重for文でjを通った数(list内のソレが対象かどうか探した回数)です。
で、その増え方ですが、総当たりなので
list内のオブジェクト(要素数)が増えれば、当然通る数も増加しますが
その増加の量が1要素数に対し、なぜか尋常ではない量でした。
list内の要素数が少ないと遅延は起きません。でもlistに沢山入ると遅延するのです。

ちなみに、↓のような感じで書いてました

コード:

 for(it = ObjectMgr::getInstance()->objectlist.begin(); it !=ObjectMgr::getInstance()->objectlist.end(); ++it)
    {
            for(it2 = ObjectMgr::getInstance()->objectlist.begin(); it2 !=ObjectMgr::getInstance()->objectlist.end(); ++it2)
        {
    if((*it)->ID==プレイヤー ){//判定メイン側をitで抽出
      if((*it2)->ID==敵){//判定対象側をit2で抽出
      衝突判定
      }
     if((*it2)->ID==敵の弾){//判定対象
      衝突判定
               }
     if((*it2)->ID==アイテム){//判定対象
      衝突判定
     }
    }
   //次の判定メイン側
    if((*it)->ID==プレイヤーの弾 ){
      if(割愛==敵){
      衝突判定
      }
     if(割愛==敵の弾){
      衝突判定
               }
     if(割愛==アイテム){
      衝突判定
     }
    }
   //次の判定メイン側
    if((*it)->ID==オプション ){
      if(割愛==敵){
      衝突判定
      }
     if(割愛==敵の弾){
      衝突判定
               }
     if(割愛==アイテム){
      衝突判定
     }
    }
   //次の判定メイン側
    if((*it)->ID==オプションの弾 ){
      if(割愛==敵){
      衝突判定
      }
     if(割愛==敵の弾){
      衝突判定
               }
     if(割愛==アイテム){
      衝突判定
     }
    }
   //以下、同じ様に衝突判定させたいものを並べて行く(衝突のケースは重複しない様に)

        }
    }
これを、listを二つ(objectlistA,objectlistB)にわけ、
衝突判定処理の部分で、player側のオブジェクトはobjectlistAに、
enemy側のオブジェクトはobjectlistBに入れるようにして、

コード:

 for(it = ObjectMgr::getInstance()->objectlistA.begin(); it !=ObjectMgr::getInstance()->objectlistA.end(); ++it)
    {
   if((*it)->ID==プレイヤー){   //listAからプレイヤーを抽出
            
                 for(it2 = ObjectMgr::getInstance()->objectlistB.begin(); it2 !=ObjectMgr::getInstance()->objectlistB.end(); ++it2)
                 { 
      if((*it2)->ID==敵){//listBから判定対象を探す
       衝突判定
       }
      if((*it2)->ID==敵の弾){//listBから判定対象を探す
       衝突判定
                }
      if((*it2)->ID==アイテム){//listBから判定対象を探す
       衝突判定
      }
                 }

   if((*it)->ID==プレイヤーの弾){   //プレイヤーの弾を抽出
            
                 for(it2 = ObjectMgr::getInstance()->objectlistB.begin(); it2 !=ObjectMgr::getInstance()->objectlistB.end(); ++it2)
                 { 
      if((*it2)->ID==敵){
       衝突判定
       }
      if((*it2)->ID==敵の弾){
       衝突判定
                }
      if((*it2)->ID==アイテム){
       衝突判定
      }
                 }

   if((*it)->ID==オプション){   //オプションを抽出
            
                 for(it2 = ObjectMgr::getInstance()->objectlistB.begin(); it2 !=ObjectMgr::getInstance()->objectlistB.end(); ++it2)
                 { 
      if((*it2)->ID==敵){
       衝突判定
       }
      if((*it2)->ID==敵の弾){
       衝突判定
                }
      if((*it2)->ID==アイテム){
       衝突判定
      }
                 }

   if((*it)->ID==オプションの弾){   //オプションの弾を抽出
            
                 for(it2 = ObjectMgr::getInstance()->objectlistB.begin(); it2 !=ObjectMgr::getInstance()->objectlistB.end(); ++it2)
                 { 
      if((*it2)->ID==敵){
       衝突判定
       }
      if((*it2)->ID==敵の弾){
       衝突判定
                }
      if((*it2)->ID==アイテム){
       衝突判定
      }
                 }
        }
    }
としたら、まったく遅延が起きなくなりました。

listBにオブジェクトを200ほど入れても遅延は起きません。
なので、自分としてはこれで解決したと思ったのです。

確かに、数値として出して無いのはうやむや?っぽくて
アレですが、皆さんの言われる数値の出し方が自分はちょっとハッキリわかりません。
ループ数も凄いスピードで増え続けていますし、
時間を置く場所もわかりませんし、
どの状態でいつの数値をどう説明すればいいのか、ちょっとわからないのです。
それは自分がまだプログラムの初心者で、その辺のテクというか、
知識というか技術というか、それらが不足しているからでしょうね。
その辺の手ほどきをして頂ければ、正確に実行し、お伝えしたいと思いますが。。

usaoさん、とりあえず、
ソフト屋さんも回答で"2重for文のループ数は場合によっては脅威"だと言ってらっしゃいます
softya(ソフト屋) さんが書きました:2重forの内側で、大体何回ぐらいループしてますか?
list要素数次第では恐ろしい回数回りそうです。
実際、二重for分のjの場所に設置したカウンターは
毎フレーム凄まじい増加量が見て取れました。
数千のレベルではありません。数万、いや、数十万のレベルで増え続けてました。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#55

投稿記事 by softya(ソフト屋) » 5年前

>その増加の量が1要素数に対し、なぜか尋常ではない量でした。

やっくるさんの予想と、その数に大幅なズレが有るなら想定外の動作をしている証拠です。
今回一見直ったように見ますが、実はプログラムは思っても見なかった動作をしている可能性があります。
やっくるさんが1フレームに1回だけこ当たり判定の2重ループが動作すると説明してますが、そう思い込んでいるだけの可能性が高いです。

>アレですが、皆さんの言われる数値の出し方が自分はちょっとハッキリわかりません。
>ループ数も凄いスピードで増え続けていますし、
>時間を置く場所もわかりませんし、
>どの状態でいつの数値をどう説明すればいいのか、ちょっとわからないのです。
>それは自分がまだプログラムの初心者で、その辺のテクというか、
>知識というか技術というか、それらが不足しているからでしょうね。

プログラムの動きを書いたのはご自身ですから、1フレームに通る回数、1フレームにループするであろう回数、処理として許容できる時間を想定できるはずです。
想定できないなら、一度じっくり考えてみてください。
そしてそれを検証できる方法を考えます。検証する方法を考えるのはプログラムを組むこととなんら変わりがありません。
プログラムを考えれたなら、検証する方法も同様に考えられるはずです。

テストとデバッグは、プログラムの工程の大半を占める非常に大事な作業です。
これが出来ないとプログラムは完成しませんので身につけましょう。今がその時だと私は思います。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

ISLe()

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#56

投稿記事 by ISLe() » 5年前

やっくる さんが書きました:あと、No: 27の件をお待ちしております。
ISLe() さんが書きました: Enemyクラスがインターフェースとして働くことになるのでそれに関する部分は自ずと共通化されることになります。
各キャラクタの固有部分が分離されて、オブジェクト別に状況に応じた柔軟な設計が可能になります。
自分の中ではENEMYクラス内でswitch文で分けて処理する方法以外考えられないので
ソレ以外の方法?でどうされてくるのか楽しみです。
省メモリを意識したサンプルコードにしてみました。

コード:

#include "DxLib.h"
#include <list>
using std::list;

struct Object;
struct ObjectOp {
	virtual void Update(Object *o) = 0;
};
struct Object {
	int  x,  y;
	int vx, vy;
	ObjectOp *op;
	Object(int x, int y, ObjectOp *op) : x(x), y(y), vx(0), vy(0), op(op) {}
	virtual void Update();
	virtual void Render();
};
void Object::Update() {
	op->Update(this);
}
void Object::Render() {
	DrawCircle(x, y, 4, GetColor(255,255,255), TRUE);
}
struct BoundOp : public ObjectOp {
	virtual void Update(Object *o);
};
void BoundOp::Update(Object *o) {
	if (o->vx == 0) o->vx = GetRand(1) ? 1 : -1;
	o->x += o->vx;
	o->y += o->vy;
	if ((o->vx > 0 && o->x > 640-4) || (o->vx < 0 && o->x <= 0+4)) o->vx =-o->vx;
	if ((o->vy > 0 && o->y > 480-4) || (o->vy < 0 && o->y <= 0+4)) o->vy =-o->vy;
	else o->vy += 1;
}
struct ReflectOp : public ObjectOp {
	virtual void Update(Object *o);
};
void ReflectOp::Update(Object *o) {
	if (o->vx == 0) o->vx = GetRand(1) ? 1 : -1;
	if (o->vy == 0) o->vy = GetRand(1) ? 1 : -1;
	o->x += o->vx;
	o->y += o->vy;
	if ((o->vx > 0 && o->x > 640-4) || (o->vx < 0 && o->x <= 0+4)) o->vx =-o->vx;
	if ((o->vy > 0 && o->y > 480-4) || (o->vy < 0 && o->y <= 0+4)) o->vy =-o->vy;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	ChangeWindowMode(TRUE);
	if (DxLib_Init() != 0) return 0;
	SetDrawScreen(DX_SCREEN_BACK);

	list<Object*> l;
	BoundOp   bound_op;   // 跳躍オペレータ
	ReflectOp reflect_op; // 反射オペレータ
	for (int i=0; i<100; i++) {
		ObjectOp *op;
		if (GetRand(1) == 0) op = &bound_op; else op = &reflect_op; // オペレータ選択
		l.push_back(new Object(GetRand(640-40-1)+20, GetRand(480-40-1)+20, op));
	}

	while (ProcessMessage() == 0 && ScreenFlip() == 0 && ClearDrawScreen() == 0) {
		for (Object *o : l) { o->Update(); o->Render(); }
	}
	
	DxLib_End();
	return 0;
}
クラスのインターフェースとインプリメント(実装)の分離は、これも検索すれば山ほど記事が見付かると思います。
デザインパターンとしても有名ですしね。

このサンプルでは全部パブリックにしてしまってますが、
オブジェクトのメンバ周りやそのアクセスをどうするかといったあたりはいろんな方法があるので調べてみてください。

アバター
usao
記事: 1565
登録日時: 6年前

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#57

投稿記事 by usao » 5年前

何かがおかしい ときに,その原因はこうではないか? という仮説を立てたら,
その仮説を実証(あるいは棄却してよいことを実証)するためにはどのようなことを調べればよいか? という話ですね.
証拠集めですから,ぼんやりした調査ではなく,
「もし△△であるならば,○○という変数の値はこのタイミングでは××でなければならない」
といったように具体的でなければなりません.

例えば,↓のようなプログラムがおかしな動きをしていて,
「※」の箇所の処理が想定回数以上繰り返されている「のかもしれない」と怪しんだとき,
それを確かめるにはどうするか,ということですよね.

コード:

void F( int N )
{
  for( i=0; i<N; i++ )
  {
    //※
  }
}

//
int main()
{
  while( 1 )  //メインループ
  {
    ...
    F( 100 );
    ...
  }
  ...
}
ここでの想定回数とは, 100回/メインループ内処理1回 です.
(メインループ内の処理が1回行われるごとにF()が1回だけ呼ばれて,
 結果として「※」の箇所は,メインループ内の処理1回ごとに,N=100回だけ行われる)

「※」の箇所が想定以上に実行されるとしたら,考えられる原因は以下の2つです

(A)本当に メインループ内処理1回に対して,F()は1回しか呼ばれていないのだろうか?
 これが2回呼ばれていたら,想定回数の2倍の回数だけ「※」の箇所が実行されてしまう.
(B)関数F()の1回のコールに対して,本当に「※」の箇所はN=100回だけ処理されているのだろうか?
 何かが間違っていて,N+5回だとか,3*N回だとかになってたりしないのだろうか?

これらを確認するとしたら,例えば,以下のように確認のための表示などを入れてみれば良いわけです.
( (A)を調べるための記述には//(A), (B)を調べるための記述には//(B) と注釈を添えました )

コード:

void F( int N )
{
  printf( "F()に入った\n" );  //(A)F()が呼ばれたことがわかるように
  int counter = 0;  //(B)「※」の箇所の処理回数をカウントする用
  for( i=0; i<N; i++ )
  {
    //※
    ++counter;  //(B)
  }
  printf( "※の箇所は%d回走った\n", counter );  //(B) F()を1回コールすると何回「※」の箇所が走るのかを表示
}

//
int main()
{
  while( 1 )  //メインループ
  {
    printf( "メイン処理\n" );  //(A)メイン処理の各回の開始がわかるように.
    ...
    F( 100 );
    ...
  }
  ...
}
想定通りに動いていれば,
(A) 繰り返される「メイン処理」なる表示の間には,常に1回だけ「F()に入った」という表示がされるハズ
(B) そしてその際には 常に,「※の箇所は100回走った」と表示がされるハズ
ですよね.
(想定通りの流れで動いていれば,「※」の箇所が100回走るハズ ということを調査.
 これがそうなっていなければ,「※」の部分を走る仕組みが想定外の流れになっている.
 それがわかったら,次は,「じゃあどうして想定外になってるのか?そうなる原因として考えられるのは…」
 という感じで追及してく.)

もちろん,必要に応じて
・F()から抜ける箇所でも「F()から抜ける」とか表示する
・F()に入った箇所で,引数Nの値も表示する
等々,物事をはっきりさせるために必要だと思う物が他にもあれば追加します.
(とにかく,表示した内容を見ることで知りたいことが明確にならなければ意味がありませんので,
 足りないのは×. 十分すぎるほどに情報を吐かせるくらいでよいです.)

#例えば表示が流れるのが早すぎたりとかで(今回は逆に遅いという話ですが)
 プログラム動作中に表示を追うのがつらいとかいう時は fprintf()にしてファイルに吐くとか,
 あるいはリダイレクトしてprintf()の内容を適当にテキストファイルにでも吐かせるとか.

アバター
Tatu
記事: 445
登録日時: 9年前
住所: 北海道

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#58

投稿記事 by Tatu » 5年前

カウンターを0に戻す処理がなかったから
カウンターが増え続けたということはないですよね?

やっくる

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#59

投稿記事 by やっくる » 5年前

ISLe()さん
自分なりに努力したのですが
大変申し訳ないのです。
書かれているソースが私のレベルでは
まったくトレース出来ませんでした。

自分が勉強している(駆け出し用の)参考書でも
覗いているネットのサイトでも
見た事も無い様なプログラム文も多々ありますし、
なによりどこもかしこも書かれてあることが複雑すぎて、
結局ISLe()さんは自分と同じ状況に立たされたとき
なにをどうやってまとめるのか、
理解することが出来ませんでした。

自分のレベルとで天と地の差があるのは最初から承知していますが
そのISLe()さんにわざわざソースを書いてもらったにも関わらず
なにひとつ得られなかったことが自分は残念でなりません。
「初心者相手にこんな難解なレベルにしあげてさ、この意地悪!」なんて
申しません。どのレベルまでいけばISLe()さんのこのソースを
トレース出来るようになるのかまったく検討がつきませんが、
いつか今回のISLe()さんのソースを見て、
理解出来る自分がいたらいいなと思います。
ありがとうございました。

ISLe()

Re: Objectを大量に描画したいが…重い。なにかが悪い。

#60

投稿記事 by ISLe() » 5年前

おそらく、「実装を分離する」という概念そのものを受け入れられないでいるのではないでしょうかね。

No.7で、制御関数を外から繋ぐようなことを本で読んだと書いておられますが、たぶんそこに書いてあったのと同じようなものだと思いますが。

No.56のコードは、あくまでも、概念の説明のために、基本的な文法だけを用い、単独で動作するように、書き下ろしたものです。

閉鎖

“C言語何でも質問掲示板” へ戻る