ページ 11

人工生命について

Posted: 2012年12月07日(金) 02:08
by ボラ天使
ニコニコ動画などで人工生命の動画を見て面白いと思い、DXライブラリを使って作ってみようと思ったのですが、ランダムウォークがうまくいきません。

仕様としては、
・壁に当たったら別方向に移動
・他のキャラクターと当たったら別方向に移動(どう種族を含む)

これを実装したいと思います。

将来的には360度全方位に移動したいと思っているのですが、最初は4方向に絞りたいと思います。

今は、壁に当たったら別方向に移動までできたのですが、キャラクターとの当たり判定ができません。

コード:

#include "DxLib.h"

#define NIKU_KAZU 5		//肉食の数
#define	SOU_KAZU 5		//草食の数
#define KUSA_KAZU 5		//草の数

#define UP 0			//上
#define RIGHT 1			//右
#define DOWN 2			//下
#define LEFT 3			//左

int i, j;

typedef struct Chara {
	int x;
	int y;
	int color;
	int life;
}Chara;			//キャラクターの構造体

Chara carni[NIKU_KAZU];
Chara herbi[SOU_KAZU];
Chara weed[KUSA_KAZU];



void DrawWall();
void CharaInit();
void DrawCarni();
void DrawHerbi();
void DrawWeed();

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
			 LPSTR lpCmdLine, int nCmdShow )
{
	ChangeWindowMode(TRUE);

	if( DxLib_Init() == -1 )		// DXライブラリ初期化処理
	{
		 return -1;		// エラーが起きたら直ちに終了
	}
	
	CharaInit();			//キャラクターの初期化
	

	// キーが押されるまでループ(キー判定には『CheckHitKeyAll』を使用)
	while(ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0 )
	{
		ClsDrawScreen();
		DrawWall();			//壁の表示
		
		DrawCarni();		//肉食動物の移動と表示
		DrawHerbi();		//草食動物の移動と表示
		DrawWeed();			//草の表示

		ScreenFlip();
	}

	DxLib_End() ;			// DXライブラリ使用の終了処理

	return 0 ;			// ソフトの終了
}

//壁の表示
void DrawWall() {
	DrawBox(0, 0, 640, 10, GetColor(255,255,255),TRUE);
	DrawBox(0, 0, 10, 480, GetColor(255,255,255),TRUE);
	DrawBox(640, 480, 640-10, 0, GetColor(255,255,255),TRUE);
	DrawBox(640, 480, 0, 480-10, GetColor(255,255,255),TRUE);

}

//キャラクターの初期化
void CharaInit() {
	for (i=0;i<NIKU_KAZU;++i) {
		carni[i].x = GetRand(640-31)+10;
		carni[i].y = GetRand(480-31)+10;
		carni[i].color = GetColor(255, 0, 0);
		carni[i].life = 100;
	}

	for (i=0;i<SOU_KAZU;++i) {
		herbi[i].x = GetRand(640-31)+10;
		herbi[i].y = GetRand(480-31)+10;
		herbi[i].color = GetColor(0, 255, 0);
		herbi[i].life = 100;
	}

	for (i=0;i<KUSA_KAZU;++i) {
		weed[i].x = GetRand(640-31)+10;
		weed[i].y = GetRand(480-31)+10;
		weed[i].color = GetColor(0, 0, 255);
		weed[i].life = 100;
	}
}

//肉食動物の移動と表示
void DrawCarni() {
	static int move[NIKU_KAZU];
	
		

	for (i=0;i<NIKU_KAZU;++i) {
		if (move[i] == UP) carni[i].y -= 5;
		if (move[i] == RIGHT) carni[i].x += 5;
		if (move[i] == DOWN) carni[i].y += 5;
		if (move[i] == LEFT) carni[i].x -= 5;

		if (carni[i].x < 10) {
			carni[i].x = 10;
			move[i] = GetRand(3);
		}
		if (carni[i].x > 640-20) {
			carni[i].x = 640-20;
			move[i] = GetRand(3);
		}
		if (carni[i].y < 10) {
			carni[i].y = 10;
			move[i] = GetRand(3);
		}
		if (carni[i].y > 480-20) {
			carni[i].y = 480-20;
			move[i] = GetRand(3);
		}
		

		DrawBox(carni[i].x, carni[i].y, carni[i].x+10, carni[i].y+10, carni[i].color, TRUE);
	}
}

//草食動物の移動と表示
void DrawHerbi() {
	static int move[NIKU_KAZU];
	for (i=0;i<SOU_KAZU;++i) {
		if (move[i] == UP) herbi[i].y -= 5;
		if (move[i] == RIGHT) herbi[i].x += 5;
		if (move[i] == DOWN) herbi[i].y += 5;
		if (move[i] == LEFT) herbi[i].x -= 5;

		if (herbi[i].x < 10) {
			herbi[i].x = 10;
			move[i] = GetRand(3);
		}
		if (herbi[i].x > 640-20) {
			herbi[i].x = 640-20;
			move[i] = GetRand(3);
		}
		if (herbi[i].y < 10) {
			herbi[i].y = 10;
			move[i] = GetRand(3);
		}
		if (herbi[i].y > 480-20) {
			herbi[i].y = 480-20;
			move[i] = GetRand(3);
		}
		DrawBox(herbi[i].x, herbi[i].y, herbi[i].x+10, herbi[i].y+10, herbi[i].color, TRUE);
	}
}

//草の表示
void DrawWeed() {
	static int move[NIKU_KAZU];
	for (i=0;i<KUSA_KAZU;++i) {
		DrawBox(weed[i].x, weed[i].y, weed[i].x+10, weed[i].y+10, weed[i].color, TRUE);
	}
}


どのようにしたらいいのでしょうか?

Re: 人工生命について

Posted: 2012年12月07日(金) 04:57
by てんむすキツネ
まず、いろんなところにある
10や30といった数値や、壁のある座標を
定数か何かの形で定義した方がいいと思います。

コード:

DrawBox(0, 0, 640, 10, GetColor(255,255,255),TRUE);//これの10の値を定数としたりとか
DrawBox(0, 0, 10, 480, GetColor(255,255,255),TRUE);
DrawBox(640, 480, 640-10, 0, GetColor(255,255,255),TRUE);
DrawBox(640, 480, 0, 480-10, GetColor(255,255,255),TRUE);
キャラの当たり判定を同種族とも行うのであれば
別々に考える必要はなさそうですね。
キャラをどのような画像を使って表現しているのかわかりませんが、
基本、円形で計算すればよいかと思います。

キャラの中心から半径一定以内に別キャラが入った場合
移動する角度を変えるだけでいいと思います。


せっかくなので360°移動する場合も載せときます。

まず、計算にはsinとcosを使います。
向かっている方向をangleという変数に保存し
進むスピードをspdという変数に保存したとします。
するとxとy座標は

コード:

x += spd*cos(angle);
y += spd*sin(angle);
で求められます。
角度は度でなくラジアンなので注意です。

あたった後の方向転換についてです。
肉食と草食があるようですが、ここは同種族として考えています。
( 縄張り争いや相手が天敵とか考えません。当たらずとも、一定の距離内に入った時点で反対方向へ逃げ出せばいいと思うので )

決まった角度に毎回避けるとランダムウォークとはいえませんし
会ったからといってわざわざ引き返すのも変だと思います。
なので
ぶつかったどちらかが
現在の進む方向に-50~+50°のランダムな角度に進ませて
もう一方をその反対の角度で進ませるというのはどうでしょうか?( -35 なら +35)
そうすれば引き返すだけでなくすれ違うように移動することも可能となると思います。

Re: 人工生命について

Posted: 2012年12月07日(金) 11:35
by ボラ天使
返信ありがとうございます。

10とかは画像サイズで定義しようと思います。

当たり判定を組むにあたって、僕の作っていたプログラムだと、1匹当たりに何回もループしなくてはいけなくてとても重いプログラムになっています。

どのように判定したらいいのでしょうか?

僕は毎回forでcarniとherbi[j]の当たり判定を全部繰り返していました。

Re: 人工生命について

Posted: 2012年12月07日(金) 11:45
by softya(ソフト屋)
#define NIKU_KAZU 5 //肉食の数
#define SOU_KAZU 5 //草食の数
#define KUSA_KAZU 5 //草の数
ぐらいで重い判定になると思えません。
どの様な当たり判定をしているのでしょうか?

掲載されたプログラムには見当たらないようですが。

Re: 人工生命について

Posted: 2012年12月07日(金) 12:55
by Rag
肉食動物、草食動物の描画のところにDrawBoxということは、動物の表示は(今のところ)四角ですよね?
かつ移動がとりあえず4方向ということであれば当たり判定は単純に自分以外の動物の座標を見て幅、高さより近いかだけ判断すれば良いと思います。
もちろん動物同士の距離を見て逃げる、追うがあるなら天紆さんの通り円でやっても、単純に上下何pix、左右何pix内に入ったら、とやってもできそうです。

ついでですが、肉食動物、草食動物の初期化や移動、大きさなどは共通している部分が多いのでmainでstructにせずクラス化してしまった方が良いと思います。
動物の大きさはsizeとしてメンバに加えてはどうでしょう。

当たり判定の回数を気にされてるようですが、当たり判定は基本的にすべてのオブジェクト同士でやると思っておいた方が良いです。
形状が複雑で判定の処理が重い場合はまず距離だけで判断して判定処理までやるか否かという分岐にはできます。

Re: 人工生命について

Posted: 2012年12月07日(金) 14:42
by nil
処理速度については他の方も仰っているように気にしない方が良いです。
結局計算よりも描画のほうが重いのですから。
実感がわかないのならば計測をしてみるとよいでしょう。

キャラクタは草食動物、草、肉食動物と分けて定義するのではなく、
Charaに種類を表す変数を持たせてその変数にしたがって処理を分岐させたほうがすっきりすると思います。

Re: 人工生命について

Posted: 2012年12月07日(金) 16:14
by ボラ天使
皆さん回答ありがとうございます。

当たり判定について詳しくないのでどうしたらいいか分からず短形の当たり判定の式を検索して作ったやつですがコードを載せておきます。

コード:

#include "DxLib.h"

#define NIKU_KAZU 5		//肉食の数
#define	SOU_KAZU 5		//草食の数
#define KUSA_KAZU 5		//草の数

#define UP 0			//上
#define RIGHT 1			//右
#define DOWN 2			//下
#define LEFT 3			//左

int i, j, t;
int Cmove[NIKU_KAZU];	//肉食の移動方向
int Hmove[NIKU_KAZU];	//草食の移動方向

typedef struct Chara {
	int x;
	int y;
	int color;
	int life;
	int width;
	int height;
}Chara;			//キャラクターの構造体

Chara carni[NIKU_KAZU];
Chara herbi[SOU_KAZU];
Chara weed[KUSA_KAZU];



void DrawWall();
void CharaInit();
void DrawCarni();
void DrawHerbi();
void DrawWeed();

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
			 LPSTR lpCmdLine, int nCmdShow )
{
	ChangeWindowMode(TRUE);

	if( DxLib_Init() == -1 )		// DXライブラリ初期化処理
	{
		 return -1;		// エラーが起きたら直ちに終了
	}
	
	CharaInit();			//キャラクターの初期化
	

	// キーが押されるまでループ(キー判定には『CheckHitKeyAll』を使用)
	while(ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0 )
	{
		ClsDrawScreen();
		DrawWall();			//壁の表示
		
		DrawCarni();		//肉食動物の移動と表示
		DrawHerbi();		//草食動物の移動と表示
		DrawWeed();			//草の表示

		ScreenFlip();
	}

	DxLib_End() ;			// DXライブラリ使用の終了処理

	return 0 ;			// ソフトの終了
}

//壁の表示
void DrawWall() {
	DrawBox(0, 0, 640, 10, GetColor(255,255,255),TRUE);
	DrawBox(0, 0, 10, 480, GetColor(255,255,255),TRUE);
	DrawBox(640, 480, 640-10, 0, GetColor(255,255,255),TRUE);
	DrawBox(640, 480, 0, 480-10, GetColor(255,255,255),TRUE);

}

//キャラクターの初期化
void CharaInit() {
	for (i=0;i<NIKU_KAZU;++i) {
		carni[i].x = GetRand(640-31)+10;
		carni[i].y = GetRand(480-31)+10;
		carni[i].color = GetColor(255, 0, 0);
		carni[i].life = 100;
		carni[i].width = 10;
		carni[i].height = 10;
	}

	for (i=0;i<SOU_KAZU;++i) {
		herbi[i].x = GetRand(640-31)+10;
		herbi[i].y = GetRand(480-31)+10;
		herbi[i].color = GetColor(0, 255, 0);
		herbi[i].life = 100;
		herbi[i].width = 10;
		herbi[i].height = 10;
	}

	for (i=0;i<KUSA_KAZU;++i) {
		weed[i].x = GetRand(640-31)+10;
		weed[i].y = GetRand(480-31)+10;
		weed[i].color = GetColor(0, 0, 255);
		weed[i].life = 100;
		weed[i].width = 10;
		weed[i].height = 10;
	}
}

//肉食動物の移動と表示
void DrawCarni() {
	
		

	for (i=0;i<NIKU_KAZU;++i) {
		for (j=0;j<SOU_KAZU;++j) {
			if ((carni[i].x < herbi[j].x+herbi[i].width) &&
				(carni[i].x+carni[i].width > herbi[i].x) &&
				(carni[i].y < herbi[j].y+herbi[j].height) &&
				(carni[i].y+carni[i].height > herbi[j].y)) {
					Cmove[i] = GetRand(3);
					Hmove[j] = GetRand(3);
			}
		}
				
				
		if (Cmove[i] == UP) carni[i].y -= 5;
		if (Cmove[i] == RIGHT) carni[i].x += 5;
		if (Cmove[i] == DOWN) carni[i].y += 5;
		if (Cmove[i] == LEFT) carni[i].x -= 5;

		if (carni[i].x < 10) {
			carni[i].x = 10;
			Cmove[i] = GetRand(3);
		}
		if (carni[i].x > 640-20) {
			carni[i].x = 640-20;
			Cmove[i] = GetRand(3);
		}
		if (carni[i].y < 10) {
			carni[i].y = 10;
			Cmove[i] = GetRand(3);
		}
		if (carni[i].y > 480-20) {
			carni[i].y = 480-20;
			Cmove[i] = GetRand(3);
		}
		

		DrawBox(carni[i].x, carni[i].y, carni[i].x+carni[i].width, carni[i].y+carni[i].height, carni[i].color, TRUE);
	}
}

//草食動物の移動と表示
void DrawHerbi() {
	for (i=0;i<SOU_KAZU;++i) {
		if (Hmove[i] == UP) herbi[i].y -= 5;
		if (Hmove[i] == RIGHT) herbi[i].x += 5;
		if (Hmove[i] == DOWN) herbi[i].y += 5;
		if (Hmove[i] == LEFT) herbi[i].x -= 5;

		if (herbi[i].x < 10) {
			herbi[i].x = 10;
			Hmove[i] = GetRand(3);
		}
		if (herbi[i].x > 640-20) {
			herbi[i].x = 640-20;
			Hmove[i] = GetRand(3);
		}
		if (herbi[i].y < 10) {
			herbi[i].y = 10;
			Hmove[i] = GetRand(3);
		}
		if (herbi[i].y > 480-20) {
			herbi[i].y = 480-20;
			Hmove[i] = GetRand(3);
		}
		DrawBox(herbi[i].x, herbi[i].y, herbi[i].x+herbi[i].width, herbi[i].y+herbi[i].height, herbi[i].color, TRUE);
	}
}

//草の表示
void DrawWeed() {
	for (i=0;i<KUSA_KAZU;++i) {
		DrawBox(weed[i].x, weed[i].y, weed[i].x+weed[i].width, weed[i].y+weed[i].height, weed[i].color, TRUE);
	}
}


これは今は同族を抜いて、肉食と草食の当たり判定だけを作ったつもりです。
これから少し用事があるので夜にもう一度ソースを書き直したいと思います。

Re: 人工生命について

Posted: 2012年12月07日(金) 17:34
by てんむすキツネ
自分の経験から言って、描画と計算は完全に分けたほうがいいです。
キャラの座標取得関数と描画関数を別に作ったほうがいいと思います。

まだじっくり見ていないので自分の勘違いかもしれませんが、
もう一つ気になった箇所があります。
毎フレーム進む方向を変更していることですね。
これでは物凄い挙動不審にブルブル震えながら移動することになるかとおもいます。

Re: 人工生命について

Posted: 2012年12月07日(金) 19:15
by nil
このコードで何か問題はあるのでしょうか?

CmoveとHmoveを構造体の外に出した理由は何ですか?
それならばいっそキャラクタ構造体を草食肉食植物と分割したほうがいいのでは?
もしくはmove変数をChara構造体に持たせて草を処理するときのみその変数を無視したほうが効率はいいと思います。

天紆 狐さんのおっしゃるとおり関数分けを行ったほうが良いかと。
Drawに移動という意味はありませんからね。

あと気になった点はというと全体的にコメントの量がかなり少ないことですね。

Re: 人工生命について

Posted: 2012年12月07日(金) 20:46
by ボラ天使
改訂版です

コード:

#include "DxLib.h"
#include <math.h>

#define NIKU_KAZU 20		//肉食の数
#define	SOU_KAZU 20		//草食の数
#define KUSA_KAZU 5		//草の数

#define UP 0			//上
#define RIGHT 1			//右
#define DOWN 2			//下
#define LEFT 3			//左

#define PI 3.141592654f	//円周率
int i, j, t;

typedef struct Chara {
	int x;			//x座標
	int y;			//y座標
	int color;		//キャラの色
	int life;		//ライフ
	int width;		//キャラの横幅
	int height;		//キャラの縦幅
	int move;		//向き
	int spd;		//スピード
	double angle;	//角度
}Chara;			//キャラクターの構造体

Chara carni[NIKU_KAZU];
Chara herbi[SOU_KAZU];
Chara weed[KUSA_KAZU];



void DrawWall();
void CharaInit();
void DrawCarni();
void MoveCarni();
void DrawHerbi();
void MoveHerbi();
void DrawWeed();
void Atari();

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
			 LPSTR lpCmdLine, int nCmdShow )
{
	ChangeWindowMode(TRUE);

	if( DxLib_Init() == -1 )		// DXライブラリ初期化処理
	{
		 return -1;		// エラーが起きたら直ちに終了
	}
	
	CharaInit();			//キャラクターの初期化
	

	// キーが押されるまでループ(キー判定には『CheckHitKeyAll』を使用)
	while(ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0 )
	{
		ClsDrawScreen();
		DrawWall();			//壁の表示
		
		Atari();

		MoveCarni();
		MoveHerbi();

		DrawCarni();		//肉食動物の移動と表示
		DrawHerbi();		//草食動物の移動と表示
		DrawWeed();			//草の表示

		ScreenFlip();
	}

	DxLib_End() ;			// DXライブラリ使用の終了処理

	return 0 ;			// ソフトの終了
}

//壁の表示
void DrawWall() {
	DrawBox(0, 0, 640, 10, GetColor(255,255,255),TRUE);
	DrawBox(0, 0, 10, 480, GetColor(255,255,255),TRUE);
	DrawBox(640, 480, 640-10, 0, GetColor(255,255,255),TRUE);
	DrawBox(640, 480, 0, 480-10, GetColor(255,255,255),TRUE);

}

//キャラクターの初期化
void CharaInit() {
	for (i=0;i<NIKU_KAZU;++i) {
		carni[i].x = GetRand(640-31)+10;
		carni[i].y = GetRand(480-31)+10;
		carni[i].color = GetColor(255, 0, 0);
		carni[i].life = 1;
		carni[i].width = 10;
		carni[i].height = 10;
		carni[i].move = GetRand(3);
		carni[i].spd = 3;
		carni[i].angle = GetRand(4*PI)-2*PI;
	}

	for (i=0;i<SOU_KAZU;++i) {
		herbi[i].x = GetRand(640-31)+10;
		herbi[i].y = GetRand(480-31)+10;
		herbi[i].color = GetColor(0, 255, 0);
		herbi[i].life = 1;
		herbi[i].width = 10;
		herbi[i].height = 10;
		carni[i].move = GetRand(3);
		herbi[i].spd = 3;
		herbi[i].angle = GetRand(4*PI)-2*PI;
	}

	for (i=0;i<KUSA_KAZU;++i) {
		weed[i].x = GetRand(640-31)+10;
		weed[i].y = GetRand(480-31)+10;
		weed[i].color = GetColor(0, 0, 255);
		weed[i].life = 100;
		weed[i].width = 10;
		weed[i].height = 10;
	}
}

//肉食動物の移動と表示
void DrawCarni() {
	for (i=0;i<NIKU_KAZU;++i) {
		if (carni[i].life > 0) {
			DrawBox(carni[i].x, carni[i].y, carni[i].x+carni[i].width, carni[i].y+carni[i].height, carni[i].color, TRUE);
		}
	}
}

//肉食動物の移動
void MoveCarni() {
	for (i=0;i<NIKU_KAZU;++i) {
		/*
		if (carni[i].move == UP) carni[i].y -= carni[i].spd;
		if (carni[i].move == RIGHT) carni[i].x += carni[i].spd;
		if (carni[i].move == DOWN) carni[i].y += carni[i].spd;
		if (carni[i].move == LEFT) carni[i].x -= carni[i].spd;
		*/
		carni[i].x += carni[i].spd * cos(carni[i].angle);
		carni[i].y += carni[i].spd * sin(carni[i].angle);

		if (carni[i].x < 10) {
			carni[i].x = 10;
			carni[i].angle = GetRand(4*PI)-2*PI;
		}
		if (carni[i].x > 640-20) {
			carni[i].x = 640-20;
			carni[i].angle = GetRand(4*PI)-2*PI;
		}
		if (carni[i].y < 10) {
			carni[i].y = 10;
			carni[i].angle = GetRand(4*PI)-2*PI;
		}
		if (carni[i].y > 480-20) {
			carni[i].y = 480-20;
			carni[i].angle = GetRand(4*PI)-2*PI;
		}
	}
}
//草食動物の表示
void DrawHerbi() {
	for (i=0;i<SOU_KAZU;++i) {
		if (herbi[i].life > 0) {
			DrawBox(herbi[i].x, herbi[i].y, herbi[i].x+herbi[i].width, herbi[i].y+herbi[i].height, herbi[i].color, TRUE);
		}
	}
}

//草食動物の移動
void MoveHerbi() {
	for (i=0;i<SOU_KAZU;++i) {
		/*
		if (herbi[i].move == UP) herbi[i].y -= herbi[i].spd;
		if (herbi[i].move == RIGHT) herbi[i].x += herbi[i].spd;
		if (herbi[i].move == DOWN) herbi[i].y += herbi[i].spd;
		if (herbi[i].move == LEFT) herbi[i].x -= herbi[i].spd;
		*/

		herbi[i].x += herbi[i].spd * cos(herbi[i].angle);
		herbi[i].y += herbi[i].spd * sin(herbi[i].angle);

		if (herbi[i].x < 10) {
			herbi[i].x = 10;
			herbi[i].angle = GetRand(4*PI)-2*PI;
		}
		if (herbi[i].x > 640-20) {
			herbi[i].x = 640-20;
			herbi[i].angle = GetRand(4*PI)-2*PI;
		}
		if (herbi[i].y < 10) {
			herbi[i].y = 10;
			herbi[i].angle = GetRand(4*PI)-2*PI;
		}
		if (herbi[i].y > 480-20) {
			herbi[i].y = 480-20;
			herbi[i].angle = GetRand(4*PI)-2*PI;
		}
	}
}

//草の表示
void DrawWeed() {
	for (i=0;i<KUSA_KAZU;++i) {
		DrawBox(weed[i].x, weed[i].y, weed[i].x+weed[i].width, weed[i].y+weed[i].height, weed[i].color, TRUE);
	}
}

void Atari() {
	for (i=0;i<NIKU_KAZU;++i) {
		for (j=0;j<SOU_KAZU;++j) {
			if ((carni[i].x < herbi[j].x+herbi[i].width) &&
				(carni[i].x+carni[i].width > herbi[i].x) &&
				(carni[i].y < herbi[j].y+herbi[j].height) &&
				(carni[i].y+carni[i].height > herbi[j].y)) {
					carni[i].angle = GetRand(PI) - PI;
					herbi[j].angle = GetRand(PI) - PI;
			}
		}
	}
}
360度回るようになりました。

ランダムで360度回したかったのですが、少数の乱数が分からず今はてきとうになっています。

このプログラムを実行すると当たり判定がうまくいかずよくわからないところで小刻みに震え始めます。

これは先ほど言っていた毎フレームランダムの数を取っているからなのでしょうか?

Re: 人工生命について

Posted: 2012年12月07日(金) 23:40
by softya(ソフト屋)
なにか生物っぽくない動きなので、相手や壁との方向転換の方法を変えたほうが良いと思います。
壁に関してはとりあえず完全ランダムに方向が変換するのは右か左のターンが等条件の時だけにして、それ以外は当たった面と自分のなす角が広からいい方向を選んだほうが良いと思います。
生物同士が当たったら、単に跳ね返るんじゃなくて避ける動作になるように考えてみて下さい。
それと進行し続けるのはなくて止まったり、方向転換したりをランダムに取り入れてみてください。ただし、ランダムな方向転換は進行方向に+-45度程度にしておいたらそれっぽくなるのでは?

こういうシミュレーションは実際の動物の動きとかのビデオをよく観察して、それっぽいルールを導き出すことです。

Re: 人工生命について

Posted: 2012年12月07日(金) 23:42
by てんむすキツネ
DxLibであれば大抵60FPSに無理やり保たれるようになっていた記憶が・・・
つまり、毎フレーム向かう方向を変更するということは
1秒のうちに60回方向転換していることになります。

なので、座標の追加は毎フレーム行うが、
一定カウンタ以上歩く、もしくは何かと接触するまでは
向かう方向を変えない、といったことをすることで
簡単に防ぐことができると思います。

ラジアンについてある程度理解されているでしょうか?
(自分は実際数学で勉強したわけでないのであやふやですが)
まず「度*PI/180」で度の値の角度になります。
ラジアンは少数の値ですので
角度と違ってわかりずらいので
決まった角度を与えたい場合はこちらで計算したほうが分かりやすいかもしれませんね。

/180という部分でなんとなくわかるかもしれませんが
PI = 180度 です。つまり半円分で
PI*2 が円一周分となります。
なので

コード:

GetRand(4*PI)-2*PI;
の4*PIは ( 2*PI )*2 と同じで2周分ということになります。
円はちょうど1周回れば0度と同じですよね?
つまり、2*PIでいいわけです。

Re: 人工生命について

Posted: 2012年12月07日(金) 23:44
by Rag
まず小数の乱数ですが、GetRand(int n)は0~nの整数を返します。
L99のGetRand(4*PI)はintにキャストされGetRand(12)で0,1,2,...,11,12となっています。
もし小数点以下2桁の乱数を得たいなら

コード:

double num = GetRand(400*PI)/100.0;
というように先に得たい乱数の範囲を100倍し、後で100分の1にします。
角度計算はラジアンでやるよりも度数でやった方が分かりやすいです。
cos()、sin()に整数に近い幅の値が渡されてますので360度に方向転換できるように見えて実は約6方向にしか転換できないです。

次に当たり判定の部分ですが、&&が多く分かりづらくなっていますので

コード:

if (abs(carni[i].x-herbi[i].x)<herbi[i].width && abs(carni[i].y-herbi[i].y)<herbi[i].height)
という感じにするとすっきりします。
また、今は動物の座標が画像(四角)の左上基準になっていますが、この先肉食動物と草食動物で大きさが異なるというようにする場合、
当たり判定の処理が肉食動物から見た場合と草食動物から見た場合に分けて考えなければならなくなるので
できれば座標の基準点は左上ではなく中心に持ってきた方が良いかと思います。

余計なお世話かもしれませんが、関数を作った場合はまずDrawStringやprintfなどで意図した値が入っているかどうか、入れた値から正しい結果が返ってくるか確認してみてください。
if文で条件式が複雑になっていると実はどうやっても通らない条件が書かれているということは良くありますので。

Re: 人工生命について

Posted: 2012年12月08日(土) 01:56
by ボラ天使
更に改訂版です。

壁に当たったら自分と反対方向に+-90度向きを変えるようにしました(つもり)。

それと肉食動物と草食動物が当たった場合には草食動物は食べられてしまうので、lifeを0にして表示をしないようにしました。

移動については今は肉食動物だけ変更してみて、乱数で止まり、タイマーで移動or停止の乱数を再度実行するプログラムにしてみました。(乱数はてきとうに作ったらいい感じなので確認してません)

あとは肉食動物は一定時間で消えるようにしました。
しかし、草食動物を食べることによりlifeが1たささって、1秒タイムが伸びるようにしたつもりです。

コードも載せておきます。

コード:

#include "DxLib.h"
#include <math.h>

#define NIKU_KAZU 5		//肉食の数
#define	SOU_KAZU 50		//草食の数
#define KUSA_KAZU 30		//草の数

#define UP 0			//上
#define RIGHT 1			//右
#define DOWN 2			//下
#define LEFT 3			//左

#define PI 3.141592654f	//円周率
int i, j, t;

typedef struct Chara {
	int x;			//x座標
	int y;			//y座標
	int color;		//キャラの色
	int life;		//ライフ
	int width;		//キャラの横幅
	int height;		//キャラの縦幅
	int move;		//向き
	int spd;		//スピード
	double angle;	//角度
	int count;		//回数のカウント
}Chara;			//キャラクターの構造体

Chara carni[NIKU_KAZU];
Chara herbi[SOU_KAZU];
Chara weed[KUSA_KAZU];



void DrawWall();
void CharaInit();
void DrawCarni();
void MoveCarni();
void DrawHerbi();
void MoveHerbi();
void DrawWeed();
void Atari();

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
			 LPSTR lpCmdLine, int nCmdShow )
{

	ChangeWindowMode(TRUE);

	if( DxLib_Init() == -1 )		// DXライブラリ初期化処理
		 return -1;		// エラーが起きたら直ちに終了
	AllocConsole();
	freopen("CONOUT$", "w", stdout); 
	freopen("CONIN$", "r", stdin);
	CharaInit();			//キャラクターの初期化
	

	// キーが押されるまでループ(キー判定には『CheckHitKeyAll』を使用)
	while(ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0 )
	{
		ClsDrawScreen();
		DrawWall();			//壁の表示
		
		Atari();

		MoveCarni();
		MoveHerbi();
		DrawCarni();		//肉食動物の移動と表示
		DrawHerbi();		//草食動物の移動と表示
		DrawWeed();			//草の表示

		ScreenFlip();
	}

	DxLib_End() ;			// DXライブラリ使用の終了処理

	return 0 ;			// ソフトの終了
}

//壁の表示
void DrawWall() {
	DrawBox(0, 0, 640, 10, GetColor(255,255,255),TRUE);
	DrawBox(0, 0, 10, 480, GetColor(255,255,255),TRUE);
	DrawBox(640, 480, 640-10, 0, GetColor(255,255,255),TRUE);
	DrawBox(640, 480, 0, 480-10, GetColor(255,255,255),TRUE);

}

//キャラクターの初期化
void CharaInit() {
	for (i=0;i<NIKU_KAZU;++i) {
		carni[i].x = GetRand(640-31)+10;
		carni[i].y = GetRand(480-31)+10;
		carni[i].color = GetColor(255, 0, 0);
		carni[i].life = 5;
		carni[i].width = 10;
		carni[i].height = 10;
		carni[i].move = GetRand(1);
		carni[i].spd = 3;
		carni[i].angle = GetRand(200*PI)/100.0;
		carni[i].count = 1;
	}


	for (i=0;i<SOU_KAZU;++i) {
		herbi[i].x = GetRand(640-31)+10;
		herbi[i].y = GetRand(480-31)+10;
		herbi[i].color = GetColor(0, 255, 0);
		herbi[i].life = 5;
		herbi[i].width = 10;
		herbi[i].height = 10;
		carni[i].move = GetRand(1);
		herbi[i].spd = 3;
		herbi[i].angle = GetRand(200*PI)/100.0;
		herbi[i].count = 1;
	}

	for (i=0;i<KUSA_KAZU;++i) {
		weed[i].x = GetRand(640-31)+10;
		weed[i].y = GetRand(480-31)+10;
		weed[i].color = GetColor(0, 0, 255);
		weed[i].life = 100;
		weed[i].width = 10;
		weed[i].height = 10;
	}
}

//肉食動物の移動と表示
void DrawCarni() {
	for (i=0;i<NIKU_KAZU;++i) {
		if (!(carni[i].count < carni[i].life * 60))
			carni[i].life = 0;
			
			if (carni[i].life > 0) {
				DrawBox(carni[i].x, carni[i].y, carni[i].x+carni[i].width, carni[i].y+carni[i].height, carni[i].color, TRUE);
			}
		
	}
}

//肉食動物の移動
void MoveCarni() {
	for (i=0;i<NIKU_KAZU;++i) {
		if (carni[i].move == 0) {
			if (carni[i].count % 20 == 0) {
				carni[i].move = GetRand(1);
				carni[i].angle = GetRand(200*PI)/100.0;
			}
		} else {
			carni[i].x += carni[i].spd * cos(carni[i].angle);
			carni[i].y += carni[i].spd * sin(carni[i].angle);

			if (carni[i].x < 10) {
				carni[i].x = 10;
				carni[i].angle += PI + GetRand(PI * 100 * PI)/100.0 - 1/2*PI;
			}
			if (carni[i].x > 640-20) {
				carni[i].x = 640-20;
				//carni[i].angle = GetRand(200*PI)/100.0;
				carni[i].angle += PI + GetRand(PI * 100 * PI)/100.0 - 1/2*PI;
			}
			if (carni[i].y < 10) {
				carni[i].y = 10;
				//carni[i].angle = GetRand(200*PI)/100.0;
				carni[i].angle += PI + GetRand(PI * 100 * PI)/100.0 - 1/2*PI;
			}
			if (carni[i].y > 480-20) {
				carni[i].y = 480-20;
				//carni[i].angle = GetRand(200*PI)/100.0;
				carni[i].angle += PI + GetRand(PI * 100 * PI)/100.0 - 1/2*PI;
			}

			if (carni[i].count % 100 == GetRand(30)+GetRand(30)) {
				carni[i].move = GetRand(1);
			}
		}

		if (carni[i].count % 60 == 0) {
			carni[i].life--;
		}

		carni[i].count++;
	}
}
//草食動物の表示
void DrawHerbi() {
	for (i=0;i<SOU_KAZU;++i) {
		if (herbi[i].life > 0) {
			DrawBox(herbi[i].x, herbi[i].y, herbi[i].x+herbi[i].width, herbi[i].y+herbi[i].height, herbi[i].color, TRUE);
		}
	}
}

//草食動物の移動
void MoveHerbi() {
	for (i=0;i<SOU_KAZU;++i) {
		/*
		if (herbi[i].move == UP) herbi[i].y -= herbi[i].spd;
		if (herbi[i].move == RIGHT) herbi[i].x += herbi[i].spd;
		if (herbi[i].move == DOWN) herbi[i].y += herbi[i].spd;
		if (herbi[i].move == LEFT) herbi[i].x -= herbi[i].spd;
		*/

		herbi[i].x += herbi[i].spd * cos(herbi[i].angle);
		herbi[i].y += herbi[i].spd * sin(herbi[i].angle);

		if (herbi[i].x < 10) {
			herbi[i].x = 10;
			herbi[i].angle = GetRand(200*PI)/100.0;
		}
		if (herbi[i].x > 640-20) {
			herbi[i].x = 640-20;
			herbi[i].angle = GetRand(200*PI)/100.0;
		}
		if (herbi[i].y < 10) {
			herbi[i].y = 10;
			herbi[i].angle = GetRand(200*PI)/100.0;
		}
		if (herbi[i].y > 480-20) {
			herbi[i].y = 480-20;
			herbi[i].angle = GetRand(200*PI)/100.0;
		}
	}
}

//草の表示
void DrawWeed() {
	for (i=0;i<KUSA_KAZU;++i) {
		DrawBox(weed[i].x, weed[i].y, weed[i].x+weed[i].width, weed[i].y+weed[i].height, weed[i].color, TRUE);
	}
}

void Atari() {
	for (i=0;i<NIKU_KAZU;++i) {
		for (j=0;j<SOU_KAZU;++j) {
			/*
			if ((carni[i].x < herbi[j].x+herbi[i].width) &&
				(carni[i].x+carni[i].width > herbi[i].x) &&
				(carni[i].y < herbi[j].y+herbi[j].height) &&
				(carni[i].y+carni[i].height > herbi[j].y))*/
			if (carni[i].life > 0 && herbi[j].life > 0) {
				if (abs(carni[i].x-herbi[j].x)<herbi[j].width && abs(carni[i].y-herbi[j].y)<herbi[j].height)
				{
						carni[i].angle = GetRand(200*PI)/100.0;
						herbi[j].angle = GetRand(200*PI)/100.0;
						carni[i].life++;
						herbi[j].life = 0;
						carni[i].count = 0;
				}
			}
		}
	}
}
(結構てきとうに作ったので理由が確かではない場合や、望んでいる結果を出すことのできないプログラムを作ってる可能性があります。)
バランスは今最悪に悪いです。

Re: 人工生命について

Posted: 2012年12月08日(土) 12:24
by softya(ソフト屋)
気になる所
・int i, j, t;がグローバル変数。バグの元なので面倒でもローカル変数に。
・i,j,kがややこしいので別の名前を推奨
・SetDrawScreen( DX_SCREEN_BACK );が無いので処理落ちが発生している。

で、動きは気になりますがとりあえず動いているとは思います。
あとの問題点は、ボラ天使 さんがどうしたいかによりますので、そこを書いてくださいね。

Re: 人工生命について

Posted: 2012年12月09日(日) 09:02
by てんむすキツネ
次の質問が無いのであれば、トピックを解決にしておいてもいいと思いますよ。

また問題が出てきたら、新しく立てていただけばよいので。

今回は質問用に一つのcppファイルにわざわざまとめてくださっているのかもしれませんが
もし、そうでないのであれば、分割してみてはどうでしょう?
確実に行う処理も増えてますし
よりリアルな動きにさせたり配置するオブジェクトが増えたりすれば
コードがぐちゃぐちゃになりかねません。

ヘッダーファイル等も用いたりして、
何がどの情報で、どこで使われるのかはっきりさせておくべきかと思います。

今のプログラムも
「メインループ」「計算( 壁、草食動物、肉食動物 )」「描画」
と、少なくとも3つに分けることができます。

Re: 人工生命について

Posted: 2012年12月09日(日) 11:31
by nil
>今のプログラムも
>「メインループ」「計算( 壁、草食動物、肉食動物 )」「描画」
>と、少なくとも3つに分けることができます。
その分け方とは関数分けするということでしょうか、
それともファイル分けするということでしょうか。

私であれば
メイン関数、壁、動物
とファイルに分けます。

オブジェクト指向的に書くのなら
メイン関数、物体クラス、壁クラス、物体から派生した草、草食、肉食クラスと分けるでしょうか。

Re: 人工生命について

Posted: 2012年12月09日(日) 11:56
by ボラ天使
皆さん本当にありがとうございました。

一応最初に臨んだ結果ができたので今回は解決にしたいと思います。

C++については勉強していないのでクラスとかはわからないですけど、そのうちやってみたいと思います。

ファイル分けも確かにしないとプロトタイプ宣言で大変なことになりそうなので分けたいと思います。

それではまたわからないところがあったら質問しにきますのでその時もよろしくお願いします。